diff options
Diffstat (limited to 'sys/dev/ipmi')
-rw-r--r-- | sys/dev/ipmi/ipmi.c | 1096 | ||||
-rw-r--r-- | sys/dev/ipmi/ipmi_pci.c | 309 | ||||
-rw-r--r-- | sys/dev/ipmi/ipmi_smbios.c | 538 | ||||
-rw-r--r-- | sys/dev/ipmi/ipmivars.h | 138 |
4 files changed, 2081 insertions, 0 deletions
diff --git a/sys/dev/ipmi/ipmi.c b/sys/dev/ipmi/ipmi.c new file mode 100644 index 0000000..3b47f21 --- /dev/null +++ b/sys/dev/ipmi/ipmi.c @@ -0,0 +1,1096 @@ +/*- + * 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/kernel.h> +#include <sys/malloc.h> +#include <sys/poll.h> +#include <sys/selinfo.h> + +#include <sys/disk.h> +#include <sys/module.h> +#include <sys/bus.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <machine/clock.h> +#include <sys/rman.h> +#include <sys/watchdog.h> +#include <sys/sysctl.h> + +#ifdef LOCAL_MODULE +#include <ipmi.h> +#include <ipmivars.h> +#else +#include <sys/ipmi.h> +#include <dev/ipmi/ipmivars.h> +#endif + +struct ipmi_done_list { + u_char *data; + int channel; + int msgid; + int len; + TAILQ_ENTRY(ipmi_done_list) list; +}; + +#define MAX_TIMEOUT 3 * hz + +static int ipmi_wait_for_ibf(device_t, int); +static int ipmi_wait_for_obf(device_t, int); +static void ipmi_clear_obf(device_t, int); +static void ipmi_error(device_t); +static void ipmi_check_read(device_t); +static int ipmi_write(device_t, u_char *, int); +static void ipmi_wait_for_tx_okay(device_t); +static void ipmi_wait_for_rx_okay(device_t); +static void ipmi_wait_for_not_busy(device_t); +static void ipmi_set_busy(device_t); +static int ipmi_ready_to_read(device_t); +#ifdef IPMB +static int ipmi_handle_attn(device_t dev); +static int ipmi_ipmb_checksum(u_char, int); +static int ipmi_ipmb_send_message(device_t, u_char, u_char, u_char, + u_char, u_char, int) +#endif + +static d_ioctl_t ipmi_ioctl; +static d_poll_t ipmi_poll; +static d_open_t ipmi_open; +static d_close_t ipmi_close; + +int ipmi_attached = 0; + +#define IPMI_MINOR 0 + +static int on = 1; +SYSCTL_NODE(_hw, OID_AUTO, ipmi, CTLFLAG_RD, 0, "IPMI driver parameters"); +SYSCTL_INT(_hw_ipmi, OID_AUTO, on, CTLFLAG_RW, + &on, 0, ""); + +static struct cdevsw ipmi_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_NEEDGIANT, + .d_open = ipmi_open, + .d_close = ipmi_close, + .d_ioctl = ipmi_ioctl, + .d_poll = ipmi_poll, + .d_name = "ipmi", +}; + +MALLOC_DEFINE(M_IPMI, "ipmi", "ipmi"); + +static int +ipmi_open(struct cdev *dev, int flags, int fmt, struct thread *td) +{ + struct ipmi_softc *sc; + + if (!on) + return ENOENT; + + sc = dev->si_drv1; + if (sc->ipmi_refcnt) { + return EBUSY; + } + sc->ipmi_refcnt = 1; + + return 0; +} + +static int +ipmi_poll(struct cdev *dev, int poll_events, struct thread *td) +{ + struct ipmi_softc *sc; + int revents = 0; + + sc = dev->si_drv1; + + ipmi_check_read(sc->ipmi_dev); + + if (poll_events & (POLLIN | POLLRDNORM)) { + if (!TAILQ_EMPTY(&sc->ipmi_done)) + revents |= poll_events & (POLLIN | POLLRDNORM); + if (TAILQ_EMPTY(&sc->ipmi_done) && sc->ipmi_requests == 0) { + revents |= POLLERR; + } + } + + if (revents == 0) { + if (poll_events & (POLLIN | POLLRDNORM)) + selrecord(td, &sc->ipmi_select); + } + + return revents; +} + +static int +ipmi_close(struct cdev *dev, int flags, int fmt, struct thread *td) +{ + struct ipmi_softc *sc; + int error = 0; + + sc = dev->si_drv1; + + sc->ipmi_refcnt = 0; + + return error; +} + +#ifdef IPMB +static int +ipmi_ipmb_checksum(u_char *data, int len) +{ + u_char sum = 0; + + for (; len; len--) { + sum += *data++; + } + return -sum; +} + +static int +ipmi_ipmb_send_message(device_t dev, u_char channel, u_char netfn, + u_char command, u_char seq, u_char *data, int data_len) +{ + u_char *temp; + struct ipmi_softc *sc = device_get_softc(dev); + int error; + u_char slave_addr = 0x52; + + temp = malloc(data_len + 10, M_IPMI, M_WAITOK); + bzero(temp, data_len + 10); + temp[0] = IPMI_APP_REQUEST << 2; + temp[1] = IPMI_SEND_MSG; + temp[2] = channel; + temp[3] = slave_addr; + temp[4] = netfn << 2; + temp[5] = ipmi_ipmb_check_sum(&temp[3], 2); + temp[6] = sc->ipmi_address; + temp[7] = seq << 2 | sc->ipmi_lun; + temp[8] = command; + + bcopy(data, &temp[9], data_len); + temp[data_len + 9] = ipmi_ipmb_check(&temp[6], data_len + 3); + ipmi_write(sc->ipmi_dev, temp, data_len + 9); + free(temp, M_IPMI); + + while (!ipmi_ready_to_read(dev)) + DELAY(1000); + temp = malloc(IPMI_MAX_RX, M_IPMI, M_WAITOK); + bzero(temp, IPMI_MAX_RX); + error = ipmi_read(dev, temp, IPMI_MAX_RX); + free(temp, M_IPMI); + + return error; +} + +static int +ipmi_handle_attn(device_t dev) +{ + u_char temp[IPMI_MAX_RX]; + struct ipmi_softc *sc = device_get_softc(dev); + int error; + + device_printf(sc->ipmi_dev, "BMC has a message\n"); + temp[0] = IPMI_APP_REQUEST << 2; + temp[1] = IPMI_GET_MSG_FLAGS; + ipmi_write(sc->ipmi_dev, temp, 2); + while (!ipmi_ready_to_read(dev)) + DELAY(1000); + bzero(temp, IPMI_MAX_RX); + error = ipmi_read(dev, temp, IPMI_MAX_RX); + + if (temp[2] == 0) { + if (temp[3] & IPMI_MSG_BUFFER_FULL) { + device_printf(sc->ipmi_dev, "message buffer full"); + } + if (temp[3] & IPMI_WDT_PRE_TIMEOUT) { + device_printf(sc->ipmi_dev, + "watchdog about to go off"); + } + if (temp[3] & IPMI_MSG_AVAILABLE) { + temp[0] = IPMI_APP_REQUEST << 2; + temp[1] = IPMI_GET_MSG; + ipmi_write(sc->ipmi_dev, temp, 2); + while (!ipmi_ready_to_read(dev)) + DELAY(1000); + bzero(temp, IPMI_MAX_RX); + error = ipmi_read(dev, temp, IPMI_MAX_RX); + + device_printf(sc->ipmi_dev, "throw out message "); + dump_buf(temp, 16); + } + } else + return -1; + return error; +} +#endif + +static int +ipmi_ready_to_read(device_t dev) +{ + struct ipmi_softc *sc = device_get_softc(dev); + int status, flags; + + if (sc->ipmi_bios_info.smic_mode) { + flags = INB(sc, sc->ipmi_smic_flags); +#ifdef IPMB + if (flags & SMIC_STATUS_SMS_ATN) { + ipmi_handle_attn(dev); + return 0; + } +#endif + if (flags & SMIC_STATUS_RX_RDY) + return 1; + } else if (sc->ipmi_bios_info.kcs_mode) { + status = INB(sc, sc->ipmi_kcs_status_reg); +#ifdef IPMB + if (status & KCS_STATUS_SMS_ATN) { + ipmi_handle_attn(dev); + return 0; + } +#endif + if (status & KCS_STATUS_OBF) + return 1; + } else { + device_printf(dev,"Unsupported mode\n"); + } + + return 0; +} + +void +ipmi_intr(void *arg) { + device_t dev = arg; + + ipmi_check_read(dev); +} + +static void +ipmi_check_read(device_t dev){ + struct ipmi_softc *sc = device_get_softc(dev); + struct ipmi_done_list *item; + int status; + u_char *temp; + + if (!sc->ipmi_requests) + return; + + untimeout((timeout_t *)ipmi_check_read, dev, sc->ipmi_timeout_handle); + + if(ipmi_ready_to_read(dev)) { + sc->ipmi_requests--; + temp = malloc(IPMI_MAX_RX, M_IPMI, M_WAITOK); + bzero(temp, IPMI_MAX_RX); + status = ipmi_read(dev, temp, IPMI_MAX_RX); + item = malloc(sizeof(struct ipmi_done_list), M_IPMI, M_WAITOK); + bzero(item, sizeof(struct ipmi_done_list)); + item->data = temp; + item->len = status; + if (ticks - sc->ipmi_timestamp > MAX_TIMEOUT) { + device_printf(dev, "read timeout when ready\n"); + TAILQ_INSERT_TAIL(&sc->ipmi_done, item, list); + selwakeup(&sc->ipmi_select); + } else if (status) { + TAILQ_INSERT_TAIL(&sc->ipmi_done, item, list); + selwakeup(&sc->ipmi_select); + } + } else { + if (ticks - sc->ipmi_timestamp > MAX_TIMEOUT) { + sc->ipmi_requests--; + device_printf(dev, "read timeout when not ready\n"); + temp = malloc(IPMI_MAX_RX, M_IPMI, M_WAITOK); + bzero(temp, IPMI_MAX_RX); + sc->ipmi_busy = 0; + wakeup(&sc->ipmi_busy); + status = -1; + item = malloc(sizeof(struct ipmi_done_list), + M_IPMI, M_WAITOK); + bzero(item, sizeof(struct ipmi_done_list)); + item->data = temp; + item->len = status; + TAILQ_INSERT_TAIL(&sc->ipmi_done, item, list); + selwakeup(&sc->ipmi_select); + } + } + if (sc->ipmi_requests) + sc->ipmi_timeout_handle + = timeout((timeout_t *)ipmi_check_read, dev, hz/30); +} + +static int +ipmi_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data, + int flags, struct thread *td) +{ + struct ipmi_softc *sc; + struct ipmi_req *req = (struct ipmi_req *)data; + struct ipmi_recv *recv = (struct ipmi_recv *)data; + struct ipmi_addr addr; + struct ipmi_done_list *item; + u_char *temp; + int error, len; + + sc = dev->si_drv1; + + switch (cmd) { + case IPMICTL_SEND_COMMAND: + /* clear out old stuff in queue of stuff done */ + while((item = TAILQ_FIRST(&sc->ipmi_done))) { + TAILQ_REMOVE(&sc->ipmi_done, item, list); + free(item->data, M_IPMI); + free(item, M_IPMI); + } + + error = copyin(req->addr, &addr, sizeof(addr)); + temp = malloc(req->msg.data_len + 2, M_IPMI, M_WAITOK); + if (temp == NULL) { + return ENOMEM; + } + temp[0] = req->msg.netfn << 2; + temp[1] = req->msg.cmd; + error = copyin(req->msg.data, &temp[2], + req->msg.data_len); + if (error != 0) { + free(temp, M_IPMI); + return error; + } + error = ipmi_write(sc->ipmi_dev, + temp, req->msg.data_len + 2); + free(temp, M_IPMI); + + if (error != 1) + return EIO; + sc->ipmi_requests++; + sc->ipmi_timestamp = ticks; + ipmi_check_read(sc->ipmi_dev); + + return 0; + case IPMICTL_RECEIVE_MSG_TRUNC: + case IPMICTL_RECEIVE_MSG: + item = TAILQ_FIRST(&sc->ipmi_done); + if (!item) { + return EAGAIN; + } + + error = copyin(recv->addr, &addr, sizeof(addr)); + if (error != 0) + return error; + TAILQ_REMOVE(&sc->ipmi_done, item, list); + addr.channel = IPMI_BMC_CHANNEL; + recv->recv_type = IPMI_RESPONSE_RECV_TYPE; + recv->msgid = item->msgid; + recv->msg.netfn = item->data[0] >> 2; + recv->msg.cmd = item->data[1]; + error = len = item->len; + len -= 2; + if (len < 0) + len = 1; + if (recv->msg.data_len < len && cmd == IPMICTL_RECEIVE_MSG) { + TAILQ_INSERT_HEAD(&sc->ipmi_done, item, list); + return EMSGSIZE; + } + len = min(recv->msg.data_len, len); + recv->msg.data_len = len; + error = copyout(&addr, recv->addr,sizeof(addr)); + if (error == 0) + error = copyout(&item->data[2], recv->msg.data, len); + free(item->data, M_IPMI); + free(item, M_IPMI); + + if (error != 0) + return error; + return 0; + case IPMICTL_SET_MY_ADDRESS_CMD: + sc->ipmi_address = *(int*)data; + return 0; + case IPMICTL_GET_MY_ADDRESS_CMD: + *(int*)data = sc->ipmi_address; + return 0; + case IPMICTL_SET_MY_LUN_CMD: + sc->ipmi_lun = *(int*)data & 0x3; + return 0; + case IPMICTL_GET_MY_LUN_CMD: + *(int*)data = sc->ipmi_lun; + return 0; + case IPMICTL_SET_GETS_EVENTS_CMD: + /* + device_printf(sc->ipmi_dev, + "IPMICTL_SET_GETS_EVENTS_CMD NA\n"); + */ + return 0; + case IPMICTL_REGISTER_FOR_CMD: + case IPMICTL_UNREGISTER_FOR_CMD: + return EOPNOTSUPP; + } + + device_printf(sc->ipmi_dev, "Unknown IOCTL %lX\n", cmd); + + return ENOIOCTL; +} + +static int +ipmi_wait_for_ibf(device_t dev, int state) { + struct ipmi_softc *sc = device_get_softc(dev); + int status, start = ticks; + int first = 1; + + if (state == 0) { + /* WAIT FOR IBF = 0 */ + do { + if (first) + first =0; + else + DELAY(100); + status = INB(sc, sc->ipmi_kcs_status_reg); + } while (ticks - start < MAX_TIMEOUT + && status & KCS_STATUS_IBF); + } else { + /* WAIT FOR IBF = 1 */ + do { + if (first) + first =0; + else + DELAY(100); + status = INB(sc, sc->ipmi_kcs_status_reg); + } while (ticks - start < MAX_TIMEOUT + && !(status & KCS_STATUS_IBF)); + } + return status; +} + +static int +ipmi_wait_for_obf(device_t dev, int state) { + struct ipmi_softc *sc = device_get_softc(dev); + int status, start = ticks; + int first = 1; + + if (state == 0) { + /* WAIT FOR OBF = 0 */ + do { + if (first) + first = 0; + else + DELAY(100); + status = INB(sc, sc->ipmi_kcs_status_reg); + } while (ticks - start < MAX_TIMEOUT + && status & KCS_STATUS_OBF); + } else { + /* WAIT FOR OBF = 1 */ + do { + if (first) + first =0; + else + DELAY(100); + status = INB(sc, sc->ipmi_kcs_status_reg); + } while (ticks - start < MAX_TIMEOUT + && !(status & KCS_STATUS_OBF)); + } + return status; +} + +static void +ipmi_clear_obf(device_t dev, int status) { + struct ipmi_softc *sc = device_get_softc(dev); + int data; + + /* Clear OBF */ + if (status & KCS_STATUS_OBF) { + data = INB(sc, sc->ipmi_kcs_data_out_reg); + } +} + +static void +ipmi_error(device_t dev) { + struct ipmi_softc *sc = device_get_softc(dev); + int status, data = 0; + int retry = 0; + + for(;;){ + status = ipmi_wait_for_ibf(dev, 0); + + /* ABORT */ + OUTB(sc, sc->ipmi_kcs_command_reg, + KCS_CONTROL_GET_STATUS_ABORT); + + /* Wait for IBF = 0 */ + status = ipmi_wait_for_ibf(dev, 0); + + /* Clear OBF */ + ipmi_clear_obf(dev, status); + + if (status & KCS_STATUS_OBF) { + data = INB(sc, sc->ipmi_kcs_data_out_reg); + device_printf(dev, "Data %x\n", data); + } + + /* 0x00 to DATA_IN */ + OUTB(sc, sc->ipmi_kcs_data_in_reg, 0x00); + + /* Wait for IBF = 0 */ + status = ipmi_wait_for_ibf(dev, 0); + + if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_READ) { + + /* Wait for OBF = 1 */ + status = ipmi_wait_for_obf(dev, 1); + + /* Read error status */ + data = INB(sc, sc->ipmi_kcs_data_out_reg); + + /* Write READ into Data_in */ + OUTB(sc, sc->ipmi_kcs_data_in_reg, KCS_DATA_IN_READ); + + /* Wait for IBF = 0 */ + status = ipmi_wait_for_ibf(dev, 0); + } + + /* IDLE STATE */ + if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_IDLE) { + /* Wait for OBF = 1 */ + status = ipmi_wait_for_obf(dev, 1); + + /* Clear OBF */ + ipmi_clear_obf(dev, status); + break; + } + + retry++; + if (retry > 2) { + device_printf(dev, "Retry exhausted %x\n", retry); + break; + } + } +} + +static void +ipmi_wait_for_tx_okay(device_t dev) { + struct ipmi_softc *sc = device_get_softc(dev); + int flags; + + do { + flags = INB(sc, sc->ipmi_smic_flags); + } while(!flags & SMIC_STATUS_TX_RDY); +} + +static void +ipmi_wait_for_rx_okay(device_t dev) { + struct ipmi_softc *sc = device_get_softc(dev); + int flags; + + do { + flags = INB(sc, sc->ipmi_smic_flags); + } while(!flags & SMIC_STATUS_RX_RDY); +} + +static void +ipmi_wait_for_not_busy(device_t dev) { + struct ipmi_softc *sc = device_get_softc(dev); + int flags; + + do { + flags = INB(sc, sc->ipmi_smic_flags); + } while(flags & SMIC_STATUS_BUSY); +} + +static void +ipmi_set_busy(device_t dev) { + struct ipmi_softc *sc = device_get_softc(dev); + int flags; + + flags = INB(sc, sc->ipmi_smic_flags); + flags |= SMIC_STATUS_BUSY; + OUTB(sc, sc->ipmi_smic_flags, flags); +} + +int +ipmi_read(device_t dev, u_char *bytes, int len){ + struct ipmi_softc *sc = device_get_softc(dev); + int status, flags, data, i = -1, error; + + bzero(bytes, len); + if (sc->ipmi_bios_info.smic_mode) { + ipmi_wait_for_not_busy(dev); + do { + flags = INB(sc, sc->ipmi_smic_flags); + } while(!flags & SMIC_STATUS_RX_RDY); + + OUTB(sc, sc->ipmi_smic_ctl_sts, SMIC_CC_SMS_RD_START); + ipmi_wait_for_rx_okay(dev); + ipmi_set_busy(dev); + ipmi_wait_for_not_busy(dev); + status = INB(sc, sc->ipmi_smic_ctl_sts); + if (status != SMIC_SC_SMS_RD_START) { + error = INB(sc, sc->ipmi_smic_data); + device_printf(dev, "Read did not start %x %x\n", + status, error); + sc->ipmi_busy = 0; + return -1; + } + for (i = -1; ; len--) { + i++; + data = INB(sc, sc->ipmi_smic_data); + if (len > 0) + *bytes++ = data; + else { + device_printf(dev, "Read short %x\n", data); + break; + } + do { + flags = INB(sc, sc->ipmi_smic_flags); + } while(!flags & SMIC_STATUS_RX_RDY); + + OUTB(sc, sc->ipmi_smic_ctl_sts, SMIC_CC_SMS_RD_NEXT); + ipmi_wait_for_rx_okay(dev); + ipmi_set_busy(dev); + ipmi_wait_for_not_busy(dev); + status = INB(sc, sc->ipmi_smic_ctl_sts); + if (status == SMIC_SC_SMS_RD_NEXT) { + continue; + } else if (status == SMIC_SC_SMS_RD_END) { + break; + } else { + device_printf(dev, "Read did not next %x\n", + status); + } + } + i++; + data = INB(sc, sc->ipmi_smic_data); + if (len > 0) + *bytes++ = data; + else + device_printf(dev, "Read short %x\n", data); + + OUTB(sc, sc->ipmi_smic_ctl_sts, SMIC_CC_SMS_RD_END); + i++; + + } else if (sc->ipmi_bios_info.kcs_mode) { + for (i = -1; ; len--) { + /* Wait for IBF = 0 */ + status = ipmi_wait_for_ibf(dev, 0); + + /* Read State */ + if (KCS_STATUS_STATE(status) + == KCS_STATUS_STATE_READ) { + i++; + + /* Wait for OBF = 1 */ + status = ipmi_wait_for_obf(dev, 1); + + /* Read Data_out */ + data = INB(sc, sc->ipmi_kcs_data_out_reg); + if (len > 0) + *bytes++ = data; + else { + device_printf(dev, "Read short %x byte %d\n", data, i); + break; + } + + /* Write READ into Data_in */ + OUTB(sc, sc->ipmi_kcs_data_in_reg, + KCS_DATA_IN_READ); + + /* Idle State */ + } else if (KCS_STATUS_STATE(status) + == KCS_STATUS_STATE_IDLE) { + i++; + + /* Wait for OBF = 1*/ + status = ipmi_wait_for_obf(dev, 1); + + /* Read Dummy */ + data = INB(sc, sc->ipmi_kcs_data_out_reg); + break; + + /* error state */ + } else { + device_printf(dev, + "read status error %x byte %d\n", + status, i); + sc->ipmi_busy = 0; + ipmi_error(dev); + return -1; + } + } + } else { + device_printf(dev, "Unsupported mode\n"); + } + sc->ipmi_busy = 0; + wakeup(&sc->ipmi_busy); + + return i; +} + + +static int +ipmi_write(device_t dev, u_char *bytes, int len){ + struct ipmi_softc *sc = device_get_softc(dev); + int status, flags, retry; + + while(sc->ipmi_busy){ + status = tsleep(&sc->ipmi_busy, PCATCH, "ipmi", 0); + if (status) + return status; + } + sc->ipmi_busy = 1; + if (sc->ipmi_bios_info.smic_mode) { + ipmi_wait_for_not_busy(dev); + + OUTB(sc, sc->ipmi_smic_ctl_sts, SMIC_CC_SMS_WR_START); + ipmi_wait_for_tx_okay(dev); + OUTB(sc, sc->ipmi_smic_data, *bytes++); + len--; + ipmi_set_busy(dev); + ipmi_wait_for_not_busy(dev); + status = INB(sc, sc->ipmi_smic_ctl_sts); + if (status != SMIC_SC_SMS_WR_START) { + device_printf(dev, "Write did not start %x\n",status); + sc->ipmi_busy = 0; + return -1; + } + for(len--; len; len--) { + OUTB(sc, sc->ipmi_smic_ctl_sts, SMIC_CC_SMS_WR_NEXT); + ipmi_wait_for_tx_okay(dev); + OUTB(sc, sc->ipmi_smic_data, *bytes++); + ipmi_set_busy(dev); + ipmi_wait_for_not_busy(dev); + status = INB(sc, sc->ipmi_smic_ctl_sts); + if (status != SMIC_SC_SMS_WR_NEXT) { + device_printf(dev, "Write did not next %x\n", + status); + sc->ipmi_busy = 0; + return -1; + } + } + do { + flags = INB(sc, sc->ipmi_smic_flags); + } while(!flags & SMIC_STATUS_TX_RDY); + OUTB(sc, sc->ipmi_smic_ctl_sts, SMIC_CC_SMS_WR_END); + ipmi_wait_for_tx_okay(dev); + OUTB(sc, sc->ipmi_smic_data, *bytes); + ipmi_set_busy(dev); + ipmi_wait_for_not_busy(dev); + status = INB(sc, sc->ipmi_smic_ctl_sts); + if (status != SMIC_SC_SMS_WR_END) { + device_printf(dev, "Write did not end %x\n",status); + return -1; + } + } else if (sc->ipmi_bios_info.kcs_mode) { + for (retry = 0; retry < 10; retry++) { + /* Wait for IBF = 0 */ + status = ipmi_wait_for_ibf(dev, 0); + + /* Clear OBF */ + ipmi_clear_obf(dev, status); + + /* Write start to command */ + OUTB(sc, sc->ipmi_kcs_command_reg, + KCS_CONTROL_WRITE_START); + + /* Wait for IBF = 0 */ + status = ipmi_wait_for_ibf(dev, 0); + if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_WRITE) + break; + DELAY(1000000); + } + + for(len--; len; len--) { + if (KCS_STATUS_STATE(status) + != KCS_STATUS_STATE_WRITE) { + /* error state */ + device_printf(dev, "status error %x\n",status); + ipmi_error(dev); + sc->ipmi_busy = 0; + return -1; + break; + } else { + /* Clear OBF */ + ipmi_clear_obf(dev, status); + + /* Data to Data */ + OUTB(sc, sc->ipmi_kcs_data_out_reg, *bytes++); + + /* Wait for IBF = 0 */ + status = ipmi_wait_for_ibf(dev, 0); + + if (KCS_STATUS_STATE(status) + != KCS_STATUS_STATE_WRITE) { + device_printf(dev, "status error %x\n" + ,status); + ipmi_error(dev); + return -1; + } else { + /* Clear OBF */ + ipmi_clear_obf(dev, status); + } + } + } + /* Write end to command */ + OUTB(sc, sc->ipmi_kcs_command_reg, KCS_CONTROL_WRITE_END); + + /* Wait for IBF = 0 */ + status = ipmi_wait_for_ibf(dev, 0); + + if (KCS_STATUS_STATE(status) != KCS_STATUS_STATE_WRITE) { + /* error state */ + device_printf(dev, "status error %x\n",status); + ipmi_error(dev); + sc->ipmi_busy = 0; + return -1; + } else { + /* Clear OBF */ + ipmi_clear_obf(dev, status); + OUTB(sc, sc->ipmi_kcs_data_out_reg, *bytes++); + } + } else { + device_printf(dev, "Unsupported mode\n"); + } + sc->ipmi_busy = 2; + return 1; +} + +/* + * Watchdog event handler. + */ + +static void +ipmi_set_watchdog(device_t dev, int sec) { + u_char *temp; + int s; + + temp = malloc(IPMI_MAX_RX, M_IPMI, M_WAITOK); + + temp[0] = IPMI_APP_REQUEST << 2; + if (sec) { + temp[1] = IPMI_SET_WDOG; + temp[2] = IPMI_SET_WD_TIMER_DONT_STOP + | IPMI_SET_WD_TIMER_SMS_OS; + temp[3] = IPMI_SET_WD_ACTION_RESET; + temp[4] = 0; + temp[5] = 0; /* Timer use */ + temp[6] = (sec * 10) & 0xff; + temp[7] = (sec * 10) / 2550; + } else { + temp[1] = IPMI_SET_WDOG; + temp[2] = IPMI_SET_WD_TIMER_SMS_OS; + temp[3] = 0; + temp[4] = 0; + temp[5] = 0; /* Timer use */ + temp[6] = 0; + temp[7] = 0; + } + + s = splhigh(); + ipmi_write(dev, temp, 8); + + while (!ipmi_ready_to_read(dev)) + DELAY(1000); + bzero(temp, IPMI_MAX_RX); + ipmi_read(dev, temp, IPMI_MAX_RX); + + if (sec) { + temp[0] = IPMI_APP_REQUEST << 2; + temp[1] = IPMI_RESET_WDOG; + + ipmi_write(dev, temp, 2); + + while (!ipmi_ready_to_read(dev)) + DELAY(1000); + bzero(temp, IPMI_MAX_RX); + ipmi_read(dev, temp, IPMI_MAX_RX); + } + splx(s); + + free(temp, M_IPMI); + /* + dump_watchdog(dev); + */ +} + +static void +ipmi_wd_event(void *arg, unsigned int cmd, int *error) +{ + struct ipmi_softc *sc = arg; + unsigned int timeout; + + /* disable / enable */ + if (!(cmd & WD_ACTIVE)) { + ipmi_set_watchdog(sc->ipmi_dev, 0); + *error = 0; + return; + } + + cmd &= WD_INTERVAL; + /* convert from power-of-to-ns to WDT ticks */ + if (cmd >= 64) { + *error = EINVAL; + return; + } + timeout = ((uint64_t)1 << cmd) / 1800000000; + + /* reload */ + ipmi_set_watchdog(sc->ipmi_dev, timeout); + + *error = 0; +} + +int +ipmi_attach(device_t dev) +{ + struct ipmi_softc *sc = device_get_softc(dev); + u_char temp[1024]; + int i; + int status; + int unit; + + TAILQ_INIT(&sc->ipmi_done); + sc->ipmi_address = IPMI_BMC_SLAVE_ADDR; + sc->ipmi_lun = IPMI_BMC_SMS_LUN; + temp[0] = IPMI_APP_REQUEST << 2; + temp[1] = IPMI_GET_DEVICE_ID; + ipmi_write(dev, temp, 2); + + while (!ipmi_ready_to_read(dev)) + DELAY(1000); + bzero(temp, sizeof(temp)); + ipmi_read(dev, temp, sizeof(temp)); + device_printf(dev, "IPMI device rev. %d, firmware rev. %d.%d, " + "version %d.%d\n", + temp[4] & 0x0f, + temp[5] & 0x0f, temp[7], + temp[7] & 0x0f, temp[7] >> 4); + + temp[0] = IPMI_APP_REQUEST << 2; + temp[1] = IPMI_CLEAR_FLAGS; + temp[2] = 8; + ipmi_write(dev, temp, 3); + + while (!ipmi_ready_to_read(dev)) + DELAY(1000); + bzero(temp, sizeof(temp)); + ipmi_read(dev, temp, sizeof(temp)); + if (temp[2] == 0xc0) { + device_printf(dev, "Clear flags is busy\n"); + } + if (temp[2] == 0xc1) { + device_printf(dev, "Clear flags illegal\n"); + } + + for(i = 0; i < 8; i++){ + temp[0] = IPMI_APP_REQUEST << 2; + temp[1] = IPMI_GET_CHANNEL_INFO; + temp[2] = i; + ipmi_write(dev, temp, 3); + while (!ipmi_ready_to_read(dev)) + DELAY(1000); + bzero(temp, sizeof(temp)); + ipmi_read(dev, temp, sizeof(temp)); + if (temp[2]) { + break; + } + } + device_printf(dev, "Number of channels %d\n", i); + + /* probe for watchdog */ + bzero(temp, sizeof(temp)); + temp[0] = IPMI_APP_REQUEST << 2; + temp[1] = IPMI_GET_WDOG; + status = ipmi_write(dev, temp, 2); + while (!ipmi_ready_to_read(dev)) + DELAY(1000); + bzero(temp, sizeof(temp)); + ipmi_read(dev, temp, sizeof(temp)); + if (temp[0] == 0x1c && temp[2] == 0x00) { + device_printf(dev, "Attached watchdog\n"); + /* register the watchdog event handler */ + sc->ipmi_ev_tag = EVENTHANDLER_REGISTER(watchdog_list, + ipmi_wd_event, sc, 0); + } + unit = device_get_unit(sc->ipmi_dev); + /* force device to be ipmi0 since that is what ipmitool expects */ + sc->ipmi_dev_t = make_dev(&ipmi_cdevsw, unit, UID_ROOT, GID_OPERATOR, + 0660, "ipmi%d", 0); + sc->ipmi_dev_t->si_drv1 = sc; + + ipmi_attached = 1; + + return 0; +} + +int +ipmi_detach(device_t dev) +{ + struct ipmi_softc *sc; + + sc = device_get_softc(dev); + if (sc->ipmi_requests) + untimeout((timeout_t *)ipmi_check_read, dev, + sc->ipmi_timeout_handle); + destroy_dev(sc->ipmi_dev_t); + return 0; +} + +#ifdef DEBUG +static void +dump_buf(u_char *data, int len){ + char buf[20]; + char line[1024]; + char temp[30]; + int count = 0; + int i=0; + + printf("Address %p len %d\n", data, len); + if (len > 256) + len = 256; + line[0] = '\000'; + for (; len > 0; len--, data++) { + sprintf(temp, "%02x ", *data); + strcat(line, temp); + if (*data >= ' ' && *data <= '~') + buf[count] = *data; + else if (*data >= 'A' && *data <= 'Z') + buf[count] = *data; + else + buf[count] = '.'; + if (++count == 16) { + buf[count] = '\000'; + count = 0; + printf(" %3x %s %s\n", i, line, buf); + i+=16; + line[0] = '\000'; + } + } + buf[count] = '\000'; + + for (; count != 16; count++) { + strcat(line, " "); + } + printf(" %3x %s %s\n", i, line, buf); +} +#endif diff --git a/sys/dev/ipmi/ipmi_pci.c b/sys/dev/ipmi/ipmi_pci.c new file mode 100644 index 0000000..b91891d --- /dev/null +++ b/sys/dev/ipmi/ipmi_pci.c @@ -0,0 +1,309 @@ +/*- + * 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/malloc.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/selinfo.h> + +#include <sys/bus.h> +#include <sys/conf.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <dev/pci/pcireg.h> +#include <dev/pci/pcivar.h> + +#ifdef LOCAL_MODULE +#include <ipmivars.h> +#else +#include <dev/ipmi/ipmivars.h> +#endif + +static int ipmi_pci_probe(device_t dev); +static int ipmi_pci_attach(device_t dev); +static int ipmi_pci_detach(device_t dev); + +static device_method_t ipmi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ipmi_pci_probe), + DEVMETHOD(device_attach, ipmi_pci_attach), + DEVMETHOD(device_detach, ipmi_pci_detach), + { 0, 0 } +}; + +struct ipmi_ident +{ + u_int16_t vendor; + u_int16_t device; + char *desc; +} ipmi_identifiers[] = { + {0x1028, 0x000d, "Dell PE2650 SMIC interface"}, + {0, 0, 0} +}; + +static int +ipmi_pci_probe(device_t dev) { + struct ipmi_ident *m; + + if (ipmi_attached) + return ENXIO; + + for (m = ipmi_identifiers; m->vendor != 0; m++) { + if ((m->vendor == pci_get_vendor(dev)) && + (m->device == pci_get_device(dev))) { + device_set_desc(dev, m->desc); + return (BUS_PROBE_DEFAULT); + } + } + + return ENXIO; +} + +static int +ipmi_pci_attach(device_t dev) { + struct ipmi_softc *sc = device_get_softc(dev); + device_t parent, smbios_attach_dev = NULL; + devclass_t dc; + int status, flags; + int error; + + + /* + * We need to attach to something that can address the BIOS/ + * SMBIOS memory range. This is usually the isa bus however + * during a static kernel boot the isa bus is not available + * so we run up the tree to the nexus bus. A module load + * will use the isa bus attachment. If neither work bail + * since the SMBIOS defines stuff we need to know to attach to + * this device. + */ + dc = devclass_find("isa"); + if (dc != NULL) { + smbios_attach_dev = devclass_get_device(dc, 0); + } + + if (smbios_attach_dev == NULL) { + smbios_attach_dev = dev; + for (;;) { + parent = device_get_parent(smbios_attach_dev); + if (parent == NULL) + break; + if (strcmp(device_get_name(smbios_attach_dev), + "nexus") == 0) + break; + smbios_attach_dev = parent; + } + } + + if (smbios_attach_dev == NULL) { + device_printf(dev, "Couldn't find isa/nexus device\n"); + goto bad; + } + sc->ipmi_smbios_dev = ipmi_smbios_identify(NULL, smbios_attach_dev); + if (sc->ipmi_smbios_dev == NULL) { + device_printf(dev, "Couldn't find isa device\n"); + goto bad; + } + error = ipmi_smbios_probe(sc->ipmi_smbios_dev); + if (error != 0) { + goto bad; + } + sc->ipmi_dev = dev; + error = ipmi_smbios_query(dev); + device_delete_child(dev, sc->ipmi_smbios_dev); + if (error != 0) + goto bad; + + /* Now we know about the IPMI attachment info. */ + if (sc->ipmi_bios_info.kcs_mode) { + if (sc->ipmi_bios_info.io_mode) + device_printf(dev, "KCS mode found at io 0x%llx " + "alignment 0x%x on %s\n", + (long long)sc->ipmi_bios_info.address, + sc->ipmi_bios_info.offset, + device_get_name(device_get_parent(sc->ipmi_dev))); + else + device_printf(dev, "KCS mode found at mem 0x%llx " + "alignment 0x%x on %s\n", + (long long)sc->ipmi_bios_info.address, + sc->ipmi_bios_info.offset, + device_get_name(device_get_parent(sc->ipmi_dev))); + + sc->ipmi_kcs_status_reg = sc->ipmi_bios_info.offset; + sc->ipmi_kcs_command_reg = sc->ipmi_bios_info.offset; + sc->ipmi_kcs_data_out_reg = 0; + sc->ipmi_kcs_data_in_reg = 0; + + if (sc->ipmi_bios_info.io_mode) { + sc->ipmi_io_rid = PCIR_BAR(0); + sc->ipmi_io_res = bus_alloc_resource(dev, + SYS_RES_IOPORT, &sc->ipmi_io_rid, + sc->ipmi_bios_info.address, + sc->ipmi_bios_info.address + + (sc->ipmi_bios_info.offset * 2), + sc->ipmi_bios_info.offset * 2, + RF_ACTIVE); + } else { + sc->ipmi_mem_rid = PCIR_BAR(0); + sc->ipmi_mem_res = bus_alloc_resource(dev, + SYS_RES_MEMORY, &sc->ipmi_mem_rid, + sc->ipmi_bios_info.address, + sc->ipmi_bios_info.address + + (sc->ipmi_bios_info.offset * 2), + sc->ipmi_bios_info.offset * 2, + RF_ACTIVE); + } + + if (!sc->ipmi_io_res){ + device_printf(dev, "couldn't configure pci io res\n"); + goto bad; + } + + status = INB(sc, sc->ipmi_kcs_status_reg); + if (status == 0xff) { + device_printf(dev, "couldn't find it\n"); + goto bad; + } + if(status & KCS_STATUS_OBF){ + ipmi_read(dev, NULL, 0); + } + } else if (sc->ipmi_bios_info.smic_mode) { + if (sc->ipmi_bios_info.io_mode) + device_printf(dev, "SMIC mode found at io 0x%llx " + "alignment 0x%x on %s\n", + (long long)sc->ipmi_bios_info.address, + sc->ipmi_bios_info.offset, + device_get_name(device_get_parent(sc->ipmi_dev))); + else + device_printf(dev, "SMIC mode found at mem 0x%llx " + "alignment 0x%x on %s\n", + (long long)sc->ipmi_bios_info.address, + sc->ipmi_bios_info.offset, + device_get_name(device_get_parent(sc->ipmi_dev))); + + sc->ipmi_smic_data = 0; + sc->ipmi_smic_ctl_sts = sc->ipmi_bios_info.offset; + sc->ipmi_smic_flags = sc->ipmi_bios_info.offset * 2; + + if (sc->ipmi_bios_info.io_mode) { + sc->ipmi_io_rid = PCIR_BAR(0); + sc->ipmi_io_res = bus_alloc_resource(dev, + SYS_RES_IOPORT, &sc->ipmi_io_rid, + sc->ipmi_bios_info.address, + sc->ipmi_bios_info.address + + (sc->ipmi_bios_info.offset * 3), + sc->ipmi_bios_info.offset * 3, + RF_ACTIVE); + } else { + sc->ipmi_mem_rid = PCIR_BAR(0); + sc->ipmi_mem_res = bus_alloc_resource(dev, + SYS_RES_MEMORY, &sc->ipmi_mem_rid, + sc->ipmi_bios_info.address, + sc->ipmi_bios_info.address + + (sc->ipmi_bios_info.offset * 2), + sc->ipmi_bios_info.offset * 2, + RF_ACTIVE); + } + + if (!sc->ipmi_io_res && !sc->ipmi_mem_res){ + device_printf(dev, "couldn't configure pci res\n"); + goto bad; + } + + flags = INB(sc, sc->ipmi_smic_flags); + if (flags == 0xff) { + device_printf(dev, "couldn't find it\n"); + goto bad; + } + if ((flags & SMIC_STATUS_SMS_ATN) + && (flags & SMIC_STATUS_RX_RDY)){ + ipmi_read(dev, NULL, 0); + } + } else { + device_printf(dev, "No IPMI interface found\n"); + goto bad; + } + ipmi_attach(dev); + + sc->ipmi_irq_rid = 0; + sc->ipmi_irq_res = bus_alloc_resource_any(sc->ipmi_dev, SYS_RES_IRQ, + &sc->ipmi_irq_rid, RF_SHAREABLE | RF_ACTIVE); + if (sc->ipmi_irq_res == NULL) { + device_printf(sc->ipmi_dev, "can't allocate interrupt\n"); + } else { + if (bus_setup_intr(sc->ipmi_dev, sc->ipmi_irq_res, + INTR_TYPE_MISC, ipmi_intr, + sc->ipmi_dev, &sc->ipmi_irq)) { + device_printf(sc->ipmi_dev, + "can't set up interrupt\n"); + return (EINVAL); + } + } + + return 0; +bad: + return ENXIO; +} + +static int ipmi_pci_detach(device_t dev) { + struct ipmi_softc *sc; + + sc = device_get_softc(dev); + ipmi_detach(dev); + if (sc->ipmi_ev_tag) + EVENTHANDLER_DEREGISTER(watchdog_list, sc->ipmi_ev_tag); + + if (sc->ipmi_mem_res) + bus_release_resource(dev, SYS_RES_MEMORY, sc->ipmi_mem_rid, + sc->ipmi_mem_res); + if (sc->ipmi_io_res) + bus_release_resource(dev, SYS_RES_IOPORT, sc->ipmi_io_rid, + sc->ipmi_io_res); + if (sc->ipmi_irq) + bus_teardown_intr(sc->ipmi_dev, sc->ipmi_irq_res, + sc->ipmi_irq); + if (sc->ipmi_irq_res) + bus_release_resource(sc->ipmi_dev, SYS_RES_IRQ, + sc->ipmi_irq_rid, sc->ipmi_irq_res); + + return 0; +} + +static driver_t ipmi_pci_driver = { + "ipmi", + ipmi_methods, + sizeof(struct ipmi_softc) +}; + +DRIVER_MODULE(ipmi_foo, pci, ipmi_pci_driver, ipmi_devclass, 0, 0); diff --git a/sys/dev/ipmi/ipmi_smbios.c b/sys/dev/ipmi/ipmi_smbios.c new file mode 100644 index 0000000..de14dfb --- /dev/null +++ b/sys/dev/ipmi/ipmi_smbios.c @@ -0,0 +1,538 @@ +/*- + * 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/kernel.h> +#include <sys/poll.h> +#include <sys/selinfo.h> + +#include <sys/module.h> +#include <sys/bus.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> +#include <sys/watchdog.h> + +#include <vm/vm.h> +#include <vm/vm_param.h> +#include <vm/pmap.h> +#include <machine/pc/bios.h> + +#ifdef LOCAL_MODULE +#include <ipmi.h> +#include <ipmivars.h> +#else +#include <sys/ipmi.h> +#include <dev/ipmi/ipmivars.h> +#endif + +struct smbios_table_entry { + uint8_t anchor_string[4]; + uint8_t checksum; + uint8_t length; + uint8_t major_version; + uint8_t minor_version; + uint16_t maximum_structure_size; + uint8_t entry_point_revision; + uint8_t formatted_area[5]; + uint8_t DMI_anchor_string[5]; + uint8_t intermediate_checksum; + uint16_t structure_table_length; + uint32_t structure_table_address; + uint16_t number_structures; + uint8_t BCD_revision; +}; + +struct structure_header { + uint8_t type; + uint8_t length; + uint16_t handle; +}; + +struct ipmi_device { + uint8_t type; + uint8_t length; + uint16_t handle; + uint8_t interface_type; + uint8_t spec_revision; + uint8_t i2c_slave_address; + uint8_t NV_storage_device_address; + uint64_t base_address; + uint8_t base_address_modifier; + uint8_t interrupt_number; +}; + +#define SMBIOS_START 0xf0000 +#define SMBIOS_STEP 0x10 +#define SMBIOS_OFF 0 +#define SMBIOS_LEN 4 +#define SMBIOS_SIG "_SM_" + +devclass_t ipmi_devclass; +typedef void (*dispatchproc_t)(uint8_t *p, char **table, + struct ipmi_get_info *info); + +static void smbios_run_table(uint8_t *, int, dispatchproc_t *, + struct ipmi_get_info *); +static char *get_strings(char *, char **); +static int smbios_cksum (struct smbios_table_entry *); +static void smbios_t38_proc_info(uint8_t *, char **, struct ipmi_get_info *); +static int ipmi_smbios_attach (device_t); +static int ipmi_smbios_modevent(module_t, int, void *); + +static void +smbios_t38_proc_info(uint8_t *p, char **table, struct ipmi_get_info *info) +{ + struct ipmi_device *s = (struct ipmi_device *) p; + + bzero(info, sizeof(struct ipmi_get_info)); + if (s->interface_type == 0x01) + info->kcs_mode = 1; + if (s->interface_type == 0x02) + info->smic_mode = 1; + info->address = s->base_address & ~1; + switch (s->base_address_modifier >> 6) { + case 0x00: + info->offset = 1; + break; + case 0x01: + info->offset = 4; + break; + case 0x10: + info->offset = 2; + break; + case 0x11: + info->offset = 0; + break; + } + info->io_mode = s->base_address & 1; +} + +static char * +get_strings(char *p, char **table) +{ + /* Scan for strings, stoping at a single null byte */ + while (*p != 0) { + *table++ = p; + p += strlen(p) + 1; + } + *table = 0; + + /* Skip past terminating null byte */ + return p + 1; +} + + +static void +smbios_run_table(uint8_t *p, int entries, dispatchproc_t *dispatchstatus, + struct ipmi_get_info *info) +{ + struct structure_header *s; + char *table[20]; + uint8_t *nextp; + + while(entries--) { + s = (struct structure_header *) p; + nextp = get_strings(p + s->length, table); + + /* + * No strings still has a double-null at the end, + * skip over it + */ + if (table[0] == 0) + nextp++; + + if (dispatchstatus[*p]) { + (dispatchstatus[*p])(p, table, info); + } + p = nextp; + } +} + +device_t +ipmi_smbios_identify (driver_t *driver, device_t parent) +{ + device_t child = NULL; + u_int32_t addr; + int length; + int rid1, rid2; + + addr = bios_sigsearch(SMBIOS_START, SMBIOS_SIG, SMBIOS_LEN, + SMBIOS_STEP, SMBIOS_OFF); + if (addr != 0) { + rid1 = 0; + length = ((struct smbios_table_entry *)BIOS_PADDRTOVADDR(addr)) + ->length; + + child = BUS_ADD_CHILD(parent, 0, "ipmi", -1); + if (driver != NULL) + device_set_driver(child, driver); + bus_set_resource(child, SYS_RES_MEMORY, rid1, addr, length); + rid2 = 1; + length = ((struct smbios_table_entry *)BIOS_PADDRTOVADDR(addr)) + ->structure_table_length; + addr = ((struct smbios_table_entry *)BIOS_PADDRTOVADDR(addr)) + ->structure_table_address; + bus_set_resource(child, SYS_RES_MEMORY, rid2, addr, length); + device_set_desc(child, "System Management BIOS"); + } else { + device_printf(parent, "Failed to find SMBIOS signature\n"); + } + + return child; +} + +int +ipmi_smbios_probe(device_t dev) +{ + struct resource *res1 = NULL, *res2 = NULL; + int rid1, rid2; + int error; + + if (ipmi_attached) + return(EBUSY); + + error = 0; + rid1 = 0; + rid2 = 1; + res1 = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid1, + 0ul, ~0ul, 1, RF_ACTIVE | RF_SHAREABLE); + + if (res1 == NULL) { + device_printf(dev, "Unable to allocate memory resource.\n"); + error = ENOMEM; + goto bad; + } + res2 = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid2, + 0ul, ~0ul, 1, RF_ACTIVE | RF_SHAREABLE); + if (res2 == NULL) { + device_printf(dev, "Unable to allocate memory resource.\n"); + error = ENOMEM; + goto bad; + } + + if (smbios_cksum( + (struct smbios_table_entry *)rman_get_virtual(res1))) { + device_printf(dev, "SMBIOS checksum failed.\n"); + error = ENXIO; + goto bad; + } + +bad: + if (res1) + bus_release_resource(dev, SYS_RES_MEMORY, rid1, res1); + if (res2) + bus_release_resource(dev, SYS_RES_MEMORY, rid2, res2); + return error; +} + + +int +ipmi_smbios_query(device_t dev) +{ + struct ipmi_softc *sc = device_get_softc(dev); + dispatchproc_t dispatch_smbios_ipmi[256]; + struct resource *res = NULL , *res2 = NULL; + int rid, rid2; + int error; + + error = 0; + + rid = 0; + res = bus_alloc_resource(sc->ipmi_smbios_dev, SYS_RES_MEMORY, &rid, + 0ul, ~0ul, 1, RF_ACTIVE | RF_SHAREABLE ); + if (res == NULL) { + device_printf(dev, "Unable to allocate memory resource.\n"); + error = ENOMEM; + goto bad; + } + rid2 = 1; + res2 = bus_alloc_resource(sc->ipmi_smbios_dev, SYS_RES_MEMORY, &rid2, + 0ul, ~0ul, 1, RF_ACTIVE | RF_SHAREABLE); + if (res2 == NULL) { + device_printf(dev, "Unable to allocate memory resource.\n"); + error = ENOMEM; + goto bad; + } + + sc->ipmi_smbios = + (struct smbios_table_entry *)rman_get_virtual(res); + + sc->ipmi_busy = 0; + + device_printf(sc->ipmi_smbios_dev, "SMBIOS Version: %d.%02d", + sc->ipmi_smbios->major_version, + sc->ipmi_smbios->minor_version); + if (bcd2bin(sc->ipmi_smbios->BCD_revision)) + printf(", revision: %d.%02d", + bcd2bin(sc->ipmi_smbios->BCD_revision >> 4), + bcd2bin(sc->ipmi_smbios->BCD_revision & 0x0f)); + printf("\n"); + + bzero(&sc->ipmi_bios_info, sizeof(sc->ipmi_bios_info)); + + bzero((void *)dispatch_smbios_ipmi, sizeof(dispatch_smbios_ipmi)); + dispatch_smbios_ipmi[38] = (void *)smbios_t38_proc_info; + smbios_run_table( + (void *)rman_get_virtual(res2), + sc->ipmi_smbios->number_structures, + dispatch_smbios_ipmi, + (void*)&sc->ipmi_bios_info); + +bad: + if (res) + bus_release_resource(sc->ipmi_smbios_dev, SYS_RES_MEMORY, + rid, res); + res = NULL; + if (res2) + bus_release_resource(sc->ipmi_smbios_dev, SYS_RES_MEMORY, + rid2, res2); + res2 = NULL; + return 0; +} + +static int +ipmi_smbios_attach(device_t dev) +{ + struct ipmi_softc *sc = device_get_softc(dev); + int error; + int status, flags; + + error = 0; + sc->ipmi_smbios_dev = dev; + sc->ipmi_dev = dev; + ipmi_smbios_query(dev); + + if (sc->ipmi_bios_info.kcs_mode) { + if (sc->ipmi_bios_info.io_mode) + device_printf(dev, "KCS mode found at io 0x%llx " + "alignment 0x%x on %s\n", + (long long)sc->ipmi_bios_info.address, + sc->ipmi_bios_info.offset, + device_get_name(device_get_parent(sc->ipmi_dev))); + else + device_printf(dev, "KCS mode found at mem 0x%llx " + "alignment 0x%x on %s\n", + (long long)sc->ipmi_bios_info.address, + sc->ipmi_bios_info.offset, + device_get_name(device_get_parent(sc->ipmi_dev))); + + sc->ipmi_kcs_status_reg = sc->ipmi_bios_info.offset; + sc->ipmi_kcs_command_reg = sc->ipmi_bios_info.offset; + sc->ipmi_kcs_data_out_reg = 0; + sc->ipmi_kcs_data_in_reg = 0; + + if (sc->ipmi_bios_info.io_mode) { + sc->ipmi_io_rid = 2; + sc->ipmi_io_res = bus_alloc_resource(dev, + SYS_RES_IOPORT, &sc->ipmi_io_rid, + sc->ipmi_bios_info.address, + sc->ipmi_bios_info.address + + (sc->ipmi_bios_info.offset * 2), + sc->ipmi_bios_info.offset * 2, + RF_ACTIVE); + } else { + sc->ipmi_mem_rid = 2; + sc->ipmi_mem_res = bus_alloc_resource(dev, + SYS_RES_MEMORY, &sc->ipmi_mem_rid, + sc->ipmi_bios_info.address, + sc->ipmi_bios_info.address + + (sc->ipmi_bios_info.offset * 2), + sc->ipmi_bios_info.offset * 2, + RF_ACTIVE); + } + + if (!sc->ipmi_io_res){ + device_printf(dev, + "couldn't configure smbios io res\n"); + goto bad; + } + + status = INB(sc, sc->ipmi_kcs_status_reg); + if (status == 0xff) { + device_printf(dev, "couldn't find it\n"); + error = ENXIO; + goto bad; + } + if(status & KCS_STATUS_OBF){ + ipmi_read(dev, NULL, 0); + } + } else if (sc->ipmi_bios_info.smic_mode) { + if (sc->ipmi_bios_info.io_mode) + device_printf(dev, "SMIC mode found at io 0x%llx " + "alignment 0x%x on %s\n", + (long long)sc->ipmi_bios_info.address, + sc->ipmi_bios_info.offset, + device_get_name(device_get_parent(sc->ipmi_dev))); + else + device_printf(dev, "SMIC mode found at mem 0x%llx " + "alignment 0x%x on %s\n", + (long long)sc->ipmi_bios_info.address, + sc->ipmi_bios_info.offset, + device_get_name(device_get_parent(sc->ipmi_dev))); + + sc->ipmi_smic_data = 0; + sc->ipmi_smic_ctl_sts = sc->ipmi_bios_info.offset; + sc->ipmi_smic_flags = sc->ipmi_bios_info.offset * 2; + + if (sc->ipmi_bios_info.io_mode) { + sc->ipmi_io_rid = 2; + sc->ipmi_io_res = bus_alloc_resource(dev, + SYS_RES_IOPORT, &sc->ipmi_io_rid, + sc->ipmi_bios_info.address, + sc->ipmi_bios_info.address + + (sc->ipmi_bios_info.offset * 3), + sc->ipmi_bios_info.offset * 3, + RF_ACTIVE); + } else { + sc->ipmi_mem_res = bus_alloc_resource(dev, + SYS_RES_MEMORY, &sc->ipmi_mem_rid, + sc->ipmi_bios_info.address, + sc->ipmi_bios_info.address + + (sc->ipmi_bios_info.offset * 2), + sc->ipmi_bios_info.offset * 2, + RF_ACTIVE); + } + + if (!sc->ipmi_io_res && !sc->ipmi_mem_res){ + device_printf(dev, "couldn't configure smbios res\n"); + error = ENXIO; + goto bad; + } + + flags = INB(sc, sc->ipmi_smic_flags); + if (flags == 0xff) { + device_printf(dev, "couldn't find it\n"); + error = ENXIO; + goto bad; + } + if ((flags & SMIC_STATUS_SMS_ATN) + && (flags & SMIC_STATUS_RX_RDY)){ + /* skip in smbio mode + ipmi_read(dev, NULL, 0); + */ + } + } else { + device_printf(dev, "No IPMI interface found\n"); + error = ENXIO; + goto bad; + } + ipmi_attach(dev); + + return 0; +bad: + /* + device_delete_child(device_get_parent(dev), dev); + */ + return error; +} + +static int +smbios_cksum (struct smbios_table_entry *e) +{ + u_int8_t *ptr; + u_int8_t cksum; + int i; + + ptr = (u_int8_t *)e; + cksum = 0; + for (i = 0; i < e->length; i++) { + cksum += ptr[i]; + } + + return cksum; +} + + +static int +ipmi_smbios_detach (device_t dev) +{ + struct ipmi_softc *sc; + + sc = device_get_softc(dev); + ipmi_detach(dev); + if (sc->ipmi_ev_tag) + EVENTHANDLER_DEREGISTER(watchdog_list, sc->ipmi_ev_tag); + + if (sc->ipmi_mem_res) + bus_release_resource(dev, SYS_RES_MEMORY, sc->ipmi_mem_rid, + sc->ipmi_mem_res); + if (sc->ipmi_io_res) + bus_release_resource(dev, SYS_RES_IOPORT, sc->ipmi_io_rid, + sc->ipmi_io_res); + return 0; +} + + +static device_method_t ipmi_methods[] = { + /* Device interface */ + DEVMETHOD(device_identify, ipmi_smbios_identify), + DEVMETHOD(device_probe, ipmi_smbios_probe), + DEVMETHOD(device_attach, ipmi_smbios_attach), + DEVMETHOD(device_detach, ipmi_smbios_detach), + { 0, 0 } +}; + +static driver_t ipmi_smbios_driver = { + "ipmi", + ipmi_methods, + sizeof(struct ipmi_softc), +}; + +static int +ipmi_smbios_modevent (mod, what, arg) + module_t mod; + int what; + void * arg; +{ + device_t * devs; + int count; + int i; + + switch (what) { + case MOD_LOAD: + return 0; + case MOD_UNLOAD: + devclass_get_devices(ipmi_devclass, &devs, &count); + for (i = 0; i < count; i++) { + device_delete_child(device_get_parent(devs[i]), + devs[i]); + } + break; + default: + break; + } + + return 0; +} + +DRIVER_MODULE(ipmi, isa, ipmi_smbios_driver, ipmi_devclass, + ipmi_smbios_modevent, 0); diff --git a/sys/dev/ipmi/ipmivars.h b/sys/dev/ipmi/ipmivars.h new file mode 100644 index 0000000..c793eab --- /dev/null +++ b/sys/dev/ipmi/ipmivars.h @@ -0,0 +1,138 @@ +/*- + * 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. + * + * $FreeBSD$ + */ + +struct ipmi_get_info { + int kcs_mode; + int smic_mode; + uint64_t address; + int offset; + int io_mode; +}; + +struct ipmi_softc { + device_t ipmi_dev; + device_t ipmi_smbios_dev; + struct cdev *ipmi_dev_t; + int ipmi_refcnt; + struct smbios_table_entry *ipmi_smbios; + struct ipmi_get_info ipmi_bios_info; + int ipmi_kcs_status_reg; + int ipmi_kcs_command_reg; + int ipmi_kcs_data_out_reg; + int ipmi_kcs_data_in_reg; + int ipmi_smic_data; + int ipmi_smic_ctl_sts; + int ipmi_smic_flags; + int ipmi_io_rid; + struct resource * ipmi_io_res; + int ipmi_mem_rid; + struct resource * ipmi_mem_res; + int ipmi_irq_rid; + struct resource * ipmi_irq_res; + void *ipmi_irq; + u_char ipmi_address; + u_char ipmi_lun; + int ipmi_busy; + struct selinfo ipmi_select; + int ipmi_timestamp; + int ipmi_requests; + struct callout_handle ipmi_timeout_handle; + TAILQ_HEAD(,ipmi_done_list) ipmi_done; + eventhandler_tag ipmi_ev_tag; +}; + +struct ipmi_ipmb { + u_char foo; +}; + +/* KCS status flags */ +#define KCS_STATUS_OBF 0x01 /* Data Out ready from BMC */ +#define KCS_STATUS_IBF 0x02 /* Data In from System */ +#define KCS_STATUS_SMS_ATN 0x04 /* Ready in RX queue */ +#define KCS_STATUS_C_D 0x08 /* Command/Data register write*/ +#define KCS_STATUS_OEM1 0x10 +#define KCS_STATUS_OEM2 0x20 +#define KCS_STATUS_S0 0x40 +#define KCS_STATUS_S1 0x80 + #define KCS_STATUS_STATE(x) ((x)>>6) + #define KCS_STATUS_STATE_IDLE 0x0 + #define KCS_STATUS_STATE_READ 0x1 + #define KCS_STATUS_STATE_WRITE 0x2 + #define KCS_STATUS_STATE_ERROR 0x3 +#define KCS_IFACE_STATUS_ABORT 0x01 +#define KCS_IFACE_STATUS_ILLEGAL 0x02 +#define KCS_IFACE_STATUS_LENGTH_ERR 0x06 + +/* KCD control codes */ +#define KCS_CONTROL_GET_STATUS_ABORT 0x60 +#define KCS_CONTROL_WRITE_START 0x61 +#define KCS_CONTROL_WRITE_END 0x62 +#define KCS_DATA_IN_READ 0x68 + +/* SMIC status flags */ +#define SMIC_STATUS_BUSY 0x01 /* System set and BMC clears it */ +#define SMIC_STATUS_SMS_ATN 0x04 /* BMC has a message */ +#define SMIC_STATUS_EVT_ATN 0x08 /* Event has been RX */ +#define SMIC_STATUS_SMI 0x08 /* asserted SMI */ +#define SMIC_STATUS_TX_RDY 0x40 /* Ready to accept WRITE */ +#define SMIC_STATUS_RX_RDY 0x80 /* Ready to read */ + +/* SMIC control codes */ +#define SMIC_CC_SMS_GET_STATUS 0x40 +#define SMIC_CC_SMS_WR_START 0x41 +#define SMIC_CC_SMS_WR_NEXT 0x42 +#define SMIC_CC_SMS_WR_END 0x43 +#define SMIC_CC_SMS_RD_START 0x44 +#define SMIC_CC_SMS_RD_NEXT 0x45 +#define SMIC_CC_SMS_RD_END 0x46 + +/* SMIC status codes */ +#define SMIC_SC_SMS_RDY 0xc0 +#define SMIC_SC_SMS_WR_START 0xc1 +#define SMIC_SC_SMS_WR_NEXT 0xc2 +#define SMIC_SC_SMS_WR_END 0xc3 +#define SMIC_SC_SMS_RD_START 0xc4 +#define SMIC_SC_SMS_RD_NEXT 0xc5 +#define SMIC_SC_SMS_RD_END 0xc6 + +#define RES(x) (x)->ipmi_io_res ? (x)->ipmi_io_res : (x)->ipmi_mem_res +#define INB(sc, x) bus_space_read_1(rman_get_bustag(RES(sc)), \ + rman_get_bushandle(RES(sc)), (x)) +#define OUTB(sc, x, value) bus_space_write_1(rman_get_bustag(RES(sc)), \ + rman_get_bushandle(RES(sc)), (x), value) + +int ipmi_attach(device_t); +int ipmi_detach(device_t); +int ipmi_smbios_query(device_t); +int ipmi_smbios_probe(device_t); +int ipmi_read(device_t, u_char *, int); +void ipmi_intr(void *); + +device_t ipmi_smbios_identify (driver_t *driver, device_t parent); +extern devclass_t ipmi_devclass; +extern int ipmi_attached; |