[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH 19/24] hw/misc: Add an iBT device model
From: |
Cédric Le Goater |
Subject: |
[PATCH 19/24] hw/misc: Add an iBT device model |
Date: |
Wed, 7 Apr 2021 19:16:32 +0200 |
Implement an IPMI BT interface model using a chardev backend to
communicate with an external PowerNV machine. It uses the OpenIPMI
simulator protocol for virtual machines described in :
https://github.com/cminyard/openipmi/blob/master/lanserv/README.vm
and implemented by the 'ipmi-bmc-extern' model on the host side.
To use, start the Aspeed BMC machine with :
-chardev socket,id=ipmi0,host=localhost,port=9002,ipv4,server,nowait \
-global driver=aspeed.ibt,property=chardev,value=ipmi0
and the PowerNV machine with :
-chardev socket,id=ipmi0,host=localhost,port=9002,reconnect=10 \
-device ipmi-bmc-extern,id=bmc0,chardev=ipmi0 \
-device isa-ipmi-bt,bmc=bmc0,irq=10 -nodefaults
Cc: Hao Wu <wuhaotsh@google.com>
Cc: Corey Minyard <cminyard@mvista.com>
Signed-off-by: Cédric Le Goater <clg@kaod.org>
Reviewed-by: Joel Stanley <joel@jms.id.au>
Message-Id: <20210329121912.271900-1-clg@kaod.org>
Signed-off-by: Cédric Le Goater <clg@kaod.org>
---
include/hw/arm/aspeed_soc.h | 2 +
include/hw/misc/aspeed_ibt.h | 47 +++
hw/arm/aspeed_ast2600.c | 12 +
hw/arm/aspeed_soc.c | 12 +
hw/misc/aspeed_ibt.c | 596 +++++++++++++++++++++++++++++++++++
hw/misc/meson.build | 1 +
hw/misc/trace-events | 7 +
7 files changed, 677 insertions(+)
create mode 100644 include/hw/misc/aspeed_ibt.h
create mode 100644 hw/misc/aspeed_ibt.c
diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h
index d9161d26d645..f0c36b8f7d35 100644
--- a/include/hw/arm/aspeed_soc.h
+++ b/include/hw/arm/aspeed_soc.h
@@ -30,6 +30,7 @@
#include "hw/usb/hcd-ehci.h"
#include "qom/object.h"
#include "hw/misc/aspeed_lpc.h"
+#include "hw/misc/aspeed_ibt.h"
#define ASPEED_SPIS_NUM 2
#define ASPEED_EHCIS_NUM 2
@@ -65,6 +66,7 @@ struct AspeedSoCState {
AspeedSDHCIState sdhci;
AspeedSDHCIState emmc;
AspeedLPCState lpc;
+ AspeedIBTState ibt;
};
#define TYPE_ASPEED_SOC "aspeed-soc"
diff --git a/include/hw/misc/aspeed_ibt.h b/include/hw/misc/aspeed_ibt.h
new file mode 100644
index 000000000000..a02a57df9ff8
--- /dev/null
+++ b/include/hw/misc/aspeed_ibt.h
@@ -0,0 +1,47 @@
+/*
+ * ASPEED iBT Device
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * This code is licensed under the GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ */
+#ifndef ASPEED_IBT_H
+#define ASPEED_IBT_H
+
+#include "hw/sysbus.h"
+#include "chardev/char-fe.h"
+
+#define TYPE_ASPEED_IBT "aspeed.ibt"
+#define ASPEED_IBT(obj) OBJECT_CHECK(AspeedIBTState, (obj), TYPE_ASPEED_IBT)
+
+#define ASPEED_IBT_NR_REGS (0x1C >> 2)
+
+#define ASPEED_IBT_BUFFER_SIZE 64
+
+typedef struct AspeedIBTState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+
+ /*< public >*/
+ CharBackend chr;
+ bool connected;
+
+ uint8_t recv_msg[ASPEED_IBT_BUFFER_SIZE];
+ uint8_t recv_msg_len;
+ int recv_msg_index;
+ int recv_msg_too_many;
+ bool recv_waiting;
+ int in_escape;
+
+ uint8_t send_msg[ASPEED_IBT_BUFFER_SIZE];
+ uint8_t send_msg_len;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+
+ uint32_t regs[ASPEED_IBT_NR_REGS];
+
+} AspeedIBTState;
+
+#endif /* ASPEED_IBT_H */
diff --git a/hw/arm/aspeed_ast2600.c b/hw/arm/aspeed_ast2600.c
index c60824bfeecb..bb650d31f5ad 100644
--- a/hw/arm/aspeed_ast2600.c
+++ b/hw/arm/aspeed_ast2600.c
@@ -219,6 +219,8 @@ static void aspeed_soc_ast2600_init(Object *obj)
snprintf(typename, sizeof(typename), "aspeed.hace-%s", socname);
object_initialize_child(obj, "hace", &s->hace, typename);
+
+ object_initialize_child(obj, "ibt", &s->ibt, TYPE_ASPEED_IBT);
}
/*
@@ -510,6 +512,16 @@ static void aspeed_soc_ast2600_realize(DeviceState *dev,
Error **errp)
sysbus_mmio_map(SYS_BUS_DEVICE(&s->hace), 0, sc->memmap[ASPEED_DEV_HACE]);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->hace), 0,
aspeed_soc_get_irq(s, ASPEED_DEV_HACE));
+
+ /* iBT */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->ibt), errp)) {
+ return;
+ }
+ memory_region_add_subregion(&s->lpc.iomem,
+ sc->memmap[ASPEED_DEV_IBT] - sc->memmap[ASPEED_DEV_LPC],
+ &s->ibt.iomem);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->ibt), 0,
+ aspeed_soc_get_irq(s, ASPEED_DEV_IBT));
}
static void aspeed_soc_ast2600_class_init(ObjectClass *oc, void *data)
diff --git a/hw/arm/aspeed_soc.c b/hw/arm/aspeed_soc.c
index 4a95d27d9d63..5ab4cefc7e8b 100644
--- a/hw/arm/aspeed_soc.c
+++ b/hw/arm/aspeed_soc.c
@@ -219,6 +219,8 @@ static void aspeed_soc_init(Object *obj)
snprintf(typename, sizeof(typename), "aspeed.hace-%s", socname);
object_initialize_child(obj, "hace", &s->hace, typename);
+
+ object_initialize_child(obj, "ibt", &s->ibt, TYPE_ASPEED_IBT);
}
static void aspeed_soc_realize(DeviceState *dev, Error **errp)
@@ -438,6 +440,16 @@ static void aspeed_soc_realize(DeviceState *dev, Error
**errp)
sysbus_mmio_map(SYS_BUS_DEVICE(&s->hace), 0, sc->memmap[ASPEED_DEV_HACE]);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->hace), 0,
aspeed_soc_get_irq(s, ASPEED_DEV_HACE));
+
+ /* iBT */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->ibt), errp)) {
+ return;
+ }
+ memory_region_add_subregion(&s->lpc.iomem,
+ sc->memmap[ASPEED_DEV_IBT] - sc->memmap[ASPEED_DEV_LPC],
+ &s->ibt.iomem);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->lpc), 1 + aspeed_lpc_ibt,
+ qdev_get_gpio_in(DEVICE(&s->lpc), aspeed_lpc_ibt));
}
static Property aspeed_soc_properties[] = {
DEFINE_PROP_LINK("dram", AspeedSoCState, dram_mr, TYPE_MEMORY_REGION,
diff --git a/hw/misc/aspeed_ibt.c b/hw/misc/aspeed_ibt.c
new file mode 100644
index 000000000000..69a2096ccb00
--- /dev/null
+++ b/hw/misc/aspeed_ibt.c
@@ -0,0 +1,596 @@
+/*
+ * ASPEED iBT Device
+ *
+ * Copyright (c) 2016-2021 Cédric Le Goater, IBM Corporation.
+ *
+ * This code is licensed under the GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "sysemu/qtest.h"
+#include "sysemu/sysemu.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "migration/vmstate.h"
+#include "hw/misc/aspeed_ibt.h"
+#include "trace.h"
+
+#define BT_IO_REGION_SIZE 0x1C
+
+#define TO_REG(o) (o >> 2)
+
+#define BT_CR0 0x0 /* iBT config */
+#define BT_CR0_IO_BASE 16
+#define BT_CR0_IRQ 12
+#define BT_CR0_EN_CLR_SLV_RDP 0x8
+#define BT_CR0_EN_CLR_SLV_WRP 0x4
+#define BT_CR0_ENABLE_IBT 0x1
+#define BT_CR1 0x4 /* interrupt enable */
+#define BT_CR1_IRQ_H2B 0x01
+#define BT_CR1_IRQ_HBUSY 0x40
+#define BT_CR2 0x8 /* interrupt status */
+#define BT_CR2_IRQ_H2B 0x01
+#define BT_CR2_IRQ_HBUSY 0x40
+#define BT_CR3 0xc /* unused */
+#define BT_CTRL 0x10
+#define BT_CTRL_B_BUSY 0x80
+#define BT_CTRL_H_BUSY 0x40
+#define BT_CTRL_OEM0 0x20
+#define BT_CTRL_SMS_ATN 0x10
+#define BT_CTRL_B2H_ATN 0x08
+#define BT_CTRL_H2B_ATN 0x04
+#define BT_CTRL_CLR_RD_PTR 0x02
+#define BT_CTRL_CLR_WR_PTR 0x01
+#define BT_BMC2HOST 0x14
+#define BT_INTMASK 0x18
+#define BT_INTMASK_B2H_IRQEN 0x01
+#define BT_INTMASK_B2H_IRQ 0x02
+#define BT_INTMASK_BMC_HWRST 0x80
+
+/*
+ * VM IPMI defines
+ */
+#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
+
+/*
+ * These routines are inspired by the 'ipmi-bmc-extern' model and by
+ * the lanserv simulator of OpenIPMI. See :
+ * https://github.com/cminyard/openipmi/blob/master/lanserv/serial_ipmi.c
+ */
+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 vm_add_char(unsigned char ch, unsigned char *c, unsigned int *pos)
+{
+ switch (ch) {
+ case VM_MSG_CHAR:
+ case VM_CMD_CHAR:
+ case VM_ESCAPE_CHAR:
+ c[(*pos)++] = VM_ESCAPE_CHAR;
+ c[(*pos)++] = ch | 0x10;
+ break;
+
+ default:
+ c[(*pos)++] = ch;
+ }
+}
+
+static void aspeed_ibt_dump_msg(const char *func, unsigned char *msg,
+ unsigned int len)
+{
+ if (trace_event_get_state_backends(TRACE_ASPEED_IBT_CHR_DUMP_MSG)) {
+ int size = len * 3 + 1;
+ char tmp[size];
+ int i, n = 0;
+
+ for (i = 0; i < len; i++) {
+ n += snprintf(tmp + n, size - n, "%02x:", msg[i]);
+ }
+ tmp[size - 1] = 0;
+
+ trace_aspeed_ibt_chr_dump_msg(func, tmp, len);
+ }
+}
+
+static void aspeed_ibt_chr_write(AspeedIBTState *ibt, const uint8_t *buf,
+ int len)
+{
+ int i;
+
+ if (!qemu_chr_fe_get_driver(&ibt->chr)) {
+ return;
+ }
+
+ aspeed_ibt_dump_msg(__func__, ibt->recv_msg, ibt->recv_msg_len);
+
+ for (i = 0; i < len; i++) {
+ qemu_chr_fe_write(&ibt->chr, &buf[i], 1);
+ }
+}
+
+static void vm_send(AspeedIBTState *ibt)
+{
+ unsigned int i;
+ unsigned int len = 0;
+ unsigned char c[(ibt->send_msg_len + 7) * 2];
+ uint8_t netfn;
+
+ /*
+ * The VM IPMI message format does not follow the IPMI BT
+ * interface format. The sequence and the netfn bytes need to be
+ * swapped.
+ */
+ netfn = ibt->send_msg[1];
+ ibt->send_msg[1] = ibt->send_msg[2];
+ ibt->send_msg[2] = netfn;
+
+ /* No length byte in the VM IPMI message format. trim it */
+ for (i = 1; i < ibt->send_msg_len; i++) {
+ vm_add_char(ibt->send_msg[i], c, &len);
+ }
+
+ vm_add_char(-ipmb_checksum(&ibt->send_msg[1], ibt->send_msg_len - 1, 0),
+ c, &len);
+ c[len++] = VM_MSG_CHAR;
+
+ aspeed_ibt_chr_write(ibt, c, len);
+}
+
+static void aspeed_ibt_update_irq(AspeedIBTState *ibt)
+{
+ bool raise = false;
+
+ /* H2B rising */
+ if ((ibt->regs[TO_REG(BT_CTRL)] & BT_CTRL_H2B_ATN) &&
+ ((ibt->regs[TO_REG(BT_CR1)] & BT_CR1_IRQ_H2B) == BT_CR1_IRQ_H2B)) {
+ ibt->regs[TO_REG(BT_CR2)] |= BT_CR2_IRQ_H2B;
+
+ /*
+ * Also flag the fact that we are waiting for the guest/driver
+ * to read a received message
+ */
+ ibt->recv_waiting = true;
+ raise = true;
+ }
+
+ /* H_BUSY falling (not supported) */
+ if ((ibt->regs[TO_REG(BT_CTRL)] & BT_CTRL_H_BUSY) &&
+ ((ibt->regs[TO_REG(BT_CR1)] & BT_CR1_IRQ_HBUSY) == BT_CR1_IRQ_HBUSY)) {
+ ibt->regs[TO_REG(BT_CR2)] |= BT_CR2_IRQ_HBUSY;
+
+ raise = true;
+ }
+
+ if (raise) {
+ qemu_irq_raise(ibt->irq);
+ }
+}
+
+static void vm_handle_msg(AspeedIBTState *ibt, unsigned char *msg,
+ unsigned int len)
+{
+ uint8_t seq;
+
+ aspeed_ibt_dump_msg(__func__, ibt->recv_msg, ibt->recv_msg_len);
+
+ if (len < 4) {
+ qemu_log_mask(LOG_GUEST_ERROR, " %s: Message too short\n", __func__);
+ return;
+ }
+
+ if (ipmb_checksum(ibt->recv_msg, ibt->recv_msg_len, 0) != 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, " %s: Message checksum failure\n",
+ __func__);
+ return;
+ }
+
+ /* Trim the checksum byte */
+ ibt->recv_msg_len--;
+
+ /*
+ * The VM IPMI message format does not follow the IPMI BT
+ * interface format. The sequence and the netfn bytes need to be
+ * swapped.
+ */
+ seq = ibt->recv_msg[0];
+ ibt->recv_msg[0] = ibt->recv_msg[1];
+ ibt->recv_msg[1] = seq;
+
+ aspeed_ibt_update_irq(ibt);
+}
+
+/* TODO: handle commands */
+static void vm_handle_cmd(AspeedIBTState *ibt, unsigned char *msg,
+ unsigned int len)
+{
+ aspeed_ibt_dump_msg(__func__, ibt->recv_msg, ibt->recv_msg_len);
+
+ if (len < 1) {
+ qemu_log_mask(LOG_GUEST_ERROR, " %s: Command too short\n", __func__);
+ return;
+ }
+
+ switch (msg[0]) {
+ case VM_CMD_VERSION:
+ break;
+
+ case VM_CMD_CAPABILITIES:
+ if (len < 2) {
+ return;
+ }
+ break;
+
+ case VM_CMD_RESET:
+ break;
+ }
+}
+
+static void vm_handle_char(AspeedIBTState *ibt, unsigned char ch)
+{
+ unsigned int len = ibt->recv_msg_len;
+
+ switch (ch) {
+ case VM_MSG_CHAR:
+ case VM_CMD_CHAR:
+ if (ibt->in_escape) {
+ qemu_log_mask(LOG_GUEST_ERROR, " %s: Message ended in escape\n",
+ __func__);
+ } else if (ibt->recv_msg_too_many) {
+ qemu_log_mask(LOG_GUEST_ERROR, " %s: Message too long\n",
__func__);
+ } else if (ibt->recv_msg_len == 0) {
+ /* Nothing to do */
+ } else if (ch == VM_MSG_CHAR) {
+ /* Last byte of message. Signal BMC as the host would do */
+ ibt->regs[TO_REG(BT_CTRL)] |= BT_CTRL_H2B_ATN;
+
+ vm_handle_msg(ibt, ibt->recv_msg, ibt->recv_msg_len);
+
+ /* Message is only handled when read by BMC (!B_BUSY) */
+ } else if (ch == VM_CMD_CHAR) {
+ vm_handle_cmd(ibt, ibt->recv_msg, ibt->recv_msg_len);
+
+ /* Command is now handled. reset receive state */
+ ibt->in_escape = 0;
+ ibt->recv_msg_len = 0;
+ ibt->recv_msg_too_many = 0;
+ }
+ break;
+
+ case VM_ESCAPE_CHAR:
+ if (!ibt->recv_msg_too_many) {
+ ibt->in_escape = 1;
+ }
+ break;
+
+ default:
+ if (ibt->in_escape) {
+ ibt->in_escape = 0;
+ ch &= ~0x10;
+ }
+
+ if (!ibt->recv_msg_too_many) {
+ if (len >= sizeof(ibt->recv_msg)) {
+ ibt->recv_msg_too_many = 1;
+ break;
+ }
+
+ ibt->recv_msg[len] = ch;
+ ibt->recv_msg_len++;
+ }
+ break;
+ }
+}
+
+static void vm_connected(AspeedIBTState *ibt)
+{
+ unsigned int len = 0;
+ unsigned char c[5];
+
+ vm_add_char(VM_CMD_VERSION, c, &len);
+ vm_add_char(VM_PROTOCOL_VERSION, c, &len);
+ c[len++] = VM_CMD_CHAR;
+
+ aspeed_ibt_chr_write(ibt, c, len);
+}
+
+static void aspeed_ibt_chr_event(void *opaque, QEMUChrEvent event)
+{
+ AspeedIBTState *ibt = ASPEED_IBT(opaque);
+
+ switch (event) {
+ case CHR_EVENT_OPENED:
+ vm_connected(ibt);
+ ibt->connected = true;
+ break;
+
+ case CHR_EVENT_CLOSED:
+ if (!ibt->connected) {
+ return;
+ }
+ ibt->connected = false;
+ break;
+ case CHR_EVENT_BREAK:
+ case CHR_EVENT_MUX_IN:
+ case CHR_EVENT_MUX_OUT:
+ /* Ignore */
+ break;
+ }
+ trace_aspeed_ibt_chr_event(ibt->connected);
+}
+
+static int aspeed_ibt_chr_can_receive(void *opaque)
+{
+ AspeedIBTState *ibt = ASPEED_IBT(opaque);
+
+ return !ibt->recv_waiting && !(ibt->regs[TO_REG(BT_CTRL)] &
BT_CTRL_B_BUSY);
+}
+
+static void aspeed_ibt_chr_receive(void *opaque, const uint8_t *buf,
+ int size)
+{
+ AspeedIBTState *ibt = ASPEED_IBT(opaque);
+ int i;
+
+ if (!ibt->connected) {
+ qemu_log_mask(LOG_GUEST_ERROR, " %s: not connected !?\n", __func__);
+ return;
+ }
+
+ for (i = 0; i < size; i++) {
+ vm_handle_char(ibt, buf[i]);
+ }
+}
+
+static void aspeed_ibt_write(void *opaque, hwaddr offset, uint64_t data,
+ unsigned size)
+{
+ AspeedIBTState *ibt = ASPEED_IBT(opaque);
+
+ trace_aspeed_ibt_write(offset, data);
+
+ switch (offset) {
+ case BT_CTRL:
+ /* CLR_WR_PTR: cleared before a message is written */
+ if (data & BT_CTRL_CLR_WR_PTR) {
+ memset(ibt->send_msg, 0, sizeof(ibt->send_msg));
+ ibt->send_msg_len = 0;
+ trace_aspeed_ibt_event("CLR_WR_PTR");
+ }
+
+ /* CLR_RD_PTR: cleared before a message is read */
+ else if (data & BT_CTRL_CLR_RD_PTR) {
+ ibt->recv_msg_index = -1;
+ trace_aspeed_ibt_event("CLR_RD_PTR");
+ }
+
+ /*
+ * H2B_ATN: raised by host to end message, cleared by BMC
+ * before reading message
+ */
+ else if (data & BT_CTRL_H2B_ATN) {
+ ibt->regs[TO_REG(BT_CTRL)] &= ~BT_CTRL_H2B_ATN;
+ trace_aspeed_ibt_event("H2B_ATN");
+ }
+
+ /* B_BUSY: raised and cleared by BMC when message is read */
+ else if (data & BT_CTRL_B_BUSY) {
+ ibt->regs[TO_REG(BT_CTRL)] ^= BT_CTRL_B_BUSY;
+ trace_aspeed_ibt_event("B_BUSY");
+ }
+
+ /*
+ * B2H_ATN: raised by BMC and cleared by host
+ *
+ * Also simulate the host busy bit which is set while the host
+ * is reading the message from the BMC
+ */
+ else if (data & BT_CTRL_B2H_ATN) {
+ trace_aspeed_ibt_event("B2H_ATN");
+ ibt->regs[TO_REG(BT_CTRL)] |= (BT_CTRL_B2H_ATN | BT_CTRL_H_BUSY);
+
+ vm_send(ibt);
+
+ ibt->regs[TO_REG(BT_CTRL)] &= ~(BT_CTRL_B2H_ATN | BT_CTRL_H_BUSY);
+
+ /* signal H_BUSY falling but that's a bit useless */
+ aspeed_ibt_update_irq(ibt);
+ }
+
+ /* Anything else is unexpected */
+ else {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected CTRL setting\n",
+ __func__);
+ }
+
+ /* Message was read by BMC. we can reset the receive state */
+ if (!(ibt->regs[TO_REG(BT_CTRL)] & BT_CTRL_B_BUSY)) {
+ trace_aspeed_ibt_event("B_BUSY cleared");
+ ibt->recv_waiting = false;
+ ibt->in_escape = 0;
+ ibt->recv_msg_len = 0;
+ ibt->recv_msg_too_many = 0;
+ }
+ break;
+
+ case BT_BMC2HOST:
+ if (ibt->send_msg_len < sizeof(ibt->send_msg)) {
+ trace_aspeed_ibt_event("BMC2HOST");
+ ibt->send_msg[ibt->send_msg_len++] = data & 0xff;
+ }
+ break;
+
+ case BT_CR0: /* TODO: iBT config */
+ case BT_CR1: /* interrupt enable */
+ case BT_CR3: /* unused */
+ case BT_INTMASK:
+ ibt->regs[TO_REG(offset)] = (uint32_t) data;
+ break;
+ case BT_CR2: /* interrupt status. writing 1 clears. */
+ ibt->regs[TO_REG(offset)] ^= (uint32_t) data;
+ qemu_irq_lower(ibt->irq);
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: not implemented 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ break;
+ }
+}
+
+static uint64_t aspeed_ibt_read(void *opaque, hwaddr offset, unsigned size)
+{
+ AspeedIBTState *ibt = ASPEED_IBT(opaque);
+ uint64_t val = 0;
+
+ switch (offset) {
+ case BT_BMC2HOST:
+ trace_aspeed_ibt_event("BMC2HOST");
+ /*
+ * The IPMI BT interface requires the first byte to be the
+ * length of the message
+ */
+ if (ibt->recv_msg_index == -1) {
+ val = ibt->recv_msg_len;
+ ibt->recv_msg_index++;
+ } else if (ibt->recv_msg_index < ibt->recv_msg_len) {
+ val = ibt->recv_msg[ibt->recv_msg_index++];
+ }
+ break;
+
+ case BT_CR0:
+ case BT_CR1:
+ case BT_CR2:
+ case BT_CR3:
+ case BT_CTRL:
+ case BT_INTMASK:
+ return ibt->regs[TO_REG(offset)];
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: not implemented 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ return 0;
+ }
+
+ trace_aspeed_ibt_read(offset, val);
+ return val;
+}
+
+static const MemoryRegionOps aspeed_ibt_ops = {
+ .read = aspeed_ibt_read,
+ .write = aspeed_ibt_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static void aspeed_ibt_reset(DeviceState *dev)
+{
+ AspeedIBTState *ibt = ASPEED_IBT(dev);
+
+ memset(ibt->regs, 0, sizeof(ibt->regs));
+
+ memset(ibt->recv_msg, 0, sizeof(ibt->recv_msg));
+ ibt->recv_msg_len = 0;
+ ibt->recv_msg_index = -1;
+ ibt->recv_msg_too_many = 0;
+ ibt->recv_waiting = false;
+ ibt->in_escape = 0;
+
+ memset(ibt->send_msg, 0, sizeof(ibt->send_msg));
+ ibt->send_msg_len = 0;
+}
+
+static void aspeed_ibt_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ AspeedIBTState *ibt = ASPEED_IBT(dev);
+
+ if (!qemu_chr_fe_get_driver(&ibt->chr) && !qtest_enabled()) {
+ warn_report("Aspeed iBT has no chardev backend");
+ } else {
+ qemu_chr_fe_set_handlers(&ibt->chr, aspeed_ibt_chr_can_receive,
+ aspeed_ibt_chr_receive, aspeed_ibt_chr_event,
+ NULL, ibt, NULL, true);
+ }
+
+ sysbus_init_irq(sbd, &ibt->irq);
+ memory_region_init_io(&ibt->iomem, OBJECT(ibt), &aspeed_ibt_ops, ibt,
+ TYPE_ASPEED_IBT, BT_IO_REGION_SIZE);
+
+ sysbus_init_mmio(sbd, &ibt->iomem);
+}
+
+static Property aspeed_ibt_props[] = {
+ DEFINE_PROP_CHR("chardev", AspeedIBTState, chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_aspeed_ibt = {
+ .name = "aspeed.bt",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AspeedIBTState, ASPEED_IBT_NR_REGS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void aspeed_ibt_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ dc->realize = aspeed_ibt_realize;
+ dc->reset = aspeed_ibt_reset;
+ dc->desc = "ASPEED iBT Device";
+ dc->vmsd = &vmstate_aspeed_ibt;
+ device_class_set_props(dc, aspeed_ibt_props);
+}
+
+static const TypeInfo aspeed_ibt_info = {
+ .name = TYPE_ASPEED_IBT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AspeedIBTState),
+ .class_init = aspeed_ibt_class_init,
+};
+
+static void aspeed_ibt_register_types(void)
+{
+ type_register_static(&aspeed_ibt_info);
+}
+
+type_init(aspeed_ibt_register_types);
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 1e7b8b064bd1..ed8196dc4380 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -110,6 +110,7 @@ softmmu_ss.add(when: 'CONFIG_PVPANIC_PCI', if_true:
files('pvpanic-pci.c'))
softmmu_ss.add(when: 'CONFIG_AUX', if_true: files('auxbus.c'))
softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files(
'aspeed_hace.c',
+ 'aspeed_ibt.c',
'aspeed_lpc.c',
'aspeed_scu.c',
'aspeed_sdmc.c',
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index d0a89eb05964..e8fcacdfd9e9 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -19,6 +19,13 @@ allwinner_h3_dramphy_write(uint64_t offset, uint64_t data,
unsigned size) "write
allwinner_sid_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%"
PRIx64 " data 0x%" PRIx64 " size %" PRIu32
allwinner_sid_write(uint64_t offset, uint64_t data, unsigned size) "offset
0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+# aspeed_ibt.c
+aspeed_ibt_chr_dump_msg(const char *func, const char *buf, uint32_t len) "%s:
%s #%d bytes"
+aspeed_ibt_chr_event(bool connected) "connected:%d"
+aspeed_ibt_read(uint64_t offset, uint64_t value) "offset:0x%" PRIx64 "
value:0x%" PRIx64
+aspeed_ibt_write(uint64_t offset, uint64_t value) "offset:0x%" PRIx64 "
value:0x%" PRIx64
+aspeed_ibt_event(const char* event) "%s"
+
# avr_power.c
avr_power_read(uint8_t value) "power_reduc read value:%u"
avr_power_write(uint8_t value) "power_reduc write value:%u"
--
2.26.3