【梦想终会实现】Linux驱动学习6
1、ioctrl接口的应用层与驱动层实现
// 驱动层部分,对应ioctrl的cmd:PWM_IOCTL_SET_FREQ、PWM_IOCTL_STOP
static int x210_pwm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
switch (cmd)
{
case PWM_IOCTL_SET_FREQ:
printk("PWM_IOCTL_SET_FREQ:\r\n");
if (arg == 0)
return -EINVAL;
PWM_Set_Freq(arg);
break;
case PWM_IOCTL_STOP:
default:
printk("PWM_IOCTL_STOP:\r\n");
PWM_Stop();
break;
}
return 0;
}
// file_operations 结构体
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = x210_pwm_open,
.release = x210_pwm_close,
.ioctl = x210_pwm_ioctl,
};
// 中间件层实现
fd = open("dev/buzzer", O_RDWR);
if (fd < 0)
{
...
}
ioctrl(fd, PWM_IOCTL_SET_FREQ, 1000); // 1000ms响一次
sleep(3);
ioctrl(fd, PWM_IOCTL_SET_FREQ, 300); // 300ms响一次
sleep(3);
为何ioctrl存在而不是直接使用write?
明显为write仅支持不复杂的数据写入,若针对复杂的数据结构,若还用write接口,无疑会增加难度,故使用ioctrl接口,以应对复杂的控制。类似于文件的读、写、执行属性一样,ioctrl类似于执行这个属性。当然也可以通过给write写0.1.2.3这些魔法数字,驱动层根据不同的case来做特定处理,但这样的魔法数字会导致中间件层无法理解。
2、杂散设备:蜂鸣器、ADC
为什么还要给杂散设备归个类呢?
就是为了使用杂散类的接口,给该设备创建个主次设备号。
杂散设备kernel源码分析:
misc.c
static LIST_HEAD(misc_list); // 定义双向链表头节点,以实现内核中杂项类设备的添加。
static DEFINE_MUTEX(misc_mtx); // 定义misc_mtx互斥锁
#define MISC_MAJOR 10 // 杂散类设备主设备号均为10,此设备号不同
#define MISC_DYNAMIC_MINOR 255
static int __init misc_init(void)
{
int err;
#ifdef CONFIG_PROC_FS // /proc虚拟文件系统,已不再使用
proc_create("misc", 0, NULL, &misc_proc_fops);
#endif
misc_class = class_create(THIS_MODULE, "misc"); // 创建misc类
err = PTR_ERR(misc_class);
if (IS_ERR(misc_class))
goto fail_remove;
err = -EIO;
if (register_chrdev(MISC_MAJOR,"misc",&misc_fops)) // 注册主设备号
goto fail_printk;
misc_class->devnode = misc_devnode;
return 0;
fail_printk:
printk("unable to get major %d for misc devices\n", MISC_MAJOR);
class_destroy(misc_class);
fail_remove:
remove_proc_entry("misc", NULL);
return err;
}
int misc_register(struct miscdevice * misc)
{
struct miscdevice *c;
dev_t dev;
int err = 0;
INIT_LIST_HEAD(&misc->list); // 杂项类设备使用链表管理,此为初始化链表,使其指向自己。
mutex_lock(&misc_mtx);
list_for_each_entry(c, &misc_list, list) { // 遍历次设备号
if (c->minor == misc->minor) {
mutex_unlock(&misc_mtx);
return -EBUSY;
}
}
if (misc->minor == MISC_DYNAMIC_MINOR) { // 次设备号创建,若为255则交由内核分配从设备号
int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS); // 找到第一个为0的位
if (i >= DYNAMIC_MINORS) {
mutex_unlock(&misc_mtx);
return -EBUSY;
}
misc->minor = DYNAMIC_MINORS - i - 1;
set_bit(i, misc_minors);
}
dev = MKDEV(MISC_MAJOR, misc->minor); // 组合主次设备号成设备唯一的设备号
misc->this_device = device_create(misc_class, misc->parent, dev,
misc, "%s", misc->name); // 根据主设备号和次设备号创建一个设备节点
if (IS_ERR(misc->this_device)) {
int i = DYNAMIC_MINORS - misc->minor - 1;
if (i < DYNAMIC_MINORS && i >= 0)
clear_bit(i, misc_minors);
err = PTR_ERR(misc->this_device);
goto out;
}
/*
* Add it to the front, so that later devices can "override"
* earlier defaults
*/
list_add(&misc->list, &misc_list);
out:
mutex_unlock(&misc_mtx);
return err;
}
3、 设备号温故:
设备号创建温故:
register_chrdev() 分配主设备号,第一个入参0,表示交由内核分配主设备号,同时注册了所有的次设备号。可以直接绑定到内核中。
register_chrdev_region() 静态分配从设备号
* register_chrdev_region() 函数 - 注册一组设备编号
* @from: 指定设备编号范围内的第一个编号;必须包含主设备号。若为0,表示自由分配一个主设备号。
* @count: 需要注册的连续设备编号的数量
* @name: 设备或驱动程序的名称。*alloc_chrdev_region() 动态分配从设备号
* `alloc_chrdev_region()` 函数 - 注册一组字符设备编号
* @dev:输出参数,用于存储分配的第一个编号
* @baseminor:所请求的编号范围中的最小编号
* @count:所需的编号数量
* @name:与该设备或驱动程序相关联的名称*
* 分配一系列的字符设备编号。 主设备编号将动态选取,并与第一个从设备编号一同返回至 @dev 中。 返回零或一个负的错误代码。
// 经典源码分析
dev_t devt = MKDEV(arg->dev_major, arg->dev_minor);
if (!MAJOR(devt)) // 若不存在主设备号,则动态分配。
rc = alloc_chrdev_region(&devt, MINOR(devt), 1, devinfo.name);
else // 若存在主设备号,则使用。
rc = register_chrdev_region(devt, 1, devinfo.name);
4、创建类和创建设备不冲突。
类创建用class_create(),设备创建用device_create(),设备创建时需要主次设备号,用***_chrdev_***相关函数创建即可,随后使用cdev绑定到内核。device_create()关联应用层的udev处理,即上抛相关信息,上层接收到后创建/删除对应的设备文件。同时可以使用device_create_file的形式创建一些属性,以实现用户可以在/sys目录下操作内核。
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/sysfs.h>
#include <linux/uaccess.h>
static struct cdev my_cdev;
static dev_t dev_number;
static struct class *cls;
static int my_value = 0; // 示例属性值
// 定义属性结构体
static struct device_attribute my_attr = {
.attr = {
.name = "my_value",
.mode = S_IRUGO | S_IWUSR,
},
.show = my_value_show,
.store = my_value_store,
};
// 实现 show 函数
static ssize_t my_value_show(struct device *dev, struct device_attribute *attr, char *buf) {
return sprintf(buf, "%d\n", my_value);
}
// 实现 store 函数
static ssize_t my_value_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {
int ret = kstrtol(buf, 10, &my_value);
if (ret < 0)
return ret;
return count;
}
static int __init my_init_module(void) {
// 分配设备号
alloc_chrdev_region(&dev_number, 0, 1, "my_device");
// 初始化 cdev 结构体
cdev_init(&my_cdev, &my_fops);
my_cdev.owner = THIS_MODULE;
// 添加 cdev 到内核
cdev_add(&my_cdev, dev_number, 1);
// 创建设备类
cls = class_create(THIS_MODULE, "my_class");
// 创建设备节点
struct device *dev = device_create(cls, NULL, dev_number, NULL, "my_device");
// 创建 sysfs 属性文件
if (IS_ERR(device_create_file(dev, &my_attr))) {
pr_err("Failed to create sysfs attribute\n");
return PTR_ERR(device_create_file(dev, &my_attr));
}
return 0;
}
static void __exit my_exit_module(void) {
// 销毁 sysfs 属性文件
device_remove_file(cls->dev, &my_attr);
// 销毁设备节点
device_destroy(cls, dev_number);
// 销毁设备类
class_destroy(cls);
// 删除 cdev
cdev_del(&my_cdev);
// 释放设备号
unregister_chrdev_region(dev_number, 1);
}
module_init(my_init_module);
module_exit(my_exit_module);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device with sysfs attributes");
5、为什么把/sys目录叫sysfs?
因为sysfs挂载于/sys目录下。sysfs是Linux的虚文件系统本身并不存在。
root@ubuntu:/sys/class/misc$ mount | grep sysfs
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
为什么需要mount?Linux中一切皆文件,允许将文件系统与自身的目录树挂接起来。通过挂载,将不同文件系统隔离开来,各文件系统均有读、写、执行权限。支持通过mount挂载USB(U盘)、网络设备(NFS、MSB)、虚拟文件设备(/sys、/proc)。
mount [-t fstype] [-o options] device dir
- -t fstype:指定文件系统类型,如
ext4
、ntfs
、nfs
等。如果不指定,mount
会尝试自动检测文件系统类型。- -o options:指定挂载选项,多个选项之间用逗号分隔。常见的选项包括:
ro
:以只读模式挂载。rw
:以读写模式挂载(默认)。sync
:同步模式,所有写操作都会立即写入磁盘。async
:异步模式,写操作可能会延迟写入磁盘(默认)。noexec
:禁止在文件系统上执行程序。exec
:允许在文件系统上执行程序(默认)。nosuid
:忽略文件系统上的 SUID 和 SGID 位。suid
:允许文件系统上的 SUID 和 SGID 位(默认)。nodev
:不允许文件系统上包含设备文件。dev
:允许文件系统上包含设备文件(默认)。remount
:重新挂载已经挂载的文件系统,可以修改挂载选项。示例:mount /dev/sdb1 /mnt/data
6、
字符设备使用数组,杂散类设备使用链表。
位图:使用位图来表示设备的设备号或状态标志,相对于数组,可以占用更少的内存和按位访问有更快的执行效率。
静态位图的声明:
static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);
声明后后续可以访问位图:
int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS); // 查找位图中第一个不为0的位。
7、