diff --git a/Documentation/superio/common/ssdt.md b/Documentation/superio/common/ssdt.md new file mode 100644 index 0000000000..4353cde3aa --- /dev/null +++ b/Documentation/superio/common/ssdt.md @@ -0,0 +1,56 @@ +# SuperIO SSTD generator + +This page describes the common SSDT ACPI generator for SuperIO chips that can +be found in coreboot. + +## Functional description + +In order to automatically generate ACPI functions you need to add +a new `chip superio/common` and `device pnp xx.0 on` to your devicetree. + +The xx denotes the hexadecimal address of the SuperIO. + +Place the regular LDN pnp devices behind those two entries. + +The code will automatically guess the function based on the decoded +I/O range and ISA IRQ number. + +## Example devicetree.cb + +This example is based on AST2400. + +```code +# Add a "container" for proper ACPI code generation +chip superio/common + device pnp 2e.0 on # just for the base device, not for the LDNs + chip superio/aspeed/ast2400 + device pnp 2e.0 off end + device pnp 2e.2 on # SUART1 + io 0x60 = 0x3f8 + irq 0x70 = 4 + end + device pnp 2e.3 on # SUART2 + io 0x60 = 0x2f8 + irq 0x70 = 3 + end + device pnp 2e.4 on # SWC + io 0x60 = 0xa00 + io 0x62 = 0xa10 + io 0x64 = 0xa20 + io 0x66 = 0xa30 + irq 0x70 = 0 + end + end + end +end +``` + +## TODO + +1) Add ACPI HIDs to every SuperIO driver +2) Don't guess ACPI HID of LDNs if it's known +3) Add "enter config" and "exit config" bytes +4) Generate support methods that allow + * Setting resource settings at runtime + * Getting resource settings at runtime + * Disabling LDNs at runtime diff --git a/Documentation/superio/index.md b/Documentation/superio/index.md index eef4d579ef..39965fde07 100644 --- a/Documentation/superio/index.md +++ b/Documentation/superio/index.md @@ -5,3 +5,6 @@ This section contains documentation about coreboot on specific SuperIOs. ## Nuvoton - [NPCD378](nuvoton/npcd378.md) + +## Common +- [SSDT generator for generic SuperIOs](common/ssdt.md) 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__ */