字符驱动设计----mini2440LED驱动设计之路
来源:互联网 发布:有hao123软件 编辑:程序博客网 时间:2024/06/15 14:17
之前有一段时间学过驱动,学的很吃力,总是理解不通透,于是就放下了。最近再拾起时,思考之前遇到的问题,归结于零碎化,于是试着换一种思路去探究。
大多数书籍在介绍字符驱动过于理论化,纵览一章都是些文字,再附上一些零碎的代码,看的人头晕,时间长了自然就不想看了。 对于驱动的学习,刚开始不能过于理论化,一定要结合实际,要不然像空中楼台,住在上面,心里老感觉不踏实。那么如何入手呢?我觉得三点是很重要的:
1 驱动设计的总体框架(对于每种类型的驱动设计,最好画出模型图)
2 参考现有实例化的驱动
3 针对某一具体硬件,自己写驱动来实现
接下来以字符驱动设计为例,也是mini2440led驱动实现。
1 字符设备驱动模型如下图所示,这是一个总体调用框架图,具体的字符设备驱动模型参照另外一篇引用的博客【字符设备驱动模型】,驱动层主要做的工作是file_operations结构体中一些关键函数的实现,包括open,read,ioctl。本例中主要实现open,ioctl。
2 现有驱动模型实例
#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/delay.h>#include <asm/irq.h>#include <asm/arch/regs-gpio.h>#include <asm/hardware.h>#define DEVICE_NAME "leds" /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */#define LED_MAJOR 231 /* 主设备号 *//* 应用程序执行ioctl(fd, cmd, arg)时的第2个参数 */#define IOCTL_LED_ON 0#define IOCTL_LED_OFF 1/* 用来指定LED所用的GPIO引脚 */static unsigned long led_table [] = { S3C2410_GPB5, S3C2410_GPB6, S3C2410_GPB7, S3C2410_GPB8,};/* 用来指定GPIO引脚的功能:输出 */static unsigned int led_cfg_table [] = { S3C2410_GPB5_OUTP, S3C2410_GPB6_OUTP, S3C2410_GPB7_OUTP, S3C2410_GPB8_OUTP,};/* 应用程序对设备文件/dev/leds执行open(...)时, * 就会调用s3c24xx_leds_open函数 */static int s3c24xx_leds_open(struct inode *inode, struct file *file){ int i; for (i = 0; i < 4; i++) { // 设置GPIO引脚的功能:本驱动中LED所涉及的GPIO引脚设为输出功能 s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]); } return 0;}/* 应用程序对设备文件/dev/leds执行ioclt(...)时, * 就会调用s3c24xx_leds_ioctl函数 */static int s3c24xx_leds_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){ if (arg > 4) { return -EINVAL; } switch(cmd) { case IOCTL_LED_ON: // 设置指定引脚的输出电平为0 s3c2410_gpio_setpin(led_table[arg], 0); return 0; case IOCTL_LED_OFF: // 设置指定引脚的输出电平为1 s3c2410_gpio_setpin(led_table[arg], 1); return 0; default: return -EINVAL; }}/* 这个结构是字符设备驱动程序的核心 * 当应用程序操作设备文件时所调用的open、read、write等函数, * 最终会调用这个结构中指定的对应函数 */static struct file_operations s3c24xx_leds_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = s3c24xx_leds_open, .ioctl = s3c24xx_leds_ioctl,};/* * 执行“insmod s3c24xx_leds.ko”命令时就会调用这个函数 */static int __init s3c24xx_leds_init(void){ int ret; /* 注册字符设备驱动程序 * 参数为主设备号、设备名字、file_operations结构; * 这样,主设备号就和具体的file_operations结构联系起来了, * 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数 * LED_MAJOR可以设为0,表示由内核自动分配主设备号 */ ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops); if (ret < 0) { printk(DEVICE_NAME " can't register major number\n"); return ret; } printk(DEVICE_NAME " initialized\n"); return 0;}/* * 执行”rmmod s3c24xx_leds.ko”命令时就会调用这个函数 */static void __exit s3c24xx_leds_exit(void){ /* 卸载驱动程序 */ unregister_chrdev(LED_MAJOR, DEVICE_NAME);}/* 这两行指定驱动程序的初始化函数和卸载函数 */module_init(s3c24xx_leds_init);module_exit(s3c24xx_leds_exit);/* 描述驱动程序的一些信息,不是必须的 */MODULE_AUTHOR("http://my.csdn.net/czxyhll."); // 驱动程序的作者MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver"); // 一些描述信息MODULE_LICENSE("GPL"); // 遵循的协议
现在就对字符设备驱动进行分析:
1 在open函数里有s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]),此函数实现相应GPIO的功能,包括输入输出及其他功能。这个函数看似简单,如果想彻底弄明白还是有一定的难度,应为此函数的实现涉及到I/O内存空间,物理地址PA与虚拟地址VA的映射,GPIO寄存器的方面的知识。先附上其函数原型。
void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function){void __iomem *base = S3C24XX_GPIO_BASE(pin);unsigned long mask;unsigned long con;unsigned long flags;if (pin < S3C2410_GPIO_BANKB) {mask = 1 << S3C2410_GPIO_OFFSET(pin);} else {mask = 3 << S3C2410_GPIO_OFFSET(pin)*2;}switch (function) {case S3C2410_GPIO_LEAVE:mask = 0;function = 0;break;case S3C2410_GPIO_INPUT:case S3C2410_GPIO_OUTPUT:case S3C2410_GPIO_SFN2:case S3C2410_GPIO_SFN3:if (pin < S3C2410_GPIO_BANKB) {function -= 1;function &= 1;function <<= S3C2410_GPIO_OFFSET(pin);} else {function &= 3;function <<= S3C2410_GPIO_OFFSET(pin)*2;}}/* modify the specified register wwith IRQs off */local_irq_save(flags);con = __raw_readl( );con &= ~mask;con |= function;__raw_writel(con, base + 0x00);local_irq_restore(flags);}
此函数里比较关键的两点:1 传递过来的实参值 led_table[i], led_cfg_table[i]。这里讨论当i=0的情况,即led_table[0]=S3C2410_GPB5,led_cfg_table[0]=S3C2410_GPB5_OUTP的值。
2 *base = S3C24XX_GPIO_BASE(pin),*base到底等于多少?
3 GPIO寄存器的读写。
先讨论第1点,S3C2410_GPB5的值,根据以下宏定义 #define S3C2410_GPB5 S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5)
#define S3C2410_GPIONO(bank,offset) ((bank) + (offset))
#define S3C2410_GPIO_BANKB (32*1)
可获得S3C2410_GPB5=32*1+5=37=0b100101=pin //取i=0时
S3C2410_GPB5_OUTP的值,根据以下宏定义 #define S3C2410_GPB5_OUTP (0x01 << 10)
可获知 S3C2410_GPB5_OUTP= (0x01 << 10)=function //对应GPB5[11:10],参考芯片手册
现在来讨论第2点,*base的值,这个比较麻烦。
#define S3C24XX_GPIO_BASE(x) S3C2410_GPIO_BASE(x)
#define S3C2410_GPIO_BASE(pin) ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
#define S3C24XX_VA_GPIO ((S3C24XX_PA_GPIO - S3C24XX_PA_UART) + S3C24XX_VA_UART)
#define S3C24XX_PA_GPIO S3C2410_PA_GPIO
#define S3C2410_PA_GPIO (0x56000000)
#define S3C24XX_PA_UART S3C2410_PA_UART
#define S3C2410_PA_UART (0x50000000)
#define S3C24XX_VA_UART S3C_VA_UART
#define S3C_VA_UART S3C_ADDR(0x01000000)/* UART */
#define S3C_ADDR(x) (S3C_ADDR_BASE + (x))
#define S3C_ADDR_BASE (0xF4000000) // I/O内存地址基地址,为什么从0xF4000000开始,
自底向上进行分析,S3C24XX_VA_UART=(0xF4000000+0x01000000)=0xF5000000 //Uart的虚拟起始基地址,这个值也可以是其他数值,只要映射时不冲突就行,分配时尽量往高端,为了使动态映射区足够大供内核使用。
S3C2410_PA_UART=0x50000000 //uart物理地址
S3C24XX_VA_GPIO=(0x56000000-0x50000000)+0xF5000000=0xFB000000 //GPIO的虚拟起始基址,看到这儿有什么想法,最直接的一个想法是对于I/0端口映射是线性的。参照下图
*base=S3C2410_GPIO_BASE(37)=((((37) & ~31) >> 1) + 0xFB000000 =0b10000+0xFB000000=0xFB000010 //GPBCON对于的虚拟地址
((((pin) & ~31) >> 1) 的含义很明确,就是确定是哪个GPXCON寄存器的地址(X=A,B...H)。
接下来讨论GPIO寄存器的读写,代码如下。local_irq_save(flags); //关中断con = __raw_readl( ); con &= ~mask;con |= function;__raw_writel(con, base + 0x00); //设置相应寄存器local_irq_restore(flags); //开中断
这几条语句就是设置GPBCON寄存器为function功能,这里设置GPB5[11:10]引脚为输出功能
接下来讨论第二个函数 s3c2410_gpio_setpin(led_table[arg], 0)
函数原型如下:
void s3c2410_gpio_setpin(unsigned int pin, unsigned int to){void __iomem *base = S3C24XX_GPIO_BASE(pin);unsigned long offs = S3C2410_GPIO_OFFSET(pin);unsigned long flags;unsigned long dat;local_irq_save(flags);dat = __raw_readl(base + 0x04);dat &= ~(1 << offs);dat |= to << offs;__raw_writel(dat, base + 0x04);local_irq_restore(flags);}
EXPORT_SYMBOL(s3c2410_gpio_setpin);
S3C24XX_GPIO_BASE(pin) //上文中已经分析了其用途,主要是获取每组寄存器的基地址。例如GPBCON
S3C2410_GPIO_OFFSET(pin) //寄存器组内偏移地址。例如GPB5
local_irq_save(flags); //关闭中断
dat = __raw_readl(base + 0x04); //读寄存器内容,base+0x04表示寄存器GPXDAT (X=A,B,C...F)
dat &= ~(1 << offs); // 清空其要设置的位,保持其他位不变
dat |= to << offs; //
__raw_writel(dat, base + 0x04); // 设置寄存器GPXDAT
local_irq_restore(flags); //开中断
刚开始感觉mini2440led驱动设计比较难,很多地方不懂,待仔细分析后,主要难道就是上面重点分析的两个函数。这里有几个函数没有展开进行分析,像__raw_readl() ,__raw_writel(),local_irq_save(),local_irq_restore()。这些函数大家可以展开分析。好了,写的手有点发酸了,该休息了。
- 字符驱动设计----mini2440LED驱动设计之路
- 字符驱动设计文档
- 字符设备驱动设计
- Mini2440LED平台设备驱动
- 中断驱动设计----mini2440 按键驱动设计之路
- 字符设备驱动设计原理
- 对mini2440LED灯驱动开发
- Linux驱动之内核定时器驱动设计
- linux驱动之内核定时器驱动设计
- 领域驱动设计实现之路
- 领域驱动设计实现之路
- 领域驱动设计实现之路
- Linux驱动设计之信号量
- 字符驱动设计----我的学习参考资料
- Linux字符设备驱动设计知识点
- mini2440Led驱动——linux-3.10.59
- 设计浅谈 -- 分层设计之驱动框架
- 设计模式驱动设计
- 大数据量,海量数据 处理方法总结
- 创建和读写文件的一些简单方法
- experiment : convert a string to crc32
- Google C++编程风格指南(非常有用)
- poj1125 - Stockbroker Grapevine
- 字符驱动设计----mini2440LED驱动设计之路
- Android上dip、dp、px、sp等单位说明
- Linux设备模型(中)之上层容器 (转)
- 常见设计模式之【适配器模式】
- windows下的dll文件和linux下的.so文件
- maven 配置
- Android 报错 - 无法解析类型 java.lang.Object。从必需的 .class 文件间接引用了它
- 动态数据类型 笔记
- android学习之一 工程目录认识