一、底层接口封装
假如要为图中设备编写驱动,首先要做什么?
我认为应该是对各个器件进行抽象,也就是把可能的各种操作封装成接口并把需要的数据封装成结构体。这样作有两个好处,一是可以不再考虑器件的实现细节,直接调用接口就可完成各种操作;而是通过对接口的测试,可以较早的完成对器件的验证。
以上图为例,包含两个器件,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的数据收发需要主动调用,没有采用中断或轮询方式。具体实现可参照NetLoop()