Linux下的网络设备驱动(一)
转载自: http://my.csdn.net/weiqing1981127
一.网络设备驱动基础
1. 以太网基础理论
以太网是局域网的一种,它使用载波监听多路访问及冲突检测技术(CSMA/CD),并以10M/S的速率运行在多种类型的电缆上,常用的网卡芯片有DM9000、DM9161、CS8900芯片。
以太网的拓扑结构有总线型和星型,以太网的工作模式有半双工和全双工。目前双绞线是以太网最普通的传输介质,它多用于从主机到集线器或交换机的连接,光纤主要用于交换机间的级联和交换机到路由器间的点到点的链路上。
在局域网中,多个节点是共享传输介质的,这就必须有某种机制来决定某个时刻,哪个设备占用传输介质来传输数据,因此,局域网的链路层要有介质访问控制的功能,即数据链路层分为逻辑链路控制LLC子层和介质访问控制MAC子层。
以太网的帧结构主要有Ethernet II 、Ethernet 802.3raw、Ethernet802.3SAP、Ethernet802.3SNAP
以太网控制器实现了MAC层功能,而且必须与PHY(物理层收发器)联合使用,前者与OSI的数据链路层相关,后者实现物理层功能。MII(媒体无关接口)是连接快速以太网MAC和PHY的标准接口,以太网的设备驱动是通过MII与PHY通信,以配置PHY ID、速率、双工模式等参数。
2. Linux网络驱动层次
Linux网络驱动可以划分四层,即网络协议接口层、网络设备接口层、设备驱动功能层和设备物理媒介层,内核中是通过以dev_base为头指针的设备链表来管理所有的网络设备。网络设备驱动的编写主要是网络设备net_device的初始化和数据包的收发函数。
网络协议接口层
网络协议接口层最主要的功能是给上层协议提供了透明的数据包发送和接收接口,当上层的API或IP需要发送数据包时,它将调用网络协议接口层的dev_queue_xmit函数发送一个内容为sk_buff的数据;当上层对数据包的接收数据,则是通过向netif_rx函数传递一个sk_buff数据结构的指针来完成的。Sk_buff套接字缓冲区为linux网络层提供了高效的缓冲区处理和流量控制机制,当发送数据包时,Linux内核的网络处理模块必须建立一个需要传输的数据包sk_buff,然后将sk_buff递交给下层,各层在sk_buff中添加不同的协议头直至交给网络设备发送。同样的,当网络设备从网络媒介上接收到数据包后,必须去掉不同的协议头再交给用户。
下面是sk_buff的部分结构体成员定义
struct sk_buff {
……
unsigned int len,
data_len;
__u16 mac_len,
hdr_len;
sk_buff_data_t transport_header; //传输层的头
sk_buff_data_t network_header; //网络层的头
sk_buff_data_t mac_header; //MAC层的头
sk_buff_data_t tail; //有效数据的尾指针
sk_buff_data_t end;
unsigned char *head, //整个缓冲区的头
*data; //有效数据的头指针
unsigned int truesize;
atomic_t users;
};
对于sk_buff操作,除了分配和释放外,需要知道如下几个函数:在缓冲区尾部增加数据skb_put;在缓冲区开头增加数据skb_push;在缓冲区开头移除数据skb_pull;调整整个缓冲区的头部skb_reserve。
网络设备接口层
网络设备接口层主要是为变化多端的网络定义了一个统一且抽象的net_device,实现了多种硬件在软件层次上的统一。网络设备驱动主要是填充net_device的成员并注册net_device来实现硬件操作函数和内核的挂接。通常情况下,网络设备驱动以中断方式接受数据,而net_device中则定义了poll_controller这种纯轮询的接口方式,另外由于宽带接口每秒会收到几千个数据包,如果使用中断方式会导致系统性能下降,为了提高linux在宽带系统上的性能,网络子系统开发者创建了一种基于轮询的数据接收方式是NAPI(New API),其数据接收流程是”接收中断来临—关闭接收中断—以轮询方式接收完所有数据—开启接收中断—接收中断来临……”,与NAPI相关的函数有添加NAPI,使能NAPI,调度NAPI,具体函数是netif_napi_add,napi_enable,napi_schedule。最后需要注意的是对于NAPI方式和中断方式接收数据,驱动设计上还是有一些不同的,比如NAPI方式是用netif_receive_skb函数将数据包传递给内核,而不是使用netif_rx函数。
设备驱动功能层
对于具体的设备,工程师应该实现net_device中的open,stop,tx,hard_header,get_stats,tx_timeout,interruppt等函数。
网络设备媒介层
网络设备媒介层直接对应实际的硬件设备,我们需要定义一组读写设备内部寄存器的函数,如ior,iow。
二.网络设备驱动移植
下面我们主要讲解基于mini2440的DM9000网卡驱动的移植
首先看内涵DM9000代码在/driver/net/dm9000.c
查看/driver/net/Makefile
obj-$(CONFIG_DM9000) += dm9000.o
查看/driver/net/Konfig
menuconfig NET_ETHERNET
bool "Ethernet (10 or 100Mbit)"
config DM9000
tristate "DM9000 support"
depends on ARM || BLACKFIN || MIPS
select CRC32
select MII
所以配置内核make menuconfig 时,需要选中这一项。
根据开发板电路图知道DM9000的AEN端口接到了nGCS4上,同时DM9000的INT端口接到了IRQ_EINT7上,另外DM9000的CMD端口接到了LADDR2上,最后发现DM9000上数据线是SD0-SD15,即数据线的位数是16位。
根据mini2440地址空间的分配与片选信号的定义知道,引脚nGCS4对应的空间的起始地址为0x20000000,这个由系统地址线控制。同时我们知道DM9000使用的中断号就是IRQ_EINT7。另外,DM9000上的CMD信号是控制地址端口还是数据端口的,如果CMD为0,即LADDR2为0表示访问地址寄存器,当CMD为1,即LADDR2为1表示访问数据寄存器。
下面我们进行DM9000驱动的移植,在mach-mini2440.c中添加如下代码
#include <linux/dm9000.h>
#define MACH_MINI2440_DM9K_BASE (S3C2410_CS4 + 0x300)
static struct resource mini2440_dm9k_resource[] = {
[0] = { //地址端口
.start = MACH_MINI2440_DM9K_BASE,
.end = MACH_MINI2440_DM9K_BASE + 3,
.flags = IORESOURCE_MEM
},
[1] = { //数据端口
.start = MACH_MINI2440_DM9K_BASE + 4,
.end = MACH_MINI2440_DM9K_BASE + 7,
.flags = IORESOURCE_MEM
},
[2] = { //中断号
.start = IRQ_EINT7,
.end = IRQ_EINT7,
.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE,//高电平触发
}
};
static struct dm9000_plat_data mini2440_dm9k_pdata = {
//数据线的位数是16位,没有使用E2PROM
.flags = (DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM),
.dev_addr = { 0x08,0x90,0x90,0x90,0x90,0x90}, //MAC地址
};
static struct platform_device mini2440_device_eth = {
.name = "dm9000", //设备名
.id = -1,
.num_resources = ARRAY_SIZE(mini2440_dm9k_resource),
.resource = mini2440_dm9k_resource, //资源
.dev = {
.platform_data = &mini2440_dm9k_pdata, //私有数据
},
};
最后在mini2440的BSP文件mach-mini2440.c中添加如下代码
static struct platform_device *mini2440_devices[] __initdata = {
……
& mini2440_device_eth, //添加
};
这样移植完毕后编译内核生成内核镜像。