文章目录
有些物理总线已为人熟知:USB、I2S、I2C、UART、SPI、PIC、SATA等。这种总线是名为控制器的硬件设备。由于它们是SoC的一部分,因此无法删除,不可发现,也称为平台设备。
从SoC的角度来看,这些设备(总线)内部通过专用总线连接,而且大部分时间是专用的,专门针对制造商。从内核的角度来看,这些是根设备,未连接到任何设备,也就是未连接到任何总线上。这就是伪平台总线的用途,伪平台总线也称为平台总线,是内核虚拟总线,该总线实际不存在,用于连接不在内核所知物理总线上的设备和驱动程序,也就是根设备和其相关的驱动程序。在下面的讨论中,平台设备是指依靠伪平台总线的设备。
处理平台设备实际上需要两个步骤:
- 注册管理设备的平台驱动程序(具有唯一的名称)。
- 注册平台设备(与驱动程序具有相同的名称)及其资源,以便内核获取设备位置。
1 平台驱动程序
在进一步介绍之前,请注意以下警告,并非所有平台设备都由平台驱动程序处理。平台驱动程序专用于不基于传统总线的设备。I2C设备或SPI设备是平台设备,但分别依赖I2C或SPI总线,而不是平台总线。对于平台驱动程序一切都需手工完成。平台驱动程序必须实现probe函数,在插入模块或设备声明时,内核调用它。在开发平台驱动程序时,必须填写主结构struct platform_driver
,由它来代表平台驱动程序,并用专门函数把驱动程序注册到平台总线上。
struct platform_driver
定义在include/linux/platform_device.h中
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; /* 设备的ID表 */
bool prevent_deferred_probe;
};
在内核中注册平台驱动程序很简单,只需在init函数中调用platform_driver_register()或platform_driver_probe()。这两个函数之间的区别如下:
- platform_driver_register():注册驱动程序并将其放入由内核维护的驱动程序列表中,以便每当发现新的匹配时就可以按需调用其probe函数。为防止驱动程序在该列表中插入和注册,请使用下一个函数。
- platform_driver_probe():调用该函数后,内核立即运行匹配循环,检查是否有平台设备名称匹配,如果匹配则调用驱动程序的probe,这意味着设备存在,否则,驱动程序将被忽略。此方法可防止延迟探测,因为它不会在系统上注册驱动程序。
2 平台设备
实际上,应该叫伪平台设备,完成驱动程序后,必须向内核提供需要该驱动程序的设备。平台设备在内核中表示为struct platform_device
的实例,定义在文件include/linux/platform_device.h中。如下所示:
struct platform_device {
const char *name; /* 用于和platform_driver进行匹配的名字 */
int id; /* 设备ID */
bool id_auto;
struct device dev;
u32 num_resources; /* resource的个数*/
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 platform_device
的name字段要和 struct platform_driver.driver.name
相同,这样才能匹配上。
2.1 资源和平台数据
在可热插拔的另一端,内核不知道系统上存在那些设备、它们能够做什么,或者需要什么才能运行。因为没有自主协商的过程,所以提供给内核的任何信息都会收到欢迎。有两种方法可以把相关设备所需的资源和数据通知内核。
1 设备配置—废弃的旧方法
这种方法用于不支持设备树的内核版本,使用这种方法,驱动程序可以保持其通用性,使设备注册到与开发板相关的源文件中。
资源
资源代表设备在硬件方面的所有特征元素,以及设备所需的所有元素,以便设置使其正常运行,平台设备struct platform_device
由struct source *resource
描述其资源。内核中只有6种类型的资源,全部列在include/linux/ioport.h中,并用标志来描述资源类型:
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200 /*内存区域 */
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400 /* IRQ线 */
#define IORESOURCE_DMA 0x00000800 /* DMA通道 */
#define IORESOURCE_BUS 0x00001000 /* 总线 */
资源在内核中表示为struct resource
的实例:
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
};
- start/end:这代表资源的开始结束位置。对于I/O或内存区域,它表示开始结束位置。对于中断线、总线或DMA通道,开始结束必须具有相同的值。
- flags:这是表示资源类型的掩码,例如IORESOURCE_BUS
- name:标识或描述资源
一旦提供了资源,就需要在驱动中获取使用它们。probe功能是获取它们的好地方。嵌入在struct platform_device
中的struct resource
可以通过platform_get_resource函数进行检索:
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type,unsigned int num);
第一个参数是平台设备自身的实例。第二个参数说明需要什么样的资源。对于内存,它应该是IORESOURCE_MEM。num是一个索引,表示需要那个资源类型,0表示第一个,以此类推。
平台数据
所有类型不属于上一部分所列举资源的其他数据都属于这里(如GPIO)。无论它们是什么类型,struct platform_device
都包含struct device
字段,该字段又包含struct platform_data
字段。通常,应该将数据嵌入结构中,将其传输到platform_data字段中。
声明平台设备
用设备的资源和数据注册设备,在这个废弃的方法中,它们声明在单独的模块中或者arch/<arch>/mach-xxx/yyy.c的开发板init文件中,在函数platform_device_register()
内实现声明:
static struct platform_device my_device = {
//进行初始化
};
platform_device_register(&my_device);
2 设备配置—推荐的新方法
在第一种方法中,任何修改都需要重构整个内核。如果内核必须包含所有应用程序/开发板特殊配置,则其大小将会大大增加。为了简单起见,从内核源中分离设备声明,并引入一个新的概念:设备树(DTS)。设备树(DTS)的主要目的是从内核中删除特定且从未测试过的代码。使用设备树,平台数据和资源时同质的。设备树是硬件描述文件,其格式类似于树形结构,每个设备用一个结点表示,任何数据、资源或配置数据都表示为结点的属性。这样,在做一些修改只需重新编译设备树。
3 设备、驱动程序和总线匹配
在匹配发生之前,Linux会调用platform_match(struct device *dev,struct device_driver *drv)
。平台设备(struct platform_device
的实例)通过字符(name字段)与驱动程序匹配(struct platform_driver.driver.name
)。根据Linux设备模型,总线元素是最重要的部分。每个总线都维护一个注册的驱动程序和设备列表。总线驱动程序负责设备和驱动程序的匹配。每当连接新设备或者向总线添加新的驱动程序时,总线都会启动匹配循环。
现在,假设使用I2C核心提供的函数注册新的I2C设备。内核提供如下方法触发I2C总线匹配循环:调用由I2C总线驱动程序注册的I2C核心匹配函数,以检查是否有已注册的驱动程序与该设备匹配。如果没有匹配,则什么都不做;如果发现匹配,则内核通知设备管理器(udev/mdev),由它加载与设备匹配的驱动程序。一旦设备驱动程序加载完成,其probe()函数将立即执行。不仅I2C这样运行,而且每个总线自己的匹配机制都大致于此相同。总线匹配循环在每个设备或驱动程序注册时被触发。
平台设备和平台驱动程序如何匹配
MODULE_DEVICE_TABLE(type,name)
宏让驱动程序公开其ID表,该表描述它可以支持那些设备。同时,如果驱动程序可以编译成模块,则平台驱动实例的driver.name字段要与模块名称匹配。如果不匹配,模块则不会自动加载,除非已经使用MODULE_ALIAS宏为模块添加了另一个名称。编译时,从所有驱动程序中提取该消息,以构建设备表。当设备和驱动程序匹配时,内核遍历设备表。如果找到的条目与添加的设备兼容,并与设备/供应商ID或名称匹配,则加载提供该匹配的模块,运行模块的init函数,调用probe函数。
MODULE_DEVICE_TABLE宏在linux/module.h中定义:
/* Creates an alias so file2alias.c can find device table. */
#define MODULE_DEVICE_TABLE(type, name) \
extern const typeof(name) __mod_##type##__##name##_device_table \
__attribute__ ((unused, alias(__stringify(name))))
- type:这可以是i2c、spi、acpi、of、platform、usb、pci。也可以是在include/linux/mod_devicetable.h中找到的其他任何总线
- name:这是XXX_device_id数组上的指针,用于设备匹配。对于I2C设备。结构是i2c_device_id。对于设备树的Open Firmware(开放固件,OF)匹配机制,必须使用of_device_id。
4 Platfrom架构驱动程序
代码原文章:https://www.toutiao.com/article/6874918991081505283/?log_from=d62ff69d6ea06_1651283905265
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/slab.h>
#define BUFFER_MAX (10)
#define OK (0)
#define ERROR (-1)
struct cdev *gDev;
struct file_operations *gFile;
dev_t devNum;
unsigned int subDevNum = 1;
int reg_major = 232;
int reg_minor = 0;
char *buffer;
#define LEDBASE 0x56000010
#define LEDLEN 0x0c
int hello_open(struct inode *p, struct file *f)
{
printk(KERN_INFO "hello_open\r\n");
return 0;
}
ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)
{
printk(KERN_INFO "hello_write\r\n");
return 0;
}
ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l)
{
printk(KERN_INFO "hello_read\r\n");
return 0;
}
static int hellodev_probe(struct platform_device *pdev)
{
printk(KERN_INFO "hellodev_probe\n");
devNum = MKDEV(reg_major, reg_minor);
if(OK == register_chrdev_region(devNum, subDevNum, "helloworld")){
printk(KERN_INFO "register_chrdev_region ok \n");
}else {
printk(KERN_INFO "register_chrdev_region error n");
return ERROR;
}
printk(KERN_INFO " hello driver init \n");
gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);
gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
gFile->open = hello_open;
gFile->read = hello_read;
gFile->write = hello_write;
gFile->owner = THIS_MODULE;
cdev_init(gDev, gFile);
cdev_add(gDev, devNum, 1);
return 0;
}
static int hellodev_remove(struct platform_device *pdev)
{
printk(KERN_INFO "hellodev_remove \n");
cdev_del(gDev);
kfree(gFile);
kfree(gDev);
unregister_chrdev_region(devNum, subDevNum);
return;
}
static void hello_plat_release(struct device *dev)
{
return;
}
static struct resource hello_dev_resource[] = {
[0] = {
.start = LEDBASE,
.end = LEDBASE + LEDLEN - 1,
.flags = IORESOURCE_MEM,
}
};
struct platform_device hello_device = {
.name = "hello-device",
.id = -1,
.num_resources = ARRAY_SIZE(hello_dev_resource),
.resource = hello_dev_resource,
.dev = {
.release = hello_plat_release,
}
};
static struct platform_driver hellodev_driver = {
.probe = hellodev_probe,
.remove = hellodev_remove,
.driver = {
.owner = THIS_MODULE,
.name = "hello-device",
},
};
int charDrvInit(void)
{
platform_device_register(&hello_device);
return platform_driver_register(&hellodev_driver);
}
void __exit charDrvExit(void)
{
platform_device_unregister(&hello_device);
platform_driver_unregister(&hellodev_driver);
return;
}
module_init(charDrvInit);
module_exit(charDrvExit);
MODULE_LICENSE("GPL");
Makefile:
ifneq ($(KERNELRELEASE),)
obj-m := helloDev.o
else
PWD := $(shell pwd)
#KDIR:=/home/jinxin/linux-4.9.229
#KDIR:= /lib/modules/4.4.0-31-generic/build
KDIR := /lib/modules/`uname -r`/build
all:
make -C $(KDIR) M=$(PWD)
clean:
rm -rf *.o *.ko *.mod.c *.symvers *.c~ *~
endif