前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux X86-ACPI PNP Hardware ID的识别框架

Linux X86-ACPI PNP Hardware ID的识别框架

作者头像
杨源鑫
发布2019-07-04 15:24:48
3.9K0
发布2019-07-04 15:24:48
举报
文章被收录于专栏:嵌入式开发圈嵌入式开发圈

ACPI规范与PNP===>Hardware ID

基于X86架构的Linux内核,在移植驱动的过程中,发现GPIO和I2C的device ID添加到pnp驱动框架后无法进入probe函数,后面找了下原因,因为pnp遵循的是ACPI规范,是由于如下Hardware ID字段是需要从BIOS中进行描述的,而目前的驱动匹配不到对应的字段,自然就不可能注册成功了。 PNP是什么东西?不是三极管的那个PNP啦,这个PNP表示的是:Plug-and-Play,译文为即插即用。 PNP的作用是自动配置底层计算机中的板卡和其他设备,然后告诉对应设备都做了什么。PnP的任务是把物理设备和软件设备驱动程序相配合,并操作设备,在每个设备和它的驱动程序之间建立通信信道。然后,PnP分配下列资源给设备和硬件:I/O地址、IRQ、DMA通道和内存段。即插即用设备配置的控制权将从系统BIOS传递到系统软件,所以驱动中一定会有代码进行描述,到时可以跟一下这部分的代码深入了解一下。由于PNP遵循ACPI的规范,那么既然是规范,那肯定要照着做了,规范怎么说,那就怎么做。 以下是关于ACPI Spec中对Hardware ID的描述,描述如下:

有关ASL语言可以参考ACPI SPEC手册的ACPI Source Language (ASL)Reference章节。如上,关于Hardware ID手册中的意思大致如下: 该对象用于向OSPM提供设备的PNP ID或ACPI ID。 在描述平台时,任何_HID对象的使用都是可选的。但是,_HID对象必须是用于描述将由OSPM枚举的任何设备。 当总线枚举器不能检测到设备ID时,OSPM只枚举一个设备。当总线枚举器不能检测到设备ID时。例如,ISA总线上的设备是由OSPM列举。除了OSPM使用_ADR对象来描述总线枚举器枚举的设备。 其中OPSM是:OSPM(OS-directed Power Management) :OSPM 操作系统支持 ACPI 的一个部分,操作系统 (OS)可以从操作系统下驱动程序的角度控制 ACPI 子模块,同时支持 ACPI 包括 SCI 中断,设备事件,系统事件模式,这些事件模式可以充分支持 Hot-plug 方式。 所以解决驱动匹配不上的问题,只要在BIOS中的ASL工程中对应Hardware描述的部分添加一个字段描述,确保驱动中的字段和BIOS中的字段一致,这样就可以匹配成功了。

  • 分析内核是如何获取BIOS传递的参数表

接下来主要来看看在Linux内核中,内核是怎么去通过BIOS传递的参数表,传递对应的字串,然后内核又是如何来解析它,最终为Linux驱动统一模型所用。其实ARM和X86的驱动本质并没有太大的区别,都是有了一个基地址,然后依靠偏移来获取定位寄存器,写值驱动设备。ARM也会去解析uboot传递的参数,然而并没有那么的复杂,而X86对设备驱动进行了统一的管理,这点与ARM软件架构的实现是有很大区别的,比如,让GPIO的基地址在BIOS中进行统一分配,使用BIOS来统一管理电源等等。。。而ARM就相对来说简单很多,没有这么多的步骤,使用标准的Linux驱动模型+类ARM裸机操作(操作的地址需要进行映射,将物理地址转换成虚拟地址,这点和单片机是不太一样的),也就是说,如果掌握了ARM的驱动模型,同样的,只要我们拥有X86架构的CPU数据手册,我们同样也可以使用ARM的思想来完成对X86架构的CPU的各类驱动BSP的编写。

以下是较为重要的结构体描述:

代码语言:javascript
复制
 1在这个结构体里发现,_HID是以内核链表成员的形式加载进Linux内核的
 2(内核源码/include/acpi/Acpi_bus.h)
 3struct acpi_hardware_id {
 4    struct list_head list; 
 5    char *id;
 6};
 7
 8//ACPI的对象类型结构体
 9typedef u32 acpi_object_type;
10//ACPI对象
11union acpi_object {
12    acpi_object_type type;  /* See definition of acpi_ns_type for values */
13    struct {
14        acpi_object_type type;  /* ACPI_TYPE_INTEGER */
15        u64 value;  /* The actual number */
16    } integer;
17
18    struct {
19        acpi_object_type type;  /* ACPI_TYPE_STRING */
20        u32 length; /* # of bytes in string, excluding trailing null */
21        char *pointer;  /* points to the string value */
22    } string;
23
24    struct {
25        acpi_object_type type;  /* ACPI_TYPE_BUFFER */
26        u32 length; /* # of bytes in buffer */
27        u8 *pointer;    /* points to the buffer */
28    } buffer;
29
30    struct {
31        acpi_object_type type;  /* ACPI_TYPE_PACKAGE */
32        u32 count;  /* # of elements in package */
33        union acpi_object *elements;    /* Pointer to an array of ACPI_OBJECTs */
34    } package;
35
36    struct {
37        acpi_object_type type;  /* ACPI_TYPE_LOCAL_REFERENCE */
38        acpi_object_type actual_type;   /* Type associated with the Handle */
39        acpi_handle handle; /* object reference */
40    } reference;
41
42    struct {
43        acpi_object_type type;  /* ACPI_TYPE_PROCESSOR */
44        u32 proc_id;
45        acpi_io_address pblk_address;
46        u32 pblk_length;
47    } processor;
48
49    struct {
50        acpi_object_type type;  /* ACPI_TYPE_POWER */
51        u32 system_level;
52        u32 resource_order;
53    } power_resource;
54};
55
56typedef char acpi_bus_id[8];
57typedef unsigned long acpi_bus_address;
58typedef char acpi_device_name[40];
59typedef char acpi_device_class[20];
60
61//这是一个位段,用来描述pnp中的类型
62struct acpi_pnp_type {
63    u32 hardware_id:1;
64    u32 bus_address:1;
65    u32 platform_id:1;
66    u32 reserved:29;
67};
68
69//acpi的pnp设备,包括对象名称、ID类型、以及各种ID,具体参考ACPI spec
70struct acpi_device_pnp {
71    acpi_bus_id bus_id;     /* Object name */
72    struct acpi_pnp_type type;  /* ID type */
73    acpi_bus_address bus_address;   /* _ADR */
74    char *unique_id;        /* _UID */
75    struct list_head ids;       /* _HID and _CIDs */
76    acpi_device_name device_name;   /* Driver-determined */
77    acpi_device_class device_class; /*        "          */
78    union acpi_object *str_obj; /* unicode string for _STR method */
79};

那X86架构的CPU在启动内核的时候又是如何知道BIOS传递过来的HID参数?我们可以来看看X86架构在Linux下的启动流程:

不管是在ARM还是X86平台,本质都是将一系列代码拷贝到对应的存储器对应的区域中,这个存储器一般是NOR FLASH或者NAND FLASH,当然现在还有EMMC等其它的存储设备,然后在执行Uboot(ARM的叫法,也叫bootloader,用来引导内核,而X86用的是BIOS,也差不多)中,通过地址跳转的形式去启动内核,如果是我们自己实现的Bootloader,一般会在作为uboot的第一、第二阶段以后,通过如下的代码跳转到操作系统启动的模式:

代码语言:javascript
复制
 1/* 0. 帮内核设置串口: 内核启动的开始部分会从串口打印一些信息,但是内核一开始没有初始化串口 */
 2    uart0_init();
 3
 4    /* 1. 从NAND FLASH里把内核读入内存 */
 5    puts("Copy kernel from nand\n\r");
 6    nand_read(0x60000+64, (unsigned char *)0x30008000, 0x200000);
 7    puthex(0x1234ABCD);
 8    puts("\n\r");
 9    puthex(*p);
10    puts("\n\r");
11
12    /* 2. 设置参数 */
13    puts("Set boot params\n\r");
14    setup_start_tag();
15    setup_memory_tags();
16    setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0");
17    setup_end_tag();
18
19    /* 3. 跳转执行 */
20    puts("Boot kernel\n\r");
21    theKernel = (void (*)(int, int, unsigned int))0x30008000;
22    theKernel(0, 362, 0x30000100);  

如上代码段,Linux内核在启动的过程中会去解析ARM传递过去的参数:noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0。

ARM的启动相对来说比较简单: uboot----->内核------>文件系统------>app,在uboot之前一般还会有IC厂商的固件驱动代码。

而X86架构的CPU与ARM的启动形式就不太一样,显然比这里要复杂得多,由于BIOS的源代码并不开放,所以我们也并不知道BIOS的内幕具体是怎么实现的,但我们可以从以下这张图可以得知X86架构从BIOS到kernel的整个流程,在这里我们能够得知,X86的OS和BIOS之间衔接的桥梁是ACPI,使用ACPI来对一些资源进行统一管理,我们要获取的这个Hardware ID其实就是ACPI Tables中的其中一个参数。

到这里我们就明白了,不懂BIOS是怎么实现的也没有什么关系,我们只要去百度下载一个ACPI的Spec,不就可以知道BIOS中具体的工作是做什么了吗?只要了解了BIOS和内核之间是要完成什么样的事情,对于我们驱动工程师来说就已经足够了。

接下来我们来看看在X86 Linux内核的启动过程中,是如何去识别BIOS传递过来的Hardware ID的?

不管是ARM架构的还是X86架构的CPU,在启动Linux内核的时候一定要进入start_kernel函数,这个函数位于:

内核源码/init/main.c

在这个函数中,会做操作系统的设备等一系列初始化,与ACPI最关键的地方在这个函数:acpi_early_init,这里完成的工作主要有如下:

代码语言:javascript
复制
 1acpi_early_init
 2acpi_reallocate_root_table
 3acpi_initialize_subsystem
 4(drivers/acpi/acpica/Tbxfload.c)
 51.acpi_load_tables:
 6--->acpi_status __init acpi_load_tables(void)
 72.acpi_tb_load_namespace:
 8--->static acpi_status acpi_tb_load_namespace(void)  
 93.acpi_ns_load_table
10在table中会得到一系列参数,包括Hardware ID,需要根据不同的参数表来解析
11--->
12(1)acpi_ut_acquire_mutex
13(2)acpi_tb_is_table_loaded
14(3)acpi_tb_allocate_owner_id
15(4)acpi_ns_parse_table

原来,内核就是这样来获取BIOS传递过来的table的,这个table中就会包括Hardware ID,当然还会有其它的ID,具体请参考ACPI的Spec,根据Linux实现的驱动模型,那么有设备,自然就要有驱动,驱动和设备要相辅相成,在:内核源码/drivers/acpi/bus.c中就实现了acpi的驱动,在这个文件中,我们看到:

代码语言:javascript
复制
 1static int __init acpi_init(void)
 2{
 3    int result;
 4
 5    if (acpi_disabled) {
 6        printk(KERN_INFO PREFIX "Interpreter disabled.\n");
 7        return -ENODEV;
 8    }
 9
10    acpi_kobj = kobject_create_and_add("acpi", firmware_kobj);
11    if (!acpi_kobj) {
12        printk(KERN_WARNING "%s: kset create error\n", __func__);
13        acpi_kobj = NULL;
14    }
15
16    init_acpi_device_notify();
17    result = acpi_bus_init();
18    if (result) {
19        disable_acpi();
20        return result;
21    }
22
23    pci_mmcfg_late_init();
24    acpi_scan_init();
25    acpi_ec_init();
26    acpi_debugfs_init();
27    acpi_sleep_proc_init();
28    acpi_wakeup_device_init();
29    return 0;
30}

那么acpi_init函数又是怎么被内核调用的呢?通过subsys_initcall(acpi_init)这个宏来调用,我们将subsys_initcall展开看看,在内核源码/include/init.h

代码语言:javascript
复制
1#define subsys_initcall(fn)        __define_initcall(fn, 4)

将__define_initcall(fn,4)这个宏继续展开:

代码语言:javascript
复制
1#define __define_initcall(fn, id) \
2    static initcall_t __initcall_##fn##id __used \
3    __attribute__((__section__(".initcall" #id ".init"))) = fn; \
4    LTO_REFERENCE_INITCALL(__initcall_##fn##id)

其中initcall_t是函数指针,原型:typedef int (*initcall_t)(void);

属性 __attribute__((__section__())) 则表示把对象放在一个这个由括号中的名称所指代的section中,这个对象就是我们的acpi_init函数。由此可见__define_initcall主要是完成以下几个功能:

(1)声明一个名称为__initcall_##fn的函数指针;

(2) 将这个函数指针初始化为fn;

(3) 编译的时候需要把这个函数指针变量放置到名称为 ".initcall" level ".init"的section中。

而对应的这些.include,level,.init定义在Vmlinux.lds.h中,这个文件在内核源码/include/asm-generic/Vmlinux.lds.h中:

在Linux4.0的内核实现如下:

代码语言:javascript
复制
 1#define INIT_CALLS_LEVEL(level)                        \
 2        VMLINUX_SYMBOL(__initcall##level##_start) = .;      \
 3        *(.initcall##level##.init)              \
 4        *(.initcall##level##s.init)             \
 5#define INIT_CALLS                            \
 6        VMLINUX_SYMBOL(__initcall_start) = .;           \
 7        *(.initcallearly.init)                  \
 8        INIT_CALLS_LEVEL(0)                 \
 9        INIT_CALLS_LEVEL(1)                 \
10        INIT_CALLS_LEVEL(2)                 \
11        INIT_CALLS_LEVEL(3)                 \
12        INIT_CALLS_LEVEL(4)                 \
13        INIT_CALLS_LEVEL(5)                 \
14        INIT_CALLS_LEVEL(rootfs)                \
15        INIT_CALLS_LEVEL(6)                 \
16        INIT_CALLS_LEVEL(7)                 \
17        VMLINUX_SYMBOL(__initcall_end) = .;

__initcall_start和__initcall_end以及INITCALLS中定义的SECTION都是在arch/x86/kernel/vmlinux.lds.S中放在.init.begin段中的,如下代码,这是linux4.0内核中实现的:

代码语言:javascript
复制
 1SECTIONS{
 2    ......
 3/* Init code and data - will be freed after init */
 4    . = ALIGN(PAGE_SIZE);
 5    .init.begin : AT(ADDR(.init.begin) - LOAD_OFFSET) {
 6        __init_begin = .; /* paired with __init_end */
 7    }
 8    ......
 9    . = ALIGN(PAGE_SIZE);
10
11    /* freed after init ends here */
12    .init.end : AT(ADDR(.init.end) - LOAD_OFFSET) {
13        __init_end = .;
14    }
15    ......
16}

而这些SECTION里的函数在初始化时被顺序执行,具体的调用流程是这样的:

rest_init ====> kernel_thread(kernel_init, NULL, CLONE_FS) ====> kernel_init ====> kernel_init_freeable ====>do_basic_setup ====> do_initcalls

在内核启动的最后一步,开启一条内核线程来加载这些函数,从而成功装载acpi驱动。

代码语言:javascript
复制
1static void __init do_initcalls(void)
2{
3    int level;
4
5    for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
6        do_initcall_level(level);
7}

接下来再接着看acpi_init函数,这个函数中会调用acpi_scan_init函数,acpi_scan_init函数会完成如下:

1、注册ACPI总线

代码语言:javascript
复制
1result = bus_register(&acpi_bus_type);

2、完成与apci相关的一系列初始化

代码语言:javascript
复制
 1acpi_pci_root_init();
 2acpi_pci_link_init();
 3acpi_processor_init();
 4acpi_lpss_init();
 5acpi_apd_init();
 6acpi_cmos_rtc_init();
 7acpi_container_init();
 8acpi_memory_hotplug_init();
 9acpi_pnp_init();
10acpi_int340x_thermal_init();

3、重点:调用acpi_bus_scan函数

在这个函数中会继续调用acpi_walk_namespace(ACPI_TYPE_ANY, handle, ACPI_UINT32_MAX,acpi_bus_check_add, NULL, NULL, &device);

注册acpi_bus_check_add函数,在acpi_bus_check_add函数中继续调用:acpi_add_single_object函数:

static int acpi_add_single_object(struct acpi_device **child,acpi_handle handle, int type,unsigned long long sta)

在acpi_add_single_object函数中的主要操作:

1、调用acpi_init_device_object等完成acpi设备、电源管理相关等的初始化,详情见后面分析

2、调用acpi_device_add获取设备的HID信息,实际上是通过链表的遍历形式去获取

代码语言:javascript
复制
 1 list_for_each_entry(acpi_device_bus_id, &acpi_bus_id_list, node) {
 2       if (!strcmp(acpi_device_bus_id->bus_id,
 3    acpi_device_hid(device))) {
 4    acpi_device_bus_id->instance_no++;
 5    found = 1;
 6    kfree(new_bus_id);
 7    break;
 8    }
 9    }
10    const char *acpi_device_hid(struct acpi_device *device)
11    {
12        struct acpi_hardware_id *hid;
13        //判断链表是否为空,如果为空,返回无效的hid,其实是一个字串:"device"
14        if (list_empty(&device->pnp.ids))
15            return dummy_hid;
16        //通过list成员返回该结构体的起始地址,也就是acpi_hardware_id这个结构体的起始地址
17        hid = list_first_entry(&device->pnp.ids, struct acpi_hardware_id, list);
18        //找到该结构体的起始地址后,即可以获得结构体中的id成员,这个id就是我们当前要获取的HID
19        return hid->id;
20        }

3、通过dev_set_name函数

代码语言:javascript
复制
1 dev_set_name(&device->dev, "%s:%02x", acpi_device_bus_id->bus_id, acpi_device_bus_id->instance_no);

设置在文件系统中/sys/devices/XXX下面的name

4、调用acpi_init_device_object函数:

代码语言:javascript
复制
1void acpi_init_device_object(struct acpi_device *device, acpi_handle handle,int type, unsigned long long sta)

(1)、初始化内核链表用来存储pnp设备中关于的链表等其它的信息

代码语言:javascript
复制
1INIT_LIST_HEAD(&device->pnp.ids);
2device->device_type = type;
3device->handle = handle;
4....

(2)、调用acpi_set_pnp_ids将ids的保存到ids中,具体操作见后面的剖析

5、调用acpi_set_pnp_ids函数:

代码语言:javascript
复制
1static void acpi_set_pnp_ids(acpi_handle handle, struct acpi_device_pnp *pnp,int device_type)
2

首先会根据swicth语句来判断设备类型:device_type,这里找到的是ACPI总线的设备类型ACPI_BUS_TYPE_DEVICE

代码语言:javascript
复制
1switch (device_type)
2{
3...
4case ACPI_BUS_TYPE_DEVICE:
5...
6}
7case ACPI_BUS_TYPE_DEVICE:

在该选项ACPI_BUS_TYPE_DEVICE中:

5.1 首先会判断acpi句柄是否为ACPI的根对象,如果是,则会直接添加id节点到pnp->ids的链表中去。

5.2 接下来,调用acpi_get_object_info函数:

代码语言:javascript
复制
1acpi_status acpi_get_object_info(acpi_handle handle,struct acpi_device_info **return_buffer)
2

通过acpi_get_object_info这个函数得到设备的_HID和_CIDs信息,获取之前需要对命名空间的句柄进行转换,怎么转?

通过acpi_ns_validate_handle这个函数转。

代码语言:javascript
复制
1struct acpi_namespace_node *acpi_ns_validate_handle(acpi_handle handle)

acpi_ns_validate_handle 对传入的名字空间句柄转换为名字空间节点,这是在处理根节点的特殊情况。

这个句柄其实是:

代码语言:javascript
复制
1typedef void *acpi_handle;    /* Actually a ptr to a NS Node */

为什么是Object?在ACPI标准手册上关于ASL语言中可以查询到这样的描述:

ObjectType must have. A fixed list is written as ( a , b , c , … ) where the number of arguments depends on the specific ObjectType , and some elements can be nested objects, that is (a, b, (q, r, s,t), d) .

大致意思是,对象类型一定要包含,它是一个固定的列表写成(a,b,c...)参数,取决于特定的对象类型,有些元素也是可以嵌套的,比如(a,b,(q,r,s,t),d)。

该函数中会尝试去判断函数传过来的参数--句柄是否存在,或者句柄是否为根对象:

if ((!handle) || (handle == ACPI_ROOT_OBJECT)),只要有一个成立,则会return (acpi_gbl_root_node);

接下来尝试校验句柄:

if (ACPI_GET_DESCRIPTOR_TYPE(handle) != ACPI_DESC_TYPE_NAMED)

如果该句柄不是描述命名空间类型的句柄,则会return (NULL);

以上条件都不满足,则:return (ACPI_CAST_PTR(struct acpi_namespace_node, handle));

#define ACPI_CAST_PTR(t, p) ((t *) (acpi_uintptr_t) (p))

它的原型是将命名空间句柄强制转换为apci命名空间节点,因为只有这样,才能正确的解析BIOS传递过来的关于_HID的信息。

(1)提供运行_HID/_UID/_SUB/_CID的方法,这里只看_HID的执行方法

代码语言:javascript
复制
 1if ((type == ACPI_TYPE_DEVICE) || (type == ACPI_TYPE_PROCESSOR)) {
 2    ...
 3    //得到_HID的信息
 4    status = acpi_ut_execute_HID(node, &hid);
 5    if (ACPI_SUCCESS(status)) {
 6        info_size += hid->length;
 7        valid |= ACPI_VALID_HID;
 8        }
 9        ...
10        }

(2)复制ID信息到返回缓冲区 or 保留区域 & 检测id是否为PCI根桥

acpi_ns_copy_device_id //将HID、UID、SUB和CIDs复制到返回缓冲区,如果是可变长度的字符串则会被复制到保留区域

acpi_ut_is_pci_root_bridge //对于HID和CID,会检查ID是否为PCI根桥,如果是,则要info->flags |= ACPI_PCI_ROOT_BRIDGE;

3、保存HID等ID的信息到device的pnp->ids里,这里只分析HID,同样是利用了内核链表的尾插机制,将id源源不断的接在链表的尾部

代码语言:javascript
复制
 1if (info->valid & ACPI_VALID_HID) {
 2    acpi_add_id(pnp, info->hardware_id.string);
 3    pnp->type.platform_id = 1;
 4}
 5static void acpi_add_id(struct acpi_device_pnp *pnp, const char *dev_id)
 6    最关键的一步:
 7    list_add_tail(&id->list, &pnp->ids);
 8    如果没有拥有相关的HID,可以直接对Handle进行添加,而不用通过BIOS去获取:
 9    if (acpi_is_video_device(handle))
10        acpi_add_id(pnp, ACPI_VIDEO_HID);
11    else if (acpi_bay_match(handle))
12        acpi_add_id(pnp, ACPI_BAY_HID);
13    else if (acpi_dock_match(handle))
14        acpi_add_id(pnp, ACPI_DOCK_HID);
15    else if (acpi_ibm_smbus_match(handle))
16        acpi_add_id(pnp, ACPI_SMBUS_IBM_HID);
17    else if (list_empty(&pnp->ids) &&
18        acpi_object_is_system_bus(handle)) {
19        /* \_SB, \_TZ, LNXSYBUS */
20        acpi_add_id(pnp, ACPI_BUS_HID);
21        strcpy(pnp->device_name, ACPI_BUS_DEVICE_NAME);
22        strcpy(pnp->device_class, ACPI_BUS_CLASS);
23    }

例如:#define ACPI_BAY_HID "LNXIOBAY",那么PNP设备又是如何被加载到ACPI中的呢?而Hardware ID传进来的字符串又是如何被PNP识别的呢?接下来请看内核源码/drivers/acpi/acpi_pnp.c

代码语言:javascript
复制
1void __init acpi_pnp_init(void)
2{
3    acpi_scan_add_handler(&acpi_pnp_handler);
4}

acpi_pnp_init这个函数是在acpi_scan_init中被调用的,也就是前面讲到的。接下来我们来看看acpi_scan_add_handler这个函数:

代码语言:javascript
复制
1int acpi_scan_add_handler(struct acpi_scan_handler *handler)
2{
3    if (!handler)
4        return -EINVAL;
5
6    list_add_tail(&handler->list_node, &acpi_scan_handlers_list);
7    return 0;
8}

很明显,这个函数完成的功能就是将节点插入到链表中去,插入的是什么节点?我们来看看acpi_pnp_handler:

代码语言:javascript
复制
1static struct acpi_scan_handler acpi_pnp_handler = {
2    .ids = acpi_pnp_device_ids,
3    .match = acpi_pnp_match,
4    .attach = acpi_pnp_attach,
5};

这是一个结构体变量,这里的ids其实就是一个字符串,这个字符串就是acpi的设备id,只不过在这被初始化成了pnp设备id,其实是一个意思,因为PNP设备是注册在ACPI之上的。

代码语言:javascript
复制
1struct acpi_device_id {
2    __u8 id[ACPI_ID_LEN];
3    kernel_ulong_t driver_data;
4};
代码语言:javascript
复制
1static const struct acpi_device_id acpi_pnp_device_ids[] = {
2    /* pata_isapnp */
3    {"PNP0600"},        /* Generic ESDI/IDE/ATA compatible hard disk controller */
4    /* floppy */
5    {"PNP0700"},
6    /* ipmi_si */
7    {"IPI0001"},
8    ......
9};

而acpi_pnp_match是完成对BIOS传递过来的ID与这里的ID进行比较,如果存在这个ID,才会将对应的驱动注册到内核中去,这样内核才会去执行对应的驱动:

代码语言:javascript
复制
 1static bool matching_id(char *idstr, char *list_id)
 2{
 3    int i;
 4    if (memcmp(idstr, list_id, 3)){
 5        return false;
 6    }
 7
 8    for (i = 3; i < 7; i++) {
 9        char c = toupper(idstr[i]);
10
11        if (!isxdigit(c)
12            || (list_id[i] != 'X' && c != toupper(list_id[i])))
13            return false;
14    }
15    return true;
16}
17
18static bool acpi_pnp_match(char *idstr, const struct acpi_device_id **matchid)
19{
20    const struct acpi_device_id *devid;
21
22    for (devid = acpi_pnp_device_ids; devid->id[0]; devid++) {
23        if (matching_id(idstr, (char *)devid->id)) {
24            if (matchid)
25                *matchid = devid;
26
27            return true;
28        }
29        }
30
31    return false;
32}
33
34static int acpi_pnp_attach(struct acpi_device *adev,
35               const struct acpi_device_id *id)
36{
37    return 1;
38}

至此,我们已经完全明白内核是如何接收到BIOS传过来的Hardware ID的整个流程,确实是非常难的,简单的问题被复杂化,但没有办法,因为要统一管理的东西太多太多了,所以一定需要一个模型来进行管理。如果我们不想使用BIOS与ACPI的机制,完全也可以绕开这个流程,用标准的Linux驱动模型去实现,不过还是建议,还是使用标准的ACPI的流程,这样才有助于软件工程项目管理。

参考文章:

http://blog.csdn.net/jiangwei0512

http://blog.csdn.net/morixinguan/article/details/79138325

http://blog.chinaunix.net/uid-27717694-id-3624294.html

http://blog.csdn.net/wh_19910525/article/details/16370863

https://www.ibm.com/developerworks/cn/linux/l-acpi/part1/

http://www.latelee.org/embedded-linux/kernel-note-7%EF%BC%8Dintel-lpc_ich-driver.html

http://www.latelee.org/embedded-linux/kernel-note-10-intel-gpio-driver.html

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-03-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 嵌入式开发圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
项目管理
CODING 项目管理(CODING Project Management,CODING-PM)工具包含迭代管理、需求管理、任务管理、缺陷管理、文件/wiki 等功能,适用于研发团队进行项目管理或敏捷开发实践。结合敏捷研发理念,帮助您对产品进行迭代规划,让每个迭代中的需求、任务、缺陷无障碍沟通流转, 让项目开发过程风险可控,达到可持续性快速迭代。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档