[kvm-unit-tests RFC v2 10/18] acpi: Add MADT table parse code

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Zhenzhong Duan <zhenzhong.duan@xxxxxxxxx>

Support LAPIC, X2APIC and WAKEUP sub-table, other sub-table are ignored
for now. Also add a wakeup wrapping function which is used by TDX.

The parsed result is stored in id_map[] and acpi_mp_wake_mailbox.

Signed-off-by: Zhenzhong Duan <zhenzhong.duan@xxxxxxxxx>
Reviewed-by: Yu Zhang <yu.c.zhang@xxxxxxxxx>
Link: https://lore.kernel.org/r/20220303071907.650203-10-zhenzhong.duan@xxxxxxxxx
Signed-off-by: Qian Wen <qian.wen@xxxxxxxxx>
---
 lib/acpi.c      | 160 ++++++++++++++++++++++++++++++++++++++++++++++++
 lib/acpi.h      |  59 +++++++++++++++++-
 lib/x86/setup.c |   4 ++
 3 files changed, 222 insertions(+), 1 deletion(-)

diff --git a/lib/acpi.c b/lib/acpi.c
index 0440cddb..9db32e2a 100644
--- a/lib/acpi.c
+++ b/lib/acpi.c
@@ -1,5 +1,7 @@
 #include "libcflat.h"
 #include "acpi.h"
+#include "errno.h"
+#include "asm/barrier.h"
 
 #ifdef CONFIG_EFI
 struct acpi_table_rsdp *efi_rsdp = NULL;
@@ -127,3 +129,161 @@ int acpi_table_parse_madt(enum acpi_madt_type mtype, acpi_table_handler handler)
 
 	return count;
 }
+
+struct acpi_madt_multiproc_wakeup_mailbox *acpi_mp_wake_mailbox;
+
+#define smp_store_release(p, val)					\
+do {									\
+	barrier();							\
+	WRITE_ONCE(*p, val);						\
+} while (0)
+
+static inline bool test_bit(int nr, const void *addr)
+{
+	const u32 *p = (const u32 *)addr;
+	return ((1UL << (nr & 31)) & (p[nr >> 5])) != 0;
+}
+
+int acpi_wakeup_cpu(int apicid, unsigned long start_ip, unsigned char* cpus)
+{
+	u8 timeout = 0xFF;
+
+	/*
+	 * According to the ACPI specification r6.4, sec 5.2.12.19, the
+	 * mailbox-based wakeup mechanism cannot be used more than once
+	 * for the same CPU, so skip sending wake commands to already
+	 * awake CPU.
+	 */
+	if (test_bit(apicid, cpus)) {
+		printf("CPU already awake (APIC ID %x), skipping wakeup\n",
+		       apicid);
+		return -EINVAL;
+	}
+
+	/*
+	 * Mailbox memory is shared between firmware and OS. Firmware will
+	 * listen on mailbox command address, and once it receives the wakeup
+	 * command, CPU associated with the given apicid will be booted. So,
+	 * the value of apic_id and wakeup_vector has to be set before updating
+	 * the wakeup command. So use smp_store_release to let the compiler know
+	 * about it and preserve the order of writes.
+	 */
+	smp_store_release(&acpi_mp_wake_mailbox->apic_id, apicid);
+	smp_store_release(&acpi_mp_wake_mailbox->wakeup_vector, start_ip);
+	smp_store_release(&acpi_mp_wake_mailbox->command,
+			  ACPI_MP_WAKE_COMMAND_WAKEUP);
+
+	/*
+	 * After writing wakeup command, wait for maximum timeout of 0xFF
+	 * for firmware to reset the command address back zero to indicate
+	 * the successful reception of command.
+	 * NOTE: 255 as timeout value is decided based on our experiments.
+	 *
+	 * XXX: Change the timeout once ACPI specification comes up with
+	 *      standard maximum timeout value.
+	 */
+	while (READ_ONCE(acpi_mp_wake_mailbox->command) && timeout--)
+		cpu_relax();
+
+	/* If timed out (timeout == 0), return error.
+	 * Otherwise, the CPU wakes up successfully.
+	 */
+	return timeout == 0 ? -EIO : 0;
+}
+
+static bool parse_madt_table(struct acpi_table *madt, unsigned char* id_map)
+{
+	u64 table_start = (unsigned long)madt + sizeof(struct acpi_table_madt);
+	u64 table_end = (unsigned long)madt + madt->length;
+	struct acpi_subtable_header *sub_table;
+	bool failed = false;
+	u32 uid, apic_id;
+	u8 enabled;
+
+	while (table_start < table_end && !failed) {
+		struct acpi_madt_local_apic *processor;
+		struct acpi_madt_local_x2apic *processor2;
+		struct acpi_madt_multiproc_wakeup *mp_wake;
+
+		sub_table = (struct acpi_subtable_header *)table_start;
+
+		switch (sub_table->type) {
+		case ACPI_MADT_TYPE_LOCAL_APIC:
+			processor = (struct acpi_madt_local_apic *)sub_table;
+
+			if (BAD_MADT_ENTRY(processor, table_end)) {
+				failed = true;
+				break;
+			}
+
+			uid = processor->processor_id;
+			apic_id = processor->id;
+			enabled = processor->lapic_flags & ACPI_MADT_ENABLED;
+
+			/* Ignore invalid ID */
+			if (apic_id == 0xff)
+				break;
+			if (enabled)
+				id_map[uid] = apic_id;
+
+			printf("apicid %x uid %x %s\n", apic_id, uid,
+			       enabled ? "enabled" : "disabled");
+			break;
+
+		case ACPI_MADT_TYPE_LOCAL_X2APIC:
+			processor2 = (struct acpi_madt_local_x2apic *)sub_table;
+
+			if (BAD_MADT_ENTRY(processor2, table_end)) {
+				failed = true;
+				break;
+			}
+
+			uid = processor2->uid;
+			apic_id = processor2->local_apic_id;
+			enabled = processor2->lapic_flags & ACPI_MADT_ENABLED;
+
+			/* Ignore invalid ID */
+			if (apic_id == 0xffffffff)
+				break;
+			if (enabled)
+				id_map[uid] = apic_id;
+
+			printf("x2apicid %x uid %x %s\n", apic_id, uid,
+			       enabled ? "enabled" : "disabled");
+			break;
+		case ACPI_MADT_TYPE_MULTIPROC_WAKEUP:
+			mp_wake = (struct acpi_madt_multiproc_wakeup *)sub_table;
+
+			if (BAD_MADT_ENTRY(mp_wake, table_end)) {
+				failed = true;
+				break;
+			}
+
+			if (acpi_mp_wake_mailbox)
+				printf("WARN: duplicate mailbox %lx\n", (u64)acpi_mp_wake_mailbox);
+
+			acpi_mp_wake_mailbox = (void *)mp_wake->base_address;
+			printf("MP Wake (Mailbox version[%d] base_address[%lx])\n",
+					mp_wake->mailbox_version, mp_wake->base_address);
+			break;
+		default:
+			/* Ignored currently */
+			break;
+		}
+		if (!failed)
+			table_start += sub_table->length;
+	}
+
+	return !failed;
+}
+
+bool parse_acpi_table(unsigned char* id_map)
+{
+	struct acpi_table *tb;
+
+	tb = find_acpi_table_addr(MADT_SIGNATURE);
+	if (tb)
+		return parse_madt_table(tb, id_map);
+
+	return false;
+}
diff --git a/lib/acpi.h b/lib/acpi.h
index c330c877..311fbb1b 100644
--- a/lib/acpi.h
+++ b/lib/acpi.h
@@ -245,9 +245,64 @@ enum acpi_madt_type {
 	ACPI_MADT_TYPE_GENERIC_MSI_FRAME = 13,
 	ACPI_MADT_TYPE_GENERIC_REDISTRIBUTOR = 14,
 	ACPI_MADT_TYPE_GENERIC_TRANSLATOR = 15,
-	ACPI_MADT_TYPE_RESERVED = 16	/* 16 and greater are reserved */
+	ACPI_MADT_TYPE_MULTIPROC_WAKEUP = 16,
+	ACPI_MADT_TYPE_RESERVED = 17	/* 17 and greater are reserved */
 };
 
+#define BAD_MADT_ENTRY(entry, end) (                                        \
+		(!entry) || (unsigned long)entry + sizeof(*entry) > end ||  \
+		((struct acpi_subtable_header *)entry)->length < sizeof(*entry))
+
+/*
+ * MADT Subtables, correspond to Type in struct acpi_subtable_header
+ */
+
+/* 0: Processor Local APIC */
+
+struct acpi_madt_local_apic {
+	struct acpi_subtable_header header;
+	u8 processor_id;        /* ACPI processor id */
+	u8 id;                  /* Processor's local APIC id */
+	u32 lapic_flags;
+};
+
+/* 9: Processor Local X2APIC (ACPI 4.0) */
+
+struct acpi_madt_local_x2apic {
+	struct acpi_subtable_header header;
+	u16 reserved;           /* reserved - must be zero */
+	u32 local_apic_id;      /* Processor x2APIC ID  */
+	u32 lapic_flags;
+	u32 uid;                /* ACPI processor UID */
+};
+
+/* 16: Multiprocessor wakeup (ACPI 6.4) */
+
+struct acpi_madt_multiproc_wakeup {
+	struct acpi_subtable_header header;
+	u16 mailbox_version;
+	u32 reserved;		/* reserved - must be zero */
+	u64 base_address;
+};
+
+#define ACPI_MULTIPROC_WAKEUP_MB_OS_SIZE        2032
+#define ACPI_MULTIPROC_WAKEUP_MB_FIRMWARE_SIZE  2048
+
+struct acpi_madt_multiproc_wakeup_mailbox {
+	u16 command;
+	u16 reserved;		/* reserved - must be zero */
+	u32 apic_id;
+	u64 wakeup_vector;
+	u8 reserved_os[ACPI_MULTIPROC_WAKEUP_MB_OS_SIZE];	/* reserved for OS use */
+	u8 reserved_firmware[ACPI_MULTIPROC_WAKEUP_MB_FIRMWARE_SIZE];	/* reserved for firmware use */
+};
+
+#define ACPI_MP_WAKE_COMMAND_WAKEUP	1
+
+/*
+ * Common flags fields for MADT subtables
+ */
+
 /* MADT Local APIC flags */
 #define ACPI_MADT_ENABLED		(1)	/* 00: Processor is usable if set */
 
@@ -298,5 +353,7 @@ struct acpi_table_gtdt {
 void set_efi_rsdp(struct acpi_table_rsdp *rsdp);
 void *find_acpi_table_addr(u32 sig);
 int acpi_table_parse_madt(enum acpi_madt_type mtype, acpi_table_handler handler);
+int acpi_wakeup_cpu(int apicid, unsigned long start_ip, unsigned char* online_cpus);
+bool parse_acpi_table(unsigned char* id_map);
 
 #endif
diff --git a/lib/x86/setup.c b/lib/x86/setup.c
index 20807700..406d04e3 100644
--- a/lib/x86/setup.c
+++ b/lib/x86/setup.c
@@ -339,6 +339,10 @@ efi_status_t setup_efi(efi_bootinfo_t *efi_bootinfo)
 		return status;
 	}
 
+	/* Parse all acpi tables, currently only MADT table */
+	if (!parse_acpi_table(id_map))
+		return EFI_NOT_FOUND;
+
 	phase = "AMD SEV";
 	status = setup_amd_sev();
 
-- 
2.25.1





[Index of Archives]     [KVM ARM]     [KVM ia64]     [KVM ppc]     [Virtualization Tools]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Questions]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux