acpi: Support ordering SSDT data by device

Add a /chosen property to control the order in which the data appears
in the SSDT. This allows matching up U-Boot's output from a dump of the
known-good data obtained from within Linux.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
diff --git a/drivers/core/acpi.c b/drivers/core/acpi.c
index df3d7ba..a9b7fc1 100644
--- a/drivers/core/acpi.c
+++ b/drivers/core/acpi.c
@@ -108,6 +108,85 @@
 	return 0;
 }
 
+static struct acpi_item *find_acpi_item(const char *devname)
+{
+	int i;
+
+	for (i = 0; i < item_count; i++) {
+		struct acpi_item *item = &acpi_item[i];
+
+		if (!strcmp(devname, item->dev->name))
+			return item;
+	}
+
+	return NULL;
+}
+
+/**
+ * sort_acpi_item_type - Sort the ACPI items into the desired order
+ *
+ * This looks up the ordering in the device tree and then adds each item one by
+ * one into the supplied buffer
+ *
+ * @ctx: ACPI context
+ * @start: Start position to put the sorted items. The items will follow each
+ *	other in sorted order
+ * @type: Type of items to sort
+ * @return 0 if OK, -ve on error
+ */
+static int sort_acpi_item_type(struct acpi_ctx *ctx, void *start,
+			       enum gen_type_t type)
+{
+	const u32 *order;
+	int size;
+	int count;
+	void *ptr;
+	void *end = ctx->current;
+
+	ptr = start;
+	order = ofnode_read_chosen_prop("u-boot,acpi-ssdt-order", &size);
+	if (!order) {
+		log_warning("Failed to find ordering, leaving as is\n");
+		return 0;
+	}
+
+	/*
+	 * This algorithm rewrites the context buffer without changing its
+	 * length. So there is no need to update ctx-current
+	 */
+	count = size / sizeof(u32);
+	while (count--) {
+		struct acpi_item *item;
+		const char *name;
+		ofnode node;
+
+		node = ofnode_get_by_phandle(fdt32_to_cpu(*order++));
+		name = ofnode_get_name(node);
+		item = find_acpi_item(name);
+		if (!item) {
+			log_err("Failed to find item '%s'\n", name);
+			return log_msg_ret("find", -ENOENT);
+		}
+		if (item->type == type) {
+			log_debug("   - add %s\n", item->dev->name);
+			memcpy(ptr, item->buf, item->size);
+			ptr += item->size;
+		}
+	}
+
+	/*
+	 * If the sort order is missing an item then the output will be too
+	 * small. Report this error since the item needs to be added to the
+	 * ordering for the ACPI tables to be complete.
+	 */
+	if (ptr != end) {
+		log_warning("*** Missing bytes: ptr=%p, end=%p\n", ptr, end);
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
 acpi_method acpi_get_method(struct udevice *dev, enum method_t method)
 {
 	struct acpi_ops *aops;
@@ -163,11 +242,16 @@
 
 int acpi_fill_ssdt(struct acpi_ctx *ctx)
 {
+	void *start = ctx->current;
 	int ret;
 
 	log_debug("Writing SSDT tables\n");
+	item_count = 0;
 	ret = acpi_recurse_method(ctx, dm_root(), METHOD_FILL_SSDT, TYPE_SSDT);
 	log_debug("Writing SSDT finished, err=%d\n", ret);
+	ret = sort_acpi_item_type(ctx, start, TYPE_SSDT);
+	if (ret)
+		return log_msg_ret("build", ret);
 
 	return ret;
 }