1. 概述
PHY芯片為OSI的最底層-物理層(Physical Layer),通過MII/GMII/RMII/SGMII/XGMII等多種媒體獨立接口(介質無關接口)與數據鏈路層的MAC芯片相連,并通過MDIO接口實現對PHY狀態的監控、配置和管理。
PHY與MAC整體的大致連接框架如下(圖片來源于網絡):
PHY的整個硬件系統組成比較復雜,PHY與MAC相連(也可以通過一個中間設備相連),MAC與CPU相連(有集成在內部的,也有外接的方式)。
PHY與MAC通過MII和MDIO/MDC相連,MII是走網絡數據的,MDIO/MDC是用來與PHY的寄存器通訊的,對PHY進行配置。
PHY的驅動與I2C/SPI的驅動一樣,分為控制器驅動和設備器驅動。本節先講控制器驅動。
2. PHY的控制器驅動總述
PHY的控制器驅動和SPI/I2C非常類似,控制器的核心功能是實現具體的讀寫功能。區別在于PHY的控制器讀寫功能的實現大致可以分為兩種方式():
直接調用CPU的MDIO控制器(直接調用cpu對應的寄存器)的方式;
通過GPIO/外圍soc模擬MDIO時序的方式;
PHY的控制器一般被描述為mdio_bus平臺設備(注意:這是一個設備,等同于SPI/I2C中的master設備;和總線、驅動、設備中的bus不是一個概念)。
既然是平臺設備,那么設備樹中必定要有可以被解析為平臺設備的節點,也要有對應的平臺設備驅動。與SPI驅動類似,PHY設備模型也是在控制器驅動的probe函數中注冊的。
3. 通過GPIO/外圍soc模擬MDIO時序的方式
3.1 控制器平臺設備在設備樹中的大致描述方式(不完全準確,主要描述匹配的規則)
#linux-4.9.225Documentationdevicetreeindingssocfslcpm_qe etwork.txt *MDIO Currentlydefinedcompatibles:fsl,pq1-fec-mdio(regissameasfirstresourceofFECdevice)fsl,cpm2-mdio-bitbang(regisportCregisters) Propertiesforfsl,cpm2-mdio-bitbang: fsl,mdio-pin:pinofportCcontrollingmdiodata fsl,mdc-pin:pinofportCcontrollingmdioclock Example:mdio@10d40{ compatible="fsl,mpc8272ads-mdio-bitbang", "fsl,mpc8272-mdio-bitbang", "fsl,cpm2-mdio-bitbang"; reg=<10d40?14>; #address-cells=<1>; #size-cells=<0>; fsl,mdio-pin=<12>; fsl,mdc-pin=<13>; #linux-4.9.225Documentationdevicetreeindingsphy xxx_phy:xxx-phy@xxx{//描述控制器下掛PHY設備的節點 reg=<0x0>;//PHY的地址 }; };
3.2 控制器平臺驅動代碼走讀
3.2.1 控制器平臺驅動的注冊
staticconststructof_device_idfs_enet_mdio_bb_match[]={
{
.compatible="fsl,cpm2-mdio-bitbang",//匹配平臺設備的名稱
},
{},
};
MODULE_DEVICE_TABLE(of,fs_enet_mdio_bb_match);
staticstructplatform_driverfs_enet_bb_mdio_driver={
.driver={
.name="fsl-bb-mdio",
.of_match_table=fs_enet_mdio_bb_match,
},
.probe=fs_enet_mdio_probe,
.remove=fs_enet_mdio_remove,
};
module_platform_driver(fs_enet_bb_mdio_driver);//注冊控制器平臺設備驅動
3.2.2 控制器平臺驅動的probe函數走讀
/********************************************************************************************** 通過GPIO/外圍soc模擬MDIO時序方式的MDIO驅動(probe函數中完成PHY設備的創建和注冊) ***********************************************************************************************/ #linux-4.9.225drivers etethernetfreescalefs_enetmii-bitbang.c fs_enet_mdio_probe(structplatform_device*ofdev) |---bitbang=kzalloc(sizeof(structbb_info),GFP_KERNEL) | |---bitbang->ctrl.ops=&bb_ops----------------------------------------------->|staticstructmdiobb_opsbb_ops={ ||.owner=THIS_MODULE, ||.set_mdc=mdc, ||.set_mdio_dir=mdio_dir, ||.set_mdio_data=mdio,|-->實現為GPIO的讀寫 ||.get_mdio_data=mdio_read, ||}; |<---------------------------------------------------------| |---?new_bus?=?alloc_mdio_bitbang(&bitbang->ctrl)| ||---bus=mdiobus_alloc()-----------||structmdiobb_ctrl*ctrl=bus->priv|| ||---bus->read=mdiobb_read-----------||ctrl->ops->set_mdc|| ||---bus->write=mdiobb_write-----------|--mdiobb_read/mdiobb_write/mdiobb_reset函數的實現-|ctrl->ops->set_mdio_dir|--| ||---bus->reset=mdiobb_reset-----------|/|ctrl->ops->set_mdio_data| ||---bus->priv=ctrl<----------------------------????????????????????????????????????????????|?ctrl->ops->get_mdio_data| | |---fs_mii_bitbang_init//設置用來模擬mdc和mdio的管腳資源 ||---of_address_to_resource(np,0,&res)//轉換設備樹地址并作為資源返回,設備樹中指定 || ||---snprintf(bus->id,MII_BUS_ID_SIZE,"%x",res.start)//把資源的起始地址設置為bus->id || ||---data=of_get_property(np,"fsl,mdio-pin",&len) ||---mdio_pin=*data//決定控制mdio數據的端口的引腳 || ||---data=of_get_property(np,"fsl,mdc-pin",&len) ||---mdc_pin=*data//控制mdio時鐘的端口引腳 || ||---bitbang->dir=ioremap(res.start,resource_size(&res)) || ||---bitbang->dat=bitbang->dir+4 ||---bitbang->mdio_msk=1<(31?-?mdio_pin)??????????????? |????|---?bitbang->mdc_msk=1<(31?-?mdc_pin) |???? |---?of_mdiobus_register(new_bus,?ofdev->dev.of_node)//注冊mii_bus設備,并通過設備樹子節點創建PHY設備<===of_mdiobus_register(struct?mii_bus?*mdio,?struct?device_node?*np) |????|---?mdio->phy_mask=~0//屏蔽所有PHY,防止自動探測。相反,設備樹中列出的phy將在總線注冊后填充 ||---mdio->dev.of_node=np ||---mdiobus_register(mdio)//@注意@注冊MDIO總線設備(注意是總線設備不是總線,因為總線也是一種設備。mdio_bus是在其他地方注冊的,后面會講到) |||---__mdiobus_register(bus,THIS_MODULE) ||||---bus->owner=owner ||||---bus->dev.parent=bus->parent ||||---bus->dev.class=&mdio_bus_class ||||---bus->dev.groups=NULL ||||---dev_set_name(&bus->dev,"%s",bus->id)//設置總線設備的名稱 ||||---device_register(&bus->dev)//注冊總線設備 || ||---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(addr0)????????????????????????????????//如果未獲得子節點的"reg"屬性,則在后面再啟用掃描可能存在的PHY的,然后注冊 |?????????|????|---?scanphys?=?true? |?????????|????|---?continue |?????????|? |?????????|---?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_device_create(mdio,?addr,?phy_id,?0,?NULL)??? |?????????|????|---else?//我這里采用的是else分支 |?????????|????|????|---phy?=?get_phy_device(mdio,?addr,?is_c45)????????//在@bus上的@addr處讀取PHY的ID寄存器,然后分配并返回表示它的phy_device |?????????|????|????????|---?get_phy_id(bus,?addr,?&phy_id,?is_c45,?&c45_ids)????????//通過mdio得到PHY的ID |?????????|????|????????|---?phy_device_create(bus,?addr,?phy_id,?is_c45,?&c45_ids)??//創建PHY設備 |?????????|????|?????????????|---?struct?phy_device?*dev |?????????|????|?????????????|---?struct?mdio_device?*mdiodev |?????????|????|?????????????|---?dev?=?kzalloc(sizeof(*dev),?GFP_KERNEL) |?????????|????|?????????????|---?mdiodev?=?&dev->mdio//mdiodev是最新的內核引入,較老的版本沒有這個結構 ||||---mdiodev->dev.release=phy_device_release ||||---mdiodev->dev.parent=&bus->dev ||||---mdiodev->dev.bus=&mdio_bus_type//PHY設備和驅動都會掛在mdio_bus下,匹配時會調用對應的match函數---| ||||---mdiodev->bus=bus| ||||---mdiodev->pm_ops=MDIO_BUS_PHY_PM_OPS| ||||---mdiodev->bus_match=phy_bus_match//真正實現PHY設備和驅動匹配的函數<--------------------------------| |?????????|????|?????????????|---?mdiodev->addr=addr ||||---mdiodev->flags=MDIO_DEVICE_FLAG_PHY ||||---mdiodev->device_free=phy_mdio_device_free ||||---diodev->device_remove=phy_mdio_device_remove ||||---dev->speed=SPEED_UNKNOWN ||||---dev->duplex=DUPLEX_UNKNOWN ||||---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->phy_id=phy_id ||||---if(c45_ids) |||||---dev->c45_ids=*c45_ids ||||---dev->irq=bus->irq[addr] ||||---dev_set_name(&mdiodev->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_PREFIXMDIO_ID_FMT,MDIO_ID_ARGS(phy_id))//加載內核模塊(這里沒有細致研究過) ||||---device_initialize(&mdiodev->dev)//設備模型中的一些設備,主要是kset、kobject、ktype的設置 ||| |||---irq_of_parse_and_map(child,0)//將中斷解析并映射到linuxvirq空間(未深入研究) |||---if(of_property_read_bool(child,"broken-turn-around"))//MDIO總線中的TA(Turnaroundtime) ||||---mdio->phy_ignore_ta_mask|=1<mdio.dev.of_node=child ||| |||---phy_device_register(phy)//注冊PHY設備 ||||---mdiobus_register_device(&phydev->mdio)//注冊到mdiodev->bus,其實筆者認為這是一個虛擬的注冊,僅僅是根據PHY的地址在mdiodev->bus->mdio_map數組對應位置填充這個mdiodev |||||---mdiodev->bus->mdio_map[mdiodev->addr]=mdiodev//方便通過mdiodev->bus統一管理和查找,以及關聯bus的讀寫函數,方便PHY的功能配置 |||| ||||---device_add(&phydev->mdio.dev)//注冊到linux設備模型框架中 || ||---if(!scanphys)//如果從子節點的"reg"屬性中獲得PHY設備的地址,scanphys=false,這里就直接返回了,因為不需要再掃描了 |||---return0 || /****************************************************************************************************************** 一般來說只要設備樹種指定了PHY設備的"reg"屬性,后面的流程可以自動忽略 ****************************************************************************************************************** ||---for_each_available_child_of_node(np,child)//自動掃描具有空"reg"屬性的PHY ||---if(of_find_property(child,"reg",NULL))//跳過具有reg屬性集的PHY |||---continue || ||---for(addr=0;addrdev,"scanphy%sataddress%i ",child->name,addr)//打印掃描的PHY,建議開發人員設置"reg"屬性 || ||---if(of_mdiobus_child_is_phy(child)) ||---of_mdiobus_register_phy(mdio,child,addr)//注冊PHY設備 | ******************************************************************************************************************/
4. 直接調用CPU的MDIO控制器的方式
4.1 控制器平臺設備在設備樹中的大致描述方式(不完全準確,主要描述匹配的規則)
#linux4.9.225Documentationdevicetreeindingspowerpcfslfman.txt
ExampleforFManv3internalMDIO:
mdio@e3120{//描述MDIO控制器驅動節點
compatible="fsl,fman-mdio";
reg=<0xe3120?0xee0>;
fsl,fman-internal-mdio;
tbi1:tbi-phy@8{//描述控制器下掛PHY設備的節點
reg=<0x8>;
device_type="tbi-phy";
};
};
4.2 控制器平臺驅動代碼走讀
4.2.1 控制器平臺驅動的注冊
#linux-4.9.225drivers etethernetfreescalefsl_pq_mdio.c staticconststructof_device_idfsl_pq_mdio_match[]={ ...... /*NoKconfigoptionforFmansupportyet*/ { .compatible="fsl,fman-mdio",//匹配平臺設備的名稱 .data=&(structfsl_pq_mdio_data){ .mii_offset=0, /*FmanTBIoperationsarehandledelsewhere*/ }, }, ...... {}, }; staticstructplatform_driverfsl_pq_mdio_driver={ .driver={ .name="fsl-pq_mdio", .of_match_table=fsl_pq_mdio_match, }, .probe=fsl_pq_mdio_probe, .remove=fsl_pq_mdio_remove, }; module_platform_driver(fsl_pq_mdio_driver);//注冊控制器平臺設備驅動
4.2.2 控制器平臺驅動的probe函數走讀
/**************************************************************************************** 直接調用CPU的MDIO控制器的方式的MDIO控制器驅動(probe函數中涉及PHY設備的創建和注冊) ****************************************************************************************/ #linux-4.9.225drivers etethernetfreescalefsl_pq_mdio.c fsl_pq_mdio_probe(structplatform_device*pdev |---structfsl_pq_mdio_priv*priv |---structmii_bus*new_bus | |---new_bus=mdiobus_alloc_size(sizeof(*priv))//分配結構體 |---priv=new_bus->priv |---new_bus->name="FreescalePowerQUICCMIIBus" |---new_bus->read=&fsl_pq_mdio_read//總線的讀接口 |---new_bus->write=&fsl_pq_mdio_write//總線的寫接口 |---new_bus->reset=&fsl_pq_mdio_reset//總線的復位接口 | |---of_address_to_resource(np,0,&res)//獲取控制器地址資源 |---snprintf(bus->id,MII_BUS_ID_SIZE,"%x",res.start)//把資源的起始地址設置為bus->id | |---of_mdiobus_register(new_bus,np)//注冊mii_bus設備,并通過設備樹中控制器的子節點創建PHY設備,這一點與模擬方式流程相同
of_mdiobus_register的流程與第四小節一致,這里就不再列出。
5. 控制器的讀寫會在哪里得到調用?
在PHY設備的注冊中(讀PHY ID)、PHY的初始化、自協商、中斷、狀態、能力獲取等流程中經常可以看到phy_read和phy_write兩個函數(下一節要講的PHY驅動),這兩個函數的實現就依賴于控制器設備mii_bus的讀寫。
phy_read和phy_write定義在linux-4.9.225includelinuxphy.h中,如下:
staticinlineintphy_read(structphy_device*phydev,u32regnum)
{
returnmdiobus_read(phydev->mdio.bus,phydev->mdio.addr,regnum);
}
staticinlineintphy_write(structphy_device*phydev,u32regnum,u16val)
{
returnmdiobus_write(phydev->mdio.bus,phydev->mdio.addr,regnum,val);
}
其中mdiobus_read和mdiobus_write定義在linux-4.9.225drivers etphymdio_bus.c中,如下:
/** *mdiobus_read-ConveniencefunctionforreadingagivenMIImgmtregister *@bus:themii_busstruct *@addr:thephyaddress *@regnum:registernumbertoread * *NOTE:MUSTNOTbecalledfrominterruptcontext, *becausethebusread/writefunctionsmaywaitforaninterrupt *toconcludetheoperation. */ intmdiobus_read(structmii_bus*bus,intaddr,u32regnum) { intretval; BUG_ON(in_interrupt()); mutex_lock(&bus->mdio_lock); retval=bus->read(bus,addr,regnum); mutex_unlock(&bus->mdio_lock); returnretval; } /** *mdiobus_write-ConveniencefunctionforwritingagivenMIImgmtregister *@bus:themii_busstruct *@addr:thephyaddress *@regnum:registernumbertowrite *@val:valuetowriteto@regnum * *NOTE:MUSTNOTbecalledfrominterruptcontext, *becausethebusread/writefunctionsmaywaitforaninterrupt *toconcludetheoperation. */ intmdiobus_write(structmii_bus*bus,intaddr,u32regnum,u16val) { interr; BUG_ON(in_interrupt()); mutex_lock(&bus->mdio_lock); err=bus->write(bus,addr,regnum,val); mutex_unlock(&bus->mdio_lock); returnerr; }
可以清楚的看到bus->read和bus->write讀寫接口在這里得到調用。
6. mdio_bus總線
接下來要講的PHY設備驅動是基于device、driver、bus的連接方式。其驅動涉及如下幾個重要部分:
總線 - sturct mii_bus (mii stand for media independent interface)
設備 - struct phy_device
驅動 - struct phy_driver
關于PHY設備的創建和注冊已經在第5節的probe函數中有過詳細的描述(需要注意的是:phy設備不像i2c/spi有一個board_info函數進行設備的添加,而是直接讀取phy中的寄存器<根據IEEE的規定,PHY芯片的前16個寄存器的內容必須是固定的>),本節就不再描述;
6.1 總線注冊的入口函數
#linux-4.9.225drivers
etphyphy_device.c
staticint__initphy_init(void)
{
intrc;
rc=mdio_bus_init();//mdio_bus總線的注冊
if(rc)
returnrc;
rc=phy_drivers_register(genphy_driver,ARRAY_SIZE(genphy_driver),THIS_MODULE);//通用PHY驅動
if(rc)
mdio_bus_exit();
returnrc;
}
subsys_initcall(phy_init);
subsys_initcall(phy_init) 這行的作用非常重要,這一行就決定了內核在啟動的時候會調用該函數,注冊完了之后緊接著又注冊一個通用的PHY驅動。
6.2 總線注冊函數--- mdio_bus_init解析
#linux-4.9.225drivers
etphymdio_bus.c
staticstructclassmdio_bus_class={
.name="mdio_bus",
.dev_release=mdiobus_release,
};
staticintmdio_bus_match(structdevice*dev,structdevice_driver*drv)
{
structmdio_device*mdio=to_mdio_device(dev);
if(of_driver_match_device(dev,drv))
return1;
if(mdio->bus_match)
returnmdio->bus_match(dev,drv);
return0;
}
structbus_typemdio_bus_type={
.name="mdio_bus",//總線名稱
.match=mdio_bus_match,//用來匹配總線上設備和驅動的函數
.pm=MDIO_BUS_PM_OPS,
};
EXPORT_SYMBOL(mdio_bus_type);
int__initmdio_bus_init(void)
{
intret;
ret=class_register(&mdio_bus_class);//注冊設備類(在linux設備模型中,我再仔細講這個類的概念)
if(!ret){
ret=bus_register(&mdio_bus_type);//總線注冊
if(ret)
class_unregister(&mdio_bus_class);
}
returnret;
}
其中(1) class_register(&mdio_bus_class)執行后會有以下設備類:
/sys/class/mdio_bus
(2)bus_register(&mdio_bus_type)執行后會有以下總線類型:
/sys/bus/mdio_bus
6.3 總線中的match函數解析
/** *mdio_bus_match-determineifgivenMDIOdriversupportsthegiven *MDIOdevice *@dev:targetMDIOdevice *@drv:givenMDIOdriver * *Description:GivenaMDIOdevice,andaMDIOdriver,return1if *thedriversupportsthedevice.Otherwise,return0.Thismay *requirecallingthedevicesownmatchfunction,sincedifferentclasses *ofMDIOdeviceshavedifferentmatchcriteria. */ staticintmdio_bus_match(structdevice*dev,structdevice_driver*drv) { structmdio_device*mdio=to_mdio_device(dev); if(of_driver_match_device(dev,drv)) return1; if(mdio->bus_match)//實現匹配的函數 returnmdio->bus_match(dev,drv); return0; }
7. 設備驅動的注冊
在phy_init函數中不僅注冊了mdio_bus總線,還注冊了一個通用的PHY驅動作為缺省的內核PHY驅動,但是如果PHY芯片的內部寄存器和802.3定義的并不一樣或者需要特殊的功能配置以實現更強的功能,這就需要專有的驅動。
關于通用PHY驅動的知識,網上有一大堆講解,本節就不再重復的去描述。
對于市場上存在的主流PHY品牌,一般在內核源碼 drivers etphy目錄下都有對應的驅動。本節主要以realtek RTL8211F為例,講述PHY的驅動,代碼如下:
#linux-4.9.225drivers
etphy
ealtek.c
staticstructphy_driverrealtek_drvs[]={
......
,{
.phy_id=0x001cc916,
.name="RTL8211FGigabitEthernet",
.phy_id_mask=0x001fffff,
.features=PHY_GBIT_FEATURES,
.flags=PHY_HAS_INTERRUPT,
.config_aneg=&genphy_config_aneg,
.config_init=&rtl8211f_config_init,
.read_status=&genphy_read_status,
.ack_interrupt=&rtl8211f_ack_interrupt,
.config_intr=&rtl8211f_config_intr,
.suspend=genphy_suspend,
.resume=genphy_resume,
},
};
module_phy_driver(realtek_drvs);//注冊PHY驅動
staticstructmdio_device_id__maybe_unusedrealtek_tbl[]={
{0x001cc912,0x001fffff},
{0x001cc914,0x001fffff},
{0x001cc915,0x001fffff},
{0x001cc916,0x001fffff},
{}
};
MODULE_DEVICE_TABLE(mdio,realtek_tbl);
7.1 phy驅動的注冊
(1)同一品牌的PHY設備有多種不同的型號,內核為了支持一次可以注冊多個型號的PHY的驅動,在includelinuxphy.h中提供了用于注冊PHY驅動的宏module_phy_driver。該宏的定義如下:
#linux-4.9.225includelinuxphy.h
#definephy_module_driver(__phy_drivers,__count)
staticint__initphy_module_init(void)
{
returnphy_drivers_register(__phy_drivers,__count,THIS_MODULE);
}
#definemodule_phy_driver(__phy_drivers)
phy_module_driver(__phy_drivers,ARRAY_SIZE(__phy_drivers))
(2)其中phy_driver_register定義如下(注意這里與老版本內核有一定的改動)
/**
*phy_driver_register-registeraphy_driverwiththePHYlayer
*@new_driver:newphy_drivertoregister
*@owner:moduleowningthisPHY
*/
intphy_driver_register(structphy_driver*new_driver,structmodule*owner)
{
intretval;
new_driver->mdiodrv.flags|=MDIO_DEVICE_IS_PHY;
new_driver->mdiodrv.driver.name=new_driver->name;//驅動名稱
new_driver->mdiodrv.driver.bus=&mdio_bus_type;//驅動掛載的總線
new_driver->mdiodrv.driver.probe=phy_probe;//PHY設備和驅動匹配后調用的probe函數
new_driver->mdiodrv.driver.remove=phy_remove;
new_driver->mdiodrv.driver.owner=owner;
retval=driver_register(&new_driver->mdiodrv.driver);//向linux設備模型框架中注冊device_driver驅動
if(retval){
pr_err("%s:Error%dinregisteringdriver
",
new_driver->name,retval);
returnretval;
}
pr_debug("%s:Registerednewdriver
",new_driver->name);
return0;
}
intphy_drivers_register(structphy_driver*new_driver,intn,
structmodule*owner)
{
inti,ret=0;
for(i=0;i0)
phy_driver_unregister(new_driver+i);
break;
}
}
returnret;
}
7.2 MODULE_DEVICE_TABLE宏的作用
7.2.1 C語言宏定義##連接符和#符的使用
1 . ## 連接符號"##" 連接符號其功能是在帶參數的宏定義中將兩個子串(token)聯接起來,從而形成一個新的子串。但它不可以是第一個或者最后一個子串。所謂的子串(token)就是指編譯器能夠識別的最小語法單元。
簡單的說,“##”是一種分隔連接方式,它的作用是先分隔,然后進行強制連接。其中,分隔的作用類似于空格。
我們知道在普通的宏定義中,預處理器一般把空格解釋成分段標志,并把分隔后的每一段和前面的定義比較,相同的就被替換。
如果采用空格來分隔,被替換后段與段之間存在一些空格。如果我們不希望出現這些空格,就可以通過添加一些 “##”來替代空格。例如:
#defineexample(name,type)name_##type##_type
"name"和第一個 *之間,以及第2個*和第二個 "type" 之間沒有被分隔,所以預處理器會把name_##type##*type解釋成3段:"name*"、"type"、以及"_type",其中只有"type"是在宏前面出現過的,所以它可以被宏替換。
2 . # 符號單獨的一個 "#" 則表示: 替換這個變量后,再加雙引號引起來。例如,宏定義 __stringify_1(x) :
#linux-4.9.225includelinuxstringify.h #define__stringify_1(x)#x
那么 __stringify_1(realtek_tbl) <=等價于=> ”realtek_tbl"
7.2.2 alias函數
alias定義的函數將作為另一個函數的別名。gcc官方的說明部分內容如下:5.24 Declaring Attributes of Functions:
alias (“target”)The alias attribute causes the declaration to be emitted as an alias for another symbol, which must be specified. For instance,
void__f(){/*Dosomething.*/;}
voidf()__attribute__((weak,alias("__f")));
declares f' to be a weak alias for__f'. In C++, the mangled name for the target must be used. It is an error if `__f' is not defined in the same translation unit.
7.2.3 指定變量的屬性 - - - unused的用法
unused 表示該函數或變量可能不使用,這個屬性可以避免編譯器產生警告信息。在gcc官方的說明部分內容如下:
5.31 Specifying Attributes of Variables:unusedThis attribute, attached to a variable, means that the variable is meant to be possibly unused. GCC will not produce a warning for this variable.
7.2.4 MODULE_DEVICE_TABLE解析
MODULE_DEVICE_TABLE宏定義在 /include/linux/module.h中,如下:
/*Createsanaliassofile2alias.ccanfinddevicetable.*/ #defineMODULE_DEVICE_TABLE(type,name) externconsttypeof(name)__mod_##type##__##name##_device_table __attribute__((unused,alias(__stringify(name))))
根據代碼把這個宏展開之后會發現:生成了一個 _mod_type__name_device_table 的符號表,其中type為類型,name是這個驅動的名稱。在內核編譯的時候將這部分符號單獨放置在一個區域。
當內核運行的時,用戶可以通過類型(tpye)和類型對應的設備表中名稱(name)中動態的加載驅動,在表中查找到了這個符號之后可以迅速的加載驅動。
MODULE_DEVICE_TABLE的第一個參數是設備的類型,如果是PHY設備,那自然是MDIO(如果是PCI設備,那將是pci)。后面一個參數是設備表,這個設備表的最后一個元素是空的,用于標識結束。
7.2.5 MODULE_DEVICE_TABLE(mdio, realtek_tbl)解析(待驗證,后續再來修改)
1. 定義
/**
*structmdio_device_id-identifiesPHYdevicesonanMDIO/MIIbus
*@phy_id:Theresultof
*(mdio_read(&MII_PHYSID1)<16?|?mdio_read(&PHYSID2))?&?@phy_id_mask
?*?????for?this?PHY?type
?*?@phy_id_mask:?Defines?the?significant?bits?of?@phy_id.??A?value?of?0
?*?????is?used?to?terminate?an?array?of?struct?mdio_device_id.
?*/
struct?mdio_device_id?{
?__u32?phy_id;
?__u32?phy_id_mask;
};
?
static?struct?mdio_device_id?__maybe_unused?realtek_tbl[]?=?{
?{?0x001cc912,?0x001fffff?},
?{?0x001cc914,?0x001fffff?},
?{?0x001cc915,?0x001fffff?},
?{?0x001cc916,?0x001fffff?},
?{?}
};
?
MODULE_DEVICE_TABLE(mdio,?realtek_tbl);
2 . 展開:
#defineMODULE_DEVICE_TABLE(mdio,realtek_tbl) externconststructmdio_device_id__mod_mdio__realtek_tbl_device_table __attribute__((unused,"realtek_tbl")))
生成一個名為__mod_mdio__realtek_tbl_device_table,內核構建時,depmod程序會在所有模塊中搜索符號__mod_mdio__realtek_tbl_device_table,把數據(設備列表)從模塊中抽出,添加到映射文件 /lib/modules/KERNEL_VERSION/modules.mdiomap 中,當depmod結束之后,所有的MDIO設備連同他們的模塊名字都被該文件列出。在需要驅動的時候,由modules.mdiomap 文件來找尋恰當的驅動程序。
8. 設備驅動與控制器驅動之間的關系圖

審核編輯:湯梓紅
-
控制器
+關注
關注
114文章
17787瀏覽量
193086 -
以太網
+關注
關注
41文章
5997瀏覽量
180800 -
Mac
+關注
關注
0文章
1127瀏覽量
55309 -
PHY
+關注
關注
2文章
334瀏覽量
54077 -
GPIO
+關注
關注
16文章
1328瀏覽量
56218
原文標題:【網絡驅動】以太網掃盲(三)PHY的控制器驅動框架分析
文章出處:【微信號:嵌入式與Linux那些事,微信公眾號:嵌入式與Linux那些事】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
基于Xilinx FPGA的千兆以太網控制器的開發
以太網控制器(MAC)的基本框架怎么搭建
以太網控制器外部PHY芯片模擬程序代碼實現
以太網MAC芯片與PHY芯片的關系是什么
以太網控制器芯片的設計及實現
以太網接口的數據采集控制器
Microchip以太網開關和EtherCAT工業控制器及MAC PHY控制設計解決方案
使用C2000 EtherCAT從站控制器的SMI進行以太網PHY配置
AN4754-將Microchip橋接控制器與外部以太網PHY搭配使用
以太網PHY的控制器驅動框架分析
評論