Linux驱动开发前奏

目录

1、驱动分类

2、 驱动学习方法

3、硬件访问技术

3.1 地址映射

3.2 寄存器读写


1、驱动分类

        驱动分类有很多种方法,比如常规分类法和总线分类法。

        常规分类法:分为字符设备、块设备和网络设备。

        字符设备:是一种按字节来访问的设备,字符驱动负责驱动字符设备,这样的驱动实现open、close、read和write系统调用,例如:串口、LED、按键。

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

        网络设备:网络接口可以是一个网络设备,如网卡;但也可以是一个纯粹的软件设备,比如回环接口(lo)。一个网络接口负责发送和接受数据报文。

        总线分类法:USB设备、PCI设备、平台总线设备。

2、 驱动学习方法

        ①、驱动程序模型:我们知道Linux内核就是由各种驱动组成的,内核源码中大约85%都为驱动程序的代码;内核中实现的驱动程序种类齐全,我们可以在通类型驱动的基础上进行修改以符合具体的设备;学习驱动更重要的是 搞清楚现有驱动的框架(一般是分析范例->制作思维导图->自己编写代码->驱动程序模型),在这个框架上添加相应硬件。

        ②、硬件操作实现:可参考ARM裸机代码,将其移植到驱动框架中去。

        ③、驱动程序测试

3、硬件访问技术

        驱动程序控制设备,主要是通过访问设备内的寄存器来达到控制目的,因此我们讨论如何访问硬件,就是如何访问这些寄存器了,访问流程主要有两部分:地址映射和寄存器读写。

3.1 地址映射

        在linux系统中,无论是内核程序还是应用程序,都是以虚拟地址访问,而芯片手册中给出的硬件寄存器地址或者RAM地址则是物理地址,无法直接使用,因此,我们读写寄存器的第一步就是将它的物理地址映射为虚拟地址。

        ①、动态映射

        所谓动态映射,是指在驱动程序中采用ioremap函数将物理地址映射为虚拟地址。

原型:void* ioremap(physaddr,size)
参数:physaddr:带映射的物理地址
      size:映射的区域长度
返回值:映射后的虚拟地址   

        ②、静态映射

        所谓静态映射,是指Linux系统根据用户事先制定的 映射关系,在内核启动时,自动的将物理地址映射为虚拟地址,仔细思考会考虑这两个问题1、如何事先制定映射关系?2、内核启动后,在什么地方完成自动映射?

        映射的举例: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(物理地址)可以计算出物理地址所在的物理页帧号

      对于ok6410处理器,该结构填充如下:

/* 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));
}

3.2 寄存器读写

        完成地址映射后,就可以读写寄存器了,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));
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值