Linux 设备驱动的软件架构思想(一)(摘录)

Linux 设备驱动的软件架构思想(一)(摘录)

在前面几章我们看到了 globalmem 、 globalfifo 这样类型的简单的字符设备驱动,但是,纵观 Linux 内核的源代码,读者几乎找不到有如此简单形式的驱动。
在实际的 Linux 驱动中, Linux 内核尽量做得更多,以便于底层的驱动可以做得更少。而且,也特别强调了驱动的跨平台特性。因此, Linux 内核势必会为不同的驱动子系统设计不同的框架。

举一个简单的例子,假设我们要通过 SPI 总线访问某外设,假设 CPU 的名字叫 XXX1 , SPI 外设叫 YYY1 。在访问 YYY1 外设的时候,要通过操作 CPU XXX1 上的 SPI 控制器的寄存器才能达到访问 SPI 外设 YYY1 的目的,这显然是不被接受的,因为这意味着外设 YYY1 用在不同的 CPU XXX1 和 XXX2 上的时候需要不同的驱动。同时,如果CPU XXX1 除了支持 YYY1 以外,还要支持外设 YYY2 、 YYY3 、 YYY4 等,这个 XXX 的代码就要重复出现在 YYY1 、YYY2 、 YYY3 、 YYY4 的驱动里面

这种软件架构是一种典型的网状耦合,网状耦合一般不太适合人类的思维逻辑,会把我们的思维搞乱。对于网状耦合的M ∶ N ,我们一般要提炼出一个中间 “1” ,让 M 与 “1” 耦合, N 也与这个 “1” 耦合
在这里插入图片描述那么,我们可以用如图 12.4 所示的思想对主机控制器驱动和外设驱动进行分离。这样的结果是,外设 YYY1 、 YYY2 、YYY3 、 YYY4 的驱动与主机控制器 XXX1 、 XXX2 、 XXX3 、 XXX4 的驱动不相关,主机控制器驱动不关心外设,而外设驱动也不关心主机,外设只是访问核心层的通用 API 进行数据传输,主机和外设之间可以进行任意组合。
在这里插入图片描述
如果我们不进行如图 12.4 所示的主机和外设分离,外设 YYY1 、 YYY2 、 YYY3 和主机 XXX1 、 XXX2 、 XXX3 进行组合的时候,需要 9 个不同的驱动。设想一共有 m 个主机控制器, n 个外设,分离的结果是需要 m+n 个驱动,不分离则需要 m*n个驱动。因为, m 个主机控制器, n 个外设的驱动都可以被充分地复用了。

platform 设备驱动
在 Linux 2.6 以后的设备驱动模型中,需关心总线、设备和驱动这 3 个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。

一个现实的 Linux 设备和驱动通常都需要挂接在一种总线上,对于本身依附于 PCI 、 USB 、 I 2 C 、 SPI 等的设备而言,这自然不是问题,但是在嵌入式系统里面,在 SoC 系统中集成的独立外设控制器、挂接在 SoC 内存空间的外设等却不依附于此类总线。基于这一背景, Linux 发明了一种虚拟的总线,称为 platform 总线,相应的设备称为platform_device ,而驱动成为 platform_driver 。
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;
};

platform_driver 结构体

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

直接填充 platform_driver 的 suspend ()、 resume ()做电源管理回调的方法目前已经过时,较好的做法是实现platform_driver 的 device_driver 中的 dev_pm_ops 结构体成员(后续的 Linux 电源管理章节会对此进行更细致的介绍)
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 */
	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 地位对等的 i2c_driver 、 spi_driver 、 usb_driver 、 pci_driver 中都包含了 device_driver 结构体实例成员。它其实描述了各种 xxx_driver ( xxx 是总线名)在驱动意义上的一些共性。
系统为 platform 总线定义了一个 bus_type 的实例 platform_bus_type ,其定义位于 drivers/base/platform.c 下
platform 总线的 bus_type 实例 platform_bus_type

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

这里要重点关注其 match ()成员函数,正是此成员函数确定了 platform_device 和 platform_driver 之间是如何进行匹配
platform_bus_type 的 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 */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

从代码可以看出,匹配 platform_device 和 platform_driver 有 4 种可能性,一是基于设备树风格的匹配;二是基于 ACPI 风格的匹配;三是匹配 ID 表(即 platform_device 设备名是否出现在platform_driver 的 ID 表内);第四种是匹配 platform_device 设备名和驱动的名字。

对于 Linux 2.6ARM 平台而言,对 platform_device 的定义通常在 BSP 的板文件中实现,在板文件中,将platform_device 归纳为一个数组,最终通过 platform_add_devices ()函数统一注册。 platform_add_devices ()函数可以将平台设备添加到系统中,这个函数的原型为:

int platform_add_devices(struct platform_device **devs, int num);

该函数的第一个参数为平台设备数组的指针,第二个参数为平台设备的数量,它内部调用了platform_device_register ()函数以注册单个的平台设备。Linux 3.x 之后, ARM Linux 不太喜欢人们以编码的形式去填写 platform_device 和注册,而倾向于根据设备树中的内容自动展开 platform_device 。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值