目录
前言
什么是总线设备驱动模型
一、重要结构体
二、设备树语法
二、编程思路
1.platform_driver结构体
三、使用设备树
1.步进电机
2.红外遥控
四、代码示例
前言
在这里主要记录学习韦东山老师Linux驱动人入门实验班的笔记,韦东山老师的驱动课程讲的非常好,想要学习驱动的小伙伴可以去b站学习他的课程。
什么是总线设备驱动模型
在 Linux 中,总线、设备和驱动模型(Bus-Device-Driver model)是一种用于管理设备和驱动程序的架构设计。
一、总线(Bus)
总线是连接设备和驱动的一种抽象概念。它代表了一种通信通道或机制,用于在不同的硬件组件之间传输数据和控制信号。Linux 内核中有多种类型的总线,例如 PCI 总线、USB 总线、I2C 总线等。
总线的主要作用包括:
- 发现设备:总线负责检测连接到其上的设备,并向内核报告设备的存在。
- 匹配设备和驱动:总线根据设备和驱动的特定属性,尝试将设备与合适的驱动程序进行匹配。
二、设备(Device)
设备在 Linux 中表示具体的硬件实体。每个设备都有一组特定的属性和功能,例如设备类型、设备编号、设备的硬件地址等。
设备对象通常包含以下信息:
- 设备名称:用于标识设备的字符串。
- 设备属性:描述设备特性的各种参数,如设备的厂商信息、设备型号等。
- 设备操作函数指针:指向一组用于操作设备的函数,这些函数实现了设备的具体功能,如读、写、控制等操作。
三、驱动(Driver)
驱动程序是用于控制特定类型设备的软件模块。它实现了与设备通信和操作设备的功能。
驱动对象通常包含以下内容:
- 驱动名称:用于标识驱动程序的字符串。
- 设备支持列表:驱动程序可以支持的设备类型列表。
- 驱动操作函数指针:指向一组用于操作设备的函数,这些函数与设备对象中的操作函数指针相对应,实现了对设备的具体控制操作。
四、工作原理
当系统启动或设备插入时,总线会检测到新设备的存在,并收集设备的信息。然后,总线会遍历已注册的驱动程序列表,尝试将设备与合适的驱动进行匹配。匹配通常基于设备和驱动的特定属性,如设备的类型、厂商 ID、设备 ID 等。
如果找到匹配的驱动程序,内核会将设备和驱动进行关联,并调用驱动程序中的初始化函数来完成设备的初始化和配置。此后,当应用程序需要对设备进行操作时,通过系统调用进入内核空间,内核会调用相应驱动程序中的操作函数来实现对设备的读、写、控制等操作。
一、重要结构体
platfrom_driver结构体:
platform_driver 结构体是在 Linux 内核中定义的一个结构体,用于在驱动程序中注册和管理平台设备驱动。它包含以下字段:
struct device_driver driver:指向 struct device_driver 结构体的指针,表示该平台驱动程序所属的设备驱动。
const struct platform_device_id *id_table:一个指向 struct platform_device_id 结构体数组的指针,用于匹配与该平台驱动程序相匹配的设备。
int (*probe)(struct platform_device *pdev):一个函数指针,指向设备的探测函数,用于在设备被注册到系统后执行特定的操作。
int (*remove)(struct platform_device *pdev):一个函数指针,指向设备的移除函数,用于在设备被注销时执行特定的操作。
void (*shutdown)(struct platform_device *pdev):一个函数指针,指向设备的关机函数,用于在系统关机时执行特定的操作。
struct device_driver driver:用于表示该平台驱动程序的设备驱动。
struct list_head driver_entry:用于将平台驱动程序添加到全局驱动程序链表中的链表节点。
二、设备树语法
二、编程思路
在原来第一个模板的基础上
1.platform_driver结构体
还需要构造platform_driver结构体和of_device_id结构体
static struct platform_driver gpio_platfrom_drive = {
.driver = {
.name = "100ask_gpio_plat_drv",
.of_match_table = gpio_dt_ids,
},
.probe = gpio_drv_probe,
.remove = gpio_drv_remove,
};
struct of_device_id { char name[32]; char type[32]; char compatible[128]; const void *data; };
在Linux内核中,of_device_id结构体用于存储设备树绑定信息,用于设备与驱动程序之间的匹配。它包含以下字段:
name:设备树绑定的名称。
type:设备树绑定的类型。
compatible:设备树绑定的兼容字符串,用于指定设备与驱动程序之间的兼容关系。
data:指向附加数据的指针,可以在设备树绑定中使用。
gpio_drv_probe和gpio_drv_remove分别代替了原来init入口函数和exit出口函数的作用,而在原来的这两个函数中是注册和反注册platform_driver结构体。
使用的函数为:
platform_driver_register 是一个函数,用于注册平台设备驱动程序。它将驱动程序与平台设备进行绑定,使得驱动程序能够管理其对应的平台设备。
函数原型为:
int platform_driver_register(struct platform_driver *drv);
参数说明:
drv:指向平台驱动程序的指针,其类型为 struct platform_driver。平台驱动程序是一个结构体,包含了驱动程序的各种回调函数和其他属性。定义了平台设备和驱动程序之间的关联关系。
返回值:
成功注册平台驱动程序时,返回 0;注册失败时,返回负数错误代码。
使用 platform_driver_register 函数可以将平台驱动程序注册到内核中,以便在加载平台设备时自动调用相应的驱动程序。注册平台驱动程序后,内核会通过设备树 (Device Tree) 或 ACPI (Advanced Configuration and Power Interface) 系统启动方法来查找并匹配平台设备,并自动加载和绑定对应的驱动程序。
注册平台驱动程序时,需要确保驱动程序的结构体中的回调函数和其他属性正确设置,以便驱动程序能够正确地管理和操作平台设备。
static int gpio_drv_probe(struct platform_device *pdev)
gpio_drv_probe中使用到的结构体:
struct device_node *np = pdev->dev.of_node;
platform_device 结构体包含的重要成员包括:
name:设备的名称,用于唯一标识设备。
id:设备的 ID,用于区分同一类型的不同设备。
num_resources:设备所需的资源数量。
resource:用于描述设备的资源信息,如内存范围、中断、I/O 端口等。
dev:指向设备所属的 struct device 结构体的指针,用于与设备的核心操作进行交互。
pdata:设备特定的数据,用于向设备驱动程序传递设备特定的信息。
driver_data:指向设备驱动程序特定数据的指针,用于与设备驱动程序交互。
struct device_node {
const char *name; // 设备节点名称
const char *type; // 设备节点类型
const char *fullname; // 设备节点完整路径名称
struct device_node *parent; // 父设备节点
struct device_node *child; // 子设备节点
struct device_node *sibling; // 兄弟设备节点
void *data; // 设备节点特定数据
// 其他成员...
};
struct resource {
resource_size_t start; // 资源起始地址
resource_size_t end; // 资源结束地址(包含)
resource_size_t flags; // 资源标志
const char *name; // 资源名称
struct resource *parent; // 父资源
struct resource *sibling; // 兄弟资源
struct resource *child; // 子资源
};
gpio_drv_probe中使用到的函数:
- of_gpio_count
of_gpio_count 是一个函数,用于获取设备的GPIO数量。它是在Device Tree中使用的,用于解析设备节点中定义的GPIO信息。
函数原型为:
int of_gpio_count(struct device_node *np);
参数说明:
struct device_node *np:指向设备节点的指针。
返回值:
返回设备节点中定义的GPIO数量。如果没有定义GPIO,则返回0。
使用 of_gpio_count 函数可以确定设备节点中定义的GPIO数量,从而在设备驱动程序中进行相应的GPIO初始化和管理操作。
- kmalloc
kmalloc 是一个内核函数,用于动态分配内核空间的连续内存块。它可以用于分配任意大小的内存区域。
函数原型为:
void *kmalloc(size_t size, gfp_t flags);
参数说明:
size:要分配的内存块的大小,以字节为单位。
flags:分配内存的标志,用于指定分配内存的行为。
返回值:
返回指向分配的内存块的指针,如果分配失败,则返回NULL。
- platform_get_resource
platform_get_resource 是一个函数,用于获取给定平台设备的资源信息。它可以用于获取平台设备上的寄存器地址、中断号、IO地址等资源的信息。
函数原型为:
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);
参数说明:
dev:指向平台设备的指针。
type:要获取的资源类型,可以是 IORESOURCE_MEM、IORESOURCE_IO 或 IORESOURCE_IRQ。这些类型定义在 include/linux/ 和 include/linux/ 头文件中。
num:要获取的资源号,资源号从 0 开始计数。
返回值:
返回指向资源的 struct resource 结构体指针,如果获取失败,则返回 NULL。
使用 platform_get_resource 函数可以获取平台设备上的资源信息。例如,可以使用它来获取平台设备上的寄存器地址,以便进行寄存器访问。需要注意的是,资源的类型和编号需要事先了解,以便正确地调用 platform_get_resource 函数。
三、使用设备树
1.步进电机
-
修改设备树:
arch/arm/boot/dts/100ask_imx6ull
添加节点:
motor {
compatible = "100ask,gpiodemo";
gpios = <&gpio4 19 GPIO_ACTIVE_HIGH>,
<&gpio4 20 GPIO_ACTIVE_HIGH>,
<&gpio4 21 GPIO_ACTIVE_HIGH>,
<&gpio4 22 GPIO_ACTIVE_HIGH>;
};
-
编译:make dtbs
-
复制到单板上
ubuntu:
cp arch/arm/boot/dts/100ask_imx6ull ~/nfs_rootfs/
开发板:
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt cp /mnt/100ask_imx6ull /boot reboot
-
测试
insmod gpio_drv.ko ./button_test /dev/motor ...
2.红外遥控
-
修改设备树:
arch/arm/boot/dts/100ask_imx6ull
添加节点:
irda {
compatible = "100ask,gpiodemo";
gpios = <&gpio4 19 GPIO_ACTIVE_HIGH>;
};
-
编译:make dtbs
-
复制到单板上
PC: cp arch/arm/boot/dts/100ask_imx6ull ~/nfs_rootfs/ 开发板: mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt cp /mnt/100ask_imx6ull /boot reboot
-
测试
insmod gpio_drv.ko ./button_test /dev/irda
四、代码示例
static int irda_drv_probe(struct platform_device *pdev)
{
int err = 0;
int i;
struct device_node *np = pdev->dev.of_node;
struct resource *res;
if (np)
{
count = of_gpio_count(np);
if (!count)
{
return -EINVAL;
}
gpios = kmalloc(count * sizeof(struct gpio_desc), GFP_KERNEL);
for (i = 0; i < count; i++)
{
gpios[i].gpio = of_get_gpio(np, i);
sprintf(gpios[i].name, "%s_pin_%d", np->name, i);
}
}
else
{
count = 0;
while (1)
{
res = platform_get_resource(pdev, IORESOURCE_IRQ, count);
if (res)
{
count++;
}
else
{
break;
}
}
if(!count)
{
return -EINVAL;
}
gpios = kmalloc(count * sizeof(struct gpio_desc), GFP_KERNEL);
for (i = 0; i < count; i++)
{
res = platform_get_resource(pdev, IORESOURCE_IRQ, count);
gpios[i].gpio = res->start;
sprintf(gpios[i].name, "%s_pin_%d", pdev->name, i);
}
}
for (i = 0; i < count; i++)
{
gpios[i].irq = gpio_to_irq(gpios[i].gpio);
setup_timer(&gpios[i].irda_timer, irda_timer_expire, (unsigned long)&gpios[i]);
err = request_irq(gpios[i].irq, irda_irq_handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, gpios[i].name, &gpios[i]);
}
major = register_chrdev(0, "irda_drv", &irda_drv);
irda_class = class_create(THIS_MODULE, "irda_class");
device_create(irda_class, NULL, MKDEV(major, 0), NULL, "irda_drv");
return err;
}
static int irda_drv_remove(struct platform_device *pdev)
{
int i;
device_destroy(irda_class, MKDEV(major, 0));
class_destroy(irda_class);
unregister_chrdev(major, "irda_drv");
for (i= 0; i < count; i++)
{
free_irq(gpios[i].irq, &gpios[i]);
del_timer(&gpios[i].irda_timer);
}
return 0;
}
static struct of_device_id irda_dt_ids[] = {
{.compatible = "irda,demo",},
};
static struct platform_driver irda_platform_driver = {
.driver = {
.name = "irda_plat_drive",
.of_match_table = irda_dt_ids,
},
.probe = irda_drv_probe,
.remove = irda_drv_remove,
};