本文主要参考
https://www.bilibili.com/video/BV1HZ4y1377n?from=search&seid=13712221424119728211
《Linux设备驱动开发详解》
0x00 网络设备驱动架构
1·
对于上层应用就俩接口,一个收,一个发
对于所有的网络设备就用 net_device结构体描述
从上而下,发包,就是个call_back
从下而上,收包,就是个响应中断
0x01 网络协议接口层
1.net_device结构体中有两个成员
struct net_device {
struct net_device {
。。。。
const struct net_device_ops *netdev_ops;
const struct ethtool_ops *ethtool_ops;
。。。。
}
其中*netdev_ops 就是 file_operation
struct net_device_ops {
int (*ndo_open)(struct net_device *dev);
int (*ndo_stop)(struct net_device *dev);
netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb,
struct net_device *dev);
int (*ndo_set_mac_address)(struct net_device *dev,
void *addr);
int (*ndo_do_ioctl)(struct net_device *dev,
struct ifreq *ifr, int cmd);
int (*ndo_set_config)(struct net_device *dev,
struct ifmap *map);
void (*ndo_tx_timeout) (struct net_device *dev);
......
}
ndo_open: 网卡工作 **
ndo_start_xmit: 开始发包
ndo_set_mac_address:设置mac地址, 不一定都能设
ndo_tx_timeout : 发包失败,超时。 **
ethtool_ops很少用,设置一些interface
2. sk_buff
网络协议接口层最主要的功能是给上层协议提供透明的数据包发送和接收接口。当上层ARP或IP需要发送数据包时,它将调用网络协议接口层的devqueue_xmit()函数发送该数据包,同时需传递给该函数- -个指向struct sk buff 数据结构的指针。dev queue xmit() 函数的原型为:
int dev_queue_xmit(struck sk_buff *skb)
同样地,上层对数据包的接收也通过向netif _rx() 丽数传递- -个struct sk. _buff 数据结构的指针来完成。netif _rx()函数的原型为:
int netif_ rx(struct. sk_ buff *skb) ;
sk buff结构体非常重要,它定义于include/linux/skbuff.h文件中,含义为“套接字缓冲区”,用于在Linux网络子系统中的各层之间传递数据,是Linux网络子系统数据传递的“中枢神经”
当发送数据包时,Linux内核的网络处理模块必须建立—个包含要传输的数据包的sk_buff,然后将sk buf交给下层,各层在sk _buff 中添加不同的协议头直至交给网络设备发送。同样地,当网络设备从网络媒介上接收到数据包后,它必须将接收到的数据转换为sk_buff数据结构并传递给上层,各层剥去相应的协议头直至交给用户。
struct sk_buff {
union {
struct {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
union {
struct net_device *dev;
/* Some protocols might use this space to store information,
* while device pointer would be NULL.
* UDP receive path is one user.
*/
unsigned long dev_scratch;
};
};
....
unsigned int len,
data_len;
__u16 mac_len,
hdr_len;
...
__u32 priority;
....
union {
__be16 inner_protocol;
__u8 inner_ipproto;
};
__u16 inner_transport_header;
__u16 inner_network_header;
__u16 inner_mac_header;
__be16 protocol;
__u16 transport_header;
__u16 network_header;
__u16 mac_header;
/* private: */
__u32 headers_end[0];
/* public: */
/* These elements must be at the end, see alloc_skb() for details. */
sk_buff_data_t tail;
sk_buff_data_t end;
unsigned char *head,
*data;
....
};
能尽量不去拷贝,就不拷贝,会浪费很多资源和时间,所以采用这种传递结构体(只是传指针),一层一层包头的剥离/添加
有两次必须要拷贝:
用户层——》内核层
内核 ——》网卡
**
值得注意
head和end指向缓冲区的头部和尾部,
而data和tail指向实际数据的头部和尾部。
每一层 会在head和data之间填充协议头,或者在tail和end之间添加新的协议数据。
0x02 网络设备接口层
1. net_device
(1)全局信息
char name IIFNAMESIZ];
name是网络设备的名称。
(2)硬件信息
unsigned long mem end;
unsigned. long mem_ start;
unsigned long base_ addr;
unsigned char i rq;
unsigned char if_ _port;
unsigned char dma;
mem gstart 和mem end 分别定义了设备所使用的共享内存的起始和结束地址。
base addr 为网络设备I/O基地址。
irq为设备使用的中断号。
if port指定多端口设备使用哪-一个端口, 该字段仅针对多端口设备。例如,如果设备同
时支持IF PORT 10BASE2 (同轴电缆)和IF PORT 10BASET (双绞线),则可使用该字段。
dma指定分配给设备的DMA通道。
(3)接口信息
unsigned short hard_ header_ len;
hard header len 是网络设备的硬件头长度,在以太网设备的初始化函数中,该成员被赋
为ETH HLEN,即14。
unsigned short type;
type是接口的硬件类型。
unsigned mtu;
mtu指最大传输单元(MTU)。
unsigned char* dev_ addr;
用于存放设备的硬件地址,驱动可能提供了设置MAC地址的接口,这会导致用户设置的MAC地址等存入该成员,如代码清单14.2 drivers/net/ethernet/moxa/moxart. ether.c 中的moxart set _mac address()_
static int moxart_set_mac_address(struct net_device *ndev, void *addr)
{
struct sockaddr *address = addr;
if (!is_valid_ether_addr(address->sa_data))
return -EADDRNOTAVAIL;
memcpy(ndev->dev_addr, address->sa_data, ndev->addr_len);
moxart_update_mac_address(ndev);
return 0;
}
(完成了memcpy()以及最终硬件上MAC地址的变更)
unsigned short flags;
flags指网络接口标志,以IFF ( Interface Flags)开头,部分标志由内核来管理,其他的在接口初始化时被设置以说明设备接口的能力和特性。接口标志包括IFF__UP(当设备被激活并可以开始发送数据包时,内核设置该标志)、IFF AUTOMEDIA (设备可在多种媒介间切换)、IFF BROADCAST (允许广播)、IFF DEBUG (调试模式,可用于控制printk 调用的详细程度)、IFF LOOPBACK (回环)、IFF MULTICAST (允许组播)、IFF NOARP (接口不能执行ARP)和IFF POINTOPOINT (接口连接到点到点链路)等。
(4)设备操作函数见上文
(5)辅助成员
unsigned long trans_start;
unsigned long last_rx;
trans_ start 记永最后的数据包开始发送时的时间戳,last _rx 记录最后一次接收到数据包时的时间戳,这两个时间戳记录的都是jffies,(linux时间子系统中)驱动程序应维护这两个成员。
通常情况下,网络设备驱动以中断方式接收数据包,而poll_controller() 则采用纯轮询方
式,另外一种数据接收方式是NAPI ( New API),其数据接收流程为“接收中断来临——>关闭
接收中断→以轮询方式接收所有数据包直到收空—->开启接收中断——>接收中断来临…..
内核中提供了如下与NAPI相关的API:
初始化一个NAPI
void netif_napi_add(struct net_device *dev, struct napi_struct *napi,
int (*poll)(struct napi_struct *, int), int weight)
{
INIT_LIST_HEAD(&napi->poll_list);
hrtimer_init(&napi->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED);
napi->timer.function = napi_watchdog;
init_gro_hash(napi);
napi->skb = NULL;
napi->poll = poll;
if (weight > NAPI_POLL_WEIGHT)
netdev_err_once(dev, "%s() called with weight %d\n", __func__,
weight);
napi->weight = weight;
list_add(&napi->dev_list, &dev->napi_list);
napi->dev = dev;
#ifdef CONFIG_NETPOLL
napi->poll_owner = -1;
#endif
set_bit(NAPI_STATE_SCHED, &napi->state);
napi_hash_add(napi);
}
其中的 poll 参数是NAPI要调度执行的轮询函数
删除NAPI
void netif_napi_del(struct napi_struct *napi)
{
might_sleep();
if (napi_hash_del(napi))
synchronize_net();
list_del_init(&napi->dev_list);
napi_free_frags(napi);
flush_gro_hash(napi);
napi->gro_bitmask = 0;
}
使能 和 禁止
static inline void napi_enable(struct napi_struct *n);
static inline void napi_disable(struct napi_struct *n);
napi_schedule 调用轮询实例的运行
static inline void napi_schedule(struct napi_struct *n);
在NAPI处理完成后调用
static inline void napi_complete(struct napi_struct *n);
0x03 设备驱动功能层
1.对于发包:主要是去填充 netdeivce结构中的属性成员,和 net_device_ops里的函数指针
2.对于收包:收包由中断引起,这里需要负责中断处理函数,
其中有两个 XXX_interrupt() : 中断类型判断等基本工作
XXX_rx(): 生成数据包递交给上层
3.对于特定的设备
我们还可以定义相关的私有数据和操作,并封装为-一个私有信息结构体
**xxx private,
让其指针赋值给net device 的私有成员**。
在xxx private 结构体中可包含设备的特殊属性和操作、自旋锁与信号量、定时器以及统计信息等,这都由工程师自定义。在驱动中,要用到私有数据的时候,则使用在netdevice.h中定义的接口:
static inline. vold *netdev_ priv(const struct net_ device *dev) ;
比如在驱动drivers/net/ethernet/davicom/dm9000.c的dm9000 probe() 函数中,使用alloc
etherdev(sizeof(struct board_ info)) 分配网络设备,board_info 结构体就成了这个网络设备的私
有数据,在其他函数中可以简单地提取这个私有数据,例如:
static int dm9000_ start_xmit (struct sk_ buff *skb, struct net_ device *dev)
{
unsigned long flags;
board_ info_t *db = netdey_ priv (dev) ;
...
}
0x04 网络设备的初始化
1 网络设备驱动的注册与注销
注册
int register_netdev(struct net_ device *dev);
注销
void unregister_netdev(struct net_ device *dev);
net_ device 的生成和成员的赋值并不一定要由工程帅亲目动于逐个完成,可以利用下值
的宏帮助我们填充:
#define alloc_netdev(sizeof_ priv, name, setup) \
alloc_netdev_ mqs(sizeof_ priv, name, setup, 1,1)
#define alloc_etherdev(sizeof_ priv) alloc_etherdev_mq{sizeof_ priv, 1)
#define alloc_etherdev_mq(sizeof_priv, count) alloc_etherdev_mqs (sizeof_priv,count, count)
其中 alloc_netdev,以及 alloc_etherdev宏引用alloc_netdev_mqs为
struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
unsigned char name_assign_type,
void (*setup)(struct net_device *),
unsigned int txqs, unsigned int rxqs);
会生成个 net_device 结构体
sizeof_priv:私有成员大小
name:设备名
(setup)(struct net_device ):setup函数指针 用于预置net_device从成员的值
txqs:发送子队列
rxqs:接收子队列
对应的free_netdev() 释放net_device结构体
2 网络设备的初始化
网络设备的初始化主要需要完成如下几个方面的工作。
●进行硬件上的准备工作,检查网络设备是否存在,如果存在,则检测设备所使用的硬件资源。
●进行软件接口上的准备工作,分配net_device结构体并对其数据和函数指针成员赋值。
●获得设备的私有信息指针并初始化各成员的值。如果私有信息中包括自旋锁或信号量等并发或同步机制,则需对其进行初始化。
对netdevice结构体成员及私有数据的赋值都可能需要与硬件初始化工作协同进行,即.
硬件检测出了相应的资源,需要根据检测结果填充net device 结构体成员和私有数据。
■ 模板
static int xxx_register (void)
{/*分配net_ _device结构体并对其成员赋值*/
xxx_dev = alloc_netdev (sizeof(struct xxx_ priv), "sn%d",xxx_init();
if (xxx_ dev == NULL)
.../*分配net_ device失败 */
/*注册net_ device结构体*/
if ((result = register_ netdev(xxx_ dev)))
...
}
static void xxx_unregister(void)
{
...
/*注销net_ device结构体*/
unregister_netdev(xxx_dev);
/*释放net_ device结构体*/
free_netdev(xxx_dev);
}
●探测xxx网络设备是否存在。探测的方法类似于数学上的“反证法”,即先假设存在设备xxx,访问该设备,如果设备的表现与预期- -致,就确定设备存在;否则,假设错误,设备xxx不存在。
●探测设备的具体硬件配置。一些设备驱动编写得非常通用,对于同类的设备使用统一的驱动,我们需要在初始化时探测设备的具体型号。另外,即便是同一-设备,在硬件上的配置也可能不一样,我们也可以探测设备所使用的硬件资源。
●申请设备所需要的硬件资源,如用request region() 函数进行I/O端口的申请等,但是这个过程可以放在设备的打开函数xxx open() 中完成。
3 网络设备的打开与释放
0x05 数据发送的流程
发包时,调用驱动程序提供的hard_start_transmit(),在初始化net_device时这个函数指针指向 xxx_tx()函数
■ 发送流程
①网络设备驱动程序从上层协议传递过来的sk, _buff 参数获得数据包的有效数据和长度,将有效数据放入临时缓冲区。
②对于以太网,如果有效数据的长度小于以太网冲突检测所要求数据帧的最小长度ETH ZLEN,则给临时缓冲区的末尾填充0.
③设置硬件的寄存器,驱使网络设备进行数据发送操作。
■ 模板
int xxx_tx (struct sk_ buff *skb, struct net_ _device. *dev)
{
int len;
char *data, shortpkt[ETH_ 2LEN];
if (xxx_send_availablel...)) { /*发送队列未满,可以发送*/
/* 获得有效数据指针和长度 */
data = skb->data;
len = skb->len;
if (len < ETH_ ZLEN)
{
/*如果帧长小于以大网帧最小长度,补0 */
memset(shortpkt, 0,ETH_ ZLEN) ;
memcpy (shortpkt,skb->data, skb->len) ;
len = ETH_ZLEN;
data = shortpkt;
}
dev->trans_start = jiffles; /*记录发送时间戳*/
if (avail) {/*. 设置硬件寄存器。让硬件把数据包发送出去*/
XXx_ _hw_ tx(data, len, dev) ;
} else {
netif_stop_queue(dev); //用来阻止队列发包
...
}
}
}
■ 传输超时
这个队列相当于TCP的“窗口”不一定发完
当发生传送超时,驱动必须在接口统计量中标记这个错误,并安排设备被复位到一个干净的能发送新报文的状态,当一个超时发生,用netif wake queue重启队列。
net dev> watchdlog. timeo= TX TIMEOUT:
static const struct met device. ops metdev, ops = {
...
ndo tx_ timcout = xxx_ tx_ timeout,
...
};
xxx_tx_timeout (struct net_ device *dev)
{
...
netif_ wake_queue(dev);
}
0x06 数据接收流程
■ 接收流程 NON-NAPI
中断方式
网络设备接收数据的主要方法是由中断引发设备的中断处理函数,中断处理函数判断中断类型,如果为接收中断,则读取接收到的数据,分配sk. _buffer数据结构和数据缓冲区,将接收到的数据拷入数据缓冲区,并调用netif_rx()将sk_ _buffer传递给上层协议。
■ 模板
static ingreturn_t net_interrup(int irq, void* der_id)
{
switch(status & ISQ EWENT_ MASK){
case ISQ RECEIVER EVENT:
/* Got a packet*/
net_rx(dev);
break;
....
}
}
static void net_rx(struct net_device *dev)
{
...
lenght = get_rev_len(...);
//分配新的套接字缓冲区
skb = dev_alloc_skb(length + 2);
//对齐
skb_ reserve(skb,2); /* longnrord align L3 header */
skb->dev = dev;
//读取硬件上接收的数据
insw(ioaddr + RX_FRAME_PORT, skb_put(skb, lenght), lenght >> 1);
if (lenght & 1)
skb->data[lenght - 1] = inw(ioaddr + RX_FRAME_PORT);
//获取上层协议类型
skb->proteocal = eth_ type_ trans(skb,dev);
//把数据包交给上层
netif_ rx(skb);
//记录时间戳
dev->last_rx = jiffies;
...
lp->stats.rx_packets++;
lp->stats.rX_bytes += length;
}
■ 接收流程 NAPI
轮询方式
如果是NAPI兼容的设备驱动,则可以通过poll 方式接收数据包。在这种情况下,我们需要为该设备驱动提供作为netif napi _add()参数的xxx poll() 函数,
■ 模板
static int xxx_poll (struct napi_struct *napi, int budget)
{
int npackets = 0;
struct sk_buff *skb;
struct xxx_priv *priv = container_of(napi, struct xxx_priv, napi);
struct xxx_packet *pkt;
while(npackets < budget && priv->rx_queue)
{
/*从队列中取出数据包*/
pkt = xxx_dequeue_buf(dev);
/*接下来的处理和中断触发的数据包接收一致*/
skb = dev_alloc_skb(pkt->datalen + 2);
...
skb_reserve(skb, 2);
memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
skb->dev = dev;
skb->proteocal = eth_ type_ trans(skb,dev);
/*调用netif_receive_skb, 而不是net_rx, 将数据包交给上层协议*/
netif_receive_skb(skb);
/*更改统计数据*/
priv->stats.rx_packets++;
priv->stats.rx_bytes += pkt->datalen;
xxx_release_buffer(pkt);
npackets++;
}
if (npackets < budget)
{
napi_complete(napi);
xxx_enable_rx_int (...); /*再次启动网络设备的接收中断*/
}
return npackets;
}
虽然NAPI兼容的设备驱动以xxx_poll()方式接收数据包,但是仍然需要首次数据包接收中断来触发这个过程。与数据包的中断接收方式不同的是,以轮询方式接收数据包时,当第一次中断发生后,中断处理程序要禁止设备的数据包接收中断并调度NAPI
static void xxx_interrupt(int irq, void *dev_id)
{
switch(status & ISQ_EVENT_MASK)
{
case ISQ_RECEIVER & ISQ_EVENT_MASK:
.../*获得数据包*/
xxx_disable_rx_int(...); /*禁止接收中断*/
napi_schedule(&priv->napi);
break;
... /*其他类型的中断*/
}
}
与NAPI一些其他的工作
1)在私有数据结构体(如xxx priv) 中增加一个成员:
struct napi_struct napi;
在代码中就可以方便地使用container_of()通过NAPI成员反向获得对应的 xxx_ priv
指针。
2)通常会在设备驱动初始化时调用:
netif_napi_add(dev, napi, xxx_ poll,XXX_NET_NAPI_WEIGHT) ;
3)通常会在netdevice结构体的open()和stop()成员函数中分别调用napi_enable()和napi disable()
0x07 网络连接状态
网络适配器硬件电路可以检测出链路上是否有载波,载波反映了网络的连接是否正常。
网络设备驱动可以通过netif carrier on() 和netif carrier off() 函数改变设备的连接状态,如果驱动检测到连接状态发生变化,也应该以netif carrier on() 和netif carrier. off() 函数显式地通知内核。
除了netif carrier on()和netif carrier off() 函数以外,另一个函数netif carrier ok() 可用于向调用者返回链路上的载波信号是否存在。
这几个函数都接收一个net device 设备结构体指针作为参数,原型分别为:
void netif_carrier_on (struct net_device *dev);
void netif_carrier_off(struct net_device *dev);
int netif_carrier_ok (struct net_device *dev);
用 中断检测链路状态,用定时器,读取物理设备相关寄存器,获得载波状态,更新连接状态,这个定时器在打开函数中完成
0x08 参数设置和统计
网络设备驱动程序提供一些供系统进行参数设置或读取设备相关信息的方法。
(感觉用来debug的,偷一波懒)
参考 《Linux设备驱动开发详解》P378 - P379
0x09 DM9000网卡设备驱动实例
源码版本: 5.9
DM9000是开发板采用的网络芯片,是一个高度集成且功耗很低的高速网络控制器,可以和CPU直连,支持10/100MB以太网连接,芯片内部自带4KB双字节的SRAM (3KB用来发送,13KB 用来接收)。针对不同的处理器,接口支持8位、16 位和32位。DM9000一般直接挂在外面的内存总线上。
DM9000网卡驱动位于内核源代码的drivers/net/ethernet/davicom/dm9000.c中,它基于平台驱动架构,下文抽取了它的主干。其核心工作是实现了前文所述net device结构体中的hard_start_xmit(). open(). stop() set_multicast _list(), do_ioctl(). tx_timeout() 等成员函数,并借助中断辅助进行网络数据包的收发,另外它也实现了ethtool ops 中的成员函数。
#ifdef CONFIG_OF
static const struct of_device_id dm9000_of_matches[] = {
{ .compatible = "davicom,dm9000", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dm9000_of_matches);
#endif
static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.pm = &dm9000_drv_pm_ops,
.of_match_table = of_match_ptr(dm9000_of_matches),
},
.probe = dm9000_probe,
.remove = dm9000_drv_remove,
};
果不其然,封装成平台设备
根据 dm9000_of_matches, match到设备
进入probe
/*
* Search DM9000 board, allocate space and register it
*/
static int
dm9000_probe(struct platform_device *pdev)
{
struct dm9000_plat_data *pdata = dev_get_platdata(&pdev->dev);
struct board_info *db; /* Point a board information structure */
struct net_device *ndev;
struct device *dev = &pdev->dev;
const unsigned char *mac_src;
int ret = 0;
int iosize;
int i;
u32 id_val;
int reset_gpios;
enum of_gpio_flags flags;
struct regulator *power;
bool inv_mac_addr = false;
......
/* Init network device */
ndev = alloc_etherdev(sizeof(struct board_info));//创造一个 net_device
if (!ndev)
return -ENOMEM;
......
/* setup board info structure */
db = netdev_priv(ndev); //得到设备的私有数据
/* from this point we assume that we have found a DM9000 */
ndev->netdev_ops = &dm9000_netdev_ops; //和file_operiation挂上
ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
ndev->ethtool_ops = &dm9000_ethtool_ops;
platform_set_drvdata(pdev, ndev);
ret = register_netdev(ndev); //net_device设备初始化
}
主干:创造一个net_device 和file_operiation挂上,最后net_device初始化
之后看一下file_operiationfile_operiation &dm9000_netdev_ops
static const struct net_device_ops dm9000_netdev_ops = {
.ndo_open = dm9000_open, //设备开始
.ndo_stop = dm9000_stop, //设备停止
.ndo_start_xmit = dm9000_start_xmit, //数据包发送函数
.ndo_tx_timeout = dm9000_timeout, //发送包超时
.ndo_set_rx_mode = dm9000_hash_table,
.ndo_do_ioctl = dm9000_ioctl, //参数设置
.ndo_set_features = dm9000_set_features,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = eth_mac_addr,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = dm9000_poll_controller, //纯poll,用来debug
#endif
};
这就是整个 linux网络设备驱动的主干 (主要是发包的函数)
分别来看
dm9000_open
/*
* Open the interface.
* The interface is opened whenever "ifconfig" actives it.
*/
static int
dm9000_open(struct net_device *dev)
{
struct board_info *db = netdev_priv(dev);
......
/* Initialize DM9000 board */
dm9000_init_dm9000(dev); //初始化dm9000
if (request_irq(dev->irq, dm9000_interrupt, irq_flags, dev->name, dev))
return -EAGAIN;
/* Now that we have an interrupt handler hooked up we can unmask
* our interrupts
*/
dm9000_unmask_interrupts(db);
/* Init driver variable */
db->dbug_cnt = 0;
mii_check_media(&db->mii, netif_msg_link(db), 1);
netif_start_queue(dev); //打开检测网络连接的定时器
/* Poll initial link status */
schedule_delayed_work(&db->phy_poll, 1);
return 0;
}
首先初始化 dm9000设备(设置一些底层寄存器) ,之后检测 request_irq ,之后检测网络连接状态,初始化poll
dm9000_stop
static int
dm9000_stop(struct net_device *ndev)
{
struct board_info *db = netdev_priv(ndev);
......
netif_stop_queue(ndev); //关闭检测网络连接的定时器
netif_carrier_off(ndev); //关闭检测载波
/* free interrupt */
free_irq(ndev->irq, ndev);//关中断
dm9000_shutdown(ndev);
return 0;
}
dm9000_start_xmit
/*
* Hardware start transmission.
* Send a packet to media from the upper layer.
*/
static int
dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
unsigned long flags;
struct board_info *db = netdev_priv(dev); //私有数据
......
/* Move data to DM9000 TX RAM */
writeb(DM9000_MWCMD, db->io_addr); //把包移动到网卡的ram里
......
/* TX control: First packet immediately send, second packet queue */
if (db->tx_pkt_cnt == 1) {
dm9000_send_packet(dev, skb->ip_summed, skb->len);
} else {
/* Second packet */
db->queue_pkt_len = skb->len;
db->queue_ip_summed = skb->ip_summed;
netif_stop_queue(dev);
}
spin_unlock_irqrestore(&db->lock, flags);
/* free this SKB */
dev_consume_skb_any(skb);
return NETDEV_TX_OK;
}
可以看到,发包一个一个发
dm9000_timeout
发送超时
/* Our watchdog timed out. Called by the networking layer */
static void dm9000_timeout(struct net_device *dev, unsigned int txqueue)
{
struct board_info *db = netdev_priv(dev);
u8 reg_save;
unsigned long flags;
/* Save previous register address */
spin_lock_irqsave(&db->lock, flags);
db->in_timeout = 1;
reg_save = readb(db->io_addr);
netif_stop_queue(dev); //停止发包队列
dm9000_init_dm9000(dev);//初始硬件设备
dm9000_unmask_interrupts(db);
/* We can accept TX packets again */
netif_trans_update(dev); /* prevent tx timeout */
netif_wake_queue(dev); //唤醒队列
/* Restore previous register address */
writeb(reg_save, db->io_addr);
db->in_timeout = 0;
spin_unlock_irqrestore(&db->lock, flags);
}
中断接收包
dm9000_interrupt 中断处理函数
static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
{
......
/* Received the coming packet */
if (int_status & ISR_PRS)
dm9000_rx(dev); //判断中断类型,为接收中断
/* Transmit Interrupt check */
if (int_status & ISR_PTS)
dm9000_tx_done(dev, db);
......
spin_unlock_irqrestore(&db->lock, flags);
return IRQ_HANDLED;
}
来看接收中断函数的处理
dm9000_rx
/*
* Received a packet and pass to upper layer
*/
static void
dm9000_rx(struct net_device *dev)
{
......
/* Check packet ready or not */
do {
......
/* Move data from DM9000 */
if (GoodPacket &&
((skb = netdev_alloc_skb(dev, RxLen + 4)) != NULL)) {
skb_reserve(skb, 2);
rdptr = skb_put(skb, RxLen - 4);
/* Read received packet from RX SRAM */
(db->inblk)(db->io_data, rdptr, RxLen);
dev->stats.rx_bytes += RxLen;
/* Pass to upper layer */
skb->protocol = eth_type_trans(skb, dev);
if (dev->features & NETIF_F_RXCSUM) {
if ((((rxbyte & 0x1c) << 3) & rxbyte) == 0)
skb->ip_summed = CHECKSUM_UNNECESSARY;
else
skb_checksum_none_assert(skb);
}
netif_rx(skb); //调用
dev->stats.rx_packets++;
} else {
/* need to dump the packet's data */
(db->dumpblk)(db->io_data, RxLen);
}
} while (rxbyte & DM9000_PKT_RDY);
}
DM9000驱动的实现与具体CPU无关,在将该驱动移植到特定电路板时,只需要在板文件中为与板上DM9000对应的平台设备的寄存器和数据基地址进行赋值,并指定正确的
IRQ资源即可。
0X0A 其他
两台主机间建立UDP时所走过的函数
暂时还没有探究,准备在linux源码分析中写
INTERNET协议栈
来看一下驱动在整个Linux网络系统的位置
(在视频上扒下来的)
0x0B 总结
net_device
sk_buff