|  | /* | 
|  | * IPMI BMC 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. | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * This is designed to connect with OpenIPMI's lanserv serial interface | 
|  | * using the "VM" connection type.  See that for details. | 
|  | */ | 
|  |  | 
|  | #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/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; | 
|  |  | 
|  | /* 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) | 
|  | { | 
|  | 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) { | 
|  | /* Send the reset */ | 
|  | ibe->outbuf[0] = VM_CMD_RESET; | 
|  | ibe->outbuf[1] = VM_CMD_CHAR; | 
|  | ibe->outlen = 2; | 
|  | ibe->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); | 
|  | } | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | static void extern_timeout(void *opaque) | 
|  | { | 
|  | IPMIBmcExtern *ibe = opaque; | 
|  | IPMIInterface *s = ibe->parent.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_rsp(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) | 
|  | { | 
|  | IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(b); | 
|  | IPMIInterface *s = ibe->parent.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_rsp(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) | 
|  | { | 
|  | IPMIInterface *s = ibe->parent.intf; | 
|  | IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(s); | 
|  |  | 
|  | switch (hw_op) { | 
|  | case VM_CMD_VERSION: | 
|  | /* We only support one version at this time. */ | 
|  | break; | 
|  |  | 
|  | case VM_CMD_NOATTN: | 
|  | k->set_atn(s, 0, 0); | 
|  | break; | 
|  |  | 
|  | case VM_CMD_ATTN: | 
|  | k->set_atn(s, 1, 0); | 
|  | break; | 
|  |  | 
|  | case VM_CMD_ATTN_IRQ: | 
|  | k->set_atn(s, 1, 1); | 
|  | break; | 
|  |  | 
|  | case VM_CMD_POWEROFF: | 
|  | k->do_hw_op(s, IPMI_POWEROFF_CHASSIS, 0); | 
|  | break; | 
|  |  | 
|  | case VM_CMD_RESET: | 
|  | k->do_hw_op(s, IPMI_RESET_CHASSIS, 0); | 
|  | break; | 
|  |  | 
|  | case VM_CMD_ENABLE_IRQ: | 
|  | k->set_irq_enable(s, 1); | 
|  | break; | 
|  |  | 
|  | case VM_CMD_DISABLE_IRQ: | 
|  | k->set_irq_enable(s, 0); | 
|  | break; | 
|  |  | 
|  | case VM_CMD_SEND_NMI: | 
|  | k->do_hw_op(s, IPMI_SEND_NMI, 0); | 
|  | break; | 
|  |  | 
|  | case VM_CMD_GRACEFUL_SHUTDOWN: | 
|  | k->do_hw_op(s, IPMI_SHUTDOWN_VIA_ACPI_OVERTEMP, 0); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void handle_msg(IPMIBmcExtern *ibe) | 
|  | { | 
|  | IPMIInterfaceClass *k = IPMI_INTERFACE_GET_CLASS(ibe->parent.intf); | 
|  |  | 
|  | 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 checksum */ | 
|  | } | 
|  |  | 
|  | timer_del(ibe->extern_timer); | 
|  | ibe->waiting_rsp = false; | 
|  | k->handle_rsp(ibe->parent.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) | 
|  | { | 
|  | 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; | 
|  |  | 
|  | out_hw_op: | 
|  | handle_hw_op(ibe, hw_op); | 
|  | } | 
|  |  | 
|  | static void chr_event(void *opaque, QEMUChrEvent event) | 
|  | { | 
|  | IPMIBmcExtern *ibe = opaque; | 
|  | IPMIInterface *s = ibe->parent.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(ibe->parent.intf, IPMI_POWEROFF_CHASSIS, 1) == 0) { | 
|  | v |= VM_CAPABILITIES_POWER; | 
|  | } | 
|  | if (k->do_hw_op(ibe->parent.intf, IPMI_SHUTDOWN_VIA_ACPI_OVERTEMP, 1) | 
|  | == 0) { | 
|  | v |= VM_CAPABILITIES_GRACEFUL_SHUTDOWN; | 
|  | } | 
|  | if (k->do_hw_op(ibe->parent.intf, IPMI_RESET_CHASSIS, 1) == 0) { | 
|  | v |= VM_CAPABILITIES_RESET; | 
|  | } | 
|  | if (k->do_hw_op(ibe->parent.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_rsp(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_bmc_extern_handle_reset(IPMIBmc *b) | 
|  | { | 
|  | IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(b); | 
|  |  | 
|  | ibe->send_reset = true; | 
|  | continue_send(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) { | 
|  | IPMIInterface *ii = ibe->parent.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_rsp(ii, ibe->outbuf[0], ibe->inbuf + 1, 3); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const VMStateDescription vmstate_ipmi_bmc_extern = { | 
|  | .name = TYPE_IPMI_BMC_EXTERN, | 
|  | .version_id = 1, | 
|  | .minimum_version_id = 1, | 
|  | .post_load = ipmi_bmc_extern_post_migrate, | 
|  | .fields = (const VMStateField[]) { | 
|  | VMSTATE_BOOL(send_reset, IPMIBmcExtern), | 
|  | VMSTATE_BOOL(waiting_rsp, IPMIBmcExtern), | 
|  | VMSTATE_END_OF_LIST() | 
|  | } | 
|  | }; | 
|  |  | 
|  | 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); | 
|  |  | 
|  | vmstate_register(NULL, 0, &vmstate_ipmi_bmc_extern, ibe); | 
|  | } | 
|  |  | 
|  | static void ipmi_bmc_extern_init(Object *obj) | 
|  | { | 
|  | IPMIBmcExtern *ibe = IPMI_BMC_EXTERN(obj); | 
|  |  | 
|  | ibe->extern_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, extern_timeout, 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_END_OF_LIST(), | 
|  | }; | 
|  |  | 
|  | static void ipmi_bmc_extern_class_init(ObjectClass *oc, void *data) | 
|  | { | 
|  | DeviceClass *dc = DEVICE_CLASS(oc); | 
|  | IPMIBmcClass *bk = IPMI_BMC_CLASS(oc); | 
|  |  | 
|  | bk->handle_command = ipmi_bmc_extern_handle_command; | 
|  | bk->handle_reset = ipmi_bmc_extern_handle_reset; | 
|  | dc->hotpluggable = false; | 
|  | dc->realize = ipmi_bmc_extern_realize; | 
|  | device_class_set_props(dc, ipmi_bmc_extern_properties); | 
|  | } | 
|  |  | 
|  | static const TypeInfo ipmi_bmc_extern_type = { | 
|  | .name          = TYPE_IPMI_BMC_EXTERN, | 
|  | .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) | 
|  | { | 
|  | type_register_static(&ipmi_bmc_extern_type); | 
|  | } | 
|  |  | 
|  | type_init(ipmi_bmc_extern_register_types) |