---- 整理自 王利涛老师 课程
实验环境:宅学部落 www.zhaixue.cc
文章目录
- 0. 什么是设备模型
- 1. 设备模型基础:kobject(kernel object)
- 2. 设备模型基础:attribute
- 3. kobject 和 sysfs 的关联
- 4. sysfs 目录创建流程
- 5. sysfs 文件创建流程
- 6. VFS inode 的生成过程
- 7. sysfs 文件打开过程分析
- 8. sysfs 的读写过程分析
- 9. kobject 的生命周期
- 10. 设备模型基础:kset
- 11. 热插拔事件:uevent
- 12. 用 OOP 思想分析设备模型
- 13. 设备模型:bus
- 14. 设备模型:device
- 15. 设备模型:device_driver
- 16. bus probe 和 driver probe
- 17. 设备模型:class
- 18. device 的二次抽象
- 19. 实现一个总线子系统
- 20. 驱动复用:match_table
- 21. 设备的热插拔 hotplug 机制
- 22. 从字符驱动到总线驱动
0. 什么是设备模型
- 设备模型核心数据结构:
kobject、kset、uevent
device、device_driver、bus、class - 设备模型的作用
电源管理
热插拔时间管理:hotplug - 设备模型的好处:
- 代码复用:多个设备复用一个驱动
- 资源的动态申请和释放
- 简化驱动的编写
- 热插拔机制:hotplug / uevent
- 设备模型在内核驱动中的地位
- 驱动的 OOP 思想:USB、platform、I2C、……
- 该节课程主要内容:
- 设备模型基础:kobject、kset、attribute、uevent
- 文件系统编程接口:sysfs、注册、挂载、读写
- 设备模型:device、device_driver、bus、class
- 设备热插拔事件:hotplug / uevent
- 如何编写总线型驱动
- 如何从零实现一个 bus 子系统:hello bus
- 如何往 bus 子系统注册设备、注册驱动
- 如何实现驱动的复用性:match_table
- 如何自动创建设备节点
- 应用程序:mdev / udev 机制分析
1. 设备模型基础:kobject(kernel object)
- 对应 sysfs 下的目录:每个 kobject 对应 /sys/ 目录下面的一个目录
- 结构体定义:
struct kobject
,name 指定的就是这个目录的名字
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct kernfs_node *sd; /* sysfs directory entry */
struct kref kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
- kobject 的作用:内核通常用 kobject 结构将各个对象连接起来组成一个分层的结构体系
- kobject 的初始化和添加流程
- 常用的相关 API 接口
struct kobject * __must_check kobject_create(void);
struct kobject * __must_check kobject_create_and_add(const char *name, struct kobject *parent);
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...);
void kobject_del(struct kobject *kobj);
struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject *kobj);
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
static struct kobject *kobj_hello;
static int kobject_hello_init(void)
{
//kobj_hello = kobject_create_and_add("hello", NULL); // 创建/sys/hello
kobj_hello = kobject_create_and_add("hello", kernel_kobj); // 创建/sys/kernel/hello
if (!kobj_hello) {
return -ENOMEM;
}
return 0;
}
static void kobject_hello_exit(void)
{
kobject_put(kobj_hello);
}
module_init(kobject_hello_init);
module_exit(kobject_hello_exit);
MODULE_LICENSE("GPL");
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
2. 设备模型基础:attribute
- 关键结构体:attribute、kobj_attribute
- 初始化宏:
__ATTR(_name, _mode, _show, _store)
- 属性的读写方法:show、store
- 编程示例:
在 /sys 指定目录下创建文件
通过 /sys 接口修改内核数据 - attribute 相关编程接口
static inline int __must_check sysfs_create_file(struct kobject *kobj, const struct attribute *attr);
static inline void sysfs_remove_file(struct kobject *kobj, const struct attribute *attr);
也可以直接:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
unsigned long hello_value;
static ssize_t value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "hello_value = %lu\n", hello_value);
}
static ssize_t value_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
char tmp_buf[10] = {0};
strncpy(tmp_buf, buf, count);
hello_value = simple_strtoul(tmp_buf, NULL, 0);
return count;
}
static struct kobj_attribute value_attribute = {
.attr = {
.name = "value",
.mode = 0664,
},
.show = value_show,
.store = value_store,
};
char hello_buf[100];
static ssize_t hello_buf_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
strncpy(buf, hello_buf, strlen(hello_buf));
return strlen(hello_buf);
}
static ssize_t hello_buf_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
strncpy(hello_buf, buf, count);
return count;
}
static struct kobj_attribute foo_attribute = __ATTR(buf, 0664, hello_buf_show, hello_buf_store);
static struct kobject *kobj_hello;
static int kobject_hello_init(void)
{
int retval;
kobj_hello = kobject_create_and_add("hello", NULL); // 创建目录 /sys/hello
if (!kobj_hello) {
return -ENOMEM;
}
retval = sysfs_create_file(kobj_hello, &foo_attribute.attr); // 创建文件 /sys/hello/buf
if (retval) {
printk(KERN_ALERT "%s: create sysfs file failed\n", __func__);
kobject_put(kobj_hello);
return -1;
}
retval = sysfs_create_file(kobj_hello, &value_attribute.attr); // 创建文件 /sys/hello/value
if (retval) {
printk(KERN_ALERT "%s: create sysfs file failed\n", __func__);
kobject_put(kobj_hello);
return -1;
}
return 0;
}
static void kobject_hello_exit(void)
{
kobject_put(kobj_hello);
}
module_init(kobject_hello_init);
module_exit(kobject_hello_exit);
MODULE_LICENSE("GPL");
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
- 属性群组:attribute_group
- 二进制属性:bin_attribute
- 宏:__ATTRIBUTE_GROUPS(_name)
- 宏:__BIN_ATTR(_name, _mode, _read, _write, _size)
- 编程示例:
在 /sys 指定目录下创建一组文件
在 /sys 指定目录下创建二进制文件 - attribute 相关编程接口
int __must_check sysfs_create_bin_file(struct kobject *kobj,
const struct bin_attribute *attr);
void sysfs_remove_bin_file(struct kobject *kobj,
const struct bin_attribute *attr);
int __must_check sysfs_create_group(struct kobject *kobj,
const struct attribute_group *grp);
void sysfs_remove_group(struct kobject *kobj,
const struct attribute_group *grp);
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
char hello_buf[100];
static ssize_t hello_buf_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t offset, size_t count)
{
//memcpy(buf, hello_buf + offset, count);
memcpy(buf, attr->private + offset, count);
return count;
}
static ssize_t hello_buf_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t offset, size_t count)
{
//memcpy(hello_buf + offset, buf, count);
memcpy(attr->private + offset, buf, count); // <------
return count;
}
static struct bin_attribute hello_bin_attribute = {
.attr = {
.name = "hello_bin_attribute",
.mode = 0644,
},
.read = hello_buf_read,
.write = hello_buf_write,
.size = 100,
.private = hello_buf, // <------ 传入hello_buf地址
};
static struct kobject *kobj_hello;
static int kobject_hello_init(void)
{
int retval;
kobj_hello = kobject_create_and_add("hello", NULL);
if (!kobj_hello)
return -ENOMEM;
retval = sysfs_create_bin_file(kobj_hello, &hello_bin_attribute); // <------
if (retval) {
printk(KERN_ALERT "%s: create sysfs file failed\n", __func__);
return -1;
}
return 0;
}
void kobject_hello_exit(void)
{
sysfs_remove_bin_file(kobj_hello, &hello_bin_attribute);
kobject_put(kobj_hello);
}
module_init(kobject_hello_init);
module_exit(kobject_hello_exit);
MODULE_LICENSE("GPL");
等价的两种写法:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
unsigned long hello_value;
static ssize_t value_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
return sprintf(buf, "hello_value = %lu\n", hello_value);
}
static ssize_t value_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
char tmp_buf[10] = {0};
strncpy(tmp_buf, buf, count);
hello_value = simple_strtoul(tmp_buf, NULL, 0);
return count;
}
static struct kobj_attribute value_attribute = {
.attr = {
.name = "value",
.mode = 0664,
},
.show = value_show,
.store = value_store,
};
char hello_buf[100];
static ssize_t hello_buf_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
strncpy(buf, hello_buf, strlen(hello_buf));
return strlen(hello_buf);
}
static ssize_t hello_buf_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
strncpy(hello_buf, buf, count);
return count;
}
static struct kobj_attribute foo_attribute = __ATTR(buf, 0664, hello_buf_show, hello_buf_store);
static struct attribute *hello_attrs[] = {
&value_attribute.attr,
&foo_attribute.attr,
NULL,
};
#if 1
static struct attribute_group hello_group = {
.attrs = hello_attrs,
};
#else
ATTRIBUTE_GROUPS(hello);
#endif
static struct kobject *kobj_hello;
static int kobject_hello_init(void)
{
int retval;
kobj_hello = kobject_create_and_add("hello", NULL);
if (!kobj_hello)
return -ENOMEM;
retval = sysfs_create_group(kobj_hello, &hello_group); // <------ 创建多个文件
if (retval) {
printk(KERN_ALERT "%s: create sysfs file group failed\n", __func__);
kobject_put(kobj_hello);
return -1;
}
return 0;
}
void kobject_hello_exit(void)
{
kobject_put(kobj_hello);
}
module_init(kobject_hello_init);
module_exit(kobject_hello_exit);
MODULE_LICENSE("GPL");
3. kobject 和 sysfs 的关联
- sysfs 文件系统简介
- sysfs 文件系统的注册
- sysfs 文件系统的挂载
- kobject 和 sysfs 如何建立关联
回到 do_new_mount:
再回到这里:
4. sysfs 目录创建流程
- kobject 和 sysfs 的关联
- sysfs 目录创建过程:kobject_create_and_add
- 私有指针的用途
父目录为 NULL 时,“hello” 目录就挂在到 sysfs 的根目录:
5. sysfs 文件创建流程
- 第 4 节和第 5 节流程走下来,我们就知道了 kobject、attribute 和 sysfs 的关联。
6. VFS inode 的生成过程
- VFS 层面的 inode 是如何生成的?
各个文件系统都有自己的 inode
inode->priv = minix_inode
- inode 的作用:
包含了一系列操作集:i_fop、i_op
定义了 VFS 和具体文件系统的各种借口
打开文件时,file->f_op = inode->f_fop
- vfs inode 与 kernfs_node 的关联
内核会调用 kernfs_get_inode 来为 kernfs_node 分配一个对应的 VFS inode:
7. sysfs 文件打开过程分析
- 核心结构体之间的关联
- file 指针和 inode 之间的关联
- file、seq_file、kernfs_open_file 的关联
- kernfs_open_file 和 kernfs_inode 的关联
- 私有指针的用途
回到在上一小节中建立 vfs inode 和 kernfs_node 关联的地方:
8. sysfs 的读写过程分析
根据前面分析的过程,直接到这里:
这里回顾下之前的内容:
这里再回到之前的内容:
9. kobject 的生命周期
- 为什么要引入生命周期? – 例如 USB,插上占用内存,拔掉不占用内存
- 内嵌结构体:cdev、device
- 结构体成员:对应属性
- 计数接口:
void kobject_del (struct kobject *kobj);
struct kobject *kobject_get (struct kobject *kobj);
void kobject_put (struct kobject *kobj);
kobj_type->release // 回调函数
- kobject 引用计数
当添加一个 kobject 时:自身和 parent 都加 1
当移除一个 kobject 时:自身和 parent 都减 1
在 kobject 下创建属性,引用计数不变
- 实验:kref
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
unsigned long hello_value;
static ssize_t value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "hello_value = %lu\n", hello_value);
}
static ssize_t value_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
char tmp_buf[10] = {0};
strncpy(tmp_buf, buf, count);
hello_value = simple_strtoul(tmp_buf, NULL, 0);
return count;
}
static struct kobj_attribute value_attribute = {
.attr = {
.name = "value",
.mode = 0664,
},
.show = value_show,
.store = value_store,
};
char hello_buf[100];
static ssize_t hello_buf_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
strncpy(buf, hello_buf, strlen(hello_buf));
return strlen(hello_buf);
}
static ssize_t hello_buf_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
strncpy(hello_buf, buf, count);
return count;
}
static struct kobj_attribute foo_attribute = __ATTR(buf, 0664, hello_buf_show, hello_buf_store);
static struct kobject *kobj_hello;
static struct kobject *kobj_child;
static int kobject_hello_init(void)
{
int retval;
kobj_hello = kobject_create_and_add("hello", NULL);
if (!kobj_hello)
return -ENOMEM;
printk("kobj_hello.refcount = %d\n", kobj_hello->kref.refcount.refs.counter);
retval = sysfs_create_file(kobj_hello, &foo_attribute.attr);
if (retval) {
printk(KERN_ALERT "%s: create sysfs file failed\n", __func__);
kobject_put(kobj_hello);
return -1;
}
printk("kobj_hello.refcount = %d\n", kobj_hello->kref.refcount.refs.counter);
retval = sysfs_create_file(kobj_hello, &value_attribute.attr);
if (retval) {
printk(KERN_ALERT "%s: create sysfs file failed\n", __func__);
kobject_put(kobj_hello);
return -1;
}
printk("kobj_hello.refcount = %d\n", kobj_hello->kref.refcount.refs.counter);
// 创建 "child" kobject,并将其作为 "hello" kobject 的子对象
kobj_child = kobject_create_and_add("child", kobj_hello);
if (!kobj_child) {
kobject_put(kobj_hello);
return -ENOMEM;
}
printk("kobj_hello.refcount = %d, after child kobject created.\n", kobj_hello->kref.refcount.refs.counter);
printk("kobj_child.refcount = %d\n", kobj_child->kref.refcount.refs.counter);
#if 0
kobject_put(kobj_hello);
kobject_put(kobj_hello);
printk("hello.refcount = %d\n", kobj_hello->kref.refcount.refs.counter);
printk("child.refcount = %d\n", kobj_child->kref.refcount.refs.counter);
#endif
return 0;
}
void kobject_hello_exit(void)
{
kobject_put(kobj_child);
kobject_put(kobj_hello);
}
module_init(kobject_hello_init);
module_exit(kobject_hello_exit);
MODULE_LICENSE("GPL");
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
10. 设备模型基础:kset
- kset 的定义:一组 kobject,是 kobject 的容器;kset 本身也是一个内核对象,需要内嵌一个 kobject 对象
- kset 的作用
- kset 与 sysfs 之间的对应关系
- kset 编程接口及示例:
创建一个 kset 对象
指定 kset 的 parent
创建 kset 下的属性
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
unsigned long hello_value;
static ssize_t value_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
return sprintf(buf, "hello_value = %lu\n", hello_value);
}
static ssize_t value_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count)
{
char tmp_buf[10] = {0};
strncpy(tmp_buf, buf, count);
hello_value = simple_strtoul(tmp_buf, NULL, 0);
return count;
}
struct attribute value_attr = {
.name = "value",
.mode = 0644,
};
struct sysfs_ops value_sysfs_ops = {
.show = value_show,
.store = value_store,
};
static struct attribute *value_attr_array[] = {
&value_attr,
NULL,
};
/******************************************************************/
char hello_buf[100];
static ssize_t hello_buf_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
strncpy(buf, hello_buf, strlen(hello_buf));
return strlen(hello_buf);
}
static ssize_t hello_buf_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count)
{
strncpy(hello_buf, buf, count);
return count;
}
static struct attribute buf_attr = {
.name = "buf",
.mode = 0644,
};
struct sysfs_ops buf_sysfs_ops = {
.show = hello_buf_show,
.store = hello_buf_store,
};
static struct attribute *buf_attr_array[] = {
&buf_attr,
NULL,
};
/*------------------------------------------------------------------*/
void my_obj_release(struct kobject *kobj)
{
printk("%s: kfree %s\n", __func__, kobj->name);
kfree(kobj);
}
static struct kobject *kobj_hello;
static struct kset *kset_hello;
static struct kobject *kobj_value, *kobj_buf;
static struct kobj_type value_type, buf_type;
static int kobject_hello_init(void)
{
int retval;
kobj_hello = kobject_create_and_add("hello", NULL);
if (!kobj_hello) {
return -ENOMEM;
}
// 创建一个 kset,名为 kset_hello
// kset_hello = kset_create_and_add("kset_hello", NULL, kobj_hello); // 指定父节点创建
kset_hello = kset_create_and_add("kset_hello", NULL, NULL);
if (!kset_hello) {
kobject_put(kobj_hello);
return -ENOMEM;
}
kobj_value = kzalloc(sizeof(struct kobject), GFP_KERNEL);
kobj_value->kset = kset_hello; // 设置 kobject 所属的 kset
value_type.release = my_obj_release;
value_type.default_attrs = value_attr_array; // 绑定属性
value_type.sysfs_ops = &value_sysfs_ops;
kobj_buf = kzalloc(sizeof(struct kobject), GFP_KERNEL);
kobj_buf->kset = kset_hello; // 设置 kobject 所属的 kset
buf_type.release = my_obj_release;
buf_type.default_attrs = buf_attr_array; // 绑定属性
buf_type.sysfs_ops = &buf_sysfs_ops;
// retval = kobject_init_and_add(kobj_value, &value_type, kobj_hello, "value");
retval = kobject_init_and_add(kobj_value, &value_type, NULL, "value");
retval = kobject_init_and_add(kobj_buf, &buf_type, NULL, "buf");
return 0;
}
void kobject_hello_exit(void)
{
kobject_del(kobj_value);
kobject_put(kobj_value);
kobject_put(kobj_buf);
kobject_del(kobj_hello);
kobject_put(kobj_hello);
kset_unregister(kset_hello);
}
module_init(kobject_hello_init);
module_exit(kobject_hello_exit);
MODULE_LICENSE("GPL");
11. 热插拔事件:uevent
- 什么是热插拔事件?
- 什么是 uevent?
- uevent 的主要功能是什么?
- 如何监听 uevent?
- 什么是 uevent_helper?
- udev、mdev 是什么?
- 知识点:
一个 kobject 要有 kset,然后才能发送 uevent
一个死循环程序,一直监听 uevent – udevadm monitor
指定要运行程序 – uevent_helper - 热插拔(hotplug)uevent 编程实验
编写一个内核模块,发送 uevent 事件
在用户空间监听 uevent
内核编译配置:uevent_helper
执行用户空间的指定程序
11.1 x86 平台上的实验
11.1.1 实验 1
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
static struct kobject *kobj_hello;
static struct kset *kset_hello;
static struct kobject *kobj_value;
static struct kobj_type value_type;
static int kobject_hello_init(void)
{
return 0;
}
void kobject_hello_exit(void)
{
}
module_init(kobject_hello_init);
module_exit(kobject_hello_exit);
MODULE_LICENSE("GPL");
obj-m = hello.o
all:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean
11.1.2 实验 2
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
void value_obj_release(struct kobject *kobj)
{
printk("%s: %s released\n", __func__, kobj->name);
kfree(kobj);
}
static struct kobject *kobj_hello;
static struct kset *kset_hello;
static struct kobject *kobj_value;
static struct kobj_type value_type;
static int kobject_hello_init(void)
{
int retval;
// 创建一个名为 kset_hello 的 kset 并将其添加到 sysfs 中
kset_hello = kset_create_and_add("kset_hello", NULL, NULL);
if (!kset_hello)
return -ENOMEM;
// 分配并初始化kobject kobj_value,将其挂载到 kset_hello 下
kobj_value = kzalloc(sizeof(struct kobject), GFP_KERNEL);
kobj_value->kset = kset_hello;
value_type.release = value_obj_release;
// 初始化 kobject 并将其添加到 sysfs 中,名称为 value
retval = kobject_init_and_add(kobj_value, &value_type, NULL, "value");
// 触发 uevent,通知用户空间 kobject 状态发生了变化
kobject_uevent(kobj_value, KOBJ_CHANGE); // <== 这里KOBJ_CHANGE
return 0;
}
void kobject_hello_exit(void)
{
kobject_put(kobj_value);
kset_unregister(kset_hello);
}
module_init(kobject_hello_init);
module_exit(kobject_hello_exit);
MODULE_LICENSE("GPL");
11.1.3 实验 3
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
static struct kobject *kobj_hello;
static struct kset *kset_hello;
static struct kobject *kobj_value;
static struct kobj_type value_type;
static int kobject_hello_init(void)
{
kobj_hello = kobject_create_and_add("hello", NULL);
if (!kobj_hello)
return -ENOMEM;
kobject_uevent(kobj_hello, KOBJ_CHANGE); // <== 监听不到
return 0;
}
void kobject_hello_exit(void)
{
kobject_put(kobj_hello);
}
module_init(kobject_hello_init);
module_exit(kobject_hello_exit);
MODULE_LICENSE("GPL");
11.1.4 实验 4:不使用一直监听的 udevadm monitor,使用 uevent_helper
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
void value_obj_release(struct kobject *kobj)
{
printk("%s: %s released\n", __func__, kobj->name);
kfree(kobj);
}
static struct kobject *kobj_hello;
static struct kset *kset_hello;
static struct kobject *kobj_value;
static struct kobj_type value_type;
static int kobject_hello_init(void)
{
int retval;
#if 1
kobj_hello = kobject_create_and_add("hello", NULL);
if (!kobj_hello)
return -ENOMEM;
kobject_uevent(kobj_hello, KOBJ_CHANGE);
#endif
#if 1
kset_hello = kset_create_and_add("kset_hello", NULL, NULL);
if (!kset_hello)
return -ENOMEM;
// 分配并初始化kobject kobj_value,将其挂载到 kset_hello 下
kobj_value = kzalloc(sizeof(struct kobject), GFP_KERNEL);
kobj_value->kset = kset_hello;
value_type.release = value_obj_release;
// 初始化 kobject 并将其添加到 sysfs 中,名称为 value
retval = kobject_init_and_add(kobj_value, &value_type, NULL, "value");
// 触发 uevent,通知用户空间 kobject 状态发生了变化
kobject_uevent(kobj_value, KOBJ_CHANGE); // <== 这里
#endif
return 0;
}
void kobject_hello_exit(void)
{
#if 1
kobject_put(kobj_hello);
#endif
#if 1
kobject_put(kobj_value);
kset_unregister(kset_hello);
#endif
}
module_init(kobject_hello_init);
module_exit(kobject_hello_exit);
MODULE_LICENSE("GPL");
#!/bin/sh
mknod /dev/hello0 c 253 0
mknod /dev/hello1 c 253 1
mknod /dev/hello2 c 253 2
mknod /dev/hello3 c 253 3
mknod /dev/hello4 c 253 4
mknod /dev/hello5 c 253 5
11.2 arm 平台上的实验
11.2.1 编译配置内核 uevent_helper
11.2.2 实验
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
void value_obj_release(struct kobject *kobj)
{
printk("%s: %s released\n", __func__, kobj->name);
kfree(kobj);
}
static struct kobject *kobj_hello;
static struct kset *kset_hello;
static struct kobject *kobj_value;
static struct kobj_type value_type;
static int kobject_hello_init(void)
{
int retval;
#if 1
kobj_hello = kobject_create_and_add("hello", NULL);
if (!kobj_hello)
return -ENOMEM;
kobject_uevent(kobj_hello, KOBJ_CHANGE);
#endif
#if 1
kset_hello = kset_create_and_add("kset_hello", NULL, NULL);
if (!kset_hello)
return -ENOMEM;
// 分配并初始化kobject kobj_value,将其挂载到 kset_hello 下
kobj_value = kzalloc(sizeof(struct kobject), GFP_KERNEL);
kobj_value->kset = kset_hello;
value_type.release = value_obj_release;
// 初始化 kobject 并将其添加到 sysfs 中,名称为 value
retval = kobject_init_and_add(kobj_value, &value_type, NULL, "value");
// 触发 uevent,通知用户空间 kobject 状态发生了变化
kobject_uevent(kobj_value, KOBJ_CHANGE); // <== 这里
#endif
return 0;
}
void kobject_hello_exit(void)
{
#if 1
kobject_put(kobj_hello);
#endif
#if 1
kobject_put(kobj_value);
kset_unregister(kset_hello);
#endif
}
module_init(kobject_hello_init);
module_exit(kobject_hello_exit);
MODULE_LICENSE("GPL");
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
11.3 源码分析:uevent 热插拔事件处理流程
11.4 编程实战
- 发送自定义信息到用户空间
- 编写用户解析工具,解析 uevent 信息
- 自动创建设备文件
- 自动删除设备文件
// netlink_monitor.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <linux/netlink.h>
const char *action = "";
const char *devpath = "";
const char *subsystem = "";
const char *firmware = "";
int major = 0;
int minor = 0;
const char *addr = "";
const char *devname = "";
const char *name = "";
int open_socket(void)
{
int fd;
struct sockaddr_nl socknl_addr = {
.nl_family = AF_NETLINK,
.nl_pad = 1,
.nl_pid = getpid(),
.nl_groups = 0xffffffff,
};
fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
if (fd < 0) {
printf("%s: create socket ep failed\n", __func__);
return -1;
}
if (bind(fd, (struct sockaddr *) &socknl_addr, sizeof(socknl_addr)) < 0) {
printf("%s: bind socket failed\n", __func__);
close(fd);
return -1;
}
return fd;
}
void parse_event(const char *msg)
{
while (*msg) {
printf("%s\n", msg);
if (!strncmp(msg, "ACTION=", 7)) {
msg += 7;
action = msg;
} else if (!strncmp(msg, "DEVPATH=", 8)) {
msg += 8;
devpath = msg;
} else if (!strncmp(msg, "SUBSYSTEM=", 10)) {
msg += 10;
subsystem = msg;
} else if (!strncmp(msg, "MAJOR=", 6)) {
msg += 6;
major = atoi(msg);
} else if (!strncmp(msg, "MINOR=", 6)) {
msg += 6;
minor = atoi(msg);
} else if (!strncmp(msg, "ADDR=", 5)) {
msg += 5;
addr = msg;
} else if (!strncmp(msg, "NAME=", 5)) {
msg += 5;
name = msg;
} else if (!strncmp(msg, "DEVNAME=", 8)) {
msg += 8;
devname = msg;
}
while(*msg++);
}
printf("---------------------------------------------------------\n");
}
void make_hello_node(const char *devname, mode_t mode, int major, int minor)
{
char pathname[20];
strcpy(pathname, "/dev/");
strcpy(&pathname[5], devname);
if (!strcmp(action, "add"))
mknod(pathname, 0666, ((major<<20) | minor));
if (!strcmp(action, "remove"))
remove(pathname);
}
int main(void)
{
int fd, len;
char recv_msg[4096 + 2];
fd = open_socket();
do {
while((len = recv(fd, recv_msg, 4096, 0)) > 0) {
if(len == 4096)
continue;
recv_msg[len] = '\0';
recv_msg[len + 1] = '\0';
parse_event(recv_msg);
make_hello_node(devname, 0666, major, minor);
}
} while(1);
}
// hello.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
static struct kobject *kobj_hello;
static struct kset *kset_hello;
static struct kobj_type hello_kobj_type;
static int hello_uevent(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env)
{
add_uevent_var(env, "ADDR=%s", "China");
add_uevent_var(env, "NAME=%s", "Jiangsu");
add_uevent_var(env, "DEVNAME=%s", "Huaian");
return 0;
}
static const struct kset_uevent_ops hello_uevent_ops = {
.uevent = hello_uevent,
.filter = NULL,
.name = NULL,
};
void hello_kobj_release(struct kobject *kobj)
{
kfree(kobj);
}
static int kobject_hello_init(void)
{
int ret;
kset_hello = kset_create_and_add("kset_hello", &hello_uevent_ops, NULL);
kobj_hello = kzalloc(sizeof(struct kobject), GFP_KERNEL);
kobj_hello->kset = kset_hello;
hello_kobj_type.release = hello_kobj_release;
ret = kobject_init_and_add(kobj_hello, &hello_kobj_type, NULL, "kobj_hello");
if (ret) {
kset_unregister(kset_hello);
return ret;
}
kobject_uevent(kobj_hello, KOBJ_ADD);
return 0;
}
void kobject_hello_exit(void)
{
kobject_put(kobj_hello);
kset_unregister(kset_hello);
}
module_init(kobject_hello_init);
module_exit(kobject_hello_exit);
MODULE_LICENSE("GPL");
12. 用 OOP 思想分析设备模型
12.1 设备模型中的 OOP 思想(Object-Oriented Programming,面向对象编程)
- 俄罗斯套娃模式开启:
kobject/kset ⇒ device/cdev ⇒ usb_device/net_device
kobject_uevent/kobject_add ⇒ xx_register/xx_add - OOP 思想的 C 语言模拟实现:
OOP:封装、继承、多态、抽象类、接口
实现:函数指针、内嵌结构体、私有指针
12.2 设备模型的核心要素
- bus:match、uevent、probe、suspend
- device:kobject、parent、devt、init_nama
- device_driver:name、probe、match_table
- class:name
- 设备模型的工作机制:
- 注册一个总线:实现 match 方法
- 往总线注册设备
- 往总线注册驱动
- 驱动的 probe
13. 设备模型:bus
13.1 结构体
13.2 如何注册一个总线
- 注册接口:bus_register
- 实现总线的 match 方法
- 在 bus 下创建属性文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
static int hello_bus_match(struct device *dev, struct device_driver *driver)
{
int match;
// 比较设备名称和驱动名称,返回匹配结果
match = !strncmp(dev_name(dev), driver->name, strlen(driver->name));
return match;
}
// 定义一个新的总线类型
struct bus_type hello_bus_type = {
.name = "hello_bus", // 总线名称
.match = hello_bus_match, // 匹配函数
};
EXPORT_SYMBOL_GPL(hello_bus_type);
// 显示总线名称的函数
static ssize_t hello_bus_show(struct bus_type *bus, char *buf)
{
return sprintf(buf, "bus name: %s\n", hello_bus_type.name);
}
static struct bus_attribute hello_bus_attr = {
.attr = {
.name = "hello_bus_attr",
.mode = 0644,
},
.show = hello_bus_show,
};
static int __init hello_bus_init(void)
{
int ret;
// 注册新的总线类型
ret = bus_register(&hello_bus_type);
if (ret)
return ret;
// 创建总线属性文件
ret = bus_create_file(&hello_bus_type, &hello_bus_attr);
if (ret) {
bus_unregister(&hello_bus_type);
return ret;
}
return 0;
}
static void __exit hello_bus_exit(void)
{
bus_remove_file(&hello_bus_type, &hello_bus_attr);
bus_unregister(&hello_bus_type);
}
module_init(hello_bus_init);
module_exit(hello_bus_exit);
MODULE_LICENSE("GPL");
ifneq ($(KERNELRELEASE),)
obj-m += bus.o
obj-m += device.o
obj-m += driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
13.3 bus 和 kobject/kset 的关联
bus:内嵌 kset
13.4 bus_register 注册流程分析
13.5 bus_create_file 过程分析
14. 设备模型:device
14.1 结构体:device 和 device_attribute
14.2 如何向 bus 上注册设备和创建设备属性文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
static int hello_bus_match(struct device *dev, struct device_driver *driver)
{
int match;
// 比较设备名称和驱动名称,返回匹配结果
match = !strncmp(dev_name(dev), driver->name, strlen(driver->name));
return match;
}
// 定义一个新的总线类型
struct bus_type hello_bus_type = {
.name = "hello_bus", // 总线名称
.match = hello_bus_match, // 匹配函数
};
EXPORT_SYMBOL_GPL(hello_bus_type);
// 显示总线名称的函数
static ssize_t hello_bus_show(struct bus_type *bus, char *buf)
{
return sprintf(buf, "bus name: %s\n", hello_bus_type.name);
}
static struct bus_attribute hello_bus_attr = {
.attr = {
.name = "hello_bus_attr",
.mode = 0644,
},
.show = hello_bus_show,
};
static int __init hello_bus_init(void)
{
int ret;
// 注册新的总线类型
ret = bus_register(&hello_bus_type);
if (ret)
return ret;
// 创建总线属性文件
ret = bus_create_file(&hello_bus_type, &hello_bus_attr);
if (ret) {
bus_unregister(&hello_bus_type);
return ret;
}
return 0;
}
static void __exit hello_bus_exit(void)
{
bus_remove_file(&hello_bus_type, &hello_bus_attr);
bus_unregister(&hello_bus_type);
}
module_init(hello_bus_init);
module_exit(hello_bus_exit);
MODULE_LICENSE("GPL");
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
extern struct bus_type hello_bus_type;
void hello_device_release (struct device *dev)
{
printk("%s\n", __func__);
}
// 定义一个设备结构
struct device hello_device = {
.init_name = "hello", // 设备名称
.bus = &hello_bus_type, // 所属总线类型
.release = hello_device_release,
.devt = ((251 << 20) | 0), // 设备号
};
static ssize_t hello_device_show (struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf, "hello_device name: hello\n");
}
// 定义设备属性结构
static struct device_attribute hello_device_attr = {
.attr = {
.name = "hello_device_attr",
.mode = 0444,
},
.show = hello_device_show,
};
static int __init hello_device_init(void)
{
int ret;
ret = device_register(&hello_device);
if (ret)
return ret;
ret = device_create_file(&hello_device, &hello_device_attr);
if (ret) {
device_unregister(&hello_device);
return ret;
}
return 0;
}
static void __exit hello_device_exit(void)
{
device_remove_file(&hello_device, &hello_device_attr);
device_unregister(&hello_device);
}
module_init(hello_device_init);
module_exit(hello_device_exit);
MODULE_LICENSE("GPL");
ifneq ($(KERNELRELEASE),)
obj-m += bus.o
obj-m += device.o
obj-m += driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
14.3 设备注册过程分析:device_register
14.4 创建设备属性文件:device_create_file
- 默认属性文件:uevent、dev
- 默认链接文件:subsystem
- 自定义属性文件:hello_device_attr
回看之前 sysfs_create_file 部分的内容。
15. 设备模型:device_driver
15.1 结构体 device_driver 和 driver_attribute
15.2 如何向总线添加一个驱动和创建驱动属性文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
static int hello_bus_match(struct device *dev, struct device_driver *driver)
{
int match;
// 比较设备名称和驱动名称,返回匹配结果
match = !strncmp(dev_name(dev), driver->name, strlen(driver->name));
return match;
}
// 定义一个新的总线类型
struct bus_type hello_bus_type = {
.name = "hello_bus", // 总线名称
.match = hello_bus_match, // 匹配函数
};
EXPORT_SYMBOL_GPL(hello_bus_type);
// 显示总线名称的函数
static ssize_t hello_bus_show(struct bus_type *bus, char *buf)
{
return sprintf(buf, "bus name: %s\n", hello_bus_type.name);
}
static struct bus_attribute hello_bus_attr = {
.attr = {
.name = "hello_bus_attr",
.mode = 0644,
},
.show = hello_bus_show,
};
static int __init hello_bus_init(void)
{
int ret;
// 注册新的总线类型
ret = bus_register(&hello_bus_type);
if (ret)
return ret;
// 创建总线属性文件
ret = bus_create_file(&hello_bus_type, &hello_bus_attr);
if (ret) {
bus_unregister(&hello_bus_type);
return ret;
}
return 0;
}
static void __exit hello_bus_exit(void)
{
bus_remove_file(&hello_bus_type, &hello_bus_attr);
bus_unregister(&hello_bus_type);
}
module_init(hello_bus_init);
module_exit(hello_bus_exit);
MODULE_LICENSE("GPL");
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
extern struct bus_type hello_bus_type;
void hello_device_release (struct device *dev)
{
printk("%s\n", __func__);
}
// 定义一个设备结构
struct device hello_device = {
.init_name = "hello", // 设备名称
.bus = &hello_bus_type, // 所属总线类型
.release = hello_device_release,
.devt = ((251 << 20) | 0), // 设备号
};
static ssize_t hello_device_show (struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf, "hello_device name: hello\n");
}
// 定义设备属性结构
static struct device_attribute hello_device_attr = {
.attr = {
.name = "hello_device_attr",
.mode = 0444,
},
.show = hello_device_show,
};
static int __init hello_device_init(void)
{
int ret;
ret = device_register(&hello_device);
if (ret)
return ret;
ret = device_create_file(&hello_device, &hello_device_attr);
if (ret) {
device_unregister(&hello_device);
return ret;
}
return 0;
}
static void __exit hello_device_exit(void)
{
device_remove_file(&hello_device, &hello_device_attr);
device_unregister(&hello_device);
}
module_init(hello_device_init);
module_exit(hello_device_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/string.h>
extern struct bus_type hello_bus_type;
// 设备驱动的探测函数
static int hello_driver_probe(struct device *dev)
{
printk("%s: probe and init hello_device\n", __func__);
return 0;
}
// 设备驱动的移除函数
static int hello_driver_remove(struct device *dev)
{
return 0;
}
// 定义设备驱动结构体
struct device_driver hello_driver = {
.name = "hello", // 驱动名称
.bus = &hello_bus_type, // 关联的总线类型
.probe = hello_driver_probe,
.remove = hello_driver_remove,
};
// 展示驱动属性
static ssize_t hello_driver_show(struct device_driver *driver, char *buf)
{
return sprintf(buf, "hello_driver name: %s\n", hello_driver.name);
}
// 定义驱动属性结构体
static struct driver_attribute hello_driver_attr = {
.attr = {
.name = "hello_driver_attr", // 属性名称
.mode = 0444,
},
.show = hello_driver_show,
};
static int __init hello_driver_init(void)
{
int ret;
ret = driver_register(&hello_driver);
if (ret)
return ret;
ret = driver_create_file(&hello_driver, &hello_driver_attr);
if (ret) {
driver_unregister(&hello_driver);
return ret;
}
return 0;
}
static void __exit hello_driver_exit(void)
{
driver_remove_file(&hello_driver, &hello_driver_attr);
driver_unregister(&hello_driver);
}
module_init(hello_driver_init);
module_exit(hello_driver_exit);
MODULE_LICENSE("GPL");
ifneq ($(KERNELRELEASE),)
obj-m += bus.o
obj-m += device.o
obj-m += driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
15.3 驱动注册过程分析:driver_register
16. bus probe 和 driver probe
由于上一节中:
因为这里的 if-else 判断是分开的,要么执行 bus 的 probe,要么执行 driver 的 probe,所以:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
static int hello_bus_match(struct device *dev, struct device_driver *driver)
{
int match;
match = !strncmp(dev_name(dev), driver->name, strlen(driver->name));
return match;
}
static int hello_bus_probe(struct device *dev)
{
struct device_driver *drv = dev->driver;
printk("%s\n", __func__);
if (drv->probe) {
drv->probe(dev);
}
return 0;
}
struct bus_type hello_bus_type = {
.name = "hello_bus",
.match = hello_bus_match,
.probe = hello_bus_probe,
};
EXPORT_SYMBOL_GPL(hello_bus_type);
static void hello_bus_device_release(struct device *dev)
{
printk("hello_bus release\n");
}
struct device hello_bus_device = {
.init_name = "hello_bus_device",
.release = hello_bus_device_release,
};
EXPORT_SYMBOL_GPL(hello_bus_device);
static ssize_t hello_bus_show(struct bus_type *bus, char *buf)
{
return sprintf(buf, "bus name: %s\n", hello_bus_type.name);
}
static struct bus_attribute hello_bus_attr = {
.attr = {
.name = "hello_bus_attr",
.mode = 0644,
},
.show = hello_bus_show,
};
static int __init hello_bus_init(void)
{
int ret;
ret = bus_register(&hello_bus_type);
if (ret)
return ret;
ret = bus_create_file(&hello_bus_type, &hello_bus_attr);
if (ret) {
bus_unregister(&hello_bus_type);
return ret;
}
ret = device_register(&hello_bus_device);
if (ret) {
bus_remove_file(&hello_bus_type, &hello_bus_attr);
bus_unregister(&hello_bus_type);
return ret;
}
printk("create hello_bus success\n");
return 0;
}
static void __exit hello_bus_exit(void)
{
device_unregister(&hello_bus_device);
bus_remove_file(&hello_bus_type, &hello_bus_attr);
bus_unregister(&hello_bus_type);
}
module_init(hello_bus_init);
module_exit(hello_bus_exit);
MODULE_LICENSE("GPL");
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
extern struct bus_type hello_bus_type;
extern struct device hello_bus_device;
void hello_device_release (struct device *dev)
{
printk("%s\n", __func__);
}
// 定义一个设备结构
struct device hello_device = {
.parent = &hello_bus_device,
.init_name = "hello", // 设备名称
.bus = &hello_bus_type, // 所属总线类型
.release = hello_device_release,
.devt = ((251 << 20) | 0), // 设备号
};
static ssize_t hello_device_show (struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf, "hello_device name: hello\n");
}
// 定义设备属性结构
static struct device_attribute hello_device_attr = {
.attr = {
.name = "hello_device_attr",
.mode = 0444,
},
.show = hello_device_show,
};
static int __init hello_device_init(void)
{
int ret;
ret = device_register(&hello_device);
if (ret)
return ret;
ret = device_create_file(&hello_device, &hello_device_attr);
if (ret) {
device_unregister(&hello_device);
return ret;
}
printk("create hello_device success\n");
return 0;
}
static void __exit hello_device_exit(void)
{
device_remove_file(&hello_device, &hello_device_attr);
device_unregister(&hello_device);
}
module_init(hello_device_init);
module_exit(hello_device_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/string.h>
extern struct bus_type hello_bus_type;
// 设备驱动的探测函数
static int hello_driver_probe(struct device *dev)
{
printk("%s: probe and init hello_device\n", __func__);
return 0;
}
// 设备驱动的移除函数
static int hello_driver_remove(struct device *dev)
{
printk("%s: driver remove\n", __func__);
return 0;
}
// 定义设备驱动结构体
struct device_driver hello_driver = {
.name = "hello", // 驱动名称
.bus = &hello_bus_type, // 关联的总线类型
.probe = hello_driver_probe,
.remove = hello_driver_remove,
};
// 展示驱动属性
static ssize_t hello_driver_show(struct device_driver *driver, char *buf)
{
return sprintf(buf, "hello_driver name: %s\n", hello_driver.name);
}
// 定义驱动属性结构体
static struct driver_attribute hello_driver_attr = {
.attr = {
.name = "hello_driver_attr", // 属性名称
.mode = 0444,
},
.show = hello_driver_show,
};
static int __init hello_driver_init(void)
{
int ret;
ret = driver_register(&hello_driver);
if (ret)
return ret;
ret = driver_create_file(&hello_driver, &hello_driver_attr);
if (ret) {
driver_unregister(&hello_driver);
return ret;
}
printk("create and register hello_driver success\n");
return 0;
}
static void __exit hello_driver_exit(void)
{
driver_remove_file(&hello_driver, &hello_driver_attr);
driver_unregister(&hello_driver);
}
module_init(hello_driver_init);
module_exit(hello_driver_exit);
MODULE_LICENSE("GPL");
ifneq ($(KERNELRELEASE),)
obj-m += bus.o
obj-m += device.o
obj-m += driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
17. 设备模型:class
- 对某类设备的一个抽象,封装出一些标准的接口
- 以 RTC 为例:RTC、file_operations,系统调用:ioctl、cmd、set_time、set_alarm,不同厂家接口不同
- 抽象出一个 rtc_class,设置时间、设置闹钟
17.1 编程接口
- class_create
- class_create_file
17.2 编程实例
- 如何去创建类?如何去创建设备?
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
static int hello_bus_match(struct device *dev, struct device_driver *driver)
{
int match;
match = !strncmp(dev_name(dev), driver->name, strlen(driver->name));
return match;
}
struct bus_type hello_bus_type = {
.name = "hello_bus",
.match = hello_bus_match,
};
EXPORT_SYMBOL_GPL(hello_bus_type);
static ssize_t hello_bus_show(struct bus_type *bus, char *buf)
{
return sprintf(buf, "bus name: %s\n", hello_bus_type.name);
}
static struct bus_attribute hello_bus_attr = {
.attr = {
.name = "hello_bus_attr",
.mode = 0644,
},
.show = hello_bus_show,
};
static int __init hello_bus_init(void)
{
int ret;
ret = bus_register(&hello_bus_type);
if (ret)
return ret;
ret = bus_create_file(&hello_bus_type, &hello_bus_attr);
if (ret) {
bus_unregister(&hello_bus_type);
return ret;
}
return 0;
}
static void __exit hello_bus_exit(void)
{
bus_remove_file(&hello_bus_type, &hello_bus_attr);
bus_unregister(&hello_bus_type);
}
module_init(hello_bus_init);
module_exit(hello_bus_exit);
MODULE_LICENSE("GPL");
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
struct class *hello_class;
EXPORT_SYMBOL_GPL(hello_class);
static ssize_t hello_class_show(struct class *class, struct class_attribute *attr, char *buf)
{
return sprintf(buf, "class name: %s\n", class->name);
}
// 定义类属性
static struct class_attribute hello_class_attr = {
.attr = {
.name = "hello_class_attr",
.mode = 0444,
},
.show = hello_class_show,
};
static int __init hello_class_init(void)
{
int ret;
// 创建一个新的类
hello_class = class_create(THIS_MODULE, "hello_class");
ret = PTR_ERR(hello_class);
if (IS_ERR(hello_class))
return ret;
// 创建类属性文件
ret = class_create_file(hello_class, &hello_class_attr);
if (ret) {
class_destroy(hello_class);
return ret;
}
return 0;
}
static void __exit hello_class_exit(void)
{
class_remove_file(hello_class, &hello_class_attr);
class_destroy(hello_class);
}
module_init(hello_class_init);
module_exit(hello_class_exit);
MODULE_LICENSE("GPL");
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
extern struct bus_type hello_bus_type;
extern struct class *hello_class;
void hello_device_release (struct device *dev)
{
printk("%s\n", __func__);
}
struct device hello_device = {
.init_name = "hello",
.bus = &hello_bus_type,
.release = hello_device_release,
.devt = ((251 << 20) | 0),
};
static ssize_t hello_device_show (struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "hello_device name: hello\n");
}
static struct device_attribute hello_device_attr = {
.attr = {
.name = "hello_device_attr",
.mode = 0444,
},
.show = hello_device_show,
};
static int __init hello_device_init(void)
{
int ret;
ret = device_register(&hello_device);
if (ret)
return ret;
ret = device_create_file(&hello_device, &hello_device_attr);
if (ret) {
device_unregister(&hello_device);
return ret;
}
// 创建另一个新的设备实例
device_create(hello_class, NULL, (251<<20 | 1), 0, "hello2");
return 0;
}
static void __exit hello_device_exit(void)
{
device_remove_file(&hello_device, &hello_device_attr);
device_unregister(&hello_device);
device_destroy(hello_class, 0);
}
module_init(hello_device_init);
module_exit(hello_device_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/string.h>
extern struct bus_type hello_bus_type;
static int hello_driver_probe(struct device *dev)
{
printk("%s: probe and init hello_device\n", __func__);
return 0;
}
static int hello_driver_remove(struct device *dev)
{
printk("%s: driver remove\n", __func__);
return 0;
}
struct device_driver hello_driver = {
.name = "hello",
.bus = &hello_bus_type,
.probe = hello_driver_probe,
.remove = hello_driver_remove,
};
static ssize_t hello_driver_show(struct device_driver *driver, char *buf)
{
return sprintf(buf, "hello_driver name: %s\n", hello_driver.name);
}
static struct driver_attribute hello_driver_attr = {
.attr = {
.name = "hello_driver_attr",
.mode = 0444,
},
.show = hello_driver_show,
};
static int __init hello_driver_init(void)
{
int ret;
ret = driver_register(&hello_driver);
if (ret)
return ret;
ret = driver_create_file(&hello_driver, &hello_driver_attr);
if (ret) {
driver_unregister(&hello_driver);
return ret;
}
return 0;
}
static void __exit hello_driver_exit(void)
{
driver_remove_file(&hello_driver, &hello_driver_attr);
driver_unregister(&hello_driver);
}
module_init(hello_driver_init);
module_exit(hello_driver_exit);
MODULE_LICENSE("GPL");
ifneq ($(KERNELRELEASE),)
obj-m += bus.o
obj-m += device.o
obj-m += driver.o
obj-m += class.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
17.3 驱动案例分析:class 在内核驱动中的应用
- class 接口的抽象:rtc_class_ops
set_time
set_alarm
- rtc_device 的抽象
cdev:注册一个字符设备驱动
device:通过 bus 调用对应的 probe
rtc_class_ops:通过该接口,回调具体 RTC 的 set_time 函数
回到 pl031_probe 函数:
18. device 的二次抽象
18.1 内核中的 OOP 思想
18.2 进一步抽象
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
static int hello_bus_match(struct device *dev, struct device_driver *driver)
{
int match;
match = !strncmp(dev_name(dev), driver->name, strlen(driver->name));
return match;
}
struct bus_type hello_bus_type = {
.name = "hello_bus",
.match = hello_bus_match,
};
EXPORT_SYMBOL_GPL(hello_bus_type);
static int __init hello_bus_init(void)
{
int ret = 0;
ret = bus_register(&hello_bus_type);
return ret;
}
static void __exit hello_bus_exit(void)
{
bus_unregister(&hello_bus_type);
}
module_init(hello_bus_init);
module_exit(hello_bus_exit);
MODULE_LICENSE("GPL");
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/slab.h>
#include "hello.h"
extern struct bus_type hello_bus_type;
static void hello_device_release (struct device *dev)
{
struct hello_device *p = container_of(dev, struct hello_device, dev);
printk("%s: %s device released\n", __func__, p->name);
//kfree(p);
}
int hello_device_register(struct hello_device *hdev)
{
int ret;
hdev->dev.init_name = hdev->name;
hdev->dev.bus = &hello_bus_type;
hdev->dev.devt = ((251 << 20) | hdev->id);
hdev->dev.release = &hello_device_release;
ret = device_register(&hdev->dev);
return ret;
}
void hello_device_unregister(struct hello_device *hdev)
{
device_unregister(&hdev->dev);
}
struct hello_device hello_dev = {
.name = "hello",
.id = 1,
};
static int __init hello_device_init(void)
{
return hello_device_register(&hello_dev);
}
static void __exit hello_device_exit(void)
{
hello_device_unregister(&hello_dev);
}
module_init(hello_device_init);
module_exit(hello_device_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/string.h>
#include "hello.h"
extern struct bus_type hello_bus_type;
static int hello_driver_probe(struct device *dev)
{
printk("%s: probe and init hello_device\n", __func__);
return 0;
}
static int hello_driver_remove(struct device *dev)
{
printk("%s: driver remove\n", __func__);
return 0;
}
struct hello_driver {
int (*probe)(struct hello_device *);
int (*remove)(struct hello_device *);
struct device_driver driver;
};
static struct hello_driver hello_drv = {
.driver = {
.probe = hello_driver_probe,
.remove = hello_driver_remove,
.name = "hello",
.bus = &hello_bus_type,
},
};
int hello_driver_register(struct hello_driver *drv)
{
int ret = 0;
ret = driver_register(&drv->driver);
return ret;
}
void hello_driver_unregister(struct hello_driver *drv)
{
driver_unregister(&drv->driver);
}
static int __init hello_driver_init(void)
{
return hello_driver_register(&hello_drv);
}
static void __exit hello_driver_exit(void)
{
hello_driver_unregister(&hello_drv);
}
module_init(hello_driver_init);
module_exit(hello_driver_exit);
MODULE_LICENSE("GPL");
#ifndef __HELLO_H_
#define __HELLO_H_
struct hello_device {
char *name;
int id;
struct device dev;
};
#endif
ifneq ($(KERNELRELEASE),)
obj-m += bus.o
obj-m += device.o
obj-m += driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
19. 实现一个总线子系统
- 如何实现一个子系统:hello bus
- 接口的封装:
- 往总线注册设备
- 往总线注册驱动
- 总线功能实现:match、probe、……
- 头文件声明:接口进行声明
- 编程示例
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include "hello.h"
static int hello_bus_match(struct device *dev, struct device_driver *driver)
{
int match;
match = !strncmp(dev_name(dev), driver->name, strlen(driver->name));
return match;
}
struct bus_type hello_bus_type = {
.name = "hello_bus",
.match = hello_bus_match,
};
EXPORT_SYMBOL_GPL(hello_bus_type);
static int __init hello_bus_init(void)
{
int ret = 0;
ret = bus_register(&hello_bus_type);
return ret;
}
static void __exit hello_bus_exit(void)
{
bus_unregister(&hello_bus_type);
}
module_init(hello_bus_init);
module_exit(hello_bus_exit);
static void hello_device_release(struct device *dev)
{
struct hello_device *p = container_of(dev, struct hello_device, dev);
printk("%s: %s device released\n", __func__, p->name);
//kfree(p);
}
int hello_device_register(struct hello_device *hdev)
{
int ret = 0;
hdev->dev.init_name = hdev->name;
hdev->dev.bus = &hello_bus_type;
hdev->dev.devt = ((251 << 20) | hdev->id);
hdev->dev.release = hello_device_release;
ret = device_register(&hdev->dev);
return ret;
}
EXPORT_SYMBOL_GPL(hello_device_register);
void hello_device_unregister(struct hello_device *hdev)
{
device_unregister(&hdev->dev);
}
EXPORT_SYMBOL_GPL(hello_device_unregister);
int hello_driver_register(struct hello_driver *drv)
{
int ret = 0;
drv->driver.name = drv->name;
drv->driver.bus = &hello_bus_type;
drv->driver.probe = drv->probe;
drv->driver.remove = drv->remove;
ret = driver_register(&drv->driver);
return ret;
}
EXPORT_SYMBOL_GPL(hello_driver_register);
void hello_driver_unregister(struct hello_driver *drv)
{
driver_unregister(&drv->driver);
}
EXPORT_SYMBOL_GPL(hello_driver_unregister);
MODULE_LICENSE("GPL");
#ifndef __HELLO_H_
#define __HELLO_H_
struct hello_device {
char *name;
int id;
struct device dev;
};
struct hello_driver {
char *name;
int (*probe)(struct device *);
int (*remove)(struct device *);
struct device_driver driver;
};
extern int hello_device_register(struct hello_device *hdev);
extern void hello_device_unregister(struct hello_device *hdev);
extern int hello_driver_register(struct hello_driver *drv);
extern void hello_driver_unregister(struct hello_driver *drv);
#endif
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/slab.h>
#include "hello.h"
struct hello_device hello_dev = {
.name = "test",
.id = 1,
};
static int __init hello_device_init(void)
{
return hello_device_register(&hello_dev);
}
static void __exit hello_device_exit(void)
{
hello_device_unregister(&hello_dev);
}
module_init(hello_device_init);
module_exit(hello_device_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/string.h>
#include "hello.h"
static int hello_driver_probe(struct device *dev)
{
printk("%s: probe and init hello_device: %s\n", __func__, dev_name(dev));
return 0;
}
static int hello_driver_remove(struct device *dev)
{
printk("%s: driver remove: %s\n", __func__, dev_name(dev));
return 0;
}
static struct hello_driver hello_drv = {
.name = "test",
.probe = hello_driver_probe,
.remove = hello_driver_remove,
};
static int __init hello_driver_init(void)
{
return hello_driver_register(&hello_drv);
}
static void __exit hello_driver_exit(void)
{
hello_driver_unregister(&hello_drv);
}
module_init(hello_driver_init);
module_exit(hello_driver_exit);
MODULE_LICENSE("GPL");
ifneq ($(KERNELRELEASE),)
obj-m += core.o
obj-m += my_device.o my_driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
20. 驱动复用:match_table
- 进一步完善子系统,增加功能
- 驱动复用:多个设备共享同一个驱动
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include "hello.h"
static int hello_bus_match(struct device *dev, struct device_driver *driver)
{
int match;
struct device_id *id;
struct hello_driver *drv;
match = !strncmp(dev_name(dev), driver->name, strlen(driver->name));
if (match)
return match;
drv = container_of(driver, struct hello_driver, driver);
id = drv->id_table;
// 遍历驱动的 ID 表,根据设备的 init_name 进行匹配
while (id->name[0]) {
if (strcmp(id->name, dev->init_name) == 0) {
return 1;
}
id++;
}
return 0;
}
struct bus_type hello_bus_type = {
.name = "hello_bus",
.match = hello_bus_match,
};
EXPORT_SYMBOL_GPL(hello_bus_type);
static int __init hello_bus_init(void)
{
int ret = 0;
ret = bus_register(&hello_bus_type);
return ret;
}
static void __exit hello_bus_exit(void)
{
bus_unregister(&hello_bus_type);
}
module_init(hello_bus_init);
module_exit(hello_bus_exit);
static void hello_device_release(struct device *dev)
{
struct hello_device *p = container_of(dev, struct hello_device, dev);
printk("%s: %s device released\n", __func__, p->name);
//kfree(p);
}
int hello_device_register(struct hello_device *hdev)
{
int ret = 0;
hdev->dev.init_name = hdev->name;
hdev->dev.bus = &hello_bus_type;
hdev->dev.devt = ((251 << 20) | hdev->id);
hdev->dev.release = hello_device_release;
ret = device_register(&hdev->dev);
return ret;
}
EXPORT_SYMBOL_GPL(hello_device_register);
void hello_device_unregister(struct hello_device *hdev)
{
device_unregister(&hdev->dev);
}
EXPORT_SYMBOL_GPL(hello_device_unregister);
int hello_driver_register(struct hello_driver *drv)
{
int ret = 0;
drv->driver.name = drv->name;
drv->driver.bus = &hello_bus_type;
drv->driver.probe = drv->probe;
drv->driver.remove = drv->remove;
ret = driver_register(&drv->driver);
return ret;
}
EXPORT_SYMBOL_GPL(hello_driver_register);
void hello_driver_unregister(struct hello_driver *drv)
{
driver_unregister(&drv->driver);
}
EXPORT_SYMBOL_GPL(hello_driver_unregister);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/string.h>
#include "hello.h"
static int hello_driver_probe(struct device *dev)
{
printk("%s: probe and init hello_device: %s\n", __func__, dev_name(dev));
return 0;
}
static int hello_driver_remove(struct device *dev)
{
printk("%s: driver remove: %s\n", __func__, dev_name(dev));
return 0;
}
struct device_id compat_table[] = {
{ .name = "test1", .dev_id = 1, },
{ .name = "test2", .dev_id = 2, },
{ .name = "test3", .dev_id = 3, },
{ },
};
static struct hello_driver hello_drv = {
.name = "test",
.probe = hello_driver_probe,
.remove = hello_driver_remove,
.id_table = compat_table,
};
static int __init hello_driver_init(void)
{
return hello_driver_register(&hello_drv);
}
static void __exit hello_driver_exit(void)
{
hello_driver_unregister(&hello_drv);
}
module_init(hello_driver_init);
module_exit(hello_driver_exit);
MODULE_LICENSE("GPL");
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/slab.h>
#include "hello.h"
struct hello_device hello_dev = {
.name = "test",
.id = 1,
};
static int __init hello_device_init(void)
{
return hello_device_register(&hello_dev);
}
static void __exit hello_device_exit(void)
{
hello_device_unregister(&hello_dev);
}
module_init(hello_device_init);
module_exit(hello_device_exit);
MODULE_LICENSE("GPL");
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/slab.h>
#include "hello.h"
struct hello_device hello_dev1 = {
.name = "test1",
.id = 2,
};
static int __init hello_device_init(void)
{
return hello_device_register(&hello_dev1);
}
static void __exit hello_device_exit(void)
{
hello_device_unregister(&hello_dev1);
}
module_init(hello_device_init);
module_exit(hello_device_exit);
MODULE_LICENSE("GPL");
#ifndef __HELLO_H_
#define __HELLO_H_
struct hello_device {
char *name;
int id;
struct device dev;
};
struct device_id {
char name[30];
int dev_id;
};
struct hello_driver {
char *name;
int (*probe)(struct device *);
int (*remove)(struct device *);
struct device_driver driver;
struct device_id *id_table;
};
extern int hello_device_register(struct hello_device *hdev);
extern void hello_device_unregister(struct hello_device *hdev);
extern int hello_driver_register(struct hello_driver *drv);
extern void hello_driver_unregister(struct hello_driver *drv);
#endif
ifneq ($(KERNELRELEASE),)
obj-m += core.o
obj-m += my_device.o my_device1.o my_driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
21. 设备的热插拔 hotplug 机制
- device 热插拔实现机制:uevent
- 自动创建设备节点
- 应用程序:udev
- 嵌入式应用程序:mdev
- 实验:利用第 11.4 节 和第 20 节中的程序
22. 从字符驱动到总线驱动
22.1 将字符型驱动升级为 device_driver 总线型驱动
- 支持用户接口:设置时间和闹钟
- 中断线程化
- 理解初始化流程、probe 流程
- 将 RTC 注册到 hello bus 子系统
- 将 RTC device 注册到 hello bus
- 将 RTC driver 注册到 hello bus
- 自动创建设备节点:mdev -d
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include "hello.h"
static int hello_bus_match(struct device *dev, struct device_driver *driver)
{
int match;
struct device_id *id;
struct hello_driver *drv;
match = !strncmp(dev_name(dev), driver->name, strlen(driver->name));
if (match)
return match;
drv = container_of(driver, struct hello_driver, driver);
id = drv->id_table;
// 遍历驱动的 ID 表,根据设备的 init_name 进行匹配
while (id->name[0]) {
if (strcmp(id->name, dev->init_name) == 0) {
return 1;
}
id++;
}
return 0;
}
struct bus_type hello_bus_type = {
.name = "hello_bus",
.match = hello_bus_match,
};
EXPORT_SYMBOL_GPL(hello_bus_type);
static int __init hello_bus_init(void)
{
int ret = 0;
ret = bus_register(&hello_bus_type);
return ret;
}
static void __exit hello_bus_exit(void)
{
bus_unregister(&hello_bus_type);
}
module_init(hello_bus_init);
module_exit(hello_bus_exit);
static void hello_device_release(struct device *dev)
{
struct hello_device *p = container_of(dev, struct hello_device, dev);
printk("%s: %s device released\n", __func__, p->name);
//kfree(p);
}
int hello_device_register(struct hello_device *hdev)
{
int ret = 0;
hdev->dev.init_name = hdev->name;
hdev->dev.bus = &hello_bus_type;
hdev->dev.devt = ((251 << 20) | hdev->id);
hdev->dev.release = hello_device_release;
ret = device_register(&hdev->dev);
return ret;
}
EXPORT_SYMBOL_GPL(hello_device_register);
void hello_device_unregister(struct hello_device *hdev)
{
device_unregister(&hdev->dev);
}
EXPORT_SYMBOL_GPL(hello_device_unregister);
int hello_driver_register(struct hello_driver *drv)
{
int ret = 0;
drv->driver.name = drv->name;
drv->driver.bus = &hello_bus_type;
drv->driver.probe = drv->probe;
drv->driver.remove = drv->remove;
ret = driver_register(&drv->driver);
return ret;
}
EXPORT_SYMBOL_GPL(hello_driver_register);
void hello_driver_unregister(struct hello_driver *drv)
{
driver_unregister(&drv->driver);
}
EXPORT_SYMBOL_GPL(hello_driver_unregister);
MODULE_LICENSE("GPL");
#ifndef __HELLO_H_
#define __HELLO_H_
struct hello_device {
char *name;
int id;
struct device dev;
};
struct device_id {
char name[30];
int dev_id;
};
struct hello_driver {
char *name;
int (*probe)(struct device *);
int (*remove)(struct device *);
struct device_driver driver;
struct device_id *id_table;
};
extern int hello_device_register(struct hello_device *hdev);
extern void hello_device_unregister(struct hello_device *hdev);
extern int hello_driver_register(struct hello_driver *drv);
extern void hello_driver_unregister(struct hello_driver *drv);
#endif
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/slab.h>
#include "hello.h"
struct hello_device rtc_dev = {
.name = "rtc3",
.id = 3,
};
static int __init rtc_device_init(void)
{
return hello_device_register(&rtc_dev);
}
static void __exit rtc_device_exit(void)
{
hello_device_unregister(&rtc_dev);
}
module_init(rtc_device_init);
module_exit(rtc_device_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/workqueue.h>
#include "hello.h"
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
} rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
#define RTC_BASE 0x10017000
volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
void set_rtc_alarm(rtc_reg_t *regs)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = regs->RTCDR; /* get current time */
current_time = tmp;
regs->RTCMR = tmp + 1;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
static irqreturn_t rtc_irq_thread(int irq, void *p)
{
struct rtc_time tm;
tm.hour = (current_time % 86400) / 3600;
tm.min = (current_time % 3600) / 60;
tm.sec = current_time % 60;
printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
return IRQ_HANDLED;
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
set_rtc_alarm(regs);
return IRQ_WAKE_THREAD;
}
static int rtc_driver_probe(struct device *dev)
{
printk("%s: probe and init hello_device: %s\n", __func__, dev_name(dev));
set_rtc_alarm(regs);
return 0;
}
static int rtc_driver_remove(struct device *dev)
{
printk("%s: driver remove: %s\n", __func__, dev_name(dev));
return 0;
}
struct device_id compat_table[] = {
{ .name = "rtc1", .dev_id = 1, },
{ .name = "rtc2", .dev_id = 2, },
{ .name = "rtc3", .dev_id = 3, },
{ },
};
static struct hello_driver rtc_drv = {
.name = "rtc",
.probe = rtc_driver_probe,
.remove = rtc_driver_remove,
.id_table = compat_table,
};
static int __init rtc_driver_init(void)
{
irqreturn_t ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
ret = request_threaded_irq(39, rtc_alarm_handler, rtc_irq_thread, 0, "rtc0-test", NULL);
if (ret == -1) {
printk("request_irq failed!\n");
return -1;
}
return hello_driver_register(&rtc_drv);
}
static void __exit rtc_driver_exit(void)
{
free_irq(39,NULL);
hello_driver_unregister(&rtc_drv);
}
module_init(rtc_driver_init);
module_exit(rtc_driver_exit);
MODULE_LICENSE("GPL");
ifneq ($(KERNELRELEASE),)
obj-m := core.o rtc_device.o rtc_driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
实验前需要重新编译内核,关掉内核默认使用的 rtc-pl031:
22.2 使用 uevent 机制实时更新时间
- 驱动中发送 uevent 到用户空间
- 用户应用程序监听 uevent
- 解析 uevent 信息,自动创建设备节点
- 实时显示 RTC 系统时间
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include "hello.h"
static int hello_bus_match(struct device *dev, struct device_driver *driver)
{
struct device_id *id;
struct hello_driver *drv;
if (strcmp(dev_name(dev), driver->name) == 0)
return 1;
drv = container_of(driver, struct hello_driver, driver);
id = drv->id_table;
while (id->name[0]) {
if (strcmp(id->name, dev_name(dev)) == 0) {
return 1;
}
id++;
}
return 0;
}
struct bus_type hello_bus_type = {
.name = "hello_bus",
.match = hello_bus_match,
};
EXPORT_SYMBOL_GPL(hello_bus_type);
static int __init hello_bus_init(void)
{
int ret = 0;
ret = bus_register(&hello_bus_type);
return ret;
}
static void __exit hello_bus_exit(void)
{
bus_unregister(&hello_bus_type);
}
module_init(hello_bus_init);
module_exit(hello_bus_exit);
static void hello_device_release(struct device *dev)
{
struct hello_device *p = container_of(dev, struct hello_device, dev);
printk("%s: %s device released\n", __func__, p->name);
//kfree(p);
}
int hello_device_uevent(struct device *dev, enum kobject_action action, char *env[])
{
return kobject_uevent_env(&dev->kobj, action, env);
}
EXPORT_SYMBOL_GPL(hello_device_uevent);
int hello_device_register(struct hello_device *hdev)
{
int ret = 0;
hdev->dev.init_name = hdev->name;
hdev->dev.bus = &hello_bus_type;
hdev->dev.devt = ((251 << 20) | hdev->id);
hdev->dev.release = hello_device_release;
ret = device_register(&hdev->dev);
return ret;
}
EXPORT_SYMBOL_GPL(hello_device_register);
void hello_device_unregister(struct hello_device *hdev)
{
device_unregister(&hdev->dev);
}
EXPORT_SYMBOL_GPL(hello_device_unregister);
int hello_driver_register(struct hello_driver *drv)
{
int ret = 0;
drv->driver.name = drv->name;
drv->driver.bus = &hello_bus_type;
drv->driver.probe = drv->probe;
drv->driver.remove = drv->remove;
ret = driver_register(&drv->driver);
return ret;
}
EXPORT_SYMBOL_GPL(hello_driver_register);
void hello_driver_unregister(struct hello_driver *drv)
{
driver_unregister(&drv->driver);
}
EXPORT_SYMBOL_GPL(hello_driver_unregister);
MODULE_LICENSE("GPL");
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/slab.h>
#include "hello.h"
struct hello_device rtc_dev = {
.name = "rtc3",
.id = 3,
};
static int __init rtc_device_init(void)
{
return hello_device_register(&rtc_dev);
}
static void __exit rtc_device_exit(void)
{
hello_device_unregister(&rtc_dev);
}
module_init(rtc_device_init);
module_exit(rtc_device_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/workqueue.h>
#include "hello.h"
typedef volatile struct{
unsigned long RTCDR; /* +0x00: data register */
unsigned long RTCMR; /* +0x04: match register */
unsigned long RTCLR; /* +0x08: load register */
unsigned long RTCCR; /* +0x0C: control register */
unsigned long RTCIMSC; /* +0x10: interrupt mask set and clear register*/
unsigned long RTCRIS; /* +0x14: raw interrupt status register*/
unsigned long RTCMIS; /* +0x18: masked interrupt status register */
unsigned long RTCICR; /* +0x1C: interrupt clear register */
} rtc_reg_t;
struct rtc_time{
unsigned int year;
unsigned int mon;
unsigned int day;
unsigned int hour;
unsigned int min;
unsigned int sec;
};
#define RTC_BASE 0x10017000
volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
struct device *devp;
void set_rtc_alarm(rtc_reg_t *regs)
{
unsigned long tmp = 0;
tmp = regs->RTCCR; /* write enable */
tmp = tmp & 0xFFFFFFFE;
regs->RTCCR = tmp;
tmp = regs->RTCDR; /* get current time */
current_time = tmp;
regs->RTCMR = tmp + 10;/* set alarm time */
regs->RTCICR = 1; /* clear RTCINTR interrupt */
regs->RTCIMSC = 1; /* set the mask */
tmp = regs->RTCCR; /* write disable */
tmp = tmp | 0x1;
regs->RTCCR = tmp;
}
static irqreturn_t rtc_irq_thread(int irq, void *p)
{
char time[20];
char name[20];
char *envp[] = {
time,
name,
NULL,
};
struct rtc_time tm;
tm.hour = (current_time % 86400) / 3600;
tm.min = (current_time % 3600) / 60;
tm.sec = current_time % 60;
// printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
sprintf(time, "TIME=%d:%d:%d", tm.hour, tm.min, tm.sec);
sprintf(name, "NAME=%s", "test_name");
hello_device_uevent(devp, KOBJ_CHANGE, envp);
return IRQ_HANDLED;
}
static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
set_rtc_alarm(regs);
return IRQ_WAKE_THREAD;
}
static int rtc_driver_probe(struct device *dev)
{
printk("%s: probe and init hello_device: %s\n", __func__, dev_name(dev));
devp = dev;
set_rtc_alarm(regs);
return 0;
}
static int rtc_driver_remove(struct device *dev)
{
printk("%s: driver remove: %s\n", __func__, dev_name(dev));
devp = NULL;
return 0;
}
struct device_id compat_table[] = {
{ .name = "rtc1", .dev_id = 1, },
{ .name = "rtc2", .dev_id = 2, },
{ .name = "rtc3", .dev_id = 3, },
{ },
};
static struct hello_driver rtc_drv = {
.name = "rtc",
.probe = rtc_driver_probe,
.remove = rtc_driver_remove,
.id_table = compat_table,
};
static int __init rtc_driver_init(void)
{
irqreturn_t ret = 0;
regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t));
printk("rtc_init\n");
ret = request_threaded_irq(39, rtc_alarm_handler, rtc_irq_thread, 0, "rtc0-test", NULL);
if (ret == -1) {
printk("request_irq failed!\n");
return -1;
}
return hello_driver_register(&rtc_drv);
}
static void __exit rtc_driver_exit(void)
{
free_irq(39,NULL);
hello_driver_unregister(&rtc_drv);
}
module_init(rtc_driver_init);
module_exit(rtc_driver_exit);
MODULE_LICENSE("GPL");
#ifndef __HELLO_H_
#define __HELLO_H_
struct hello_device {
char *name;
int id;
struct device dev;
};
struct device_id {
char name[30];
int dev_id;
};
struct hello_driver {
char *name;
int (*probe)(struct device *);
int (*remove)(struct device *);
struct device_driver driver;
struct device_id *id_table;
};
extern int hello_device_register(struct hello_device *hdev);
extern void hello_device_unregister(struct hello_device *hdev);
extern int hello_driver_register(struct hello_driver *drv);
extern void hello_driver_unregister(struct hello_driver *drv);
extern int hello_device_uevent(struct device *hdev, enum kobject_action action, char *env[]);
#endif
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <linux/netlink.h>
const char *action = "";
const char *devpath = "";
const char *subsystem = "";
const char *bustype = "";
const char *addr = "";
const char *devname = "";
const char *name = "";
const char *time = "";
int major = 0;
int minor = 0;
int open_socket(void)
{
int fd;
struct sockaddr_nl socknl_addr = {
.nl_family = AF_NETLINK,
.nl_pad = 1,
.nl_pid = getpid(),
.nl_groups = 0xffffffff,
};
fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
if (fd < 0) {
printf("%s: create socket ep failed\n", __func__);
return -1;
}
if (bind(fd, (struct sockaddr *) &socknl_addr, sizeof(socknl_addr)) < 0) {
printf("%s: bind socket failed\n", __func__);
close(fd);
return -1;
}
return fd;
}
void parse_event(const char *msg)
{
while (*msg) {
printf("%s\n", msg);
if (!strncmp(msg, "ACTION=", 7)) {
msg += 7;
action = msg;
} else if (!strncmp(msg, "DEVPATH=", 8)) {
msg += 8;
devpath = msg;
} else if (!strncmp(msg, "SUBSYSTEM=", 10)) {
msg += 10;
subsystem = msg;
} else if (!strncmp(msg, "MAJOR=", 6)) {
msg += 6;
major = atoi(msg);
} else if (!strncmp(msg, "MINORR=", 7)) {
msg += 7;
minor = atoi(msg);
} else if (!strncmp(msg, "BUSTYPE=", 8)) {
msg += 8;
bustype = msg;
} else if (!strncmp(msg, "TIME=", 5)) {
msg += 5;
time = msg;
} else if (!strncmp(msg, "DEVNAME=", 8)) {
msg += 8;
devname = msg;
} else if (!strncmp(msg, "NAME=", 5)) {
msg += 5;
name = msg;
}
while(*msg++);
}
printf("-------------------%s---------------------------\n", time);
}
void make_hello_mode(const char *devname, mode_t mode, int major, int minor)
{
char pathname[20];
strcpy(pathname, "/dev/");
strcpy(&pathname[5], devname);
if (!strcmp(action, "add"))
mknod(pathname, 0666, ((major<<20) | minor));
if (!strcmp(action, "remove"))
remove(pathname);
}
int main(void)
{
int skt_fd, len;
char msg_buf[5000];
skt_fd = open_socket();
do {
while((len = recv(skt_fd, msg_buf, 4096, 0)) > 0) {
if(len == 4096)
continue;
msg_buf[len] = '\0';
msg_buf[len + 1] = '\0';
parse_event(msg_buf);
make_hello_mode(devname, 0666, major, minor);
}
} while(1);
}
ifneq ($(KERNELRELEASE),)
obj-m := core.o rtc_device.o rtc_driver.o
else
EXTRA_CFLAGS += -DDEBUG
KDIR:=/home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif