qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

ipmi_bmc_extern.c (15766B)


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