qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 9/9] IPMI: Add an external connection simulation int


From: minyard
Subject: [Qemu-devel] [PATCH 9/9] IPMI: Add an external connection simulation interface
Date: Mon, 09 Jul 2012 14:17:09 -0500

From: Corey Minyard <address@hidden>

This adds an interface for IPMI that connects to a remote
BMC over a chardev (generally a TCP socket).  The OpenIPMI
lanserv simulator describes this interface, see that for
interface details.

Signed-off-by: Corey Minyard <address@hidden>
---
 default-configs/i386-softmmu.mak   |    1 +
 default-configs/x86_64-softmmu.mak |    1 +
 hw/Makefile.objs                   |    1 +
 hw/ipmi_extern.c                   |  421 ++++++++++++++++++++++++++++++++++++
 hw/ipmi_extern.h                   |   75 +++++++
 5 files changed, 499 insertions(+), 0 deletions(-)
 create mode 100644 hw/ipmi_extern.c
 create mode 100644 hw/ipmi_extern.h

diff --git a/default-configs/i386-softmmu.mak b/default-configs/i386-softmmu.mak
index 8c99d5d..325f92e 100644
--- a/default-configs/i386-softmmu.mak
+++ b/default-configs/i386-softmmu.mak
@@ -12,6 +12,7 @@ CONFIG_ISA_IPMI=y
 CONFIG_IPMI_KCS=y
 CONFIG_IPMI_BT=y
 CONFIG_IPMI_LOCAL=y
+CONFIG_IPMI_EXTERN=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 4d01883..2ac9177 100644
--- a/default-configs/x86_64-softmmu.mak
+++ b/default-configs/x86_64-softmmu.mak
@@ -12,6 +12,7 @@ CONFIG_ISA_IPMI=y
 CONFIG_IPMI_KCS=y
 CONFIG_IPMI_BT=y
 CONFIG_IPMI_LOCAL=y
+CONFIG_IPMI_EXTERN=y
 CONFIG_SERIAL=y
 CONFIG_PARALLEL=y
 CONFIG_I8254=y
diff --git a/hw/Makefile.objs b/hw/Makefile.objs
index 193227d..06757b0 100644
--- a/hw/Makefile.objs
+++ b/hw/Makefile.objs
@@ -25,6 +25,7 @@ hw-obj-$(CONFIG_ISA_IPMI) += isa_ipmi.o
 hw-obj-$(CONFIG_IPMI_KCS) += ipmi_kcs.o
 hw-obj-$(CONFIG_IPMI_BT) += ipmi_bt.o
 hw-obj-$(CONFIG_IPMI_LOCAL) += ipmi_sim.o
+hw-obj-$(CONFIG_IPMI_EXTERN) += ipmi_extern.o
 
 hw-obj-$(CONFIG_SERIAL) += serial.o
 hw-obj-$(CONFIG_PARALLEL) += parallel.o
diff --git a/hw/ipmi_extern.c b/hw/ipmi_extern.c
new file mode 100644
index 0000000..bbb8469
--- /dev/null
+++ b/hw/ipmi_extern.c
@@ -0,0 +1,421 @@
+/*
+ * IPMI BMC external connection
+ *
+ * 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.
+ */
+
+/*
+ * This is designed to connect with OpenIPMI's lanserv serial interface
+ * using the "VM" connection type.  See that for details.
+ */
+
+#include "ipmi_extern.h"
+
+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(IPMIState *s, IPMIExternState *es)
+{
+    if (es->outlen == 0)
+       goto check_reset;
+
+ send:
+    es->outpos += qemu_chr_fe_write(es->chr, es->outbuf + es->outpos,
+                                   es->outlen - es->outpos);
+    if (es->outpos < es->outlen) {
+       /* Not fully transmitted, try again in a 10ms */
+       qemu_mod_timer(es->extern_timer,
+                      qemu_get_clock_ns(vm_clock) + 10000000);
+    } else {
+       /* Sent */
+       es->outlen = 0;
+       es->outpos = 0;
+       if (!es->sending_cmd)
+           es->waiting_rsp = 1;
+       else
+           es->sending_cmd = 0;
+
+    check_reset:
+       if (es->send_reset) {
+           /* Send the reset */
+           es->outbuf[0] = VM_CMD_RESET;
+           es->outbuf[1] = VM_CMD_CHAR;
+           es->outlen = 2;
+           es->outpos = 0;
+           es->send_reset = 0;
+           es->sending_cmd = 1;
+           goto send;
+       }
+
+       if (es->waiting_rsp)
+           /* Make sure we get a response within 4 seconds. */
+           qemu_mod_timer(es->extern_timer,
+                          qemu_get_clock_ns(vm_clock) + 4000000000ULL);
+    }
+    return;
+}
+
+static void extern_timeout(void *opaque)
+{
+    IPMIState *s = opaque;
+    IPMIExternState *es = s->ifdata;
+
+    ipmi_lock();
+    if (!es->connected) {
+       if (es->is_listen)
+           goto out;
+       /* Try to reconnect every 10 seconds. */
+       qemu_mod_timer(es->extern_timer,
+                      qemu_get_clock_ns(vm_clock) + 10000000000ULL);
+       es->chr = qemu_chr_new_from_opts(s->chropts, NULL);
+       if (es->chr)
+           qemu_chr_add_handlers(es->chr, can_receive, receive, chr_event, s);
+    } else if (es->waiting_rsp && (es->outlen == 0)) {
+       /* The message response timed out, return an error. */
+       es->waiting_rsp = 0;
+       es->inbuf[1] = es->outbuf[1] | 0x04;
+       es->inbuf[2] = es->outbuf[2];
+       es->inbuf[3] = IPMI_CC_TIMEOUT;
+       s->handle_rsp(s, es->outbuf[0], es->inbuf + 1, 3);
+    } else {
+       continue_send(s, es);
+    }
+ out:
+    ipmi_unlock();
+}
+
+static void addchar(IPMIExternState *es, unsigned char ch)
+{
+    switch (ch) {
+    case VM_MSG_CHAR:
+    case VM_CMD_CHAR:
+    case VM_ESCAPE_CHAR:
+       es->outbuf[es->outlen] = VM_ESCAPE_CHAR;
+       es->outlen++;
+       ch |= 0x10;
+       /* No break */
+
+    default:
+       es->outbuf[es->outlen] = ch;
+       es->outlen++;
+    }
+}
+
+static void ipmi_extern_handle_command(IPMIState *s,
+                                      uint8_t *cmd, unsigned int cmd_len,
+                                      unsigned int max_cmd_len,
+                                      uint8_t msg_id)
+{
+    IPMIExternState *es = s->ifdata;
+    uint8_t err = 0, csum;
+    unsigned int i;
+
+    ipmi_lock();
+    if (es->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 (!es->connected)
+       err = IPMI_CC_BMC_INIT_IN_PROGRESS;
+    if (err) {
+       unsigned char rsp[3];
+       rsp[0] = cmd[0] | 0x04;
+       rsp[1] = cmd[1];
+       rsp[2] = err;
+       es->waiting_rsp = 0;
+       s->handle_rsp(s, msg_id, rsp, 3);
+       goto out;
+    }
+
+    addchar(es, msg_id);
+    for (i = 0; i < cmd_len; i++)
+       addchar(es, cmd[i]);
+
+    csum = ipmb_checksum(&msg_id, 1, 0);
+    addchar(es, -ipmb_checksum(cmd, cmd_len, csum));
+
+    es->outbuf[es->outlen] = VM_MSG_CHAR;
+    es->outlen++;
+
+    /* Start the transmit */
+    continue_send(s, es);
+
+ out:
+    ipmi_unlock();
+    return;
+}
+
+static void handle_hw_op(IPMIState *s, unsigned char hw_op)
+{
+    switch (hw_op) {
+    case VM_CMD_VERSION:
+       /* We only support one version at this time. */
+       break;
+
+    case VM_CMD_NOATTN:
+       s->set_atn(s, 0, 0);
+       break;
+
+    case VM_CMD_ATTN:
+       s->set_atn(s, 1, 0);
+       break;
+
+    case VM_CMD_ATTN_IRQ:
+       s->set_atn(s, 1, 1);
+       break;
+
+    case VM_CMD_POWEROFF:
+       s->do_hw_op(s, IPMI_POWEROFF_CHASSIS, 0);
+       break;
+
+    case VM_CMD_RESET:
+       s->do_hw_op(s, IPMI_RESET_CHASSIS, 0);
+       break;
+
+    case VM_CMD_ENABLE_IRQ:
+       s->set_irq_enable(s, 1);
+       break;
+
+    case VM_CMD_DISABLE_IRQ:
+       s->set_irq_enable(s, 0);
+       break;
+
+    case VM_CMD_SEND_NMI:
+       s->do_hw_op(s, IPMI_SEND_NMI, 0);
+       break;
+    }
+}
+
+static void handle_msg(IPMIState *s, IPMIExternState *es)
+{
+    if (es->in_escape) {
+       ipmi_debug("msg escape not ended\n");
+       return;
+    }
+    if (es->inpos < 5) {
+       ipmi_debug("msg too short\n");
+       return;
+    }
+    if (es->in_too_many) {
+       es->inbuf[3] = IPMI_CC_REQUEST_DATA_TRUNCATED;
+       es->inpos = 4;
+    } else if (ipmb_checksum(es->inbuf, es->inpos, 0) != 0) {
+       ipmi_debug("msg checksum failure\n");
+       return;
+    } else
+       es->inpos--; /* Remove checkum */
+
+    qemu_del_timer(es->extern_timer);
+    es->waiting_rsp = 0;
+    s->handle_rsp(s, es->inbuf[0], es->inbuf + 1, es->inpos - 1);
+}
+
+static int can_receive(void *opaque)
+{
+    return 1;
+}
+
+static void receive(void *opaque, const uint8_t *buf, int size)
+{
+    IPMIState *s = opaque;
+    IPMIExternState *es = s->ifdata;
+    int i;
+    unsigned char hw_op;
+
+    ipmi_lock();
+    for (i = 0; i < size; i++) {
+       unsigned char ch = buf[i];
+
+       switch (ch) {
+       case VM_MSG_CHAR:
+           handle_msg(s, es);
+           es->in_too_many = 0;
+           es->inpos = 0;
+           break;
+
+       case VM_CMD_CHAR:
+           if (es->in_too_many) {
+               ipmi_debug("cmd in too many\n");
+               es->in_too_many = 0;
+               es->inpos = 0;
+               break;
+           }
+           if (es->in_escape) {
+               ipmi_debug("cmd in escape\n");
+               es->in_too_many = 0;
+               es->inpos = 0;
+               es->in_escape = 0;
+               break;
+           }
+           es->in_too_many = 0;
+           if (es->inpos < 1)
+               break;
+           hw_op = es->inbuf[0];
+           es->inpos = 0;
+           goto out_hw_op;
+           break;
+
+       case VM_ESCAPE_CHAR:
+           es->in_escape = 1;
+           break;
+
+       default:
+           if (es->in_escape) {
+               ch &= ~0x10;
+               es->in_escape = 0;
+           }
+
+           if (es->in_too_many)
+               break;
+
+           if (es->inpos >= sizeof(es->inbuf)) {
+               es->in_too_many = 1;
+               break;
+           }
+
+           es->inbuf[es->inpos] = ch;
+           es->inpos++;
+           break;
+       }
+    }
+    ipmi_unlock();
+    return;
+
+ out_hw_op:
+    ipmi_unlock();
+    /*
+     * We don't want to handle hardware operations while holding the
+     * lock, that may call back into this code to report a reset.
+     */
+    handle_hw_op(s, hw_op);
+}
+
+static void chr_event(void *opaque, int event)
+{
+    IPMIState *s = opaque;
+    IPMIExternState *es = s->ifdata;
+    unsigned char v;
+
+    ipmi_lock();
+    switch (event) {
+    case CHR_EVENT_OPENED:
+       es->connected = 1;
+       es->outpos = 0;
+       es->outlen = 0;
+       addchar(es, VM_CMD_VERSION);
+       addchar(es, VM_PROTOCOL_VERSION);
+       es->outbuf[es->outlen] = VM_CMD_CHAR;
+       es->outlen++;
+       addchar(es, VM_CMD_CAPABILITIES);
+       v = VM_CAPABILITIES_IRQ | VM_CAPABILITIES_ATTN;
+       if (s->do_hw_op(s, IPMI_POWEROFF_CHASSIS, 1) == 0)
+           v |= VM_CAPABILITIES_POWER;
+       if (s->do_hw_op(s, IPMI_RESET_CHASSIS, 1) == 0)
+           v |= VM_CAPABILITIES_RESET;
+       if (s->do_hw_op(s, IPMI_SEND_NMI, 1) == 0)
+           v |= VM_CAPABILITIES_NMI;
+       addchar(es, v);
+       es->outbuf[es->outlen] = VM_CMD_CHAR;
+       es->outlen++;
+       es->sending_cmd = 0;
+       continue_send(s, es);
+       break;
+
+    case CHR_EVENT_CLOSED:
+       if (!es->connected)
+           return;
+       es->connected = 0;
+       if (es->is_listen)
+           return;
+       qemu_chr_delete(es->chr);
+       es->chr = NULL;
+       if (es->waiting_rsp) {
+           es->waiting_rsp = 0;
+           es->inbuf[1] = es->outbuf[1] | 0x04;
+           es->inbuf[2] = es->outbuf[2];
+           es->inbuf[3] = IPMI_CC_BMC_INIT_IN_PROGRESS;
+           s->handle_rsp(s, es->outbuf[0], es->inbuf + 1, 3);
+       }
+       /* Try to open in a bit */
+       qemu_mod_timer(es->extern_timer,
+                      qemu_get_clock_ns(vm_clock) + 10000000000ULL);
+       break;
+    }
+    ipmi_unlock();
+}
+
+static void ipmi_extern_handle_reset(IPMIState *s)
+{
+    IPMIExternState *es = s->ifdata;
+
+    ipmi_lock();
+    es->send_reset = 1;
+    continue_send(s, es);
+    ipmi_unlock();
+}
+
+static void ipmi_extern_init(IPMIState *s)
+{
+    IPMIExternState *es;
+
+    es = g_malloc0(sizeof(*es));
+    s->handle_command = ipmi_extern_handle_command;
+
+    s->ifdata = es;
+    es->is_listen = qemu_opt_get_bool(s->chropts, "server", 0);
+    es->extern_timer = qemu_new_timer_ns(vm_clock, extern_timeout, s);
+    if (!es->is_listen)
+       /* Make sure we connect in time. */
+       qemu_mod_timer(es->extern_timer,
+                      qemu_get_clock_ns(vm_clock) + 10000000000ULL);
+    es->chr = qemu_chr_new_from_opts(s->chropts, NULL);
+    if (es->chr)
+       qemu_chr_add_handlers(es->chr, can_receive, receive, chr_event, s);
+
+    s->handle_reset = ipmi_extern_handle_reset;
+}
+
+static void ipmi_extern_register_types(void)
+{
+    register_ipmi_sim(IPMI_SIM_EXTERNAL, ipmi_extern_init);
+}
+
+type_init(ipmi_extern_register_types)
diff --git a/hw/ipmi_extern.h b/hw/ipmi_extern.h
new file mode 100644
index 0000000..65a0834
--- /dev/null
+++ b/hw/ipmi_extern.h
@@ -0,0 +1,75 @@
+/*
+ * IPMI BMC external connection
+ *
+ * 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_EXTERN_H
+#define HW_IPMI_EXTERN_H
+
+#include <stdint.h>
+#include "qemu-timer.h"
+#include "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
+
+typedef struct IPMIExternState {
+    int connected;
+    int is_listen;
+    CharDriverState *chr;
+
+    unsigned char inbuf[MAX_IPMI_MSG_SIZE + 2];
+    unsigned int inpos;
+    int in_escape;
+    int in_too_many;
+    int waiting_rsp;
+    int 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. */
+    int send_reset;
+} IPMIExternState;
+
+#endif
-- 
1.7.4.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]