第十六章PHY -基于Linux3.10
来源:互联网 发布:牛耳软件教育 编辑:程序博客网 时间:2024/04/30 02:50
下载地址《http://download.csdn.net/detail/shichaog/8620701》
16.1 PHY
本章和OSI模型中的物理层和数据链路层关系密切。在嵌入式SOC上,通常集成有ARM核和MAC控制器,以及增加数据传输带宽的MAC专用DMA,对这种形式的SOC通常使用外接物理PHY设备的方法,外接的PHY芯片如RTL8201F、88E1111、88E6096等,集成型以太网控制器集成了MAC和PHY,如DM9000、RTL8139CP等,它们常用于没有MAC控制器的SOC上,如S3c2440。MAC控制器将收到的数据通过MII、SMII、GMII、RGMII等接口将数据传递给CPU。向下PHY将MAC将数据转换成模拟信号通过RJ45向外传输、或者通光模块将PHY的模拟信号转换成光信息传输。
MDI = MEDIUMDEPENDENT INTERFACE PCS = PHYSICAL CODINGSUBLAYER
GMII = GIGABITMEDIA INDEPENDENT INTERFACE PMA = PHY MEDIUM ATTACHMENT
PHY = PHYSICALLAYER DEVICE PMD = PHYSICAL MEDIUM DEPENDENT
图16.1.1 千兆以太网架构 ieee802.3 clause34
图16.1.2百兆以太网clause21
100M/1000M常用的编码格式分别是4B/5B和8B/10B;100M-TX具有自适应功能,在两个网卡连接后,各自会发送Fast Link Pulse 脉冲,通过该脉冲检测出双方通信速率和各自通信模式,并根据该模式自动选择最优的工作模式。这种自动模式选择由PHY芯片实现,通常PHY芯片将其称之为自协商(Auto Negotiation)这自适应功能在万兆模式就不再支持了。
PHY将信号按如下的格式进行传输:
前导符+开始位+目的MAC地址+源MAC地址+类型长度+数据+padding(optional)+32bitCRC
对于百兆,
前导符是:
10101010 1010101010101010 10101010 10101010 10101010 10101010
开始位是:
10101011
PHY和MAC连接的方式主要有三种:
l 集成型,距离最短
l 同一块PCB上,通过铜制导线相连
l 通过非屏蔽双绞线、屏蔽双绞线、光纤相连
RECONCILIATIONSUBLAYER就是针对第三中情况而设计,其使MAC层可以用一个方法向PHY发送和接收数据而不必关心和PHY的具体连接方式。
MII和GMII实际上市承载信号传输的总线定义,它们的时钟线和数据信号形式略有差别。控制PHY工作的总线是mdio。
clause22.2.4参看PHY寄存器及其每个bit的定义。所有PHY芯片遵循该手册的定义,常用到的是前五个寄存器。
图16.2.11000BASE-X PCS和PMA的职责细分
从上图可以看到PCS和MAC之间的传输都是8bit,而PCS和PMA之间的传输数据位宽变成了10bit。
PCS层主要完成PCS传输、载波侦听、同步、PCS接收和自协商。
PMA实现8B到10B的映射功能。
PMD实现一个串行器和解串器的功能,即PMA传递过来的是并行10bit分成10次,每次一个bit传输到PMD层。最终在网线或者光纤上传递是一个比特一个比特串行传输的。这部分通常包括:混合信号处理技术减少近端反射、自适应均衡、基线漂移校正BLW、串扰消除、回波消除、时钟回复、错误校正。
LLC识别网络协议,对其进行封装。
16.2 MAC驱动
单独一个PHY是没法进行数据传输的,还有MAC控制器也是需要初始化的。由于不同厂商的MAC控制器细节不同,所以这里并不详细。
24 static int XXX_drv_probe(struct platform_device *pdev) 25 { 26 struct device_node *np = pdev->dev.of_node; 27 struct net_device *ndev; 28 struct XXX_info *lp; 29 struct resource *res; 30 const char *macaddr; 31 int ret_val = 0; 32 //alloc_etherdev为以太网设备申请空间 34 ndev = alloc_etherdev(sizeof(struct XXX_info)); 35 if (ndev == NULL) { 36 dev_err(&pdev->dev, "alloc_etherdev fail.\n"); 37 return -ENOMEM; 38 }//lp的指针用于存储34行申请的以太网设备都有字段+一些满足MAC厂商特有功能的字段 39 lp = netdev_priv(ndev); //设备树获得MAC控制器基地址并映射该地址 41 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 49 lp->regbase = devm_ioremap(&pdev->dev, res->start, resource_size(res));//MAC 中断号 57 ndev->irq = platform_get_irq(pdev, 0);//设置private字段 64 SET_NETDEV_DEV(ndev, &pdev->dev); 65 ndev->dev.dma_mask = pdev->dev.dma_mask; 66 ndev->dev.coherent_dma_mask = pdev->dev.coherent_dma_mask; 67 68 spin_lock_init(&lp->lock); 69 lp->ndev = ndev; 70 lp->msg_enable = netif_msg_init(msg_level, NETIF_MSG_DRV); //设备树解析 72 XXX_of_parse(np, lp); 73 74 if (lp->ipc_tx) 75 ndev->features |= NETIF_F_HW_CSUM; 89 /* request gpio for PHY reset control */ 90 if (gpio_is_valid(lp->rst_gpio)) { 91 ret_val = devm_gpio_request(&pdev->dev, lp->rst_gpio, "phy reset"); 96 gpio_direction_output(lp->rst_gpio, !lp->rst_gpio_active); 97 }//mdio读写方法。100 lp->new_bus.name = "XXX MII Bus",101 lp->new_bus.read = &XXX_mdio_read,102 lp->new_bus.write = &XXX_mdio_write,103 lp->new_bus.reset = &XXX_mdio_reset,104 snprintf(lp->new_bus.id, MII_BUS_ID_SIZE, "%s", pdev->name);105 lp->new_bus.priv = lp;106 lp->new_bus.irq = kmalloc(sizeof(int)*PHY_MAX_ADDR, GFP_KERNEL);114 lp->new_bus.parent = &pdev->dev;115 lp->new_bus.state = MDIOBUS_ALLOCATED;116 //这行会注册该设备,mdiobus_register会被调用,完成mdio驱动注册。118 ret_val = of_mdiobus_register(&lp->new_bus, pdev->dev.of_node);//找到第一个PHY设备,该MAC将使用这个PHY设备进行通信125 lp->phydev = phy_find_first(&lp->new_bus);/* 初始化该以太网设备ndev->header_ops= ð_header_ops;ndev->type = ARPHRD_ETHER;ndev->hard_header_len = ETH_HLEN;ndev->mtu = ETH_DATA_LEN;ndev->addr_len= ETH_ALEN;ndev->tx_queue_len= 1000;/* Ethernet wants good queues */ndev->flags = IFF_BROADCAST|IFF_MULTICAST;ndev->priv_flags|= IFF_TX_SKB_SHARING;memset(dev->broadcast, 0xFF, ETH_ALEN);*/137 ether_setup(ndev);//net device operations 初始化,这是一个非常重要的函数操作集,这是各厂商针对各自MAC控制器而写,初始化、打开、发送、多播、超时、邻居表初始化等138 ndev->netdev_ops = &XXX_netdev_ops;//NAPI注册,该功能使得一个接收或者发送中断可以发送多个数据包,这样提高数据包的收发效率。139 ndev->watchdog_timeo = XXX_TX_WATCHDOG;140 netif_napi_add(ndev, &lp->napi, XXX_napi, XXX_NAPI_WEIGHT);//解析设备树,获得MAC地址142 macaddr = of_get_mac_address(pdev->dev.of_node);143 if (macaddr)144 memcpy(ndev->dev_addr, macaddr, ETH_ALEN);145 146 if (!is_valid_ether_addr(ndev->dev_addr))147 eth_hw_addr_random(ndev);//设置设备不可用状态149 XXX_disable(lp);153 if (gpio_is_valid(lp->rst_gpio))154 gpio_set_value_cansleep(lp->rst_gpio, lp->rst_gpio_active);//ethtool工具支持156 SET_ETHTOOL_OPS(ndev, &XXX_ethtool_ops);//注册网卡设备,138行提及的初始化成员ndo_init会被调用完成设备的初始化。157 ret_val = register_netdev(ndev);163 platform_set_drvdata(pdev, ndev);164 dev_notice(&pdev->dev, "MAC Address[%pM].\n", ndev->dev_addr);165 166 return 0;178 }179 static const struct of_device_id XXX_eth_dt_ids[] = {180 { .compatible = "XXX,eth" },181 { /* sentinel */ }182 };183 static struct platform_driver XXX_driver = {184 .probe = XXX_drv_probe,185 .remove = XXX_drv_remove,186 .driver = {187 .name = "XXX-eth",188 .owner = THIS_MODULE,189 .of_match_table = XXX_eth_dt_ids,190 },191 };192 193 module_platform_driver(XXX_driver);
193module_platform_driver会创建module_init(XXX_driver)和module_exit(XXX_driver)两个宏,XXX_driver 的probe函数会被调用。
24~178都是XXX_drv_probe的内容。
这里的MAC控制器使用device-tree解析设备,这部分内容在《Linux系统启动那些事—基于Linux 3.10内核》有涉及,这里略过若干行。
struct net_device_ops {//网络设备注册时会被调用int (*ndo_init)(struct net_device *dev); //打开一个网卡,配置硬件使能,注册中断服务函数,使能NAPI,使能数据发送队列,初始化DMA,检测link状态,//调用linkwatch_fire_event通知内核其它部分该事件,以让其它部分进行相应的处理,如路由表项的更新。启动PHY。int (*ndo_open)(struct net_device *dev); //禁止发送、禁止NAPI,释放中断号,停止PHY、设备无载波调用linkwatch_fire_event通知内核其它部分该事件.int (*ndo_stop)(struct net_device *dev);//网络设备发送数据包函数,这部分是SOC息息相关的DMA和相关环形缓冲区的管理。netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev);//MAC地址重置int (*ndo_set_mac_address)(struct net_device *dev, void *addr);//设备MAC地址合法性验证int (*ndo_validate_addr)(struct net_device *dev)//用户空间ioctl的内核支持。int (*ndo_do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);//网卡设备的管理int (*ndo_set_config)(struct net_device *dev, struct ifmap *map);//MTU変更int (*ndo_change_mtu)(struct net_device *dev, int new_mtu);//邻居设置int (*ndo_neigh_setup)(struct net_device *dev, struct neigh_parms *);//发送超时void (*ndo_tx_timeout) (struct net_device *dev);};16.3 mdio总线初始化driver/net/phy/mdio_bus.c 91 static struct class mdio_bus_class = { 92 .name = "mdio_bus", 93 .dev_release = mdiobus_release, 94 };447 struct bus_type mdio_bus_type = {448 .name = "mdio_bus",449 .match = mdio_bus_match,450 .pm = MDIO_BUS_PM_OPS,451 .dev_attrs = mdio_dev_attrs,452 };453 EXPORT_SYMBOL(mdio_bus_type);454 455 int __init mdio_bus_init(void)456 {457 int ret;458 459 ret = class_register(&mdio_bus_class);460 if (!ret) {461 ret = bus_register(&mdio_bus_type);462 if (ret)463 class_unregister(&mdio_bus_class);464 }465 466 return ret;467 }
459在/sys/class/目录下注册一个mdio_bus类,mdiobus_release是删除这个类的方法。用户空间可以通过这个类得到mdio总线的信息。
461注册一个驱动核心层,bus_register注册mdio总线,后面PHY设备和PHY设备驱动会被挂载在这个总线上,I2C等总线也是调用这个接口进行注册的。
还记得上一节的XXX_drv_probe函数的第118行的of_mdiobus_register函数不?这个函数注册mii总线并且根据设备树创建PHY设备。
33 int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np) 34 { 35 struct phy_device *phy; 36 struct device_node *child; 37 const __be32 *paddr; 38 u32 addr; 39 bool is_c45, scanphys = false; 40 int rc, i, len; 41 //禁止自举探测,因为在设备树中已经有PHY设备的信息了。 44 mdio->phy_mask = ~0; 45 //清除PHY设备的中断。 47 if (mdio->irq) 48 for (i=0; i<PHY_MAX_ADDR; i++) 49 mdio->irq[i] = PHY_POLL;//获得PHY在设备树中的节点 51 mdio->dev.of_node = np;//这里的mdiobus_register并不是总线的注册,而是调用mdiobus_scan扫描PHY设备,但是对于设备树方法会跳过mdiobus_scan方法,因为PHY设备的相关信息已经存放在设备树的节点里了。得到PHY设备后将其挂接到mii总线上。 54 rc = mdiobus_register(mdio); 55 if (rc) 56 return rc; 57 58 /*59~108 循环子节点,为存在的每一个PHY创建一个phy_device表示结构体 */ 59 for_each_available_child_of_node(np, child) {//PHY地址,一个mii总线最多支持32个PHY设备,所以这里的值为0~31。 61 paddr = of_get_property(child, "reg", &len); 68 69 addr = be32_to_cpup(paddr); //802.3-c45和802.3-c22两种标准的PHY ID读取略有区别。 82 is_c45 = of_device_is_compatible(child, 83 "ethernet-phy-ieee802.3-c45");//验证PHY ID信息正确性。正确的话会调用phy_device_create创建phy_device结构体来表示PHY设备。这个结构体设置的信息主要有://速率、双工、link、以及将PHY状态设置为PHY_DOWN还创建一个内核守护进程,用于维护PHY状态变化。 84 phy = get_phy_device(mdio, addr, is_c45);//增加该节点的引用计数。 95 of_node_get(child); 96 phy->dev.of_node = child;//得到了PHY的所有信息,这里注册PHY设备。主要是将mii总线上存放PHY设备的phy_map数组中,数组的索引是PHY的物理地址,最大是31。 99 rc = phy_device_register(phy);108 }//114~161处理在PHY设备树中没有被赋予地址的PHY设备的初始化。过程同上。114 for_each_available_child_of_node(np, child) {115 /* Skip PHYs with reg property set */116 paddr = of_get_property(child, "reg", &len);117 if (paddr)118 continue;120 is_c45 = of_device_is_compatible(child,121 "ethernet-phy-ieee802.3-c45");122 123 for (addr = 0; addr < PHY_MAX_ADDR; addr++) {124 /* skip already registered PHYs */125 if (mdio->phy_map[addr])126 continue;132 phy = get_phy_device(mdio, addr, is_c45);133 if (!phy || IS_ERR(phy))134 continue;143 /* Associate the OF node with the device structure so it144 * can be looked up later */145 of_node_get(child);146 phy->dev.of_node = child;147 148 /* All data is now stored in the phy struct;149 * register it */150 rc = phy_device_register(phy);157 dev_info(&mdio->dev, "registered phy %s at address %i\n",158 child->name, addr);159 break;160 }161 }162 163 return 0;164 }
16.3 PHY驱动
16.3.1 PHY初始化
PHY层的初始化,PHY驱动通常可以使用缺省内核PHY驱动,该驱动是按照ieee802.3协议规定的标准来设计的。
PHY层从driver/net/phy/phy_device.c文件开始。
1112 static struct phy_driver genphy_driver = {1113 .phy_id = 0xffffffff,1114 .phy_id_mask = 0xffffffff,1115 .name = "Generic PHY",1116 .config_init = genphy_config_init,1117 .features = 0,1118 .config_aneg = genphy_config_aneg,1119 .read_status = genphy_read_status,1120 .suspend = genphy_suspend,1121 .resume = genphy_resume,1122 .driver = {.owner= THIS_MODULE, },1123 };1124 1125 static int __init phy_init(void)1126 {1127 int rc;//mdio总线初始化1129 rc = mdio_bus_init();1130 if (rc)1131 return rc;//PHY设备注册。1133 rc = phy_driver_register(&genphy_driver);1134 if (rc)1135 mdio_bus_exit();1136 1137 return rc;1138 }1139 1146 subsys_initcall(phy_init);1133是对driver_register的封装,该函数用于向mdio总线上注册genphy_driver,并扫描mdio总线,如果发现有没有驱动的设备,会使用这个驱动进行绑定,一个驱动可以对应多个设备。从其名称可以知道,这个驱动具有通用性。include/uapi/linux/mii.h#define MII_BMCR 0x00/* Basic mode control register */#define MII_BMSR 0x01/* Basic mode status register *#define BMCR_ISOLATE 0x0400/* Isolate data paths from MII */#define BMCR_PDOWN 0x0800/* Enable low power state */#define BMCR_ANENABLE 0x1000/* Enable auto negotiation */drivers/net/phy/phy_device.c 976 int genphy_resume(struct phy_device *phydev) 977 { 978 int value; 979 980 mutex_lock(&phydev->lock); 981 /**static inline int phy_read(struct mii_phy* phy, int reg)*{* return phy->mdio_read(phy->dev, phy->mii_id, reg);*}*/ 982 value = phy_read(phydev, MII_BMCR); 983 phy_write(phydev, MII_BMCR, (value & ~BMCR_PDOWN)); 984 985 mutex_unlock(&phydev->lock); 986 987 return 0; 988 }
MII_BMCR寄存器的各位定义如下,其第十一个bit置位,以上电,唤醒PHY设备,其它的函数操作和这里的类似,参考手册就能看懂。就不再赘述了。
图:控制寄存器各bit定义,摘自ieee802.3 clause 22.2.4.1
16.3.2 PHY驱动实例
剩下一个问题就是驱动的编写了。
<phy.h>#define PHY_BASIC_FEATURES (SUPPORTED_10baseT_Half | \SUPPORTED_10baseT_Full | \SUPPORTED_100baseT_Half | \SUPPORTED_100baseT_Full | \SUPPORTED_Autoneg | \SUPPORTED_TP | \SUPPORTED_MII)static int 8201f_config_init(struct phy_device *phydev){int val;u32 features;/* For now, I'll claim that the generic driver supports* all possible port types */features =PHY_BASIC_FEATURES;/* Do we support autonegotiation? */val = phy_read(phydev, MII_BMSR);if (val < 0)return val;if (val & BMSR_ANEGCAPABLE)features |= SUPPORTED_Autoneg;if (val & BMSR_100FULL)features |= SUPPORTED_100baseT_Full;if (val & BMSR_100HALF)features |= SUPPORTED_100baseT_Half;if (val & BMSR_10FULL)features |= SUPPORTED_10baseT_Full;if (val & BMSR_10HALF)features |= SUPPORTED_10baseT_Half;if (val & BMSR_ESTATEN) {val = phy_read(phydev, MII_ESTATUS);if (val < 0)return val;if (val & ESTATUS_1000_TFULL)features |= SUPPORTED_1000baseT_Full;if (val & ESTATUS_1000_THALF)features |= SUPPORTED_1000baseT_Half;}phydev->supported = features;phydev->advertising = features;return 0;}int 8201f_config_aneg(struct phy_device *phydev){int result;if (AUTONEG_ENABLE != phydev->autoneg)return genphy_setup_forced(phydev);result = genphy_config_advert(phydev);if (result < 0) /* error */return result;if (result == 0) {/* Advertisment hasn't changed, but maybe aneg was never on to* begin with? Or maybe phy was isolated? */int ctl = phy_read(phydev, MII_BMCR);if (ctl < 0)return ctl;if (!(ctl & BMCR_ANENABLE) || (ctl & BMCR_ISOLATE))result = 1; /* do restart aneg */}/* Only restart aneg if we are advertising something different* than we were before.*/if (result > 0)result = genphy_restart_aneg(phydev);return result;}static struct phy_driver 8201f_driver = { .phy_id = 0x001cc810, .name = "Realtack 8201f", .phy_id_mask = 0x0ffffff0, .features = PHY_BASIC_FEATURES, .config_init = 8201f_config_init, .config_aneg = 8201f_config_aneg, .read_status = genphy_read_status, .driver = { .owner = THIS_MODULE,},};static int __init 8201f_init(void){ int ret; ret = phy_driver_register(&8201f_driver); if (ret) phy_driver_unregister(&8201f_driver); return ret;}static void __exit 8201f_exit(void){ phy_driver_unregister(&8201f_driver);}module_init(8201f_init);module_exit(8201f_exit);
16.3.3 PHY状态机
Windows桌面的右下角有个表示网络状态的图标,如果将无线网以及有线网从RJ45拔出,会发现显示网络图标的图标变成一个黄色三角形,三角形里面还有一个感叹号,如果将鼠标放上去,显示无网络访问,插上网线或者打开无线网,右下角的图标会显示连上网络信号的强度。这个过程就涉及到PHY的状态是如何的,并且还需要某种机制通知网络协议栈。这节就是Linux下PHY状态的管理,PHY状态的要比上连上和断开这两种状态多很多。
一个PC可以有多张网卡,每张网卡会对应一个PHY物理芯片,每一个PHY芯片状态会由一个PHY状态机管理,在注册PHY设备时这个状态机会被创建。其创建流程见图16.3.1。其起点of_mdiobus_register是在16.2节的XXX_drv_probe函数调用的。
图16.3.3 PHY状态机创建
INIT_DELAYED_WORK(&dev->state_queue,phy_state_machine);
其第一个参数传递一个delayed_work 类型的结构体。
struct delayed_work {struct work_struct work;struct timer_list timer;};#define INIT_DELAYED_WORK(_work, _func) \do { \INIT_WORK(&(_work)->work, (_func));\init_timer(&(_work)->timer);\} while (0)
phy_state_machine()这个函数由上面注册的timer间隔一秒钟调用一次。
PHY各状态如下:
DOWN:PHY设备和PHY驱动还未准备好,处于这个状态的PHY,probe方法将被调用,probe函数将PHY设置为STARTING或者READY状态。
STARTING:PHY设备正在启动,以太网驱动还未准备好。
READY:PHY已经初始化完毕,可以接收和发送数据包,但是控制器还没有。phy_probe()会设置这个状态。
PENDING:PHY设备正在启动中,以太网驱动已经准备好。phy_start()会设置这个状态。
UP:PHY和与之连接的MAC控制器已经可以工作,中断在这个状态下将是能。设置自协商AN的定时器。
AN:正在自协商link状态,当前link状态是down,phy_timer()在检测到PHY处于UP状态时会设置,在PHY使能了自协商时config_aneg()会设置这个状态。
自协商结果:
没有link,状态设置成NOLINK,
有link,状态设置成RUNNING,调用adjust_link
超时,重试自协商
自协商未完成,且不支持magic_aneg,状态被设置成FORCING
NOLINK:PHY已经初始化完毕,但是物理链路并不存在(网线、光纤拔出)
timer注意到link通了,状态切到RUNNING
config_aneg切到AN
phy_stop切到HALTED状态
FORCING:PHY被强制设定
link已经可以工作,状态设置到RUNNING
link down,重试FORCING
phy_stop切到HALTED状态
RUNNING:PHY在接收、发送数据
当轮询PHY状态时timer会设置CHANGELINK标志,
irq会设置CHANGELINK
config_aneg切到AN
phy_stop切到HALTED状态
CHANGELINK:link状态改变
link连通,timer将状态设置为RUNNING
link不通,timer将状态设置为NOLINK
phy_stop切到HALTED状态
HALTED:PHY已经准备好,但是轮询和中断还未完成;或者PHY处于错误状态。
phy_start将状态设置为RESUMING
RESUMING:PHY处于HALTED状态,这里设置,让其再次运行。
FORCING或者AN完成时,状态将被设置为RUNNING
AN未完成,状态将被设置成AN
phy_stop将状态设置成HALTED
PHY设备状态以及各状态的设置函数如图16.3.2:
图16.3.2 PHY设备及状态管理函数
phy_state_machine会调用下面三个函数,其中netif_carrier_off、netif_carrier_on用于向内核通知链路层载波信号存不存在,至于检测工作,是由PHY芯片完成,PHY芯片的一个特性是自协商,也有一种叫switch的芯片也支持自协商,但是switch包含了多个PHY,PHY完成检测物理线路上是否有载波,PHY的检测是由硬件逻辑电路的状态机完成的。
netif_carrier_offphy_aneg_donenetif_carrier_on
这里来看看一个物理芯片PHY状态发生改变是如何通知给CPU的。netif_carrier_on和netif_carrier_off都会判断PHY设备的载波状态,对于on,要设置载波状态,off则清除载波状态,然后调用linkwatch_fire_event发送事件给内核。linkwatch_fire_event的定义如下:
void linkwatch_fire_event(struct net_device *dev){if (!test_and_set_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state)) {linkwatch_add_event(dev);} else if (!urgent)return;linkwatch_schedule_work(urgent);}
在网络设备一节中提到完了设备的表示结构体是struct net_device,,其有一个字段是link_watch_list,该链表是用来存放link发生改变时的事件,所以__linkwatch_run_queue工做就是将发生link状态改变的事件的网络设备net_device的link_watch_list链表挂到lweventlist。lweventlist在net/core/link_watch.c一开始就声明的链表。
static LIST_HEAD(lweventlist);static void linkwatch_add_event(struct net_device *dev){if (list_empty(&dev->link_watch_list)) {list_add_tail(&dev->link_watch_list, &lweventlist);}
对linkwatch_event的调用被间接调用了,linkwatch_schedule_work调用schedule_delayed_work(&linkwatch_work,0);向系统全工作队列system_wq添加一项,就是下面DECLARE_DELAYED_WORK声明的delayed工作。
staticDECLARE_DELAYED_WORK(linkwatch_work, linkwatch_event);
linkwatch_event将被内核调度运行,函数如下:
static void linkwatch_event(struct work_struct *dummy){rtnl_lock();__linkwatch_run_queue(time_after(linkwatch_nextevent, jiffies));rtnl_unlock();}
__linkwatch_run_queue遍历lweventlist链表上产生link状态改变的设备,并将其从链表上移除,并清除net_device的__LINK_STATE_LINKWATCH_PENDING标志。然后发送NETDEV_CHANGE通知前面注册的netdev_chain通知链。
- 第十六章PHY -基于Linux3.10
- 第十章 网络工具--基于Linux3.10
- 第十二章 trie路由--基于Linux3.10
- 第十四章 netlink机制--基于Linux3.10
- 第三章 套接字相关数据结构--基于Linux3.10
- 第四章 网络层接收数据包流程--基于Linux3.10
- 第六章 应用层(网络)--基于Linux3.10
- 第七章 tcp发送(传输层)--基于Linux3.10
- 第九章 tcp拥塞控制--基于Linux3.10
- 第十一章 Linux包过滤防火墙-netfilter--基于Linux3.10
- 第十五章 提升网络性能技术--基于Linux3.10
- 编写i2c驱动-基于Linux3.10
- 第一章网络子系统初始化--基于Linux3.10
- 内存管理-之启动-基于linux3.10
- 虚拟文件系统 (VFS)-基于linux3.10
- 内存管理-之启动-基于linux3.10
- 编写i2c驱动-基于Linux3.10
- 第二章 主机到网络层(网卡)--基于Linux3.10
- C#界面美化之美化单个控件
- 黑马程序员_JAVA:面对对象
- java中的枚举
- 浅谈如何静态创建Fragment
- 黑马程序员_JAVA:内部类
- 第十六章PHY -基于Linux3.10
- 基于百度云推送的高仿微信实时聊天Android源码+
- 有序数组A,B的中位数
- 黑马程序员_JAVA:网络编程
- hdu2844
- linux修改内核频率
- 设计模式总论
- fprintf与stderr、stdout的使用
- 初学for循环