uboot之eth实现过程

本文乃fireaxe原创,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,并注明原作者及原链接。内容可任意使用,但对因使用该内容引起的后果不做任何保证。
作者:[email protected]
博客:fireaxe.blog.chinaunix.net

一、底层接口封装

假如要为图中设备编写驱动,首先要做什么?

我认为应该是对各个器件进行抽象,也就是把可能的各种操作封装成接口并把需要的数据封装成结构体。这样作有两个好处,一是可以不再考虑器件的实现细节,直接调用接口就可完成各种操作;而是通过对接口的测试,可以较早的完成对器件的验证。

uboot之eth实现过程_第1张图片

以上图为例,包含两个器件,MAC与PHY。

PHY需要的接口也是读写寄存器的接口与数据寄存器基址。

char tsec_phy_read(phy_dev *phydev,  char *regAddr);

void tsec_phy_write(phy_dev *phydev,  char *regAddr, char value);

phy_regs = PHY_REG_BASE;

 

MAC需要的接口是读写寄存器的接口,需要的数据是寄存器基址。

char tsec_mac_read(mac_dev *macdev,  char *regAddr);

void tsec_mac_write(mac_dev *macdev,  char *regAddr, char value);

mac_regs = MAC_REG_BASE;

因为需要通过MIIM传送数据PHY,还需要一组操作MIIM的接口。当需要配置PHY的寄存器时,PHY寄存器读写接口会调用这组接口。

char tsec_mii_read(mac_dev * macdev,  char *regAddr);

void tsec_mii_write(mac_dev * macdev,  char *regAddr, char value);

 

二、功能抽象

我们已经有个所需的各种接口,剩下的就是对功能进行抽象。作为一个网口芯片,不考虑特殊配置,最常用的接口也就三四个。

int tsec_init(eth_device *dev);

void tsec_halt(eth_device *dev);

int tsec_send(eth_device *dev, void *pack, int len);

int tsec_recv(eth_device *dev, void *pack, int len);

tsec_init与tsec_halt需要对PHY与MAC的寄存器进行设置,完成所需配置,因此需要使用MAC与PHY的寄存器设置接口。tsec_read与tsec_write只需要把起始地址与长度通知MAC芯片,然后启动发送就可以了。因此只需MAC寄存器设置接口。

 

三、数据结构

现在,所需接口都有了,但还有一个问题,各层函数的第一个参数是什么?

这个参数与面向对象有关。假如是使用C++实现,我们的第一步就不再是设定接口,而是划分类,然后再为各个类实现接口。即使用C实现,也是可以C++的面向对象功能。(说白了C++也不过是从语言层面做了层封装,一个标准的C++类的成员函数,其实是第一个入参为this指针的C函数)

为了更清楚看到这一点,下面列一下这三个结构体的定义:

struct eth_device {

                int  (*init) (eth_device *dev);

                int  (*send) (eth_device *dev, void *pack, int len);

                int  (*recv) (eth_device *dev, void *pack, int len);

                void (*halt) (eth_device *dev);

                struct *phy_dev;

                struct *mac_dev;

};

struct phy_dev {

                regs = PHY_REG_BASE;

                int  (*read) (phy_dev *phydev,  char *regAddr);

                int  (*write) (phy_dev *phydev,  char *regAddr, char value);

                struct *mac_dev;

};

struct mac_dev {

                regs = PHY_REG_BASE;

                int  (*read) (mac_dev *macdev,  char *regAddr);

                int  (*write) (mac_dev *macdev,  char *regAddr, char value);

                int  (*mii_read) (mac_dev * macdev,  char *regAddr);

                int  (*mii_write) (mac_dev * macdev,  char *regAddr, char value);

};

 

四、数据结构初始化接口

一个设备上可能会有多个网口,我们为每个网口实例化一个eth_device结构体(或者叫对象),初始化时为它连接所需的mac与phy。之后既可以通过它实现各种操作。当然每个结构体都是需要初始化的,因此我们再创建一组初始化函数。

eth_int();

mac_init();

phy_init();

 

五、设备注册接口

关于多个网口问题。网口多了管理起来也麻烦,比如说交换机,可能有20口甚至40口之多。因此有必要建立一组数据结构来管理。常见的管理结构是链表,可以方便的查找与遍历。下面提供一组链表管理接口。

mac_register(mac_dev *macdev);

phy_register(phy_dev *phydev);

eth_register(eth_dev *ethdev);

 

五、回到uboot

整理完开发过程后,回到uboot的实现。把uboot的实现与上述进行对比,发现还是有些不同的。这没关系,因为在实际实现时,可能会根据需要进行一些简化与抽象,但并不影响整个结构。

有些差别的是PHY初始化这一部分。我所用的PHY芯片是Davicom的DM9161,uboot中存在着davicom.c 。它与phy.c的关系是,phy.c实现phy芯片公用部分,这个文件实现某个芯片,如DM9161的一些特殊操作,如状态查询、初始配置等。没关系,我们在phy_dev中增加对应接口即可。

下面是uboot的函数调用关系,

uboot之eth实现过程_第2张图片

Uboot的数据收发需要主动调用,没有采用中断或轮询方式。具体实现可参照NetLoop()

 

本文乃fireaxe原创,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,并注明原作者及原链接。内容可任意使用,但对因使用该内容引起的后果不做任何保证。
作者:[email protected]
博客:fireaxe.blog.chinaunix.net

你可能感兴趣的:(uboot)