字符设备驱动----LED驱动程序
2015-11-15 11:42
267 查看
一. 概念介绍
一般用户在应用程序里调用的 open, read, write 函数是 c 库的函数,这些函数会触发 swi val异常,从而引发系统调用,进入到内核空间,
内核通过VFS(virtual Filesystem)来实现调用不同的驱动函数。
例如:我们有一个函数,
int main() { int fd1, fd2; int val = 1; fd1 = open("/dev/led", O_RDWR); write(fd1, &val, 4); fd2 = open("hello.txt", O_RDWR); write(fd2, &val, 4); }
函数里相同的open、write函数,引发的不同的行为,一个是操控硬件,一个是写文件。
简单的调用关系如下:
用户 –> 系统调用 –> 驱动程序
open –> sys.open –> led.open
write –> sys.write –> led.write
二. 字符设备驱动框架
实现步骤:1. 实现驱动的 led.open, led.write, led.read 操作
2. 定义file_operations结构体, 把驱动函数填充到里面
3. 把这个结构告诉内核, 通个函数 register_chrdev(major, “first_drv”, &first_drv_fops) 来实现
4. 谁来调用注册函数 –>驱动的入口函数来调用这个注册函数, first_drv_init
5. 修饰一下这个函数入口函数,module_init(first_drv_init)
//第一步:驱动功能实现 static int first_drv_open(struct inode *inode, struct file *file) { //printk("first_drv_open\n"); return 0; } static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { //printk("first_drv_write\n"); return 0; } //第二步:定义结构体,并把驱动函数填充进去 static struct file_operations first_drv_fops = { .owner = 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);// 注册告诉内核 return 0; } static void first_drv_exit(void) { unregister_chrdev(major, "first_drv"); // 卸载 } //第五步:修饰入口函数,及退出函数 module_init(first_drv_init); module_exit(first_drv_exit);
三. 关联 [设备号] 与 [设备节点]
设备号要与设备结点关联起来,才能通过open(“/dev/xyz”)方便的操作。1. 设置主设备号
驱动程序可以自动分配主设备号, 也可以手工指定
// 设置为 0 时是系统自动分配主设备号 major = register_chrdev(0, "first_drv", &first_drv_fops); // 通过 [cat /proc/device] 看一下系统为我们的first_drv分配的设备号是多少 // 手动分配 111主设备号给 first_drv register_chrdev(111, "first_drv", &first_drv_fops);
2. 设置设备节点
当应该程序 执行 open(“/dev/xyz”) 操作时,这个/dev/xyz怎么来的
2.1 手动创建
// 创建设备节点 mknod /dev/xyz c(表示是字符设备) 主设备号 次设备号 //查看设备信息: ls -l /dev/xyz
2.2 自动创建
mdev – 根据系统信息创建设备节点
int major; static int first_drv_init(void) { major = register_chrdev(0, "first_drv", &first_drv_fops); //创建设备信息,执行后会出现 /sys/class/firstdrv 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); }
四. 完善LED驱动
完善LED驱动,也就是硬件的操作。单片机中可以直接操作物理地址,但在驱动里只能操作虚拟地址
虚拟地址怎么来的,用 ioremap( ) 函数来映射,映射完后操作虚拟地址就像操作物理地址一样。
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"); //映射 GPIO 的物理地址 0x56000050 到虚拟地址, gpfcon操作的是虚拟地址 gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); gpfdat = gpfcon + 1; return 0; }
映射完后,就可以操作这些地址,来控制硬件寄存器
static int first_drv_open(struct inode *inode, struct file *file) { //printk("first_drv_open\n"); /* 配制GPF4为输出 */ *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2))); *gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2))); return 0; }
另外,当用户空间的数据要传到内核空间里,同样不能直接用,
也要通过 copy_from_user() 函数,把用户空间的值传到内核空间里。
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 给内核空间 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; }
相关文章推荐
- Linux 自检和 SystemTap
- Linux内核链表实现过程
- 深入理解PHP内核(二)之SAPI探究
- C++中Semaphore内核对象用法实例
- 深入理解PHP内核(一)
- 深入php内核之php in array
- 修改内核 内存分配 root、文件系统和内核镜像的位置
- 移植linux-2.6.30.4到S3C2440
- 看《Linux0.11内核完全注释2.01》的方法
- 升级LINUX内核(支持8G内存)的命令
- FreeBSD系统优化部分内核参数调整中文注释
- Linux2.6X内核中文件相关结构体总结
- 内核的主要配置文件的详细说明
- redhat AS4内核配置更改再编译
- Linux启动添加内核参数简介
- 几个重要的Linux操作系统 内核文件介绍
- linux 2.4内核编译详解
- linux2.6内核编译方法详述
- 自己动手编译Linux内核
- linux 2.4内核编译详解