diff mbox series

[RFC,v2,1/3] hw/peci: add initial support for PECI

Message ID 20220913182149.1468366-2-titusr@google.com (mailing list archive)
State New, archived
Headers show
Series Initial PECI bus support | expand

Commit Message

Titus Rwantare Sept. 13, 2022, 6:21 p.m. UTC
PECI - Platform Environment Control Interface

This commit adds support for reading basic sensor values from a client
on the PECI bus.
BMCs can use the PECI wire to get thermal information out of an Intel
cpu. Additionally, on hardware, various MSRs are exposed over the
PECI bus. Part of PCI config space is exposed due to Intel posting
various platform configuration in PCI config space.

Commands implemented:
- Ping
- GetDIB
- GetTemp
- GetPkgConfig (partial)

Commands not implemented:
- RdIAMSR
- RdPCIConfig
- RdPCIConfigLocal

Signed-off-by: Titus Rwantare <titusr@google.com>
Reviewed-by: Hao Wu <wuhaotsh@google.com>
Reviewed-by: Patrick Venture <venture@google.com>
---
 MAINTAINERS            |   7 ++
 hw/Kconfig             |   1 +
 hw/meson.build         |   1 +
 hw/peci/Kconfig        |   2 +
 hw/peci/meson.build    |   1 +
 hw/peci/peci-client.c  | 230 +++++++++++++++++++++++++++++++++++++++++
 hw/peci/peci-core.c    | 182 ++++++++++++++++++++++++++++++++
 hw/peci/trace-events   |   5 +
 hw/peci/trace.h        |   1 +
 include/hw/peci/peci.h | 194 ++++++++++++++++++++++++++++++++++
 meson.build            |   1 +
 11 files changed, 625 insertions(+)
 create mode 100644 hw/peci/Kconfig
 create mode 100644 hw/peci/meson.build
 create mode 100644 hw/peci/peci-client.c
 create mode 100644 hw/peci/peci-core.c
 create mode 100644 hw/peci/trace-events
 create mode 100644 hw/peci/trace.h
 create mode 100644 include/hw/peci/peci.h

Comments

Peter Delevoryas Sept. 13, 2022, 6:29 p.m. UTC | #1
On Tue, Sep 13, 2022 at 06:21:47PM +0000, Titus Rwantare wrote:
> PECI - Platform Environment Control Interface
> 
> This commit adds support for reading basic sensor values from a client
> on the PECI bus.
> BMCs can use the PECI wire to get thermal information out of an Intel
> cpu. Additionally, on hardware, various MSRs are exposed over the
> PECI bus. Part of PCI config space is exposed due to Intel posting
> various platform configuration in PCI config space.
> 
> Commands implemented:
> - Ping
> - GetDIB
> - GetTemp
> - GetPkgConfig (partial)
> 
> Commands not implemented:
> - RdIAMSR
> - RdPCIConfig
> - RdPCIConfigLocal
> 
> Signed-off-by: Titus Rwantare <titusr@google.com>
> Reviewed-by: Hao Wu <wuhaotsh@google.com>
> Reviewed-by: Patrick Venture <venture@google.com>

Reviewed-by: Peter Delevoryas <peter@pjd.dev>

> ---
>  MAINTAINERS            |   7 ++
>  hw/Kconfig             |   1 +
>  hw/meson.build         |   1 +
>  hw/peci/Kconfig        |   2 +
>  hw/peci/meson.build    |   1 +
>  hw/peci/peci-client.c  | 230 +++++++++++++++++++++++++++++++++++++++++
>  hw/peci/peci-core.c    | 182 ++++++++++++++++++++++++++++++++
>  hw/peci/trace-events   |   5 +
>  hw/peci/trace.h        |   1 +
>  include/hw/peci/peci.h | 194 ++++++++++++++++++++++++++++++++++
>  meson.build            |   1 +
>  11 files changed, 625 insertions(+)
>  create mode 100644 hw/peci/Kconfig
>  create mode 100644 hw/peci/meson.build
>  create mode 100644 hw/peci/peci-client.c
>  create mode 100644 hw/peci/peci-core.c
>  create mode 100644 hw/peci/trace-events
>  create mode 100644 hw/peci/trace.h
>  create mode 100644 include/hw/peci/peci.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5ce4227ff6..14ab29679d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -3212,6 +3212,13 @@ F: tests/qtest/adm1272-test.c
>  F: tests/qtest/max34451-test.c
>  F: tests/qtest/isl_pmbus_vr-test.c
>  
> +PECI
> +M: Titus Rwantare <titusr@google.com>
> +S: Maintained
> +F: hw/peci/peci-core.c
> +F: hw/peci/peci-client.c
> +F: include/hw/peci/peci.h
> +
>  Firmware schema specifications
>  M: Philippe Mathieu-Daudé <f4bug@amsat.org>
>  R: Daniel P. Berrange <berrange@redhat.com>
> diff --git a/hw/Kconfig b/hw/Kconfig
> index 38233bbb0f..300ab48127 100644
> --- a/hw/Kconfig
> +++ b/hw/Kconfig
> @@ -24,6 +24,7 @@ source net/Kconfig
>  source nubus/Kconfig
>  source nvme/Kconfig
>  source nvram/Kconfig
> +source peci/Kconfig
>  source pci-bridge/Kconfig
>  source pci-host/Kconfig
>  source pcmcia/Kconfig
> diff --git a/hw/meson.build b/hw/meson.build
> index c7ac7d3d75..340cc88a52 100644
> --- a/hw/meson.build
> +++ b/hw/meson.build
> @@ -28,6 +28,7 @@ subdir('pci')
>  subdir('pci-bridge')
>  subdir('pci-host')
>  subdir('pcmcia')
> +subdir('peci')
>  subdir('rdma')
>  subdir('rtc')
>  subdir('scsi')
> diff --git a/hw/peci/Kconfig b/hw/peci/Kconfig
> new file mode 100644
> index 0000000000..fe4f665d21
> --- /dev/null
> +++ b/hw/peci/Kconfig
> @@ -0,0 +1,2 @@
> +config PECI
> +    bool
> diff --git a/hw/peci/meson.build b/hw/peci/meson.build
> new file mode 100644
> index 0000000000..01cfa95abe
> --- /dev/null
> +++ b/hw/peci/meson.build
> @@ -0,0 +1 @@
> +softmmu_ss.add(when: 'CONFIG_PECI', if_true: files('peci-core.c', 'peci-client.c'))
> diff --git a/hw/peci/peci-client.c b/hw/peci/peci-client.c
> new file mode 100644
> index 0000000000..2aa797b5f6
> --- /dev/null
> +++ b/hw/peci/peci-client.c
> @@ -0,0 +1,230 @@
> +/*
> + * PECI Client device
> + * Copyright 2021 Google LLC
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/peci/peci.h"
> +#include "hw/qdev-core.h"
> +#include "hw/qdev-properties.h"
> +#include "migration/vmstate.h"
> +#include "qapi/error.h"
> +#include "qapi/visitor.h"
> +#include "qemu/module.h"
> +#include "qemu/log.h"
> +#include "qom/object.h"
> +
> +/*
> + * A PECI client represents an Intel socket and the peripherals attached to it
> + * that are accessible over the PECI bus.
> + */
> +
> +#define PECI_CLIENT_DEFAULT_TEMP 30
> +
> +static void peci_client_update_temps(PECIClientDevice *client)
> +{
> +    uint8_t temp_cpu = 0;
> +    for (size_t i = 0; i < client->cpu_cores; i++) {
> +        if (temp_cpu < client->core_temp[i]) {
> +            temp_cpu = client->core_temp[i];
> +        }
> +    }
> +    client->core_temp_max = -1 * (client->tjmax - temp_cpu);
> +
> +    uint8_t temp_dimm = 0;
> +    for (size_t i = 0; i < client->dimms; i++) {
> +        if (temp_dimm < client->dimm_temp[i]) {
> +            temp_dimm = client->dimm_temp[i];
> +        }
> +    }
> +    client->dimm_temp_max = temp_dimm;
> +}
> +
> +PECIClientDevice *peci_get_client(PECIBus *bus, uint8_t addr)
> +{
> +    PECIClientDevice *client;
> +    BusChild *var;
> +
> +    QTAILQ_FOREACH(var, &bus->qbus.children, sibling) {
> +        DeviceState *dev = var->child;
> +        client = PECI_CLIENT(dev);
> +
> +        if (client->address == addr) {
> +            return client;
> +        }
> +    }
> +    return 0;
> +}
> +
> +
> +PECIClientDevice *peci_add_client(PECIBus *bus,
> +                                  uint8_t address,
> +                                  PECIClientProperties *props)
> +{
> +    DeviceState *dev = qdev_new("peci-client");
> +    PECIClientDevice *client;
> +
> +    /* Only 8 addresses supported as of rev 4.1, 0x30 to 0x37 */
> +    if (address < PECI_BASE_ADDR || address > PECI_BASE_ADDR + 7) {
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: failed to add client at 0x%02x",
> +                      __func__, address);
> +        return 0;
> +    }
> +
> +    client = PECI_CLIENT(dev);
> +    client->address = address;
> +
> +    /* these fields are optional, get set to max if unset or invalid */
> +    if (!props->cpu_cores || props->cpu_cores > PECI_NUM_CPUS_MAX) {
> +        props->cpu_cores = PECI_NUM_CPUS_MAX;
> +    }
> +    if (!props->dimms || props->dimms > PECI_NUM_DIMMS_MAX) {
> +        props->dimms = PECI_NUM_DIMMS_MAX;
> +    }
> +    if (!props->cpu_family) {
> +        props->cpu_family = FAM6_ICELAKE_X;
> +    }
> +    if (!props->dimms_per_channel ||
> +        props->dimms_per_channel > PECI_DIMMS_PER_CHANNEL_MAX) {
> +        props->dimms_per_channel = 2;
> +    }
> +
> +    client->cpu_id = props->cpu_family;
> +    client->cpu_cores = props->cpu_cores;
> +    client->dimms = props->dimms;
> +    client->dimms_per_channel = props->dimms_per_channel;
> +
> +    /* TODO: find real revisions and TJ max for supported families */
> +    client->tjmax = 90;
> +    client->tcontrol = -5;
> +
> +    switch (props->cpu_family) {
> +    case FAM6_HASWELL_X:
> +        client->revision = 0x31;
> +        break;
> +
> +    case FAM6_BROADWELL_X:
> +        client->revision = 0x32;
> +        break;
> +
> +    case FAM6_SKYLAKE_X:
> +    case FAM6_SKYLAKE_XD:
> +        client->revision = 0x36;
> +        break;
> +
> +    case FAM6_ICELAKE_X:
> +    case FAM6_SAPPHIRE_RAPIDS_X:
> +        client->revision = 0x40;
> +        client->ucode = 0x8c0004a0;
> +        break;
> +
> +    default:
> +        qemu_log_mask(LOG_UNIMP, "%s: unsupported cpu: 0x%x\n",
> +                      __func__, props->cpu_family);
> +        client->revision = 0x31;
> +        break;
> +    }
> +
> +    qdev_realize_and_unref(&client->qdev, &bus->qbus, &error_abort);
> +    bus->num_clients += 1;
> +    return client;
> +}
> +
> +static void peci_client_get(Object *obj, Visitor *v, const char *name,
> +                                   void *opaque, Error **errp)
> +{
> +    /* use millidegrees convention */
> +    uint32_t value = *(uint8_t *)opaque * 1000;
> +    visit_type_uint32(v, name, &value, errp);
> +}
> +
> +static void peci_client_set(Object *obj, Visitor *v, const char *name,
> +                                   void *opaque, Error **errp)
> +{
> +    PECIClientDevice *client = PECI_CLIENT(obj);
> +    uint8_t *internal = opaque;
> +    uint32_t value;
> +
> +    if (!visit_type_uint32(v, name, &value, errp)) {
> +        return;
> +    }
> +
> +    g_assert(value <= 255000);
> +
> +    *internal = value / 1000;
> +    peci_client_update_temps(client);
> +}
> +
> +static void peci_client_reset(Object *obj, ResetType type)
> +{
> +    PECIClientDevice *client = PECI_CLIENT(obj);
> +    client->core_temp_max = 0;
> +    client->dimm_temp_max = 0;
> +}
> +
> +static const VMStateDescription vmstate_peci_client = {
> +    .name = TYPE_PECI_CLIENT,
> +    .version_id = 0,
> +    .minimum_version_id = 0,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT8(address, PECIClientDevice),
> +        VMSTATE_UINT8(device_info, PECIClientDevice),
> +        VMSTATE_UINT8(revision, PECIClientDevice),
> +        VMSTATE_UINT32(cpu_id, PECIClientDevice),
> +        VMSTATE_UINT8(cpu_cores, PECIClientDevice),
> +        VMSTATE_UINT8(tjmax, PECIClientDevice),
> +        VMSTATE_INT8(tcontrol, PECIClientDevice),
> +        VMSTATE_UINT8_ARRAY(core_temp, PECIClientDevice, PECI_NUM_CPUS_MAX),
> +        VMSTATE_UINT8(dimms, PECIClientDevice),
> +        VMSTATE_UINT8(dimms_per_channel, PECIClientDevice),
> +        VMSTATE_UINT8_ARRAY(dimm_temp, PECIClientDevice , PECI_NUM_DIMMS_MAX),
> +        VMSTATE_END_OF_LIST()
> +    },
> +};
> +
> +static void peci_client_realize(DeviceState *dev, Error **errp)
> +{
> +    PECIClientDevice *client = PECI_CLIENT(dev);
> +    for (size_t i = 0; i < client->cpu_cores; i++) {
> +        client->core_temp[i] = PECI_CLIENT_DEFAULT_TEMP;
> +        object_property_add(OBJECT(client), "cpu_temp[*]", "uint8",
> +                            peci_client_get,
> +                            peci_client_set, NULL, &client->core_temp[i]);
> +    }
> +
> +    for (size_t i = 0; i < client->dimms; i++) {
> +        client->dimm_temp[i] = PECI_CLIENT_DEFAULT_TEMP;
> +        object_property_add(OBJECT(client), "dimm_temp[*]", "uint8",
> +                            peci_client_get,
> +                            peci_client_set, NULL, &client->dimm_temp[i]);
> +    }
> +}
> +
> +static void peci_client_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    ResettableClass *rc = RESETTABLE_CLASS(klass);
> +
> +    dc->desc = "PECI Client Device";
> +    dc->bus_type = TYPE_PECI_BUS;
> +    dc->realize = peci_client_realize;
> +    dc->vmsd = &vmstate_peci_client;
> +    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
> +    rc->phases.enter = peci_client_reset;
> +}
> +
> +static const TypeInfo peci_client_info = {
> +    .name = TYPE_PECI_CLIENT,
> +    .parent = TYPE_DEVICE,
> +    .instance_size = sizeof(PECIClientDevice),
> +    .class_init = peci_client_class_init,
> +};
> +
> +static void peci_client_register_types(void)
> +{
> +    type_register_static(&peci_client_info);
> +}
> +
> +type_init(peci_client_register_types)
> diff --git a/hw/peci/peci-core.c b/hw/peci/peci-core.c
> new file mode 100644
> index 0000000000..8210bfa198
> --- /dev/null
> +++ b/hw/peci/peci-core.c
> @@ -0,0 +1,182 @@
> +/*
> + * PECI - Platform Environment Control Interface
> + * Copyright 2021 Google LLC
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +/*
> + * This PECI implementation aims to simulate a host with peci as viewed by a
> + * BMC. This is developed with OpenBMC firmware running in QEMU.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/peci/peci.h"
> +#include "hw/qdev-properties.h"
> +#include "migration/vmstate.h"
> +#include "qemu/module.h"
> +#include "qemu/log.h"
> +#include "trace.h"
> +
> +#define PECI_FCS_OK         0
> +#define PECI_FCS_ERR        1
> +
> +static void peci_rd_pkg_cfg(PECIClientDevice *client, PECICmd *pcmd)
> +{
> +    PECIPkgCfg *resp = (PECIPkgCfg *)pcmd->tx;
> +    uint8_t index = pcmd->rx[1];
> +    uint16_t param = pcmd->rx[3] | pcmd->rx[2];
> +
> +    switch (index) {
> +    case PECI_MBX_CPU_ID: /* CPU Family ID*/
> +        trace_peci_rd_pkg_cfg("CPU ID");
> +        switch (param) {
> +        case PECI_PKG_ID_CPU_ID:
> +            resp->data = client->cpu_id;
> +            break;
> +
> +        case PECI_PKG_ID_MICROCODE_REV:
> +            resp->data = client->ucode;
> +            break;
> +
> +        default:
> +            qemu_log_mask(LOG_UNIMP, "%s: CPU ID param %u\n", __func__, param);
> +            break;
> +        }
> +        break;
> +
> +    case PECI_MBX_DTS_MARGIN:
> +        trace_peci_rd_pkg_cfg("DTS MARGIN");
> +    /*
> +     * Processors return a value of DTS reading in 10.6 format
> +     * (10 bits signed decimal, 6 bits fractional).
> +     */
> +        resp->data = (-1 * client->core_temp_max) << 6;
> +        break;
> +
> +    case PECI_MBX_DDR_DIMM_TEMP:
> +        trace_peci_rd_pkg_cfg("DDR DIMM TEMP");
> +        if (param > PECI_NUM_CHANNELS_MAX) {
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                          "%s: DDR_DIMM_TEMP unsupported channel index %d",
> +                          __func__, param);
> +            param = PECI_NUM_CHANNELS_MAX;
> +        }
> +        uint8_t channel_index = param * client->dimms_per_channel;
> +        memcpy(&resp->data,
> +               &client->dimm_temp[channel_index],
> +               sizeof(client->dimm_temp[0]) * client->dimms_per_channel);
> +        break;
> +
> +    case PECI_MBX_TEMP_TARGET:
> +        trace_peci_rd_pkg_cfg("TEMP TARGET");
> +        PECITempTarget target = {
> +            .tcontrol = client->tcontrol,
> +            .tjmax = client->tjmax,
> +        };
> +        memcpy(&resp->data, &target, sizeof(target));
> +        break;
> +
> +    case PECI_MBX_PPIN:
> +        trace_peci_rd_pkg_cfg("PPIN");
> +        resp->data = 0xdeadbeef;
> +        break;
> +
> +    default:
> +        qemu_log_mask(LOG_UNIMP, "%s: unimplemented PkgCfg Index: %u\n",
> +                      __func__, index);
> +        resp->cc = PECI_DEV_CC_INVALID_REQ;
> +        return;
> +    }
> +
> +    resp->cc = PECI_DEV_CC_SUCCESS;
> +}
> +
> +int peci_handle_cmd(PECIBus *bus, PECICmd *pcmd)
> +{
> +    PECIClientDevice *client = peci_get_client(bus, pcmd->addr);
> +
> +    if (!client) { /* ignore commands if clients aren't found */
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "%s: no peci client at address: 0x%02x\n",
> +                      __func__, pcmd->addr);
> +        return PECI_FCS_ERR;
> +    }
> +
> +    /* clear output buffer before handling command */
> +    for (size_t i = 0; i < pcmd->rd_length; i++) {
> +        pcmd->tx[i] = 0;
> +    }
> +
> +    switch (pcmd->cmd) {
> +    case PECI_CMD_PING:
> +        trace_peci_handle_cmd("PING!", pcmd->addr);
> +        break;
> +
> +    case PECI_CMD_GET_DIB: /* Device Info Byte */
> +        trace_peci_handle_cmd("GetDIB", pcmd->addr);
> +        PECIDIB dib = {
> +            .device_info = client->device_info,
> +            .revision = client->revision,
> +        };
> +        memcpy(pcmd->tx, &dib, sizeof(PECIDIB));
> +        break;
> +
> +    case PECI_CMD_GET_TEMP: /* maximum die temp in socket */
> +        trace_peci_handle_cmd("GetTemp", pcmd->addr);
> +        /*
> +         * The data is returned as a negative value representing the number of
> +         * degrees centigrade below the maximum processor junction temperature
> +         */
> +        memcpy(pcmd->tx, &client->core_temp_max, sizeof(client->core_temp_max));
> +        break;
> +
> +    case PECI_CMD_RD_PKG_CFG:
> +        trace_peci_handle_cmd("RdPkgConfig", pcmd->addr);
> +        peci_rd_pkg_cfg(client, pcmd);
> +        break;
> +
> +    case PECI_CMD_RD_IA_MSR:
> +        trace_peci_handle_cmd("RdIAMSR", pcmd->addr);
> +        qemu_log_mask(LOG_UNIMP, "%s: unimplemented RD_IA_MSR\n", __func__);
> +        break;
> +
> +    case PECI_CMD_RD_PCI_CFG:
> +        trace_peci_handle_cmd("RdPCIConfig", pcmd->addr);
> +        qemu_log_mask(LOG_UNIMP, "%s: unimplemented PCI_CFG\n", __func__);
> +        break;
> +
> +    case PECI_CMD_RD_PCI_CFG_LOCAL:
> +        trace_peci_handle_cmd("RdPCIConfigLocal", pcmd->addr);
> +        qemu_log_mask(LOG_UNIMP, "%s: unimplemented PCI_CFG_LOCAL\n", __func__);
> +        break;
> +
> +    case PECI_CMD_RD_END_PT_CFG:
> +        qemu_log_mask(LOG_UNIMP, "%s: unimplemented CMD_RD_END_PT_CFG\n",
> +                      __func__);
> +        break;
> +
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: unknown cmd: %x\n",
> +                      __func__, pcmd->cmd);
> +        break;
> +    }
> +
> +    return PECI_FCS_OK;
> +}
> +
> +PECIBus *peci_bus_create(DeviceState *parent)
> +{
> +    return PECI_BUS(qbus_new(TYPE_PECI_BUS, parent, "peci-bus"));
> +};
> +
> +static const TypeInfo peci_types[] = {
> +    {
> +        .name = TYPE_PECI_BUS,
> +        .parent = TYPE_BUS,
> +        .instance_size = sizeof(PECIBus),
> +    },
> +};
> +
> +DEFINE_TYPES(peci_types);
> diff --git a/hw/peci/trace-events b/hw/peci/trace-events
> new file mode 100644
> index 0000000000..f90c998dd9
> --- /dev/null
> +++ b/hw/peci/trace-events
> @@ -0,0 +1,5 @@
> +# See docs/devel/tracing.rst for syntax documentation.
> +
> +# peci-core.c
> +peci_handle_cmd(const char* s, uint8_t addr) "%s @ 0x%02" PRIx8
> +peci_rd_pkg_cfg(const char* s) "%s"
> diff --git a/hw/peci/trace.h b/hw/peci/trace.h
> new file mode 100644
> index 0000000000..4283e3bfc5
> --- /dev/null
> +++ b/hw/peci/trace.h
> @@ -0,0 +1 @@
> +#include "trace/trace-hw_peci.h"
> diff --git a/include/hw/peci/peci.h b/include/hw/peci/peci.h
> new file mode 100644
> index 0000000000..1a0abe65cd
> --- /dev/null
> +++ b/include/hw/peci/peci.h
> @@ -0,0 +1,194 @@
> +/*
> + * PECI - Platform Environment Control Interface
> + *
> + * Copyright 2021 Google LLC
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef PECI_H
> +#define PECI_H
> +
> +#include "hw/qdev-core.h"
> +#include "qom/object.h"
> +
> +#define PECI_CMD_PING               0x0
> +#define PECI_CMD_GET_DIB            0xF7
> +#define PECI_CMD_GET_TEMP           0x01
> +#define PECI_CMD_RD_PKG_CFG         0xA1
> +#define PECI_CMD_WR_PKG_CFG         0xA5
> +#define   PECI_MBX_CPU_ID            0  /* Package Identifier Read */
> +#define     PECI_PKG_ID_CPU_ID              0x0000  /* CPUID Info */
> +#define     PECI_PKG_ID_PLATFORM_ID         0x0001  /* Platform ID */
> +#define     PECI_PKG_ID_UNCORE_ID           0x0002  /* Uncore Device ID */
> +#define     PECI_PKG_ID_MAX_THREAD_ID       0x0003  /* Max Thread ID */
> +#define     PECI_PKG_ID_MICROCODE_REV       0x0004  /* CPU Microcode Revision */
> +#define     PECI_PKG_ID_MACHINE_CHECK_STATUS 0x0005  /* Machine Check Status */
> +#define   PECI_MBX_VR_DEBUG          1  /* VR Debug */
> +#define   PECI_MBX_PKG_TEMP_READ     2  /* Package Temperature Read */
> +#define   PECI_MBX_ENERGY_COUNTER    3  /* Energy counter */
> +#define   PECI_MBX_ENERGY_STATUS     4  /* DDR Energy Status */
> +#define   PECI_MBX_WAKE_MODE_BIT     5  /* "Wake on PECI" Mode bit */
> +#define   PECI_MBX_EPI               6  /* Efficient Performance Indication */
> +#define   PECI_MBX_PKG_RAPL_PERF     8  /* Pkg RAPL Performance Status Read */
> +#define   PECI_MBX_PER_CORE_DTS_TEMP 9  /* Per Core DTS Temperature Read */
> +#define   PECI_MBX_DTS_MARGIN        10 /* DTS thermal margin */
> +#define   PECI_MBX_SKT_PWR_THRTL_DUR 11 /* Socket Power Throttled Duration */
> +#define   PECI_MBX_CFG_TDP_CONTROL   12 /* TDP Config Control */
> +#define   PECI_MBX_CFG_TDP_LEVELS    13 /* TDP Config Levels */
> +#define   PECI_MBX_DDR_DIMM_TEMP     14 /* DDR DIMM Temperature */
> +#define   PECI_MBX_CFG_ICCMAX        15 /* Configurable ICCMAX */
> +#define   PECI_MBX_TEMP_TARGET       16 /* Temperature Target Read */
> +#define   PECI_MBX_CURR_CFG_LIMIT    17 /* Current Config Limit */
> +#define   PECI_MBX_PPIN              19 /* Processor Inventory Number */
> +#define   PECI_MBX_DIMM_TEMP_READ    20 /* Package Thermal Status Read */
> +#define   PECI_MBX_DRAM_IMC_TMP_READ 22 /* DRAM IMC Temperature Read */
> +#define   PECI_MBX_DDR_CH_THERM_STAT 23 /* DDR Channel Thermal Status */
> +#define   PECI_MBX_PKG_POWER_LIMIT1  26 /* Package Power Limit1 */
> +#define   PECI_MBX_PKG_POWER_LIMIT2  27 /* Package Power Limit2 */
> +#define   PECI_MBX_TDP               28 /* Thermal design power minimum */
> +#define   PECI_MBX_TDP_HIGH          29 /* Thermal design power maximum */
> +#define   PECI_MBX_TDP_UNITS         30 /* Units for power/energy registers */
> +#define   PECI_MBX_RUN_TIME          31 /* Accumulated Run Time */
> +#define   PECI_MBX_CONSTRAINED_TIME  32 /* Thermally Constrained Time Read */
> +#define   PECI_MBX_TURBO_RATIO       33 /* Turbo Activation Ratio */
> +#define   PECI_MBX_DDR_RAPL_PL1      34 /* DDR RAPL PL1 */
> +#define   PECI_MBX_DDR_PWR_INFO_HIGH 35 /* DRAM Power Info Read (high) */
> +#define   PECI_MBX_DDR_PWR_INFO_LOW  36 /* DRAM Power Info Read (low) */
> +#define   PECI_MBX_DDR_RAPL_PL2      37 /* DDR RAPL PL2 */
> +#define   PECI_MBX_DDR_RAPL_STATUS   38 /* DDR RAPL Performance Status */
> +#define   PECI_MBX_DDR_HOT_ABSOLUTE  43 /* DDR Hottest Dimm Absolute Temp */
> +#define   PECI_MBX_DDR_HOT_RELATIVE  44 /* DDR Hottest Dimm Relative Temp */
> +#define   PECI_MBX_DDR_THROTTLE_TIME 45 /* DDR Throttle Time */
> +#define   PECI_MBX_DDR_THERM_STATUS  46 /* DDR Thermal Status */
> +#define   PECI_MBX_TIME_AVG_TEMP     47 /* Package time-averaged temperature */
> +#define   PECI_MBX_TURBO_RATIO_LIMIT 49 /* Turbo Ratio Limit Read */
> +#define   PECI_MBX_HWP_AUTO_OOB      53 /* HWP Autonomous Out-of-band */
> +#define   PECI_MBX_DDR_WARM_BUDGET   55 /* DDR Warm Power Budget */
> +#define   PECI_MBX_DDR_HOT_BUDGET    56 /* DDR Hot Power Budget */
> +#define   PECI_MBX_PKG_PSYS_PWR_LIM3 57 /* Package/Psys Power Limit3 */
> +#define   PECI_MBX_PKG_PSYS_PWR_LIM1 58 /* Package/Psys Power Limit1 */
> +#define   PECI_MBX_PKG_PSYS_PWR_LIM2 59 /* Package/Psys Power Limit2 */
> +#define   PECI_MBX_PKG_PSYS_PWR_LIM4 60 /* Package/Psys Power Limit4 */
> +#define   PECI_MBX_PERF_LIMIT_REASON 65 /* Performance Limit Reasons */
> +#define PECI_CMD_RD_IA_MSR          0xB1
> +#define PECI_CMD_WR_IA_MSR          0xB5
> +#define PECI_CMD_RD_IA_MSREX        0xD1
> +#define PECI_CMD_RD_PCI_CFG         0x61
> +#define PECI_CMD_WR_PCI_CFG         0x65
> +#define PECI_CMD_RD_PCI_CFG_LOCAL   0xE1
> +#define PECI_CMD_WR_PCI_CFG_LOCAL   0xE5
> +#define PECI_CMD_RD_END_PT_CFG      0xC1
> +#define PECI_CMD_WR_END_PT_CFG      0xC5
> +#define PECI_CMD_CRASHDUMP_GET_FRAME            0x71
> +
> +/* Device Specific Completion Code (CC) Definition */
> +#define PECI_DEV_CC_SUCCESS                            0x40
> +#define PECI_DEV_CC_NEED_RETRY                         0x80
> +#define PECI_DEV_CC_OUT_OF_RESOURCE                    0x81
> +#define PECI_DEV_CC_UNAVAIL_RESOURCE                   0x82
> +#define PECI_DEV_CC_INVALID_REQ                        0x90
> +#define PECI_DEV_CC_MCA_ERROR                          0x91
> +#define PECI_DEV_CC_CATASTROPHIC_MCA_ERROR             0x93
> +#define PECI_DEV_CC_FATAL_MCA_DETECTED                 0x94
> +#define PECI_DEV_CC_PARITY_ERROR_ON_GPSB_OR_PMSB       0x98
> +#define PECI_DEV_CC_PARITY_ERROR_ON_GPSB_OR_PMSB_IERR  0x9B
> +#define PECI_DEV_CC_PARITY_ERROR_ON_GPSB_OR_PMSB_MCA   0x9C
> +
> +
> +typedef struct PECIDIB {  /* DIB - Device Info Byte(s) */
> +    uint8_t device_info; /* bit 2 - num of domains */
> +    uint8_t revision;
> +} PECIDIB;
> +
> +typedef struct  __attribute__ ((__packed__)) {
> +    uint8_t cc; /* completion code */
> +    uint32_t data;
> +} PECIPkgCfg;
> +
> +typedef struct PECITempTarget {
> +    uint8_t reserved;
> +    int8_t tcontrol;
> +    uint8_t tjmax;
> +} PECITempTarget;
> +
> +#define PECI_BASE_ADDR              0x30
> +#define PECI_BUFFER_SIZE            0x100
> +#define PECI_NUM_CPUS_MAX           56
> +#define PECI_DIMMS_PER_CHANNEL_MAX  3
> +#define PECI_NUM_CHANNELS_MAX       8
> +#define PECI_NUM_DIMMS_MAX  (PECI_NUM_CHANNELS_MAX * PECI_DIMMS_PER_CHANNEL_MAX)
> +
> +typedef struct PECIClientDevice {
> +    DeviceState qdev;
> +    uint8_t address;
> +    uint8_t device_info;
> +    uint8_t revision;
> +
> +    /* CPU */
> +    uint32_t cpu_id;
> +    uint8_t cpu_cores;
> +    uint32_t ucode;
> +    uint8_t tjmax;
> +    int8_t tcontrol;
> +    int8_t core_temp_max; /* absolute temp - tjmax */
> +    uint8_t core_temp[PECI_NUM_CPUS_MAX];
> +
> +    /* DIMMS */
> +    uint8_t dimms;
> +    uint8_t dimms_per_channel;
> +    uint8_t dimm_temp_max;
> +    uint8_t dimm_temp[PECI_NUM_DIMMS_MAX];
> +
> +} PECIClientDevice;
> +
> +#define TYPE_PECI_CLIENT "peci-client"
> +OBJECT_DECLARE_SIMPLE_TYPE(PECIClientDevice, PECI_CLIENT)
> +
> +typedef struct PECIBus {
> +    BusState qbus;
> +    uint8_t num_clients;
> +} PECIBus;
> +
> +#define TYPE_PECI_BUS "peci-bus"
> +OBJECT_DECLARE_SIMPLE_TYPE(PECIBus, PECI_BUS)
> +
> +/* Creates a basic qemu bus onto which PECI clients will be attached */
> +PECIBus *peci_bus_create(DeviceState *parent);
> +
> +enum {
> +    FAM6_HASWELL_X = 0x306F0,
> +    FAM6_BROADWELL_X = 0x406F0,
> +    FAM6_SKYLAKE_X = 0x50650,
> +    FAM6_SKYLAKE_XD = 0x50660,
> +    FAM6_ICELAKE_X = 0x606A0,
> +    FAM6_SAPPHIRE_RAPIDS_X = 0x806F3,
> +};
> +
> +typedef struct PECIClientProperties {
> +    uint32_t cpu_family;
> +    uint8_t cpu_cores;
> +    uint8_t dimms;
> +    uint8_t dimms_per_channel;
> +} PECIClientProperties;
> +
> +/* Creates a PECI client with the specified cpu and dimm count */
> +PECIClientDevice *peci_add_client(PECIBus *bus,
> +                                  uint8_t address,
> +                                  PECIClientProperties *props);
> +PECIClientDevice *peci_get_client(PECIBus *bus, uint8_t addr);
> +
> +typedef struct PECICmd {
> +    uint8_t addr;
> +    uint8_t cmd;
> +
> +    size_t wr_length;
> +    uint8_t rx[PECI_BUFFER_SIZE];
> +
> +    size_t rd_length;
> +    uint8_t tx[PECI_BUFFER_SIZE];
> +} PECICmd;
> +
> +int peci_handle_cmd(PECIBus *bus, PECICmd *pcmd);
> +
> +#endif
> diff --git a/meson.build b/meson.build
> index 20fddbd707..55594702c3 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -2965,6 +2965,7 @@ if have_system
>      'hw/nvram',
>      'hw/pci',
>      'hw/pci-host',
> +    'hw/peci',
>      'hw/ppc',
>      'hw/rdma',
>      'hw/rdma/vmw',
> -- 
> 2.37.3.968.ga6b4b080e4-goog
>
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 5ce4227ff6..14ab29679d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3212,6 +3212,13 @@  F: tests/qtest/adm1272-test.c
 F: tests/qtest/max34451-test.c
 F: tests/qtest/isl_pmbus_vr-test.c
 
+PECI
+M: Titus Rwantare <titusr@google.com>
+S: Maintained
+F: hw/peci/peci-core.c
+F: hw/peci/peci-client.c
+F: include/hw/peci/peci.h
+
 Firmware schema specifications
 M: Philippe Mathieu-Daudé <f4bug@amsat.org>
 R: Daniel P. Berrange <berrange@redhat.com>
diff --git a/hw/Kconfig b/hw/Kconfig
index 38233bbb0f..300ab48127 100644
--- a/hw/Kconfig
+++ b/hw/Kconfig
@@ -24,6 +24,7 @@  source net/Kconfig
 source nubus/Kconfig
 source nvme/Kconfig
 source nvram/Kconfig
+source peci/Kconfig
 source pci-bridge/Kconfig
 source pci-host/Kconfig
 source pcmcia/Kconfig
diff --git a/hw/meson.build b/hw/meson.build
index c7ac7d3d75..340cc88a52 100644
--- a/hw/meson.build
+++ b/hw/meson.build
@@ -28,6 +28,7 @@  subdir('pci')
 subdir('pci-bridge')
 subdir('pci-host')
 subdir('pcmcia')
+subdir('peci')
 subdir('rdma')
 subdir('rtc')
 subdir('scsi')
diff --git a/hw/peci/Kconfig b/hw/peci/Kconfig
new file mode 100644
index 0000000000..fe4f665d21
--- /dev/null
+++ b/hw/peci/Kconfig
@@ -0,0 +1,2 @@ 
+config PECI
+    bool
diff --git a/hw/peci/meson.build b/hw/peci/meson.build
new file mode 100644
index 0000000000..01cfa95abe
--- /dev/null
+++ b/hw/peci/meson.build
@@ -0,0 +1 @@ 
+softmmu_ss.add(when: 'CONFIG_PECI', if_true: files('peci-core.c', 'peci-client.c'))
diff --git a/hw/peci/peci-client.c b/hw/peci/peci-client.c
new file mode 100644
index 0000000000..2aa797b5f6
--- /dev/null
+++ b/hw/peci/peci-client.c
@@ -0,0 +1,230 @@ 
+/*
+ * PECI Client device
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/peci/peci.h"
+#include "hw/qdev-core.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+#include "qom/object.h"
+
+/*
+ * A PECI client represents an Intel socket and the peripherals attached to it
+ * that are accessible over the PECI bus.
+ */
+
+#define PECI_CLIENT_DEFAULT_TEMP 30
+
+static void peci_client_update_temps(PECIClientDevice *client)
+{
+    uint8_t temp_cpu = 0;
+    for (size_t i = 0; i < client->cpu_cores; i++) {
+        if (temp_cpu < client->core_temp[i]) {
+            temp_cpu = client->core_temp[i];
+        }
+    }
+    client->core_temp_max = -1 * (client->tjmax - temp_cpu);
+
+    uint8_t temp_dimm = 0;
+    for (size_t i = 0; i < client->dimms; i++) {
+        if (temp_dimm < client->dimm_temp[i]) {
+            temp_dimm = client->dimm_temp[i];
+        }
+    }
+    client->dimm_temp_max = temp_dimm;
+}
+
+PECIClientDevice *peci_get_client(PECIBus *bus, uint8_t addr)
+{
+    PECIClientDevice *client;
+    BusChild *var;
+
+    QTAILQ_FOREACH(var, &bus->qbus.children, sibling) {
+        DeviceState *dev = var->child;
+        client = PECI_CLIENT(dev);
+
+        if (client->address == addr) {
+            return client;
+        }
+    }
+    return 0;
+}
+
+
+PECIClientDevice *peci_add_client(PECIBus *bus,
+                                  uint8_t address,
+                                  PECIClientProperties *props)
+{
+    DeviceState *dev = qdev_new("peci-client");
+    PECIClientDevice *client;
+
+    /* Only 8 addresses supported as of rev 4.1, 0x30 to 0x37 */
+    if (address < PECI_BASE_ADDR || address > PECI_BASE_ADDR + 7) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: failed to add client at 0x%02x",
+                      __func__, address);
+        return 0;
+    }
+
+    client = PECI_CLIENT(dev);
+    client->address = address;
+
+    /* these fields are optional, get set to max if unset or invalid */
+    if (!props->cpu_cores || props->cpu_cores > PECI_NUM_CPUS_MAX) {
+        props->cpu_cores = PECI_NUM_CPUS_MAX;
+    }
+    if (!props->dimms || props->dimms > PECI_NUM_DIMMS_MAX) {
+        props->dimms = PECI_NUM_DIMMS_MAX;
+    }
+    if (!props->cpu_family) {
+        props->cpu_family = FAM6_ICELAKE_X;
+    }
+    if (!props->dimms_per_channel ||
+        props->dimms_per_channel > PECI_DIMMS_PER_CHANNEL_MAX) {
+        props->dimms_per_channel = 2;
+    }
+
+    client->cpu_id = props->cpu_family;
+    client->cpu_cores = props->cpu_cores;
+    client->dimms = props->dimms;
+    client->dimms_per_channel = props->dimms_per_channel;
+
+    /* TODO: find real revisions and TJ max for supported families */
+    client->tjmax = 90;
+    client->tcontrol = -5;
+
+    switch (props->cpu_family) {
+    case FAM6_HASWELL_X:
+        client->revision = 0x31;
+        break;
+
+    case FAM6_BROADWELL_X:
+        client->revision = 0x32;
+        break;
+
+    case FAM6_SKYLAKE_X:
+    case FAM6_SKYLAKE_XD:
+        client->revision = 0x36;
+        break;
+
+    case FAM6_ICELAKE_X:
+    case FAM6_SAPPHIRE_RAPIDS_X:
+        client->revision = 0x40;
+        client->ucode = 0x8c0004a0;
+        break;
+
+    default:
+        qemu_log_mask(LOG_UNIMP, "%s: unsupported cpu: 0x%x\n",
+                      __func__, props->cpu_family);
+        client->revision = 0x31;
+        break;
+    }
+
+    qdev_realize_and_unref(&client->qdev, &bus->qbus, &error_abort);
+    bus->num_clients += 1;
+    return client;
+}
+
+static void peci_client_get(Object *obj, Visitor *v, const char *name,
+                                   void *opaque, Error **errp)
+{
+    /* use millidegrees convention */
+    uint32_t value = *(uint8_t *)opaque * 1000;
+    visit_type_uint32(v, name, &value, errp);
+}
+
+static void peci_client_set(Object *obj, Visitor *v, const char *name,
+                                   void *opaque, Error **errp)
+{
+    PECIClientDevice *client = PECI_CLIENT(obj);
+    uint8_t *internal = opaque;
+    uint32_t value;
+
+    if (!visit_type_uint32(v, name, &value, errp)) {
+        return;
+    }
+
+    g_assert(value <= 255000);
+
+    *internal = value / 1000;
+    peci_client_update_temps(client);
+}
+
+static void peci_client_reset(Object *obj, ResetType type)
+{
+    PECIClientDevice *client = PECI_CLIENT(obj);
+    client->core_temp_max = 0;
+    client->dimm_temp_max = 0;
+}
+
+static const VMStateDescription vmstate_peci_client = {
+    .name = TYPE_PECI_CLIENT,
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT8(address, PECIClientDevice),
+        VMSTATE_UINT8(device_info, PECIClientDevice),
+        VMSTATE_UINT8(revision, PECIClientDevice),
+        VMSTATE_UINT32(cpu_id, PECIClientDevice),
+        VMSTATE_UINT8(cpu_cores, PECIClientDevice),
+        VMSTATE_UINT8(tjmax, PECIClientDevice),
+        VMSTATE_INT8(tcontrol, PECIClientDevice),
+        VMSTATE_UINT8_ARRAY(core_temp, PECIClientDevice, PECI_NUM_CPUS_MAX),
+        VMSTATE_UINT8(dimms, PECIClientDevice),
+        VMSTATE_UINT8(dimms_per_channel, PECIClientDevice),
+        VMSTATE_UINT8_ARRAY(dimm_temp, PECIClientDevice , PECI_NUM_DIMMS_MAX),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void peci_client_realize(DeviceState *dev, Error **errp)
+{
+    PECIClientDevice *client = PECI_CLIENT(dev);
+    for (size_t i = 0; i < client->cpu_cores; i++) {
+        client->core_temp[i] = PECI_CLIENT_DEFAULT_TEMP;
+        object_property_add(OBJECT(client), "cpu_temp[*]", "uint8",
+                            peci_client_get,
+                            peci_client_set, NULL, &client->core_temp[i]);
+    }
+
+    for (size_t i = 0; i < client->dimms; i++) {
+        client->dimm_temp[i] = PECI_CLIENT_DEFAULT_TEMP;
+        object_property_add(OBJECT(client), "dimm_temp[*]", "uint8",
+                            peci_client_get,
+                            peci_client_set, NULL, &client->dimm_temp[i]);
+    }
+}
+
+static void peci_client_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->desc = "PECI Client Device";
+    dc->bus_type = TYPE_PECI_BUS;
+    dc->realize = peci_client_realize;
+    dc->vmsd = &vmstate_peci_client;
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+    rc->phases.enter = peci_client_reset;
+}
+
+static const TypeInfo peci_client_info = {
+    .name = TYPE_PECI_CLIENT,
+    .parent = TYPE_DEVICE,
+    .instance_size = sizeof(PECIClientDevice),
+    .class_init = peci_client_class_init,
+};
+
+static void peci_client_register_types(void)
+{
+    type_register_static(&peci_client_info);
+}
+
+type_init(peci_client_register_types)
diff --git a/hw/peci/peci-core.c b/hw/peci/peci-core.c
new file mode 100644
index 0000000000..8210bfa198
--- /dev/null
+++ b/hw/peci/peci-core.c
@@ -0,0 +1,182 @@ 
+/*
+ * PECI - Platform Environment Control Interface
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * This PECI implementation aims to simulate a host with peci as viewed by a
+ * BMC. This is developed with OpenBMC firmware running in QEMU.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "hw/peci/peci.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+#include "trace.h"
+
+#define PECI_FCS_OK         0
+#define PECI_FCS_ERR        1
+
+static void peci_rd_pkg_cfg(PECIClientDevice *client, PECICmd *pcmd)
+{
+    PECIPkgCfg *resp = (PECIPkgCfg *)pcmd->tx;
+    uint8_t index = pcmd->rx[1];
+    uint16_t param = pcmd->rx[3] | pcmd->rx[2];
+
+    switch (index) {
+    case PECI_MBX_CPU_ID: /* CPU Family ID*/
+        trace_peci_rd_pkg_cfg("CPU ID");
+        switch (param) {
+        case PECI_PKG_ID_CPU_ID:
+            resp->data = client->cpu_id;
+            break;
+
+        case PECI_PKG_ID_MICROCODE_REV:
+            resp->data = client->ucode;
+            break;
+
+        default:
+            qemu_log_mask(LOG_UNIMP, "%s: CPU ID param %u\n", __func__, param);
+            break;
+        }
+        break;
+
+    case PECI_MBX_DTS_MARGIN:
+        trace_peci_rd_pkg_cfg("DTS MARGIN");
+    /*
+     * Processors return a value of DTS reading in 10.6 format
+     * (10 bits signed decimal, 6 bits fractional).
+     */
+        resp->data = (-1 * client->core_temp_max) << 6;
+        break;
+
+    case PECI_MBX_DDR_DIMM_TEMP:
+        trace_peci_rd_pkg_cfg("DDR DIMM TEMP");
+        if (param > PECI_NUM_CHANNELS_MAX) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: DDR_DIMM_TEMP unsupported channel index %d",
+                          __func__, param);
+            param = PECI_NUM_CHANNELS_MAX;
+        }
+        uint8_t channel_index = param * client->dimms_per_channel;
+        memcpy(&resp->data,
+               &client->dimm_temp[channel_index],
+               sizeof(client->dimm_temp[0]) * client->dimms_per_channel);
+        break;
+
+    case PECI_MBX_TEMP_TARGET:
+        trace_peci_rd_pkg_cfg("TEMP TARGET");
+        PECITempTarget target = {
+            .tcontrol = client->tcontrol,
+            .tjmax = client->tjmax,
+        };
+        memcpy(&resp->data, &target, sizeof(target));
+        break;
+
+    case PECI_MBX_PPIN:
+        trace_peci_rd_pkg_cfg("PPIN");
+        resp->data = 0xdeadbeef;
+        break;
+
+    default:
+        qemu_log_mask(LOG_UNIMP, "%s: unimplemented PkgCfg Index: %u\n",
+                      __func__, index);
+        resp->cc = PECI_DEV_CC_INVALID_REQ;
+        return;
+    }
+
+    resp->cc = PECI_DEV_CC_SUCCESS;
+}
+
+int peci_handle_cmd(PECIBus *bus, PECICmd *pcmd)
+{
+    PECIClientDevice *client = peci_get_client(bus, pcmd->addr);
+
+    if (!client) { /* ignore commands if clients aren't found */
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: no peci client at address: 0x%02x\n",
+                      __func__, pcmd->addr);
+        return PECI_FCS_ERR;
+    }
+
+    /* clear output buffer before handling command */
+    for (size_t i = 0; i < pcmd->rd_length; i++) {
+        pcmd->tx[i] = 0;
+    }
+
+    switch (pcmd->cmd) {
+    case PECI_CMD_PING:
+        trace_peci_handle_cmd("PING!", pcmd->addr);
+        break;
+
+    case PECI_CMD_GET_DIB: /* Device Info Byte */
+        trace_peci_handle_cmd("GetDIB", pcmd->addr);
+        PECIDIB dib = {
+            .device_info = client->device_info,
+            .revision = client->revision,
+        };
+        memcpy(pcmd->tx, &dib, sizeof(PECIDIB));
+        break;
+
+    case PECI_CMD_GET_TEMP: /* maximum die temp in socket */
+        trace_peci_handle_cmd("GetTemp", pcmd->addr);
+        /*
+         * The data is returned as a negative value representing the number of
+         * degrees centigrade below the maximum processor junction temperature
+         */
+        memcpy(pcmd->tx, &client->core_temp_max, sizeof(client->core_temp_max));
+        break;
+
+    case PECI_CMD_RD_PKG_CFG:
+        trace_peci_handle_cmd("RdPkgConfig", pcmd->addr);
+        peci_rd_pkg_cfg(client, pcmd);
+        break;
+
+    case PECI_CMD_RD_IA_MSR:
+        trace_peci_handle_cmd("RdIAMSR", pcmd->addr);
+        qemu_log_mask(LOG_UNIMP, "%s: unimplemented RD_IA_MSR\n", __func__);
+        break;
+
+    case PECI_CMD_RD_PCI_CFG:
+        trace_peci_handle_cmd("RdPCIConfig", pcmd->addr);
+        qemu_log_mask(LOG_UNIMP, "%s: unimplemented PCI_CFG\n", __func__);
+        break;
+
+    case PECI_CMD_RD_PCI_CFG_LOCAL:
+        trace_peci_handle_cmd("RdPCIConfigLocal", pcmd->addr);
+        qemu_log_mask(LOG_UNIMP, "%s: unimplemented PCI_CFG_LOCAL\n", __func__);
+        break;
+
+    case PECI_CMD_RD_END_PT_CFG:
+        qemu_log_mask(LOG_UNIMP, "%s: unimplemented CMD_RD_END_PT_CFG\n",
+                      __func__);
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: unknown cmd: %x\n",
+                      __func__, pcmd->cmd);
+        break;
+    }
+
+    return PECI_FCS_OK;
+}
+
+PECIBus *peci_bus_create(DeviceState *parent)
+{
+    return PECI_BUS(qbus_new(TYPE_PECI_BUS, parent, "peci-bus"));
+};
+
+static const TypeInfo peci_types[] = {
+    {
+        .name = TYPE_PECI_BUS,
+        .parent = TYPE_BUS,
+        .instance_size = sizeof(PECIBus),
+    },
+};
+
+DEFINE_TYPES(peci_types);
diff --git a/hw/peci/trace-events b/hw/peci/trace-events
new file mode 100644
index 0000000000..f90c998dd9
--- /dev/null
+++ b/hw/peci/trace-events
@@ -0,0 +1,5 @@ 
+# See docs/devel/tracing.rst for syntax documentation.
+
+# peci-core.c
+peci_handle_cmd(const char* s, uint8_t addr) "%s @ 0x%02" PRIx8
+peci_rd_pkg_cfg(const char* s) "%s"
diff --git a/hw/peci/trace.h b/hw/peci/trace.h
new file mode 100644
index 0000000000..4283e3bfc5
--- /dev/null
+++ b/hw/peci/trace.h
@@ -0,0 +1 @@ 
+#include "trace/trace-hw_peci.h"
diff --git a/include/hw/peci/peci.h b/include/hw/peci/peci.h
new file mode 100644
index 0000000000..1a0abe65cd
--- /dev/null
+++ b/include/hw/peci/peci.h
@@ -0,0 +1,194 @@ 
+/*
+ * PECI - Platform Environment Control Interface
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef PECI_H
+#define PECI_H
+
+#include "hw/qdev-core.h"
+#include "qom/object.h"
+
+#define PECI_CMD_PING               0x0
+#define PECI_CMD_GET_DIB            0xF7
+#define PECI_CMD_GET_TEMP           0x01
+#define PECI_CMD_RD_PKG_CFG         0xA1
+#define PECI_CMD_WR_PKG_CFG         0xA5
+#define   PECI_MBX_CPU_ID            0  /* Package Identifier Read */
+#define     PECI_PKG_ID_CPU_ID              0x0000  /* CPUID Info */
+#define     PECI_PKG_ID_PLATFORM_ID         0x0001  /* Platform ID */
+#define     PECI_PKG_ID_UNCORE_ID           0x0002  /* Uncore Device ID */
+#define     PECI_PKG_ID_MAX_THREAD_ID       0x0003  /* Max Thread ID */
+#define     PECI_PKG_ID_MICROCODE_REV       0x0004  /* CPU Microcode Revision */
+#define     PECI_PKG_ID_MACHINE_CHECK_STATUS 0x0005  /* Machine Check Status */
+#define   PECI_MBX_VR_DEBUG          1  /* VR Debug */
+#define   PECI_MBX_PKG_TEMP_READ     2  /* Package Temperature Read */
+#define   PECI_MBX_ENERGY_COUNTER    3  /* Energy counter */
+#define   PECI_MBX_ENERGY_STATUS     4  /* DDR Energy Status */
+#define   PECI_MBX_WAKE_MODE_BIT     5  /* "Wake on PECI" Mode bit */
+#define   PECI_MBX_EPI               6  /* Efficient Performance Indication */
+#define   PECI_MBX_PKG_RAPL_PERF     8  /* Pkg RAPL Performance Status Read */
+#define   PECI_MBX_PER_CORE_DTS_TEMP 9  /* Per Core DTS Temperature Read */
+#define   PECI_MBX_DTS_MARGIN        10 /* DTS thermal margin */
+#define   PECI_MBX_SKT_PWR_THRTL_DUR 11 /* Socket Power Throttled Duration */
+#define   PECI_MBX_CFG_TDP_CONTROL   12 /* TDP Config Control */
+#define   PECI_MBX_CFG_TDP_LEVELS    13 /* TDP Config Levels */
+#define   PECI_MBX_DDR_DIMM_TEMP     14 /* DDR DIMM Temperature */
+#define   PECI_MBX_CFG_ICCMAX        15 /* Configurable ICCMAX */
+#define   PECI_MBX_TEMP_TARGET       16 /* Temperature Target Read */
+#define   PECI_MBX_CURR_CFG_LIMIT    17 /* Current Config Limit */
+#define   PECI_MBX_PPIN              19 /* Processor Inventory Number */
+#define   PECI_MBX_DIMM_TEMP_READ    20 /* Package Thermal Status Read */
+#define   PECI_MBX_DRAM_IMC_TMP_READ 22 /* DRAM IMC Temperature Read */
+#define   PECI_MBX_DDR_CH_THERM_STAT 23 /* DDR Channel Thermal Status */
+#define   PECI_MBX_PKG_POWER_LIMIT1  26 /* Package Power Limit1 */
+#define   PECI_MBX_PKG_POWER_LIMIT2  27 /* Package Power Limit2 */
+#define   PECI_MBX_TDP               28 /* Thermal design power minimum */
+#define   PECI_MBX_TDP_HIGH          29 /* Thermal design power maximum */
+#define   PECI_MBX_TDP_UNITS         30 /* Units for power/energy registers */
+#define   PECI_MBX_RUN_TIME          31 /* Accumulated Run Time */
+#define   PECI_MBX_CONSTRAINED_TIME  32 /* Thermally Constrained Time Read */
+#define   PECI_MBX_TURBO_RATIO       33 /* Turbo Activation Ratio */
+#define   PECI_MBX_DDR_RAPL_PL1      34 /* DDR RAPL PL1 */
+#define   PECI_MBX_DDR_PWR_INFO_HIGH 35 /* DRAM Power Info Read (high) */
+#define   PECI_MBX_DDR_PWR_INFO_LOW  36 /* DRAM Power Info Read (low) */
+#define   PECI_MBX_DDR_RAPL_PL2      37 /* DDR RAPL PL2 */
+#define   PECI_MBX_DDR_RAPL_STATUS   38 /* DDR RAPL Performance Status */
+#define   PECI_MBX_DDR_HOT_ABSOLUTE  43 /* DDR Hottest Dimm Absolute Temp */
+#define   PECI_MBX_DDR_HOT_RELATIVE  44 /* DDR Hottest Dimm Relative Temp */
+#define   PECI_MBX_DDR_THROTTLE_TIME 45 /* DDR Throttle Time */
+#define   PECI_MBX_DDR_THERM_STATUS  46 /* DDR Thermal Status */
+#define   PECI_MBX_TIME_AVG_TEMP     47 /* Package time-averaged temperature */
+#define   PECI_MBX_TURBO_RATIO_LIMIT 49 /* Turbo Ratio Limit Read */
+#define   PECI_MBX_HWP_AUTO_OOB      53 /* HWP Autonomous Out-of-band */
+#define   PECI_MBX_DDR_WARM_BUDGET   55 /* DDR Warm Power Budget */
+#define   PECI_MBX_DDR_HOT_BUDGET    56 /* DDR Hot Power Budget */
+#define   PECI_MBX_PKG_PSYS_PWR_LIM3 57 /* Package/Psys Power Limit3 */
+#define   PECI_MBX_PKG_PSYS_PWR_LIM1 58 /* Package/Psys Power Limit1 */
+#define   PECI_MBX_PKG_PSYS_PWR_LIM2 59 /* Package/Psys Power Limit2 */
+#define   PECI_MBX_PKG_PSYS_PWR_LIM4 60 /* Package/Psys Power Limit4 */
+#define   PECI_MBX_PERF_LIMIT_REASON 65 /* Performance Limit Reasons */
+#define PECI_CMD_RD_IA_MSR          0xB1
+#define PECI_CMD_WR_IA_MSR          0xB5
+#define PECI_CMD_RD_IA_MSREX        0xD1
+#define PECI_CMD_RD_PCI_CFG         0x61
+#define PECI_CMD_WR_PCI_CFG         0x65
+#define PECI_CMD_RD_PCI_CFG_LOCAL   0xE1
+#define PECI_CMD_WR_PCI_CFG_LOCAL   0xE5
+#define PECI_CMD_RD_END_PT_CFG      0xC1
+#define PECI_CMD_WR_END_PT_CFG      0xC5
+#define PECI_CMD_CRASHDUMP_GET_FRAME            0x71
+
+/* Device Specific Completion Code (CC) Definition */
+#define PECI_DEV_CC_SUCCESS                            0x40
+#define PECI_DEV_CC_NEED_RETRY                         0x80
+#define PECI_DEV_CC_OUT_OF_RESOURCE                    0x81
+#define PECI_DEV_CC_UNAVAIL_RESOURCE                   0x82
+#define PECI_DEV_CC_INVALID_REQ                        0x90
+#define PECI_DEV_CC_MCA_ERROR                          0x91
+#define PECI_DEV_CC_CATASTROPHIC_MCA_ERROR             0x93
+#define PECI_DEV_CC_FATAL_MCA_DETECTED                 0x94
+#define PECI_DEV_CC_PARITY_ERROR_ON_GPSB_OR_PMSB       0x98
+#define PECI_DEV_CC_PARITY_ERROR_ON_GPSB_OR_PMSB_IERR  0x9B
+#define PECI_DEV_CC_PARITY_ERROR_ON_GPSB_OR_PMSB_MCA   0x9C
+
+
+typedef struct PECIDIB {  /* DIB - Device Info Byte(s) */
+    uint8_t device_info; /* bit 2 - num of domains */
+    uint8_t revision;
+} PECIDIB;
+
+typedef struct  __attribute__ ((__packed__)) {
+    uint8_t cc; /* completion code */
+    uint32_t data;
+} PECIPkgCfg;
+
+typedef struct PECITempTarget {
+    uint8_t reserved;
+    int8_t tcontrol;
+    uint8_t tjmax;
+} PECITempTarget;
+
+#define PECI_BASE_ADDR              0x30
+#define PECI_BUFFER_SIZE            0x100
+#define PECI_NUM_CPUS_MAX           56
+#define PECI_DIMMS_PER_CHANNEL_MAX  3
+#define PECI_NUM_CHANNELS_MAX       8
+#define PECI_NUM_DIMMS_MAX  (PECI_NUM_CHANNELS_MAX * PECI_DIMMS_PER_CHANNEL_MAX)
+
+typedef struct PECIClientDevice {
+    DeviceState qdev;
+    uint8_t address;
+    uint8_t device_info;
+    uint8_t revision;
+
+    /* CPU */
+    uint32_t cpu_id;
+    uint8_t cpu_cores;
+    uint32_t ucode;
+    uint8_t tjmax;
+    int8_t tcontrol;
+    int8_t core_temp_max; /* absolute temp - tjmax */
+    uint8_t core_temp[PECI_NUM_CPUS_MAX];
+
+    /* DIMMS */
+    uint8_t dimms;
+    uint8_t dimms_per_channel;
+    uint8_t dimm_temp_max;
+    uint8_t dimm_temp[PECI_NUM_DIMMS_MAX];
+
+} PECIClientDevice;
+
+#define TYPE_PECI_CLIENT "peci-client"
+OBJECT_DECLARE_SIMPLE_TYPE(PECIClientDevice, PECI_CLIENT)
+
+typedef struct PECIBus {
+    BusState qbus;
+    uint8_t num_clients;
+} PECIBus;
+
+#define TYPE_PECI_BUS "peci-bus"
+OBJECT_DECLARE_SIMPLE_TYPE(PECIBus, PECI_BUS)
+
+/* Creates a basic qemu bus onto which PECI clients will be attached */
+PECIBus *peci_bus_create(DeviceState *parent);
+
+enum {
+    FAM6_HASWELL_X = 0x306F0,
+    FAM6_BROADWELL_X = 0x406F0,
+    FAM6_SKYLAKE_X = 0x50650,
+    FAM6_SKYLAKE_XD = 0x50660,
+    FAM6_ICELAKE_X = 0x606A0,
+    FAM6_SAPPHIRE_RAPIDS_X = 0x806F3,
+};
+
+typedef struct PECIClientProperties {
+    uint32_t cpu_family;
+    uint8_t cpu_cores;
+    uint8_t dimms;
+    uint8_t dimms_per_channel;
+} PECIClientProperties;
+
+/* Creates a PECI client with the specified cpu and dimm count */
+PECIClientDevice *peci_add_client(PECIBus *bus,
+                                  uint8_t address,
+                                  PECIClientProperties *props);
+PECIClientDevice *peci_get_client(PECIBus *bus, uint8_t addr);
+
+typedef struct PECICmd {
+    uint8_t addr;
+    uint8_t cmd;
+
+    size_t wr_length;
+    uint8_t rx[PECI_BUFFER_SIZE];
+
+    size_t rd_length;
+    uint8_t tx[PECI_BUFFER_SIZE];
+} PECICmd;
+
+int peci_handle_cmd(PECIBus *bus, PECICmd *pcmd);
+
+#endif
diff --git a/meson.build b/meson.build
index 20fddbd707..55594702c3 100644
--- a/meson.build
+++ b/meson.build
@@ -2965,6 +2965,7 @@  if have_system
     'hw/nvram',
     'hw/pci',
     'hw/pci-host',
+    'hw/peci',
     'hw/ppc',
     'hw/rdma',
     'hw/rdma/vmw',