前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一文搞懂 Linux 网络 Phy 驱动

一文搞懂 Linux 网络 Phy 驱动

作者头像
刘盼
发布于 2023-11-05 10:39:20
发布于 2023-11-05 10:39:20
3.7K00
代码可运行
举报
文章被收录于专栏:人人都是极客人人都是极客
运行总次数:0
代码可运行

概述

上图来自 瑞昱半导体 (RealTek) 的 RTL8201F 系列网卡 PHY 芯片手册。按OSI 7层网络模型划分,网卡PHY 芯片(图中的RTL8201F)位于物理层,对应的软件层就是本文讨论的 PHY 驱动层;而 MAC 位于 数据链路层,也是通常软件上所说的网卡驱动层,它不是本文的重点,不做展开。另外,可通过 MDIO 接口对 PHY 芯片进行配置(如PHY芯片寄存器读写),而 PHY 和 MAC 通过 MII/RMII 进行数据传输。

PHY芯片通过MII/GMII/RMII/SGMII/XGMII等多种媒体独立接口(介质无关接口)与数据链路层的MAC芯片相连,并通过MDIO接口实现对PHY 状态的监控、配置和管理。

PHY与MAC整体的连接框图:

数据结构

每个 phy 芯片会创建一个 struct phy_device 类型的设备,对应的有 struct phy_driver 类型的驱动,这两者实际上是挂载在 mdio_bus_type 总线上的,mac 会被注册成 struct net_device。

phy_device

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct phy_device {
    struct phy_driver *drv;                // PHY设备驱动  
    struct mii_bus *bus;                   // 对应的MII总线 
    struct device dev;                     // 设备文件  
    u32 phy_id;                            // PHY ID  
    
    struct phy_c45_device_ids c45_ids;       
    bool is_c45;
    bool is_internal;
    bool has_fixups;
    bool suspended;

    enum phy_state state;                   // PHY状态
    u32 dev_flags;
    phy_interface_t interface;              // PHY接口  
    int addr;                               // PHY 总线地址(0~31) 

    int speed;                              // 速度  
    int duplex;                             // 双工模式  
    int pause;                              // 停止  
    int asym_pause;
    int link;

    u32 interrupts;                         // 中断使能标志  
    u32 supported;
    u32 advertising;
    u32 lp_advertising;
    int autoneg;
    int link_timeout;
    int irq;                                 // 中断号                              

    void *priv;                              // 私有数据  
    struct work_struct phy_queue;            // PHY工作队列  
    struct delayed_work state_queue;         // PHY延时工作队列  
    atomic_t irq_disable;

    struct mutex lock;

    struct net_device *attached_dev;         // 网络设备  

    void (*adjust_link)(struct net_device *dev);
};

phy_driver

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct phy_driver {
    struct mdio_driver_common mdiodrv;
    u32 phy_id;
    char *name;
    u32 phy_id_mask;
    u32 features;
    u32 flags;
    const void *driver_data;
    
    int (*soft_reset)(struct phy_device *phydev);
    int (*config_init)(struct phy_device *phydev);
    int (*probe)(struct phy_device *phydev);
    int (*suspend)(struct phy_device *phydev);
    int (*resume)(struct phy_device *phydev);
    int (*config_aneg)(struct phy_device *phydev);
    int (*aneg_done)(struct phy_device *phydev);
    int (*read_status)(struct phy_device *phydev);
    int (*ack_interrupt)(struct phy_device *phydev);
    int (*config_intr)(struct phy_device *phydev);
    int (*did_interrupt)(struct phy_device *phydev);
    void (*remove)(struct phy_device *phydev);
    int (*match_phy_device)(struct phy_device *phydev);
    int (*ts_info)(struct phy_device *phydev, struct ethtool_ts_info *ti);
    int  (*hwtstamp)(struct phy_device *phydev, struct ifreq *ifr);
    bool (*rxtstamp)(struct phy_device *dev, struct sk_buff *skb, int type);
    void (*txtstamp)(struct phy_device *dev, struct sk_buff *skb, int type);
    int (*set_wol)(struct phy_device *dev, struct ethtool_wolinfo *wol);
    void (*get_wol)(struct phy_device *dev, struct ethtool_wolinfo *wol);
    void (*link_change_notify)(struct phy_device *dev);
    int (*read_mmd)(struct phy_device *dev, int devnum, u16 regnum);
    int (*write_mmd)(struct phy_device *dev, int devnum, u16 regnum,
             u16 val);
    int (*read_page)(struct phy_device *dev);
    int (*write_page)(struct phy_device *dev, int page)
    int (*module_info)(struct phy_device *dev,
               struct ethtool_modinfo *modinfo);
    int (*module_eeprom)(struct phy_device *dev,
                 struct ethtool_eeprom *ee, u8 *data);
    int (*get_sset_count)(struct phy_device *dev);
    void (*get_strings)(struct phy_device *dev, u8 *data);
    void (*get_stats)(struct phy_device *dev,
              struct ethtool_stats *stats, u64 *data);
    int (*get_tunable)(struct phy_device *dev,
               struct ethtool_tunable *tuna, void *data);
    int (*set_tunable)(struct phy_device *dev,
                struct ethtool_tunable *tuna,
                const void *data);
    int (*set_loopback)(struct phy_device *dev, bool enable);
    ANDROID_KABI_RESERVE(1);
    ANDROID_KABI_RESERVE(2);
};

mii_bus

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct mii_bus {
    const char *name;             // 总线名字
    char id[MII_BUS_ID_SIZE];     // ID MII_BUS_ID_SIZE=61
    void *priv;                   // 私有数据
    int (*read)(struct mii_bus *bus, int phy_id, int regnum);              // 读方式
    int (*write)(struct mii_bus *bus, int phy_id, int regnum, u16 val);    // 写方式
    int (*reset)(struct mii_bus *bus);     // 复位

    struct mutex mdio_lock;

    struct device *parent;        // 父设备
    enum {
        MDIOBUS_ALLOCATED = 1,
        MDIOBUS_REGISTERED,
        MDIOBUS_UNREGISTERED,
        MDIOBUS_RELEASED,
    } state;                       // 总线状态
    struct device dev;             // 设备文件

    struct phy_device *phy_map[PHY_MAX_ADDR]; // PHY设备数组

    u32 phy_mask;
    int *irq;                   // 中断
};

net_device

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct net_device {
    char  name[IFNAMSIZ];         /* 用于存放网络设备的设备名称 */
    char  *ifalias;               /* 网络设备的别名 */
    int   ifindex;                /* 网络设备的接口索引值,独一无二的网络设备标识符 */
    struct hlist_node  name_hlist;  /* 这个字段用于构建网络设备名的哈希散列表,而struct net中的name_hlist就指向每个哈希散列表的链表头 */
    struct hlist_node  index_hlist; /* 用于构建网络设备的接口索引值哈希散列表,在struct net中的index_hlist用于指向接口索引值哈希散列表的链表头 */
    struct list_head   dev_list;    /* 用于将每一个网络设备加入到一个网络命名空间中的网络设备双链表中 */
    unsigned int       flags;       /* 网络设备接口的标识符 */
    unsigned int       priv_flags;  /* 网络设备接口的标识符,但对用户空间不可见;*/
    unsigned short     type;        /* 接口硬件类型 */
    unsigned int       mtu;         /* 网络设备接口的最大传输单元 */
    unsigned short     hard_header_len;   /* 硬件接口头长度 */
    unsigned char      *dev_addr;    /* 网络设备接口的MAC地址 */
    bool           uc_promisc;       /* 网络设备接口的单播模式 */
    unsigned int       promiscuity;  /* 网络设备接口的混杂模式 */
    unsigned int       allmulti;     /* 网络设备接口的全组播模式 */
    struct netdev_hw_addr_list  uc;  /* 辅助单播MAC地址列表 */
    struct netdev_hw_addr_list  mc;  /* 主mac地址列表 */
    struct netdev_hw_addr_list  dev_addrs;  /* hw设备地址列表 */
    unsigned char      broadcast[MAX_ADDR_LEN];   /* hw广播地址 */
    struct netdev_rx_queue  *_rx;       /* 网络设备接口的数据包接收队列 */
    struct netdev_queue *_tx            /* 网络设备接口的数据包发送队列 */
    unsigned int        num_tx_queues;  /* TX队列数 */
    unsigned int        real_num_tx_queues;  /* 当前设备活动的TX队列数 */
    unsigned long      tx_queue_len;    /* 每个队列允许的最大帧 */
    unsigned long      state;           /* 网络设备接口的状态 */
    struct net_device_stats    stats;   /* 网络设备接口的统计情况 */
    possible_net_t         nd_net;      /* 用于执行网络设备所在的命名空间 */
}

phy 设备的注册

以网卡 Fec 为例,网卡驱动在初始化 fec_probe() 时遍历 dts 的定义,创建相应 struct phy_device 类型的设备,主要步骤为:

  1. 注册网络设备 net_device
  2. 申请队列和 DMA
  3. 申请 MDIO 总线
  4. 创建并注册 Phy 设备
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fec_probe(struct platform_device *pdev)
    -> struct device_node *np = pdev->dev.of_node, *phy_node; // 获取设备树节点句柄,并创建一个phy的设备树节点句柄
    -> fec_enet_get_queue_num(pdev, &num_tx_qs, &num_rx_qs);  // 从设备树获取fsl,num-tx-queues和fsl,num-rx-queues的属性值
    -> ndev = alloc_etherdev_mqs                              // 申请net_device
    -> netdev_priv(ndev)                                      //  获取私有数据空间首地址
--------------------------------------------------------------------------------------------------------------------------
    -> of_parse_phandle(np, "phy-handle", 0)              // 从mac的设备树节点中获取phy子节点
    -> of_get_phy_mode(pdev->dev.of_node)                 // 从设备树节点中获取phy模式,phy-mode = "rmii";
    -> fec_reset_phy(pdev);                               // 复位phy
    -> fec_enet_init(ndev)                                // 申请队列和DMA,设置MAC地址
    -> of_property_read_u32(np, "fsl,wakeup_irq", &irq)   // 唤醒中断
    -> fec_enet_mii_init(pdev);                           // 注册MDIO总线、注册phy_device
        -> fep->mii_bus = mdiobus_alloc()                 //申请MDIO总线
        -> fep->mii_bus->name = "fec_enet_mii_bus";       // 总线名字
        -> fep->mii_bus->read = fec_enet_mdio_read;       // 总线的读函数
        -> fep->mii_bus->write = fec_enet_mdio_write;     // 总线的写函数
        -> snprintf(fep->mii_bus->id, MII_BUS_ID_SIZE, "%s-%x",
pdev->name, fep->dev_id + 1);                         // 总线id       
        -> of_get_child_by_name(pdev->dev.of_node, "mdio");            // 获取phy节点句柄
        -> of_mdiobus_register          // 注册mii_bus设备,并通过设备树子节点创建PHY设备 drivers/of/of_mdio.c              of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)      
            -> mdio->phy_mask = ~0;    // 屏蔽所有PHY,防止自动探测。相反,设备树中列出的phy将在总线注册后填充
            -> mdio->dev.of_node = np;
            -> mdio->reset_delay_us = DEFAULT_GPIO_RESET_DELAY;
            -> mdiobus_register(mdio)                 // 注册MDIO总线设备
                -> bus->dev.parent = bus->parent;
                -> bus->dev.class = &mdio_bus_class;  // 总线设备类“/sys/bus/mdio_bus”
                /*-----------------------------------------
                static struct class mdio_bus_class = {
                    .name = "mdio_bus",
                    .dev_release = mdiobus_release,
                };
                -------------------------------------------*/
                -> bus->dev.groups = NULL;
                -> dev_set_name(&bus->dev, "%s", bus->id);            //设置总线设备的名称
                -> device_register(&bus->dev);                        // 注册总线设备
                ->  if (bus->reset)      bus->reset(bus);             // 总线复位
---------------------------------------另一条分支解析(可忽略)--------------------------------------------------------
                ->  phydev = mdiobus_scan(bus, i);                  // 扫描phy设备
                -> phydev = get_phy_device(bus, addr);          //获取创建phy设备
                ->err = phy_device_register(phydev);          //注册phy设备
--------------------------------------------------------------------------------------------------------------------
                -> for_each_available_child_of_node(np, child) {      // 遍历这个平台设备的子节点并为每个phy注册一个phy_device
                -> addr = of_mdio_parse_addr(&mdio->dev, child)       // 从子节点的"reg"属性中获得PHY设备的地址 
                -> of_property_read_u32(np, "reg", &addr)
                -> if (addr < 0)     scanphys = true;      continue;  // 如果未获得子节点的"reg"属性,则在后面再启用扫描可能存在的PHY的,然后注册
                -> of_mdiobus_register_phy(mdio, child, addr)     }     // 创建并注册PHY设备
                    -> is_c45 = of_device_is_compatible(child,"ethernet-phy-ieee802.3-c45") //判断设备树中的PHY的属性是否指定45号条款
                -> if (!is_c45 && !of_get_phy_id(child, &phy_id))      //如果设备树中的PHY的属性未指定45号条款 且未通过"ethernet-phy-id%4x.%4x"属性指定PHY的ID
                -> phy = phy_device_create(mdio, addr, phy_id, 0, NULL);
                -> else    phy = get_phy_device(mdio, addr, is_c45);    //用这个分支
                        -> get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids);//通过mdio得到PHY的ID
                            -> mdiobus_read(bus, addr, MII_PHYSID1)
                                -> __mdiobus_read(bus, addr, regnum);
                                    -> bus->read(bus, addr, regnum)
                                -> mdiobus_read(bus, addr, MII_PHYSID2)
                            -> phy_device_create(bus, addr, phy_id, is_c45, &c45_ids)    // 创建PHY设备
                                -> struct phy_device *dev;
                                -> dev = kzalloc(sizeof(*dev), GFP_KERNEL);
                                    dev->dev.release = phy_device_release;
                                    dev->speed = 0;
                                    dev->duplex = -1;
                                    dev->pause = 0;
                                    dev->asym_pause = 0;
                                    dev->link = 1;
                                    dev->interface = PHY_INTERFACE_MODE_GMII;
                                    dev->autoneg = AUTONEG_ENABLE;      // 默认支持自协商(自动使能)
                                    dev->is_c45 = is_c45;
                                    dev->addr = addr;
                                    dev->phy_id = phy_id;
                                    if (c45_ids)
                                        dev->c45_ids = *c45_ids;
                                    dev->bus = bus;
                                    dev->dev.parent = bus->parent;
                                    dev->dev.bus = &mdio_bus_type;    //PHY设备和驱动都会挂在mdio_bus下,匹配时会调用对应的match函数  --   
                        /*----------------------------------------------------------------------------------------------------
                                    struct bus_type mdio_bus_type = {
                                        .name       = "mdio_bus",                //总线名称
                                        .match      = mdio_bus_match,            //用来匹配总线上设备和驱动的函数
                                        .pm         = MDIO_BUS_PM_OPS,
                                        .dev_groups = mdio_dev_groups,
                                    };
                        ----------------------------------------------------------------------------------------------------*/
                                    dev->irq = bus->irq != NULL ? bus->irq[addr] : PHY_POLL;
                                    dev_set_name(&dev->dev, PHY_ID_FMT, bus->id, addr);
                                    dev->state = PHY_DOWN;     //指示PHY设备和驱动程序尚未准备就绪,在PHY驱动的probe函数中会更改为READY
                                    -> INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);  //PHY的状态机(核心WORK)后续解析
                                    -> INIT_WORK(&dev->phy_queue, phy_change);   // 由phy_interrupt / timer调度以处理PHY状态的更改
                                    ->  request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT, MDIO_ID_ARGS(phy_id));            // 加载内核模块
                                    -> device_initialize(&dev->dev);     //设备模型中的一些设备,主要是kset、kobject、ktype的设置
                                -> irq_of_parse_and_map(child, 0);      //将中断解析并映射到linux virq空间(
                                -> of_node_get(child);         //将OF节点与设备结构相关联
                                -> phy->dev.of_node = child;
                                -> phy_device_register(phy)    // 注册phy设备
                                    -> if (phydev->bus->phy_map[phydev->addr])       //判断PHY是否已经注册了  
                                    -> phydev->bus->phy_map[phydev->addr] = phydev;  //添加PHY到总线的phy_map里
                                    -> phy_scan_fixups(phydev);     //执行匹配的fixups  
                                    -> device_add(&phydev->dev);    // 注册到linux设备模型框架中
                            ->  if (!scanphys)   return 0;          // 如果从子节点的"reg"属性中获得PHY设备的地址,scanphys=false,这里就直接返回了,因为不需要再扫描了
------------一般来说只要设备树种指定了PHY设备的"reg"属性,后面的流程可以自动忽略 ------------
    -> register_netdev(ndev)                  // 向内核注册net_device

phy 驱动的注册

genphy_driver 通用驱动

内核中有 genphy_driver 通用驱动,以及专有驱动(这里以 NXP TJA 驱动为例),分别如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static struct phy_driver genphy_driver = {
 .phy_id  = 0xffffffff,
 .phy_id_mask = 0xffffffff,
 .name  = "Generic PHY",
 .get_features = genphy_read_abilities,
 .suspend = genphy_suspend,
 .resume  = genphy_resume,
 .set_loopback   = genphy_loopback,
};
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static struct phy_driver tja11xx_driver[] = {
 {
  PHY_ID_MATCH_MODEL(PHY_ID_TJA1100),
  .name  = "NXP TJA1100",
  .features       = PHY_BASIC_T1_FEATURES,
  .probe  = tja11xx_probe,
  .soft_reset = tja11xx_soft_reset,
  .config_aneg = tja11xx_config_aneg,
  .config_init = tja11xx_config_init,
  .read_status = tja11xx_read_status,
  .get_sqi = tja11xx_get_sqi,
  .get_sqi_max = tja11xx_get_sqi_max,
  .suspend = genphy_suspend,
  .resume  = genphy_resume,
  .set_loopback   = genphy_loopback,
  /* Statistics */
  .get_sset_count = tja11xx_get_sset_count,
  .get_strings = tja11xx_get_strings,
  .get_stats = tja11xx_get_stats,
 }
};

module_phy_driver(tja11xx_driver);

genphy_driver 的 struct phy_driver 的注册过程如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
phy_init
  phy_driver_register()
      driver_register(&new_driver->mdiodrv.driver)
          bus_add_driver(drv)
              driver_attach(drv)
                  bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)
                      while ((dev = next_device(&i)) && !error)
                          /* 循环到注册的 PHY 设备时 */
                          fn(dev, data) = __driver_attach()
                              /* 匹配设备和驱动 */
                              driver_match_device(drv, dev)
                                  mdio_bus_match(dev, drv)
                                      phy_bus_match(dev, drv)
                                          /* 按 phy_id & phy_id_mask 匹配 */
                                          return (phydrv->phy_id & phydrv->phy_id_mask) == (phydev->phy_id & phydrv->phy_id_mask);
                              /* 匹配到设备和驱动,加载驱动 */
                              driver_probe_device(drv, dev)
                                  really_probe(dev, drv)
                                      dev->driver = drv; /* 绑定设备的驱动 */
                                      drv->probe(dev) = phy_probe

其中一个关键点是 mdio driver 的 probe 函数是一个通用函数 phy_probe:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static int phy_probe(struct device *dev)
{
    struct phy_device *phydev = to_phy_device(dev);        // 获取PHY设备
    struct device_driver *drv = phydev->mdio.dev.driver;
    struct phy_driver *phydrv = to_phy_driver(drv);        // 获取PHY驱动
    int err = 0;

    phydev->drv = phydrv;    /* 绑定 phy_device 和 phy_driver */

     /* PHY 中断模式最终配置 */
    if (!phy_drv_supports_irq(phydrv) && phy_interrupt_is_valid(phydev))    // 设置中断方式
        phydev->irq = PHY_POLL;

    if (phydrv->flags & PHY_IS_INTERNAL)
        phydev->is_internal = true;

    /* Deassert the reset signal */
    phy_device_reset(phydev, 0);

    if (phydev->drv->probe) {                 // 判断驱动有probe方式
        err = phydev->drv->probe(phydev);     /* PHY 驱动的 probe */
        if (err)
            goto out;
    }
    ......
    if (phydrv->features) {
        linkmode_copy(phydev->supported, phydrv->features);
    }
    else if (phydrv->get_features)
        err = phydrv->get_features(phydev);
    else if (phydev->is_c45)
        err = genphy_c45_pma_read_abilities(phydev);
    else
        err = genphy_read_abilities(phydev); //读取状态寄存器来确定 phy 芯片的能力

    if (err)
        goto out;

    if (!linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
                   phydev->supported))
        phydev->autoneg = 0;

    if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
                  phydev->supported))
        phydev->is_gigabit_capable = 1;
    if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
                  phydev->supported))
        phydev->is_gigabit_capable = 1;
    
    /* PHY 功能特性配置 */
    of_set_phy_supported(phydev);
    phy_advertise_supported(phydev);
    ......
    of_set_phy_eee_broken(phydev);
    ......
    if (!test_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported) &&
        !test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, phydev->supported)) {
        linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT,
                 phydev->supported);
        linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
                 phydev->supported);
    }

    /* Set the state to READY by default */
    phydev->state = PHY_READY;    /* 标记 PHY 设备已经就绪 */

out:
    /* Re-assert the reset signal on error */
    if (err)
        phy_device_reset(phydev, 1);

    return err;
}

其中通用 phy 驱动会调用函数 genphy_read_abilities 来读取状态寄存器来确定 phy 芯片的能力:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
genphy_read_abilities()
   `-| {
     |  val = phy_read(phydev, MII_BMSR); // 读取 mdio 0x01 寄存器来确定 phy 的 10/100M 能力
     |  linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported, val & BMSR_ANEGCAPABLE);
     |  linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->supported, val & BMSR_100FULL);
     |  linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->supported, val & BMSR_100HALF);
     |  linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, phydev->supported, val & BMSR_10FULL);
     |  linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, phydev->supported, val & BMSR_10HALF);
     |  if (val & BMSR_ESTATEN) {
     |   val = phy_read(phydev, MII_ESTATUS); // 读取 mdio 0x0f 寄存器来确定 phy 的 1000M 能力
     |   linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, phydev->supported, val & ESTATUS_1000_TFULL);
     |   linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, phydev->supported, val & ESTATUS_1000_THALF);
     |   linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, phydev->supported, val & ESTATUS_1000_XFULL);
     |  }
     | }

NXP TJA 专有驱动

NXP TJA 驱动的 struct phy_driver 的注册过程如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define phy_module_driver(__phy_drivers, __count)           \
static int __init phy_module_init(void)                 \
{                                   \
    return phy_drivers_register(__phy_drivers, __count, THIS_MODULE); \
}                                   \
module_init(phy_module_init);                       \
static void __exit phy_module_exit(void)                \
{                                   \
    phy_drivers_unregister(__phy_drivers, __count);         \
}                                   \
module_exit(phy_module_exit)

#define module_phy_driver(__phy_drivers)                \
    phy_module_driver(__phy_drivers, ARRAY_SIZE(__phy_drivers))
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int phy_drivers_register(struct phy_driver *new_driver, int n,
             struct module *owner)
{
    int i, ret = 0;

    for (i = 0; i < n; i++) {
        ret = phy_driver_register(new_driver + i, owner);    // 注册数组中所有的phy驱动
        if (ret) {
            while (i-- > 0)
                phy_driver_unregister(new_driver + i);
            break;
        }
    }
    return ret;
}
EXPORT_SYMBOL(phy_drivers_register);

根据上面的分析,由于存在 phydev->drv->probe,所以会调用其注册的函数 tja11xx_probe。

网卡 fec 和 Phy 的协作

在 linux 内核中,以太网 mac 会被注册成 struct net_device,phy 芯片会被注册成 struct phy_device。 phy_device 的状态怎么传递给 net_device,让其在 link 状态变化时做出对应的配置改变,这个任务就落在上述的 struct phylink 中介身上。

下面就以 fec 网口驱动为例,展示一下网卡 fec 和 phy 的协作过程。整个 phy 驱动的主要调用流程如下图所示:

一个 phy 驱动的原理其实是非常简单的,一般流程如下:

  1. 用轮询/中断的方式通过 mdio 总线读取 phy 芯片的状态。
  2. 在 phy link 状态变化的情况下,正确配置 mac 的状态。(例如:根据 phy 自协商的速率 10/100/1000M 把 mac 配置成对应速率)

phy 芯片状态在 phy 设备注册的时候已经体现,这里详细讲下如何在 phy link 状态变化的情况下,正确配置 mac 的状态。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void phy_state_machine(struct work_struct *work)
{

 old_state = phydev->state;

    /* (1) 状态机主体 */
 switch (phydev->state) {
    /* (1.1) 在 PHY_DOWN/PHY_READY 状态下不动作 */
 case PHY_DOWN:
 case PHY_READY:
  break;
    
    /* (1.2) 在 PHY_UP 状态下,表明网口被 up 起来,需要启动自协商并且查询自协商后的 link 状态
             如果自协商结果是 link up,进入 PHY_RUNNING 状态
             如果自协商结果是 link down,进入 PHY_NOLINK 状态
     */
 case PHY_UP:
  needs_aneg = true;

  break;

    /* (1.3) 在运行的过程中定时轮询 link 状态
             如果 link up,进入 PHY_RUNNING 状态
             如果 link down,进入 PHY_NOLINK 状态
     */
 case PHY_NOLINK:
 case PHY_RUNNING:
  err = phy_check_link_status(phydev);
  break;

 }

    /* (2) 如果需要,启动自协商配置 */
 if (needs_aneg)
  err = phy_start_aneg(phydev);

    /* (3) 如果 phy link 状态有变化,通知给对应网口 netdev */
 if (old_state != phydev->state) {
  phydev_dbg(phydev, "PHY state change %s -> %s\n",
      phy_state_to_str(old_state),
      phy_state_to_str(phydev->state));
  if (phydev->drv && phydev->drv->link_change_notify)
   phydev->drv->link_change_notify(phydev);
 }

    /* (4) 重新启动 work,周期为 1s */
 if (phy_polling_mode(phydev) && phy_is_started(phydev))
  phy_queue_state_machine(phydev, PHY_STATE_TIME);
}

自协商配置

具体启动 phy 自协商的代码流程如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
phy_state_machine()
`-| phy_start_aneg()
   `-| phy_config_aneg()
      `-| genphy_config_aneg()
         `-| __genphy_config_aneg()
            `-| genphy_setup_master_slave() // (1) 如果是千兆网口,配置其 master/slave
               `-| {
                 |  phy_modify_changed(phydev, MII_CTRL1000,    // 配置 mdio 0x09 寄存器
                 |     (CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER | CTL1000_PREFER_MASTER), ctl);
                 | }
              | genphy_config_advert() // (2) 设置本端的 advert 能力
               `-| {
                 |  linkmode_and(phydev->advertising, phydev->advertising, phydev->supported);
                 |  adv = linkmode_adv_to_mii_adv_t(phydev->advertising);
                 |  phy_modify_changed(phydev, MII_ADVERTISE,   // 10M/100M 能力配置到 mdio 0x04 寄存器
                 |       ADVERTISE_ALL | ADVERTISE_100BASE4 |
                 |       ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM, adv);
                 |  if (!(bmsr & BMSR_ESTATEN)) return changed;
                 |  adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
                 |  phy_modify_changed(phydev, MII_CTRL1000,    // 1000M 能力配置到 mdio 0x09 寄存器
                 |       ADVERTISE_1000FULL | ADVERTISE_1000HALF, adv);
                 | }
              | genphy_check_and_restart_aneg()
               `-| genphy_restart_aneg() // (3) 启动 phy 自协商
                  `-| {
                    |  phy_modify(phydev, MII_BMCR, BMCR_ISOLATE,   // 配置 mdio 0x00 寄存器
                    |       BMCR_ANENABLE | BMCR_ANRESTART);
                    | }

link 状态读取

phy link 状态读取的代码流程如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
phy_state_machine()
`-| phy_check_link_status()
   `-| phy_read_status()    // (1) 读取 link 状态 
      `-| genphy_read_status()
         `-| {
           |  genphy_update_link(phydev);   // (1.1) 更新 link 状态
           |  if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link) return 0;
           |  genphy_read_master_slave(phydev); // (1.2) 如果是千兆网口,更新本端和对端的 master/slave
           |  genphy_read_lpa(phydev);  // (1.3) 更新对端(link partner) 声明的能力
           |  if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) {
           |    phy_resolve_aneg_linkmode(phydev);  // (1.4.1) 自协商模式,解析 link 结果
           |  } else if (phydev->autoneg == AUTONEG_DISABLE) {
           |    genphy_read_status_fixed(phydev); // (1.4.2) 固定模式,解析 link 结果
           |  }
           | }
     | if (phydev->link && phydev->state != PHY_RUNNING) {  // (2) link 状态 change 事件:变成 link up
     |   phydev->state = PHY_RUNNING;
     |   phy_link_up(phydev);   // link up 事件,通知给 phylink
     | } else if (!phydev->link && phydev->state != PHY_NOLINK) {  // (3) link 状态 change 事件:变成 link down
     |   phydev->state = PHY_NOLINK;
     |   phy_link_down(phydev); // link down 事件,通知给 phylink
     | }

link 状态通知

phy 的 link 状态变化怎么通知给 netdev,并且让 mac 做出相应的配置改变,这个是通过一个中介 phylink 来实现的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
phy_link_up()/phy_link_down()
`-| phydev->phy_link_change(phydev, true/false);
   `-| phylink_phy_change()
      `-| {
        |  pl->phy_state.speed = phydev->speed;     // (1) 把 `phy_device`  状态更新给 `phylink`
        |  pl->phy_state.duplex = phydev->duplex;
        |  pl->phy_state.interface = phydev->interface;
        |  pl->phy_state.link = up;
        |  phylink_run_resolve(pl);     // (2) 通知 `phylink` 的轮询任务启动
        | }
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-11-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 人人都是极客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
【Java篇】静动交融,内外有别:从静态方法到内部类的深度解析
static(静态)是 Java 中的一个关键字,主要用于修饰类中的变量或方法。被 static 修饰的成员属于 类本身 而非某个实例对象。它在编译阶段就会被加载到方法区中(Java 8 之后元空间/Metaspace),无需通过创建对象就能访问。其生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)
半截诗
2025/03/24
1650
【14】JAVASE-面向对象-内部类【从零开始学JAVA】
Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机,Java 仍是企业和开发人员的首选开发平台。
用户4919348
2024/05/25
780
【14】JAVASE-面向对象-内部类【从零开始学JAVA】
Java内部类
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/details/52388689
DannyHoo
2018/09/13
4280
静态内部类和内部类的区别_内部类不能定义为抽象类
参考静态内部类和非静态内部类的区别中提到的 正常情况下,你不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分,因为它是static 的。只是将嵌套类置于接口的命名空间内,这并不违反接口的规则,我试验了一下 ,无论是静态类还是非静态类都可以写在接口里。
全栈程序员站长
2022/11/10
3920
静态内部类和内部类的区别_内部类不能定义为抽象类
Java基础——package+权限修饰符+内部类
编译:在D盘下有Person_Test.java ,在D盘下打开dos命令行执行 javac -d . Person_Test.java 命令就会生成一个D:/com/heima/Person_Test.class
阿Q说代码
2021/05/13
2870
Java基础——package+权限修饰符+内部类
如何讲清楚 Java 面向对象的问题与知识?(类与对象,封装,继承,多态,接口,内部类...)
这个项目是从20年末就立好的 flag,经过几年的学习,回过头再去看很多知识点又有新的理解。所以趁着找实习的准备,结合以前的学习储备,创建一个主要针对应届生和初学者的 Java 开源知识项目,专注 Java 后端面试题 + 解析 + 重点知识详解 + 精选文章的开源项目,希望它能伴随你我一直进步!
BWH_Steven
2021/02/24
1.2K0
如何讲清楚 Java 面向对象的问题与知识?(类与对象,封装,继承,多态,接口,内部类...)
几行代码带你彻底搞懂Java内部类
内部类 基本概述 --- 当一个类的定义放在另一个类的实体时,则该类叫做内部类,该类所在的类叫做外部类 在一个类体中可以出现的内容:成员变量、成员方法、构造方法、构造块、静态语句块、静态变量、方法、内部类 嵌套类 - 内部类(成员内部类、局部内部类、匿名内部类) - 静态嵌套类 语法格式 class 外部类类名{ class 内部类类名{ 内部类类体; } } 成员内部类 --- 成员内部类定义在另一个类或接口中的内部类 注意事项 - 必须先创建外部类对象才能创建成员内部类对象 -
ruochen
2021/05/15
6190
几行代码带你彻底搞懂Java内部类
Java学习【抽象类和接口】
抽象类是一种不能被实例化的类,其中至少包含一个抽象方法(即没有实现体的方法)。抽象类通常用于定义一组相关的类的共同特征,并强制其子类实现特定的方法。
2的n次方
2024/10/15
620
Java学习【抽象类和接口】
第十三天 面向对象-final static 匿名对象内部类包代码块【悟空教程】
第13天 面向对象 第1章 面向对象其他知识点 1.1 final与static关键字 1.1.1 final关键字 1.1.1.1 final概念 继承的出现提高了代码的复用性,并方便开发。但随之也有问题,有些类在描述完之后,不想被继承,或者有些类中的部分方法功能是固定的,不想让子类重写。可是当子类继承了这些特殊类之后,就可以对其中的方法进行重写,那怎么解决呢? 要解决上述的这些问题,需要使用到一个关键字final,final的意思为最终,不可变。final是个修饰符,它可以用来修饰类,类的成员,以及局
Java帮帮
2018/06/11
4160
内部类的简单理解
我们知道,在java中类是单继承的,一个类只能继承另一个具体类或抽象类(可以实现多个接口)。这种设计的目的是因为在多继承中,当多个父类中有重复的属性或者方法时,子类的调用结果会含糊不清,因此用了单继承。
zhangjiqun
2024/12/16
820
java_内部类、匿名内部类的使用
创建内部类对象格式: 外部类名.内部类名 对象名 = new 外部类型().new 内部类型();
咕咕星
2020/08/19
7070
java基础第九篇之final和内部类等
* 特点1.在同一个类中 静态代码块是优先于构造方法执行,静态代码块优先于main方法
海仔
2019/08/05
4580
基础篇:JAVA内部类的使用介绍
1 四种内部类 成员内部类 静态内部类 局部内部类 匿名内部类 2 内部类的使用场景和优点 内部类的优点:每个内部类都能独立地继承一个类(实现多个接口),无论外部类是否已经继承或者实现,对于内部类都没有影响。内部类的存在使得Java的多继承机制变得更加完善 在开发设计中会存在一些使用接口很难解决的问题,而类却只能继承一个父类。这个时候可以利用内部类去继承其他父类,及implements多个接口能力来解决。内部类使得多重继承的解决方案变得更加完整 public class HashMap<K,V> exten
潜行前行
2020/12/11
5330
基础篇:JAVA内部类的使用介绍
Java内部类复习笔记
什么是Java内部类? 在类内部可定义成员变量和方法,且在类内部也可以定义另一个类。如果在类 Outer 的内部再定义一个类 Inner,此时类 Inner 就称为内部类(或称为嵌套类),而类 Out
框架师
2021/03/05
6340
Java基础8:深入理解内部类
https://h2pl.github.io/2018/04/25/javase8
程序员黄小斜
2019/04/06
1.6K0
Java学习笔记之内部类 & API
    在一个类中定义一个类,举例:在一个类A的内部定义一个类B,类B就被称为内部类
Jetpropelledsnake21
2021/12/01
3470
Java学习笔记之内部类 & API
Java基础(十一):抽象类、接口、内部类
选择保留其中一个,通过“接口名.super.方法名"的方法选择保留哪个接口的默认方法
冬天vs不冷
2025/01/21
3100
Java基础(十一):抽象类、接口、内部类
java之内部类
成员内部类的优点: ⑴ 内部类作为外部类的成员,可以访问外部类的私有成员或属性。(即使将外部类声明为PRIVATE,但是对于处于其内部的内部类还是可见的。) ⑵ 用内部类定义在外部类中不可访问的属性。这样就在外部类中实现了比外部类的private还要小的访问权限。 注意:内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。对于一个名为outer的外部类和其内部定义的名为inner的内部类。编译完成后出现outer.class和outer$inner.class两类。
猫头虎
2024/04/07
1320
10(02)总结形式参数,包,修饰符,内部类
6:内部类(理解) (1)把类定义在另一个类的内部,该类就被称为内部类。 举例:把类B定义在类A中,类B就被称为内部类。 (2)内部类的访问规则 A:可以直接访问外部类的成员,包括私有 B:外部类要想访问内部类成员,必须创建对象 /* 内部类概述: 把类定义在其他类的内部,这个类就被称为内部类。 举例:在类A中定义了一个类B,类B就是内部类。 内部的访问特点: A:内部类可以直接访问外部类的成员,包括私有。 B:外部类要访问内部类的成员,必须创建对象。 */ class Outer {
Java帮帮
2018/03/15
6020
【抬抬小手学Python】 内部类
关于内部类的部分内容,正在深入了解中。 每天都在修改更新中。 一、成员内部类 /** * 需要知道两个前提: * 1.成员内部类是外围类的一个成员,它两关系特别好,推心置腹,抵足而眠,都是最好
查理不是猹
2022/01/07
4910
推荐阅读
相关推荐
【Java篇】静动交融,内外有别:从静态方法到内部类的深度解析
更多 >
LV.0
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档