第十六章PHY -基于Linux3.10

2023-11-09

下载地址《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 it
144              * 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_off
phy_aneg_done
netif_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通知链。



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

第十六章PHY -基于Linux3.10 的相关文章

  • SSL 和 Tkinter 不存在于 Python 3.5.2、Debian Linux 的源代码构建中

    我刚刚将 Python 3 5 2 下载到我的 Debian 机器上并使用以下命令构建它 configure make make test sudo make install 一切正常 但在make test输出中 它显示安装程序由于未安装
  • 查找系统日志最大消息长度

    大多数 Unix 程序员都会习惯由syslog h 并且许多实现 例如 glibc 对发送给它的 syslog 消息的大小没有真正的限制 但通常对侦听的应用程序有限制 dev log 我想知道是否有人知道如何找到系统日志的最大消息大小 或者
  • Linux 缓冲区溢出环境变量

    我一直在审查不同类型的缓冲区溢出 并遇到了一个我不记得为什么会发生的问题 下面的代码是我尝试执行缓冲区溢出的程序 include
  • 在 LINUX 上测量 TLB 未命中的命令

    有人可以指导我使用一个命令来测量 LINUX 上的 TLB 未命中吗 是否可以将轻微页面错误视为 TLB 未命中 您可以使用perf去做这个 前提是你的CPU支持 Use perf list了解可用的计数器 当我拿到这个列表并查找 TLB
  • 内核如何区分线程和进程

    Linux 中的线程被称为轻量级进程 无论是进程还是线程 它们的实现都是通过task struct数据结构 1 gt 那么 从这个意义上说 内核如何区分线程和进程 2 gt 当发生上下文切换时 线程如何在上下文切换中获得更少的开销 因为在此
  • 保护一个保存 MySQL 数据库的简单 Linux 服务器?

    这是一个初学者问题 但我浏览了该网站上的许多问题 但没有找到简单直接的答案 我正在设置一个运行 Ubuntu 的 Linux 服务器来存储 MySQL 数据库 该服务器尽可能安全非常重要 据我所知 我主要担心的是传入的 DoS DDoS 攻
  • 用于列出用户和组的 Python 脚本

    我正在尝试编写一个脚本 在自己的行上输出每个用户及其组 如下所示 user1 group1 user2 group1 user3 group2 user10 group6 etc 我正在为此用 python 编写一个脚本 但想知道如何做到这
  • 无法使用Linux服务启动Archiva 2.1.0

    我正在尝试在 Linux 上启动最近发布的 Apache Archiva v2 1 0 独立版 出于测试目的 该 zip 已解压缩在 opt archiva 2 0 文档说 http archiva apache org docs 2 1
  • 在ubuntu 18.04上安装python 2.7

    有没有办法在 Ubuntu 18 04 上安装 Python 2 7 我尝试了这个命令 但它不起作用 sudo apt install python minimal 有没有办法手动安装 我尝试使用 python 2 7 作为不支持 pyth
  • 如何在汇编语言中换行打印多个字符串

    我试图在汇编中的不同行上打印多个字符串 但使用我的代码 它只打印最后一个字符串 我对汇编语言非常陌生 所以请耐心等待 section text global start start mov edx len mov edx len1 mov
  • vm.dirty_ratio 和 vm.dirty_background_ratio 之间的区别?

    我目前正在试验中找到的内核参数 proc sys vm 尤其dirty ratio and dirty background ratio 内核文档对两者都有以下解释 脏背景比例 包含 以包含空闲页面的总可用内存的百分比表示 和可回收页 后台
  • 从 bash 脚本返回值

    我想创建一个返回值的 Bash 文件 意思是 在脚本 script a bash 中我有一定的计算 脚本 script b bash 会调用它 script a bash return 1 5 script b bash a value s
  • 使用 Shell 脚本提供密码

    我已将客户端和服务器设置为无密码登录 就像无密码登录一样 通过将服务器的 RSA 密钥复制到所有客户端的 root ssh id rsa pub 来实现 但这是我手动完成的 我喜欢使用 shell 脚本自动执行此过程 并通过脚本向计算机提供
  • 每个进程是否都存在内核堆栈?

    每个用户空间进程是否都存在一个内核堆栈和一个用户空间堆栈 如果两个堆栈都存在 那么每个用户空间进程应该有 2 个堆栈指针 对吗 在 Linux 中 每个任务 用户空间或内核线程 都有一个 8kb 或 4kb 的内核堆栈 具体取决于内核配置
  • 使用openssl从服务器获取证书

    我正在尝试获取远程服务器的证书 然后可以将其添加到我的密钥库中并在我的 Java 应用程序中使用 一位高级开发人员 正在度假 告诉我我可以运行这个 openssl s client connect host host 9999 获取转储的原
  • 在linux x86平台上学习ARM所需的工具[关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我有一个 x86 linux 机器 在阅读一些关于 ARM 的各种信息时 我很好奇 现在我想花一些时间学
  • 如何重命名共享库以避免同名冲突?

    我找到了一个图书馆 libjson http sourceforge net projects libjson 我正在尝试将其构建为共享库并在项目中使用 建造很简单 修复 Makefile 错误后 SHARED 1 make install
  • 在命令行上解密使用 PHP openssl_encrypt 制作的文件

    我有一个要加密的字符串 encryptThis Super Secret Text echo openssl encrypt encryptThis aes 128 cbc 1234 FALSE F68A9A229A516752 然后我通过
  • 模拟用户输入以使用不同参数多次调用脚本

    我必须使用提供的脚本 该脚本在脚本运行时接受用户输入而不是参数 我无法解决这个问题 脚本的一个例子是 bin bash echo param one read one doSomething echo param two read two
  • 除了 iptables 之外还有数据包管理实用程序吗? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在寻找一个 Linux 实用程序 它可以根据一组规则更改网络数据包的有效负载 理想情况下 我会使用

随机推荐

  • 修炼离线:(五)hbase映射表插入hive

    一 创建hive表 sql drop table if exists ods odsyyy create table if not exists ods odsfff row id string comment 行记录唯一ID 对应ROW
  • Makefile入门二、理解$@、$^和$<

    文章目录 一 理解 lt 的含义 二 举例 三 简提Makefile中打印日志信息 前面简单记录了一下Makefile中helloworld的用法 这次来理解一些 lt 的含义 一 理解 lt 的含义 Makefile中 格式为这样的 ta
  • Spring Roo 实站( 一 )部署安装 & 第一个示例程序

    一 安装 注 可以参与官网spring roo static springsource org spring roo reference html intro html intro exploring sampleROO OPTS http
  • 【Linux 驱动篇(二)】LED 驱动开发

    文章目录 一 Linux 下 LED 灯驱动原理 1 地址映射 1 1 ioremap 函数 1 2 iounmap 函数 2 I O 内存访问函数 2 1 读操作函数 2 2 写操作函数 二 实验程序编写 1 LED 灯驱动程序编写 2
  • 电脑不能开热点的一种可以尝试的解决方法

    1 说明 方法不一定万能 个人情况 win10 以前可以开热点 不知何时起不能再开 会显示 我们无法设置移动热点 2 解决办法 1 管理员方式打开cmd 2 运行命令 netsh int ip reset netsh winsock res
  • 【金融系列】【statsmodels】如何用Python做实证研究?介绍一个功能和STATA很像的Python包,最小二乘,虚拟变量

    博主本科接触的研究主要是公司金融方向的研究 在公司金融的实证研究中 我们的终极目标是建立变量间的因果关系 我们需要识别因果关系 来检验理论 评价政策效果 或作出预测 目前该领域的研究大部分是使用了STATA和R这两种工具来开展研究的 其实作
  • 亲测可用:opencv图片序列转视频

    亲测可用 glob函数可以遍历文件夹下文件 完毕后可在项目目录下生成output avi视频 可以稍作改进 转换的时候显示当前转换图像 include
  • 网络安全专业毕业设计最新最全选题精华汇总-持续更新中

    前言 大家好 这里是海浪学长毕设专题 大四是整个大学期间最忙碌的时光 一边要忙着准备考研 考公 考教资或者实习为毕业后面临的升学就业做准备 一边要为毕业设计耗费大量精力 学长给大家整理了网络安全专业最新精选选题 如遇选题困难或选题有任何疑问
  • 将语雀文档迁移到飞书

    前言 我爬虫课程的文字版内容沉淀在语雀的知识库中 一开始感觉很不错 随着课程一直在卖 很快就超过了200人的限制 我已经是个人版中最高级的会员了 但语雀知识库的协作人数依旧限制在200人 即花钱无法解决问题 先说一下我的需求 我需要一个可以
  • 深度学习之实现图像数据增强

    深度学习之实现图像数据增强 前言 数据增强的意思就是让数据量增多 对于深度学习来说 大的数据量可以训练出更好的深度学习模型 在图像增强方面 我们常用的手段如下 旋转 翻转 缩放 平移 尺度变换 对比度变换 噪声扰动 颜色变换 1 使用ten
  • What the f*ck Python!(中文翻译版)

    What the f ck Python From https github com leisurelicht wtfpython cn 一些有趣且鲜为人知的 Python 特性 Python 是一个设计优美的解释型高级语言 它提供了很多能
  • SPFA 算法模板

    SPFA 代替 Dijkstra 计算最短路 题目 题目链接 题解 SPFA 一般时间复杂度为 O m O m O m 最坏情况下为 O
  • CNN人脸识别项目(dlib+opencv)

    CNN人脸识别 获取数据集 读取数据集 数据预处理 建立模型进行预训练 对图片进行人脸进行检测并进行可视化 总结 思路 一个CNN人脸识别项目首先必不可少的是数据集 获取的方式有网站数据库 PubFig Public Figures Fac
  • ORACLE查询删除重复记录三种方法

    比如现在有一人员表 表名 peosons 若想将姓名 身份证号 住址这三个字段完全相同的记录查询出来 复制代码代码如下 select p1 from persons p1 persons p2 where p1 id lt gt p2 id
  • 如何实现Java的JDBC编程(基本概念,驱动包安装)

    Java的JDBC编程 这里写目录标题 Java的JDBC编程 1 概念 2 安装驱动包 3 实现JDBC编程 1 概念 何为 JDBC编程 就是通过Java代码来操作数据库 咱们学数据库 无论是命令行 还是图形化界面 都不是开发中的主流使
  • 测试工作中一定要学会做业务总结

    前言 在日常的测试工作中 不知道大家是否会有梳理自己测试业务的习惯 我个人觉得这个事情是值得做的 最好培养成一个习惯 另外一定要依托于业务价值来实现自己的变现 梳理被测业务 就是一种很好的自我提升方式 测试工作中一定要学会业务总结 一 为什
  • JavaWeb之xml学习笔记一(约束初步使用)

    html和xml的不同点 元素和属性 html中的元素和属性都是预先设定的 而xml中没有预想设定的元素和属性 xml是可扩张的标记语言 标记可以用户自定义元素和属性 使用目的 html使用来显示界面的 注重的是外观和功能 而xml是用来传
  • Feign远程调用 请求头丢失问题及解决方案

    问题描述 在微服务项目中 通过Feign远程调用另一个模块的接口 由于请求头没传过去导致接口调用失败 解决办法 feign调用远程接口时 会扫描所有的拦截器 执行apply方法 我们可以创建一个拦截器放到spring容器中 在拦截器里把原来
  • module ‘scipy.misc‘ has no attribute ‘imresize‘

    报错 AttributeError module scipy misc has no attribute imresize 解决方法 使用skimage库 conda install scikit image 注意 scipy misc i
  • 第十六章PHY -基于Linux3.10

    下载地址 http download csdn net detail shichaog 8620701 16 1 PHY 本章和OSI模型中的物理层和数据链路层关系密切 在嵌入式SOC上 通常集成有ARM核和MAC控制器 以及增加数据传输带