acpi: Add SPCR table

TESTED works on IO and MMIO console with linux using 'earlycon=' in the
commandline argument.

Signed-off-by: Arthur Heymans <arthur@aheymans.xyz>
Change-Id: I64e624c17a27b9215a8ba83bd6cbb2c0a7aa1dfc
Reviewed-on: https://review.coreboot.org/c/coreboot/+/75685
Reviewed-by: Lean Sheng Tan <sheng.tan@9elements.com>
Reviewed-by: Nico Huber <nico.h@gmx.de>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
diff --git a/src/acpi/acpi.c b/src/acpi/acpi.c
index cfc388b..e0e72f7 100644
--- a/src/acpi/acpi.c
+++ b/src/acpi/acpi.c
@@ -1799,6 +1799,75 @@
 	return lpi_desc->header.length;
 }
 
+static uint8_t acpi_spcr_type(void)
+{
+	/* 16550-compatible with parameters defined in Generic Address Structure */
+	if (CONFIG(DRIVERS_UART_8250IO) || CONFIG(DRIVERS_UART_8250MEM))
+		return 0x12;
+	if (CONFIG(DRIVERS_UART_PL011))
+		return 0x3;
+
+	printk(BIOS_ERR, "%s: unknown serial type\n", __func__);
+	return 0xff;
+}
+
+static void acpi_create_spcr(acpi_spcr_t *spcr)
+{
+	acpi_header_t *header = &(spcr->header);
+	struct lb_serial serial;
+
+	/* The caller checks the header size, so call this first */
+	memset((void *)spcr, 0, sizeof(acpi_spcr_t));
+
+	if (!CONFIG(CONSOLE_SERIAL))
+		return;
+
+	if (fill_lb_serial(&serial) != CB_SUCCESS)
+		return;
+
+	memcpy(header->signature, "SPCR", 4);
+	header->length = sizeof(acpi_spcr_t);
+	header->revision = get_acpi_table_revision(SPCR);
+	memcpy(header->oem_id, OEM_ID, 6);
+	memcpy(header->oem_table_id, ACPI_TABLE_CREATOR, 8);
+	memcpy(header->asl_compiler_id, ASLC, 4);
+	header->asl_compiler_revision = asl_revision;
+
+	spcr->interface_type = acpi_spcr_type();
+	assert(serial.type == LB_SERIAL_TYPE_IO_MAPPED
+	       || serial.type == LB_SERIAL_TYPE_MEMORY_MAPPED);
+	spcr->base_address.space_id = serial.type == LB_SERIAL_TYPE_IO_MAPPED ?
+		ACPI_ADDRESS_SPACE_IO : ACPI_ADDRESS_SPACE_MEMORY;
+	spcr->base_address.bit_width = serial.regwidth * 8;
+	spcr->base_address.bit_offset = 0;
+	switch (serial.regwidth) {
+	case 1:
+		spcr->base_address.access_size = ACPI_ACCESS_SIZE_BYTE_ACCESS;
+		break;
+	case 2:
+		spcr->base_address.access_size = ACPI_ACCESS_SIZE_WORD_ACCESS;
+		break;
+	case 4:
+		spcr->base_address.access_size = ACPI_ACCESS_SIZE_DWORD_ACCESS;
+		break;
+	default:
+		printk(BIOS_ERR, "%s, Invalid serial regwidth\n", __func__);
+	}
+
+	spcr->base_address.addrl = serial.baseaddr;
+	spcr->base_address.addrh = 0;
+	spcr->interrupt_type = 0;
+	spcr->irq = 0;
+	spcr->configured_baudrate = 0; /* Have the OS use whatever is currently set */
+	spcr->parity = 0;
+	spcr->stop_bits = 1;
+	spcr->flow_control = 0;
+	spcr->terminal_type = 2; /* 2 = VT-UTF8 */
+	spcr->language = 0;
+	spcr->pci_did = 0xffff;
+	spcr->pci_vid = 0xffff;
+}
+
 unsigned long __weak fw_cfg_acpi_tables(unsigned long start)
 {
 	return 0;
@@ -2122,6 +2191,15 @@
 		}
 	}
 
+	printk(BIOS_DEBUG, "ACPI:    * SPCR\n");
+	acpi_spcr_t * const spcr = (acpi_spcr_t *)current;
+	acpi_create_spcr(spcr);
+	if (spcr->header.length >= sizeof(acpi_spcr_t)) {
+		current += spcr->header.length;
+		acpi_add_table(rsdp, spcr);
+	}
+	current = acpi_align_current(current);
+
 	printk(BIOS_DEBUG, "current = %lx\n", current);
 
 	for (dev = all_devices; dev; dev = dev->next) {
@@ -2292,6 +2370,8 @@
 		return 1;
 	case LPIT: /* ACPI 5.1 up to 6.3: 0 */
 		return 0;
+	case SPCR:
+		return 4;
 	default:
 		return -1;
 	}
diff --git a/src/include/acpi/acpi.h b/src/include/acpi/acpi.h
index d3f209a..1704393 100644
--- a/src/include/acpi/acpi.h
+++ b/src/include/acpi/acpi.h
@@ -75,6 +75,7 @@
 	/* Tables defined by ACPI and used by coreboot */
 	BERT, CEDT, DBG2, DMAR, DSDT, EINJ, FACS, FADT, HEST, HMAT, HPET, IVRS,
 	MADT, MCFG, RSDP, RSDT, SLIT, SRAT, SSDT, TCPA, TPM2, XSDT, ECDT, LPIT,
+	SPCR,
 	/* Additional proprietary tables used by coreboot */
 	VFCT, NHLT, SPMI, CRAT
 };
@@ -1328,6 +1329,12 @@
 } __packed acpi_spcr_t;
 _Static_assert(sizeof(acpi_spcr_t) == 88, "acpi_spcr_t must have an 88 byte size\n");
 
+#define PC_AT_COMPATIBLE_INTERRUPT (1 << 0)
+#define IO_APIC_COMPATIBLE_INTERRUPT (1 << 1)
+#define IO_SAPIC_COMPATIBLE_INTERRUPT (1 << 2)
+#define ARMH_GIC_COMPATIBLE_INTERRUPT (1 << 3)
+#define RISCV_PLIC_COMPATIBLE_INTERRUPT (1 << 4)
+
 uintptr_t get_coreboot_rsdp(void);
 void acpi_create_einj(acpi_einj_t *einj, uintptr_t addr, u8 actions);