虽然Linux驱动程序应该是和具体的硬件平台分离的,但是为了更好的理解DM9000的驱动程序,这里还是结合一下Mini2440开发板,这样也可以更好的体会如何实现驱动和平台分离。
本文分成以下几个部分:
一、Mini2440开发板上DM9000的电气连接和Mach-mini2440.c文件的关系。
二、两个重要的结构体介绍:sk_buff和net_device
三、具体代码分析
一、Mini2440开发板上DM9000的电气连接和Mach-mini2440.c文件的关系
Mini2440开发板上DM9000与S3C2440的连接关系如下:
这个DM9000平台设备作为众多平台设备中的一个在板子初始化的时候就被添加到了总线上。代码清单如下:
其中片选信号AEN使用了nGCS4,所以网卡的内存区域在BANK4,也就是从地址0x20000000开始。DM9000的TXD[2:0]作为strap pin在电路图中是空接的,所以IO base是300H。中断使用了EINT7。这些内容在Mach文件中有如下体现:#define S3C2410_CS4 (0x20000000)
#define MACH_MINI2440_DM9K_BASE (S3C2410_CS4 + 0x300)
static struct resource mini2440_dm9k_resource[] __initdata = {
[0] = {
.start = MACH_MINI2440_DM9K_BASE,
.end = MACH_MINI2440_DM9K_BASE + 3,
.flags = IORESOURCE_MEM
},
[1] = {
.start = MACH_MINI2440_DM9K_BASE + 4,
.end = MACH_MINI2440_DM9K_BASE + 7,
.flags = IORESOURCE_MEM
},
[2] = {
.start = IRQ_EINT7,
.end = IRQ_EINT7,
.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE,
}
};
另外在Mach文件中还定义了DM9000平台设备,设备名称为“dm9000”,设备资源就是上面定义的IO和中断资源。代码清单如下:
static struct dm9000_plat_data mini2440_dm9k_pdata __initdata = {
.flags = (DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM),
};
static struct platform_device mini2440_device_eth __initdata = {
.name = "dm9000",
.id = -1,
.num_resources = ARRAY_SIZE(mini2440_dm9k_resource),
.resource = mini2440_dm9k_resource,
.dev = {
.platform_data = &mini2440_dm9k_pdata,
},
};
MACHINE_START(MINI2440, "MINI2440")
/* Maintainer: Michel Pollet <[email protected]> */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io = mini2440_map_io,
.init_machine = mini2440_init, /*初始化函数*/
.init_irq = s3c24xx_init_irq,
.timer = &s3c24xx_timer,
MACHINE_END
static void __init mini2440_init(void)
{
...
...
platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
...
...
}
static struct platform_device *mini2440_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_wdt,
/* &s3c_device_adc,*/ /* ADC doesn't like living with touchscreen ! */
&s3c_device_i2c0,
&s3c_device_rtc,
&s3c_device_usbgadget,
&mini2440_device_eth, /*dm9000是众多平台设备中的一个*/
&mini2440_led1,
&mini2440_led2,
&mini2440_led3,
&mini2440_led4,
&mini2440_button_device,
&s3c_device_nand,
&s3c_device_sdi,
&s3c_device_iis,
&mini2440_audio,
/* &s3c_device_timer[0],*/ /* buzzer pwm, no API for it */
/* remaining devices are optional */
};
二、两个重要的结构体简单介绍:sk_buff和net_device
*sk_buff
如果把网络传输看成是运送货物的话,那么sk_buff就是这个“货物”了,所有经手这个货物的人都要干点什么事儿,要么加个包装,要么印个戳儿等等。收货的时候就要拆掉这些包装,得到我们需要的货物(payload data)。没有货物你还运输什么呢?由此可见sk_buff的重要性了。关于sk_buff的详细介绍和几个操作它的函数,参考:“linux内核sk_buff的结构分析” http://www.linuxidc.com/Linux/2011-07/39163.htm,写得非常明白了。赞一个~
*net_device
又是一个庞大的结构体。好吧,我承认我从来就没有看全过这个结构体。它在内核中就是指代了一个网络设备。驱动程序需要在探测的时候分配并初始化这个结构体,然后使用register_netdev来注册它,这样就可以把操作硬件的函数与内核挂接在一起。
三、具体代码的分析
在顺序分析之前先看三个结构体变量和一个自定义的结构体。
* dm9000_driver变量。是platform_driver结构体变量,其中包含了重要的:驱动的名字(用来match)和几个重要操作函数。
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,
};
* dm9000_netdev_ops变量。是net_device_ops结构体变量, 其中定义了操作net_device的重要函数,我们在驱动程序中根据需要的操作要填充这些函数。代码清单如下:
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_multicast_list = dm9000_hash_table,
.ndo_do_ioctl = dm9000_ioctl,
.ndo_change_mtu = eth_change_mtu,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = eth_mac_addr,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = dm9000_poll_controller,
#endif
};
* dm9000_ethtool_ops变量。是ethtool_ops结构体变量,为了支持ethtool,其中的函数主要是用于查询和设置网卡参数(当然也有的驱动程序可能不支持ethtool)。代码清单如下:
static const struct ethtool_ops dm9000_ethtool_ops = {
.get_drvinfo = dm9000_get_drvinfo,
.get_settings = dm9000_get_settings,
.set_settings = dm9000_set_settings,
.get_msglevel = dm9000_get_msglevel,
.set_msglevel = dm9000_set_msglevel,
.nway_reset = dm9000_nway_reset,
.get_link = dm9000_get_link,
.get_eeprom_len = dm9000_get_eeprom_len,
.get_eeprom = dm9000_get_eeprom,
.set_eeprom = dm9000_set_eeprom,
};
*board_info结构体。用来保存芯片相关的一些私有信息。具体在代码中分析。下面是这个结构体的清单。
/* Structure/enum declaration ------------------------------- */
typedef struct board_info {
void __iomem *io_addr; /* Register I/O base address */
void __iomem *io_data; /* Data I/O address */
u16 irq; /* IRQ */
u16 tx_pkt_cnt;
u16 queue_pkt_len;
u16 queue_start_addr;
u16 dbug_cnt;
u8 io_mode; /* 0:word, 2:byte */
u8 phy_addr;
u8 imr_all;
unsigned int flags;
unsigned int in_suspend :1;
int debug_level;
enum dm9000_type type;
void (*inblk)(void __iomem *port, void *data, int length);
void (*outblk)(void __iomem *port, void *data, int length);
void (*dumpblk)(void __iomem *port, int length);
struct device *dev; /* parent device */
struct resource *addr_res; /* resources found */
struct resource *data_res;
struct resource *addr_req; /* resources requested */
struct resource *data_req;
struct resource *irq_res;
struct mutex addr_lock; /* phy and eeprom access lock */
struct delayed_work phy_poll;
struct net_device *ndev;
spinlock_t lock;
struct mii_if_info mii;
u32 msg_enable;
} board_info_t;
下面看一下具体代码。
分析代码还是从init顺序开始。
1. 注册平台驱动。
主要完成的任务是:将驱动添加到总线上,完成驱动和设备的match,并执行驱动的probe函数。代码清单如下:
static struct platform_driver dm9000_driver = {2.probe函数。
主要完成的任务是:探测设备获得并保存资源信息,根据这些信息申请内存和中断,最后调用register_netdev注册这个网络设备。以下是代码清单,可以分成几个部分来看:
1)首先定义了几个局部变量:
struct dm9000_plat_data *pdata = pdev->dev.platform_data;
struct board_info *db; /* Point a board information structure */
struct net_device *ndev;
2)初始化一个网络设备。关键系统函数:alloc_etherdev()
3)获得资源信息并将其保存在board_info变量db中。关键系统函数:netdev_priv(), platform_get_resource()
4)根据资源信息分配内存,申请中断等等, 并将申请后的资源信息也保存到db中,并且填充ndev中的参数。关键系统函数:request_mem_region(), ioremap()。自定义函数:dm9000_set_io()
5)完成了第4步以后,回顾一下db和ndev中都有了什么:
struct board_info *db:
addr_res -- 地址资源
data_res -- 数据资源
irq_res -- 中断资源
addr_req -- 分配的地址内存资源
io_addr -- 寄存器I/O基地址
data_req -- 分配的数据内存资源
io_data -- 数据I/O基地址
dumpblk -- IO模式
outblk -- IO模式
inblk -- IO模式
lock -- 自旋锁(已经被初始化)
addr_lock -- 互斥锁(已经被初始化)
struct net_device *ndev:
base_addr -- 设备IO地址
irq -- 设备IRQ号
6) 设备复位。硬件操作函数dm9000_reset()
7) 读一下生产商和制造商的ID,应该是0x9000 0A46。关键函数:ior()
8) 读一下芯片类型。
========以上步骤结束后我们可以认为已经找到了DM9000========
9) 借助ether_setup()函数来部分初始化ndev。因为对以太网设备来讲,很多操作与属性是固定的,内核可以帮助完成。
10) 手动初始化ndev的ops和db的mii部分。
11) (如果有的话)从EEPROM中读取节点地址。这里可以看到mini2440这个板子上没有为DM9000外挂EEPROM,所以读取出来的全部是0xff。见函数dm9000_read_eeprom。关于外挂EEPROM,可以参考datasheet上的7.EEPROM Format一节。
12) 很显然ndev是我们在probe函数中定义的局部变量,如果我想在其他地方使用它怎么办呢? 这就需要把它保存起来。内核提供了这个方法,使用函数platform_set_drvdata()可以将ndev保存成平台总线设备的私有数据。以后再要使用它时只需调用platform_get_drvdata()就可以了。
13) 使用register_netdev()注册ndev。
下面是代码清单: