网卡驱动属于数据链路层
1、kernel space(对应于要学的网络子系统) :
system call interface 给应用程序提供访问网络子系统
protocol agnostic interface 协议无关接口
network protocols 网络协议
device agnostic interface 设备无关接口
帧的传送:
1)从协议设备相关 即网卡传送:dev_queue_xmit 把 skb 往下送
2)把网卡收到的 帧传给协议(设备无关层) netif_rx
当底层设备驱动程序收到一个报文 就会调用netif_rx 将数据传至设备无关层然后
通过netif_rx_schedule函数将sk_buff在上层协议中进行排队等待处理
3)
net_device
{
char name[ifnamsiz]; 设备名 如eth%d 后面的数字由内核指定 不要自己写上 让内核来给我们写上。
unsigned long state ; 设备状态
usigned long base_addr ; IO基地址
unsigned int irq ; 中断号
int (*init)(struct net_device *dev)
}
dev.init当 register_netdev被执行的时候会被调用.
2、驱动动程序宏观概念
1)网卡驱动程序没有主次编号
2)通过net_device结构体来描述这个网卡信息
struct net_device * alloc_etherdev(int sizeof_priv)
struct net_device * alloc_netdev
(
int sizeof_priv ,
const * mask ,
void (*setup)(struct net_device*)
)
这两个函数来分配这 net_device这个结构体 并且前面的那个函数与后面的函数的区别是
前面的函数的会调用后面的函数 并且使用默认的参数来填充后面的setup 函数。
3、网卡驱动程序 各个函数的主要处理流程:
1)网卡的初始化函数:probe
主要是对net_device这个结构体进行初始化 当调用int register_netdev(struct net_device * dev)来注册 设备描述符时候
这个函数会调用 net_device 里面的init函数进行进一步的初始化, 主要任务是指定函数指针
这个函数是内核调用register_driver的时候后调用。
更具体的说是注册驱动调用宏 module_int() -> dm9000_init-> platform_drive注册的时候调用 dm9000_probe函数
struct net_device
{
//设备名 如eth%d 后面的数字由内核指定 不要自己写上 让内核来给我们写上。
char name[IFNAMSIZ];
/*
* The device initialization function. Called only once.
*当驱动程序调用 register_netdev 有内核自动调用这个函数
*/
int (*init)(struct net_device *dev);
/* device name hash chain */
struct hlist_node name_hlist;
/* shared mem end */
unsigned long mem_end;
/* shared mem start */
unsigned long mem_start;
/* device I/O address */
unsigned long base_addr;
/* device IRQ number */
unsigned int irq;
/* DMA channel */
unsigned char dma;
/*
* 使用ifconfig的时候会激活这个函数(后面的参数不一样 可能不会调用这个函数 待续) 接口被打开
* 在这里应该注册他需要的资源 如io端口 IRQ DMA
*/
int (*open) (struct net_device * dev)
//?什么时候调用
int (*stop)
/*
*协议来调用这个函数来发送自己的包 任何网卡都是这样的。
*这个函数发送的是完整的的报文 包括了协议头和所有
*/
int (*hard_start_xmit) (struct sk_buff *skb,struct net_device *dev);
/*
*这个函数是取出源地址和目的地址 这个函数 在 xit之前调用 且可以自己制定也可以 通过netdevice 这个函数默认制定
*/
static inline int dev_hard_header(struct sk_buff *skb, struct net_device *dev, unsigned short type, const void *daddr, const void *saddr, unsigned len)
/*处理特定的接口的 ioctl命令
*/
int (*do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
2)注册网卡设备
注册函数 ,实际内核调用的是另一个函数 (驱动会马上用来操作设备)
int register_netdev(struct net_device * dev)
相对的
从系统中除去接口调用函数
void unregister_netdev(struct net_device * dev)
3)open函数
1)) 申请中断 dma申请
字符设备驱动调用module_intit
来申请中断 这个是网卡设备驱动程序的特殊性
dma和中断的注册放在open函数里面 。原因是网卡驱动程序可以调用disable的函数
这样就可以不用占用中断号!当然可以也放在module_init函数里面来做。只是很浪费资源
我们可以禁用网卡时也会一直占宝贵的资源但是字符设备的话是一直是使用者的不会说你可以禁止使用
2))中断号的申请
int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs ),
unsigned long irqflags,
const char * devname,
void *dev_id
);
参数解释:
*irq是要申请的硬件中断号。在Intel平台,范围0--15。
*handler是向系统登记的中断处理函数。这是一个回调函数,中断发生时系统调用这个函数,传入的参数包括
硬件中 断号,device id,寄存器值。
*dev_id就是下面的request_irq时传递给系统的参数dev_id。
irqflags是中断处理的一些属性。比较重要的有
SA_INTERRUPT, 标明中断处理程序是快速处理程序(设置
SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT)。快速处理程序被调用时屏蔽所有中断。
慢速处理程序不屏蔽。还有一个SA_SHIRQ属性,设置了以后运行多个设备共享中断。dev_id在中断共享时会用到。
一般设置为这个设备的 device结构本身或者NULL。中断处理程序可以用dev_id找到相应的控制这个中断的设备,或
者用irq2dev_map找到中断对应的设备。
void free_irq(unsigned int irq,void *dev_id);
3))
设置寄存器,启动设备
4)) 申请IO内存 IO访问
io端口 先申请后访问 request_region()申请函数 ,告诉内核要使用该IO端口
5))启动发送队列 只有这样 协议站才会往里面放发送包 给你去发送
4
)发送函数
什么时候调用发送函数 ?不是中断触发的而是由协议栈来触发 ,其源头可能是应为用户调用 ifcongfig 或者是系统内核 的请求
发送队列应该现停止
1)) 初始化
2))
写 txcmd 发送命令
3))写 txlen 数据长度
5))发送端是否准备好
4))写数据把协议栈传进来的 skb里面的数据写进发送缓冲
writeblock(struct net_device *dev ,char *pdata , int len); pdata 应该是skb的data指针 len 是对应的skb的len
5
)接收函数
什么时候接受 这个是由中断来触发。当网卡收到包时候 产生中断
1))
分配 skb skb =dev_alloc_skb(pkt->datalen+2)
看代码的时候会看到:skb_reserve(skb,2)挪出两个字节因为报文前面有14个字节,而第十五个字节开始是真正的数据。
(skb是以4字节为单位进行分配的)所以先预留两个字节。当把收到的数据放进skb时第十六个字节就刚好是有效数据了。
就是要让真实的数据刚好处于开头位置。具体看数据对齐有关笔记。
2)) 拷贝数据到skb
3)) 调用net_ifrx 交给设备无关接口 netif_rx(skb)
6)触发中断的集中可能 (通知cpu发生了什么事情)
1)新报文到达。触发数据收中断
2)报文发送完成 TXlen 产生发生完成中断
3)出错。
4、skb的一些了解
struct sk_buff
{
struct sk_buff *next;
struct sk_buff *prev;
struct sock *sk;
ktime_t tstamp;
struct net_device *dev; 正在使用的网卡设备
struct dst_entry *dst;
struct sec_path *sp;
sk_buff_data_t tail; 是数据的结束
sk_buff_data_t end; 指向tail指向的最后位置
unsigned char *head, 指向分配的空间的开始
*data; 指向有效数据的开始
。。。。。
}
1、以上四个并不会直接来有我们使用 可以使用 分配skb的函数 dev_alloc_skb(usigned int len )来填写以上四个指针 其实这个 函数会调用 alloc_skb(usigned int len ,int priority) 后者使用GFP_ATOMATIC优先级
2、skb 的填充函数1 usigned char * skb_put(struct sk_buff * buff, int len); 这个函数把数据放到skb的后面 影响tai指针 返回值 是tail的前一个值。 驱动可以使用返回值 通过引用 memcpy(skb_put(不填任何字节),data,len)来拷贝数据到skb
3、void *memcpy(void*dest, const void *src, size_t n); 功能 由src指向地址为起始地址的连续n个字节的数据复制到以destin指向 地址为起始地址的空间内。 头文件