From f36e29e8da2f1b4a776b157e5f6c8368546da803 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Wed, 23 Oct 2024 15:19:50 +0200 Subject: arm: acpi: Add generic ACPI methods Add generic ACPI code to generate - MADT GICC - MADT GICD - MADT GICR - MADT GIC ITS - PPTT processor - PPTT cache as commonly used on arm platforms. Signed-off-by: Patrick Rudolph Reviewed-by: Simon Glass Cc: Tom Rini Cc: Simon Glass --- arch/arm/lib/Makefile | 1 + arch/arm/lib/acpi_table.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 arch/arm/lib/acpi_table.c (limited to 'arch/arm/lib') diff --git a/arch/arm/lib/Makefile b/arch/arm/lib/Makefile index 67275fba616..a7efed6771d 100644 --- a/arch/arm/lib/Makefile +++ b/arch/arm/lib/Makefile @@ -86,6 +86,7 @@ obj-y += psci-dt.o obj-$(CONFIG_DEBUG_LL) += debug.o obj-$(CONFIG_BLOBLIST) += xferlist.o +obj-$(CONFIG_GENERATE_ACPI_TABLE) += acpi_table.o # For EABI conformant tool chains, provide eabi_compat() ifneq (,$(findstring -mabi=aapcs-linux,$(PLATFORM_CPPFLAGS))) diff --git a/arch/arm/lib/acpi_table.c b/arch/arm/lib/acpi_table.c new file mode 100644 index 00000000000..286ed7cecaa --- /dev/null +++ b/arch/arm/lib/acpi_table.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Based on acpi.c from coreboot + * + * Copyright (C) 2024 9elements GmbH + */ + +#define LOG_CATEGORY LOGC_ACPI + +#include +#include +#include +#include + +void acpi_write_madt_gicc(struct acpi_madt_gicc *gicc, uint cpu_num, + uint perf_gsiv, ulong phys_base, ulong gicv, + ulong gich, uint vgic_maint_irq, u64 gicr_base, + ulong mpidr, uint efficiency) +{ + memset(gicc, '\0', sizeof(struct acpi_madt_gicc)); + gicc->type = ACPI_APIC_GICC; + gicc->length = sizeof(struct acpi_madt_gicc); + gicc->cpu_if_num = cpu_num; + gicc->processor_id = cpu_num; + gicc->flags = ACPI_MADTF_ENABLED; + gicc->perf_gsiv = perf_gsiv; + gicc->phys_base = phys_base; + gicc->gicv = gicv; + gicc->gich = gich; + gicc->vgic_maint_irq = vgic_maint_irq; + gicc->gicr_base = gicr_base; + gicc->mpidr = mpidr; + gicc->efficiency = efficiency; +} + +void acpi_write_madt_gicd(struct acpi_madt_gicd *gicd, uint gic_id, + ulong phys_base, uint gic_version) +{ + memset(gicd, '\0', sizeof(struct acpi_madt_gicd)); + gicd->type = ACPI_APIC_GICD; + gicd->length = sizeof(struct acpi_madt_gicd); + gicd->gic_id = gic_id; + gicd->phys_base = phys_base; + gicd->gic_version = gic_version; +} + +void acpi_write_madt_gicr(struct acpi_madt_gicr *gicr, + u64 discovery_range_base_address, + u32 discovery_range_length) +{ + memset(gicr, '\0', sizeof(struct acpi_madt_gicr)); + gicr->type = ACPI_APIC_GICR; + gicr->length = sizeof(struct acpi_madt_gicr); + gicr->discovery_range_base_address = discovery_range_base_address; + gicr->discovery_range_length = discovery_range_length; +} + +void acpi_write_madt_its(struct acpi_madt_its *its, + u32 its_id, + u64 physical_base_address) +{ + memset(its, '\0', sizeof(struct acpi_madt_its)); + its->type = ACPI_APIC_ITS; + its->length = sizeof(struct acpi_madt_its); + its->gic_its_id = its_id; + its->physical_base_address = physical_base_address; +} + +int acpi_pptt_add_proc(struct acpi_ctx *ctx, const u32 flags, const u32 parent, + const u32 proc_id, const u32 num_resources, + const u32 *resource_list) +{ + struct acpi_pptt_proc *proc = ctx->current; + int offset; + + offset = ctx->current - ctx->tab_start; + proc->hdr.type = ACPI_PPTT_TYPE_PROC; + proc->flags = flags; + proc->parent = parent; + proc->proc_id = proc_id; + proc->num_resources = num_resources; + proc->hdr.length = sizeof(struct acpi_pptt_proc) + + sizeof(u32) * num_resources; + + if (resource_list) + memcpy(proc + 1, resource_list, sizeof(u32) * num_resources); + + acpi_inc(ctx, proc->hdr.length); + + return offset; +} + +int acpi_pptt_add_cache(struct acpi_ctx *ctx, const u32 flags, + const u32 next_cache_level, const u32 size, + const u32 sets, const u8 assoc, const u8 attributes, + const u16 line_size) +{ + struct acpi_pptt_cache *cache = ctx->current; + int offset; + + offset = ctx->current - ctx->tab_start; + cache->hdr.type = ACPI_PPTT_TYPE_CACHE; + cache->hdr.length = sizeof(struct acpi_pptt_cache); + cache->flags = flags; + cache->next_cache_level = next_cache_level; + cache->size = size; + cache->sets = sets; + cache->assoc = assoc; + cache->attributes = attributes; + cache->line_size = line_size; + acpi_inc(ctx, cache->hdr.length); + + return offset; +} -- cgit v1.3.1 From 763bad3e1cf202147fbddb74c9876d7426ddb26b Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Wed, 23 Oct 2024 15:19:51 +0200 Subject: acpi: Add fill_madt to acpi_ops Add a new method to acpi_ops to let drivers fill out ACPI MADT. The code is unused for now until drivers implement the new ops. TEST: Booted on QEMU sbsa using driver model generated MADT. Signed-off-by: Patrick Rudolph Reviewed-by: Simon Glass Cc: Simon Glass --- arch/arm/lib/acpi_table.c | 13 +++++++++++++ drivers/core/acpi.c | 16 ++++++++++++++++ include/dm/acpi.h | 26 ++++++++++++++++++++++++++ lib/acpi/acpi_table.c | 2 +- test/dm/acpi.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 1 deletion(-) (limited to 'arch/arm/lib') diff --git a/arch/arm/lib/acpi_table.c b/arch/arm/lib/acpi_table.c index 286ed7cecaa..e0add049261 100644 --- a/arch/arm/lib/acpi_table.c +++ b/arch/arm/lib/acpi_table.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include void acpi_write_madt_gicc(struct acpi_madt_gicc *gicc, uint cpu_num, uint perf_gsiv, ulong phys_base, ulong gicv, @@ -112,3 +114,14 @@ int acpi_pptt_add_cache(struct acpi_ctx *ctx, const u32 flags, return offset; } + +void *acpi_fill_madt(struct acpi_madt *madt, struct acpi_ctx *ctx) +{ + uclass_probe_all(UCLASS_CPU); + uclass_probe_all(UCLASS_IRQ); + + /* All SoCs must use the driver model */ + acpi_fill_madt_subtbl(ctx); + + return ctx->current; +} diff --git a/drivers/core/acpi.c b/drivers/core/acpi.c index 9f784228921..4763963914b 100644 --- a/drivers/core/acpi.c +++ b/drivers/core/acpi.c @@ -48,6 +48,7 @@ enum method_t { METHOD_FILL_SSDT, METHOD_INJECT_DSDT, METHOD_SETUP_NHLT, + METHOD_FILL_MADT, }; /* Prototype for all methods */ @@ -282,6 +283,8 @@ acpi_method acpi_get_method(struct udevice *dev, enum method_t method) switch (method) { case METHOD_WRITE_TABLES: return aops->write_tables; + case METHOD_FILL_MADT: + return aops->fill_madt; case METHOD_FILL_SSDT: return aops->fill_ssdt; case METHOD_INJECT_DSDT: @@ -328,6 +331,19 @@ int acpi_recurse_method(struct acpi_ctx *ctx, struct udevice *parent, return 0; } +int acpi_fill_madt_subtbl(struct acpi_ctx *ctx) +{ + int ret; + + log_debug("Writing MADT table\n"); + ret = acpi_recurse_method(ctx, dm_root(), METHOD_FILL_MADT, TYPE_NONE); + log_debug("Writing MADT finished, err=%d\n", ret); + if (ret) + return log_msg_ret("build", ret); + + return ret; +} + int acpi_fill_ssdt(struct acpi_ctx *ctx) { void *start = ctx->current; diff --git a/include/dm/acpi.h b/include/dm/acpi.h index 3adfe217678..d6bc0c099ad 100644 --- a/include/dm/acpi.h +++ b/include/dm/acpi.h @@ -146,6 +146,22 @@ struct acpi_ops { */ int (*write_tables)(const struct udevice *dev, struct acpi_ctx *ctx); + /** + * fill_madt() - Generate MADT sub-tables for a device + * + * This is called to create the MADT table. The method should write out + * whatever sub-table is needed by this device. It will end up in the + * MADT table. + * + * Note that this is called 'fill' because the entire contents of the + * MADT is build by calling this method on all devices. + * + * @dev: Device to write + * @ctx: ACPI context to use + * @return 0 if OK, -ve on error + */ + int (*fill_madt)(const struct udevice *dev, struct acpi_ctx *ctx); + /** * fill_ssdt() - Generate SSDT code for a device * @@ -231,6 +247,16 @@ int acpi_copy_name(char *out_name, const char *name); */ int acpi_write_dev_tables(struct acpi_ctx *ctx); +/** + * acpi_fill_madt_subtbl() - Generate ACPI tables for MADT + * + * This is called to create the MADT sub-tables for all devices. + * + * @ctx: ACPI context to use + * Return: 0 if OK, -ve on error + */ +int acpi_fill_madt_subtbl(struct acpi_ctx *ctx); + /** * acpi_fill_ssdt() - Generate ACPI tables for SSDT * diff --git a/lib/acpi/acpi_table.c b/lib/acpi/acpi_table.c index 639d78125f6..5b9b4d2f290 100644 --- a/lib/acpi/acpi_table.c +++ b/lib/acpi/acpi_table.c @@ -257,11 +257,11 @@ int acpi_write_madt(struct acpi_ctx *ctx, const struct acpi_writer *entry) header->revision = ACPI_MADT_REV_ACPI_3_0; acpi_inc(ctx, sizeof(struct acpi_madt)); + /* TODO: Get rid of acpi_fill_madt and use driver model */ current = acpi_fill_madt(madt, ctx); /* (Re)calculate length and checksum */ header->length = (uintptr_t)current - (uintptr_t)madt; - header->checksum = table_compute_checksum((void *)madt, header->length); acpi_add_table(ctx, madt); ctx->current = (void *)madt + madt->header.length; diff --git a/test/dm/acpi.c b/test/dm/acpi.c index 7ccd7f8d20a..39a26bbb492 100644 --- a/test/dm/acpi.c +++ b/test/dm/acpi.c @@ -95,6 +95,21 @@ static int testacpi_get_name(const struct udevice *dev, char *out_name) return acpi_copy_name(out_name, ACPI_TEST_DEV_NAME); } +static int testacpi_fill_madt(const struct udevice *dev, struct acpi_ctx *ctx) +{ + u64 *data = ctx->current; + + /* Only fill madt once */ + if (device_get_uclass_id(dev->parent) != UCLASS_TEST_ACPI) + return 0; + + *data = 0xdeadbeef; + + acpi_inc(ctx, sizeof(u64)); + + return 0; +} + static int testacpi_fill_ssdt(const struct udevice *dev, struct acpi_ctx *ctx) { const char *data; @@ -124,6 +139,7 @@ static int testacpi_inject_dsdt(const struct udevice *dev, struct acpi_ctx *ctx) struct acpi_ops testacpi_ops = { .get_name = testacpi_get_name, .write_tables = testacpi_write_tables, + .fill_madt = testacpi_fill_madt, .fill_ssdt = testacpi_fill_ssdt, .inject_dsdt = testacpi_inject_dsdt, }; @@ -527,6 +543,33 @@ static int dm_test_acpi_fill_ssdt(struct unit_test_state *uts) } DM_TEST(dm_test_acpi_fill_ssdt, UTF_SCAN_PDATA | UTF_SCAN_FDT); +/* Test acpi_fill_madt() */ +static int dm_test_acpi_fill_madt(struct unit_test_state *uts) +{ + struct acpi_ctx ctx; + u64 *buf; + + buf = malloc(BUF_SIZE); + ut_assertnonnull(buf); + + acpi_reset_items(); + ctx.current = buf; + buf[1] = 'z'; /* sentinel */ + ut_assertok(acpi_fill_madt_subtbl(&ctx)); + + /* + * These values come from acpi-test2's acpi-ssdt-test-data property. + * This device comes first because of u-boot,acpi-ssdt-order + */ + ut_asserteq(0xdeadbeef, buf[0]); + + ut_asserteq('z', buf[1]); + + return 0; +} + +DM_TEST(dm_test_acpi_fill_madt, UTF_SCAN_PDATA | UTF_SCAN_FDT); + /* Test acpi_inject_dsdt() */ static int dm_test_acpi_inject_dsdt(struct unit_test_state *uts) { -- cgit v1.3.1 From 581e0cac2db7141fbc0c65131aceea51ffbeb199 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Wed, 23 Oct 2024 15:20:02 +0200 Subject: arm: gic-v3-its: Rename objects The code accesses the gic-v3 node, but not the gic-v3-its node, thus rename the objects to clarify which node it operates on. The following commit will make use of the gic-v3-its node for real. Signed-off-by: Patrick Rudolph Reviewed-by: Simon Glass --- arch/arm/lib/gic-v3-its.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'arch/arm/lib') diff --git a/arch/arm/lib/gic-v3-its.c b/arch/arm/lib/gic-v3-its.c index 2cc0a32f9d4..22fa46a3417 100644 --- a/arch/arm/lib/gic-v3-its.c +++ b/arch/arm/lib/gic-v3-its.c @@ -35,10 +35,10 @@ static int gic_v3_its_get_gic_addr(struct gic_v3_its_priv *priv) int ret; ret = uclass_get_device_by_driver(UCLASS_IRQ, - DM_DRIVER_GET(arm_gic_v3_its), &dev); + DM_DRIVER_GET(arm_gic_v3), &dev); if (ret) { pr_err("%s: failed to get %s irq device\n", __func__, - DM_DRIVER_GET(arm_gic_v3_its)->name); + DM_DRIVER_GET(arm_gic_v3)->name); return ret; } @@ -158,13 +158,13 @@ int gic_lpi_tables_init(u64 base, u32 num_redist) return 0; } -static const struct udevice_id gic_v3_its_ids[] = { +static const struct udevice_id gic_v3_ids[] = { { .compatible = "arm,gic-v3" }, {} }; -U_BOOT_DRIVER(arm_gic_v3_its) = { +U_BOOT_DRIVER(arm_gic_v3) = { .name = "gic-v3", .id = UCLASS_IRQ, - .of_match = gic_v3_its_ids, + .of_match = gic_v3_ids, }; -- cgit v1.3.1 From 11a86874c0a76a712d130b0aec2bbd1dbffa759d Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Wed, 23 Oct 2024 15:20:03 +0200 Subject: arm: gic-v3-its: Implement of_xlate Translate IRQs by implementing of_xlate() as required by irq_get_by_index() to parse interrupt properties. Map DT interrupts to ARM GIC interrupts as follows: - Interrupt numbers ID32-ID1019 are used for SPIs - ID0-ID15 are used for SGIs - ID16-ID31 are used for PPIs TEST: Booted on qemu sbsa-ref that has a GICV3. Signed-off-by: Patrick Rudolph Reviewed-by: Moritz Fischer --- arch/arm/lib/gic-v3-its.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'arch/arm/lib') diff --git a/arch/arm/lib/gic-v3-its.c b/arch/arm/lib/gic-v3-its.c index 22fa46a3417..58f8bf864f1 100644 --- a/arch/arm/lib/gic-v3-its.c +++ b/arch/arm/lib/gic-v3-its.c @@ -4,9 +4,11 @@ */ #include #include +#include #include #include #include +#include #include #include #include @@ -163,8 +165,30 @@ static const struct udevice_id gic_v3_ids[] = { {} }; +static int arm_gic_v3_of_xlate(struct irq *irq, struct ofnode_phandle_args *args) +{ + if (args->args_count < 3) { + log_debug("Invalid args_count: %d\n", args->args_count); + return -EINVAL; + } + + if (args->args[0] == GIC_SPI) + irq->id = args->args[1] + 32; + else + irq->id = args->args[1] + 16; + + irq->flags = args->args[2]; + + return 0; +} + +static const struct irq_ops arm_gic_v3_ops = { + .of_xlate = arm_gic_v3_of_xlate, +}; + U_BOOT_DRIVER(arm_gic_v3) = { .name = "gic-v3", .id = UCLASS_IRQ, .of_match = gic_v3_ids, + .ops = &arm_gic_v3_ops, }; -- cgit v1.3.1 From df8d759d9db17b55d6283a9a1d84d196e9cc1dd4 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Wed, 23 Oct 2024 15:20:04 +0200 Subject: arm: lib: Add GICV2 driver Add a generic GICV2 driver that: - parses the DT and generates the ACPI MADT subtables - implement of_xlate() and allows irq_get_by_index() to return the correct interrupt mappings Map DT interrupts to ARM GIC interrupts as follows: - Interrupt numbers ID32-ID1019 are used for SPIs - ID0-ID15 are used for SGIs - ID16-ID31 are used for PPIs TEST: Booted on QEMU raspb4 using GICV2 driver model generated MADT. Signed-off-by: Patrick Rudolph Reviewed-by: Simon Glass --- arch/arm/Kconfig | 7 ++++ arch/arm/lib/Makefile | 1 + arch/arm/lib/gic-v2.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 arch/arm/lib/gic-v2.c (limited to 'arch/arm/lib') diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 060636e9e2d..263f85b0d0f 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -113,6 +113,13 @@ config GICV2 config GICV3 bool +config DRIVER_GICV2 + bool "ARM GICV2 driver" + select IRQ + help + ARM GICV2 driver. + Basic support for parsing the GICV2 node and generate ACPI tables. + config GIC_V3_ITS bool "ARM GICV3 ITS" select IRQ diff --git a/arch/arm/lib/Makefile b/arch/arm/lib/Makefile index a7efed6771d..cf760514505 100644 --- a/arch/arm/lib/Makefile +++ b/arch/arm/lib/Makefile @@ -68,6 +68,7 @@ obj-$(CONFIG_FSL_LAYERSCAPE) += ccn504.o ifneq ($(CONFIG_GICV2)$(CONFIG_GICV3),) obj-y += gic_64.o endif +obj-$(CONFIG_DRIVER_GICV2) += gic-v2.o obj-$(CONFIG_GIC_V3_ITS) += gic-v3-its.o obj-y += interrupts_64.o else diff --git a/arch/arm/lib/gic-v2.c b/arch/arm/lib/gic-v2.c new file mode 100644 index 00000000000..b70434a45d4 --- /dev/null +++ b/arch/arm/lib/gic-v2.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2019 Broadcom. + */ +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_ACPIGEN +/** + * acpi_gicv2_fill_madt() - Fill out the body of the MADT + * + * Write GICD and GICR tables based on collected devicetree data. + * + * @dev: Device to write ACPI tables for + * @ctx: ACPI context to write MADT sub-tables to + * Return: 0 if OK + */ +static int acpi_gicv2_fill_madt(const struct udevice *dev, struct acpi_ctx *ctx) +{ + struct acpi_madt_gicd *gicd; + fdt_addr_t addr; + + addr = dev_read_addr_index(dev, 0); + if (addr == FDT_ADDR_T_NONE) { + pr_err("%s: failed to get GICD address\n", __func__); + return -EINVAL; + } + + gicd = ctx->current; + acpi_write_madt_gicd(gicd, dev_seq(dev), addr, 2); + acpi_inc(ctx, gicd->length); + + return 0; +} + +static struct acpi_ops gic_v2_acpi_ops = { + .fill_madt = acpi_gicv2_fill_madt, +}; +#endif + +static const struct udevice_id gic_v2_ids[] = { + { .compatible = "arm,arm11mp-gic" }, + { .compatible = "arm,cortex-a15-gic" }, + { .compatible = "arm,cortex-a7-gic" }, + { .compatible = "arm,cortex-a5-gic" }, + { .compatible = "arm,cortex-a9-gic" }, + { .compatible = "arm,eb11mp-gic" }, + { .compatible = "arm,gic-400" }, + { .compatible = "arm,pl390" }, + { .compatible = "arm,tc11mp-gic" }, + { .compatible = "qcom,msm-8660-qgic" }, + { .compatible = "qcom,msm-qgic2" }, + {} +}; + +static int arm_gic_v2_of_xlate(struct irq *irq, struct ofnode_phandle_args *args) +{ + if (args->args_count != 3) { + log_debug("Invalid args_count: %d\n", args->args_count); + return -EINVAL; + } + + /* ARM Generic Interrupt Controller v1 and v2 */ + if (args->args[0] == GIC_SPI) + irq->id = args->args[1] + 32; + else + irq->id = args->args[1] + 16; + + irq->flags = args->args[2]; + + return 0; +} + +static const struct irq_ops arm_gic_v2_ops = { + .of_xlate = arm_gic_v2_of_xlate, +}; + +U_BOOT_DRIVER(arm_gic_v2) = { + .name = "gic-v2", + .id = UCLASS_IRQ, + .of_match = gic_v2_ids, + .ops = &arm_gic_v2_ops, + ACPI_OPS_PTR(&gic_v2_acpi_ops) +}; -- cgit v1.3.1 From 142f92bf0465b0fc9fb59a055c1f25f18d3acaf3 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Wed, 23 Oct 2024 15:20:06 +0200 Subject: drivers/arm: Implement acpi_fill_madt Fill the MADT table in the GIC driver and armv8 CPU driver to drop SoC specific code. While the GIC only needs devicetree data, the CPU driver needs additional information stored in the cpu_plat struct. While on it update the only board making use of the existing drivers and writing ACPI MADT in mainboard code. TEST: Booted on QEMU sbsa-ref using GICV3 driver model generated MADT. Booted on QEMU raspb4 using GICV2 driver model generated MADT. Signed-off-by: Patrick Rudolph Reviewed-by: Simon Glass Cc: Simon Glass --- arch/arm/lib/gic-v3-its.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++- drivers/cpu/Kconfig | 1 + drivers/cpu/armv8_cpu.c | 80 +++++++++++++++++++++++++++++++++++++++++- drivers/cpu/armv8_cpu.h | 10 ++++++ 4 files changed, 178 insertions(+), 2 deletions(-) (limited to 'arch/arm/lib') diff --git a/arch/arm/lib/gic-v3-its.c b/arch/arm/lib/gic-v3-its.c index 58f8bf864f1..51cc2397768 100644 --- a/arch/arm/lib/gic-v3-its.c +++ b/arch/arm/lib/gic-v3-its.c @@ -5,9 +5,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -28,12 +30,14 @@ static u32 lpi_id_bits; struct gic_v3_its_priv { ulong gicd_base; ulong gicr_base; + ulong gicr_length; }; static int gic_v3_its_get_gic_addr(struct gic_v3_its_priv *priv) { struct udevice *dev; fdt_addr_t addr; + fdt_size_t size; int ret; ret = uclass_get_device_by_driver(UCLASS_IRQ, @@ -51,12 +55,13 @@ static int gic_v3_its_get_gic_addr(struct gic_v3_its_priv *priv) } priv->gicd_base = addr; - addr = dev_read_addr_index(dev, 1); + addr = dev_read_addr_size_index(dev, 1, &size); if (addr == FDT_ADDR_T_NONE) { pr_err("%s: failed to get GICR address\n", __func__); return -EINVAL; } priv->gicr_base = addr; + priv->gicr_length = size; return 0; } @@ -160,6 +165,42 @@ int gic_lpi_tables_init(u64 base, u32 num_redist) return 0; } +#ifdef CONFIG_ACPIGEN +/** + * acpi_gicv3_fill_madt() - Fill out the body of the MADT + * + * Write GICD and GICR tables based on collected devicetree data. + * + * @dev: Device to write ACPI tables for + * @ctx: ACPI context to write MADT sub-tables to + * Return: 0 if OK + */ +static int acpi_gicv3_fill_madt(const struct udevice *dev, struct acpi_ctx *ctx) +{ + struct acpi_madt_gicd *gicd; + struct acpi_madt_gicr *gicr; + + struct gic_v3_its_priv priv; + + if (gic_v3_its_get_gic_addr(&priv)) + return -EINVAL; + + gicd = ctx->current; + acpi_write_madt_gicd(gicd, dev_seq(dev), priv.gicd_base, 3); + acpi_inc(ctx, gicd->length); + + gicr = ctx->current; + acpi_write_madt_gicr(gicr, priv.gicr_base, priv.gicr_length); + acpi_inc(ctx, gicr->length); + + return 0; +} + +struct acpi_ops gic_v3_acpi_ops = { + .fill_madt = acpi_gicv3_fill_madt, +}; +#endif + static const struct udevice_id gic_v3_ids[] = { { .compatible = "arm,gic-v3" }, {} @@ -191,4 +232,50 @@ U_BOOT_DRIVER(arm_gic_v3) = { .id = UCLASS_IRQ, .of_match = gic_v3_ids, .ops = &arm_gic_v3_ops, + ACPI_OPS_PTR(&gic_v3_acpi_ops) +}; + +#ifdef CONFIG_ACPIGEN +/** + * acpi_gic_its_fill_madt() - Fill out the body of the MADT + * + * Write ITS tables based on collected devicetree data. + * + * @dev: Device to write ACPI tables for + * @ctx: ACPI context to write MADT sub-tables to + * Return: 0 if OK + */ +static int acpi_gic_its_fill_madt(const struct udevice *dev, struct acpi_ctx *ctx) +{ + struct acpi_madt_its *its; + fdt_addr_t addr; + + addr = dev_read_addr_index(dev, 0); + if (addr == FDT_ADDR_T_NONE) { + pr_err("%s: failed to get GIC ITS address\n", __func__); + return -EINVAL; + } + + its = ctx->current; + acpi_write_madt_its(its, dev_seq(dev), addr); + acpi_inc(ctx, its->length); + + return 0; +} + +struct acpi_ops gic_v3_its_acpi_ops = { + .fill_madt = acpi_gic_its_fill_madt, +}; +#endif + +static const struct udevice_id gic_v3_its_ids[] = { + { .compatible = "arm,gic-v3-its" }, + {} +}; + +U_BOOT_DRIVER(arm_gic_v3_its) = { + .name = "gic-v3-its", + .id = UCLASS_IRQ, + .of_match = gic_v3_its_ids, + ACPI_OPS_PTR(&gic_v3_its_acpi_ops) }; diff --git a/drivers/cpu/Kconfig b/drivers/cpu/Kconfig index 9c0df331d7f..4cc3679c009 100644 --- a/drivers/cpu/Kconfig +++ b/drivers/cpu/Kconfig @@ -29,6 +29,7 @@ config CPU_RISCV config CPU_ARMV8 bool "Enable generic ARMv8 CPU driver" depends on CPU && ARM64 + select IRQ help Support CPU cores for armv8 architecture. diff --git a/drivers/cpu/armv8_cpu.c b/drivers/cpu/armv8_cpu.c index 19f072be430..4eedfe5e2c5 100644 --- a/drivers/cpu/armv8_cpu.c +++ b/drivers/cpu/armv8_cpu.c @@ -4,10 +4,11 @@ */ #include #include +#include #include #include -#include #include +#include #include #include #include @@ -47,8 +48,85 @@ int armv8_cpu_fill_ssdt(const struct udevice *dev, struct acpi_ctx *ctx) return 0; } +int armv8_cpu_fill_madt(const struct udevice *dev, struct acpi_ctx *ctx) +{ + struct acpi_madt_gicc *gicc; + struct cpu_plat *cpu_plat; + struct udevice *gic; + u64 gicc_gicv = 0; + u64 gicc_gich = 0; + u64 gicc_gicr_base = 0; + u64 gicc_phys_base = 0; + u32 gicc_perf_gsiv = 0; + u64 gicc_mpidr; + u32 gicc_vgic_maint_irq = 0; + int addr_index; + fdt_addr_t addr; + int ret; + struct irq req_irq; + + cpu_plat = dev_get_parent_plat(dev); + if (!cpu_plat) + return 0; + + ret = irq_get_interrupt_parent(dev, &gic); + if (ret) { + log_err("%s: Failed to find interrupt parent for %s\n", + __func__, dev->name); + return -ENODEV; + } + + addr_index = 1; + + if (device_is_compatible(gic, "arm,gic-v3")) { + addr = dev_read_addr_index(gic, addr_index++); + if (addr != FDT_ADDR_T_NONE) + gicc_gicr_base = addr; + } + + addr = dev_read_addr_index(gic, addr_index++); + if (addr != FDT_ADDR_T_NONE) + gicc_phys_base = addr; + + addr = dev_read_addr_index(gic, addr_index++); + if (addr != FDT_ADDR_T_NONE) + gicc_gich = addr; + + addr = dev_read_addr_index(gic, addr_index++); + if (addr != FDT_ADDR_T_NONE) + gicc_gicv = addr; + + ret = irq_get_by_index(gic, 0, &req_irq); + if (!ret) + gicc_vgic_maint_irq = req_irq.id; + + gicc_mpidr = dev_read_u64_default(dev, "reg", 0); + if (!gicc_mpidr) + gicc_mpidr = dev_read_u32_default(dev, "reg", 0); + + /* + * gicc_vgic_maint_irq and gicc_gicv are the same for every CPU + */ + gicc = ctx->current; + acpi_write_madt_gicc(gicc, + dev_seq(dev), + gicc_perf_gsiv, /* FIXME: needs a PMU driver */ + gicc_phys_base, + gicc_gicv, + gicc_gich, + gicc_vgic_maint_irq, + gicc_gicr_base, + gicc_mpidr, + 0); /* FIXME: Not defined in DT */ + + acpi_inc(ctx, gicc->length); + + return 0; +} + struct acpi_ops armv8_cpu_acpi_ops = { .fill_ssdt = armv8_cpu_fill_ssdt, + .fill_madt = armv8_cpu_fill_madt, }; #endif diff --git a/drivers/cpu/armv8_cpu.h b/drivers/cpu/armv8_cpu.h index 2c4b0252cf8..48c705e98de 100644 --- a/drivers/cpu/armv8_cpu.h +++ b/drivers/cpu/armv8_cpu.h @@ -18,4 +18,14 @@ */ int armv8_cpu_fill_ssdt(const struct udevice *dev, struct acpi_ctx *ctx); +/** + * armv8_cpu_fill_madt() - Fill the MADT + * Parses the FDT and writes the MADT subtables. + * + * @dev: cpu device to generate ACPI tables for + * @ctx: ACPI context pointer + * @return: 0 if OK, or a negative error code. + */ +int armv8_cpu_fill_madt(const struct udevice *dev, struct acpi_ctx *ctx); + #endif \ No newline at end of file -- cgit v1.3.1 From 34bfe8eff895b864247d923ce37110a9053592ee Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Wed, 23 Oct 2024 15:20:13 +0200 Subject: arm: cpu: Add ACPI parking protocol support On Arm platforms that use ACPI they cannot rely on the "spin-table" CPU bringup usually defined in the FDT. Thus implement the 'ACPI Multi-processor Startup for ARM Platforms', also referred to as 'ACPI parking protocol'. The ACPI parking protocol works similar to the spin-table mechanism, but the specification also covers lots of shortcomings of the spin-table implementations. Every CPU defined in the ACPI MADT table has it's own 4K page where the spinloop code and the OS mailbox resides. When selected the U-Boot board code must make sure that the secondary CPUs enter u-boot after relocation as well, so that they can enter the spinloop code residing in the ACPI parking protocol pages. The OS will then write to the mailbox and generate an IPI to release the CPUs from the spinloop code. For now it's only implemented on ARMv8, but can easily be extended to other platforms, like ARMv7. TEST: Boots all CPUs on qemu-system-aarch64 -machine raspi4b Signed-off-by: Patrick Rudolph Reviewed-by: Simon Glass Cc: Simon Glass Cc: Tom Rini --- arch/arm/cpu/armv8/Makefile | 1 + arch/arm/cpu/armv8/acpi_park_v8.S | 113 +++++++++++++++++++++++++++++ arch/arm/include/asm/acpi_table.h | 32 ++++++++ arch/arm/lib/acpi_table.c | 149 ++++++++++++++++++++++++++++++++++++++ include/acpi/acpi_table.h | 10 +++ include/bloblist.h | 1 + lib/Kconfig | 16 ++++ lib/acpi/acpi_table.c | 4 + 8 files changed, 326 insertions(+) create mode 100644 arch/arm/cpu/armv8/acpi_park_v8.S (limited to 'arch/arm/lib') diff --git a/arch/arm/cpu/armv8/Makefile b/arch/arm/cpu/armv8/Makefile index bba4f570dbb..4f4368ff8c7 100644 --- a/arch/arm/cpu/armv8/Makefile +++ b/arch/arm/cpu/armv8/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_ARM_SMCCC) += smccc-call.o ifndef CONFIG_SPL_BUILD obj-$(CONFIG_ARMV8_SPIN_TABLE) += spin_table.o spin_table_v8.o +obj-$(CONFIG_ACPI_PARKING_PROTOCOL) += acpi_park_v8.o else obj-$(CONFIG_ARCH_SUNXI) += fel_utils.o endif diff --git a/arch/arm/cpu/armv8/acpi_park_v8.S b/arch/arm/cpu/armv8/acpi_park_v8.S new file mode 100644 index 00000000000..0bc605d28df --- /dev/null +++ b/arch/arm/cpu/armv8/acpi_park_v8.S @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2024 9elements GmbH + * Author: Patrick Rudolph + * + * This file provides ARMv8 specific code for the generic part of the + * ACPI parking protocol implementation. It contains the spinning code + * that will be installed into the parking protocol and it points the + * secondary CPUs to their own parking protocol page once it has been + * set up by the generic part. + */ + +#include +#include + +/* Filled by C code */ +.global acpi_pp_tables +acpi_pp_tables: + .quad 0 + +.global acpi_pp_etables +acpi_pp_etables: + .quad 0 + +/* Read by C code */ +.global acpi_pp_code_size +acpi_pp_code_size: + .word __secondary_pp_code_end - __secondary_pp_code_start + +.global acpi_pp_secondary_jump +ENTRY(acpi_pp_secondary_jump) +0: + /* + * Cannot use atomic operations since the MMU and D-cache + * might be off. Use the MPIDR instead to find the spintable. + */ + + /* Check if parking protocol table is ready */ + ldr x1, =acpi_pp_tables + ldr x0, [x1] + cbnz x0, 0f + wfe + b 0b + +0: /* Get end of page tables in x3 */ + ldr x1, =acpi_pp_etables + ldr x3, [x1] + + /* Get own CPU ID in w2 */ + mrs x2, mpidr_el1 + lsr x9, x2, #32 + bfi x2, x9, #24, #8 /* w2 is aff3:aff2:aff1:aff0 */ + +0: /* Loop over all parking protocol pages */ + cmp x0, x3 + b.ge hlt + + /* Fetch CPU_ID from current page */ + ldr x1, [x0, #ACPI_PP_CPU_ID_OFFSET] + lsr x9, x1, #32 + bfi x1, x9, #24, #8 /* w1 is aff3:aff2:aff1:aff0 */ + + /* Compare CPU_IDs */ + cmp w1, w2 + b.eq 0f + + add x0, x0, #ACPI_PP_PAGE_SIZE + b 0b + +hlt: wfi + b hlt /* Should never happen. */ + +0: /* x0 points to the 4K-aligned, parking protocol page */ + add x2, x0, #ACPI_PP_CPU_CODE_OFFSET + + /* Jump to spin code in own parking protocol page */ + br x2 +ENDPROC(acpi_pp_secondary_jump) + +.align 8 +__secondary_pp_code_start: +.global acpi_pp_code_start +ENTRY(acpi_pp_code_start) + /* x0 points to the 4K-aligned, parking protocol page */ + + /* Prepare defines for spinning code */ + mov w3, #ACPI_PP_CPU_ID_INVALID + mov x2, #ACPI_PP_JMP_ADR_INVALID + + /* Mark parking protocol page as ready */ + str w3, [x0, #ACPI_PP_CPU_ID_OFFSET] + dsb sy + +0: wfe + ldr w1, [x0, #ACPI_PP_CPU_ID_OFFSET] + + /* Check CPU ID is valid */ + cmp w1, w3 + b.eq 0b + + /* Check jump address valid */ + ldr x1, [x0, #ACPI_PP_CPU_JMP_OFFSET] + cmp x1, x2 + b.eq 0b + + /* Clear jump address before jump */ + str x2, [x0, #ACPI_PP_CPU_JMP_OFFSET] + dsb sy + + br x1 +ENDPROC(acpi_pp_code_start) + /* Secondary Boot Code ends here */ +__secondary_pp_code_end: diff --git a/arch/arm/include/asm/acpi_table.h b/arch/arm/include/asm/acpi_table.h index c65eabe8374..7386f55ad34 100644 --- a/arch/arm/include/asm/acpi_table.h +++ b/arch/arm/include/asm/acpi_table.h @@ -4,6 +4,7 @@ #define __ASM_ACPI_TABLE_H__ #ifndef __ACPI__ +#ifndef __ASSEMBLY__ #include @@ -109,7 +110,38 @@ int acpi_pptt_add_cache(struct acpi_ctx *ctx, const u32 flags, const u32 sets, const u8 assoc, const u8 attributes, const u16 line_size); +/* Multi-processor Startup for ARM Platforms */ +/** + * struct acpi_pp_page - MP startup handshake mailbox + * + * Defines a 4096 byte memory region that is used for starting secondary CPUs on + * an Arm system that follows the "Multi-processor Startup for ARM Platforms" spec. + * + * @cpu_id: MPIDR as returned by the Multiprocessor Affinity Register. + * On 32bit Arm systems the upper bits are unused. + * @jumping_address: On 32bit Arm systems the address must be below 4 GiB + * @os_reserved: Reserved for OS use. Firmware must not access this memory. + * @spinning_code: Reserved for firmware use. OS must not access this memory. + * The spinning code will be installed by firmware and the secondary + * CPUs will enter it before the control is handed over to the OS. + */ +struct acpi_pp_page { + u64 cpu_id; + u64 jumping_address; + u8 os_reserved[2032]; + u8 spinning_code[2048]; +} __packed; + #endif /* !__ASSEMBLY__ */ #endif /* !__ACPI__ */ +/* Multi-processor Startup for ARM Platforms defines */ +#define ACPI_PP_CPU_ID_INVALID 0xffffffff +#define ACPI_PP_JMP_ADR_INVALID 0 +#define ACPI_PP_PAGE_SIZE 4096 +#define ACPI_PP_CPU_ID_OFFSET 0 +#define ACPI_PP_CPU_JMP_OFFSET 8 +#define ACPI_PP_CPU_CODE_OFFSET 2048 +#define ACPI_PP_VERSION 1 + #endif /* __ASM_ACPI_TABLE_H__ */ diff --git a/arch/arm/lib/acpi_table.c b/arch/arm/lib/acpi_table.c index e0add049261..f760b7fbce4 100644 --- a/arch/arm/lib/acpi_table.c +++ b/arch/arm/lib/acpi_table.c @@ -7,12 +7,48 @@ #define LOG_CATEGORY LOGC_ACPI +#include +#include +#include +#include #include +#include #include #include #include +#include #include #include +#include +#include + +/* defined in assembly file */ +/** + * acpi_pp_code_size - Spinloop code size * + */ +extern u16 acpi_pp_code_size; + +/** + * acpi_pp_tables - Start of ACPI PP tables. + */ +extern ulong acpi_pp_tables; + +/** + * acpi_pp_etables - End of ACPI PP tables. + */ +extern ulong acpi_pp_etables; + +/** + * acpi_pp_code_start() - Spinloop code + * + * Architectural spinloop code to be installed in each parking protocol + * page. The spinloop code must be less than 2048 bytes. + * + * The spinloop code will be entered after calling + * acpi_parking_protocol_install(). + * + */ +void acpi_pp_code_start(void); void acpi_write_madt_gicc(struct acpi_madt_gicc *gicc, uint cpu_num, uint perf_gsiv, ulong phys_base, ulong gicv, @@ -125,3 +161,116 @@ void *acpi_fill_madt(struct acpi_madt *madt, struct acpi_ctx *ctx) return ctx->current; } + +/** + * acpi_write_pp_setup_one_page() - Fill out one page used by the PP + * + * Fill out the struct acpi_pp_page to contain the spin-loop + * code and the mailbox area. After this function the page is ready for + * the secondary core's to enter the spin-loop code. + * + * @page: Pointer to current parking protocol page + * @gicc: Pointer to corresponding GICC sub-table + */ +static void acpi_write_pp_setup_one_page(struct acpi_pp_page *page, + struct acpi_madt_gicc *gicc) +{ + void *reloc; + + /* Update GICC. Mark parking protocol as available. */ + gicc->parking_proto = ACPI_PP_VERSION; + gicc->parked_addr = virt_to_phys(page); + + /* Prepare parking protocol page */ + memset(page, '\0', sizeof(struct acpi_pp_page)); + + /* Init mailbox. Set MPIDR so core's will find their page. */ + page->cpu_id = gicc->mpidr; + page->jumping_address = ACPI_PP_JMP_ADR_INVALID; + + /* Relocate spinning code */ + reloc = &page->spinning_code[0]; + + log_debug("Relocating spin table from %lx to %lx (size %x)\n", + (ulong)&acpi_pp_code_start, (ulong)reloc, acpi_pp_code_size); + memcpy(reloc, &acpi_pp_code_start, acpi_pp_code_size); + + if (!CONFIG_IS_ENABLED(SYS_DCACHE_OFF)) + flush_dcache_range((unsigned long)page, + (unsigned long)(page + 1)); +} + +void acpi_write_park(struct acpi_madt *madt) +{ + struct acpi_pp_page *start, *page; + struct acpi_madt_gicc *gicc; + int ret, i, ncpus = 0; + + /* + * According to the "Multi-processor Startup for ARM Platforms": + * - Every CPU as specified by MADT GICC has it's own 4K page + * - Every page is divided into two sections: OS and FW reserved + * - Memory occupied by "Parking Protocol" must be marked 'Reserved' + * - Spinloop code should reside in FW reserved 2048 bytes + * - Spinloop code will check the mailbox in OS reserved area + */ + + if (acpi_pp_code_size > sizeof(page->spinning_code)) { + log_err("Spinning code too big to fit: %d\n", + acpi_pp_code_size); + return; + } + + /* Count all MADT GICCs including BSP */ + for (i = sizeof(struct acpi_madt); i < madt->header.length; + i += gicc->length) { + gicc = (struct acpi_madt_gicc *)((void *)madt + i); + if (gicc->type != ACPI_APIC_GICC) + continue; + ncpus++; + } + log_debug("Found %#x GICCs in MADT\n", ncpus); + + /* Allocate pages linearly due to assembly code requirements */ + start = bloblist_add(BLOBLISTT_ACPI_PP, ACPI_PP_PAGE_SIZE * ncpus, + ilog2(SZ_4K)); + if (!start) { + log_err("Failed to allocate memory for ACPI-parking-protocol pages\n"); + return; + } + log_debug("Allocated parking protocol at %p\n", start); + page = start; + + if (IS_ENABLED(CONFIG_EFI_LOADER)) { + /* Default mapping is 'BOOT CODE'. Mark as reserved instead. */ + ret = efi_add_memory_map((u64)(uintptr_t)start, + ncpus * ACPI_PP_PAGE_SIZE, + EFI_RESERVED_MEMORY_TYPE); + + if (ret) + log_err("Reserved memory mapping failed addr %p size %x\n", + start, ncpus * ACPI_PP_PAGE_SIZE); + } + + /* Prepare the parking protocol pages */ + for (i = sizeof(struct acpi_madt); i < madt->header.length; + i += gicc->length) { + gicc = (struct acpi_madt_gicc *)((void *)madt + i); + if (gicc->type != ACPI_APIC_GICC) + continue; + + acpi_write_pp_setup_one_page(page++, gicc); + } + + acpi_pp_etables = virt_to_phys(start) + + ACPI_PP_PAGE_SIZE * ncpus; + acpi_pp_tables = virt_to_phys(start); + + /* Make sure other cores see written value in memory */ + if (!CONFIG_IS_ENABLED(SYS_DCACHE_OFF)) + flush_dcache_all(); + + /* Send an event to wake up the secondary CPU. */ + asm("dsb ishst\n" + "sev"); +} diff --git a/include/acpi/acpi_table.h b/include/acpi/acpi_table.h index 07c5f65d753..b8b1f1338c6 100644 --- a/include/acpi/acpi_table.h +++ b/include/acpi/acpi_table.h @@ -1237,6 +1237,16 @@ int acpi_iort_add_smmu_v3(struct acpi_ctx *ctx, */ void *acpi_fill_madt(struct acpi_madt *madt, struct acpi_ctx *ctx); +/** + * acpi_write_park() - Installs the ACPI parking protocol. + * + * Sets up the ACPI parking protocol and installs the spinning code for + * secondary CPUs. + * + * @madt: The MADT to update + */ +void acpi_write_park(struct acpi_madt *madt); + /** * acpi_get_rsdp_addr() - get ACPI RSDP table address * diff --git a/include/bloblist.h b/include/bloblist.h index b0706b5637d..ff32d3fecfd 100644 --- a/include/bloblist.h +++ b/include/bloblist.h @@ -110,6 +110,7 @@ enum bloblist_tag_t { BLOBLISTT_ACPI_TABLES = 4, BLOBLISTT_TPM_EVLOG = 5, BLOBLISTT_TPM_CRB_BASE = 6, + BLOBLISTT_ACPI_PP = 7, /* Standard area to allocate blobs used across firmware components */ BLOBLISTT_AREA_FIRMWARE = 0x10, diff --git a/lib/Kconfig b/lib/Kconfig index 2e0fc1bc8f5..6ad079a1284 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -315,6 +315,22 @@ config GENERATE_ACPI_TABLE by the operating system. It defines platform-independent interfaces for configuration and power management monitoring. +config ACPI_PARKING_PROTOCOL + bool "Support ACPI parking protocol method" + depends on GENERATE_ACPI_TABLE + depends on ARMV8_MULTIENTRY + depends on BLOBLIST_TABLES + default y if !SEC_FIRMWARE_ARMV8_PSCI && !ARMV8_PSCI + help + Say Y here to support "ACPI parking protocol" enable method + for booting Linux. + + To use this feature, you must do: + - Bring secondary CPUs into U-Boot proper in a board-specific + manner. This must be done *after* relocation. Otherwise, the + secondary CPUs will spin in unprotected memory-area because the + master CPU protects the relocated spin code. + config SPL_TINY_MEMSET bool "Use a very small memset() in SPL" depends on SPL diff --git a/lib/acpi/acpi_table.c b/lib/acpi/acpi_table.c index e6ebffcf1b0..6473d95c102 100644 --- a/lib/acpi/acpi_table.c +++ b/lib/acpi/acpi_table.c @@ -297,6 +297,10 @@ int acpi_write_madt(struct acpi_ctx *ctx, const struct acpi_writer *entry) /* (Re)calculate length and checksum */ header->length = (uintptr_t)current - (uintptr_t)madt; + + if (IS_ENABLED(CONFIG_ACPI_PARKING_PROTOCOL)) + acpi_write_park(madt); + header->checksum = table_compute_checksum((void *)madt, header->length); acpi_add_table(ctx, madt); ctx->current = (void *)madt + madt->header.length; -- cgit v1.3.1