嵌入式Linux设备驱动程序:发现硬件配置

嵌入式Linux设备驱动程序:发现硬件配置

Embedded Linux device drivers: Discovering the hardware configuration

Interfacing with Device Drivers

了解硬件配置             

虚拟驱动程序演示了一个设备驱动程序的结构,但是由于它只操作内存结构,因此它缺乏与实际硬件的交互。设备驱动程序通常是用来与硬件交互的。部分原因是能够在第一时间发现硬件,记住它可能位于不同配置的不同地址。             

在某些情况下,硬件本身提供信息。PCI或USB等可发现总线上的设备具有查询模式,该模式返回资源需求和唯一标识符。内核将标识符和可能的其他特征与设备驱动程序匹配,并将它们结合起来。             

然而,嵌入式板上的大多数硬件块没有这样的标识符。您必须自己以设备树的形式或称为平台数据的C结构来提供信息。             

在Linux的标准驱动程序模型中,设备驱动程序向相应的子系统注册:PCI、USB、开放固件(设备树)、平台设备等等。注册包括一个标识符和一个称为探测函数的回调函数,如果硬件ID和驱动程序的ID匹配,则调用该函数。对于PCI和USB,ID基于供应商和设备的产品ID;对于设备树和平台设备,它是一个名称(文本字符串)。

设备树             

我在第三章中介绍了设备树,都是关于引导程序的。在这里,我想向您展示Linux设备驱动程序是如何与这些信息连接起来的。             

作为一个例子,我将使用ARM多功能板,arch/ARM/boot/dts/Versatile-ab.dts公司,以太网适配器在此处定义:

net@10010000 {     compatible = "smsc,lan91c111";     reg = <0x10010000 0x10000>;     interrupts = <25>;};

平台数据             

在没有设备树支持的情况下,有一种使用C结构描述硬件的后备方法,称为平台数据。             

每个硬件由struct platform_device描述,它有一个名称和一个指向资源数组的指针。资源的类型由标志确定,这些标志包括:             

IORESOURCE_MEM:这是内存区域的物理地址             

IORESOURCE_IO:这是IO寄存器的物理地址或端口号             

IORESOURCE_IRQ:这是中断号             

下面是一个以太网控制器的平台数据示例,该数据取自arch/arm/mach versatile/core.c,为清晰起见,对其进行了编辑:

#define VERSATILE_ETH_BASE     0x10010000   #define IRQ_ETH                25   static struct resource smc91x_resources[] = {     [0] = {       .start          = VERSATILE_ETH_BASE,       .end            = VERSATILE_ETH_BASE + SZ_64K - 1,       .flags          = IORESOURCE_MEM,},
      [1] = {       .start          = IRQ_ETH,       .end            = IRQ_ETH,       .flags          = IORESOURCE_IRQ,}, 
   };   static struct platform_device smc91x_device = {     .name           = "smc91x",     .id             = 0,     .num_resources  = ARRAY_SIZE(smc91x_resources),     .resource       = smc91x_resources,};

它有一个64KB的内存区和一个中断。平台数据必须在内核中注册,通常在板初始化时:

 void __init versatile_init(void)  {    platform_device_register(&versatile_flash_device);    platform_device_register(&versatile_i2c_device);    platform_device_register(&smc91x_device);     [ ...]

将硬件与设备驱动程序链接             

在上一节中,您已经看到了如何使用设备树和平台数据来描述以太网适配器。相应的驱动程序代码在drivers/net/ethernet/smsc/smc91x.c中,它同时处理设备树和平台数据。以下是初始化代码,为清晰起见再次编辑:

static const struct of_device_id smc91x_match[] = {     { .compatible = "smsc,lan91c94", },     { .compatible = "smsc,lan91c111", },     {},   };   MODULE_DEVICE_TABLE(of, smc91x_match);   static struct platform_driver smc_driver = {.probe = smc_drv_probe, 
     .remove = smc_drv_remove, 
     .driver ={       .name   = "smc91x",       .of_match_table = of_match_ptr(smc91x_match),     },   };   static int __init smc_driver_init(void)   {     return platform_driver_register(&smc_driver);   }   static void __exit smc_driver_exit(void)   {     platform_driver_unregister(&smc_driver);   }   module_init(smc_driver_init);   module_exit(smc_driver_exit);

当驱动程序初始化时,它调用platform_driver_register(),指向struct platform_driver,其中有一个对探测函数的回调、一个驱动程序名smc91x和一个指向“设备”id的struct的指针。             

如果这个驱动程序是由设备树配置的,内核将在设备树节点中的compatible属性和compatible structure元素所指向的字符串之间寻找匹配。对于每个匹配,它调用probe函数。             

另一方面,如果它是通过平台数据配置的,则将为指向的字符串上的每个匹配调用probe函数驱动程序名.             

probe函数提取有关接口的信息:

static int smc_drv_probe(struct platform_device *pdev)  {     struct smc91x_platdata *pd = dev_get_platdata(&pdev->dev);     const struct of_device_id *match = NULL;     struct resource *res, *ires;     int irq;     
     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);     ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0);     [...]     addr = ioremap(res->start, SMC_IO_EXTENT);     irq = ires->start;[...] 
  }

对platform_get_resource()的调用从设备树或平台数据中提取内存和irq信息。由驱动程序映射内存并安装中断处理程序。第三个参数在前面的两种情况下都为零,如果有一个以上的特定类型的资源,则会起作用。             

设备树允许您配置的不仅仅是基本内存范围和中断。probe函数中有一段代码从设备树中提取可选参数。在此代码段中,它获取register io width属性:

match = of_match_device(of_match_ptr(smc91x_match), &pdev->dev);  if (match) {    struct device_node *np = pdev->dev.of_node;    u32 val;    [...]    of_property_read_u32(np, "reg-io-width", &val);    [...]}

对于大多数驱动程序,在Documentation/deviceree/bindings中记录了特定的绑定。对于这个特定的驱动程序,信息在Documentation/deviceree/bindings/net/smsc911x.txt中。             

这里要记住的主要一点是,驱动程序应该注册一个探测函数和足够的信息,以便内核调用探测,因为它发现与它所知道的硬件匹配。设备树描述的硬件和设备驱动程序之间的链接是通过compatible属性实现的。平台数据和驱动程序之间的链接是通过名称实现的。             

摘要             

设备驱动程序的工作是处理设备,通常是物理硬件,但有时是虚拟接口,并以一致和有用的方式将它们呈现给用户空间。Linux设备驱动程序分为三大类:字符、块和网络。在这三种接口中,字符驱动接口是最灵活的,因此也是最常见的。Linux驱动程序适合于一个称为驱动程序模型的框架,该模型通过sysfs公开。在/sys中几乎可以看到设备和驱动程序的整个状态。             

每个嵌入式系统都有自己独特的硬件接口和需求集。Linux为大多数标准接口提供了驱动程序,通过选择正确的内核配置,您可以很快得到一个工作的目标板。这就给您留下了非标准组件,您必须添加自己的设备支持。              

在某些情况下,您可以通过使用GPIO、I2C等的通用驱动程序来回避这个问题,并编写用户空间代码来完成这项工作。我建议将此作为一个起点,因为它让您有机会在不编写内核代码的情况下熟悉硬件。编写内核驱动程序并不是特别困难,但是如果你真的这么做了,你需要小心编码,以免损害系统的稳定性。             

我已经讨论过如何编写内核驱动程序代码:如果你沿着这条路走下去,你将不可避免地想知道如何检查它是否正常工作并检测出任何错误。

 

posted @ 2020-07-11 11:03  吴建明wujianming  阅读(396)  评论(0编辑  收藏  举报