驱动程序实例(一):LED设备驱动程序( platform + cdev)
结合之前对Linux内核的platform总线 ,以及对字符设备的cdev接口的分析,本文将编写基于platform总线与cdev接口的LED设备的实例代码并对其进行分析。
platform总线分析,详见Linux platform驱动模型。
字符设备的cdev接口分析,详见Linux字符设备驱动(一):cdev接口。
硬件接口:
CPU:s5pv210;
LED的GPIO:GPIO_J0_3 ~ GPIO_J0_5;
LED的工作方式:低电平亮,高电平灭。
1. led_device.c
本文将设备信息写成一个模块的形式,需要时加载该模块即可。
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/ioport.h> #include <linux/platform_device.h> //定义并初始化LED设备的相关资源 static struct resource led_resource = { .start = 0xE0200240, .end = 0xE0200240 + 8 - 1, .flags = IORESOURCE_MEM, }; //定义并初始化LED设备信息 static struct platform_device led_dev = { .name = "led", //设备名称 .id = -1, //设备数量,-1表示只有一个设备 .num_resources = 1, //资源数量 .resource = &led_resource, //资源指针 .dev = { .release = led_release, }, }; //注册LED设备 static int __init led_device_init(void) { return platform_device_register(&led_dev); } //注销LED设备 static void __exit led_device_exit(void) { platform_device_unregister(&led_dev); } module_init(led_device_init); module_exit(led_device_exit); MODULE_AUTHOR("Lin"); MODULE_DESCRIPTION("led device for x210"); MODULE_LICENSE("GPL");
2. led_driver.c
led_driver_init():模块加载函数
platform_driver_register()将驱动对象模块注册到平台总线
led_probe()探测函数,提取相应的信息
platform_get_resource()获取设备资源
request_mem_region()、ioremap()虚拟内存映射
readl()、write()初始化硬件设备
cdev_alloc()申请cdev内存
cdev_init()初始化cdev对象,将cdev与fops结构体绑定
alloc_chrdev_region()申请设备号
cdev_add()注册LED设备对象,将cdev添加至系统字符设备链表中
class_create()创建设备类
device_create()创建设备文件
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/ioport.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <asm/io.h> #include <linux/leds.h> #include <asm/uaccess.h> #include <linux/cdev.h> #include <linux/kdev_t.h> #include <linux/ioctl.h> #define LED_IOC_MAGIC 'l' //ioctl幻数 #define LED_IOC_MAXNR 2 //ioctl最大命令序数 #define LED_ON _IO(LED_IOC_MAGIC, 0) //ioctl自定义命令 #define LED_OFF _IO(LED_IOC_MAGIC, 1) #define DEVNAME "led" //设备名称 static int led_major = 0; //主设备号 static int led_minor = 0; //次设备号 const int led_count = 1; //次设备数量 //GPIO寄存器变量定义 typedef struct GPJ0REG { volatile unsigned int gpj0con; volatile unsigned int gpj0dat; }gpj0_reg_t; static gpj0_reg_t *pGPIOREG = NULL; static dev_t led_devnum; //设备号 static struct cdev *led_cdev = NULL; //设备对象 static struct class *led_cls = NULL; //设备类 static struct device *led_dev = NULL; //设备 //LED设备的ioctl函数实现 static int led_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { int reg_value = 0; //检测命令的有效性 if (_IOC_TYPE(cmd) != LED_IOC_MAGIC) return -EINVAL; if (_IOC_NR(cmd) > LED_IOC_MAXNR) return -EINVAL; //根据命令,执行相应的硬件操作 switch(cmd) { case LED_ON: reg_value = readl(&pGPIOREG->gpj0dat); reg_value &= ~((1 << 3) | (1 << 4) | (1 << 5)); writel(&pGPIOREG->gpj0dat, reg_value); break; case LED_OFF: reg_value = readl(&pGPIOREG->gpj0dat); reg_value |= (1 << 3) | (1 << 4) | (1 << 5); writel(&pGPIOREG->gpj0dat, reg_value); break; default: return -EINVAL; } return 0; } static int led_open(struct inode *inode, struct file *filp) { return 0; } static int led_release(struct inode *inode, struct file *filp) { return 0; } //LED设备的write函数实现 static ssize_t led_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { char kbuf[20] = {0}; int reg_value = 0; memset(kbuf, 0, sizeof(kbuf)); if (copy_from_user(kbuf, user_buf, count)) { return -EFAULT; } if (kbuf[0] == '0') { reg_value = readl(&(pGPIOREG->gpj0dat)); reg_value |= (1 << 3) | (1 << 4) | (1 << 5); writel(reg_value, &(pGPIOREG->gpj0dat)); } else { reg_value = readl(&(pGPIOREG->gpj0dat)); reg_value &= ~((1 << 3) | (1 << 4) | (1 << 5)); writel(reg_value, &(pGPIOREG->gpj0dat)); } return 1; } //定义并初始化LED设备的操作集 static const struct file_operations led_ops = { .owner = THIS_MODULE, .open = led_open, .write = led_write, .ioctl = led_ioctl, .release = led_release, }; //LED设备的probe函数实现 static int led_probe(struct platform_device *pdev) { struct resource *res_led = NULL; int ret = -1; int reg_value = 0; int i = 0; /****************************申请资源*******************************/ //获取资源 res_led = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res_led) { return -ENOMEM; } //动态内存映射 if (!request_mem_region(res_led->start, resource_size(res_led), "GPIOJ0")) { return -EBUSY; } pGPIOREG = ioremap(res_led->start, resource_size(res_led)); if (pGPIOREG == NULL) { ret = -ENOENT; goto ERR_STEP; } /****************************初始化资源*******************************/ //设置GPIO为输出模式 reg_value = readl(&(pGPIOREG->gpj0con)); reg_value |= (1 << (3*4)) | (1 << (4*4)) | (1 << (5*4)); writel(reg_value, &(pGPIOREG->gpj0con)); /***************************创建接口(cdev)***************************/ //申请cdev内存 led_cdev = cdev_alloc(); if (led_cdev == NULL) { ret = -ENOMEM; goto ERR_STEP1; } //初始化led_cdev(将led_cdev于led_ops关联) cdev_init(led_cdev, &led_ops); //申请设备号 ret = alloc_chrdev_region(&led_devnum, led_minor, led_count, DEVNAME); if (ret < 0) { goto ERR_STEP1; } //注册LED设备对象(将cdev添加至系统字符设备链表中) ret = cdev_add(led_cdev, led_devnum, led_count); if (ret < 0) { goto ERR_STEP2; } //创建设备类 led_cls = class_create(THIS_MODULE, DEVNAME); if (IS_ERR(led_cls)) { ret = PTR_ERR(led_cls); goto ERR_STEP3; } //在设备类下创建设备文件 led_major = MAJOR(led_devnum); for(i = led_minor; i < (led_count + led_minor); i++) { led_dev = device_create(led_cls, NULL, MKDEV(led_major, i), NULL, "%s%d", DEVNAME, i); if(IS_ERR(led_dev)) { ret = PTR_ERR(led_dev); goto ERR_STEP4; } } return 0; /*******************************倒映式错误处理*******************************/ ERR_STEP4: for(--i; i >= led_minor; i--) { device_destroy(led_cls, MKDEV(led_major, i)); } class_destroy(led_cls); ERR_STEP3: cdev_del(led_cdev); ERR_STEP2: unregister_chrdev_region(led_devnum, led_count); ERR_STEP1: iounmap(pGPIOREG); ERR_STEP: release_mem_region(res_led->start, resource_size(res_led)); return ret; } int led_remove(struct platform_device *pdev) { int i = 0; //删除设备文件 for(i = led_minor; i < (led_count + led_minor); i++) { device_destroy(led_cls, MKDEV(led_major, i)); } //删除设备类 class_destroy(led_cls); //删除设备对象 cdev_del(led_cdev); //注销设备号 unregister_chrdev_region(led_devnum, led_count); //释放内存 iounmap(pGPIOREG); return 0; } //定义并初始化LED驱动信息 static struct platform_driver led_drv = { .driver = { .name = "led", .owner = THIS_MODULE, }, .probe = led_probe, .remove = led_remove, }; //注册LED驱动 static int __init led_driver_init(void) { return platform_driver_register(&led_drv); } //注销LED驱动 static void __exit led_driver_exit(void) { platform_driver_unregister(&led_drv); } module_init(led_driver_init); module_exit(led_driver_exit); MODULE_AUTHOR("Lin"); MODULE_DESCRIPTION("led driver for x210"); MODULE_LICENSE("GPL");
3. 测试所用应用程序
运行应用程序之前,需确保上述两个模块(device、driver)被装载。运行结果表明LED设备能被应用程序操作。
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <sys/ioctl.h> #define FILE_NAME "/dev/led0" #define LED_ON _IO(LED_IOC_MAGIC, 0) #define LED_OFF _IO(LED_IOC_MAGIC, 1) char WriteBuf[30]; char ReadBuf[30]; char ScanBuf[30]; int main(void) { int fd = -1; int i = 0; //打开设备文件 if ((fd = open(FILE_NAME, O_RDWR)) < 0) { printf("%s open error\n", FILE_NAME); return -1; } while (1) { memset(ScanBuf, 0, sizeof(ScanBuf)); printf("please input data for LED\n"); if (scanf("%s", ScanBuf)) { //打开LED设备 if (!strcmp(ScanBuf, "on")) { write(fd, "1", 1); } //关闭LED设备 else if (!strcmp(ScanBuf, "off")) { write(fd, "0", 1); } //闪烁LED设备 else if (!strcmp(ScanBuf, "flash")) { for (i=5; i>0; i--) { ioctl(fd, LED_ON); sleep(1); ioctl(fd, LED_OFF); sleep(1); } }
else { break; } } } close(fd); return 0; }