学习 LED 驱动的小结
s3c_led.c
学习小结 学习字符设备驱动首先要知道 insmod 加载驱动后调用的 init 函数注册设备驱动的基本流程:
- /* Every Linux kernel module must include this head */
- #include < linux / init.h >
- /* Every Linux kernel module must include this head */
- #include < linux / kernel.h >
- /* printk() */
- #include < linux / fs.h >
- /* struct fops */
- #include < linux / errno.h >
- /* error codes */
- #include < linux / cdev.h >
- /* cdev_alloc() */
- #include < asm / io.h >
- /* ioremap() */
- #include < linux / ioport.h >
- /* request_mem_region() */
- #include < asm / ioctl.h >
- /* Linux kernel space head file for macro _IO() to generate ioctl command */
- #ifndef __KERNEL__#include < sys / ioctl.h >
- /* User space head file for macro _IO() to generate ioctl command */
- #define DEV_NAME "led" //定义设备名称
- #define LED_NUM 4 //定义设备数量
- /* Set the LED dev major number */
- #ifndef LED_MAJOR#define LED_MAJOR 0 //定义默认的主设备号为0,一般这个定义的设备号是固定不可用的,但是这为自动分配主设备号的逻辑提供了方便。
- #endif#define DRV_MAJOR_VER 1#define DRV_MINOR_VER 0#define DRV_REVER_VER 0#define DISABLE 0 //禁用某个特性的宏
- #define ENABLE 1 //使能某个特性的宏
- #define GPIO_INPUT 0x00 //宏定义 GPIO输入模式用00代替
- #define GPIO_OUTPUT 0x01 //宏定义 GPIO输出模式用01代替
- #define PLATDRV_MAGIC 0x60 //定义了一个魔数
- #define LED_OFF _IO(PLATDRV_MAGIC, 0x18) //#define __IO volatile 的作用就是指示编译器不要因优化而省略此指令,必须每次都直接读写其值。
- #define LED_ON _IO(PLATDRV_MAGIC, 0x19)
- /*魔数有着特殊的功能。我们定义了led_on和led_off,但是这个宏定义可能和系统的别的重复,因此我们采用魔数机制,
- 定义一个系统未用的魔数,然后让魔数生成我们定义的led_on和led_off, 这样,我们的定义就不会和系统的相同了*/
- #define S3C_GPB_BASE 0x56000010 //定义led GPB引脚控制寄存器的基址
- #define GPBCON_OFFSET 0 //定义GPBCON偏移地址 (GPBCON是用来选定引脚并设置输入或者输出模式)
- #define GPBDAT_OFFSET 4 // 定义GPBDAT偏移地址
- //GPBDAT寄存器用于读/写 引脚数据;
- //当引脚被设为输入时,读此寄存器可知相应引脚的电平状态是高还是低;
- //当引脚被设为输出时,写此寄存器相应位可以令此引脚输出高电平或是低电平。
- #define GPBUP_OFFSET 8 // 定义GPBUP偏移地址
- //某位为1时,相应引脚无内部上拉电阻;
- //为0时,相应引脚使用内部上拉电阻。
- //上拉电阻的作用在于:当GPIO引脚处于第三态(即不是输出高电平,也不是输出低电平,而是呈高阻态,即相当于没接芯片)时,它的电平状态由上拉电阻、下拉电阻确定。
- #define S3C_GPB_LEN 0x10
- /* 0x56000010~0x56000020 */
- //GPB寄存器的内存地址总长度
- int led[LED_NUM] = {
- 5,
- 6,
- 8,
- 10
- };
- /* Four LEDs use GPB5,GPB6,GPB8,GPB10 */
- static void __iomem * s3c_gpb_membase; //定义s3c_gpb_membase为void __iomem*类型指针 ,在后面用来保存映射后的虚拟空间基地址。
- #define s3c_gpio_write(val, reg) __raw_writel((val), (reg) + s3c_gpb_membase) //设置当前寄存器的值的宏
- #define s3c_gpio_read(reg) __raw_readl((reg) + s3c_gpb_membase) //读取当前虚拟地址寄存器的值
- int dev_count = ARRAY_SIZE(led); //设备个数赋值给dev_count
- int dev_major = LED_MAJOR; //主设备号赋值给dev_major
- int dev_minor = 0; //次设备号赋值0
- int debug = DISABLE; //出错定义赋值
- static struct cdev * led_cdev; //定义一个cdev类型的结构体指针,cdev是内核中表示字符设备的一个结构体
- static int s3c_hw_init(void) //硬件初始化函数
- {
- int i;
- volatile unsigned long gpb_con,
- gpb_dat,
- gpb_up; //分别定义gob_con ;gpb_dat ;gpb_up变量,因为是保存寄存器地址的所以要用volatile类型,防止被优化。
- if (!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN, "s3c2440 led"))
- //request_mem_region这个宏是为申请I/O内存的函数
- //#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name))
- //其中,参数start是I/O内存资源的起始物理地址(是CPU在RAM物理地址空间中的物理地址),
- //参数n指定I/O内存资源的大小。
- //在请求IO内存资源成功后,开始用ioremap进行映射操作.
- {
- return - EBUSY;
- }
- //判断是否申请失败,如果硬件资源被占据着则返回-EBUSY.成功则往下执行。
- if (! (s3c_gpb_membase = ioremap(S3C_GPB_BASE, S3C_GPB_LEN))) //ioremap用来将IO资源的物理地址映射到内核虚拟地址空间
- {
- release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN); //若映射失败则,释放指定的I/O内存资源。
- return - ENOMEM;
- } //内存申请成功切地址映射成功则进行对各个寄存器的操作
- for (i = 0; i < dev_count; i++) //LED灯默认初始状态为全灭
- {
- /* Set GPBCON register, set correspond GPIO port as input or output mode */
- gpb_con = s3c_gpio_read(GPBCON_OFFSET); //读取GPBCON的虚拟地址并保存在gpb_con中
- gpb_con &= ~ (0x3 << (2 * led[i]));
- /* Clear the correspond LED GPIO configure register 先将相应的led的gpio引脚复位清零*/
- gpb_con |= GPIO_OUTPUT << (2 * led[i]);
- /* Set the correspond LED GPIO as output mode 然后将相应的led的gpio引脚设置为输出模式*/
- s3c_gpio_write(gpb_con, GPBCON_OFFSET); //再将设置好的GPBCON引脚信息写入寄存器中
- /* Set GPBUP register, set correspond GPIO port pull up resister as enable or disable */
- gpb_up = s3c_gpio_read(GPBUP_OFFSET); //读取GPBUP的虚拟地址并保存在gpb_up中
- gpb_up &= ~ (0x1 << led[i]);
- /* Enable pull up resister */
- gpb_up |= (0x1 << led[i]);
- /* Disable pull up resister 关闭相应的led的gpbio引脚的上拉电阻*/
- s3c_gpio_write(gpb_up, GPBUP_OFFSET); //将设置好的GPBUP引脚信息写入寄存器中
- /* Set GPBDAT register, set correspond GPIO port power level as high level or low level */
- gpb_dat = s3c_gpio_read(GPBDAT_OFFSET); //读取GPBDAT的虚拟地址并保存在gpb_dat中
- gpb_dat &= ~ (0x1 << led[i]);
- /* This port set to low level, then turn LED on */
- gpb_dat |= (0x1 << led[i]);
- /* This port set to high level, then turn LED off 将这个引脚的电平拉高*/
- s3c_gpio_write(gpb_dat, GPBDAT_OFFSET); //将设置好的GPBDAT引脚信息写入寄存器中
- }
- return 0;
- }
- static void turn_led(int which, unsigned int cmd) //由ioctl调用,控制单个LED的亮灭
- {
- volatile unsigned long gpb_dat;
- gpb_dat = s3c_gpio_read(GPBDAT_OFFSET); //设置gpb_dat变量来存储GPBDAT的在内存中的虚拟地址
- if (LED_ON == cmd) {
- gpb_dat &= ~ (0x1 << led[which]);
- /* Turn LED On */
- } else if (LED_OFF == cmd) {
- gpb_dat |= (0x1 << led[which]);
- /* Turn LED off */
- }
- s3c_gpio_write(gpb_dat, GPBDAT_OFFSET); //设置好的GPBDAT引脚信息写入内存
- }
- static void s3c_hw_term(void) //调用LED结束后释放所占内存资源及设置相应GPIO引脚关闭LED
- {
- int i;
- volatile unsigned long gpb_dat;
- for (i = 0; i < dev_count; i++) {
- gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
- gpb_dat |= (0x1 << led[i]);
- /* Turn LED off */
- s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
- } //设置相应的GPBDAT引脚信息,使LED不通
- release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN); //释放指定的IO内存资源
- iounmap(s3c_gpb_membase); //iounmap函数用于取消ioremap()所做的映射
- }
- static int led_open(struct inode * inode, struct file * file) //驱动功能函数open.
- {
- int minor = iminor(inode);
- /通过索引节点获取次设备号
- file->private_data = (void *)minor;/ / 将次设备号保存到private_data中
- /*private_data用于在系统调用期间保存各种状态信息*/
- printk(KERN_DEBUG "/dev/led%d opened.\n", minor); //打印成功信息
- return 0;
- }
- static int led_release(struct inode * inode, struct file * file) //驱动功能函数release
- {
- printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode));
- return 0;
- }
- //内核中用inode结构表示具体的文件,而用file结构表示打开的文件描述符。
- //struct file代表一个打开的文件,在执行file_operation中的open操作时被创建,
- //这里需要注意的是与用户空间file指针的区别,一个在内核,而file指针在用户空间,由c库来定义。
- //struct inode被内核用来代表一个文件,注意和struct file的区别,struct inode一个是代表文件,struct file一个是代表打开的文件
- static void print_help(void) {
- printk("Follow is the ioctl() commands for %s driver:\n", DEV_NAME);
- //printk("Enable Driver debug command: %u\n", SET_DRV_DEBUG);
- printk("Turn LED on command : %u\n", LED_ON);
- printk("Turn LED off command : %u\n", LED_OFF);
- return;
- }
- static long led_ioctl(struct file * file, unsigned int cmd, unsigned long arg) //led 中断控制函数
- {
- int which = (int) file - >private_data;
- switch (cmd) {
- case LED_ON:
- turn_led(which, LED_ON);
- break;
- case LED_OFF:
- turn_led(which, LED_OFF);
- break;
- default:
- printk(KERN_ERR "%s driver don't support ioctl command=%d\n", DEV_NAME, cmd);
- print_help();
- break;
- } //实现具体第几盏灯亮或灭
- static struct file_operations led_fops = //结构体file_operations在头文件 linux/fs.h中定义,用来存储驱动内核模块提供的对设备进行各种操作的函数的指针.
- {.owner = THIS_MODULE,
- // 指向拥有该结构的模块的指针,避免正在操作时被卸载,一般为初始化为THIS_MODULES
- .open = led_open,
- //传递led_open函数打开设备
- .release = led_release,
- //传递led_release函数关闭设备
- .unlocked_ioctl = led_ioctl,
- // 传递中断函数,不使用BLK的文件系统,将使用此种函数指针代替ioctl
- };
- static int __init s3c_led_init(void) //在内核中初始化led驱动(底层函数)
- {
- int result;
- dev_t devno;
- if (0 != s3c_hw_init()) //判断硬件是否初始化成功
- {
- printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n");
- return - ENODEV;
- }
- /* Alloc the device for driver */
- if (0 != dev_major)
- /* Static 已知主设备号,静态获得设备编号并注册*/
- {
- devno = MKDEV(dev_major, 0); ////通过主设备号和次设备号构建32位设备号
- result = register_chrdev_region(devno, dev_count, DEV_NAME); //已知主设备号,静态注册字符设备号
- } else {
- result = alloc_chrdev_region( & devno, dev_minor, dev_count, DEV_NAME); //未知主设备号,动态分配设备号
- dev_major = MAJOR(devno); //根据设备号devno获得主设备号
- }
- /* Alloc for device major failure */
- if (result < 0) {
- printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major);
- return - ENODEV;
- }
- printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major); //设备号分配失败则打印错误
- if (NULL == (led_cdev = cdev_alloc())) //创建一个cdev结构体,一个字符设备用一个cdev结构体来描述
- {
- printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME);
- unregister_chrdev_region(devno, dev_count);
- return - ENOMEM;
- } //cdev_alloc它主要完成了空间的申请和简单的初始化操作,分配一个cdev(在内核中代表字符设备),如果失败了则打印出错并释放分配的设备号
- led_cdev - >owner = THIS_MODULE; //指明设备所属模块
- cdev_init(led_cdev, &led_fops); //初始化cdev的file_operations,使字符设备与操作的函数绑定,led_fops结构体中的成员指向驱动提供的功能函数
- result = cdev_add(led_cdev, devno, dev_count); //cdev_add为注册设备函数,通常发生在驱动模块的加载函数中,并将返回值传递给result,为0则成功
- if (0 != result) {
- printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result);
- goto ERROR; //注册失败则打印错误并跳转到出错处理.
- }
- printk(KERN_ERR "S3C %s driver[major=%d] version %d.%d.%d installed successfully!\n", DEV_NAME, dev_major, DRV_MAJOR_VER, DRV_MINOR_VER, DRV_REVER_VER);
- return 0; //注册成功则打印设备的信息.
- ERROR: printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME);
- cdev_del(led_cdev); //注销设备,通常发生在驱动模块的卸载函数中
- unregister_chrdev_region(devno, dev_count); //释放注册的设备号
- return result;
- }
- static void __exit s3c_led_exit(void) //卸载模块函数
- {
- dev_t devno = MKDEV(dev_major, dev_minor);
- s3c_hw_term();
- cdev_del(led_cdev);
- unregister_chrdev_region(devno, dev_count);
- printk(KERN_ERR "S3C %s driver version %d.%d.%d removed!\n", DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER, DRV_REVER_VER);
- return;
- }
- /* These two functions defined in <linux/init.h> */
- module_init(s3c_led_init); // insmod 加载内核模块定义的宏,即加载led驱动
- module_exit(s3c_led_exit); // rmmod 卸载模块所定义的宏,退出内核模块,即卸载led驱动
- module_param(debug, int, S_IRUGO);
- module_param(dev_major, int, S_IRUGO);
- MODULE_AUTHOR(DRV_AUTHOR);
- MODULE_DESCRIPTION(DRV_DESC);
- MODULE_LICENSE("GPL");
来源: http://lib.csdn.net/article/linux/41965