diff options
Diffstat (limited to 'roms/skiboot/hw/bt.c')
-rw-r--r-- | roms/skiboot/hw/bt.c | 720 |
1 files changed, 720 insertions, 0 deletions
diff --git a/roms/skiboot/hw/bt.c b/roms/skiboot/hw/bt.c new file mode 100644 index 000000000..5016feab6 --- /dev/null +++ b/roms/skiboot/hw/bt.c @@ -0,0 +1,720 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Block Transfer, typically what IPMI goes over + * + * Copyright 2013-2019 IBM Corp. + */ + +#define pr_fmt(fmt) "BT: " fmt + +#include <skiboot.h> +#include <lpc.h> +#include <lock.h> +#include <device.h> +#include <timebase.h> +#include <ipmi.h> +#include <bt.h> +#include <timer.h> +#include <ipmi.h> +#include <timebase.h> +#include <chip.h> +#include <interrupts.h> + +/* BT registers */ +#define BT_CTRL 0 +#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_HOST2BMC 1 +#define BT_INTMASK 2 +#define BT_INTMASK_B2H_IRQEN 0x01 +#define BT_INTMASK_B2H_IRQ 0x02 +#define BT_INTMASK_BMC_HWRST 0x80 + +/* Maximum size of the HW FIFO */ +#define BT_FIFO_LEN 64 + +/* Default poll interval before interrupts are working */ +#define BT_DEFAULT_POLL_MS 200 + +/* + * Minimum size of an IPMI request/response including + * mandatory headers. + */ +#define BT_MIN_REQ_LEN 3 +#define BT_MIN_RESP_LEN 4 + +/* How long (in uS) to poll for new ipmi data. */ +#define POLL_TIMEOUT 10000 + +/* Maximum number of outstanding messages to allow in the queue. */ +#define BT_MAX_QUEUE_LEN 10 + +/* How long (in seconds) before a message is timed out. */ +#define BT_MSG_TIMEOUT 3 + +/* Maximum number of times to attempt sending a message before giving up. */ +#define BT_MAX_RETRIES 1 + +/* Macro to enable printing BT message queue for debug */ +#define BT_QUEUE_DEBUG 0 + +/* BT message logging macros */ +#define _BT_Q_LOG(level, msg, fmt, args...) \ + do { if (msg) \ + prlog(level, "seq 0x%02x netfn 0x%02x cmd 0x%02x: " fmt "\n", \ + (msg)->seq, ((msg)->ipmi_msg.netfn >> 2), (msg)->ipmi_msg.cmd, ##args); \ + else \ + prlog(level, "seq 0x?? netfn 0x?? cmd 0x??: " fmt "\n", ##args); \ + } while (0) + +#define BT_Q_ERR(msg, fmt, args...) \ + _BT_Q_LOG(PR_ERR, msg, fmt, ##args) + +#define BT_Q_DBG(msg, fmt, args...) \ + _BT_Q_LOG(PR_DEBUG, msg, fmt, ##args) + +#define BT_Q_TRACE(msg, fmt, args...) \ + _BT_Q_LOG(PR_TRACE, msg, fmt, ##args) + +struct bt_msg { + struct list_node link; + unsigned long tb; + uint8_t seq; + uint8_t send_count; + bool disable_retry; + struct ipmi_msg ipmi_msg; +}; + +struct bt_caps { + uint8_t num_requests; + uint16_t input_buf_len; + uint16_t output_buf_len; + uint8_t msg_timeout; + uint8_t max_retries; +}; + +struct bt { + uint32_t base_addr; + struct lock lock; + struct list_head msgq; + struct list_head msgq_sync; /* separate list for synchronous messages */ + struct timer poller; + bool irq_ok; + int queue_len; + struct bt_caps caps; +}; + +static struct bt bt; +static struct bt_msg *inflight_bt_msg; /* Holds in flight message */ + +static int ipmi_seq; + +static inline uint8_t bt_inb(uint32_t reg) +{ + return lpc_inb(bt.base_addr + reg); +} + +static inline void bt_outb(uint8_t data, uint32_t reg) +{ + lpc_outb(data, bt.base_addr + reg); +} + +static inline void bt_set_h_busy(bool value) +{ + uint8_t rval; + + rval = bt_inb(BT_CTRL); + if (value != !!(rval & BT_CTRL_H_BUSY)) + bt_outb(BT_CTRL_H_BUSY, BT_CTRL); +} + +static inline void bt_assert_h_busy(void) +{ + uint8_t rval; + rval = bt_inb(BT_CTRL); + assert(rval & BT_CTRL_H_BUSY); +} + +static void get_bt_caps_complete(struct ipmi_msg *msg) +{ + /* Ignore errors, we'll fallback to using the defaults, no big deal */ + if (msg->data[0] == 0) { + prlog(PR_DEBUG, "Got illegal BMC BT capability\n"); + goto out; + } + + if (msg->data[1] != BT_FIFO_LEN) { + prlog(PR_DEBUG, "Got a input buffer len (%u) cap which differs from the default\n", + msg->data[1]); + } + + if (msg->data[2] != BT_FIFO_LEN) { + prlog(PR_DEBUG, "Got a output buffer len (%u) cap which differs from the default\n", + msg->data[2]); + } + + /* + * IPMI Spec says that the value for buffer sizes are: + * "the largest value allowed in first byte" + * Therefore we want to add one to what we get + */ + bt.caps.num_requests = msg->data[0]; + bt.caps.input_buf_len = msg->data[1] + 1; + bt.caps.output_buf_len = msg->data[2] + 1; + bt.caps.msg_timeout = msg->data[3]; + bt.caps.max_retries = msg->data[4]; + prlog(PR_DEBUG, "BMC BT capabilities received:\n"); + prlog(PR_DEBUG, "buffer sizes: %d input %d output\n", + bt.caps.input_buf_len, bt.caps.output_buf_len); + prlog(PR_DEBUG, "number of requests: %d\n", bt.caps.num_requests); + prlog(PR_DEBUG, "msg timeout: %d max retries: %d\n", + bt.caps.msg_timeout, bt.caps.max_retries); + +out: + ipmi_free_msg(msg); +} + +static void get_bt_caps(void) +{ + + struct ipmi_msg *bmc_caps; + /* + * Didn't sent a message, now is a good time to ask the BMC for its + * capabilities. + */ + bmc_caps = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, IPMI_GET_BT_CAPS, + get_bt_caps_complete, NULL, NULL, 0, sizeof(struct bt_caps)); + if (!bmc_caps) + prerror("Couldn't create BMC BT capabilities msg\n"); + + if (bmc_caps && ipmi_queue_msg(bmc_caps)) + prerror("Couldn't enqueue request for BMC BT capabilities\n"); + + /* Ignore errors, we'll fallback to using the defaults, no big deal */ +} + +static inline bool bt_idle(void) +{ + uint8_t bt_ctrl = bt_inb(BT_CTRL); + + return !(bt_ctrl & BT_CTRL_B_BUSY) && !(bt_ctrl & BT_CTRL_H2B_ATN); +} + +/* Must be called with bt.lock held */ +static void bt_msg_del(struct bt_msg *bt_msg) +{ + list_del(&bt_msg->link); + bt.queue_len--; + + /* once inflight_bt_msg out of list, it should be emptyed */ + if (bt_msg == inflight_bt_msg) + inflight_bt_msg = NULL; + + unlock(&bt.lock); + ipmi_cmd_done(bt_msg->ipmi_msg.cmd, + IPMI_NETFN_RETURN_CODE(bt_msg->ipmi_msg.netfn), + IPMI_TIMEOUT_ERR, &bt_msg->ipmi_msg); + lock(&bt.lock); +} + +static void bt_init_interface(void) +{ + /* Clear interrupt condition & enable irq */ + bt_outb(BT_INTMASK_B2H_IRQ | BT_INTMASK_B2H_IRQEN, BT_INTMASK); + + /* Take care of a stable H_BUSY if any */ + bt_set_h_busy(false); +} + +static void bt_reset_interface(void) +{ + bt_outb(BT_INTMASK_BMC_HWRST, BT_INTMASK); + bt_init_interface(); +} + +/* + * Try and send a message from the message queue. Caller must hold + * bt.bt_lock and bt.lock and ensue the message queue is not + * empty. + */ +static void bt_send_msg(struct bt_msg *bt_msg) +{ + int i; + struct ipmi_msg *ipmi_msg; + + ipmi_msg = &bt_msg->ipmi_msg; + + /* Send the message */ + bt_outb(BT_CTRL_CLR_WR_PTR, BT_CTRL); + + /* Byte 1 - Length */ + bt_outb(ipmi_msg->req_size + BT_MIN_REQ_LEN, BT_HOST2BMC); + + /* Byte 2 - NetFn/LUN */ + bt_outb(ipmi_msg->netfn, BT_HOST2BMC); + + /* Byte 3 - Seq */ + bt_outb(bt_msg->seq, BT_HOST2BMC); + + /* Byte 4 - Cmd */ + bt_outb(ipmi_msg->cmd, BT_HOST2BMC); + + /* Byte 5:N - Data */ + for (i = 0; i < ipmi_msg->req_size; i++) + bt_outb(ipmi_msg->data[i], BT_HOST2BMC); + + BT_Q_TRACE(bt_msg, "Message sent to host"); + bt_msg->send_count++; + + bt_outb(BT_CTRL_H2B_ATN, BT_CTRL); + + return; +} + +static void bt_clear_fifo(void) +{ + int i; + + for (i = 0; i < bt.caps.input_buf_len; i++) + bt_outb(0xff, BT_HOST2BMC); +} + +static void bt_flush_msg(void) +{ + bt_assert_h_busy(); + bt_outb(BT_CTRL_B2H_ATN | BT_CTRL_CLR_RD_PTR | BT_CTRL_CLR_WR_PTR, BT_CTRL); + bt_clear_fifo(); + /* Can't hurt to clear the write pointer again, just to be sure */ + bt_outb(BT_CTRL_CLR_WR_PTR, BT_CTRL); + bt_set_h_busy(false); +} + +static void bt_get_resp(void) +{ + int i; + struct ipmi_msg *ipmi_msg; + uint8_t resp_len, netfn, seq, cmd; + uint8_t cc = IPMI_CC_NO_ERROR; + + /* Indicate to the BMC that we are busy */ + bt_set_h_busy(true); + + /* Clear B2H_ATN and read pointer */ + bt_outb(BT_CTRL_B2H_ATN, BT_CTRL); + bt_outb(BT_CTRL_CLR_RD_PTR, BT_CTRL); + + /* Read the response */ + /* Byte 1 - Length (includes header size) */ + resp_len = bt_inb(BT_HOST2BMC) - BT_MIN_RESP_LEN; + + /* Byte 2 - NetFn/LUN */ + netfn = bt_inb(BT_HOST2BMC); + + /* Byte 3 - Seq */ + seq = bt_inb(BT_HOST2BMC); + + /* Byte 4 - Cmd */ + cmd = bt_inb(BT_HOST2BMC); + + /* Byte 5 - Completion Code */ + cc = bt_inb(BT_HOST2BMC); + + /* Find the corresponding message */ + if (inflight_bt_msg == NULL || inflight_bt_msg->seq != seq) { + /* A response to a message we no longer care about. */ + prlog(PR_INFO, "Nobody cared about a response to an BT/IPMI message" + "(seq 0x%02x netfn 0x%02x cmd 0x%02x)\n", seq, (netfn >> 2), cmd); + bt_flush_msg(); + return; + } + + ipmi_msg = &inflight_bt_msg->ipmi_msg; + + /* + * Make sure we have enough room to store the response. As all values + * are unsigned we will also trigger this error if + * bt_inb(BT_HOST2BMC) < BT_MIN_RESP_LEN (which should never occur). + */ + if (resp_len > ipmi_msg->resp_size) { + BT_Q_ERR(inflight_bt_msg, "Invalid resp_len %d", resp_len); + resp_len = ipmi_msg->resp_size; + cc = IPMI_ERR_MSG_TRUNCATED; + } + ipmi_msg->resp_size = resp_len; + + /* Byte 6:N - Data */ + for (i = 0; i < resp_len; i++) + ipmi_msg->data[i] = bt_inb(BT_HOST2BMC); + bt_set_h_busy(false); + + BT_Q_TRACE(inflight_bt_msg, "IPMI MSG done"); + + list_del(&inflight_bt_msg->link); + /* Ready to send next message */ + inflight_bt_msg = NULL; + bt.queue_len--; + unlock(&bt.lock); + + /* Call IPMI layer to finish processing the message. */ + ipmi_cmd_done(cmd, netfn, cc, ipmi_msg); + lock(&bt.lock); + + return; +} + +static void bt_expire_old_msg(uint64_t tb) +{ + struct bt_msg *bt_msg = inflight_bt_msg; + + if (bt_msg && bt_msg->tb > 0 && !chip_quirk(QUIRK_SIMICS) && + (tb_compare(tb, bt_msg->tb + + secs_to_tb(bt.caps.msg_timeout)) == TB_AAFTERB)) { + if (bt_msg->send_count <= bt.caps.max_retries && + !bt_msg->disable_retry) { + /* A message timeout is usually due to the BMC + * clearing the H2B_ATN flag without actually + * doing anything. The data will still be in the + * FIFO so just reset the flag.*/ + BT_Q_ERR(bt_msg, "Retry sending message"); + + /* This means we have started message timeout, but not + * yet sent message to BMC as driver was not free to + * send message. Lets resend message. + */ + if (bt_msg->send_count == 0) + bt_send_msg(bt_msg); + else + bt_outb(BT_CTRL_H2B_ATN, BT_CTRL); + + bt_msg->send_count++; + bt_msg->tb = tb; + } else { + BT_Q_ERR(bt_msg, "Timeout sending message"); + bt_msg_del(bt_msg); + + /* + * Timing out a message is inherently racy as the BMC + * may start writing just as we decide to kill the + * message. Hopefully resetting the interface is + * sufficient to guard against such things. + */ + bt_reset_interface(); + } + } +} + +#if BT_QUEUE_DEBUG +static void print_debug_queue_info(void) +{ + struct bt_msg *msg; + static bool printed; + + if (!list_empty(&bt.msgq_sync) || !list_empty(&bt.msgq)) { + printed = false; + prlog(PR_DEBUG, "-------- BT Sync Msg Queue -------\n"); + list_for_each(&bt.msgq_sync, msg, link) { + BT_Q_DBG(msg, "[ sent %d ]", msg->send_count); + } + prlog(PR_DEBUG, "---------- BT Msg Queue ----------\n"); + list_for_each(&bt.msgq, msg, link) { + BT_Q_DBG(msg, "[ sent %d ]", msg->send_count); + } + prlog(PR_DEBUG, "----------------------------------\n"); + } else if (!printed) { + printed = true; + prlog(PR_DEBUG, "------- BT Msg Queue Empty -------\n"); + } +} +#endif + +static void bt_send_and_unlock(void) +{ + /* Busy? */ + if (inflight_bt_msg) + goto out_unlock; + + if (!lpc_ok()) + goto out_unlock; + + /* Synchronous messages gets priority over normal message */ + if (!list_empty(&bt.msgq_sync)) + inflight_bt_msg = list_top(&bt.msgq_sync, struct bt_msg, link); + else if (!list_empty(&bt.msgq)) + inflight_bt_msg = list_top(&bt.msgq, struct bt_msg, link); + else + goto out_unlock; + + assert(inflight_bt_msg); + /* + * Start the message timeout once it gets to the top + * of the queue. This will ensure we timeout messages + * in the case of a broken bt interface as occurs when + * the BMC is not responding to any IPMI messages. + */ + if (inflight_bt_msg->tb == 0) + inflight_bt_msg->tb = mftb(); + + /* + * Only send it if we haven't already. + * Timeouts and retries happen in bt_expire_old_msg() + * called from bt_poll() + */ + if (bt_idle() && inflight_bt_msg->send_count == 0) + bt_send_msg(inflight_bt_msg); + +out_unlock: + unlock(&bt.lock); +} + +static void bt_poll(struct timer *t __unused, void *data __unused, + uint64_t now) +{ + uint8_t bt_ctrl; + + /* Don't do anything if the LPC bus is offline */ + if (!lpc_ok()) + return; + + /* + * If we can't get the lock assume someone else will notice + * the new message and process it. + */ + lock(&bt.lock); + +#if BT_QUEUE_DEBUG + print_debug_queue_info(); +#endif + + bt_ctrl = bt_inb(BT_CTRL); + + /* Is there a response waiting for us? */ + if (bt_ctrl & BT_CTRL_B2H_ATN) + bt_get_resp(); + + bt_expire_old_msg(now); + + /* Check for sms_atn */ + if (bt_inb(BT_CTRL) & BT_CTRL_SMS_ATN) { + bt_outb(BT_CTRL_SMS_ATN, BT_CTRL); + unlock(&bt.lock); + ipmi_sms_attention(); + lock(&bt.lock); + } + + /* + * Send messages if we can. If the BMC was really quick we + * could loop back to the start and check for a response + * instead of unlocking, but testing shows the BMC isn't that + * fast so we will wait for the IRQ or a call to the pollers instead. + */ + bt_send_and_unlock(); + + schedule_timer(&bt.poller, + bt.irq_ok ? TIMER_POLL : msecs_to_tb(BT_DEFAULT_POLL_MS)); +} + +static void bt_ipmi_poll(void) +{ + bt_poll(NULL, NULL, mftb()); +} + +static void bt_add_msg(struct bt_msg *bt_msg) +{ + bt_msg->tb = 0; + bt_msg->seq = ipmi_seq++; + bt_msg->send_count = 0; + bt.queue_len++; + if (bt.queue_len > BT_MAX_QUEUE_LEN) { + /* Maximum queue length exceeded, remove oldest messages. */ + BT_Q_ERR(bt_msg, "Maximum queue length exceeded"); + /* First try to remove message from normal queue */ + if (!list_empty(&bt.msgq)) + bt_msg = list_tail(&bt.msgq, struct bt_msg, link); + else if (!list_empty(&bt.msgq_sync)) + bt_msg = list_tail(&bt.msgq_sync, struct bt_msg, link); + assert(bt_msg); + BT_Q_ERR(bt_msg, "Removed from queue"); + bt_msg_del(bt_msg); + } +} + +/* Add message to synchronous message list */ +static int bt_add_ipmi_msg_head(struct ipmi_msg *ipmi_msg) +{ + struct bt_msg *bt_msg = container_of(ipmi_msg, struct bt_msg, ipmi_msg); + + lock(&bt.lock); + bt_add_msg(bt_msg); + list_add_tail(&bt.msgq_sync, &bt_msg->link); + bt_send_and_unlock(); + + return 0; +} + +static int bt_add_ipmi_msg(struct ipmi_msg *ipmi_msg) +{ + struct bt_msg *bt_msg = container_of(ipmi_msg, struct bt_msg, ipmi_msg); + + lock(&bt.lock); + bt_add_msg(bt_msg); + list_add_tail(&bt.msgq, &bt_msg->link); + bt_send_and_unlock(); + + return 0; +} + +static void bt_irq(uint32_t chip_id __unused, uint32_t irq_mask __unused) +{ + uint8_t ireg; + + ireg = bt_inb(BT_INTMASK); + + bt.irq_ok = true; + if (ireg & BT_INTMASK_B2H_IRQ) { + bt_outb(BT_INTMASK_B2H_IRQ | BT_INTMASK_B2H_IRQEN, BT_INTMASK); + bt_poll(NULL, NULL, mftb()); + } +} + +/* + * Allocate an ipmi message and bt container and return the ipmi + * message struct. Allocates enough space for the request and response + * data. + */ +static struct ipmi_msg *bt_alloc_ipmi_msg(size_t request_size, size_t response_size) +{ + struct bt_msg *bt_msg; + + bt_msg = zalloc(sizeof(struct bt_msg) + MAX(request_size, response_size)); + if (!bt_msg) + return NULL; + + bt_msg->ipmi_msg.req_size = request_size; + bt_msg->ipmi_msg.resp_size = response_size; + bt_msg->ipmi_msg.data = (uint8_t *) (bt_msg + 1); + + return &bt_msg->ipmi_msg; +} + +/* + * Free a previously allocated ipmi message. + */ +static void bt_free_ipmi_msg(struct ipmi_msg *ipmi_msg) +{ + struct bt_msg *bt_msg = container_of(ipmi_msg, struct bt_msg, ipmi_msg); + + free(bt_msg); +} + +/* + * Do not resend IPMI messages to BMC. + */ +static void bt_disable_ipmi_msg_retry(struct ipmi_msg *ipmi_msg) +{ + struct bt_msg *bt_msg = container_of(ipmi_msg, struct bt_msg, ipmi_msg); + + bt_msg->disable_retry = true; +} + +/* + * Remove a message from the queue. The memory allocated for the ipmi message + * will need to be freed by the caller with bt_free_ipmi_msg() as it will no + * longer be in the queue of messages. + */ +static int bt_del_ipmi_msg(struct ipmi_msg *ipmi_msg) +{ + struct bt_msg *bt_msg = container_of(ipmi_msg, struct bt_msg, ipmi_msg); + + lock(&bt.lock); + list_del(&bt_msg->link); + bt.queue_len--; + bt_send_and_unlock(); + return 0; +} + +static struct ipmi_backend bt_backend = { + .alloc_msg = bt_alloc_ipmi_msg, + .free_msg = bt_free_ipmi_msg, + .queue_msg = bt_add_ipmi_msg, + .queue_msg_head = bt_add_ipmi_msg_head, + .dequeue_msg = bt_del_ipmi_msg, + .disable_retry = bt_disable_ipmi_msg_retry, + .poll = bt_ipmi_poll, +}; + +static struct lpc_client bt_lpc_client = { + .interrupt = bt_irq, +}; + +void bt_init(void) +{ + struct dt_node *n; + const struct dt_property *prop; + uint32_t irq; + + /* Set sane capability defaults */ + bt.caps.num_requests = 1; + bt.caps.input_buf_len = BT_FIFO_LEN; + bt.caps.output_buf_len = BT_FIFO_LEN; + bt.caps.msg_timeout = BT_MSG_TIMEOUT; + bt.caps.max_retries = BT_MAX_RETRIES; + + /* We support only one */ + n = dt_find_compatible_node(dt_root, NULL, "ipmi-bt"); + if (!n) { + prerror("No BT device\n"); + return; + } + + /* Get IO base */ + prop = dt_find_property(n, "reg"); + if (!prop) { + prerror("Can't find reg property\n"); + return; + } + if (dt_property_get_cell(prop, 0) != OPAL_LPC_IO) { + prerror("Only supports IO addresses\n"); + return; + } + bt.base_addr = dt_property_get_cell(prop, 1); + init_timer(&bt.poller, bt_poll, NULL); + + bt_init_interface(); + init_lock(&bt.lock); + + /* + * The iBT interface comes up in the busy state until the daemon has + * initialised it. + */ + list_head_init(&bt.msgq); + list_head_init(&bt.msgq_sync); + inflight_bt_msg = NULL; + bt.queue_len = 0; + + prlog(PR_INFO, "Interface initialized, IO 0x%04x\n", bt.base_addr); + + ipmi_register_backend(&bt_backend); + + /* + * We initially schedule the poller as a relatively fast timer, at + * least until we have at least one interrupt occurring at which + * point we turn it into a background poller + */ + schedule_timer(&bt.poller, msecs_to_tb(BT_DEFAULT_POLL_MS)); + + irq = dt_prop_get_u32(n, "interrupts"); + bt_lpc_client.interrupts = LPC_IRQ(irq); + lpc_register_client(dt_get_chip_id(n), &bt_lpc_client, + IRQ_ATTR_TARGET_OPAL); + + /* Enqueue an IPMI message to ask the BMC about its BT capabilities */ + get_bt_caps(); + + prlog(PR_DEBUG, "Using LPC IRQ %d\n", irq); +} |