Linux下DM9000网卡驱动实验
1.1 硬件系统介绍
1.1.1 网络驱动程序的特点
网络驱动程序是介于硬件和内核之间传送数据包,不是面向流的设备,不能象/dev/tty1那样简单的映射到文件系统的节点上。Linux调用这些接口的方式是给他们分配一个独立的名字(如eth0)。这样的名字在文件系统中并没有对应项。内核和网络设备驱动程序之间的通信与字符设备驱动程序和快设备驱动程序与内核间的通信是完全不同的。内核不再调用read/write,它调用与数据包传送相关的函数。
1.1.2 DM9000以太网芯片介绍
网络控制器选用DAVICOM公司的DM9000快速以太网控制处理器,合成了MAC, PHY, MMU。该处理器配备有标准10M/100M自适应,16K大容量的FIFO,4路多功能GPIO,掉电,全双工工作等功能。物理层支持以太网接口协议。由于数据有时是以猝发形式收到的,因此,DM9000还集成有接收缓冲区,以便在接收到数据时能把数据放到这个缓冲区中,然后由数据链路层直接从该缓冲区里取走数据。链路层通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡,它们一起处理与电缆的物理接口细节数据,它的缓冲区可用来暂时存储要发送或接收的帧。
网络控制器包括MAC和PHY两个部分,其中MAC层控制器作为逻辑控制。
1.1.3 2410与DM9000的硬件连接
DM9000的模块示意图
接口电路图:
1.2 网卡驱动实验步骤
1.2.1 步骤一:编写简单的网络设备驱动程序
以下是一个网络驱动程序完整源码,这仅仅只是一个可编译,加载和卸载的驱动程序,不能完成任何实际的工作。不过,可把它当作模板,网络设备的驱动程序就从这个模板上添加而成。并且,针对每一个驱动程序的接口,都做了介绍和编程指导。
程序清单 1‑0‑1 简单的网络程序模板 test_net.c
/********************************************************************************* * * net_test.c a simple net device driver sample * * ********************************************************************************* */
#ifndef __KERNEL__ #define __KERNEL__ #endif #ifndef MODULE #define MODULE #endif #define __NO_VERSION__ #include #include #include #include #include #include #include #include #include static int test_init(struct net_device *dev); static int test_open(struct net_device *dev); static int test_release(struct net_device *dev); static int test_config(struct net_device *dev, struct ifmap *map); static int test_tx(struct sk_buff *skb, struct net_device *dev); int test_init_module(void); void test_cleanup(void); #define DEVICE_NAME "test" //内核模块名称 module_init(test_init_module); module_exit(test_cleanup); //定义结构体变量并初始化必须的成员 static struct net_device net_test = { init:test_init, }; static int test_init(struct net_device *dev) { ether_setup(dev);//初始化结构体变量中与硬件无关的成员 strcpy(dev->name,"eth0");
dev->open =test_open; dev->stop =test_release; dev->set_config =test_config; dev->hard_start_xmit =test_tx; dev->flags &=~(IFF_BROADCAST|IFF_LOOPBACK|IFF_MULTICAST); //设置驱动中感兴趣的成员... SET_MODULE_OWNER(dev);//设置owner成员 return 0; } //当网络设备驱动程序不能自动识别硬件配置时,系统管理员会使用配制工具将正确的硬件配置传递给 //驱动程序。实际上就是调用驱动程序的set_config()方法。 static int test_config(struct net_device *dev, struct ifmap *map) { return 0; } static int test_tx(struct sk_buff *skb, struct net_device *dev) { return 0; } static int test_open(struct net_device *dev) { MOD_INC_USE_COUNT; netif_start_queue(dev);//第一次调用open()方法必须调用 return 0; } static int test_release(struct net_device *dev) { netif_stop_queue(dev);//最后一次调用stop()方法必须调用 MOD_DEC_USE_COUNT; return 0; } int test_init_module(void) { int result = 0; //初始化模块自身(包括请求一些资源) result = register_netdev(&net_test);//注册网络设备驱动程序 if(result < 0) { printk(KERN_ERR DEVICE_NAME":error % i registering device \"%s\"\n", result , net_test.name); return result; } printk(KERN_ERR DEVICE_NAME ":init OK\n"); return 0; } void test_cleanup(void) { unregister_netdev(&net_test); //卸载驱动程序 } ////////////////////////////////////////////////////////////// |
1.2.2 步骤二:在Linux平台上编译和运行简单的网络设备驱动程序
编译驱动程序需要编写Makefile文件,与其他驱动的Makefile类似。
# Makefile for net_test.c
# a simple net device driver
# Aka 2006.08
OBJS = net_test.o
SRC = net_test.c
CC = /usr/local/arm/2.95.3/bin/arm-linux-gcc
LD = /usr/local/arm/2.95.3/bin/arm-linux-ld
#INCLUDE =-I/HHARM2410-R3/kernel/include/linux -I/HHARM2410-R3/kernel/include
INCLUDE =-I/9200E/arm-kernel/linux-2.4.19-rmk7/include
CFLAGS = -D__KERNEL__ -DMODULE -O2 -Wall -Wstrict-prototypes-Wno-trigraphs -Wmissing-prototypes
All: $(OBJS)
%.o:%.c
$(CC) $(CFLAGS) $(INCLUDE) -c $^ -o $@
.PHONY: clean
clean:
-rm -f *.o
distclean:
@make clean
rm -f tags *~
编译,测试驱动文件net_test.o,要把这个文件下载到HHARM2410的开发平台上,需要建立好主机与开发板的连接和开发环境。参考附录一建立开发环境。
在主机上编译驱动程序,在源文件目下输入
[localhost]# make
生成驱动文件模块test_net.o
通过网络或者NFS,把驱动文件net_test.o传到开发板的系统中。
加载驱动模块如下
[localhost]# insmod test_net.o
卸载驱动模块如下:
[localhost]# rmmod test_net.o
1.1.1 步骤三:网络驱动程序的数据结构和抽象接口
首先熟悉以下重要的数据结构,然后对照DM9000的驱动程序,察看具体数据结构的用法。
保存网络设备信息的结构体net_device
net_device结构存储了网络设备的操作方法和其他信息。其定义如下面的清单。仅仅列出了部分常用的成员,对于普通的网络设备驱动程序已足够。想详细了解的结构体参考LDD(Linux Device Driver)或内核源码/include/linux/netdevice.h
程序清单 0‑2
struct net_device { char name[IFNAMSIZ]; unsigned short flags; /* interface flags (a la BSD) */ unsigned long base_addr; /* device I/O address */ unsigned int irq; /* device IRQ number */ unsigned char dev_addr[MAX_ADDR_LEN]; /* hw address */ unsigned char addr_len; /* hardware address length */ unsigned long trans_start; /* Time (in jiffies) of last Tx */ unsigned long last_rx; /* Time of last Rx */ int watchdog_timeo; void *priv; /* pointer to private data */ struct module *owner; /* open/release and usage marking */ int (*init)(struct net_device *dev); int (*open)(struct net_device *dev); int (*stop)(struct net_device *dev); int (*set_config)(struct net_device *dev, struct ifmap *map); int (*hard_start_xmit) (struct sk_buff *skb,struct net_device *dev); int (*set_mac_address)(struct net_device *dev, void *addr); //.............. }; |
网卡实现的抽象接口
init(),open(),stop(),set_config()方法在模板中已经清楚地介绍了,这里重点讲解hard_start_xmit()方法。当内核需要发送一个数据包时,他调用hard_start_xmit()方法将数据放到一个输出队列。数据包包含在一个套接字缓冲区结构体(struct sk_buff)变量中。对于驱动程序来说,sk_buff只是一个数据包,不用关心里面复杂的结构成员。
程序清单 0‑3
static int test_tx(struct sk_buff * skb , struct net_device * dev) { int len; netif_stop_queue(dev); //让内核暂停调用hard_start_xmit()方法 len = skb->len < ETH_ZLEN ? ETH_ZLEN :skb->len; //把len个数据写入网络设备硬件缓冲区并启动发送; dev->trans_start = jiffies; //记录时间 dev_kfree_skb(skb); //等待数据发送完毕 netif_wake_queue(dev); return 0; } static int test_set_mac_address(struct net_device * dev, void *addr) { struct socketaddr * mac_addr; mac_addr = addr; if(netif_running(dev)) { Return –EBUSY; } memcpy(dev->dev_addr , mac_addr->sa_data , dev->addr_len); //改变mac地址,与具体硬件相关。 return 0; } |
一般网络驱动程序不对发送数据进行缓存,而是直接使用硬件的发送功能把数据发送出去。接收数据则通过硬件中断来通知的。在中断处理程序中,把硬件帧信息填入一个sk_buff结构中,然后调用netif_rx()传递给上层处理。当然,在使用中断之前,需要向系统注册中断处理程序。
下面是中断服务程序的写法:
static void net_irq_handle(int irq, void * dev_id, struct pt_regs * regs) { struct net_device * dev; struct sk_buff *skb; unsigned int length; u8 * dec; dev = (struct net_device *)dev_id; if(发送成功) { 清除发送中断标志; netif_wake_queue(dev); } if(接收成功) { 清除接受中断标志; length = 数据包长度; skb = dev_alloc_skb(length + 2); if(!skb) { return; } skb_reserve(skb, 2); dec = skb->data; //把数据包(length长度)读到dec指向的内存中; skb->dev = dev; skb->protocol = eth_type_trans(skb, dev); skb->ip_summed = CHECKSUM_UNNECESSARY; netif_rx(skb); dev->last_rx = jiffies; } if(出错) 错误处理 清除中断源; } |
1.1.1 步骤四:分析DM9000的网卡驱动程序
驱动程序共包含三个文件dm9000x.c ,dm9000.c 和dm9000.h,都存放在drivers/net/目录下,其中dm9000x.c 主要包括以下函数:
底层硬件操作函数:这些函数与硬件相关,与驱动程序编写相关不大。
void outb(unsigned char value, unsigned long addr) void outw(unsigned short value, unsigned long addr) unsigned char inb(unsigned long addr) unsigned short inw(unsigned long addr) u8 ior(board_info_t *db, int reg) // Read a byte from I/O port void iow(board_info_t *db, int reg, u8 value) // Write a byte to I/O port
|
驱动程序的函数接口,
int init_module(void) ; //加载设备驱动程序 void cleanup_module(void) //卸载设备驱动程序 static void dmfe_init_dm9000(struct DEVICE *dev) /* Initilize DM910X board */ dmfe_probe(struct DEVICE *dev); //初始化net_device 结构体 static int dmfe_open(struct DEVICE *dev) static int dmfe_stop(struct DEVICE *dev) static int dmfe_start_xmit(struct sk_buff *skb, struct DEVICE *dev) //发送一个数据包 static void dmfe_packet_receive(struct DEVICE *dev, board_info_t *db) //接收数据包,被中断调用
|
其中intinit_module(void)函数调用了dmfe_probe(struct DEVICE *dev)来初始化net_device结构体
if(strcmp(dev->name,"eth0")==0) if(strcmp(dev->name,"eth1")==0) if(strcmp(dev->name,"eth3")==0)
outb(DM9000_VID_L, iobase); id_val = inb(iobase + 4); outb(DM9000_VID_H, iobase); id_val |= inb(iobase + 4) << 8; outb(DM9000_PID_L, iobase); id_val |= inb(iobase + 4) << 16; outb(DM9000_PID_H, iobase); id_val |= inb(iobase + 4) << 24; if (id_val == DM9000_ID) { printk("HHTech DM9000 %s I/O: %x,VID: %x,MAC: ", dev->name,iobase, id_val); dm9000_count++; /* Init network device */ dev = init_etherdev(dev, 0); /* Allocated board information structure */ irqline = 3; db = (void *)(kmalloc(sizeof(*db), GFP_KERNEL|GFP_DMA)); memset(db, 0, sizeof(*db)); dev->priv = db; /* link device and board info */ db->next_dev = dmfe_root_dev; dmfe_root_dev = dev; db->ioaddr = iobase; db->io_data = iobase + 4; /* driver system function */ dev->base_addr = iobase; //dev->irq = 68; //INT4 166;//irqline; dev->open = &dmfe_open; dev->hard_start_xmit = &dmfe_start_xmit; dev->stop = &dmfe_stop; dev->get_stats = &dmfe_get_stats; dev->set_multicast_list = &dm9000_hash_table; dev->do_ioctl = &dmfe_do_ioctl;
|
在dmfe_open(structDEVICE *dev)中,申请了中断处理程序,然后调用了
dmfe_init_dm9000(dev);初始化DM9000芯片。
static int dmfe_open(struct DEVICE *dev) { if(request_irq(dev->irq, &dmfe_interrupt,SA_INTERRUPT/*SA_SHIRQ*/, "DM9000 device",dev)) return -EAGAIN; /* Initilize DM910X board */ dmfe_init_dm9000(dev); netif_wake_queue(dev); //add by simon 2001.9.4 for kernel 2.4 MOD_INC_USE_COUNT; }
|
Stop()方法停止网络驱动程序:
static int dmfe_stop(struct DEVICE *dev) { netif_stop_queue(dev); //add by simon 2001.9.4 for kernel 2.4 /* free interrupt */ free_irq(dev->irq, dev); /* RESET devie */ phy_write(db, 0x00, 0x8000); /* PHY RESET */ iow(db, 0x1f, 0x01); /* Power-Down PHY */ iow(db, 0xff, 0x80); /* Disable all interrupt */ iow(db, 0x05, 0x00); /* Disable RX */ MOD_DEC_USE_COUNT; return 0; }
|
发送数据接口,对照前面的简单的驱动框架。
static int dmfe_start_xmit(struct sk_buff *skb, struct DEVICE *dev) { netif_stop_queue(dev); //add by simon 2001.9.4 for kernel 2.4 /* Disable all interrupt */ iow(db, 0xff, 0x80); /* Move data to DM9000 TX RAM */ data_ptr = (char *)skb->data; outb(0xf8, db->ioaddr); tmplen = (skb->len + 1) / 2; /* Word mode*/ for (i = 0; i < tmplen; i++) outw(((u16 *)data_ptr)[i], db->io_data);
/* TX control: First packet immediately send, second packet queue */ if (db->tx_pkt_cnt == 0) { /* First Packet */ db->tx_pkt_cnt++; /* Set TX length to DM9000 */ iow(db, 0xfc, skb->len & 0xff); iow(db, 0xfd, (skb->len >> 8) & 0xff); /* Issue TX polling command */ iow(db, 0x2, 0x1); /* Cleared after TX complete */ dev->trans_start = jiffies; /* saved the time stamp */ } else { /* Second packet */ db->tx_pkt_cnt++; db->queue_pkt_len = skb->len; }
/* free this SKB */ dev_kfree_skb(skb); /* Re-enable resource check */ if (db->tx_pkt_cnt == 1) netif_wake_queue(dev); //add by simon 2001.9.4 for kernel 2.4
/* Re-enable interrupt mask */ iow(db, 0xff, 0x83); return 0; }
|
中断处理程序,用来接收数据
static void dmfe_interrupt(int irq, void *dev_id, struct pt_regs *regs) { db = (board_info_t *)dev->priv; spin_lock(&db->lock); // add by simon 2001.9.4 for kernel 2.4 /* Save previous register address */ reg_save = inb(db->ioaddr); /* Disable all interrupt */ iow(db, 0xff, 0x80); /* Got DM9000 interrupt status */ int_status = ior(db, 0xfe); /* Got ISR */ iow(db, 0xfe, int_status); /* Clear ISR status */ //printk("I%x ", int_status); /* Received the coming packet */ if (int_status & 1) dmfe_packet_receive(dev, db);
/* Trnasmit Interrupt check */ if (int_status & 2) { tx_status = ior(db, 0x01); /* Got TX status */ if (tx_status & 0xc) { /* One packet sent complete */ db->tx_pkt_cnt--; dev->trans_start = 0; db->stats.tx_packets++; /* Queue packet check & send */ if (db->tx_pkt_cnt > 0) { iow(db, 0xfc, db->queue_pkt_len & 0xff); iow(db, 0xfd, (db->queue_pkt_len >> 8) & 0xff); iow(db, 0x2, 0x1); dev->trans_start = jiffies; } //dev->tbusy = 0; /* Active upper layer, send again */ //mark above by simon 2001.9.4 for kernel 2.4 netif_wake_queue(dev); //mark_bh(NET_BH); //mark by simon 2001.9.4 } }
/* Re-enable interrupt mask */ iow(db, 0xff, 0x83); /* Restore previous register address */ outb(reg_save, db->ioaddr);
|
1.1.2 步骤五:调试运行DM9000网卡驱动程序
在内核的DM9000驱动代码中,直接增加调试和打印信息,然后重新编译内核,下载到开发板上,察看调试信息。内核中的打印输出语句用printk()。
把内核中的DM9000的网络驱动程序/drivers/net/dm9000x.c中的注释掉的printk语句打开。详细见dm9000x.c文件。
第一步,打印网络的配置信息,与ifconfig显示的信息比较。
1, 在/kernel/drivers/net/dm9000x.c中,注释掉的printk有效。主要是在dmfe_probe(struct DEVICE *dev)函数中的打印信息.
2, 然后再/kernel目录下编译内核,make zImage
3, 通过TFTP下载刚编译的内核镜像zImage到开发板中
tftp 30008000 zImage tftp 30800000 ramdisk.image.gz go 30008000
|
4,观察系统启动时有网络驱动程序打印出来的信息。进入系统后用ifconfig察看网络信息。
第二步:观察中断调用,数据接收的情况
1, 在/kernel/drivers/net/dm9000x.c中,使宏定义有效
//#undef DM9000_DEBUG
#define DM9000_DEBUG
主要是在
static void dmfe_interrupt(int irq, void*dev_id, struct pt_regs *regs)
static void dmfe_packet_receive(structDEVICE *dev, board_info_t *db)
中的打印信息, 注释掉的printk有效.
2, 然后再/kernel目录下编译内核,make zImage
3, 通过TFTP下载刚编译的内核镜像zImage到开发板中
tftp 30008000 zImage
tftp 30800000 ramdisk.image.gz
go 30008000
4,观察系统启动时有网络驱动程序打印出来的信息。进入系统后挂载NFS系统(如果还没有挂载NFS),可以看到打印出网络中断和数据接收的信息。
第二步:用户层调用网络socket接口。
1, 因为操作系统封装了网络设备驱动程序,用户层只能通过socket接口使用网络。 HHARM2410-STUDY\modules.TestApp\ethernet-performance-test提供了测试网络流量的测试程序,使用了socket接口。
2, 分别编译HHARM2410-STUDY\modules.TestApp\ethernet-performance-test\run-on-board和HHARM2410-STUDY\modules.TestApp\ethernet-performance-test\run-on-LINUX-PC
3,开发板用网线(对接线)同PC机相连;
PC机上编译好eth-perf-pc/中的程序,生成server可执行程序,运行
./server
在minicom下执行客户端程序(开发板上)
./client 192.168.2.111 –t 1 –p 1
其中192.168.2.111为PC机的IP,-t表示time,-p表示package。
4,如果这时候没有注释掉网络驱动程序dm9000的中断和接收数据的打印信息,屏幕会频繁的提示产生中断和接收到数据。
还可以用
[localhost]# cat /prop/interrupts
察看中断使用情况,可以看到网络驱动程序使用中断号,和使用次数。