From: Corey Minyard <cminyard@xxxxxxxxxx> Add the basic IPMI types and infrastructure to QEMU. Low-level interfaces and simulation interfaces will register with this; it's kind of the go-between to tie them together. Signed-off-by: Corey Minyard <cminyard@xxxxxxxxxx> --- default-configs/i386-softmmu.mak | 1 + default-configs/x86_64-softmmu.mak | 1 + hw/Makefile.objs | 2 + hw/ipmi.c | 147 +++++++++++++++++++++++++++ hw/ipmi.h | 192 ++++++++++++++++++++++++++++++++++++ qemu-doc.texi | 2 + qemu-options.hx | 35 +++++++ sysemu.h | 8 ++ vl.c | 46 +++++++++ 9 files changed, 434 insertions(+), 0 deletions(-) create mode 100644 hw/ipmi.c create mode 100644 hw/ipmi.h diff --git a/default-configs/i386-softmmu.mak b/default-configs/i386-softmmu.mak index 2c78175..eb17afc 100644 --- a/default-configs/i386-softmmu.mak +++ b/default-configs/i386-softmmu.mak @@ -7,6 +7,7 @@ CONFIG_VGA_ISA=y CONFIG_VGA_CIRRUS=y CONFIG_VMWARE_VGA=y CONFIG_VMMOUSE=y +CONFIG_IPMI=y CONFIG_SERIAL=y CONFIG_PARALLEL=y CONFIG_I8254=y diff --git a/default-configs/x86_64-softmmu.mak b/default-configs/x86_64-softmmu.mak index 233a856..e4e3e4f 100644 --- a/default-configs/x86_64-softmmu.mak +++ b/default-configs/x86_64-softmmu.mak @@ -7,6 +7,7 @@ CONFIG_VGA_ISA=y CONFIG_VGA_CIRRUS=y CONFIG_VMWARE_VGA=y CONFIG_VMMOUSE=y +CONFIG_IPMI=y CONFIG_SERIAL=y CONFIG_PARALLEL=y CONFIG_I8254=y diff --git a/hw/Makefile.objs b/hw/Makefile.objs index 30c0b78..0d55997 100644 --- a/hw/Makefile.objs +++ b/hw/Makefile.objs @@ -20,6 +20,8 @@ hw-obj-$(CONFIG_M48T59) += m48t59.o hw-obj-$(CONFIG_ESCC) += escc.o hw-obj-$(CONFIG_EMPTY_SLOT) += empty_slot.o +hw-obj-$(CONFIG_IPMI) += ipmi.o + hw-obj-$(CONFIG_SERIAL) += serial.o hw-obj-$(CONFIG_PARALLEL) += parallel.o hw-obj-$(CONFIG_I8254) += i8254_common.o i8254.o diff --git a/hw/ipmi.c b/hw/ipmi.c new file mode 100644 index 0000000..86a097b --- /dev/null +++ b/hw/ipmi.c @@ -0,0 +1,147 @@ +/* + * QEMU IPMI emulation + * + * Copyright (c) 2012 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 "hw.h" +#include "ipmi.h" +#include "sysemu.h" +#include "qmp-commands.h" + +static void (*ipmi_init_handlers[4])(IPMIState *s); + +void register_ipmi_type(unsigned int type, void (*init)(IPMIState *s)) +{ + ipmi_init_handlers[type] = init; +} + +static void (*ipmi_sim_init_handlers[2])(IPMIState *s); + +void register_ipmi_sim(unsigned int iftype, void (*init)(IPMIState *s)) +{ + ipmi_sim_init_handlers[iftype] = init; +} + + +#ifdef DO_IPMI_THREAD +static void *ipmi_thread(void *opaque) +{ + IPMIState *s = opaque; + int64_t wait_until; + + ipmi_lock(); + for (;;) { + qemu_cond_wait(&s->waker, &s->lock); + wait_until = 0; + while (s->do_wake) { + s->do_wake = 0; + s->handle_if_event(s); + } + } + ipmi_unlock(); + return NULL; +} +#endif + +static int ipmi_do_hw_op(IPMIState *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; + qemu_mutex_lock_iothread(); + qmp_inject_nmi(NULL); + qemu_mutex_unlock_iothread(); + 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_set_irq_enable(IPMIState *s, int val) +{ + s->irqs_enabled = val; +} + +static void ipmi_reset_handler(void *opaque) +{ + IPMIState *s = opaque; + + if (s->handle_reset) + s->handle_reset(s); +} + +void ipmi_init(unsigned int type, IPMIState *s) +{ + s->do_hw_op = ipmi_do_hw_op; + s->set_irq_enable = ipmi_set_irq_enable; + + if (!ipmi_init_handlers[type]) { + fprintf(stderr, "IPMI type is not supported\n"); + abort(); + } + ipmi_init_handlers[type](s); + + if (!s->slave_addr) + s->slave_addr = 0x20; + + if (s->chropts) { + if (!ipmi_sim_init_handlers[IPMI_SIM_EXTERNAL]) { + fprintf(stderr, "IPMI: external interface requested, but" + " no external interface is compiled in.\n"); + abort(); + } + ipmi_sim_init_handlers[IPMI_SIM_EXTERNAL](s); + } else { + if (!ipmi_sim_init_handlers[IPMI_SIM_INTERNAL]) { + fprintf(stderr, "IPMI: local interface requested, but" + " no local interface is compiled in.\n"); + abort(); + } + ipmi_sim_init_handlers[IPMI_SIM_INTERNAL](s); + } + + qemu_register_reset(ipmi_reset_handler, s); + +#ifdef DO_IPMI_THREAD + qemu_mutex_init(&s->lock); + qemu_cond_init(&s->waker); + qemu_thread_create(&s->thread, ipmi_thread, s, 0); +#endif +} diff --git a/hw/ipmi.h b/hw/ipmi.h new file mode 100644 index 0000000..5517198 --- /dev/null +++ b/hw/ipmi.h @@ -0,0 +1,192 @@ +/* + * IPMI general stuff + * + * Copyright (c) 2012 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. + */ + +#ifndef HW_IPMI_H +#define HW_IPMI_H + +#include "memory.h" +#include "qemu-common.h" +#include "qemu-char.h" + +/* #define DO_IPMI_THREAD */ + +#ifdef DO_IPMI_THREAD +#include "qemu-thread.h" + +#define ipmi_lock() qemu_mutex_lock(&s->lock) +#define ipmi_unlock() qemu_mutex_unlock(&s->lock) +#define ipmi_signal() do { s->do_wake=1; qemu_cond_signal(&s->waker); } while(0) + +#else +#define ipmi_lock() do {} while(0) +#define ipmi_unlock() do {} while(0) +#define ipmi_signal() \ +do { \ + s->do_wake = 1; \ + while (s->do_wake) { \ + s->do_wake = 0; \ + s->handle_if_event(s); \ + } \ +} while(0) +#endif + +#define MAX_IPMI_MSG_SIZE 300 + +enum ipmi_op { + IPMI_RESET_CHASSIS, + IPMI_POWEROFF_CHASSIS, + IPMI_POWERON_CHASSIS, + IPMI_POWERCYCLE_CHASSIS, + IPMI_PULSE_DIAG_IRQ, + IPMI_SHUTDOWN_VIA_ACPI_OVERTEMP, + IPMI_SEND_NMI +}; + +#define IPMI_CC_INVALID_CMD 0xc1 +#define IPMI_CC_COMMAND_INVALID_FOR_LUN 0xc2 +#define IPMI_CC_TIMEOUT 0xc3 +#define IPMI_CC_OUT_OF_SPACE 0xc4 +#define IPMI_CC_INVALID_RESERVATION 0xc5 +#define IPMI_CC_REQUEST_DATA_TRUNCATED 0xc6 +#define IPMI_CC_REQUEST_DATA_LENGTH_INVALID 0xc7 +#define IPMI_CC_PARM_OUT_OF_RANGE 0xc9 +#define IPMI_CC_CANNOT_RETURN_REQ_NUM_BYTES 0xca +#define IPMI_CC_REQ_ENTRY_NOT_PRESENT 0xcb +#define IPMI_CC_INVALID_DATA_FIELD 0xcc +#define IPMI_CC_BMC_INIT_IN_PROGRESS 0xd2 +#define IPMI_CC_COMMAND_NOT_SUPPORTED 0xd5 + +#define IPMI_NETFN_APP 0x06 + +#define IPMI_DEBUG 1 + +/* Phyical interface types. */ +#define IPMI_KCS 1 +#define IPMI_BT 2 +#define IPMI_SMIC 3 + +#define IPMI_SMBIOS_KCS 0x01 +#define IPMI_SMBIOS_SMIC 0x02 +#define IPMI_SMBIOS_BT 0x03 +#define IPMI_SMBIOS_SSIF 0x04 + +/* Simulation types */ +#define IPMI_SIM_INTERNAL 0 +#define IPMI_SIM_EXTERNAL 1 + +/* + * Create a separate thread for the IPMI device itself. This is a + * better simulation and lets the IPMI device do things asynchronously + * if necessary. + */ + +typedef struct IPMIState { +#ifdef DO_IPMI_THREAD + QemuThread thread; + QemuCond waker; + QemuMutex lock; +#endif + int do_wake; + + int obf_irq_set; + int atn_irq_set; + qemu_irq irq; + int use_irq; + int irqs_enabled; + + unsigned int smbios_type; + + MemoryRegion io; + + uint8_t outmsg[MAX_IPMI_MSG_SIZE]; + unsigned int outpos; + unsigned int outlen; + + uint8_t inmsg[MAX_IPMI_MSG_SIZE]; + unsigned int inlen; + int write_end; + + unsigned char slave_addr; + + /* + * If we have an external interface, it's chardev and options go here. + * Othersize chropts is NULL. + */ + void *chropts; /* Really QemuOpts */ + + /* + * The interface (local simulator or external interface) puts + * its data here. + */ + void *ifdata; + + /* Called when the system resets to report to the interface. */ + void (*handle_reset)(struct IPMIState *s); + + /* The individual types (kcs, bt) store their data here. */ + void *typeinfo; + + void (*handle_if_event)(struct IPMIState *s); + + /* + * Handle a command to the other end. Must be called + * with ipmi_lock held. + */ + void (*handle_command)(struct IPMIState *s, + uint8_t *cmd, unsigned int cmd_len, + unsigned int max_cmd_len, + uint8_t msg_id); + + /* + * The interfaces use this to perform certain ops + */ + void (*set_atn)(struct IPMIState *s, int val, int irq); + + /* Perform various operations on the hardware. If checkonly is true, + it will return if the operation can be performed, but it will not + do the operation. */ + int (*do_hw_op)(struct IPMIState *s, enum ipmi_op op, int checkonly); + + /* + * Handle a response from the other end. Must be called + * with ipmi_lock held. + */ + void (*handle_rsp)(struct IPMIState *s, uint8_t msg_id, + unsigned char *rsp, unsigned int rsp_len); + + void (*set_irq_enable)(struct IPMIState *s, int val); +} IPMIState; + +void register_ipmi_type(unsigned int type, void (*init)(IPMIState *s)); +void register_ipmi_sim(unsigned int iftype, void (*init)(IPMIState *s)); +void ipmi_init(unsigned int type, IPMIState *s); + +#ifdef IPMI_DEBUG +#define ipmi_debug(fs, ...) \ + fprintf(stderr, "IPMI (%s): " fs, __func__, ##__VA_ARGS__) +#else +#define ipmi_debug(fs,...) +#endif + +#endif diff --git a/qemu-doc.texi b/qemu-doc.texi index 0af0ff4..effa2da 100644 --- a/qemu-doc.texi +++ b/qemu-doc.texi @@ -195,6 +195,8 @@ PCI and ISA network adapters @item Serial ports @item +IPMI BMC, either and internal or external one +@item Creative SoundBlaster 16 sound card @item ENSONIQ AudioPCI ES1370 sound card diff --git a/qemu-options.hx b/qemu-options.hx index 125a4da..823f6bc 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -2204,6 +2204,41 @@ Three button serial mouse. Configure the guest to use Microsoft protocol. @end table ETEXI +DEF("ipmi", HAS_ARG, QEMU_OPTION_ipmi, \ + "-ipmi [kcs|bt,]dev|local|none IPMI interface to the dev, or internal BMC\n", + QEMU_ARCH_ALL) +STEXI +@item -ipmi [bt|kcs,]@var{dev}|local|none +@findex -ipmi +Set up an IPMI interface. The physical interface may either be +KCS or BT, the default is KCS. Two options are available for +simulation of the IPMI BMC. If @code{local} is specified, then a +minimal internal BMC is used. This BMC is basically useful as a +watchdog timer and for fooling a system into thinking IPMI is there. + +If @var{dev} is specified (see the serial section above for details on +what can be specified for @var{dev}) then a connection to an external IPMI +simulator is made. This interface has the ability to do power control +and reset, so it can do the normal IPMI types of things required. + +The OpenIPMI project's lanserv simulator is capable of providing +this interface. It is also capable of an IPMI LAN interface, and +you can do power control (the lanserv simulator is capable of starting +a VM, too) and reset of a virtual machine over a standard remote LAN +interface. For details on this, see OpenIPMI. + +The remote connection to a LAN interface will reconnect if disconnected, +so if a remote BMC fails and restarts, it will still be usable. + +For instance, to connect to an external interface on the local machine +port 9002 with a BT physical interface, do the following: +@table @code +@item -ipmi bt,tcp:localhost:9002 +@end table + +Use @code{-ipmi none} to disable IPMI. +ETEXI + DEF("parallel", HAS_ARG, QEMU_OPTION_parallel, \ "-parallel dev redirect the parallel port to char device 'dev'\n", QEMU_ARCH_ALL) diff --git a/sysemu.h b/sysemu.h index eb3da5a..b71cd7a 100644 --- a/sysemu.h +++ b/sysemu.h @@ -172,6 +172,14 @@ int do_pcie_aer_inject_error(Monitor *mon, extern CharDriverState *serial_hds[MAX_SERIAL_PORTS]; +/* IPMI devices */ + +#define MAX_IPMI_DEVICES 1 + +extern int do_local_ipmi; +extern int ipmi_types[MAX_IPMI_DEVICES]; +extern QemuOpts *ipmi_hds[MAX_IPMI_DEVICES]; + /* parallel ports */ #define MAX_PARALLEL_PORTS 3 diff --git a/vl.c b/vl.c index 2e59f21..ea3decd 100644 --- a/vl.c +++ b/vl.c @@ -120,6 +120,7 @@ int main(int argc, char **argv) #include "hw/xen.h" #include "hw/qdev.h" #include "hw/loader.h" +#include "hw/ipmi.h" #include "bt-host.h" #include "net.h" #include "net/slirp.h" @@ -193,6 +194,9 @@ static int no_frame = 0; #endif int no_quit = 0; CharDriverState *serial_hds[MAX_SERIAL_PORTS]; +int do_local_ipmi; +int ipmi_types[MAX_IPMI_DEVICES]; +QemuOpts *ipmi_hds[MAX_IPMI_DEVICES]; CharDriverState *parallel_hds[MAX_PARALLEL_PORTS]; CharDriverState *virtcon_hds[MAX_VIRTIO_CONSOLES]; int win2k_install_hack = 0; @@ -1936,6 +1940,7 @@ struct device_config { DEV_VIRTCON, /* -virtioconsole */ DEV_DEBUGCON, /* -debugcon */ DEV_GDB, /* -gdb, -s */ + DEV_IPMI, /* -ipmi */ } type; const char *cmdline; Location loc; @@ -1993,6 +1998,42 @@ static int serial_parse(const char *devname) return 0; } +static int ipmi_parse(const char *devname) +{ + static int index = 0; + + if (strcmp(devname, "none") == 0) + return 0; + if (index >= MAX_IPMI_DEVICES) { + fprintf(stderr, "qemu: too many IPMI devices\n"); + exit(1); + } + if (strncmp(devname, "kcs,", 4) == 0) { + ipmi_types[index] = IPMI_KCS; + devname += 4; + } else if (strncmp(devname, "bt,", 3) == 0) { + ipmi_types[index] = IPMI_BT; + devname += 3; + } else if (strncmp(devname, "smic,", 5) == 0) { + ipmi_types[index] = IPMI_SMIC; + devname += 5; + } else + ipmi_types[index] = IPMI_KCS; + if (strcmp(devname, "local") == 0) { + do_local_ipmi = 1; + index++; + return 0; + } + ipmi_hds[index] = qemu_chr_parse_compat("ipmi", devname); + if (!ipmi_hds[index]) { + fprintf(stderr, "qemu: could parse ipmi device '%s': %s\n", + devname, strerror(errno)); + return -1; + } + index++; + return 0; +} + static int parallel_parse(const char *devname) { static int index = 0; @@ -2875,6 +2916,9 @@ int main(int argc, char **argv, char **envp) default_monitor = 0; } break; + case QEMU_OPTION_ipmi: + add_device_config(DEV_IPMI, optarg); + break; case QEMU_OPTION_watchdog: if (watchdog) { fprintf(stderr, @@ -3520,6 +3564,8 @@ int main(int argc, char **argv, char **envp) if (foreach_device_config(DEV_SERIAL, serial_parse) < 0) exit(1); + if (foreach_device_config(DEV_IPMI, ipmi_parse) < 0) + exit(1); if (foreach_device_config(DEV_PARALLEL, parallel_parse) < 0) exit(1); if (foreach_device_config(DEV_VIRTCON, virtcon_parse) < 0) -- 1.7.4.1 -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html