linux内核驱动学习(七)----驱动分类 | 学习方法 | 硬件访问

驱动分类:

对于驱动,我们一般按两种方法进行分类:常规分类法和总线分类法。

按照常规分类法,可以分为以下三类:
1、字符设备:
字节为最小访问单位的设备。 一般通过字符设备文件来访问字符设备驱动程序。 字符驱动程序则负责驱动字符设备, ,这样的驱动通常支持open、close、read、write系统调用,应用程序可以通过设备文件(比如/dev/ttySAC0等)来访问字符设备(串口)。例如:串口\ led\按键

2、块设备:
以块(一般512字节)为最 小传输单位的设备。大多数UNIX系统中,块设备不能按字节处理数据。常见的块设备包括硬盘、Flash、sd卡。
Linux追踪则允许块设备传送任意数目的字节
块设备的特别之处:
    a)操作硬件的接口的实现方式不一样
         块设备驱动程序是先将用户发来的数据组织成块,再写入设备;或从设备中读出若干块数据,再从中挑出用户需要的
    b)数据块上的数据可以有一定的格式。
          通常在快设备中按一定的格式存放数据,不同的文件系统类型就是定义这些格式的。内核中,文件系统的层次位于块设备驱动程序上面的,这意味着块设备驱动程序除了向用户层提供与字符设备一           样的接口外,还要向内核其他部件提供一些接口,这些接口用户看不到,但是可以使用这些接口是的可以在块设备上存放文件系统,挂载块设备。

块设备与字符设备的区别仅仅在于驱动向内核提供的接口不一样,而向用户层提供的接口是一样的。

3、网络接口
即可以是一个硬件设备,如网卡;也可以是纯软件的设备。比如回环接口(lo)。一个网络接口负责 发送和接收数据报文。
对于网络驱动程序并不同于字符设备和块设备,库、内核提供了一套和数据包传输相关的函数,而不是普通的系统调用(open\write)

按照总线分类法,也可分为以下三类:
1、USB 设备
2、PCI设备
3、平台总线设备
譬如USB无线网卡:按常规分类为网络接口,按总线分类:USB设备。

驱动学习方法:

我们知道Linux内核就是由各种驱动组成的,内核源码中大约85%都为驱动程序的代码。内核中实现的驱动程序种类齐全,我们可以在通类型驱动的基础上进行修改以符合具体的设备。
学习驱动更重要的是 搞清楚现有驱动的框架,在这个框架上添加相应硬件。

对于硬件操作,可参考ARM裸机代码,将其移植到驱动框架中去。

还有一点就是 在驱动学习初期最好不要过多去阅读内核代码,以免造成混乱。


硬件访问:

硬件访问的实质:驱动程序控制设备,主要是通过设备内的寄存器来达到控制的目的,因此我们讨论如何访问硬件,就成了如何访问这些寄存器了。

一、地址映射

裸机直接使用物理地址去操作寄存器;而在Linux系统中使用的为虚拟地址(无论内核程序还是应用程序)。则当操作寄存器时,需要完成物理地址到虚拟地址的映射。
地址映射又分为:
1.1动态映射:
在驱动程序中采用ioremap函数将物理地址映射为虚拟地址。
函数原型:void * ioremap(physaddr, size)
参数:physaddr:待映射的物理地址
size:映射的区域长度
返回值:映射后的虚拟地址

1.2静态映射:是指Linux系统根据用户事先指定的映射关系在内核启动时,自动地将物理地址映射为虚拟地址。

映射的举例:IO内存的静态映射,linux系统在建立IO内存物理地址到虚拟地址的映射时,映射关系是怎么指定的呢?这就需要map_desc这个结构数组了,映射就是在这个结构数组中添加新成员来完成的。
即在静态映射中,用户 是通过map_desc结构来指明物理地址与虚拟地址的映射关系 。
文件Map.h中 (linux-ok6410\arch\arm\include\asm\mach)在静态映射中,用 户 是通过map_desc结构来指明物理地址与虚拟地址的映射关系 。
struct map_desc {
unsigned long virtual; /* 映射后的虚拟地址 */
unsigned long pfn; /* 物理地址所在的页帧号 */
unsigned long length; /* 映射长度 */
unsigned int type; /* 映射的设备类型 */
};


pfn: 利用 __phys_to_pfn(物理地址)可以计算出物理地址所在的物理页帧号
对于6410处理器,关于该结构的填充如下:
Cpu.c (linux-ok6410\arch\arm\mach-s3c64xx) 
/* minimal IO mapping */
static struct map_desc s3c_iodesc[] __initdata = {
	{
		.virtual	= (unsigned long)S3C_VA_SYS,
		.pfn		= __phys_to_pfn(S3C64XX_PA_SYSCON),
		.length		= SZ_4K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)S3C_VA_MEM,
		.pfn		= __phys_to_pfn(S3C64XX_PA_SROM),
		.length		= SZ_4K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)(S3C_VA_UART + UART_OFFS),
		.pfn		= __phys_to_pfn(S3C_PA_UART),
		.length		= SZ_4K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)VA_VIC0,
		.pfn		= __phys_to_pfn(S3C64XX_PA_VIC0),
		.length		= SZ_16K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)VA_VIC1,
		.pfn		= __phys_to_pfn(S3C64XX_PA_VIC1),
		.length		= SZ_16K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)S3C_VA_TIMER,
		.pfn		= __phys_to_pfn(S3C_PA_TIMER),
		.length		= SZ_16K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)S3C64XX_VA_GPIO,
		.pfn		= __phys_to_pfn(S3C64XX_PA_GPIO),
		.length		= SZ_4K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)S3C64XX_VA_MODEM,
		.pfn		= __phys_to_pfn(S3C64XX_PA_MODEM),
		.length		= SZ_4K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)S3C_VA_WATCHDOG,
		.pfn		= __phys_to_pfn(S3C64XX_PA_WATCHDOG),
		.length		= SZ_4K,
		.type		= MT_DEVICE,
	}, {
		.virtual	= (unsigned long)S3C_VA_USB_HSPHY,
		.pfn		= __phys_to_pfn(S3C64XX_PA_USB_HSPHY),
		.length		= SZ_1K,
		.type		= MT_DEVICE,
	},


};


内核启动时,在以下函数内完成自动映射
/* read cpu identification code */

void __init s3c64xx_init_io(struct map_desc *mach_desc, int size)
{
	unsigned long idcode;

	/* initialise the io descriptors we need for initialisation */
	iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc)); //建立映射
	iotable_init(mach_desc, size);

	idcode = __raw_readl(S3C_VA_SYS + 0x118);
	if (!idcode) {
		/* S3C6400 has the ID register in a different place,
		 * and needs a write before it can be read. */

		__raw_writel(0x0, S3C_VA_SYS + 0xA1C);
		idcode = __raw_readl(S3C_VA_SYS + 0xA1C);
	}

	s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids));
}


二、寄存器读写

完成地址映射后,就可以读写寄存器了,linux内核(3.0.1)提供了一系列函数,来读取寄存器
/* read cpu identification code */

void __init s3c64xx_init_io(struct map_desc *mach_desc, int size)
{
	unsigned long idcode;

	/* initialise the io descriptors we need for initialisation */
	iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc)); //建立映射
	iotable_init(mach_desc, size);

	idcode = __raw_readl(S3C_VA_SYS + 0x118);
	if (!idcode) {
		/* S3C6400 has the ID register in a different place,
		 * and needs a write before it can be read. */

		__raw_writel(0x0, S3C_VA_SYS + 0xA1C);
		idcode = __raw_readl(S3C_VA_SYS + 0xA1C);
	}

	s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids));
}












你可能感兴趣的:(地址映射,Linux内核模块,硬件访问,驱动分类)