diff options
Diffstat (limited to 'roms/skiboot/hw/lpc-mbox.c')
-rw-r--r-- | roms/skiboot/hw/lpc-mbox.c | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/roms/skiboot/hw/lpc-mbox.c b/roms/skiboot/hw/lpc-mbox.c new file mode 100644 index 000000000..f5bb97ea4 --- /dev/null +++ b/roms/skiboot/hw/lpc-mbox.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * LPC MBOX + * + * Copyright 2017-2018 IBM Corp. + */ + +#define pr_fmt(fmt) "LPC-MBOX: " fmt + +#include <skiboot.h> +#include <lpc.h> +#include <console.h> +#include <opal.h> +#include <device.h> +#include <interrupts.h> +#include <processor.h> +#include <errorlog.h> +#include <trace.h> +#include <timebase.h> +#include <timer.h> +#include <cpu.h> +#include <chip.h> +#include <io.h> + +#include <lpc-mbox.h> + +#define MBOX_FLAG_REG 0x0f +#define MBOX_STATUS_0 0x10 +#define MBOX_STATUS_1 0x11 +#define MBOX_STATUS_1_ATTN (1 << 7) +#define MBOX_STATUS_1_RESP (1 << 5) +#define MBOX_BMC_CTRL 0x12 +#define MBOX_CTRL_INT_STATUS (1 << 7) +#define MBOX_CTRL_INT_MASK (1 << 1) +#define MBOX_CTRL_INT_PING (1 << 0) +#define MBOX_CTRL_INT_SEND (MBOX_CTRL_INT_PING | MBOX_CTRL_INT_MASK) +#define MBOX_HOST_CTRL 0x13 +#define MBOX_BMC_INT_EN_0 0x14 +#define MBOX_BMC_INT_EN_1 0x15 +#define MBOX_HOST_INT_EN_0 0x16 +#define MBOX_HOST_INT_EN_1 0x17 + +#define MBOX_MAX_QUEUE_LEN 5 + +struct mbox { + uint32_t base; + int queue_len; + bool irq_ok; + uint8_t seq; + struct timer poller; + void (*callback)(struct bmc_mbox_msg *msg, void *priv); + void *drv_data; + void (*attn)(uint8_t bits, void *priv); + void *attn_data; + struct lock lock; + uint8_t sequence; + unsigned long timeout; +}; + +static struct mbox mbox; + +/* + * MBOX accesses + */ + +static void bmc_mbox_outb(uint8_t val, uint8_t reg) +{ + lpc_outb(val, mbox.base + reg); +} + +static uint8_t bmc_mbox_inb(uint8_t reg) +{ + return lpc_inb(mbox.base + reg); +} + +static void bmc_mbox_recv_message(struct bmc_mbox_msg *msg) +{ + uint8_t *msg_data = (uint8_t *)msg; + int i; + + for (i = 0; i < BMC_MBOX_READ_REGS; i++) + msg_data[i] = bmc_mbox_inb(i); +} + +/* This needs work, don't write the data bytes that aren't needed */ +static void bmc_mbox_send_message(struct bmc_mbox_msg *msg) +{ + uint8_t *msg_data = (uint8_t *)msg; + int i; + + if (!lpc_ok()) + /* We're going to have to handle this better */ + prlog(PR_ERR, "LPC isn't ok\n"); + + for (i = 0; i < BMC_MBOX_WRITE_REGS; i++) + bmc_mbox_outb(msg_data[i], i); + + /* + * Don't touch the response byte - it's setup to generate an interrupt + * to the host (us) when written to, or the host status reg - we don't + * currently use it, or the BMC status reg - we're not allowed to. + */ + + /* Ping */ + prlog(PR_TRACE, "Sending BMC interrupt\n"); + bmc_mbox_outb(MBOX_CTRL_INT_SEND, MBOX_HOST_CTRL); +} + +int bmc_mbox_enqueue(struct bmc_mbox_msg *msg, unsigned int timeout_sec) +{ + if (!mbox.base) { + prlog(PR_CRIT, "Using MBOX without init!\n"); + return OPAL_WRONG_STATE; + } + + lock(&mbox.lock); + if (mbox.timeout) { + prlog(PR_DEBUG, "MBOX message already in flight\n"); + if (mftb() > mbox.timeout) { + prlog(PR_ERR, "In flight message dropped on the floor\n"); + } else { + unlock(&mbox.lock); + return OPAL_BUSY; + } + } + + mbox.timeout = mftb() + secs_to_tb(timeout_sec); + msg->seq = ++mbox.sequence; + + bmc_mbox_send_message(msg); + unlock(&mbox.lock); + + schedule_timer(&mbox.poller, mbox.irq_ok ? + TIMER_POLL : msecs_to_tb(MBOX_DEFAULT_POLL_MS)); + + return 0; +} + +static void mbox_poll(struct timer *t __unused, void *data __unused, + uint64_t now __unused) +{ + struct bmc_mbox_msg msg; + + if (!lpc_ok()) + return; + + /* + * This status bit being high means that someone touched the + * response byte (byte 13). + * There is probably a response for the previously sent commant + */ + lock(&mbox.lock); + if (bmc_mbox_inb(MBOX_STATUS_1) & MBOX_STATUS_1_RESP) { + /* W1C on that reg */ + bmc_mbox_outb(MBOX_STATUS_1_RESP, MBOX_STATUS_1); + + prlog(PR_INSANE, "Got a regular interrupt\n"); + + bmc_mbox_recv_message(&msg); + if (mbox.sequence != msg.seq) { + prlog(PR_ERR, "Got a response to a message we no longer care about\n"); + goto out_response; + } + + mbox.timeout = 0; + if (mbox.callback) + mbox.callback(&msg, mbox.drv_data); + else + prlog(PR_ERR, "Detected NULL callback for mbox message\n"); + } + +out_response: + + /* + * The BMC has touched byte 15 to get our attention as it has + * something to tell us. + */ + if (bmc_mbox_inb(MBOX_STATUS_1) & MBOX_STATUS_1_ATTN) { + uint8_t action, all; + + /* W1C on that reg */ + bmc_mbox_outb(MBOX_STATUS_1_ATTN, MBOX_STATUS_1); + + all = action = bmc_mbox_inb(MBOX_FLAG_REG); + prlog(PR_TRACE, "Got a status register interrupt with action 0x%02x\n", + action); + if (action & MBOX_ATTN_BMC_REBOOT) { + /* + * It's unlikely that something needs to be done at the + * driver level. Let libflash deal with it. + * Print something just in case, it is quite a signficant + * event. + */ + prlog(PR_WARNING, "BMC reset detected\n"); + action &= ~MBOX_ATTN_BMC_REBOOT; + } + + if (action & MBOX_ATTN_BMC_WINDOW_RESET) + action &= ~MBOX_ATTN_BMC_WINDOW_RESET; + + if (action & MBOX_ATTN_BMC_FLASH_LOST) + action &= ~MBOX_ATTN_BMC_FLASH_LOST; + + if (action & MBOX_ATTN_BMC_DAEMON_READY) + action &= ~MBOX_ATTN_BMC_DAEMON_READY; + + if (action) + prlog(PR_ERR, "Got a status bit set that don't know about: 0x%02x\n", + action); + + mbox.attn(all, mbox.attn_data); + } + + unlock(&mbox.lock); + + schedule_timer(&mbox.poller, + mbox.irq_ok ? TIMER_POLL : msecs_to_tb(MBOX_DEFAULT_POLL_MS)); +} + +static void mbox_irq(uint32_t chip_id __unused, uint32_t irq_mask __unused) +{ + mbox.irq_ok = true; + mbox_poll(NULL, NULL, 0); +} + +static struct lpc_client mbox_lpc_client = { + .interrupt = mbox_irq, +}; + +static bool mbox_init_hw(void) +{ + /* Disable all status interrupts except attentions */ + bmc_mbox_outb(0x00, MBOX_HOST_INT_EN_0); + bmc_mbox_outb(MBOX_STATUS_1_ATTN, MBOX_HOST_INT_EN_1); + + /* Cleanup host interrupt and status */ + bmc_mbox_outb(MBOX_CTRL_INT_STATUS, MBOX_HOST_CTRL); + + /* Disable host control interrupt for now (will be + * re-enabled when needed). Clear BMC interrupts + */ + bmc_mbox_outb(MBOX_CTRL_INT_MASK, MBOX_BMC_CTRL); + + return true; +} + +int bmc_mbox_register_callback(void (*callback)(struct bmc_mbox_msg *msg, void *priv), + void *drv_data) +{ + mbox.callback = callback; + mbox.drv_data = drv_data; + return 0; +} + +int bmc_mbox_register_attn(void (*callback)(uint8_t bits, void *priv), + void *drv_data) +{ + mbox.attn = callback; + mbox.attn_data = drv_data; + return 0; +} + +uint8_t bmc_mbox_get_attn_reg(void) +{ + return bmc_mbox_inb(MBOX_FLAG_REG); +} + +void mbox_init(void) +{ + const struct dt_property *prop; + struct dt_node *np; + uint32_t irq, chip_id; + + if (mbox.base) { + prlog(PR_ERR, "Duplicate call to mbox_init()\n"); + return; + } + + prlog(PR_DEBUG, "Attempting mbox init\n"); + np = dt_find_compatible_node(dt_root, NULL, "mbox"); + if (!np) { + /* Only an ERROR on P9 and above, otherwise just + * a warning for someone doing development + */ + prlog((proc_gen <= proc_gen_p8) ? PR_DEBUG : PR_ERR, + "No device tree entry\n"); + return; + } + + /* Read the interrupts property if any */ + irq = dt_prop_get_u32_def(np, "interrupts", 0); + if (!irq) { + prlog(PR_ERR, "No interrupts property\n"); + return; + } + + if (!lpc_present()) { + prlog(PR_ERR, "LPC not present\n"); + return; + } + + /* Get IO base */ + prop = dt_find_property(np, "reg"); + if (!prop) { + prlog(PR_ERR, "Can't find reg property\n"); + return; + } + if (dt_property_get_cell(prop, 0) != OPAL_LPC_IO) { + prlog(PR_ERR, "Only supports IO addresses\n"); + return; + } + mbox.base = dt_property_get_cell(prop, 1); + + if (!mbox_init_hw()) { + prlog(PR_DEBUG, "Couldn't init HW\n"); + return; + } + + /* Disable the standard interrupt we don't care */ + bmc_mbox_outb(MBOX_CTRL_INT_MASK, MBOX_HOST_CTRL); + + /* Clear the status reg bits that we intend to use for interrupts */ + /* W1C */ + bmc_mbox_outb(MBOX_STATUS_1_RESP | MBOX_STATUS_1_ATTN, MBOX_STATUS_1); + + mbox.queue_len = 0; + mbox.callback = NULL; + mbox.drv_data = NULL; + mbox.timeout = 0; + mbox.sequence = 0; + init_lock(&mbox.lock); + + init_timer(&mbox.poller, mbox_poll, NULL); + + chip_id = dt_get_chip_id(np); + mbox_lpc_client.interrupts = LPC_IRQ(irq); + lpc_register_client(chip_id, &mbox_lpc_client, IRQ_ATTR_TARGET_OPAL); + + /* Enable interrupts */ + bmc_mbox_outb(MBOX_STATUS_1_ATTN | MBOX_STATUS_1_RESP, MBOX_HOST_INT_EN_1); + + prlog(PR_DEBUG, "Enabled on chip %d, IO port 0x%x, IRQ %d\n", + chip_id, mbox.base, irq); +} + + |