当前位置: 首页 > news >正文

ACPI TABLE 方式加载device driver--以spi controller为例

目录

Revision history

一、为什么要为SPI driver添加ACPI support?

1.1 当前SPI controller driver存在的问题

1.2 ACPI support for SPI device

二、Add SPI master & slave in ACPI table

2.1 SPI device info in DSDT table

2.2 ACPI device enumeration

三、Add ACPI support in SPI driver

3.1 SPI master controller driver

3.2 SPI slave device enumeration

3.3 SPI device & driver match

四、参考资料

一、为什么要为SPI driver添加ACPI support?

1.1 当前SPI controller driver存在的问题

1、在SPI host controller driver中有很多board specific 代码,Controller信息以static的形式存在driver中,不易于扩展和移植;

2、在SPI controller driver中强制添加slave device SPI flash,没有考虑到该SPI bus是否连接SPI device,以及slave device是否是SPI flash,使得driver通用性变差。

以上这两个问题,导致SPI controller driver的易用性和通用性变差,无法提供给OS/OEM厂商直接使用。如果OEM厂商换了新的SPI device,需要修改controller driver,重新编译安装。

1.2 ACPI support for SPI device

ACPI 5中引入了SPISerialBus,可以枚举SPI bus controller上连接的SPI slave device。

我们可以在ACPI table中添加SPI controller node,并在controller node下通过SPISerialBus添加SPI slave node;

在SPI controller driver中,首先会从ACPI table中获取controller info,并在controller初始化成功后,由controller通过ACPI table来枚举系统中存在的SPI slave device。

这样就能解决前面提到的两个问题:

1)  SPI driver从ACPI table中获取controller info,减少了driver中board specific信息,增强driver的可移植性;

2)  SPI controller 根据ACPI table来枚举系统中实际连接的SPI slave device,更加灵活, driver release 给OS/OEM厂商后,厂商只需要根据实际的SPI使用情况,修改ACPI table,即可驱动不同的SPI device。

二、Add SPI master & slave in ACPI table

2.1 SPI device info in DSDT table

在ACPI DSDT table中添加SPI master & slave configuration和resource信息,这部分由BIOS完成。

我们测试用的DSDT table中SPI master & slave相关的hierarchical结构如下图所示。

系统中有一个SPI controller,即SPI master,使用_HID method设置hardware ID为”SPI0001”,使用_CRS method声明controller使用的memory和IRQ资源。

SPI master上连接着两个SPI slave device,分别是SLV1,使用_HID method设置hardware ID为”SPI0002”;SLV2,使用_HID method设置hardware ID为”SPI0003”。这两个SPI slave都有_CRS method声明该slave的configuration信息。

这部分信息可以根据board实际情况进行调整。

DSDT中详细的配置信息如下:

Device (SPIC)    ///SPI Host Controller

          { 

Name(_HID,  EisaId("SPI0001"))

Name (_UID, 1)

Method (_STA, 0, NotSerialized)           //device status

{

Return (0x0F)

}

 Method (_CRS, 0, Serialized)             //current resource setting

{

                        Name (BUF0, ResourceTemplate ()

                        {

                             Memory32Fixed (ReadWrite,

0xFED10000,         // Address Base   ==> mmio_bas_add      

0x00000100,         // Address Length

                            )

IRQ (Level, ActiveLow, Shared, ) ///IRQ Number   ==> irq_no 

                                {5}

                        })

                        Return (BUF0)

                    }      

 Device (SLV1)       ///SPI Slave Device 1

{        

Name(_HID, EisaId("SPI0002"))                   // ==> device name 

Name(_DDN, "m25p80 serial port")

Name (BUF1, ResourceTemplate ()

{

SPISerialBus (0,                                     //Device selection ==> chip_select       

PolarityLow,                        //Device selection polarity

FourWireMode,                   //WireMode

8,                                         //DataBitLength

ControllerInitiated,               //SlaveMode

1000,                                   //ConnectionSpeed ==> max_speed_hz

ClockPolarityHigh,                    //ClockPolarity

ClockPhaseSecond,                     //ClockPhase

 "\\_SB.PCI0.SPIC", 

0,

ResourceConsumer,

, )

                                               }) 

Method (_STA, 0, NotSerialized)           // ==> device status 

{

Return (0x0F)

}

Method (_CRS, 0, Serialized)      // ==> device setting in SPISerialBus 

{

Return (BUF1)

}

}     

Device (SLV2)       ///SPI Slave Device 2

{        

Name(_HID, EisaId("SPI0003"))  // ==> device name 

Name(_DDN, "m25p80 serial port")

Name (BUF1, ResourceTemplate ()

{

SPISerialBus(1,                            //Device selection ==> chip_select       

 PolarityLow,               //Device selection polarity

FourWireMode,           //WireMode

8,                                //DataBitLength

ControllerInitiated,      //SlaveMode

1000,                          //ConnectionSpeed ==> max_speed_hz

ClockPolarityHigh,     //ClockPolarity

ClockPhaseSecond,     //ClockPhase

"\\_SB.PCI0.SPIC", 

0,

ResourceConsumer,

, )

                                               })      

                                      Method (_STA, 0, NotSerialized)          // ==> device status 

                                     {

                                               Return (0x0F)

                                     }

 Method (_CRS, 0, Serialized)       // ==> device setting in SPISerialBus 

{

Return (BUF1)

}

}              

                 } 

2.2 ACPI device enumeration

         ACPI device enumeration大概分成以下三个步骤: 我们以SPI controller & slave为例进行说明。

1)  系统初始化时load & parse ACPI table,创建ACPI namespace

ACPI namespace中,每个device object都使用struct acpi_namespace_node结构对象表示,并根据table中的device 父母兄弟关系,创建acpi namespace node hierarchy。

                  以SPI controller & slave为例,每个SPI controller和slave都对应着一个

         acpi_namespace_node,device node中有_HID表示该device的hardware ID ,_CRS表示

         该device当前占有的资源和配置信息。

根据第一节中的DSDT 信息,创建的ACPI namespace中,SPI controller & slave部分如下图1所示。

图1 SPI controller & slave in ACPI namespace

2)  创建acpi_device hierarchy

acpi subsystem子系统初始化的最后,调用acpi_bus_scan函数,从ACPI_ROOT_OBJECT开始,遍历整个ACPI namespace,为每个找到的node创建对应的struct acpi_device对象,构成acpi_device hierarchy,并延续了ACPI namespace中的device node的父子兄弟关系。

遍历ACPI namespace的callstack如下:

acpi_bus_scan(ACPI_ROOT_OBJECT)

          acpi_walk_namespace

                   acpi_check_add_device

                            acpi_add_single_object

                  SPI controller & slave的acpi namespace和acpi_device hierarchy关系如下图2所示。

图2:walk acpi namespace to create acpi_device foreach device object

每个acpi_device object都可以通过/sys/devices/LNXSYSTEM:00/查看。

以SPI controller为例是:

/sys/devices/LNXSYSTEM:00/LNXSYBUS\:00/PNP0A08\:00/SPI0001\:00

3)建立acpi_device和”physical” device的companion链接

acpi device可以链接到linux physical device hierarchy中表示“physical”的device,比如PCI bus上的device,platform_device等。acpi_device和”physical” device通过companion建立联系。

SPI controller对应的physical node是一个platform_device,由ACPI subsystem在在acpi_bus_attach时,调用acpi_create_platform_device函数创建,并设置

platform_dev->dev.acpi_node.companion = &adev。

SPI slave对应的physical node是spi_device对象,由SPI master在枚举SPI slave时,调用acpi_register_spi_devices分配并添加到device hierarchy中,并设置

spi_device->dev.acpi_node.companion = &adev。

初始化acpi_device对应的physical device node时,需要执行acpi_device的_CRS method,从DSDT table中获取device的resource和configuration信息。

以SPI controller & slave为例,acpi_device hierarchy和physical device hierarchy的对应关系如下图3所示。

图3:create physical device node hierarchy for SPI

如果一个acpi_device和physical device能够通过companion关联起来,就会在acpi_device的sys目录下创建一个physical_node指向对应的physical device;对应地,physical device的sys目录下会有一个firmware node指向acpi_device。

以SPI controller为例,acpi_device的sys目录下的physical_node如下

# ls -l /sys/devices/LNXSYSTM\:00/LNXSYBUS\:00/PNP0A08\:00/SPI0001\:00/

physical_node -> ../../../../pci0000:00/SPI0001:00

physical platform device的sys目录下的firmware_node如下:

# ls -l  /sys/bus/platform/devices/SPI0001\:00/

firmware_node -> ../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/SPI0001:00

SPI slave1 对应的acpi_device和physical spi_device关系如下:

#ls -l /sys/devices/LNXSYSTM\:00/LNXSYBUS\:00/PNP0A08\:00/SPI0001\:00/SPI0002\:00/

physical_node -> ../../../../../pci0000:00/SPI0001:00/spi_master/spi0/spi-SPI0002:00

         # ls -l /sys/bus/spi/devices/spi-SPI0002\:00/

firmware_node -> ../../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/SPI0001:00/SPI0002:00

         sysfs中SPI controller 和slave对应的acpi_device和physical device的关系如下图4所示。

图4: physical_node & firmware node for SPI in sysfs

1:为什么要引入companion呢?

acpi_device和acpi_handle更多地是和ACPICA subsystem、ACPI table进行交互;

Platform_device、spi_device等具体的physical device是和相应的platform_driver、spi_driver交互,更多和硬件交互,实现function部分。如果platform_device在使用过程中,需要访问ACPICA 接口,需要通过它对应的acpi_device companion来操作。比如SPI master在枚举SPI bus上的slave device时,是通过遍历SPI master对应的acpi_device(parent)中的child device来实现的。

2:为什么不直接使用acpi_driver,而是要引入platform_driver呢?

         Acpi_driver的接口跟一般的device_driver不太一样,如果是使用acpi_driver的话,几乎是需要重新编写driver。为了最大程度地复用之前的device_driver,引入了platform_driver。

三、Add ACPI support in SPI driver

3.1 SPI master controller driver

3.1.1 SPI controller enumeration

从2.2 ACPI device enumeration一节中,我们可以把SPI controller device enumeration简单地理解成从DSDT table中获取SPIC 信息,创建acpi_device和platform_device对象的过程。这个过程可以分成以下两个步骤:

1、  创建acpi_device对象

Linux kernel遍历ACPI namespace时,根据DSDT table中SPIC的信息,为SPI controller创

一个struct acpi_device *adev对象。

创建acpi_device对象时,需要执行SPI controller的_HID method,从DSDT table中获取device的hardware id,加入到device->pnp.ids list中。这样一来就能使用acpi_match_device的方式进行device和driver match了。

从DSDT table中获取SPIC的_HID信息的callstack如下:

         acpi_add_single_object

                  acpi_init_device_object            //从DSDT table中获取信息初始化acpi_device对象

                          acpi_set_device_status     //run _STA method to get device status

                          acpi_set_pnp_ids

                                   acpi_get_object_info        //执行_HID method获取device的hardware id info

                                   acpi_add_id(pnp, info->hardware_id.string);   

//将device HID加入到adev->pnp.ids中,如果该device有其他ID标识,比如CID,也会加入到dev->pnp.ids list中

 

2、  创建platform_device对象

ACPI subsystem调用acpi_create_platform_device函数为SPI controller node创建一个struct platform_deivce *pdev对象。

初始化platform_device对象时,需要执行SPI controller 的_CRS method,从DSDT table中获取SPI controller的resource。在我们的平台上,SPI controller的resources包括一个memory region和一个IRQ。

struct platform_device *acpi_create_platform_device(struct acpi_device *adev)

{

         struct platform_device *pdev = NULL;

         struct acpi_device *acpi_parent;

         struct platform_device_info pdevinfo;

         struct resource_list_entry *rentry;

         struct list_head resource_list;

         struct resource *resources = NULL;

         int count;

          ……

          /* 从DSDT table中获取resource信息

         * 初始化resource_list存放执行_CRS method从DSDT table中得到的resource信息,

* memory和IRQ分别对应着resource_list中的一个entry;

         * 返回resource的个数,这里是2;

          */

         INIT_LIST_HEAD(&resource_list);

         count = acpi_dev_get_resources(adev, &resource_list, NULL, NULL);  //run _CRS method

if (count > 0) {

                   resources = kmalloc(count * sizeof(struct resource),GFP_KERNEL);

                   count = 0;

//将resource_list中的每个entry都转换成struct resource*

                   list_for_each_entry(rentry, &resource_list, node)    

                            resources[count++] = rentry->res;

                   acpi_dev_free_resource_list(&resource_list);

         }

……

//initialise platform_device

          memset(&pdevinfo, 0, sizeof(pdevinfo));

         pdevinfo.name = dev_name(&adev->dev);

         pdevinfo.id = -1;

         pdevinfo.res = resources;

         pdevinfo.num_res = count;

         pdevinfo.acpi_node.companion = adev;          //platform_device & acpi_device

         //alloc/initialise and register platform_device

         pdev = platform_device_register_full(&pdevinfo);

         if (IS_ERR(pdev))

                   dev_err(&adev->dev, "platform device creation failed: %ld\n", PTR_ERR(pdev));

         else

                   dev_dbg(&adev->dev, "created platform device %s\n", dev_name(&pdev->dev));

         kfree(resources);

         return pdev;

}

3.1.2 platform_device and driver match

platform_device registered之后,就会调用platform_bus->match callback( platform_match函数),遍历platform bus中的driver list,找到一个能支持该device的driver。

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

         /* 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);      

}

我们看一下,如果要使用ACPI style match进行device和driver match的callstack,看看使用这种方式进行match时,需要如何编写platform_driver。

acpi_driver_match_device(dev, drv)

acpi_match_device(drv->acpi_match_table, dev);

__acpi_match_device(adev, ids);

//将device->pnp.ids中的每个id跟drv->acpi_match_table中的每个entry进行比对

ids = drv->acpi_match_table

for (id = ids; id->id[0]; id++)        

                  list_for_each_entry(hwid, &device->pnp.ids, list)   

                           if (!strcmp((char *) id->id, hwid->id))

                                   return id;   

         acpi_match_device会将dev->pnp.ids中的每个id跟drv->acpi_macth_table中的id进行比对,如果有相同的,表示该driver能支持这个device。

因此,要使用ACPI style match,在编写SPI controller platform driver时,需要在drv->acpi_match_table中,添加一个entry,对应着DSDT table中的SPIC(SPI0001)。这样在platform bus match时,才能为SPI controller的platform_device匹配到我们的platform_driver。

以SPI0001 (SPIC)为例的修改如下所示:

1)在platform_driver中加入acpi_match_table

static struct platform_driver  via_spi_driver = {

         .probe  = via_spi_probe,

         .remove = via_spi_remove,

         .suspend    = via_spi_suspend,

         .resume     = via_spi_resume,

         .driver = {

                  .name = VIA_SPI_NAME,

                  .owner = THIS_MODULE,

                  .acpi_match_table = ACPI_PTR(via_spi_acpi_match),

         },

};

2)在acpi_match_table中列出当前的platform_driver支持的所有的SPI controller,

把我们的SPI controller对应的acpi_device_id加入到acpi_match_table中;

static struct acpi_device_id via_spi_acpi_match[] ={

         {"SPI0001"},

         {},

};

MODULE_DEVICE_TABLE(acpi,via_spi_acpi_match);

3.1.3 SPI master initialization

       SPI master controller对应的platform_device和platform_driver通过acpi_match_table match之后,执行driver->probe callback,完成SPI controller的初始化操作。

         SPI controller driver->probe的主要工作如下:

1)获取controller操作需要的硬件资源,包括memory、IRQ等。

这些resource已经在创建platform_device,添加到platform_device中了。(在ACPI为SPI controller创建platform_device时,已经通过_CRS method从DSDT table中获取,填充到platform_device对象中了,具体实现参考3.1.1。)

在SPI controller 对应的platform_driver->probe callback中,调用platform_get_resource、platform_get_irq即可从对应的platform_device中获取memory和IRQ资源信息。

2)初始化spi_master

       调用spi_alloc_master来创建一个spi_master对象。spi_master初始化完成后,调用spi_register_master,向SPI core subsystem中添加一个spi_master。

static int via_spi_probe(struct platform_device *pdev)

{

         ……

         for (i=0; i < via_spi_chip_info->num_master; i++) {

                  master = spi_alloc_master(&pdev->dev, sizeof(struct via_spi_master_extension));

                  if (master == NULL) {

                          via_spi_printk(KERN_INFO "No memory for spi_master!\n");

                          ret = -ENOMEM;

                          goto put_master;

                  }

                  via_spi_chip_info->master[i] = spi_master_get(master);

         }

         ……

         //get info for this spi_master

ret = via_spi_master_start(via_spi_chip_info, &vt3409_master_0);

         ……

         //register spi_master to device hierarchy

         ret = spi_register_master(via_spi_chip_info->master[0]);

}

spi_master初始化完成后,调用spi_register_master向系统注册,最终会调用device_add将SPI master添加到device hierarchy中。

3.2 SPI slave device enumeration

SPI slave不能自动enumeration,必须由spi master 根据configuration table来手动枚举。

如果是使用ACPI configuration table,即去遍历ACPI namespace中,查看SPI master device的scope中有没有SPI slave信息。如果能找到SPI slave device,就会创建一个struct spi_device对象来表示SPI slave,并调用spi_add_device添加到device hierarchy中。

         和SPI controller类似,SPI slave device enumeration的过程也可以理解成创建acpi_device和spi_device的过程。不过,SPI slave对应的acpi_device已经在系统初始化,扫描ACPI namespace时创建完成了,已经存在于acpi_device hierarchy中了。acpi_device创建和初始化的步骤跟SPI controller一样,也需要执行_HID method 获取slave device的hardware ID,并添加到adev->pnp.ids list中。

         SPI master 扫描slave并创建spi_device时,需要执行SPI slave的_CRS method,从DSDT table中获取SPI slave的configuration信息。ACPI5.0中,使用SPISerialBus object来描述SPI slave device的bus connection configuration信息,包括chipselect、clock、connection speed等。在DSDT 中添加SPI slave node,并使用SPISerialBus描述SPI slave device的bus connection信息之后,kernel能够通过DSDT table获取SPI slave信息,这样就不需要在device driver中提供spi_device的bus connection信息了。

SPI master枚举SPI slave device的流程如下:

spi_register_master

         acpi_register_spi_device

                  acpi_walk_namespace //在ACPI namespace中,从SPI master开始遍历

acpi_spi_add_device

         在acpi_spi_add_device中,创建spi_device对象,从DSDT table中获取SPI device configuration信息完成初始化,然后调用spi_add_device添加到device hierarchy中。

static acpi_status acpi_spi_add_device(acpi_handle handle, u32 level, void *data, void **return_value)

{

                  struct spi_master *master = data;

                  struct list_head resource_list;

                  struct acpi_device *adev;

                  struct spi_device *spi;

                  int ret;

                  if (acpi_bus_get_device(handle, &adev))

                           return AE_OK;

                  if (acpi_bus_get_status(adev) || !adev->status.present)

                           return AE_OK;

                  //memory allocation for spi_device

                  spi = spi_alloc_device(master);

                  ……

                  ACPI_COMPANION_SET(&spi->dev, adev);

                  spi->irq = -1;

          /* 从DSDT table中获取configuration信息

         * 初始化resource_list存放执行_CRS method从DSDT table中得到的configuration信息,

* acpi_spi_add_resource会解析根据SPISerialBus中的信息,初始化spi_device对象

          */

                  INIT_LIST_HEAD(&resource_list);

                  ret = acpi_dev_get_resources(adev, &resource_list, acpi_spi_add_resource, spi);

                  acpi_dev_free_resource_list(&resource_list);

                  adev->power.flags.ignore_parent = true;

                  strlcpy(spi->modalias, acpi_device_hid(adev), sizeof(spi->modalias));

                  if (spi_add_device(spi)) //add spi_device to device hierarchy

{

                           adev->power.flags.ignore_parent = false;

                           dev_err(&master->dev, "failed to add SPI device %s from ACPI\n", dev_name(&adev->dev));

                           spi_dev_put(spi);

                  }

                  return AE_OK;

}

SPI master扫描SPI bus的代码在SPI core subsystem中,列出在这里是为了方便理解,这部分不需要修改。

int spi_register_master(struct spi_master *master)

{

         static atomic_t             dyn_bus_id = ATOMIC_INIT((1<<15) - 1);

         struct device                *dev = master->dev.parent;

         struct boardinfo  *bi;

         int                      status = -ENODEV;

         int                      dynamic = 0;

         if (!dev)

                  return -ENODEV;

         status = of_spi_register_master(master);

         if (status)

                  return status;

         /* even if it's just one always-selected device, there must be at least one chipselect */

         if (master->num_chipselect == 0)

                  return -EINVAL;

         if ((master->bus_num < 0) && master->dev.of_node)

                  master->bus_num = of_alias_get_id(master->dev.of_node, "spi");

         /* convention:  dynamically assigned bus IDs count down from the max */

         if (master->bus_num < 0) {

                  /* FIXME switch to an IDR based scheme, something like

                   * I2C now uses, so we can't run out of "dynamic" IDs

                   */

                  master->bus_num = atomic_dec_return(&dyn_bus_id);

                  dynamic = 1;

         }

         spin_lock_init(&master->bus_lock_spinlock);

         mutex_init(&master->bus_lock_mutex);

         master->bus_lock_flag = 0;

         init_completion(&master->xfer_completion);

         if (!master->max_dma_len)

                  master->max_dma_len = INT_MAX;

         /* register the device, then userspace will see it.registration fails if the bus ID is in use.*/

         dev_set_name(&master->dev, "spi%u", master->bus_num);

         status = device_add(&master->dev);

……

         /* If we're using a queued driver, start the queue */

         if (master->transfer)

                  dev_info(dev, "master is unqueued, this is deprecated\n");

         else {

                  status = spi_master_initialize_queue(master);

                  if (status) {

                          device_del(&master->dev);

                          goto done;

                  }

         }

         mutex_lock(&board_lock);

         list_add_tail(&master->list, &spi_master_list);

         list_for_each_entry(bi, &board_list, list)

                  spi_match_master_to_boardinfo(master, &bi->board_info);

         mutex_unlock(&board_lock);

         /* Register devices from the device tree and ACPI */

         of_register_spi_devices(master);

         acpi_register_spi_devices(master);     //扫描SPI slave device

done:

         return status;

}

static void acpi_register_spi_devices(struct spi_master *master)

{

         acpi_status status;

         acpi_handle handle;

         handle = ACPI_HANDLE(master->dev.parent);

         if (!handle)

                  return;

         //遍历ACPI namespace,寻找SPI masters的child device

         status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1,

                                        acpi_spi_add_device, NULL,

                                        master, NULL);

         if (ACPI_FAILURE(status))

                  dev_warn(&master->dev, "failed to enumerate SPI slaves\n");

}

3.3 SPI device & driver match

在SPI master完成SPI salve enumeration,创建spi_device并添加到device hierarchy,即向SPI bus 添加新设备,会触发执行spi_bus_type->match callback,遍历SPI bus上的driver,寻找一个能跟该spi_device 匹配的spi_driver。

在spi_bus_type->match callback中,spi_driver 和spi_device可以通过acpi_device_id和spi_device_id两种方式来做device match。

static int spi_match_device(struct device *dev, struct device_driver *drv)

{

         const struct spi_device *spi = to_spi_device(dev);

         const struct spi_driver *sdrv = to_spi_driver(drv);

/* match with acpi_device_id */

         if (acpi_driver_match_device(dev, drv))       //通过adev->pnp.ids match

                  return 1;            

         /* match with spi_device_id */

         if (sdrv->id_table)                                         

                  return !!spi_match_id(sdrv->id_table, spi);

         return strcmp(spi->modalias, drv->name) == 0;

}

         我们以M25p80 SPI flash driver为例,分别介绍在使用spi_device_id和acpi_device_id进行device和driver match时,SPI device driver该如何设计。

3.3.1使用acpi_device_id匹配spi_device

         使用ACPI style的device match callstack如下:

         spi_match_device

                  acpi_driver_match_device(dev,drv)

acpi_match_device(drv->acpi_match_table, dev);

__acpi_match_device(adev, ids);

//将device->pnp.ids中的每个id跟drv->acpi_match_table中的每个entry进行比对

ids = drv->acpi_match_table

for (id = ids; id->id[0]; id++)        

                           list_for_each_entry(hwid, &device->pnp.ids, list)   

                                   if (!strcmp((char *) id->id, hwid->id))

                                            return id;   

acpi_match_device会将dev->pnp.ids中的每个id跟drv->acpi_macth_table中的id进行比对。如果有相同的,表示该driver能支持这个device。和所有的acpi_device一样,dev->pnp.ids是在acpi_add_single_object函数中,通过执行_HID method从DSDT table中得到的device hardware id info。

因此,使用ACPI style match时,在编写spi_driver时,需要在drv->acpi_match_table中,添加2个entry,分别对应着DSDT table中的SLV1(SPI0002)和SLV2(SPI0003)。这样在spi bus match时,才能为SPIslave的spi_device匹配到我们的spi_driver。

以m25p80 spi flash driver为例的修改如下所示:

         1)在spi_driver中,添加acpi_match_table

static struct spi_driver m25p80_driver = {

         .driver = {

                  .name        = "m25p80",

                  .owner       = THIS_MODULE,

                  .acpi_match_table = ACPI_PTR(m25p80_acpi_ids),

         },

         .id_table    = spi_nor_ids,

         .probe        = m25p_probe,

         .remove     = m25p_remove,

};

2)将SPI0002和SPI0003加入到acpi_match_table中

static const struct acpi_device_id m25p80_acpi_ids[] = {

         { "SPI0002",  INFO(0x202014,  0,  64 * 1024,  16, 0) },

{ "SPI0003",  INFO(0x202014,  0,  64 * 1024,  16, 0) },

         {},

};

MODULE_DEVICE_TABLE(acpi, m25p80_acpi_ids);

注:在m25p80中,SPI_driver->probe callback中调用了spi_nor_scan函数对SPI flash做进一步的初始化操作。为了保持spi_nor_scan接口的一致,需要将acpi_device_id转换成spi_device_id之后再调用

--- ../../ Kernel-3.16.0/drivers/mtd/devices/m25p80.c        2017-03-29 10:58:32.849037642 +0800

+++ drivers/mtd/devices/m25p80.c        2017-04-01 16:47:32.376433617 +0800

static int m25p_probe(struct spi_device *spi)

{

……

/*

  * board specific setup should have ensured the SPI clock used here

  * matches what the READ command supports, at least until this driver

@@ -198,6 +242,12 @@

        enum read_mode mode = SPI_NOR_NORMAL;

        int ret;

+       struct spi_device_id spi_id;

+       struct acpi_device_id *acpi_id = acpi_match_device(m25p80_acpi_ids, &spi->dev);

+

+       memcpy(spi_id.name, acpi_id->id, 8*sizeof(u8));

+       spi_id.driver_data = acpi_id->driver_data;

+      

        flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL);

        if (!flash)

                 return -ENOMEM;

@@ -223,7 +273,8 @@

                 mode = SPI_NOR_QUAD;

        else if (spi->mode & SPI_RX_DUAL)

                 mode = SPI_NOR_DUAL;

-        ret = spi_nor_scan(nor, spi_get_device_id(spi), mode);

+

+       ret = spi_nor_scan(nor, &spi_id, mode);

        if (ret)

                 return ret;

}

3.3.2使用spi_device_id匹配spi_device

使用spi_device_id的device match callstack如下:

         spi_match_device

                  spi_match_id(sdrv->id_table, spi);

                          if (!strcmp(sdev->modalias, id->name))

                                   return id;

spi_match_device会将spi->modaliassdrv->id_table中的id进行比对。如果有相同的,表示该driver能支持这个device。spi->modalias即为SPI slave device的hardware info string,在acpi_add_single_object函数中,通过执行_HID method从DSDT table中得到的。

因此,使用spi_device_id进行device match,在编写spi_driver时,需要在drv->id_table中,添加2个entry,分别对应着DSDT table中的SLV1(SPI0002)和SLV2(SPI0003)。这样在spi bus match时,才能为SPIslave的spi_device匹配到我们的spi_driver。

         以m25p80 spi flashdriver为例的修改如下所示:

         1)在spi_driver中添加id_table

static struct spi_driver m25p80_driver = {

                  .driver = {

                          .name        = "m25p80",

                          .owner       = THIS_MODULE,

                  },

                  .id_table    = spi_nor_ids,                     //list of SPI devices supported by this driver

                  .probe        = m25p_probe,

                  .remove     = m25p_remove,

};

module_spi_driver(m25p80_driver);

2)在id_table中添加SPI0002 spi flash device,即在spi_nor_ids中,加入我们SPI0002的spi_device_id;

const struct spi_device_id spi_nor_ids[] = {

         ……

        { "m25p20",  INFO(0x202012,  0,  64 * 1024,   4, 0) },

        { "m25p40",  INFO(0x202013,  0,  64 * 1024,   8, 0) },

        { "m25p80",  INFO(0x202014,  0,  64 * 1024,  16, 0) },

+       { "SPI0002",  INFO(0x202014,  0,  64 * 1024,  16, 0) },         

+       { "SPI0003",  INFO(0x202014,  0,  64 * 1024,  16, 0) },

        { "m25p16",  INFO(0x202015,  0,  64 * 1024,  32, 0) },

        { "m25p32",  INFO(0x202016,  0,  64 * 1024,  64, 0) },

        { "m25p64",  INFO(0x202017,  0,  64 * 1024, 128, 0) },

……

 }

四、参考资料

1.Documentation/acpi/enumeration.txt

2.ACPI Ver5.0 spec

http://www.lryc.cn/news/623747.html

相关文章:

  • 字节 Golang 大模型应用开发框架 Eino简介
  • Pulsar存储计算分离架构设计之存储层BookKeeper(上)
  • 在线编程题目之小试牛刀
  • C#高级语法_委托
  • Windows平台Frida逆向分析环境完整搭建指南
  • 从需求到部署全套方案:餐饮服务许可证数据可视化分析系统的大数据技术实战
  • 发票识别工具,合并PDF提取信息
  • JavaScript字符串详解
  • 001.Redis 简介及安装
  • 【杂谈】-以质代量:谷歌主动学习范式重构AI训练逻辑
  • Mac(四)自定义按键工具 Hammerspoon 的安装和使用
  • vue封装请求拦截器 响应拦截器
  • SCAI采用公平发射机制成功登陆LetsBonk,60%代币供应量已锁仓
  • 智能合约里的 “拒绝服务“ 攻击:让你的合约变成 “死机的手机“
  • 数学建模 14 中心对数比变换
  • 原子操作及基于原子操作的shared_ptr实现
  • Leaflet赋能:WebGIS视角下的省域区县天气可视化实战攻略
  • 数据结构:二叉搜索树(Binary Search Tree)
  • ansible管理变量和事实
  • 《Python学习之文件操作:从入门到精通》
  • 剑指offer第2版——面试题5:替换空格
  • Java注解学习记录
  • 26. 值传递和引用传递的区别的什么?为什么说Java中只有值传递
  • 大模型对齐算法合集(一)
  • Zemax 中的透镜设计 - 像差理论
  • 评测系统构建
  • 深入分析 Linux PCI Express 子系统
  • 计算机网络 TCP time_wait 状态 详解
  • 10 SQL进阶-SQL优化(8.15)
  • Matlab课程实践——基于MATLAB设计的计算器软件(简单、科学、电工、矩阵及贷款计算)