diff mbox series

[v4,13/59] acpi: Support generating a multi-function _DSM for devices

Message ID 20200922184544.2920969-5-sjg@chromium.org
State Accepted
Delegated to: Bin Meng
Headers show
Series dm: Add programatic generation of ACPI tables (part D) | expand

Commit Message

Simon Glass Sept. 22, 2020, 6:44 p.m. UTC
Add a function to generate ACPI code for a _DSM method for a device.
This includes functions for starting and ending each part of the _DSM.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

 include/acpi/acpi_device.h |  14 +++++
 include/acpi/acpigen.h     |  99 +++++++++++++++++++++++++++++
 lib/acpi/acpi_device.c     |  43 +++++++++++++
 lib/acpi/acpigen.c         |  54 ++++++++++++++++
 test/dm/acpigen.c          | 126 +++++++++++++++++++++++++++++++++++++
 5 files changed, 336 insertions(+)
diff mbox series

Patch

diff --git a/include/acpi/acpi_device.h b/include/acpi/acpi_device.h
index 11461e168d3..a5b12217820 100644
--- a/include/acpi/acpi_device.h
+++ b/include/acpi/acpi_device.h
@@ -28,6 +28,9 @@  struct udevice;
 /* Length of a full path to an ACPI device */
 #define ACPI_PATH_MAX		30
 
+/* UUID for an I2C _DSM method */
+#define ACPI_DSM_I2C_HID_UUID	"3cdff6f7-4267-4555-ad05-b30a3d8938de"
+
 /* Values that can be returned for ACPI device _STA method */
 enum acpi_dev_status {
 	ACPI_DSTATUS_PRESENT		= BIT(0),
@@ -319,6 +322,17 @@  int acpi_device_write_gpio_desc(struct acpi_ctx *ctx,
 int acpi_device_write_interrupt_or_gpio(struct acpi_ctx *ctx,
 					struct udevice *dev, const char *prop);
 
+/**
+ * acpi_device_write_dsm_i2c_hid() - Write a device-specific method for HID
+ *
+ * This writes a DSM for an I2C Human-Interface Device based on the config
+ * provided
+ *
+ * @hid_desc_reg_offset: HID register offset
+ */
+int acpi_device_write_dsm_i2c_hid(struct acpi_ctx *ctx,
+				  int hid_desc_reg_offset);
+
 /**
  * acpi_device_write_i2c_dev() - Write an I2C device to ACPI
  *
diff --git a/include/acpi/acpigen.h b/include/acpi/acpigen.h
index fa9409e3528..34b3115bc9c 100644
--- a/include/acpi/acpigen.h
+++ b/include/acpi/acpigen.h
@@ -666,4 +666,103 @@  void acpigen_write_return_singleton_buffer(struct acpi_ctx *ctx, uint arg);
  */
 void acpigen_write_return_byte(struct acpi_ctx *ctx, uint arg);
 
+/**
+ * acpigen_write_dsm_start() - Start a _DSM method
+ *
+ * Generate ACPI AML code to start the _DSM method.
+ *
+ * The functions need to be called in the correct sequence as below.
+ *
+ * Within the <generate-code-here> region, Local0 and Local1 must be are left
+ * untouched, but Local2-Local7 can be used
+ *
+ * Arguments passed into _DSM method:
+ * Arg0 = UUID
+ * Arg1 = Revision
+ * Arg2 = Function index
+ * Arg3 = Function-specific arguments
+ *
+ * AML code generated looks like this:
+ * Method (_DSM, 4, Serialized) {   -- acpigen_write_dsm_start)
+ *	ToBuffer (Arg0, Local0)
+ *	If (LEqual (Local0, ToUUID(uuid))) {  -- acpigen_write_dsm_uuid_start
+ *		ToInteger (Arg2, Local1)
+ *		If (LEqual (Local1, 0)) {  -- acpigen_write_dsm_uuid_start_cond
+ *			<generate-code-here>
+ *		}                          -- acpigen_write_dsm_uuid_end_cond
+ *		...
+ *		If (LEqual (Local1, n)) {  -- acpigen_write_dsm_uuid_start_cond
+ *			<generate-code-here>
+ *		}                          -- acpigen_write_dsm_uuid_end_cond
+ *		Return (Buffer (One) { 0x0 })
+ *	}                                  -- acpigen_write_dsm_uuid_end
+ *	...
+ *	If (LEqual (Local0, ToUUID(uuidn))) {
+ *	...
+ *	}
+ *	Return (Buffer (One) { 0x0 })  -- acpigen_write_dsm_end
+ * }
+ *
+ * @ctx: ACPI context pointer
+ */
+void acpigen_write_dsm_start(struct acpi_ctx *ctx);
+
+/**
+ * acpigen_write_dsm_uuid_start() - Start a new UUID block
+ *
+ * This starts generation of code to handle a particular UUID:
+ *
+ *	If (LEqual (Local0, ToUUID(uuid))) {
+ *		ToInteger (Arg2, Local1)
+ *
+ * @ctx: ACPI context pointer
+ */
+int acpigen_write_dsm_uuid_start(struct acpi_ctx *ctx, const char *uuid);
+
+/**
+ * acpigen_write_dsm_uuid_start_cond() - Start a new condition block
+ *
+ * This starts generation of condition-checking code to handle a particular
+ * function:
+ *
+ *		If (LEqual (Local1, i))
+ *
+ * @ctx: ACPI context pointer
+ */
+void acpigen_write_dsm_uuid_start_cond(struct acpi_ctx *ctx, int seq);
+
+/**
+ * acpigen_write_dsm_uuid_end_cond() - Start a new condition block
+ *
+ * This ends generation of condition-checking code to handle a particular
+ * function:
+ *
+ *		}
+ *
+ * @ctx: ACPI context pointer
+ */
+void acpigen_write_dsm_uuid_end_cond(struct acpi_ctx *ctx);
+
+/**
+ * acpigen_write_dsm_uuid_end() - End a UUID block
+ *
+ * This ends generation of code to handle a particular UUID:
+ *
+ *		Return (Buffer (One) { 0x0 })
+ *
+ * @ctx: ACPI context pointer
+ */
+void acpigen_write_dsm_uuid_end(struct acpi_ctx *ctx);
+
+/**
+ * acpigen_write_dsm_end() - End a _DSM method
+ *
+ * This ends generates of the _DSM block:
+ *
+ *	Return (Buffer (One) { 0x0 })
+ *
+ * @ctx: ACPI context pointer
+ */
+void acpigen_write_dsm_end(struct acpi_ctx *ctx);
+
 #endif
diff --git a/lib/acpi/acpi_device.c b/lib/acpi/acpi_device.c
index 3c75b6d9629..8248664a10a 100644
--- a/lib/acpi/acpi_device.c
+++ b/lib/acpi/acpi_device.c
@@ -487,6 +487,49 @@  int acpi_device_add_power_res(struct acpi_ctx *ctx, u32 tx_state_val,
 	return 0;
 }
 
+int acpi_device_write_dsm_i2c_hid(struct acpi_ctx *ctx,
+				  int hid_desc_reg_offset)
+{
+	int ret;
+
+	acpigen_write_dsm_start(ctx);
+	ret = acpigen_write_dsm_uuid_start(ctx, ACPI_DSM_I2C_HID_UUID);
+	if (ret)
+		return log_ret(ret);
+
+	acpigen_write_dsm_uuid_start_cond(ctx, 0);
+	/* ToInteger (Arg1, Local2) */
+	acpigen_write_to_integer(ctx, ARG1_OP, LOCAL2_OP);
+	/* If (LEqual (Local2, 0x0)) */
+	acpigen_write_if_lequal_op_int(ctx, LOCAL2_OP, 0x0);
+	/*   Return (Buffer (One) { 0x1f }) */
+	acpigen_write_return_singleton_buffer(ctx, 0x1f);
+	acpigen_pop_len(ctx);	/* Pop : If */
+	/* Else */
+	acpigen_write_else(ctx);
+	/*   If (LEqual (Local2, 0x1)) */
+	acpigen_write_if_lequal_op_int(ctx, LOCAL2_OP, 0x1);
+	/*     Return (Buffer (One) { 0x3f }) */
+	acpigen_write_return_singleton_buffer(ctx, 0x3f);
+	acpigen_pop_len(ctx);	/* Pop : If */
+	/*   Else */
+	acpigen_write_else(ctx);
+	/*     Return (Buffer (One) { 0x0 }) */
+	acpigen_write_return_singleton_buffer(ctx, 0x0);
+	acpigen_pop_len(ctx);	/* Pop : Else */
+	acpigen_pop_len(ctx);	/* Pop : Else */
+	acpigen_write_dsm_uuid_end_cond(ctx);
+
+	acpigen_write_dsm_uuid_start_cond(ctx, 1);
+	acpigen_write_return_byte(ctx, hid_desc_reg_offset);
+	acpigen_write_dsm_uuid_end_cond(ctx);
+
+	acpigen_write_dsm_uuid_end(ctx);
+	acpigen_write_dsm_end(ctx);
+
+	return 0;
+}
+
 /* ACPI 6.3 section 6.4.3.8.2.1 - I2cSerialBus() */
 static void acpi_device_write_i2c(struct acpi_ctx *ctx,
 				  const struct acpi_i2c *i2c)
diff --git a/lib/acpi/acpigen.c b/lib/acpi/acpigen.c
index 2518bf83dda..d859f378413 100644
--- a/lib/acpi/acpigen.c
+++ b/lib/acpi/acpigen.c
@@ -609,6 +609,60 @@  void acpigen_write_return_byte(struct acpi_ctx *ctx, uint arg)
 	acpigen_write_byte(ctx, arg);
 }
 
+void acpigen_write_dsm_start(struct acpi_ctx *ctx)
+{
+	/* Method (_DSM, 4, Serialized) */
+	acpigen_write_method_serialized(ctx, "_DSM", 4);
+
+	/* ToBuffer (Arg0, Local0) */
+	acpigen_write_to_buffer(ctx, ARG0_OP, LOCAL0_OP);
+}
+
+int acpigen_write_dsm_uuid_start(struct acpi_ctx *ctx, const char *uuid)
+{
+	int ret;
+
+	/* If (LEqual (Local0, ToUUID(uuid))) */
+	acpigen_write_if(ctx);
+	acpigen_emit_byte(ctx, LEQUAL_OP);
+	acpigen_emit_byte(ctx, LOCAL0_OP);
+	ret = acpigen_write_uuid(ctx, uuid);
+	if (ret)
+		return log_msg_ret("uuid", ret);
+
+	/* ToInteger (Arg2, Local1) */
+	acpigen_write_to_integer(ctx, ARG2_OP, LOCAL1_OP);
+
+	return 0;
+}
+
+void acpigen_write_dsm_uuid_start_cond(struct acpi_ctx *ctx, int seq)
+{
+	/* If (LEqual (Local1, i)) */
+	acpigen_write_if_lequal_op_int(ctx, LOCAL1_OP, seq);
+}
+
+void acpigen_write_dsm_uuid_end_cond(struct acpi_ctx *ctx)
+{
+	acpigen_pop_len(ctx);	/* If */
+}
+
+void acpigen_write_dsm_uuid_end(struct acpi_ctx *ctx)
+{
+	/* Default case: Return (Buffer (One) { 0x0 }) */
+	acpigen_write_return_singleton_buffer(ctx, 0x0);
+
+	acpigen_pop_len(ctx);	/* If (LEqual (Local0, ToUUID(uuid))) */
+}
+
+void acpigen_write_dsm_end(struct acpi_ctx *ctx)
+{
+	/* Return (Buffer (One) { 0x0 }) */
+	acpigen_write_return_singleton_buffer(ctx, 0x0);
+
+	acpigen_pop_len(ctx);	/* Method _DSM */
+}
+
 /**
  * acpigen_get_dw0_in_local5() - Generate code to put dw0 cfg0 in local5
  *
diff --git a/test/dm/acpigen.c b/test/dm/acpigen.c
index cce19f11209..381fcb97022 100644
--- a/test/dm/acpigen.c
+++ b/test/dm/acpigen.c
@@ -10,6 +10,7 @@ 
 #include <dm.h>
 #include <irq.h>
 #include <malloc.h>
+#include <uuid.h>
 #include <acpi/acpigen.h>
 #include <acpi/acpi_device.h>
 #include <acpi/acpi_table.h>
@@ -1218,3 +1219,128 @@  static int dm_test_acpi_write_return(struct unit_test_state *uts)
 	return 0;
 }
 DM_TEST(dm_test_acpi_write_return, 0);
+
+/* Test emitting a DSM for an I2C HID */
+static int dm_test_acpi_write_i2c_dsm(struct unit_test_state *uts)
+{
+	char uuid_str[UUID_STR_LEN + 1];
+	const int reg_offset = 0x20;
+	struct acpi_ctx *ctx;
+	u8 *ptr;
+
+	ut_assertok(alloc_context(&ctx));
+
+	ptr = acpigen_get_current(ctx);
+	ut_assertok(acpi_device_write_dsm_i2c_hid(ctx, reg_offset));
+
+	/* acpigen_write_dsm_start() */
+	ut_asserteq(METHOD_OP, *ptr++);
+	ut_asserteq(0x78, acpi_test_get_length(ptr));
+	ptr += 3;
+	ut_asserteq_strn("_DSM", (char *)ptr);
+	ptr += 4;
+	ut_asserteq(ACPI_METHOD_SERIALIZED_MASK | 4, *ptr++);
+
+	ut_asserteq(TO_BUFFER_OP, *ptr++);
+	ut_asserteq(ARG0_OP, *ptr++);
+	ut_asserteq(LOCAL0_OP, *ptr++);
+
+	/* acpigen_write_dsm_uuid_start() */
+	ut_asserteq(IF_OP, *ptr++);
+	ut_asserteq(0x65, acpi_test_get_length(ptr));
+	ptr += 3;
+	ut_asserteq(LEQUAL_OP, *ptr++);
+	ut_asserteq(LOCAL0_OP, *ptr++);
+
+	ut_asserteq(BUFFER_OP, *ptr++);
+	ut_asserteq(UUID_BIN_LEN + 6, acpi_test_get_length(ptr));
+	ptr += 3;
+	ut_asserteq(WORD_PREFIX, *ptr++);
+	ut_asserteq(UUID_BIN_LEN, get_unaligned((u16 *)ptr));
+	ptr += 2;
+	uuid_bin_to_str(ptr, uuid_str, UUID_STR_FORMAT_GUID);
+	ut_asserteq_str(ACPI_DSM_I2C_HID_UUID, uuid_str);
+	ptr += UUID_BIN_LEN;
+
+	ut_asserteq(TO_INTEGER_OP, *ptr++);
+	ut_asserteq(ARG2_OP, *ptr++);
+	ut_asserteq(LOCAL1_OP, *ptr++);
+
+	/* acpigen_write_dsm_uuid_start_cond() */
+	ut_asserteq(IF_OP, *ptr++);
+	ut_asserteq(0x34, acpi_test_get_length(ptr));
+	ptr += 3;
+	ut_asserteq(LEQUAL_OP, *ptr++);
+	ut_asserteq(LOCAL1_OP, *ptr++);
+	ut_asserteq(ZERO_OP, *ptr++);
+
+	/*
+	 * See code from acpi_device_write_dsm_i2c_hid(). We don't check every
+	 * piece
+	 */
+	ut_asserteq(TO_INTEGER_OP, *ptr++);
+	ut_asserteq(ARG1_OP, *ptr++);
+	ut_asserteq(LOCAL2_OP, *ptr++);
+
+	ut_asserteq(IF_OP, *ptr++);
+	ut_asserteq(0xd, acpi_test_get_length(ptr));
+	ptr += 3;
+	ut_asserteq(LEQUAL_OP, *ptr++);
+	ut_asserteq(LOCAL2_OP, *ptr++);
+	ut_asserteq(ZERO_OP, *ptr++);	/* function 0 */
+
+	ut_asserteq(RETURN_OP, *ptr++);
+	ut_asserteq(BUFFER_OP, *ptr++);
+	ptr += 5;
+
+	ut_asserteq(ELSE_OP, *ptr++);
+	ptr += 3;
+
+	ut_asserteq(IF_OP, *ptr++);
+	ut_asserteq(0xd, acpi_test_get_length(ptr));
+	ptr += 3;
+	ut_asserteq(LEQUAL_OP, *ptr++);
+	ut_asserteq(LOCAL2_OP, *ptr++);
+	ut_asserteq(ONE_OP, *ptr++);
+
+	ut_asserteq(RETURN_OP, *ptr++);
+	ut_asserteq(BUFFER_OP, *ptr++);
+	ptr += 5;
+
+	ut_asserteq(ELSE_OP, *ptr++);
+	ptr += 3;
+
+	ut_asserteq(RETURN_OP, *ptr++);
+	ut_asserteq(BUFFER_OP, *ptr++);
+	ptr += 5;
+
+	/* acpigen_write_dsm_uuid_start_cond() */
+	ut_asserteq(IF_OP, *ptr++);
+	ut_asserteq(9, acpi_test_get_length(ptr));
+	ptr += 3;
+	ut_asserteq(LEQUAL_OP, *ptr++);
+	ut_asserteq(LOCAL1_OP, *ptr++);
+	ut_asserteq(ONE_OP, *ptr++);	/* function 1 */
+
+	ut_asserteq(RETURN_OP, *ptr++);
+	ut_asserteq(BYTE_PREFIX, *ptr++);
+	ut_asserteq(reg_offset, *ptr++);
+
+	/* acpigen_write_dsm_uuid_end() */
+	ut_asserteq(RETURN_OP, *ptr++);
+	ut_asserteq(BUFFER_OP, *ptr++);
+	ptr += 5;
+
+	/* acpigen_write_dsm_end */
+	ut_asserteq(RETURN_OP, *ptr++);
+	ut_asserteq(BUFFER_OP, *ptr++);
+	ptr += 5;
+
+	ut_asserteq_ptr(ptr, ctx->current);
+
+	free_context(&ctx);
+
+	return 0;
+}
+DM_TEST(dm_test_acpi_write_i2c_dsm, 0);
+