qemu/hw/ppc/pnv_bmc.c
Cédric Le Goater e2392d4395 ppc/pnv: Create BMC devices at machine init
The BMC of the OpenPOWER systems monitors the machine state using
sensors, controls the power and controls the access to the PNOR flash
device containing the firmware image required to boot the host.

QEMU models the power cycle process, access to the sensors and access
to the PNOR device. But, for these features to be available, the QEMU
PowerNV machine needs two extras devices on the command line, an IPMI
BT device for communication and a BMC backend device:

  -device ipmi-bmc-sim,id=bmc0 -device isa-ipmi-bt,bmc=bmc0,irq=10

The BMC properties are then defined accordingly in the device tree and
OPAL self adapts. If a BMC device and an IPMI BT device are not
available, OPAL does not try to communicate with the BMC in any
manner. This is not how real systems behave.

To be closer to the default behavior, create an IPMI BMC simulator
device and an IPMI BT device at machine initialization time. We loose
the ability to define an external BMC device but there are benefits:

  - a better match with real systems,
  - a better test coverage of the OPAL code,
  - system powerdown and reset commands that work,
  - a QEMU device tree compliant with the specifications (*).

(*) Still needs a MBOX device.

Signed-off-by: Cédric Le Goater <clg@kaod.org>
Message-Id: <20191121162340.11049-1-clg@kaod.org>
Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
2019-12-17 10:39:47 +11:00

233 lines
6.8 KiB
C

/*
* QEMU PowerNV, BMC related functions
*
* Copyright (c) 2016-2017, IBM Corporation.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "qapi/error.h"
#include "target/ppc/cpu.h"
#include "qemu/log.h"
#include "hw/ipmi/ipmi.h"
#include "hw/ppc/fdt.h"
#include "hw/ppc/pnv.h"
#include <libfdt.h>
/* TODO: include definition in ipmi.h */
#define IPMI_SDR_FULL_TYPE 1
/*
* OEM SEL Event data packet sent by BMC in response of a Read Event
* Message Buffer command
*/
typedef struct OemSel {
/* SEL header */
uint8_t id[2];
uint8_t type;
uint8_t timestamp[4];
uint8_t manuf_id[3];
/* OEM SEL data (6 bytes) follows */
uint8_t netfun;
uint8_t cmd;
uint8_t data[4];
} OemSel;
#define SOFT_OFF 0x00
#define SOFT_REBOOT 0x01
static void pnv_gen_oem_sel(IPMIBmc *bmc, uint8_t reboot)
{
/* IPMI SEL Event are 16 bytes long */
OemSel sel = {
.id = { 0x55 , 0x55 },
.type = 0xC0, /* OEM */
.manuf_id = { 0x0, 0x0, 0x0 },
.timestamp = { 0x0, 0x0, 0x0, 0x0 },
.netfun = 0x3A, /* IBM */
.cmd = 0x04, /* AMI OEM SEL Power Notification */
.data = { reboot, 0xFF, 0xFF, 0xFF },
};
ipmi_bmc_gen_event(bmc, (uint8_t *) &sel, 0 /* do not log the event */);
}
void pnv_bmc_powerdown(IPMIBmc *bmc)
{
pnv_gen_oem_sel(bmc, SOFT_OFF);
}
void pnv_dt_bmc_sensors(IPMIBmc *bmc, void *fdt)
{
int offset;
int i;
const struct ipmi_sdr_compact *sdr;
uint16_t nextrec;
offset = fdt_add_subnode(fdt, 0, "bmc");
_FDT(offset);
_FDT((fdt_setprop_string(fdt, offset, "name", "bmc")));
offset = fdt_add_subnode(fdt, offset, "sensors");
_FDT(offset);
_FDT((fdt_setprop_cell(fdt, offset, "#address-cells", 0x1)));
_FDT((fdt_setprop_cell(fdt, offset, "#size-cells", 0x0)));
for (i = 0; !ipmi_bmc_sdr_find(bmc, i, &sdr, &nextrec); i++) {
int off;
char *name;
if (sdr->header.rec_type != IPMI_SDR_COMPACT_TYPE &&
sdr->header.rec_type != IPMI_SDR_FULL_TYPE) {
continue;
}
name = g_strdup_printf("sensor@%x", sdr->sensor_owner_number);
off = fdt_add_subnode(fdt, offset, name);
_FDT(off);
g_free(name);
_FDT((fdt_setprop_cell(fdt, off, "reg", sdr->sensor_owner_number)));
_FDT((fdt_setprop_string(fdt, off, "name", "sensor")));
_FDT((fdt_setprop_string(fdt, off, "compatible", "ibm,ipmi-sensor")));
_FDT((fdt_setprop_cell(fdt, off, "ipmi-sensor-reading-type",
sdr->reading_type)));
_FDT((fdt_setprop_cell(fdt, off, "ipmi-entity-id",
sdr->entity_id)));
_FDT((fdt_setprop_cell(fdt, off, "ipmi-entity-instance",
sdr->entity_instance)));
_FDT((fdt_setprop_cell(fdt, off, "ipmi-sensor-type",
sdr->sensor_type)));
}
}
/*
* HIOMAP protocol handler
*/
#define HIOMAP_C_RESET 1
#define HIOMAP_C_GET_INFO 2
#define HIOMAP_C_GET_FLASH_INFO 3
#define HIOMAP_C_CREATE_READ_WINDOW 4
#define HIOMAP_C_CLOSE_WINDOW 5
#define HIOMAP_C_CREATE_WRITE_WINDOW 6
#define HIOMAP_C_MARK_DIRTY 7
#define HIOMAP_C_FLUSH 8
#define HIOMAP_C_ACK 9
#define HIOMAP_C_ERASE 10
#define HIOMAP_C_DEVICE_NAME 11
#define HIOMAP_C_LOCK 12
#define BLOCK_SHIFT 12 /* 4K */
static uint16_t bytes_to_blocks(uint32_t bytes)
{
return bytes >> BLOCK_SHIFT;
}
static void hiomap_cmd(IPMIBmcSim *ibs, uint8_t *cmd, unsigned int cmd_len,
RspBuffer *rsp)
{
PnvMachineState *pnv = PNV_MACHINE(qdev_get_machine());
PnvPnor *pnor = pnv->pnor;
uint32_t pnor_size = pnor->size;
uint32_t pnor_addr = PNOR_SPI_OFFSET;
bool readonly = false;
rsp_buffer_push(rsp, cmd[2]);
rsp_buffer_push(rsp, cmd[3]);
switch (cmd[2]) {
case HIOMAP_C_MARK_DIRTY:
case HIOMAP_C_FLUSH:
case HIOMAP_C_ERASE:
case HIOMAP_C_ACK:
break;
case HIOMAP_C_GET_INFO:
rsp_buffer_push(rsp, 2); /* Version 2 */
rsp_buffer_push(rsp, BLOCK_SHIFT); /* block size */
rsp_buffer_push(rsp, 0); /* Timeout */
rsp_buffer_push(rsp, 0); /* Timeout */
break;
case HIOMAP_C_GET_FLASH_INFO:
rsp_buffer_push(rsp, bytes_to_blocks(pnor_size) & 0xFF);
rsp_buffer_push(rsp, bytes_to_blocks(pnor_size) >> 8);
rsp_buffer_push(rsp, 0x01); /* erase size */
rsp_buffer_push(rsp, 0x00); /* erase size */
break;
case HIOMAP_C_CREATE_READ_WINDOW:
readonly = true;
/* Fall through */
case HIOMAP_C_CREATE_WRITE_WINDOW:
memory_region_set_readonly(&pnor->mmio, readonly);
memory_region_set_enabled(&pnor->mmio, true);
rsp_buffer_push(rsp, bytes_to_blocks(pnor_addr) & 0xFF);
rsp_buffer_push(rsp, bytes_to_blocks(pnor_addr) >> 8);
rsp_buffer_push(rsp, bytes_to_blocks(pnor_size) & 0xFF);
rsp_buffer_push(rsp, bytes_to_blocks(pnor_size) >> 8);
rsp_buffer_push(rsp, 0x00); /* offset */
rsp_buffer_push(rsp, 0x00); /* offset */
break;
case HIOMAP_C_CLOSE_WINDOW:
memory_region_set_enabled(&pnor->mmio, false);
break;
case HIOMAP_C_DEVICE_NAME:
case HIOMAP_C_RESET:
case HIOMAP_C_LOCK:
default:
qemu_log_mask(LOG_GUEST_ERROR, "HIOMAP: unknow command %02X\n", cmd[2]);
break;
}
}
#define HIOMAP 0x5a
static const IPMICmdHandler hiomap_cmds[] = {
[HIOMAP] = { hiomap_cmd, 3 },
};
static const IPMINetfn hiomap_netfn = {
.cmd_nums = ARRAY_SIZE(hiomap_cmds),
.cmd_handlers = hiomap_cmds
};
/*
* Instantiate the machine BMC. PowerNV uses the QEMU internal
* simulator but it could also be external.
*/
IPMIBmc *pnv_bmc_create(void)
{
Object *obj;
obj = object_new(TYPE_IPMI_BMC_SIMULATOR);
object_property_set_bool(obj, true, "realized", &error_fatal);
/* Install the HIOMAP protocol handlers to access the PNOR */
ipmi_sim_register_netfn(IPMI_BMC_SIMULATOR(obj), IPMI_NETFN_OEM,
&hiomap_netfn);
return IPMI_BMC(obj);
}