diff options
author | Alistair Popple <alistair@popple.id.au> | 2014-08-12 17:59:39 +1000 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-08-13 15:42:27 +1000 |
commit | 391291ba265ab36819ea10b2459bcf56468b07b3 (patch) | |
tree | 939f1869af96f58b1428214de5f4a1b052fdb71e | |
parent | 793b06d73e1543350fc7c4c7c615c594ec1ddd62 (diff) | |
download | talos-skiboot-391291ba265ab36819ea10b2459bcf56468b07b3.tar.gz talos-skiboot-391291ba265ab36819ea10b2459bcf56468b07b3.zip |
ipmi: Add a base IPMI stack with a BT driver
This patch adds a basic IPMI layer to the sapphire core and support
for a BT IPMI interface as found on the Aspeed BMC of the Palmetto
platform
[ Changed the compatible property -- BenH ]
Signed-off-by: Alistair Popple <alistair@popple.id.au>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
-rw-r--r-- | core/Makefile.inc | 2 | ||||
-rw-r--r-- | core/ipmi.c | 36 | ||||
-rw-r--r-- | hw/Makefile.inc | 2 | ||||
-rw-r--r-- | hw/bt.c | 400 | ||||
-rw-r--r-- | include/bt.h | 31 | ||||
-rw-r--r-- | include/ipmi.h | 75 |
6 files changed, 544 insertions, 2 deletions
diff --git a/core/Makefile.inc b/core/Makefile.inc index 90decf9f..274318d8 100644 --- a/core/Makefile.inc +++ b/core/Makefile.inc @@ -6,7 +6,7 @@ CORE_OBJS += malloc.o lock.o cpu.o utils.o fdt.o opal.o interrupts.o CORE_OBJS += timebase.o opal-msg.o pci.o pci-opal.o fast-reboot.o CORE_OBJS += device.o exceptions.o trace.o affinity.o vpd.o CORE_OBJS += hostservices.o platform.o nvram.o flash-nvram.o hmi.o -CORE_OBJS += console-log.o +CORE_OBJS += console-log.o ipmi.o CORE=core/built-in.o $(CORE): $(CORE_OBJS:%=core/%) diff --git a/core/ipmi.c b/core/ipmi.c new file mode 100644 index 00000000..b21a0f3f --- /dev/null +++ b/core/ipmi.c @@ -0,0 +1,36 @@ +/* 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 <stdio.h> +#include <bt.h> +#include <ipmi.h> + +static void ipmi_cmd_done(struct ipmi_msg *msg) +{ + if (msg->cc != IPMI_CC_NO_ERROR) { + prerror("IPMI: Got error response 0x%02x\n", msg->cc); + } + + switch (msg->netfn) { + default: + prerror("IPMI: Invalid IPMI function code in response\n"); + } +} + +void ipmi_init(void) +{ + bt_init(ipmi_cmd_done); +} diff --git a/hw/Makefile.inc b/hw/Makefile.inc index f1ffe55c..320e33b0 100644 --- a/hw/Makefile.inc +++ b/hw/Makefile.inc @@ -4,7 +4,7 @@ SUBDIRS += hw HW_OBJS = xscom.o chiptod.o gx.o cec.o lpc.o lpc-uart.o psi.o HW_OBJS += homer.o slw.o occ.o nx.o fsi-master.o centaur.o HW_OBJS += p7ioc.o p7ioc-inits.o p7ioc-phb.o p5ioc2.o p5ioc2-phb.o -HW_OBJS += phb3.o sfc-ctrl.o fake-rtc.o +HW_OBJS += phb3.o sfc-ctrl.o fake-rtc.o bt.o HW=hw/built-in.o include $(SRC)/hw/fsp/Makefile.inc diff --git a/hw/bt.c b/hw/bt.c new file mode 100644 index 00000000..407f0295 --- /dev/null +++ b/hw/bt.c @@ -0,0 +1,400 @@ +/* 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 <time.h> +#include <ipmi.h> +#include <bt.h> + +/* BT registers */ +#define BT_CTRL 0 +#define BT_CTRL_B_BUSY 0x00000080 +#define BT_CTRL_H_BUSY 0x00000040 +#define BT_CTRL_OEM0 0x00000020 +#define BT_CTRL_SMS_ATN 0x00000010 +#define BT_CTRL_B2H_ATN 0x00000008 +#define BT_CTRL_H2B_ATN 0x00000004 +#define BT_CTRL_CLR_RD_PTR 0x00000002 +#define BT_CTRL_CLR_WR_PTR 0x00000001 +#define BT_HOST2BMC 1 +#define BT_INTMASK 2 +#define BT_INTMASK_BMC_HWRST 0x00000001 + +/* + * 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 + +enum bt_states { + BT_STATE_IDLE = 0, + BT_STATE_RESP_WAIT, + BT_STATE_B_BUSY, +}; + +const char *state_str[] = { + "BT_STATE_IDLE", + "BT_STATE_RESP_WAIT", + "BT_STATE_B_BUSY", +}; + +struct bt_msg { + struct list_node link; + struct ipmi_msg *ipmi_msg; + uint8_t seq; + uint8_t lun; +}; + +struct bt { + uint32_t base_addr; + enum bt_states state; + struct lock lock; + struct list_head msgq; + void (*ipmi_cmd_done)(struct ipmi_msg *); +}; +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 bool bt_idle(void) +{ + return !(bt_inb(BT_CTRL) & (BT_CTRL_B_BUSY | BT_CTRL_H2B_ATN)); +} + +static void bt_setmask(uint8_t mask, uint32_t reg) +{ + uint8_t tmp; + tmp = bt_inb(reg); + bt_outb(tmp | mask, reg); +} + +static void bt_clearmask(uint8_t mask, uint32_t reg) +{ + uint8_t tmp; + tmp = bt_inb(reg); + bt_outb(tmp & ~mask, reg); +} + +static inline void bt_set_state(enum bt_states next_state) +{ + bt.state = next_state; +} + +static int bt_add_ipmi_msg(struct ipmi_msg *ipmi_msg) +{ + struct bt_msg *bt_msg = malloc(sizeof(struct bt_msg)); + + if (!bt_msg) + return -1; + + bt_msg->lun = 0; + bt_msg->seq = ipmi_seq++; + bt_msg->ipmi_msg = ipmi_msg; + list_add_tail(&bt.msgq, &bt_msg->link); + + return 0; +} + +static void bt_reset_interface(void) +{ + bt_setmask(BT_INTMASK_BMC_HWRST, BT_INTMASK); + bt_set_state(BT_STATE_B_BUSY); +} + +static bool bt_try_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); + if (!bt_msg) + return true; + + ipmi_msg = bt_msg->ipmi_msg; + + if (!bt_idle()) { + prerror("BT: Interface in an unexpected state, attempting reset\n"); + bt_reset_interface(); + return false; + } + + /* Send the message */ + bt_setmask(BT_CTRL_CLR_WR_PTR, BT_CTRL); + + /* Byte 1 - Length */ + bt_outb(ipmi_msg->req_data_len + BT_MIN_REQ_LEN, BT_HOST2BMC); + + /* Byte 2 - NetFn/LUN */ + bt_outb((ipmi_msg->netfn << 2) | (bt_msg->lun & 0x3), 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_data_len; i++) + bt_outb(ipmi_msg->req_data[i], BT_HOST2BMC); + + bt_setmask(BT_CTRL_H2B_ATN, BT_CTRL); + bt_set_state(BT_STATE_RESP_WAIT); + return true; +} + +static void bt_flush_msg(void) +{ + bt_setmask(BT_CTRL_H_BUSY, BT_CTRL); + bt_clearmask(BT_CTRL_B2H_ATN, BT_CTRL); + bt_setmask(BT_CTRL_CLR_RD_PTR, BT_CTRL); + bt_clearmask(BT_CTRL_H_BUSY, BT_CTRL); +} + +static bool bt_get_resp(void) +{ + int i; + struct bt_msg *bt_msg; + struct ipmi_msg *ipmi_msg; + uint8_t resp_len; + uint8_t netfn; + uint8_t cc = IPMI_CC_NO_ERROR; + + /* Wait for BMC to signal response */ + if (!(bt_inb(BT_CTRL) & BT_CTRL_B2H_ATN)) + return true; + + bt_msg = list_top(&bt.msgq, struct bt_msg, link); + 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_B_BUSY); + return false; + } + + ipmi_msg = bt_msg->ipmi_msg; + bt_setmask(BT_CTRL_H_BUSY, BT_CTRL); + bt_clearmask(BT_CTRL_B2H_ATN, BT_CTRL); + bt_setmask(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; + + /* + * 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_data_len) { + prerror("BT: Invalid resp_len %d for ipmi_msg->cmd = 0x%02x\n", resp_len, ipmi_msg->cmd); + resp_len = ipmi_msg->resp_data_len; + cc = IPMI_ERR_MSG_TRUNCATED; + } + ipmi_msg->resp_data_len = resp_len; + + /* Byte 2 - NetFn/LUN */ + netfn = bt_inb(BT_HOST2BMC); + ipmi_msg->netfn = netfn >> 2; + bt_msg->lun = netfn & 0x3; + + /* Byte 3 - Seq */ + bt_msg->seq = bt_inb(BT_HOST2BMC); + + /* Byte 4 - Cmd */ + ipmi_msg->cmd = bt_inb(BT_HOST2BMC); + + /* Byte 5 - Completion Code */ + ipmi_msg->cc = bt_inb(BT_HOST2BMC); + + /* Byte 6:N - Data */ + for (i = 0; i < resp_len; i++) + ipmi_msg->resp_data[i] = bt_inb(BT_HOST2BMC); + bt_clearmask(BT_CTRL_H_BUSY, BT_CTRL); + + if (cc != IPMI_CC_NO_ERROR) { + prerror("BT: Host error 0x%02x receiving BT/IPMI response\n", cc); + ipmi_msg->cc = cc; + } + + /* Make sure the other side is idle before we move to the idle state */ + bt_set_state(BT_STATE_B_BUSY); + list_del(&bt_msg->link); + + /* + * Call the IPMI layer to finish processing the message. + */ + if (bt.ipmi_cmd_done) + bt.ipmi_cmd_done(ipmi_msg); + + /* + * The IPMI layer should have freed any data it allocated for the IPMI + * message in the completion function above. + */ + free(bt_msg); + + /* Immediately send the next message */ + return false; +} + +static void bt_poll(void) +{ + bool ret = true; + + do { + switch(bt.state) { + case BT_STATE_IDLE: + ret = bt_try_send_msg(); + break; + + case BT_STATE_RESP_WAIT: + ret = bt_get_resp(); + break; + + case BT_STATE_B_BUSY: + if (bt_idle()) { + bt_set_state(BT_STATE_IDLE); + ret = false; + } + break; + } + } + while(!ret); +} + +/* + * Crank the state machine to wait for a specific state. Returns true on + * success and false if there is a timeout. + */ +static bool bt_wait_state(enum bt_states state) +{ + int timeout; + struct timespec ts; + + ts.tv_sec = 0; + ts.tv_nsec = 100000; + for (timeout = POLL_TIMEOUT; timeout > 0; timeout--) { + if (bt.state == state) + return true; + bt_poll(); + nanosleep(&ts, NULL); + } + + return false; +} + +/* + * Add an ipmi message to the queue and wait for a response. + */ +int bt_add_ipmi_msg_wait(struct ipmi_msg *msg) +{ + int ret = 0; + + /* + * TODO: We may need finer grained locks if we start using an + * asynchronous operation model, but this should be fine for the moment. + */ + lock(&bt.lock); + if (!bt_wait_state(BT_STATE_IDLE)) { + ret = -1; + goto out; + } + + if (bt_add_ipmi_msg(msg)) { + ret = -1; + goto out; + } + + /* Make sure we get out of the idle state */ + bt_poll(); + + if (!bt_wait_state(BT_STATE_IDLE)) { + ret = -1; + goto out; + } + +out: + unlock(&bt.lock); + return ret; +} + +void bt_del_ipmi_msg(struct ipmi_msg *ipmi_msg) +{ + struct bt_msg *bt_msg; + struct bt_msg *next; + + lock(&bt.lock); + list_for_each_safe(&bt.msgq, bt_msg, next, link) { + if (bt_msg->ipmi_msg == ipmi_msg) { + list_del(&bt_msg->link); + break; + } + } + unlock(&bt.lock); +} + +void bt_init(void (*ipmi_cmd_done)(struct ipmi_msg *)) +{ + struct dt_node *n; + const struct dt_property *prop; + + /* We support only one */ + n = dt_find_compatible_node(dt_root, NULL, "ibmi-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); + bt_reset_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_B_BUSY); + bt.ipmi_cmd_done = ipmi_cmd_done; + list_head_init(&bt.msgq); +} diff --git a/include/bt.h b/include/bt.h new file mode 100644 index 00000000..4eac355b --- /dev/null +++ b/include/bt.h @@ -0,0 +1,31 @@ +/* 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. + */ + +#ifndef __BT_H +#define __BT_H + +#include <ipmi.h> + +/* Initialise the BT interface */ +void bt_init(void (*ipmi_cmd_done)(struct ipmi_msg *)); + +/* Add an IPMI message to the BT queue and wait for a resposne */ +int bt_add_ipmi_msg_wait(struct ipmi_msg *msg); + +/* Remove an IPMI message from the BT queue */ +void bt_del_ipmi_msg(struct ipmi_msg *ipmi_msg); + +#endif diff --git a/include/ipmi.h b/include/ipmi.h new file mode 100644 index 00000000..3e08cdb7 --- /dev/null +++ b/include/ipmi.h @@ -0,0 +1,75 @@ +/* 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. + */ + +#ifndef __IPMI_H +#define __IPMI_H + +#include <stdint.h> + +/* + * IPMI codes as defined by the standard. + */ +#define IPMI_NETFN_APP_REQUEST 0x06 +#define IPMI_NETFN_APP_RESPONSE 0x07 +#define IPMI_GET_DEVICE_ID_CMD 0x01 +#define IPMI_COLD_RESET_CMD 0x02 +#define IPMI_WARM_RESET_CMD 0x03 +#define IPMI_CLEAR_MSG_FLAGS_CMD 0x30 +#define IPMI_GET_DEVICE_GUID_CMD 0x08 +#define IPMI_GET_MSG_FLAGS_CMD 0x31 +#define IPMI_SEND_MSG_CMD 0x34 +#define IPMI_GET_MSG_CMD 0x33 +#define IPMI_SET_BMC_GLOBAL_ENABLES_CMD 0x2e +#define IPMI_GET_BMC_GLOBAL_ENABLES_CMD 0x2f +#define IPMI_READ_EVENT_MSG_BUFFER_CMD 0x35 +#define IPMI_GET_CHANNEL_INFO_CMD 0x42 + +#define IPMI_NETFN_STORAGE_REQUEST 0x0a +#define IPMI_NETFN_STORAGE_RESPONSE 0x0b +#define IPMI_GET_SEL_INFO_CMD 0x40 +#define IPMI_GET_SEL_TIME_CMD 0x48 +#define IPMI_SET_SEL_TIME_CMD 0x49 + +/* + * IPMI response codes. + */ +#define IPMI_CC_NO_ERROR 0x00 +#define IPMI_NODE_BUSY_ERR 0xc0 +#define IPMI_INVALID_COMMAND_ERR 0xc1 +#define IPMI_TIMEOUT_ERR 0xc3 +#define IPMI_ERR_MSG_TRUNCATED 0xc6 +#define IPMI_REQ_LEN_INVALID_ERR 0xc7 +#define IPMI_REQ_LEN_EXCEEDED_ERR 0xc8 +#define IPMI_NOT_IN_MY_STATE_ERR 0xd5 /* IPMI 2.0 */ +#define IPMI_LOST_ARBITRATION_ERR 0x81 +#define IPMI_BUS_ERR 0x82 +#define IPMI_NAK_ON_WRITE_ERR 0x83 +#define IPMI_ERR_UNSPECIFIED 0xff + +struct ipmi_msg { + uint8_t netfn; + uint8_t cmd; + uint8_t cc; + uint8_t *req_data; + uint8_t req_data_len; + uint8_t *resp_data; + uint8_t resp_data_len; +}; + +/* Initialise the IPMI interface */ +void ipmi_init(void); + +#endif |