@@ -43,6 +43,7 @@ Supported devices
* GPIO controller
* Analog to Digital Converter (ADC)
* Pulse Width Modulation (PWM)
+ * Keyboard Controller Style (KCS) channels
Missing devices
---------------
@@ -50,7 +51,6 @@ Missing devices
* LPC/eSPI host-to-BMC interface, including
* Keyboard and mouse controller interface (KBCI)
- * Keyboard Controller Style (KCS) channels
* BIOS POST code FIFO
* System Wake-up Control (SWC)
* Shared memory (SHM)
@@ -46,6 +46,7 @@
#define NPCM7XX_CLK_BA (0xf0801000)
#define NPCM7XX_MC_BA (0xf0824000)
#define NPCM7XX_RNG_BA (0xf000b000)
+#define NPCM7XX_KCS_BA (0xf0007000)
/* USB Host modules */
#define NPCM7XX_EHCI_BA (0xf0806000)
@@ -82,6 +83,7 @@ enum NPCM7xxInterrupt {
NPCM7XX_UART1_IRQ,
NPCM7XX_UART2_IRQ,
NPCM7XX_UART3_IRQ,
+ NPCM7XX_KCS_HIB_IRQ = 9,
NPCM7XX_TIMER0_IRQ = 32, /* Timer Module 0 */
NPCM7XX_TIMER1_IRQ,
NPCM7XX_TIMER2_IRQ,
@@ -353,6 +355,7 @@ static void npcm7xx_init(Object *obj)
object_initialize_child(obj, "gpio[*]", &s->gpio[i], TYPE_NPCM7XX_GPIO);
}
+ object_initialize_child(obj, "kcs", &s->kcs, TYPE_NPCM7XX_KCS);
object_initialize_child(obj, "ehci", &s->ehci, TYPE_NPCM7XX_EHCI);
object_initialize_child(obj, "ohci", &s->ohci, TYPE_SYSBUS_OHCI);
@@ -509,6 +512,12 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp)
npcm7xx_irq(s, NPCM7XX_GPIO0_IRQ + i));
}
+ /* KCS modules. Cannot fail. */
+ sysbus_realize(SYS_BUS_DEVICE(&s->kcs), &error_abort);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->kcs), 0, NPCM7XX_KCS_BA);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->kcs), 0,
+ npcm7xx_irq(s, NPCM7XX_KCS_HIB_IRQ));
+
/* USB Host */
object_property_set_bool(OBJECT(&s->ehci), "companion-enable", true,
&error_abort);
@@ -574,7 +583,6 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp)
create_unimplemented_device("npcm7xx.shm", 0xc0001000, 4 * KiB);
create_unimplemented_device("npcm7xx.vdmx", 0xe0800000, 4 * KiB);
create_unimplemented_device("npcm7xx.pcierc", 0xe1000000, 64 * KiB);
- create_unimplemented_device("npcm7xx.kcs", 0xf0007000, 4 * KiB);
create_unimplemented_device("npcm7xx.gfxi", 0xf000e000, 4 * KiB);
create_unimplemented_device("npcm7xx.gpio[0]", 0xf0010000, 4 * KiB);
create_unimplemented_device("npcm7xx.gpio[1]", 0xf0011000, 4 * KiB);
@@ -8,5 +8,6 @@ ipmi_ss.add(when: 'CONFIG_ISA_IPMI_BT', if_true: files('isa_ipmi_bt.c'))
ipmi_ss.add(when: 'CONFIG_PCI_IPMI_BT', if_true: files('pci_ipmi_bt.c'))
ipmi_ss.add(when: 'CONFIG_IPMI_SSIF', if_true: files('smbus_ipmi.c'))
ipmi_ss.add(when: 'CONFIG_IPMI_HOST', if_true: files('ipmi_host.c'))
+ipmi_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_kcs.c'))
softmmu_ss.add_all(when: 'CONFIG_IPMI', if_true: ipmi_ss)
new file mode 100644
@@ -0,0 +1,570 @@
+/*
+ * Nuvoton NPCM7xx KCS Module
+ *
+ * Copyright 2020 Google LLC
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/ipmi/ipmi_host.h"
+#include "hw/ipmi/npcm7xx_kcs.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/units.h"
+
+/* Registers in each KCS channel. */
+typedef enum NPCM7xxKCSRegister {
+ NPCM7XX_KCSST,
+ NPCM7XX_KCSDO,
+ NPCM7XX_KCSDI,
+ NPCM7XX_KCSDOC,
+ NPCM7XX_KCSDOM,
+ NPCM7XX_KCSDIC,
+ NPCM7XX_KCSCTL,
+ NPCM7XX_KCSIC,
+ NPCM7XX_KCSIE,
+ NPCM7XX_KCS_REGS_END,
+} NPCM7xxKCSRegister;
+
+static const hwaddr npcm7xx_kcs_channel_base[] = {
+ 0x0c, 0x1e, 0x30, 0x42
+};
+
+#define NPCM7XX_KCS_REG_DIFF 2
+
+/* Register field definitions. */
+#define NPCM7XX_CTL_OBEIE BIT(1)
+#define NPCM7XX_CTL_IBFIE BIT(0)
+
+/* IPMI 2.0 Table 9.1 status register bits */
+#define KCS_ST_STATE(rv) extract8(rv, 6, 2)
+#define KCS_ST_CMD BIT(3)
+#define KCS_ST_SMS_ATN BIT(2)
+#define KCS_ST_IBF BIT(1)
+#define KCS_ST_OBF BIT(0)
+
+/* IPMI 2.0 Table 9.2 state bits */
+enum KCSState {
+ IDLE_STATE,
+ READ_STATE,
+ WRITE_STATE,
+ ERROR_STATE,
+};
+
+/* IPMI 2.0 Table 9.3 control codes */
+#define KCS_CMD_GET_STATUS_ABORT 0x60
+#define KCS_CMD_WRITE_START 0x61
+#define KCS_CMD_WRITE_END 0x62
+#define KCS_CMD_READ 0x68
+
+/* Host Side Operations. */
+
+static uint8_t npcm7xx_kcs_host_read_byte(NPCM7xxKCSChannel *c)
+{
+ uint8_t v;
+
+ v = c->dbbout;
+ c->status &= ~KCS_ST_OBF;
+ if (c->ctl & NPCM7XX_CTL_OBEIE) {
+ qemu_irq_raise(c->owner->irq);
+ }
+
+ return v;
+}
+
+static void npcm7xx_kcs_host_write_byte(NPCM7xxKCSChannel *c, uint8_t value,
+ bool is_cmd)
+{
+ c->dbbin = value;
+ c->status |= KCS_ST_IBF;
+ if (is_cmd) {
+ c->status |= KCS_ST_CMD;
+ } else {
+ c->status &= ~KCS_ST_CMD;
+ }
+ if (c->ctl & NPCM7XX_CTL_IBFIE) {
+ qemu_irq_raise(c->owner->irq);
+ }
+}
+
+static void npcm7xx_kcs_handle_event(NPCM7xxKCSChannel *c)
+{
+ uint8_t v;
+ IPMIHostClass *hk;
+
+ switch (KCS_ST_STATE(c->status)) {
+ case IDLE_STATE:
+ if (c->status & KCS_ST_OBF) {
+ /* Read a dummy byte. */
+ npcm7xx_kcs_host_read_byte(c);
+ if (c->outlen > 0) {
+ /* Send to ipmi host when msg ends. */
+ hk = IPMI_HOST_GET_CLASS(c->host);
+ hk->handle_command(c->host, c->outmsg, c->outlen,
+ MAX_IPMI_MSG_SIZE, c->last_msg_id);
+ /* The last byte has been read. return to empty state. */
+ c->outlen = 0;
+ }
+ }
+ if (c->inlen > 0) {
+ /* Send to bmc the next request */
+ npcm7xx_kcs_host_write_byte(c, KCS_CMD_WRITE_START, true);
+ c->last_byte_not_ready = true;
+ }
+ break;
+ case READ_STATE:
+ if (c->status & KCS_ST_OBF) {
+ /* Read in a byte from BMC */
+ v = npcm7xx_kcs_host_read_byte(c);
+ if (c->outlen < MAX_IPMI_MSG_SIZE) {
+ c->outmsg[c->outlen++] = v;
+ }
+ npcm7xx_kcs_host_write_byte(c, KCS_CMD_READ, false);
+ }
+ break;
+ case WRITE_STATE:
+ if (c->status & KCS_ST_IBF) {
+ /* The guest hasn't read the byte yet. We'll wait. */
+ break;
+ }
+ /* Clear OBF. */
+ c->status &= ~KCS_ST_OBF;
+ /* If it's the last byte written, we need to first send a command. */
+ if (c->last_byte_not_ready && c->inpos == c->inlen - 1) {
+ npcm7xx_kcs_host_write_byte(c, KCS_CMD_WRITE_END, true);
+ c->last_byte_not_ready = false;
+ } else {
+ npcm7xx_kcs_host_write_byte(c, c->inmsg[c->inpos++], false);
+ if (!c->last_byte_not_ready) {
+ /* The last byte has been sent. */
+ c->inlen = 0;
+ c->inpos = 0;
+ }
+ }
+ break;
+ case ERROR_STATE:
+ if (c->status & KCS_ST_OBF) {
+ /* Read in error byte from BMC */
+ npcm7xx_kcs_host_read_byte(c);
+ }
+ /* Force abort */
+ c->outlen = 0;
+ c->inlen = 0;
+ c->inpos = 0;
+ if (!(c->status & KCS_ST_IBF)) {
+ npcm7xx_kcs_host_write_byte(c, KCS_CMD_GET_STATUS_ABORT, true);
+ }
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+/* Received a request from the host and send it to BMC core. */
+static void npcm7xx_kcs_handle_req(IPMIResponder *ii, uint8_t msg_id,
+ unsigned char *req, unsigned req_len)
+{
+ IPMIResponderClass *iic = IPMI_RESPONDER_GET_CLASS(ii);
+ NPCM7xxKCSChannel *c = iic->get_backend_data(ii);
+
+ /* Drop the request if the last request is still not handled. */
+ if (c->inlen > 0) {
+ return;
+ }
+ if (req_len == 0) {
+ return;
+ }
+ if (req_len > MAX_IPMI_MSG_SIZE) {
+ /* Truncate the extra bytes that don't fit. */
+ req_len = MAX_IPMI_MSG_SIZE;
+ }
+ memcpy(c->inmsg, req, req_len);
+ c->inpos = 0;
+ c->inlen = req_len;
+
+ c->last_msg_id = msg_id;
+
+ npcm7xx_kcs_handle_event(c);
+}
+
+/* Core Side Operations. */
+/* Return the channel index for addr. If the addr is out of range, return -1. */
+static int npcm7xx_kcs_channel_index(hwaddr addr)
+{
+ int index;
+
+ if (unlikely(addr < npcm7xx_kcs_channel_base[0])) {
+ return -1;
+ }
+ if (unlikely(addr >= npcm7xx_kcs_channel_base[NPCM7XX_KCS_NR_CHANNELS])) {
+ return -1;
+ }
+ if (unlikely(addr % NPCM7XX_KCS_REG_DIFF)) {
+ return -1;
+ }
+
+ for (index = 0; index < NPCM7XX_KCS_NR_CHANNELS; ++index) {
+ if (addr < npcm7xx_kcs_channel_base[index + 1]) {
+ return index;
+ }
+ }
+
+ g_assert_not_reached();
+}
+
+static NPCM7xxKCSRegister npcm7xx_kcs_reg_index(hwaddr addr, int channel)
+{
+ NPCM7xxKCSRegister reg;
+
+ reg = (addr - npcm7xx_kcs_channel_base[channel]) / NPCM7XX_KCS_REG_DIFF;
+ return reg;
+}
+
+static uint8_t npcm7xx_kcs_read_byte(NPCM7xxKCSChannel *c,
+ NPCM7xxKCSRegister reg)
+{
+ uint8_t v = 0;
+
+ v = c->dbbin;
+
+ c->status &= ~KCS_ST_IBF;
+ if (c->ctl & NPCM7XX_CTL_IBFIE) {
+ qemu_irq_lower(c->owner->irq);
+ }
+
+ if (reg == NPCM7XX_KCSDIC) {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: Host nSCIPME interrupt is not implemented.\n",
+ __func__);
+ }
+
+ npcm7xx_kcs_handle_event(c);
+ return v;
+}
+
+static void npcm7xx_kcs_write_byte(NPCM7xxKCSChannel *c,
+ NPCM7xxKCSRegister reg, uint8_t value)
+{
+ c->dbbout = value;
+
+ c->status |= KCS_ST_OBF;
+ if (c->ctl & NPCM7XX_CTL_OBEIE) {
+ qemu_irq_lower(c->owner->irq);
+ }
+
+ if (reg == NPCM7XX_KCSDOC) {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: Host nSCIPME interrupt is not implemented.\n",
+ __func__);
+ } else if (reg == NPCM7XX_KCSDOM) {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: Host nSMI interrupt is not implemented.\n",
+ __func__);
+ }
+
+ npcm7xx_kcs_handle_event(c);
+}
+
+static uint8_t npcm7xx_kcs_read_status(NPCM7xxKCSChannel *c)
+{
+ uint8_t status = c->status;
+
+ return status;
+}
+
+static void npcm7xx_kcs_write_status(NPCM7xxKCSChannel *c, uint8_t value)
+{
+ static const uint8_t mask =
+ KCS_ST_CMD | KCS_ST_IBF | KCS_ST_OBF;
+
+ if (value & mask) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: read-only bits in 0x%02x ignored\n",
+ __func__, value);
+ value &= ~mask;
+ }
+
+ c->status = (c->status & mask) | value;
+}
+
+static void npcm7xx_kcs_write_ctl(NPCM7xxKCSChannel *c, uint8_t value)
+{
+ if (value & ~(NPCM7XX_CTL_OBEIE | NPCM7XX_CTL_IBFIE)) {
+ qemu_log_mask(LOG_UNIMP, "%s: Host interrupts are not implemented.\n",
+ __func__);
+ }
+
+ c->ctl = value;
+}
+
+static uint64_t npcm7xx_kcs_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ NPCM7xxKCSState *s = opaque;
+ uint64_t value = 0;
+ int channel;
+ NPCM7xxKCSRegister reg;
+
+ channel = npcm7xx_kcs_channel_index(addr);
+ if (channel < 0 || channel >= NPCM7XX_KCS_NR_CHANNELS) {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: read from addr 0x%04" HWADDR_PRIx
+ " is invalid or unimplemented.\n",
+ __func__, addr);
+ return value;
+ }
+
+ reg = npcm7xx_kcs_reg_index(addr, channel);
+ switch (reg) {
+ case NPCM7XX_KCSDI:
+ case NPCM7XX_KCSDIC:
+ value = npcm7xx_kcs_read_byte(&s->channels[channel], reg);
+ break;
+ case NPCM7XX_KCSDO:
+ case NPCM7XX_KCSDOC:
+ case NPCM7XX_KCSDOM:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: register @ 0x%04" HWADDR_PRIx " is write-only\n",
+ __func__, addr);
+ break;
+ case NPCM7XX_KCSST:
+ value = npcm7xx_kcs_read_status(&s->channels[channel]);
+ break;
+ case NPCM7XX_KCSCTL:
+ value = s->channels[channel].ctl;
+ break;
+ case NPCM7XX_KCSIC:
+ value = s->channels[channel].ic;
+ break;
+ case NPCM7XX_KCSIE:
+ value = s->channels[channel].ie;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: read from invalid addr 0x%04" HWADDR_PRIx "\n",
+ __func__, addr);
+ }
+
+ return value;
+}
+
+static void npcm7xx_kcs_write(void *opaque, hwaddr addr, uint64_t v,
+ unsigned int size)
+{
+ NPCM7xxKCSState *s = opaque;
+ int channel;
+ NPCM7xxKCSRegister reg;
+
+ channel = npcm7xx_kcs_channel_index(addr);
+ if (channel < 0 || channel >= NPCM7XX_KCS_NR_CHANNELS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to addr 0x%04" HWADDR_PRIx
+ " is invalid or unimplemented.\n",
+ __func__, addr);
+ return;
+ }
+
+ reg = npcm7xx_kcs_reg_index(addr, channel);
+ switch (reg) {
+ case NPCM7XX_KCSDI:
+ case NPCM7XX_KCSDIC:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
+ __func__, addr);
+ break;
+ case NPCM7XX_KCSDO:
+ case NPCM7XX_KCSDOC:
+ case NPCM7XX_KCSDOM:
+ npcm7xx_kcs_write_byte(&s->channels[channel], reg, v);
+ break;
+ case NPCM7XX_KCSST:
+ npcm7xx_kcs_write_status(&s->channels[channel], v);
+ break;
+ case NPCM7XX_KCSCTL:
+ npcm7xx_kcs_write_ctl(&s->channels[channel], v);
+ break;
+ case NPCM7XX_KCSIC:
+ qemu_log_mask(LOG_UNIMP, "%s: Host interrupts are not implemented.\n",
+ __func__);
+ break;
+ case NPCM7XX_KCSIE:
+ qemu_log_mask(LOG_UNIMP, "%s: Host interrupts are not implemented.\n",
+ __func__);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: read from invalid addr 0x%04" HWADDR_PRIx "\n",
+ __func__, addr);
+ }
+}
+
+static const MemoryRegionOps npcm7xx_kcs_ops = {
+ .read = npcm7xx_kcs_read,
+ .write = npcm7xx_kcs_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1, /* KCS registers are 8-bits. */
+ .max_access_size = 1, /* KCS registers are 8-bits. */
+ .unaligned = false,
+ },
+};
+
+static const VMStateDescription vmstate_npcm7xx_kcs_channel = {
+ .name = "npcm7xx-kcs-channel",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(status, NPCM7xxKCSChannel),
+ VMSTATE_UINT8(dbbout, NPCM7xxKCSChannel),
+ VMSTATE_UINT8(dbbin, NPCM7xxKCSChannel),
+ VMSTATE_UINT8(ctl, NPCM7xxKCSChannel),
+ VMSTATE_UINT8(ic, NPCM7xxKCSChannel),
+ VMSTATE_UINT8(ie, NPCM7xxKCSChannel),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static IPMIHost *npcm7xx_kcs_get_host(IPMIResponder *ii)
+{
+ NPCM7xxKCSChannel *c = NPCM7XX_KCS_CHANNEL(ii);
+
+ return c->host;
+}
+
+static void npcm7xx_kcs_set_host(IPMIResponder *ii, IPMIHost *h)
+{
+ NPCM7xxKCSChannel *c = NPCM7XX_KCS_CHANNEL(ii);
+
+ c->host = h;
+}
+
+static void *npcm7xx_kcs_get_backend_data(IPMIResponder *ii)
+{
+ NPCM7xxKCSChannel *c = NPCM7XX_KCS_CHANNEL(ii);
+
+ return c;
+}
+
+static void npcm7xx_kcs_channel_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ IPMIResponderClass *iic = IPMI_RESPONDER_CLASS(klass);
+
+ QEMU_BUILD_BUG_ON(NPCM7XX_KCS_REGS_END != NPCM7XX_KCS_CHANNEL_NR_REGS);
+
+ dc->desc = "NPCM7xx KCS Channel";
+ dc->vmsd = &vmstate_npcm7xx_kcs_channel;
+
+ iic->get_host = npcm7xx_kcs_get_host;
+ iic->set_host = npcm7xx_kcs_set_host;
+ iic->get_backend_data = npcm7xx_kcs_get_backend_data;
+ iic->handle_req = npcm7xx_kcs_handle_req;
+}
+
+static const TypeInfo npcm7xx_kcs_channel_info = {
+ .name = TYPE_NPCM7XX_KCS_CHANNEL,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(NPCM7xxKCSChannel),
+ .class_init = npcm7xx_kcs_channel_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_IPMI_RESPONDER },
+ { },
+ },
+};
+
+static void npcm7xx_kcs_enter_reset(Object *obj, ResetType type)
+{
+ NPCM7xxKCSState *s = NPCM7XX_KCS(obj);
+ int i;
+
+ for (i = 0; i < NPCM7XX_KCS_NR_CHANNELS; i++) {
+ NPCM7xxKCSChannel *c = &s->channels[i];
+
+ c->status = 0x00;
+ c->ctl = 0xc0;
+ c->ic = 0x41;
+ c->ie = 0x00;
+ }
+}
+
+static void npcm7xx_kcs_hold_reset(Object *obj)
+{
+ NPCM7xxKCSState *s = NPCM7XX_KCS(obj);
+
+ qemu_irq_lower(s->irq);
+}
+
+static const VMStateDescription vmstate_npcm7xx_kcs = {
+ .name = "npcm7xx-kcs",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void npcm7xx_kcs_realize(DeviceState *dev, Error **errp)
+{
+ NPCM7xxKCSState *s = NPCM7XX_KCS(dev);
+ SysBusDevice *sbd = &s->parent;
+ int i;
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &npcm7xx_kcs_ops, s,
+ TYPE_NPCM7XX_KCS, 4 * KiB);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+
+ for (i = 0; i < NPCM7XX_KCS_NR_CHANNELS; i++) {
+ s->channels[i].owner = s;
+ if (!qdev_realize(DEVICE(&s->channels[i]), NULL, errp)) {
+ return;
+ }
+ }
+}
+
+static void npcm7xx_kcs_init(Object *obj)
+{
+ NPCM7xxKCSState *s = NPCM7XX_KCS(obj);
+ int i;
+
+ for (i = 0; i < NPCM7XX_KCS_NR_CHANNELS; i++) {
+ object_initialize_child(obj, g_strdup_printf("channels[%d]", i),
+ &s->channels[i], TYPE_NPCM7XX_KCS_CHANNEL);
+ }
+}
+
+static void npcm7xx_kcs_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "NPCM7xx Timer Controller";
+ dc->vmsd = &vmstate_npcm7xx_kcs;
+ dc->realize = npcm7xx_kcs_realize;
+ rc->phases.enter = npcm7xx_kcs_enter_reset;
+ rc->phases.hold = npcm7xx_kcs_hold_reset;
+}
+
+static const TypeInfo npcm7xx_kcs_info = {
+ .name = TYPE_NPCM7XX_KCS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NPCM7xxKCSState),
+ .class_init = npcm7xx_kcs_class_init,
+ .instance_init = npcm7xx_kcs_init,
+};
+
+static void npcm7xx_kcs_register_type(void)
+{
+ type_register_static(&npcm7xx_kcs_channel_info);
+ type_register_static(&npcm7xx_kcs_info);
+}
+type_init(npcm7xx_kcs_register_type);
@@ -20,6 +20,7 @@
#include "hw/adc/npcm7xx_adc.h"
#include "hw/cpu/a9mpcore.h"
#include "hw/gpio/npcm7xx_gpio.h"
+#include "hw/ipmi/npcm7xx_kcs.h"
#include "hw/mem/npcm7xx_mc.h"
#include "hw/misc/npcm7xx_clk.h"
#include "hw/misc/npcm7xx_gcr.h"
@@ -85,6 +86,7 @@ typedef struct NPCM7xxState {
NPCM7xxMCState mc;
NPCM7xxRNGState rng;
NPCM7xxGPIOState gpio[8];
+ NPCM7xxKCSState kcs;
EHCISysBusState ehci;
OHCISysBusState ohci;
NPCM7xxFIUState fiu[2];
new file mode 100644
@@ -0,0 +1,105 @@
+/*
+ * Nuvoton NPCM7xx KCS Module
+ *
+ * Copyright 2020 Google LLC
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef NPCM7XX_KCS_H
+#define NPCM7XX_KCS_H
+
+#include "exec/memory.h"
+#include "hw/ipmi/ipmi.h"
+#include "hw/ipmi/ipmi_host.h"
+#include "hw/ipmi/ipmi_responder.h"
+#include "hw/irq.h"
+#include "hw/sysbus.h"
+
+#define NPCM7XX_KCS_NR_CHANNELS 3
+/*
+ * Number of registers in each KCS channel. Don't change this without
+ * incrementing the version_id in the vmstate.
+ */
+#define NPCM7XX_KCS_CHANNEL_NR_REGS 9
+
+typedef struct NPCM7xxKCSState NPCM7xxKCSState;
+
+/**
+ * struct NPCM7xxKCSChannel - KCS Channel that can be read or written by the
+ * host.
+ * @parent: Parent device.
+ * @owner: The KCS module that manages this KCS channel.
+ * @status: The status of this KCS module.
+ * @dbbout: The output buffer to the host.
+ * @dbbin: The input buffer from the host.
+ * @ctl: The control register.
+ * @ic: The host interrupt control register. Not implemented.
+ * @ie: The host interrupt enable register. Not implemented.
+ * @inmsg: The input message from the host. To be put in dbbin.
+ * @inpos: The current position of input message.
+ * @inlen: The length of input message.
+ * @outmsg: The input message from the host. To be put in dbbout.
+ * @outpos: The current position of output message.
+ * @outlen: The length of output message.
+ * @last_byte_not_ready: The last byte in inmsg is not ready to be sent.
+ * @last_msg_id: The message id of last incoming request from host.
+ */
+typedef struct NPCM7xxKCSChannel {
+ DeviceState parent;
+
+ NPCM7xxKCSState *owner;
+ IPMIHost *host;
+ /* Core side registers. */
+ uint8_t status;
+ uint8_t dbbout;
+ uint8_t dbbin;
+ uint8_t ctl;
+ uint8_t ic;
+ uint8_t ie;
+
+ /* Host side buffers. */
+ uint8_t inmsg[MAX_IPMI_MSG_SIZE];
+ uint32_t inpos;
+ uint32_t inlen;
+ uint8_t outmsg[MAX_IPMI_MSG_SIZE];
+ uint32_t outpos;
+ uint32_t outlen;
+
+ /* Flags. */
+ bool last_byte_not_ready;
+ uint8_t last_msg_id;
+} NPCM7xxKCSChannel;
+
+/**
+ * struct NPCM7xxKCSState - Keyboard Control Style (KCS) Module device state.
+ * @parent: System bus device.
+ * @iomem: Memory region through which registers are accessed.
+ * @irq: GIC interrupt line to fire on reading or writing the buffer.
+ * @channels: The KCS channels this module manages.
+ */
+struct NPCM7xxKCSState {
+ SysBusDevice parent;
+
+ MemoryRegion iomem;
+
+ qemu_irq irq;
+ NPCM7xxKCSChannel channels[NPCM7XX_KCS_NR_CHANNELS];
+};
+
+#define TYPE_NPCM7XX_KCS_CHANNEL "npcm7xx-kcs-channel"
+#define NPCM7XX_KCS_CHANNEL(obj) \
+ OBJECT_CHECK(NPCM7xxKCSChannel, (obj), TYPE_NPCM7XX_KCS_CHANNEL)
+
+#define TYPE_NPCM7XX_KCS "npcm7xx-kcs"
+#define NPCM7XX_KCS(obj) \
+ OBJECT_CHECK(NPCM7xxKCSState, (obj), TYPE_NPCM7XX_KCS)
+
+#endif /* NPCM7XX_KCS_H */