/*********************************************************************************
*
* 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.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) //接收数据包,被中断调用
|
其中int init_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(struct DEVICE *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, 0x 1f, 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(struct DEVICE *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
察看中断使用情况,可以看到网络驱动程序使用中断号,和使用次数。