Message ID | 20200614025523.40183-9-sjg@chromium.org |
---|---|
State | Superseded |
Headers | show |
Series | dm: Add programmatic generation of ACPI tables (part B) | expand |
Hi Simon, On Sun, Jun 14, 2020 at 10:55 AM Simon Glass <sjg at chromium.org> wrote: > > More complex device properties can be provided to drivers via a > device-specific data (_DSD) object. > > To create this we need to build it up in a separate data structure and > then generate the ACPI code, due to its recursive nature. > > Add an implementation of this. > > Signed-off-by: Simon Glass <sjg at chromium.org> > Reviewed-by: Wolfgang Wallner <wolfgang.wallner at br-automation.com> > --- > > Changes in v3: > - Allow the name parameter to be NULL > - Add error checking to acpi_dp_add_integer_array() > - Fix 'acpi_device.v' typo > - Drop unused ACPI_CPU_STRING > > include/acpi/acpi_dp.h | 216 ++++++++++++++++++++++ > include/acpi/acpigen.h | 1 + > lib/acpi/Makefile | 1 + > lib/acpi/acpi_dp.c | 323 ++++++++++++++++++++++++++++++++ > test/dm/Makefile | 1 + > test/dm/acpi_dp.c | 405 +++++++++++++++++++++++++++++++++++++++++ > 6 files changed, 947 insertions(+) > create mode 100644 include/acpi/acpi_dp.h > create mode 100644 lib/acpi/acpi_dp.c > create mode 100644 test/dm/acpi_dp.c > > diff --git a/include/acpi/acpi_dp.h b/include/acpi/acpi_dp.h > new file mode 100644 > index 0000000000..3fd048e111 > --- /dev/null > +++ b/include/acpi/acpi_dp.h > @@ -0,0 +1,216 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Device properties, a temporary data structure for adding to ACPI code > + * > + * Copyright 2019 Google LLC > + * Mostly taken from coreboot file acpi_device.h > + */ > + > +#ifndef __ACPI_DP_H > +#define __ACPI_DP_H > + > +struct acpi_ctx; > + > +/* > + * Writing Device Properties objects via _DSD > + * > + * This is described in ACPI 6.3 section 6.2.5 > + * > + * This provides a structure to handle nested device-specific data which ends > + * up in a _DSD table. > + * > + * https://www.kernel.org/doc/html/latest/firmware-guide/acpi/DSD-properties-rules.html > + * https://uefi.org/sites/default/files/resources/_DSD-device-properties-UUID.pdf > + * https://uefi.org/sites/default/files/resources/_DSD-hierarchical-data-extension-UUID-v1.1.pdf > + * > + * The Device Property Hierarchy can be multiple levels deep with multiple > + * children possible in each level. In order to support this flexibility > + * the device property hierarchy must be built up before being written out. > + * > + * For example: > + * > + * // Child table with string and integer nits: /* */ > + * struct acpi_dp *child = acpi_dp_new_table("CHLD"); > + * acpi_dp_add_string(child, "childstring", "CHILD"); > + * acpi_dp_add_integer(child, "childint", 100); > + * > + * // _DSD table with integer and gpio and child pointer nits: /* */ > + * struct acpi_dp *dsd = acpi_dp_new_table("_DSD"); > + * acpi_dp_add_integer(dsd, "number1", 1); > + * acpi_dp_add_gpio(dsd, "gpio", "\_SB.PCI0.GPIO", 0, 0, 1); > + * acpi_dp_add_child(dsd, "child", child); > + * > + * // Write entries into SSDT and clean up resources nits: /* */ > + * acpi_dp_write(dsd); > + * > + * Name(_DSD, Package() { > + * ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301") > + * Package() { > + * Package() { "gpio", Package() { \_SB.PCI0.GPIO, 0, 0, 0 } } > + * Package() { "number1", 1 } > + * } > + * ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b") > + * Package() { > + * Package() { "child", CHLD } > + * } > + * } > + * Name(CHLD, Package() { > + * ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301") > + * Package() { > + * Package() { "childstring", "CHILD" } > + * Package() { "childint", 100 } > + * } > + * } > + */ > + > +#define ACPI_DP_UUID "daffd814-6eba-4d8c-8a91-bc9bbf4aa301" > +#define ACPI_DP_CHILD_UUID "dbb8e3e6-5886-4ba6-8795-1319f52a966b" > + > +/** > + * enum acpi_dp_type - types of device property objects > + * > + * These refer to the types defined by struct acpi_dp below > + * > + * @ACPI_DP_TYPE_UNKNOWN: Unknown / do not use > + * @ACPI_DP_TYPE_INTEGER: Integer value (u64) in @integer > + * @ACPI_DP_TYPE_STRING: String value in @string > + * @ACPI_DP_TYPE_REFERENCE: Reference to another object, with value in @string > + * @ACPI_DP_TYPE_TABLE: Type for a top-level table which may have children > + * @ACPI_DP_TYPE_ARRAY: Array of items with first item in @array and following > + * items linked from that item's @next > + * @ACPI_DP_TYPE_CHILD: Child object, with siblings in that child's @next > + */ > +enum acpi_dp_type { > + ACPI_DP_TYPE_UNKNOWN, > + ACPI_DP_TYPE_INTEGER, > + ACPI_DP_TYPE_STRING, > + ACPI_DP_TYPE_REFERENCE, > + ACPI_DP_TYPE_TABLE, > + ACPI_DP_TYPE_ARRAY, > + ACPI_DP_TYPE_CHILD, > +}; > + > +/** > + * struct acpi_dp - ACPI device properties > + * > + * @type: Table type > + * @name: Name of object, typically _DSD but could be CHLD for a child object. > + * This can be NULL if there is no name > + * @next: Next object in list (next array element or next sibling) > + * @child: Pointer to first child, if @type == ACPI_DP_TYPE_CHILD, else NULL > + * @array: First array element, if @type == ACPI_DP_TYPE_ARRAY, else NULL > + * @integer: Integer value of the property, if @type == ACPI_DP_TYPE_INTEGER > + * @string: String value of the property, if @type == ACPI_DP_TYPE_STRING; > + * child name if @type == ACPI_DP_TYPE_CHILD; > + * reference name if @type == ACPI_DP_TYPE_REFERENCE; > + */ > +struct acpi_dp { > + enum acpi_dp_type type; > + const char *name; > + struct acpi_dp *next; > + union { > + struct acpi_dp *child; > + struct acpi_dp *array; > + }; > + union { > + u64 integer; > + const char *string; > + }; > +}; > + > +/** > + * acpi_dp_new_table() - Start a new Device Property table > + * > + * @ref: ACPI reference (e.g. "_DSD") > + * @return pointer to table, or NULL if out of memory > + */ > +struct acpi_dp *acpi_dp_new_table(const char *ref); > + > +/** > + * acpi_dp_add_integer() - Add integer Device Property > + * > + * A new node is added to the end of the property list of @dp > + * > + * @dp: Table to add this property to > + * @name: Name of property, or NULL for none > + * @value: Integer value > + * @return pointer to new node, or NULL if out of memory > + */ > +struct acpi_dp *acpi_dp_add_integer(struct acpi_dp *dp, const char *name, > + u64 value); > + > +/** > + * acpi_dp_add_string() - Add string Device Property > + * > + * A new node is added to the end of the property list of @dp > + * > + * @dp: Table to add this property to > + * @name: Name of property, or NULL for none > + * @string: String value > + * @return pointer to new node, or NULL if out of memory > + */ > +struct acpi_dp *acpi_dp_add_string(struct acpi_dp *dp, const char *name, > + const char *string); > + > +/** > + * acpi_dp_add_reference() - Add reference Device Property > + * > + * A new node is added to the end of the property list of @dp > + * > + * @dp: Table to add this property to > + * @name: Name of property, or NULL for none > + * @reference: Reference value > + * @return pointer to new node, or NULL if out of memory > + */ > +struct acpi_dp *acpi_dp_add_reference(struct acpi_dp *dp, const char *name, > + const char *reference); > + > +/** > + * acpi_dp_add_array() - Add array Device Property > + * > + * A new node is added to the end of the property list of @dp, with the array > + * attached to that. > + * > + * @dp: Table to add this property to > + * @name: Name of property, or NULL for none > + * @return pointer to new node, or NULL if out of memory > + */ > +struct acpi_dp *acpi_dp_add_array(struct acpi_dp *dp, struct acpi_dp *array); > + > +/** > + * acpi_dp_add_integer_array() - Add an array of integers > + * > + * A new node is added to the end of the property list of @dp, with the array > + * attached to that. Each element of the array becomes a new node. > + * > + * @dp: Table to add this property to > + * @name: Name of property, or NULL for none > + * @return pointer to new array node, or NULL if out of memory > + */ > +struct acpi_dp *acpi_dp_add_integer_array(struct acpi_dp *dp, const char *name, > + u64 *array, int len); > + > +/** > + * acpi_dp_add_child() - Add a child table of Device Properties > + * > + * A new node is added as a child of @dp > + * > + * @dp: Table to add this child to > + * @name: Name of child, or NULL for none > + * @child: Child node to add > + * @return pointer to new array node, or NULL if out of memory new child node > + */ > +struct acpi_dp *acpi_dp_add_child(struct acpi_dp *dp, const char *name, > + struct acpi_dp *child); > + > +/** > + * acpi_dp_write() - Write Device Property hierarchy and clean up resources > + * > + * This writes the table using acpigen and then frees it > + * > + * @table: Table to write > + * @return 0 if OK, -ve on error > + */ > +int acpi_dp_write(struct acpi_ctx *ctx, struct acpi_dp *table); > + > +#endif > diff --git a/include/acpi/acpigen.h b/include/acpi/acpigen.h > index f45a19714b..40cd72504a 100644 > --- a/include/acpi/acpigen.h > +++ b/include/acpi/acpigen.h > @@ -31,6 +31,7 @@ enum { > PACKAGE_OP = 0x12, > DUAL_NAME_PREFIX = 0x2e, > MULTI_NAME_PREFIX = 0x2f, > + ROOT_PREFIX = 0x5c, > }; > > /** > diff --git a/lib/acpi/Makefile b/lib/acpi/Makefile > index 85a1f774ad..5c2f793701 100644 > --- a/lib/acpi/Makefile > +++ b/lib/acpi/Makefile > @@ -3,4 +3,5 @@ > > obj-y += acpigen.o > obj-y += acpi_device.o > +obj-y += acpi_dp.o > obj-y += acpi_table.o > diff --git a/lib/acpi/acpi_dp.c b/lib/acpi/acpi_dp.c > new file mode 100644 > index 0000000000..ebbc5d5538 > --- /dev/null > +++ b/lib/acpi/acpi_dp.c > @@ -0,0 +1,323 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Generation of tables for particular device types > + * > + * Copyright 2019 Google LLC > + * Mostly taken from coreboot file acpi_device.c > + */ > + > +#include <common.h> > +#include <dm.h> > +#include <log.h> > +#include <malloc.h> > +#include <uuid.h> > +#include <acpi/acpigen.h> > +#include <acpi/acpi_dp.h> > +#include <dm/acpi.h> > + > +static void acpi_dp_write_array(struct acpi_ctx *ctx, > + const struct acpi_dp *array); > + > +static void acpi_dp_write_value(struct acpi_ctx *ctx, > + const struct acpi_dp *prop) > +{ > + switch (prop->type) { > + case ACPI_DP_TYPE_INTEGER: > + acpigen_write_integer(ctx, prop->integer); > + break; > + case ACPI_DP_TYPE_STRING: > + case ACPI_DP_TYPE_CHILD: > + acpigen_write_string(ctx, prop->string); > + break; > + case ACPI_DP_TYPE_REFERENCE: > + acpigen_emit_namestring(ctx, prop->string); > + break; > + case ACPI_DP_TYPE_ARRAY: > + acpi_dp_write_array(ctx, prop->array); > + break; > + default: > + break; > + } > +} > + > +/* Package (2) { "prop->name", VALUE } */ > +static void acpi_dp_write_property(struct acpi_ctx *ctx, > + const struct acpi_dp *prop) > +{ > + acpigen_write_package(ctx, 2); > + acpigen_write_string(ctx, prop->name); > + acpi_dp_write_value(ctx, prop); > + acpigen_pop_len(ctx); > +} > + > +/* Write array of Device Properties */ > +static void acpi_dp_write_array(struct acpi_ctx *ctx, > + const struct acpi_dp *array) > +{ > + const struct acpi_dp *dp; > + char *pkg_count; > + > + /* Package element count determined as it is populated */ > + pkg_count = acpigen_write_package(ctx, 0); > + > + /* > + * Only acpi_dp of type DP_TYPE_TABLE is allowed to be an array. > + * DP_TYPE_TABLE does not have a value to be written. Thus, start > + * the loop from next type in the array. > + */ > + for (dp = array->next; dp; dp = dp->next) { > + acpi_dp_write_value(ctx, dp); > + (*pkg_count)++; > + } > + > + acpigen_pop_len(ctx); > +} > + > +static void acpi_dp_free(struct acpi_dp *dp) > +{ > + assert(dp); > + while (dp) { > + struct acpi_dp *p = dp->next; > + > + switch (dp->type) { > + case ACPI_DP_TYPE_CHILD: > + acpi_dp_free(dp->child); > + break; > + case ACPI_DP_TYPE_ARRAY: > + acpi_dp_free(dp->array); > + break; > + default: > + break; > + } > + > + free(dp); > + dp = p; > + } > +} > + > +int acpi_dp_write_(struct acpi_ctx *ctx, struct acpi_dp *table) This does not look like a public API but an internal helper. Can we rename this to acpi_dp_write_internal or acpi_dp_write_helper, and make it static? > +{ > + struct acpi_dp *dp, *prop; > + char *dp_count, *prop_count = NULL; > + int child_count = 0; > + int ret; > + > + assert(table); > + if (table->type != ACPI_DP_TYPE_TABLE) > + return 0; > + > + /* Name (name) */ > + acpigen_write_name(ctx, table->name); > + > + /* Device Property list starts with the next entry */ > + prop = table->next; > + > + /* Package (DP), default to assuming no properties or children */ > + dp_count = acpigen_write_package(ctx, 0); > + > + /* Print base properties */ > + for (dp = prop; dp; dp = dp->next) { > + if (dp->type == ACPI_DP_TYPE_CHILD) { > + child_count++; > + } else { > + /* > + * The UUID and package is only added when > + * we come across the first property. This > + * is to avoid creating a zero-length package > + * in situations where there are only children. > + */ > + if (!prop_count) { > + *dp_count += 2; > + /* ToUUID (ACPI_DP_UUID) */ > + ret = acpigen_write_uuid(ctx, ACPI_DP_UUID); > + if (ret) > + return log_msg_ret("touuid", ret); > + /* > + * Package (PROP), element count determined as > + * it is populated > + */ > + prop_count = acpigen_write_package(ctx, 0); > + } > + (*prop_count)++; > + acpi_dp_write_property(ctx, dp); > + } > + } > + > + if (prop_count) { > + /* Package (PROP) length, if a package was written */ > + acpigen_pop_len(ctx); > + } > + > + if (child_count) { > + /* Update DP package count to 2 or 4 */ > + *dp_count += 2; > + /* ToUUID (ACPI_DP_CHILD_UUID) */ > + ret = acpigen_write_uuid(ctx, ACPI_DP_CHILD_UUID); > + if (ret) > + return log_msg_ret("child uuid", ret); > + > + /* Print child pointer properties */ > + acpigen_write_package(ctx, child_count); > + > + for (dp = prop; dp; dp = dp->next) > + if (dp->type == ACPI_DP_TYPE_CHILD) > + acpi_dp_write_property(ctx, dp); > + /* Package (CHILD) length */ > + acpigen_pop_len(ctx); > + } > + > + /* Package (DP) length */ > + acpigen_pop_len(ctx); > + > + /* Recursively parse children into separate tables */ > + for (dp = prop; dp; dp = dp->next) { > + if (dp->type == ACPI_DP_TYPE_CHILD) { > + ret = acpi_dp_write_(ctx, dp->child); > + if (ret) > + return log_msg_ret("dp child", ret); > + } > + } > + > + return 0; > +} > + > +int acpi_dp_write(struct acpi_ctx *ctx, struct acpi_dp *table) > +{ > + int ret; > + > + ret = acpi_dp_write_(ctx, table); > + > + /* Clean up */ > + acpi_dp_free(table); > + > + if (ret) > + return log_msg_ret("write", ret); > + > + return 0; > +} > + > +static struct acpi_dp *acpi_dp_new(struct acpi_dp *dp, enum acpi_dp_type type, > + const char *name) > +{ > + struct acpi_dp *new; > + > + new = malloc(sizeof(struct acpi_dp)); > + if (!new) > + return NULL; > + > + memset(new, '\0', sizeof(*new)); > + new->type = type; > + new->name = name; > + > + if (dp) { > + /* Add to end of property list */ > + while (dp->next) > + dp = dp->next; > + dp->next = new; > + } > + > + return new; > +} > + > +struct acpi_dp *acpi_dp_new_table(const char *name) > +{ > + return acpi_dp_new(NULL, ACPI_DP_TYPE_TABLE, name); > +} > + > +struct acpi_dp *acpi_dp_add_integer(struct acpi_dp *dp, const char *name, > + u64 value) > +{ > + struct acpi_dp *new; > + > + assert(dp); > + new = acpi_dp_new(dp, ACPI_DP_TYPE_INTEGER, name); > + > + if (new) > + new->integer = value; > + > + return new; > +} > + > +struct acpi_dp *acpi_dp_add_string(struct acpi_dp *dp, const char *name, > + const char *string) > +{ > + struct acpi_dp *new; > + > + assert(dp); > + new = acpi_dp_new(dp, ACPI_DP_TYPE_STRING, name); > + if (new) > + new->string = string; > + > + return new; > +} > + > +struct acpi_dp *acpi_dp_add_reference(struct acpi_dp *dp, const char *name, > + const char *reference) > +{ > + struct acpi_dp *new; > + > + assert(dp); > + new = acpi_dp_new(dp, ACPI_DP_TYPE_REFERENCE, name); > + if (new) > + new->string = reference; > + > + return new; > +} > + > +struct acpi_dp *acpi_dp_add_child(struct acpi_dp *dp, const char *name, > + struct acpi_dp *child) > +{ > + struct acpi_dp *new; > + > + assert(dp); > + if (child->type != ACPI_DP_TYPE_TABLE) > + return NULL; > + > + new = acpi_dp_new(dp, ACPI_DP_TYPE_CHILD, name); > + if (new) { > + new->child = child; > + new->string = child->name; > + } > + > + return new; > +} > + > +struct acpi_dp *acpi_dp_add_array(struct acpi_dp *dp, struct acpi_dp *array) > +{ > + struct acpi_dp *new; > + > + assert(dp); > + assert(array); > + if (array->type != ACPI_DP_TYPE_TABLE) > + return NULL; > + > + new = acpi_dp_new(dp, ACPI_DP_TYPE_ARRAY, array->name); > + if (new) > + new->array = array; > + > + return new; > +} > + > +struct acpi_dp *acpi_dp_add_integer_array(struct acpi_dp *dp, const char *name, > + u64 *array, int len) > +{ > + struct acpi_dp *dp_array; > + int i; > + > + assert(dp); > + if (len <= 0) > + return NULL; > + > + dp_array = acpi_dp_new_table(name); > + if (!dp_array) > + return NULL; > + > + for (i = 0; i < len; i++) > + if (!acpi_dp_add_integer(dp_array, NULL, array[i])) > + break; > + > + if (!acpi_dp_add_array(dp, dp_array)) > + return NULL; > + > + return dp_array; > +} > diff --git a/test/dm/Makefile b/test/dm/Makefile > index e3e0cccf01..eb62faa0e3 100644 > --- a/test/dm/Makefile > +++ b/test/dm/Makefile > @@ -15,6 +15,7 @@ obj-$(CONFIG_UT_DM) += core.o > ifneq ($(CONFIG_SANDBOX),) > obj-$(CONFIG_ACPIGEN) += acpi.o > obj-$(CONFIG_ACPIGEN) += acpigen.o > +obj-$(CONFIG_ACPIGEN) += acpi_dp.o > obj-$(CONFIG_SOUND) += audio.o > obj-$(CONFIG_BLK) += blk.o > obj-$(CONFIG_BOARD) += board.o > diff --git a/test/dm/acpi_dp.c b/test/dm/acpi_dp.c > new file mode 100644 > index 0000000000..c11394786f > --- /dev/null > +++ b/test/dm/acpi_dp.c > @@ -0,0 +1,405 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Tests for ACPI code generation via a device-property table > + * > + * Copyright 2019 Google LLC > + * Written by Simon Glass <sjg at chromium.org> > + */ > + > +#include <common.h> > +#include <dm.h> > +#include <uuid.h> > +#include <acpi/acpigen.h> > +#include <acpi/acpi_dp.h> > +#include <asm/unaligned.h> > +#include <dm/acpi.h> > +#include <dm/test.h> > +#include <test/ut.h> > + > +#define TEST_INT8 0x7d > +#define TEST_INT16 0x2345 > +#define TEST_INT32 0x12345678 > +#define TEST_INT64 0x4567890123456 > +#define TEST_STR "testing acpi strings" > +#define TEST_REF "\\SB.I2C0.TPM2" > +#define EXPECT_REF "SB__I2C0TPM2" > + > +static int alloc_context(struct acpi_ctx **ctxp) > +{ > + struct acpi_ctx *ctx; > + > + *ctxp = NULL; > + ctx = malloc(sizeof(*ctx)); > + if (!ctx) > + return -ENOMEM; > + memset(ctx, '\0', sizeof(*ctx)); > + ctx->current = malloc(500); nits: a random magic number for the context size? > + if (!ctx->current) > + return -ENOMEM; > + *ctxp = ctx; > + > + return 0; > +} > + > +static void free_context(struct acpi_ctx **ctxp) > +{ > + free(*ctxp); > + *ctxp = NULL; > +} Can we make the above 2 routines a public API for test codes only? > + > +/** > + * get_length() - decode a three-byte length field > + * > + * @ptr: Length encoded as per ACPI > + * @return decoded length, or -EINVAL on error > + */ > +static int get_length(u8 *ptr) > +{ > + if (!(*ptr & 0x80)) > + return -EINVAL; > + > + return (*ptr & 0xf) | ptr[1] << 4 | ptr[2] << 12; > +} This same function has repeated in various files. This merites a new API in the ACPI core. > + > +/* Test emitting an empty table */ > +static int dm_test_acpi_dp_new_table(struct unit_test_state *uts) > +{ > + struct acpi_ctx *ctx; > + struct acpi_dp *dp; > + u8 *ptr; > + > + ut_assertok(alloc_context(&ctx)); > + > + dp = acpi_dp_new_table("FRED"); > + ut_assertnonnull(dp); > + > + ptr = acpigen_get_current(ctx); > + ut_assertok(acpi_dp_write(ctx, dp)); > + ut_asserteq(10, acpigen_get_current(ctx) - ptr); > + ut_asserteq(NAME_OP, *(u8 *)ptr); > + ut_asserteq_strn("FRED", (char *)ptr + 1); > + ut_asserteq(PACKAGE_OP, ptr[5]); > + ut_asserteq(4, get_length(ptr + 6)); > + ut_asserteq(0, ptr[9]); > + > + free_context(&ctx); > + > + return 0; > +} > +DM_TEST(dm_test_acpi_dp_new_table, 0); > + > +/* Test emitting an integer */ > +static int dm_test_acpi_dp_int(struct unit_test_state *uts) > +{ > + struct acpi_ctx *ctx; > + char uuid[UUID_STR_LEN + 1]; > + struct acpi_dp *dp; > + u8 *ptr; > + > + ut_assertok(alloc_context(&ctx)); > + > + dp = acpi_dp_new_table("FRED"); > + ut_assertnonnull(dp); > + ut_assertnonnull(acpi_dp_add_integer(dp, "MARY", TEST_INT32)); > + > + ptr = acpigen_get_current(ctx); > + ut_assertok(acpi_dp_write(ctx, dp)); > + ut_asserteq(54, acpigen_get_current(ctx) - ptr); > + ut_asserteq(NAME_OP, *(u8 *)ptr); > + ut_asserteq_strn("FRED", (char *)ptr + 1); > + ut_asserteq(PACKAGE_OP, ptr[5]); > + ut_asserteq(48, get_length(ptr + 6)); > + ut_asserteq(2, ptr[9]); > + > + /* UUID */ > + ut_asserteq(BUFFER_OP, ptr[10]); > + ut_asserteq(22, get_length(ptr + 11)); > + ut_asserteq(WORD_PREFIX, ptr[14]); > + ut_asserteq(16, get_unaligned((u16 *)(ptr + 15))); > + uuid_bin_to_str(ptr + 17, uuid, 1); > + ut_asserteq_str(ACPI_DP_UUID, uuid); > + > + /* Container package */ > + ut_asserteq(PACKAGE_OP, ptr[33]); > + ut_asserteq(20, get_length(ptr + 34)); > + ut_asserteq(1, ptr[37]); > + > + /* Package with name and (integer) value */ > + ut_asserteq(PACKAGE_OP, ptr[38]); > + ut_asserteq(15, get_length(ptr + 39)); > + ut_asserteq(2, ptr[42]); > + ut_asserteq(STRING_PREFIX, ptr[43]); > + ut_asserteq_str("MARY", (char *)ptr + 44); > + > + ut_asserteq(DWORD_PREFIX, ptr[49]); > + ut_asserteq(TEST_INT32, get_unaligned((u32 *)(ptr + 50))); > + > + free_context(&ctx); > + > + return 0; > +} > +DM_TEST(dm_test_acpi_dp_int, 0); > + > +/* Test emitting a 64-bit integer */ > +static int dm_test_acpi_dp_int64(struct unit_test_state *uts) > +{ > + struct acpi_ctx *ctx; > + struct acpi_dp *dp; > + u8 *ptr; > + > + ut_assertok(alloc_context(&ctx)); > + > + dp = acpi_dp_new_table("FRED"); > + ut_assertnonnull(dp); > + ut_assertnonnull(acpi_dp_add_integer(dp, "MARY", TEST_INT64)); > + > + ptr = acpigen_get_current(ctx); > + ut_assertok(acpi_dp_write(ctx, dp)); > + ut_asserteq(58, acpigen_get_current(ctx) - ptr); > + > + ut_asserteq(QWORD_PREFIX, ptr[49]); > + ut_asserteq_64(TEST_INT64, get_unaligned((u64 *)(ptr + 50))); > + > + free_context(&ctx); > + > + return 0; > +} > +DM_TEST(dm_test_acpi_dp_int64, 0); > + > +/* Test emitting a 16-bit integer */ > +static int dm_test_acpi_dp_int16(struct unit_test_state *uts) > +{ > + struct acpi_ctx *ctx; > + struct acpi_dp *dp; > + u8 *ptr; > + > + ut_assertok(alloc_context(&ctx)); > + > + dp = acpi_dp_new_table("FRED"); > + ut_assertnonnull(dp); > + ut_assertnonnull(acpi_dp_add_integer(dp, "MARY", TEST_INT16)); > + > + ptr = acpigen_get_current(ctx); > + ut_assertok(acpi_dp_write(ctx, dp)); > + ut_asserteq(52, acpigen_get_current(ctx) - ptr); > + > + ut_asserteq(WORD_PREFIX, ptr[49]); > + ut_asserteq(TEST_INT16, get_unaligned((u16 *)(ptr + 50))); > + > + free_context(&ctx); > + > + return 0; > +} > +DM_TEST(dm_test_acpi_dp_int16, 0); > + > +/* Test emitting a 8-bit integer */ > +static int dm_test_acpi_dp_int8(struct unit_test_state *uts) > +{ > + struct acpi_ctx *ctx; > + struct acpi_dp *dp; > + u8 *ptr; > + > + ut_assertok(alloc_context(&ctx)); > + > + dp = acpi_dp_new_table("FRED"); > + ut_assertnonnull(dp); > + ut_assertnonnull(acpi_dp_add_integer(dp, "MARY", TEST_INT8)); > + > + ptr = acpigen_get_current(ctx); > + ut_assertok(acpi_dp_write(ctx, dp)); > + ut_asserteq(51, acpigen_get_current(ctx) - ptr); > + > + ut_asserteq(BYTE_PREFIX, ptr[49]); > + ut_asserteq(TEST_INT8, ptr[50]); > + > + free_context(&ctx); > + > + return 0; > +} > +DM_TEST(dm_test_acpi_dp_int8, 0); > + > +/* Test emitting multiple values */ > +static int dm_test_acpi_dp_multiple(struct unit_test_state *uts) > +{ > + struct acpi_ctx *ctx; > + struct acpi_dp *dp; > + u8 *ptr; > + > + ut_assertok(alloc_context(&ctx)); > + > + dp = acpi_dp_new_table("FRED"); > + ut_assertnonnull(dp); > + ut_assertnonnull(acpi_dp_add_integer(dp, "int16", TEST_INT16)); > + ut_assertnonnull(acpi_dp_add_string(dp, "str", TEST_STR)); > + ut_assertnonnull(acpi_dp_add_reference(dp, "ref", TEST_REF)); > + > + ptr = acpigen_get_current(ctx); > + ut_assertok(acpi_dp_write(ctx, dp)); > + ut_asserteq(110, acpigen_get_current(ctx) - ptr); > + > + ut_asserteq(WORD_PREFIX, ptr[0x32]); > + ut_asserteq(TEST_INT16, get_unaligned((u16 *)(ptr + 0x33))); > + ut_asserteq(STRING_PREFIX, ptr[0x3f]); > + ut_asserteq_str(TEST_STR, (char *)ptr + 0x40); > + ut_asserteq(ROOT_PREFIX, ptr[0x5f]); > + ut_asserteq(MULTI_NAME_PREFIX, ptr[0x60]); > + ut_asserteq(3, ptr[0x61]); > + ut_asserteq_strn(EXPECT_REF, (char *)ptr + 0x62); > + > + free_context(&ctx); > + > + return 0; > +} > +DM_TEST(dm_test_acpi_dp_multiple, 0); > + > +/* Test emitting an array */ > +static int dm_test_acpi_dp_array(struct unit_test_state *uts) > +{ > + struct acpi_ctx *ctx; > + struct acpi_dp *dp; > + u64 speed[4]; > + u8 *ptr; > + > + ut_assertok(alloc_context(&ctx)); > + > + dp = acpi_dp_new_table("FRED"); > + ut_assertnonnull(dp); > + speed[0] = TEST_INT8; > + speed[1] = TEST_INT16; > + speed[2] = TEST_INT32; > + speed[3] = TEST_INT64; > + ut_assertnonnull(acpi_dp_add_integer_array(dp, "speeds", speed, > + ARRAY_SIZE(speed))); > + > + ptr = acpigen_get_current(ctx); > + ut_assertok(acpi_dp_write(ctx, dp)); > + ut_asserteq(75, acpigen_get_current(ctx) - ptr); > + > + ut_asserteq(BYTE_PREFIX, ptr[0x38]); > + ut_asserteq(TEST_INT8, ptr[0x39]); > + > + ut_asserteq(WORD_PREFIX, ptr[0x3a]); > + ut_asserteq(TEST_INT16, get_unaligned((u16 *)(ptr + 0x3b))); > + > + ut_asserteq(DWORD_PREFIX, ptr[0x3d]); > + ut_asserteq(TEST_INT32, get_unaligned((u32 *)(ptr + 0x3e))); > + > + ut_asserteq(QWORD_PREFIX, ptr[0x42]); > + ut_asserteq_64(TEST_INT64, get_unaligned((u64 *)(ptr + 0x43))); > + > + free_context(&ctx); > + > + return 0; > +} > +DM_TEST(dm_test_acpi_dp_array, 0); > + > +/* Test emitting a child */ > +static int dm_test_acpi_dp_child(struct unit_test_state *uts) > +{ > + struct acpi_ctx *ctx; > + struct acpi_dp *dp, *child1, *child2; > + char uuid[UUID_STR_LEN + 1]; > + u8 *ptr, *pptr; > + int i; > + > + ut_assertok(alloc_context(&ctx)); > + > + child1 = acpi_dp_new_table("child"); > + ut_assertnonnull(child1); > + ut_assertnonnull(acpi_dp_add_integer(child1, "height", TEST_INT16)); > + > + child2 = acpi_dp_new_table("child"); > + ut_assertnonnull(child2); > + ut_assertnonnull(acpi_dp_add_integer(child2, "age", TEST_INT8)); > + > + dp = acpi_dp_new_table("FRED"); > + ut_assertnonnull(dp); > + > + ut_assertnonnull(acpi_dp_add_child(dp, "anna", child1)); > + ut_assertnonnull(acpi_dp_add_child(dp, "john", child2)); > + > + ptr = acpigen_get_current(ctx); > + ut_assertok(acpi_dp_write(ctx, dp)); > + ut_asserteq(178, acpigen_get_current(ctx) - ptr); > + > + /* UUID for child extension using Hierarchical Data Extension UUID */ > + ut_asserteq(BUFFER_OP, ptr[10]); > + ut_asserteq(22, get_length(ptr + 11)); > + ut_asserteq(WORD_PREFIX, ptr[14]); > + ut_asserteq(16, get_unaligned((u16 *)(ptr + 15))); > + uuid_bin_to_str(ptr + 17, uuid, 1); > + ut_asserteq_str(ACPI_DP_CHILD_UUID, uuid); > + > + /* Package with two children */ > + ut_asserteq(PACKAGE_OP, ptr[0x21]); > + ut_asserteq(0x28, get_length(ptr + 0x22)); > + ut_asserteq(2, ptr[0x25]); > + > + /* First we expect the two children as string/value */ > + pptr = ptr + 0x26; > + for (i = 0; i < 2; i++) { > + ut_asserteq(PACKAGE_OP, pptr[0]); > + ut_asserteq(0x11, get_length(pptr + 1)); > + ut_asserteq(2, pptr[4]); > + ut_asserteq(STRING_PREFIX, pptr[5]); > + ut_asserteq_str(i ? "john" : "anna", (char *)pptr + 6); > + ut_asserteq(STRING_PREFIX, pptr[11]); > + ut_asserteq_str("child", (char *)pptr + 12); > + pptr += 0x12; > + } > + > + /* Write the two children */ > + ut_asserteq(0x4a, pptr - ptr); > + for (i = 0; i < 2; i++) { > + const char *prop = i ? "age" : "height"; > + const int datalen = i ? 1 : 2; > + int len = strlen(prop) + 1; > + > + ut_asserteq(NAME_OP, pptr[0]); > + ut_asserteq_strn("chil", (char *)pptr + 1); > + ut_asserteq(PACKAGE_OP, pptr[5]); > + ut_asserteq(0x27 + len + datalen, get_length(pptr + 6)); > + ut_asserteq(2, pptr[9]); > + > + /* UUID */ > + ut_asserteq(BUFFER_OP, pptr[10]); > + ut_asserteq(22, get_length(pptr + 11)); > + ut_asserteq(WORD_PREFIX, pptr[14]); > + ut_asserteq(16, get_unaligned((u16 *)(pptr + 15))); > + uuid_bin_to_str(pptr + 17, uuid, 1); > + ut_asserteq_str(ACPI_DP_UUID, uuid); > + pptr += 33; > + > + /* Containing package */ > + ut_asserteq(i ? 0xa1 : 0x6b, pptr - ptr); > + ut_asserteq(PACKAGE_OP, pptr[0]); > + ut_asserteq(0xb + len + datalen, get_length(pptr + 1)); > + ut_asserteq(1, pptr[4]); > + > + /* Package containing the property-name string and the value */ > + pptr += 5; > + ut_asserteq(i ? 0xa6 : 0x70, pptr - ptr); > + ut_asserteq(PACKAGE_OP, pptr[0]); > + ut_asserteq(6 + len + datalen, get_length(pptr + 1)); > + ut_asserteq(2, pptr[4]); > + > + ut_asserteq(STRING_PREFIX, pptr[5]); > + ut_asserteq_str(i ? "age" : "height", (char *)pptr + 6); > + pptr += 6 + len; > + if (i) { > + ut_asserteq(BYTE_PREFIX, pptr[0]); > + ut_asserteq(TEST_INT8, pptr[1]); > + } else { > + ut_asserteq(WORD_PREFIX, pptr[0]); > + ut_asserteq(TEST_INT16, > + get_unaligned((u16 *)(pptr + 1))); > + } > + pptr += 1 + datalen; > + } > + ut_asserteq(178, pptr - ptr); > + > + free_context(&ctx); > + > + return 0; > +} > +DM_TEST(dm_test_acpi_dp_child, 0); Regards, Bin
diff --git a/include/acpi/acpi_dp.h b/include/acpi/acpi_dp.h new file mode 100644 index 0000000000..3fd048e111 --- /dev/null +++ b/include/acpi/acpi_dp.h @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Device properties, a temporary data structure for adding to ACPI code + * + * Copyright 2019 Google LLC + * Mostly taken from coreboot file acpi_device.h + */ + +#ifndef __ACPI_DP_H +#define __ACPI_DP_H + +struct acpi_ctx; + +/* + * Writing Device Properties objects via _DSD + * + * This is described in ACPI 6.3 section 6.2.5 + * + * This provides a structure to handle nested device-specific data which ends + * up in a _DSD table. + * + * https://www.kernel.org/doc/html/latest/firmware-guide/acpi/DSD-properties-rules.html + * https://uefi.org/sites/default/files/resources/_DSD-device-properties-UUID.pdf + * https://uefi.org/sites/default/files/resources/_DSD-hierarchical-data-extension-UUID-v1.1.pdf + * + * The Device Property Hierarchy can be multiple levels deep with multiple + * children possible in each level. In order to support this flexibility + * the device property hierarchy must be built up before being written out. + * + * For example: + * + * // Child table with string and integer + * struct acpi_dp *child = acpi_dp_new_table("CHLD"); + * acpi_dp_add_string(child, "childstring", "CHILD"); + * acpi_dp_add_integer(child, "childint", 100); + * + * // _DSD table with integer and gpio and child pointer + * struct acpi_dp *dsd = acpi_dp_new_table("_DSD"); + * acpi_dp_add_integer(dsd, "number1", 1); + * acpi_dp_add_gpio(dsd, "gpio", "\_SB.PCI0.GPIO", 0, 0, 1); + * acpi_dp_add_child(dsd, "child", child); + * + * // Write entries into SSDT and clean up resources + * acpi_dp_write(dsd); + * + * Name(_DSD, Package() { + * ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301") + * Package() { + * Package() { "gpio", Package() { \_SB.PCI0.GPIO, 0, 0, 0 } } + * Package() { "number1", 1 } + * } + * ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b") + * Package() { + * Package() { "child", CHLD } + * } + * } + * Name(CHLD, Package() { + * ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301") + * Package() { + * Package() { "childstring", "CHILD" } + * Package() { "childint", 100 } + * } + * } + */ + +#define ACPI_DP_UUID "daffd814-6eba-4d8c-8a91-bc9bbf4aa301" +#define ACPI_DP_CHILD_UUID "dbb8e3e6-5886-4ba6-8795-1319f52a966b" + +/** + * enum acpi_dp_type - types of device property objects + * + * These refer to the types defined by struct acpi_dp below + * + * @ACPI_DP_TYPE_UNKNOWN: Unknown / do not use + * @ACPI_DP_TYPE_INTEGER: Integer value (u64) in @integer + * @ACPI_DP_TYPE_STRING: String value in @string + * @ACPI_DP_TYPE_REFERENCE: Reference to another object, with value in @string + * @ACPI_DP_TYPE_TABLE: Type for a top-level table which may have children + * @ACPI_DP_TYPE_ARRAY: Array of items with first item in @array and following + * items linked from that item's @next + * @ACPI_DP_TYPE_CHILD: Child object, with siblings in that child's @next + */ +enum acpi_dp_type { + ACPI_DP_TYPE_UNKNOWN, + ACPI_DP_TYPE_INTEGER, + ACPI_DP_TYPE_STRING, + ACPI_DP_TYPE_REFERENCE, + ACPI_DP_TYPE_TABLE, + ACPI_DP_TYPE_ARRAY, + ACPI_DP_TYPE_CHILD, +}; + +/** + * struct acpi_dp - ACPI device properties + * + * @type: Table type + * @name: Name of object, typically _DSD but could be CHLD for a child object. + * This can be NULL if there is no name + * @next: Next object in list (next array element or next sibling) + * @child: Pointer to first child, if @type == ACPI_DP_TYPE_CHILD, else NULL + * @array: First array element, if @type == ACPI_DP_TYPE_ARRAY, else NULL + * @integer: Integer value of the property, if @type == ACPI_DP_TYPE_INTEGER + * @string: String value of the property, if @type == ACPI_DP_TYPE_STRING; + * child name if @type == ACPI_DP_TYPE_CHILD; + * reference name if @type == ACPI_DP_TYPE_REFERENCE; + */ +struct acpi_dp { + enum acpi_dp_type type; + const char *name; + struct acpi_dp *next; + union { + struct acpi_dp *child; + struct acpi_dp *array; + }; + union { + u64 integer; + const char *string; + }; +}; + +/** + * acpi_dp_new_table() - Start a new Device Property table + * + * @ref: ACPI reference (e.g. "_DSD") + * @return pointer to table, or NULL if out of memory + */ +struct acpi_dp *acpi_dp_new_table(const char *ref); + +/** + * acpi_dp_add_integer() - Add integer Device Property + * + * A new node is added to the end of the property list of @dp + * + * @dp: Table to add this property to + * @name: Name of property, or NULL for none + * @value: Integer value + * @return pointer to new node, or NULL if out of memory + */ +struct acpi_dp *acpi_dp_add_integer(struct acpi_dp *dp, const char *name, + u64 value); + +/** + * acpi_dp_add_string() - Add string Device Property + * + * A new node is added to the end of the property list of @dp + * + * @dp: Table to add this property to + * @name: Name of property, or NULL for none + * @string: String value + * @return pointer to new node, or NULL if out of memory + */ +struct acpi_dp *acpi_dp_add_string(struct acpi_dp *dp, const char *name, + const char *string); + +/** + * acpi_dp_add_reference() - Add reference Device Property + * + * A new node is added to the end of the property list of @dp + * + * @dp: Table to add this property to + * @name: Name of property, or NULL for none + * @reference: Reference value + * @return pointer to new node, or NULL if out of memory + */ +struct acpi_dp *acpi_dp_add_reference(struct acpi_dp *dp, const char *name, + const char *reference); + +/** + * acpi_dp_add_array() - Add array Device Property + * + * A new node is added to the end of the property list of @dp, with the array + * attached to that. + * + * @dp: Table to add this property to + * @name: Name of property, or NULL for none + * @return pointer to new node, or NULL if out of memory + */ +struct acpi_dp *acpi_dp_add_array(struct acpi_dp *dp, struct acpi_dp *array); + +/** + * acpi_dp_add_integer_array() - Add an array of integers + * + * A new node is added to the end of the property list of @dp, with the array + * attached to that. Each element of the array becomes a new node. + * + * @dp: Table to add this property to + * @name: Name of property, or NULL for none + * @return pointer to new array node, or NULL if out of memory + */ +struct acpi_dp *acpi_dp_add_integer_array(struct acpi_dp *dp, const char *name, + u64 *array, int len); + +/** + * acpi_dp_add_child() - Add a child table of Device Properties + * + * A new node is added as a child of @dp + * + * @dp: Table to add this child to + * @name: Name of child, or NULL for none + * @child: Child node to add + * @return pointer to new array node, or NULL if out of memory + */ +struct acpi_dp *acpi_dp_add_child(struct acpi_dp *dp, const char *name, + struct acpi_dp *child); + +/** + * acpi_dp_write() - Write Device Property hierarchy and clean up resources + * + * This writes the table using acpigen and then frees it + * + * @table: Table to write + * @return 0 if OK, -ve on error + */ +int acpi_dp_write(struct acpi_ctx *ctx, struct acpi_dp *table); + +#endif diff --git a/include/acpi/acpigen.h b/include/acpi/acpigen.h index f45a19714b..40cd72504a 100644 --- a/include/acpi/acpigen.h +++ b/include/acpi/acpigen.h @@ -31,6 +31,7 @@ enum { PACKAGE_OP = 0x12, DUAL_NAME_PREFIX = 0x2e, MULTI_NAME_PREFIX = 0x2f, + ROOT_PREFIX = 0x5c, }; /** diff --git a/lib/acpi/Makefile b/lib/acpi/Makefile index 85a1f774ad..5c2f793701 100644 --- a/lib/acpi/Makefile +++ b/lib/acpi/Makefile @@ -3,4 +3,5 @@ obj-y += acpigen.o obj-y += acpi_device.o +obj-y += acpi_dp.o obj-y += acpi_table.o diff --git a/lib/acpi/acpi_dp.c b/lib/acpi/acpi_dp.c new file mode 100644 index 0000000000..ebbc5d5538 --- /dev/null +++ b/lib/acpi/acpi_dp.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generation of tables for particular device types + * + * Copyright 2019 Google LLC + * Mostly taken from coreboot file acpi_device.c + */ + +#include <common.h> +#include <dm.h> +#include <log.h> +#include <malloc.h> +#include <uuid.h> +#include <acpi/acpigen.h> +#include <acpi/acpi_dp.h> +#include <dm/acpi.h> + +static void acpi_dp_write_array(struct acpi_ctx *ctx, + const struct acpi_dp *array); + +static void acpi_dp_write_value(struct acpi_ctx *ctx, + const struct acpi_dp *prop) +{ + switch (prop->type) { + case ACPI_DP_TYPE_INTEGER: + acpigen_write_integer(ctx, prop->integer); + break; + case ACPI_DP_TYPE_STRING: + case ACPI_DP_TYPE_CHILD: + acpigen_write_string(ctx, prop->string); + break; + case ACPI_DP_TYPE_REFERENCE: + acpigen_emit_namestring(ctx, prop->string); + break; + case ACPI_DP_TYPE_ARRAY: + acpi_dp_write_array(ctx, prop->array); + break; + default: + break; + } +} + +/* Package (2) { "prop->name", VALUE } */ +static void acpi_dp_write_property(struct acpi_ctx *ctx, + const struct acpi_dp *prop) +{ + acpigen_write_package(ctx, 2); + acpigen_write_string(ctx, prop->name); + acpi_dp_write_value(ctx, prop); + acpigen_pop_len(ctx); +} + +/* Write array of Device Properties */ +static void acpi_dp_write_array(struct acpi_ctx *ctx, + const struct acpi_dp *array) +{ + const struct acpi_dp *dp; + char *pkg_count; + + /* Package element count determined as it is populated */ + pkg_count = acpigen_write_package(ctx, 0); + + /* + * Only acpi_dp of type DP_TYPE_TABLE is allowed to be an array. + * DP_TYPE_TABLE does not have a value to be written. Thus, start + * the loop from next type in the array. + */ + for (dp = array->next; dp; dp = dp->next) { + acpi_dp_write_value(ctx, dp); + (*pkg_count)++; + } + + acpigen_pop_len(ctx); +} + +static void acpi_dp_free(struct acpi_dp *dp) +{ + assert(dp); + while (dp) { + struct acpi_dp *p = dp->next; + + switch (dp->type) { + case ACPI_DP_TYPE_CHILD: + acpi_dp_free(dp->child); + break; + case ACPI_DP_TYPE_ARRAY: + acpi_dp_free(dp->array); + break; + default: + break; + } + + free(dp); + dp = p; + } +} + +int acpi_dp_write_(struct acpi_ctx *ctx, struct acpi_dp *table) +{ + struct acpi_dp *dp, *prop; + char *dp_count, *prop_count = NULL; + int child_count = 0; + int ret; + + assert(table); + if (table->type != ACPI_DP_TYPE_TABLE) + return 0; + + /* Name (name) */ + acpigen_write_name(ctx, table->name); + + /* Device Property list starts with the next entry */ + prop = table->next; + + /* Package (DP), default to assuming no properties or children */ + dp_count = acpigen_write_package(ctx, 0); + + /* Print base properties */ + for (dp = prop; dp; dp = dp->next) { + if (dp->type == ACPI_DP_TYPE_CHILD) { + child_count++; + } else { + /* + * The UUID and package is only added when + * we come across the first property. This + * is to avoid creating a zero-length package + * in situations where there are only children. + */ + if (!prop_count) { + *dp_count += 2; + /* ToUUID (ACPI_DP_UUID) */ + ret = acpigen_write_uuid(ctx, ACPI_DP_UUID); + if (ret) + return log_msg_ret("touuid", ret); + /* + * Package (PROP), element count determined as + * it is populated + */ + prop_count = acpigen_write_package(ctx, 0); + } + (*prop_count)++; + acpi_dp_write_property(ctx, dp); + } + } + + if (prop_count) { + /* Package (PROP) length, if a package was written */ + acpigen_pop_len(ctx); + } + + if (child_count) { + /* Update DP package count to 2 or 4 */ + *dp_count += 2; + /* ToUUID (ACPI_DP_CHILD_UUID) */ + ret = acpigen_write_uuid(ctx, ACPI_DP_CHILD_UUID); + if (ret) + return log_msg_ret("child uuid", ret); + + /* Print child pointer properties */ + acpigen_write_package(ctx, child_count); + + for (dp = prop; dp; dp = dp->next) + if (dp->type == ACPI_DP_TYPE_CHILD) + acpi_dp_write_property(ctx, dp); + /* Package (CHILD) length */ + acpigen_pop_len(ctx); + } + + /* Package (DP) length */ + acpigen_pop_len(ctx); + + /* Recursively parse children into separate tables */ + for (dp = prop; dp; dp = dp->next) { + if (dp->type == ACPI_DP_TYPE_CHILD) { + ret = acpi_dp_write_(ctx, dp->child); + if (ret) + return log_msg_ret("dp child", ret); + } + } + + return 0; +} + +int acpi_dp_write(struct acpi_ctx *ctx, struct acpi_dp *table) +{ + int ret; + + ret = acpi_dp_write_(ctx, table); + + /* Clean up */ + acpi_dp_free(table); + + if (ret) + return log_msg_ret("write", ret); + + return 0; +} + +static struct acpi_dp *acpi_dp_new(struct acpi_dp *dp, enum acpi_dp_type type, + const char *name) +{ + struct acpi_dp *new; + + new = malloc(sizeof(struct acpi_dp)); + if (!new) + return NULL; + + memset(new, '\0', sizeof(*new)); + new->type = type; + new->name = name; + + if (dp) { + /* Add to end of property list */ + while (dp->next) + dp = dp->next; + dp->next = new; + } + + return new; +} + +struct acpi_dp *acpi_dp_new_table(const char *name) +{ + return acpi_dp_new(NULL, ACPI_DP_TYPE_TABLE, name); +} + +struct acpi_dp *acpi_dp_add_integer(struct acpi_dp *dp, const char *name, + u64 value) +{ + struct acpi_dp *new; + + assert(dp); + new = acpi_dp_new(dp, ACPI_DP_TYPE_INTEGER, name); + + if (new) + new->integer = value; + + return new; +} + +struct acpi_dp *acpi_dp_add_string(struct acpi_dp *dp, const char *name, + const char *string) +{ + struct acpi_dp *new; + + assert(dp); + new = acpi_dp_new(dp, ACPI_DP_TYPE_STRING, name); + if (new) + new->string = string; + + return new; +} + +struct acpi_dp *acpi_dp_add_reference(struct acpi_dp *dp, const char *name, + const char *reference) +{ + struct acpi_dp *new; + + assert(dp); + new = acpi_dp_new(dp, ACPI_DP_TYPE_REFERENCE, name); + if (new) + new->string = reference; + + return new; +} + +struct acpi_dp *acpi_dp_add_child(struct acpi_dp *dp, const char *name, + struct acpi_dp *child) +{ + struct acpi_dp *new; + + assert(dp); + if (child->type != ACPI_DP_TYPE_TABLE) + return NULL; + + new = acpi_dp_new(dp, ACPI_DP_TYPE_CHILD, name); + if (new) { + new->child = child; + new->string = child->name; + } + + return new; +} + +struct acpi_dp *acpi_dp_add_array(struct acpi_dp *dp, struct acpi_dp *array) +{ + struct acpi_dp *new; + + assert(dp); + assert(array); + if (array->type != ACPI_DP_TYPE_TABLE) + return NULL; + + new = acpi_dp_new(dp, ACPI_DP_TYPE_ARRAY, array->name); + if (new) + new->array = array; + + return new; +} + +struct acpi_dp *acpi_dp_add_integer_array(struct acpi_dp *dp, const char *name, + u64 *array, int len) +{ + struct acpi_dp *dp_array; + int i; + + assert(dp); + if (len <= 0) + return NULL; + + dp_array = acpi_dp_new_table(name); + if (!dp_array) + return NULL; + + for (i = 0; i < len; i++) + if (!acpi_dp_add_integer(dp_array, NULL, array[i])) + break; + + if (!acpi_dp_add_array(dp, dp_array)) + return NULL; + + return dp_array; +} diff --git a/test/dm/Makefile b/test/dm/Makefile index e3e0cccf01..eb62faa0e3 100644 --- a/test/dm/Makefile +++ b/test/dm/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_UT_DM) += core.o ifneq ($(CONFIG_SANDBOX),) obj-$(CONFIG_ACPIGEN) += acpi.o obj-$(CONFIG_ACPIGEN) += acpigen.o +obj-$(CONFIG_ACPIGEN) += acpi_dp.o obj-$(CONFIG_SOUND) += audio.o obj-$(CONFIG_BLK) += blk.o obj-$(CONFIG_BOARD) += board.o diff --git a/test/dm/acpi_dp.c b/test/dm/acpi_dp.c new file mode 100644 index 0000000000..c11394786f --- /dev/null +++ b/test/dm/acpi_dp.c @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Tests for ACPI code generation via a device-property table + * + * Copyright 2019 Google LLC + * Written by Simon Glass <sjg at chromium.org> + */ + +#include <common.h> +#include <dm.h> +#include <uuid.h> +#include <acpi/acpigen.h> +#include <acpi/acpi_dp.h> +#include <asm/unaligned.h> +#include <dm/acpi.h> +#include <dm/test.h> +#include <test/ut.h> + +#define TEST_INT8 0x7d +#define TEST_INT16 0x2345 +#define TEST_INT32 0x12345678 +#define TEST_INT64 0x4567890123456 +#define TEST_STR "testing acpi strings" +#define TEST_REF "\\SB.I2C0.TPM2" +#define EXPECT_REF "SB__I2C0TPM2" + +static int alloc_context(struct acpi_ctx **ctxp) +{ + struct acpi_ctx *ctx; + + *ctxp = NULL; + ctx = malloc(sizeof(*ctx)); + if (!ctx) + return -ENOMEM; + memset(ctx, '\0', sizeof(*ctx)); + ctx->current = malloc(500); + if (!ctx->current) + return -ENOMEM; + *ctxp = ctx; + + return 0; +} + +static void free_context(struct acpi_ctx **ctxp) +{ + free(*ctxp); + *ctxp = NULL; +} + +/** + * get_length() - decode a three-byte length field + * + * @ptr: Length encoded as per ACPI + * @return decoded length, or -EINVAL on error + */ +static int get_length(u8 *ptr) +{ + if (!(*ptr & 0x80)) + return -EINVAL; + + return (*ptr & 0xf) | ptr[1] << 4 | ptr[2] << 12; +} + +/* Test emitting an empty table */ +static int dm_test_acpi_dp_new_table(struct unit_test_state *uts) +{ + struct acpi_ctx *ctx; + struct acpi_dp *dp; + u8 *ptr; + + ut_assertok(alloc_context(&ctx)); + + dp = acpi_dp_new_table("FRED"); + ut_assertnonnull(dp); + + ptr = acpigen_get_current(ctx); + ut_assertok(acpi_dp_write(ctx, dp)); + ut_asserteq(10, acpigen_get_current(ctx) - ptr); + ut_asserteq(NAME_OP, *(u8 *)ptr); + ut_asserteq_strn("FRED", (char *)ptr + 1); + ut_asserteq(PACKAGE_OP, ptr[5]); + ut_asserteq(4, get_length(ptr + 6)); + ut_asserteq(0, ptr[9]); + + free_context(&ctx); + + return 0; +} +DM_TEST(dm_test_acpi_dp_new_table, 0); + +/* Test emitting an integer */ +static int dm_test_acpi_dp_int(struct unit_test_state *uts) +{ + struct acpi_ctx *ctx; + char uuid[UUID_STR_LEN + 1]; + struct acpi_dp *dp; + u8 *ptr; + + ut_assertok(alloc_context(&ctx)); + + dp = acpi_dp_new_table("FRED"); + ut_assertnonnull(dp); + ut_assertnonnull(acpi_dp_add_integer(dp, "MARY", TEST_INT32)); + + ptr = acpigen_get_current(ctx); + ut_assertok(acpi_dp_write(ctx, dp)); + ut_asserteq(54, acpigen_get_current(ctx) - ptr); + ut_asserteq(NAME_OP, *(u8 *)ptr); + ut_asserteq_strn("FRED", (char *)ptr + 1); + ut_asserteq(PACKAGE_OP, ptr[5]); + ut_asserteq(48, get_length(ptr + 6)); + ut_asserteq(2, ptr[9]); + + /* UUID */ + ut_asserteq(BUFFER_OP, ptr[10]); + ut_asserteq(22, get_length(ptr + 11)); + ut_asserteq(WORD_PREFIX, ptr[14]); + ut_asserteq(16, get_unaligned((u16 *)(ptr + 15))); + uuid_bin_to_str(ptr + 17, uuid, 1); + ut_asserteq_str(ACPI_DP_UUID, uuid); + + /* Container package */ + ut_asserteq(PACKAGE_OP, ptr[33]); + ut_asserteq(20, get_length(ptr + 34)); + ut_asserteq(1, ptr[37]); + + /* Package with name and (integer) value */ + ut_asserteq(PACKAGE_OP, ptr[38]); + ut_asserteq(15, get_length(ptr + 39)); + ut_asserteq(2, ptr[42]); + ut_asserteq(STRING_PREFIX, ptr[43]); + ut_asserteq_str("MARY", (char *)ptr + 44); + + ut_asserteq(DWORD_PREFIX, ptr[49]); + ut_asserteq(TEST_INT32, get_unaligned((u32 *)(ptr + 50))); + + free_context(&ctx); + + return 0; +} +DM_TEST(dm_test_acpi_dp_int, 0); + +/* Test emitting a 64-bit integer */ +static int dm_test_acpi_dp_int64(struct unit_test_state *uts) +{ + struct acpi_ctx *ctx; + struct acpi_dp *dp; + u8 *ptr; + + ut_assertok(alloc_context(&ctx)); + + dp = acpi_dp_new_table("FRED"); + ut_assertnonnull(dp); + ut_assertnonnull(acpi_dp_add_integer(dp, "MARY", TEST_INT64)); + + ptr = acpigen_get_current(ctx); + ut_assertok(acpi_dp_write(ctx, dp)); + ut_asserteq(58, acpigen_get_current(ctx) - ptr); + + ut_asserteq(QWORD_PREFIX, ptr[49]); + ut_asserteq_64(TEST_INT64, get_unaligned((u64 *)(ptr + 50))); + + free_context(&ctx); + + return 0; +} +DM_TEST(dm_test_acpi_dp_int64, 0); + +/* Test emitting a 16-bit integer */ +static int dm_test_acpi_dp_int16(struct unit_test_state *uts) +{ + struct acpi_ctx *ctx; + struct acpi_dp *dp; + u8 *ptr; + + ut_assertok(alloc_context(&ctx)); + + dp = acpi_dp_new_table("FRED"); + ut_assertnonnull(dp); + ut_assertnonnull(acpi_dp_add_integer(dp, "MARY", TEST_INT16)); + + ptr = acpigen_get_current(ctx); + ut_assertok(acpi_dp_write(ctx, dp)); + ut_asserteq(52, acpigen_get_current(ctx) - ptr); + + ut_asserteq(WORD_PREFIX, ptr[49]); + ut_asserteq(TEST_INT16, get_unaligned((u16 *)(ptr + 50))); + + free_context(&ctx); + + return 0; +} +DM_TEST(dm_test_acpi_dp_int16, 0); + +/* Test emitting a 8-bit integer */ +static int dm_test_acpi_dp_int8(struct unit_test_state *uts) +{ + struct acpi_ctx *ctx; + struct acpi_dp *dp; + u8 *ptr; + + ut_assertok(alloc_context(&ctx)); + + dp = acpi_dp_new_table("FRED"); + ut_assertnonnull(dp); + ut_assertnonnull(acpi_dp_add_integer(dp, "MARY", TEST_INT8)); + + ptr = acpigen_get_current(ctx); + ut_assertok(acpi_dp_write(ctx, dp)); + ut_asserteq(51, acpigen_get_current(ctx) - ptr); + + ut_asserteq(BYTE_PREFIX, ptr[49]); + ut_asserteq(TEST_INT8, ptr[50]); + + free_context(&ctx); + + return 0; +} +DM_TEST(dm_test_acpi_dp_int8, 0); + +/* Test emitting multiple values */ +static int dm_test_acpi_dp_multiple(struct unit_test_state *uts) +{ + struct acpi_ctx *ctx; + struct acpi_dp *dp; + u8 *ptr; + + ut_assertok(alloc_context(&ctx)); + + dp = acpi_dp_new_table("FRED"); + ut_assertnonnull(dp); + ut_assertnonnull(acpi_dp_add_integer(dp, "int16", TEST_INT16)); + ut_assertnonnull(acpi_dp_add_string(dp, "str", TEST_STR)); + ut_assertnonnull(acpi_dp_add_reference(dp, "ref", TEST_REF)); + + ptr = acpigen_get_current(ctx); + ut_assertok(acpi_dp_write(ctx, dp)); + ut_asserteq(110, acpigen_get_current(ctx) - ptr); + + ut_asserteq(WORD_PREFIX, ptr[0x32]); + ut_asserteq(TEST_INT16, get_unaligned((u16 *)(ptr + 0x33))); + ut_asserteq(STRING_PREFIX, ptr[0x3f]); + ut_asserteq_str(TEST_STR, (char *)ptr + 0x40); + ut_asserteq(ROOT_PREFIX, ptr[0x5f]); + ut_asserteq(MULTI_NAME_PREFIX, ptr[0x60]); + ut_asserteq(3, ptr[0x61]); + ut_asserteq_strn(EXPECT_REF, (char *)ptr + 0x62); + + free_context(&ctx); + + return 0; +} +DM_TEST(dm_test_acpi_dp_multiple, 0); + +/* Test emitting an array */ +static int dm_test_acpi_dp_array(struct unit_test_state *uts) +{ + struct acpi_ctx *ctx; + struct acpi_dp *dp; + u64 speed[4]; + u8 *ptr; + + ut_assertok(alloc_context(&ctx)); + + dp = acpi_dp_new_table("FRED"); + ut_assertnonnull(dp); + speed[0] = TEST_INT8; + speed[1] = TEST_INT16; + speed[2] = TEST_INT32; + speed[3] = TEST_INT64; + ut_assertnonnull(acpi_dp_add_integer_array(dp, "speeds", speed, + ARRAY_SIZE(speed))); + + ptr = acpigen_get_current(ctx); + ut_assertok(acpi_dp_write(ctx, dp)); + ut_asserteq(75, acpigen_get_current(ctx) - ptr); + + ut_asserteq(BYTE_PREFIX, ptr[0x38]); + ut_asserteq(TEST_INT8, ptr[0x39]); + + ut_asserteq(WORD_PREFIX, ptr[0x3a]); + ut_asserteq(TEST_INT16, get_unaligned((u16 *)(ptr + 0x3b))); + + ut_asserteq(DWORD_PREFIX, ptr[0x3d]); + ut_asserteq(TEST_INT32, get_unaligned((u32 *)(ptr + 0x3e))); + + ut_asserteq(QWORD_PREFIX, ptr[0x42]); + ut_asserteq_64(TEST_INT64, get_unaligned((u64 *)(ptr + 0x43))); + + free_context(&ctx); + + return 0; +} +DM_TEST(dm_test_acpi_dp_array, 0); + +/* Test emitting a child */ +static int dm_test_acpi_dp_child(struct unit_test_state *uts) +{ + struct acpi_ctx *ctx; + struct acpi_dp *dp, *child1, *child2; + char uuid[UUID_STR_LEN + 1]; + u8 *ptr, *pptr; + int i; + + ut_assertok(alloc_context(&ctx)); + + child1 = acpi_dp_new_table("child"); + ut_assertnonnull(child1); + ut_assertnonnull(acpi_dp_add_integer(child1, "height", TEST_INT16)); + + child2 = acpi_dp_new_table("child"); + ut_assertnonnull(child2); + ut_assertnonnull(acpi_dp_add_integer(child2, "age", TEST_INT8)); + + dp = acpi_dp_new_table("FRED"); + ut_assertnonnull(dp); + + ut_assertnonnull(acpi_dp_add_child(dp, "anna", child1)); + ut_assertnonnull(acpi_dp_add_child(dp, "john", child2)); + + ptr = acpigen_get_current(ctx); + ut_assertok(acpi_dp_write(ctx, dp)); + ut_asserteq(178, acpigen_get_current(ctx) - ptr); + + /* UUID for child extension using Hierarchical Data Extension UUID */ + ut_asserteq(BUFFER_OP, ptr[10]); + ut_asserteq(22, get_length(ptr + 11)); + ut_asserteq(WORD_PREFIX, ptr[14]); + ut_asserteq(16, get_unaligned((u16 *)(ptr + 15))); + uuid_bin_to_str(ptr + 17, uuid, 1); + ut_asserteq_str(ACPI_DP_CHILD_UUID, uuid); + + /* Package with two children */ + ut_asserteq(PACKAGE_OP, ptr[0x21]); + ut_asserteq(0x28, get_length(ptr + 0x22)); + ut_asserteq(2, ptr[0x25]); + + /* First we expect the two children as string/value */ + pptr = ptr + 0x26; + for (i = 0; i < 2; i++) { + ut_asserteq(PACKAGE_OP, pptr[0]); + ut_asserteq(0x11, get_length(pptr + 1)); + ut_asserteq(2, pptr[4]); + ut_asserteq(STRING_PREFIX, pptr[5]); + ut_asserteq_str(i ? "john" : "anna", (char *)pptr + 6); + ut_asserteq(STRING_PREFIX, pptr[11]); + ut_asserteq_str("child", (char *)pptr + 12); + pptr += 0x12; + } + + /* Write the two children */ + ut_asserteq(0x4a, pptr - ptr); + for (i = 0; i < 2; i++) { + const char *prop = i ? "age" : "height"; + const int datalen = i ? 1 : 2; + int len = strlen(prop) + 1; + + ut_asserteq(NAME_OP, pptr[0]); + ut_asserteq_strn("chil", (char *)pptr + 1); + ut_asserteq(PACKAGE_OP, pptr[5]); + ut_asserteq(0x27 + len + datalen, get_length(pptr + 6)); + ut_asserteq(2, pptr[9]); + + /* UUID */ + ut_asserteq(BUFFER_OP, pptr[10]); + ut_asserteq(22, get_length(pptr + 11)); + ut_asserteq(WORD_PREFIX, pptr[14]); + ut_asserteq(16, get_unaligned((u16 *)(pptr + 15))); + uuid_bin_to_str(pptr + 17, uuid, 1); + ut_asserteq_str(ACPI_DP_UUID, uuid); + pptr += 33; + + /* Containing package */ + ut_asserteq(i ? 0xa1 : 0x6b, pptr - ptr); + ut_asserteq(PACKAGE_OP, pptr[0]); + ut_asserteq(0xb + len + datalen, get_length(pptr + 1)); + ut_asserteq(1, pptr[4]); + + /* Package containing the property-name string and the value */ + pptr += 5; + ut_asserteq(i ? 0xa6 : 0x70, pptr - ptr); + ut_asserteq(PACKAGE_OP, pptr[0]); + ut_asserteq(6 + len + datalen, get_length(pptr + 1)); + ut_asserteq(2, pptr[4]); + + ut_asserteq(STRING_PREFIX, pptr[5]); + ut_asserteq_str(i ? "age" : "height", (char *)pptr + 6); + pptr += 6 + len; + if (i) { + ut_asserteq(BYTE_PREFIX, pptr[0]); + ut_asserteq(TEST_INT8, pptr[1]); + } else { + ut_asserteq(WORD_PREFIX, pptr[0]); + ut_asserteq(TEST_INT16, + get_unaligned((u16 *)(pptr + 1))); + } + pptr += 1 + datalen; + } + ut_asserteq(178, pptr - ptr); + + free_context(&ctx); + + return 0; +} +DM_TEST(dm_test_acpi_dp_child, 0);