编写步骤
- 确定主设备号,也可以设为0让内核自动分配
- 定义 file_operations 结构体
- 实现 file_operations 结构体中的 open、read、write等函数
- register_chrdev 注册字符设备驱动
- 完成入口函数、出口函数(安装驱动时调用入口函数、卸载驱动时调用出口函数)
- 自动创建设备节点 class_create、device_create
注意:
-
驱动程序操作硬件:
ioremap 映射寄存器的物理地址得到虚拟地址
-
驱动程序与应用程序之间传递信息:
copy_to_user、copy_from_user
class_create、device_create
内核中定义有 struct class 结构体,顾名思义,一个 struct class 结构体对应一个类,内核同时提供了 class_create 函数,用它创建 class 类放在 /sys/class 目录下,一旦创建好这个类,再调用 device_create 就可以在 /dev 目录下创建设备节点。
eg:
hello_class = class_create(THIS_MODULE, "hello_class");
device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");
上述代码的 class_create 会在 /sys/class 下创建 hello_class 目录,这个目录下有一个链接文件 hello,在它所指向的文件中包含着更多的设备信息(比如:dev文件中有主设备号、次设备号信息)
/sys/devices:系统中所有设备的存放目录
/sys/class:系统中的所有设备按照其功能放置的目录结构,同时该目录下的文件也链接到了 /sys/devices 目录 (eg:/sys/class/leds 存放所有的led设备、/sys/class/input 存放所有的输入类设备)
驱动设计思想
面向对象
字符设备驱动程序抽象出一个 file_operations 结构体;
分层
最简单的驱动程序:以操作一盏 led 灯为例(./ledtest /dev/myled on ./ledtest /dev/myled off)
在驱动程序中只需要:(1)在open函数中初始化 led
(2)在write函数中根据传进来的值控制 led
这两步都是直接对相应的寄存器进行操作
缺点:将驱动程序和硬件资源绑定死,更换硬件资源时需要重新编写驱动程序
分层思想:上下分层,将 led 驱动程序分为两层,将驱动程序和硬件相分离
leddrv.c 中并没有具体的对于寄存器的操作,只负责注册 file_operations 结构体,它的 open/write 成员会调用 board_X.c 中提供的硬件 led_opr 中的对应函数
board_X.c 中实现对硬件的具体操作,构造各自的 led_operations 硬件操作结构体
缺点:实现了硬件操作与驱动程序的相分离,但是在硬件操作中,对于某一个 led,它的寄存器操作是写死的,那么如果这个 led 的引脚发生改变,board_X.c 文件就需要重新编写编译
分离
对于分层的改进,不再将 led 与某个引脚相固定,对于分层中的硬件操作层,再分离为引脚资源(led使用那个引脚)、引脚操作(对于引脚的初始化和控制)
board_A.c 文件中指定引脚资源,实现 led_resource 结构体
static struct led_resource board_A_led = {
.pin = GROUP_PIN(3,1),
};
chipY_gpio.c 文件中完成硬件操作,定义 led_operations 结构体,并实现 init、ctl 函数
通过分离的思想进而演变出了总线设备驱动模型和设备树
驱动的三种编写方法
传统写法
使用哪个引脚,怎么操作引脚,都写死在代码中。最简单,不考虑扩展性,可以快速实现功能。修改引脚时,需要重新编译。
总线设备驱动模型
引入平台设备(platform_device)、平台驱动(platform_driver),将 “ 驱动 ” 与 “ 硬件 ” 相分离
更换引脚时,led_drv.c 不需要更改,但是 led_dev.c 需要更改
缺点:资源文件 led_dev.c 是要存放在Linux内核中的,当资源过多时,就导致了更多的资源文件,及其占用内核空间
设备树
通过配置文件来定义资源
设备树中的节点通过一定条件,内核可以将它转换为 platform_device 结构体
设备树文件DTS —> 设备树文件DTB —> platform_device
修改引脚时,只需要修改 dts 文件,并编译得到 dtb 文件,把它传给内核,无需重新编译内核、驱动
总线设备驱动模型
概念
**总线(bus) ** :负责管理挂载对应总线的设备以及驱动;
**设备(device) ** :挂载在某个总线的物理设备;
驱动(driver) :与特定设备相关的软件,负责初始化该设备以及提供一些操作该设备的操作方式;
工作原理
总线管理着两个链表:设备链表、驱动链表
当我们向内核注册一个驱动时,它会加入到驱动链表
当我们往内核加入一个设备时,它会加入到设备链表
若安装一个 platform_device 结构体,即将这个结构体加入总线设备链表中,它会与总线驱动链表从前到后进行匹配(总线结构体 bus_type 中的 match 函数),若匹配成功,则调用 platform_driver 中的 probe 函数
重要结构体
平台总线结构体
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
平台设备结构体
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
资源结构体
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
};
平台驱动结构体
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
}
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
enum probe_type probe_type;
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
匹配规则
设备与驱动的匹配是 platform_driver 结构体中的 match 函数做的
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
//第1种匹配方式,OF类型匹配,设备树采用的匹配方式
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
//第2种匹配方式,ACPI匹配
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
//第3种匹配方式,id_table匹配
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
//第4种匹配方式,直接比较驱动和设备的name字段
return (strcmp(pdev->name, drv->name) == 0);
}
第二种
platform_device.driver_override 和 platform_driver.driver.name可以设置 platform_device 的 driver_override,强制选择某个 platform_driver
struct platform_device {
...
char *driver_override;
...
}
struct platform_driver {
...
struct device_driver driver;
...
}
struct device_driver {
...
const char *name;
...
}
第三种
platform_device. name 和 platform_driver.id_table[i].name
Platform_driver.id_table 是“platform_device_id”指针,表示该 drv 支持若干个 device,它里面列出了各个 device 的 {.name, .driver_data} ,其中的 “ name ” 表示该drv 支持的设备的名字,driver_data 是些提供给该 device 的私有数据。
struct platform_device {
...
const char *name;
...
}
struct platform_driver {
...
const struct platform_device_id *id_table;
...
}
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
第四种
platform_device.name 和 platform_driver.driver.name
platform_driver.id_table 可能为空,这时可以根据 platform_driver.driver.name 来寻找同名的 platform_device。
struct platform_driver {
...
struct device_driver driver;
...
}
struct device_driver {
const char *name;
}
struct platform_device {
const char *name;
}
代码示例
static int major = 0;
struct led_operations *p_led_opr;
void led_class_create_device(int minor)
{
// /dev/100ask_led0,1,...
device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor);
}
void led_class_destroy_device(int minor)
{
device_destroy(led_class, MKDEV(major, minor));
}
void register_led_operations(struct led_operations *opr)
{
p_led_opr = opr;
}
static struct file_operations led_drv = {
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
static struct led_operations board_demo_led_opr = {
.init = board_demo_led_init,
.ctl = board_demo_led_ctl,
};
// 平台驱动有入口函数和出口函数
// chip_demo_gpio_probe 中获取硬件资源,并通过 led_class_create_device 自动创建设备节点
static struct platform_driver chip_demo_gpio_driver = {
.probe = chip_demo_gpio_probe,
.remove = chip_demo_gpio_remove,
.driver = {
.name = "100ask_led",
},
};
static struct resource resources[] = {
{
.start = GROUP_PIN(3,1),
.flags = IORESOURCE_IRQ,
.name = "100ask_led_pin",
},
{
.start = GROUP_PIN(5,8),
.flags = IORESOURCE_IRQ,
.name = "100ask_led_pin",
},
};
// 平台硬件也有入口函数、出口函数
static struct platform_device board_A_led_dev = {
.name = "100ask_led",
.num_resources = ARRAY_SIZE(resources),
.resource = resources,
.dev = {
.release = led_dev_release,
},
};