Currently there is no support for GPE block devices described in the ACPI specification. However there is a need to implement it as new platforms are coming that will make use of that feature. Add new acpi_scan_handler, that will attach itself to devices with "ACPI0006" _HID. Implement .attach() callback. Check for _CRS and _PRS objects, as their presence is required in non-FADT GPE block device, per ACPI specification. Extract address ranges/irq from _CRS/_PRS object. Call acpi_install_gpe_block() from ACPICA to install the GPE block. Then call acpi_update_all_gpes() to initialize newly installed GPE block. Link: https://uefi.org/specs/ACPI/6.5/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#gpe-block-device Suggested-by: Rafael J. Wysocki <rafael.j.wysocki@xxxxxxxxx> Signed-off-by: Michal Wilczynski <michal.wilczynski@xxxxxxxxx> --- drivers/acpi/scan.c | 111 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 7cae369ca33b..f00bfe63fbcf 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -2174,6 +2174,116 @@ static int acpi_scan_attach_handler(struct acpi_device *device) return ret; } +static const struct acpi_device_id gpe_block_ids[] = { + {"ACPI0006"}, + {} +}; + +static int acpi_gpe_fill_address(struct acpi_generic_address *gpe_block_address, + u8 type, + int *register_count, + struct resource_entry *rentry) +{ + if (gpe_block_address->address) + return -EINVAL; + + gpe_block_address->address = rentry->res->start; + gpe_block_address->space_id = type; + *register_count = (rentry->res->end - rentry->res->start); + *register_count /= ACPI_GPE_REGISTER_WIDTH; + + return 0; +} + +static int acpi_gpe_block_attach(struct acpi_device *adev, + const struct acpi_device_id *not_used) +{ + struct acpi_generic_address gpe_block_address = {}; + struct list_head resource_list; + struct resource_entry *rentry; + int register_count; + acpi_status status; + u32 irq = 0; + int ret; + + if (!acpi_has_method(adev->handle, METHOD_NAME__CRS) && + !acpi_has_method(adev->handle, METHOD_NAME__PRS)) + /* It's not a block GPE if it doesn't contain _CRS or _PRS. + * Specification says that ACPI0006 _HID can also refer to + * FADT described GPE's. It's not an error, so it's reasonable + * to return 0. + */ + return 0; + + INIT_LIST_HEAD(&resource_list); + ret = acpi_dev_get_resources(adev, &resource_list, NULL, NULL); + if (ret) + return ret; + + list_for_each_entry(rentry, &resource_list, node) { + switch (resource_type(rentry->res)) { + case IORESOURCE_IO: + ret = acpi_gpe_fill_address(&gpe_block_address, + ACPI_ADR_SPACE_SYSTEM_IO, + ®ister_count, + rentry); + if (ret) { + acpi_handle_err(adev->handle, + "Multiple IO blocks in GPE block\n"); + return ret; + } + break; + case IORESOURCE_MEM: + ret = acpi_gpe_fill_address(&gpe_block_address, + ACPI_ADR_SPACE_SYSTEM_MEMORY, + ®ister_count, + rentry); + if (ret) { + acpi_handle_err(adev->handle, + "Multiple MEM blocks in GPE block\n"); + return ret; + } + break; + case IORESOURCE_IRQ: + if (irq) { + acpi_handle_err(adev->handle, + "Multiple IRQ blocks in GPE block\n"); + return -EINVAL; + } + irq = rentry->res->start; + break; + default: + break; + } + } + + acpi_dev_free_resource_list(&resource_list); + + /* GPE block needs to define one address range and one irq line */ + if (!gpe_block_address.address || !irq) + return -ENODEV; + + status = acpi_install_gpe_block(adev->handle, + &gpe_block_address, + register_count, + irq); + if (ACPI_FAILURE(status)) + return -EINVAL; + + status = acpi_update_all_gpes(); + if (ACPI_FAILURE(status)) { + acpi_remove_gpe_block(adev->handle); + return -ENODEV; + } + + return 1; +} + +static struct acpi_scan_handler gpe_block_device_handler = { + .ids = gpe_block_ids, + .attach = acpi_gpe_block_attach, +}; + static int acpi_bus_attach(struct acpi_device *device, void *first_pass) { bool skip = !first_pass && device->flags.visited; @@ -2623,6 +2733,7 @@ void __init acpi_scan_init(void) acpi_init_lpit(); acpi_scan_add_handler(&generic_device_handler); + acpi_scan_add_handler(&gpe_block_device_handler); /* * If there is STAO table, check whether it needs to ignore the UART -- 2.41.0