PC架构中的绝大部分设备都是通过PCI局部总线与CPU进行通信,系统上电之后需要进行PCI总线的扫描,当扫描到一个PCI设备之后,在内核会创建一个物理设备对象(PDO),如果是PCI设备,该对象类型为pci_dev,并且对该对象进行初始化,分配相应的资源,上述行为属于总线驱动的工作范畴。PDO初始化成功之后,系统会调用device_add完成设备驱动程序的加载。Device_add函数一方面会通过设备管理机制向udev守护进程发送添加设备的消息;另一方面会调用bus_add_device()函数加载设备驱动。加载设备驱动时,Linux内核会遍历相应总线上的所有驱动程序,如果能够找到匹配的驱动,那么系统会调用驱动程序所提供的probe()函数探测具体的设备。在设备驱动程序的probe()函数中可以创建具体的逻辑设备对象(FDO),并且对其进行初始化。这些工作都完成之后,一个物理设备的驱动全部加载成功,在/dev目录下就可以看到具体的设备节点了。
在嵌入式系统中一般都不会采用复杂的PCI局部总线,而是将设备直接挂接到CPU的外围扩展总线上,在At91rm9200为核心的嵌入式系统中,s1d13506作为显示控制器可以直接挂接到EBI总线上,并且分配有固定的访问地址,例如可以将CS3作为s1d13506的片选信号对其进行访问,访问空间为512MB,满足了嵌入式应用的需求。这样的硬件结构实现较为单一,不存在标准的局部总线,因此s1d13506驱动设计过程中就不会存在设备扫描、资源自动分配等工作,而是固定的将设备挂接到一个虚拟的伪总线上,生成一个PDO对象,然后再加载具体的设备驱动。下面对s1d13506驱动的实现进行说明。
与Linux中的标准驱动程序类似,S1d13506驱动采用分层的设计框架,底层为总线驱动,上层为设备驱动,向外导出字符设备。
在硬件层s1d13506控制器直接挂接到了EBI总线上,在内核中该物理设备对象被挂接到一条虚拟的伪总线上,该伪总线为platform_bus,platform.c为该总线的核心驱动。Platform bus不具备自动识别的功能,所以需要主动将s1d13506注册到该总线上。首先需要定义s1d13506的物理设备对象dk_s1dfb_device,该对象的具体描述如下:
struct platform_device {
char * name; /* 设备名称 */
u32 id;
struct device dev; /* 用于设备管理的对象 */
u32 num_resources; /* 资源数 */
struct resource * resource; /* 与设备相关的资源 */
};
在系统初始化时,调用platform_device_register()函数将dk_s1dfb_device对象添加到platform bus上。在PDO中一个重要的域为resource,该域的实现需要与具体的嵌入式系统硬件相关:
static struct resource dk_s1dfb_resource[] = {
{ /* video mem */
.name = "s1d13806 memory",
.start = 0x40200000,
.end = 0x40200000 + 0x200000 -1,
.flags = IORESOURCE_MEM,
}, { /* video registers */
.name = "s1d13806 registers",
.start = 0x40000000,
.end = 0x40000000 + 0x200000 -1,
.flags = IORESOURCE_MEM,
},
};
在platform_device_register()函数中会调用device_add()函数加载设备驱动,该过程与标准PCI设备保持一致,并且通过udev机制创建设备节点。在设备驱动中,需要定义一个device_driver对象,然后通过driver_register函数将驱动注册到platform伪总线上。设备驱动中的核心函数是probe函数,该函数完成内存地址的映射,并且创建生成framebuffer设备,该设备的对外接口通过ops函数导出。由s1d13506构成的图形设备驱动程序框架如下图所示:
为了提高效率,设备驱动可以通过memap机制将s1d13506的显存空间映射到应用程序的地址空间,这样应用程序可以直接对现存空间进行操作,而无需数据的拷贝,为了达到这样的目的,在framebuffer操作层提供了fb_mmap()的接口,具体方法需要由s1d13506设备驱动提供。
用户147174 2008-12-29 18:39