概述
上圖來自 瑞昱半導體 (RealTek) 的 RTL8201F 系列網(wǎng)卡 PHY 芯片手冊。按OSI 7層網(wǎng)絡模型劃分,網(wǎng)卡PHY 芯片(圖中的RTL8201F)位于物理層,對應的軟件層就是本文討論的 PHY 驅(qū)動層;而 MAC 位于 數(shù)據(jù)鏈路層,也是通常軟件上所說的網(wǎng)卡驅(qū)動層,它不是本文的重點,不做展開。另外,可通過 MDIO 接口對 PHY 芯片進行配置(如PHY芯片寄存器讀寫),而 PHY 和 MAC 通過 MII/RMII 進行數(shù)據(jù)傳輸。
PHY芯片通過MII/GMII/RMII/SGMII/XGMII等多種媒體獨立接口(介質(zhì)無關接口)與數(shù)據(jù)鏈路層的MAC芯片相連,并通過MDIO接口實現(xiàn)對PHY 狀態(tài)的監(jiān)控、配置和管理。
PHY與MAC整體的連接框圖:
數(shù)據(jù)結(jié)構
每個 phy 芯片會創(chuàng)建一個 struct phy_device 類型的設備,對應的有 struct phy_driver 類型的驅(qū)動,這兩者實際上是掛載在 mdio_bus_type 總線上的,mac 會被注冊成 struct net_device。
phy_device
struct?phy_device?{
????struct?phy_driver?*drv;????????????????//?PHY設備驅(qū)動??
????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狀態(tài)
????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;??????????????????????????????//?私有數(shù)據(jù)??
????struct?work_struct?phy_queue;????????????//?PHY工作隊列??
????struct?delayed_work?state_queue;?????????//?PHY延時工作隊列??
????atomic_t?irq_disable;
????struct?mutex?lock;
????struct?net_device?*attached_dev;?????????//?網(wǎng)絡設備??
????void?(*adjust_link)(struct?net_device?*dev);
};
phy_driver
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
struct?mii_bus?{
????const?char?*name;?????????????//?總線名字
????char?id[MII_BUS_ID_SIZE];?????//?ID?MII_BUS_ID_SIZE=61
????void?*priv;???????????????????//?私有數(shù)據(jù)
????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;???????????????????????//?總線狀態(tài)
????struct?device?dev;?????????????//?設備文件
????struct?phy_device?*phy_map[PHY_MAX_ADDR];?//?PHY設備數(shù)組
????u32?phy_mask;
????int?*irq;???????????????????//?中斷
};
net_device
struct?net_device?{
????char??name[IFNAMSIZ];?????????/*?用于存放網(wǎng)絡設備的設備名稱?*/
????char??*ifalias;???????????????/*?網(wǎng)絡設備的別名?*/
????int???ifindex;????????????????/*?網(wǎng)絡設備的接口索引值,獨一無二的網(wǎng)絡設備標識符?*/
????struct?hlist_node??name_hlist;??/*?這個字段用于構建網(wǎng)絡設備名的哈希散列表,而struct?net中的name_hlist就指向每個哈希散列表的鏈表頭?*/
????struct?hlist_node??index_hlist;?/*?用于構建網(wǎng)絡設備的接口索引值哈希散列表,在struct?net中的index_hlist用于指向接口索引值哈希散列表的鏈表頭?*/
????struct?list_head???dev_list;????/*?用于將每一個網(wǎng)絡設備加入到一個網(wǎng)絡命名空間中的網(wǎng)絡設備雙鏈表中?*/
????unsigned?int???????flags;???????/*?網(wǎng)絡設備接口的標識符?*/
????unsigned?int???????priv_flags;??/*?網(wǎng)絡設備接口的標識符,但對用戶空間不可見;*/
????unsigned?short?????type;????????/*?接口硬件類型?*/
????unsigned?int???????mtu;?????????/*?網(wǎng)絡設備接口的最大傳輸單元?*/
????unsigned?short?????hard_header_len;???/*?硬件接口頭長度?*/
????unsigned?char??????*dev_addr;????/*?網(wǎng)絡設備接口的MAC地址?*/
????bool???????????uc_promisc;???????/*?網(wǎng)絡設備接口的單播模式?*/
????unsigned?int???????promiscuity;??/*?網(wǎng)絡設備接口的混雜模式?*/
????unsigned?int???????allmulti;?????/*?網(wǎng)絡設備接口的全組播模式?*/
????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;???????/*?網(wǎng)絡設備接口的數(shù)據(jù)包接收隊列?*/
????struct?netdev_queue?*_tx????????????/*?網(wǎng)絡設備接口的數(shù)據(jù)包發(fā)送隊列?*/
????unsigned?int????????num_tx_queues;??/*?TX隊列數(shù)?*/
????unsigned?int????????real_num_tx_queues;??/*?當前設備活動的TX隊列數(shù)?*/
????unsigned?long??????tx_queue_len;????/*?每個隊列允許的最大幀?*/
????unsigned?long??????state;???????????/*?網(wǎng)絡設備接口的狀態(tài)?*/
????struct?net_device_stats????stats;???/*?網(wǎng)絡設備接口的統(tǒng)計情況?*/
????possible_net_t?????????nd_net;??????/*?用于執(zhí)行網(wǎng)絡設備所在的命名空間?*/
};?
phy 設備的注冊
以網(wǎng)卡 Fec 為例,網(wǎng)卡驅(qū)動在初始化 fec_probe() 時遍歷 dts 的定義,創(chuàng)建相應 struct phy_device 類型的設備,主要步驟為:
- 注冊網(wǎng)絡設備 net_device申請隊列和 DMA申請 MDIO 總線創(chuàng)建并注冊 Phy 設備
fec_probe(struct?platform_device?*pdev)
????->?struct?device_node?*np?=?pdev->dev.of_node,?*phy_node;?//?獲取設備樹節(jié)點句柄,并創(chuàng)建一個phy的設備樹節(jié)點句柄
????->?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)??????????????????????????????????????//??獲取私有數(shù)據(jù)空間首地址
--------------------------------------------------------------------------------------------------------------------------
????->?of_parse_phandle(np,?"phy-handle",?0)??????????????//?從mac的設備樹節(jié)點中獲取phy子節(jié)點
????->?of_get_phy_mode(pdev->dev.of_node)?????????????????//?從設備樹節(jié)點中獲取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;???????//?總線的讀函數(shù)
????????->?fep->mii_bus->write?=?fec_enet_mdio_write;?????//?總線的寫函數(shù)
????????->?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節(jié)點句柄
????????->?of_mdiobus_register??????????//?注冊mii_bus設備,并通過設備樹子節(jié)點創(chuàng)建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);??????????//獲取創(chuàng)建phy設備
????????????????->err?=?phy_device_register(phydev);??????????//注冊phy設備
--------------------------------------------------------------------------------------------------------------------
????????????????->?for_each_available_child_of_node(np,?child)?{??????//?遍歷這個平臺設備的子節(jié)點并為每個phy注冊一個phy_device
????????????????->?addr?=?of_mdio_parse_addr(&mdio->dev,?child)???????//?從子節(jié)點的"reg"屬性中獲得PHY設備的地址?
????????????????->?of_property_read_u32(np,?"reg",?&addr)
????????????????->?if?(addr?<?0)?????scanphys?=?true;??????continue;??//?如果未獲得子節(jié)點的"reg"屬性,則在后面再啟用掃描可能存在的PHY的,然后注冊
????????????????->?of_mdiobus_register_phy(mdio,?child,?addr)?????}?????//?創(chuàng)建并注冊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)????//?創(chuàng)建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;??????//?默認支持自協(xié)商(自動使能)
????????????????????????????????????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設備和驅(qū)動都會掛在mdio_bus下,匹配時會調(diào)用對應的match函數(shù)??--???
????????????????????????/*----------------------------------------------------------------------------------------------------
????????????????????????????????????struct?bus_type?mdio_bus_type?=?{
????????????????????????????????????????.name???????=?"mdio_bus",????????????????//總線名稱
????????????????????????????????????????.match??????=?mdio_bus_match,????????????//用來匹配總線上設備和驅(qū)動的函數(shù)
????????????????????????????????????????.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設備和驅(qū)動程序尚未準備就緒,在PHY驅(qū)動的probe函數(shù)中會更改為READY
????????????????????????????????????->?INIT_DELAYED_WORK(&dev->state_queue,?phy_state_machine);??//PHY的狀態(tài)機(核心WORK)后續(xù)解析
????????????????????????????????????->?INIT_WORK(&dev->phy_queue,?phy_change);???//?由phy_interrupt?/?timer調(diào)度以處理PHY狀態(tài)的更改
????????????????????????????????????->??request_module(MDIO_MODULE_PREFIX?MDIO_ID_FMT,?MDIO_ID_ARGS(phy_id));????????????//?加載內(nèi)核模塊
????????????????????????????????????->?device_initialize(&dev->dev);?????//設備模型中的一些設備,主要是kset、kobject、ktype的設置
????????????????????????????????->?irq_of_parse_and_map(child,?0);??????//將中斷解析并映射到linux?virq空間(
????????????????????????????????->?of_node_get(child);?????????//將OF節(jié)點與設備結(jié)構相關聯(lián)
????????????????????????????????->?phy->dev.of_node?=?child;
????????????????????????????????->?phy_device_register(phy)????//?注冊phy設備
????????????????????????????????????->?if?(phydev->bus->phy_map[phydev->addr])???????//判斷PHY是否已經(jīng)注冊了??
????????????????????????????????????->?phydev->bus->phy_map[phydev->addr]?=?phydev;??//添加PHY到總線的phy_map里
????????????????????????????????????->?phy_scan_fixups(phydev);?????//執(zhí)行匹配的fixups??
????????????????????????????????????->?device_add(&phydev->dev);????//?注冊到linux設備模型框架中
????????????????????????????->??if?(!scanphys)???return?0;??????????//?如果從子節(jié)點的"reg"屬性中獲得PHY設備的地址,scanphys=false,這里就直接返回了,因為不需要再掃描了
------------一般來說只要設備樹種指定了PHY設備的"reg"屬性,后面的流程可以自動忽略?------------
????->?register_netdev(ndev)??????????????????//?向內(nèi)核注冊net_device
phy 驅(qū)動的注冊
genphy_driver 通用驅(qū)動
內(nèi)核中有 genphy_driver 通用驅(qū)動,以及專有驅(qū)動(這里以 NXP TJA 驅(qū)動為例),分別如下:
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,
};
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 的注冊過程如下:
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)
??????????????????????????/*?循環(huán)到注冊的?PHY?設備時?*/
??????????????????????????fn(dev,?data)?=?__driver_attach()
??????????????????????????????/*?匹配設備和驅(qū)動?*/
??????????????????????????????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);
??????????????????????????????/*?匹配到設備和驅(qū)動,加載驅(qū)動?*/
??????????????????????????????driver_probe_device(drv,?dev)
??????????????????????????????????really_probe(dev,?drv)
??????????????????????????????????????dev->driver?=?drv;?/*?綁定設備的驅(qū)動?*/
??????????????????????????????????????drv->probe(dev)?=?phy_probe
其中一個關鍵點是 mdio driver 的 probe 函數(shù)是一個通用函數(shù) phy_probe:
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驅(qū)動
????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)?{?????????????????//?判斷驅(qū)動有probe方式
????????err?=?phydev->drv->probe(phydev);?????/*?PHY?驅(qū)動的?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);?//讀取狀態(tài)寄存器來確定?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?設備已經(jīng)就緒?*/
out:
????/*?Re-assert?the?reset?signal?on?error?*/
????if?(err)
????????phy_device_reset(phydev,?1);
????return?err;
}
其中通用 phy 驅(qū)動會調(diào)用函數(shù) genphy_read_abilities 來讀取狀態(tài)寄存器來確定 phy 芯片的能力:
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 專有驅(qū)動
NXP TJA 驅(qū)動的 struct phy_driver 的注冊過程如下:
#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))
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);????//?注冊數(shù)組中所有的phy驅(qū)動
????????if?(ret)?{
????????????while?(i--?>?0)
????????????????phy_driver_unregister(new_driver?+?i);
????????????break;
????????}
????}
????return?ret;
}
EXPORT_SYMBOL(phy_drivers_register);
根據(jù)上面的分析,由于存在 phydev->drv->probe,所以會調(diào)用其注冊的函數(shù) tja11xx_probe。
網(wǎng)卡 fec 和 Phy 的協(xié)作
在 linux 內(nèi)核中,以太網(wǎng) mac 會被注冊成 struct net_device,phy 芯片會被注冊成 struct phy_device。 phy_device 的狀態(tài)怎么傳遞給 net_device,讓其在 link 狀態(tài)變化時做出對應的配置改變,這個任務就落在上述的 struct phylink 中介身上。
下面就以 fec 網(wǎng)口驅(qū)動為例,展示一下網(wǎng)卡 fec 和 phy 的協(xié)作過程。整個 phy 驅(qū)動的主要調(diào)用流程如下圖所示:
一個 phy 驅(qū)動的原理其實是非常簡單的,一般流程如下:
- 用輪詢/中斷的方式通過 mdio 總線讀取 phy 芯片的狀態(tài)。在 phy link 狀態(tài)變化的情況下,正確配置 mac 的狀態(tài)。(例如:根據(jù) phy 自協(xié)商的速率 10/100/1000M 把 mac 配置成對應速率)
phy 芯片狀態(tài)在 phy 設備注冊的時候已經(jīng)體現(xiàn),這里詳細講下如何在 phy link 狀態(tài)變化的情況下,正確配置 mac 的狀態(tài)。
void?phy_state_machine(struct?work_struct?*work)
{
?old_state?=?phydev->state;
????/*?(1)?狀態(tài)機主體?*/
?switch?(phydev->state)?{
????/*?(1.1)?在?PHY_DOWN/PHY_READY?狀態(tài)下不動作?*/
?case?PHY_DOWN:
?case?PHY_READY:
??break;
????
????/*?(1.2)?在?PHY_UP?狀態(tài)下,表明網(wǎng)口被?up?起來,需要啟動自協(xié)商并且查詢自協(xié)商后的?link?狀態(tài)
?????????????如果自協(xié)商結(jié)果是?link?up,進入?PHY_RUNNING?狀態(tài)
?????????????如果自協(xié)商結(jié)果是?link?down,進入?PHY_NOLINK?狀態(tài)
?????*/
?case?PHY_UP:
??needs_aneg?=?true;
??break;
????/*?(1.3)?在運行的過程中定時輪詢?link?狀態(tài)
?????????????如果?link?up,進入?PHY_RUNNING?狀態(tài)
?????????????如果?link?down,進入?PHY_NOLINK?狀態(tài)
?????*/
?case?PHY_NOLINK:
?case?PHY_RUNNING:
??err?=?phy_check_link_status(phydev);
??break;
?}
????/*?(2)?如果需要,啟動自協(xié)商配置?*/
?if?(needs_aneg)
??err?=?phy_start_aneg(phydev);
????/*?(3)?如果?phy?link?狀態(tài)有變化,通知給對應網(wǎng)口?netdev?*/
?if?(old_state?!=?phydev->state)?{
??phydev_dbg(phydev,?"PHY?state?change?%s?->?%sn",
??????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);
}
自協(xié)商配置
具體啟動 phy 自協(xié)商的代碼流程如下:
phy_state_machine()
`-|?phy_start_aneg()
???`-|?phy_config_aneg()
??????`-|?genphy_config_aneg()
?????????`-|?__genphy_config_aneg()
????????????`-|?genphy_setup_master_slave()?//?(1)?如果是千兆網(wǎng)口,配置其?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?自協(xié)商
??????????????????`-|?{
????????????????????|??phy_modify(phydev,?MII_BMCR,?BMCR_ISOLATE,???//?配置?mdio?0x00?寄存器
????????????????????|???????BMCR_ANENABLE?|?BMCR_ANRESTART);
????????????????????|?}
link 狀態(tài)讀取
phy link 狀態(tài)讀取的代碼流程如下:
phy_state_machine()
`-|?phy_check_link_status()
???`-|?phy_read_status()????//?(1)?讀取?link?狀態(tài)?
??????`-|?genphy_read_status()
?????????`-|?{
???????????|??genphy_update_link(phydev);???//?(1.1)?更新?link?狀態(tài)
???????????|??if?(phydev->autoneg?==?AUTONEG_ENABLE?&&?old_link?&&?phydev->link)?return?0;
???????????|??genphy_read_master_slave(phydev);?//?(1.2)?如果是千兆網(wǎng)口,更新本端和對端的?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)?自協(xié)商模式,解析?link?結(jié)果
???????????|??}?else?if?(phydev->autoneg?==?AUTONEG_DISABLE)?{
???????????|????genphy_read_status_fixed(phydev);?//?(1.4.2)?固定模式,解析?link?結(jié)果
???????????|??}
???????????|?}
?????|?if?(phydev->link?&&?phydev->state?!=?PHY_RUNNING)?{??//?(2)?link?狀態(tài)?change?事件:變成?link?up
?????|???phydev->state?=?PHY_RUNNING;
?????|???phy_link_up(phydev);???//?link?up?事件,通知給?phylink
?????|?}?else?if?(!phydev->link?&&?phydev->state?!=?PHY_NOLINK)?{??//?(3)?link?狀態(tài)?change?事件:變成?link?down
?????|???phydev->state?=?PHY_NOLINK;
?????|???phy_link_down(phydev);?//?link?down?事件,通知給?phylink
?????|?}
link 狀態(tài)通知
phy 的 link 狀態(tài)變化怎么通知給 netdev,并且讓 mac 做出相應的配置改變,這個是通過一個中介 phylink 來實現(xiàn)的。
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`??狀態(tài)更新給?`phylink`
????????|??pl->phy_state.duplex?=?phydev->duplex;
????????|??pl->phy_state.interface?=?phydev->interface;
????????|??pl->phy_state.link?=?up;
????????|??phylink_run_resolve(pl);?????//?(2)?通知?`phylink`?的輪詢?nèi)蝿諉?
????????|?}