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 中,函数内容如下所示:
platform_match

第一种匹配方式, 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 结构体中的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 设备信息框架如下所示:

/* 寄存器地址定义*/
#define PERIPH1_REGISTER_BASE (0X20000000) /* 外设 1 寄存器首地址 */ 
#define PERIPH2_REGISTER_BASE (0X020E0068) /* 外设 2 寄存器首地址 */
#define REGISTER_LENGTH 4 5 

/* 资源 */
static struct resource xxx_resources[] = { 
    [0] = { 
        .start = PERIPH1_REGISTER_BASE,
        .end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    }, 
    [1] = {
        .start = PERIPH2_REGISTER_BASE,
        .end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    },
};

/* platform 设备结构体 */
static struct platform_device xxxdevice = {
    .name = "xxx-gpio",
    .id = -1,
    .num_resources = ARRAY_SIZE(xxx_resources),
    .resource = xxx_resources,
};
 
/* 设备模块加载 */
static int __init xxxdevice_init(void)
{
    return platform_device_register(&xxxdevice);
}

/* 设备模块注销 */
static void __exit xxx_resourcesdevice_exit(void)
{
    platform_device_unregister(&xxxdevice);
}

module_init(xxxdevice_init);
module_exit(xxxdevice_exit);

版权声明:本文为CSDN博主「monkea123」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/monkea123/article/details/109082515


Share Tweet Send
0 Comments
Loading...
You've successfully subscribed to 隔窗听雨
Great! Next, complete checkout for full access to 隔窗听雨
Welcome back! You've successfully signed in
Success! Your account is fully activated, you now have access to all content.
豫ICP备19045256号-1