diff options
Diffstat (limited to 'sys/dev/ipmi/ipmi_smic.c')
-rw-r--r-- | sys/dev/ipmi/ipmi_smic.c | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/sys/dev/ipmi/ipmi_smic.c b/sys/dev/ipmi/ipmi_smic.c new file mode 100644 index 0000000..bdffd1c --- /dev/null +++ b/sys/dev/ipmi/ipmi_smic.c @@ -0,0 +1,361 @@ +/*- + * 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/rman.h> +#include <sys/selinfo.h> +#include <machine/bus.h> + +#ifdef LOCAL_MODULE +#include <ipmi.h> +#include <ipmivars.h> +#else +#include <sys/ipmi.h> +#include <dev/ipmi/ipmivars.h> +#endif + +static void smic_wait_for_tx_okay(struct ipmi_softc *); +static void smic_wait_for_rx_okay(struct ipmi_softc *); +static void smic_wait_for_not_busy(struct ipmi_softc *); +static void smic_set_busy(struct ipmi_softc *); + +static void +smic_wait_for_tx_okay(struct ipmi_softc *sc) +{ + int flags; + + do { + flags = INB(sc, SMIC_FLAGS); + } while (!(flags & SMIC_STATUS_TX_RDY)); +} + +static void +smic_wait_for_rx_okay(struct ipmi_softc *sc) +{ + int flags; + + do { + flags = INB(sc, SMIC_FLAGS); + } while (!(flags & SMIC_STATUS_RX_RDY)); +} + +static void +smic_wait_for_not_busy(struct ipmi_softc *sc) +{ + int flags; + + do { + flags = INB(sc, SMIC_FLAGS); + } while (flags & SMIC_STATUS_BUSY); +} + +static void +smic_set_busy(struct ipmi_softc *sc) +{ + int flags; + + flags = INB(sc, SMIC_FLAGS); + flags |= SMIC_STATUS_BUSY; + flags &= ~SMIC_STATUS_RESERVED; + OUTB(sc, SMIC_FLAGS, flags); +} + +/* + * Start a transfer with a WR_START transaction that sends the NetFn/LUN + * address. + */ +static int +smic_start_write(struct ipmi_softc *sc, u_char data) +{ + u_char error, status; + + smic_wait_for_not_busy(sc); + + OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_START); + OUTB(sc, SMIC_DATA, data); + smic_set_busy(sc); + smic_wait_for_not_busy(sc); + status = INB(sc, SMIC_CTL_STS); + if (status != SMIC_SC_SMS_WR_START) { + error = INB(sc, SMIC_DATA); + device_printf(sc->ipmi_dev, "SMIC: Write did not start %02x\n", + error); + return (0); + } + return (1); +} + +/* + * Write a byte in the middle of the message (either the command or one of + * the data bytes) using a WR_NEXT transaction. + */ +static int +smic_write_next(struct ipmi_softc *sc, u_char data) +{ + u_char error, status; + + OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_NEXT); + smic_wait_for_tx_okay(sc); + OUTB(sc, SMIC_DATA, data); + smic_set_busy(sc); + smic_wait_for_not_busy(sc); + status = INB(sc, SMIC_CTL_STS); + if (status != SMIC_SC_SMS_WR_NEXT) { + error = INB(sc, SMIC_DATA); + device_printf(sc->ipmi_dev, "SMIC: Write did not next %02x\n", + error); + return (0); + } + return (1); +} + +/* + * Write the last byte of a transfer to end the write phase via a WR_END + * transaction. + */ +static int +smic_write_last(struct ipmi_softc *sc, u_char data) +{ + u_char error, status; + + OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_END); + smic_wait_for_tx_okay(sc); + OUTB(sc, SMIC_DATA, data); + smic_set_busy(sc); + smic_wait_for_not_busy(sc); + status = INB(sc, SMIC_CTL_STS); + if (status != SMIC_SC_SMS_WR_END) { + error = INB(sc, SMIC_DATA); + device_printf(sc->ipmi_dev, "SMIC: Write did not end %02x\n", + error); + return (0); + } + return (1); +} + +/* + * Start the read phase of a transfer with a RD_START transaction. + */ +static int +smic_start_read(struct ipmi_softc *sc, u_char *data) +{ + u_char error, status; + + smic_wait_for_not_busy(sc); + + OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_START); + smic_wait_for_rx_okay(sc); + smic_set_busy(sc); + smic_wait_for_not_busy(sc); + status = INB(sc, SMIC_CTL_STS); + if (status != SMIC_SC_SMS_RD_START) { + error = INB(sc, SMIC_DATA); + device_printf(sc->ipmi_dev, "SMIC: Read did not start %02x\n", + error); + return (0); + } + *data = INB(sc, SMIC_DATA); + return (1); +} + +/* + * Read a byte via a RD_NEXT transaction. If this was the last byte, return + * 2 rather than 1. + */ +static int +smic_read_byte(struct ipmi_softc *sc, u_char *data) +{ + u_char error, status; + + OUTB(sc, SMIC_CTL_STS, SMIC_SC_SMS_RD_NEXT); + smic_wait_for_rx_okay(sc); + smic_set_busy(sc); + smic_wait_for_not_busy(sc); + status = INB(sc, SMIC_CTL_STS); + if (status != SMIC_SC_SMS_RD_NEXT && + status != SMIC_SC_SMS_RD_END) { + error = INB(sc, SMIC_DATA); + device_printf(sc->ipmi_dev, "SMIC: Read did not next %02x\n", + error); + return (0); + } + *data = INB(sc, SMIC_DATA); + if (status == SMIC_SC_SMS_RD_NEXT) + return (1); + else + return (2); +} + +/* Complete a transfer via a RD_END transaction after reading the last byte. */ +static int +smic_read_end(struct ipmi_softc *sc) +{ + u_char error, status; + + OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_END); + smic_set_busy(sc); + smic_wait_for_not_busy(sc); + status = INB(sc, SMIC_CTL_STS); + if (status != SMIC_SC_SMS_RDY) { + error = INB(sc, SMIC_DATA); + device_printf(sc->ipmi_dev, "SMIC: Read did not end %02x\n", + error); + return (0); + } + return (1); +} + +static int +smic_polled_request(struct ipmi_softc *sc, struct ipmi_request *req) +{ + u_char *cp, data; + int i, state; + + /* First, start the message with the address. */ + if (!smic_start_write(sc, req->ir_addr)) + return (0); + + if (req->ir_requestlen == 0) { + /* Send the command as the last byte. */ + if (!smic_write_last(sc, req->ir_command)) + return (0); + } else { + /* Send the command. */ + if (!smic_write_next(sc, req->ir_command)) + return (0); + + /* Send the payload. */ + cp = req->ir_request; + for (i = 0; i < req->ir_requestlen - 1; i++) + if (!smic_write_next(sc, *cp++)) + return (0); + if (!smic_write_last(sc, *cp)) + return (0); + } + + /* Start the read phase by reading the NetFn/LUN. */ + if (smic_start_read(sc, &data) != 1) + return (0); + if (data != IPMI_REPLY_ADDR(req->ir_addr)) { + device_printf(sc->ipmi_dev, "SMIC: Reply address mismatch\n"); + return (0); + } + + /* Read the command. */ + if (smic_read_byte(sc, &data) != 1) + return (0); + if (data != req->ir_command) { + device_printf(sc->ipmi_dev, "SMIC: Command mismatch\n"); + return (0); + } + + /* Read the completion code. */ + state = smic_read_byte(sc, &req->ir_compcode); + if (state == 0) + return (0); + + /* Finally, read the reply from the BMC. */ + i = 0; + while (state == 1) { + state = smic_read_byte(sc, &data); + if (state == 0) + return (0); + if (state == 2) + break; + if (i < req->ir_replybuflen) + req->ir_reply[i] = data; + i++; + } + + /* Terminate the transfer. */ + if (!smic_read_end(sc)) + return (0); + req->ir_replylen = i; + if (req->ir_replybuflen < i && req->ir_replybuflen != 0) + device_printf(sc->ipmi_dev, + "SMIC: Read short: %zd buffer, %d actual\n", + req->ir_replybuflen, i); + return (1); +} + +static void +smic_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) { + ok = 0; + for (i = 0; i < 3 && !ok; i++) + ok = smic_polled_request(sc, req); + if (ok) + req->ir_error = 0; + else + req->ir_error = EIO; + ipmi_complete_request(sc, req); + } + IPMI_UNLOCK(sc); + kthread_exit(0); +} + +static int +smic_startup(struct ipmi_softc *sc) +{ + + return (kthread_create(smic_loop, sc, &sc->ipmi_kthread, 0, 0, + "%s: smic", device_get_nameunit(sc->ipmi_dev))); +} + +int +ipmi_smic_attach(struct ipmi_softc *sc) +{ + int flags; + + /* Setup function pointers. */ + sc->ipmi_startup = smic_startup; + sc->ipmi_enqueue_request = ipmi_polled_enqueue_request; + + /* See if we can talk to the controller. */ + flags = INB(sc, SMIC_FLAGS); + if (flags == 0xff) { + device_printf(sc->ipmi_dev, "couldn't find it\n"); + return (ENXIO); + } + + return (0); +} |