diff mbox series

[v4,19/35] acpi: Support writing Device Properties objects via _DSD

Message ID 20200707191212.2542638-11-sjg@chromium.org
State Accepted
Commit 0e5a0a00d6e44dc0c7e1466ceb3e452b43ceeb1b
Headers show
Series dm: Add programmatic generation of ACPI tables (part B) | expand

Commit Message

Simon Glass July 7, 2020, 7:11 p.m. UTC
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 v4:
- Drop embedded // comments in file comment
- Correct return value comment in acpi_dp_add_child()
- Rename acpi_dp_write_() to acpi_dp_write_internal() and make static
- Use a #define for the test context size
- Add a header file for test to allow sharing the context init function
- Rename and get_length() into a shared test file
- Add missing arg to comment for acpi_dp_write()

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 | 217 +++++++++++++++++++++++
 include/acpi/acpigen.h |   1 +
 lib/acpi/Makefile      |   1 +
 lib/acpi/acpi_dp.c     | 323 ++++++++++++++++++++++++++++++++++
 test/dm/Makefile       |   1 +
 test/dm/acpi.h         |  29 ++++
 test/dm/acpi_dp.c      | 385 +++++++++++++++++++++++++++++++++++++++++
 test/dm/acpigen.c      |  39 ++---
 8 files changed, 974 insertions(+), 22 deletions(-)
 create mode 100644 include/acpi/acpi_dp.h
 create mode 100644 lib/acpi/acpi_dp.c
 create mode 100644 test/dm/acpi.h
 create mode 100644 test/dm/acpi_dp.c

Comments

Bin Meng July 13, 2020, 2:58 a.m. UTC | #1
On Wed, Jul 8, 2020 at 3:12 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 v4:
> - Drop embedded // comments in file comment
> - Correct return value comment in acpi_dp_add_child()
> - Rename acpi_dp_write_() to acpi_dp_write_internal() and make static
> - Use a #define for the test context size
> - Add a header file for test to allow sharing the context init function
> - Rename and get_length() into a shared test file
> - Add missing arg to comment for acpi_dp_write()
>
> 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 | 217 +++++++++++++++++++++++
>  include/acpi/acpigen.h |   1 +
>  lib/acpi/Makefile      |   1 +
>  lib/acpi/acpi_dp.c     | 323 ++++++++++++++++++++++++++++++++++
>  test/dm/Makefile       |   1 +
>  test/dm/acpi.h         |  29 ++++
>  test/dm/acpi_dp.c      | 385 +++++++++++++++++++++++++++++++++++++++++
>  test/dm/acpigen.c      |  39 ++---
>  8 files changed, 974 insertions(+), 22 deletions(-)
>  create mode 100644 include/acpi/acpi_dp.h
>  create mode 100644 lib/acpi/acpi_dp.c
>  create mode 100644 test/dm/acpi.h
>  create mode 100644 test/dm/acpi_dp.c
>

Reviewed-by: Bin Meng <bmeng.cn at gmail.com>
diff mbox series

Patch

diff --git a/include/acpi/acpi_dp.h b/include/acpi/acpi_dp.h
new file mode 100644
index 0000000000..a82330856a
--- /dev/null
+++ b/include/acpi/acpi_dp.h
@@ -0,0 +1,217 @@ 
+/* 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 child 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
+ *
+ * @ctx: ACPI context
+ * @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 31fa20a806..b10226f8f8 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..32528e1ec4
--- /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;
+	}
+}
+
+static int acpi_dp_write_internal(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_internal(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_internal(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 5641070ea1..b03c96da06 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.h b/test/dm/acpi.h
new file mode 100644
index 0000000000..885dff85d3
--- /dev/null
+++ b/test/dm/acpi.h
@@ -0,0 +1,29 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Common functions for ACPI tests
+ *
+ * Copyright 2020 Google LLC
+ * Written by Simon Glass <sjg at chromium.org>
+ */
+
+#ifndef __TEST_DM_ACPI_H
+#define __TEST_DM_ACPI_H
+
+/**
+ * acpi_test_alloc_context_size() - Allocate an ACPI context of a given size
+ *
+ * @ctxp: Returns allocated context
+ * @size: Size to allocate in bytes
+ * @return 0 if OK, -ENOMEM if out of memory
+ */
+int acpi_test_alloc_context_size(struct acpi_ctx **ctxp, int size);
+
+/**
+ * acpi_test_get_length() - decode a three-byte length field
+ *
+ * @ptr: Length encoded as per ACPI
+ * @return decoded length, or -EINVAL on error
+ */
+int acpi_test_get_length(u8 *ptr);
+
+#endif /*__TEST_DM_ACPI_H */
diff --git a/test/dm/acpi_dp.c b/test/dm/acpi_dp.c
new file mode 100644
index 0000000000..28696aaff6
--- /dev/null
+++ b/test/dm/acpi_dp.c
@@ -0,0 +1,385 @@ 
+// 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>
+#include "acpi.h"
+
+/* Maximum size of the ACPI context needed for most tests */
+#define ACPI_CONTEXT_SIZE	500
+
+#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)
+{
+	return acpi_test_alloc_context_size(ctxp, ACPI_CONTEXT_SIZE);
+
+	return 0;
+}
+
+static void free_context(struct acpi_ctx **ctxp)
+{
+	free(*ctxp);
+	*ctxp = NULL;
+}
+
+/* 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, acpi_test_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, acpi_test_get_length(ptr + 6));
+	ut_asserteq(2, ptr[9]);
+
+	/* UUID */
+	ut_asserteq(BUFFER_OP, ptr[10]);
+	ut_asserteq(22, acpi_test_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, acpi_test_get_length(ptr + 34));
+	ut_asserteq(1, ptr[37]);
+
+	/* Package with name and (integer) value */
+	ut_asserteq(PACKAGE_OP, ptr[38]);
+	ut_asserteq(15, acpi_test_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, acpi_test_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, acpi_test_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, acpi_test_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, acpi_test_get_length(pptr + 6));
+		ut_asserteq(2, pptr[9]);
+
+		/* UUID */
+		ut_asserteq(BUFFER_OP, pptr[10]);
+		ut_asserteq(22, acpi_test_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, acpi_test_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, acpi_test_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);
diff --git a/test/dm/acpigen.c b/test/dm/acpigen.c
index 1cdbcf2131..a488db8b3e 100644
--- a/test/dm/acpigen.c
+++ b/test/dm/acpigen.c
@@ -18,6 +18,7 @@ 
 #include <dm/test.h>
 #include <dm/uclass-internal.h>
 #include <test/ut.h>
+#include "acpi.h"
 
 /* Maximum size of the ACPI context needed for most tests */
 #define ACPI_CONTEXT_SIZE	150
@@ -31,7 +32,7 @@ 
 #define TEST_INT32	0x12345678
 #define TEST_INT64	0x4567890123456
 
-static int alloc_context_size(struct acpi_ctx **ctxp, int size)
+int acpi_test_alloc_context_size(struct acpi_ctx **ctxp, int size)
 {
 	struct acpi_ctx *ctx;
 
@@ -51,9 +52,17 @@  static int alloc_context_size(struct acpi_ctx **ctxp, int size)
 	return 0;
 }
 
+int acpi_test_get_length(u8 *ptr)
+{
+	if (!(*ptr & 0x80))
+		return -EINVAL;
+
+	return (*ptr & 0xf) | ptr[1] << 4 | ptr[2] << 12;
+}
+
 static int alloc_context(struct acpi_ctx **ctxp)
 {
-	return alloc_context_size(ctxp, ACPI_CONTEXT_SIZE);
+	return acpi_test_alloc_context_size(ctxp, ACPI_CONTEXT_SIZE);
 }
 
 static void free_context(struct acpi_ctx **ctxp)
@@ -357,20 +366,6 @@  static int dm_test_acpi_spi(struct unit_test_state *uts)
 }
 DM_TEST(dm_test_acpi_spi, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
 
-/**
- * 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 a length */
 static int dm_test_acpi_len(struct unit_test_state *uts)
 {
@@ -379,7 +374,7 @@  static int dm_test_acpi_len(struct unit_test_state *uts)
 	u8 *ptr;
 	int i;
 
-	ut_assertok(alloc_context_size(&ctx, size));
+	ut_assertok(acpi_test_alloc_context_size(&ctx, size));
 
 	ptr = acpigen_get_current(ctx);
 
@@ -387,7 +382,7 @@  static int dm_test_acpi_len(struct unit_test_state *uts)
 	acpigen_write_len_f(ctx);
 	acpigen_emit_byte(ctx, 0x23);
 	acpigen_pop_len(ctx);
-	ut_asserteq(1 + 3, get_length(ptr));
+	ut_asserteq(1 + 3, acpi_test_get_length(ptr));
 
 	/* Write 200 bytes so we need two length bytes */
 	ptr = ctx->current;
@@ -395,7 +390,7 @@  static int dm_test_acpi_len(struct unit_test_state *uts)
 	for (i = 0; i < 200; i++)
 		acpigen_emit_byte(ctx, 0x23);
 	acpigen_pop_len(ctx);
-	ut_asserteq(200 + 3, get_length(ptr));
+	ut_asserteq(200 + 3, acpi_test_get_length(ptr));
 
 	/* Write 40KB so we need three length bytes */
 	ptr = ctx->current;
@@ -403,7 +398,7 @@  static int dm_test_acpi_len(struct unit_test_state *uts)
 	for (i = 0; i < 40000; i++)
 		acpigen_emit_byte(ctx, 0x23);
 	acpigen_pop_len(ctx);
-	ut_asserteq(40000 + 3, get_length(ptr));
+	ut_asserteq(40000 + 3, acpi_test_get_length(ptr));
 
 	free_context(&ctx);
 
@@ -429,7 +424,7 @@  static int dm_test_acpi_package(struct unit_test_state *uts)
 	acpigen_emit_byte(ctx, 0x23);
 	acpigen_pop_len(ctx);
 	ut_asserteq(PACKAGE_OP, ptr[0]);
-	ut_asserteq(5, get_length(ptr + 1));
+	ut_asserteq(5, acpi_test_get_length(ptr + 1));
 	ut_asserteq(3, ptr[4]);
 
 	free_context(&ctx);
@@ -613,7 +608,7 @@  static int dm_test_acpi_uuid(struct unit_test_state *uts)
 				       "dbb8e3e6-5886-4ba6-8795-1319f52a966b"));
 	ut_asserteq(23, acpigen_get_current(ctx) - ptr);
 	ut_asserteq(BUFFER_OP, ptr[0]);
-	ut_asserteq(22, get_length(ptr + 1));
+	ut_asserteq(22, acpi_test_get_length(ptr + 1));
 	ut_asserteq(0xdbb8e3e6, get_unaligned((u32 *)(ptr + 7)));
 	ut_asserteq(0x5886, get_unaligned((u16 *)(ptr + 11)));
 	ut_asserteq(0x4ba6, get_unaligned((u16 *)(ptr + 13)));