| /* | 
 |  * 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 "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 bool pnv_bmc_is_simulator(IPMIBmc *bmc) | 
 | { | 
 |     return object_dynamic_cast(OBJECT(bmc), TYPE_IPMI_BMC_SIMULATOR); | 
 | } | 
 |  | 
 | 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; | 
 |  | 
 |     if (!pnv_bmc_is_simulator(bmc)) { | 
 |         return; | 
 |     } | 
 |  | 
 |     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 uint32_t blocks_to_bytes(uint16_t blocks) | 
 | { | 
 |     return blocks << BLOCK_SHIFT; | 
 | } | 
 |  | 
 | static int hiomap_erase(PnvPnor *pnor, uint32_t offset, uint32_t size) | 
 | { | 
 |     MemTxResult result; | 
 |     int i; | 
 |  | 
 |     for (i = 0; i < size / 4; i++) { | 
 |         result = memory_region_dispatch_write(&pnor->mmio, offset + i * 4, | 
 |                                               0xFFFFFFFF, MO_32, | 
 |                                               MEMTXATTRS_UNSPECIFIED); | 
 |         if (result != MEMTX_OK) { | 
 |             return -1; | 
 |         } | 
 |     } | 
 |     return 0; | 
 | } | 
 |  | 
 | static void hiomap_cmd(IPMIBmcSim *ibs, uint8_t *cmd, unsigned int cmd_len, | 
 |                        RspBuffer *rsp) | 
 | { | 
 |     PnvPnor *pnor = PNV_PNOR(object_property_get_link(OBJECT(ibs), "pnor", | 
 |                                                       &error_abort)); | 
 |     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_ACK: | 
 |         break; | 
 |  | 
 |     case HIOMAP_C_ERASE: | 
 |         if (hiomap_erase(pnor, blocks_to_bytes(cmd[5] << 8 | cmd[4]), | 
 |                         blocks_to_bytes(cmd[7] << 8 | cmd[6]))) { | 
 |             rsp_buffer_set_error(rsp, IPMI_CC_UNSPECIFIED); | 
 |         } | 
 |         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: unknown 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 | 
 | }; | 
 |  | 
 |  | 
 | void pnv_bmc_set_pnor(IPMIBmc *bmc, PnvPnor *pnor) | 
 | { | 
 |     if (!pnv_bmc_is_simulator(bmc)) { | 
 |         return; | 
 |     } | 
 |  | 
 |     object_ref(OBJECT(pnor)); | 
 |     object_property_add_const_link(OBJECT(bmc), "pnor", OBJECT(pnor)); | 
 |  | 
 |     /* Install the HIOMAP protocol handlers to access the PNOR */ | 
 |     ipmi_sim_register_netfn(IPMI_BMC_SIMULATOR(bmc), IPMI_NETFN_OEM, | 
 |                             &hiomap_netfn); | 
 | } | 
 |  | 
 | /* | 
 |  * Instantiate the machine BMC. PowerNV uses the QEMU internal | 
 |  * simulator but it could also be external. | 
 |  */ | 
 | IPMIBmc *pnv_bmc_create(PnvPnor *pnor) | 
 | { | 
 |     Object *obj; | 
 |  | 
 |     obj = object_new(TYPE_IPMI_BMC_SIMULATOR); | 
 |     qdev_realize(DEVICE(obj), NULL, &error_fatal); | 
 |     pnv_bmc_set_pnor(IPMI_BMC(obj), pnor); | 
 |  | 
 |     return IPMI_BMC(obj); | 
 | } | 
 |  | 
 | typedef struct ForeachArgs { | 
 |     const char *name; | 
 |     Object *obj; | 
 | } ForeachArgs; | 
 |  | 
 | static int bmc_find(Object *child, void *opaque) | 
 | { | 
 |     ForeachArgs *args = opaque; | 
 |  | 
 |     if (object_dynamic_cast(child, args->name)) { | 
 |         if (args->obj) { | 
 |             return 1; | 
 |         } | 
 |         args->obj = child; | 
 |     } | 
 |     return 0; | 
 | } | 
 |  | 
 | IPMIBmc *pnv_bmc_find(Error **errp) | 
 | { | 
 |     ForeachArgs args = { TYPE_IPMI_BMC, NULL }; | 
 |     int ret; | 
 |  | 
 |     ret = object_child_foreach_recursive(object_get_root(), bmc_find, &args); | 
 |     if (ret) { | 
 |         error_setg(errp, "machine should have only one BMC device. " | 
 |                    "Use '-nodefaults'"); | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     return args.obj ? IPMI_BMC(args.obj) : NULL; | 
 | } |