Linux下光PHY

编程入门 行业动态 更新时间:2024-10-26 02:29:21

<a href=https://www.elefans.com/category/jswz/34/1770067.html style=Linux下光PHY"/>

Linux下光PHY

参考:

下载的平台内核源码为: at91-sama5d27_som1

1 硬件环境

    A5处理器有一个GMAC控制器。要想实现以太网通信,需要加上一个PHY芯片与MAC连接。使用的PHY芯片是 KSZ8041FTL,光口PHY。该芯片的厂商为Micrel。
    内核:经过Atmel基于A5平台定制的4.9.0系内核;
    硬件:A5芯片,其中mac contorller是使用Cadence的IP核,phy芯片使用Micrel的KSZ8041FTL;

2 驱动

    事实上,我认为网卡驱动程序可以分为两层,一层专注于与数据的收发,另外一层则是MDIO,对PHY芯片的控制信息。
    MDIO驱动模型,套用了常规的总线驱动设备模型。此处的“总线”即指MDIO总线,“驱动”则是由PHY相关芯片的驱动程序注册,而“设备”则是由MacController完成触发。
    MDIO总线的定义在~/drivers/net/phy/mdio_bus.c


    驱动端的注册在调用int phy_driver_register(struct phy_driver *new_driver)函数时触发,该函数定义在~/drivers/net/phy/phy_device.c中。
    设备端的注册则在int phy_device_register(struct phy_device *phydev)函数完成,该函数定义在~/drviers/net/phy_device.c中。

     首先,~/drivers/net/phy 目录下内核提供了phy芯片访问通用的代码,包括了phy_device.c、phy.c和mirel.c通用代码,其中MDIO专注于维护总线,phy_device.c则是专注于为mac 和 phy芯片提供接口,而phy.c则是提供以USB为介质的接口函数。
    其次,关于mdio_bus.c,如果MACcontroller中有MDIO接口那么使用的是该文件,但如果没有MDIO接口,那么可能需要使用GPIO模拟,那么使用的是mdio-gpio.c。还有在net/目录下的mdio.c暂时没有发现什么用处。
    然后,对于phy芯片毫无疑问,是存储在~/drivrs/net/phy目录下,例如我们使用的是micrel phy芯片,所以编译进内核的是~/drivrs/net/phy/ micrel.c。
     最后,mac controller驱动,因为使用的是以太网协议,所有有关此协议的mac驱动都存放在 /drivers/net/ethernet/下,在net目录下还有一些其它网络协议,如PPP,则相关mac驱动存储在/drivers/net/ppp目录下。另外,由于我们使用的是cadence,所以对应的驱动代码都在~/drivers/net/ethernet/cadence中的macb.c文件。

源码追踪
    先放弃内核完成的部分(比如MDIO总线驱动),而从硬件模块角度出发。显然,有两个硬件MacController和PHY芯片。
如上所述遵循的是总线-设备-驱动模型,那么随意从设备或者驱动分析都是可以的(因为模型中,任何一端无论是设备还是驱动加载时,都会去另外一端寻找匹配的支持)。

    下面就先以Mac Controller端分析。
    MAC Controller芯片端的驱动是Cadence,是~/drivers/net/ethernet/cadence中的macb.c文件(蛛网使用的内核为macb_main.c)。分析驱动肯定从入口函数分析。

    1)设备树中patible属性触发了macb_probe的调用。


2)mac_probe函数,正如之前所述maccontroller的功能除了“向下”与phy芯片建立MDIO的控制数据的交换,还需要“向上”完成网卡的本职工作,即数据流的收发。因此mac_probe的代码也可以划分为两部分,在此只关注与PHY芯片的交互,MII协议(MDIO可以算是MII的一个组成部分),因为在该函数中并无mdio类似的关键词,但有mii的关键词,所以相信会在mii相关处理函数中完成了驱动挂接。

3)macb_mii_init函数,如上所述mdio是属于mii的一个组成部分,所以macb_mii_init函数中也分为两部分,mii相关的工作和MDIO相关的工作。显然需要继续追踪的是of_mdiobus_register函数,该函数应该会完成MDIO总线的注册。(由此可知,总线虽然定义了(mdio_bus.c),但是没有实例化,是在此提出注册完成初始化)

4)of_mdiobus_register函数,这里需要注意mii_bus中的phy_mask属性,该属性对应位如果置1,那么会屏蔽掉对该PHY地址的扫描工作。而且这里有两个需要继续追踪,一个是mdiobus_register函数,还有一个是of_mdiobus_register_phy函数。

5-1)mdiobus_register 函数,完成MDIO总线的初始化工作,同时将dev挂接到MDIO总线的设备端,此时dev并没有初始化。因为mac conroller会作为mdio总线的设备端,所以加载了驱动当然也需要挂接,只不过因为没有初始化所以不会执行初始化工作。
    另外在注册完MDIO总线时,会扫描总线上是否挂有phy设备,但是由于在4)中将mask全为1,所以会屏蔽掉扫描的操作。

5-2)of_mdiobus_register_phy 函数,根据设备树中相关的定义,ethernet节点下还挂接了phy节点,所以在4)中的扫描子节点时会发现0x1的phy节点,0x1即为phy addr。从而进入of_mdiobus_register_phy 函数。详细分析如下注释

设备树中的phy子节点:arch\arm\boot\dts\at91-sama5d27_som1.dtsi

6)get_phy_device 初始化并且构造phy结构体,将作为mdio总线的设备端加入到总线中。下面贴出了初始化代码,非常容易理解,在此就不多解释。需要注意的是,从此phy_device特性是phy_addr是0x1(因为在5)中是解析到了子节点且将reg参数作为addr去扫描的,而硬件上设定了phy芯片的addr就是0x1,所以是有回应的);phy_id这是ieee定义的必须在每个phy芯片中的addr为2和3的寄存器中定义phy_id标识phy芯片,所以在创建设备后的初始化中也会读取该ID(注意区分phy_id和addr区别后者是由硬件设计决定的);bus_type为刚初始化的&mdio_bus_type。


7)phy_device_register,6)中完成的是phy芯片的初始化工作,而此处则是挂接到mdio总线上。

   至此,完成了MDIO总线和设备端的初始化和注册工作。

    接下来分析phy芯片的注册挂接以及匹配的过程。根据硬件和2中所述,phy芯片完成MDIO总线的驱动端注册,且是在micrel.c(~/drivers/net/phy/micrel.c)中定义。但是没有module_init的初始化函数,而是定义了一堆如下的结构体。最后仔细分析,发现在于module_phy_driver宏定义。跟踪进去即可发现它完成了驱动端的注册,相对简单在此就不赘述。

    下面看micrel.c驱动源码:
    文件:linux-at91\drivers\net\phy\micrel.c
    module_phy_driver(ksphy_driver);
        —> phy_drivers_register
            —>phy_driver_register

/*** phy_driver_register - register a phy_driver with the PHY layer* @new_driver: new phy_driver to register* @owner: module owning this PHY*/
int phy_driver_register(struct phy_driver *new_driver, struct module *owner)
{int retval;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;  //当匹配成功回调用的函数new_driver->mdiodrv.driver.remove = phy_remove;new_driver->mdiodrv.driver.owner = owner; retval = driver_register(&new_driver->mdiodrv.driver);   //device_driver注册if (retval) {pr_err("%s: Error %d in registering driver\n",new_driver->name, retval);return retval;}pr_debug("%s: Registered new driver\n", new_driver->name);return 0;
}
EXPORT_SYMBOL(phy_driver_register);

 driver_register
    bus_add_driver
        driver_attach
            __driver_attach
                driver_match_device
                    drv->bus->match //也就是 mdio_bus_type 里面的 mdio_bus_match

/*** driver_register - register driver with bus* @drv: driver to register** We pass off most of the work to the bus_add_driver() call,* since most of the things we have to do deal with the bus* structures.*/
int driver_register(struct device_driver *drv)
{int ret;struct device_driver *other;BUG_ON(!drv->bus->p);if ((drv->bus->probe && drv->probe) ||(drv->bus->remove && drv->remove) ||(drv->bus->shutdown && drv->shutdown))printk(KERN_WARNING "Driver '%s' needs updating - please use ""bus_type methods\n", drv->name);other = driver_find(drv->name, drv->bus);if (other) {printk(KERN_ERR "Error: Driver '%s' is already registered, ""aborting...\n", drv->name);return -EBUSY;}ret = bus_add_driver(drv);if (ret)return ret;ret = driver_add_groups(drv, drv->groups);if (ret) {bus_remove_driver(drv);return ret;}kobject_uevent(&drv->p->kobj, KOBJ_ADD);return ret;
}
EXPORT_SYMBOL_GPL(driver_register);

    可以很清楚的发现,这是一个高度抽象化的框架,首先,内核定义了一系列需要用到的操作函数作为phy_driver的结构体成员,这样各phy芯片厂商可以根据自身芯片的特点去实现各成员函数。然后,phy_device.c负责调用各个接口函数的操作。
    换句话说,内核实现了ieee定义的mdio标准,完成MDIO的通信任务,而各大phy芯片厂商必然满足该标准,所以内核的代码可以复用所有的phy芯片,但这是流程化的操作,而具体访问的地址是多少,则由phy芯片厂商定义,所以内核定义的某些成员函数由芯片厂商作为驱动程序去实例化。这样,大大减轻了phy芯片驱动开发的工作量。
    最后,简单说下匹配的问题。匹配函数在mdio_bus.c中定义,显然我们定义的设备树只会进入最后的分支。从上述分析中可以知道在初始化phy_device中,已经从芯片中读取到了ID值是0x 00221510,所以和驱动(micrel.c)中定义phy_driver结构体匹配上,完成匹配工作。

3 单独看MDIO

    Linux的mdio主要是为了管理PHY芯片寄存器的,跟踪代码发现,它会创建PHY设备及进行一些初始化工作。
    文件路径:drivers\net\phy\mdio_bus.c
–> __mdiobus_register
    --> device_register
        --> mdiobus_scan
             --> get_phy_device
                 --> get_phy_id // 读寄存器
                     --> phy_device_create // 创建phy设备
                    ->INIT_DELAYED_WORK(&dev->state_queue,phy_state_machine); // !!!初始化状态机函数
        --> phy_device_register

    备注:MAC和MDIO都作为内核platform设备由platform虚拟总线来管理,需要将xxx_mdio_driver这个驱动注册到platform总线中,在注册的过程中,会调用probe方法申请一条MDIO 总线,然后调用mdiobus_register()注册,MDIO总线以后会用来挂接PHY设备。
    暂时没有找到Atmel-A5处理器的mdio驱动文件。。。
    下面是TI公司的MDIO驱动,可以看看他的过程:
    文件路径:drivers\net\ethernet\ti\davinci_mdio.c
    device_initcall(davinci_mdio_init);
        --> davinci_mdio_init
        -->platform_driver_register(&davinci_mdio_driver);

    davinci_mdio_probe
        --> mdiobus_register

 PHY驱动
    Linux内核有很多的PHY驱动,电梯项目的PHY芯片-LAN8720使用的是通用的驱动(驱动名:Generic PHY,代码路径:drivers/net/phy/phy_device.c),其它的驱动形式类似。本文主要看内核中支持的micrel.c驱动源码,其支持了PHY芯片KSZ8041(还不能确定是否支持FTL型号)。

通用PHY驱动phy_device.c,PHY注册,PHY被调用过程如下:

代码路径:drivers/net/phy/phy_device.c
phy_init
–> mdio_bus_init 注册mdio总线
    --> class_register(&mdio_bus_class);
    --> bus_register(&mdio_bus_type);
–> phy_driver_register(&genphy_driver);
 // 赋值总线、probe等
     --> driver_register

MAC驱动
文件:linux-at91\drivers\net\ethernet\cadence\macb_main.c

4 调试修改

设置光PHY支持光口模式:

linux-at91\drivers\net\phy\micrel.c, micrel系列phy芯片驱动中有使能光纤模式的相关代码。


通过阅读参考手册,发现需要在设备树中添加 “micrel,fiber-mode” 属性,来使能光纤模式。
首先参考Documents文档中设备树节点的添加方法,
linux-at91\Documentation\devicetree\bindings\net\phy.txt

必要属性:

更多推荐

Linux下光PHY

本文发布于:2024-02-25 20:07:34,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1700148.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:Linux   PHY

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!