1. 程式人生 > >第十六章PHY -基於Linux3.10

第十六章PHY -基於Linux3.10

單獨一個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通知鏈。