From 437fd90c0250dee670290f9b714253671a990160 Mon Sep 17 00:00:00 2001 From: José Pekkarinen Date: Wed, 18 May 2016 13:18:31 +0300 Subject: These changes are the raw update to qemu-2.6. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Collission happened in the following patches: migration: do cleanup operation after completion(738df5b9) Bug fix.(1750c932f86) kvmclock: add a new function to update env->tsc.(b52baab2) The code provided by the patches was already in the upstreamed version. Change-Id: I3cc11841a6a76ae20887b2e245710199e1ea7f9a Signed-off-by: José Pekkarinen --- qemu/hw/ipmi/Makefile.objs | 5 + qemu/hw/ipmi/ipmi.c | 151 ++++ qemu/hw/ipmi/ipmi_bmc_extern.c | 519 ++++++++++++ qemu/hw/ipmi/ipmi_bmc_sim.c | 1810 ++++++++++++++++++++++++++++++++++++++++ qemu/hw/ipmi/isa_ipmi_bt.c | 530 ++++++++++++ qemu/hw/ipmi/isa_ipmi_kcs.c | 495 +++++++++++ 6 files changed, 3510 insertions(+) create mode 100644 qemu/hw/ipmi/Makefile.objs create mode 100644 qemu/hw/ipmi/ipmi.c create mode 100644 qemu/hw/ipmi/ipmi_bmc_extern.c create mode 100644 qemu/hw/ipmi/ipmi_bmc_sim.c create mode 100644 qemu/hw/ipmi/isa_ipmi_bt.c create mode 100644 qemu/hw/ipmi/isa_ipmi_kcs.c (limited to 'qemu/hw/ipmi') diff --git a/qemu/hw/ipmi/Makefile.objs b/qemu/hw/ipmi/Makefile.objs new file mode 100644 index 000000000..a90318d5b --- /dev/null +++ b/qemu/hw/ipmi/Makefile.objs @@ -0,0 +1,5 @@ +common-obj-$(CONFIG_IPMI) += ipmi.o +common-obj-$(CONFIG_IPMI_LOCAL) += ipmi_bmc_sim.o +common-obj-$(CONFIG_IPMI_LOCAL) += ipmi_bmc_extern.o +common-obj-$(CONFIG_ISA_IPMI_KCS) += isa_ipmi_kcs.o +common-obj-$(CONFIG_ISA_IPMI_BT) += isa_ipmi_bt.o diff --git a/qemu/hw/ipmi/ipmi.c b/qemu/hw/ipmi/ipmi.c new file mode 100644 index 000000000..6adec1e99 --- /dev/null +++ b/qemu/hw/ipmi/ipmi.c @@ -0,0 +1,151 @@ +/* + * QEMU IPMI emulation + * + * Copyright (c) 2015 Corey Minyard, MontaVista Software, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "hw/hw.h" +#include "hw/ipmi/ipmi.h" +#include "sysemu/sysemu.h" +#include "qmp-commands.h" +#include "qom/object_interfaces.h" +#include "qapi/visitor.h" + +static int ipmi_do_hw_op(IPMIInterface *s, enum ipmi_op op, int checkonly) +{ + switch (op) { + case IPMI_RESET_CHASSIS: + if (checkonly) { + return 0; + } + qemu_system_reset_request(); + return 0; + + case IPMI_POWEROFF_CHASSIS: + if (checkonly) { + return 0; + } + qemu_system_powerdown_request(); + return 0; + + case IPMI_SEND_NMI: + if (checkonly) { + return 0; + } + qmp_inject_nmi(NULL); + return 0; + + case IPMI_POWERCYCLE_CHASSIS: + case IPMI_PULSE_DIAG_IRQ: + case IPMI_SHUTDOWN_VIA_ACPI_OVERTEMP: + case IPMI_POWERON_CHASSIS: + default: + return IPMI_CC_COMMAND_NOT_SUPPORTED; + } +} + +static void ipmi_interface_class_init(ObjectClass *class, void *data) +{ + IPMIInterfaceClass *ik = IPMI_INTERFACE_CLASS(class); + + ik->do_hw_op = ipmi_do_hw_op; +} + +static TypeInfo ipmi_interface_type_info = { + .name = TYPE_IPMI_INTERFACE, + .parent = TYPE_INTERFACE, + .class_size = sizeof(IPMIInterfaceClass), + .class_init = ipmi_interface_class_init, +}; + +static void isa_ipmi_bmc_check(Object *obj, const char *name, + Object *val, Error **errp) +{ + IPMIBmc *bmc = IPMI_BMC(val); + + if (bmc->intf) + error_setg(errp, "BMC object is already in use"); +} + +void ipmi_bmc_find_and_link(Object *obj, Object **bmc) +{ + object_property_add_link(obj, "bmc", TYPE_IPMI_BMC, bmc, + isa_ipmi_bmc_check, + OBJ_PROP_LINK_UNREF_ON_RELEASE, + &error_abort); +} + +static Property ipmi_bmc_properties[] = { + DEFINE_PROP_UINT8("slave_addr", IPMIBmc, slave_addr, 0x20), + DEFINE_PROP_END_OF_LIST(), +}; + +static void bmc_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->props = ipmi_bmc_properties; +} + +static TypeInfo ipmi_bmc_type_info = { + .name = TYPE_IPMI_BMC, + .parent = TYPE_DEVICE, + .instance_size = sizeof(IPMIBmc), + .abstract = true, + .class_size = sizeof(IPMIBmcClass), + .class_init = bmc_class_init, +}; + +static void ipmi_register_types(void) +{ + type_register_static(&ipmi_interface_type_info); + type_register_static(&ipmi_bmc_type_info); +} + +type_init(ipmi_register_types) + +static IPMIFwInfo *ipmi_fw_info; +static unsigned int ipmi_fw_info_len; + +static uint32_t current_uuid = 1; + +void ipmi_add_fwinfo(IPMIFwInfo *info, Error **errp) +{ + info->uuid = current_uuid++; + ipmi_fw_info = g_realloc(ipmi_fw_info, + sizeof(*ipmi_fw_info) * (ipmi_fw_info_len + 1)); + ipmi_fw_info[ipmi_fw_info_len] = *info; +} + +IPMIFwInfo *ipmi_first_fwinfo(void) +{ + return ipmi_fw_info; +} + +IPMIFwInfo *ipmi_next_fwinfo(IPMIFwInfo *current) +{ + current++; + if (current >= &ipmi_fw_info[ipmi_fw_info_len]) { + return NULL; + } + return current; +} diff --git a/qemu/hw/ipmi/ipmi_bmc_extern.c b/qemu/hw/ipmi/ipmi_bmc_extern.c new file mode 100644 index 000000000..fe12112a2 --- /dev/null +++ b/qemu/hw/ipmi/ipmi_bmc_extern.c @@ -0,0 +1,519 @@ +/* + * IPMI BMC external connection + * + * Copyright (c) 2015 Corey Minyard, MontaVista Software, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * This is designed to connect with OpenIPMI's lanserv serial interface + * using the "VM" connection type. See that for details. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/timer.h" +#include "sysemu/char.h" +#include "sysemu/sysemu.h" +#include "hw/ipmi/ipmi.h" + +#define VM_MSG_CHAR 0xA0 /* Marks end of message */ +#define VM_CMD_CHAR 0xA1 /* Marks end of a command */ +#define VM_ESCAPE_CHAR 0xAA /* Set bit 4 from the next byte to 0 */ + +#define VM_PROTOCOL_VERSION 1 +#define VM_CMD_VERSION 0xff /* A version number byte follows */ +#define VM_CMD_NOATTN 0x00 +#define VM_CMD_ATTN 0x01 +#define VM_CMD_ATTN_IRQ 0x02 +#define VM_CMD_POWEROFF 0x03 +#define VM_CMD_RESET 0x04 +#define VM_CMD_ENABLE_IRQ 0x05 /* Enable/disable the messaging irq */ +#define VM_CMD_DISABLE_IRQ 0x06 +#define VM_CMD_SEND_NMI 0x07 +#define VM_CMD_CAPABILITIES 0x08 +#define VM_CAPABILITIES_POWER 0x01 +#define VM_CAPABILITIES_RESET 0x02 +#define VM_CAPABILITIES_IRQ 0x04 +#define VM_CAPABILITIES_NMI 0x08 +#define VM_CAPABILITIES_ATTN 0x10 +#define VM_CMD_FORCEOFF 0x09 + +#define TYPE_IPMI_BMC_EXTERN "ipmi-bmc-extern" +#define IPMI_BMC_EXTERN(obj) OBJECT_CHECK(IPMIBmcExtern, (obj), \ + TYPE_IPMI_BMC_EXTERN) +typedef struct IPMIBmcExtern { + IPMIBmc parent; + + CharDriverState *chr; + + bool connected; + + unsigned char inbuf[MAX_IPMI_MSG_SIZE + 2]; + unsigned int inpos; + bool in_escape; + bool in_too_many; + bool waiting_rsp; + bool sending_cmd; + + unsigned char outbuf[(MAX_IPMI_MSG_SIZE + 2) * 2 + 1]; + unsigned int outpos; + unsigned int outlen; + + struct QEMUTimer *extern_timer; + + /* A reset event is pending to be sent upstream. */ + bool send_reset; +} IPMIBmcExtern; + +static int can_receive(void *opaque); +static void receive(void *opaque, const uint8_t *buf, int size); +static void chr_event(void *opaque, int event); + +static unsigned char +ipmb_checksum(const unsigned char *data, int size, unsigned char start) +{ + unsigned char csum = start; + + for (; size > 0; size--, data++) { + csum += *data; + } + return csum; +} + +static void continue_send(IPMIBmcExtern *ibe) +{ + if (ibe->outlen == 0) { + goto check_reset; + } + send: + ibe->outpos += qemu_chr_fe_write(ibe->chr, ibe->outbuf + ibe->outpos, + ibe->outlen - ibe->outpos); + if (ibe->outpos < ibe->outlen) { + /* Not fully transmitted, try again in a 10ms */ + timer_mod_ns(ibe->extern_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 10000000); + } else { + /* Sent */ + ibe->outlen = 0; + ibe->outpos = 0; + if (!ibe->sending_cmd) { + ibe->waiting_rsp = true; + } else { + ibe->sending_cmd = false; + } + check_reset: + if (ibe->connected && ibe->send_reset) { + /* Send the reset */ + ibe->outbuf[0] = VM_CMD_RESET; + ibe->outbuf[1] = VM_CMD_CHAR; + ibe->outlen = 2; + ibe->outpos = 0; + ibe->send_reset = false; + ibe->sending_cmd = true; + goto send; + } + + if (ibe->waiting_rsp) { + /* Make sure we get a response within 4 seconds. */ + timer_mod_ns(ibe->extern_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 4000000000ULL); + } + } + return; +} + +static void extern_timeout(void *opaque) +{ + IPMIBmcExtern *ibe = opaque; + IPMIInterface *s = ibe->parent.intf; + + if (ibe->connected) { + if (ibe->waiting_rsp && (ibe->outlen == 0)) { + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + /* The message response timed out, return an error. */ + ibe->waiting_rsp = false; + ibe->inbuf[1] = ibe->outbuf[1] | 0x04; + ibe->inbuf[2] = ibe->outbuf[2]; + ibe->inbuf[3] = IPMI_CC_TIMEOUT; + k->handle_rsp(s, ibe->outbuf[0], ibe->inbuf + 1, 3); + } else { + continue_send(ibe); + } + } +} + +static void addchar(IPMIBmcExtern *ibe, unsigned char ch) +{ + switch (ch) { + case VM_MSG_CHAR: + case VM_CMD_CHAR: + case VM_ESCAPE_CHAR: + ibe->outbuf[ibe->outlen] = VM_ESCAPE_CHAR; + ibe->outlen++; + ch |= 0x10; + /* No break */ + + default: + ibe->outbuf[ibe->outlen] = ch; + ibe->outlen++; + } +} + +static void ipmi_bmc_extern_handle_command(IPMIBmc *b, + uint8_t *cmd, unsigned int cmd_len, + unsigned int max_cmd_len, + uint8_t msg_id) +{ + IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(b); + IPMIInterface *s = ibe->parent.intf; + uint8_t err = 0, csum; + unsigned int i; + + if (ibe->outlen) { + /* We already have a command queued. Shouldn't ever happen. */ + fprintf(stderr, "IPMI KCS: Got command when not finished with the" + " previous commmand\n"); + abort(); + } + + /* If it's too short or it was truncated, return an error. */ + if (cmd_len < 2) { + err = IPMI_CC_REQUEST_DATA_LENGTH_INVALID; + } else if ((cmd_len > max_cmd_len) || (cmd_len > MAX_IPMI_MSG_SIZE)) { + err = IPMI_CC_REQUEST_DATA_TRUNCATED; + } else if (!ibe->connected) { + err = IPMI_CC_BMC_INIT_IN_PROGRESS; + } + if (err) { + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + unsigned char rsp[3]; + rsp[0] = cmd[0] | 0x04; + rsp[1] = cmd[1]; + rsp[2] = err; + ibe->waiting_rsp = false; + k->handle_rsp(s, msg_id, rsp, 3); + goto out; + } + + addchar(ibe, msg_id); + for (i = 0; i < cmd_len; i++) { + addchar(ibe, cmd[i]); + } + csum = ipmb_checksum(&msg_id, 1, 0); + addchar(ibe, -ipmb_checksum(cmd, cmd_len, csum)); + + ibe->outbuf[ibe->outlen] = VM_MSG_CHAR; + ibe->outlen++; + + /* Start the transmit */ + continue_send(ibe); + + out: + return; +} + +static void handle_hw_op(IPMIBmcExtern *ibe, unsigned char hw_op) +{ + IPMIInterface *s = ibe->parent.intf; + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + + switch (hw_op) { + case VM_CMD_VERSION: + /* We only support one version at this time. */ + break; + + case VM_CMD_NOATTN: + k->set_atn(s, 0, 0); + break; + + case VM_CMD_ATTN: + k->set_atn(s, 1, 0); + break; + + case VM_CMD_ATTN_IRQ: + k->set_atn(s, 1, 1); + break; + + case VM_CMD_POWEROFF: + k->do_hw_op(s, IPMI_POWEROFF_CHASSIS, 0); + break; + + case VM_CMD_RESET: + k->do_hw_op(s, IPMI_RESET_CHASSIS, 0); + break; + + case VM_CMD_ENABLE_IRQ: + k->set_irq_enable(s, 1); + break; + + case VM_CMD_DISABLE_IRQ: + k->set_irq_enable(s, 0); + break; + + case VM_CMD_SEND_NMI: + k->do_hw_op(s, IPMI_SEND_NMI, 0); + break; + + case VM_CMD_FORCEOFF: + qemu_system_shutdown_request(); + break; + } +} + +static void handle_msg(IPMIBmcExtern *ibe) +{ + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(ibe->parent.intf); + + if (ibe->in_escape) { + ipmi_debug("msg escape not ended\n"); + return; + } + if (ibe->inpos < 5) { + ipmi_debug("msg too short\n"); + return; + } + if (ibe->in_too_many) { + ibe->inbuf[3] = IPMI_CC_REQUEST_DATA_TRUNCATED; + ibe->inpos = 4; + } else if (ipmb_checksum(ibe->inbuf, ibe->inpos, 0) != 0) { + ipmi_debug("msg checksum failure\n"); + return; + } else { + ibe->inpos--; /* Remove checkum */ + } + + timer_del(ibe->extern_timer); + ibe->waiting_rsp = false; + k->handle_rsp(ibe->parent.intf, ibe->inbuf[0], ibe->inbuf + 1, ibe->inpos - 1); +} + +static int can_receive(void *opaque) +{ + return 1; +} + +static void receive(void *opaque, const uint8_t *buf, int size) +{ + IPMIBmcExtern *ibe = opaque; + int i; + unsigned char hw_op; + + for (i = 0; i < size; i++) { + unsigned char ch = buf[i]; + + switch (ch) { + case VM_MSG_CHAR: + handle_msg(ibe); + ibe->in_too_many = false; + ibe->inpos = 0; + break; + + case VM_CMD_CHAR: + if (ibe->in_too_many) { + ipmi_debug("cmd in too many\n"); + ibe->in_too_many = false; + ibe->inpos = 0; + break; + } + if (ibe->in_escape) { + ipmi_debug("cmd in escape\n"); + ibe->in_too_many = false; + ibe->inpos = 0; + ibe->in_escape = false; + break; + } + ibe->in_too_many = false; + if (ibe->inpos < 1) { + break; + } + hw_op = ibe->inbuf[0]; + ibe->inpos = 0; + goto out_hw_op; + break; + + case VM_ESCAPE_CHAR: + ibe->in_escape = true; + break; + + default: + if (ibe->in_escape) { + ch &= ~0x10; + ibe->in_escape = false; + } + if (ibe->in_too_many) { + break; + } + if (ibe->inpos >= sizeof(ibe->inbuf)) { + ibe->in_too_many = true; + break; + } + ibe->inbuf[ibe->inpos] = ch; + ibe->inpos++; + break; + } + } + return; + + out_hw_op: + handle_hw_op(ibe, hw_op); +} + +static void chr_event(void *opaque, int event) +{ + IPMIBmcExtern *ibe = opaque; + IPMIInterface *s = ibe->parent.intf; + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + unsigned char v; + + switch (event) { + case CHR_EVENT_OPENED: + ibe->connected = true; + ibe->outpos = 0; + ibe->outlen = 0; + addchar(ibe, VM_CMD_VERSION); + addchar(ibe, VM_PROTOCOL_VERSION); + ibe->outbuf[ibe->outlen] = VM_CMD_CHAR; + ibe->outlen++; + addchar(ibe, VM_CMD_CAPABILITIES); + v = VM_CAPABILITIES_IRQ | VM_CAPABILITIES_ATTN; + if (k->do_hw_op(ibe->parent.intf, IPMI_POWEROFF_CHASSIS, 1) == 0) { + v |= VM_CAPABILITIES_POWER; + } + if (k->do_hw_op(ibe->parent.intf, IPMI_RESET_CHASSIS, 1) == 0) { + v |= VM_CAPABILITIES_RESET; + } + if (k->do_hw_op(ibe->parent.intf, IPMI_SEND_NMI, 1) == 0) { + v |= VM_CAPABILITIES_NMI; + } + addchar(ibe, v); + ibe->outbuf[ibe->outlen] = VM_CMD_CHAR; + ibe->outlen++; + ibe->sending_cmd = false; + continue_send(ibe); + break; + + case CHR_EVENT_CLOSED: + if (!ibe->connected) { + return; + } + ibe->connected = false; + if (ibe->waiting_rsp) { + ibe->waiting_rsp = false; + ibe->inbuf[1] = ibe->outbuf[1] | 0x04; + ibe->inbuf[2] = ibe->outbuf[2]; + ibe->inbuf[3] = IPMI_CC_BMC_INIT_IN_PROGRESS; + k->handle_rsp(s, ibe->outbuf[0], ibe->inbuf + 1, 3); + } + break; + } +} + +static void ipmi_bmc_extern_handle_reset(IPMIBmc *b) +{ + IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(b); + + ibe->send_reset = true; + continue_send(ibe); +} + +static void ipmi_bmc_extern_realize(DeviceState *dev, Error **errp) +{ + IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(dev); + + if (!ibe->chr) { + error_setg(errp, "IPMI external bmc requires chardev attribute"); + return; + } + + qemu_chr_add_handlers(ibe->chr, can_receive, receive, chr_event, ibe); +} + +static int ipmi_bmc_extern_post_migrate(void *opaque, int version_id) +{ + IPMIBmcExtern *ibe = opaque; + + /* + * We don't directly restore waiting_rsp, Instead, we return an + * error on the interface if a response was being waited for. + */ + if (ibe->waiting_rsp) { + IPMIInterface *ii = ibe->parent.intf; + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + + ibe->waiting_rsp = false; + ibe->inbuf[1] = ibe->outbuf[1] | 0x04; + ibe->inbuf[2] = ibe->outbuf[2]; + ibe->inbuf[3] = IPMI_CC_BMC_INIT_IN_PROGRESS; + iic->handle_rsp(ii, ibe->outbuf[0], ibe->inbuf + 1, 3); + } + return 0; +} + +static const VMStateDescription vmstate_ipmi_bmc_extern = { + .name = TYPE_IPMI_BMC_EXTERN, + .version_id = 1, + .minimum_version_id = 1, + .post_load = ipmi_bmc_extern_post_migrate, + .fields = (VMStateField[]) { + VMSTATE_BOOL(send_reset, IPMIBmcExtern), + VMSTATE_BOOL(waiting_rsp, IPMIBmcExtern), + VMSTATE_END_OF_LIST() + } +}; + +static void ipmi_bmc_extern_init(Object *obj) +{ + IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(obj); + + ibe->extern_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, extern_timeout, ibe); + vmstate_register(NULL, 0, &vmstate_ipmi_bmc_extern, ibe); +} + +static Property ipmi_bmc_extern_properties[] = { + DEFINE_PROP_CHR("chardev", IPMIBmcExtern, chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static void ipmi_bmc_extern_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + IPMIBmcClass *bk = IPMI_BMC_CLASS(oc); + + bk->handle_command = ipmi_bmc_extern_handle_command; + bk->handle_reset = ipmi_bmc_extern_handle_reset; + dc->realize = ipmi_bmc_extern_realize; + dc->props = ipmi_bmc_extern_properties; +} + +static const TypeInfo ipmi_bmc_extern_type = { + .name = TYPE_IPMI_BMC_EXTERN, + .parent = TYPE_IPMI_BMC, + .instance_size = sizeof(IPMIBmcExtern), + .instance_init = ipmi_bmc_extern_init, + .class_init = ipmi_bmc_extern_class_init, + }; + +static void ipmi_bmc_extern_register_types(void) +{ + type_register_static(&ipmi_bmc_extern_type); +} + +type_init(ipmi_bmc_extern_register_types) diff --git a/qemu/hw/ipmi/ipmi_bmc_sim.c b/qemu/hw/ipmi/ipmi_bmc_sim.c new file mode 100644 index 000000000..dc9c14cd2 --- /dev/null +++ b/qemu/hw/ipmi/ipmi_bmc_sim.c @@ -0,0 +1,1810 @@ +/* + * IPMI BMC emulation + * + * Copyright (c) 2015 Corey Minyard, MontaVista Software, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "sysemu/sysemu.h" +#include "qemu/timer.h" +#include "hw/ipmi/ipmi.h" +#include "qemu/error-report.h" + +#define IPMI_NETFN_CHASSIS 0x00 + +#define IPMI_CMD_GET_CHASSIS_CAPABILITIES 0x00 +#define IPMI_CMD_GET_CHASSIS_STATUS 0x01 +#define IPMI_CMD_CHASSIS_CONTROL 0x02 +#define IPMI_CMD_GET_SYS_RESTART_CAUSE 0x09 + +#define IPMI_NETFN_SENSOR_EVENT 0x04 + +#define IPMI_CMD_SET_SENSOR_EVT_ENABLE 0x28 +#define IPMI_CMD_GET_SENSOR_EVT_ENABLE 0x29 +#define IPMI_CMD_REARM_SENSOR_EVTS 0x2a +#define IPMI_CMD_GET_SENSOR_EVT_STATUS 0x2b +#define IPMI_CMD_GET_SENSOR_READING 0x2d +#define IPMI_CMD_SET_SENSOR_TYPE 0x2e +#define IPMI_CMD_GET_SENSOR_TYPE 0x2f + +/* #define IPMI_NETFN_APP 0x06 In ipmi.h */ + +#define IPMI_CMD_GET_DEVICE_ID 0x01 +#define IPMI_CMD_COLD_RESET 0x02 +#define IPMI_CMD_WARM_RESET 0x03 +#define IPMI_CMD_SET_ACPI_POWER_STATE 0x06 +#define IPMI_CMD_GET_ACPI_POWER_STATE 0x07 +#define IPMI_CMD_GET_DEVICE_GUID 0x08 +#define IPMI_CMD_RESET_WATCHDOG_TIMER 0x22 +#define IPMI_CMD_SET_WATCHDOG_TIMER 0x24 +#define IPMI_CMD_GET_WATCHDOG_TIMER 0x25 +#define IPMI_CMD_SET_BMC_GLOBAL_ENABLES 0x2e +#define IPMI_CMD_GET_BMC_GLOBAL_ENABLES 0x2f +#define IPMI_CMD_CLR_MSG_FLAGS 0x30 +#define IPMI_CMD_GET_MSG_FLAGS 0x31 +#define IPMI_CMD_GET_MSG 0x33 +#define IPMI_CMD_SEND_MSG 0x34 +#define IPMI_CMD_READ_EVT_MSG_BUF 0x35 + +#define IPMI_NETFN_STORAGE 0x0a + +#define IPMI_CMD_GET_SDR_REP_INFO 0x20 +#define IPMI_CMD_GET_SDR_REP_ALLOC_INFO 0x21 +#define IPMI_CMD_RESERVE_SDR_REP 0x22 +#define IPMI_CMD_GET_SDR 0x23 +#define IPMI_CMD_ADD_SDR 0x24 +#define IPMI_CMD_PARTIAL_ADD_SDR 0x25 +#define IPMI_CMD_DELETE_SDR 0x26 +#define IPMI_CMD_CLEAR_SDR_REP 0x27 +#define IPMI_CMD_GET_SDR_REP_TIME 0x28 +#define IPMI_CMD_SET_SDR_REP_TIME 0x29 +#define IPMI_CMD_ENTER_SDR_REP_UPD_MODE 0x2A +#define IPMI_CMD_EXIT_SDR_REP_UPD_MODE 0x2B +#define IPMI_CMD_RUN_INIT_AGENT 0x2C +#define IPMI_CMD_GET_SEL_INFO 0x40 +#define IPMI_CMD_GET_SEL_ALLOC_INFO 0x41 +#define IPMI_CMD_RESERVE_SEL 0x42 +#define IPMI_CMD_GET_SEL_ENTRY 0x43 +#define IPMI_CMD_ADD_SEL_ENTRY 0x44 +#define IPMI_CMD_PARTIAL_ADD_SEL_ENTRY 0x45 +#define IPMI_CMD_DELETE_SEL_ENTRY 0x46 +#define IPMI_CMD_CLEAR_SEL 0x47 +#define IPMI_CMD_GET_SEL_TIME 0x48 +#define IPMI_CMD_SET_SEL_TIME 0x49 + + +/* Same as a timespec struct. */ +struct ipmi_time { + long tv_sec; + long tv_nsec; +}; + +#define MAX_SEL_SIZE 128 + +typedef struct IPMISel { + uint8_t sel[MAX_SEL_SIZE][16]; + unsigned int next_free; + long time_offset; + uint16_t reservation; + uint8_t last_addition[4]; + uint8_t last_clear[4]; + uint8_t overflow; +} IPMISel; + +#define MAX_SDR_SIZE 16384 + +typedef struct IPMISdr { + uint8_t sdr[MAX_SDR_SIZE]; + unsigned int next_free; + uint16_t next_rec_id; + uint16_t reservation; + uint8_t last_addition[4]; + uint8_t last_clear[4]; + uint8_t overflow; +} IPMISdr; + +typedef struct IPMISensor { + uint8_t status; + uint8_t reading; + uint16_t states_suppt; + uint16_t assert_suppt; + uint16_t deassert_suppt; + uint16_t states; + uint16_t assert_states; + uint16_t deassert_states; + uint16_t assert_enable; + uint16_t deassert_enable; + uint8_t sensor_type; + uint8_t evt_reading_type_code; +} IPMISensor; +#define IPMI_SENSOR_GET_PRESENT(s) ((s)->status & 0x01) +#define IPMI_SENSOR_SET_PRESENT(s, v) ((s)->status = (s->status & ~0x01) | \ + !!(v)) +#define IPMI_SENSOR_GET_SCAN_ON(s) ((s)->status & 0x40) +#define IPMI_SENSOR_SET_SCAN_ON(s, v) ((s)->status = (s->status & ~0x40) | \ + ((!!(v)) << 6)) +#define IPMI_SENSOR_GET_EVENTS_ON(s) ((s)->status & 0x80) +#define IPMI_SENSOR_SET_EVENTS_ON(s, v) ((s)->status = (s->status & ~0x80) | \ + ((!!(v)) << 7)) +#define IPMI_SENSOR_GET_RET_STATUS(s) ((s)->status & 0xc0) +#define IPMI_SENSOR_SET_RET_STATUS(s, v) ((s)->status = (s->status & ~0xc0) | \ + (v & 0xc0)) +#define IPMI_SENSOR_IS_DISCRETE(s) ((s)->evt_reading_type_code != 1) + +#define MAX_SENSORS 20 +#define IPMI_WATCHDOG_SENSOR 0 + +typedef struct IPMIBmcSim IPMIBmcSim; +typedef struct RspBuffer RspBuffer; + +#define MAX_NETFNS 64 + +typedef struct IPMICmdHandler { + void (*cmd_handler)(IPMIBmcSim *s, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp); + unsigned int cmd_len_min; +} IPMICmdHandler; + +typedef struct IPMINetfn { + unsigned int cmd_nums; + const IPMICmdHandler *cmd_handlers; +} IPMINetfn; + +typedef struct IPMIRcvBufEntry { + QTAILQ_ENTRY(IPMIRcvBufEntry) entry; + uint8_t len; + uint8_t buf[MAX_IPMI_MSG_SIZE]; +} IPMIRcvBufEntry; + +#define TYPE_IPMI_BMC_SIMULATOR "ipmi-bmc-sim" +#define IPMI_BMC_SIMULATOR(obj) OBJECT_CHECK(IPMIBmcSim, (obj), \ + TYPE_IPMI_BMC_SIMULATOR) +struct IPMIBmcSim { + IPMIBmc parent; + + QEMUTimer *timer; + + uint8_t bmc_global_enables; + uint8_t msg_flags; + + bool watchdog_initialized; + uint8_t watchdog_use; + uint8_t watchdog_action; + uint8_t watchdog_pretimeout; /* In seconds */ + bool watchdog_expired; + uint16_t watchdog_timeout; /* in 100's of milliseconds */ + + bool watchdog_running; + bool watchdog_preaction_ran; + int64_t watchdog_expiry; + + uint8_t device_id; + uint8_t ipmi_version; + uint8_t device_rev; + uint8_t fwrev1; + uint8_t fwrev2; + uint8_t mfg_id[3]; + uint8_t product_id[2]; + + uint8_t restart_cause; + + uint8_t acpi_power_state[2]; + uint8_t uuid[16]; + + IPMISel sel; + IPMISdr sdr; + IPMISensor sensors[MAX_SENSORS]; + + /* Odd netfns are for responses, so we only need the even ones. */ + const IPMINetfn *netfns[MAX_NETFNS / 2]; + + QemuMutex lock; + /* We allow one event in the buffer */ + uint8_t evtbuf[16]; + + QTAILQ_HEAD(, IPMIRcvBufEntry) rcvbufs; +}; + +#define IPMI_BMC_MSG_FLAG_WATCHDOG_TIMEOUT_MASK (1 << 3) +#define IPMI_BMC_MSG_FLAG_EVT_BUF_FULL (1 << 1) +#define IPMI_BMC_MSG_FLAG_RCV_MSG_QUEUE (1 << 0) +#define IPMI_BMC_MSG_FLAG_WATCHDOG_TIMEOUT_MASK_SET(s) \ + (IPMI_BMC_MSG_FLAG_WATCHDOG_TIMEOUT_MASK & (s)->msg_flags) +#define IPMI_BMC_MSG_FLAG_EVT_BUF_FULL_SET(s) \ + (IPMI_BMC_MSG_FLAG_EVT_BUF_FULL & (s)->msg_flags) +#define IPMI_BMC_MSG_FLAG_RCV_MSG_QUEUE_SET(s) \ + (IPMI_BMC_MSG_FLAG_RCV_MSG_QUEUE & (s)->msg_flags) + +#define IPMI_BMC_RCV_MSG_QUEUE_INT_BIT 0 +#define IPMI_BMC_EVBUF_FULL_INT_BIT 1 +#define IPMI_BMC_EVENT_MSG_BUF_BIT 2 +#define IPMI_BMC_EVENT_LOG_BIT 3 +#define IPMI_BMC_MSG_INTS_ON(s) ((s)->bmc_global_enables & \ + (1 << IPMI_BMC_RCV_MSG_QUEUE_INT_BIT)) +#define IPMI_BMC_EVBUF_FULL_INT_ENABLED(s) ((s)->bmc_global_enables & \ + (1 << IPMI_BMC_EVBUF_FULL_INT_BIT)) +#define IPMI_BMC_EVENT_LOG_ENABLED(s) ((s)->bmc_global_enables & \ + (1 << IPMI_BMC_EVENT_LOG_BIT)) +#define IPMI_BMC_EVENT_MSG_BUF_ENABLED(s) ((s)->bmc_global_enables & \ + (1 << IPMI_BMC_EVENT_MSG_BUF_BIT)) + +#define IPMI_BMC_WATCHDOG_USE_MASK 0xc7 +#define IPMI_BMC_WATCHDOG_ACTION_MASK 0x77 +#define IPMI_BMC_WATCHDOG_GET_USE(s) ((s)->watchdog_use & 0x7) +#define IPMI_BMC_WATCHDOG_GET_DONT_LOG(s) (((s)->watchdog_use >> 7) & 0x1) +#define IPMI_BMC_WATCHDOG_GET_DONT_STOP(s) (((s)->watchdog_use >> 6) & 0x1) +#define IPMI_BMC_WATCHDOG_GET_PRE_ACTION(s) (((s)->watchdog_action >> 4) & 0x7) +#define IPMI_BMC_WATCHDOG_PRE_NONE 0 +#define IPMI_BMC_WATCHDOG_PRE_SMI 1 +#define IPMI_BMC_WATCHDOG_PRE_NMI 2 +#define IPMI_BMC_WATCHDOG_PRE_MSG_INT 3 +#define IPMI_BMC_WATCHDOG_GET_ACTION(s) ((s)->watchdog_action & 0x7) +#define IPMI_BMC_WATCHDOG_ACTION_NONE 0 +#define IPMI_BMC_WATCHDOG_ACTION_RESET 1 +#define IPMI_BMC_WATCHDOG_ACTION_POWER_DOWN 2 +#define IPMI_BMC_WATCHDOG_ACTION_POWER_CYCLE 3 + +struct RspBuffer { + uint8_t buffer[MAX_IPMI_MSG_SIZE]; + unsigned int len; +}; + +#define RSP_BUFFER_INITIALIZER { } + +static inline void rsp_buffer_set_error(RspBuffer *rsp, uint8_t byte) +{ + rsp->buffer[2] = byte; +} + +/* Add a byte to the response. */ +static inline void rsp_buffer_push(RspBuffer *rsp, uint8_t byte) +{ + if (rsp->len >= sizeof(rsp->buffer)) { + rsp_buffer_set_error(rsp, IPMI_CC_REQUEST_DATA_TRUNCATED); + return; + } + rsp->buffer[rsp->len++] = byte; +} + +static inline void rsp_buffer_pushmore(RspBuffer *rsp, uint8_t *bytes, + unsigned int n) +{ + if (rsp->len + n >= sizeof(rsp->buffer)) { + rsp_buffer_set_error(rsp, IPMI_CC_REQUEST_DATA_TRUNCATED); + return; + } + + memcpy(&rsp->buffer[rsp->len], bytes, n); + rsp->len += n; +} + +static void ipmi_sim_handle_timeout(IPMIBmcSim *ibs); + +static void ipmi_gettime(struct ipmi_time *time) +{ + int64_t stime; + + stime = qemu_clock_get_ns(QEMU_CLOCK_HOST); + time->tv_sec = stime / 1000000000LL; + time->tv_nsec = stime % 1000000000LL; +} + +static int64_t ipmi_getmonotime(void) +{ + return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +} + +static void ipmi_timeout(void *opaque) +{ + IPMIBmcSim *ibs = opaque; + + ipmi_sim_handle_timeout(ibs); +} + +static void set_timestamp(IPMIBmcSim *ibs, uint8_t *ts) +{ + unsigned int val; + struct ipmi_time now; + + ipmi_gettime(&now); + val = now.tv_sec + ibs->sel.time_offset; + ts[0] = val & 0xff; + ts[1] = (val >> 8) & 0xff; + ts[2] = (val >> 16) & 0xff; + ts[3] = (val >> 24) & 0xff; +} + +static void sdr_inc_reservation(IPMISdr *sdr) +{ + sdr->reservation++; + if (sdr->reservation == 0) { + sdr->reservation = 1; + } +} + +static int sdr_add_entry(IPMIBmcSim *ibs, + const struct ipmi_sdr_header *sdrh_entry, + unsigned int len, uint16_t *recid) +{ + struct ipmi_sdr_header *sdrh = + (struct ipmi_sdr_header *) &ibs->sdr.sdr[ibs->sdr.next_free]; + + if ((len < IPMI_SDR_HEADER_SIZE) || (len > 255)) { + return 1; + } + + if (ipmi_sdr_length(sdrh_entry) != len) { + return 1; + } + + if (ibs->sdr.next_free + len > MAX_SDR_SIZE) { + ibs->sdr.overflow = 1; + return 1; + } + + memcpy(sdrh, sdrh_entry, len); + sdrh->rec_id[0] = ibs->sdr.next_rec_id & 0xff; + sdrh->rec_id[1] = (ibs->sdr.next_rec_id >> 8) & 0xff; + sdrh->sdr_version = 0x51; /* Conform to IPMI 1.5 spec */ + + if (recid) { + *recid = ibs->sdr.next_rec_id; + } + ibs->sdr.next_rec_id++; + set_timestamp(ibs, ibs->sdr.last_addition); + ibs->sdr.next_free += len; + sdr_inc_reservation(&ibs->sdr); + return 0; +} + +static int sdr_find_entry(IPMISdr *sdr, uint16_t recid, + unsigned int *retpos, uint16_t *nextrec) +{ + unsigned int pos = *retpos; + + while (pos < sdr->next_free) { + struct ipmi_sdr_header *sdrh = + (struct ipmi_sdr_header *) &sdr->sdr[pos]; + uint16_t trec = ipmi_sdr_recid(sdrh); + unsigned int nextpos = pos + ipmi_sdr_length(sdrh); + + if (trec == recid) { + if (nextrec) { + if (nextpos >= sdr->next_free) { + *nextrec = 0xffff; + } else { + *nextrec = (sdr->sdr[nextpos] | + (sdr->sdr[nextpos + 1] << 8)); + } + } + *retpos = pos; + return 0; + } + pos = nextpos; + } + return 1; +} + +static void sel_inc_reservation(IPMISel *sel) +{ + sel->reservation++; + if (sel->reservation == 0) { + sel->reservation = 1; + } +} + +/* Returns 1 if the SEL is full and can't hold the event. */ +static int sel_add_event(IPMIBmcSim *ibs, uint8_t *event) +{ + event[0] = 0xff; + event[1] = 0xff; + set_timestamp(ibs, event + 3); + if (ibs->sel.next_free == MAX_SEL_SIZE) { + ibs->sel.overflow = 1; + return 1; + } + event[0] = ibs->sel.next_free & 0xff; + event[1] = (ibs->sel.next_free >> 8) & 0xff; + memcpy(ibs->sel.last_addition, event + 3, 4); + memcpy(ibs->sel.sel[ibs->sel.next_free], event, 16); + ibs->sel.next_free++; + sel_inc_reservation(&ibs->sel); + return 0; +} + +static int attn_set(IPMIBmcSim *ibs) +{ + return IPMI_BMC_MSG_FLAG_RCV_MSG_QUEUE_SET(ibs) + || IPMI_BMC_MSG_FLAG_EVT_BUF_FULL_SET(ibs) + || IPMI_BMC_MSG_FLAG_WATCHDOG_TIMEOUT_MASK_SET(ibs); +} + +static int attn_irq_enabled(IPMIBmcSim *ibs) +{ + return (IPMI_BMC_MSG_INTS_ON(ibs) && IPMI_BMC_MSG_FLAG_RCV_MSG_QUEUE_SET(ibs)) + || (IPMI_BMC_EVBUF_FULL_INT_ENABLED(ibs) && + IPMI_BMC_MSG_FLAG_EVT_BUF_FULL_SET(ibs)); +} + +static void gen_event(IPMIBmcSim *ibs, unsigned int sens_num, uint8_t deassert, + uint8_t evd1, uint8_t evd2, uint8_t evd3) +{ + IPMIInterface *s = ibs->parent.intf; + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + uint8_t evt[16]; + IPMISensor *sens = ibs->sensors + sens_num; + + if (!IPMI_BMC_EVENT_MSG_BUF_ENABLED(ibs)) { + return; + } + if (!IPMI_SENSOR_GET_EVENTS_ON(sens)) { + return; + } + + evt[2] = 0x2; /* System event record */ + evt[7] = ibs->parent.slave_addr; + evt[8] = 0; + evt[9] = 0x04; /* Format version */ + evt[10] = sens->sensor_type; + evt[11] = sens_num; + evt[12] = sens->evt_reading_type_code | (!!deassert << 7); + evt[13] = evd1; + evt[14] = evd2; + evt[15] = evd3; + + if (IPMI_BMC_EVENT_LOG_ENABLED(ibs)) { + sel_add_event(ibs, evt); + } + + if (ibs->msg_flags & IPMI_BMC_MSG_FLAG_EVT_BUF_FULL) { + return; + } + + memcpy(ibs->evtbuf, evt, 16); + ibs->msg_flags |= IPMI_BMC_MSG_FLAG_EVT_BUF_FULL; + k->set_atn(s, 1, attn_irq_enabled(ibs)); +} + +static void sensor_set_discrete_bit(IPMIBmcSim *ibs, unsigned int sensor, + unsigned int bit, unsigned int val, + uint8_t evd1, uint8_t evd2, uint8_t evd3) +{ + IPMISensor *sens; + uint16_t mask; + + if (sensor >= MAX_SENSORS) { + return; + } + if (bit >= 16) { + return; + } + + mask = (1 << bit); + sens = ibs->sensors + sensor; + if (val) { + sens->states |= mask & sens->states_suppt; + if (sens->assert_states & mask) { + return; /* Already asserted */ + } + sens->assert_states |= mask & sens->assert_suppt; + if (sens->assert_enable & mask & sens->assert_states) { + /* Send an event on assert */ + gen_event(ibs, sensor, 0, evd1, evd2, evd3); + } + } else { + sens->states &= ~(mask & sens->states_suppt); + if (sens->deassert_states & mask) { + return; /* Already deasserted */ + } + sens->deassert_states |= mask & sens->deassert_suppt; + if (sens->deassert_enable & mask & sens->deassert_states) { + /* Send an event on deassert */ + gen_event(ibs, sensor, 1, evd1, evd2, evd3); + } + } +} + +static void ipmi_init_sensors_from_sdrs(IPMIBmcSim *s) +{ + unsigned int i, pos; + IPMISensor *sens; + + for (i = 0; i < MAX_SENSORS; i++) { + memset(s->sensors + i, 0, sizeof(*sens)); + } + + pos = 0; + for (i = 0; !sdr_find_entry(&s->sdr, i, &pos, NULL); i++) { + struct ipmi_sdr_compact *sdr = + (struct ipmi_sdr_compact *) &s->sdr.sdr[pos]; + unsigned int len = sdr->header.rec_length; + + if (len < 20) { + continue; + } + if (sdr->header.rec_type != IPMI_SDR_COMPACT_TYPE) { + continue; /* Not a sensor SDR we set from */ + } + + if (sdr->sensor_owner_number >= MAX_SENSORS) { + continue; + } + sens = s->sensors + sdr->sensor_owner_number; + + IPMI_SENSOR_SET_PRESENT(sens, 1); + IPMI_SENSOR_SET_SCAN_ON(sens, (sdr->sensor_init >> 6) & 1); + IPMI_SENSOR_SET_EVENTS_ON(sens, (sdr->sensor_init >> 5) & 1); + sens->assert_suppt = sdr->assert_mask[0] | (sdr->assert_mask[1] << 8); + sens->deassert_suppt = + sdr->deassert_mask[0] | (sdr->deassert_mask[1] << 8); + sens->states_suppt = + sdr->discrete_mask[0] | (sdr->discrete_mask[1] << 8); + sens->sensor_type = sdr->sensor_type; + sens->evt_reading_type_code = sdr->reading_type & 0x7f; + + /* Enable all the events that are supported. */ + sens->assert_enable = sens->assert_suppt; + sens->deassert_enable = sens->deassert_suppt; + } +} + +static int ipmi_register_netfn(IPMIBmcSim *s, unsigned int netfn, + const IPMINetfn *netfnd) +{ + if ((netfn & 1) || (netfn >= MAX_NETFNS) || (s->netfns[netfn / 2])) { + return -1; + } + s->netfns[netfn / 2] = netfnd; + return 0; +} + +static const IPMICmdHandler *ipmi_get_handler(IPMIBmcSim *ibs, + unsigned int netfn, + unsigned int cmd) +{ + const IPMICmdHandler *hdl; + + if (netfn & 1 || netfn >= MAX_NETFNS || !ibs->netfns[netfn / 2]) { + return NULL; + } + + if (cmd >= ibs->netfns[netfn / 2]->cmd_nums) { + return NULL; + } + + hdl = &ibs->netfns[netfn / 2]->cmd_handlers[cmd]; + if (!hdl->cmd_handler) { + return NULL; + } + + return hdl; +} + +static void next_timeout(IPMIBmcSim *ibs) +{ + int64_t next; + if (ibs->watchdog_running) { + next = ibs->watchdog_expiry; + } else { + /* Wait a minute */ + next = ipmi_getmonotime() + 60 * 1000000000LL; + } + timer_mod_ns(ibs->timer, next); +} + +static void ipmi_sim_handle_command(IPMIBmc *b, + uint8_t *cmd, unsigned int cmd_len, + unsigned int max_cmd_len, + uint8_t msg_id) +{ + IPMIBmcSim *ibs = IPMI_BMC_SIMULATOR(b); + IPMIInterface *s = ibs->parent.intf; + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + const IPMICmdHandler *hdl; + RspBuffer rsp = RSP_BUFFER_INITIALIZER; + + /* Set up the response, set the low bit of NETFN. */ + /* Note that max_rsp_len must be at least 3 */ + if (sizeof(rsp.buffer) < 3) { + rsp_buffer_set_error(&rsp, IPMI_CC_REQUEST_DATA_TRUNCATED); + goto out; + } + + rsp_buffer_push(&rsp, cmd[0] | 0x04); + rsp_buffer_push(&rsp, cmd[1]); + rsp_buffer_push(&rsp, 0); /* Assume success */ + + /* If it's too short or it was truncated, return an error. */ + if (cmd_len < 2) { + rsp_buffer_set_error(&rsp, IPMI_CC_REQUEST_DATA_LENGTH_INVALID); + goto out; + } + if (cmd_len > max_cmd_len) { + rsp_buffer_set_error(&rsp, IPMI_CC_REQUEST_DATA_TRUNCATED); + goto out; + } + + if ((cmd[0] & 0x03) != 0) { + /* Only have stuff on LUN 0 */ + rsp_buffer_set_error(&rsp, IPMI_CC_COMMAND_INVALID_FOR_LUN); + goto out; + } + + hdl = ipmi_get_handler(ibs, cmd[0] >> 2, cmd[1]); + if (!hdl) { + rsp_buffer_set_error(&rsp, IPMI_CC_INVALID_CMD); + goto out; + } + + if (cmd_len < hdl->cmd_len_min) { + rsp_buffer_set_error(&rsp, IPMI_CC_REQUEST_DATA_LENGTH_INVALID); + goto out; + } + + hdl->cmd_handler(ibs, cmd, cmd_len, &rsp); + + out: + k->handle_rsp(s, msg_id, rsp.buffer, rsp.len); + + next_timeout(ibs); +} + +static void ipmi_sim_handle_timeout(IPMIBmcSim *ibs) +{ + IPMIInterface *s = ibs->parent.intf; + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + + if (!ibs->watchdog_running) { + goto out; + } + + if (!ibs->watchdog_preaction_ran) { + switch (IPMI_BMC_WATCHDOG_GET_PRE_ACTION(ibs)) { + case IPMI_BMC_WATCHDOG_PRE_NMI: + ibs->msg_flags |= IPMI_BMC_MSG_FLAG_WATCHDOG_TIMEOUT_MASK; + k->do_hw_op(s, IPMI_SEND_NMI, 0); + sensor_set_discrete_bit(ibs, IPMI_WATCHDOG_SENSOR, 8, 1, + 0xc8, (2 << 4) | 0xf, 0xff); + break; + + case IPMI_BMC_WATCHDOG_PRE_MSG_INT: + ibs->msg_flags |= IPMI_BMC_MSG_FLAG_WATCHDOG_TIMEOUT_MASK; + k->set_atn(s, 1, attn_irq_enabled(ibs)); + sensor_set_discrete_bit(ibs, IPMI_WATCHDOG_SENSOR, 8, 1, + 0xc8, (3 << 4) | 0xf, 0xff); + break; + + default: + goto do_full_expiry; + } + + ibs->watchdog_preaction_ran = 1; + /* Issued the pretimeout, do the rest of the timeout now. */ + ibs->watchdog_expiry = ipmi_getmonotime(); + ibs->watchdog_expiry += ibs->watchdog_pretimeout * 1000000000LL; + goto out; + } + + do_full_expiry: + ibs->watchdog_running = 0; /* Stop the watchdog on a timeout */ + ibs->watchdog_expired |= (1 << IPMI_BMC_WATCHDOG_GET_USE(ibs)); + switch (IPMI_BMC_WATCHDOG_GET_ACTION(ibs)) { + case IPMI_BMC_WATCHDOG_ACTION_NONE: + sensor_set_discrete_bit(ibs, IPMI_WATCHDOG_SENSOR, 0, 1, + 0xc0, ibs->watchdog_use & 0xf, 0xff); + break; + + case IPMI_BMC_WATCHDOG_ACTION_RESET: + sensor_set_discrete_bit(ibs, IPMI_WATCHDOG_SENSOR, 1, 1, + 0xc1, ibs->watchdog_use & 0xf, 0xff); + k->do_hw_op(s, IPMI_RESET_CHASSIS, 0); + break; + + case IPMI_BMC_WATCHDOG_ACTION_POWER_DOWN: + sensor_set_discrete_bit(ibs, IPMI_WATCHDOG_SENSOR, 2, 1, + 0xc2, ibs->watchdog_use & 0xf, 0xff); + k->do_hw_op(s, IPMI_POWEROFF_CHASSIS, 0); + break; + + case IPMI_BMC_WATCHDOG_ACTION_POWER_CYCLE: + sensor_set_discrete_bit(ibs, IPMI_WATCHDOG_SENSOR, 2, 1, + 0xc3, ibs->watchdog_use & 0xf, 0xff); + k->do_hw_op(s, IPMI_POWERCYCLE_CHASSIS, 0); + break; + } + + out: + next_timeout(ibs); +} + +static void chassis_capabilities(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + rsp_buffer_push(rsp, 0); + rsp_buffer_push(rsp, ibs->parent.slave_addr); + rsp_buffer_push(rsp, ibs->parent.slave_addr); + rsp_buffer_push(rsp, ibs->parent.slave_addr); + rsp_buffer_push(rsp, ibs->parent.slave_addr); +} + +static void chassis_status(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + rsp_buffer_push(rsp, 0x61); /* Unknown power restore, power is on */ + rsp_buffer_push(rsp, 0); + rsp_buffer_push(rsp, 0); + rsp_buffer_push(rsp, 0); +} + +static void chassis_control(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + IPMIInterface *s = ibs->parent.intf; + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + + switch (cmd[2] & 0xf) { + case 0: /* power down */ + rsp_buffer_set_error(rsp, k->do_hw_op(s, IPMI_POWEROFF_CHASSIS, 0)); + break; + case 1: /* power up */ + rsp_buffer_set_error(rsp, k->do_hw_op(s, IPMI_POWERON_CHASSIS, 0)); + break; + case 2: /* power cycle */ + rsp_buffer_set_error(rsp, k->do_hw_op(s, IPMI_POWERCYCLE_CHASSIS, 0)); + break; + case 3: /* hard reset */ + rsp_buffer_set_error(rsp, k->do_hw_op(s, IPMI_RESET_CHASSIS, 0)); + break; + case 4: /* pulse diagnostic interrupt */ + rsp_buffer_set_error(rsp, k->do_hw_op(s, IPMI_PULSE_DIAG_IRQ, 0)); + break; + case 5: /* soft shutdown via ACPI by overtemp emulation */ + rsp_buffer_set_error(rsp, k->do_hw_op(s, + IPMI_SHUTDOWN_VIA_ACPI_OVERTEMP, 0)); + break; + default: + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_DATA_FIELD); + return; + } +} + +static void chassis_get_sys_restart_cause(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) + +{ + rsp_buffer_push(rsp, ibs->restart_cause & 0xf); /* Restart Cause */ + rsp_buffer_push(rsp, 0); /* Channel 0 */ +} + +static void get_device_id(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + rsp_buffer_push(rsp, ibs->device_id); + rsp_buffer_push(rsp, ibs->device_rev & 0xf); + rsp_buffer_push(rsp, ibs->fwrev1 & 0x7f); + rsp_buffer_push(rsp, ibs->fwrev2); + rsp_buffer_push(rsp, ibs->ipmi_version); + rsp_buffer_push(rsp, 0x07); /* sensor, SDR, and SEL. */ + rsp_buffer_push(rsp, ibs->mfg_id[0]); + rsp_buffer_push(rsp, ibs->mfg_id[1]); + rsp_buffer_push(rsp, ibs->mfg_id[2]); + rsp_buffer_push(rsp, ibs->product_id[0]); + rsp_buffer_push(rsp, ibs->product_id[1]); +} + +static void set_global_enables(IPMIBmcSim *ibs, uint8_t val) +{ + IPMIInterface *s = ibs->parent.intf; + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + bool irqs_on; + + ibs->bmc_global_enables = val; + + irqs_on = val & (IPMI_BMC_EVBUF_FULL_INT_BIT | + IPMI_BMC_RCV_MSG_QUEUE_INT_BIT); + + k->set_irq_enable(s, irqs_on); +} + +static void cold_reset(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + IPMIInterface *s = ibs->parent.intf; + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + + /* Disable all interrupts */ + set_global_enables(ibs, 1 << IPMI_BMC_EVENT_LOG_BIT); + + if (k->reset) { + k->reset(s, true); + } +} + +static void warm_reset(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + IPMIInterface *s = ibs->parent.intf; + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + + if (k->reset) { + k->reset(s, false); + } +} +static void set_acpi_power_state(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + ibs->acpi_power_state[0] = cmd[2]; + ibs->acpi_power_state[1] = cmd[3]; +} + +static void get_acpi_power_state(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + rsp_buffer_push(rsp, ibs->acpi_power_state[0]); + rsp_buffer_push(rsp, ibs->acpi_power_state[1]); +} + +static void get_device_guid(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + unsigned int i; + + for (i = 0; i < 16; i++) { + rsp_buffer_push(rsp, ibs->uuid[i]); + } +} + +static void set_bmc_global_enables(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + set_global_enables(ibs, cmd[2]); +} + +static void get_bmc_global_enables(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + rsp_buffer_push(rsp, ibs->bmc_global_enables); +} + +static void clr_msg_flags(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + IPMIInterface *s = ibs->parent.intf; + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + + ibs->msg_flags &= ~cmd[2]; + k->set_atn(s, attn_set(ibs), attn_irq_enabled(ibs)); +} + +static void get_msg_flags(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + rsp_buffer_push(rsp, ibs->msg_flags); +} + +static void read_evt_msg_buf(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + IPMIInterface *s = ibs->parent.intf; + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + unsigned int i; + + if (!(ibs->msg_flags & IPMI_BMC_MSG_FLAG_EVT_BUF_FULL)) { + rsp_buffer_set_error(rsp, 0x80); + return; + } + for (i = 0; i < 16; i++) { + rsp_buffer_push(rsp, ibs->evtbuf[i]); + } + ibs->msg_flags &= ~IPMI_BMC_MSG_FLAG_EVT_BUF_FULL; + k->set_atn(s, attn_set(ibs), attn_irq_enabled(ibs)); +} + +static void get_msg(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + IPMIRcvBufEntry *msg; + + qemu_mutex_lock(&ibs->lock); + if (QTAILQ_EMPTY(&ibs->rcvbufs)) { + rsp_buffer_set_error(rsp, 0x80); /* Queue empty */ + goto out; + } + rsp_buffer_push(rsp, 0); /* Channel 0 */ + msg = QTAILQ_FIRST(&ibs->rcvbufs); + rsp_buffer_pushmore(rsp, msg->buf, msg->len); + QTAILQ_REMOVE(&ibs->rcvbufs, msg, entry); + g_free(msg); + + if (QTAILQ_EMPTY(&ibs->rcvbufs)) { + IPMIInterface *s = ibs->parent.intf; + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + + ibs->msg_flags &= ~IPMI_BMC_MSG_FLAG_RCV_MSG_QUEUE; + k->set_atn(s, attn_set(ibs), attn_irq_enabled(ibs)); + } + +out: + qemu_mutex_unlock(&ibs->lock); + return; +} + +static unsigned char +ipmb_checksum(unsigned char *data, int size, unsigned char csum) +{ + for (; size > 0; size--, data++) { + csum += *data; + } + + return -csum; +} + +static void send_msg(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + IPMIInterface *s = ibs->parent.intf; + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + IPMIRcvBufEntry *msg; + uint8_t *buf; + uint8_t netfn, rqLun, rsLun, rqSeq; + + if (cmd[2] != 0) { + /* We only handle channel 0 with no options */ + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_DATA_FIELD); + return; + } + + if (cmd_len < 10) { + rsp_buffer_set_error(rsp, IPMI_CC_REQUEST_DATA_LENGTH_INVALID); + return; + } + + if (cmd[3] != 0x40) { + /* We only emulate a MC at address 0x40. */ + rsp_buffer_set_error(rsp, 0x83); /* NAK on write */ + return; + } + + cmd += 3; /* Skip the header. */ + cmd_len -= 3; + + /* + * At this point we "send" the message successfully. Any error will + * be returned in the response. + */ + if (ipmb_checksum(cmd, cmd_len, 0) != 0 || + cmd[3] != 0x20) { /* Improper response address */ + return; /* No response */ + } + + netfn = cmd[1] >> 2; + rqLun = cmd[4] & 0x3; + rsLun = cmd[1] & 0x3; + rqSeq = cmd[4] >> 2; + + if (rqLun != 2) { + /* We only support LUN 2 coming back to us. */ + return; + } + + msg = g_malloc(sizeof(*msg)); + msg->buf[0] = ((netfn | 1) << 2) | rqLun; /* NetFN, and make a response */ + msg->buf[1] = ipmb_checksum(msg->buf, 1, 0); + msg->buf[2] = cmd[0]; /* rsSA */ + msg->buf[3] = (rqSeq << 2) | rsLun; + msg->buf[4] = cmd[5]; /* Cmd */ + msg->buf[5] = 0; /* Completion Code */ + msg->len = 6; + + if ((cmd[1] >> 2) != IPMI_NETFN_APP || cmd[5] != IPMI_CMD_GET_DEVICE_ID) { + /* Not a command we handle. */ + msg->buf[5] = IPMI_CC_INVALID_CMD; + goto end_msg; + } + + buf = msg->buf + msg->len; /* After the CC */ + buf[0] = 0; + buf[1] = 0; + buf[2] = 0; + buf[3] = 0; + buf[4] = 0x51; + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + buf[8] = 0; + buf[9] = 0; + buf[10] = 0; + msg->len += 11; + + end_msg: + msg->buf[msg->len] = ipmb_checksum(msg->buf, msg->len, 0); + msg->len++; + qemu_mutex_lock(&ibs->lock); + QTAILQ_INSERT_TAIL(&ibs->rcvbufs, msg, entry); + ibs->msg_flags |= IPMI_BMC_MSG_FLAG_RCV_MSG_QUEUE; + k->set_atn(s, 1, attn_irq_enabled(ibs)); + qemu_mutex_unlock(&ibs->lock); +} + +static void do_watchdog_reset(IPMIBmcSim *ibs) +{ + if (IPMI_BMC_WATCHDOG_GET_ACTION(ibs) == + IPMI_BMC_WATCHDOG_ACTION_NONE) { + ibs->watchdog_running = 0; + return; + } + ibs->watchdog_preaction_ran = 0; + + + /* Timeout is in tenths of a second, offset is in seconds */ + ibs->watchdog_expiry = ipmi_getmonotime(); + ibs->watchdog_expiry += ibs->watchdog_timeout * 100000000LL; + if (IPMI_BMC_WATCHDOG_GET_PRE_ACTION(ibs) != IPMI_BMC_WATCHDOG_PRE_NONE) { + ibs->watchdog_expiry -= ibs->watchdog_pretimeout * 1000000000LL; + } + ibs->watchdog_running = 1; +} + +static void reset_watchdog_timer(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + if (!ibs->watchdog_initialized) { + rsp_buffer_set_error(rsp, 0x80); + return; + } + do_watchdog_reset(ibs); +} + +static void set_watchdog_timer(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + IPMIInterface *s = ibs->parent.intf; + IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); + unsigned int val; + + val = cmd[2] & 0x7; /* Validate use */ + if (val == 0 || val > 5) { + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_DATA_FIELD); + return; + } + val = cmd[3] & 0x7; /* Validate action */ + switch (val) { + case IPMI_BMC_WATCHDOG_ACTION_NONE: + break; + + case IPMI_BMC_WATCHDOG_ACTION_RESET: + rsp_buffer_set_error(rsp, k->do_hw_op(s, IPMI_RESET_CHASSIS, 1)); + break; + + case IPMI_BMC_WATCHDOG_ACTION_POWER_DOWN: + rsp_buffer_set_error(rsp, k->do_hw_op(s, IPMI_POWEROFF_CHASSIS, 1)); + break; + + case IPMI_BMC_WATCHDOG_ACTION_POWER_CYCLE: + rsp_buffer_set_error(rsp, k->do_hw_op(s, IPMI_POWERCYCLE_CHASSIS, 1)); + break; + + default: + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_DATA_FIELD); + } + if (rsp->buffer[2]) { + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_DATA_FIELD); + return; + } + + val = (cmd[3] >> 4) & 0x7; /* Validate preaction */ + switch (val) { + case IPMI_BMC_WATCHDOG_PRE_MSG_INT: + case IPMI_BMC_WATCHDOG_PRE_NONE: + break; + + case IPMI_BMC_WATCHDOG_PRE_NMI: + if (!k->do_hw_op(s, IPMI_SEND_NMI, 1)) { + /* NMI not supported. */ + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_DATA_FIELD); + return; + } + break; + + default: + /* We don't support PRE_SMI */ + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_DATA_FIELD); + return; + } + + ibs->watchdog_initialized = 1; + ibs->watchdog_use = cmd[2] & IPMI_BMC_WATCHDOG_USE_MASK; + ibs->watchdog_action = cmd[3] & IPMI_BMC_WATCHDOG_ACTION_MASK; + ibs->watchdog_pretimeout = cmd[4]; + ibs->watchdog_expired &= ~cmd[5]; + ibs->watchdog_timeout = cmd[6] | (((uint16_t) cmd[7]) << 8); + if (ibs->watchdog_running & IPMI_BMC_WATCHDOG_GET_DONT_STOP(ibs)) { + do_watchdog_reset(ibs); + } else { + ibs->watchdog_running = 0; + } +} + +static void get_watchdog_timer(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + rsp_buffer_push(rsp, ibs->watchdog_use); + rsp_buffer_push(rsp, ibs->watchdog_action); + rsp_buffer_push(rsp, ibs->watchdog_pretimeout); + rsp_buffer_push(rsp, ibs->watchdog_expired); + if (ibs->watchdog_running) { + long timeout; + timeout = ((ibs->watchdog_expiry - ipmi_getmonotime() + 50000000) + / 100000000); + rsp_buffer_push(rsp, timeout & 0xff); + rsp_buffer_push(rsp, (timeout >> 8) & 0xff); + } else { + rsp_buffer_push(rsp, 0); + rsp_buffer_push(rsp, 0); + } +} + +static void get_sdr_rep_info(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + unsigned int i; + + rsp_buffer_push(rsp, 0x51); /* Conform to IPMI 1.5 spec */ + rsp_buffer_push(rsp, ibs->sdr.next_rec_id & 0xff); + rsp_buffer_push(rsp, (ibs->sdr.next_rec_id >> 8) & 0xff); + rsp_buffer_push(rsp, (MAX_SDR_SIZE - ibs->sdr.next_free) & 0xff); + rsp_buffer_push(rsp, ((MAX_SDR_SIZE - ibs->sdr.next_free) >> 8) & 0xff); + for (i = 0; i < 4; i++) { + rsp_buffer_push(rsp, ibs->sdr.last_addition[i]); + } + for (i = 0; i < 4; i++) { + rsp_buffer_push(rsp, ibs->sdr.last_clear[i]); + } + /* Only modal support, reserve supported */ + rsp_buffer_push(rsp, (ibs->sdr.overflow << 7) | 0x22); +} + +static void reserve_sdr_rep(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + rsp_buffer_push(rsp, ibs->sdr.reservation & 0xff); + rsp_buffer_push(rsp, (ibs->sdr.reservation >> 8) & 0xff); +} + +static void get_sdr(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + unsigned int pos; + uint16_t nextrec; + struct ipmi_sdr_header *sdrh; + + if (cmd[6]) { + if ((cmd[2] | (cmd[3] << 8)) != ibs->sdr.reservation) { + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_RESERVATION); + return; + } + } + + pos = 0; + if (sdr_find_entry(&ibs->sdr, cmd[4] | (cmd[5] << 8), + &pos, &nextrec)) { + rsp_buffer_set_error(rsp, IPMI_CC_REQ_ENTRY_NOT_PRESENT); + return; + } + + sdrh = (struct ipmi_sdr_header *) &ibs->sdr.sdr[pos]; + + if (cmd[6] > ipmi_sdr_length(sdrh)) { + rsp_buffer_set_error(rsp, IPMI_CC_PARM_OUT_OF_RANGE); + return; + } + + rsp_buffer_push(rsp, nextrec & 0xff); + rsp_buffer_push(rsp, (nextrec >> 8) & 0xff); + + if (cmd[7] == 0xff) { + cmd[7] = ipmi_sdr_length(sdrh) - cmd[6]; + } + + if ((cmd[7] + rsp->len) > sizeof(rsp->buffer)) { + rsp_buffer_set_error(rsp, IPMI_CC_CANNOT_RETURN_REQ_NUM_BYTES); + return; + } + + rsp_buffer_pushmore(rsp, ibs->sdr.sdr + pos + cmd[6], cmd[7]); +} + +static void add_sdr(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + uint16_t recid; + struct ipmi_sdr_header *sdrh = (struct ipmi_sdr_header *) cmd + 2; + + if (sdr_add_entry(ibs, sdrh, cmd_len - 2, &recid)) { + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_DATA_FIELD); + return; + } + rsp_buffer_push(rsp, recid & 0xff); + rsp_buffer_push(rsp, (recid >> 8) & 0xff); +} + +static void clear_sdr_rep(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + if ((cmd[2] | (cmd[3] << 8)) != ibs->sdr.reservation) { + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_RESERVATION); + return; + } + + if (cmd[4] != 'C' || cmd[5] != 'L' || cmd[6] != 'R') { + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_DATA_FIELD); + return; + } + if (cmd[7] == 0xaa) { + ibs->sdr.next_free = 0; + ibs->sdr.overflow = 0; + set_timestamp(ibs, ibs->sdr.last_clear); + rsp_buffer_push(rsp, 1); /* Erasure complete */ + sdr_inc_reservation(&ibs->sdr); + } else if (cmd[7] == 0) { + rsp_buffer_push(rsp, 1); /* Erasure complete */ + } else { + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_DATA_FIELD); + return; + } +} + +static void get_sel_info(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + unsigned int i, val; + + rsp_buffer_push(rsp, 0x51); /* Conform to IPMI 1.5 */ + rsp_buffer_push(rsp, ibs->sel.next_free & 0xff); + rsp_buffer_push(rsp, (ibs->sel.next_free >> 8) & 0xff); + val = (MAX_SEL_SIZE - ibs->sel.next_free) * 16; + rsp_buffer_push(rsp, val & 0xff); + rsp_buffer_push(rsp, (val >> 8) & 0xff); + for (i = 0; i < 4; i++) { + rsp_buffer_push(rsp, ibs->sel.last_addition[i]); + } + for (i = 0; i < 4; i++) { + rsp_buffer_push(rsp, ibs->sel.last_clear[i]); + } + /* Only support Reserve SEL */ + rsp_buffer_push(rsp, (ibs->sel.overflow << 7) | 0x02); +} + +static void reserve_sel(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + rsp_buffer_push(rsp, ibs->sel.reservation & 0xff); + rsp_buffer_push(rsp, (ibs->sel.reservation >> 8) & 0xff); +} + +static void get_sel_entry(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + unsigned int val; + + if (cmd[6]) { + if ((cmd[2] | (cmd[3] << 8)) != ibs->sel.reservation) { + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_RESERVATION); + return; + } + } + if (ibs->sel.next_free == 0) { + rsp_buffer_set_error(rsp, IPMI_CC_REQ_ENTRY_NOT_PRESENT); + return; + } + if (cmd[6] > 15) { + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_DATA_FIELD); + return; + } + if (cmd[7] == 0xff) { + cmd[7] = 16; + } else if ((cmd[7] + cmd[6]) > 16) { + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_DATA_FIELD); + return; + } else { + cmd[7] += cmd[6]; + } + + val = cmd[4] | (cmd[5] << 8); + if (val == 0xffff) { + val = ibs->sel.next_free - 1; + } else if (val >= ibs->sel.next_free) { + rsp_buffer_set_error(rsp, IPMI_CC_REQ_ENTRY_NOT_PRESENT); + return; + } + if ((val + 1) == ibs->sel.next_free) { + rsp_buffer_push(rsp, 0xff); + rsp_buffer_push(rsp, 0xff); + } else { + rsp_buffer_push(rsp, (val + 1) & 0xff); + rsp_buffer_push(rsp, ((val + 1) >> 8) & 0xff); + } + for (; cmd[6] < cmd[7]; cmd[6]++) { + rsp_buffer_push(rsp, ibs->sel.sel[val][cmd[6]]); + } +} + +static void add_sel_entry(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + if (sel_add_event(ibs, cmd + 2)) { + rsp_buffer_set_error(rsp, IPMI_CC_OUT_OF_SPACE); + return; + } + /* sel_add_event fills in the record number. */ + rsp_buffer_push(rsp, cmd[2]); + rsp_buffer_push(rsp, cmd[3]); +} + +static void clear_sel(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + if ((cmd[2] | (cmd[3] << 8)) != ibs->sel.reservation) { + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_RESERVATION); + return; + } + + if (cmd[4] != 'C' || cmd[5] != 'L' || cmd[6] != 'R') { + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_DATA_FIELD); + return; + } + if (cmd[7] == 0xaa) { + ibs->sel.next_free = 0; + ibs->sel.overflow = 0; + set_timestamp(ibs, ibs->sdr.last_clear); + rsp_buffer_push(rsp, 1); /* Erasure complete */ + sel_inc_reservation(&ibs->sel); + } else if (cmd[7] == 0) { + rsp_buffer_push(rsp, 1); /* Erasure complete */ + } else { + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_DATA_FIELD); + return; + } +} + +static void get_sel_time(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + uint32_t val; + struct ipmi_time now; + + ipmi_gettime(&now); + val = now.tv_sec + ibs->sel.time_offset; + rsp_buffer_push(rsp, val & 0xff); + rsp_buffer_push(rsp, (val >> 8) & 0xff); + rsp_buffer_push(rsp, (val >> 16) & 0xff); + rsp_buffer_push(rsp, (val >> 24) & 0xff); +} + +static void set_sel_time(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + uint32_t val; + struct ipmi_time now; + + val = cmd[2] | (cmd[3] << 8) | (cmd[4] << 16) | (cmd[5] << 24); + ipmi_gettime(&now); + ibs->sel.time_offset = now.tv_sec - ((long) val); +} + +static void set_sensor_evt_enable(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + IPMISensor *sens; + + if ((cmd[2] >= MAX_SENSORS) || + !IPMI_SENSOR_GET_PRESENT(ibs->sensors + cmd[2])) { + rsp_buffer_set_error(rsp, IPMI_CC_REQ_ENTRY_NOT_PRESENT); + return; + } + sens = ibs->sensors + cmd[2]; + switch ((cmd[3] >> 4) & 0x3) { + case 0: /* Do not change */ + break; + case 1: /* Enable bits */ + if (cmd_len > 4) { + sens->assert_enable |= cmd[4]; + } + if (cmd_len > 5) { + sens->assert_enable |= cmd[5] << 8; + } + if (cmd_len > 6) { + sens->deassert_enable |= cmd[6]; + } + if (cmd_len > 7) { + sens->deassert_enable |= cmd[7] << 8; + } + break; + case 2: /* Disable bits */ + if (cmd_len > 4) { + sens->assert_enable &= ~cmd[4]; + } + if (cmd_len > 5) { + sens->assert_enable &= ~(cmd[5] << 8); + } + if (cmd_len > 6) { + sens->deassert_enable &= ~cmd[6]; + } + if (cmd_len > 7) { + sens->deassert_enable &= ~(cmd[7] << 8); + } + break; + case 3: + rsp_buffer_set_error(rsp, IPMI_CC_INVALID_DATA_FIELD); + return; + } + IPMI_SENSOR_SET_RET_STATUS(sens, cmd[3]); +} + +static void get_sensor_evt_enable(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + IPMISensor *sens; + + if ((cmd[2] >= MAX_SENSORS) || + !IPMI_SENSOR_GET_PRESENT(ibs->sensors + cmd[2])) { + rsp_buffer_set_error(rsp, IPMI_CC_REQ_ENTRY_NOT_PRESENT); + return; + } + sens = ibs->sensors + cmd[2]; + rsp_buffer_push(rsp, IPMI_SENSOR_GET_RET_STATUS(sens)); + rsp_buffer_push(rsp, sens->assert_enable & 0xff); + rsp_buffer_push(rsp, (sens->assert_enable >> 8) & 0xff); + rsp_buffer_push(rsp, sens->deassert_enable & 0xff); + rsp_buffer_push(rsp, (sens->deassert_enable >> 8) & 0xff); +} + +static void rearm_sensor_evts(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + IPMISensor *sens; + + if ((cmd[2] >= MAX_SENSORS) || + !IPMI_SENSOR_GET_PRESENT(ibs->sensors + cmd[2])) { + rsp_buffer_set_error(rsp, IPMI_CC_REQ_ENTRY_NOT_PRESENT); + return; + } + sens = ibs->sensors + cmd[2]; + + if ((cmd[3] & 0x80) == 0) { + /* Just clear everything */ + sens->states = 0; + return; + } +} + +static void get_sensor_evt_status(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + IPMISensor *sens; + + if ((cmd[2] >= MAX_SENSORS) || + !IPMI_SENSOR_GET_PRESENT(ibs->sensors + cmd[2])) { + rsp_buffer_set_error(rsp, IPMI_CC_REQ_ENTRY_NOT_PRESENT); + return; + } + sens = ibs->sensors + cmd[2]; + rsp_buffer_push(rsp, sens->reading); + rsp_buffer_push(rsp, IPMI_SENSOR_GET_RET_STATUS(sens)); + rsp_buffer_push(rsp, sens->assert_states & 0xff); + rsp_buffer_push(rsp, (sens->assert_states >> 8) & 0xff); + rsp_buffer_push(rsp, sens->deassert_states & 0xff); + rsp_buffer_push(rsp, (sens->deassert_states >> 8) & 0xff); +} + +static void get_sensor_reading(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + IPMISensor *sens; + + if ((cmd[2] >= MAX_SENSORS) || + !IPMI_SENSOR_GET_PRESENT(ibs->sensors + cmd[2])) { + rsp_buffer_set_error(rsp, IPMI_CC_REQ_ENTRY_NOT_PRESENT); + return; + } + sens = ibs->sensors + cmd[2]; + rsp_buffer_push(rsp, sens->reading); + rsp_buffer_push(rsp, IPMI_SENSOR_GET_RET_STATUS(sens)); + rsp_buffer_push(rsp, sens->states & 0xff); + if (IPMI_SENSOR_IS_DISCRETE(sens)) { + rsp_buffer_push(rsp, (sens->states >> 8) & 0xff); + } +} + +static void set_sensor_type(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + IPMISensor *sens; + + + if ((cmd[2] >= MAX_SENSORS) || + !IPMI_SENSOR_GET_PRESENT(ibs->sensors + cmd[2])) { + rsp_buffer_set_error(rsp, IPMI_CC_REQ_ENTRY_NOT_PRESENT); + return; + } + sens = ibs->sensors + cmd[2]; + sens->sensor_type = cmd[3]; + sens->evt_reading_type_code = cmd[4] & 0x7f; +} + +static void get_sensor_type(IPMIBmcSim *ibs, + uint8_t *cmd, unsigned int cmd_len, + RspBuffer *rsp) +{ + IPMISensor *sens; + + + if ((cmd[2] >= MAX_SENSORS) || + !IPMI_SENSOR_GET_PRESENT(ibs->sensors + cmd[2])) { + rsp_buffer_set_error(rsp, IPMI_CC_REQ_ENTRY_NOT_PRESENT); + return; + } + sens = ibs->sensors + cmd[2]; + rsp_buffer_push(rsp, sens->sensor_type); + rsp_buffer_push(rsp, sens->evt_reading_type_code); +} + + +static const IPMICmdHandler chassis_cmds[] = { + [IPMI_CMD_GET_CHASSIS_CAPABILITIES] = { chassis_capabilities }, + [IPMI_CMD_GET_CHASSIS_STATUS] = { chassis_status }, + [IPMI_CMD_CHASSIS_CONTROL] = { chassis_control, 3 }, + [IPMI_CMD_GET_SYS_RESTART_CAUSE] = { chassis_get_sys_restart_cause } +}; +static const IPMINetfn chassis_netfn = { + .cmd_nums = ARRAY_SIZE(chassis_cmds), + .cmd_handlers = chassis_cmds +}; + +static const IPMICmdHandler sensor_event_cmds[] = { + [IPMI_CMD_SET_SENSOR_EVT_ENABLE] = { set_sensor_evt_enable, 4 }, + [IPMI_CMD_GET_SENSOR_EVT_ENABLE] = { get_sensor_evt_enable, 3 }, + [IPMI_CMD_REARM_SENSOR_EVTS] = { rearm_sensor_evts, 4 }, + [IPMI_CMD_GET_SENSOR_EVT_STATUS] = { get_sensor_evt_status, 3 }, + [IPMI_CMD_GET_SENSOR_READING] = { get_sensor_reading, 3 }, + [IPMI_CMD_SET_SENSOR_TYPE] = { set_sensor_type, 5 }, + [IPMI_CMD_GET_SENSOR_TYPE] = { get_sensor_type, 3 }, +}; +static const IPMINetfn sensor_event_netfn = { + .cmd_nums = ARRAY_SIZE(sensor_event_cmds), + .cmd_handlers = sensor_event_cmds +}; + +static const IPMICmdHandler app_cmds[] = { + [IPMI_CMD_GET_DEVICE_ID] = { get_device_id }, + [IPMI_CMD_COLD_RESET] = { cold_reset }, + [IPMI_CMD_WARM_RESET] = { warm_reset }, + [IPMI_CMD_SET_ACPI_POWER_STATE] = { set_acpi_power_state, 4 }, + [IPMI_CMD_GET_ACPI_POWER_STATE] = { get_acpi_power_state }, + [IPMI_CMD_GET_DEVICE_GUID] = { get_device_guid }, + [IPMI_CMD_SET_BMC_GLOBAL_ENABLES] = { set_bmc_global_enables, 3 }, + [IPMI_CMD_GET_BMC_GLOBAL_ENABLES] = { get_bmc_global_enables }, + [IPMI_CMD_CLR_MSG_FLAGS] = { clr_msg_flags, 3 }, + [IPMI_CMD_GET_MSG_FLAGS] = { get_msg_flags }, + [IPMI_CMD_GET_MSG] = { get_msg }, + [IPMI_CMD_SEND_MSG] = { send_msg, 3 }, + [IPMI_CMD_READ_EVT_MSG_BUF] = { read_evt_msg_buf }, + [IPMI_CMD_RESET_WATCHDOG_TIMER] = { reset_watchdog_timer }, + [IPMI_CMD_SET_WATCHDOG_TIMER] = { set_watchdog_timer, 8 }, + [IPMI_CMD_GET_WATCHDOG_TIMER] = { get_watchdog_timer }, +}; +static const IPMINetfn app_netfn = { + .cmd_nums = ARRAY_SIZE(app_cmds), + .cmd_handlers = app_cmds +}; + +static const IPMICmdHandler storage_cmds[] = { + [IPMI_CMD_GET_SDR_REP_INFO] = { get_sdr_rep_info }, + [IPMI_CMD_RESERVE_SDR_REP] = { reserve_sdr_rep }, + [IPMI_CMD_GET_SDR] = { get_sdr, 8 }, + [IPMI_CMD_ADD_SDR] = { add_sdr }, + [IPMI_CMD_CLEAR_SDR_REP] = { clear_sdr_rep, 8 }, + [IPMI_CMD_GET_SEL_INFO] = { get_sel_info }, + [IPMI_CMD_RESERVE_SEL] = { reserve_sel }, + [IPMI_CMD_GET_SEL_ENTRY] = { get_sel_entry, 8 }, + [IPMI_CMD_ADD_SEL_ENTRY] = { add_sel_entry, 18 }, + [IPMI_CMD_CLEAR_SEL] = { clear_sel, 8 }, + [IPMI_CMD_GET_SEL_TIME] = { get_sel_time, 6 }, + [IPMI_CMD_SET_SEL_TIME] = { set_sel_time }, +}; + +static const IPMINetfn storage_netfn = { + .cmd_nums = ARRAY_SIZE(storage_cmds), + .cmd_handlers = storage_cmds +}; + +static void register_cmds(IPMIBmcSim *s) +{ + ipmi_register_netfn(s, IPMI_NETFN_CHASSIS, &chassis_netfn); + ipmi_register_netfn(s, IPMI_NETFN_SENSOR_EVENT, &sensor_event_netfn); + ipmi_register_netfn(s, IPMI_NETFN_APP, &app_netfn); + ipmi_register_netfn(s, IPMI_NETFN_STORAGE, &storage_netfn); +} + +static uint8_t init_sdrs[] = { + /* Watchdog device */ + 0x00, 0x00, 0x51, 0x02, 35, 0x20, 0x00, 0x00, + 0x23, 0x01, 0x63, 0x00, 0x23, 0x6f, 0x0f, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, + 'W', 'a', 't', 'c', 'h', 'd', 'o', 'g', +}; + +static void ipmi_sdr_init(IPMIBmcSim *ibs) +{ + unsigned int i; + int len; + size_t sdrs_size; + uint8_t *sdrs; + + sdrs_size = sizeof(init_sdrs); + sdrs = init_sdrs; + + for (i = 0; i < sdrs_size; i += len) { + struct ipmi_sdr_header *sdrh; + + if (i + IPMI_SDR_HEADER_SIZE > sdrs_size) { + error_report("Problem with recid 0x%4.4x", i); + return; + } + sdrh = (struct ipmi_sdr_header *) &sdrs[i]; + len = ipmi_sdr_length(sdrh); + if (i + len > sdrs_size) { + error_report("Problem with recid 0x%4.4x", i); + return; + } + sdr_add_entry(ibs, sdrh, len, NULL); + } +} + +static const VMStateDescription vmstate_ipmi_sim = { + .name = TYPE_IPMI_BMC_SIMULATOR, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(bmc_global_enables, IPMIBmcSim), + VMSTATE_UINT8(msg_flags, IPMIBmcSim), + VMSTATE_BOOL(watchdog_initialized, IPMIBmcSim), + VMSTATE_UINT8(watchdog_use, IPMIBmcSim), + VMSTATE_UINT8(watchdog_action, IPMIBmcSim), + VMSTATE_UINT8(watchdog_pretimeout, IPMIBmcSim), + VMSTATE_BOOL(watchdog_expired, IPMIBmcSim), + VMSTATE_UINT16(watchdog_timeout, IPMIBmcSim), + VMSTATE_BOOL(watchdog_running, IPMIBmcSim), + VMSTATE_BOOL(watchdog_preaction_ran, IPMIBmcSim), + VMSTATE_INT64(watchdog_expiry, IPMIBmcSim), + VMSTATE_UINT8_ARRAY(evtbuf, IPMIBmcSim, 16), + VMSTATE_UINT8(sensors[IPMI_WATCHDOG_SENSOR].status, IPMIBmcSim), + VMSTATE_UINT8(sensors[IPMI_WATCHDOG_SENSOR].reading, IPMIBmcSim), + VMSTATE_UINT16(sensors[IPMI_WATCHDOG_SENSOR].states, IPMIBmcSim), + VMSTATE_UINT16(sensors[IPMI_WATCHDOG_SENSOR].assert_states, IPMIBmcSim), + VMSTATE_UINT16(sensors[IPMI_WATCHDOG_SENSOR].deassert_states, + IPMIBmcSim), + VMSTATE_UINT16(sensors[IPMI_WATCHDOG_SENSOR].assert_enable, IPMIBmcSim), + VMSTATE_END_OF_LIST() + } +}; + +static void ipmi_sim_realize(DeviceState *dev, Error **errp) +{ + IPMIBmc *b = IPMI_BMC(dev); + unsigned int i; + IPMIBmcSim *ibs = IPMI_BMC_SIMULATOR(b); + + qemu_mutex_init(&ibs->lock); + QTAILQ_INIT(&ibs->rcvbufs); + + ibs->bmc_global_enables = (1 << IPMI_BMC_EVENT_LOG_BIT); + ibs->device_id = 0x20; + ibs->ipmi_version = 0x02; /* IPMI 2.0 */ + ibs->restart_cause = 0; + for (i = 0; i < 4; i++) { + ibs->sel.last_addition[i] = 0xff; + ibs->sel.last_clear[i] = 0xff; + ibs->sdr.last_addition[i] = 0xff; + ibs->sdr.last_clear[i] = 0xff; + } + + ipmi_sdr_init(ibs); + + ibs->acpi_power_state[0] = 0; + ibs->acpi_power_state[1] = 0; + + if (qemu_uuid_set) { + memcpy(&ibs->uuid, qemu_uuid, 16); + } else { + memset(&ibs->uuid, 0, 16); + } + + ipmi_init_sensors_from_sdrs(ibs); + register_cmds(ibs); + + ibs->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, ipmi_timeout, ibs); + + vmstate_register(NULL, 0, &vmstate_ipmi_sim, ibs); +} + +static void ipmi_sim_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + IPMIBmcClass *bk = IPMI_BMC_CLASS(oc); + + dc->realize = ipmi_sim_realize; + bk->handle_command = ipmi_sim_handle_command; +} + +static const TypeInfo ipmi_sim_type = { + .name = TYPE_IPMI_BMC_SIMULATOR, + .parent = TYPE_IPMI_BMC, + .instance_size = sizeof(IPMIBmcSim), + .class_init = ipmi_sim_class_init, +}; + +static void ipmi_sim_register_types(void) +{ + type_register_static(&ipmi_sim_type); +} + +type_init(ipmi_sim_register_types) diff --git a/qemu/hw/ipmi/isa_ipmi_bt.c b/qemu/hw/ipmi/isa_ipmi_bt.c new file mode 100644 index 000000000..aaea12ecd --- /dev/null +++ b/qemu/hw/ipmi/isa_ipmi_bt.c @@ -0,0 +1,530 @@ +/* + * QEMU ISA IPMI BT emulation + * + * Copyright (c) 2015 Corey Minyard, MontaVista Software, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/hw.h" +#include "hw/ipmi/ipmi.h" +#include "hw/isa/isa.h" +#include "hw/i386/pc.h" + +/* Control register */ +#define IPMI_BT_CLR_WR_BIT 0 +#define IPMI_BT_CLR_RD_BIT 1 +#define IPMI_BT_H2B_ATN_BIT 2 +#define IPMI_BT_B2H_ATN_BIT 3 +#define IPMI_BT_SMS_ATN_BIT 4 +#define IPMI_BT_HBUSY_BIT 6 +#define IPMI_BT_BBUSY_BIT 7 + +#define IPMI_BT_CLR_WR_MASK (1 << IPMI_BT_CLR_WR_BIT) +#define IPMI_BT_GET_CLR_WR(d) (((d) >> IPMI_BT_CLR_WR_BIT) & 0x1) +#define IPMI_BT_SET_CLR_WR(d, v) (d) = (((d) & ~IPMI_BT_CLR_WR_MASK) | \ + (((v & 1) << IPMI_BT_CLR_WR_BIT))) + +#define IPMI_BT_CLR_RD_MASK (1 << IPMI_BT_CLR_RD_BIT) +#define IPMI_BT_GET_CLR_RD(d) (((d) >> IPMI_BT_CLR_RD_BIT) & 0x1) +#define IPMI_BT_SET_CLR_RD(d, v) (d) = (((d) & ~IPMI_BT_CLR_RD_MASK) | \ + (((v & 1) << IPMI_BT_CLR_RD_BIT))) + +#define IPMI_BT_H2B_ATN_MASK (1 << IPMI_BT_H2B_ATN_BIT) +#define IPMI_BT_GET_H2B_ATN(d) (((d) >> IPMI_BT_H2B_ATN_BIT) & 0x1) +#define IPMI_BT_SET_H2B_ATN(d, v) (d) = (((d) & ~IPMI_BT_H2B_ATN_MASK) | \ + (((v & 1) << IPMI_BT_H2B_ATN_BIT))) + +#define IPMI_BT_B2H_ATN_MASK (1 << IPMI_BT_B2H_ATN_BIT) +#define IPMI_BT_GET_B2H_ATN(d) (((d) >> IPMI_BT_B2H_ATN_BIT) & 0x1) +#define IPMI_BT_SET_B2H_ATN(d, v) (d) = (((d) & ~IPMI_BT_B2H_ATN_MASK) | \ + (((v & 1) << IPMI_BT_B2H_ATN_BIT))) + +#define IPMI_BT_SMS_ATN_MASK (1 << IPMI_BT_SMS_ATN_BIT) +#define IPMI_BT_GET_SMS_ATN(d) (((d) >> IPMI_BT_SMS_ATN_BIT) & 0x1) +#define IPMI_BT_SET_SMS_ATN(d, v) (d) = (((d) & ~IPMI_BT_SMS_ATN_MASK) | \ + (((v & 1) << IPMI_BT_SMS_ATN_BIT))) + +#define IPMI_BT_HBUSY_MASK (1 << IPMI_BT_HBUSY_BIT) +#define IPMI_BT_GET_HBUSY(d) (((d) >> IPMI_BT_HBUSY_BIT) & 0x1) +#define IPMI_BT_SET_HBUSY(d, v) (d) = (((d) & ~IPMI_BT_HBUSY_MASK) | \ + (((v & 1) << IPMI_BT_HBUSY_BIT))) + +#define IPMI_BT_BBUSY_MASK (1 << IPMI_BT_BBUSY_BIT) +#define IPMI_BT_GET_BBUSY(d) (((d) >> IPMI_BT_BBUSY_BIT) & 0x1) +#define IPMI_BT_SET_BBUSY(d, v) (d) = (((d) & ~IPMI_BT_BBUSY_MASK) | \ + (((v & 1) << IPMI_BT_BBUSY_BIT))) + + +/* Mask register */ +#define IPMI_BT_B2H_IRQ_EN_BIT 0 +#define IPMI_BT_B2H_IRQ_BIT 1 + +#define IPMI_BT_B2H_IRQ_EN_MASK (1 << IPMI_BT_B2H_IRQ_EN_BIT) +#define IPMI_BT_GET_B2H_IRQ_EN(d) (((d) >> IPMI_BT_B2H_IRQ_EN_BIT) & 0x1) +#define IPMI_BT_SET_B2H_IRQ_EN(d, v) (d) = (((d) & ~IPMI_BT_B2H_IRQ_EN_MASK) | \ + (((v & 1) << IPMI_BT_B2H_IRQ_EN_BIT))) + +#define IPMI_BT_B2H_IRQ_MASK (1 << IPMI_BT_B2H_IRQ_BIT) +#define IPMI_BT_GET_B2H_IRQ(d) (((d) >> IPMI_BT_B2H_IRQ_BIT) & 0x1) +#define IPMI_BT_SET_B2H_IRQ(d, v) (d) = (((d) & ~IPMI_BT_B2H_IRQ_MASK) | \ + (((v & 1) << IPMI_BT_B2H_IRQ_BIT))) + +typedef struct IPMIBT { + IPMIBmc *bmc; + + bool do_wake; + + qemu_irq irq; + + uint32_t io_base; + unsigned long io_length; + MemoryRegion io; + + bool obf_irq_set; + bool atn_irq_set; + bool use_irq; + bool irqs_enabled; + + uint8_t outmsg[MAX_IPMI_MSG_SIZE]; + uint32_t outpos; + uint32_t outlen; + + uint8_t inmsg[MAX_IPMI_MSG_SIZE]; + uint32_t inlen; + + uint8_t control_reg; + uint8_t mask_reg; + + /* + * This is a response number that we send with the command to make + * sure that the response matches the command. + */ + uint8_t waiting_rsp; + uint8_t waiting_seq; +} IPMIBT; + +#define IPMI_CMD_GET_BT_INTF_CAP 0x36 + +static void ipmi_bt_handle_event(IPMIInterface *ii) +{ + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + IPMIBT *ib = iic->get_backend_data(ii); + + if (ib->inlen < 4) { + goto out; + } + /* Note that overruns are handled by handle_command */ + if (ib->inmsg[0] != (ib->inlen - 1)) { + /* Length mismatch, just ignore. */ + IPMI_BT_SET_BBUSY(ib->control_reg, 1); + ib->inlen = 0; + goto out; + } + if ((ib->inmsg[1] == (IPMI_NETFN_APP << 2)) && + (ib->inmsg[3] == IPMI_CMD_GET_BT_INTF_CAP)) { + /* We handle this one ourselves. */ + ib->outmsg[0] = 9; + ib->outmsg[1] = ib->inmsg[1] | 0x04; + ib->outmsg[2] = ib->inmsg[2]; + ib->outmsg[3] = ib->inmsg[3]; + ib->outmsg[4] = 0; + ib->outmsg[5] = 1; /* Only support 1 outstanding request. */ + if (sizeof(ib->inmsg) > 0xff) { /* Input buffer size */ + ib->outmsg[6] = 0xff; + } else { + ib->outmsg[6] = (unsigned char) sizeof(ib->inmsg); + } + if (sizeof(ib->outmsg) > 0xff) { /* Output buffer size */ + ib->outmsg[7] = 0xff; + } else { + ib->outmsg[7] = (unsigned char) sizeof(ib->outmsg); + } + ib->outmsg[8] = 10; /* Max request to response time */ + ib->outmsg[9] = 0; /* Don't recommend retries */ + ib->outlen = 10; + IPMI_BT_SET_BBUSY(ib->control_reg, 0); + IPMI_BT_SET_B2H_ATN(ib->control_reg, 1); + if (ib->use_irq && ib->irqs_enabled && + !IPMI_BT_GET_B2H_IRQ(ib->mask_reg) && + IPMI_BT_GET_B2H_IRQ_EN(ib->mask_reg)) { + IPMI_BT_SET_B2H_IRQ(ib->mask_reg, 1); + qemu_irq_raise(ib->irq); + } + goto out; + } + ib->waiting_seq = ib->inmsg[2]; + ib->inmsg[2] = ib->inmsg[1]; + { + IPMIBmcClass *bk = IPMI_BMC_GET_CLASS(ib->bmc); + bk->handle_command(ib->bmc, ib->inmsg + 2, ib->inlen - 2, + sizeof(ib->inmsg), ib->waiting_rsp); + } + out: + return; +} + +static void ipmi_bt_handle_rsp(IPMIInterface *ii, uint8_t msg_id, + unsigned char *rsp, unsigned int rsp_len) +{ + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + IPMIBT *ib = iic->get_backend_data(ii); + + if (ib->waiting_rsp == msg_id) { + ib->waiting_rsp++; + if (rsp_len > (sizeof(ib->outmsg) - 2)) { + ib->outmsg[0] = 4; + ib->outmsg[1] = rsp[0]; + ib->outmsg[2] = ib->waiting_seq; + ib->outmsg[3] = rsp[1]; + ib->outmsg[4] = IPMI_CC_CANNOT_RETURN_REQ_NUM_BYTES; + ib->outlen = 5; + } else { + ib->outmsg[0] = rsp_len + 1; + ib->outmsg[1] = rsp[0]; + ib->outmsg[2] = ib->waiting_seq; + memcpy(ib->outmsg + 3, rsp + 1, rsp_len - 1); + ib->outlen = rsp_len + 2; + } + IPMI_BT_SET_BBUSY(ib->control_reg, 0); + IPMI_BT_SET_B2H_ATN(ib->control_reg, 1); + if (ib->use_irq && ib->irqs_enabled && + !IPMI_BT_GET_B2H_IRQ(ib->mask_reg) && + IPMI_BT_GET_B2H_IRQ_EN(ib->mask_reg)) { + IPMI_BT_SET_B2H_IRQ(ib->mask_reg, 1); + qemu_irq_raise(ib->irq); + } + } +} + + +static uint64_t ipmi_bt_ioport_read(void *opaque, hwaddr addr, unsigned size) +{ + IPMIInterface *ii = opaque; + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + IPMIBT *ib = iic->get_backend_data(ii); + uint32_t ret = 0xff; + + switch (addr & 3) { + case 0: + ret = ib->control_reg; + break; + case 1: + if (ib->outpos < ib->outlen) { + ret = ib->outmsg[ib->outpos]; + ib->outpos++; + if (ib->outpos == ib->outlen) { + ib->outpos = 0; + ib->outlen = 0; + } + } else { + ret = 0xff; + } + break; + case 2: + ret = ib->mask_reg; + break; + } + return ret; +} + +static void ipmi_bt_signal(IPMIBT *ib, IPMIInterface *ii) +{ + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + + ib->do_wake = 1; + while (ib->do_wake) { + ib->do_wake = 0; + iic->handle_if_event(ii); + } +} + +static void ipmi_bt_ioport_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + IPMIInterface *ii = opaque; + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + IPMIBT *ib = iic->get_backend_data(ii); + + switch (addr & 3) { + case 0: + if (IPMI_BT_GET_CLR_WR(val)) { + ib->inlen = 0; + } + if (IPMI_BT_GET_CLR_RD(val)) { + ib->outpos = 0; + } + if (IPMI_BT_GET_B2H_ATN(val)) { + IPMI_BT_SET_B2H_ATN(ib->control_reg, 0); + } + if (IPMI_BT_GET_SMS_ATN(val)) { + IPMI_BT_SET_SMS_ATN(ib->control_reg, 0); + } + if (IPMI_BT_GET_HBUSY(val)) { + /* Toggle */ + IPMI_BT_SET_HBUSY(ib->control_reg, + !IPMI_BT_GET_HBUSY(ib->control_reg)); + } + if (IPMI_BT_GET_H2B_ATN(val)) { + IPMI_BT_SET_BBUSY(ib->control_reg, 1); + ipmi_bt_signal(ib, ii); + } + break; + + case 1: + if (ib->inlen < sizeof(ib->inmsg)) { + ib->inmsg[ib->inlen] = val; + } + ib->inlen++; + break; + + case 2: + if (IPMI_BT_GET_B2H_IRQ_EN(val) != + IPMI_BT_GET_B2H_IRQ_EN(ib->mask_reg)) { + if (IPMI_BT_GET_B2H_IRQ_EN(val)) { + if (IPMI_BT_GET_B2H_ATN(ib->control_reg) || + IPMI_BT_GET_SMS_ATN(ib->control_reg)) { + IPMI_BT_SET_B2H_IRQ(ib->mask_reg, 1); + qemu_irq_raise(ib->irq); + } + IPMI_BT_SET_B2H_IRQ_EN(ib->mask_reg, 1); + } else { + if (IPMI_BT_GET_B2H_IRQ(ib->mask_reg)) { + IPMI_BT_SET_B2H_IRQ(ib->mask_reg, 0); + qemu_irq_lower(ib->irq); + } + IPMI_BT_SET_B2H_IRQ_EN(ib->mask_reg, 0); + } + } + if (IPMI_BT_GET_B2H_IRQ(val) && IPMI_BT_GET_B2H_IRQ(ib->mask_reg)) { + IPMI_BT_SET_B2H_IRQ(ib->mask_reg, 0); + qemu_irq_lower(ib->irq); + } + break; + } +} + +static const MemoryRegionOps ipmi_bt_io_ops = { + .read = ipmi_bt_ioport_read, + .write = ipmi_bt_ioport_write, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void ipmi_bt_set_atn(IPMIInterface *ii, int val, int irq) +{ + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + IPMIBT *ib = iic->get_backend_data(ii); + + if (!!val == IPMI_BT_GET_SMS_ATN(ib->control_reg)) { + return; + } + + IPMI_BT_SET_SMS_ATN(ib->control_reg, val); + if (val) { + if (irq && ib->use_irq && ib->irqs_enabled && + !IPMI_BT_GET_B2H_ATN(ib->control_reg) && + IPMI_BT_GET_B2H_IRQ_EN(ib->mask_reg)) { + IPMI_BT_SET_B2H_IRQ(ib->mask_reg, 1); + qemu_irq_raise(ib->irq); + } + } else { + if (!IPMI_BT_GET_B2H_ATN(ib->control_reg) && + IPMI_BT_GET_B2H_IRQ(ib->mask_reg)) { + IPMI_BT_SET_B2H_IRQ(ib->mask_reg, 0); + qemu_irq_lower(ib->irq); + } + } +} + +static void ipmi_bt_handle_reset(IPMIInterface *ii, bool is_cold) +{ + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + IPMIBT *ib = iic->get_backend_data(ii); + + if (is_cold) { + /* Disable the BT interrupt on reset */ + if (IPMI_BT_GET_B2H_IRQ(ib->mask_reg)) { + IPMI_BT_SET_B2H_IRQ(ib->mask_reg, 0); + qemu_irq_lower(ib->irq); + } + IPMI_BT_SET_B2H_IRQ_EN(ib->mask_reg, 0); + } +} + +static void ipmi_bt_set_irq_enable(IPMIInterface *ii, int val) +{ + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + IPMIBT *ib = iic->get_backend_data(ii); + + ib->irqs_enabled = val; +} + +static void ipmi_bt_init(IPMIInterface *ii, Error **errp) +{ + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + IPMIBT *ib = iic->get_backend_data(ii); + + ib->io_length = 3; + + memory_region_init_io(&ib->io, NULL, &ipmi_bt_io_ops, ii, "ipmi-bt", 3); +} + +static void ipmi_bt_class_init(IPMIInterfaceClass *iic) +{ + iic->init = ipmi_bt_init; + iic->set_atn = ipmi_bt_set_atn; + iic->handle_rsp = ipmi_bt_handle_rsp; + iic->handle_if_event = ipmi_bt_handle_event; + iic->set_irq_enable = ipmi_bt_set_irq_enable; + iic->reset = ipmi_bt_handle_reset; +} + + +#define TYPE_ISA_IPMI_BT "isa-ipmi-bt" +#define ISA_IPMI_BT(obj) OBJECT_CHECK(ISAIPMIBTDevice, (obj), \ + TYPE_ISA_IPMI_BT) + +typedef struct ISAIPMIBTDevice { + ISADevice dev; + int32_t isairq; + IPMIBT bt; + IPMIFwInfo fwinfo; +} ISAIPMIBTDevice; + +static void isa_ipmi_bt_realize(DeviceState *dev, Error **errp) +{ + ISADevice *isadev = ISA_DEVICE(dev); + ISAIPMIBTDevice *iib = ISA_IPMI_BT(dev); + IPMIInterface *ii = IPMI_INTERFACE(dev); + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + + if (!iib->bt.bmc) { + error_setg(errp, "IPMI device requires a bmc attribute to be set"); + return; + } + + iib->bt.bmc->intf = ii; + + iic->init(ii, errp); + if (*errp) + return; + + if (iib->isairq > 0) { + isa_init_irq(isadev, &iib->bt.irq, iib->isairq); + iib->bt.use_irq = 1; + } + + qdev_set_legacy_instance_id(dev, iib->bt.io_base, iib->bt.io_length); + + isa_register_ioport(isadev, &iib->bt.io, iib->bt.io_base); + + iib->fwinfo.interface_name = "bt"; + iib->fwinfo.interface_type = IPMI_SMBIOS_BT; + iib->fwinfo.ipmi_spec_major_revision = 2; + iib->fwinfo.ipmi_spec_minor_revision = 0; + iib->fwinfo.base_address = iib->bt.io_base; + iib->fwinfo.register_length = iib->bt.io_length; + iib->fwinfo.register_spacing = 1; + iib->fwinfo.memspace = IPMI_MEMSPACE_IO; + iib->fwinfo.irq_type = IPMI_LEVEL_IRQ; + iib->fwinfo.interrupt_number = iib->isairq; + iib->fwinfo.acpi_parent = "\\_SB.PCI0.ISA"; + iib->fwinfo.i2c_slave_address = iib->bt.bmc->slave_addr; + ipmi_add_fwinfo(&iib->fwinfo, errp); +} + +static const VMStateDescription vmstate_ISAIPMIBTDevice = { + .name = TYPE_IPMI_INTERFACE, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_BOOL(bt.obf_irq_set, ISAIPMIBTDevice), + VMSTATE_BOOL(bt.atn_irq_set, ISAIPMIBTDevice), + VMSTATE_BOOL(bt.use_irq, ISAIPMIBTDevice), + VMSTATE_BOOL(bt.irqs_enabled, ISAIPMIBTDevice), + VMSTATE_UINT32(bt.outpos, ISAIPMIBTDevice), + VMSTATE_VBUFFER_UINT32(bt.outmsg, ISAIPMIBTDevice, 1, NULL, 0, + bt.outlen), + VMSTATE_VBUFFER_UINT32(bt.inmsg, ISAIPMIBTDevice, 1, NULL, 0, + bt.inlen), + VMSTATE_UINT8(bt.control_reg, ISAIPMIBTDevice), + VMSTATE_UINT8(bt.mask_reg, ISAIPMIBTDevice), + VMSTATE_UINT8(bt.waiting_rsp, ISAIPMIBTDevice), + VMSTATE_UINT8(bt.waiting_seq, ISAIPMIBTDevice), + VMSTATE_END_OF_LIST() + } +}; + +static void isa_ipmi_bt_init(Object *obj) +{ + ISAIPMIBTDevice *iib = ISA_IPMI_BT(obj); + + ipmi_bmc_find_and_link(obj, (Object **) &iib->bt.bmc); + + vmstate_register(NULL, 0, &vmstate_ISAIPMIBTDevice, iib); +} + +static void *isa_ipmi_bt_get_backend_data(IPMIInterface *ii) +{ + ISAIPMIBTDevice *iib = ISA_IPMI_BT(ii); + + return &iib->bt; +} + +static Property ipmi_isa_properties[] = { + DEFINE_PROP_UINT32("ioport", ISAIPMIBTDevice, bt.io_base, 0xe4), + DEFINE_PROP_INT32("irq", ISAIPMIBTDevice, isairq, 5), + DEFINE_PROP_END_OF_LIST(), +}; + +static void isa_ipmi_bt_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + IPMIInterfaceClass *iic = IPMI_INTERFACE_CLASS(oc); + + dc->realize = isa_ipmi_bt_realize; + dc->props = ipmi_isa_properties; + + iic->get_backend_data = isa_ipmi_bt_get_backend_data; + ipmi_bt_class_init(iic); +} + +static const TypeInfo isa_ipmi_bt_info = { + .name = TYPE_ISA_IPMI_BT, + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(ISAIPMIBTDevice), + .instance_init = isa_ipmi_bt_init, + .class_init = isa_ipmi_bt_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_IPMI_INTERFACE }, + { } + } +}; + +static void ipmi_register_types(void) +{ + type_register_static(&isa_ipmi_bt_info); +} + +type_init(ipmi_register_types) diff --git a/qemu/hw/ipmi/isa_ipmi_kcs.c b/qemu/hw/ipmi/isa_ipmi_kcs.c new file mode 100644 index 000000000..2742ce06c --- /dev/null +++ b/qemu/hw/ipmi/isa_ipmi_kcs.c @@ -0,0 +1,495 @@ +/* + * QEMU ISA IPMI KCS emulation + * + * Copyright (c) 2015 Corey Minyard, MontaVista Software, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/hw.h" +#include "hw/ipmi/ipmi.h" +#include "hw/isa/isa.h" +#include "hw/i386/pc.h" + +#define IPMI_KCS_OBF_BIT 0 +#define IPMI_KCS_IBF_BIT 1 +#define IPMI_KCS_SMS_ATN_BIT 2 +#define IPMI_KCS_CD_BIT 3 + +#define IPMI_KCS_OBF_MASK (1 << IPMI_KCS_OBF_BIT) +#define IPMI_KCS_GET_OBF(d) (((d) >> IPMI_KCS_OBF_BIT) & 0x1) +#define IPMI_KCS_SET_OBF(d, v) (d) = (((d) & ~IPMI_KCS_OBF_MASK) | \ + (((v) & 1) << IPMI_KCS_OBF_BIT)) +#define IPMI_KCS_IBF_MASK (1 << IPMI_KCS_IBF_BIT) +#define IPMI_KCS_GET_IBF(d) (((d) >> IPMI_KCS_IBF_BIT) & 0x1) +#define IPMI_KCS_SET_IBF(d, v) (d) = (((d) & ~IPMI_KCS_IBF_MASK) | \ + (((v) & 1) << IPMI_KCS_IBF_BIT)) +#define IPMI_KCS_SMS_ATN_MASK (1 << IPMI_KCS_SMS_ATN_BIT) +#define IPMI_KCS_GET_SMS_ATN(d) (((d) >> IPMI_KCS_SMS_ATN_BIT) & 0x1) +#define IPMI_KCS_SET_SMS_ATN(d, v) (d) = (((d) & ~IPMI_KCS_SMS_ATN_MASK) | \ + (((v) & 1) << IPMI_KCS_SMS_ATN_BIT)) +#define IPMI_KCS_CD_MASK (1 << IPMI_KCS_CD_BIT) +#define IPMI_KCS_GET_CD(d) (((d) >> IPMI_KCS_CD_BIT) & 0x1) +#define IPMI_KCS_SET_CD(d, v) (d) = (((d) & ~IPMI_KCS_CD_MASK) | \ + (((v) & 1) << IPMI_KCS_CD_BIT)) + +#define IPMI_KCS_IDLE_STATE 0 +#define IPMI_KCS_READ_STATE 1 +#define IPMI_KCS_WRITE_STATE 2 +#define IPMI_KCS_ERROR_STATE 3 + +#define IPMI_KCS_GET_STATE(d) (((d) >> 6) & 0x3) +#define IPMI_KCS_SET_STATE(d, v) ((d) = ((d) & ~0xc0) | (((v) & 0x3) << 6)) + +#define IPMI_KCS_ABORT_STATUS_CMD 0x60 +#define IPMI_KCS_WRITE_START_CMD 0x61 +#define IPMI_KCS_WRITE_END_CMD 0x62 +#define IPMI_KCS_READ_CMD 0x68 + +#define IPMI_KCS_STATUS_NO_ERR 0x00 +#define IPMI_KCS_STATUS_ABORTED_ERR 0x01 +#define IPMI_KCS_STATUS_BAD_CC_ERR 0x02 +#define IPMI_KCS_STATUS_LENGTH_ERR 0x06 + +typedef struct IPMIKCS { + IPMIBmc *bmc; + + bool do_wake; + + qemu_irq irq; + + uint32_t io_base; + unsigned long io_length; + MemoryRegion io; + + bool obf_irq_set; + bool atn_irq_set; + bool use_irq; + bool irqs_enabled; + + uint8_t outmsg[MAX_IPMI_MSG_SIZE]; + uint32_t outpos; + uint32_t outlen; + + uint8_t inmsg[MAX_IPMI_MSG_SIZE]; + uint32_t inlen; + bool write_end; + + uint8_t status_reg; + uint8_t data_out_reg; + + int16_t data_in_reg; /* -1 means not written */ + int16_t cmd_reg; + + /* + * This is a response number that we send with the command to make + * sure that the response matches the command. + */ + uint8_t waiting_rsp; +} IPMIKCS; + +#define SET_OBF() \ + do { \ + IPMI_KCS_SET_OBF(ik->status_reg, 1); \ + if (ik->use_irq && ik->irqs_enabled && !ik->obf_irq_set) { \ + ik->obf_irq_set = 1; \ + if (!ik->atn_irq_set) { \ + qemu_irq_raise(ik->irq); \ + } \ + } \ + } while (0) + +static void ipmi_kcs_signal(IPMIKCS *ik, IPMIInterface *ii) +{ + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + + ik->do_wake = 1; + while (ik->do_wake) { + ik->do_wake = 0; + iic->handle_if_event(ii); + } +} + +static void ipmi_kcs_handle_event(IPMIInterface *ii) +{ + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + IPMIKCS *ik = iic->get_backend_data(ii); + + if (ik->cmd_reg == IPMI_KCS_ABORT_STATUS_CMD) { + if (IPMI_KCS_GET_STATE(ik->status_reg) != IPMI_KCS_ERROR_STATE) { + ik->waiting_rsp++; /* Invalidate the message */ + ik->outmsg[0] = IPMI_KCS_STATUS_ABORTED_ERR; + ik->outlen = 1; + ik->outpos = 0; + IPMI_KCS_SET_STATE(ik->status_reg, IPMI_KCS_ERROR_STATE); + SET_OBF(); + } + goto out; + } + + switch (IPMI_KCS_GET_STATE(ik->status_reg)) { + case IPMI_KCS_IDLE_STATE: + if (ik->cmd_reg == IPMI_KCS_WRITE_START_CMD) { + IPMI_KCS_SET_STATE(ik->status_reg, IPMI_KCS_WRITE_STATE); + ik->cmd_reg = -1; + ik->write_end = 0; + ik->inlen = 0; + SET_OBF(); + } + break; + + case IPMI_KCS_READ_STATE: + handle_read: + if (ik->outpos >= ik->outlen) { + IPMI_KCS_SET_STATE(ik->status_reg, IPMI_KCS_IDLE_STATE); + SET_OBF(); + } else if (ik->data_in_reg == IPMI_KCS_READ_CMD) { + ik->data_out_reg = ik->outmsg[ik->outpos]; + ik->outpos++; + SET_OBF(); + } else { + ik->outmsg[0] = IPMI_KCS_STATUS_BAD_CC_ERR; + ik->outlen = 1; + ik->outpos = 0; + IPMI_KCS_SET_STATE(ik->status_reg, IPMI_KCS_ERROR_STATE); + SET_OBF(); + goto out; + } + break; + + case IPMI_KCS_WRITE_STATE: + if (ik->data_in_reg != -1) { + /* + * Don't worry about input overrun here, that will be + * handled in the BMC. + */ + if (ik->inlen < sizeof(ik->inmsg)) { + ik->inmsg[ik->inlen] = ik->data_in_reg; + } + ik->inlen++; + } + if (ik->write_end) { + IPMIBmcClass *bk = IPMI_BMC_GET_CLASS(ik->bmc); + ik->outlen = 0; + ik->write_end = 0; + ik->outpos = 0; + bk->handle_command(ik->bmc, ik->inmsg, ik->inlen, sizeof(ik->inmsg), + ik->waiting_rsp); + goto out_noibf; + } else if (ik->cmd_reg == IPMI_KCS_WRITE_END_CMD) { + ik->cmd_reg = -1; + ik->write_end = 1; + } + SET_OBF(); + break; + + case IPMI_KCS_ERROR_STATE: + if (ik->data_in_reg != -1) { + IPMI_KCS_SET_STATE(ik->status_reg, IPMI_KCS_READ_STATE); + ik->data_in_reg = IPMI_KCS_READ_CMD; + goto handle_read; + } + break; + } + + if (ik->cmd_reg != -1) { + /* Got an invalid command */ + ik->outmsg[0] = IPMI_KCS_STATUS_BAD_CC_ERR; + ik->outlen = 1; + ik->outpos = 0; + IPMI_KCS_SET_STATE(ik->status_reg, IPMI_KCS_ERROR_STATE); + } + + out: + ik->cmd_reg = -1; + ik->data_in_reg = -1; + IPMI_KCS_SET_IBF(ik->status_reg, 0); + out_noibf: + return; +} + +static void ipmi_kcs_handle_rsp(IPMIInterface *ii, uint8_t msg_id, + unsigned char *rsp, unsigned int rsp_len) +{ + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + IPMIKCS *ik = iic->get_backend_data(ii); + + if (ik->waiting_rsp == msg_id) { + ik->waiting_rsp++; + if (rsp_len > sizeof(ik->outmsg)) { + ik->outmsg[0] = rsp[0]; + ik->outmsg[1] = rsp[1]; + ik->outmsg[2] = IPMI_CC_CANNOT_RETURN_REQ_NUM_BYTES; + ik->outlen = 3; + } else { + memcpy(ik->outmsg, rsp, rsp_len); + ik->outlen = rsp_len; + } + IPMI_KCS_SET_STATE(ik->status_reg, IPMI_KCS_READ_STATE); + ik->data_in_reg = IPMI_KCS_READ_CMD; + ipmi_kcs_signal(ik, ii); + } +} + + +static uint64_t ipmi_kcs_ioport_read(void *opaque, hwaddr addr, unsigned size) +{ + IPMIInterface *ii = opaque; + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + IPMIKCS *ik = iic->get_backend_data(ii); + uint32_t ret; + + switch (addr & 1) { + case 0: + ret = ik->data_out_reg; + IPMI_KCS_SET_OBF(ik->status_reg, 0); + if (ik->obf_irq_set) { + ik->obf_irq_set = 0; + if (!ik->atn_irq_set) { + qemu_irq_lower(ik->irq); + } + } + break; + case 1: + ret = ik->status_reg; + if (ik->atn_irq_set) { + ik->atn_irq_set = 0; + if (!ik->obf_irq_set) { + qemu_irq_lower(ik->irq); + } + } + break; + } + return ret; +} + +static void ipmi_kcs_ioport_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + IPMIInterface *ii = opaque; + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + IPMIKCS *ik = iic->get_backend_data(ii); + + if (IPMI_KCS_GET_IBF(ik->status_reg)) { + return; + } + + switch (addr & 1) { + case 0: + ik->data_in_reg = val; + break; + + case 1: + ik->cmd_reg = val; + break; + } + IPMI_KCS_SET_IBF(ik->status_reg, 1); + ipmi_kcs_signal(ik, ii); +} + +const MemoryRegionOps ipmi_kcs_io_ops = { + .read = ipmi_kcs_ioport_read, + .write = ipmi_kcs_ioport_write, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void ipmi_kcs_set_atn(IPMIInterface *ii, int val, int irq) +{ + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + IPMIKCS *ik = iic->get_backend_data(ii); + + IPMI_KCS_SET_SMS_ATN(ik->status_reg, val); + if (val) { + if (irq && !ik->atn_irq_set && ik->use_irq && ik->irqs_enabled) { + ik->atn_irq_set = 1; + if (!ik->obf_irq_set) { + qemu_irq_raise(ik->irq); + } + } + } else { + if (ik->atn_irq_set) { + ik->atn_irq_set = 0; + if (!ik->obf_irq_set) { + qemu_irq_lower(ik->irq); + } + } + } +} + +static void ipmi_kcs_set_irq_enable(IPMIInterface *ii, int val) +{ + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + IPMIKCS *ik = iic->get_backend_data(ii); + + ik->irqs_enabled = val; +} + +static void ipmi_kcs_init(IPMIInterface *ii, Error **errp) +{ + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + IPMIKCS *ik = iic->get_backend_data(ii); + + ik->io_length = 2; + memory_region_init_io(&ik->io, NULL, &ipmi_kcs_io_ops, ii, "ipmi-kcs", 2); +} + +static void ipmi_kcs_class_init(IPMIInterfaceClass *iic) +{ + iic->init = ipmi_kcs_init; + iic->set_atn = ipmi_kcs_set_atn; + iic->handle_rsp = ipmi_kcs_handle_rsp; + iic->handle_if_event = ipmi_kcs_handle_event; + iic->set_irq_enable = ipmi_kcs_set_irq_enable; +} + + +#define TYPE_ISA_IPMI_KCS "isa-ipmi-kcs" +#define ISA_IPMI_KCS(obj) OBJECT_CHECK(ISAIPMIKCSDevice, (obj), \ + TYPE_ISA_IPMI_KCS) + +typedef struct ISAIPMIKCSDevice { + ISADevice dev; + int32_t isairq; + IPMIKCS kcs; + IPMIFwInfo fwinfo; +} ISAIPMIKCSDevice; + +static void ipmi_isa_realize(DeviceState *dev, Error **errp) +{ + ISADevice *isadev = ISA_DEVICE(dev); + ISAIPMIKCSDevice *iik = ISA_IPMI_KCS(dev); + IPMIInterface *ii = IPMI_INTERFACE(dev); + IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii); + + if (!iik->kcs.bmc) { + error_setg(errp, "IPMI device requires a bmc attribute to be set"); + return; + } + + iik->kcs.bmc->intf = ii; + + iic->init(ii, errp); + if (*errp) + return; + + if (iik->isairq > 0) { + isa_init_irq(isadev, &iik->kcs.irq, iik->isairq); + iik->kcs.use_irq = 1; + } + + qdev_set_legacy_instance_id(dev, iik->kcs.io_base, iik->kcs.io_length); + + isa_register_ioport(isadev, &iik->kcs.io, iik->kcs.io_base); + + iik->fwinfo.interface_name = "kcs"; + iik->fwinfo.interface_type = IPMI_SMBIOS_KCS; + iik->fwinfo.ipmi_spec_major_revision = 2; + iik->fwinfo.ipmi_spec_minor_revision = 0; + iik->fwinfo.base_address = iik->kcs.io_base; + iik->fwinfo.i2c_slave_address = iik->kcs.bmc->slave_addr; + iik->fwinfo.register_length = iik->kcs.io_length; + iik->fwinfo.register_spacing = 1; + iik->fwinfo.memspace = IPMI_MEMSPACE_IO; + iik->fwinfo.irq_type = IPMI_LEVEL_IRQ; + iik->fwinfo.interrupt_number = iik->isairq; + iik->fwinfo.acpi_parent = "\\_SB.PCI0.ISA"; + ipmi_add_fwinfo(&iik->fwinfo, errp); +} + +const VMStateDescription vmstate_ISAIPMIKCSDevice = { + .name = TYPE_IPMI_INTERFACE, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_BOOL(kcs.obf_irq_set, ISAIPMIKCSDevice), + VMSTATE_BOOL(kcs.atn_irq_set, ISAIPMIKCSDevice), + VMSTATE_BOOL(kcs.use_irq, ISAIPMIKCSDevice), + VMSTATE_BOOL(kcs.irqs_enabled, ISAIPMIKCSDevice), + VMSTATE_UINT32(kcs.outpos, ISAIPMIKCSDevice), + VMSTATE_VBUFFER_UINT32(kcs.outmsg, ISAIPMIKCSDevice, 1, NULL, 0, + kcs.outlen), + VMSTATE_VBUFFER_UINT32(kcs.inmsg, ISAIPMIKCSDevice, 1, NULL, 0, + kcs.inlen), + VMSTATE_BOOL(kcs.write_end, ISAIPMIKCSDevice), + VMSTATE_UINT8(kcs.status_reg, ISAIPMIKCSDevice), + VMSTATE_UINT8(kcs.data_out_reg, ISAIPMIKCSDevice), + VMSTATE_INT16(kcs.data_in_reg, ISAIPMIKCSDevice), + VMSTATE_INT16(kcs.cmd_reg, ISAIPMIKCSDevice), + VMSTATE_UINT8(kcs.waiting_rsp, ISAIPMIKCSDevice), + VMSTATE_END_OF_LIST() + } +}; + +static void isa_ipmi_kcs_init(Object *obj) +{ + ISAIPMIKCSDevice *iik = ISA_IPMI_KCS(obj); + + ipmi_bmc_find_and_link(obj, (Object **) &iik->kcs.bmc); + + vmstate_register(NULL, 0, &vmstate_ISAIPMIKCSDevice, iik); +} + +static void *isa_ipmi_kcs_get_backend_data(IPMIInterface *ii) +{ + ISAIPMIKCSDevice *iik = ISA_IPMI_KCS(ii); + + return &iik->kcs; +} + +static Property ipmi_isa_properties[] = { + DEFINE_PROP_UINT32("ioport", ISAIPMIKCSDevice, kcs.io_base, 0xca2), + DEFINE_PROP_INT32("irq", ISAIPMIKCSDevice, isairq, 5), + DEFINE_PROP_END_OF_LIST(), +}; + +static void isa_ipmi_kcs_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + IPMIInterfaceClass *iic = IPMI_INTERFACE_CLASS(oc); + + dc->realize = ipmi_isa_realize; + dc->props = ipmi_isa_properties; + + iic->get_backend_data = isa_ipmi_kcs_get_backend_data; + ipmi_kcs_class_init(iic); +} + +static const TypeInfo isa_ipmi_kcs_info = { + .name = TYPE_ISA_IPMI_KCS, + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(ISAIPMIKCSDevice), + .instance_init = isa_ipmi_kcs_init, + .class_init = isa_ipmi_kcs_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_IPMI_INTERFACE }, + { } + } +}; + +static void ipmi_register_types(void) +{ + type_register_static(&isa_ipmi_kcs_info); +} + +type_init(ipmi_register_types) -- cgit 1.2.3-korg