uboot移植与源码分析总结(4)-以太网驱动

转载:http://www.eefocus.com/lishutong/blog/13-06/295363_c502a.html


虽然此文名为移植,但实际的工作量很小,只需简单修改即可完成tiny210板上的以太网驱动程序移植。

tiny210开发板上板载DM9000网络芯片,集成了网络控制器和PHY,外接一个网络变压器。uboot源码目录driver/net提供了驱动程序dm9000x.c/dm9000x.h。由于DM9000通过SRAM接口挂接在s5pv210的SROM端口1上,16位总线模式;所以从思路上来说,只需要将dm9000驱动程序加入Uboot编译中,并且在uboot初始化时打开SROM端口1,并注册dm9000驱动程序,s5pv210即可访问dm9000内部寄存器。

以太网设备抽像结构及其注册

uboot对以太网设备的抽像和对串口设备的抽像方法很类似(串口设备抽像见UBoot的Serial驱动分析),每个以太网设备用struct eth_device结构表示。struct eth_device定义如下:

struct eth_device { 
    char name[16]; 
    unsigned char enetaddr[6]; 
    int iobase; 
    int state;

    int  (*init) (struct eth_device *, bd_t *); 
    int  (*send) (struct eth_device *, void *packet, int length); 
    int  (*recv) (struct eth_device *); 
    void (*halt) (struct eth_device *); 
#ifdef CONFIG_MCAST_TFTP 
    int (*mcast) (struct eth_device *, u32 ip, u8 set); 
#endif 
    int  (*write_hwaddr) (struct eth_device *); 
    struct eth_device *next; 
    int index; 
    void *priv; 
};

如果要支持多个以太网设备,则需定义多个struct eth_device,然后通过调用eth_register(device)向uboot注册该结构。所有注册的设备被加入到eth_devices所指向的链表中,eth_current指向当前的设备,这种组织方式和serial驱动类似。

static struct eth_device *eth_devices;     // 位于net/eth.c 
struct eth_device *eth_current;

DM9000x.c中包含完成了定义结构和注册工作的初始化函数,我们只需要调用:

int dm9000_initialize(bd_t *bis) 

    struct eth_device *dev = &(dm9000_info.netdev);

    /* Load MAC address from EEPROM */ 
    dm9000_get_enetaddr(dev);

    dev->init = dm9000_init; 
    dev->halt = dm9000_halt; 
    dev->send = dm9000_send; 
    dev->recv = dm9000_rx; 
    sprintf(dev->name, "dm9000");

    eth_register(dev);

    return 0; 
}

由于uboot的工作机制比较简单,不需要过于考虑效率,因而DM9000的驱动函数实现也比较简单。主要完成初始化、停止、发送帧、接收帧等操作。

s5pv210上DM9000驱动程序移植

移植步骤很简单,除了将DM9000x.c加入到uboot源码编译中外,还需要在某个地方初始化DM9000所在的SROM端口1以及调用dm9000_initialize()。

我认为这些初始化调用不能随便加入到源码中的某个位置,uboot应该有它自己的流程。所以,分析了下net/eth.c源码,因为这部分与硬件最为接近,发现int eth_initialize(bd_t *bis)完成整个以太网驱动硬件底层初始化。而其中就有调用board_eth_init(),从该函数的字面意思来看是完成与目标板相关的以太网驱动初始化。而该函数的缺省实现是直接返回错误,即初始化失败。

static int __def_eth_init(bd_t *bis) 

    return -1; 
}

int cpu_eth_init(bd_t *bis) __attribute__((weak, alias("__def_eth_init"))); 
int board_eth_init(bd_t *bis) __attribute__((weak, alias("__def_eth_init")));

依照uboot的源码风格,board_eth_init是可以被重向定义的。于是我重新定义了该函数,并将相关的初始化操作放在里面。

int board_eth_init(bd_t *bis) 

    int ret = 0;

    SROM_BW_REG &= ~(0xf << 4); 
    SROM_BW_REG |= (0<<7) | (0<<6) | (1<<5) | (1<<4);  // SROM_ADDR[22:0] <= HADDR[23:1],16位总线 
    SROM_BC1_REG = ((0<<28)|(1<<24)|(5<<16)|(1<<12)|(4<<8)|(6<<4)|(0<<0)); 
    ret = dm9000_initialize(bis); 
    return ret; 
}

通过上面的两步,即完成了以太网驱动程序的移植。

UDP/IP协议栈概述

UBoot提供了一个简单的网络协议栈,所有源码位于net目录下。实现的协议有ARP、IP、UDP、ICMP、bootp、nfs、rarp、tftp,没有实现tcp协议。所以说,这个协议只能称作是UDP/IP协议栈,而不能称作TCP/IP协议栈。

类似的协议栈我在大学里实现过一个,当时能实现ping操作、tftp文件传输,实现比较简单,跟uboot的实现类似。所以,这里不做详细分析。

一般而言,在初始完成移植后,常使用ping来完成基本通信测试。ping命令基于IP/ICMP协议,首先会向目标IP地址的主机发送一个ICMP数据包,目标主机收到响应后会将该数据包原样返回,如果uboot接收到该数据包,即认为和主机已经连通。

如果不能ping通,有可能是驱动程序根本就没有把ICMP数据包发出去,或者是发送了错误的数据包,也有可能是发出去了但主机没有收到或者没有发送响应。这里就需要借助以太网抓包工具来抓取网络上的整个数据流,才能进一步分析是何种原因。抓包工具推荐使用wireshark,软件截图如下:

uboot驱动程序移植的总结

 

移植完后,使用tftp传输文件,大概250KB/s,速度还行。这个驱动程序的移植看起来没有太多技术含量。源码都有,要做的只不过是修改后能在特定的目标板上运行。

移植本身并不复杂,uboot的doc目录下有相关的说明文档;外加源码开放,如有不理解的,可参考其它目标板的移植。这样通过参考、修改就可完成这个移植过程。

无论是移植何种驱动,移植的工作均可以分成两块,先是熟悉硬件、实现驱动接口函数;而后才是按照uboot的源码格式要求编写。前者需要通用的技术基础,跟任何bootloader、os都无关,取决于自己是否对硬件能有深入地理解;而后者只不过是适应一种软件框架。

所以,从我目前学习和理解uboot的目的出发,接下来的USB驱动、NAND驱动程序移植中,以熟悉uboot驱动框架为主,不深究硬件细节。


你可能感兴趣的:(uboot)