diff options
Diffstat (limited to 'sys/dev/ipmi')
| -rw-r--r-- | sys/dev/ipmi/ipmi.c | 998 | ||||
| -rw-r--r-- | sys/dev/ipmi/ipmi_acpi.c | 210 | ||||
| -rw-r--r-- | sys/dev/ipmi/ipmi_isa.c | 209 | ||||
| -rw-r--r-- | sys/dev/ipmi/ipmi_kcs.c | 607 | ||||
| -rw-r--r-- | sys/dev/ipmi/ipmi_pci.c | 295 | ||||
| -rw-r--r-- | sys/dev/ipmi/ipmi_smbios.c | 314 | ||||
| -rw-r--r-- | sys/dev/ipmi/ipmi_smbus.c | 131 | ||||
| -rw-r--r-- | sys/dev/ipmi/ipmi_smic.c | 407 | ||||
| -rw-r--r-- | sys/dev/ipmi/ipmi_ssif.c | 375 | ||||
| -rw-r--r-- | sys/dev/ipmi/ipmivars.h | 266 |
10 files changed, 3812 insertions, 0 deletions
diff --git a/sys/dev/ipmi/ipmi.c b/sys/dev/ipmi/ipmi.c new file mode 100644 index 000000000000..389d8d61cc56 --- /dev/null +++ b/sys/dev/ipmi/ipmi.c @@ -0,0 +1,998 @@ +/*- + * 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/conf.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/poll.h> +#include <sys/rman.h> +#include <sys/selinfo.h> +#include <sys/sysctl.h> +#include <sys/watchdog.h> + +#ifdef LOCAL_MODULE +#include <ipmi.h> +#include <ipmivars.h> +#else +#include <sys/ipmi.h> +#include <dev/ipmi/ipmivars.h> +#endif + +#ifdef IPMB +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_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 *cdev, int flags, int fmt, struct thread *td) +{ + struct ipmi_device *dev; + struct ipmi_softc *sc; + + if (!on) + return (ENOENT); + + dev = cdev->si_drv1; + sc = dev->ipmi_softc; + IPMI_LOCK(sc); + if (dev->ipmi_open) { + IPMI_UNLOCK(sc); + return (EBUSY); + } + dev->ipmi_open = 1; + IPMI_UNLOCK(sc); + + return (0); +} + +static int +ipmi_poll(struct cdev *cdev, int poll_events, struct thread *td) +{ + struct ipmi_device *dev; + struct ipmi_softc *sc; + int revents = 0; + + dev = cdev->si_drv1; + sc = dev->ipmi_softc; + + IPMI_LOCK(sc); + if (poll_events & (POLLIN | POLLRDNORM)) { + if (!TAILQ_EMPTY(&dev->ipmi_completed_requests)) + revents |= poll_events & (POLLIN | POLLRDNORM); + if (dev->ipmi_requests == 0) + revents |= POLLERR; + } + + if (revents == 0) { + if (poll_events & (POLLIN | POLLRDNORM)) + selrecord(td, &dev->ipmi_select); + } + IPMI_UNLOCK(sc); + + return (revents); +} + +static void +ipmi_purge_completed_requests(struct ipmi_device *dev) +{ + struct ipmi_request *req; + + while (!TAILQ_EMPTY(&dev->ipmi_completed_requests)) { + req = TAILQ_FIRST(&dev->ipmi_completed_requests); + TAILQ_REMOVE(&dev->ipmi_completed_requests, req, ir_link); + dev->ipmi_requests--; + ipmi_free_request(req); + } +} + +static int +ipmi_close(struct cdev *cdev, int flags, int fmt, struct thread *td) +{ + struct ipmi_request *req, *nreq; + struct ipmi_device *dev; + struct ipmi_softc *sc; +#ifdef CLONING + int bit; +#endif + + dev = cdev->si_drv1; + sc = dev->ipmi_softc; + + IPMI_LOCK(sc); + if (dev->ipmi_requests) { + /* Throw away any pending requests for this device. */ + TAILQ_FOREACH_SAFE(req, &sc->ipmi_pending_requests, ir_link, + nreq) { + if (req->ir_owner == dev) { + TAILQ_REMOVE(&sc->ipmi_pending_requests, req, + ir_link); + dev->ipmi_requests--; + ipmi_free_request(req); + } + } + + /* Throw away any pending completed requests for this device. */ + ipmi_purge_completed_requests(dev); + + /* + * If we still have outstanding requests, they must be stuck + * in an interface driver, so wait for those to drain. + */ + dev->ipmi_closing = 1; + while (dev->ipmi_requests > 0) { + msleep(&dev->ipmi_requests, &sc->ipmi_lock, PWAIT, + "ipmidrain", 0); + ipmi_purge_completed_requests(dev); + } + } + +#ifdef CLONING + /* Detach this sub-device from the main driver. */ + bit = minor(cdev) % 32; + sc->ipmi_cdev_mask &= ~(1 << bit); + TAILQ_REMOVE(&sc->ipmi_cdevs, dev, ipmi_link); + IPMI_UNLOCK(sc); + + /* Cleanup. */ + cdev->si_drv1 = NULL; + free(dev, M_IPMI); + destroy_dev(cdev); +#else + dev->ipmi_open = 0; + IPMI_UNLOCK(sc); +#endif + + return (0); +} + +#ifdef IPMB +static int +ipmi_ipmb_checksum(u_char *data, int len) +{ + u_char sum = 0; + + for (; len; len--) { + sum += *data++; + } + return (-sum); +} + +/* XXX: Needs work */ +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) +{ + struct ipmi_softc *sc = device_get_softc(dev); + struct ipmi_request *req; + u_char slave_addr = 0x52; + int error; + + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_SEND_MSG, data_len + 8, 0); + req->ir_request[0] = channel; + req->ir_request[1] = slave_addr; + req->ir_request[2] = IPMI_ADDR(netfn, 0); + req->ir_request[3] = ipmi_ipmb_checksum(&req->ir_request[1], 2); + req->ir_request[4] = sc->ipmi_address; + req->ir_request[5] = IPMI_ADDR(seq, sc->ipmi_lun); + req->ir_request[6] = command; + + bcopy(data, &req->ir_request[7], data_len); + temp[data_len + 7] = ipmi_ipmb_checksum(&req->ir_request[4], + data_len + 3); + + ipmi_submit_driver_request(sc, req); + error = req->ir_error; + ipmi_free_request(req); + + return (error); +} + +static int +ipmi_handle_attn(struct ipmi_softc *sc) +{ + struct ipmi_request *req; + int error; + + device_printf(sc->ipmi_dev, "BMC has a message\n"); + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_GET_MSG_FLAGS, 0, 1); + + ipmi_submit_driver_request(sc, req); + + if (req->ir_error == 0 && req->ir_compcode == 0) { + if (req->ir_reply[0] & IPMI_MSG_BUFFER_FULL) { + device_printf(sc->ipmi_dev, "message buffer full"); + } + if (req->ir_reply[0] & IPMI_WDT_PRE_TIMEOUT) { + device_printf(sc->ipmi_dev, + "watchdog about to go off"); + } + if (req->ir_reply[0] & IPMI_MSG_AVAILABLE) { + ipmi_free_request(req); + + req = ipmi_alloc_driver_request( + IPMI_ADDR(IPMI_APP_REQUEST, 0), IPMI_GET_MSG, 0, + 16); + + device_printf(sc->ipmi_dev, "throw out message "); + dump_buf(temp, 16); + } + } + error = req->ir_error; + ipmi_free_request(req); + + return (error); +} +#endif + +#ifdef IPMICTL_SEND_COMMAND_32 +#define PTRIN(p) ((void *)(uintptr_t)(p)) +#define PTROUT(p) ((uintptr_t)(p)) +#endif + +static int +ipmi_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, + int flags, struct thread *td) +{ + struct ipmi_softc *sc; + struct ipmi_device *dev; + struct ipmi_request *kreq; + struct ipmi_req *req = (struct ipmi_req *)data; + struct ipmi_recv *recv = (struct ipmi_recv *)data; + struct ipmi_addr addr; +#ifdef IPMICTL_SEND_COMMAND_32 + struct ipmi_req32 *req32 = (struct ipmi_req32 *)data; + struct ipmi_recv32 *recv32 = (struct ipmi_recv32 *)data; + union { + struct ipmi_req req; + struct ipmi_recv recv; + } thunk32; +#endif + int error, len; + + dev = cdev->si_drv1; + sc = dev->ipmi_softc; + +#ifdef IPMICTL_SEND_COMMAND_32 + /* Convert 32-bit structures to native. */ + switch (cmd) { + case IPMICTL_SEND_COMMAND_32: + req = &thunk32.req; + req->addr = PTRIN(req32->addr); + req->addr_len = req32->addr_len; + req->msgid = req32->msgid; + req->msg.netfn = req32->msg.netfn; + req->msg.cmd = req32->msg.cmd; + req->msg.data_len = req32->msg.data_len; + req->msg.data = PTRIN(req32->msg.data); + break; + case IPMICTL_RECEIVE_MSG_TRUNC_32: + case IPMICTL_RECEIVE_MSG_32: + recv = &thunk32.recv; + recv->addr = PTRIN(recv32->addr); + recv->addr_len = recv32->addr_len; + recv->msg.data_len = recv32->msg.data_len; + recv->msg.data = PTRIN(recv32->msg.data); + break; + } +#endif + + switch (cmd) { +#ifdef IPMICTL_SEND_COMMAND_32 + case IPMICTL_SEND_COMMAND_32: +#endif + case IPMICTL_SEND_COMMAND: + /* + * XXX: Need to add proper handling of this. + */ + error = copyin(req->addr, &addr, sizeof(addr)); + if (error) + return (error); + + IPMI_LOCK(sc); + /* clear out old stuff in queue of stuff done */ + /* XXX: This seems odd. */ + while ((kreq = TAILQ_FIRST(&dev->ipmi_completed_requests))) { + TAILQ_REMOVE(&dev->ipmi_completed_requests, kreq, + ir_link); + dev->ipmi_requests--; + ipmi_free_request(kreq); + } + IPMI_UNLOCK(sc); + + kreq = ipmi_alloc_request(dev, req->msgid, + IPMI_ADDR(req->msg.netfn, 0), req->msg.cmd, + req->msg.data_len, IPMI_MAX_RX); + error = copyin(req->msg.data, kreq->ir_request, + req->msg.data_len); + if (error) { + ipmi_free_request(kreq); + return (error); + } + IPMI_LOCK(sc); + dev->ipmi_requests++; + error = sc->ipmi_enqueue_request(sc, kreq); + IPMI_UNLOCK(sc); + if (error) + return (error); + break; +#ifdef IPMICTL_SEND_COMMAND_32 + case IPMICTL_RECEIVE_MSG_TRUNC_32: + case IPMICTL_RECEIVE_MSG_32: +#endif + case IPMICTL_RECEIVE_MSG_TRUNC: + case IPMICTL_RECEIVE_MSG: + error = copyin(recv->addr, &addr, sizeof(addr)); + if (error) + return (error); + + IPMI_LOCK(sc); + kreq = TAILQ_FIRST(&dev->ipmi_completed_requests); + if (kreq == NULL) { + IPMI_UNLOCK(sc); + return (EAGAIN); + } + addr.channel = IPMI_BMC_CHANNEL; + /* XXX */ + recv->recv_type = IPMI_RESPONSE_RECV_TYPE; + recv->msgid = kreq->ir_msgid; + recv->msg.netfn = IPMI_REPLY_ADDR(kreq->ir_addr) >> 2; + recv->msg.cmd = kreq->ir_command; + error = kreq->ir_error; + if (error) { + TAILQ_REMOVE(&dev->ipmi_completed_requests, kreq, + ir_link); + dev->ipmi_requests--; + IPMI_UNLOCK(sc); + ipmi_free_request(kreq); + return (error); + } + len = kreq->ir_replylen + 1; + if (recv->msg.data_len < len && + (cmd == IPMICTL_RECEIVE_MSG +#ifdef IPMICTL_RECEIVE_MSG_32 + || cmd == IPMICTL_RECEIVE_MSG +#endif + )) { + IPMI_UNLOCK(sc); + return (EMSGSIZE); + } + TAILQ_REMOVE(&dev->ipmi_completed_requests, kreq, ir_link); + dev->ipmi_requests--; + IPMI_UNLOCK(sc); + len = min(recv->msg.data_len, len); + recv->msg.data_len = len; + error = copyout(&addr, recv->addr,sizeof(addr)); + if (error == 0) + error = copyout(&kreq->ir_compcode, recv->msg.data, 1); + if (error == 0) + error = copyout(kreq->ir_reply, recv->msg.data + 1, + len - 1); + ipmi_free_request(kreq); + if (error) + return (error); + break; + case IPMICTL_SET_MY_ADDRESS_CMD: + IPMI_LOCK(sc); + dev->ipmi_address = *(int*)data; + IPMI_UNLOCK(sc); + break; + case IPMICTL_GET_MY_ADDRESS_CMD: + IPMI_LOCK(sc); + *(int*)data = dev->ipmi_address; + IPMI_UNLOCK(sc); + break; + case IPMICTL_SET_MY_LUN_CMD: + IPMI_LOCK(sc); + dev->ipmi_lun = *(int*)data & 0x3; + IPMI_UNLOCK(sc); + break; + case IPMICTL_GET_MY_LUN_CMD: + IPMI_LOCK(sc); + *(int*)data = dev->ipmi_lun; + IPMI_UNLOCK(sc); + break; + case IPMICTL_SET_GETS_EVENTS_CMD: + /* + device_printf(sc->ipmi_dev, + "IPMICTL_SET_GETS_EVENTS_CMD NA\n"); + */ + break; + case IPMICTL_REGISTER_FOR_CMD: + case IPMICTL_UNREGISTER_FOR_CMD: + return (EOPNOTSUPP); + default: + device_printf(sc->ipmi_dev, "Unknown IOCTL %lX\n", cmd); + return (ENOIOCTL); + } + +#ifdef IPMICTL_SEND_COMMAND_32 + /* Update changed fields in 32-bit structures. */ + switch (cmd) { + case IPMICTL_RECEIVE_MSG_TRUNC_32: + case IPMICTL_RECEIVE_MSG_32: + recv32->recv_type = recv->recv_type; + recv32->msgid = recv->msgid; + recv32->msg.netfn = recv->msg.netfn; + recv32->msg.cmd = recv->msg.cmd; + recv32->msg.data_len = recv->msg.data_len; + break; + } +#endif + return (0); +} + +/* + * Request management. + */ + +/* Allocate a new request with request and reply buffers. */ +struct ipmi_request * +ipmi_alloc_request(struct ipmi_device *dev, long msgid, uint8_t addr, + uint8_t command, size_t requestlen, size_t replylen) +{ + struct ipmi_request *req; + + req = malloc(sizeof(struct ipmi_request) + requestlen + replylen, + M_IPMI, M_WAITOK | M_ZERO); + req->ir_owner = dev; + req->ir_msgid = msgid; + req->ir_addr = addr; + req->ir_command = command; + if (requestlen) { + req->ir_request = (char *)&req[1]; + req->ir_requestlen = requestlen; + } + if (replylen) { + req->ir_reply = (char *)&req[1] + requestlen; + req->ir_replybuflen = replylen; + } + return (req); +} + +/* Free a request no longer in use. */ +void +ipmi_free_request(struct ipmi_request *req) +{ + + free(req, M_IPMI); +} + +/* Store a processed request on the appropriate completion queue. */ +void +ipmi_complete_request(struct ipmi_softc *sc, struct ipmi_request *req) +{ + struct ipmi_device *dev; + + IPMI_LOCK_ASSERT(sc); + + /* + * Anonymous requests (from inside the driver) always have a + * waiter that we awaken. + */ + if (req->ir_owner == NULL) + wakeup(req); + else { + dev = req->ir_owner; + TAILQ_INSERT_TAIL(&dev->ipmi_completed_requests, req, ir_link); + selwakeup(&dev->ipmi_select); + if (dev->ipmi_closing) + wakeup(&dev->ipmi_requests); + } +} + +/* Enqueue an internal driver request and wait until it is completed. */ +int +ipmi_submit_driver_request(struct ipmi_softc *sc, struct ipmi_request *req, + int timo) +{ + int error; + + IPMI_LOCK(sc); + error = sc->ipmi_enqueue_request(sc, req); + if (error == 0) + error = msleep(req, &sc->ipmi_lock, 0, "ipmireq", timo); + if (error == 0) + error = req->ir_error; + IPMI_UNLOCK(sc); + return (error); +} + +/* + * Helper routine for polled system interfaces that use + * ipmi_polled_enqueue_request() to queue requests. This request + * waits until there is a pending request and then returns the first + * request. If the driver is shutting down, it returns NULL. + */ +struct ipmi_request * +ipmi_dequeue_request(struct ipmi_softc *sc) +{ + struct ipmi_request *req; + + IPMI_LOCK_ASSERT(sc); + + while (!sc->ipmi_detaching && TAILQ_EMPTY(&sc->ipmi_pending_requests)) + cv_wait(&sc->ipmi_request_added, &sc->ipmi_lock); + if (sc->ipmi_detaching) + return (NULL); + + req = TAILQ_FIRST(&sc->ipmi_pending_requests); + TAILQ_REMOVE(&sc->ipmi_pending_requests, req, ir_link); + return (req); +} + +/* Default implementation of ipmi_enqueue_request() for polled interfaces. */ +int +ipmi_polled_enqueue_request(struct ipmi_softc *sc, struct ipmi_request *req) +{ + + IPMI_LOCK_ASSERT(sc); + + TAILQ_INSERT_TAIL(&sc->ipmi_pending_requests, req, ir_link); + cv_signal(&sc->ipmi_request_added); + return (0); +} + +/* + * Watchdog event handler. + */ + +static void +ipmi_set_watchdog(struct ipmi_softc *sc, int sec) +{ + struct ipmi_request *req; + int error; + + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_SET_WDOG, 6, 0); + + if (sec) { + req->ir_request[0] = IPMI_SET_WD_TIMER_DONT_STOP + | IPMI_SET_WD_TIMER_SMS_OS; + req->ir_request[1] = IPMI_SET_WD_ACTION_RESET; + req->ir_request[2] = 0; + req->ir_request[3] = 0; /* Timer use */ + req->ir_request[4] = (sec * 10) & 0xff; + req->ir_request[5] = (sec * 10) / 2550; + } else { + req->ir_request[0] = IPMI_SET_WD_TIMER_SMS_OS; + req->ir_request[1] = 0; + req->ir_request[2] = 0; + req->ir_request[3] = 0; /* Timer use */ + req->ir_request[4] = 0; + req->ir_request[5] = 0; + } + + error = ipmi_submit_driver_request(sc, req, 0); + if (error) + device_printf(sc->ipmi_dev, "Failed to set watchdog\n"); + + if (error == 0 && sec) { + ipmi_free_request(req); + + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_RESET_WDOG, 0, 0); + + error = ipmi_submit_driver_request(sc, req, 0); + if (error) + device_printf(sc->ipmi_dev, + "Failed to reset watchdog\n"); + } + + ipmi_free_request(req); + /* + dump_watchdog(sc); + */ +} + +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, 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, timeout); + + *error = 0; +} + +#ifdef CLONING +static void +ipmi_clone(void *arg, struct ucred *cred, char *name, int namelen, + struct cdev **cdev) +{ + struct ipmi_softc *sc = arg; + struct ipmi_device *dev; + int minor, unit; + + if (*cdev != NULL) + return; + + if (strcmp(name, device_get_nameunit(sc->ipmi_dev)) != 0) + return; + + dev = malloc(sizeof(struct ipmi_device), M_IPMI, M_WAITOK | M_ZERO); + + /* Reserve a sub-device. */ + IPMI_LOCK(sc); + minor = ffs(~(sc->ipmi_cdev_mask & 0xffff)); + if (minor == 0 || !sc->ipmi_cloning) { + IPMI_UNLOCK(sc); + free(dev, M_IPMI); + return; + } + minor--; + sc->ipmi_cdev_mask |= (1 << minor); + TAILQ_INSERT_TAIL(&sc->ipmi_cdevs, dev, ipmi_link); + IPMI_UNLOCK(sc); + + /* Initialize the device. */ + TAILQ_INIT(&dev->ipmi_completed_requests); + dev->ipmi_softc = sc; + dev->ipmi_address = IPMI_BMC_SLAVE_ADDR; + dev->ipmi_lun = IPMI_BMC_SMS_LUN; + unit = device_get_unit(sc->ipmi_dev); + dev->ipmi_cdev = make_dev_cred(&ipmi_cdevsw, unit * 32 + minor, cred, + UID_ROOT, GID_OPERATOR, 0660, "ipmi%d.%d", unit, minor); + if (dev->ipmi_cdev == NULL) { + IPMI_LOCK(sc); + sc->ipmi_cdev_mask &= ~(1 << minor); + TAILQ_REMOVE(&sc->ipmi_cdevs, dev, ipmi_link); + IPMI_UNLOCK(sc); + free(dev, M_IPMI); + return; + } + dev->ipmi_cdev->si_drv1 = dev; + *cdev = dev->ipmi_cdev; + dev_ref(*cdev); +} +#endif + +static void +ipmi_startup(void *arg) +{ + struct ipmi_softc *sc = arg; + struct ipmi_request *req; + device_t dev; + int error, i; + + config_intrhook_disestablish(&sc->ipmi_ich); + dev = sc->ipmi_dev; + + /* Initialize interface-independent state. */ + mtx_init(&sc->ipmi_lock, device_get_nameunit(dev), "ipmi", MTX_DEF); + cv_init(&sc->ipmi_request_added, "ipmireq"); + TAILQ_INIT(&sc->ipmi_pending_requests); +#ifdef CLONING + TAILQ_INIT(&sc->ipmi_cdevs); +#endif + + /* Initialize interface-dependent state. */ + error = sc->ipmi_startup(sc); + if (error) { + device_printf(dev, "Failed to initialize interface: %d\n", + error); + return; + } + + /* Send a GET_DEVICE_ID request. */ + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_GET_DEVICE_ID, 0, 15); + + error = ipmi_submit_driver_request(sc, req, MAX_TIMEOUT); + if (error == EWOULDBLOCK) { + device_printf(dev, "Timed out waiting for GET_DEVICE_ID\n"); + return; + } else if (error) { + device_printf(dev, "Failed GET_DEVICE_ID: %d\n", error); + return; + } else if (req->ir_compcode != 0) { + device_printf(dev, + "Bad completion code for GET_DEVICE_ID: %d\n", + req->ir_compcode); + return; + } else if (req->ir_replylen < 5) { + device_printf(dev, "Short reply for GET_DEVICE_ID: %d\n", + req->ir_replylen); + return; + } + + device_printf(dev, "IPMI device rev. %d, firmware rev. %d.%d, " + "version %d.%d\n", + req->ir_reply[1] & 0x0f, + req->ir_reply[2] & 0x0f, req->ir_reply[4], + req->ir_reply[4] & 0x0f, req->ir_reply[4] >> 4); + + ipmi_free_request(req); + + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_CLEAR_FLAGS, 1, 0); + + ipmi_submit_driver_request(sc, req, 0); + + /* XXX: Magic numbers */ + if (req->ir_compcode == 0xc0) { + device_printf(dev, "Clear flags is busy\n"); + } + if (req->ir_compcode == 0xc1) { + device_printf(dev, "Clear flags illegal\n"); + } + ipmi_free_request(req); + + for (i = 0; i < 8; i++) { + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_GET_CHANNEL_INFO, 1, 0); + req->ir_request[0] = i; + + ipmi_submit_driver_request(sc, req, 0); + + if (req->ir_compcode != 0) { + ipmi_free_request(req); + break; + } + ipmi_free_request(req); + } + device_printf(dev, "Number of channels %d\n", i); + + /* probe for watchdog */ + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_GET_WDOG, 0, 0); + + ipmi_submit_driver_request(sc, req, 0); + + if (req->ir_compcode == 0x00) { + device_printf(dev, "Attached watchdog\n"); + /* register the watchdog event handler */ + sc->ipmi_watchdog_tag = EVENTHANDLER_REGISTER(watchdog_list, + ipmi_wd_event, sc, 0); + } + ipmi_free_request(req); + +#ifdef CLONING + sc->ipmi_cloning = 1; + sc->ipmi_clone_tag = EVENTHANDLER_REGISTER(dev_clone, ipmi_clone, sc, + 1000); +#else + /* Initialize the device. */ + TAILQ_INIT(&sc->ipmi_idev.ipmi_completed_requests); + sc->ipmi_idev.ipmi_softc = sc; + sc->ipmi_idev.ipmi_address = IPMI_BMC_SLAVE_ADDR; + sc->ipmi_idev.ipmi_lun = IPMI_BMC_SMS_LUN; + sc->ipmi_idev.ipmi_cdev = make_dev(&ipmi_cdevsw, device_get_unit(dev), + UID_ROOT, GID_OPERATOR, 0660, "ipmi%d", device_get_unit(dev)); + if (sc->ipmi_idev.ipmi_cdev == NULL) { + device_printf(dev, "Failed to create cdev\n"); + return; + } + sc->ipmi_idev.ipmi_cdev->si_drv1 = &sc->ipmi_idev; +#endif +} + +int +ipmi_attach(device_t dev) +{ + struct ipmi_softc *sc = device_get_softc(dev); + int error; + + if (sc->ipmi_irq_res != NULL && sc->ipmi_intr != NULL) { + error = bus_setup_intr(dev, sc->ipmi_irq_res, INTR_TYPE_MISC, + sc->ipmi_intr, sc, &sc->ipmi_irq); + if (error) { + device_printf(dev, "can't set up interrupt\n"); + return (error); + } + } + + bzero(&sc->ipmi_ich, sizeof(struct intr_config_hook)); + sc->ipmi_ich.ich_func = ipmi_startup; + sc->ipmi_ich.ich_arg = sc; + if (config_intrhook_establish(&sc->ipmi_ich) != 0) { + device_printf(dev, "can't establish configuration hook\n"); + return (ENOMEM); + } + + ipmi_attached = 1; + return (0); +} + +int +ipmi_detach(device_t dev) +{ + struct ipmi_softc *sc; + + sc = device_get_softc(dev); + + /* Fail if there are any open handles. */ + IPMI_LOCK(sc); +#ifdef CLONING + if (!TAILQ_EMPTY(&sc->ipmi_cdevs)) { + IPMI_UNLOCK(sc); + return (EBUSY); + } + + /* Turn off cloning. */ + sc->ipmi_cloning = 0; + IPMI_UNLOCK(sc); + + EVENTHANDLER_DEREGISTER(dev_clone, sc->ipmi_clone_tag); +#else + if (sc->ipmi_idev.ipmi_open) { + IPMI_UNLOCK(sc); + return (EBUSY); + } + IPMI_UNLOCK(sc); + destroy_dev(sc->ipmi_idev.ipmi_cdev); +#endif + + /* Detach from watchdog handling and turn off watchdog. */ + if (sc->ipmi_watchdog_tag) { + EVENTHANDLER_DEREGISTER(watchdog_list, sc->ipmi_watchdog_tag); + ipmi_set_watchdog(sc, 0); + } + + /* XXX: should use shutdown callout I think. */ + /* If the backend uses a kthread, shut it down. */ + IPMI_LOCK(sc); + sc->ipmi_detaching = 1; + if (sc->ipmi_kthread) { + cv_broadcast(&sc->ipmi_request_added); + msleep(sc->ipmi_kthread, &sc->ipmi_lock, 0, "ipmi_wait", 0); + } + IPMI_UNLOCK(sc); + if (sc->ipmi_irq) + bus_teardown_intr(dev, sc->ipmi_irq_res, sc->ipmi_irq); + + ipmi_release_resources(dev); + mtx_destroy(&sc->ipmi_lock); + return (0); +} + +void +ipmi_release_resources(device_t dev) +{ + struct ipmi_softc *sc; + int i; + + sc = device_get_softc(dev); + if (sc->ipmi_irq) + bus_teardown_intr(dev, sc->ipmi_irq_res, sc->ipmi_irq); + if (sc->ipmi_irq_res) + bus_release_resource(dev, SYS_RES_IRQ, sc->ipmi_irq_rid, + sc->ipmi_irq_res); + for (i = 0; i < MAX_RES; i++) + if (sc->ipmi_io_res[i]) + bus_release_resource(dev, sc->ipmi_io_type, + sc->ipmi_io_rid + i, sc->ipmi_io_res[i]); +} + +devclass_t ipmi_devclass; + +/* XXX: Why? */ +static void +ipmi_unload(void *arg) +{ + device_t * devs; + int count; + int i; + + if (devclass_get_devices(ipmi_devclass, &devs, &count) != 0) + return; + for (i = 0; i < count; i++) + device_delete_child(device_get_parent(devs[i]), devs[i]); + free(devs, M_TEMP); +} +SYSUNINIT(ipmi_unload, SI_SUB_DRIVERS, SI_ORDER_FIRST, ipmi_unload, NULL); + +#ifdef IMPI_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_acpi.c b/sys/dev/ipmi/ipmi_acpi.c new file mode 100644 index 000000000000..e2518ebfcc0e --- /dev/null +++ b/sys/dev/ipmi/ipmi_acpi.c @@ -0,0 +1,210 @@ +/*- + * 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/kernel.h> +#include <sys/module.h> +#include <sys/rman.h> +#include <sys/selinfo.h> + +#include <contrib/dev/acpica/acpi.h> +#include <dev/acpica/acpivar.h> + +/* Hooks for the ACPI CA debugging infrastructure */ +#define _COMPONENT ACPI_BUTTON +ACPI_MODULE_NAME("IPMI") + +#ifdef LOCAL_MODULE +#include <ipmi.h> +#include <ipmivars.h> +#else +#include <sys/ipmi.h> +#include <dev/ipmi/ipmivars.h> +#endif + +static int ipmi_acpi_probe(device_t); +static int ipmi_acpi_attach(device_t); + +int +ipmi_acpi_probe(device_t dev) +{ + static char *ipmi_ids[] = {"IPI0001", NULL}; + + if (ipmi_attached) + return (EBUSY); + + if (acpi_disabled("ipmi") || + ACPI_ID_PROBE(device_get_parent(dev), dev, ipmi_ids) == NULL) + return (ENXIO); + + device_set_desc(dev, "IPMI System Interface"); + + return (0); +} + +static int +ipmi_acpi_attach(device_t dev) +{ + ACPI_HANDLE devh; + const char *mode; + struct ipmi_get_info info; + struct ipmi_softc *sc = device_get_softc(dev); + int count, error, flags, i, type; + int interface_type = 0, interface_version = 0; + + error = 0; + devh = acpi_get_handle(dev); + if (ACPI_FAILURE(acpi_GetInteger(devh, "_IFT", &interface_type))) + return (ENXIO); + + if (ACPI_FAILURE(acpi_GetInteger(devh, "_SRV", &interface_version))) + return (ENXIO); + + switch (interface_type) { + case KCS_MODE: + count = 2; + mode = "KCS"; + break; + case SMIC_MODE: + count = 3; + mode = "SMIC"; + break; + case BT_MODE: + device_printf(dev, "BT interface not supported\n"); + return (ENXIO); + case SSIF_MODE: + if (ACPI_FAILURE(acpi_GetInteger(devh, "_ADR", &flags))) + return (ENXIO); + info.address = flags >> 1; + device_printf(dev, "SSIF interface not supported on ACPI\n"); + return (0); + default: + return (ENXIO); + } + + if (bus_get_resource(dev, SYS_RES_IOPORT, 0, NULL, NULL) == 0) + type = SYS_RES_IOPORT; + else if (bus_get_resource(dev, SYS_RES_MEMORY, 0, NULL, NULL) == 0) + type = SYS_RES_MEMORY; + else { + device_printf(dev, "unknown resource type\n"); + return (ENXIO); + } + + sc->ipmi_io_rid = 0; + sc->ipmi_io_res[0] = bus_alloc_resource_any(dev, type, + &sc->ipmi_io_rid, RF_ACTIVE); + sc->ipmi_io_type = type; + sc->ipmi_io_spacing = 1; + if (sc->ipmi_io_res[0] == NULL) { + device_printf(dev, "couldn't configure I/O resource\n"); + return (ENXIO); + } + + /* If we have multiple resources, allocate up to MAX_RES. */ + for (i = 1; i < MAX_RES; i++) { + sc->ipmi_io_rid = i; + sc->ipmi_io_res[i] = bus_alloc_resource_any(dev, type, + &sc->ipmi_io_rid, RF_ACTIVE); + if (sc->ipmi_io_res[i] == NULL) + break; + } + sc->ipmi_io_rid = 0; + + /* If we have multiple resources, make sure we have enough of them. */ + if (sc->ipmi_io_res[1] != NULL && sc->ipmi_io_res[count - 1] == NULL) { + device_printf(dev, "too few I/O resources\n"); + error = ENXIO; + goto bad; + } + + device_printf(dev, "%s mode found at %s 0x%jx on %s\n", + mode, type == SYS_RES_IOPORT ? "io" : "mem", + (uintmax_t)rman_get_start(sc->ipmi_io_res[0]), + device_get_name(device_get_parent(dev))); + + sc->ipmi_dev = dev; + + /* + * Setup an interrupt if we have an interrupt resource. We + * don't support GPE interrupts via _GPE yet. + */ + sc->ipmi_irq_rid = 0; + sc->ipmi_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, + &sc->ipmi_irq_rid, RF_SHAREABLE | RF_ACTIVE); + + /* Warn if _GPE exists. */ + if (ACPI_SUCCESS(AcpiEvaluateObject(devh, "_GPE", NULL, NULL))) + device_printf(dev, "_GPE support not implemented\n"); + + /* + * We assume an alignment of 1 byte as currently the IPMI spec + * doesn't provide any way to determine the alignment via ACPI. + */ + switch (interface_type) { + case KCS_MODE: + error = ipmi_kcs_attach(sc); + if (error) + goto bad; + break; + case SMIC_MODE: + error = ipmi_smic_attach(sc); + if (error) + goto bad; + break; + } + error = ipmi_attach(dev); + if (error) + goto bad; + + return (0); +bad: + ipmi_release_resources(dev); + return (error); +} + +static device_method_t ipmi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ipmi_acpi_probe), + DEVMETHOD(device_attach, ipmi_acpi_attach), + DEVMETHOD(device_detach, ipmi_detach), + { 0, 0 } +}; + +static driver_t ipmi_acpi_driver = { + "ipmi", + ipmi_methods, + sizeof(struct ipmi_softc), +}; + +DRIVER_MODULE(ipmi_acpi, acpi, ipmi_acpi_driver, ipmi_devclass, 0, 0); +MODULE_DEPEND(ipmi_acpi, acpi, 1, 1, 1); diff --git a/sys/dev/ipmi/ipmi_isa.c b/sys/dev/ipmi/ipmi_isa.c new file mode 100644 index 000000000000..a77081ad3908 --- /dev/null +++ b/sys/dev/ipmi/ipmi_isa.c @@ -0,0 +1,209 @@ +/*- + * 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/module.h> +#include <sys/rman.h> +#include <sys/selinfo.h> + +#include <machine/pci_cfgreg.h> +#include <dev/pci/pcireg.h> + +#include <isa/isavar.h> + +#ifdef LOCAL_MODULE +#include <ipmi.h> +#include <ipmivars.h> +#else +#include <sys/ipmi.h> +#include <dev/ipmi/ipmivars.h> +#endif + +static void +ipmi_isa_identify(driver_t *driver, device_t parent) +{ + struct ipmi_get_info info; + uint32_t devid; + + if (ipmi_smbios_identify(&info) && info.iface_type != SSIF_MODE && + device_find_child(parent, "ipmi", -1) == NULL) { + /* + * XXX: Hack alert. On some broken systems, the IPMI + * interface is described via SMBIOS, but the actual + * IO resource is in a PCI device BAR, so we have to let + * the PCI device attach ipmi instead. In that case don't + * create an isa ipmi device. For now we hardcode the list + * of bus, device, function tuples. + */ + devid = pci_cfgregread(0, 4, 2, PCIR_DEVVENDOR, 4); + if (devid != 0xffffffff && + ipmi_pci_match(devid & 0xffff, devid >> 16) != NULL) + return; + BUS_ADD_CHILD(parent, 0, "ipmi", -1); + } +} + +static int +ipmi_isa_probe(device_t dev) +{ + + /* Skip any PNP devices. */ + if (isa_get_logicalid(dev) != 0) + return (ENXIO); + + device_set_desc(dev, "IPMI System Interface"); + return (BUS_PROBE_DEFAULT); +} + +static int +ipmi_isa_attach(device_t dev) +{ + struct ipmi_softc *sc = device_get_softc(dev); + struct ipmi_get_info info; + const char *mode; + int count, error, i, type; + + /* This should never fail. */ + if (!ipmi_smbios_identify(&info)) + return (ENXIO); + + /* + * Give other drivers precedence. Unfortunately, this doesn't + * work if we have an SMBIOS table that duplicates a PCI device + * that's later on the bus than the PCI-ISA bridge. + */ + if (ipmi_attached) + return (EBUSY); + + switch (info.iface_type) { + case KCS_MODE: + count = 2; + mode = "KCS"; + break; + case SMIC_MODE: + count = 3; + mode = "SMIC"; + break; + case BT_MODE: + device_printf(dev, "BT mode is unsupported\n"); + return (ENXIO); + default: + return (ENXIO); + } + error = 0; + sc->ipmi_dev = dev; + + device_printf(dev, "%s mode found at %s 0x%jx alignment 0x%x on %s\n", + mode, info.io_mode ? "io" : "mem", + (uintmax_t)info.address, info.offset, + device_get_name(device_get_parent(dev))); + if (info.io_mode) + type = SYS_RES_IOPORT; + else + type = SYS_RES_MEMORY; + + sc->ipmi_io_type = type; + sc->ipmi_io_spacing = info.offset; + if (info.offset == 1) { + sc->ipmi_io_rid = 0; + sc->ipmi_io_res[0] = bus_alloc_resource(dev, type, + &sc->ipmi_io_rid, info.address, info.address + count - 1, + count, RF_ACTIVE); + if (sc->ipmi_io_res[0] == NULL) { + device_printf(dev, "couldn't configure I/O resource\n"); + return (ENXIO); + } + } else { + for (i = 0; i < count; i++) { + sc->ipmi_io_rid = i; + sc->ipmi_io_res[i] = bus_alloc_resource(dev, type, + &sc->ipmi_io_rid, info.address + i * info.offset, + info.address + i * info.offset, 1, RF_ACTIVE); + if (sc->ipmi_io_res[i] == NULL) { + device_printf(dev, + "couldn't configure I/O resource\n"); + error = ENXIO; + sc->ipmi_io_rid = 0; + goto bad; + } + } + sc->ipmi_io_rid = 0; + } + + if (info.irq != 0) { + sc->ipmi_irq_rid = 0; + sc->ipmi_irq_res = bus_alloc_resource(dev, SYS_RES_IRQ, + &sc->ipmi_irq_rid, info.irq, info.irq, 1, + RF_SHAREABLE | RF_ACTIVE); + } + + switch (info.iface_type) { + case KCS_MODE: + error = ipmi_kcs_attach(sc); + if (error) + goto bad; + break; + case SMIC_MODE: + error = ipmi_smic_attach(sc); + if (error) + goto bad; + break; + } + + error = ipmi_attach(dev); + if (error) + goto bad; + + return (0); +bad: + ipmi_release_resources(dev); + return (error); +} + +static device_method_t ipmi_methods[] = { + /* Device interface */ + DEVMETHOD(device_identify, ipmi_isa_identify), + DEVMETHOD(device_probe, ipmi_isa_probe), + DEVMETHOD(device_attach, ipmi_isa_attach), + DEVMETHOD(device_detach, ipmi_detach), + { 0, 0 } +}; + +static driver_t ipmi_isa_driver = { + "ipmi", + ipmi_methods, + sizeof(struct ipmi_softc), +}; + +DRIVER_MODULE(ipmi_isa, isa, ipmi_isa_driver, ipmi_devclass, 0, 0); diff --git a/sys/dev/ipmi/ipmi_kcs.c b/sys/dev/ipmi/ipmi_kcs.c new file mode 100644 index 000000000000..8ad606e9b5ae --- /dev/null +++ b/sys/dev/ipmi/ipmi_kcs.c @@ -0,0 +1,607 @@ +/*- + * 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/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 kcs_clear_obf(struct ipmi_softc *, int); +static void kcs_error(struct ipmi_softc *); +static int kcs_wait_for_ibf(struct ipmi_softc *, int); +static int kcs_wait_for_obf(struct ipmi_softc *, int); + +static int +kcs_wait_for_ibf(struct ipmi_softc *sc, int state) +{ + int status, start = ticks; + + status = INB(sc, KCS_CTL_STS); + if (state == 0) { + /* WAIT FOR IBF = 0 */ + while (ticks - start < MAX_TIMEOUT && status & KCS_STATUS_IBF) { + DELAY(100); + status = INB(sc, KCS_CTL_STS); + } + } else { + /* WAIT FOR IBF = 1 */ + while (ticks - start < MAX_TIMEOUT && + !(status & KCS_STATUS_IBF)) { + DELAY(100); + status = INB(sc, KCS_CTL_STS); + } + } + return (status); +} + +static int +kcs_wait_for_obf(struct ipmi_softc *sc, int state) +{ + int status, start = ticks; + + status = INB(sc, KCS_CTL_STS); + if (state == 0) { + /* WAIT FOR OBF = 0 */ + while (ticks - start < MAX_TIMEOUT && status & KCS_STATUS_OBF) { + DELAY(100); + status = INB(sc, KCS_CTL_STS); + } + } else { + /* WAIT FOR OBF = 1 */ + while (ticks - start < MAX_TIMEOUT && + !(status & KCS_STATUS_OBF)) { + DELAY(100); + status = INB(sc, KCS_CTL_STS); + } + } + return (status); +} + +static void +kcs_clear_obf(struct ipmi_softc *sc, int status) +{ + int data; + + /* Clear OBF */ + if (status & KCS_STATUS_OBF) { + data = INB(sc, KCS_DATA); + } +} + +static void +kcs_error(struct ipmi_softc *sc) +{ + int retry, status; + u_char data; + + for (retry = 0; retry < 2; retry++) { + + /* Wait for IBF = 0 */ + status = kcs_wait_for_ibf(sc, 0); + + /* ABORT */ + OUTB(sc, KCS_CTL_STS, KCS_CONTROL_GET_STATUS_ABORT); + + /* Wait for IBF = 0 */ + status = kcs_wait_for_ibf(sc, 0); + + /* Clear OBF */ + kcs_clear_obf(sc, status); + + if (status & KCS_STATUS_OBF) { + data = INB(sc, KCS_DATA); + if (data != 0) + device_printf(sc->ipmi_dev, + "KCS Error Data %02x\n", data); + } + + /* 0x00 to DATA_IN */ + OUTB(sc, KCS_DATA, 0x00); + + /* Wait for IBF = 0 */ + status = kcs_wait_for_ibf(sc, 0); + + if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_READ) { + + /* Wait for OBF = 1 */ + status = kcs_wait_for_obf(sc, 1); + + /* Read error status */ + data = INB(sc, KCS_DATA); + if (data != 0) + device_printf(sc->ipmi_dev, "KCS error: %02x\n", + data); + + /* Write READ into Data_in */ + OUTB(sc, KCS_DATA, KCS_DATA_IN_READ); + + /* Wait for IBF = 0 */ + status = kcs_wait_for_ibf(sc, 0); + } + + /* IDLE STATE */ + if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_IDLE) { + /* Wait for OBF = 1 */ + status = kcs_wait_for_obf(sc, 1); + + /* Clear OBF */ + kcs_clear_obf(sc, status); + return; + } + } + device_printf(sc->ipmi_dev, "KCS Error retry exhausted\n"); +} + +/* + * Start to write a request. Waits for IBF to clear and then sends the + * WR_START command. + */ +static int +kcs_start_write(struct ipmi_softc *sc) +{ + int retry, status; + + for (retry = 0; retry < 10; retry++) { + /* Wait for IBF = 0 */ + status = kcs_wait_for_ibf(sc, 0); + + /* Clear OBF */ + kcs_clear_obf(sc, status); + + /* Write start to command */ + OUTB(sc, KCS_CTL_STS, KCS_CONTROL_WRITE_START); + + /* Wait for IBF = 0 */ + status = kcs_wait_for_ibf(sc, 0); + if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_WRITE) + break; + DELAY(1000000); + } + + if (KCS_STATUS_STATE(status) != KCS_STATUS_STATE_WRITE) + /* error state */ + return (0); + + /* Clear OBF */ + kcs_clear_obf(sc, status); + + return (1); +} + +/* + * Write a byte of the request message, excluding the last byte of the + * message which requires special handling. + */ +static int +kcs_write_byte(struct ipmi_softc *sc, u_char data) +{ + int status; + + /* Data to Data */ + OUTB(sc, KCS_DATA, data); + + /* Wait for IBF = 0 */ + status = kcs_wait_for_ibf(sc, 0); + + if (KCS_STATUS_STATE(status) != KCS_STATUS_STATE_WRITE) + return (0); + + /* Clear OBF */ + kcs_clear_obf(sc, status); + return (1); +} + +/* + * Write the last byte of a request message. + */ +static int +kcs_write_last_byte(struct ipmi_softc *sc, u_char data) +{ + int status; + + /* Write end to command */ + OUTB(sc, KCS_CTL_STS, KCS_CONTROL_WRITE_END); + + /* Wait for IBF = 0 */ + status = kcs_wait_for_ibf(sc, 0); + + if (KCS_STATUS_STATE(status) != KCS_STATUS_STATE_WRITE) + /* error state */ + return (0); + + /* Clear OBF */ + kcs_clear_obf(sc, status); + + /* Send data byte to DATA. */ + OUTB(sc, KCS_DATA, data); + return (1); +} + +/* + * Read one byte of the reply message. + */ +static int +kcs_read_byte(struct ipmi_softc *sc, u_char *data) +{ + int status; + u_char dummy; + + /* Wait for IBF = 0 */ + status = kcs_wait_for_ibf(sc, 0); + + /* Read State */ + if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_READ) { + + /* Wait for OBF = 1 */ + status = kcs_wait_for_obf(sc, 1); + + /* Read Data_out */ + *data = INB(sc, KCS_DATA); + + /* Write READ into Data_in */ + OUTB(sc, KCS_DATA, KCS_DATA_IN_READ); + return (1); + } + + /* Idle State */ + if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_IDLE) { + + /* Wait for OBF = 1*/ + status = kcs_wait_for_obf(sc, 1); + + /* Read Dummy */ + dummy = INB(sc, KCS_DATA); + return (2); + } + + /* Error State */ + return (0); +} + +/* + * Send a request message and collect the reply. Returns true if we + * succeed. + */ +static int +kcs_polled_request(struct ipmi_softc *sc, struct ipmi_request *req) +{ + u_char *cp, data; + int i, state; + + /* Send the request. */ + if (!kcs_start_write(sc)) { + device_printf(sc->ipmi_dev, "KCS: Failed to start write\n"); + goto fail; + } +#ifdef KCS_DEBUG + device_printf(sc->ipmi_dev, "KCS: WRITE_START... ok\n"); +#endif + + if (!kcs_write_byte(sc, req->ir_addr)) { + device_printf(sc->ipmi_dev, "KCS: Failed to write address\n"); + goto fail; + } +#ifdef KCS_DEBUG + device_printf(sc->ipmi_dev, "KCS: Wrote address: %02x\n", req->ir_addr); +#endif + + if (req->ir_requestlen == 0) { + if (!kcs_write_last_byte(sc, req->ir_command)) { + device_printf(sc->ipmi_dev, + "KCS: Failed to write command\n"); + goto fail; + } +#ifdef KCS_DEBUG + device_printf(sc->ipmi_dev, "KCS: Wrote command: %02x\n", + req->ir_command); +#endif + } else { + if (!kcs_write_byte(sc, req->ir_command)) { + device_printf(sc->ipmi_dev, + "KCS: Failed to write command\n"); + goto fail; + } +#ifdef KCS_DEBUG + device_printf(sc->ipmi_dev, "KCS: Wrote command: %02x\n", + req->ir_command); +#endif + + cp = req->ir_request; + for (i = 0; i < req->ir_requestlen - 1; i++) { + if (!kcs_write_byte(sc, *cp++)) { + device_printf(sc->ipmi_dev, + "KCS: Failed to write data byte %d\n", + i + 1); + goto fail; + } +#ifdef KCS_DEBUG + device_printf(sc->ipmi_dev, "KCS: Wrote data: %02x\n", + cp[-1]); +#endif + } + + if (!kcs_write_last_byte(sc, *cp)) { + device_printf(sc->ipmi_dev, + "KCS: Failed to write last dta byte\n"); + goto fail; + } +#ifdef KCS_DEBUG + device_printf(sc->ipmi_dev, "KCS: Wrote last data: %02x\n", + *cp); +#endif + } + + /* Read the reply. First, read the NetFn/LUN. */ + if (kcs_read_byte(sc, &data) != 1) { + device_printf(sc->ipmi_dev, "KCS: Failed to read address\n"); + goto fail; + } +#ifdef KCS_DEBUG + device_printf(sc->ipmi_dev, "KCS: Read address: %02x\n", data); +#endif + if (data != IPMI_REPLY_ADDR(req->ir_addr)) { + device_printf(sc->ipmi_dev, "KCS: Reply address mismatch\n"); + goto fail; + } + + /* Next we read the command. */ + if (kcs_read_byte(sc, &data) != 1) { + device_printf(sc->ipmi_dev, "KCS: Failed to read command\n"); + goto fail; + } +#ifdef KCS_DEBUG + device_printf(sc->ipmi_dev, "KCS: Read command: %02x\n", data); +#endif + if (data != req->ir_command) { + device_printf(sc->ipmi_dev, "KCS: Command mismatch\n"); + goto fail; + } + + /* Next we read the completion code. */ + if (kcs_read_byte(sc, &req->ir_compcode) != 1) { + device_printf(sc->ipmi_dev, + "KCS: Failed to read completion code\n"); + goto fail; + } +#ifdef KCS_DEBUG + device_printf(sc->ipmi_dev, "KCS: Read completion code: %02x\n", + req->ir_compcode); +#endif + + /* Finally, read the reply from the BMC. */ + i = 0; + for (;;) { + state = kcs_read_byte(sc, &data); + if (state == 0) { + device_printf(sc->ipmi_dev, + "KCS: Read failed on byte %d\n", i + 1); + goto fail; + } + if (state == 2) + break; + if (i < req->ir_replybuflen) { + req->ir_reply[i] = data; +#ifdef KCS_DEBUG + device_printf(sc->ipmi_dev, "KCS: Read data %02x\n", + data); + } else { + device_printf(sc->ipmi_dev, + "KCS: Read short %02x byte %d\n", data, i + 1); +#endif + } + i++; + } + req->ir_replylen = i; +#ifdef KCS_DEBUG + device_printf(sc->ipmi_dev, "KCS: READ finished (%d bytes)\n", i); + if (req->ir_replybuflen < i) +#else + if (req->ir_replybuflen < i && req->ir_replybuflen != 0) +#endif + device_printf(sc->ipmi_dev, + "KCS: Read short: %zd buffer, %d actual\n", + req->ir_replybuflen, i); + return (1); +fail: + kcs_error(sc); + return (0); +} + +static void +kcs_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 = kcs_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 +kcs_startup(struct ipmi_softc *sc) +{ + + return (kthread_create(kcs_loop, sc, &sc->ipmi_kthread, 0, 0, "%s: kcs", + device_get_nameunit(sc->ipmi_dev))); +} + +int +ipmi_kcs_attach(struct ipmi_softc *sc) +{ + int status; + + /* Setup function pointers. */ + sc->ipmi_startup = kcs_startup; + sc->ipmi_enqueue_request = ipmi_polled_enqueue_request; + + /* See if we can talk to the controller. */ + status = INB(sc, KCS_CTL_STS); + if (status == 0xff) { + device_printf(sc->ipmi_dev, "couldn't find it\n"); + return (ENXIO); + } + +#ifdef KCS_DEBUG + device_printf(sc->ipmi_dev, "KCS: initial state: %02x\n", status); +#endif + if (status & KCS_STATUS_OBF || + KCS_STATUS_STATE(status) != KCS_STATUS_STATE_IDLE) + kcs_error(sc); + + return (0); +} + +/* + * Determine the alignment automatically for a PCI attachment. In this case, + * any unused bytes will return 0x00 when read. We make use of the C/D bit + * in the CTL_STS register to try to start a GET_STATUS transaction. When + * we write the command, that bit should be set, so we should get a non-zero + * value back when we read CTL_STS if the offset we are testing is the CTL_STS + * register. + */ +int +ipmi_kcs_probe_align(struct ipmi_softc *sc) +{ + int data, status; + + sc->ipmi_io_spacing = 1; +retry: +#ifdef KCS_DEBUG + device_printf(sc->ipmi_dev, "Trying KCS align %d... ", sc->ipmi_io_spacing); +#endif + + /* Wait for IBF = 0 */ + status = INB(sc, KCS_CTL_STS); + while (status & KCS_STATUS_IBF) { + DELAY(100); + status = INB(sc, KCS_CTL_STS); + } + + OUTB(sc, KCS_CTL_STS, KCS_CONTROL_GET_STATUS_ABORT); + + /* Wait for IBF = 0 */ + status = INB(sc, KCS_CTL_STS); + while (status & KCS_STATUS_IBF) { + DELAY(100); + status = INB(sc, KCS_CTL_STS); + } + + /* If we got 0x00 back, then this must not be the CTL_STS register. */ + if (status == 0) { +#ifdef KCS_DEBUG + printf("failed\n"); +#endif + sc->ipmi_io_spacing <<= 1; + if (sc->ipmi_io_spacing > 4) + return (0); + goto retry; + } +#ifdef KCS_DEBUG + printf("ok\n"); +#endif + + /* Finish out the transaction. */ + + /* Clear OBF */ + if (status && KCS_STATUS_OBF) + data = INB(sc, KCS_DATA); + + /* 0x00 to DATA_IN */ + OUTB(sc, KCS_DATA, 0); + + /* Wait for IBF = 0 */ + status = INB(sc, KCS_CTL_STS); + while (status & KCS_STATUS_IBF) { + DELAY(100); + status = INB(sc, KCS_CTL_STS); + } + + if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_READ) { + /* Wait for IBF = 1 */ + while (!(status & KCS_STATUS_OBF)) { + DELAY(100); + status = INB(sc, KCS_CTL_STS); + } + + /* Read error status. */ + data = INB(sc, KCS_DATA); + + /* Write dummy READ to DATA_IN. */ + OUTB(sc, KCS_DATA, KCS_DATA_IN_READ); + + /* Wait for IBF = 0 */ + status = INB(sc, KCS_CTL_STS); + while (status & KCS_STATUS_IBF) { + DELAY(100); + status = INB(sc, KCS_CTL_STS); + } + } + + if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_IDLE) { + /* Wait for IBF = 1 */ + while (!(status & KCS_STATUS_OBF)) { + DELAY(100); + status = INB(sc, KCS_CTL_STS); + } + + /* Clear OBF */ + if (status && KCS_STATUS_OBF) + data = INB(sc, KCS_DATA); + } else + device_printf(sc->ipmi_dev, "KCS probe: end state %x\n", + KCS_STATUS_STATE(status)); + + return (1); +} diff --git a/sys/dev/ipmi/ipmi_pci.c b/sys/dev/ipmi/ipmi_pci.c new file mode 100644 index 000000000000..def00c9947ed --- /dev/null +++ b/sys/dev/ipmi/ipmi_pci.c @@ -0,0 +1,295 @@ +/*- + * 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/module.h> +#include <sys/rman.h> +#include <sys/selinfo.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 struct ipmi_ident +{ + u_int16_t vendor; + u_int16_t device; + char *desc; +} ipmi_identifiers[] = { + {0x1028, 0x000d, "Dell PE2650 SMIC interface"}, + {0, 0, 0} +}; + +const char * +ipmi_pci_match(uint16_t vendor, uint16_t device) +{ + struct ipmi_ident *m; + + for (m = ipmi_identifiers; m->vendor != 0; m++) + if (m->vendor == vendor && m->device == device) + return (m->desc); + return (NULL); +} + +static int +ipmi_pci_probe(device_t dev) +{ + const char *desc; + + if (ipmi_attached) + return (ENXIO); + + desc = ipmi_pci_match(pci_get_vendor(dev), pci_get_device(dev)); + if (desc != NULL) { + device_set_desc(dev, desc); + return (BUS_PROBE_DEFAULT); + } + + return (ENXIO); +} + +static int +ipmi_pci_attach(device_t dev) +{ + struct ipmi_softc *sc = device_get_softc(dev); + struct ipmi_get_info info; + const char *mode; + int error, type; + + /* Look for an IPMI entry in the SMBIOS table. */ + if (!ipmi_smbios_identify(&info)) + return (ENXIO); + + sc->ipmi_dev = dev; + + switch (info.iface_type) { + case KCS_MODE: + mode = "KCS"; + break; + case SMIC_MODE: + mode = "SMIC"; + break; + case BT_MODE: + device_printf(dev, "BT mode is unsupported\n"); + return (ENXIO); + default: + device_printf(dev, "No IPMI interface found\n"); + return (ENXIO); + } + + device_printf(dev, "%s mode found at %s 0x%jx alignment 0x%x on %s\n", + mode, info.io_mode ? "io" : "mem", + (uintmax_t)info.address, info.offset, + device_get_name(device_get_parent(dev))); + if (info.io_mode) + type = SYS_RES_IOPORT; + else + type = SYS_RES_MEMORY; + + sc->ipmi_io_rid = PCIR_BAR(0); + sc->ipmi_io_res[0] = bus_alloc_resource_any(dev, type, + &sc->ipmi_io_rid, RF_ACTIVE); + sc->ipmi_io_type = type; + sc->ipmi_io_spacing = info.offset; + + if (sc->ipmi_io_res[0] == NULL) { + device_printf(dev, "couldn't configure pci io res\n"); + return (ENXIO); + } + + sc->ipmi_irq_rid = 0; + sc->ipmi_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, + &sc->ipmi_irq_rid, RF_SHAREABLE | RF_ACTIVE); + + switch (info.iface_type) { + case KCS_MODE: + error = ipmi_kcs_attach(sc); + if (error) + goto bad; + break; + case SMIC_MODE: + error = ipmi_smic_attach(sc); + if (error) + goto bad; + break; + } + error = ipmi_attach(dev); + if (error) + goto bad; + + return (0); +bad: + ipmi_release_resources(dev); + return (error); +} + +static device_method_t ipmi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ipmi_pci_probe), + DEVMETHOD(device_attach, ipmi_pci_attach), + DEVMETHOD(device_detach, ipmi_detach), + { 0, 0 } +}; + +static driver_t ipmi_pci_driver = { + "ipmi", + ipmi_methods, + sizeof(struct ipmi_softc) +}; + +DRIVER_MODULE(ipmi_pci, pci, ipmi_pci_driver, ipmi_devclass, 0, 0); + +/* Native IPMI on PCI driver. */ + +static int +ipmi2_pci_probe(device_t dev) +{ + + if (pci_get_class(dev) == PCIC_SERIALBUS && + pci_get_subclass(dev) == 0x07) { + device_set_desc(dev, "IPMI System Interface"); + return (BUS_PROBE_GENERIC); + } + + return (ENXIO); +} + +static int +ipmi2_pci_attach(device_t dev) +{ + struct ipmi_softc *sc; + int error, iface, type; + + sc = device_get_softc(dev); + sc->ipmi_dev = dev; + + /* Interface is determined by progif. */ + switch (pci_get_progif(dev)) { + case 0: + iface = SMIC_MODE; + break; + case 1: + iface = KCS_MODE; + break; + case 2: + iface = BT_MODE; + device_printf(dev, "BT interface unsupported\n"); + return (ENXIO); + default: + device_printf(dev, "Unsupported interface: %d\n", + pci_get_progif(dev)); + return (ENXIO); + } + + /* + * Bottom bit of bar indicates resouce type. There should be + * constants in pcireg.h for fields in a BAR. + */ + sc->ipmi_io_rid = PCIR_BAR(0); + if (pci_read_config(dev, PCIR_BAR(0), 4) & 0x1) + type = SYS_RES_IOPORT; + else + type = SYS_RES_MEMORY; + sc->ipmi_io_type = type; + sc->ipmi_io_spacing = 1; + sc->ipmi_io_res[0] = bus_alloc_resource_any(dev, type, + &sc->ipmi_io_rid, RF_ACTIVE); + if (sc->ipmi_io_res[0] == NULL) { + device_printf(dev, "couldn't map ports/memory\n"); + return (ENXIO); + } + + sc->ipmi_irq_rid = 0; + sc->ipmi_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, + &sc->ipmi_irq_rid, RF_SHAREABLE | RF_ACTIVE); + + switch (iface) { + case KCS_MODE: + device_printf(dev, "using KSC interface\n"); + + /* + * We have to examine the resource directly to determine the + * alignment. + */ + if (!ipmi_kcs_probe_align(sc)) { + device_printf(dev, "Unable to determine alignment\n"); + error = ENXIO; + goto bad; + } + + error = ipmi_kcs_attach(sc); + if (error) + goto bad; + break; + case SMIC_MODE: + device_printf(dev, "using SMIC interface\n"); + + error = ipmi_smic_attach(sc); + if (error) + goto bad; + break; + } + error = ipmi_attach(dev); + if (error) + goto bad; + + return (0); +bad: + ipmi_release_resources(dev); + return (error); +} + +static device_method_t ipmi2_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ipmi2_pci_probe), + DEVMETHOD(device_attach, ipmi2_pci_attach), + DEVMETHOD(device_detach, ipmi_detach), + { 0, 0 } +}; + +static driver_t ipmi2_pci_driver = { + "ipmi", + ipmi2_methods, + sizeof(struct ipmi_softc) +}; + +DRIVER_MODULE(ipmi2_pci, pci, ipmi2_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 000000000000..dbc11ed5c34e --- /dev/null +++ b/sys/dev/ipmi/ipmi_smbios.c @@ -0,0 +1,314 @@ +/*- + * 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/selinfo.h> + +#include <vm/vm.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 + +#if __FreeBSD_version < 700020 +#define pmap_mapbios pmap_mapdev +#define pmap_unmapbios pmap_unmapdev +#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_entry { + 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; +}; + +/* Fields in the base_address field of an IPMI entry. */ +#define IPMI_BAR_MODE(ba) ((ba) & 0x0000000000000001) +#define IPMI_BAR_ADDR(ba) ((ba) & 0xfffffffffffffffe) + +/* Fields in the base_address_modifier field of an IPMI entry. */ +#define IPMI_BAM_IRQ_TRIGGER 0x01 +#define IPMI_BAM_IRQ_POLARITY 0x02 +#define IPMI_BAM_IRQ_VALID 0x08 +#define IPMI_BAM_ADDR_LSB(bam) (((bam) & 0x10) >> 4) +#define IPMI_BAM_REG_SPACING(bam) (((bam) & 0xc0) >> 6) +#define SPACING_8 0x0 +#define SPACING_32 0x1 +#define SPACING_16 0x2 + +#define SMBIOS_START 0xf0000 +#define SMBIOS_STEP 0x10 +#define SMBIOS_OFF 0 +#define SMBIOS_LEN 4 +#define SMBIOS_SIG "_SM_" + +typedef void (*dispatchproc_t)(uint8_t *p, char **table, + struct ipmi_get_info *info); + +static struct ipmi_get_info ipmi_info; +static int ipmi_probed; +static struct mtx ipmi_info_mtx; +MTX_SYSINIT(ipmi_info, &ipmi_info_mtx, "ipmi info", MTX_DEF); + +static char *get_strings(char *, char **); +static void ipmi_smbios_probe(struct ipmi_get_info *); +static int smbios_cksum (struct smbios_table_entry *); +static void smbios_run_table(uint8_t *, int, dispatchproc_t *, + struct ipmi_get_info *); +static void smbios_t38_proc_info(uint8_t *, char **, + struct ipmi_get_info *); + +static void +smbios_t38_proc_info(uint8_t *p, char **table, struct ipmi_get_info *info) +{ + struct ipmi_entry *s = (struct ipmi_entry *)p; + + bzero(info, sizeof(struct ipmi_get_info)); + switch (s->interface_type) { + case KCS_MODE: + case SMIC_MODE: + info->address = IPMI_BAR_ADDR(s->base_address) | + IPMI_BAM_ADDR_LSB(s->base_address_modifier); + info->io_mode = IPMI_BAR_MODE(s->base_address); + switch (IPMI_BAM_REG_SPACING(s->base_address_modifier)) { + case SPACING_8: + info->offset = 1; + break; + case SPACING_32: + info->offset = 4; + break; + case SPACING_16: + info->offset = 2; + break; + default: + printf("SMBIOS: Invalid register spacing\n"); + return; + } + break; + case SSIF_MODE: + if ((s->base_address & 0xffffffffffffff00) != 0) { + printf("SMBIOS: Invalid SSIF SMBus address, using BMC I2C slave address instead\n"); + info->address = s->i2c_slave_address >> 1; + break; + } + info->address = IPMI_BAR_ADDR(s->base_address) >> 1; + break; + default: + return; + } + if (s->length > offsetof(struct ipmi_entry, interrupt_number)) { + if (s->interrupt_number > 15) + printf("SMBIOS: Non-ISA IRQ %d for IPMI\n", + s->interrupt_number); + else + info->irq = s->interrupt_number; + } + info->iface_type = s->interface_type; +} + +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; + } +} + +/* + * Walk the SMBIOS table looking for an IPMI (type 38) entry. If we find + * one, return the parsed data in the passed in ipmi_get_info structure and + * return true. If we don't find one, return false. + */ +static void +ipmi_smbios_probe(struct ipmi_get_info *info) +{ + dispatchproc_t dispatch_smbios_ipmi[256]; + struct smbios_table_entry *header; + void *table; + u_int32_t addr; + + bzero(info, sizeof(struct ipmi_get_info)); + + /* Find the SMBIOS table header. */ + addr = bios_sigsearch(SMBIOS_START, SMBIOS_SIG, SMBIOS_LEN, + SMBIOS_STEP, SMBIOS_OFF); + if (addr == 0) + return; + + /* + * Map the header. We first map a fixed size to get the actual + * length and then map it a second time with the actual length so + * we can verify the checksum. + */ + header = pmap_mapbios(addr, sizeof(struct smbios_table_entry)); + table = pmap_mapbios(addr, header->length); + pmap_unmapbios((vm_offset_t)header, sizeof(struct smbios_table_entry)); + header = table; + if (smbios_cksum(header) != 0) { + pmap_unmapbios((vm_offset_t)header, header->length); + return; + } + + /* Now map the actual table and walk it looking for an IPMI entry. */ + table = pmap_mapbios(header->structure_table_address, + header->structure_table_length); + bzero((void *)dispatch_smbios_ipmi, sizeof(dispatch_smbios_ipmi)); + dispatch_smbios_ipmi[38] = (void *)smbios_t38_proc_info; + smbios_run_table(table, header->number_structures, dispatch_smbios_ipmi, + info); + + /* Unmap everything. */ + pmap_unmapbios((vm_offset_t)table, header->structure_table_length); + pmap_unmapbios((vm_offset_t)header, header->length); +} + +/* + * Return the SMBIOS IPMI table entry info to the caller. If we haven't + * searched the IPMI table yet, search it. Otherwise, return a cached + * copy of the data. + */ +int +ipmi_smbios_identify(struct ipmi_get_info *info) +{ + + mtx_lock(&ipmi_info_mtx); + switch (ipmi_probed) { + case 0: + /* Need to probe the SMBIOS table. */ + ipmi_probed++; + mtx_unlock(&ipmi_info_mtx); + ipmi_smbios_probe(&ipmi_info); + mtx_lock(&ipmi_info_mtx); + ipmi_probed++; + wakeup(&ipmi_info); + break; + case 1: + /* Another thread is currently probing the table, so wait. */ + while (ipmi_probed == 1) + msleep(&ipmi_info, &ipmi_info_mtx, 0, "ipmi info", 0); + break; + default: + /* The cached data is available. */ + break; + } + + bcopy(&ipmi_info, info, sizeof(ipmi_info)); + mtx_unlock(&ipmi_info_mtx); + + return (info->iface_type != 0); +} + +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); +} diff --git a/sys/dev/ipmi/ipmi_smbus.c b/sys/dev/ipmi/ipmi_smbus.c new file mode 100644 index 000000000000..3798a628d26f --- /dev/null +++ b/sys/dev/ipmi/ipmi_smbus.c @@ -0,0 +1,131 @@ +/*- + * 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/module.h> +#include <sys/rman.h> +#include <sys/selinfo.h> + +#include <dev/smbus/smbconf.h> +#include <dev/smbus/smbus.h> +#include <dev/smbus/smb.h> + +#include "smbus_if.h" + +#ifdef LOCAL_MODULE +#include <ipmivars.h> +#else +#include <dev/ipmi/ipmivars.h> +#endif + +static void ipmi_smbus_identify(driver_t *driver, device_t parent); +static int ipmi_smbus_probe(device_t dev); +static int ipmi_smbus_attach(device_t dev); + +static void +ipmi_smbus_identify(driver_t *driver, device_t parent) +{ + struct ipmi_get_info info; + + if (ipmi_smbios_identify(&info) && info.iface_type == SSIF_MODE && + device_find_child(parent, "ipmi", -1) == NULL) + BUS_ADD_CHILD(parent, 0, "ipmi", -1); +} + +static int +ipmi_smbus_probe(device_t dev) +{ + + device_set_desc(dev, "IPMI System Interface"); + return (BUS_PROBE_DEFAULT); +} + +static int +ipmi_smbus_attach(device_t dev) +{ + struct ipmi_softc *sc = device_get_softc(dev); + struct ipmi_get_info info; + int error; + + /* This should never fail. */ + if (!ipmi_smbios_identify(&info)) + return (ENXIO); + + if (info.iface_type != SSIF_MODE) { + device_printf(dev, "No SSIF IPMI interface found\n"); + return (ENXIO); + } + + sc->ipmi_dev = dev; + + if (info.irq != 0) { + sc->ipmi_irq_rid = 0; + sc->ipmi_irq_res = bus_alloc_resource(dev, SYS_RES_IRQ, + &sc->ipmi_irq_rid, info.irq, info.irq, 1, + RF_SHAREABLE | RF_ACTIVE); + } + + device_printf(dev, "SSIF mode found at address 0x%llx on %s\n", + (long long)info.address, device_get_name(device_get_parent(dev))); + error = ipmi_ssif_attach(sc, device_get_parent(dev), info.address); + if (error) + goto bad; + + error = ipmi_attach(dev); + if (error) + goto bad; + + return (0); +bad: + ipmi_release_resources(dev); + return (error); +} + +static device_method_t ipmi_methods[] = { + /* Device interface */ + DEVMETHOD(device_identify, ipmi_smbus_identify), + DEVMETHOD(device_probe, ipmi_smbus_probe), + DEVMETHOD(device_attach, ipmi_smbus_attach), + DEVMETHOD(device_detach, ipmi_detach), + { 0, 0 } +}; + +static driver_t ipmi_smbus_driver = { + "ipmi", + ipmi_methods, + sizeof(struct ipmi_softc) +}; + +DRIVER_MODULE(ipmi_smbus, smbus, ipmi_smbus_driver, ipmi_devclass, 0, 0); +MODULE_DEPEND(ipmi_smbus, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER); diff --git a/sys/dev/ipmi/ipmi_smic.c b/sys/dev/ipmi/ipmi_smic.c new file mode 100644 index 000000000000..c1c69b3923cd --- /dev/null +++ b/sys/dev/ipmi/ipmi_smic.c @@ -0,0 +1,407 @@ +/*- + * 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; + + smic_wait_for_tx_okay(sc); + OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_NEXT); + 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; + + smic_wait_for_tx_okay(sc); + OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_END); + 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); + + smic_wait_for_rx_okay(sc); + OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_START); + 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; + + smic_wait_for_rx_okay(sc); + OUTB(sc, SMIC_CTL_STS, SMIC_SC_SMS_RD_NEXT); + 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); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: WRITE_START address: %02x\n", + req->ir_addr); +#endif + + if (req->ir_requestlen == 0) { + /* Send the command as the last byte. */ + if (!smic_write_last(sc, req->ir_command)) + return (0); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Wrote command: %02x\n", + req->ir_command); +#endif + } else { + /* Send the command. */ + if (!smic_write_next(sc, req->ir_command)) + return (0); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Wrote command: %02x\n", + req->ir_command); +#endif + + /* Send the payload. */ + cp = req->ir_request; + for (i = 0; i < req->ir_requestlen - 1; i++) { + if (!smic_write_next(sc, *cp++)) + return (0); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Wrote data: %02x\n", + cp[-1]); +#endif + } + if (!smic_write_last(sc, *cp)) + return (0); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Write last data: %02x\n", + *cp); +#endif + } + + /* Start the read phase by reading the NetFn/LUN. */ + if (smic_start_read(sc, &data) != 1) + return (0); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Read address: %02x\n", data); +#endif + 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); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Read command: %02x\n", data); +#endif + 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); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Read completion code: %02x\n", + req->ir_compcode); +#endif + + /* Finally, read the reply from the BMC. */ + i = 0; + while (state == 1) { + state = smic_read_byte(sc, &data); + if (state == 0) + return (0); + if (i < req->ir_replybuflen) { + req->ir_reply[i] = data; +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Read data: %02x\n", + data); + } else { + device_printf(sc->ipmi_dev, + "SMIC: Read short %02x byte %d\n", data, i + 1); +#endif + } + i++; + } + + /* Terminate the transfer. */ + if (!smic_read_end(sc)) + return (0); + req->ir_replylen = i; +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: READ finished (%d bytes)\n", i); + if (req->ir_replybuflen < i) +#else + if (req->ir_replybuflen < i && req->ir_replybuflen != 0) +#endif + 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); + } + +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: initial state: %02x\n", flags); +#endif + + return (0); +} diff --git a/sys/dev/ipmi/ipmi_ssif.c b/sys/dev/ipmi/ipmi_ssif.c new file mode 100644 index 000000000000..2f90b6ef2b18 --- /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); +} diff --git a/sys/dev/ipmi/ipmivars.h b/sys/dev/ipmi/ipmivars.h new file mode 100644 index 000000000000..c56fb88c3849 --- /dev/null +++ b/sys/dev/ipmi/ipmivars.h @@ -0,0 +1,266 @@ +/*- + * 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$ + */ + +#ifndef __IPMIVARS_H__ +#define __IPMIVARS_H__ + +struct ipmi_get_info { + int iface_type; + uint64_t address; + int offset; + int io_mode; + int irq; +}; + +struct ipmi_device; + +struct ipmi_request { + TAILQ_ENTRY(ipmi_request) ir_link; + struct ipmi_device *ir_owner; /* Driver uses NULL. */ + u_char *ir_request; /* Request is data to send to BMC. */ + size_t ir_requestlen; + u_char *ir_reply; /* Reply is data read from BMC. */ + size_t ir_replybuflen; /* Length of ir_reply[] buffer. */ + int ir_replylen; /* Length of reply from BMC. */ + int ir_error; + long ir_msgid; + uint8_t ir_addr; + uint8_t ir_command; + uint8_t ir_compcode; +}; + +#define MAX_RES 3 +#define KCS_DATA 0 +#define KCS_CTL_STS 1 +#define SMIC_DATA 0 +#define SMIC_CTL_STS 1 +#define SMIC_FLAGS 2 + +struct ipmi_softc; + +/* Per struct cdev data. */ +struct ipmi_device { + TAILQ_ENTRY(ipmi_device) ipmi_link; + TAILQ_HEAD(,ipmi_request) ipmi_completed_requests; + struct selinfo ipmi_select; + struct ipmi_softc *ipmi_softc; + struct cdev *ipmi_cdev; + int ipmi_open; + int ipmi_closing; + int ipmi_requests; + u_char ipmi_address; /* IPMB address. */ + u_char ipmi_lun; +}; + +struct ipmi_kcs { +}; + +struct ipmi_smic { +}; + +struct ipmi_ssif { + device_t smbus; + int smbus_address; +}; + +struct ipmi_softc { + device_t ipmi_dev; + union { + struct ipmi_kcs kcs; + struct ipmi_smic smic; + struct ipmi_ssif ssif; + } _iface; + int ipmi_io_rid; + int ipmi_io_type; + struct resource *ipmi_io_res[MAX_RES]; + int ipmi_io_spacing; + int ipmi_irq_rid; + struct resource *ipmi_irq_res; + void *ipmi_irq; + int ipmi_detaching; +#ifdef CLONING + int ipmi_cloning; + u_int ipmi_cdev_mask; + TAILQ_HEAD(,ipmi_device) ipmi_cdevs; +#else + struct ipmi_device ipmi_idev; +#endif + TAILQ_HEAD(,ipmi_request) ipmi_pending_requests; +#ifdef CLONING + eventhandler_tag ipmi_clone_tag; +#endif + eventhandler_tag ipmi_watchdog_tag; + struct intr_config_hook ipmi_ich; + struct mtx ipmi_lock; + struct cv ipmi_request_added; + struct proc *ipmi_kthread; + driver_intr_t *ipmi_intr; + int (*ipmi_startup)(struct ipmi_softc *); + int (*ipmi_enqueue_request)(struct ipmi_softc *, struct ipmi_request *); +}; + +#define ipmi_ssif_smbus_address _iface.ssif.smbus_address +#define ipmi_ssif_smbus _iface.ssif.smbus + +struct ipmi_ipmb { + u_char foo; +}; + +#define KCS_MODE 0x01 +#define SMIC_MODE 0x02 +#define BT_MODE 0x03 +#define SSIF_MODE 0x04 + +/* 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_OK 0x00 +#define KCS_IFACE_STATUS_ABORT 0x01 +#define KCS_IFACE_STATUS_ILLEGAL 0x02 +#define KCS_IFACE_STATUS_LENGTH_ERR 0x06 +#define KCS_IFACE_STATUS_UNKNOWN_ERR 0xff + +/* KCS 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 0x10 /* asserted SMI */ +#define SMIC_STATUS_TX_RDY 0x40 /* Ready to accept WRITE */ +#define SMIC_STATUS_RX_RDY 0x80 /* Ready to read */ +#define SMIC_STATUS_RESERVED 0x22 + +/* 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 IPMI_ADDR(netfn, lun) ((netfn) << 2 | (lun)) +#define IPMI_REPLY_ADDR(addr) ((addr) + 0x4) + +#define IPMI_LOCK(sc) mtx_lock(&(sc)->ipmi_lock) +#define IPMI_UNLOCK(sc) mtx_unlock(&(sc)->ipmi_lock) +#define IPMI_LOCK_ASSERT(sc) mtx_assert(&(sc)->ipmi_lock, MA_OWNED) + +#define ipmi_alloc_driver_request(addr, cmd, reqlen, replylen) \ + ipmi_alloc_request(NULL, 0, (addr), (cmd), (reqlen), (replylen)) + +#if __FreeBSD_version < 601105 +#define bus_read_1(r, o) \ + bus_space_read_1(rman_get_bustag(r), rman_get_bushandle(r), (o)) +#define bus_write_1(r, o, v) \ + bus_space_write_1(rman_get_bustag(r), rman_get_bushandle(r), (o), (v)) +#endif + +/* I/O to a single I/O resource. */ +#define INB_SINGLE(sc, x) \ + bus_read_1((sc)->ipmi_io_res[0], (sc)->ipmi_io_spacing * (x)) +#define OUTB_SINGLE(sc, x, value) \ + bus_write_1((sc)->ipmi_io_res[0], (sc)->ipmi_io_spacing * (x), value) + +/* I/O with each register in its in I/O resource. */ +#define INB_MULTIPLE(sc, x) \ + bus_read_1((sc)->ipmi_io_res[(x)], 0) +#define OUTB_MULTIPLE(sc, x, value) \ + bus_write_1((sc)->ipmi_io_res[(x)], 0, value) + +/* + * Determine I/O method based on whether or not we have more than one I/O + * resource. + */ +#define INB(sc, x) \ + ((sc)->ipmi_io_res[1] != NULL ? INB_MULTIPLE(sc, x) : INB_SINGLE(sc, x)) +#define OUTB(sc, x, value) \ + ((sc)->ipmi_io_res[1] != NULL ? OUTB_MULTIPLE(sc, x, value) : \ + OUTB_SINGLE(sc, x, value)) + +#define MAX_TIMEOUT 3 * hz + +int ipmi_attach(device_t); +int ipmi_detach(device_t); +void ipmi_release_resources(device_t); + +/* Manage requests. */ +struct ipmi_request *ipmi_alloc_request(struct ipmi_device *, long, uint8_t, + uint8_t, size_t, size_t); +void ipmi_complete_request(struct ipmi_softc *, struct ipmi_request *); +struct ipmi_request *ipmi_dequeue_request(struct ipmi_softc *); +void ipmi_free_request(struct ipmi_request *); +int ipmi_polled_enqueue_request(struct ipmi_softc *, struct ipmi_request *); +int ipmi_submit_driver_request(struct ipmi_softc *, struct ipmi_request *, + int); + +/* Identify BMC interface via SMBIOS. */ +int ipmi_smbios_identify(struct ipmi_get_info *); + +/* Match BMC PCI device listed in SMBIOS. */ +const char *ipmi_pci_match(uint16_t, uint16_t); + +/* Interface attach routines. */ +int ipmi_kcs_attach(struct ipmi_softc *); +int ipmi_kcs_probe_align(struct ipmi_softc *); +int ipmi_smic_attach(struct ipmi_softc *); +int ipmi_ssif_attach(struct ipmi_softc *, device_t, int); + +#ifdef IPMB +int ipmi_handle_attn(struct ipmi_softc *); +#endif + +extern devclass_t ipmi_devclass; +extern int ipmi_attached; + +#endif /* !__IPMIVARS_H__ */ |
