drivers/ipmi: Add chip ops

* Add chips ops for IPMI KCS.
* Get IPMI version over KCS.
* Generates ACPI SPMI table for IPMI KCS.
* Generates SMBIOS type 38 for IPMI KCS.
* Generates ACPI SPMI device for IPMI KCS on LPC device.
* Add documentation

To use this driver on BMC that support KCS on I/O:

1. Add an entry to the devicetree.cb:

 chip drivers/ipmi
    device pnp ca2.0 on end         # IPMI KCS
 end

2. Select IPMI_KCS in Kconfig.
3. (Optional) enable LPC I/O decode for the given address.

Tested on Wedge100s.

Change-Id: I73cbd2058ccdc5395baf244f31345a85eb0047d7
Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/33255
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-06-06 15:45:51 +02:00 committed by Felix Held
parent c469712166
commit ffbc3b5f5f
7 changed files with 376 additions and 0 deletions

View File

@ -0,0 +1,7 @@
# Platform indenpendend drivers documentation
The drivers can be found in `src/drivers`. They are intended for onboard
and plugin devices, significantly reducing integration complexity and
they allow to easily reuse existing code accross platforms.
* [IPMI KCS](ipmi_kcs.md)

View File

@ -0,0 +1,47 @@
# IPMI KCS driver
The driver can be found in `src/drivers/ipmi/`. It works with BMC that provide
a KCS I/O interface as specified in the [IPMI] standard.
The driver detects the IPMI version, reserves the I/O space in coreboot's
resource allocator and writes the required ACPI and SMBIOS tables.
## For developers
To use the driver, select the `IPMI_KCS` Kconfig and add the following PNP
device under the LPC bridge device (in example for the KCS at 0xca2):
```
chip drivers/ipmi
device pnp ca2.0 on end # IPMI KCS
end
```
**Note:** The I/O base address needs to be aligned to 2.
The following registers can be set:
* `have_nv_storage`
* Boolean
* If true `nv_storage_device_address` will be added to SMBIOS type 38.
* `nv_storage_device_address`
* Integer
* The NV storage address as defined in SMBIOS spec for type 38.
* `bmc_i2c_address`
* Integer
* The i2c address of the BMC. zero if not applicable.
* `have_apic`
* Boolean
* If true the `apic_interrupt` will be added to SPMI table.
* `apic_interrupt`
* Integer
* The APIC interrupt used to notify about a change on the KCS.
* `have_gpe`
* Boolean
* If true the `gpe_interrupt` will be added to SPMI table.
* `gpe_interrupt`
* Integer
* The bit in GPE (SCI) used to notify about a change on the KCS.
[IPMI]: https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf

View File

@ -175,6 +175,7 @@ Contents:
* [Native Graphics Initialization with libgfxinit](gfx/libgfxinit.md)
* [Display panel-specific documentation](gfx/display-panel.md)
* [Architecture-specific documentation](arch/index.md)
* [Platform independend drivers documentation](drivers/index.md)
* [Northbridge-specific documentation](northbridge/index.md)
* [System on Chip-specific documentation](soc/index.md)
* [Mainboard-specific documentation](mainboard/index.md)

View File

@ -1 +1,2 @@
ramstage-$(CONFIG_IPMI_KCS) += ipmi_kcs.c
ramstage-$(CONFIG_IPMI_KCS) += ipmi_kcs_ops.c

29
src/drivers/ipmi/chip.h Normal file
View File

@ -0,0 +1,29 @@
/*
* This file is part of the coreboot project.
*
* Copyright (C) 2017 Patrick Rudolph <siro@das-labor.org>
*
* 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 _IMPI_CHIP_H_
#define _IPMI_CHIP_H_
struct drivers_ipmi_config {
u8 bmc_i2c_address;
u8 have_nv_storage;
u8 nv_storage_device_address;
u8 have_gpe;
u8 gpe_interrupt;
u8 have_apic;
u32 apic_interrupt;
};
#endif /* _IMPI_CHIP_H_ */

View File

@ -20,6 +20,10 @@
#define IPMI_NETFN_BRIDGE 0x02
#define IPMI_NETFN_SENSOREVENT 0x04
#define IPMI_NETFN_APPLICATION 0x06
#define IPMI_BMC_GET_DEVICE_ID 0x01
#define IPMI_IPMI_VERSION_MINOR(x) ((x) >> 4)
#define IPMI_IPMI_VERSION_MAJOR(x) ((x) & 0xf)
#define IPMI_NETFN_FIRMWARE 0x08
#define IPMI_NETFN_STORAGE 0x0a
#define IPMI_NETFN_TRANSPORT 0x0c
@ -29,4 +33,24 @@
extern int ipmi_kcs_message(int port, int netfn, int lun, int cmd,
const unsigned char *inmsg, int inlen,
unsigned char *outmsg, int outlen);
struct ipmi_rsp {
uint8_t lun;
uint8_t cmd;
uint8_t completion_code;
} __packed;
/* Get Device ID */
struct ipmi_devid_rsp {
struct ipmi_rsp resp;
uint8_t device_id;
uint8_t device_revision;
uint8_t fw_rev1;
uint8_t fw_rev2;
uint8_t ipmi_version;
uint8_t additional_device_support;
uint8_t manufacturer_id[3];
uint8_t product_id[2];
} __packed;
#endif

View File

@ -0,0 +1,267 @@
/*
* This file is part of the coreboot project.
*
* Copyright (C) 2019 9elements Agency GmbH
*
* 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.
*
* Place in devicetree.cb:
*
* chip drivers/ipmi
* device pnp ca2.0 on end # IPMI KCS
* end
*/
#include <console/console.h>
#include <device/device.h>
#include <device/pnp.h>
#if CONFIG(HAVE_ACPI_TABLES)
#include <arch/acpi.h>
#include <arch/acpigen.h>
#endif
#if CONFIG(GENERATE_SMBIOS_TABLES)
#include <smbios.h>
#endif
#include <version.h>
#include <delay.h>
#include "ipmi_kcs.h"
#include "chip.h"
/* 4 bit encoding */
static u8 ipmi_revision_major = 0x1;
static u8 ipmi_revision_minor = 0x0;
static int ipmi_get_device_id(struct device *dev, struct ipmi_devid_rsp *rsp)
{
int ret;
ret = ipmi_kcs_message(dev->path.pnp.port, IPMI_NETFN_APPLICATION, 0,
IPMI_BMC_GET_DEVICE_ID, NULL, 0, (u8 *)rsp,
sizeof(*rsp));
if (ret < sizeof(struct ipmi_rsp) || rsp->resp.completion_code) {
printk(BIOS_ERR, "IPMI: %s command failed (ret=%d resp=0x%x)\n",
__func__, ret, rsp->resp.completion_code);
return 1;
}
if (ret != sizeof(*rsp)) {
printk(BIOS_ERR, "IPMI: %s response truncated\n", __func__);
return 1;
}
return 0;
}
static void ipmi_kcs_init(struct device *dev)
{
struct ipmi_devid_rsp rsp;
uint32_t man_id = 0, prod_id = 0;
if (!dev->enabled)
return;
/* Get IPMI version for ACPI and SMBIOS */
if (!ipmi_get_device_id(dev, &rsp)) {
ipmi_revision_minor = IPMI_IPMI_VERSION_MINOR(rsp.ipmi_version);
ipmi_revision_major = IPMI_IPMI_VERSION_MAJOR(rsp.ipmi_version);
memcpy(&man_id, rsp.manufacturer_id,
sizeof(rsp.manufacturer_id));
memcpy(&prod_id, rsp.product_id, sizeof(rsp.product_id));
printk(BIOS_INFO, "IPMI: Found man_id 0x%06x, prod_id 0x%04x\n",
man_id, prod_id);
printk(BIOS_INFO, "IPMI: Version %01x.%01x\n",
ipmi_revision_major, ipmi_revision_minor);
} else {
/* Don't write tables if communication failed */
dev->enabled = 0;
}
}
#if CONFIG(HAVE_ACPI_TABLES)
static uint32_t uid_cnt = 0;
static unsigned long
ipmi_write_acpi_tables(struct device *dev, unsigned long current,
struct acpi_rsdp *rsdp)
{
struct drivers_ipmi_config *conf = NULL;
struct acpi_spmi *spmi;
s8 gpe_interrupt = -1;
u32 apic_interrupt = 0;
acpi_addr_t addr = {
.space_id = ACPI_ADDRESS_SPACE_IO,
.access_size = ACPI_ACCESS_SIZE_BYTE_ACCESS,
.addrl = dev->path.pnp.port,
};
current = ALIGN_UP(current, 8);
printk(BIOS_DEBUG, "ACPI: * SPMI at %lx\n", current);
spmi = (struct acpi_spmi *)current;
if (dev->chip_info)
conf = dev->chip_info;
if (conf) {
if (conf->have_gpe)
gpe_interrupt = conf->gpe_interrupt;
if (conf->have_apic)
apic_interrupt = conf->apic_interrupt;
}
/* Use command to get UID from ipmi_ssdt */
acpi_create_ipmi(dev, spmi, (ipmi_revision_major << 8) |
(ipmi_revision_minor << 4), &addr,
IPMI_INTERFACE_KCS, gpe_interrupt, apic_interrupt,
dev->command);
acpi_add_table(rsdp, spmi);
current += spmi->header.length;
return current;
}
static void ipmi_ssdt(struct device *dev)
{
const char *scope = acpi_device_scope(dev);
struct drivers_ipmi_config *conf = NULL;
if (!scope) {
printk(BIOS_ERR, "IPMI: Missing ACPI scope for %s\n",
dev_path(dev));
return;
}
if (dev->chip_info)
conf = dev->chip_info;
/* Use command to pass UID to ipmi_write_acpi_tables */
dev->command = uid_cnt++;
/* write SPMI device */
acpigen_write_scope(scope);
acpigen_write_device("SPMI");
acpigen_write_name_string("_HID", "IPI0001");
acpigen_write_name_string("_STR", "IPMI_KCS");
acpigen_write_name_byte("_UID", dev->command);
acpigen_write_STA(0xf);
acpigen_write_name("_CRS");
acpigen_write_resourcetemplate_header();
acpigen_write_io16(dev->path.pnp.port, dev->path.pnp.port, 1, 2, 1);
if (conf) {
// FIXME: is that correct?
if (conf->have_apic)
acpigen_write_irq(1 << conf->apic_interrupt);
}
acpigen_write_resourcetemplate_footer();
acpigen_write_method("_IFT", 0);
acpigen_write_return_byte(1); // KCS
acpigen_pop_len();
acpigen_write_method("_SRV", 0);
acpigen_write_return_integer((ipmi_revision_major << 8) |
(ipmi_revision_minor << 4));
acpigen_pop_len();
acpigen_pop_len(); /* pop device */
acpigen_pop_len(); /* pop scope */
}
#endif
#if CONFIG(GENERATE_SMBIOS_TABLES)
static int ipmi_smbios_data(struct device *dev, int *handle,
unsigned long *current)
{
struct drivers_ipmi_config *conf = NULL;
u8 nv_storage = 0xff;
u8 i2c_address = 0;
int len = 0;
if (dev->chip_info)
conf = dev->chip_info;
if (conf) {
if (conf->have_nv_storage)
nv_storage = conf->nv_storage_device_address;
i2c_address = conf->bmc_i2c_address;
}
// add IPMI Device Information
len += smbios_write_type38(
current, handle,
SMBIOS_BMC_INTERFACE_KCS,
ipmi_revision_minor | (ipmi_revision_major << 4),
i2c_address, // I2C address
nv_storage, // NV storage
dev->path.pnp.port | 1, // IO interface
0,
0); // no IRQ
return len;
}
#endif
static void ipmi_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 ipmi_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;
}
static struct device_operations ops = {
.read_resources = ipmi_read_resources,
.set_resources = ipmi_set_resources,
.enable_resources = DEVICE_NOOP,
.init = ipmi_kcs_init,
#if CONFIG(HAVE_ACPI_TABLES)
.write_acpi_tables = ipmi_write_acpi_tables,
.acpi_fill_ssdt_generator = ipmi_ssdt,
#endif
#if CONFIG(GENERATE_SMBIOS_TABLES)
.get_smbios_data = ipmi_smbios_data,
#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 & 1)
printk(BIOS_ERR, "%s: Base address needs to be aligned to 2\n",
dev_path(dev));
else
dev->ops = &ops;
}
struct chip_operations drivers_ipmi_ops = {
CHIP_NAME("IPMI KCS")
.enable_dev = enable_dev,
};