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 <patrick.rudolph@9elements.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/33033
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Felix Held <felix-coreboot@felixheld.de>
This commit is contained in:
Patrick Rudolph 2019-05-28 11:29:29 +02:00 committed by Patrick Georgi
parent 6b2a54030f
commit c162131d00
7 changed files with 550 additions and 0 deletions

View File

@ -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

View File

@ -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)

View File

@ -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) */

21
src/superio/common/chip.h Normal file
View File

@ -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__ */

View File

@ -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 <device/device.h>
#include <device/pnp.h>
#include <arch/acpigen.h>
#include <device/pnp_def.h>
#include <console/console.h>
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,
};

247
src/superio/common/ssdt.c Normal file
View File

@ -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 <superio/common/ssdt.h>
#include <device/device.h>
#include <device/pnp.h>
#include <arch/acpigen.h>
#include <arch/acpi.h>
#include <device/pnp_def.h>
#include <console/console.h>
#include <types.h>
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 */
}

23
src/superio/common/ssdt.h Normal file
View File

@ -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 <device/device.h>
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__ */