diff mbox series

[5/8] hw/ipmi: Take out common from ipmi_bmc_extern.c

Message ID 20210909230620.511815-6-wuhaotsh@google.com (mailing list archive)
State New, archived
Headers show
Series Handing IPMI for emulating BMC | expand

Commit Message

Hao Wu Sept. 9, 2021, 11:06 p.m. UTC
This patch refactors ipmi_bmc_extern.c and takes out the parts that can
be used both ipmi_bmc_extern.c and bmc_host_extern.c to a common file
ipmi_extern.c.

Now we have a connection called IPMIExtern which handles the connection,
and IPMIBmcExtern that handles core-side emulation specific stuff.

Basically most of the message transaction are moved. The stuff remained
are basically hardware operations like handle_reset and handle_hw_op.
These stuff have different behaviors in core-side and BMC-side
emulation.

Signed-off-by: Hao Wu <wuhaotsh@google.com>
---
 hw/ipmi/ipmi_bmc_extern.c | 420 ++++----------------------------------
 hw/ipmi/ipmi_extern.c     | 415 +++++++++++++++++++++++++++++++++++++
 hw/ipmi/ipmi_extern.h     |  90 ++++++++
 hw/ipmi/meson.build       |   2 +-
 4 files changed, 543 insertions(+), 384 deletions(-)
 create mode 100644 hw/ipmi/ipmi_extern.c
 create mode 100644 hw/ipmi/ipmi_extern.h

Comments

Corey Minyard Sept. 10, 2021, 12:27 a.m. UTC | #1
On Thu, Sep 09, 2021 at 04:06:17PM -0700, Hao Wu wrote:
> This patch refactors ipmi_bmc_extern.c and takes out the parts that can
> be used both ipmi_bmc_extern.c and bmc_host_extern.c to a common file
> ipmi_extern.c.
> 
> Now we have a connection called IPMIExtern which handles the connection,
> and IPMIBmcExtern that handles core-side emulation specific stuff.
> 
> Basically most of the message transaction are moved. The stuff remained
> are basically hardware operations like handle_reset and handle_hw_op.
> These stuff have different behaviors in core-side and BMC-side
> emulation.

Yeah, you are going to need this.

-corey

> 
> Signed-off-by: Hao Wu <wuhaotsh@google.com>
> ---
>  hw/ipmi/ipmi_bmc_extern.c | 420 ++++----------------------------------
>  hw/ipmi/ipmi_extern.c     | 415 +++++++++++++++++++++++++++++++++++++
>  hw/ipmi/ipmi_extern.h     |  90 ++++++++
>  hw/ipmi/meson.build       |   2 +-
>  4 files changed, 543 insertions(+), 384 deletions(-)
>  create mode 100644 hw/ipmi/ipmi_extern.c
>  create mode 100644 hw/ipmi/ipmi_extern.h
> 
> diff --git a/hw/ipmi/ipmi_bmc_extern.c b/hw/ipmi/ipmi_bmc_extern.c
> index a0c3a40e7c..24979ecfd5 100644
> --- a/hw/ipmi/ipmi_bmc_extern.c
> +++ b/hw/ipmi/ipmi_bmc_extern.c
> @@ -34,211 +34,43 @@
>  #include "qemu/timer.h"
>  #include "chardev/char-fe.h"
>  #include "hw/ipmi/ipmi.h"
> +#include "hw/ipmi/ipmi_extern.h"
>  #include "hw/qdev-properties.h"
>  #include "hw/qdev-properties-system.h"
>  #include "migration/vmstate.h"
>  #include "qom/object.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_CAPABILITIES_GRACEFUL_SHUTDOWN 0x20
> -#define VM_CMD_GRACEFUL_SHUTDOWN   0x09
> -
>  #define TYPE_IPMI_BMC_EXTERN "ipmi-bmc-extern"
>  OBJECT_DECLARE_SIMPLE_TYPE(IPMIBmcExtern, IPMI_BMC_EXTERN)
> +
>  struct IPMIBmcExtern {
>      IPMIBmc parent;
>  
> -    CharBackend 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;
> +    IPMIExtern conn;
>  
>      /* A reset event is pending to be sent upstream. */
>      bool send_reset;
>  };
>  
> -static unsigned char
> -ipmb_checksum(const unsigned char *data, int size, unsigned char start)
> +static void continue_send_bmc(IPMIBmcExtern *ibe)
>  {
> -        unsigned char csum = start;
> -
> -        for (; size > 0; size--, data++) {
> -                csum += *data;
> -        }
> -        return csum;
> -}
> -
> -static void continue_send(IPMIBmcExtern *ibe)
> -{
> -    int ret;
> -    if (ibe->outlen == 0) {
> -        goto check_reset;
> -    }
> - send:
> -    ret = qemu_chr_fe_write(&ibe->chr, ibe->outbuf + ibe->outpos,
> -                            ibe->outlen - ibe->outpos);
> -    if (ret > 0) {
> -        ibe->outpos += ret;
> -    }
> -    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) {
> +    if (continue_send(&ibe->conn)) {
> +        if (ibe->conn.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->conn.outbuf[0] = VM_CMD_RESET;
> +            ibe->conn.outbuf[1] = VM_CMD_CHAR;
> +            ibe->conn.outlen = 2;
> +            ibe->conn.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);
> +            ibe->conn.sending_cmd = true;
> +            continue_send(&ibe->conn);
>          }
>      }
> -    return;
>  }
>  
> -static void extern_timeout(void *opaque)
> +static void ipmi_bmc_handle_hw_op(IPMICore *ic, unsigned char hw_op,
> +                                  uint8_t operand)
>  {
> -    IPMICore *ic = opaque;
> -    IPMIBmcExtern *ibe = opaque;
> -    IPMIInterface *s = ic->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_msg(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;
> -        /* fall through */
> -    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)
> -{
> -    IPMICore *ic = IPMI_CORE(b);
> -    IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(b);
> -    IPMIInterface *s = ic->intf;
> -    uint8_t err = 0, csum;
> -    unsigned int i;
> -
> -    if (ibe->outlen) {
> -        /* We already have a command queued.  Shouldn't ever happen. */
> -        error_report("IPMI KCS: Got command when not finished with the"
> -                     " previous command");
> -        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_msg(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)
> -{
> -    IPMICore *ic = IPMI_CORE(ibe);
>      IPMIInterface *s = ic->intf;
>      IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s);
>  
> @@ -285,169 +117,22 @@ static void handle_hw_op(IPMIBmcExtern *ibe, unsigned char hw_op)
>      }
>  }
>  
> -static void handle_msg(IPMIBmcExtern *ibe)
> -{
> -    IPMICore *ic = IPMI_CORE(ibe);
> -    IPMIInterface *s = ic->intf;
> -    IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s);
> -
> -    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_msg(s, 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)
> +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 = 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;
> +    IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(b);
>  
> - out_hw_op:
> -    handle_hw_op(ibe, hw_op);
> +    ipmi_extern_handle_command(&ibe->conn, cmd, cmd_len, max_cmd_len, msg_id);
>  }
>  
> -static void chr_event(void *opaque, QEMUChrEvent event)
> +static void ipmi_bmc_extern_realize(DeviceState *dev, Error **errp)
>  {
> -    IPMICore *ic = opaque;
> -    IPMIBmcExtern *ibe = opaque;
> -    IPMIInterface *s = ic->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(s, IPMI_POWEROFF_CHASSIS, 1) == 0) {
> -            v |= VM_CAPABILITIES_POWER;
> -        }
> -        if (k->do_hw_op(s, IPMI_SHUTDOWN_VIA_ACPI_OVERTEMP, 1)
> -            == 0) {
> -            v |= VM_CAPABILITIES_GRACEFUL_SHUTDOWN;
> -        }
> -        if (k->do_hw_op(s, IPMI_RESET_CHASSIS, 1) == 0) {
> -            v |= VM_CAPABILITIES_RESET;
> -        }
> -        if (k->do_hw_op(s, 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;
> -        /*
> -         * Don't hang the OS trying to handle the ATN bit, other end will
> -         * resend on a reconnect.
> -         */
> -        k->set_atn(s, 0, 0);
> -        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_msg(s, ibe->outbuf[0], ibe->inbuf + 1, 3);
> -        }
> -        break;
> +    IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(dev);
>  
> -    case CHR_EVENT_BREAK:
> -    case CHR_EVENT_MUX_IN:
> -    case CHR_EVENT_MUX_OUT:
> -        /* Ignore */
> -        break;
> +    if (!qdev_realize(DEVICE(&ibe->conn), NULL, errp)) {
> +        return;
>      }
>  }
>  
> @@ -456,42 +141,14 @@ 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 (!qemu_chr_fe_backend_connected(&ibe->chr)) {
> -        error_setg(errp, "IPMI external bmc requires chardev attribute");
> -        return;
> -    }
> -
> -    qemu_chr_fe_set_handlers(&ibe->chr, can_receive, receive,
> -                             chr_event, NULL, ibe, NULL, true);
> +    continue_send_bmc(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) {
> -        IPMICore *ic = opaque;
> -        IPMIInterface *ii = ic->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_msg(ii, ibe->outbuf[0], ibe->inbuf + 1, 3);
> -    }
> -    return 0;
> +    IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(opaque);
> +
> +    return ipmi_extern_post_migrate(&ibe->conn, version_id);
>  }
>  
>  static const VMStateDescription vmstate_ipmi_bmc_extern = {
> @@ -501,28 +158,24 @@ static const VMStateDescription vmstate_ipmi_bmc_extern = {
>      .post_load = ipmi_bmc_extern_post_migrate,
>      .fields      = (VMStateField[]) {
>          VMSTATE_BOOL(send_reset, IPMIBmcExtern),
> -        VMSTATE_BOOL(waiting_rsp, IPMIBmcExtern),
> +        VMSTATE_BOOL(conn.waiting_rsp, IPMIBmcExtern),
>          VMSTATE_END_OF_LIST()
>      }
>  };
>  
>  static void ipmi_bmc_extern_init(Object *obj)
>  {
> +    IPMICore *ic = IPMI_CORE(obj);
>      IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(obj);
>  
> -    ibe->extern_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, extern_timeout, ibe);
> +    object_initialize_child(obj, "extern", &ibe->conn,
> +                            TYPE_IPMI_EXTERN);
> +    ibe->conn.core = ic;
>      vmstate_register(NULL, 0, &vmstate_ipmi_bmc_extern, ibe);
>  }
>  
> -static void ipmi_bmc_extern_finalize(Object *obj)
> -{
> -    IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(obj);
> -
> -    timer_free(ibe->extern_timer);
> -}
> -
>  static Property ipmi_bmc_extern_properties[] = {
> -    DEFINE_PROP_CHR("chardev", IPMIBmcExtern, chr),
> +    DEFINE_PROP_CHR("chardev", IPMIBmcExtern, conn.chr),
>      DEFINE_PROP_END_OF_LIST(),
>  };
>  
> @@ -530,9 +183,11 @@ static void ipmi_bmc_extern_class_init(ObjectClass *oc, void *data)
>  {
>      DeviceClass *dc = DEVICE_CLASS(oc);
>      IPMIBmcClass *bk = IPMI_BMC_CLASS(oc);
> +    IPMICoreClass *ck = IPMI_CORE_CLASS(oc);
>  
>      bk->handle_command = ipmi_bmc_extern_handle_command;
>      bk->handle_reset = ipmi_bmc_extern_handle_reset;
> +    ck->handle_hw_op = ipmi_bmc_handle_hw_op;
>      dc->hotpluggable = false;
>      dc->realize = ipmi_bmc_extern_realize;
>      device_class_set_props(dc, ipmi_bmc_extern_properties);
> @@ -543,9 +198,8 @@ static const TypeInfo ipmi_bmc_extern_type = {
>      .parent        = TYPE_IPMI_BMC,
>      .instance_size = sizeof(IPMIBmcExtern),
>      .instance_init = ipmi_bmc_extern_init,
> -    .instance_finalize = ipmi_bmc_extern_finalize,
>      .class_init    = ipmi_bmc_extern_class_init,
> - };
> +};
>  
>  static void ipmi_bmc_extern_register_types(void)
>  {
> diff --git a/hw/ipmi/ipmi_extern.c b/hw/ipmi/ipmi_extern.c
> new file mode 100644
> index 0000000000..f139eaef24
> --- /dev/null
> +++ b/hw/ipmi/ipmi_extern.c
> @@ -0,0 +1,415 @@
> +/*
> + * IPMI 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.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/error-report.h"
> +#include "qemu/module.h"
> +#include "qapi/error.h"
> +#include "qemu/timer.h"
> +#include "chardev/char-fe.h"
> +#include "hw/ipmi/ipmi.h"
> +#include "hw/ipmi/ipmi_extern.h"
> +#include "hw/qdev-properties.h"
> +#include "migration/vmstate.h"
> +#include "qom/object.h"
> +
> +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;
> +}
> +
> +/* Returns whether check_reset is required for IPMI_BMC_EXTERN. */
> +bool continue_send(IPMIExtern *ibe)
> +{
> +    int ret;
> +    if (ibe->outlen == 0) {
> +        return true;
> +    }
> +    ret = qemu_chr_fe_write(&ibe->chr, ibe->outbuf + ibe->outpos,
> +                            ibe->outlen - ibe->outpos);
> +    if (ret > 0) {
> +        ibe->outpos += ret;
> +    }
> +    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);
> +        return false;
> +    } else {
> +        /* Sent */
> +        ibe->outlen = 0;
> +        ibe->outpos = 0;
> +        if (!ibe->bmc_side && !ibe->sending_cmd) {
> +            ibe->waiting_rsp = true;
> +        } else {
> +            ibe->sending_cmd = false;
> +        }
> +
> +        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 true;
> +    }
> +}
> +
> +static void extern_timeout(void *opaque)
> +{
> +    IPMIExtern *ibe = opaque;
> +    IPMIInterface *s = ibe->core->intf;
> +
> +    if (ibe->connected) {
> +        /*TODO: only core-side */
> +        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_msg(s, ibe->outbuf[0], ibe->inbuf + 1, 3);
> +        } else {
> +            continue_send(ibe);
> +        }
> +    }
> +}
> +
> +static void addchar(IPMIExtern *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;
> +        /* fall through */
> +    default:
> +        ibe->outbuf[ibe->outlen] = ch;
> +        ibe->outlen++;
> +    }
> +}
> +
> +void ipmi_extern_handle_command(IPMIExtern *ibe,
> +                                       uint8_t *cmd, unsigned int cmd_len,
> +                                       unsigned int max_cmd_len,
> +                                       uint8_t msg_id)
> +{
> +    IPMIInterface *s = ibe->core->intf;
> +    uint8_t err = 0, csum;
> +    unsigned int i;
> +
> +    if (ibe->outlen) {
> +        /* We already have a command queued.  Shouldn't ever happen. */
> +        error_report("IPMI KCS: Got command when not finished with the"
> +                     " previous command");
> +        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_msg(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_msg(IPMIExtern *ibe)
> +{
> +    IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(ibe->core->intf);
> +
> +    if (ibe->in_escape) {
> +        ipmi_debug("msg escape not ended\n");
> +        return;
> +    }
> +    if (ibe->inpos < (ibe->bmc_side ? 4 : 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_msg(ibe->core->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)
> +{
> +    IPMIExtern *ibe = opaque;
> +    IPMICoreClass *ck = IPMI_CORE_GET_CLASS(ibe->core);
> +    int i;
> +    unsigned char hw_op;
> +    unsigned char hw_operand = 0;
> +
> +    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];
> +            if (ibe->inpos > 1) {
> +                hw_operand = ibe->inbuf[1];
> +            }
> +            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:
> +    ck->handle_hw_op(ibe->core, hw_op, hw_operand);
> +}
> +
> +static void chr_event(void *opaque, QEMUChrEvent event)
> +{
> +    IPMIExtern *ibe = opaque;
> +    IPMIInterface *s = ibe->core->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++;
> +        /* Only send capability for core side. */
> +        if (!ibe->bmc_side) {
> +            addchar(ibe, VM_CMD_CAPABILITIES);
> +            v = VM_CAPABILITIES_IRQ | VM_CAPABILITIES_ATTN;
> +            if (k->do_hw_op(ibe->core->intf, IPMI_POWEROFF_CHASSIS, 1) == 0) {
> +                v |= VM_CAPABILITIES_POWER;
> +            }
> +            if (k->do_hw_op(ibe->core->intf, IPMI_SHUTDOWN_VIA_ACPI_OVERTEMP, 1)
> +                == 0) {
> +                v |= VM_CAPABILITIES_GRACEFUL_SHUTDOWN;
> +            }
> +            if (k->do_hw_op(ibe->core->intf, IPMI_RESET_CHASSIS, 1) == 0) {
> +                v |= VM_CAPABILITIES_RESET;
> +            }
> +            if (k->do_hw_op(ibe->core->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;
> +        /*
> +         * Don't hang the OS trying to handle the ATN bit, other end will
> +         * resend on a reconnect.
> +         */
> +        k->set_atn(s, 0, 0);
> +        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_msg(s, ibe->outbuf[0], ibe->inbuf + 1, 3);
> +        }
> +        break;
> +
> +    case CHR_EVENT_BREAK:
> +    case CHR_EVENT_MUX_IN:
> +    case CHR_EVENT_MUX_OUT:
> +        /* Ignore */
> +        break;
> +    }
> +}
> +
> +static void ipmi_extern_realize(DeviceState *dev, Error **errp)
> +{
> +    IPMIExtern *ibe = IPMI_EXTERN(dev);
> +
> +    if (!qemu_chr_fe_backend_connected(&ibe->chr)) {
> +        error_setg(errp, "IPMI external bmc requires chardev attribute");
> +        return;
> +    }
> +
> +    qemu_chr_fe_set_handlers(&ibe->chr, can_receive, receive,
> +                             chr_event, NULL, ibe, NULL, true);
> +}
> +
> +int ipmi_extern_post_migrate(IPMIExtern *ibe, int version_id)
> +{
> +    /*
> +     * 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->core->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_msg(ii, ibe->outbuf[0], ibe->inbuf + 1, 3);
> +    }
> +    return 0;
> +}
> +
> +static void ipmi_extern_init(Object *obj)
> +{
> +    IPMIExtern *ibe = IPMI_EXTERN(obj);
> +
> +    ibe->extern_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, extern_timeout, ibe);
> +}
> +
> +static void ipmi_extern_finalize(Object *obj)
> +{
> +    IPMIExtern *ibe = IPMI_EXTERN(obj);
> +
> +    timer_del(ibe->extern_timer);
> +    timer_free(ibe->extern_timer);
> +}
> +
> +
> +static void ipmi_extern_class_init(ObjectClass *oc, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(oc);
> +
> +    dc->hotpluggable = false;
> +    dc->realize = ipmi_extern_realize;
> +}
> +
> +static const TypeInfo ipmi_extern_type = {
> +    .name          = TYPE_IPMI_EXTERN,
> +    .parent        = TYPE_DEVICE,
> +    .instance_size = sizeof(IPMIExtern),
> +    .instance_init = ipmi_extern_init,
> +    .instance_finalize = ipmi_extern_finalize,
> +    .class_init    = ipmi_extern_class_init,
> + };
> +
> +static void ipmi_extern_register_types(void)
> +{
> +    type_register_static(&ipmi_extern_type);
> +}
> +
> +type_init(ipmi_extern_register_types)
> diff --git a/hw/ipmi/ipmi_extern.h b/hw/ipmi/ipmi_extern.h
> new file mode 100644
> index 0000000000..e4aa80a0f6
> --- /dev/null
> +++ b/hw/ipmi/ipmi_extern.h
> @@ -0,0 +1,90 @@
> +/*
> + * IPMI 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.
> + */
> +
> +#ifndef HW_IPMI_EXTERN_H
> +#define HW_IPMI_EXTERN_H
> +
> +#include "qemu/osdep.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_CAPABILITIES_GRACEFUL_SHUTDOWN 0x20
> +#define VM_CMD_GRACEFUL_SHUTDOWN   0x09
> +
> +typedef struct IPMIExtern {
> +    DeviceState parent;
> +
> +    CharBackend chr;
> +
> +    IPMICore *core;
> +
> +    bool bmc_side;
> +    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;
> +} IPMIExtern;
> +
> +#define TYPE_IPMI_EXTERN "ipmi-extern"
> +#define IPMI_EXTERN(obj) \
> +    OBJECT_CHECK(IPMIExtern, (obj), TYPE_IPMI_EXTERN)
> +
> +void ipmi_extern_handle_command(IPMIExtern *ibe,
> +                                uint8_t *cmd, unsigned int cmd_len,
> +                                unsigned int max_cmd_len,
> +                                uint8_t msg_id);
> +
> +bool continue_send(IPMIExtern *ibe);
> +int ipmi_extern_post_migrate(IPMIExtern *ibe, int version_id);
> +
> +#endif /* HW_IPMI_EXTERN_H */
> diff --git a/hw/ipmi/meson.build b/hw/ipmi/meson.build
> index 9622ea2a2c..edd0bf9af9 100644
> --- a/hw/ipmi/meson.build
> +++ b/hw/ipmi/meson.build
> @@ -1,5 +1,5 @@
>  ipmi_ss = ss.source_set()
> -ipmi_ss.add(when: 'CONFIG_IPMI', if_true: files('ipmi.c', 'ipmi_kcs.c', 'ipmi_bt.c'))
> +ipmi_ss.add(when: 'CONFIG_IPMI', if_true: files('ipmi.c', 'ipmi_kcs.c', 'ipmi_bt.c', 'ipmi_extern.c'))
>  ipmi_ss.add(when: 'CONFIG_IPMI_LOCAL', if_true: files('ipmi_bmc_sim.c'))
>  ipmi_ss.add(when: 'CONFIG_IPMI_EXTERN', if_true: files('ipmi_bmc_extern.c'))
>  ipmi_ss.add(when: 'CONFIG_ISA_IPMI_KCS', if_true: files('isa_ipmi_kcs.c'))
> -- 
> 2.33.0.309.g3052b89438-goog
>
diff mbox series

Patch

diff --git a/hw/ipmi/ipmi_bmc_extern.c b/hw/ipmi/ipmi_bmc_extern.c
index a0c3a40e7c..24979ecfd5 100644
--- a/hw/ipmi/ipmi_bmc_extern.c
+++ b/hw/ipmi/ipmi_bmc_extern.c
@@ -34,211 +34,43 @@ 
 #include "qemu/timer.h"
 #include "chardev/char-fe.h"
 #include "hw/ipmi/ipmi.h"
+#include "hw/ipmi/ipmi_extern.h"
 #include "hw/qdev-properties.h"
 #include "hw/qdev-properties-system.h"
 #include "migration/vmstate.h"
 #include "qom/object.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_CAPABILITIES_GRACEFUL_SHUTDOWN 0x20
-#define VM_CMD_GRACEFUL_SHUTDOWN   0x09
-
 #define TYPE_IPMI_BMC_EXTERN "ipmi-bmc-extern"
 OBJECT_DECLARE_SIMPLE_TYPE(IPMIBmcExtern, IPMI_BMC_EXTERN)
+
 struct IPMIBmcExtern {
     IPMIBmc parent;
 
-    CharBackend 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;
+    IPMIExtern conn;
 
     /* A reset event is pending to be sent upstream. */
     bool send_reset;
 };
 
-static unsigned char
-ipmb_checksum(const unsigned char *data, int size, unsigned char start)
+static void continue_send_bmc(IPMIBmcExtern *ibe)
 {
-        unsigned char csum = start;
-
-        for (; size > 0; size--, data++) {
-                csum += *data;
-        }
-        return csum;
-}
-
-static void continue_send(IPMIBmcExtern *ibe)
-{
-    int ret;
-    if (ibe->outlen == 0) {
-        goto check_reset;
-    }
- send:
-    ret = qemu_chr_fe_write(&ibe->chr, ibe->outbuf + ibe->outpos,
-                            ibe->outlen - ibe->outpos);
-    if (ret > 0) {
-        ibe->outpos += ret;
-    }
-    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) {
+    if (continue_send(&ibe->conn)) {
+        if (ibe->conn.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->conn.outbuf[0] = VM_CMD_RESET;
+            ibe->conn.outbuf[1] = VM_CMD_CHAR;
+            ibe->conn.outlen = 2;
+            ibe->conn.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);
+            ibe->conn.sending_cmd = true;
+            continue_send(&ibe->conn);
         }
     }
-    return;
 }
 
-static void extern_timeout(void *opaque)
+static void ipmi_bmc_handle_hw_op(IPMICore *ic, unsigned char hw_op,
+                                  uint8_t operand)
 {
-    IPMICore *ic = opaque;
-    IPMIBmcExtern *ibe = opaque;
-    IPMIInterface *s = ic->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_msg(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;
-        /* fall through */
-    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)
-{
-    IPMICore *ic = IPMI_CORE(b);
-    IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(b);
-    IPMIInterface *s = ic->intf;
-    uint8_t err = 0, csum;
-    unsigned int i;
-
-    if (ibe->outlen) {
-        /* We already have a command queued.  Shouldn't ever happen. */
-        error_report("IPMI KCS: Got command when not finished with the"
-                     " previous command");
-        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_msg(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)
-{
-    IPMICore *ic = IPMI_CORE(ibe);
     IPMIInterface *s = ic->intf;
     IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s);
 
@@ -285,169 +117,22 @@  static void handle_hw_op(IPMIBmcExtern *ibe, unsigned char hw_op)
     }
 }
 
-static void handle_msg(IPMIBmcExtern *ibe)
-{
-    IPMICore *ic = IPMI_CORE(ibe);
-    IPMIInterface *s = ic->intf;
-    IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s);
-
-    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_msg(s, 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)
+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 = 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;
+    IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(b);
 
- out_hw_op:
-    handle_hw_op(ibe, hw_op);
+    ipmi_extern_handle_command(&ibe->conn, cmd, cmd_len, max_cmd_len, msg_id);
 }
 
-static void chr_event(void *opaque, QEMUChrEvent event)
+static void ipmi_bmc_extern_realize(DeviceState *dev, Error **errp)
 {
-    IPMICore *ic = opaque;
-    IPMIBmcExtern *ibe = opaque;
-    IPMIInterface *s = ic->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(s, IPMI_POWEROFF_CHASSIS, 1) == 0) {
-            v |= VM_CAPABILITIES_POWER;
-        }
-        if (k->do_hw_op(s, IPMI_SHUTDOWN_VIA_ACPI_OVERTEMP, 1)
-            == 0) {
-            v |= VM_CAPABILITIES_GRACEFUL_SHUTDOWN;
-        }
-        if (k->do_hw_op(s, IPMI_RESET_CHASSIS, 1) == 0) {
-            v |= VM_CAPABILITIES_RESET;
-        }
-        if (k->do_hw_op(s, 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;
-        /*
-         * Don't hang the OS trying to handle the ATN bit, other end will
-         * resend on a reconnect.
-         */
-        k->set_atn(s, 0, 0);
-        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_msg(s, ibe->outbuf[0], ibe->inbuf + 1, 3);
-        }
-        break;
+    IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(dev);
 
-    case CHR_EVENT_BREAK:
-    case CHR_EVENT_MUX_IN:
-    case CHR_EVENT_MUX_OUT:
-        /* Ignore */
-        break;
+    if (!qdev_realize(DEVICE(&ibe->conn), NULL, errp)) {
+        return;
     }
 }
 
@@ -456,42 +141,14 @@  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 (!qemu_chr_fe_backend_connected(&ibe->chr)) {
-        error_setg(errp, "IPMI external bmc requires chardev attribute");
-        return;
-    }
-
-    qemu_chr_fe_set_handlers(&ibe->chr, can_receive, receive,
-                             chr_event, NULL, ibe, NULL, true);
+    continue_send_bmc(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) {
-        IPMICore *ic = opaque;
-        IPMIInterface *ii = ic->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_msg(ii, ibe->outbuf[0], ibe->inbuf + 1, 3);
-    }
-    return 0;
+    IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(opaque);
+
+    return ipmi_extern_post_migrate(&ibe->conn, version_id);
 }
 
 static const VMStateDescription vmstate_ipmi_bmc_extern = {
@@ -501,28 +158,24 @@  static const VMStateDescription vmstate_ipmi_bmc_extern = {
     .post_load = ipmi_bmc_extern_post_migrate,
     .fields      = (VMStateField[]) {
         VMSTATE_BOOL(send_reset, IPMIBmcExtern),
-        VMSTATE_BOOL(waiting_rsp, IPMIBmcExtern),
+        VMSTATE_BOOL(conn.waiting_rsp, IPMIBmcExtern),
         VMSTATE_END_OF_LIST()
     }
 };
 
 static void ipmi_bmc_extern_init(Object *obj)
 {
+    IPMICore *ic = IPMI_CORE(obj);
     IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(obj);
 
-    ibe->extern_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, extern_timeout, ibe);
+    object_initialize_child(obj, "extern", &ibe->conn,
+                            TYPE_IPMI_EXTERN);
+    ibe->conn.core = ic;
     vmstate_register(NULL, 0, &vmstate_ipmi_bmc_extern, ibe);
 }
 
-static void ipmi_bmc_extern_finalize(Object *obj)
-{
-    IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(obj);
-
-    timer_free(ibe->extern_timer);
-}
-
 static Property ipmi_bmc_extern_properties[] = {
-    DEFINE_PROP_CHR("chardev", IPMIBmcExtern, chr),
+    DEFINE_PROP_CHR("chardev", IPMIBmcExtern, conn.chr),
     DEFINE_PROP_END_OF_LIST(),
 };
 
@@ -530,9 +183,11 @@  static void ipmi_bmc_extern_class_init(ObjectClass *oc, void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(oc);
     IPMIBmcClass *bk = IPMI_BMC_CLASS(oc);
+    IPMICoreClass *ck = IPMI_CORE_CLASS(oc);
 
     bk->handle_command = ipmi_bmc_extern_handle_command;
     bk->handle_reset = ipmi_bmc_extern_handle_reset;
+    ck->handle_hw_op = ipmi_bmc_handle_hw_op;
     dc->hotpluggable = false;
     dc->realize = ipmi_bmc_extern_realize;
     device_class_set_props(dc, ipmi_bmc_extern_properties);
@@ -543,9 +198,8 @@  static const TypeInfo ipmi_bmc_extern_type = {
     .parent        = TYPE_IPMI_BMC,
     .instance_size = sizeof(IPMIBmcExtern),
     .instance_init = ipmi_bmc_extern_init,
-    .instance_finalize = ipmi_bmc_extern_finalize,
     .class_init    = ipmi_bmc_extern_class_init,
- };
+};
 
 static void ipmi_bmc_extern_register_types(void)
 {
diff --git a/hw/ipmi/ipmi_extern.c b/hw/ipmi/ipmi_extern.c
new file mode 100644
index 0000000000..f139eaef24
--- /dev/null
+++ b/hw/ipmi/ipmi_extern.c
@@ -0,0 +1,415 @@ 
+/*
+ * IPMI 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.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "qemu/timer.h"
+#include "chardev/char-fe.h"
+#include "hw/ipmi/ipmi.h"
+#include "hw/ipmi/ipmi_extern.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qom/object.h"
+
+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;
+}
+
+/* Returns whether check_reset is required for IPMI_BMC_EXTERN. */
+bool continue_send(IPMIExtern *ibe)
+{
+    int ret;
+    if (ibe->outlen == 0) {
+        return true;
+    }
+    ret = qemu_chr_fe_write(&ibe->chr, ibe->outbuf + ibe->outpos,
+                            ibe->outlen - ibe->outpos);
+    if (ret > 0) {
+        ibe->outpos += ret;
+    }
+    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);
+        return false;
+    } else {
+        /* Sent */
+        ibe->outlen = 0;
+        ibe->outpos = 0;
+        if (!ibe->bmc_side && !ibe->sending_cmd) {
+            ibe->waiting_rsp = true;
+        } else {
+            ibe->sending_cmd = false;
+        }
+
+        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 true;
+    }
+}
+
+static void extern_timeout(void *opaque)
+{
+    IPMIExtern *ibe = opaque;
+    IPMIInterface *s = ibe->core->intf;
+
+    if (ibe->connected) {
+        /*TODO: only core-side */
+        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_msg(s, ibe->outbuf[0], ibe->inbuf + 1, 3);
+        } else {
+            continue_send(ibe);
+        }
+    }
+}
+
+static void addchar(IPMIExtern *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;
+        /* fall through */
+    default:
+        ibe->outbuf[ibe->outlen] = ch;
+        ibe->outlen++;
+    }
+}
+
+void ipmi_extern_handle_command(IPMIExtern *ibe,
+                                       uint8_t *cmd, unsigned int cmd_len,
+                                       unsigned int max_cmd_len,
+                                       uint8_t msg_id)
+{
+    IPMIInterface *s = ibe->core->intf;
+    uint8_t err = 0, csum;
+    unsigned int i;
+
+    if (ibe->outlen) {
+        /* We already have a command queued.  Shouldn't ever happen. */
+        error_report("IPMI KCS: Got command when not finished with the"
+                     " previous command");
+        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_msg(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_msg(IPMIExtern *ibe)
+{
+    IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(ibe->core->intf);
+
+    if (ibe->in_escape) {
+        ipmi_debug("msg escape not ended\n");
+        return;
+    }
+    if (ibe->inpos < (ibe->bmc_side ? 4 : 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_msg(ibe->core->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)
+{
+    IPMIExtern *ibe = opaque;
+    IPMICoreClass *ck = IPMI_CORE_GET_CLASS(ibe->core);
+    int i;
+    unsigned char hw_op;
+    unsigned char hw_operand = 0;
+
+    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];
+            if (ibe->inpos > 1) {
+                hw_operand = ibe->inbuf[1];
+            }
+            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:
+    ck->handle_hw_op(ibe->core, hw_op, hw_operand);
+}
+
+static void chr_event(void *opaque, QEMUChrEvent event)
+{
+    IPMIExtern *ibe = opaque;
+    IPMIInterface *s = ibe->core->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++;
+        /* Only send capability for core side. */
+        if (!ibe->bmc_side) {
+            addchar(ibe, VM_CMD_CAPABILITIES);
+            v = VM_CAPABILITIES_IRQ | VM_CAPABILITIES_ATTN;
+            if (k->do_hw_op(ibe->core->intf, IPMI_POWEROFF_CHASSIS, 1) == 0) {
+                v |= VM_CAPABILITIES_POWER;
+            }
+            if (k->do_hw_op(ibe->core->intf, IPMI_SHUTDOWN_VIA_ACPI_OVERTEMP, 1)
+                == 0) {
+                v |= VM_CAPABILITIES_GRACEFUL_SHUTDOWN;
+            }
+            if (k->do_hw_op(ibe->core->intf, IPMI_RESET_CHASSIS, 1) == 0) {
+                v |= VM_CAPABILITIES_RESET;
+            }
+            if (k->do_hw_op(ibe->core->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;
+        /*
+         * Don't hang the OS trying to handle the ATN bit, other end will
+         * resend on a reconnect.
+         */
+        k->set_atn(s, 0, 0);
+        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_msg(s, ibe->outbuf[0], ibe->inbuf + 1, 3);
+        }
+        break;
+
+    case CHR_EVENT_BREAK:
+    case CHR_EVENT_MUX_IN:
+    case CHR_EVENT_MUX_OUT:
+        /* Ignore */
+        break;
+    }
+}
+
+static void ipmi_extern_realize(DeviceState *dev, Error **errp)
+{
+    IPMIExtern *ibe = IPMI_EXTERN(dev);
+
+    if (!qemu_chr_fe_backend_connected(&ibe->chr)) {
+        error_setg(errp, "IPMI external bmc requires chardev attribute");
+        return;
+    }
+
+    qemu_chr_fe_set_handlers(&ibe->chr, can_receive, receive,
+                             chr_event, NULL, ibe, NULL, true);
+}
+
+int ipmi_extern_post_migrate(IPMIExtern *ibe, int version_id)
+{
+    /*
+     * 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->core->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_msg(ii, ibe->outbuf[0], ibe->inbuf + 1, 3);
+    }
+    return 0;
+}
+
+static void ipmi_extern_init(Object *obj)
+{
+    IPMIExtern *ibe = IPMI_EXTERN(obj);
+
+    ibe->extern_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, extern_timeout, ibe);
+}
+
+static void ipmi_extern_finalize(Object *obj)
+{
+    IPMIExtern *ibe = IPMI_EXTERN(obj);
+
+    timer_del(ibe->extern_timer);
+    timer_free(ibe->extern_timer);
+}
+
+
+static void ipmi_extern_class_init(ObjectClass *oc, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(oc);
+
+    dc->hotpluggable = false;
+    dc->realize = ipmi_extern_realize;
+}
+
+static const TypeInfo ipmi_extern_type = {
+    .name          = TYPE_IPMI_EXTERN,
+    .parent        = TYPE_DEVICE,
+    .instance_size = sizeof(IPMIExtern),
+    .instance_init = ipmi_extern_init,
+    .instance_finalize = ipmi_extern_finalize,
+    .class_init    = ipmi_extern_class_init,
+ };
+
+static void ipmi_extern_register_types(void)
+{
+    type_register_static(&ipmi_extern_type);
+}
+
+type_init(ipmi_extern_register_types)
diff --git a/hw/ipmi/ipmi_extern.h b/hw/ipmi/ipmi_extern.h
new file mode 100644
index 0000000000..e4aa80a0f6
--- /dev/null
+++ b/hw/ipmi/ipmi_extern.h
@@ -0,0 +1,90 @@ 
+/*
+ * IPMI 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.
+ */
+
+#ifndef HW_IPMI_EXTERN_H
+#define HW_IPMI_EXTERN_H
+
+#include "qemu/osdep.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_CAPABILITIES_GRACEFUL_SHUTDOWN 0x20
+#define VM_CMD_GRACEFUL_SHUTDOWN   0x09
+
+typedef struct IPMIExtern {
+    DeviceState parent;
+
+    CharBackend chr;
+
+    IPMICore *core;
+
+    bool bmc_side;
+    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;
+} IPMIExtern;
+
+#define TYPE_IPMI_EXTERN "ipmi-extern"
+#define IPMI_EXTERN(obj) \
+    OBJECT_CHECK(IPMIExtern, (obj), TYPE_IPMI_EXTERN)
+
+void ipmi_extern_handle_command(IPMIExtern *ibe,
+                                uint8_t *cmd, unsigned int cmd_len,
+                                unsigned int max_cmd_len,
+                                uint8_t msg_id);
+
+bool continue_send(IPMIExtern *ibe);
+int ipmi_extern_post_migrate(IPMIExtern *ibe, int version_id);
+
+#endif /* HW_IPMI_EXTERN_H */
diff --git a/hw/ipmi/meson.build b/hw/ipmi/meson.build
index 9622ea2a2c..edd0bf9af9 100644
--- a/hw/ipmi/meson.build
+++ b/hw/ipmi/meson.build
@@ -1,5 +1,5 @@ 
 ipmi_ss = ss.source_set()
-ipmi_ss.add(when: 'CONFIG_IPMI', if_true: files('ipmi.c', 'ipmi_kcs.c', 'ipmi_bt.c'))
+ipmi_ss.add(when: 'CONFIG_IPMI', if_true: files('ipmi.c', 'ipmi_kcs.c', 'ipmi_bt.c', 'ipmi_extern.c'))
 ipmi_ss.add(when: 'CONFIG_IPMI_LOCAL', if_true: files('ipmi_bmc_sim.c'))
 ipmi_ss.add(when: 'CONFIG_IPMI_EXTERN', if_true: files('ipmi_bmc_extern.c'))
 ipmi_ss.add(when: 'CONFIG_ISA_IPMI_KCS', if_true: files('isa_ipmi_kcs.c'))