日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux x86架构下ACPI PNP Hardware ID的识别机制

發(fā)布時(shí)間:2024/8/1 linux 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux x86架构下ACPI PNP Hardware ID的识别机制 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

參考博客:

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 ? ?

關(guān)于Hardware ID的用途,在前面已經(jīng)大致的解釋了它的用途,以及它和ACPI以及PNP之間的關(guān)系:

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

? ? 接下來主要來看看在Linux內(nèi)核中,內(nèi)核是怎么去通過BIOS傳遞的參數(shù)表,傳遞對(duì)應(yīng)的字串,然后內(nèi)核又是如何來解析它,最終為L(zhǎng)inux驅(qū)動(dòng)統(tǒng)一模型所用。其實(shí)ARM和X86的驅(qū)動(dòng)本質(zhì)并沒有太大的區(qū)別,都是有了一個(gè)基地址,然后依靠偏移來獲取定位寄存器,寫值驅(qū)動(dòng)設(shè)備。ARM也會(huì)去解析uboot傳遞的參數(shù),然而并沒有那么的復(fù)雜,而X86對(duì)設(shè)備驅(qū)動(dòng)進(jìn)行了統(tǒng)一的管理,這點(diǎn)與ARM軟件架構(gòu)的實(shí)現(xiàn)是有很大區(qū)別的,比如,讓GPIO的基地址在BIOS中進(jìn)行統(tǒng)一分配,使用BIOS來統(tǒng)一管理電源等等。。。而ARM就相對(duì)來說簡(jiǎn)單很多,沒有這么多的步驟,使用標(biāo)準(zhǔn)的Linux驅(qū)動(dòng)模型+類ARM裸機(jī)操作(操作的地址需要進(jìn)行映射,將物理地址轉(zhuǎn)換成虛擬地址,這點(diǎn)和單片機(jī)是不太一樣的),也就是說,如果掌握了ARM的驅(qū)動(dòng)模型,同樣的,只要我們擁有X86架構(gòu)的CPU數(shù)據(jù)手冊(cè),我們同樣也可以使用ARM的思想來完成對(duì)X86架構(gòu)的CPU的各類驅(qū)動(dòng)BSP的編寫。

以下是較為重要的結(jié)構(gòu)體:

在這個(gè)結(jié)構(gòu)體里發(fā)現(xiàn),_HID是以內(nèi)核鏈表成員的形式加載進(jìn)Linux內(nèi)核的 (內(nèi)核源碼/include/acpi/Acpi_bus.h) struct acpi_hardware_id {struct list_head list; char *id; };//ACPI的對(duì)象類型結(jié)構(gòu)體 typedef u32 acpi_object_type; //ACPI對(duì)象 union acpi_object {acpi_object_type type; /* See definition of acpi_ns_type for values */struct {acpi_object_type type; /* ACPI_TYPE_INTEGER */u64 value; /* The actual number */} integer;struct {acpi_object_type type; /* ACPI_TYPE_STRING */u32 length; /* # of bytes in string, excluding trailing null */char *pointer; /* points to the string value */} string;struct {acpi_object_type type; /* ACPI_TYPE_BUFFER */u32 length; /* # of bytes in buffer */u8 *pointer; /* points to the buffer */} buffer;struct {acpi_object_type type; /* ACPI_TYPE_PACKAGE */u32 count; /* # of elements in package */union acpi_object *elements; /* Pointer to an array of ACPI_OBJECTs */} package;struct {acpi_object_type type; /* ACPI_TYPE_LOCAL_REFERENCE */acpi_object_type actual_type; /* Type associated with the Handle */acpi_handle handle; /* object reference */} reference;struct {acpi_object_type type; /* ACPI_TYPE_PROCESSOR */u32 proc_id;acpi_io_address pblk_address;u32 pblk_length;} processor;struct {acpi_object_type type; /* ACPI_TYPE_POWER */u32 system_level;u32 resource_order;} power_resource; };typedef char acpi_bus_id[8]; typedef unsigned long acpi_bus_address; typedef char acpi_device_name[40]; typedef char acpi_device_class[20];//這是一個(gè)位段,用來描述pnp中的類型 struct acpi_pnp_type {u32 hardware_id:1;u32 bus_address:1;u32 platform_id:1;u32 reserved:29; };//acpi的pnp設(shè)備,包括對(duì)象名稱、ID類型、以及各種ID,具體參考ACPI spec struct acpi_device_pnp {acpi_bus_id bus_id; /* Object name */struct acpi_pnp_type type; /* ID type */acpi_bus_address bus_address; /* _ADR */char *unique_id; /* _UID */struct list_head ids; /* _HID and _CIDs */acpi_device_name device_name; /* Driver-determined */acpi_device_class device_class; /* " */union acpi_object *str_obj; /* unicode string for _STR method */ };

????那X86架構(gòu)的CPU在啟動(dòng)內(nèi)核的時(shí)候又是如何知道BIOS傳遞過來的HID參數(shù)?我們可以來看看X86架構(gòu)在Linux下的啟動(dòng)流程: ? ?

? ? 不管是在ARM還是X86平臺(tái),本質(zhì)都是將一系列代碼拷貝到對(duì)應(yīng)的存儲(chǔ)器對(duì)應(yīng)的區(qū)域中,這個(gè)存儲(chǔ)器一般是NOR FLASH或者NAND FLASH,當(dāng)然現(xiàn)在還有EMMC等其它的存儲(chǔ)設(shè)備,然后在執(zhí)行Uboot(ARM的叫法,也叫bootloader,用來引導(dǎo)內(nèi)核,而X86用的是BIOS,也差不多)中,通過地址跳轉(zhuǎn)的形式去啟動(dòng)內(nèi)核,如果是我們自己實(shí)現(xiàn)的Bootloader,一般會(huì)在作為uboot的第一、第二階段以后,通過如下的代碼跳轉(zhuǎn)到操作系統(tǒng)啟動(dòng)的模式:

/* 0. 幫內(nèi)核設(shè)置串口: 內(nèi)核啟動(dòng)的開始部分會(huì)從串口打印一些信息,但是內(nèi)核一開始沒有初始化串口 */uart0_init();/* 1. 從NAND FLASH里把內(nèi)核讀入內(nèi)存 */puts("Copy kernel from nand\n\r");nand_read(0x60000+64, (unsigned char *)0x30008000, 0x200000);puthex(0x1234ABCD);puts("\n\r");puthex(*p);puts("\n\r");/* 2. 設(shè)置參數(shù) */puts("Set boot params\n\r");setup_start_tag();setup_memory_tags();setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0");setup_end_tag();/* 3. 跳轉(zhuǎn)執(zhí)行 */puts("Boot kernel\n\r");theKernel = (void (*)(int, int, unsigned int))0x30008000;theKernel(0, 362, 0x30000100);

? ?如上代碼段,Linux內(nèi)核在啟動(dòng)的過程中會(huì)去解析ARM傳遞過去的參數(shù):noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0。

? ? ?ARM的啟動(dòng)相對(duì)來說比較簡(jiǎn)單: uboot----->內(nèi)核------>文件系統(tǒng)------>app,在uboot之前一般還會(huì)有IC廠商的固件驅(qū)動(dòng)代碼。

? ? 而X86架構(gòu)的CPU與ARM的啟動(dòng)形式就不太一樣,顯然比這里要復(fù)雜得多,由于BIOS的源代碼并不開放,所以我們也并不知道BIOS的內(nèi)幕具體是怎么實(shí)現(xiàn)的,但我們可以從以下這張圖可以得知X86架構(gòu)從BIOS到kernel的整個(gè)流程,在這里我們能夠得知,X86的OS和BIOS之間銜接的橋梁是ACPI,使用ACPI來對(duì)一些資源進(jìn)行統(tǒng)一管理,我們要獲取的這個(gè)Hardware ID其實(shí)就是ACPI Tables中的其中一個(gè)參數(shù)。


? ?到這里我們就明白了,不懂BIOS是怎么實(shí)現(xiàn)的也沒有什么關(guān)系,我們只要去百度下載一個(gè)ACPI的Spec,不就可以知道BIOS中具體的工作是做什么了嗎?只要了解了BIOS和內(nèi)核之間是要完成什么樣的事情,對(duì)于我們驅(qū)動(dòng)工程師來說就已經(jīng)足夠了。

? ?接下來我們來看看在X86 Linux內(nèi)核的啟動(dòng)過程中,是如何去識(shí)別BIOS傳遞過來的Hardware ID的?

? ?不管是ARM架構(gòu)的還是X86架構(gòu)的CPU,在啟動(dòng)Linux內(nèi)核的時(shí)候一定要進(jìn)入start_kernel函數(shù),這個(gè)函數(shù)位于:

? ?內(nèi)核源碼/init/main.c

? ?在這個(gè)函數(shù)中,會(huì)做操作系統(tǒng)的設(shè)備等一系列初始化,與ACPI最關(guān)鍵的地方在這個(gè)函數(shù):acpi_early_init,這里完成的工作主要有如下:

acpi_early_init acpi_reallocate_root_table acpi_initialize_subsystem (drivers/acpi/acpica/Tbxfload.c) 1.acpi_load_tables: --->acpi_status __init acpi_load_tables(void) 2.acpi_tb_load_namespace: --->static acpi_status acpi_tb_load_namespace(void) 3.acpi_ns_load_table 在table中會(huì)得到一系列參數(shù),包括Hardware ID,需要根據(jù)不同的參數(shù)表來解析 ---> (1)acpi_ut_acquire_mutex (2)acpi_tb_is_table_loaded (3)acpi_tb_allocate_owner_id (4)acpi_ns_parse_table

? ? ?原來,內(nèi)核就是這樣來獲取BIOS傳遞過來的table的,這個(gè)table中就會(huì)包括Hardware ID,當(dāng)然還會(huì)有其它的ID,具體請(qǐng)參考ACPI的Spec,根據(jù)Linux實(shí)現(xiàn)的驅(qū)動(dòng)模型,那么有設(shè)備,自然就要有驅(qū)動(dòng),驅(qū)動(dòng)和設(shè)備要相輔相成,在:內(nèi)核源碼/drivers/acpi/bus.c中就實(shí)現(xiàn)了acpi的驅(qū)動(dòng),在這個(gè)文件中,我們看到:

static int __init acpi_init(void) {int result;if (acpi_disabled) {printk(KERN_INFO PREFIX "Interpreter disabled.\n");return -ENODEV;}acpi_kobj = kobject_create_and_add("acpi", firmware_kobj);if (!acpi_kobj) {printk(KERN_WARNING "%s: kset create error\n", __func__);acpi_kobj = NULL;}init_acpi_device_notify();result = acpi_bus_init();if (result) {disable_acpi();return result;}pci_mmcfg_late_init();acpi_scan_init();acpi_ec_init();acpi_debugfs_init();acpi_sleep_proc_init();acpi_wakeup_device_init();return 0; }

? ? 那么acpi_init函數(shù)又是怎么被內(nèi)核調(diào)用的呢?通過subsys_initcall(acpi_init)這個(gè)宏來調(diào)用,我們將subsys_initcall展開看看,在內(nèi)核源碼/include/init.h

#define subsys_initcall(fn) __define_initcall(fn, 4)

將__define_initcall(fn,4)這個(gè)宏展開

#define __define_initcall(fn, id) \static initcall_t __initcall_##fn##id __used \__attribute__((__section__(".initcall" #id ".init"))) = fn; \LTO_REFERENCE_INITCALL(__initcall_##fn##id)

? ? 其中initcall_t是函數(shù)指針,原型:typedef int (*initcall_t)(void);

? ?屬性 __attribute__((__section__())) 則表示把對(duì)象放在一個(gè)這個(gè)由括號(hào)中的名稱所指代的section中,這個(gè)對(duì)象就是我們的acpi_init函數(shù)。由此可見__define_initcall主要是完成以下幾個(gè)功能:

(1)聲明一個(gè)名稱為__initcall_##fn的函數(shù)指針;

(2) 將這個(gè)函數(shù)指針初始化為fn;

(3) 編譯的時(shí)候需要把這個(gè)函數(shù)指針變量放置到名稱為 ".initcall" level ".init"的section中。

? ? 而對(duì)應(yīng)的這些.include,level,.init定義在Vmlinux.lds.h中,這個(gè)文件在內(nèi)核源碼/include/asm-generic/Vmlinux.lds.h中:

在Linux4.0的內(nèi)核實(shí)現(xiàn)如下:

#define INIT_CALLS_LEVEL(level) \VMLINUX_SYMBOL(__initcall##level##_start) = .; \*(.initcall##level##.init) \*(.initcall##level##s.init) \#define INIT_CALLS \VMLINUX_SYMBOL(__initcall_start) = .; \*(.initcallearly.init) \INIT_CALLS_LEVEL(0) \INIT_CALLS_LEVEL(1) \INIT_CALLS_LEVEL(2) \INIT_CALLS_LEVEL(3) \INIT_CALLS_LEVEL(4) \INIT_CALLS_LEVEL(5) \INIT_CALLS_LEVEL(rootfs) \INIT_CALLS_LEVEL(6) \INIT_CALLS_LEVEL(7) \VMLINUX_SYMBOL(__initcall_end) = .;

__initcall_start和__initcall_end以及INITCALLS中定義的SECTION都是在arch/x86/kernel/vmlinux.lds.S中放在.init.begin段中的,如下,這是linux4.0內(nèi)核中實(shí)現(xiàn)的。

SECTIONS{...... /* Init code and data - will be freed after init */. = ALIGN(PAGE_SIZE);.init.begin : AT(ADDR(.init.begin) - LOAD_OFFSET) {__init_begin = .; /* paired with __init_end */}....... = ALIGN(PAGE_SIZE);/* freed after init ends here */.init.end : AT(ADDR(.init.end) - LOAD_OFFSET) {__init_end = .;}...... }

????而這些SECTION里的函數(shù)在初始化時(shí)被順序執(zhí)行,具體的調(diào)用流程是這樣的:

rest_init ====> ?kernel_thread(kernel_init, NULL, CLONE_FS) ====>?kernel_init ====>?kernel_init_freeable ====>

do_basic_setup ====>?do_initcalls

在內(nèi)核啟動(dòng)的最后一步,開啟一條內(nèi)核線程來加載這些函數(shù),從而成功裝載acpi驅(qū)動(dòng)。

static void __init do_initcalls(void) {int level;for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)do_initcall_level(level); }

接下來再接著看acpi_init函數(shù),這個(gè)函數(shù)中會(huì)調(diào)用acpi_scan_init函數(shù),acpi_scan_init函數(shù)會(huì)完成如下:

1、注冊(cè)ACPI的驅(qū)動(dòng)模型

result = bus_register(&acpi_bus_type);

2、完成與apci相關(guān)的一系列初始化:

acpi_pci_root_init(); acpi_pci_link_init(); acpi_processor_init(); acpi_lpss_init(); acpi_apd_init(); acpi_cmos_rtc_init(); acpi_container_init(); acpi_memory_hotplug_init(); acpi_pnp_init(); acpi_int340x_thermal_init();

3、重點(diǎn):調(diào)用acpi_bus_scan函數(shù)

在這個(gè)函數(shù)中會(huì)繼續(xù)調(diào)用acpi_walk_namespace(ACPI_TYPE_ANY, handle, ACPI_UINT32_MAX,

acpi_bus_check_add, NULL, NULL, &device);

注冊(cè)acpi_bus_check_add函數(shù),在acpi_bus_check_add函數(shù)中繼續(xù)調(diào)用:acpi_add_single_object函數(shù):

static int acpi_add_single_object(struct acpi_device **child,
?acpi_handle handle, int type,

?unsigned long long sta)

在acpi_add_single_object函數(shù)中的主要操作:
1、調(diào)用acpi_init_device_object等完成acpi設(shè)備、電源管理相關(guān)等的初始化,詳情見后面分析
2、調(diào)用acpi_device_add獲取設(shè)備的HID信息,實(shí)際上是通過鏈表的遍歷形式去獲取

list_for_each_entry(acpi_device_bus_id, &acpi_bus_id_list, node) {if (!strcmp(acpi_device_bus_id->bus_id,acpi_device_hid(device))) {acpi_device_bus_id->instance_no++;found = 1;kfree(new_bus_id);break;}}const char *acpi_device_hid(struct acpi_device *device){struct acpi_hardware_id *hid;//判斷鏈表是否為空,如果為空,返回?zé)o效的hid,其實(shí)是一個(gè)字串:"device"if (list_empty(&device->pnp.ids))return dummy_hid;//通過list成員返回該結(jié)構(gòu)體的起始地址,也就是acpi_hardware_id這個(gè)結(jié)構(gòu)體的起始地址hid = list_first_entry(&device->pnp.ids, struct acpi_hardware_id, list);//找到該結(jié)構(gòu)體的起始地址后,即可以獲得結(jié)構(gòu)體中的id成員,這個(gè)id就是我們當(dāng)前要獲取的HIDreturn hid->id;}3、通過dev_set_name(&device->dev, "%s:%02x", acpi_device_bus_id->bus_id, acpi_device_bus_id->instance_no);

設(shè)置在/sys/devices/XXX下面的name

4、調(diào)用acpi_init_device_object函數(shù):

void acpi_init_device_object(struct acpi_device *device, acpi_handle handle,

int type, unsigned long long sta)

(1)、初始化內(nèi)核鏈表用來存儲(chǔ)pnp設(shè)備中關(guān)于的鏈表等其它的信息
INIT_LIST_HEAD(&device->pnp.ids);
device->device_type = type;
device->handle = handle;
....

(2)、調(diào)用acpi_set_pnp_ids將ids的保存到ids中,具體操作見后面的剖析

5、調(diào)用acpi_set_pnp_ids函數(shù):

static void acpi_set_pnp_ids(acpi_handle handle, struct acpi_device_pnp *pnp,

int device_type)

首先會(huì)根據(jù)swicth語句來判斷設(shè)備類型:device_type,這里找到的是ACPI總線的設(shè)備類型ACPI_BUS_TYPE_DEVICE
switch (device_type)
{
...
case ACPI_BUS_TYPE_DEVICE:
...

}

case ACPI_BUS_TYPE_DEVICE:
在該選項(xiàng)ACPI_BUS_TYPE_DEVICE中:
5.1 ?首先會(huì)判斷acpi句柄是否為ACPI的根對(duì)象,如果是,則會(huì)直接添加id節(jié)點(diǎn)到pnp->ids的鏈表中去。
5.2 ?接下來,調(diào)用acpi_get_object_info函數(shù):
????acpi_status acpi_get_object_info(acpi_handle handle,struct acpi_device_info **return_buffer)
????通過acpi_get_object_info這個(gè)函數(shù)得到設(shè)備的_HID和_CIDs信息,獲取之前需要對(duì)命名空間的句柄進(jìn)行轉(zhuǎn)換,怎么轉(zhuǎn)?
????通過struct acpi_namespace_node *acpi_ns_validate_handle(acpi_handle handle)這個(gè)函數(shù)轉(zhuǎn)。
????acpi_ns_validate_handle ?對(duì)傳入的名字空間句柄轉(zhuǎn)換為名字空間節(jié)點(diǎn),這是在處理根節(jié)點(diǎn)的特殊情況
這個(gè)句柄其實(shí)是: ? typedef void *acpi_handle; /* Actually a ptr to a NS Node */
????為什么是Object?在ACPI標(biāo)準(zhǔn)手冊(cè)上關(guān)于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) .?大致意思是,對(duì)象類型一定要包含,它是一個(gè)固定的列表寫成(a,b,c...)參數(shù),取決于特定的對(duì)象類型,有些元素也是可以嵌套的,比如(a,b,(q,r,s,t),d)。
????該函數(shù)中會(huì)嘗試去判斷函數(shù)傳過來的參數(shù)--句柄是否存在,或者句柄是否為根對(duì)象:
????if ((!handle) || (handle == ACPI_ROOT_OBJECT)),只要有一個(gè)成立,則會(huì)return (acpi_gbl_root_node);
接下來嘗試校驗(yàn)句柄:
????if (ACPI_GET_DESCRIPTOR_TYPE(handle) != ACPI_DESC_TYPE_NAMED)
????如果該句柄不是描述命名空間類型的句柄,則會(huì)return (NULL);
以上條件都不滿足,則:return (ACPI_CAST_PTR(struct acpi_namespace_node, handle));
#define ACPI_CAST_PTR(t, p) ? ? ? ? ? ? ((t *) (acpi_uintptr_t) (p))
????它的原型是將命名空間句柄強(qiáng)制轉(zhuǎn)換為apci命名空間節(jié)點(diǎn),因?yàn)橹挥羞@樣,才能正確的解析BIOS傳遞過來的關(guān)于_HID的信息。

(1)提供運(yùn)行_HID/_UID/_SUB/_CID的方法,這里只看_HID的執(zhí)行方法

if ((type == ACPI_TYPE_DEVICE) || (type == ACPI_TYPE_PROCESSOR)) {...//得到_HID的信息status = acpi_ut_execute_HID(node, &hid);if (ACPI_SUCCESS(status)) {info_size += hid->length;valid |= ACPI_VALID_HID;}...}(2)復(fù)制ID信息到返回緩沖區(qū) or 保留區(qū)域 & 檢測(cè)id是否為PCI根橋
????acpi_ns_copy_device_id ?//將HID、UID、SUB和CIDs復(fù)制到返回緩沖區(qū),如果是可變長(zhǎng)度的字符串則會(huì)被復(fù)制到保留區(qū)域

????acpi_ut_is_pci_root_bridge //對(duì)于HID和CID,會(huì)檢查ID是否為PCI根橋,如果是,則要info->flags |= ACPI_PCI_ROOT_BRIDGE;

3、保存HID等ID的信息到device的pnp->ids里,這里只分析HID,同樣是利用了內(nèi)核鏈表的尾插機(jī)制,將id源源不斷的接在鏈表的尾部

if (info->valid & ACPI_VALID_HID) {acpi_add_id(pnp, info->hardware_id.string);pnp->type.platform_id = 1; } static void acpi_add_id(struct acpi_device_pnp *pnp, const char *dev_id)最關(guān)鍵的一步:list_add_tail(&id->list, &pnp->ids);如果沒有擁有相關(guān)的HID,可以直接對(duì)Handle進(jìn)行添加,而不用通過BIOS去獲取:if (acpi_is_video_device(handle))acpi_add_id(pnp, ACPI_VIDEO_HID);else if (acpi_bay_match(handle))acpi_add_id(pnp, ACPI_BAY_HID);else if (acpi_dock_match(handle))acpi_add_id(pnp, ACPI_DOCK_HID);else if (acpi_ibm_smbus_match(handle))acpi_add_id(pnp, ACPI_SMBUS_IBM_HID);else if (list_empty(&pnp->ids) &&acpi_object_is_system_bus(handle)) {/* \_SB, \_TZ, LNXSYBUS */acpi_add_id(pnp, ACPI_BUS_HID);strcpy(pnp->device_name, ACPI_BUS_DEVICE_NAME);strcpy(pnp->device_class, ACPI_BUS_CLASS);}

例如:#define ACPI_BAY_HID "LNXIOBAY"

那么PNP設(shè)備又是如何被加載到ACPI中的呢?而Hardware ID傳進(jìn)來的字符串又是如何被PNP識(shí)別的呢?接下來請(qǐng)看:

內(nèi)核源碼/drivers/acpi/acpi_pnp.c

void __init acpi_pnp_init(void) {acpi_scan_add_handler(&acpi_pnp_handler); }

acpi_pnp_init這個(gè)函數(shù)是在acpi_scan_init中被調(diào)用的,也就是前面講到的。接下來我們來看看acpi_scan_add_handler這個(gè)函數(shù):

int acpi_scan_add_handler(struct acpi_scan_handler *handler) {if (!handler)return -EINVAL;list_add_tail(&handler->list_node, &acpi_scan_handlers_list);return 0; }

? ? ?很明顯,這個(gè)函數(shù)完成的功能就是將節(jié)點(diǎn)插入到鏈表中去,插入的是什么節(jié)點(diǎn)?我們來看看acpi_pnp_handler:

static struct acpi_scan_handler acpi_pnp_handler = {.ids = acpi_pnp_device_ids,.match = acpi_pnp_match,.attach = acpi_pnp_attach, };

? ? 這是一個(gè)結(jié)構(gòu)體變量,這里的ids其實(shí)就是一個(gè)字符串,這個(gè)字符串就是acpi的設(shè)備id,只不過在這被初始化成了pnp設(shè)備id,其實(shí)是一個(gè)意思,因?yàn)镻NP設(shè)備是注冊(cè)在ACPI之上的。

struct acpi_device_id {__u8 id[ACPI_ID_LEN];kernel_ulong_t driver_data; }; static const struct acpi_device_id acpi_pnp_device_ids[] = {/* pata_isapnp */{"PNP0600"}, /* Generic ESDI/IDE/ATA compatible hard disk controller *//* floppy */{"PNP0700"},/* ipmi_si */{"IPI0001"},...... };

? ? ?而acpi_pnp_match是完成對(duì)BIOS傳遞過來的ID與這里的ID進(jìn)行比較,如果存在這個(gè)ID,才會(huì)將對(duì)應(yīng)的驅(qū)動(dòng)注冊(cè)到內(nèi)核中去,這樣內(nèi)核才會(huì)去執(zhí)行對(duì)應(yīng)的驅(qū)動(dòng):

static bool matching_id(char *idstr, char *list_id) {int i;if (memcmp(idstr, list_id, 3)){return false;}for (i = 3; i < 7; i++) {char c = toupper(idstr[i]);if (!isxdigit(c)|| (list_id[i] != 'X' && c != toupper(list_id[i])))return false;}return true; }static bool acpi_pnp_match(char *idstr, const struct acpi_device_id **matchid) {const struct acpi_device_id *devid;for (devid = acpi_pnp_device_ids; devid->id[0]; devid++) {if (matching_id(idstr, (char *)devid->id)) {if (matchid)*matchid = devid;return true;}}return false; }static int acpi_pnp_attach(struct acpi_device *adev,const struct acpi_device_id *id) {return 1; }

? ? 至此,我們已經(jīng)完全明白內(nèi)核是如何接收到BIOS傳過來的Hardware ID的整個(gè)流程,確實(shí)是非常難的,簡(jiǎn)單的問題被復(fù)雜化,但沒有辦法,因?yàn)橐y(tǒng)一管理的東西太多太多了,所以一定需要一個(gè)模型來進(jìn)行管理。如果我們不想使用BIOS與ACPI的機(jī)制,完全也可以繞開這個(gè)流程,用標(biāo)準(zhǔn)的Linux驅(qū)動(dòng)模型去實(shí)現(xiàn),不過還是建議,還是使用標(biāo)準(zhǔn)的ACPI的流程,這樣才有助于軟件工程項(xiàng)目管理。





? ??







總結(jié)

以上是生活随笔為你收集整理的Linux x86架构下ACPI PNP Hardware ID的识别机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。