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版本 ...
随机推荐
- [转] C++中临时对象及返回值优化
http://www.cnblogs.com/xkfz007/articles/2506022.html 什么是临时对象? C++真正的临时对象是不可见的匿名对象,不会出现在你的源码中,但是程序在运行 ...
- 10、SQL Server 内置函数、临时对象、流程控制
SQL Server 内置函数 日期时间函数 --返回当前系统日期时间 select getdate() as [datetime],sysdatetime() as [datetime2] getd ...
- 用angular来思考问题How do I “think in AngularJS” if I have a jQuery background?
[翻译]How do I “think in AngularJS” if I have a jQuery background? 1. 不要先设计页面,然后再使用DOM操作来改变它的展现 在jQuer ...
- project facet java version 1.6 is not supported
可能你用的jdk1.5的包,而开发是用的jdk1.6,不允许1.5进行安装 法1,选中项目 Properties , 选择 Project Facets,右击选择 Java , Change Vers ...
- java编程思想-并发思维导图
- angular自定义指令详解
指令(directive)是angular里面最核心也是最难懂的东西,在慕课网看了下大漠穷秋老湿的视频,自己百度半天做了一些小test,总算把一切都搞明白了. 先列出学习来源: 指令中controll ...
- [转载]Java synchronized详解
在编写一个类时,如果该类中的代码可能运行于多线程环境下,那么就要考虑同步的问题.在Java中内置了语言级的同步原语--synchronized,这也大大简化了Java中多线程同步的使用.我们首先编写一 ...
- 你好,C++(9)坐216路公交车去买3.5元一斤的西红柿——C++中如何表达各种数值数据 3.3 数值数据类型
3.3 数值数据类型 从每天早上睁开眼睛的那一刻开始,我们几乎每时每刻都在与数字打交道:从闹钟上的6点30分,到上班坐的216路公共汽车:从新闻中说的房价跌到了100元每平米到回家买菜时的西红柿3. ...
- How to Enable or Disable a cell in DataGridview Row ?
Following link http://msdn.microsoft.com/en-us/library/ms171619.aspxhttp://windowsclient.net/blogs/f ...
- 解决spring mvc 上传报错,Field [] isn't an enum value,Failed to convert value of type 'java.lang.String[]' to required type '
没有选择附件,但是点击上传按钮的时候会报错. 之前不选择文件,直接上传空文件是可以的,后来不知道改了什么就不行了. 错误信息: -- :: [http--] TRACE org.springframe ...