网卡驱动移植

嵌入式Linux设备驱动开发之网卡驱动移植

《嵌入式Linux应用完全开发手册》第4篇第22章总结归纳

本章目标

  1. 了解Linux系统的网络栈结构
  2. 掌握移植网卡驱动程序的一般方法
  3. 掌握CS8900A、DM9000两类网卡驱动程序的移植

CS8900A网卡驱动程序移植

CS8900A网卡特性

CS8900A 是一款针对嵌入式应用的低成本局域以太网控制器。与其他以太网控制器不同,该款产品采用高集成度的设计,因此无需昂贵的外部元件。
CS8900A 包括片上RAM、10Bast-T发送和接收滤波器,以及一个有24mA驱动器的直接ISA-Bus接口。
除了高集成度,CS8900A 还具有众多性能特点,并可采用不同的配置。其独特的PacketPage架构可以自动适应网络流量模式和可用系统资源的变化。因此可以使系统的效率大大提高。
CS8900A 采用100引脚TQFP封装,是小型化及对成本敏感的以太网应用的理想选择。采用CS8900A,用户可以设计出完整的以太网电路。这些电路仅占用不到10cm²的板上空间。
CS8900A 特点如下:

  1. 单芯片的IEEE 802.3以太网解决方案
  2. 拥有完整的软件驱动程序
  3. 高效的PacketPage架构可以采用DMA从模式在I/O及存储空间运行
  4. 全双工操作
  5. 片上RAM缓冲器发送和接收架构
  6. 10Base-T端口和滤波器(极性检测及纠错)
  7. 10Base-2、10Base-5和10Base-F全部采用AUI端口
  8. 冲突自动再发送、填充及CRC(循环冗余校验)功能
  9. 可编程接收功能
  10. 流传输可降低CPU负荷
  11. DMA和片上存储器间的自动切换
  12. 可早期中断结构先置处理
  13. 自动抑制错误信息包
  14. EEPROM支持无跳线配置
  15. Boot PROM支持无盘系统
  16. 边界扫描和循环测试
  17. LED驱动器支持链接状态及局域网活动
  18. 待机及休眠模式
  19. 工作电压为3V~5V,满足商业及工业应用温度要求
  20. 5V最大功耗为120mA,5V典型功耗为90mA
  21. 采用100引脚无铅TQFP封装

CS8900A 网卡驱动程序修改

Linux系统网络架构概述

与串口驱动程序类似,网络驱动程序也分为多个层次,Linux系统网络栈的架构如图所示:
img not found
最上面的的是用户空间层,或称为应用层,它通常是一个语义层,能够理解要传输的数据。例如,超文本传输协议(HTTP)就负责传输服务器和客户机之间对Web内容的请求与响应,电子邮件协议SMTP向用户提供高效、可靠的邮件传输。
最下面的是物理设备,提供了对网络的连接能力(串口或以太网之类的高速网络)。
中间是内核空间,即网络子系统,它是驱动移植的重点所在。顶部是系统调用接口,它简单的为用户空间的应用程序提供了一种访问内核网络子系统的方法。位于它下面的是一个协议无关等,它提供了一种通用方法来使用底层传输层协议。然后是实际协议,在Linux中包括内嵌的协议TCP、UDP,当然还有IP。然后是另一个设备无关层,提供了与各个设备驱动程序通信的通用接口。

CS8900A驱动程序代码修改

Linux内核中已经有CS8900A网卡驱动程序,源文件为drivers/net/cs89x0.c。与移植扩展串口驱动程序类似,所要做的工作也是:“告诉内核”CS8900A芯片使用的资源(访问地址、中断号等),使得这些资源可用。
CS8900A在开发板上的连线如下图所示:
img not found
从上图可以确定以下几点:

  1. CS8900A的访问基址为0x19000000(由BANK3的基址为0x18000000并且ADDR24为高可以确定)。
  2. 总线位宽为16,用到nWAIT、nBE1(字节使能)信号。在CS8900A芯片手册中,nSBHE引脚被称为“System Bus High Enable”,它为低电平时表示系统数据总线上高字节(SD8-SD15)的数据有效。所以S3C2410/S3C2440中,“nBE1:nWBE1:DQM1”引脚的功能应该设为“nBE1”。
  3. 中断引脚为EINT9
    驱动文件drivers/net/cs89x0.c即可以编进内核,也可以编译为一个可加载模块。编译进内核时,它的入口函数为cs89x0_probe;编译为模块时,它的入口函数为init_module。这两个函数最终都会调用cs89x0_probe1函数来枚举CS8900A。需要在调用cs89x0_probe1函数之前,指明CS8900A芯片使用的资源。

drivers/net/cs89x0.c被编译进内核时,入口函数cs89x0_probedrivers/net/space.c文件中被调用了8次,调用过程如下:

1
2
3
4
net_olddevs_init ->
ethif_probe2(被调用8次) ->
probe_list2 ->
cs89x0_probe

下面修改驱动文件drivers/net/cs89x0.c

  1. 指定CS8900A使用的资源
    在文件的开头增加以下几行,它们在宏CONFIG_ARCH_S3C2410被定义时起作用,表示用于S3C2410/S3C2440开发板。

    1
    2
    3
    4
    5
    6
    7
    8
    #elif defined(CONFIG_ARCH_S3C2410)
    #include <asm/irq.h>
    #include <asm/arch-s3c2410/regs-mem.h>
    #define S3C24XX_PA_CS8900 0x19000000 //物理基地址
    static unsigned int netcard_portlist[] _initdata = {0,0}//在下面进行设置
    static unsigned int cs8900_irq_map[] = {IRQ_EINT9, 0, 0, 0};//中断号
    #else
    ...

    S3C24XX_PA_CS8900表示访问CS8900A时使用的物理地址,在后面需要将它映射为虚拟地址。
    netcard_portlist用来指定网卡的访问地址(还未设置),它是虚拟地址或者I/O地址,可以直接用来访问网卡。后面将指定的物理地址映射为虚拟地址之后,存入netcard_portlist
    cs8900_irq_map指定CS8900A使用的中断号。

  2. 修改入口函数cs89x0_probe
    以下使用宏CONFIG_ARCH_S3C2410包括起来的代码是新加的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    struct net_device * __init cs89x0_probe(int unit)
    {
    ...
    #if defined(CONFIG_ARCH_S3C2410)
    unsigned int oldval_bwscon; //用来保存BWSCON寄存器的值
    unsigned int oldval_bankcon3; //S3C2410_BANKCON3寄存器的值
    #endif
    ...
    io = dev->base_addr;
    irq = dev->irq;

    #if defined(CONFIG_ARCH_S3C2410)
    //cs89x0_probe会被多次调用,我们只需要一次,根据netcard_portlist[0]的值忽略后面的调用
    if(netcard_portlist[0])
    return -ENODEV;

    //将CS8900A的物理地址转换为虚拟地址,0x300是CS8900A内部的I/O空间的偏移地址
    netcard_portlist[0] = (unsigned int)ioremap(S3C24XX_PA_CS8900, SZ_1M) + 0x300;

    /*
    设置默认MAC地址
    MAC地址可以由CS8900A外接的EEPROM设定
    或者启动系统后使用ifconfig修改
    */
    dev->dev_addr[0] = 0x08;
    dev->dev_addr[1] = 0x89;
    dev->dev_addr[2] = 0x89;
    dev->dev_addr[3] = 0x89;
    dev->dev_addr[4] = 0x89;
    dev->dev_addr[5] = 0x89;

    //设置Bank3:总线宽带为16,使能nWAIT,使能UB/LB。
    oldval_bwscon = *((volatile unsigned int *)S3C2410_BWSCON);
    *((volitale unsigned int *)S3C2410_BWSCON) = (oldval_bwscon & ~(3 << 12)) \
    | S3C2410_BWSCON_DW3_16 | S3C2410_BWSCON_WS3 | S3C2410_BWSCON_ST3;

    //设置Bank3的时间参数
    oldval_bankcon3 = *((volitale unsigned int *)S3C2410_BANKCON3);
    *((volitale unsigned int *)S3C2410_BWSCON3) = 0x1f7c;
    #endif
    ...
    for(port = netcard_portlist; *port; port++) {
    if (cs89x0_probe1(dev, *port, 0) == 0)
    ...
    out:
    #if defined(CONFIG_ARCH_S3C2410)
    iounmap(netcard_portlist[0]);
    netcard_portlist[0] = 0;

    //恢复寄存器原来的值
    *((volatile unsigned int *)S3C2410_BWSCON) = oldval1_bwscon;
    *((volatile unsigned int *)S3C2410_BANKCON3) = oldval_bankcon3;
    #endif
    ...
    }

    cs89x0_probe会被调用8次,第14行用来略过后面的7次。
    第18行将CS8900A的访问地址存在netcard_portlist[0]中,这是虚拟地址,在Linux内核空间访问硬件时都使用虚拟地址。它将CS8900A的物理基址转换为虚拟地址,再加上0x300(CS8900A内部的I/O空间的偏移地址)。从图22.2的nIOR、nIOW信号可知,本开发板通过它的I/O空间来使用CS8900A。
    第25-30行设置CS8900A的MAC地址,在后面,还会尝试从CS8900A外接的EEPROM读取MAC地址,也可以在系统启动后通过ifconfig命令修改MAC地址。
    第33-35行用来设置BWSCON寄存器,将BANK3设为:总线宽度为16,使能nWAIT信号,使能UB/LB信号。
    第39-39行设置BANK3的时间参数,本书使用最宽松的值,几乎都取最大值,可根据CS8900A的数据手册进行调整。
    第42-43就是实际的枚举函数了。
    第46-53行用来处理出错情况,它将函数映射的虚拟地址释放掉,设置netcard_portlist[0]为0,将BWSCON、BANKCON3寄存器设为原来的值。

  3. 修改模块入口函数init_module、卸载函数cleanup_module
    init_module函数的修改与上述cs89x0_probe函数相似,使用宏CONFIG_ARCH_S3C2410包括起来的代码是新加的。它们的作用可以参考上面的cs89x0_probe函数的描述,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    int __init init_module(void)
    {
    struct net_device *dev = alloc_etherdev(sizeof(struct net_local));
    ...
    #if defined(CONFIG_ARCH_S3C2410)
    unsigned int oldval_bwscon; //用来保存BWSCON寄存器的值
    unsigned int oldval_bankcon3; //S3C2410_BANKCON3寄存器的值
    #endif
    ...
    if(!dev)
    return -ENOMEN;

    #if defined(CONFIG_ARCH_S3C2410)
    //将CS8900A的物理地址转换为虚拟地址,0x300是CS8900A内部的I/O空间的偏移地址
    dev->base_addr = io = (unsigned int)ioremap(S3C24XX_PA_CS8900, SZ_1M) + 0x300;
    dev->irq = irq = cs8900_irq_map[0];//中断号

    /*
    设置默认MAC地址
    MAC地址可以由CS8900A外接的EEPROM设定
    或者启动系统后使用ifconfig修改
    */
    dev->dev_addr[0] = 0x08;
    dev->dev_addr[1] = 0x89;
    dev->dev_addr[2] = 0x89;
    dev->dev_addr[3] = 0x89;
    dev->dev_addr[4] = 0x89;
    dev->dev_addr[5] = 0x89;

    //设置Bank3:总线宽带为16,使能nWAIT,使能UB/LB。
    oldval_bwscon = *((volatile unsigned int *)S3C2410_BWSCON);
    *((volitale unsigned int *)S3C2410_BWSCON) = (oldval_bwscon & ~(3 << 12)) \
    | S3C2410_BWSCON_DW3_16 | S3C2410_BWSCON_WS3 | S3C2410_BWSCON_ST3;

    //设置Bank3的时间参数
    oldval_bankcon3 = *((volitale unsigned int *)S3C2410_BANKCON3);
    *((volitale unsigned int *)S3C2410_BWSCON3) = 0x1f7c;
    #else
    dev->irq = irq;
    dev->base_addr = io;
    #endif
    ...
    if(io == 0) {
    ...
    goto out;
    }else if(io <= 0x1ff) {
    ret = -ENXIO;
    goto out;
    }
    ...
    ret = cs89x0_probe1(dev, io, 1);
    ...
    out:
    #if defined(CONFIG_ARCH_S3C2410)
    iounmap(dev->base_addr);

    //恢复寄存器原来的值
    *((volitale unsigned int *)S3C2410_BWSCON) = oldval_bwscon;
    *((volitale unsigned int *)S3C2410_BANKCON3) = oldval_bankcon3;
    #endif
    free_netdev(dev);
    return ret;
    }

    判断语句io <= 0x1ff,io变量本来的类型为int,需要将它改为unsigned int,因为之前映射得到的地址在0x80000000之上,使用int的话,这是一个负数。
    卸载驱动时,要将前面映射的虚拟地址释放掉,这需要修改cleanup_module函数,下面使用宏CONFIG_ARCH_S3C2410包括起来的代码是新加的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void __exit
    cleanup_module(void)
    {
    unregister_netdev(dev_cs89x0);
    writeword(dev_cs89x0->base_addr, ADD_PORT, PP_ChipID);
    release_region(dev_cs89x0->base_addr, NETCARD_IO_EXTEND);
    #if defined(CONFIG_ARCH_S3C2410)
    iounmap(dev_cs89x0->base_addr;
    #endif
    free_netdev(dev_cs89x0);
    }
  4. 注册中断处理程序时,指定中断触发方式
    驱动程序中,在net_open函数使用request_irq函数注册中断处理函数。如下修改,使用宏CONFIG_ARCH_S3C2410包括起来的代码是新加的,CS8900A的中断触发方式是上升沿触发。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #if 0
    writereg(dev, PP_BusCTL, ENABLE_IRQ | MEMORY_ON);
    #endif
    write_irq(dev, lp->chip_type, dev->irq);
    #if defined(CONFIG_ARCH_S3C2410)
    ret = request_irq(dev->irq, &net_interrupt, IRQF_TRIGGER_RISING, dev->name, dev);
    #else
    ret = request_irq(dev->irq, &net_interrupt, 0, dev->name, dev);
    #endif
  5. 其他修改

    1. drivers/net/cs89x0.c中适当的位置加上CONFIG_ARCH_S3C2410宏的编译开关,这可以在用到宏CONFIG_ARCH_PNX010X的一些地方,仿照它加上宏CONFIG_ARCH_S3C2410。
      ①第一个位置:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      修改前:
      static int
      net_open(struct net_device *dev)
      {
      struct net_local *lp = netdev_priv(dev);
      int result = 0;
      int i;
      int ret;

      #if !defined(CONFIG_SH_HICONSH4) && !defined(CONFIG_ARCH_PNX010X)
      修改后:
      #if !defined(CONFIG_SH_HICONSH4) && !defined(CONFIG_ARCH_PNX010X) && !defined(CONFIG_ARCH_S3C2410)
      ②第二个位置
      1
      2
      3
      4
      5
      修改前:
      #if !defined(CONFIG_MACH_IXDP2351) && !defined(CONFIG_ARCH_IXDP2X01) && !defined(CONFIG_ARCH_PNX010X)
      修改后:
      #if !defined(CONFIG_MACH_IXDP2351) && !defined(CONFIG_ARCH_IXDP2X01) && !defined(CONFIG_ARCH_PNX010X) && !defined(CONFIG_ARCH_S3C2410)
      if(((1 << dev->irq) & lp->irq_map) == 0) {
      ②第三个位置
      1
      2
      3
      4
      5
      修改前:
      #if defined(CONFIG_ARCH_PNX010X)
      修改后:
      #if defined(CONFIG_ARCH_PNX010X) && defined(CONFIG_ARCH_S3C2410)
      result = A_CNF_10B_T;
    2. 全局变量“static int io;”改为“static unsigned int io;”。

内核配置文件修改

要使用驱动文件drivers/net/cs89x0.c,需要设置配置项CONFIG_CS89X0,它在配置文件drivers/net/Kconfig中描述。修改前代码如下:

1
2
3
config CS89X0
tristate "CS89X0 support"
depends on NET_PCI && (ISA || MACH_IXDP2351 || ARCH_IXDP2X01 || ARCH_PNX010X)

本书的开发板中,CS8900A并不需要使用PCI,所以需要修改它的依赖条件,修改后的代码如下:

1
2
3
config CS89X0
tristate "CS89X0 support"
depends on (NET_PCI && (ISA || MACH_IXDP2351 || ARCH_IXDP2X01 || ARCH_PNX010X)) || ARCH_S3C2410

使用CS8900A网卡

在内核根目录下执行“make menuconfig”后,如下配置将CS8900A编入内核(也可以配置为模块)

1
2
3
4
5
Device Drivers  --->
Network device support --->
[*] Network device support
Ethernet (10 or 100Mbit) --->
<*> CS89X0 support

另外,增加对NFS的支持,如下配置:

1
2
3
4
5
6
7
File System --->
Network File Systems --->
<*> NFS file system support
[*] Provide NFSv3 client support
[*] Provide client support for the NFSv3 ACL protocool extension
[*] Provide NFSv4 client support (EXPERIMENTAL)
[*] Root file system on NFS

然后编译内核:执行“make uImage”命令即可生成arch/arm/boot/uImage
新内核具备了网络功能,这是可以通过NFS启动系统,或者从NAND Flash上启动系统后挂接NFS文件系统,可以telnet登录到开发板上等。这时候调试程序就非常方便了,不需要每次都将程序烧写到开发板上。
按照U-Boot的使用说明烧写新内核,在Linux主机上启动NFS服务,现在就可以在U-Boot控制界面修改命令行参数通过NFS启动系统了。

1
set bootargs noinitrd root=/dev/nfs console=ttySAC0 nfsroot=192.168.1.57:/work/my_root_fs ip=192.168.1.17:192.168.1.57:192.168.1.2:255.255.255.0::eth0:off

ip的格式如下:

1
ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>

当系统启动后,还可以在控制台使用以下命令挂接NFS文件系统

1
2
ifconfig eth0 192.168.1.17
mount -t nfs -o nolock 192.168.1.57:/work/nfs_root/fs_mini_mdev /mnt

要从主机上通过telnet登录开发板,首先要在开发板上启动telnet服务。

1
2
ifconfig eth0 192.168.1.17
telnetd -l /bin/sh

telnet的参数“-l /bin/sh”表示连接时运行程序“/bin/sh”,否则需要验证密码。本书构建的根文件系统中没有设置用户和密码,无法登录。
如果将CS8900A的驱动配置为模块,在内核根目录下执行“make modules”命令后,会在drivers/net下生成可加载模块cs89x0.ko。将它放到开发板根文件系统下,加载之后再设置IP,就可以挂接NFS文件系统、启动telnet服务了。

DM9000 网卡驱动程序移植

DM9000的网卡特性

DM9000是一款高度集成的、低成本的单片快速以太网MAC控制器,含有带有通用处理器接口、10M/100M物理层和16KB的SRAM。
DM9000有如下特点:

  1. 支持的处理器接口类型:以字节、字、双字的I/O指令访问DM9000的内部数据。
  2. 集成的10M/100M收发器。
  3. 支持MII/RMII接口。
  4. 支持半双工背压流量控制模式。
  5. IEEE802.3x全双工流量控制模式。
  6. 支持远端唤醒和连接状态变化。
  7. 集成4KB的双字SRAM。
  8. 支持从EEPROM中自动获取厂商ID(vendor ID)和产品ID(product ID)。
  9. 支持4个GPIO管脚。
  10. 可以使用EEPROM来配置。
  11. 低功耗模式。
  12. I/O管脚3.3V和5V兼容。
  13. 100-pin CMOS工艺 LQFP封装。

DM9000网卡驱动程序修改

DM9000在开发板上的连线如图所示:
img not found
从上图可以确定以下几点。

  1. DM9000的访问基址为0x20000000(BANK4的基址),这是物理地址。
  2. 只用到一条地址线:ADDR2。这是由DM9000的特性决定的:DM9000的地址信号和数据信号复用,使用CMD引脚来区分它们(CMD为低时数据总线上传输的是地址信号,CMD为高时传输的是数据信号)。访问DM9000内部寄存器时,需要先将CMD置为低电平,发出地址信号;然后将CMD置为高电平,读写数据。
  3. 总线位宽为16,用到nWAIT信号。
  4. 中断引脚为EINT7。

Linux内核中已经有DM9000网卡驱动程序,源文件为dirvers/net/dm9000.c。它既可以编译进内核,也可以编译为一个模块。入口函数都是dm9000_init,代码如下:

1
2
3
4
5
6
7
static int __init
dm9000_init(void)
{
printk(KERN_INFO "%s Ethernet Driver\n", CARDNAME);

return platform_driver_register(&dm9000_driver); /* search board and register */
}

第6行向内核注册平台驱动dm9000_driverdm9000_driver结构的名称为“dm9000”,如果内核中有相同名称的平台设备,则调用dm9000_probe函数,dm9000_driver结构如下定义:

1
2
3
4
5
6
7
8
9
10
static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
},
.probe = dm9000_probe,
.remove = dm9000_drv_remove,
.suspend = dm9000_drv_suspend,
.resume = dm9000_drv_resume,
};

所以首先要为DM9000定义一个平台设备的数据结构,然会修改drivers/net/dm9000.c,增加一些开发板相关的代码。

增加DM9000平台设备

增加平台设备的方法在移植串口驱动程序时已经介绍过,过程相似。需要修改arch/arm/plat-s3c24xx/common-smdk.c文件。

  1. 添加要包含的头文件,增加以下代码:
    1
    2
    3
    #if defined(CONFIG_DM9000) || defined(CONFIG_DM9000_MODULE)
    #include <linux/dm9000.h>
    #endif
  2. 添加DM9000的平台设备结构,增加以下代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    #if defined(CONFIG_DM9000) || defined(CONFIG_DM9000_MODULE)
    /*DM9000*/
    static struct resource s3c_dm9k_resource[] = {
    [0] = {
    .start = S3C2410_CS4, //ADDR2=0,发送地址时使用这个地址
    .end = S3C2410_CS4 + 3,
    .flags = IORESOURCE_MEM,
    },
    [1] = {
    .start = S3C2410_CS4 + 4, //ADDR2=1,传输数据时使用这个地址
    .end = S3C2410_CS4 + 4 + 3,
    .flags = IORESOURCE_MEM,
    },
    [2] = {
    .start = IRQ_EINT7, //中断号
    .end = IRQ_EINT7,
    .flags = IORESOURCE_IRQ,
    },
    };

    /*for the moment we limit ourselves to 16bit IO until some
    better IO routines can be written and tested*/

    static struct dm9000_plat_data s3c_dm9k_platdata = {
    .flags = DM9000_PLATF_16BITONLY, //数据总线宽度为16
    };

    static struct paltform_device s3c_device_dm9k = {
    .name = "dm9000",
    .id = 0,
    .num_resource = ARRAY_SIZE(s3c_dm9k_resource),
    .resource = s3c_dm9k_resource,
    .dev = {
    .platform_data = &s3c_dm9k_platdata,
    }
    };

    #endif /*CONFIG_DM9000*/
    以上代码是仿照arch/arm/mach-s3c2410/mach-bast.c增加的,主要修改了DM9000所使用的资源,即s3c_dm9k_resource结构。s3c_dm9k_resource定义了3个资源:两个内存空间、中断号。数组项0、1定义了访问DM9000时使用的地址,前一个地址的ADDR2为0,用来传输地址;后一个地址的ADDR2为1,用来传输数据。数组项2定义了DM9000使用的中断号。
    第25行指定访问DM9000时,数据位宽为16。DM9000支持8/16/32位的访问方式。
  3. 加入内核设备列表中
    把平台设备s3c_device_dm9k加入smdk_decs数组即可,系统启动时会把这个数组中的设备注册进入内核中,增加的代码如下:
    1
    2
    3
    4
    5
    6
    7
    static struct platform_device __initdata *smdk_devs[] = {
    ...
    #if defined(CONFIG_DM9000) || defined(CONFIG_DM9000_MODULE)
    &s3c_device_dm9k,
    #endif
    ...
    };

修改drivers/net/dm9000.c

对DM9000的枚举最终由dm9000_probe函数来完成,首先从它分析这个驱动是如何使用上面定义的两个内存空间地址和中断号的,然后再给出修改方法。

  1. 驱动源码简要分析
    dm9000_probe函数就可以看出前面定义的资源是如何被使用的,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    static int 
    dm9000_probe(struct paltform_device *pdev)
    {
    ...
    if(pdev->num_resources < 2) {
    ...
    }else if(pdev->num_resources == 2) {
    ...
    }else {
    db->addr_res = platform_get_resource(pdev, IORESORCE_MEM, 0);//S3C2410_CS4
    db->data_res = platform_get_resource(pdev, IORESORCE_MEM, 1);//S3C2410_CS4 + 4
    db->data_res = platform_get_resource(pdev, IORESORCE_IRQ, 0);//IRQ_EINT7
    ...
    i = res_size(db->addr_res);
    ...
    db->io_addr = ioremap(db->addr_res->start, i);//S3C2410_CS4对应的虚拟地址
    ...
    ndev->base_addr = (unsigned long)db->io_addr;
    ndev->irq = db->irq_res->start;//IRQ_EINT7
    ...
    }
    ...
    if(pdata != NULL) {
    ...
    if(pdata->flags & DM9000_PLATF_16BITONLY)
    dm9000_set_io(db, 2);
    ...
    }
    dm9000_reset(db);
    /*tty two times,DM9000 sometimes gets the first read wrong*/
    for(i = 0;i < 2 ;i++) {
    id_val = ior(db, DM9000_VIDL);
    id_val |= (u32)ior(db, DM9000_VIDH) << 8;
    id_val |= (u32)ior(db, DM9000_PIDL) << 16;
    id_val |= (u32)ior(db, DM9000_PIDH) << 24;
    ...
    }
    ...
    }

    arch/arm/plat-s3c24xx/common-smdk.c文件中的s3c-dm9k_resource结构中有3个数组项,表示有3个资源,所以pdev->num_resources数值为3,将执行第9行的分支。
    参考第10-第20行的代码,可以知道s3c_dm9k_resource结构中定义的两个内存空间经过映射后,它们的虚拟基地址保存在db->io_addr和db->io_data中,下面可以看到它们如何使用的。ndev->irq中保存了中断号。
    第25-26行根据arch/arm/plat-s3c24xx/common-smdk.c文件中的s3c_dm9k_platdata结构指定的访问位宽,设置了相关的读写函数。
    现在来看看程序中是如何使用db->id_addr和db->id_data来访问DM9000的。第29行的dm9000_reset函数如下定义,先往地址db->io_addr写入值DM9000_NCR,再往地址db->io_data写入NCR_RST就可以复位DM9000。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    static void
    dm9000_reset(board_info_t *db)
    {
    PRINTK("dm9000x: resetting\n");
    /*RESET device*/
    writeb(DM9000_CNR, db->io_addr);
    udelay(200);
    writeb(NCR_RST, db->io_data);
    udelay(200);
    }

    ior用来读取DM9000的寄存器,它的定义如下所示:

    1
    2
    3
    4
    5
    6
    static u8
    ior(board_info_t *db, int reg)
    {
    writeb(reg, db->io_addr);//先往地址db->io_addr写入寄存器地址
    return readb(db->io_data);//再从地址db->io_data读出数值
    }

    iow用来写DM9000的寄存器,它的定义如下所示:

    1
    2
    3
    4
    5
    6
    static void
    iow(board_info_t *db, int reg, int value)
    {
    writeb(reg, db->io_addr);//先往地址db->addr写入寄存器地址
    writeb(value,db->io_data);//再将数值写入地址db->io_data
    }
  2. 驱动源码修改:drivers/net/dm9000.c

    1. 添加要包含的头文件,增加以下代码:
      1
      2
      3
      #if defined(CONFIG_ARCH_S3C2410)
      #include <asm/arch-s3c2410/regs-mem.h>
      #endif
    2. 设置存储控制器使BANK4可用,设置默认MAC地址。
      增加的代码如下,它们被宏CONFIG_ARCH_S3C2410包含起来:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      static int
      dm9000_probe(struct platform_device *pdev)
      {
      ...
      #if defined(CONFIG_ARCH_S3C2410)
      unsigned int oldval_bwscon; //用来保存BWSCON寄存器的值
      unsigned int oldval_bankcon4; //用来保存S3C2410BANKCON4寄存器的值
      #endif
      ...
      PRINTK("dm9000_probe()\n");
      #if defined(CONFIG_ARCH_S3C2410)
      /*设置Bank4:总线宽度为16,使能nWAIT*/
      oldval_bwscon = *((volatile unsigned int*)S3C2410_BWSCON);
      *((volatile unsigned int*)S3C2410_BWSCON) = (oldval_bwscon & ~(3 << 16)) \
      | S3C2410_BWSCON_DW4_16 | S3C2410_BWSCON_WS4 | S3C2410_BWSCON_ST4;
      /*设置BANK3的时间参数*/
      oldval_bankcon4 = *((volatile unsigned int *)S3C2410_BANKCON4);
      *((volatile unsigned int *)S3C2410_BANKCON4) = 0x1f7c;
      #endif
      ...
      if(!is_valid_ether_addr(ndev->dev_addr)) {
      printk("%s: Invalid ethernet MAC address. Please set using ifconfig\n",ndev->name);
      #if defined(CONFIG_ARCH_S3C2410)
      printk("Now use the default MAC address: 08:90:90:90:90:90\n");
      ndev->dev_addr[0] = 0x08;
      ndev->dev_addr[1] = 0x90;
      ndev->dev_addr[2] = 0x90;
      ndev->dev_addr[3] = 0x90;
      ndev->dev_addr[4] = 0x90;
      ndev->dev_addr[5] = 0x90;
      #endif
      }
      ...
      out:
      printk("%s:not found (%d).\n",CARDNAME,ret);
      #if defined(CONFIG_ARCH_S3C2410)
      /*恢复寄存器原来的值*/
      oldval_bwscon = *((volatile unsigned int*)S3C2410_BWSCON);
      oldval_bankcon4 = *((volatile unsigned int *)S3C2410_BANKCON4);
      #endif
      ...
      }
    3. 注册中断时,指定触发方式
      dm9000_open中使用request_irq函数注册中断处理函数,修改它即可。DM9000的中断触发方式为上升沿触发。修改代码如下:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      static int
      dm9000_open(struct net_device *dev)
      {
      board_info_t *db = (board_info_t *)dev->priv;

      PRINTF2("entering dm9000_open\n");

      #if defined(CONFIG_ARCH_S3C2410)
      if(request_irq(dev->irq, &dm9000_interrupt, IRQF_SHARED | IRQF_TRIGGER_RISING,dev->name,dev))
      #else
      if(request_irq(dev->irq, &dm9000_interrupt, IRQF_SHARED ,dev->name,dev))
      #endif
      ...
      }

使用网卡DM9000

在内核根目录下执行“make menuconfig”命令后,如下配置内核将DM9000编译入内核

1
2
3
4
5
Device Drivers --->
Network device support --->
[*] Network device support
Ethernet (10 or 100Mbit) --->
<*> DM9000 support

然后编译内核,执行“make uImage”命令即可生成arch/arm/boot/uImage
它的使用方法与上面介绍的CS8900A一样,需要注意一下两点。

  1. 如果内核中同时加载了CS8900A和DM9000,分别使用eth0、eth1表示它们。如果它们都是编译进内核的,则eth0表示CS8900A、eth1表示DM9000。如果作为模块加载,则根据它们的加载顺序先后使用eth0、eth1来表示。
  2. 如果要同时使用CS8900A和DM9000,它们的IP不能是同一网段。
    从两款网卡芯片CS8900A和DM9000的移植过程,读者可以了解到移植、修改标准驱动程序的方法:了解驱动程序框架,确定外设使用的资源,然后将它们告诉驱动程序,并进行适当设置使它们可用。串口驱动程序移植、网卡驱动移植都遵循这个步骤。