freebsd-nq/sys/dev/ipmi/ipmi.c
2006-02-13 16:50:45 +00:00

1097 lines
25 KiB
C

/*-
* Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/poll.h>
#include <sys/selinfo.h>
#include <sys/disk.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <machine/clock.h>
#include <sys/rman.h>
#include <sys/watchdog.h>
#include <sys/sysctl.h>
#ifdef LOCAL_MODULE
#include <ipmi.h>
#include <ipmivars.h>
#else
#include <sys/ipmi.h>
#include <dev/ipmi/ipmivars.h>
#endif
struct ipmi_done_list {
u_char *data;
int channel;
int msgid;
int len;
TAILQ_ENTRY(ipmi_done_list) list;
};
#define MAX_TIMEOUT 3 * hz
static int ipmi_wait_for_ibf(device_t, int);
static int ipmi_wait_for_obf(device_t, int);
static void ipmi_clear_obf(device_t, int);
static void ipmi_error(device_t);
static void ipmi_check_read(device_t);
static int ipmi_write(device_t, u_char *, int);
static void ipmi_wait_for_tx_okay(device_t);
static void ipmi_wait_for_rx_okay(device_t);
static void ipmi_wait_for_not_busy(device_t);
static void ipmi_set_busy(device_t);
static int ipmi_ready_to_read(device_t);
#ifdef IPMB
static int ipmi_handle_attn(device_t dev);
static int ipmi_ipmb_checksum(u_char, int);
static int ipmi_ipmb_send_message(device_t, u_char, u_char, u_char,
u_char, u_char, int)
#endif
static d_ioctl_t ipmi_ioctl;
static d_poll_t ipmi_poll;
static d_open_t ipmi_open;
static d_close_t ipmi_close;
int ipmi_attached = 0;
#define IPMI_MINOR 0
static int on = 1;
SYSCTL_NODE(_hw, OID_AUTO, ipmi, CTLFLAG_RD, 0, "IPMI driver parameters");
SYSCTL_INT(_hw_ipmi, OID_AUTO, on, CTLFLAG_RW,
&on, 0, "");
static struct cdevsw ipmi_cdevsw = {
.d_version = D_VERSION,
.d_flags = D_NEEDGIANT,
.d_open = ipmi_open,
.d_close = ipmi_close,
.d_ioctl = ipmi_ioctl,
.d_poll = ipmi_poll,
.d_name = "ipmi",
};
MALLOC_DEFINE(M_IPMI, "ipmi", "ipmi");
static int
ipmi_open(struct cdev *dev, int flags, int fmt, struct thread *td)
{
struct ipmi_softc *sc;
if (!on)
return ENOENT;
sc = dev->si_drv1;
if (sc->ipmi_refcnt) {
return EBUSY;
}
sc->ipmi_refcnt = 1;
return 0;
}
static int
ipmi_poll(struct cdev *dev, int poll_events, struct thread *td)
{
struct ipmi_softc *sc;
int revents = 0;
sc = dev->si_drv1;
ipmi_check_read(sc->ipmi_dev);
if (poll_events & (POLLIN | POLLRDNORM)) {
if (!TAILQ_EMPTY(&sc->ipmi_done))
revents |= poll_events & (POLLIN | POLLRDNORM);
if (TAILQ_EMPTY(&sc->ipmi_done) && sc->ipmi_requests == 0) {
revents |= POLLERR;
}
}
if (revents == 0) {
if (poll_events & (POLLIN | POLLRDNORM))
selrecord(td, &sc->ipmi_select);
}
return revents;
}
static int
ipmi_close(struct cdev *dev, int flags, int fmt, struct thread *td)
{
struct ipmi_softc *sc;
int error = 0;
sc = dev->si_drv1;
sc->ipmi_refcnt = 0;
return error;
}
#ifdef IPMB
static int
ipmi_ipmb_checksum(u_char *data, int len)
{
u_char sum = 0;
for (; len; len--) {
sum += *data++;
}
return -sum;
}
static int
ipmi_ipmb_send_message(device_t dev, u_char channel, u_char netfn,
u_char command, u_char seq, u_char *data, int data_len)
{
u_char *temp;
struct ipmi_softc *sc = device_get_softc(dev);
int error;
u_char slave_addr = 0x52;
temp = malloc(data_len + 10, M_IPMI, M_WAITOK);
bzero(temp, data_len + 10);
temp[0] = IPMI_APP_REQUEST << 2;
temp[1] = IPMI_SEND_MSG;
temp[2] = channel;
temp[3] = slave_addr;
temp[4] = netfn << 2;
temp[5] = ipmi_ipmb_check_sum(&temp[3], 2);
temp[6] = sc->ipmi_address;
temp[7] = seq << 2 | sc->ipmi_lun;
temp[8] = command;
bcopy(data, &temp[9], data_len);
temp[data_len + 9] = ipmi_ipmb_check(&temp[6], data_len + 3);
ipmi_write(sc->ipmi_dev, temp, data_len + 9);
free(temp, M_IPMI);
while (!ipmi_ready_to_read(dev))
DELAY(1000);
temp = malloc(IPMI_MAX_RX, M_IPMI, M_WAITOK);
bzero(temp, IPMI_MAX_RX);
error = ipmi_read(dev, temp, IPMI_MAX_RX);
free(temp, M_IPMI);
return error;
}
static int
ipmi_handle_attn(device_t dev)
{
u_char temp[IPMI_MAX_RX];
struct ipmi_softc *sc = device_get_softc(dev);
int error;
device_printf(sc->ipmi_dev, "BMC has a message\n");
temp[0] = IPMI_APP_REQUEST << 2;
temp[1] = IPMI_GET_MSG_FLAGS;
ipmi_write(sc->ipmi_dev, temp, 2);
while (!ipmi_ready_to_read(dev))
DELAY(1000);
bzero(temp, IPMI_MAX_RX);
error = ipmi_read(dev, temp, IPMI_MAX_RX);
if (temp[2] == 0) {
if (temp[3] & IPMI_MSG_BUFFER_FULL) {
device_printf(sc->ipmi_dev, "message buffer full");
}
if (temp[3] & IPMI_WDT_PRE_TIMEOUT) {
device_printf(sc->ipmi_dev,
"watchdog about to go off");
}
if (temp[3] & IPMI_MSG_AVAILABLE) {
temp[0] = IPMI_APP_REQUEST << 2;
temp[1] = IPMI_GET_MSG;
ipmi_write(sc->ipmi_dev, temp, 2);
while (!ipmi_ready_to_read(dev))
DELAY(1000);
bzero(temp, IPMI_MAX_RX);
error = ipmi_read(dev, temp, IPMI_MAX_RX);
device_printf(sc->ipmi_dev, "throw out message ");
dump_buf(temp, 16);
}
} else
return -1;
return error;
}
#endif
static int
ipmi_ready_to_read(device_t dev)
{
struct ipmi_softc *sc = device_get_softc(dev);
int status, flags;
if (sc->ipmi_bios_info.smic_mode) {
flags = INB(sc, sc->ipmi_smic_flags);
#ifdef IPMB
if (flags & SMIC_STATUS_SMS_ATN) {
ipmi_handle_attn(dev);
return 0;
}
#endif
if (flags & SMIC_STATUS_RX_RDY)
return 1;
} else if (sc->ipmi_bios_info.kcs_mode) {
status = INB(sc, sc->ipmi_kcs_status_reg);
#ifdef IPMB
if (status & KCS_STATUS_SMS_ATN) {
ipmi_handle_attn(dev);
return 0;
}
#endif
if (status & KCS_STATUS_OBF)
return 1;
} else {
device_printf(dev,"Unsupported mode\n");
}
return 0;
}
void
ipmi_intr(void *arg) {
device_t dev = arg;
ipmi_check_read(dev);
}
static void
ipmi_check_read(device_t dev){
struct ipmi_softc *sc = device_get_softc(dev);
struct ipmi_done_list *item;
int status;
u_char *temp;
if (!sc->ipmi_requests)
return;
untimeout((timeout_t *)ipmi_check_read, dev, sc->ipmi_timeout_handle);
if(ipmi_ready_to_read(dev)) {
sc->ipmi_requests--;
temp = malloc(IPMI_MAX_RX, M_IPMI, M_WAITOK);
bzero(temp, IPMI_MAX_RX);
status = ipmi_read(dev, temp, IPMI_MAX_RX);
item = malloc(sizeof(struct ipmi_done_list), M_IPMI, M_WAITOK);
bzero(item, sizeof(struct ipmi_done_list));
item->data = temp;
item->len = status;
if (ticks - sc->ipmi_timestamp > MAX_TIMEOUT) {
device_printf(dev, "read timeout when ready\n");
TAILQ_INSERT_TAIL(&sc->ipmi_done, item, list);
selwakeup(&sc->ipmi_select);
} else if (status) {
TAILQ_INSERT_TAIL(&sc->ipmi_done, item, list);
selwakeup(&sc->ipmi_select);
}
} else {
if (ticks - sc->ipmi_timestamp > MAX_TIMEOUT) {
sc->ipmi_requests--;
device_printf(dev, "read timeout when not ready\n");
temp = malloc(IPMI_MAX_RX, M_IPMI, M_WAITOK);
bzero(temp, IPMI_MAX_RX);
sc->ipmi_busy = 0;
wakeup(&sc->ipmi_busy);
status = -1;
item = malloc(sizeof(struct ipmi_done_list),
M_IPMI, M_WAITOK);
bzero(item, sizeof(struct ipmi_done_list));
item->data = temp;
item->len = status;
TAILQ_INSERT_TAIL(&sc->ipmi_done, item, list);
selwakeup(&sc->ipmi_select);
}
}
if (sc->ipmi_requests)
sc->ipmi_timeout_handle
= timeout((timeout_t *)ipmi_check_read, dev, hz/30);
}
static int
ipmi_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data,
int flags, struct thread *td)
{
struct ipmi_softc *sc;
struct ipmi_req *req = (struct ipmi_req *)data;
struct ipmi_recv *recv = (struct ipmi_recv *)data;
struct ipmi_addr addr;
struct ipmi_done_list *item;
u_char *temp;
int error, len;
sc = dev->si_drv1;
switch (cmd) {
case IPMICTL_SEND_COMMAND:
/* clear out old stuff in queue of stuff done */
while((item = TAILQ_FIRST(&sc->ipmi_done))) {
TAILQ_REMOVE(&sc->ipmi_done, item, list);
free(item->data, M_IPMI);
free(item, M_IPMI);
}
error = copyin(req->addr, &addr, sizeof(addr));
temp = malloc(req->msg.data_len + 2, M_IPMI, M_WAITOK);
if (temp == NULL) {
return ENOMEM;
}
temp[0] = req->msg.netfn << 2;
temp[1] = req->msg.cmd;
error = copyin(req->msg.data, &temp[2],
req->msg.data_len);
if (error != 0) {
free(temp, M_IPMI);
return error;
}
error = ipmi_write(sc->ipmi_dev,
temp, req->msg.data_len + 2);
free(temp, M_IPMI);
if (error != 1)
return EIO;
sc->ipmi_requests++;
sc->ipmi_timestamp = ticks;
ipmi_check_read(sc->ipmi_dev);
return 0;
case IPMICTL_RECEIVE_MSG_TRUNC:
case IPMICTL_RECEIVE_MSG:
item = TAILQ_FIRST(&sc->ipmi_done);
if (!item) {
return EAGAIN;
}
error = copyin(recv->addr, &addr, sizeof(addr));
if (error != 0)
return error;
TAILQ_REMOVE(&sc->ipmi_done, item, list);
addr.channel = IPMI_BMC_CHANNEL;
recv->recv_type = IPMI_RESPONSE_RECV_TYPE;
recv->msgid = item->msgid;
recv->msg.netfn = item->data[0] >> 2;
recv->msg.cmd = item->data[1];
error = len = item->len;
len -= 2;
if (len < 0)
len = 1;
if (recv->msg.data_len < len && cmd == IPMICTL_RECEIVE_MSG) {
TAILQ_INSERT_HEAD(&sc->ipmi_done, item, list);
return EMSGSIZE;
}
len = min(recv->msg.data_len, len);
recv->msg.data_len = len;
error = copyout(&addr, recv->addr,sizeof(addr));
if (error == 0)
error = copyout(&item->data[2], recv->msg.data, len);
free(item->data, M_IPMI);
free(item, M_IPMI);
if (error != 0)
return error;
return 0;
case IPMICTL_SET_MY_ADDRESS_CMD:
sc->ipmi_address = *(int*)data;
return 0;
case IPMICTL_GET_MY_ADDRESS_CMD:
*(int*)data = sc->ipmi_address;
return 0;
case IPMICTL_SET_MY_LUN_CMD:
sc->ipmi_lun = *(int*)data & 0x3;
return 0;
case IPMICTL_GET_MY_LUN_CMD:
*(int*)data = sc->ipmi_lun;
return 0;
case IPMICTL_SET_GETS_EVENTS_CMD:
/*
device_printf(sc->ipmi_dev,
"IPMICTL_SET_GETS_EVENTS_CMD NA\n");
*/
return 0;
case IPMICTL_REGISTER_FOR_CMD:
case IPMICTL_UNREGISTER_FOR_CMD:
return EOPNOTSUPP;
}
device_printf(sc->ipmi_dev, "Unknown IOCTL %lX\n", cmd);
return ENOIOCTL;
}
static int
ipmi_wait_for_ibf(device_t dev, int state) {
struct ipmi_softc *sc = device_get_softc(dev);
int status, start = ticks;
int first = 1;
if (state == 0) {
/* WAIT FOR IBF = 0 */
do {
if (first)
first =0;
else
DELAY(100);
status = INB(sc, sc->ipmi_kcs_status_reg);
} while (ticks - start < MAX_TIMEOUT
&& status & KCS_STATUS_IBF);
} else {
/* WAIT FOR IBF = 1 */
do {
if (first)
first =0;
else
DELAY(100);
status = INB(sc, sc->ipmi_kcs_status_reg);
} while (ticks - start < MAX_TIMEOUT
&& !(status & KCS_STATUS_IBF));
}
return status;
}
static int
ipmi_wait_for_obf(device_t dev, int state) {
struct ipmi_softc *sc = device_get_softc(dev);
int status, start = ticks;
int first = 1;
if (state == 0) {
/* WAIT FOR OBF = 0 */
do {
if (first)
first = 0;
else
DELAY(100);
status = INB(sc, sc->ipmi_kcs_status_reg);
} while (ticks - start < MAX_TIMEOUT
&& status & KCS_STATUS_OBF);
} else {
/* WAIT FOR OBF = 1 */
do {
if (first)
first =0;
else
DELAY(100);
status = INB(sc, sc->ipmi_kcs_status_reg);
} while (ticks - start < MAX_TIMEOUT
&& !(status & KCS_STATUS_OBF));
}
return status;
}
static void
ipmi_clear_obf(device_t dev, int status) {
struct ipmi_softc *sc = device_get_softc(dev);
int data;
/* Clear OBF */
if (status & KCS_STATUS_OBF) {
data = INB(sc, sc->ipmi_kcs_data_out_reg);
}
}
static void
ipmi_error(device_t dev) {
struct ipmi_softc *sc = device_get_softc(dev);
int status, data = 0;
int retry = 0;
for(;;){
status = ipmi_wait_for_ibf(dev, 0);
/* ABORT */
OUTB(sc, sc->ipmi_kcs_command_reg,
KCS_CONTROL_GET_STATUS_ABORT);
/* Wait for IBF = 0 */
status = ipmi_wait_for_ibf(dev, 0);
/* Clear OBF */
ipmi_clear_obf(dev, status);
if (status & KCS_STATUS_OBF) {
data = INB(sc, sc->ipmi_kcs_data_out_reg);
device_printf(dev, "Data %x\n", data);
}
/* 0x00 to DATA_IN */
OUTB(sc, sc->ipmi_kcs_data_in_reg, 0x00);
/* Wait for IBF = 0 */
status = ipmi_wait_for_ibf(dev, 0);
if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_READ) {
/* Wait for OBF = 1 */
status = ipmi_wait_for_obf(dev, 1);
/* Read error status */
data = INB(sc, sc->ipmi_kcs_data_out_reg);
/* Write READ into Data_in */
OUTB(sc, sc->ipmi_kcs_data_in_reg, KCS_DATA_IN_READ);
/* Wait for IBF = 0 */
status = ipmi_wait_for_ibf(dev, 0);
}
/* IDLE STATE */
if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_IDLE) {
/* Wait for OBF = 1 */
status = ipmi_wait_for_obf(dev, 1);
/* Clear OBF */
ipmi_clear_obf(dev, status);
break;
}
retry++;
if (retry > 2) {
device_printf(dev, "Retry exhausted %x\n", retry);
break;
}
}
}
static void
ipmi_wait_for_tx_okay(device_t dev) {
struct ipmi_softc *sc = device_get_softc(dev);
int flags;
do {
flags = INB(sc, sc->ipmi_smic_flags);
} while(!flags & SMIC_STATUS_TX_RDY);
}
static void
ipmi_wait_for_rx_okay(device_t dev) {
struct ipmi_softc *sc = device_get_softc(dev);
int flags;
do {
flags = INB(sc, sc->ipmi_smic_flags);
} while(!flags & SMIC_STATUS_RX_RDY);
}
static void
ipmi_wait_for_not_busy(device_t dev) {
struct ipmi_softc *sc = device_get_softc(dev);
int flags;
do {
flags = INB(sc, sc->ipmi_smic_flags);
} while(flags & SMIC_STATUS_BUSY);
}
static void
ipmi_set_busy(device_t dev) {
struct ipmi_softc *sc = device_get_softc(dev);
int flags;
flags = INB(sc, sc->ipmi_smic_flags);
flags |= SMIC_STATUS_BUSY;
OUTB(sc, sc->ipmi_smic_flags, flags);
}
int
ipmi_read(device_t dev, u_char *bytes, int len){
struct ipmi_softc *sc = device_get_softc(dev);
int status, flags, data, i = -1, error;
bzero(bytes, len);
if (sc->ipmi_bios_info.smic_mode) {
ipmi_wait_for_not_busy(dev);
do {
flags = INB(sc, sc->ipmi_smic_flags);
} while(!flags & SMIC_STATUS_RX_RDY);
OUTB(sc, sc->ipmi_smic_ctl_sts, SMIC_CC_SMS_RD_START);
ipmi_wait_for_rx_okay(dev);
ipmi_set_busy(dev);
ipmi_wait_for_not_busy(dev);
status = INB(sc, sc->ipmi_smic_ctl_sts);
if (status != SMIC_SC_SMS_RD_START) {
error = INB(sc, sc->ipmi_smic_data);
device_printf(dev, "Read did not start %x %x\n",
status, error);
sc->ipmi_busy = 0;
return -1;
}
for (i = -1; ; len--) {
i++;
data = INB(sc, sc->ipmi_smic_data);
if (len > 0)
*bytes++ = data;
else {
device_printf(dev, "Read short %x\n", data);
break;
}
do {
flags = INB(sc, sc->ipmi_smic_flags);
} while(!flags & SMIC_STATUS_RX_RDY);
OUTB(sc, sc->ipmi_smic_ctl_sts, SMIC_CC_SMS_RD_NEXT);
ipmi_wait_for_rx_okay(dev);
ipmi_set_busy(dev);
ipmi_wait_for_not_busy(dev);
status = INB(sc, sc->ipmi_smic_ctl_sts);
if (status == SMIC_SC_SMS_RD_NEXT) {
continue;
} else if (status == SMIC_SC_SMS_RD_END) {
break;
} else {
device_printf(dev, "Read did not next %x\n",
status);
}
}
i++;
data = INB(sc, sc->ipmi_smic_data);
if (len > 0)
*bytes++ = data;
else
device_printf(dev, "Read short %x\n", data);
OUTB(sc, sc->ipmi_smic_ctl_sts, SMIC_CC_SMS_RD_END);
i++;
} else if (sc->ipmi_bios_info.kcs_mode) {
for (i = -1; ; len--) {
/* Wait for IBF = 0 */
status = ipmi_wait_for_ibf(dev, 0);
/* Read State */
if (KCS_STATUS_STATE(status)
== KCS_STATUS_STATE_READ) {
i++;
/* Wait for OBF = 1 */
status = ipmi_wait_for_obf(dev, 1);
/* Read Data_out */
data = INB(sc, sc->ipmi_kcs_data_out_reg);
if (len > 0)
*bytes++ = data;
else {
device_printf(dev, "Read short %x byte %d\n", data, i);
break;
}
/* Write READ into Data_in */
OUTB(sc, sc->ipmi_kcs_data_in_reg,
KCS_DATA_IN_READ);
/* Idle State */
} else if (KCS_STATUS_STATE(status)
== KCS_STATUS_STATE_IDLE) {
i++;
/* Wait for OBF = 1*/
status = ipmi_wait_for_obf(dev, 1);
/* Read Dummy */
data = INB(sc, sc->ipmi_kcs_data_out_reg);
break;
/* error state */
} else {
device_printf(dev,
"read status error %x byte %d\n",
status, i);
sc->ipmi_busy = 0;
ipmi_error(dev);
return -1;
}
}
} else {
device_printf(dev, "Unsupported mode\n");
}
sc->ipmi_busy = 0;
wakeup(&sc->ipmi_busy);
return i;
}
static int
ipmi_write(device_t dev, u_char *bytes, int len){
struct ipmi_softc *sc = device_get_softc(dev);
int status, flags, retry;
while(sc->ipmi_busy){
status = tsleep(&sc->ipmi_busy, PCATCH, "ipmi", 0);
if (status)
return status;
}
sc->ipmi_busy = 1;
if (sc->ipmi_bios_info.smic_mode) {
ipmi_wait_for_not_busy(dev);
OUTB(sc, sc->ipmi_smic_ctl_sts, SMIC_CC_SMS_WR_START);
ipmi_wait_for_tx_okay(dev);
OUTB(sc, sc->ipmi_smic_data, *bytes++);
len--;
ipmi_set_busy(dev);
ipmi_wait_for_not_busy(dev);
status = INB(sc, sc->ipmi_smic_ctl_sts);
if (status != SMIC_SC_SMS_WR_START) {
device_printf(dev, "Write did not start %x\n",status);
sc->ipmi_busy = 0;
return -1;
}
for(len--; len; len--) {
OUTB(sc, sc->ipmi_smic_ctl_sts, SMIC_CC_SMS_WR_NEXT);
ipmi_wait_for_tx_okay(dev);
OUTB(sc, sc->ipmi_smic_data, *bytes++);
ipmi_set_busy(dev);
ipmi_wait_for_not_busy(dev);
status = INB(sc, sc->ipmi_smic_ctl_sts);
if (status != SMIC_SC_SMS_WR_NEXT) {
device_printf(dev, "Write did not next %x\n",
status);
sc->ipmi_busy = 0;
return -1;
}
}
do {
flags = INB(sc, sc->ipmi_smic_flags);
} while(!flags & SMIC_STATUS_TX_RDY);
OUTB(sc, sc->ipmi_smic_ctl_sts, SMIC_CC_SMS_WR_END);
ipmi_wait_for_tx_okay(dev);
OUTB(sc, sc->ipmi_smic_data, *bytes);
ipmi_set_busy(dev);
ipmi_wait_for_not_busy(dev);
status = INB(sc, sc->ipmi_smic_ctl_sts);
if (status != SMIC_SC_SMS_WR_END) {
device_printf(dev, "Write did not end %x\n",status);
return -1;
}
} else if (sc->ipmi_bios_info.kcs_mode) {
for (retry = 0; retry < 10; retry++) {
/* Wait for IBF = 0 */
status = ipmi_wait_for_ibf(dev, 0);
/* Clear OBF */
ipmi_clear_obf(dev, status);
/* Write start to command */
OUTB(sc, sc->ipmi_kcs_command_reg,
KCS_CONTROL_WRITE_START);
/* Wait for IBF = 0 */
status = ipmi_wait_for_ibf(dev, 0);
if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_WRITE)
break;
DELAY(1000000);
}
for(len--; len; len--) {
if (KCS_STATUS_STATE(status)
!= KCS_STATUS_STATE_WRITE) {
/* error state */
device_printf(dev, "status error %x\n",status);
ipmi_error(dev);
sc->ipmi_busy = 0;
return -1;
break;
} else {
/* Clear OBF */
ipmi_clear_obf(dev, status);
/* Data to Data */
OUTB(sc, sc->ipmi_kcs_data_out_reg, *bytes++);
/* Wait for IBF = 0 */
status = ipmi_wait_for_ibf(dev, 0);
if (KCS_STATUS_STATE(status)
!= KCS_STATUS_STATE_WRITE) {
device_printf(dev, "status error %x\n"
,status);
ipmi_error(dev);
return -1;
} else {
/* Clear OBF */
ipmi_clear_obf(dev, status);
}
}
}
/* Write end to command */
OUTB(sc, sc->ipmi_kcs_command_reg, KCS_CONTROL_WRITE_END);
/* Wait for IBF = 0 */
status = ipmi_wait_for_ibf(dev, 0);
if (KCS_STATUS_STATE(status) != KCS_STATUS_STATE_WRITE) {
/* error state */
device_printf(dev, "status error %x\n",status);
ipmi_error(dev);
sc->ipmi_busy = 0;
return -1;
} else {
/* Clear OBF */
ipmi_clear_obf(dev, status);
OUTB(sc, sc->ipmi_kcs_data_out_reg, *bytes++);
}
} else {
device_printf(dev, "Unsupported mode\n");
}
sc->ipmi_busy = 2;
return 1;
}
/*
* Watchdog event handler.
*/
static void
ipmi_set_watchdog(device_t dev, int sec) {
u_char *temp;
int s;
temp = malloc(IPMI_MAX_RX, M_IPMI, M_WAITOK);
temp[0] = IPMI_APP_REQUEST << 2;
if (sec) {
temp[1] = IPMI_SET_WDOG;
temp[2] = IPMI_SET_WD_TIMER_DONT_STOP
| IPMI_SET_WD_TIMER_SMS_OS;
temp[3] = IPMI_SET_WD_ACTION_RESET;
temp[4] = 0;
temp[5] = 0; /* Timer use */
temp[6] = (sec * 10) & 0xff;
temp[7] = (sec * 10) / 2550;
} else {
temp[1] = IPMI_SET_WDOG;
temp[2] = IPMI_SET_WD_TIMER_SMS_OS;
temp[3] = 0;
temp[4] = 0;
temp[5] = 0; /* Timer use */
temp[6] = 0;
temp[7] = 0;
}
s = splhigh();
ipmi_write(dev, temp, 8);
while (!ipmi_ready_to_read(dev))
DELAY(1000);
bzero(temp, IPMI_MAX_RX);
ipmi_read(dev, temp, IPMI_MAX_RX);
if (sec) {
temp[0] = IPMI_APP_REQUEST << 2;
temp[1] = IPMI_RESET_WDOG;
ipmi_write(dev, temp, 2);
while (!ipmi_ready_to_read(dev))
DELAY(1000);
bzero(temp, IPMI_MAX_RX);
ipmi_read(dev, temp, IPMI_MAX_RX);
}
splx(s);
free(temp, M_IPMI);
/*
dump_watchdog(dev);
*/
}
static void
ipmi_wd_event(void *arg, unsigned int cmd, int *error)
{
struct ipmi_softc *sc = arg;
unsigned int timeout;
/* disable / enable */
if (!(cmd & WD_ACTIVE)) {
ipmi_set_watchdog(sc->ipmi_dev, 0);
*error = 0;
return;
}
cmd &= WD_INTERVAL;
/* convert from power-of-to-ns to WDT ticks */
if (cmd >= 64) {
*error = EINVAL;
return;
}
timeout = ((uint64_t)1 << cmd) / 1800000000;
/* reload */
ipmi_set_watchdog(sc->ipmi_dev, timeout);
*error = 0;
}
int
ipmi_attach(device_t dev)
{
struct ipmi_softc *sc = device_get_softc(dev);
u_char temp[1024];
int i;
int status;
int unit;
TAILQ_INIT(&sc->ipmi_done);
sc->ipmi_address = IPMI_BMC_SLAVE_ADDR;
sc->ipmi_lun = IPMI_BMC_SMS_LUN;
temp[0] = IPMI_APP_REQUEST << 2;
temp[1] = IPMI_GET_DEVICE_ID;
ipmi_write(dev, temp, 2);
while (!ipmi_ready_to_read(dev))
DELAY(1000);
bzero(temp, sizeof(temp));
ipmi_read(dev, temp, sizeof(temp));
device_printf(dev, "IPMI device rev. %d, firmware rev. %d.%d, "
"version %d.%d\n",
temp[4] & 0x0f,
temp[5] & 0x0f, temp[7],
temp[7] & 0x0f, temp[7] >> 4);
temp[0] = IPMI_APP_REQUEST << 2;
temp[1] = IPMI_CLEAR_FLAGS;
temp[2] = 8;
ipmi_write(dev, temp, 3);
while (!ipmi_ready_to_read(dev))
DELAY(1000);
bzero(temp, sizeof(temp));
ipmi_read(dev, temp, sizeof(temp));
if (temp[2] == 0xc0) {
device_printf(dev, "Clear flags is busy\n");
}
if (temp[2] == 0xc1) {
device_printf(dev, "Clear flags illegal\n");
}
for(i = 0; i < 8; i++){
temp[0] = IPMI_APP_REQUEST << 2;
temp[1] = IPMI_GET_CHANNEL_INFO;
temp[2] = i;
ipmi_write(dev, temp, 3);
while (!ipmi_ready_to_read(dev))
DELAY(1000);
bzero(temp, sizeof(temp));
ipmi_read(dev, temp, sizeof(temp));
if (temp[2]) {
break;
}
}
device_printf(dev, "Number of channels %d\n", i);
/* probe for watchdog */
bzero(temp, sizeof(temp));
temp[0] = IPMI_APP_REQUEST << 2;
temp[1] = IPMI_GET_WDOG;
status = ipmi_write(dev, temp, 2);
while (!ipmi_ready_to_read(dev))
DELAY(1000);
bzero(temp, sizeof(temp));
ipmi_read(dev, temp, sizeof(temp));
if (temp[0] == 0x1c && temp[2] == 0x00) {
device_printf(dev, "Attached watchdog\n");
/* register the watchdog event handler */
sc->ipmi_ev_tag = EVENTHANDLER_REGISTER(watchdog_list,
ipmi_wd_event, sc, 0);
}
unit = device_get_unit(sc->ipmi_dev);
/* force device to be ipmi0 since that is what ipmitool expects */
sc->ipmi_dev_t = make_dev(&ipmi_cdevsw, unit, UID_ROOT, GID_OPERATOR,
0660, "ipmi%d", 0);
sc->ipmi_dev_t->si_drv1 = sc;
ipmi_attached = 1;
return 0;
}
int
ipmi_detach(device_t dev)
{
struct ipmi_softc *sc;
sc = device_get_softc(dev);
if (sc->ipmi_requests)
untimeout((timeout_t *)ipmi_check_read, dev,
sc->ipmi_timeout_handle);
destroy_dev(sc->ipmi_dev_t);
return 0;
}
#ifdef 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