Linux网络设备驱动层设计

本篇主要描述linux网络设备驱动层的各主要函数和数据结构:

包括设备注册和注销,设备初始化,数据包接收发送函数,打开和释放函数等。

1. 网络设备驱动的注册和注销

register_netdev(struct net_device *dev) --- 注册一个网络设备

函数定义如下:

/**
 *	register_netdev	- register a network device
 *	@dev: device to register
 *
 *	Take a completed network device structure and add it to the kernel
 *	interfaces. A %NETDEV_REGISTER message is sent to the netdev notifier
 *	chain. 0 is returned on success. A negative errno code is returned
 *	on a failure to set up the device, or if the name is a duplicate.
 *
 *	This is a wrapper around register_netdevice that takes the rtnl semaphore
 *	and expands the device name if you passed a format string to
 *	alloc_netdev.
 */
int register_netdev(struct net_device *dev)
{
	int err;

	rtnl_lock();

	/*
	 * If the name is a format string the caller wants us to do a
	 * name allocation.
	 */
	if (strchr(dev->name, '%')) {
		err = dev_alloc_name(dev, dev->name);
		if (err < 0)
			goto out;
	}

	err = register_netdevice(dev);  //其实是调用了这个函数
out:
	rtnl_unlock();
	return err;
}
EXPORT_SYMBOL(register_netdev);
void unregister_netdev(struct net_device *dev); --- 注销一个网络设备。

/**
 *	unregister_netdev - remove device from the kernel
 *	@dev: device
 *
 *	This function shuts down a device interface and removes it
 *	from the kernel tables.
 *
 *	This is just a wrapper for unregister_netdevice that takes
 *	the rtnl semaphore.  In general you want to use this and not
 *	unregister_netdevice.
 */
void unregister_netdev(struct net_device *dev)
{
	rtnl_lock();
	unregister_netdevice(dev);  //其实是调用了这个函数
	rtnl_unlock();
}
EXPORT_SYMBOL(unregister_netdev);

net_device 的生成和成员的赋值,并不一定要由工程师逐个赋值,可以利用下面函数填充:

struct net_device *alloc_netdev(int sizeof_priv, const char *name, void(*setup)(struct netdevice *));

alloc_netdev()函数生成一个net_device结构体,对其成员赋值并返回该结构体指针。

参数:

sizeof_priv --- 设备私有成员 net_device->priv的大小.

name --- 设备名

setup() --- 函数指针,该函数用于设置 net_device 成员的值,其参数也是net_device.

alloc_netdev()定义如下:

#define alloc_netdev(sizeof_priv, name, setup) \
	alloc_netdev_mq(sizeof_priv, name, setup, 1)
extern struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name,
				       void (*setup)(struct net_device *),
				       unsigned int queue_count);
/**
 *	alloc_netdev_mq - allocate network device
 *	@sizeof_priv:	size of private data to allocate space for
 *	@name:		device name format string
 *	@setup:		callback to initialize device
 *	@queue_count:	the number of subqueues to allocate
 *
 *	Allocates a struct net_device with private data area for driver use
 *	and performs basic initialization.  Also allocates subquue structs
 *	for each queue on the device at the end of the netdevice.
 */
struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name,
		void (*setup)(struct net_device *), unsigned int queue_count)
{
	struct net_device *dev;
	size_t alloc_size;
	struct net_device *p;

	BUG_ON(strlen(name) >= sizeof(dev->name));

	if (queue_count < 1) {
		pr_err("alloc_netdev: Unable to allocate device "
		       "with zero queues.\n");
		return NULL;
	}

	alloc_size = sizeof(struct net_device);
	if (sizeof_priv) {
		/* ensure 32-byte alignment of private area */
		alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);
		alloc_size += sizeof_priv;
	}
	/* ensure 32-byte alignment of whole construct */
	alloc_size += NETDEV_ALIGN - 1;

	p = kzalloc(alloc_size, GFP_KERNEL);
	if (!p) {
		printk(KERN_ERR "alloc_netdev: Unable to allocate device.\n");
		return NULL;
	}

	dev = PTR_ALIGN(p, NETDEV_ALIGN);
	dev->padded = (char *)dev - (char *)p;

	dev->pcpu_refcnt = alloc_percpu(int);
	if (!dev->pcpu_refcnt)
		goto free_p;

	if (dev_addr_init(dev))
		goto free_pcpu;

	dev_mc_init(dev);
	dev_uc_init(dev);

	dev_net_set(dev, &init_net);

	dev->num_tx_queues = queue_count;
	dev->real_num_tx_queues = queue_count;

#ifdef CONFIG_RPS
	dev->num_rx_queues = queue_count;
	dev->real_num_rx_queues = queue_count;
#endif

	dev->gso_max_size = GSO_MAX_SIZE;

	INIT_LIST_HEAD(&dev->ethtool_ntuple_list.list);
	dev->ethtool_ntuple_list.count = 0;
	INIT_LIST_HEAD(&dev->napi_list);
	INIT_LIST_HEAD(&dev->unreg_list);
	INIT_LIST_HEAD(&dev->link_watch_list);
	dev->priv_flags = IFF_XMIT_DST_RELEASE;
	setup(dev);
	strcpy(dev->name, name);
	return dev;

free_pcpu:
	free_percpu(dev->pcpu_refcnt);
free_p:
	kfree(p);
	return NULL;
}
EXPORT_SYMBOL(alloc_netdev_mq);
struct net_device *alloc_etherdev(int sizeof_priv);

alloc_etherdev() 是 alloc_netdev() 针对以太网设备的“快捷”函数,源代码如下:

#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
extern struct net_device *alloc_etherdev_mq(int sizeof_priv, unsigned int queue_count);
/**
 * alloc_etherdev_mq - Allocates and sets up an Ethernet device
 * @sizeof_priv: Size of additional driver-private structure to be allocated
 *	for this Ethernet device
 * @queue_count: The number of queues this device has.
 *
 * Fill in the fields of the device structure with Ethernet-generic
 * values. Basically does everything except registering the device.
 *
 * Constructs a new net device, complete with a private data area of
 * size (sizeof_priv).  A 32-byte (not bit) alignment is enforced for
 * this private data area.
 */

struct net_device *alloc_etherdev_mq(int sizeof_priv, unsigned int queue_count)
{
	return alloc_netdev_mq(sizeof_priv, "eth%d", ether_setup, queue_count);
}
EXPORT_SYMBOL(alloc_etherdev_mq);
该函数的第三个参数是 ether_setup()的函数指针。

void ether_setup(struct net_device *dev); --- ether_setup()函数是Linux内核提供的, 专门针对以太网设备的net_device成员快速赋值的函数:

/**
 * ether_setup - setup Ethernet network device
 * @dev: network device
 * Fill in the fields of the device structure with Ethernet-generic values.
 */
void ether_setup(struct net_device *dev)
{
	dev->header_ops		= ð_header_ops;
	dev->type		= ARPHRD_ETHER;
	dev->hard_header_len 	= ETH_HLEN;
	dev->mtu		= ETH_DATA_LEN;
	dev->addr_len		= ETH_ALEN;
	dev->tx_queue_len	= 1000;	/* Ethernet wants good queues */
	dev->flags		= IFF_BROADCAST|IFF_MULTICAST;

	memset(dev->broadcast, 0xFF, ETH_ALEN);

}
EXPORT_SYMBOL(ether_setup);
void free_netdevice(struct net_device *dev);

释放net_device结构体,与alloc_netdev()/alloc_etherdev() 函数相反。

定义如下:

/**
 *	free_netdev - free network device
 *	@dev: device
 *
 *	This function does the last stage of destroying an allocated device
 * 	interface. The reference to the device object is released.
 *	If this is the last reference then it will be freed.
 */
void free_netdev(struct net_device *dev)
{
	struct napi_struct *p, *n;

	release_net(dev_net(dev));

	kfree(dev->_tx);

	kfree(rcu_dereference_raw(dev->ingress_queue));

	/* Flush device addresses */
	dev_addr_flush(dev);

	/* Clear ethtool n-tuple list */
	ethtool_ntuple_flush(dev);

	list_for_each_entry_safe(p, n, &dev->napi_list, dev_list)
		netif_napi_del(p);

	free_percpu(dev->pcpu_refcnt);
	dev->pcpu_refcnt = NULL;

	/*  Compatibility with error handling in drivers */
	if (dev->reg_state == NETREG_UNINITIALIZED) {
		kfree((char *)dev - dev->padded);
		return;
	}

	BUG_ON(dev->reg_state != NETREG_UNREGISTERED);
	dev->reg_state = NETREG_RELEASED;

	/* will free via device release */
	put_device(&dev->dev);
}
EXPORT_SYMBOL(free_netdev);
net_device 结构体的分配和网络设备驱动的注册,需要在网络设备驱动模块加载函数中进行;

而net_device 结构体的释放和网络设备驱动的注销,需要在网络设备驱动的卸载函数中完成。

示例如下:

int xxx_module_init(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))
   ... //注册失败处理

...
}
void xxx_cleanup(void)
{
...

// 注销 net_device 结构体
unregister_netdev(xxx_dev);

//释放 net_device 结构体
free_netdev(xxx_dev);
}
2. 网络设备的初始化

网络设备初始化需完成如下几项工作:

--- 检查网络设备是否存在,如果存在,检测设备所使用的硬件资源。

--- 分配 net_device 并对其数据和成员赋值

--- 获得设备私有信息,并初始化其各成员。如私有信息中有自旋锁或信号量等并发或同步机制,需要对其进行初始化。

示例代码如下:

void xxx_init(struct net_device *dev)
{

struct xxx_priv *priv;  //设备私有信息结构体

xxx_hw_init();  //检查硬件设备是否存在,及设备所使用的硬件资源

ether_setup(dev);  //初始化以太网设备的 net_device 成员

//设置设备成员函数指针:
dev->open = xxx_open;
dev->stop = xxx_release;
dev->set_config = xxx_config;
dev->hard_start_xmit = xxx_tx;
dev->do_ioctl = xxx_ioctl;
dev->get_stats = xxx_stats;
dev-> change_mtu = xxx_change_mtu;
dev->rebuild_header = xxx_rebuild_header;
dev->hard_header = xxx_header;
dev->tx_timeout = xxx_tx_timeout;
dev->watchdog_timeo = timeout;

//如果使用NAPI, 则设置poll函数
if(use_napi)
{
    dev->poll = xxx_poll;
}

//取得私有信息,并初始化
priv = netdev_priv(dev);
... //初始化设备私有数据区

}

xxx_hw_init() 函数完成硬件相关的初始化操作。

内容如下:

--- 探测 xxx 网络设备是否存在。

--- 探测 xxx 网络设备的具体硬件配置。

--- 申请设备所需要的硬件资源,如request_region()函数进行I/O端口的申请等,这个过程可以放在设备的打开函数xxx_open()中完成。

3. 网络设备的打开与释放

网络设备的打开函数需要完成如下工作:

--- 使能设备所使用的硬件资源,如申请 I/O区域,中断 和DMA通道等。

--- 调用Linux内核提供的 netif_start_queue()函数,激活设备的发送队列。

void netif_start_queue(struct net_device *dev); 

定义于include/linux/netdevice.h中, 如下:

/**
 *	netif_start_queue - allow transmit
 *	@dev: network device
 *
 *	Allow upper layers to call the device hard_start_xmit routine.
 */
static inline void netif_start_queue(struct net_device *dev)
{
	netif_tx_start_queue(netdev_get_tx_queue(dev, 0));
}
static inline void netif_tx_start_queue(struct netdev_queue *dev_queue)
{
	clear_bit(__QUEUE_STATE_XOFF, &dev_queue->state);
}
网络设备的关闭函数需要完成如下工作:

---  调用 Linux 内核提供的 netif_stop_queue()函数,停止设备传输工作。

--- 释放设备所使用的 I/O 区域,中断和 DMA 资源。

void netif_stop_queue(struct net_device *dev);

定义如下:

/**
 *	netif_stop_queue - stop transmitted packets
 *	@dev: network device
 *
 *	Stop upper layers calling the device hard_start_xmit routine.
 *	Used for flow control when transmit resources are unavailable.
 */
static inline void netif_stop_queue(struct net_device *dev)
{
	netif_tx_stop_queue(netdev_get_tx_queue(dev, 0));
}
static inline void netif_tx_stop_queue(struct netdev_queue *dev_queue)
{
	if (WARN_ON(!dev_queue)) {
		printk(KERN_INFO "netif_stop_queue() cannot be called before "
		       "register_netdev()");
		return;
	}
	set_bit(__QUEUE_STATE_XOFF, &dev_queue->state);
}
网络设备打开和关闭函数示例如下:

int xxx_open(struct net_device *dev)
{

//申请端口, irq等,类似于fops->open
ret = request_irq(dev->irq, &xxx_interrupt, 0, dev->name, dev);   //注册中断,xxx_interrupt 是中断处理程序  

...
netif_start_queue(dev);
...
}
int xxx_release(struct net_device *dev)
{

//释放端口,irq,类似于fops->close
free_irq(dev->irq, dev);

...
netif_stop_queue(dev);   //cannot transmit anymore
...
}
4. 数据发送流程

Linux网络子系统在发送数据包时,会调用net_device->hard_start_transmit()函数,用于启动数据包的发送。

设备初始化时,这个函数需被初始化成指向设备的 xxx_tx() 函数.

数据包发送流程如下:

a. 上层会传递过来sk_buff参数,从中可以获得数据包有效数据和长度,将有效数据放入临时缓冲区。

b. 对于以太网,如果有效数据长度小于以太网所要求的数据帧最小长度 ETH_ZLEN, 则给临时缓冲区末尾填充0.

c. 设置硬件寄存器,驱使网络设备进行数据发送操作

发送函数示例代码如下:

int xxx_tx(struct sk_buff *skb, struct net_device *dev)
{
int len;
char *data, shortpkt[ETH_ZLEN];

//获得有效数据指针和长度
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 = jiffies;    //记录发送时间戳

//设置硬件寄存器,让硬件把数据包发送出去
xxx_hw_tx(data, len, dev);

}

当数据传输超时时,意味着当前的发送操作失败,此时,会调用数据包发送超时处理函数 xxx_tx_timeout().

xxx_tx_timeout() 函数会调用 linux内核提供的netif_wake_queue()函数,重新启动设备发送队列,代码如下所示:

void xxx_tx_timeout(struct net_device *dev)
{
...
netif_wake_queue(dev);
}
/**
 *	netif_wake_queue - restart transmit
 *	@dev: network device
 *
 *	Allow upper layers to call the device hard_start_xmit routine.
 *	Used for flow control when transmit resources are available.
 */
static inline void netif_wake_queue(struct net_device *dev)
{
	netif_tx_wake_queue(netdev_get_tx_queue(dev, 0));
}
static inline void netif_tx_wake_queue(struct netdev_queue *dev_queue)
{
#ifdef CONFIG_NETPOLL_TRAP
	if (netpoll_trap()) {
		netif_tx_start_queue(dev_queue);
		return;
	}
#endif
	if (test_and_clear_bit(__QUEUE_STATE_XOFF, &dev_queue->state))
		__netif_schedule(dev_queue->qdisc);
}

5. 数据接收流程

网络设备接收数据的主要方法是由中断引发设备的中断处理函数,中断处理函数会判断中断类型,

如果为接收中断,则读取接收到的数据,分配sk_buff数据结构和数据缓冲区,将接收到的数据复制到数据缓冲区,

并调用netif_rx()函数,将sk_buff 传递给上层协议。

示例代码如下:

static void xxx_interrupt (int irq, void *dev_id, struct pt_regs *regs)
{
...

switch (status & ISQ_EVENT_MASK) {
    case ISQ_RECEIVER_EVENT:   //判断中断类型为数据包接收中断
        //获取数据包
        xxx_rx(dev);
        break;
    //其他类型的中断
    }
}

xxx_rx() --- 完成数据包的接收工作

示例代码如下:

static void xxx_rx(struct xxx_device *dev)
{
...
length = get_rev_len(...);  //从硬件读取接收数据包的有效数据长度

//分配新的套接字缓冲区
skb = dev_alloc_skb(length + 2); //分配sk_buff 和 数据缓冲区
skb_reserve(skb, 2);   //对其,预留2个字节的协议头存储区
skb->dev = dev;

//读取硬件上接收到的数据,并放入数据缓冲区
insw(ioaddr + RX_FRAME_PORT, skb_put(skb, length), length >> 1);
if (length & 1)
    skb->data[length - 1] = inw(ioaddr + RX_FRAME_PORT);

//获取上层协议类型
skb->protocol = eth_type_trans(skb, dev);  //解析接收数据包上层协议的类型

//把数据包传递给上层
netif_rx(skb);

//记录时间戳
dev->last_rx = jiffies;
}
如果是NAPI兼容的设备驱动,还可以通过poll方式接收数据包,这种情况我们需为该设备驱动提供xxx_poll() 函数

xxx_poll()代码如下所示:

static int xxx_poll(struct net_device *dev, int *budget)  //budget是在初始化阶段分配给接口的weight值
{
	int npackets = 0, quota = min(dev->quota, *budget);  //dev->quota 是当前CPU能从所有接口中接收数据包的最大数目
                                                             //poll函数必须接收两者的最小值,表示轮询函数本次要处理的数据包个数。
	struct sk_buff *skb;
	struct sk_buff *priv = netdev_priv(dev);
	struct xxx_packet *pkt;

	while (npackets < quota && priv ->rx_queue){       //循环读取设备的缓冲区,读取数据包并传递给上层
                                                           //当priv->rx_queue = NULL 时,意味着网络设备接收缓冲区中的数据包被读取完毕,则一个轮询结束。
		//从队列中取出数据包
		pkt = xxx_dequeue_buf(dev);

                //接下来的处理和中断触发的数据包接收一致
		skb = dev_alloc_skb(pkt->datalen + 2);
		if (!skb){
			...
			continue;
		}
		skb_reserve(skb, 2);
		memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
		skb->dev = dev;
		skb->protocol = eth_type_trans(skb, dev);

                //调用netif_receive_skb 而不是 netif_rx将数据包传递给上层协议,这里体现了轮询机制和中断机制的差别
		netif_receive_skb(skb);

                //更改统计数据
		priv->stats.rx_packets++;
		priv->stats.rx_bytes += pkt->datalen;
		xxx_release_buffer(pkt);
	}

        //处理完所有数据包
	*budget -= npackets;
	dev->quota -=npackets;

	if (!priv->rx_queue){
		netif_rx_complete(dev);
		xxx_enable_rx_int(...);  //再次使能网络设备的接收中断
		return 0;
	}
	return 1;
}
int netif_receive_skb(struct sk_buff *skb);

这里使用netif_receive_skb()来接收数据包,而不是netif_rx(), 来将数据提交给上层协议,这里体现了中断处理机制和轮询机制之间的差别。

定义如下:

/**
 *	netif_receive_skb - process receive buffer from network
 *	@skb: buffer to process
 *
 *	netif_receive_skb() is the main receive data processing function.
 *	It always succeeds. The buffer may be dropped during processing
 *	for congestion control or by the protocol layers.
 *
 *	This function may only be called from softirq context and interrupts
 *	should be enabled.
 *
 *	Return values (usually ignored):
 *	NET_RX_SUCCESS: no congestion
 *	NET_RX_DROP: packet was dropped
 */
int netif_receive_skb(struct sk_buff *skb)
{
	if (netdev_tstamp_prequeue)
		net_timestamp_check(skb);

	if (skb_defer_rx_timestamp(skb))
		return NET_RX_SUCCESS;

#ifdef CONFIG_RPS
	{
		struct rps_dev_flow voidflow, *rflow = &voidflow;
		int cpu, ret;

		rcu_read_lock();

		cpu = get_rps_cpu(skb->dev, skb, &rflow);

		if (cpu >= 0) {
			ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
			rcu_read_unlock();
		} else {
			rcu_read_unlock();
			ret = __netif_receive_skb(skb);
		}

		return ret;
	}
#else
	return __netif_receive_skb(skb);
#endif
}
EXPORT_SYMBOL(netif_receive_skb);
虽然NAPI兼容的设备驱动以poll方式接收数据包,但首次数据包接收时,仍需要中断来触发poll过程,同时中断处理程序要禁止设备的数据包接收中断,代码如下所示:

static void xxx_poll_interrupt(int irq, void *dev_id, struct pt_regs *reg){
	switch (status & ISQ_EVENT_MASK){
		case ISQ_RECEIVE_EVENT:
			... //获取数据包
			xxx_disable_rx_int(...);  //禁止接收中断
			netif_rx_disable(dev);
			break;
		... //其他类型中断
	}
}
netif_rx_schedule() 函数中断程序调用,该函数将设备的poll方法添加到网络层的poll处理队列中,排队并准备接收数据包,最终触发一个NET_RX_SOFTIRQ软中断,通知网络层接收数据包。
NAPI驱动程序各部分的调用关系,如下图所示:


6. 网络连接状态

网络适配器硬件电路上可以检测出链路上是否有载波,载波反映了网络连接是否正常。

网络设备驱动可以通过 netif_carrier_on() 和 netif_carrier_off()函数来改变设备的连接状态,并显式的通知内核。

netif_carrier_ok() 可用来查询链路上的载波信号是否存在。

定义如下:

/**
 *	netif_carrier_on - set carrier
 *	@dev: network device
 *
 * Device has detected that carrier.
 */
void netif_carrier_on(struct net_device *dev)
{
	if (test_and_clear_bit(__LINK_STATE_NOCARRIER, &dev->state)) {
		if (dev->reg_state == NETREG_UNINITIALIZED)
			return;
		linkwatch_fire_event(dev);
		if (netif_running(dev))
			__netdev_watchdog_up(dev);
	}
}
EXPORT_SYMBOL(netif_carrier_on);
/**
 *	netif_carrier_off - clear carrier
 *	@dev: network device
 *
 * Device has detected loss of carrier.
 */
void netif_carrier_off(struct net_device *dev)
{
	if (!test_and_set_bit(__LINK_STATE_NOCARRIER, &dev->state)) {
		if (dev->reg_state == NETREG_UNINITIALIZED)
			return;
		linkwatch_fire_event(dev);
	}
}
EXPORT_SYMBOL(netif_carrier_off);
/**
 *	netif_carrier_ok - test if carrier present
 *	@dev: network device
 *
 * Check if carrier is present on device
 */
static inline int netif_carrier_ok(const struct net_device *dev)
{
	return !test_bit(__LINK_STATE_NOCARRIER, &dev->state);
}
网络设备驱动程序中,经常设置一个定时器来对链路状态进行周期性检查。

当定时器到期之后,在定时器处理函数中读取网络设备的相关寄存器获得载波状态,从而更新设备的连接状态。

代码如下所示:

static void xxx_timer(unsigned long data)
{
	struct net_device *dev = (struct net_device *)data;
	u16 link;
	...
	if (!(dev->flags & IFF_UP)){
		goto set_timer;
	}

	//获取物理上的连接状态
	if (link = xxx_chk_link(dev)){       //用于读取网络设备硬件相关的寄存器,以获得连接状态,具体由硬件决定
		if (!(dev->flags) & IFF_RUNNING){
			netif_carrier_on(dev);
			dev->flags |= IFF_RUNNING;
			printk(KERN_DEBUG "%s: link up\n", dev->name);
		}
	}
	else {
		if (dev->flags & IFF_RUNNIG) {
			netif_carrier_off(dev);
			dev->flags &= ~IFF_RUNNING;
			printk(KERN_DEBUG "%s: link down\n", dev->name);
		}
	}

	//set_timer():
	priv->timer.expires = jiffies + 1*HZ;
	priv ->timer.data = (unsigned long) dev;
	priv -> timer.function = &xxx_timer;  //timer handler
	add_timer(&priv->timer);	
}
启动定时器最适合在设备的open函数中完成:

static int xxx_open(struct net_device *dev){
	struct xxx_priv *priv = (struct xxx_priv *)dev->priv;
	...
	priv->timer.expires = jiffies + 3 * HZ;
	priv->timer.data = (unsigned long) dev;
	priv->timer.function = &xxx_timer;  //timer handler
	add_timer(&priv->timer);
	...
}
7. 参数设置和统计数据

7.1 设置mac地址

当用户调用ioctl()函数,并指定SIOCSIFHWADDR命令时, 就是要设置该网络设备的mac 地址了,代码如下所示:

static int set_mac_address(struct net_device *dev, void *addr)
{
	if(netif_running(dev)){
		return -EBUSY;  //设备忙,此时不允许设置mac addr
	}

	//设置以太网mac地址
	xxx_set_mac(dev, addr);
	return 0;
}
netif_running(struct net_device dev);

定义如下:

/**
 *	netif_running - test if up
 *	@dev: network device
 *
 *	Test if the device has been brought up.
 */
static inline int netif_running(const struct net_device *dev)
{
	return test_bit(__LINK_STATE_START, &dev->state);
}
7.2 ifconfig

当用户调用ioctl, 并指定SIOCSIFMAP命令时, ifconfig就会引发这一调用,系统会调用 set_config()函数。

set_config()函数,会传递一个ifmap结构体,该结构体中主要包含用户欲设置的设备要使用的 I/O 地址,中断等信息。

ifmap 定义如下:

/*
 *	Device mapping structure. I'd just gone off and designed a 
 *	beautiful scheme using only loadable modules with arguments
 *	for driver options and along come the PCMCIA people 8)
 *
 *	Ah well. The get() side of this is good for WDSETUP, and it'll
 *	be handy for debugging things. The set side is fine for now and
 *	being very small might be worth keeping for clean configuration.
 */

struct ifmap {
	unsigned long mem_start;
	unsigned long mem_end;
	unsigned short base_addr; 
	unsigned char irq;
	unsigned char dma;
	unsigned char port;
	/* 3 bytes spare */
};
网络设备驱动中set_config()函数的实现实例,如下所示:

int xxx_config(struct net_device *dev, struct ifmap *map)
{
	if (netif_running(dev))     //不能设置运行中的设备
		return -EBUSY;

	//假设不允许改变I/O地址
	if (map->base_addr != dev->base_addr) {    //拒绝I/O地址修改
		printk(KERN_WARNING "xxx: can't change I/O address \n");
		return -EOPNTSUPP;
	}

	//假设允许改变IRQ
	if (map->irq != dev->irq) {  //接受IRQ的修改
		dev->irq = map->irq;
	}
	return 0;
}
7.3 get_stats() 函数
驱动程序还提供 get_stats() 函数,用来向用户反馈设备状态和统计信息。

该函数返回的是一个 net_device_stats 结构体, 定义如下:

/*
 *	Old network device statistics. Fields are native words
 *	(unsigned long) so they can be read and written atomically.
 */

struct net_device_stats {
	unsigned long	rx_packets;   //接收到的数据包数
	unsigned long	tx_packets;   //发送的数据包数
	unsigned long	rx_bytes;     //接收到的字节数
	unsigned long	tx_bytes;     //发送的字节数
	unsigned long	rx_errors;    //收到的错误的数据包数
        unsigned long	tx_errors;    //发生发送错误的数据包数
	unsigned long	rx_dropped;
	unsigned long	tx_dropped;
	unsigned long	multicast;
	unsigned long	collisions;
	unsigned long	rx_length_errors;
	unsigned long	rx_over_errors;
	unsigned long	rx_crc_errors;
	unsigned long	rx_frame_errors;
	unsigned long	rx_fifo_errors;
	unsigned long	rx_missed_errors;
	unsigned long	tx_aborted_errors;
	unsigned long	tx_carrier_errors;
	unsigned long	tx_fifo_errors;
	unsigned long	tx_heartbeat_errors;
	unsigned long	tx_window_errors;
	unsigned long	rx_compressed;
	unsigned long	tx_compressed;
};
net_device_stats 结构体适合包含在设备的私有信息结构体中,而其统计信息的修改应该在设备驱动的发送和接收操作中完成,这些操作包括中断处理程序,数据包发送函数,数据包发送超时函数, 和数据包接收函数 等。

示例代码如下:

//发送超时函数
void xxx_tx_timeout(struct net_device *dev)
{
	struct xxx_priv *priv = netdev_priv(dev);
	...
	priv->stats.tx_errors++;    //发送错误包数+1
	...
}
//中断处理函数
static void xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	switch (status & ISQ_EVENT_MASK){
		...
		case ISQ_TRASMITTER_EVENT:
			priv->stats.tx_packets++;   //数据包发送成功,tx_pacakets +1
			netif_wake_queue(dev);   //通知上层协议
			if ((status & (TX_OK | TX_LOST_CRS | TX_SQE_ERROR |
					TX_LATE_COL |TX_16_COL)) != TX_OK) {
				//根据不同的情况,对net_device_stats的不同成员+1
				if ((status & TX_OK) == 0)
					priv->stats.tx_errors++;
				if (status & TX_LOST_CRS)
					priv->stats.tx_carrier_errors++;
				if (status & TX_SQE_ERROR)
					priv->stats.tx_heartbeat_errors++;
				if (status & TX_LATE_COL)
					priv->stats.tx_window_errors++;
				if (status & TX_16_COL)
					priv->stats.tx_aborted_errors++;
			}
		case ISQ_RX_MISS_EVENT:
			priv->stats.rx_missed_errors += (status >> 6)
			break;
		case ISQ_TX_COL_EVENT:
			priv->stats.collisions += (status >> 6);
			break;
	}

}


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值