summaryrefslogtreecommitdiffstats
path: root/sys/dev/ipmi/ipmi_ssif.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/ipmi/ipmi_ssif.c')
-rw-r--r--sys/dev/ipmi/ipmi_ssif.c375
1 files changed, 375 insertions, 0 deletions
diff --git a/sys/dev/ipmi/ipmi_ssif.c b/sys/dev/ipmi/ipmi_ssif.c
new file mode 100644
index 0000000..2f90b6e
--- /dev/null
+++ b/sys/dev/ipmi/ipmi_ssif.c
@@ -0,0 +1,375 @@
+/*-
+ * Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/condvar.h>
+#include <sys/eventhandler.h>
+#include <sys/kernel.h>
+#include <sys/kthread.h>
+#include <sys/module.h>
+#include <sys/selinfo.h>
+
+#include <dev/smbus/smbconf.h>
+#include <dev/smbus/smb.h>
+
+#include "smbus_if.h"
+
+#ifdef LOCAL_MODULE
+#include <ipmivars.h>
+#else
+#include <dev/ipmi/ipmivars.h>
+#endif
+
+#define SMBUS_WRITE_SINGLE 0x02
+#define SMBUS_WRITE_START 0x06
+#define SMBUS_WRITE_CONT 0x07
+#define SMBUS_READ_START 0x03
+#define SMBUS_READ_CONT 0x09
+#define SMBUS_DATA_SIZE 32
+
+#ifdef SSIF_DEBUG
+static void
+dump_buffer(device_t dev, const char *msg, u_char *bytes, int len)
+{
+ int i;
+
+ device_printf(dev, "%s:", msg);
+ for (i = 0; i < len; i++)
+ printf(" %02x", bytes[i]);
+ printf("\n");
+}
+#endif
+
+static int
+ssif_polled_request(struct ipmi_softc *sc, struct ipmi_request *req)
+{
+ u_char ssif_buf[SMBUS_DATA_SIZE];
+ device_t dev = sc->ipmi_dev;
+ device_t smbus = sc->ipmi_ssif_smbus;
+ u_char *cp, block, count, offset;
+ size_t len;
+ int error;
+
+ /* Acquire the bus while we send the request. */
+ if (smbus_request_bus(smbus, dev, SMB_WAIT) != 0)
+ return (0);
+
+ /*
+ * First, send out the request. Begin by filling out the first
+ * packet which includes the NetFn/LUN and command.
+ */
+ ssif_buf[0] = req->ir_addr;
+ ssif_buf[1] = req->ir_command;
+ if (req->ir_requestlen > 0)
+ bcopy(req->ir_request, &ssif_buf[2],
+ min(req->ir_requestlen, SMBUS_DATA_SIZE - 2));
+
+ /* Small requests are sent with a single command. */
+ if (req->ir_requestlen <= 30) {
+#ifdef SSIF_DEBUG
+ dump_buffer(dev, "WRITE_SINGLE", ssif_buf,
+ req->ir_requestlen + 2);
+#endif
+ error = smbus_error(smbus_bwrite(smbus,
+ sc->ipmi_ssif_smbus_address, SMBUS_WRITE_SINGLE,
+ req->ir_requestlen + 2, ssif_buf));
+ if (error) {
+#ifdef SSIF_ERROR_DEBUG
+ device_printf(dev, "SSIF: WRITE_SINGLE error %d\n",
+ error);
+#endif
+ goto fail;
+ }
+ } else {
+ /* Longer requests are sent out in 32-byte messages. */
+#ifdef SSIF_DEBUG
+ dump_buffer(dev, "WRITE_START", ssif_buf, SMBUS_DATA_SIZE);
+#endif
+ error = smbus_error(smbus_bwrite(smbus,
+ sc->ipmi_ssif_smbus_address, SMBUS_WRITE_START,
+ SMBUS_DATA_SIZE, ssif_buf));
+ if (error) {
+#ifdef SSIF_ERROR_DEBUG
+ device_printf(dev, "SSIF: WRITE_START error %d\n",
+ error);
+#endif
+ goto fail;
+ }
+
+ len = req->ir_requestlen - (SMBUS_DATA_SIZE - 2);
+ cp = req->ir_request + (SMBUS_DATA_SIZE - 2);
+ while (len > 0) {
+#ifdef SSIF_DEBUG
+ dump_buffer(dev, "WRITE_CONT", cp,
+ min(len, SMBUS_DATA_SIZE));
+#endif
+ error = smbus_error(smbus_bwrite(smbus,
+ sc->ipmi_ssif_smbus_address, SMBUS_WRITE_CONT,
+ min(len, SMBUS_DATA_SIZE), cp));
+ if (error) {
+#ifdef SSIF_ERROR_DEBUG
+ device_printf(dev, "SSIF: WRITE_CONT error %d\n",
+ error);
+#endif
+ goto fail;
+ }
+ cp += SMBUS_DATA_SIZE;
+ len -= SMBUS_DATA_SIZE;
+ }
+
+ /*
+ * The final WRITE_CONT transaction has to have a non-zero
+ * length that is also not SMBUS_DATA_SIZE. If our last
+ * WRITE_CONT transaction in the loop sent SMBUS_DATA_SIZE
+ * bytes, then len will be 0, and we send an extra 0x00 byte
+ * to terminate the transaction.
+ */
+ if (len == 0) {
+ char c = 0;
+
+#ifdef SSIF_DEBUG
+ dump_buffer(dev, "WRITE_CONT", &c, 1);
+#endif
+ error = smbus_error(smbus_bwrite(smbus,
+ sc->ipmi_ssif_smbus_address, SMBUS_WRITE_CONT,
+ 1, &c));
+ if (error) {
+#ifdef SSIF_ERROR_DEBUG
+ device_printf(dev, "SSIF: WRITE_CONT error %d\n",
+ error);
+#endif
+ goto fail;
+ }
+ }
+ }
+
+ /* Release the bus. */
+ smbus_release_bus(smbus, dev);
+
+ /* Give the BMC 100ms to chew on the request. */
+ tsleep(&error, 0, "ssifwt", hz / 10);
+
+ /* Try to read the first packet. */
+read_start:
+ if (smbus_request_bus(smbus, dev, SMB_WAIT) != 0)
+ return (0);
+ count = SMBUS_DATA_SIZE;
+ error = smbus_error(smbus_bread(smbus,
+ sc->ipmi_ssif_smbus_address, SMBUS_READ_START, &count, ssif_buf));
+ if (error == ENXIO || error == EBUSY) {
+ smbus_release_bus(smbus, dev);
+#ifdef SSIF_DEBUG
+ device_printf(dev, "SSIF: READ_START retry\n");
+#endif
+ /* Give the BMC another 10ms. */
+ tsleep(&error, 0, "ssifwt", hz / 100);
+ goto read_start;
+ }
+ if (error) {
+#ifdef SSIF_ERROR_DEBUG
+ device_printf(dev, "SSIF: READ_START failed: %d\n", error);
+#endif
+ goto fail;
+ }
+#ifdef SSIF_DEBUG
+ device_printf("SSIF: READ_START: ok\n");
+#endif
+
+ /*
+ * If this is the first part of a multi-part read, then we need to
+ * skip the first two bytes.
+ */
+ if (count == SMBUS_DATA_SIZE && ssif_buf[0] == 0 && ssif_buf[1] == 1)
+ offset = 2;
+ else
+ offset = 0;
+
+ /* We had better get the reply header. */
+ if (count < 3) {
+ device_printf(dev, "SSIF: Short reply packet\n");
+ goto fail;
+ }
+
+ /* Verify the NetFn/LUN. */
+ if (ssif_buf[offset] != IPMI_REPLY_ADDR(req->ir_addr)) {
+ device_printf(dev, "SSIF: Reply address mismatch\n");
+ goto fail;
+ }
+
+ /* Verify the command. */
+ if (ssif_buf[offset + 1] != req->ir_command) {
+ device_printf(dev, "SMIC: Command mismatch\n");
+ goto fail;
+ }
+
+ /* Read the completion code. */
+ req->ir_compcode = ssif_buf[offset + 2];
+
+ /* If this is a single read, just copy the data and return. */
+ if (offset == 0) {
+#ifdef SSIF_DEBUG
+ dump_buffer(dev, "READ_SINGLE", ssif_buf, count);
+#endif
+ len = count - 3;
+ bcopy(&ssif_buf[3], req->ir_reply,
+ min(req->ir_replybuflen, len));
+ goto done;
+ }
+
+ /*
+ * This is the first part of a multi-read transaction, so copy
+ * out the payload and start looping.
+ */
+#ifdef SSIF_DEBUG
+ dump_buffer(dev, "READ_START", ssif_buf + 2, count - 2);
+#endif
+ bcopy(&ssif_buf[5], req->ir_reply, min(req->ir_replybuflen, count - 5));
+ len = count - 5;
+ block = 1;
+
+ for (;;) {
+ /* Read another packet via READ_CONT. */
+ count = SMBUS_DATA_SIZE;
+ error = smbus_error(smbus_bread(smbus,
+ sc->ipmi_ssif_smbus_address, SMBUS_READ_CONT, &count,
+ ssif_buf));
+ if (error) {
+#ifdef SSIF_ERROR_DEBUG
+ printf("SSIF: READ_CONT failed: %d\n", error);
+#endif
+ goto fail;
+ }
+#ifdef SSIF_DEBUG
+ device_printf(dev, "SSIF: READ_CONT... ok\n");
+#endif
+
+ /* Verify the block number. 0xff marks the last block. */
+ if (ssif_buf[0] != 0xff && ssif_buf[0] != block) {
+ device_printf(dev, "SSIF: Read wrong block %d %d\n",
+ ssif_buf[0], block);
+ goto fail;
+ }
+ if (ssif_buf[0] != 0xff && count < SMBUS_DATA_SIZE) {
+ device_printf(dev,
+ "SSIF: Read short middle block, length %d\n",
+ count);
+ goto fail;
+ }
+#ifdef SSIF_DEBUG
+ if (ssif_buf[0] == 0xff)
+ dump_buffer(dev, "READ_END", ssif_buf + 1, count - 1);
+ else
+ dump_buffer(dev, "READ_CONT", ssif_buf + 1, count - 1);
+#endif
+ if (len < req->ir_replybuflen)
+ bcopy(&ssif_buf[1], &req->ir_reply[len],
+ min(req->ir_replybuflen - len, count - 1));
+ len += count - 1;
+
+ /* If this was the last block we are done. */
+ if (ssif_buf[0] != 0xff)
+ break;
+ block++;
+ }
+
+done:
+ /* Save the total length and return success. */
+ req->ir_replylen = len;
+ smbus_release_bus(smbus, dev);
+ return (1);
+
+fail:
+ smbus_release_bus(smbus, dev);
+ return (0);
+}
+
+static void
+ssif_loop(void *arg)
+{
+ struct ipmi_softc *sc = arg;
+ struct ipmi_request *req;
+ int i, ok;
+
+ IPMI_LOCK(sc);
+ while ((req = ipmi_dequeue_request(sc)) != NULL) {
+ IPMI_UNLOCK(sc);
+ ok = 0;
+ for (i = 0; i < 5; i++) {
+ ok = ssif_polled_request(sc, req);
+ if (ok)
+ break;
+
+ /* Wait 60 ms between retries. */
+ tsleep(&ok, 0, "retry", 60 * hz / 1000);
+#ifdef SSIF_RETRY_DEBUG
+ device_printf(sc->ipmi_dev,
+ "SSIF: Retrying request (%d)\n", i + 1);
+#endif
+ }
+ if (ok)
+ req->ir_error = 0;
+ else
+ req->ir_error = EIO;
+ IPMI_LOCK(sc);
+ ipmi_complete_request(sc, req);
+ IPMI_UNLOCK(sc);
+
+ /* Enforce 10ms between requests. */
+ tsleep(&ok, 0, "delay", hz / 100);
+
+ IPMI_LOCK(sc);
+ }
+ IPMI_UNLOCK(sc);
+ kthread_exit(0);
+}
+
+static int
+ssif_startup(struct ipmi_softc *sc)
+{
+
+ return (kthread_create(ssif_loop, sc, &sc->ipmi_kthread, 0, 0,
+ "%s: ssif", device_get_nameunit(sc->ipmi_dev)));
+}
+
+int
+ipmi_ssif_attach(struct ipmi_softc *sc, device_t smbus, int smbus_address)
+{
+
+ /* Setup smbus address. */
+ sc->ipmi_ssif_smbus = smbus;
+ sc->ipmi_ssif_smbus_address = smbus_address;
+
+ /* Setup function pointers. */
+ sc->ipmi_startup = ssif_startup;
+ sc->ipmi_enqueue_request = ipmi_polled_enqueue_request;
+
+ return (0);
+}
OpenPOWER on IntegriCloud