之前买过好几本Linux 设备驱动的书,不过对设备驱动一知半解,什么叫设备,什么又叫驱动?最近工作需要,从源码级别深入的研究了一下 Linux 下的设备与驱动的概念,略有所收获
一般提起驱动开发,都是面向硬件的,至少是底层开发,依赖具体的平台,Linux 作为一个通用的操作系统内核,当前不可能顾及所以的具体外设驱动,只能抽取出驱动的共性,抽象出一个设备驱动模型(框架)出来,这个设备模型从上层看,无论设备多么简单或者复杂,把共性的设备操作,如:注册、反注册、打开、关闭、控制、读写等 封装成 总线、驱动、设备,与设备树配合起来,为上层提供通用的设备操作接口,如文件接口、socket 接口,为底层设备驱动开发提供便利,降低驱动开发的难度。
其实就是抽象与分层,让驱动开发就像是【填空题】,照着模板填空补充,就可以开发具体的设备驱动了。设备驱动的目的,就是驱使设备工作起来,可以让上层应用操作。
ubuntu 20.04
VMware Workstation Pro 16
基于qemu(模拟器),vexpress-a9 平台
Linux 6.0.10 (当前最新版本)
注册一个简单的misc 设备,掌握misc 设备注册的方法
大家经常提到的是 三大类设备:【字符设备】、【块设备】、【网络设备】。
misc 设备是什么呢?为何使用 misc 设备?
如今Linux 设备驱动非常的庞大,所以当前接触的一些外设,都有类似的驱动模型,misc (杂类设备)属于 char 字符设备。
使用 misc 设备的好处就是 Linux 提供了完善的 misc 设备管理,使用 misc 设备提供的API,就可以方便的注册管理 一个 misc 具体设备,使用 misc 设备最核心的 一般是使用 open、close、ioctl 接口,这些接口,可以让用户太的应用操作设备。
注册了一个 misc 设备,如 led0,用户态程序通过 open("/dev/led0", O_RDWR),就可以打开内核驱动misc 设备,通过 ioctl 就可以控制 内核驱动 misc 设备。
也就有一些设备,不是直接读写的,大部分操作都是控制命令,如空调的控制,一般有打开空调、关闭空调、调节空调的温度、模式等操作,可以把空调作为misc 设备来控制,打开关闭使用 open close,调节温度、模式等使用 ioctl。
这里就注意一点: 驱动与设备的概念,这里 misc 属于设备。
这里为了方便,注册一个简单的设备,与上一篇 嵌入式Linux 开发经验:platform_driver_register 的使用方法 平台驱动 配合起来,当 平台驱动匹配设备树节点成功后,再初始化 misc 设备。
新建 linux-6.0.10/drivers/led_control/led_misc.c
#include "led_misc.h"
#define LED_MISC_DEVICE_NAME "led_misc"
struct led_misc_dev
{
struct miscdevice misc;
};
struct led_misc_dev *led_miscdev;
/* 打开设备,用户态执行 open 命令,就会走到这里 */
static int led_misc_open(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "%s : enter\n", __func__);
return 0;
}
/* 打开设备,用户态执行 close 命令,就会走到这里 */
static int led_misc_close(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "%s : enter\n", __func__);
return 0;
}
/* 内存映射,大部分功能都差不多 */
static int led_misc_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret = 0;
if (filp == NULL)
{
printk(KERN_ERR "invalid file!");
return -EFAULT;
}
if (vma == NULL)
{
printk(KERN_ERR "invalid vma area");
return -EFAULT;
}
ret = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
vma->vm_end - vma->vm_start, vma->vm_page_prot);
printk(KERN_INFO "%s : ret = %d\n", __func__, ret);
return ret;
}
/* 设备控制类,用户态执行 ioctl 命令,就会走到这里 */
static long led_misc_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{
printk(KERN_INFO "%s : enter\n", __func__);
return 0;
}
/* 设备的操作,用户态 应用通过【文件】操作接口操作 */
static const struct file_operations led_misc_fops =
{
.owner = THIS_MODULE,
.llseek = no_llseek,
.unlocked_ioctl = led_misc_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = led_misc_ioctl,
#endif
.mmap = led_misc_mmap,
.open = led_misc_open,
.release = led_misc_close,
};
/* 注意这个 初始化不是自动初始化,放在 平台驱动 probe 函数 */
int led_miscdev_init(void)
{
int ret;
led_miscdev = kzalloc(sizeof(*led_miscdev), GFP_KERNEL);
if (!led_miscdev)
return -ENOMEM;
led_miscdev->misc.minor = MISC_DYNAMIC_MINOR;
led_miscdev->misc.fops = &led_misc_fops;
led_miscdev->misc.name = LED_MISC_DEVICE_NAME;
led_miscdev->misc.nodename = LED_MISC_DEVICE_NAME;
ret = misc_register(&led_miscdev->misc);
if (ret < 0)
{
printk(KERN_INFO "%s : error\n", __func__);
}
else
{
printk(KERN_INFO "%s : ok\n", __func__);
}
return ret;
}
/* 可以放在 平台驱动 remove 函数 */
void led_miscdev_exit(void)
{
misc_deregister(&led_miscdev->misc);
printk(KERN_INFO "%s : ok\n", __func__);
}
linux-6.0.10/drivers/led_control/led_misc.h#ifndef __LED_MISC_H__
#define __LED_MISC_H__
#include
#include
#include
int led_miscdev_init(void);
void led_miscdev_exit(void);
#endif
上面是 注册 misc 设备的实现,但是需要调用才能执行,配合 平台驱动, led_miscdev_init 放在 平台驱动的 probe 函数中,led_miscdev_exit 可以放在 平台驱动 remove 函数,也可以放在 平台驱动 module_exit 的执行函数中
修改 linux-6.0.10/drivers/led_control/led_control.c
static int led_control_probe(struct platform_device *pdev)
{
printk(KERN_INFO "%s : enter\n", __func__);
led_miscdev_init(); /* 设备树节点匹配后,调用 */
return 0;
}
static int led_control_remove(struct platform_device *pdev)
{
printk(KERN_INFO "%s : enter\n", __func__);
//led_miscdev_exit(); /* 移除设备驱动时,释放 misc 设备 */
return 0;
}
static void __exit led_control_driver_exit(void)
{
printk(KERN_INFO "%s : enter\n", __func__);
led_miscdev_exit(); /* 移除设备驱动时,释放 misc 设备 */
platform_driver_unregister(&led_control_driver);
}
linux-6.0.10/drivers/led_control/led_control.h,添加 #include "led_misc.h"#ifndef __LED_CONTROL_H__
#define __LED_CONTROL_H__
#include
#include
#include "led_misc.h"
#endif
linux-6.0.10/drivers/led_control/Makefile,增加obj-$(CONFIG_LED_CONTROL) += led_control.o
obj-$(CONFIG_LED_CONTROL) += led_misc.o
编译与 qemu 运行方法参考上篇 嵌入式Linux 开发经验:platform_driver_register 的使用方法 平台驱动的注册中提到的方法
【小技巧】,这里把 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- 编译命令 做成一个 shell 脚本
vim mk.sh
#!/bin/bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- $1 $2 $3
chmod +x mk.sh 增加执行权限
编译时: ./mk.sh -j4 就可以编译了

更新 zImge Linux 内核编译的产物,开启 qemu 查看注册的 misc 设备
启动 qemu 的信息包括 如下:
led_control_driver_init : enter
led_control_probe : enter
led_miscdev_init : ok

说明设备树节点匹配后,正确调用了 misc 设备的初始化函数 led_miscdev_init
Linux shell 查看 注册的 misc 设备
ls /sys/class/misc/ -la, 可以查看 ,注意在 /sys/class/misc/ 目录下

本篇与上一篇 平台驱动配合,记录了一下 平台驱动+ misc 设备的操作流程,部分简单的设备,可以利用Linux 设备驱动框架提供的便利,想填空题一样快速开发构建自己的实际的设备驱动。
使用 Linux 设备种类大概有三种,不过细分, misc 属于 char 字符设备,当前还有各种形形色色的功能不同的设备。 Misc 设备属于比较常用的简单的控制类设备