platform平台驱动模型 总线-驱动-设备

platform 总线

(1)相对于usb、pci、i2c等物理总线来说,Linux 提出了 platform 这个虚拟总线,相应的就有 platform_driver 和 platform_device。platform总线是虚拟的、抽象出来的。
(2)CPU与外部通信的2种方式:地址总线式连接和专用接口式连接。平台总线对应地址总线式连接设备,也就是SoC内部集成的各种内部外设。

Linux系统内核使用bus_type结构体表示总线,此结构体定义在文件include/linux/device.h,bus_type 结构体内容如下:

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);
	/* match 函数有两个参数:dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。*/
	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;
};

platform 总线是 bus_type 的一个具体实例,定义在文件drivers/base/platform.c,platform 总线定义如下:

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_groups	= platform_dev_groups,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};

platform_match 函数定义在文件 drivers/base/platform.c 中,函数内容如下所示:

第一种匹配方式, OF 类型的匹配,也就是设备树采用的匹配方式,每个设备节点的 compatible 属性会和 of_match_table表中的所有成员比较,查看是否有相同的条目。
第二种匹配方式,ACPI 匹配方式。
第三种匹配方式,id_table 匹配,每个platform_driver 结构体有一个 id_table 成员变量。
第四种匹配方式,如果第三种匹配方式的 id_table不存在的话就直接比较驱动和 设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。

platform 驱动

struct platform_driver {
	int (*probe)(struct platform_device *); 
	//一般驱动的提供者会编写probe 函数,当驱动与设备匹配成功以后 probe 函数就会执行,非常重要的函数!!
	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_table 表,id_table 是个表(也就是数组),每个元素的类型为 platform_device_id
	bool prevent_deferred_probe;
};
//platform_driver 结构体
struct platform_device_id { 
	char name[PLATFORM_NAME_SIZE];
	kernel_ulong_t driver_data; 
};
device_driver 结构体
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 */

	const struct of_device_id	*of_match_table;//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;
};

of_device_id 结构体

of_device_id 结构体中的compatible 属性值和 of_match_table 中每个项目的 compatible成员变量进行比较

在编写 platform 驱动的时候,首先定义一个 platform_driver 结构体变量,然后实现结构体中的各个成员变量,重点是实现匹配方法以及 probe 函数。当驱动和设备匹配成功以后 probe函数就会执行,具体的驱动程序在 probe 函数里面编写,比如字符设备驱动等等。

初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用platform_driver_register 函数向 Linux 内核注册一个 platform 驱动

int platform_driver_register (struct platform_driver *driver)

函数参数和返回值含义如下:
driver:要注册的 platform 驱动。
返回值:负数,失败;0,成功。

还需要在驱动卸载函数中通过 platform_driver_unregister 函数卸载 platform 驱动,

void platform_driver_unregister(struct platform_driver *drv)

函数参数和返回值含义如下:
drv:要卸载的 platform 驱动。
返回值:无。

platform 驱动框架如下所示:

/*xxx_probe 函数,当驱动和设备匹配成功以后此函数就会执行,以前在驱动入口 init 
函数里面编写的字符设备驱动程序就全部放到此 probe 函数里面。
比如注册字符设备驱动、添加 cdev、创建类等等。*/

static int xxx_probe(struct platform_device *dev)
 { 
 ......
 cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */
 /* 函数具体内容 */
 return 0;
 }
/*xxx_remove 函数,platform_driver 结构体中的 remove 成员变量,当关闭 plat
form设备驱动的时候此函数就会执行,以前在驱动卸载 exit 函数里面要做的事情
就放到此函数中来。比如,使用 iounmap 释放内存、删除 cdev,注销设备号等等。*/
 static int xxx_remove(struct platform_device *dev)
 {
 ......
 cdev_del(&xxxdev.cdev);/* 删除 cdev */
 /* 函数具体内容 */
 return 0;
 }
/* xxx_of_match 匹配表,如果使用设备树的话将通过此匹配表进行驱动和设备的匹配。
设置了一个匹配项,此匹配项的 compatible 值为“xxx-gpio”,因此当设备树中
设备节点的 compatible 属性值为“xxx-gpio”的时候此设备就会与此驱动匹配。
 {  Sentinel  }是一个标记,of_device_id 表最后一个匹配项必须是空的。*/ 
static const struct of_device_id xxx_of_match[] = {
 { .compatible = "xxx-gpio" },
 { /* Sentinel */ }
 };
 /*定义一个 platform_driver 结构体变量 xxx_driver,表示 platform 驱动,设置 paltform_driver 中的 device_driver 成员变量的 name 和 of_match_table 这两个属性。其中
name 属性用于传统的驱动与设备匹配,也就是检查驱动和设备的 name 字段是不是相同。
of_match_table 属性就是用于设备树下的驱动与设备检查。对于一个完整的驱动程序,必须提供
有设备树和无设备树两种匹配方法。一开始两行设置 probe 和 remove 这两成员变量。*/
static struct platform_driver xxx_device_driver = {
	.probe		= xxx_probe,
	.remove		= xxx_remove,
#ifdef CONFIG_PM
	.suspend	= xxx_suspend,
	.resume		= xxx_resume,
#endif
	.driver		= {
		.name	= "xxx",
		.of_match_table = xxx_of_match,
	},
};

static int __init xxx_init(void)
{
	int ret = bus_register(&xxx_bus_type);
	if (ret == 0)
		platform_driver_register(&xxx_device_driver);
	return ret;
}

static void __exit xxx_exit(void)
{
	platform_driver_unregister(&xxx_device_driver);
	bus_unregister(&xxx_bus_type);
}

platform 设备

platform_device 这个结构体表示 platform 设备,这里我们要注意,如果内核支持设备树的话就不要再使用 platform_device 来描述设备了,因为改用设备树去描述了。当然了,你如果一定要用 platform_device 来描述设备信息的话也是可以的。

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;
};

name 表示设备名字,要和所使用的 platform 驱动的 name 字段相同,否则的话设备就无法匹配到对应的驱动。比如对应的 platform 驱动的 name 字段为“xxx-gpio”,那么此 name字段也要设置为“xxx-gpio”。

num_resources 表示资源数量,一般为下一行 resource 资源的大小。

resource 表示资源,也就是设备信息,比如外设寄存器等。Linux 内核使用 resource结构体表示资源

struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	struct resource *parent, *sibling, *child;
};

start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止地址,name 表示资源名字,flags 表示资源类型,可选的资源类型都定义在了文件include/linux/ioport.h 里面

#define IORESOURCE_BITS		0x000000ff	/* Bus-specific bits */

#define IORESOURCE_TYPE_BITS	0x00001f00	/* Resource type */
#define IORESOURCE_IO		0x00000100	/* PCI/ISA I/O ports */
#define IORESOURCE_MEM		0x00000200
#define IORESOURCE_REG		0x00000300	/* Register offsets */
#define IORESOURCE_IRQ		0x00000400
#define IORESOURCE_DMA		0x00000800
#define IORESOURCE_BUS		0x00001000

#define IORESOURCE_PREFETCH	0x00002000	/* No side effects */
#define IORESOURCE_READONLY	0x00004000
#define IORESOURCE_CACHEABLE	0x00008000
#define IORESOURCE_RANGELENGTH	0x00010000
#define IORESOURCE_SHADOWABLE	0x00020000

#define IORESOURCE_SIZEALIGN	0x00040000	/* size indicates alignment */
#define IORESOURCE_STARTALIGN	0x00080000	/* start field is alignment */

#define IORESOURCE_MEM_64	0x00100000
#define IORESOURCE_WINDOW	0x00200000	/* forwarded by bridge */
#define IORESOURCE_MUXED	0x00400000	/* Resource is software muxed */

#define IORESOURCE_EXCLUSIVE	0x08000000	/* Userland may not map this resource */
#define IORESOURCE_DISABLED	0x10000000
#define IORESOURCE_UNSET	0x20000000	/* No address assigned yet */
#define IORESOURCE_AUTO		0x40000000
#define IORESOURCE_BUSY		0x80000000	/* Driver has marked this resource busy */

/* PnP IRQ specific bits (IORESOURCE_BITS) */
#define IORESOURCE_IRQ_HIGHEDGE		(1<<0)
#define IORESOURCE_IRQ_LOWEDGE		(1<<1)
#define IORESOURCE_IRQ_HIGHLEVEL	(1<<2)
#define IORESOURCE_IRQ_LOWLEVEL		(1<<3)
#define IORESOURCE_IRQ_SHAREABLE	(1<<4)
#define IORESOURCE_IRQ_OPTIONAL 	(1<<5)

/* PnP DMA specific bits (IORESOURCE_BITS) */
#define IORESOURCE_DMA_TYPE_MASK	(3<<0)
#define IORESOURCE_DMA_8BIT		(0<<0)
#define IORESOURCE_DMA_8AND16BIT	(1<<0)
#define IORESOURCE_DMA_16BIT		(2<<0)

#define IORESOURCE_DMA_MASTER		(1<<2)
#define IORESOURCE_DMA_BYTE		(1<<3)
#define IORESOURCE_DMA_WORD		(1<<4)

#define IORESOURCE_DMA_SPEED_MASK	(3<<6)
#define IORESOURCE_DMA_COMPATIBLE	(0<<6)
#define IORESOURCE_DMA_TYPEA		(1<<6)
#define IORESOURCE_DMA_TYPEB		(2<<6)
#define IORESOURCE_DMA_TYPEF		(3<<6)

/* PnP memory I/O specific bits (IORESOURCE_BITS) */
#define IORESOURCE_MEM_WRITEABLE	(1<<0)	/* dup: IORESOURCE_READONLY */
#define IORESOURCE_MEM_CACHEABLE	(1<<1)	/* dup: IORESOURCE_CACHEABLE */
#define IORESOURCE_MEM_RANGELENGTH	(1<<2)	/* dup: IORESOURCE_RANGELENGTH */
#define IORESOURCE_MEM_TYPE_MASK	(3<<3)
#define IORESOURCE_MEM_8BIT		(0<<3)
#define IORESOURCE_MEM_16BIT		(1<<3)
#define IORESOURCE_MEM_8AND16BIT	(2<<3)
#define IORESOURCE_MEM_32BIT		(3<<3)
#define IORESOURCE_MEM_SHADOWABLE	(1<<5)	/* dup: IORESOURCE_SHADOWABLE */
#define IORESOURCE_MEM_EXPANSIONROM	(1<<6)

/* PnP I/O specific bits (IORESOURCE_BITS) */
#define IORESOURCE_IO_16BIT_ADDR	(1<<0)
#define IORESOURCE_IO_FIXED		(1<<1)

/* PCI ROM control bits (IORESOURCE_BITS) */
#define IORESOURCE_ROM_ENABLE		(1<<0)	/* ROM is enabled, same as PCI_ROM_ADDRESS_ENABLE */
#define IORESOURCE_ROM_SHADOW		(1<<1)	/* ROM is copy at C000:0 */
#define IORESOURCE_ROM_COPY		(1<<2)	/* ROM is alloc'd copy, resource field overlaid */
#define IORESOURCE_ROM_BIOS_COPY	(1<<3)	/* ROM is BIOS copy, resource field overlaid */

/* PCI control bits.  Shares IORESOURCE_BITS with above PCI ROM.  */
#define IORESOURCE_PCI_FIXED		(1<<4)	/* Do not move resource */

比如IORESOURCE_MEM表示这个资源是地址资源,IORESOURCE_IRQ表示这个资源是中断资源…。有了这几个属性,就可以完整的描述一个资源,但如果每个资源都需要单独管理而不是组成某种数据结构,显然是一种非常愚蠢的做法,所以内核的resource结构还提供了三个指针:parent,sibling,child(24),分别用来表示资源的父资源,兄弟资源,子资源,这样内核就可以使用树结构来高效的管理大量的系统资源,linux内核有两种树结构:iomem_resource,ioport_resource,进行板级开发的时候,通常将主板上的ROM资源放入iomem_resource树的一个节点,而将系统固有的I/O资源挂到ioport_resource树上。
两种写法表示了地址资源和中断资源,强烈推荐使用DEFINE_RES_XXX的版本。在这里插入图片描述

//IO地址资源,自己填充resource结构体+flags宏
struct resource res= {
	.start = 0x10000000,
	.end	 = 0x20000000-1,
	.flags = IORESOURCE_MEM
};
//IO地址资源,使用内核提供的定义宏
struct resource res = DEFINE_RES_MEM(0x20000000, 1024);
//中断资源,自己填充resource结构体+flags宏
struct resource res = {	
		.start	= 10,
		.flags	= IORESOURCE_IRQ,
};
//中断资源,使用内核提供的定义宏
struct resource res = DEFINE_RES_IRQ(11);

struct resource res[] = {
	[0] = {
		.start	= 0x10000000,
		.end	= 0x20000000-1,
		.flags	= IORESOURCE_MEM
	},
	[1] = DEFINE_RES_MEM(0x20000000, 1024),
	[2] = {
		.start	= 10,   //中断号
		.flags	= IORESOURCE_IRQ|IRQF_TRIGGER_RISING //include/linux/interrupt.h
	},
	[3] = DEFINE_RES_IRQ(11),	
};

在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中,此函数原型如下所示:

int platform_device_register(struct platform_device *pdev)

函数参数和返回值含义如下:

pdev:要注册的 platform 设备。
返回值:负数,失败;0,成功。

如果不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform设备,platform_device_unregister 函数原型如下:

void platform_device_unregister(struct platform_device *pdev)

函数参数和返回值含义如下:

pdev:要注销的 platform 设备。
返回值:无

platform 设备信息框架如下所示:

platform 设备框架
1 /* 寄存器地址定义*/
2 #define PERIPH1_REGISTER_BASE (0X20000000) /* 外设 1 寄存器首地址 */ 
3 #define PERIPH2_REGISTER_BASE (0X020E0068) /* 外设 2 寄存器首地址 */
4 #define REGISTER_LENGTH 4 5 
6 /* 资源 */
7 static struct resource xxx_resources[] = { 8 [0] = { 9 .start = PERIPH1_REGISTER_BASE,
10 .end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1),
11 .flags = IORESOURCE_MEM,
12 }, 
13 [1] = {
14 .start = PERIPH2_REGISTER_BASE,
15 .end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1),
16 .flags = IORESOURCE_MEM,
17 },
18 };
19
20 /* platform 设备结构体 */
21 static struct platform_device xxxdevice = {
22 .name = "xxx-gpio",
23 .id = -1,
24 .num_resources = ARRAY_SIZE(xxx_resources),
25 .resource = xxx_resources,
26 };
27 
28 /* 设备模块加载 */
29 static int __init xxxdevice_init(void)
30 {
31 return platform_device_register(&xxxdevice);
32 }
33
34 /* 设备模块注销 */
35 static void __exit xxx_resourcesdevice_exit(void)
36 {
37 platform_device_unregister(&xxxdevice);
38 }
39
40 module_init(xxxdevice_init);
41 module_exit(xxxdevice_exit);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值