<linux设备驱动程序开发初探(2)>
在<linux设备驱动程序开发初探(1)>中,已经实现了一个最简单的、框架式的linux驱动程序,以及编译、加载和使用的概念与总体流程。这个程序至少有两个方面需要改进:
# 在驱动程序first_drv.c中,手动指定了主设备号为111,如果这个设备号在系统中已经被占用,那么这样的写法就不行。可以让系统自动分配一个主设备号:
int major; static int first_drv_init(void) { major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核 return 0; }
这样在模块加载后,系统会自动分配一个主设备号。我们需要手动建立在 /dev/ 目录中的设备节点,这时就要查找这个主设备号了。这种手动建立设备节点的方式不是最好的,可以有一种机制,让系统能够在加载了模块之后,自动创建设备节点,这就是“热拔插 hot plug”机制,具体实现常用udev机制,或者在嵌入式系统中常用的mdev为udev的简化版。
mdev程序会根据 /sys/dev 目录下面的记录的信息进行自动创建、删除设备节点的工作。
为了使用嵌入式系统中的mdev机制,需要在编写驱动程序是注册两个类,“告诉内核的mdev机制”相关的信息,这些信息会被记录在/sys/dev 目录下面供mdev机制(程序)使用:
static struct class *firstdrv_class; static struct class_device *firstdrv_class_dev;
并在加载和卸载模块的时候使用(注册与注销):
int major; static int first_drv_init(void) { major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核 firstdrv_class = class_create(THIS_MODULE, "firstdrv"); firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* 创建设备节点名称 /dev/xyz */ return 0; } static void first_drv_exit(void) { unregister_chrdev(major, "first_drv"); // 卸载 class_device_unregister(firstdrv_class_dev); class_destroy(firstdrv_class); } MODULE_LICENSE("GPL"); //mdev机制必须添加这条注册信息
需要注意的是,驱动程序最后一定要进行GPL的声明,不然无法使用mdev机制。
更改这些信息后重新编译运行便可以发现设备节点被自动加载了,在/sys/class目录下就有关于first_drv的信息。之所以mdev程序会在/sys/class下面信息的改变自动运行,是因为在linux系统启动的脚本文件/etc/init.d/rcS中创建了这个程序的运行热拔插的条件:
echo /sbin/mdev > /proc/sys/kernel/hotplug
=====================================================
<改装最简单驱动程序,控制LEDs>
在裸机上编写LED控制程序时使用的是物理地址,但是在linux的用户空间使用的是虚拟地址,因此需要进行物理地址到虚拟地址的转换,使用的是ioremap函数
先定义两个指针用于指向GPIO的配置寄存器和数据寄存器:
volatile unsigned long *gpfcon = NULL; volatile unsigned long *gpfdat = NULL;
在注册函数static int first_drv_init(void)中进行地址转换:
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); gpfdat = gpfcon + 1;
在注销函数中也需要用iounmap函数声明
iounmap(gpfcon);
这样使用这两个指针指向的寄存器时就可以控制led了。完整的程序如下:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> static struct class *firstdrv_class; static struct class_device *firstdrv_class_dev; volatile unsigned long *gpfcon = NULL; volatile unsigned long *gpfdat = NULL; static int first_drv_open(struct inode *inode, struct file *file) { //printk("first_drv_open\n"); /* 假设led接在GPF4、5、6三个IO上,低电平点亮。配置GPF4,5,6为输出 */ *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2))); *gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2))); return 0; } static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val; //printk("first_drv_write\n"); copy_from_user(&val, buf, count); // copy_to_user(); if (val == 1) { // 点灯 *gpfdat &= ~((1<<4) | (1<<5) | (1<<6)); } else { // 灭灯 *gpfdat |= (1<<4) | (1<<5) | (1<<6); } return 0; } static struct file_operations first_drv_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = first_drv_open, .write = first_drv_write, }; int major; static int first_drv_init(void) { major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核 firstdrv_class = class_create(THIS_MODULE, "firstdrv"); firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */ gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); gpfdat = gpfcon + 1; return 0; } static void first_drv_exit(void) { unregister_chrdev(major, "first_drv"); // 卸载 class_device_unregister(firstdrv_class_dev); class_destroy(firstdrv_class); iounmap(gpfcon); } module_init(first_drv_init); module_exit(first_drv_exit); MODULE_LICENSE("GPL"); //mdev机制必须添加这条注册信息