新接口注册LED字符驱动设备
#include <linux/init.h> // __init __exit #include <linux/module.h> // module_init module_exit #include <linux/fs.h> //file_operations #include <asm/uaccess.h> //copy_from_user copy_to_user #include <mach/regs-gpio.h> #include <mach/gpio-bank.h> #include <asm/string.h> #include <linux/ioport.h> //request_mem_region #include <asm/io.h> //ioremap #include <linux/cdev.h> #define MYNAME "led_dev" #define DEVNUM 1 #define S5PV210_PA_GPIOJ0CON 0xe0200240 volatile unsigned int *rGPJ0CON = NULL; volatile unsigned int *rGPJ0DAT = NULL; static dev_t led_dev_no = 0; /************ 静态使用cdev**********/ //static struct cdev led_cdev; /************ 动态使用cdev**********/ static struct cdev *pled_cdev = NULL; static int led_open(struct inode *inode, struct file *file); ssize_t led_read(struct file *file, char __user *user, size_t count, loff_t *loff); ssize_t led_write(struct file *file, const char __user *user, size_t count, loff_t *loff); static int led_release(struct inode *inode, struct file *file); static char kbuf[100] = {0}; static const struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .read = led_read, .write = led_write, .release = led_release, }; int led_open(struct inode *inode, struct file *file) { printk(KERN_INFO "led_open successful\n"); return 0; } ssize_t led_read(struct file *file, char __user *user, size_t ucount, loff_t *loff) { printk(KERN_INFO "led_read successful\n"); if (copy_to_user(user,kbuf , ucount)) { printk(KERN_INFO "copy_to_user fail\n"); return -EINVAL; } printk(KERN_INFO "copy_to_user successful\n"); return strlen(kbuf); } ssize_t led_write(struct file *file, const char __user *user, size_t ucount, loff_t *loff) { printk(KERN_INFO "led_write successful\n"); memset(kbuf,0,sizeof(kbuf)); if (copy_from_user(kbuf, user, ucount)) { printk(KERN_INFO "copy_from_user fail\n"); return -EINVAL; } if(!strcmp(kbuf,"on")) { *rGPJ0CON &=0xff000fff; *rGPJ0CON |=0x00111000; *rGPJ0DAT &=~((0x01<<3)|(0x01<<4)|(0x01<<5)); } else if(!strcmp(kbuf,"off")) { *rGPJ0CON &=0xff000fff; *rGPJ0CON |=0x00111000; *rGPJ0DAT |=((0x01<<3)|(0x01<<4)|(0x01<<5)); } return ucount; printk(KERN_INFO "copy_from_user successful\n"); } int led_release(struct inode *inode, struct file *file) { printk(KERN_INFO "led_release successful\n"); return 0; } // 模块安装函数 static int __init chrdev_init(void) { int ret = -1; int retval = -1; printk(KERN_INFO "chrdev_init successful\n"); /******指定主设备号注册*****/ //led_dev_no = MKDEV(250,0); //retval = register_chrdev_region(led_dev_no, DEVNUM,MYNAME); /******内核自动分配主设备号注册*****/ retval = alloc_chrdev_region(&led_dev_no,0,DEVNUM,MYNAME); if (retval) { printk(KERN_WARNING "alloc_chrdev_region fail\n."); ret = -EINVAL; goto err_reg; } printk(KERN_INFO "alloc_chrdev_region successful major = %d,minor = %d \n",MAJOR(led_dev_no),MINOR(led_dev_no)); /****动态申请cdev实体******/ pled_cdev = cdev_alloc(); printk(KERN_INFO "cdev_alloc successful\n"); cdev_init(pled_cdev, &led_fops); retval = cdev_add(pled_cdev, led_dev_no, DEVNUM); if (retval) { printk(KERN_WARNING "cdev_add fail\n."); ret = -EINVAL; goto err_add; } printk(KERN_INFO "cdev_add successful\n"); if (request_mem_region(S5PV210_PA_GPIOJ0CON, 8, "GPIOJ0CON") == NULL) { printk(KERN_WARNING "failed to get memory region\n"); ret = -ENOENT; goto err_req; } rGPJ0CON = ioremap(S5PV210_PA_GPIOJ0CON,8); if (rGPJ0CON == NULL) { printk(KERN_WARNING "fail to ioremap() region\n"); ret = -ENOENT; goto err_map; } rGPJ0DAT = rGPJ0CON+1; return 0; err_map: printk(KERN_INFO "err_map err\n"); release_mem_region(S5PV210_PA_GPIOJ0CON,8); err_req: printk(KERN_INFO "err_req err\n"); /***for static cdev******/ //cdev_del(&led_cdev); /***for dynamic cdev******/ cdev_del(pled_cdev); err_add: printk(KERN_INFO "err_add err\n"); unregister_chrdev_region(led_dev_no, DEVNUM); err_reg: return ret; } // 模块卸载函数 static void __exit chrdev_exit(void) { iounmap(rGPJ0CON); release_mem_region(S5PV210_PA_GPIOJ0CON,8); /***for static cdev******/ //cdev_del(&led_cdev); /***for dynamic cdev******/ cdev_del(pled_cdev); unregister_chrdev_region(led_dev_no, DEVNUM); printk(KERN_INFO "chrdev_exit successful\n"); } module_init(chrdev_init); module_exit(chrdev_exit); // MODULE_xxx这种宏作用是用来添加模块描述信息 MODULE_LICENSE("GPL"); // 描述模块的许可证 MODULE_AUTHOR("musk"); // 描述模块的作者 MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息 MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
一. 关于设备注册新老接口
1.1. 老接口:register_chrdev
1.2. 新接口:register_chrdev_region/alloc_chrdev_region + cdev
1.3. 新老接口不同点
a. 老接口不能注册子设备号
b. 新接口可以注册子设备号,并且可以一次注册多个子设备号。
c. register_chrdev = alloc_chrdev_region + cdev_init + cdev_add,所有新接口步骤更多
二. dev_t 成员
2.1. dev_t定义:
typedef u_long dev_t;
2.2. dev;设备号,31位,高12位是主设备号,低20位是次设备号。以下函数可以操作设备号:
#define MINORBITS 20 #define MINORMASK ((1U << MINORBITS) - 1) #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //获得主设备号 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //获得此设备号 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //由主次设备号得到设备号
三. 注册/主线设备号
3.1. 静态注册函数register_chrdev_region:
/** * register_chrdev_region() - register a range of device numbers * @from: the first in the desired range of device numbers; must include * the major number. * @count: the number of consecutive device numbers required * @name: the name of the device or driver. * * Return value is zero on success, a negative error code on failure. */ int register_chrdev_region(dev_t from, unsigned count, const char *name) { struct char_device_struct *cd; dev_t to = from + count; dev_t n, next; for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+1, 0); if (next > to) next = to; cd = __register_chrdev_region(MAJOR(n), MINOR(n), next - n, name); if (IS_ERR(cd)) goto fail; } return 0; fail: to = n; for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+1, 0); kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n)); } return PTR_ERR(cd); }
a. 此函数需要静态指定设备号。
3.2. 动态申请注册函数alloc_chrdev_region
/** * alloc_chrdev_region() - register a range of char device numbers * @dev: output parameter for first assigned number * @baseminor: first of the requested range of minor numbers * @count: the number of minor numbers required * @name: the name of the associated device or driver * * Allocates a range of char device numbers. The major number will be * chosen dynamically, and returned (along with the first minor number) * in @dev. Returns zero or a negative error code. */ int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) { struct char_device_struct *cd; cd = __register_chrdev_region(0, baseminor, count, name); if (IS_ERR(cd)) return PTR_ERR(cd); *dev = MKDEV(cd->major, cd->baseminor); return 0; }
3.2.1. 参数dev是输出型参数,也就是自动分配的设备号
3.3. 注销设备号unregister_chrdev_region
/** * unregister_chrdev_region() - return a range of device numbers * @from: the first in the range of numbers to unregister * @count: the number of device numbers to unregister * * This function will unregister a range of @count device numbers, * starting with @from. The caller should normally be the one who * allocated those numbers in the first place... */ void unregister_chrdev_region(dev_t from, unsigned count) { dev_t to = from + count; dev_t n, next; for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+1, 0); if (next > to) next = to; kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n)); } }
4. cdev结构体
4.1. cdev结构体定义
struct cdev { struct kobject kobj; //内嵌的kobject对象 struct module *owner; //使属模块 const struct file_operations *ops; //文件操作结构体 struct list_head list; //linux 内核所维护的链表指针 dev_t dev; //设备号 unsigned int count; //设备数目 };
4.2. cdev定义有两种方式,struct cdev cdev;另外一种struct cdev *cdev; cdev=cdev_alloc();一种静态声明定义,另一种动态分配。
4.3. cdev相关操作函数
4.3.1. cdev通过函数cdev_init()初始化,主要工作就是将file_operations和cdev关联起来。file_operations是字符驱动需要实现的主要内容。
4.3.2. cdev通过cdev_add()实现cdev的注册,所谓注册就是将cdev根据设备号(dev_t)添加到cdev数组(cdev_map)中供系统管理。
4.3.3. cdev通过cdev_del()将cdev从cdev_map中移除。
4.4. cdev初始化
4.4.1. 静态初始化
/** * cdev_init() - initialize a cdev structure * @cdev: the structure to initialize * @fops: the file_operations for this device * * Initializes @cdev, remembering @fops, making it ready to add to the * system with cdev_add(). */ void cdev_init(struct cdev *cdev, const struct file_operations *fops) { memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops; }
4.4.2. 动态初始化
/** * cdev_alloc() - allocate a cdev structure * * Allocates and returns a cdev structure, or NULL on failure. */ struct cdev *cdev_alloc(void) { struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL); if (p) { INIT_LIST_HEAD(&p->list); kobject_init(&p->kobj, &ktype_cdev_dynamic); } return p; }
4.5. cdev_add添加字符设备
/** * cdev_add() - add a char device to the system * @p: the cdev structure for the device * @dev: the first device number for which this device is responsible * @count: the number of consecutive minor numbers corresponding to this * device * * cdev_add() adds the device represented by @p to the system, making it * live immediately. A negative error code is returned on failure. */ int cdev_add(struct cdev *p, dev_t dev, unsigned count) { p->dev = dev; p->count = count; return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p); }
4.6. cdev_del()删除字符设备
/** * cdev_del() - remove a cdev from the system * @p: the cdev structure to be removed * * cdev_del() removes @p from the system, possibly freeing the structure * itself. */ void cdev_del(struct cdev *p) { cdev_unmap(p->dev, p->count); kobject_put(&p->kobj); }
5.应用层调用内核
5.1. 应用层代码如下
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #define DEVFILE "/dev/led_dev" int main(void) { char buf[100] = {0}; int fd = -1; if((fd =open(DEVFILE, O_RDWR))<0) { perror("open"); return -1; } printf("open successful fd = %d\n",fd); if(write(fd, "on", strlen("on"))<0) { perror("write"); return -1; } sleep(3); memset(buf,0,sizeof(buf)); if(read(fd, buf, 3)<0) { perror("read"); return -1; } printf("read data = %s\n",buf); if(write(fd, "off", strlen("off"))<0) { perror("write"); return -1; } sleep(3); memset(buf,0,sizeof(buf)); if(read(fd, buf, 4)<0) { perror("read"); return -1; } printf("read data = %s\n",buf); close(fd); return 0; }