这里,再针对DM9000网卡驱动具体地分析一下网卡驱动的架构
首先,Linux网络设备驱动从上到下分为四层:
1、网络协议接口层:向网络协议(ARP&IP)提供统一的数据包发送接口,通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接收数据。这一层的存在使得上层协议独立于具体的设备
2、网络设备接口层:向网络协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器,从宏观上规划了具体操作硬件的设备驱动功能层的结构
3、设备驱动功能层:各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,通过dev_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作(open() --> dev_interrupt --> dev_rx())
4、网络设备与媒介层:是完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被驱动功能层中的函数物理上驱动。对于Linux系统而言,网络设备和媒介都可以是虚拟的。
在设计具体的网络设备驱动程序是,我们需要完成的主要工作是编写设备驱动功能层的相关函数以填充net_device数据结构体的内容,并将net_device注册入内核。
DM9000网卡驱动位于内核源码的driver/net/dm9000.c,它是基于平台驱动架构的
ps:此处按代码的书写顺序分析架构
typedef struct board_info {
u32 io_addr; //寄存器I/O基地址
u32 io_data; //数据I/O基地址
... ...
}board_info_t;
static struct net_device *dm9000_dev = NULL;
static int irq = DM9KS_IRQ;
static int iobase = DM9KS_MIN_IO;
static unsigned char dev_addr[MAX_ADDR_LEN]; // hw address
//12、如下实现各net_device(dm9000_xxx)各成员函数
//13、网卡打开函数
static int dm9000_open(struct net_device *dev)
{
ret = request_irq(dev->irq, &dm9000_interrupt, 0, dev->name, dev); //申请端口、IRQ等(注册中断)
... ...
netif_start_queue(dev); //激活设备发送队列
}
//17、当我们在打开网卡的时候即有申请IRQ中断,因为网卡是通过中断来实现数据的接收的
static void dm9000_interrupt(int irq, void *dev_id)
{
...
switch(status & IRQ_EVENT_MASK){ //当有中断来到时,判断中断的类型
case IRQ_RECEIVER_EVENT: //当中断类型为有数据来到时
dm9000_rx(dev); //调用网卡的数据接收函数
break;
//其他类型的中断
... ...
}
}
//18、网卡数据包接收函数
static void dm9000_tx(struct dm9000_device *dev)
{
... ...
length = get_rev_len(...); //从硬件读取到接收数据包有效数据的长度
skb = dev_alloc_skb(length + 2); //分配新的套接字缓冲区
skb->reserve(skb, 2); //对齐
skb->dev = dev;
insw(ioaddr + RX_FRAME_PORT, skb_put(skb, length), length >> 1); //读取硬件上接收到的数据
if(length & 1)
{
skb->data[length - 1] = inw(ioaddr + RX_FRAME_PORT); //并放入数据缓冲区
skb->protocol = eth_type_trans(skb, dev); //解析收到数据包上层协议的类型
netif_rx(skb); //将数据包上交给上层协议(ARP&IP)
dev->last_rx = jiffies; //记录接收时间戳
... ...
}
}
//14、关闭网卡
static int dm9000_stop(struct net_device *dev)
{
free_irq(dev->irq, dev); //释放端口、IRQ等
... ...
netif_stop_queue(dev); //停止设备传送包
}
//15、数据包发送函数
static int dm9000_start_xmit(struct sk_buf, struct net_device *dev)
{
int len;
char *data, shortpkt(ETH_ZLEN);
if(dm9000_send_available(...)){ //发送队列未满,可以发送
data = skb->data; //网络设备驱动程序从上层协议传过来的skb_buff参数获得数据包的有效数据和长度,将有效数据放入临时缓冲区
len = skb->len;
if(len < ETH_ZLEN) //对于以太网,如果有效数据的长度小于以太网冲突检测所要求数据帧的最小长度ETH_ZLEN,则给临时缓冲区的末尾填充0
{
menset(shortpkt, 0, ETH_ZLEN);
memcpy(shortpkt, skb->data, skb->len);
len = ETH_ZLEN;
data = shortpkt;
}
dev->trans_start = jiffies;
dm9000_hw_tx(data, len, dev); //设置硬件寄存器,让硬件把数据包发送出去
... ...
}else{
netif_stop_queue(dev); //当发送队列为满或因其他原因来不及发送当前上层传下来的包,则调用此函数阻止上层继续向网络设备驱动传递数据包。当忙于发送的数据包被发送完成后,TX结束的中断处理中,应该调用netif_wakeup_queue()唤醒被阻塞的上层,以启动它继续向网络设备驱动传送数据包
... ...
}
}
//16、当数据传输超时时,意味着当前的发送操作失败或硬件已陷入位置状态,此时,数据包发送超时处理函数将被调用,重新启动设备发送队列
static void dm9000_tx_timeout(struct net_dev *dev)
{
... ...
netif_wake_up(dev);
}
//7、实现dm9000_probe函数
static net_device *_init dm9000_probe(void)
{
struct net_device *ndev;
int err;
//8、分配一个维护网卡的结构
ndev = alloc_etherdev(sizeof(struct board_info));
//9、配置网卡的特性
err = dm9000_probe1(ndev);
//10、注册网卡设备
err = register_netdev(ndev);
}
//11、配置网卡特性具体实现函数dm9000_probe1
static __init dm9000_probe1(struct net_device *ndev)
{
struct board_info *db;
//映射IO地址和定义IRQ number
iobase = ioremap(0x20000000, 1024);
irq = IRQ_EINT7;
//对底层硬件的操作
... ...
//配置网卡公有属性
db->io_addr = iobase;
db->io_data = iobase + 4;
dev->base_addr = iobase;
dev->open = &dm9000_open;
dev->hard_start_xmit = &dm9000_start_xmit;
dev->tx_timeout = &dm9000_tx_timeout;
dev->stop = &dm9000_stop;
dev->get_stats = &dm9000_get_stats;
dev->set_multicast_list = &dm9000_hash_table;
dev->do_ioctl = &dm9000_do_ioctl;
... ...
}
//6、构造平台驱动结构体
static struct platform_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
},
.probe = dm9000_probe,
.remove = dm9000_remove,
} ;
//1、入口函数
static int __init dm9000_init(void)
{
//2、注册dm9000_driver平台驱动到内核
return platform_driver_register(&dm9000_driver);
}
//3、出口函数
static void __exit dm9000_exit(void)
{
//4、注销网卡驱动
platform_driver_unregister(&dm9000_driver);
}
//5、模块注册
module_init(dm9000_init);
module_exit(dm9000_exit);
MODULE_LICENSE("GPL");