qemu/hw/ipmi/ipmi_bmc_extern.c
<<
>>
Prefs
   1/*
   2 * IPMI BMC external connection
   3 *
   4 * Copyright (c) 2015 Corey Minyard, MontaVista Software, LLC
   5 *
   6 * Permission is hereby granted, free of charge, to any person obtaining a copy
   7 * of this software and associated documentation files (the "Software"), to deal
   8 * in the Software without restriction, including without limitation the rights
   9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10 * copies of the Software, and to permit persons to whom the Software is
  11 * furnished to do so, subject to the following conditions:
  12 *
  13 * The above copyright notice and this permission notice shall be included in
  14 * all copies or substantial portions of the Software.
  15 *
  16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22 * THE SOFTWARE.
  23 */
  24
  25/*
  26 * This is designed to connect with OpenIPMI's lanserv serial interface
  27 * using the "VM" connection type.  See that for details.
  28 */
  29
  30#include "qemu/osdep.h"
  31#include "qemu/error-report.h"
  32#include "qemu/module.h"
  33#include "qapi/error.h"
  34#include "qemu/timer.h"
  35#include "chardev/char-fe.h"
  36#include "hw/ipmi/ipmi.h"
  37#include "hw/qdev-properties.h"
  38#include "migration/vmstate.h"
  39#include "qom/object.h"
  40
  41#define VM_MSG_CHAR        0xA0 /* Marks end of message */
  42#define VM_CMD_CHAR        0xA1 /* Marks end of a command */
  43#define VM_ESCAPE_CHAR     0xAA /* Set bit 4 from the next byte to 0 */
  44
  45#define VM_PROTOCOL_VERSION        1
  46#define VM_CMD_VERSION             0xff /* A version number byte follows */
  47#define VM_CMD_NOATTN              0x00
  48#define VM_CMD_ATTN                0x01
  49#define VM_CMD_ATTN_IRQ            0x02
  50#define VM_CMD_POWEROFF            0x03
  51#define VM_CMD_RESET               0x04
  52#define VM_CMD_ENABLE_IRQ          0x05 /* Enable/disable the messaging irq */
  53#define VM_CMD_DISABLE_IRQ         0x06
  54#define VM_CMD_SEND_NMI            0x07
  55#define VM_CMD_CAPABILITIES        0x08
  56#define   VM_CAPABILITIES_POWER    0x01
  57#define   VM_CAPABILITIES_RESET    0x02
  58#define   VM_CAPABILITIES_IRQ      0x04
  59#define   VM_CAPABILITIES_NMI      0x08
  60#define   VM_CAPABILITIES_ATTN     0x10
  61#define   VM_CAPABILITIES_GRACEFUL_SHUTDOWN 0x20
  62#define VM_CMD_GRACEFUL_SHUTDOWN   0x09
  63
  64#define TYPE_IPMI_BMC_EXTERN "ipmi-bmc-extern"
  65OBJECT_DECLARE_SIMPLE_TYPE(IPMIBmcExtern, IPMI_BMC_EXTERN)
  66struct IPMIBmcExtern {
  67    IPMIBmc parent;
  68
  69    CharBackend chr;
  70
  71    bool connected;
  72
  73    unsigned char inbuf[MAX_IPMI_MSG_SIZE + 2];
  74    unsigned int inpos;
  75    bool in_escape;
  76    bool in_too_many;
  77    bool waiting_rsp;
  78    bool sending_cmd;
  79
  80    unsigned char outbuf[(MAX_IPMI_MSG_SIZE + 2) * 2 + 1];
  81    unsigned int outpos;
  82    unsigned int outlen;
  83
  84    struct QEMUTimer *extern_timer;
  85
  86    /* A reset event is pending to be sent upstream. */
  87    bool send_reset;
  88};
  89
  90static unsigned char
  91ipmb_checksum(const unsigned char *data, int size, unsigned char start)
  92{
  93        unsigned char csum = start;
  94
  95        for (; size > 0; size--, data++) {
  96                csum += *data;
  97        }
  98        return csum;
  99}
 100
 101static void continue_send(IPMIBmcExtern *ibe)
 102{
 103    int ret;
 104    if (ibe->outlen == 0) {
 105        goto check_reset;
 106    }
 107 send:
 108    ret = qemu_chr_fe_write(&ibe->chr, ibe->outbuf + ibe->outpos,
 109                            ibe->outlen - ibe->outpos);
 110    if (ret > 0) {
 111        ibe->outpos += ret;
 112    }
 113    if (ibe->outpos < ibe->outlen) {
 114        /* Not fully transmitted, try again in a 10ms */
 115        timer_mod_ns(ibe->extern_timer,
 116                     qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 10000000);
 117    } else {
 118        /* Sent */
 119        ibe->outlen = 0;
 120        ibe->outpos = 0;
 121        if (!ibe->sending_cmd) {
 122            ibe->waiting_rsp = true;
 123        } else {
 124            ibe->sending_cmd = false;
 125        }
 126    check_reset:
 127        if (ibe->connected && ibe->send_reset) {
 128            /* Send the reset */
 129            ibe->outbuf[0] = VM_CMD_RESET;
 130            ibe->outbuf[1] = VM_CMD_CHAR;
 131            ibe->outlen = 2;
 132            ibe->outpos = 0;
 133            ibe->send_reset = false;
 134            ibe->sending_cmd = true;
 135            goto send;
 136        }
 137
 138        if (ibe->waiting_rsp) {
 139            /* Make sure we get a response within 4 seconds. */
 140            timer_mod_ns(ibe->extern_timer,
 141                         qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 4000000000ULL);
 142        }
 143    }
 144    return;
 145}
 146
 147static void extern_timeout(void *opaque)
 148{
 149    IPMIBmcExtern *ibe = opaque;
 150    IPMIInterface *s = ibe->parent.intf;
 151
 152    if (ibe->connected) {
 153        if (ibe->waiting_rsp && (ibe->outlen == 0)) {
 154            IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s);
 155            /* The message response timed out, return an error. */
 156            ibe->waiting_rsp = false;
 157            ibe->inbuf[1] = ibe->outbuf[1] | 0x04;
 158            ibe->inbuf[2] = ibe->outbuf[2];
 159            ibe->inbuf[3] = IPMI_CC_TIMEOUT;
 160            k->handle_rsp(s, ibe->outbuf[0], ibe->inbuf + 1, 3);
 161        } else {
 162            continue_send(ibe);
 163        }
 164    }
 165}
 166
 167static void addchar(IPMIBmcExtern *ibe, unsigned char ch)
 168{
 169    switch (ch) {
 170    case VM_MSG_CHAR:
 171    case VM_CMD_CHAR:
 172    case VM_ESCAPE_CHAR:
 173        ibe->outbuf[ibe->outlen] = VM_ESCAPE_CHAR;
 174        ibe->outlen++;
 175        ch |= 0x10;
 176        /* fall through */
 177    default:
 178        ibe->outbuf[ibe->outlen] = ch;
 179        ibe->outlen++;
 180    }
 181}
 182
 183static void ipmi_bmc_extern_handle_command(IPMIBmc *b,
 184                                       uint8_t *cmd, unsigned int cmd_len,
 185                                       unsigned int max_cmd_len,
 186                                       uint8_t msg_id)
 187{
 188    IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(b);
 189    IPMIInterface *s = ibe->parent.intf;
 190    uint8_t err = 0, csum;
 191    unsigned int i;
 192
 193    if (ibe->outlen) {
 194        /* We already have a command queued.  Shouldn't ever happen. */
 195        error_report("IPMI KCS: Got command when not finished with the"
 196                     " previous command");
 197        abort();
 198    }
 199
 200    /* If it's too short or it was truncated, return an error. */
 201    if (cmd_len < 2) {
 202        err = IPMI_CC_REQUEST_DATA_LENGTH_INVALID;
 203    } else if ((cmd_len > max_cmd_len) || (cmd_len > MAX_IPMI_MSG_SIZE)) {
 204        err = IPMI_CC_REQUEST_DATA_TRUNCATED;
 205    } else if (!ibe->connected) {
 206        err = IPMI_CC_BMC_INIT_IN_PROGRESS;
 207    }
 208    if (err) {
 209        IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s);
 210        unsigned char rsp[3];
 211        rsp[0] = cmd[0] | 0x04;
 212        rsp[1] = cmd[1];
 213        rsp[2] = err;
 214        ibe->waiting_rsp = false;
 215        k->handle_rsp(s, msg_id, rsp, 3);
 216        goto out;
 217    }
 218
 219    addchar(ibe, msg_id);
 220    for (i = 0; i < cmd_len; i++) {
 221        addchar(ibe, cmd[i]);
 222    }
 223    csum = ipmb_checksum(&msg_id, 1, 0);
 224    addchar(ibe, -ipmb_checksum(cmd, cmd_len, csum));
 225
 226    ibe->outbuf[ibe->outlen] = VM_MSG_CHAR;
 227    ibe->outlen++;
 228
 229    /* Start the transmit */
 230    continue_send(ibe);
 231
 232 out:
 233    return;
 234}
 235
 236static void handle_hw_op(IPMIBmcExtern *ibe, unsigned char hw_op)
 237{
 238    IPMIInterface *s = ibe->parent.intf;
 239    IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s);
 240
 241    switch (hw_op) {
 242    case VM_CMD_VERSION:
 243        /* We only support one version at this time. */
 244        break;
 245
 246    case VM_CMD_NOATTN:
 247        k->set_atn(s, 0, 0);
 248        break;
 249
 250    case VM_CMD_ATTN:
 251        k->set_atn(s, 1, 0);
 252        break;
 253
 254    case VM_CMD_ATTN_IRQ:
 255        k->set_atn(s, 1, 1);
 256        break;
 257
 258    case VM_CMD_POWEROFF:
 259        k->do_hw_op(s, IPMI_POWEROFF_CHASSIS, 0);
 260        break;
 261
 262    case VM_CMD_RESET:
 263        k->do_hw_op(s, IPMI_RESET_CHASSIS, 0);
 264        break;
 265
 266    case VM_CMD_ENABLE_IRQ:
 267        k->set_irq_enable(s, 1);
 268        break;
 269
 270    case VM_CMD_DISABLE_IRQ:
 271        k->set_irq_enable(s, 0);
 272        break;
 273
 274    case VM_CMD_SEND_NMI:
 275        k->do_hw_op(s, IPMI_SEND_NMI, 0);
 276        break;
 277
 278    case VM_CMD_GRACEFUL_SHUTDOWN:
 279        k->do_hw_op(s, IPMI_SHUTDOWN_VIA_ACPI_OVERTEMP, 0);
 280        break;
 281    }
 282}
 283
 284static void handle_msg(IPMIBmcExtern *ibe)
 285{
 286    IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(ibe->parent.intf);
 287
 288    if (ibe->in_escape) {
 289        ipmi_debug("msg escape not ended\n");
 290        return;
 291    }
 292    if (ibe->inpos < 5) {
 293        ipmi_debug("msg too short\n");
 294        return;
 295    }
 296    if (ibe->in_too_many) {
 297        ibe->inbuf[3] = IPMI_CC_REQUEST_DATA_TRUNCATED;
 298        ibe->inpos = 4;
 299    } else if (ipmb_checksum(ibe->inbuf, ibe->inpos, 0) != 0) {
 300        ipmi_debug("msg checksum failure\n");
 301        return;
 302    } else {
 303        ibe->inpos--; /* Remove checkum */
 304    }
 305
 306    timer_del(ibe->extern_timer);
 307    ibe->waiting_rsp = false;
 308    k->handle_rsp(ibe->parent.intf, ibe->inbuf[0], ibe->inbuf + 1, ibe->inpos - 1);
 309}
 310
 311static int can_receive(void *opaque)
 312{
 313    return 1;
 314}
 315
 316static void receive(void *opaque, const uint8_t *buf, int size)
 317{
 318    IPMIBmcExtern *ibe = opaque;
 319    int i;
 320    unsigned char hw_op;
 321
 322    for (i = 0; i < size; i++) {
 323        unsigned char ch = buf[i];
 324
 325        switch (ch) {
 326        case VM_MSG_CHAR:
 327            handle_msg(ibe);
 328            ibe->in_too_many = false;
 329            ibe->inpos = 0;
 330            break;
 331
 332        case VM_CMD_CHAR:
 333            if (ibe->in_too_many) {
 334                ipmi_debug("cmd in too many\n");
 335                ibe->in_too_many = false;
 336                ibe->inpos = 0;
 337                break;
 338            }
 339            if (ibe->in_escape) {
 340                ipmi_debug("cmd in escape\n");
 341                ibe->in_too_many = false;
 342                ibe->inpos = 0;
 343                ibe->in_escape = false;
 344                break;
 345            }
 346            ibe->in_too_many = false;
 347            if (ibe->inpos < 1) {
 348                break;
 349            }
 350            hw_op = ibe->inbuf[0];
 351            ibe->inpos = 0;
 352            goto out_hw_op;
 353            break;
 354
 355        case VM_ESCAPE_CHAR:
 356            ibe->in_escape = true;
 357            break;
 358
 359        default:
 360            if (ibe->in_escape) {
 361                ch &= ~0x10;
 362                ibe->in_escape = false;
 363            }
 364            if (ibe->in_too_many) {
 365                break;
 366            }
 367            if (ibe->inpos >= sizeof(ibe->inbuf)) {
 368                ibe->in_too_many = true;
 369                break;
 370            }
 371            ibe->inbuf[ibe->inpos] = ch;
 372            ibe->inpos++;
 373            break;
 374        }
 375    }
 376    return;
 377
 378 out_hw_op:
 379    handle_hw_op(ibe, hw_op);
 380}
 381
 382static void chr_event(void *opaque, QEMUChrEvent event)
 383{
 384    IPMIBmcExtern *ibe = opaque;
 385    IPMIInterface *s = ibe->parent.intf;
 386    IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s);
 387    unsigned char v;
 388
 389    switch (event) {
 390    case CHR_EVENT_OPENED:
 391        ibe->connected = true;
 392        ibe->outpos = 0;
 393        ibe->outlen = 0;
 394        addchar(ibe, VM_CMD_VERSION);
 395        addchar(ibe, VM_PROTOCOL_VERSION);
 396        ibe->outbuf[ibe->outlen] = VM_CMD_CHAR;
 397        ibe->outlen++;
 398        addchar(ibe, VM_CMD_CAPABILITIES);
 399        v = VM_CAPABILITIES_IRQ | VM_CAPABILITIES_ATTN;
 400        if (k->do_hw_op(ibe->parent.intf, IPMI_POWEROFF_CHASSIS, 1) == 0) {
 401            v |= VM_CAPABILITIES_POWER;
 402        }
 403        if (k->do_hw_op(ibe->parent.intf, IPMI_SHUTDOWN_VIA_ACPI_OVERTEMP, 1)
 404            == 0) {
 405            v |= VM_CAPABILITIES_GRACEFUL_SHUTDOWN;
 406        }
 407        if (k->do_hw_op(ibe->parent.intf, IPMI_RESET_CHASSIS, 1) == 0) {
 408            v |= VM_CAPABILITIES_RESET;
 409        }
 410        if (k->do_hw_op(ibe->parent.intf, IPMI_SEND_NMI, 1) == 0) {
 411            v |= VM_CAPABILITIES_NMI;
 412        }
 413        addchar(ibe, v);
 414        ibe->outbuf[ibe->outlen] = VM_CMD_CHAR;
 415        ibe->outlen++;
 416        ibe->sending_cmd = false;
 417        continue_send(ibe);
 418        break;
 419
 420    case CHR_EVENT_CLOSED:
 421        if (!ibe->connected) {
 422            return;
 423        }
 424        ibe->connected = false;
 425        /*
 426         * Don't hang the OS trying to handle the ATN bit, other end will
 427         * resend on a reconnect.
 428         */
 429        k->set_atn(s, 0, 0);
 430        if (ibe->waiting_rsp) {
 431            ibe->waiting_rsp = false;
 432            ibe->inbuf[1] = ibe->outbuf[1] | 0x04;
 433            ibe->inbuf[2] = ibe->outbuf[2];
 434            ibe->inbuf[3] = IPMI_CC_BMC_INIT_IN_PROGRESS;
 435            k->handle_rsp(s, ibe->outbuf[0], ibe->inbuf + 1, 3);
 436        }
 437        break;
 438
 439    case CHR_EVENT_BREAK:
 440    case CHR_EVENT_MUX_IN:
 441    case CHR_EVENT_MUX_OUT:
 442        /* Ignore */
 443        break;
 444    }
 445}
 446
 447static void ipmi_bmc_extern_handle_reset(IPMIBmc *b)
 448{
 449    IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(b);
 450
 451    ibe->send_reset = true;
 452    continue_send(ibe);
 453}
 454
 455static void ipmi_bmc_extern_realize(DeviceState *dev, Error **errp)
 456{
 457    IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(dev);
 458
 459    if (!qemu_chr_fe_backend_connected(&ibe->chr)) {
 460        error_setg(errp, "IPMI external bmc requires chardev attribute");
 461        return;
 462    }
 463
 464    qemu_chr_fe_set_handlers(&ibe->chr, can_receive, receive,
 465                             chr_event, NULL, ibe, NULL, true);
 466}
 467
 468static int ipmi_bmc_extern_post_migrate(void *opaque, int version_id)
 469{
 470    IPMIBmcExtern *ibe = opaque;
 471
 472    /*
 473     * We don't directly restore waiting_rsp, Instead, we return an
 474     * error on the interface if a response was being waited for.
 475     */
 476    if (ibe->waiting_rsp) {
 477        IPMIInterface *ii = ibe->parent.intf;
 478        IPMIInterfaceClass *iic = IPMI_INTERFACE_GET_CLASS(ii);
 479
 480        ibe->waiting_rsp = false;
 481        ibe->inbuf[1] = ibe->outbuf[1] | 0x04;
 482        ibe->inbuf[2] = ibe->outbuf[2];
 483        ibe->inbuf[3] = IPMI_CC_BMC_INIT_IN_PROGRESS;
 484        iic->handle_rsp(ii, ibe->outbuf[0], ibe->inbuf + 1, 3);
 485    }
 486    return 0;
 487}
 488
 489static const VMStateDescription vmstate_ipmi_bmc_extern = {
 490    .name = TYPE_IPMI_BMC_EXTERN,
 491    .version_id = 1,
 492    .minimum_version_id = 1,
 493    .post_load = ipmi_bmc_extern_post_migrate,
 494    .fields      = (VMStateField[]) {
 495        VMSTATE_BOOL(send_reset, IPMIBmcExtern),
 496        VMSTATE_BOOL(waiting_rsp, IPMIBmcExtern),
 497        VMSTATE_END_OF_LIST()
 498    }
 499};
 500
 501static void ipmi_bmc_extern_init(Object *obj)
 502{
 503    IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(obj);
 504
 505    ibe->extern_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, extern_timeout, ibe);
 506    vmstate_register(NULL, 0, &vmstate_ipmi_bmc_extern, ibe);
 507}
 508
 509static void ipmi_bmc_extern_finalize(Object *obj)
 510{
 511    IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(obj);
 512
 513    timer_del(ibe->extern_timer);
 514    timer_free(ibe->extern_timer);
 515}
 516
 517static Property ipmi_bmc_extern_properties[] = {
 518    DEFINE_PROP_CHR("chardev", IPMIBmcExtern, chr),
 519    DEFINE_PROP_END_OF_LIST(),
 520};
 521
 522static void ipmi_bmc_extern_class_init(ObjectClass *oc, void *data)
 523{
 524    DeviceClass *dc = DEVICE_CLASS(oc);
 525    IPMIBmcClass *bk = IPMI_BMC_CLASS(oc);
 526
 527    bk->handle_command = ipmi_bmc_extern_handle_command;
 528    bk->handle_reset = ipmi_bmc_extern_handle_reset;
 529    dc->hotpluggable = false;
 530    dc->realize = ipmi_bmc_extern_realize;
 531    device_class_set_props(dc, ipmi_bmc_extern_properties);
 532}
 533
 534static const TypeInfo ipmi_bmc_extern_type = {
 535    .name          = TYPE_IPMI_BMC_EXTERN,
 536    .parent        = TYPE_IPMI_BMC,
 537    .instance_size = sizeof(IPMIBmcExtern),
 538    .instance_init = ipmi_bmc_extern_init,
 539    .instance_finalize = ipmi_bmc_extern_finalize,
 540    .class_init    = ipmi_bmc_extern_class_init,
 541 };
 542
 543static void ipmi_bmc_extern_register_types(void)
 544{
 545    type_register_static(&ipmi_bmc_extern_type);
 546}
 547
 548type_init(ipmi_bmc_extern_register_types)
 549