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