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