From c162131d00c7f3b8827f9dd08754497c36884123 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Tue, 28 May 2019 11:29:29 +0200 Subject: superio/common: Add ssdtgen for generic SuperIOs Add a generic SuperIO ACPI generator, dropping the need to include additional code in DSDT for SuperIO. It generates a device HID based on the decoded I/O range. Tested on Supermicro X11SSH-TF using AST2400. The SSDT contains no errors and all devices are present. Possible TODOs: * Add "enter config" and "exit config" bytes * Generate support methods to enter and exit config mode * Generate support methods to query, change or disable current resource settings on specific LDNs Change-Id: I2716ae0580d68e5d4fcc484cb1648a2cdc1f4ca0 Signed-off-by: Patrick Rudolph Reviewed-on: https://review.coreboot.org/c/coreboot/+/33033 Tested-by: build bot (Jenkins) Reviewed-by: Felix Held --- src/arch/x86/include/arch/acpi.h | 8 ++ src/superio/common/chip.h | 21 ++++ src/superio/common/generic.c | 192 ++++++++++++++++++++++++++++++ src/superio/common/ssdt.c | 247 +++++++++++++++++++++++++++++++++++++++ src/superio/common/ssdt.h | 23 ++++ 5 files changed, 491 insertions(+) create mode 100644 src/superio/common/chip.h create mode 100644 src/superio/common/generic.c create mode 100644 src/superio/common/ssdt.c create mode 100644 src/superio/common/ssdt.h (limited to 'src') diff --git a/src/arch/x86/include/arch/acpi.h b/src/arch/x86/include/arch/acpi.h index 16e4269dde..e3456d8f0a 100644 --- a/src/arch/x86/include/arch/acpi.h +++ b/src/arch/x86/include/arch/acpi.h @@ -129,6 +129,14 @@ typedef struct acpi_gen_regaddr { #define ACPI_ACCESS_SIZE_DWORD_ACCESS 3 #define ACPI_ACCESS_SIZE_QWORD_ACCESS 4 +/* Common ACPI HIDs */ +#define ACPI_HID_FDC "PNP0700" +#define ACPI_HID_KEYBOARD "PNP0303" +#define ACPI_HID_MOUSE "PNP0F03" +#define ACPI_HID_COM "PNP0501" +#define ACPI_HID_LPT "PNP0400" +#define ACPI_HID_PNP "PNP0C02" + /* Generic ACPI header, provided by (almost) all tables */ typedef struct acpi_table_header { char signature[4]; /* ACPI signature (4 ASCII characters) */ diff --git a/src/superio/common/chip.h b/src/superio/common/chip.h new file mode 100644 index 0000000000..fd618c54ce --- /dev/null +++ b/src/superio/common/chip.h @@ -0,0 +1,21 @@ +/* + * This file is part of the coreboot project. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __SUPERIO_COMMON_CHIP_H__ +#define __SUPERIO_COMMON_CHIP_H__ + +struct superio_common_config { + /* FIXME: Add enter conf/exit conf codes here for SSDT generation */ +}; + +#endif /* __SUPERIO_COMMON_CHIP_H__ */ diff --git a/src/superio/common/generic.c b/src/superio/common/generic.c new file mode 100644 index 0000000000..bffa9f3403 --- /dev/null +++ b/src/superio/common/generic.c @@ -0,0 +1,192 @@ +/* + * This file is part of the coreboot project. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include + +static void generic_set_resources(struct device *dev) +{ + struct resource *res; + + for (res = dev->resource_list; res; res = res->next) { + if (!(res->flags & IORESOURCE_ASSIGNED)) + continue; + + res->flags |= IORESOURCE_STORED; + report_resource_stored(dev, res, ""); + } +} + +static void generic_read_resources(struct device *dev) +{ + struct resource *res = new_resource(dev, 0); + res->base = dev->path.pnp.port; + res->size = 2; + res->flags = IORESOURCE_IO | IORESOURCE_ASSIGNED | IORESOURCE_FIXED; +} + +#if CONFIG(HAVE_ACPI_TABLES) +static void generic_ssdt(struct device *dev) +{ + const char *scope = acpi_device_scope(dev); + const char *name = acpi_device_name(dev); + + if (!scope || !name) { + printk(BIOS_ERR, "%s: Missing ACPI path/scope\n", + dev_path(dev)); + return; + } + + /* Device */ + acpigen_write_scope(scope); + acpigen_write_device(name); + + printk(BIOS_DEBUG, "%s.%s: %s\n", scope, name, dev_path(dev)); + + acpigen_write_name_string("_HID", "PNP0C02"); + acpigen_write_name_string("_DDN", dev_name(dev)); + + /* OperationRegion("IOID", SYSTEMIO, port, 2) */ + struct opregion opreg = OPREGION("IOID", SYSTEMIO, dev->path.pnp.port, 2); + acpigen_write_opregion(&opreg); + + struct fieldlist l[] = { + FIELDLIST_OFFSET(0), + FIELDLIST_NAMESTR("INDX", 8), + FIELDLIST_NAMESTR("DATA", 8), + }; + + /* Field (IOID, AnyAcc, NoLock, Preserve) + * { + * Offset (0), + * INDX, 8, + * DATA, 8, + * } */ + acpigen_write_field(opreg.name, l, ARRAY_SIZE(l), FIELD_BYTEACC | FIELD_NOLOCK | + FIELD_PRESERVE); + + struct fieldlist i[] = { + FIELDLIST_OFFSET(0x07), + FIELDLIST_NAMESTR("LDN", 8), + FIELDLIST_OFFSET(0x21), + FIELDLIST_NAMESTR("SCF1", 8), + FIELDLIST_NAMESTR("SCF2", 8), + FIELDLIST_NAMESTR("SCF3", 8), + FIELDLIST_NAMESTR("SCF4", 8), + FIELDLIST_NAMESTR("SCF5", 8), + FIELDLIST_NAMESTR("SCF6", 8), + FIELDLIST_NAMESTR("SCF7", 8), + FIELDLIST_OFFSET(0x29), + FIELDLIST_NAMESTR("CKCF", 8), + FIELDLIST_OFFSET(0x2F), + FIELDLIST_NAMESTR("SCFF", 8), + FIELDLIST_OFFSET(0x30), + FIELDLIST_NAMESTR("ACT0", 1), + FIELDLIST_NAMESTR("ACT1", 1), + FIELDLIST_NAMESTR("ACT2", 1), + FIELDLIST_NAMESTR("ACT3", 1), + FIELDLIST_NAMESTR("ACT4", 1), + FIELDLIST_NAMESTR("ACT5", 1), + FIELDLIST_NAMESTR("ACT6", 1), + FIELDLIST_NAMESTR("ACT7", 1), + FIELDLIST_OFFSET(0x60), + FIELDLIST_NAMESTR("IOH0", 8), + FIELDLIST_NAMESTR("IOL0", 8), + FIELDLIST_NAMESTR("IOH1", 8), + FIELDLIST_NAMESTR("IOL1", 8), + FIELDLIST_NAMESTR("IOH2", 8), + FIELDLIST_NAMESTR("IOL2", 8), + FIELDLIST_NAMESTR("IOH3", 8), + FIELDLIST_NAMESTR("IOL3", 8), + FIELDLIST_OFFSET(0x70), + FIELDLIST_NAMESTR("INTR", 4), + FIELDLIST_OFFSET(0x71), + FIELDLIST_NAMESTR("INTT", 2), + FIELDLIST_OFFSET(0x72), + FIELDLIST_NAMESTR("ITR2", 4), + FIELDLIST_OFFSET(0x73), + FIELDLIST_NAMESTR("ITR2", 2), + FIELDLIST_OFFSET(0x74), + FIELDLIST_NAMESTR("DMCH", 8), + FIELDLIST_OFFSET(0xE0), + FIELDLIST_NAMESTR("RGE0", 8), + FIELDLIST_NAMESTR("RGE1", 8), + FIELDLIST_NAMESTR("RGE2", 8), + FIELDLIST_NAMESTR("RGE3", 8), + FIELDLIST_NAMESTR("RGE4", 8), + FIELDLIST_NAMESTR("RGE5", 8), + FIELDLIST_NAMESTR("RGE6", 8), + FIELDLIST_NAMESTR("RGE7", 8), + FIELDLIST_NAMESTR("RGE8", 8), + FIELDLIST_NAMESTR("RGE9", 8), + FIELDLIST_NAMESTR("RGEA", 8), + FIELDLIST_OFFSET(0xF0), + FIELDLIST_NAMESTR("OPT0", 8), + FIELDLIST_NAMESTR("OPT1", 8), + FIELDLIST_NAMESTR("OPT2", 8), + FIELDLIST_NAMESTR("OPT3", 8), + FIELDLIST_NAMESTR("OPT4", 8), + FIELDLIST_NAMESTR("OPT5", 8), + FIELDLIST_NAMESTR("OPT6", 8), + FIELDLIST_NAMESTR("OPT7", 8), + FIELDLIST_NAMESTR("OPT8", 8), + FIELDLIST_NAMESTR("OPT9", 8), + }; + + acpigen_write_indexfield("INDX", "DATA", i, ARRAY_SIZE(i), FIELD_BYTEACC | + FIELD_NOLOCK | FIELD_PRESERVE); + + acpigen_pop_len(); /* Device */ + acpigen_pop_len(); /* Scope */ +} + +static const char *generic_acpi_name(const struct device *dev) +{ + return "SIO0"; +} +#endif + +static struct device_operations ops = { + .read_resources = generic_read_resources, + .set_resources = generic_set_resources, + .enable_resources = DEVICE_NOOP, +#if CONFIG(HAVE_ACPI_TABLES) + .acpi_fill_ssdt_generator = generic_ssdt, + .acpi_name = generic_acpi_name, +#endif +}; + +static void enable_dev(struct device *dev) +{ + if (dev->path.type != DEVICE_PATH_PNP) + printk(BIOS_ERR, "%s: Unsupported device type\n", dev_path(dev)); + else if (!dev->path.pnp.port) + printk(BIOS_ERR, "%s: Base address not set\n", dev_path(dev)); + else + dev->ops = &ops; + + /* + * Need to call enable_dev() on the devices "behind" the Generic Super I/O. + * coreboot's generic allocator doesn't expect them behind PnP devices. + */ + scan_static_bus(dev); +} + +struct chip_operations superio_common_ops = { + CHIP_NAME("Generic Super I/O") + .enable_dev = enable_dev, +}; diff --git a/src/superio/common/ssdt.c b/src/superio/common/ssdt.c new file mode 100644 index 0000000000..035f06a71e --- /dev/null +++ b/src/superio/common/ssdt.c @@ -0,0 +1,247 @@ +/* + * This file is part of the coreboot project. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +struct superio_dev { + const char *acpi_hid; + u16 io_base[4]; + u8 irq[2]; +}; + +static const struct superio_dev superio_devs[] = { + {ACPI_HID_FDC, {0x3f0, 0x3f2, 0x3f7}, {6, } }, + {ACPI_HID_KEYBOARD, {60, 64, }, {1, } }, + {ACPI_HID_MOUSE, {60, 64, }, {12, } }, + {ACPI_HID_COM, {0x3f8, 0x2f8, 0x3e8, 0x2e8}, {4, 3} }, + {ACPI_HID_LPT, {0x378, }, {7, } }, +}; + +static const u8 io_idx[] = {PNP_IDX_IO0, PNP_IDX_IO1, PNP_IDX_IO2, PNP_IDX_IO3}; +static const u8 irq_idx[] = {PNP_IDX_IRQ0, PNP_IDX_IRQ1}; + +static const struct superio_dev *superio_guess_function(struct device *dev) +{ + for (size_t i = 0; i < ARRAY_SIZE(io_idx); i++) { + struct resource *res = probe_resource(dev, io_idx[i]); + if (!res || !res->base) + continue; + + for (size_t j = 0; j < ARRAY_SIZE(superio_devs); j++) { + for (size_t k = 0; k < 4; k++) { + if (!superio_devs[j].io_base[k]) + continue; + if (superio_devs[j].io_base[k] == res->base) + return &superio_devs[j]; + } + } + } + for (size_t i = 0; i < ARRAY_SIZE(irq_idx); i++) { + struct resource *res = probe_resource(dev, irq_idx[i]); + if (!res || !res->size) + continue; + for (size_t j = 0; j < ARRAY_SIZE(superio_devs); j++) { + for (size_t k = 0; k < 2; k++) { + if (!superio_devs[j].irq[k]) + continue; + if (superio_devs[j].irq[k] == res->base) + return &superio_devs[j]; + } + } + } + return NULL; +} + +/* Return true if there are resources to report */ +static bool has_resources(struct device *dev) +{ + for (size_t i = 0; i < ARRAY_SIZE(io_idx); i++) { + struct resource *res = probe_resource(dev, io_idx[i]); + if (!res || !res->base || !res->size) + continue; + return 1; + } + for (size_t i = 0; i < ARRAY_SIZE(irq_idx); i++) { + struct resource *res = probe_resource(dev, irq_idx[i]); + if (!res || !res->size || res->base > 16) + continue; + return 1; + } + return 0; +} + +/* Add IO and IRQ resources for _CRS or _PRS */ +static void ldn_gen_resources(struct device *dev) +{ + uint16_t irq = 0; + for (size_t i = 0; i < ARRAY_SIZE(io_idx); i++) { + struct resource *res = probe_resource(dev, io_idx[i]); + if (!res || !res->base) + continue; + resource_t base = res->base; + resource_t size = res->size; + while (size > 0) { + resource_t sz = size > 255 ? 255 : size; + /* TODO: Needs test with regions >= 256 bytes */ + acpigen_write_io16(base, base, 1, sz, 1); + size -= sz; + base += sz; + } + } + for (size_t i = 0; i < ARRAY_SIZE(irq_idx); i++) { + struct resource *res = probe_resource(dev, irq_idx[i]); + if (!res || !res->size || res->base >= 16) + continue; + irq |= 1 << res->base; + } + if (irq) + acpigen_write_irq(irq); + +} + +/* Add resource base and size for additional SuperIO code */ +static void ldn_gen_resources_use(struct device *dev) +{ + char name[5]; + for (size_t i = 0; i < ARRAY_SIZE(io_idx); i++) { + struct resource *res = probe_resource(dev, io_idx[i]); + if (!res || !res->base || !res->size) + continue; + + snprintf(name, sizeof(name), "IO%XB", i); + name[4] = '\0'; + acpigen_write_name_integer(name, res->base); + + snprintf(name, sizeof(name), "IO%XS", i); + name[4] = '\0'; + acpigen_write_name_integer(name, res->size); + } +} + +const char *superio_common_ldn_acpi_name(const struct device *dev) +{ + u8 ldn = dev->path.pnp.device & 0xff; + u8 vldn = (dev->path.pnp.device >> 8) & 0x7; + static char name[5]; + + snprintf(name, sizeof(name), "L%02X%01X", ldn, vldn); + + name[4] = '\0'; + + return name; +} + +static const char *name_from_hid(const char *hid) +{ + static const struct { + const char *hid; + const char *name; + } lookup[] = { + {ACPI_HID_FDC, "FDC" }, + {ACPI_HID_KEYBOARD, "PS2 Keyboard" }, + {ACPI_HID_MOUSE, "PS2 Mouse"}, + {ACPI_HID_COM, "COM port" }, + {ACPI_HID_LPT, "LPT" }, + {ACPI_HID_PNP, "Generic PNP device" }, + }; + + for (size_t i = 0; hid && i < ARRAY_SIZE(lookup); i++) { + if (strcmp(hid, lookup[i].hid) == 0) + return lookup[i].name; + } + return "Generic device"; +} + +void superio_common_fill_ssdt_generator(struct device *dev) +{ + const char *scope = acpi_device_scope(dev); + const char *name = acpi_device_name(dev); + const u8 ldn = dev->path.pnp.device & 0xff; + const u8 vldn = (dev->path.pnp.device >> 8) & 0x7; + const char *hid; + + if (!scope || !name) { + printk(BIOS_ERR, "%s: Missing ACPI path/scope\n", dev_path(dev)); + return; + } + if (vldn) { + printk(BIOS_DEBUG, "%s: Ignoring virtual LDN\n", dev_path(dev)); + return; + } + + printk(BIOS_DEBUG, "%s.%s: %s\n", scope, name, dev_path(dev)); + + /* Scope */ + acpigen_write_scope(scope); + + /* Device */ + acpigen_write_device(name); + + acpigen_write_name_byte("_UID", 0); + acpigen_write_name_byte("LDN", ldn); + acpigen_write_name_byte("VLDN", vldn); + + acpigen_write_STA(dev->enabled ? 0xf : 0); + + if (!dev->enabled) { + acpigen_pop_len(); /* Device */ + acpigen_pop_len(); /* Scope */ + return; + } + + if (has_resources(dev)) { + /* Resources - _CRS */ + acpigen_write_name("_CRS"); + acpigen_write_resourcetemplate_header(); + ldn_gen_resources(dev); + acpigen_write_resourcetemplate_footer(); + + /* Resources - _PRS */ + acpigen_write_name("_PRS"); + acpigen_write_resourcetemplate_header(); + ldn_gen_resources(dev); + acpigen_write_resourcetemplate_footer(); + + /* Resources base and size for 3rd party ACPI code */ + ldn_gen_resources_use(dev); + } + + hid = acpi_device_hid(dev); + if (!hid) { + printk(BIOS_ERR, "%s: SuperIO driver doesn't provide a _HID\n", dev_path(dev)); + /* Try to guess it... */ + const struct superio_dev *sdev = superio_guess_function(dev); + if (sdev && sdev->acpi_hid) { + hid = sdev->acpi_hid; + printk(BIOS_WARNING, "%s: Guessed _HID is '%s'\n", dev_path(dev), hid); + } else { + hid = ACPI_HID_PNP; + printk(BIOS_ERR, "%s: Failed to guessed _HID\n", dev_path(dev)); + } + } + + acpigen_write_name_string("_HID", hid); + acpigen_write_name_string("_DDN", name_from_hid(hid)); + + acpigen_pop_len(); /* Device */ + acpigen_pop_len(); /* Scope */ +} diff --git a/src/superio/common/ssdt.h b/src/superio/common/ssdt.h new file mode 100644 index 0000000000..8c63742798 --- /dev/null +++ b/src/superio/common/ssdt.h @@ -0,0 +1,23 @@ +/* + * This file is part of the coreboot project. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __SUPERIO_COMMON_SSDT_H__ +#define __SUPERIO_COMMON_SSDT_H__ + +#include + +const char *superio_common_ldn_acpi_name(const struct device *dev); +void superio_common_fill_ssdt_generator(struct device *dev); + +#endif /* __SUPERIO_COMMON_SSDT_H__ */ -- cgit v1.2.3