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:
parent
c469712166
commit
ffbc3b5f5f
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
ramstage-$(CONFIG_IPMI_KCS) += ipmi_kcs.c
|
||||
ramstage-$(CONFIG_IPMI_KCS) += ipmi_kcs_ops.c
|
||||
|
|
|
@ -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_ */
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
Loading…
Reference in New Issue