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

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

将 globalfifo 作为 platform 设备
前面章节的 globalfifo 驱动挂接到 platform 总线上,这要完成两个工作。
1 )将 globalfifo 移植为 platform 驱动。
2 )在板文件中添加 globalfifo 这个 platform 设备。
为完成将 globalfifo 移植到 platform 驱动的工作,需要在原始的 globalfifo 字符设备驱动中套一层 platform_driver 的外壳注意进行这一工作后,并没有改变 globalfifo 是字符设备的本质,只是将其挂接到了platform 总线上。

为 globalfifo 添加 platform_driver

static int globalfifo_probe(struct platform_device *pdev)
{
 int ret;
 dev_t devno = MKDEV(globalfifo_major, 0);

if (globalfifo_major)

ret = register_chrdev_region(devno, 1, "globalfifo");
else {
ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");

globalfifo_major = MAJOR(devno);
 }
 if (ret < 0)

return ret;

globalfifo_devp = devm_kzalloc(&pdev->dev, sizeof(*globalfifo_devp),
GFP_KERNEL);
if (!globalfifo_devp) {
 ret = -ENOMEM;
 goto fail_malloc;
}

globalfifo_setup_cdev(globalfifo_devp, 0);
 mutex_init(&globalfifo_devp->mutex);
 init_waitqueue_head(&globalfifo_devp->r_wait);
 init_waitqueue_head(&globalfifo_devp->w_wait);

return 0;

fail_malloc:
 unregister_chrdev_region(devno, 1);
 return ret;
}

static int globalfifo_remove(struct platform_device *pdev)
{
 cdev_del(&globalfifo_devp->cdev);
 unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);
 return 0;
}
static struct platform_driver globalfifo_driver = {

.driver = {
 .name = "globalfifo",
 .owner = THIS_MODULE,
 },
 .probe = globalfifo_probe,
 .remove = globalfifo_remove,
};
module_platform_driver(globalfifo_driver);

module_platform_driver ()宏所定义的模块加载和卸载函数仅仅通过platform_driver_register ()、 platform_driver_unregister ()函数进行 platform_driver 的注册与注销,而原先注册和注销字符设备的工作已经被移交到 platform_driver 的 probe ()和 remove ()成员函数中。

代码 未列出的部分与原始的 globalfifo 驱动相同,都是实现作为字符设备驱动核心的 file_operations 的成员函数。注册完 globalfifo 对应的 platform_driver 后,我们会发现 /sys/bus/platform/drivers 目录下多出了一个名字叫globalfifo 的子目录。

为了完成在板文件中添加 globalfifo 这个 platform 设备的工作,需要在板文件 arch/arm/mach-<soc 名 >/mach-< 板名>.c )中添加相应的代码

globalfifo 对应的 platform_device

static struct platform_device globalfifo_device = {
 .name= "globalfifo",
 .id= -1,
};

并最终通过类似于 platform_add_devices ()的函数把这个 platform_device 注册进系统。如果一切顺利,我们会在 /sys/devices/platform 目录下看到一个名字叫 globalfifo 的子目录, /sys/devices/platform/globalfifo 中会有一个 driver文件,它是指向 /sys/bus/platform/drivers/globalfifo 的符号链接,这证明驱动和设备匹配上了。
platform 设备资源和数据
platform_device 结构体定义的第 6~7 行,它们描述了 platform_device 的资源,资源本身由
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 和 flags 这 3 个字段,它们分别标明了资源的开始值、结束值和类型, flags 可以为IORESOURCE_IO 、 IORESOURCE_MEM 、 IORESOURCE_IRQ 、 IORE-SOURCE_DMA 等。 start 、 end 的含义会随着 flags 而变更,如当 flags 为 IORESOURCE_MEM 时, start 、 end 分别表示该 platform_device 占据的内存的开始地址和结束地址;当 flags 为IORESOURCE_IRQ 时, start 、 end 分别表示该 platform_device 使用的中断号的开始值和结束值,如果只使用了 1 个中断号,开始和结束值相同。对于同种类型的资源而言,可以有多份,例如说某设备占据了两个内存区域,则可以定义两个 IORESOURCE_MEM 资源。

对 resource 的定义也通常在 BSP 的板文件中进行,而在具体的设备驱动中通platform_get_resource ()这样的 API来获取,此 API 的原型为:

struct resource *platform_get_resource(struct platform_device *dev,
				       unsigned int type, unsigned int num)
{
	int i;

	for (i = 0; i < dev->num_resources; i++) {
		struct resource *r = &dev->resource[i];

		if (type == resource_type(r) && num-- == 0)
			return r;
	}
	return NULL;
}
EXPORT_SYMBOL_GPL(platform_get_resource);

例如在 arch/arm/mach-at91/board-sam9261ek.c 板文件中为 DM9000 网卡定义了如下 resouce :

static struct resource dm9000_resource[] = {
[0] = {
.start = AT91_CHIPSELECT_2,
.end= AT91_CHIPSELECT_2+ 3,
.flags = IORESOURCE_MEM
},
[1] = {
.start = AT91_CHIPSELECT_2+ 0x44,
.end= AT91_CHIPSELECT_2+ 0xFF,
.flags = IORESOURCE_MEM
},[2] = {
.flags = IORESOURCE_IRQ
| IORESOURCE_IRQ_LOWEDGE | IORESOURCE_IRQ_HIGHEDGE,
}
};

在 DM9000 网卡的驱动中则是通过如下办法拿到这 3 份资源:

db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

对于 IRQ 而言, platform_get_resource ()还有一个进行了封装的变体 platform_get_irq (),其原型为:

int platform_get_irq(struct platform_device *dev, unsigned int num);

它实际上调用了 “platform_get_resource ( dev , IORESOURCE_IRQ , num ); ” 。

设备除了可以在 BSP 中定义资源以外,还可以附加一些数据信息,因为对设备的硬件描述除了中断、内存等标准资源以外,可能还会有一些配置信息,而这些配置信息也依赖于板,不适宜直接放置在设备驱动上。因此, platform 也提供了 platform_data 的支持, platform_data 的形式是由每个驱动自定义的,如对于 DM9000 网卡而言, platform_data 为一个 dm9000_plat_data 结构体,完成定义后,就可以将 MAC 地址、总线宽度、板上有无EEPROM 信息等放入 platform_data 中

platform_data 的使用

static struct dm9000_plat_data dm9000_platdata = {
.flags= DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM,
};
static struct platform_device dm9000_device = {
 .name= "dm9000",
 .id= 0,
 .num_resources = ARRAY_SIZE(dm9000_resource),
 .resource= dm9000_resource,
.dev= {
   .platform_data = &dm9000_platdata,
};

而在 DM9000 网卡的驱动 drivers/net/ethernet/davicom/dm9000.c 的 probe ()中,通过如下方式就拿到了platform_data :

struct dm9000_plat_data *pdata = dev_get_platdata(&pdev->dev);

其中, pdev 为 platform_device 的指针。
由以上分析可知,在设备驱动中引入 platform 的概念至少有如下好处。
1 )使得设备被挂接在一个总线上,符合 Linux 2.6 以后内核的设备模型。其结果是使配套的 sysfs 节点、设备电源管理都成为可能。
2 )隔离 BSP 和驱动。在 BSP 中定义 platform 设备和设备使用的资源、设备的具体配置信息,而在驱动中,只需要通过通用 API 去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
3 )让一个驱动支持多个设备实例。譬如 DM9000 的驱动只有一份,但是我们可以在板级添加多份 DM9000 的platform_device ,它们都可以与唯一的驱动匹配。

在 Linux 3.x 之后的内核中, DM9000 驱动实际上已经可以通过设备树的方法被枚举,可以参见补丁 net : dm9000 :Allow instantiation using device tree (内核 commit 的 ID 是 0b8bf1ba )。

index a2408c8..dd243a1 100644
--- a/drivers/net/ethernet/davicom/dm9000.c
+++ b/drivers/net/ethernet/davicom/dm9000.c
@@ -29,6+29,8@@
#include <linux/spinlock.h>
#include <linux/crc32.h>
#include <linux/mii.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
#include <linux/ethtool.h>
#include <linux/dm9000.h>
#include <linux/delay.h>
@@ -1351,6+1353,31@@ static const struct net_device_ops dm9000_netdev_ops = {
#endif
};
+static struct dm9000_plat_data *dm9000_parse_dt(struct device *dev)
+{
+ ...
+}
+/*
* Search DM9000board, allocate space and register it
*/
@@ -1366,6+1393,12@@ dm9000_probe(struct platform_device *pdev)
int i;
u32id_val;
+ if (!pdata) {
+ pdata = dm9000_parse_dt(&pdev->dev);
+ if (IS_ERR(pdata))
+
return PTR_ERR(pdata);
+ }
+
/* Init network device */
ndev = alloc_etherdev(sizeof(struct board_info));
if (!ndev)
@@ -1676,11+1709,20@@ dm9000_drv_remove(struct platform_device *pdev)
return 0;
}
+#ifdef CONFIG_OF
+static const struct of_device_id dm9000_of_matches[] = {
+ { .compatible = "davicom,dm9000", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dm9000_of_matches);
+#endif
+
static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
.pm = &dm9000_drv_pm_ops,
+.of_match_table = of_match_ptr(dm9000_of_matches),
},
.probe = dm9000_probe,
.remove = dm9000_drv_remove,

改为设备树后,在板上添加 DM9000 网卡的动作就变成了简单地修改 dts 文件,如 arch/arm/boot/dts/s3c6410-mini6410.dts 中就有这样的代码:

srom-cs1@18000000 {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x180000000x8000000>;
ranges;
ethernet@18000000{
compatible = "davicom,dm9000";
reg = <0x180000000x20x180000040x2>;
interrupt-parent = <&gpn>;
interrupts = <7IRQ_TYPE_LEVEL_HIGH>;
davicom,no-eeprom;
};
};

设备驱动的分层思想
设备驱动核心层和例化
其实,在分层设计的时候, Linux 内核大量使用了面向对象的设计思想。在面向对象的程序设计中,可以为某一类相似的事物定义一个基类,而具体的事物可以继承这个基类中的函数。如果对于继承的这个事物而言,某成员函数的实现与基类一致,那它就可以直接继承基类的函数;相反,它也可以重写( Overriding ),对父类的函数进行重新定义。若子类中的方法与父类中的某方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法。这种面向对象的 “ 多态 ” 设计思想极大地提高了代码的可重用能力,是对现实世界中事物之间关系的一种良好呈现。

Linux 内核完全是由 C 语言和汇编语言写成,但是却频繁地用到了面向对象的设计思想。在设备驱动方面,往往为同类的设备设计了一个框架,而框架中的核心层则实现了该设备通用的一些功能。同样的,如果具体的设备不想使用核心层的函数,也可以重写。
举个例子:

return_type core_funca(xxx_device * bottom_dev, param1_type param1, param1_type param2)
{
if (bottom_dev->funca)
return bottom_dev->funca(param1, param2);
/*
核心层通用的
funca 代码 */
...
}

在上述 core_funca 的实现中,会检查底层设备是否重写了 funca (),如果重写了,就调用底层的代码
,否则,直接使用通用层的。这样做的好处是,核心层的代码可以处理绝大多数与该类设备的 funca ()对应
的功能,只有少数特殊设备需要重新实现 funca ()

再看一个例子:

return_type core_funca(xxx_device * bottom_dev, param1_type param1, param1_type param2)
{
/* 通用的步骤代码 A */
typea_dev_commonA();
...
/*底层操作ops1 */
bottom_dev->funca_ops1();
/* 通用的步骤代码 B */
typea_dev_commonB();
...
/*底层操作ops2 */
bottom_dev->funca_ops2();
/* 通用的步骤代码 C */
typea_dev_commonC();
...
/**底层操作ops3*/
bottom_dev->funca_ops3();
}


上述代码假定为了实现 funca (),对于同类设备而言,操作流程一致,都要经过 “ 通用代码 A 、
底层 ops1 、通用代码 B 、底层 ops2 、通用代码 C 、底层 ops3” 这几步,分层设计带来
的明显好处是,对于通用代码 A 、 B 、 C ,具体的底层驱动不需要再实现,而仅仅只要关心其
底层的操作 ops1 、 ops2 、 ops3 则可。

图示明确反映了设备驱动的核心层与具体设备驱动的关系,实际上,这种分层可能只有两层(见图 a ),也可能是多层的(图 b )。
在这里插入图片描述在这里插入图片描述这样的分层化设计在 Linux 的 input 、 RTC 、 MTD 、 I 2 C 、 SPI 、 tty 、 USB 等诸多类型设备驱动中屡见不鲜。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值