| /* Copyright 2013-2014 IBM Corp. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
| * implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <skiboot.h> |
| #include <lpc.h> |
| #include <lock.h> |
| #include <device.h> |
| #include <timebase.h> |
| #include <ipmi.h> |
| #include <bt.h> |
| #include <timer.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 |
| |
| /* 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 TB ticks) before a message is timed out. |
| */ |
| #define BT_MSG_TIMEOUT (secs_to_tb(3)) |
| |
| /* |
| * Maximum number of times to attempt sending a message before giving up. |
| */ |
| #define BT_MAX_RETRY_COUNT 1 |
| |
| #define BT_QUEUE_DEBUG 0 |
| |
| #define BT_ERR(msg, fmt, args...) \ |
| do { prerror("BT seq 0x%02x cmd 0x%02x: " fmt "\n", \ |
| (msg)->seq, (msg)->ipmi_msg.cmd, ##args); } while(0) |
| |
| enum bt_states { |
| BT_STATE_IDLE = 0, |
| BT_STATE_RESP_WAIT, |
| }; |
| |
| struct bt_msg { |
| struct list_node link; |
| unsigned long tb; |
| uint8_t seq; |
| uint8_t retry_count; |
| struct ipmi_msg ipmi_msg; |
| }; |
| |
| struct bt { |
| uint32_t base_addr; |
| enum bt_states state; |
| struct lock lock; |
| struct list_head msgq; |
| struct timer poller; |
| bool irq_ok; |
| int queue_len; |
| }; |
| static struct bt bt; |
| |
| 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 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); |
| } |
| |
| static inline void bt_set_state(enum bt_states next_state) |
| { |
| bt.state = next_state; |
| } |
| |
| static void bt_msg_del(struct bt_msg *bt_msg) |
| { |
| list_del(&bt_msg->link); |
| bt.queue_len--; |
| ipmi_cmd_done(bt_msg->ipmi_msg.cmd, bt_msg->ipmi_msg.netfn + (1 << 2), |
| IPMI_TIMEOUT_ERR, &bt_msg->ipmi_msg); |
| } |
| |
| 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); |
| |
| bt_set_state(BT_STATE_IDLE); |
| } |
| |
| 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(void) |
| { |
| int i; |
| struct bt_msg *bt_msg; |
| struct ipmi_msg *ipmi_msg; |
| |
| bt_msg = list_top(&bt.msgq, struct bt_msg, link); |
| assert(bt_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_msg->tb = mftb(); |
| bt_outb(BT_CTRL_H2B_ATN, BT_CTRL); |
| bt_set_state(BT_STATE_RESP_WAIT); |
| |
| return; |
| } |
| |
| static void bt_flush_msg(void) |
| { |
| bt_outb(BT_CTRL_B2H_ATN | BT_CTRL_CLR_RD_PTR, BT_CTRL); |
| bt_set_h_busy(false); |
| } |
| |
| static void bt_get_resp(void) |
| { |
| int i; |
| struct bt_msg *tmp_bt_msg, *bt_msg = NULL; |
| 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 messasge */ |
| list_for_each(&bt.msgq, tmp_bt_msg, link) { |
| if (tmp_bt_msg->seq == seq) { |
| bt_msg = tmp_bt_msg; |
| break; |
| } |
| |
| } |
| if (!bt_msg) { |
| /* A response to a message we no longer care about. */ |
| prlog(PR_INFO, "BT: Nobody cared about a response to an BT/IPMI message\n"); |
| bt_flush_msg(); |
| bt_set_state(BT_STATE_IDLE); |
| return; |
| } |
| |
| ipmi_msg = &bt_msg->ipmi_msg; |
| |
| /* |
| * Make sure we have enough room to store the resposne. 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_ERR(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_set_state(BT_STATE_IDLE); |
| |
| list_del(&bt_msg->link); |
| bt.queue_len--; |
| unlock(&bt.lock); |
| |
| /* |
| * Call the IPMI layer to finish processing the message. |
| */ |
| #if BT_QUEUE_DEBUG |
| prlog(PR_DEBUG, "cmd 0x%02x done\n", seq); |
| #endif |
| |
| ipmi_cmd_done(cmd, netfn, cc, ipmi_msg); |
| lock(&bt.lock); |
| |
| return; |
| } |
| |
| static void bt_expire_old_msg(void) |
| { |
| unsigned long tb; |
| struct bt_msg *bt_msg; |
| |
| tb = mftb(); |
| bt_msg = list_top(&bt.msgq, struct bt_msg, link); |
| |
| if (bt_msg && bt_msg->tb > 0 && (bt_msg->tb + BT_MSG_TIMEOUT) < tb) { |
| if (bt_msg->retry_count < BT_MAX_RETRY_COUNT) { |
| /* 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_ERR(bt_msg, "Retry sending message"); |
| bt_msg->retry_count++; |
| bt_msg->tb = mftb(); |
| bt_outb(BT_CTRL_H2B_ATN, BT_CTRL); |
| } else { |
| BT_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 = false; |
| |
| if (!list_empty(&bt.msgq)) { |
| printed = false; |
| prlog(PR_DEBUG, "-------- BT Msg Queue --------\n"); |
| list_for_each(&bt.msgq, msg, link) { |
| prlog(PR_DEBUG, "Seq: 0x%02x Cmd: 0x%02x\n", msg->seq, msg->ipmi_msg.cmd); |
| } |
| prlog(PR_DEBUG, "-----------------------------\n"); |
| } else if (!printed) { |
| printed = true; |
| prlog(PR_DEBUG, "----- BT Msg Queue Empty -----\n"); |
| } |
| } |
| #else |
| static void print_debug_queue_info(void) {} |
| #endif |
| |
| static void bt_send_and_unlock(void) |
| { |
| if (lpc_ok() && bt_idle() && !list_empty(&bt.msgq) |
| && bt.state == BT_STATE_IDLE) |
| bt_send_msg(); |
| |
| unlock(&bt.lock); |
| return; |
| } |
| |
| static void bt_poll(struct timer *t __unused, void *data __unused) |
| { |
| 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); |
| bt_ctrl = bt_inb(BT_CTRL); |
| |
| print_debug_queue_info(); |
| bt_expire_old_msg(); |
| |
| /* Is there a response waiting for us? */ |
| if (bt.state == BT_STATE_RESP_WAIT && |
| (bt_ctrl & BT_CTRL_B2H_ATN)) |
| bt_get_resp(); |
| |
| /* 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_add_msg(struct bt_msg *bt_msg) |
| { |
| bt_msg->tb = 0; |
| bt_msg->seq = ipmi_seq++; |
| bt_msg->retry_count = 0; |
| bt.queue_len++; |
| if (bt.queue_len > BT_MAX_QUEUE_LEN) { |
| /* Maximum queue length exceeded - remove the oldest message |
| from the queue. */ |
| BT_ERR(bt_msg, "Maximum queue length exceeded"); |
| bt_msg = list_tail(&bt.msgq, struct bt_msg, link); |
| assert(bt_msg); |
| BT_ERR(bt_msg, "Removed from queue"); |
| bt_msg_del(bt_msg); |
| } |
| } |
| |
| 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(&bt.msgq, &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; |
| } |
| |
| void bt_irq(void) |
| { |
| 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); |
| } |
| } |
| |
| /* |
| * 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); |
| } |
| |
| /* |
| * 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, |
| }; |
| |
| void bt_init(void) |
| { |
| struct dt_node *n; |
| const struct dt_property *prop; |
| |
| /* We support only one */ |
| n = dt_find_compatible_node(dt_root, NULL, "ipmi-bt"); |
| if (!n) |
| return; |
| |
| /* Get IO base */ |
| prop = dt_find_property(n, "reg"); |
| if (!prop) { |
| prerror("BT: Can't find reg property\n"); |
| return; |
| } |
| if (dt_property_get_cell(prop, 0) != OPAL_LPC_IO) { |
| prerror("BT: 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. |
| */ |
| bt_set_state(BT_STATE_IDLE); |
| list_head_init(&bt.msgq); |
| bt.queue_len = 0; |
| |
| printf("BT: Interface intialized, 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)); |
| } |