源码开放学ARM—DM9000网卡驱动

来源:互联网 发布:七雄战记更新网络错误 编辑:程序博客网 时间:2024/06/07 12:11

Linux 网卡驱动实验

源码开放学ARM:http://www.lumit.org/LASO/zh/chp106-4.html

标准Board Linux启动 & uBuntu Linux 连接

  • pc linux ( 192.168.0.200 )

  • board linux ( 192.168.0.201 )

    ifconfig sudo ifconfig eth0 192.168.0.xxx

测试网络连接 对ping可以连通

uboot 启动 & uBuntu Linux 连接

  1. 开发板上的跳线 S2 拨到 SDBOOT 那边,通过 SD 卡启动

  2. 启动之后进入 uboot 选项,修改环境变量

    [FriendlyLEG-TINY210]# printenvbaudrate=115200bootdelay=3ethact=dm9000ethaddr=00:01:02:03:04:05gatewayip=192.168.0.1ipaddr=192.168.0.201netmask=255.255.255.0serverip=192.168.0.200stderr=serialstdin=serialstdout=serial[FriendlyLEG-TINY210]# setenv ipaddr 192.168.0.201[FriendlyLEG-TINY210]# setenv serverip 192.168.0.200[FriendlyLEG-TINY210]# saveenvSaving Environment to NAND...Erasing Nand...Erasing at 0x40000 -- 100% complete.Writing to Nand... done[FriendlyLEG-TINY210]# 
  3. 重启开发板,验证是否设置成功? 看到 is alive 表示成功

    FriendlyLEG-TINY210# ping 192.168.0.200
    dm9000 i/o: 0x88001000, id: 0x90000a46 DM9000: running in 16 bit mode 
    MAC: 00:01:02:03:04:05 
    operating at 100M full duplex mode 
    Using dm9000 device 
    host 192.168.0.200 is alive 
    FriendlyLEG-TINY210#

uboot 和 uBuntu 之间的 tftp 连接

ubuntu下搭建tftp server

  1. Install tftpd and related packages.
    $ sudo apt-get install xinetd tftpd tftp

  2. Create /etc/xinetd.d/tftp and put this entry:

    service tftp{protocol        = udpport            = 69socket_type     = dgramwait            = yesuser            = nobodyserver          = /usr/sbin/in.tftpdserver_args     = /tftpbootdisable         = no}
  3. Make /tftpboot directory
    $ sudo mkdir /tftpboot
    $ sudo chmod -R 777 /tftpboot
    $ sudo chown -R nobody /tftpboot

  4. Start tftpd through xinetd
    $ sudo /etc/init.d/xinetd restart

  5. test tftp in localhost
    $ cp something /tftpboot

    $ tftp localhost$ get something  Received 4315348 bytes in 0.4 seconds$ tftp> quit$ ls something
  6. test tftp in board $ tftp 21000000 zImage

    uboot 和 uBuntu 之间的 uImage 内核下载测试

设置 uboot 参数

# setenv bootargs root=/dev/mtdblock4 console=ttySAC0,115200 init=/linuxrc lcd=S70# setenv ipaddr 192.168.0.201# setenv serverip 192.168.0.200# saveenv# printenv

制作 uImage 内核文件

# tftp 21000000 uImage# bootm 21000000

获得 mkimage

DM9000 驱动代码实现

平台设备的注册 platform_device

arch/arm/mach-s5pv210/mach-mini210.c

平台驱动的注册 platform_driver

drivers/net/dm9000.c

头文件的包含

drivers/net/dm9000.h include/linux/dm9000.h

几个重要的文件

  • .config 
    make menuconfig 的配置选项保存在这个文件中
    CONFIG_DM9000 = m

  • include/generated/autoconf.h 
    make 执行时会通过脚本分析 .config 文件,生成 autoconf.h 参与内核编译

    #define CONFIG_DM9000 1

  • driver/net/makefile
    obj-$(CONFIG_DM9000) = dm9000.o

    Makefile

    obj-m := dm9000.o

    KDIR := /home/limingth/tiny210/src/linux-2.6.35.7

    modules: make -C $(KDIR) SUBDIRS=$(PWD) $@ ls -l dm9000.ko

    clean: -rm .o.ko

    step by step

  • install tftpd
    sudo apt-get install tftpd

  • install inetd 
    sudo apt-get install openbsd-inetd

    server dir /srv/tftp

    sudo vi /etc/inetd.conf

    #:BOOT: TFTP service is provided primarily for booting.  Most sites#       run this only on machines acting as "boot servers."#tftpdgramudpwaitnobody/usr/sbin/tcpd/usr/sbin/in.tftpd /srv/tftptftpdgramudpwaitnobody/usr/sbin/tcpd/usr/sbin/in.tftpd /home/limingth/tiny210

    sudo /etc/init.d/openbsd-inetd restart

  • download Image
    limingth@ubuntu:~$ tftp 127.0.0.1
    tftp> get zImage2
    tftp> quit

  • change u-boot bootargs

    操作步骤

    FriendlyLEG-TINY210# setenv bootargs root=/dev/mtdblock4 console=ttySAC0,115200 init=/linuxrc lcd=S70 FriendlyLEG-TINY210# printenv baudrate=115200 bootargs=root=/dev/mtdblock4 console=ttySAC0,115200 init=/linuxrc lcd=S70 bootdelay=3 ethact=dm9000 ethaddr=08:0a:0a:0a:0a:0a gatewayip=192.168.0.1 ip=192.168.0.201 ipaddr=192.168.0.201 netmask=255.255.255.0 serverip=192.168.0.200 stderr=serial stdin=serial stdout=serial

    Environment size: 315/16380 bytes

* 启动命令 
tftp 21000000 uImage bootm 21000000

修改内核源码

按以下方法修改内核以适应uboot,详参见liukun321的文章
http://blog.csdn.net/liukun321/article/details/7383669, 忽略此步骤将会出现加载kernel时卡死在 Uncompressing Linux… done, booting the kernel….

arch/arm/mach-s5pv210/include/mach/memory.h 文件26,27行内容,  将Maximum of 256MiB in one bank的限制改为Maximum of 512MiB in one bank 作如下修改:#defineSECTION_SIZE_BITS   29#defineNODE_MEM_SIZE_BITS   29

生成 uImage

limingth@ubuntu:~/tiny210/src$ ./mkimage Usage: ./mkimage -l image          -l ==> list image header information       ./mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image          -A ==> set architecture to 'arch'          -O ==> set operating system to 'os'          -T ==> set image type to 'type'          -C ==> set compression type 'comp'          -a ==> set load address to 'addr' (hex)          -e ==> set entry point to 'ep' (hex)          -n ==> set image name to 'name'          -d ==> use image data from 'datafile'          -x ==> set XIP (execute in place)         ./mkimage [-D dtc_options] -f fit-image.its fit-image       ./mkimage -V ==> print version information and exit       limingth@ubuntu:~/tiny210/src$ ./mkimage -A arm -O linux -T kernel -C none -a 21000000 -e 21000040 -n "linux-2.6.28" -d zImage2  myuImageImage Name:   linux-2.6.28Created:      Wed Aug 29 13:32:34 2012Image Type:   ARM Linux Kernel Image (uncompressed)Data Size:    4288476 Bytes = 4187.96 kB = 4.09 MBLoad Address: 21000000Entry Point:  21000040

/include/generated/mach-types.h
./arch/arm/include/asm/mach-types.h

limingth@ubuntu:~/tiny210/src/linux-2.6.35.7$ vi arch/arm/include/asm/mach-types.h limingth@ubuntu:~/tiny210/src/linux-2.6.35.7$ vi include/generated/mach-types.h in line 2950#define MACH_TYPE_MINI210              3466

DM9000 驱动代码实现

#include <linux/module.h>#include <linux/ioport.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/init.h>#include <linux/skbuff.h>#include <linux/spinlock.h>#include <linux/crc32.h>#include <linux/mii.h>#include <linux/ethtool.h>#include <linux/dm9000.h>#include <linux/delay.h>#include <linux/platform_device.h>#include <linux/irq.h>#include <linux/slab.h>#include <asm/delay.h>#include <asm/irq.h>#include <asm/io.h>#include "dm9000.h"MODULE_AUTHOR("AKAE");MODULE_DESCRIPTION("Sample DM9000 driver");MODULE_LICENSE("Dual BSD/GPL");#define DEVICE_NAME"dm9000"/*board_info结构体,用来保存芯片相关的一些私有信息*/typedef struct board_info {void __iomem*io_addr;/*映射到Linux内存空间的地址口虚拟地址*/void __iomem*io_data;/*映射到Linux内存空间的数据口虚拟地址*/u16 irq;/*IRQ*/u16tx_pkt_cnt;/*待发送数据包数量,最多只能有两个*/u16queue_pkt_len;/*待发送数据包长度*/u8imr_all;/*用于给DM9000_IMR赋值的一个变量*/struct resource*addr_res;   /*地址口地址*/struct resource *data_res;/*数据口地址*/struct resource *irq_res;/*IRQ*/struct mutex addr_lock;/*互斥信号两*/spinlock_tlock;/*自旋锁*/} board_info_t;/*Read a byte from I/O port*/static u8 ior(board_info_t * db, int reg){writeb(reg, db->io_addr);return readb(db->io_data);}/*Write a byte to I/O port*/static void iow(board_info_t * db, int reg, int value){writeb(reg, db->io_addr);/*将寄存器地址写入映射进内存的i\o内存空间,通过地址找到相应的寄存器*/writeb(value, db->io_data);/*将数据写入寄存器*/}/*调用时机:当网卡有数据需要发送的时候,该函数被调用*//*第二个包的发送将在dm9000_tx_done中实现,这是因为当第一个数据发送完之后会产生一个中断,则会调用dm9000_tx_done对应的函数*/static int dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev){unsigned long flags;board_info_t *db = netdev_priv(dev);/*获得自旋锁并禁止中断,中断状态保存在flags中*/spin_lock_irqsave(&db->lock, flags);/*将io_addr写入寄存器MWCMD(写数据到DM9000内的发送SRAM中),进行这个操作后,向io_data写入的数据会传输到dm9000内部TX_SRAM中*/writeb(DM9000_MWCMD, db->io_addr);writesw(db->io_data, skb->data, (skb->len+1) >> 1);/*待发送的数据包数量(tx_pkt_cnt)加1*/db->tx_pkt_cnt++;/* TX control: First packet immediately send, second packet queue */if (db->tx_pkt_cnt == 1) {iow(db, DM9000_TXPLL, skb->len);  iow(db, DM9000_TXPLH, skb->len >> 8); iow(db, DM9000_TCR, TCR_TXREQ);} else {/* Second packet */db->queue_pkt_len = skb->len;}/*释放自旋锁,并恢复中断之前的状态*/spin_unlock_irqrestore(&db->lock, flags);/*每个数据包写入网卡SRAM后都要释放skb*/  dev_kfree_skb(skb);return NETDEV_TX_OK;}static void dm9000_tx_done(struct net_device *dev, board_info_t *db){/*读取dm9000寄存器NSR(NetworkStatus Register)获取发送的状态,存在变量tx_status中*/int tx_status = ior(db, DM9000_NSR);/*若有数据包发送完成,则进入*/if (tx_status & (NSR_TX2END | NSR_TX1END)) {/*待发送的数据包数量(tx_pkt_cnt)减1*/db->tx_pkt_cnt--;/*已发送的数据包数量(stats.tx_packets)加1*/dev->stats.tx_packets++;/*检查变量tx_pkt_cnt是否大于0(如果大于0,表明还有数据包要发送),则再次发送*/if (db->tx_pkt_cnt > 0){/*将要发送的数据包的长度写入DM9000的两个发送数据包长度寄存器中*/iow(db, DM9000_TXPLL, db->queue_pkt_len);iow(db, DM9000_TXPLH, db->queue_pkt_len >> 8);/*提出发送请求,发送完成后该位自动清零*/iow(db, DM9000_TCR, TCR_TXREQ);}}}/*该结构体封装了dm9000接收的数据包的头信息*/ struct dm9000_rxhdr {u8RxPktReady;u8RxStatus;__le16RxLen;} __attribute__((__packed__));/*告诉编译器取消结构在编译过程中的优化*/static void dm9000_rx(struct net_device *dev){board_info_t *db = netdev_priv(dev);struct dm9000_rxhdr rxhdr;struct sk_buff *skb;u8 rxbyte, *rdptr;int RxLen;/*接收数据包的长度*//* Check packet ready or not */do {/* 读取寄存器MRCMDX,MRCMDX寄存器地址保存在了db->io_addr中,下面要读取MRCMDX寄存器的值只需要读取db->io_data即可*/ior(db, DM9000_MRCMDX);/*读取MRCMDX寄存器的值,rxbyte为8位,因为MRCMDX寄存器存储的值就是8位*/rxbyte = readb(db->io_data);/*DM9000_PKT_RDY定义为0x01,而rxbyte为我们读取的第一个字节,其值只能是0x00(表示还没接收)或0x01(表示已经接收)*/if (!(rxbyte & DM9000_PKT_RDY))return;/*将MRCMD寄存器地址写入db->io_addr*/writeb(DM9000_MRCMD, db->io_addr);/*一次性从MRCMD寄存器(即RX_SRAM)中读入四个字节的内容到rxhdr变量,*/readsw(db->io_data, &rxhdr, (sizeof(rxhdr)+1) >> 1);/*获取接收数据包的长度*/RxLen = le16_to_cpu(rxhdr.RxLen);skb = dev_alloc_skb(RxLen + 4);skb_reserve(skb, 2);rdptr = (u8 *) skb_put(skb, RxLen - 4);/*读取数据放入rdptr所在位置,rdptr位置为skb_tail,所以这句话就是读取RX_SRAM内容到skb*/readsw(db->io_data, rdptr, (RxLen+1) >> 1);dev->stats.rx_bytes += RxLen;/*函数eth_type_trans用于从以太网数据包中提取网络协议内容,并把它放入skb结构的相应位置*/skb->protocol = eth_type_trans(skb, dev);/*调用netif_rx将数据交给协议栈*/netif_rx(skb);} while (rxbyte & DM9000_PKT_RDY);}/*网卡有三种类型的中断:新报文到达中断,报文发送完成中断,出错中断*/static irqreturn_t dm9000_interrupt(int irq, void *dev_id){struct net_device *dev = dev_id;board_info_t *db = netdev_priv(dev);int int_status;unsigned long flags;u8 reg_save;/*获得自旋锁并禁止中断,中断状态保存在flags中*/spin_lock_irqsave(&db->lock, flags);/*保存中断前的状态*/reg_save = readb(db->io_addr);/* 禁用DM9000中断*/iow(db, DM9000_IMR, IMR_PAR);/*中断状态寄存器,当一个中断到来时,该寄存器存放着中断类型。DM9000中断处理函数通过读取该寄存器,得到目前中断信息*//*读取该中断状态寄存器之后,还需要将读取结果存放回该寄存器,也就是需要清除中断状态,否则将无法再次响应中断*/int_status = ior(db, DM9000_ISR);iow(db, DM9000_ISR, int_status);/*检测中断状态寄存器,如果是由于收到数据而触发的中断,显然调用dm9000_rx()把数据取走,传递给上层*/if (int_status & ISR_PRS)dm9000_rx(dev);/*检测中断状态寄存器,如果是由于发送完了数据而触发的中断,则调用dm9000_tx_done()函数*/if (int_status & ISR_PTS)dm9000_tx_done(dev, db);/*重新使能DM9000各中断功能*/iow(db, DM9000_IMR, db->imr_all);/*恢复中断前的状态*/writeb(reg_save, db->io_addr);/*自旋锁解锁*/spin_unlock_irqrestore(&db->lock, flags);return IRQ_HANDLED;}/*当使用命令ifconfig eth0 up时,网卡被打开,执行这一函数,向内核注册中断,复位并初始化dm9000,检查MII接口,使能传输等*/static int dm9000_open(struct net_device *dev){/*返回board_info_t的地址*/board_info_t *db = netdev_priv(dev); /*IRQF_TRIGGER_MASK为中断触发方式,定义在Interrupt.h中*/  unsigned long irqflags = db->irq_res->flags & IRQF_TRIGGER_MASK;/*设置为共享中断*/irqflags |= IRQF_SHARED;/*申请中断并注册中断服务程序*/request_irq(dev->irq, dm9000_interrupt, irqflags, dev->name, dev);/*复位dm9000*/iow(db, DM9000_NCR, 1);do {udelay(100);} while (ior(db, DM9000_NCR) & 0x1);/*初始化DM9000*//*DM9000的GPIO0默认为输出做POWER_DOWN功能,默认值为1,表明要使PHY层POWER_DOWN,即不启用PHY,若希望启用PHY,则驱动程序需要通过写“0”将PWER_DOWN信号清零*/iow(db, DM9000_GPR, 0);/* Enable PHY *//*使能数据包接收中断,使能数据包传输中断*/db->imr_all = IMR_PAR | IMR_PTM | IMR_PRM;iow(db, DM9000_IMR, db->imr_all);/*设置DM9000的RCR接收控制寄存器的第0位为1,接收使能*/iow(db, DM9000_RCR, RCR_RXEN);return 0;}/*驱动支持的网卡设备操作函数*/static const struct net_device_ops dm9000_netdev_ops = {.ndo_open= dm9000_open,.ndo_start_xmit= dm9000_start_xmit,};/*内核加载驱动后,自动执行驱动的probe函数,进行资源的探测和申请资源*/static int __devinit dm9000_probe(struct platform_device *pdev){struct board_info *db;/*将获得的资源信息存放在这个结构体里*/struct net_device *ndev;/*定义一个网络设备*/int iosize;int i;u32 id_val;/*分配ndev,使用alloc_etherdev()函数分配一个网络设备的结构体,原型在include/linux/etherdevice.h */ndev = alloc_etherdev(sizeof(struct board_info));/*通过SET_NETDEV_DEV()将platform_device与net_device接洽关联起来*/SET_NETDEV_DEV(ndev, &pdev->dev);/*下面都是设置board_info结构体,将获得的资源信息存放在这个结构体里*//*struct board_info *db和struct net_device *ndev都是局部变量。但是又需要board_info和net_device二者建立一一对应关系,而board_info 是一个自定义结构,通过netdev_priv(ndev)就把board_info放入net_device里,建立了联系。创建net_device 时已经为board_info 留了空间:ndev = alloc_etherdev(sizeof(struct board_info));*/db = netdev_priv(ndev);/*将网络设备结构与自定义设备结构建立关联,获取net_device结构的私有成员保存到struct board_info *db中*/spin_lock_init(&db->lock);/*初始化自旋锁*/  mutex_init(&db->addr_lock);/*初始化互斥信号量*/db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);/*获取DM9000地址口的资源地址*/db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);/*获取DM9000数据口的资源地址*/db->irq_res  = platform_get_resource(pdev, IORESOURCE_IRQ, 0);/*获取DM9000中断资源*/iosize = resource_size(db->addr_res);/*将地址口映射到linux内存空间的虚拟地址*/db->io_addr = ioremap(db->addr_res->start, iosize);iosize = resource_size(db->data_res);/*将数据口映射到linux内存空间的虚拟地址*/db->io_data = ioremap(db->data_res->start, iosize);/*****************************设置结构体board_info结束***************************************************//*填充net_device结构体*/ndev->base_addr = (unsigned long)db->io_addr;/*设置网络设备地址*/ndev->irq= db->irq_res->start;/*设置网络设备中断资源地址*//*获取DM9000的ID号,需要多次获得以免失败*/for (i = 0; i < 8; 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;if (id_val == DM9000_ID)break;}/*借助ether_setup()函数来部分初始化ndev。因为对以太网设备来讲,很多操作与属性是固定的,内核可以帮助完成*/ether_setup(ndev);/*驱动支持的网卡设备操作函数*/ndev->netdev_ops = &dm9000_netdev_ops;/*注册ndev网络设备*/register_netdev(ndev);return 0;}static struct platform_driver dm9000_driver = {.driver= {.name    = "dm9000",         /*驱动的名字*/.owner = THIS_MODULE,},.probe   = dm9000_probe,             /*重要的函数:资源探测函数*/};/*第一步:加载网卡驱动首先要被执行的*/static int __init dm9000_init(void){printk(KERN_INFO "%s Ethernet Driver\n", DEVICE_NAME);return platform_driver_register(&dm9000_driver);/*调用函数platform_driver_register()函数注册驱动*/}static void __exit dm9000_cleanup(void){platform_driver_unregister(&dm9000_driver);}module_init(dm9000_init);module_exit(dm9000_cleanup);
0 0
原创粉丝点击