前言
-
之前买过好几本Linux 设备驱动的书,不过对设备驱动一知半解,什么叫设备,什么又叫驱动?最近工作需要,从源码级别深入的研究了一下 Linux 下的设备与驱动的概念,略有所收获
-
一般提起驱动开发,都是面向硬件的,至少是底层开发,依赖具体的平台,Linux 作为一个通用的操作系统内核,当前不可能顾及所以的具体外设驱动,只能抽取出驱动的共性,抽象出一个设备驱动模型(框架)出来,这个设备模型从上层看,无论设备多么简单或者复杂,把共性的设备操作,如:注册、反注册、打开、关闭、控制、读写等 封装成 总线、驱动、设备,与设备树配合起来,为上层提供通用的设备操作接口,如文件接口、socket 接口,为底层设备驱动开发提供便利,降低驱动开发的难度。
-
其实就是抽象与分层,让驱动开发就像是【填空题】,照着模板填空补充,就可以开发具体的设备驱动了。设备驱动的目的,就是驱使设备工作起来,可以让上层应用操作。
测试环境搭建
-
ubuntu 20.04
-
VMware Workstation Pro 16
-
基于qemu(模拟器),vexpress-a9 平台
-
Linux 6.0.10 (当前最新版本)
-
注册一个简单的misc 设备,掌握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 设备
-
这里就注意一点: 驱动与设备的概念,这里 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 <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
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 <linux/of.h>
#include <linux/platform_device.h>
#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 设备成功了,接下来可以编写 用户态的应用,通过文件操作接口,如 open close ioctl 来控制这个 内核 misc 设备了
小结
-
本篇与上一篇 平台驱动配合,记录了一下 平台驱动+ misc 设备的操作流程,部分简单的设备,可以利用Linux 设备驱动框架提供的便利,想填空题一样快速开发构建自己的实际的设备驱动。
-
使用 Linux 设备种类大概有三种,不过细分, misc 属于 char 字符设备,当前还有各种形形色色的功能不同的设备。 Misc 设备属于比较常用的简单的控制类设备