1、Linux网络设备驱动的体系结构
Linux网络驱动程序的体系结构可划分为4个层次。Linux内核源代码中提供了网络设备接口及以上层次的代码,因此移植特定网络硬件的驱动程序的主要工作就是完成设备驱动功能层的相应代码,根据底层具体的硬件特性,定义网络设备接口struct net_device类型的结构体变量,并实现其中相应的操作函数及中断处理程序。
Linux中所有的网络设备都抽象为一个统一的接口,即网络设备接口,通过struct net_device类型的结构体变量表示网络设备在内核中的运行情况,这里既包括回环(loopback)设备,也包括硬件网络设备接口。内核通过以dev_base<drives/net/space.c>为头指针的设备链表来管理所有的网络设备。
net_device数据结构
struct net_device结构体是整个网络驱动结构的核心,其中定义了很多供网络协议接口层调用设备的标准方法,该结构在<include/linux/netdevice.h>文件中定义,下面只列出其中主要的成员。
1)全局信息及底层硬件信息
name: 网络设备名称;
base_addr,irq: 网络设备的I/O基地址,中断号,ifconfig命令可显示和修改;
hard_header_len: 硬件头的长度,以太网中值为14;
mtu: 最大传输单元,以太网中值为1500B;
dev_addr[MAX_ADDR_LEN]: MAC地址,ether_setup会对其进行正确的设置;
2)主要的操作方法
int (*init)(struct net_device *dev); 设备初始化和向系统注册的函数,仅调用一次;
int (*open)(struct net_device *dev); 设备打开接口函数,当用ifconfig激活网络设备时被调用,注册所用的系统资源(I/O端口,IRQ,DMA等)同时激活硬件并增加使用计数;
int (*stop)(struct net_device *dev); 执行open方法的反操作;
*hard_start_xmit; 初始化数据包传输的函数;
网络驱动程序实现原理
Linux网络系统各个层次之间的数据传送都是通过套接字缓冲区sk_buff完成的,sk_buff数据结构是各层协议数据处理的对象。sk_buff<linux/skbuff.h>是驱动程序与网络之间交换数据的媒介,驱动程序向网络发送数据时,必须从其中获取数据源和数据长度;驱动程序从网络上接收到数据后也要将数据保存到sk_buff中才能交给上层协议处理。
网络驱动程序主要的几个操作如下:
1) 初始化(init)
驱动程序必须有一个初始化方法。在把驱动程序载入系统的时候会调用这个初始化程序,它做以下几方面的工作。
检测设备。配置和初始化硬件。配置或协商好硬件占用的资源以后,就可以向系统申请这些资源。有些资源是可以和别的设备共享的,如中断。有些是不能共享的,如IO、DMA。接下来你要初始化device结构中的变量。最后,你可以让硬件正式开始工作。
2) 打开(open)
open这个方法在网络设备驱动里是网络设备被激活的时候被调用(即设备状态由down-->up)。所以实际上很多在initialize中的工作可以放到这里来做。比如资源的申请,硬件的激活。如果dev->open返回非0(error),则硬件的状态还是down。 open方法另一个作用是如果驱动程序做为一个模块被装入,则要防止模块卸载时设备处于打开状态。在open方法里要调用MOD_INC_USE_COUNT宏。
3)关闭(stop)
stop方法做和open相反的工作。可以释放某些资源以减少系统负担。stop是在设备状态由up转为down时被调用的。另外如果是做为模块装入的驱动程序,close里应该调用MOD_DEC_USE_COUNT,减少设备被引用的次数,以使驱动程序可以被卸载。
4) 发送(hard_start_xmit)
所有的网络设备驱动都必须有这个发送方法。在系统调用驱动程序的xmit时,发送的数据放在一个sk_buff结构中。一般的驱动把数据传给硬件发出去。也有一些特殊的设备比如loopback把数据组成一个接收数据再回送给系统,或者dummy设备直接丢弃数据。如果发送成功,hard_start_xmit方法里释放sk_buff,返回0(发送成功)。如果设备暂时无法处理,比如硬件忙,则返回1。这时如果dev->tbusy置为非0,则系统认为硬件忙,要等到dev->tbusy置0以后才会再次发送。tbusy的置0任务一般由中断完成。硬件在发送结束后产生中断,这时可以把tbusy置0,然后用mark_bh()调用通知系统可以再次发送。在发送不成功的情况下,也可以不置dev->tbusy为非0,这样系统会不断尝试重发。如果hard_start_xmit发送不成功,则不要释放sk_buff。传送下来的sk_buff中的数据已经包含硬件需要的帧头。所以在发送方法里不需要再填充硬件帧头,数据可以直接提交给硬件发送。sk_buff是被锁住的(locked),确保其他程序不会存取它。
5) 接收(reception)
有数据收到时,驱动程序通知系统。一般设备收到数据后都会产生一个中断,在中断处理程序中驱动程序申请一块sk_buff(skb),从硬件读出数据放置到申请好的缓冲区里。接下来填充sk_buff中的一些信息。skb->dev= dev,判断收到帧的协议类型,填入skb->protocol(多协议的支持)。把指针skb->mac.raw指向硬件数据然后丢弃硬件帧头(skb_pull)。还要设置skb->pkt_type,标明第二层(链路层)数据类型。可以是以下类型:
PACKET_BROADCAST : 链路层广播;
PACKET_MULTICAST : 链路层组播;
PACKET_SELF : 发给自己的帧;
PACKET_OTHERHOST : 发给别人的帧(监听模式时会有这种帧)。
最后调用netif_rx()把数据传送给协议层。netif_rx()里数据放入处理队列然后返回,真正的处理是在中断返回以后,这样可以减少中断时间。调用netif_rx()以后,驱动程序就不能再存取数据缓冲区skb。
2、dm9000网卡驱动简析
Dm9000是嵌入式平台上常用的MAC+PHY芯片。针对各种SoC,2.6内核引进了platform设备模型来封装相关驱动,Dm9000驱动就用platform改写了。
在分析dm9000驱动前,先看一下platform,其大致调用过程如下:
start_kernelàrest_initàkernel_initàdo_basic_setupà driver_inità platform_bus_init
int __init platform_bus_init(void)
{
。。。。。。
error = device_register(&platform_bus);
。。。。。。
error = bus_register(&platform_bus_type);
。。。。。。
}
dm9000驱动模块初始化函数dm9000_init使用了platform_driver_register来注册:
static int __init dm9000_init(void)
{
printk(KERN_INFO "%s Ethernet Driver, V%s/n", CARDNAME, DRV_VERSION);
return platform_driver_register(&dm9000_driver);
}
其中,&dm9000_driver如下定义:
static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
},
.probe = dm9000_probe,
.remove = __devexit_p(dm9000_drv_remove),
.suspend = dm9000_drv_suspend,
.resume = dm9000_drv_resume,
};
使用platform_driver_register(&dm9000_driver) 注册dm9000_driver,会调用其中的.probe也就是这里的dm9000_probe函数。其调用过程如下:
platform_driver_register(&dm9000_driver)à
driver_register(&drv->driver)à
bus_add_driver(drv)à
driver_attach(drv) à
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)à
__driver_attachà
driver_probe_device(drv, dev)à
really_probe(dev, drv) à
if (dev->bus->probe) dev->bus->probe(dev)
else if (drv->probe) drv->probe(dev) //调用dm9000_probe()
dm9000_probe函数分析
dm9000_probe函数实现如下:
static int __devinit dm9000_probe(struct platform_device *pdev)
{
......
ndev = alloc_etherdev(sizeof(struct board_info));
//分配空间,并初始化部分东西
......
db = ndev->priv;
memset(db, 0, sizeof(*db));
db->dev = &pdev->dev;
db->ndev = ndev;
INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work);
db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
iosize = res_size(db->addr_res);
db->addr_req = request_mem_region(db->addr_res->start, iosize,pdev->name);
db->io_addr = ioremap(db->addr_res->start, iosize);
iosize = res_size(db->data_res);
db->data_req = request_mem_region(db->data_res->start, iosize, pdev->name);
db->io_data = ioremap(db->data_res->start, iosize);
//上述代码初始化bd结构体,bd是dm9000.c中定义的board结构,其中db = ndev->priv;
db初始化为net device中的priv成员。
......
//初始化net_device结构体中的一些重要函数指针及变量
ether_setup(ndev);
ndev->open = &dm9000_open;
ndev->hard_start_xmit = &dm9000_start_xmit;
ndev->tx_timeout = &dm9000_timeout;
ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
ndev->stop = &dm9000_stop;
ndev->set_multicast_list = &dm9000_hash_table;
ndev->ethtool_ops = &dm9000_ethtool_ops;
ndev->do_ioctl = &dm9000_ioctl;
......
//初始化MII接口使用的最重要2个函数
db->mii.mdio_read = dm9000_phy_read;
db->mii.mdio_write = dm9000_phy_write;
......
//从E2PROM中读取MAC地址,并验证是否为合法MAC地址
for (i = 0; i < 6; i += 2)
dm9000_read_eeprom(db, i / 2, ndev->dev_addr+i);
if (!is_valid_ether_addr(ndev->dev_addr))
dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please "
"set using ifconfig/n", ndev->name);
......
//最终注册
ret = register_netdev(ndev);
}
DM9000的驱动初始化基本是由probe函数进行。
实际发送数据在dm9000_start_xmit函数中实现,而接收数据在中断处理函数dm9000_interrupt实现。具体实现,请大家自己看代码去!