From:http://blog./u2/70445/showart_1761797.html
在成功移植Dm9000驱动到我的EIEVK-100(SMDK2440)开发板的前提下,本文从以下几个方面说明相关原理及过程: 1.硬件情况 2.Dm9000驱动移植详细过程 3.Platform_device与platform_driver 4.Dm9000驱动代码简要分析 一. 硬件情况 DM9000在电路板上的连接中与编程相关的如下: 知道上面这些信息已经足够移植驱动了。 二. Dm9000驱动移植详细过程 1.在内核编译配置选项中,driver-->net-->10/100M net-->DM9000 support 选项选中。 2.在arch/arm/mach-s3c2410/devs.c 中添加dm9000的platform_device。 #include
static struct resource eievk_dm9000_resource[] = { [0]= { .start = 0x10000000, //this is based on EIEVK board .end = 0x10000003, .flags = IORESOURCE_MEM, }, [1]={ .start = 0x10000004, .end = 0x10000007, .flags = IORESOURCE_MEM, }, [2]={ .start = IRQ_EINT7, .end = IRQ_EINT7, .flags = IORESOURCE_IRQ, } }; static struct dm9000_plat_data eievk_dm9000_platdata ={ .flags = DM9000_PLATF_16BITONLY,//work in 16bit mode }; struct platform_device eievk_dm9000_device = { .name = "dm9000", .id = -1, .num_resources = 3, .resource = eievk_dm9000_resource, .dev = { .platform_data = &eievk_dm9000_platdata, } }; EXPORT_SYMBOL(eievk_dm9000_device); /*end add beelike@126.com */ 3.在arch/arm/mach-s3c2410/devs.h中 声明平台设备 eievk_dm9000_device : extern struct platform_device eievk_dm9000_device; 4.在arm/arm/mach-s3c2410/mach-smdk2410.c中将eievk_dm9000_device添加到平台设备列表中: static struct platform_device *smdk2440_devices[] __initdata = { &s3c_device_usb, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c, &s3c_device_iis, &eievk_dm9000_device, //add dm9000 by beelike &s3c_device_nand, }; 5.OK,经过上述努力,Dm9000设备已经成功注册进入驱动核心。下面进入driver/net/dm9000.C中,还需要做两方面的工作:设置芯片MAC地址,使能DM9000的中断。 根据2440资料。有几个地方需要设置: 1)GPFCON (56000050) GPF7 [15:14] 置 10 ,功能设置为EINT7 。 这可以用函数实现:s3c2410_gpio_cfgpin(S3C2410_GPF7, S3C2410_GPF3_EINT7); 2)EXTINT0 (56000088) [30:28] 置0 低电平触发中断 (复位默认为全0,可能不必设) 3)外部中断屏蔽寄存器。EINTMASK (560000a4) [7] 置0 以enable interrupt EINT7 4)全局中断屏蔽寄存器 INTMASK (4A000008) [4] 置0 使能EINT4_7。 代码修改: static char net_mac_addr[]={0x00,0xe0,0x3d,0xf4,0xdd,0xf7};//MAC static void *extint0,*intmsk,*eintmsk; #define EINTMASK (0x560000a4) //外部中断屏蔽 #define EXTINT0 (0x56000088) //外部中断方式 #define INTMSK (0x4A000008) //中断屏蔽 /*******************end add ***********************/
/***********beelike add ********/ for(i=0;i<6;i++){ ndev->dev_addr[i]=net_mac_addr[i]; } extint0=ioremap_nocache(EXTINT0,4);// intmsk=ioremap_nocache(INTMSK,4); eintmsk=ioremap_nocache(EINTMASK ,4); s3c2410_gpio_cfgpin(S3C2410_GPF7, S3C2410_GPF7_EINT7); writel(readl(extint0)&0x8fffffff,extint0); //eint7 low level writel(readl(intmsk)&(~(1<<4)),intmsk); // writel(readl(extint0)&(~(1<<7)),extint0); iounmap(intmsk); iounmap(extint0); iounmap(eintmsk); /******************end *********/ OK,编译下载之后,启动内核后: Mount -t proc proc /proc Ifconfig eth0 192.168.1.12 netmask 255.255.255.0 eth0: link up, 100Mbps, full-duplex, lpa 0x41E1 开发板与主机之间能正常ping通。 整个驱动移植过程结束。 三. Platform_device和platform_driver 通过Platform机制开发发底层驱动的大致流程为: 定义 platform_device---注册 platform_device ---定义 platform_driver-----注册 platform_driver。 1. Platform_device 定义于 kernel\include\linux\platform_device.h中, struct platform_device { const char * name; u32 id; struct device dev; u32 num_resources; struct resource * resource; }; 定义一个platform_device一般需要初始化两个方面的内容:设备占用的资源resource和设备私有数据dev.platform_data。最重要的是resource
Resource定义于kernel\include\linux\ioport.h中, struct resource { const char *name; unsigned long start, end; unsigned long flags; struct resource *parent, *sibling, *child; }; 实际上是对地址范围及其属性的一个描述。最后几个用于树型结构的指针是内核用于管理所有资源的。 而platform_data则是设置给struct device dev;中的platform_data指针(void *)。这个指针内核并不使用,而是驱动自身来定义及使用。 比如说对于DM9000,对应的platform_data定义于include/linux/dm9000.H中。 struct dm9000_plat_data { unsigned int flags; /* allow replacement IO routines */ void (*inblk)(void __iomem *reg, void *data, int len); void (*outblk)(void __iomem *reg, void *data, int len); void (*dumpblk)(void __iomem *reg, int len); }; OK,初始化完资源和platform_data,一个平台设备就定义好了。把这个平台设备变量的地址添加到资源列表中去。比如在2410平台: 在arm/arm/mach-s3c2410/mach-smdk2410.c把设备地址添加到*smdk2410_devices[] __initdata 数组中去。 最后在arch/arm/mach-3sc2410/cpu.c 中初始化函数__init s3c_arch_init(void)会对smdk2410_devices[]每一个设备的指针ptr调用platform_device_register(ptr)。主要是建立device的层次结构(建立sysfs入口),将设备占用的资源添加到内核资源管理。接下来看看platform_driver: 2. platform_driver结构定义于include/linux/platform_device.H : int (*probe)(struct platform_device *); int (*remove)(struct platform_device *); void (*shutdown)(struct platform_device *); int (*suspend)(struct platform_device *, pm_message_t state); int (*resume)(struct platform_device *); struct device_driver driver; }; 它内部封装了一个device_driver,更有意思的是其它的全是函数,并且这些函数名与device_driver中提供的一样,只是参数由device * 变成了 platform_device * 。 驱动应该实现platform_driver中的这些操作,而内嵌的device_driver中的对应函数则在注册时被指定为内核指定的操作,这些指定操作只是把调用参数转换成platform_driver和platform_device来调用platform_driver提供的操作而已。 好像有点乱。。不过代码可以解释一切: 平台驱动注册: int platform_driver_register(struct platform_driver *drv) { drv->driver.bus = &platform_bus_type; if (drv->probe) drv->driver.probe = platform_drv_probe; if (drv->remove) drv->driver.remove = platform_drv_remove; if (drv->shutdown) drv->driver.shutdown = platform_drv_shutdown; if (drv->suspend) drv->driver.suspend = platform_drv_suspend; if (drv->resume) drv->driver.resume = platform_drv_resume; return driver_register(&drv->driver); } OK,如果device_driver的方法没有定义就会变成对应的platform_drv_*方法。 来看看其中的一个的实现:比如 platform_drv_probe static int platform_drv_probe(struct device *_dev) { struct platform_driver *drv = to_platform_driver(_dev->driver); struct platform_device *dev = to_platform_device(_dev); return drv->probe(dev); } 事情很清楚,先把设备的device_driver转成platform_driver,同样转换device为platform_device。然后去调用platform_driver提供的函数。类型转换当然是通过container_of()宏实现的。 因此,驱动只需要实现platform_driver中的方法。然后注册即可。 关于注册,由上面的代码可知,最终也是通过 driver_register(&drv->driver);来做的。 3.更深入的小窥一下平台设备与平台驱动的注册: 根据LDD3中指出的设备模型,一个设备和驱动必然属于某一个总线。Platform_device和platform-driver在层次上隶属于叫platform_bus_type的总线类型。OK,平台驱动注册的时候(平台设备必须先于驱动注册)将引用它所属总线的匹配函数去决定总线上每一个设备是否属于自己。然后二者建立联系:设备的驱动指针指向该驱动,驱动的设备列表中加入匹配的设备。 当然,这是在设备和驱动这一层面来说的,更深入一层,kobjects和ksets建立层次关系,建立sysfs入口等等。。 注意,platform_bus_type的匹配函数只是比较一下driver和device的name是否相同。因此,同一设备的platform_device和platform_driver的name应该设为相同的。见platform_bus_type匹配函数定义: { struct platform_device *pdev = container_of(dev, struct platform_device, dev); return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0); } 因此,dm9000的platform_device和platform_driver的name都为"Dm9000"。 4.下面一个问题:资源怎么用??Platform_data一般怎么用? 资源描述的是设备占用的IO内存,IO端口,及中断线。 Dm9000驱动中是这样使用的。这符合惯例: 在probe中获取资源,并且申请资源,最后映射到内核空间,把映射结果保存起来。 在net_device中的open函数里,注册中断处理函数。 Platform_data的使用极为灵活,首先platform_data结构不同设备之间没有定论,一般可用来保存特定于设备的一些配置,操作等。比如对于DM9000,可以存在按字节,按字访问的不同模式,因此其platform_data定义成这样: struct dm9000_plat_data { unsigned int flags; /* allow replacement IO routines */ void (*inblk)(void __iomem *reg, void *data, int len); void (*outblk)(void __iomem *reg, void *data, int len); void (*dumpblk)(void __iomem *reg, int len); }; 其中flags是8/16位模式的选择标志,下面三个是在该模式下的IO存取函数。 然后Dm9000驱动只使用了它的flags标志,其余的并不使用。 因为对于网络net_device,有一个叫着private_data的指针,在分配一个net_device的时候可以让内核为其开辟指定大小的内存。这部分内存可以通过net_device访问,而且内容也是驱动开发者自定义的。在DM9000的驱动中,net_devict的private_data使用了一个叫board_info的结构体来包括更多设备相关的信息和操作。 dm9000_plat_data提供的内容也被包括进board_info。因此驱动只使用了初始时设置的flags,除此外dm9000_plat_data中的方法没有使用的必要。 从中得到的启示: Net_device则包含一个private区域. 这样既实现了设备模型的统一管理,又实现了保持不同设备的信息与方法的灵活性。 四. Dm9000驱动源码的简要分析。 1.定义并注册DM9000 的 platform_device 。定义设备占用资源和platform_data。(具体的见前面) 2.将platform_device添加到板子的设备列表中去,在系统初始化时注册入内核。 3.在DM9000.C中,定义了dm9000的platform_driver。 .driver = { .name = "dm9000", .owner = THIS_MODULE, }, .probe = dm9000_probe, .remove = dm9000_drv_remove, .suspend = dm9000_drv_suspend, .resume = dm9000_drv_resume, }; 这里面关健的东西是name和probe,remove。 4.在模块初始化函数module_init(dm9000_init);中注册dm9000_driver。 5.驱动还定义了一个数据结构:board_info来记录芯片的信息及操作。如统计信息,读写操作,占用的IO地址资源,状态。 6.OK,现在接着4说,模块初始化函数最终将调用probe函数。这个函数完成的基本过程 : 1)获取一个netdevice: 2)获取设备资源: 3)申请IO资源,映射到内核并保存映射地址: 4)根据DM9000数据位宽设置 读写数据帧的函数指针。 5)OK,现在复位芯片:dm9000_reset(db); db中已经包含了详细的芯片信息。 6)读取芯片ID号并判断是否为0x90000A46。 7)初始化以太网ndev : ether_setup(ndev); 8)设置ndev的基本操作: 9)添加一些打开中断,设置MAC地址的操作在这里。 10)将ndev记录于平台设备platform_dev中去。注册ndev。 OK,probe的使命OVER了。这也是ndev与platform_dev建立联系的地方。 7.OK,一旦probe正常的执行完,内核中注册好了eth0这个网络接口(因为只有一个网卡)。在系统启动之后,配置eth0,这将引起ndev->open()调用。来看看open做些什么? 8.OK,配置好eth0接口后,网络设备连接好。数据收发就绪。现在简要的分析一下收发过程:
9.接收过程: 然后把skb_buffer交给上层协议:netif_rx(skb); 最后更新接口统计信息:db->stats.rx_packets++; 收到包总数+1。 整个DM9000驱动的移植和源码主要部分的简要分析至此结束。 |
|