int register_chrdev_region(dev_t from,unsigned count, const char *name);
/ * register_chrdev_region() - register arange of device numbers
* @from: the first in the desired range of devicenumbers; must include
* the major number.
* @count: the number of consecutive device numbersrequired
* @name: the name of the device or driver.
*Return value is zero on success, a negative error code on failure.*/
这种方式主要用于,驱动开发者事先知道该驱动主设备号的情况
(2)动态申请
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
/* alloc_chrdev_region() - register a rangeof char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minornumbers
* @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 errorcode.*/
这种方式由系统动态分配一个设备号,返回的设备号保存在参数dev中。
Step 2:注册字符设备
在Linux内核中庸struct cdev表示一个字符设备。
字符设备的注册与注销分别通过下面的两个函数来实现:
int cdev_add(structcdev *p, dev_t dev, unsigned count);
/**
*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 codeis returned on failure.
*/
void cdev_del(structcdev *p);
不过,在注册一个字符设备之前,要调用下面这个函数来初始化struct cdev结构体:
void cdev_init(structcdev *cdev, const struct file_operations *fops)
/**
*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().
*/
另外,struct cdev结构体变量可以声明为一个指针,内核提供了一个函数来申请:
struct cdev *cdev_alloc(void);
Step 3:创建设备节点
有两种方法:
一是通过 mknod命令来创建。如:
mknod /dev/yourname c major minor
其中“yourname”可以是任意符合unix下路径名的名字,不一定要是你代码里定义的驱动或设备的名字;c 表示创建字符设备节点,major是你成功申请的主设备号,minor是次设备号,这个可以是任意的(在次设备号范围内)
另外一种方法是通过udev自动生成。这种方法需要在你的代码里创建一个设备类,然后在这个设备类的基础上,创建一个设备;另外应用程序需要跑一个udevd的后台程序。
struct class* class_create(owner, name);
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata,const char *fmt, ...)
这样Linux驱动编写的一般步骤就完成了,我们阅读Linux内核驱动的代码,应该先找到程序的入口函数module_init(char_test_init);参数中的就是函数入口,函数名可以自己定义,从入口进入,根据以上的步骤阅读代码,那么Linux内核驱动的框架就显得简单明了了。
接下来以LED驱动为例子,阅读下LED驱动的代码。
#include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <asm/uaccess.h> #include <asm/string.h> #include <asm/uaccess.h>//kmalloc函数头文 #include <linux/slab.h> #include <linux/mutex.h> //驱动头文件 #include <mach/gpio.h> /* \arch\arm\mach-s5pv210\include\ */ #include <mach/regs-gpio.h> #include <plat/gpio-cfg.h> /* \arch\arm\plat-samsung\include\ */ #define LED_ON _IOW('a',2,int) #define LED_OFF _IOW('a',3,int) #define LED_ALL_ON _IO('a',0xF) #define LED_ALL_OFF _IO('a',0) #define LED_LIUSHUI_ON _IO('a',98) #define LED_LIUSHUI_OFF _IO('a',99) MODULE_LICENSE("GPL"); int devno_major=0; int devno_minor=0; int init_gpio_led(void) { if(!gpio_request(S5PV210_GPJ2(0), "led_1")) { return -1; } if(!gpio_request(S5PV210_GPJ2(1), "led_2")) { return -1; } if(!gpio_request(S5PV210_GPJ2(2), "led_3")) { return -1; } if(!gpio_request(S5PV210_GPJ2(3), "led_4")) { return -1; } gpio_direction_output(S5PV210_GPJ2(0), 1); gpio_direction_output(S5PV210_GPJ2(1), 1); gpio_direction_output(S5PV210_GPJ2(2), 1); gpio_direction_output(S5PV210_GPJ2(3), 1); return 0; } module_param(devno_major, int, 0440); struct cdev *pdev=NULL; struct class * myclass = NULL; struct device *mdevice = NULL; int test_open(struct inode *_inode,struct file *_file) { printk(KERN_INFO "%s\n", __FUNCTION__); return 0; } int test_close(struct inode *_inode,struct file *_file) { printk(KERN_INFO "%s\n", __FUNCTION__); return 0; } ssize_t test_read (struct file *_file, char __user * buf, size_t count, loff_t * offset) { return 0; } ssize_t test_write (struct file *_file, const char __user * buf, size_t count, loff_t * offset) { return 0; } long test_ioctl (struct file * _file, unsigned int cmd, unsigned long arg) { int *args=(int *)arg; int k; if (_IOC_DIR(cmd) == _IOC_READ) //该命令,是用户想从内核读一个数据 { //我就必须要验证你提供的地址,是否可写 if (!access_ok(VERIFY_WRITE, arg, _IOC_SIZE(cmd)) ) { return -EFAULT; } } else if (_IOC_DIR(cmd) == _IOC_WRITE) { if (!access_ok(VERIFY_READ, arg, _IOC_SIZE(cmd))) { return -EFAULT; } } get_user(k,args); switch(cmd) { case LED_ON: __gpio_set_value(S5PV210_GPJ2(k),0); break; case LED_OFF: __gpio_set_value(S5PV210_GPJ2(k),1); break; case LED_ALL_ON: __gpio_set_value(S5PV210_GPJ2(0),0); __gpio_set_value(S5PV210_GPJ2(1),0); __gpio_set_value(S5PV210_GPJ2(2),0); __gpio_set_value(S5PV210_GPJ2(3),0); break; case LED_ALL_OFF: __gpio_set_value(S5PV210_GPJ2(0),1); __gpio_set_value(S5PV210_GPJ2(1),1); __gpio_set_value(S5PV210_GPJ2(2),1); __gpio_set_value(S5PV210_GPJ2(3),1); break; case LED_LIUSHUI_ON : __gpio_set_value(S5PV210_GPJ2(0),1); __gpio_set_value(S5PV210_GPJ2(0),0); __gpio_set_value(S5PV210_GPJ2(1),1); __gpio_set_value(S5PV210_GPJ2(1),0); __gpio_set_value(S5PV210_GPJ2(2),1); __gpio_set_value(S5PV210_GPJ2(2),0); __gpio_set_value(S5PV210_GPJ2(3),1); __gpio_set_value(S5PV210_GPJ2(3),0); break; case LED_LIUSHUI_OFF : break; default: break; } return -1; } const struct file_operations fops= //传统的字符设备访问方式 这里我偷懒只用ioctl实现对硬件的访问 { .open =test_open, .release=test_close, .read=test_read, .write=test_write, .unlocked_ioctl = test_ioctl, }; int char_test_init(void) { int r,res; dev_t devno; //32位数,其中的12位用来表示主设备号,其余的20位表示次设备号 //申请设备号 if(devno_major>0)//静态指定 { devno =MKDEV(devno_major,devno_minor); r=register_chrdev_region(devno,1,"test"); } else//动态申请 { r=alloc_chrdev_region(&devno, devno_minor, 1, "test"); } if(r!=0) { printk(KERN_ERR "register char dev number failed\n"); return -1; } devno_major=MAJOR(devno); //获取主设备号 devno_minor=MINOR(devno); //获取次设备号 printk(KERN_INFO"major: %d minor: %d\n", devno_major, devno_minor); //注册字符设备 pdev =cdev_alloc(); cdev_init(pdev,&fops); //字符设备初始化 cdev_add(pdev,devno,1); //通过此函数告诉内核该结构的信息 //生成设备节点 myclass=class_create(THIS_MODULE, "char_test"); mdevice=device_create(myclass,NULL,devno,NULL,"test"); res=init_gpio_led();//初始化端口 if(res!=0) return -1; return 0; } void char_test_exit(void) { dev_t devno=MKDEV(devno_majkfree(pt->p_buf); //释放设备号 device_destroy(myclass,devno); class_destroy(myclass); //注销字符设备 cdev_del(pdev); gpio_free(S5PV210_GPJ2(0)); gpio_free(S5PV210_GPJ2(1)); gpio_free(S5PV210_GPJ2(2)); gpio_free(S5PV210_GPJ2(3)); unregister_chrdev_region(devno,1); } module_init(char_test_init); //这里为函数入口 module_exit(char_test_exit); //这里为退出函数
理解了上述代码后,那么如何将自己编写的驱动写进Linux内核呢?
我们在linux内核中编写驱动,一般都是在/kernel/driver/ 下建立自己的目录,在新建的目录下创建C文件编写。比如说新建hello目录,在hello目录下新建hello.c及hello.h一些相关的头文件。驱动编写完成后,还需要配置Kconfig以及Makefile文件。以hello为列:
其中Kconfig是在编译前执行配置命令make menuconfig时用到的,而Makefile是执行编译命令make是用到的:
Kconfig文件的内容
config HELLO
tristate "First Android Driver"
default n
help
This is the first android driver.
Makefile文件的内容
obj-$(CONFIG_HELLO) += hello.o
在Kconfig文件中,tristate表示编译选项HELLO支持在编译内核时,hello模块支持以模块、内建和不编译三种编译方法,默认是不编译,因此,在编译内核前,我们还需要执行make menuconfig命令来配置编译选项,使得hello可以以模块或者内建的方法进行编译。
在Makefile文件中,根据选项HELLO的值,执行不同的编译方法
修改arch/arm/Kconfig和drivers/kconfig两个文件,在menu "Device Drivers"和endmenu之间添加一行:
source "drivers/hello/Kconfig"(有些源码在arch/arm/Kconfig中没有menu "Device Drivers"和endmenu,那是因为在drivers/kconfig已经包含。所以只需要在drivers/kconfig添加即可。)
这样,执行make menuconfig时,就可以配置hello模块的编译选项了。
修改drivers/Makefile文件,添加一行:
obj-$(CONFIG_HELLO) += hello/
配置编译选项:
/Android-5.0.2/kernel/common$ make menuconfig
找到"Device Drivers" => "First Android Drivers"选项,设置为y
注意,如果内核不支持动态加载模块,这里不能选择m,虽然我们在Kconfig文件中配置了HELLO选项为tristate。要支持动态加载模块选项,必须要在配置菜单中选择Enable loadable module support选项;在支持动态卸载模块选项,必须要在Enable loadable module support菜单项中,选择Module unloading选项。
这些工作做完之后,就可以直接在/kernel目录下make了。
编译成功后,就可以在hello目录下看到hello.o文件了,这时候编译出来的zImage已经包含了hello驱动。
参考博客:http://blog.csdn.net/luoshengyang/article/details/6568411 罗老师