Linux网卡驱动架构分析
一、网卡驱动架构
由上到下层次依次为:应用程序→系统调用接口→协议无关接口→网络协议栈→设备无关接口→设备驱动。
二、重要数据结构
1、Linux内核中每一个网卡由一个net_device结构来描述。
2、网卡操作函数集:net_device_ops,这个数据结构是上面net_device的一个成员。
3、网络数据包:sk_buff。
三、网卡驱动代码分析
所用文件为cs89x0.c,主要分析三个部分:网卡初始化、发送数据、接收数据。
㈠网卡初始化
网卡驱动初始化主要在函数init_module中完成,部分代码如下:
int __init init_module(void)
{
struct net_device *dev = alloc_etherdev(sizeof(struct net_local));
struct net_local *lp;
int ret = ;
...
dev->irq = irq;
dev->base_addr = io;
...
ret = cs89x0_probe1(dev, io, 1);
...
}
cs89x0_probe1函数部分代码如下:
static int __init cs89x0_probe1(struct net_device *dev, int ioaddr, int modular)
{
struct net_local *lp = netdev_priv(dev);
static unsigned version_printed;
int i;
int tmp;
unsigned rev_type = ;
int eeprom_buff[CHKSUM_LEN];
int retval;
...
writeword(ioaddr, ADD_PORT, PP_ChipID);
tmp = readword(ioaddr, DATA_PORT); //对硬件的初始化
...
for (i = 0; i < ETH_ALEN/2; i++) //初始化MAC地址
{
dev->dev_addr[i*2] = eeprom_buff[i];
dev->dev_addr[i*2+1] = eeprom_buff[i] >> 8;
}
...
dev->netdev_ops = &net_ops; //初始化netdev_ops
...
retval = register_netdev(dev); //注册网卡驱动
}
由代码可以看出
1、定义并分配net_device结构,使用alloc_etherdev函数。
2、初始化net_device。(包括中断号、I/O基地址、MAC地址、netdev_ops)
3、初始化硬件
4、将网卡驱动注册到内核,使用函数register_netdev
㈡发送数据
初始化netdev_ops时将其赋值为&net_ops,可在这个结构中找到发送函数
static const struct net_device_ops net_ops = {
.ndo_open = net_open,
.ndo_stop = net_close,
.ndo_tx_timeout = net_timeout,
.ndo_start_xmit = net_send_packet,
.ndo_get_stats = net_get_stats,
.ndo_set_multicast_list = set_multicast_list,
.ndo_set_mac_address = set_mac_address,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = net_poll_controller,
#endif
.ndo_change_mtu = eth_change_mtu,
.ndo_validate_addr = eth_validate_addr,
};
net_send_packet代码如下:
static netdev_tx_t net_send_packet(struct sk_buff *skb,struct net_device *dev)
{
struct net_local *lp = netdev_priv(dev);
unsigned long flags; if (net_debug > ) {
printk("%s: sent %d byte packet of type %x\n",
dev->name, skb->len,
(skb->data[ETH_ALEN+ETH_ALEN] << ) | skb->data[ETH_ALEN+ETH_ALEN+]);
} spin_lock_irqsave(&lp->lock, flags);
netif_stop_queue(dev); /* initiate a transmit sequence */
writeword(dev->base_addr, TX_CMD_PORT, lp->send_cmd);
writeword(dev->base_addr, TX_LEN_PORT, skb->len); /* Test to see if the chip has allocated memory for the packet */
if ((readreg(dev, PP_BusST) & READY_FOR_TX_NOW) == ) { spin_unlock_irqrestore(&lp->lock, flags);
if (net_debug) printk("cs89x0: Tx buffer not free!\n");
return NETDEV_TX_BUSY;
}
/* Write the contents of the packet */
writewords(dev->base_addr, TX_FRAME_PORT,skb->data,(skb->len+1) >>1);
spin_unlock_irqrestore(&lp->lock, flags);
dev->stats.tx_bytes += skb->len;
dev_kfree_skb (skb); return NETDEV_TX_OK;
}
这部分代码做了这些事情(红色高亮部分)
1、通知上层协议停止向网卡发送数据
由于网卡现在要向外发送数据包,所以要停止接收数据包
2、将skb中的数据写入寄存器中并发送走
3、释放skb空间
但是到这里并不算完,如果这就完了上层协议还是无法向网卡发送数据,网卡不能正常工作,显然这是不正常的。那么在什么地方重新允许上层协议向网卡发送数据包呢?
其实,当网卡发送走一个数据包后,会进入网卡中断程序中,查找request_irq的知中断处理程序名称为net_interrupt
static irqreturn_t net_interrupt(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
struct net_local *lp;
int ioaddr, status;
int handled = 0; ioaddr = dev->base_addr;
lp = netdev_priv(dev); while ((status = readword(dev->base_addr, ISQ_PORT)))
{
switch(status & ISQ_EVENT_MASK)
{
...
case ISQ_TRANSMITTER_EVENT:
dev->stats.tx_packets++;
netif_wake_queue(dev); /* Inform upper layers. */
if ((status & ( TX_OK |
TX_LOST_CRS |
TX_SQE_ERROR |
TX_LATE_COL |
TX_16_COL)) != TX_OK) {
if ((status & TX_OK) == 0)
dev->stats.tx_errors++;
if (status & TX_LOST_CRS)
dev->stats.tx_carrier_errors++;
if (status & TX_SQE_ERROR)
dev->stats.tx_heartbeat_errors++;
if (status & TX_LATE_COL)
dev->stats.tx_window_errors++;
if (status & TX_16_COL)
dev->stats.tx_aborted_errors++;
}
break;
...
}
}
}
4、通知上层协议,可以向网卡发送数据包。使用函数netif_wake_queue
㈢数据接收
当网卡接受到一个数据包后,进入网卡中断处理程序
static irqreturn_t net_interrupt(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
struct net_local *lp;
int ioaddr, status;
int handled = ; ioaddr = dev->base_addr;
lp = netdev_priv(dev); while ((status = readword(dev->base_addr, ISQ_PORT)))
{
switch(status & ISQ_EVENT_MASK)
{
...
case ISQ_RECEIVER_EVENT:
/* Got a packet(s). */
net_rx(dev);
break;
}
}
}
net_rx函数代码如下
static void net_rx(struct net_device *dev)
{
struct sk_buff *skb;
int status, length; int ioaddr = dev->base_addr;
status = readword(ioaddr, RX_FRAME_PORT);
length = readword(ioaddr, RX_FRAME_PORT); if ((status & RX_OK) == ) {
count_rx_errors(status, dev);
return;
} /* Malloc up new buffer. */
skb = dev_alloc_skb(length + 2);
if (skb == NULL) {
#if 0 /* Again, this seems a cruel thing to do */
printk(KERN_WARNING "%s: Memory squeeze, dropping packet.\n", dev->name);
#endif
dev->stats.rx_dropped++;
return;
}
skb_reserve(skb, ); /* longword align L3 header */ readwords(ioaddr, RX_FRAME_PORT, skb_put(skb, length), length >> );
if (length & )
skb->data[length-1] = readword(ioaddr, RX_FRAME_PORT); if (net_debug > ) {
printk( "%s: received %d byte packet of type %x\n",
dev->name, length,
(skb->data[ETH_ALEN+ETH_ALEN] << ) | skb->data[ETH_ALEN+ETH_ALEN+]);
} skb->protocol=eth_type_trans(skb,dev);
netif_rx(skb);
dev->stats.rx_packets++;
dev->stats.rx_bytes += length;
}
由代码可以看出:
1、读取接收状态
2、读取接收到数据的长度
3、分配skb结构,skb = dev_alloc_skb(length + 2);
4、从硬件寄存器中读取数据存入skb
5、江封装好的数据包向上发送给协议栈,使用函数netif_rx
网卡驱动架构到这里大致就分析完了。如果有疑问或错误,欢迎指出。
Linux网卡驱动架构分析的更多相关文章
- Linux 网卡驱动设备程序设计(1)
一.网卡驱动架构分析 1. Linux 网络子系统 #系统调用接口层 为应用程序提供访问网络子系统的统一方法. #协议无关层 提供通用的方法来使用传输层协议. #协议栈的实现 实现具体的网络协议 #设 ...
- linux i2c驱动架构-dm368 i2c驱动分析
linux i2c驱动架构-dm368 i2c驱动分析 在阅读本文最好先熟悉一种i2c设备的驱动程序,并且浏览一下i2c-core.c以及芯片提供商的提供的i2c总线驱动(i2c-davinc ...
- Exynos4412 IIC总线驱动开发(一)—— IIC 基础概念及驱动架构分析
关于Exynos4412 IIC 裸机开发请看 :Exynos4412 裸机开发 —— IIC总线 ,下面回顾下 IIC 基础概念 一.IIC 基础概念 IIC(Inter-Integrated Ci ...
- linux驱动基础系列--linux spi驱动框架分析
前言 主要是想对Linux 下spi驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型等也不进行详细说明原理.如果有任何错误地方,请指出,谢谢! spi ...
- 【Linux开发】Linux V4L2驱动架构解析与开发导引
Linux V4L2驱动架构解析与开发导引 Andrew按:众所周知,linux中可以采用灵活的多层次的驱动架构来对接口进行统一与抽象,最低层次的驱动总是直接面向硬件的,而最高层次的驱动在linux中 ...
- Linux USB驱动框架分析 【转】
转自:http://blog.chinaunix.net/uid-11848011-id-96188.html 初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结 ...
- linux驱动基础系列--linux spi驱动框架分析(续)
前言 这篇文章是对linux驱动基础系列--linux spi驱动框架分析的补充,主要是添加了最新的linux内核里设备树相关内容. spi设备树相关信息 如之前的文章里所述,控制器的device和s ...
- Linux USB驱动框架分析【转】
转自:http://blog.csdn.net/jeffade/article/details/7701431 Linux USB驱动框架分析(一) 初次接触和OS相关的设备驱动编写,感觉还挺有意思的 ...
- 【原创】Linux PCI驱动框架分析(二)
背 景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...
随机推荐
- PHP 計算字符串長度函數
PHP內置的字符串長度函數strlen無法正確處理中文字符串,它得到的只是字符串所占的字節數.對於GB2312的中文編碼,strlen得到的值是漢字個數的2倍,而對於UTF-8編碼的中文,就是3倍的差 ...
- Array数组基本案例:图书基本录入输出系统
import java.util.Scanner; public class ArrayTest{ public static void main(String args[]){ Scanner sc ...
- MVC模式实现登录以及增删改查之登录(一)
我在这里用的不是maven项目,用的一般的web项目,所以需要用到的架包需要自己去下载添加,在项目中,一定注意环境的配置,我用的是jre1.7 1 新建项目 2 建立好MVC的管理包,导入对应的架 ...
- Android中的缓存机制与实现
分步阅读 Android开发本质上就是手机和互联网中的web服务器之间进行通信,就必然需要从服务端获取数据,而反复通过网络获取数据是比较耗时的,特别是访问比较多的时候,会极大影响了性能,Android ...
- SQL Server 存储过程分页
每每面试,总会有公司问到分页.在下不才,在这里写几种分页,望路过的各位大神尽情拍砖. 先从创建数据库说起.源码如下 一.创建数据库 /********************************* ...
- Set Keep-Alive Values---C到C#代码的转换
一.什么是Keep-Alive模式? 我们知道HTTP协议采用“请求-应答”模式,当使用普通模式,即非KeepAlive模式时,每个请求/应答客户和服务器都要新建一个连接,完成 之后立即断开连接(HT ...
- shareSDK微博分享出现: 分享失败: 错误描述:Insufficient app permissions! 错误码:10014
这个错误是由于appKey所在账号没有微博高级写入接口权限, 需要申请, 详见: http://www.mamicode.com/info-detail-936938.html
- 基于Jquery easyui 选中特定的tab并更新
获取选中的 Tab // 获取选中的 tab panel 和它的 tab 对象 var pp = $('#tt').tabs('getSelected'); var tab = pp.panel('o ...
- WARN TaskSetManager: Lost task 0.0 in stage 0.0 (TID 0, worker1): java.lang.ClassNotFoundException: com.spark.firstApp.HelloSpark$$anonfun$2
进行如下设置,解决报错信息. val conf = new SparkConf().setAppName("helloSpark").setMaster("spark:/ ...
- aborb()程序结束形式
abort()与exit()的区别? 分类: MFC 2011-01-04 14:13 2233人阅读 评论(0) ...