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(addr< 0) //如果未获得子节点的"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<< addr | | | | | |--- of_node_get(child)//将OF节点与设备结构相关联,以便以后查找 | | |--- phy->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;addr< PHY_MAX_ADDR; addr++) //循环遍历扫描 | |--- if (mdiobus_is_registered_device(mdio, addr)) //跳过已注册的PHY | | |--- continue | | | |--- dev_info(&mdio->dev,"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;i< n; i++) { ret = phy_driver_register(new_driver + i, owner);//注册数组中所有的phy驱动 if (ret) { while (i-- >0) 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. 设备驱动与控制器驱动之间的关系图
审核编辑:汤梓红
-
控制器
+关注
关注
112文章
16332浏览量
177800 -
以太网
+关注
关注
40文章
5419浏览量
171592 -
Mac
+关注
关注
0文章
1104浏览量
51458 -
PHY
+关注
关注
2文章
301浏览量
51732 -
GPIO
+关注
关注
16文章
1204浏览量
52051
原文标题:【网络驱动】以太网扫盲(三)PHY的控制器驱动框架分析
文章出处:【微信号:嵌入式与Linux那些事,微信公众号:嵌入式与Linux那些事】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论