(2006-08-06) rescue-bootcd

This commit is contained in:
2006-08-06 00:00:00 +02:00
parent 2f796b816a
commit decb062d20
21091 changed files with 7076462 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
#
# IPMI device configuration
#
menu "IPMI"
config IPMI_HANDLER
tristate 'IPMI top-level message handler'
help
This enables the central IPMI message handler, required for IPMI
to work.
IPMI is a standard for managing sensors (temperature,
voltage, etc.) in a system.
See Documentation/IPMI.txt for more details on the driver.
If unsure, say N.
config IPMI_PANIC_EVENT
bool 'Generate a panic event to all BMCs on a panic'
depends on IPMI_HANDLER
help
When a panic occurs, this will cause the IPMI message handler to
generate an IPMI event describing the panic to each interface
registered with the message handler.
config IPMI_PANIC_STRING
bool 'Generate OEM events containing the panic string'
depends on IPMI_PANIC_EVENT
help
When a panic occurs, this will cause the IPMI message handler to
generate IPMI OEM type f0 events holding the IPMB address of the
panic generator (byte 4 of the event), a sequence number for the
string (byte 5 of the event) and part of the string (the rest of the
event). Bytes 1, 2, and 3 are the normal usage for an OEM event.
You can fetch these events and use the sequence numbers to piece the
string together.
config IPMI_DEVICE_INTERFACE
tristate 'Device interface for IPMI'
depends on IPMI_HANDLER
help
This provides an IOCTL interface to the IPMI message handler so
userland processes may use IPMI. It supports poll() and select().
config IPMI_SI
tristate 'IPMI System Interface handler'
depends on IPMI_HANDLER
help
Provides a driver for System Interfaces (KCS, SMIC, BT).
Currently, only KCS and SMIC are supported. If
you are using IPMI, you should probably say "y" here.
config IPMI_WATCHDOG
tristate 'IPMI Watchdog Timer'
depends on IPMI_HANDLER
help
This enables the IPMI watchdog timer.
config IPMI_POWEROFF
tristate 'IPMI Poweroff'
depends on IPMI_HANDLER
help
This enables a function to power off the system with IPMI if
the IPMI management controller is capable of this.
endmenu

View File

@@ -0,0 +1,15 @@
#
# Makefile for the ipmi drivers.
#
ipmi_si-objs := ipmi_si_intf.o ipmi_kcs_sm.o ipmi_smic_sm.o ipmi_bt_sm.o
obj-$(CONFIG_IPMI_HANDLER) += ipmi_msghandler.o
obj-$(CONFIG_IPMI_DEVICE_INTERFACE) += ipmi_devintf.o
obj-$(CONFIG_IPMI_SI) += ipmi_si.o
obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o
obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o
ipmi_si.o: $(ipmi_si-objs)
$(LD) -r -o $@ $(ipmi_si-objs)

View File

@@ -0,0 +1,513 @@
/*
* ipmi_bt_sm.c
*
* The state machine for an Open IPMI BT sub-driver under ipmi_si.c, part
* of the driver architecture at http://sourceforge.net/project/openipmi
*
* Author: Rocky Craig <first.last@hp.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* THIS SOFTWARE IS PROVIDED ``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 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA. */
#include <linux/kernel.h> /* For printk. */
#include <linux/string.h>
#include <linux/ipmi_msgdefs.h> /* for completion codes */
#include "ipmi_si_sm.h"
#define IPMI_BT_VERSION "v33"
static int bt_debug = 0x00; /* Production value 0, see following flags */
#define BT_DEBUG_ENABLE 1
#define BT_DEBUG_MSG 2
#define BT_DEBUG_STATES 4
/* Typical "Get BT Capabilities" values are 2-3 retries, 5-10 seconds,
and 64 byte buffers. However, one HP implementation wants 255 bytes of
buffer (with a documented message of 160 bytes) so go for the max.
Since the Open IPMI architecture is single-message oriented at this
stage, the queue depth of BT is of no concern. */
#define BT_NORMAL_TIMEOUT 2000000 /* seconds in microseconds */
#define BT_RETRY_LIMIT 2
#define BT_RESET_DELAY 6000000 /* 6 seconds after warm reset */
enum bt_states {
BT_STATE_IDLE,
BT_STATE_XACTION_START,
BT_STATE_WRITE_BYTES,
BT_STATE_WRITE_END,
BT_STATE_WRITE_CONSUME,
BT_STATE_B2H_WAIT,
BT_STATE_READ_END,
BT_STATE_RESET1, /* These must come last */
BT_STATE_RESET2,
BT_STATE_RESET3,
BT_STATE_RESTART,
BT_STATE_HOSED
};
struct si_sm_data {
enum bt_states state;
enum bt_states last_state; /* assist printing and resets */
unsigned char seq; /* BT sequence number */
struct si_sm_io *io;
unsigned char write_data[IPMI_MAX_MSG_LENGTH];
int write_count;
unsigned char read_data[IPMI_MAX_MSG_LENGTH];
int read_count;
int truncated;
long timeout;
unsigned int error_retries; /* end of "common" fields */
int nonzero_status; /* hung BMCs stay all 0 */
};
#define BT_CLR_WR_PTR 0x01 /* See IPMI 1.5 table 11.6.4 */
#define BT_CLR_RD_PTR 0x02
#define BT_H2B_ATN 0x04
#define BT_B2H_ATN 0x08
#define BT_SMS_ATN 0x10
#define BT_OEM0 0x20
#define BT_H_BUSY 0x40
#define BT_B_BUSY 0x80
/* Some bits are toggled on each write: write once to set it, once
more to clear it; writing a zero does nothing. To absolutely
clear it, check its state and write if set. This avoids the "get
current then use as mask" scheme to modify one bit. Note that the
variable "bt" is hardcoded into these macros. */
#define BT_STATUS bt->io->inputb(bt->io, 0)
#define BT_CONTROL(x) bt->io->outputb(bt->io, 0, x)
#define BMC2HOST bt->io->inputb(bt->io, 1)
#define HOST2BMC(x) bt->io->outputb(bt->io, 1, x)
#define BT_INTMASK_R bt->io->inputb(bt->io, 2)
#define BT_INTMASK_W(x) bt->io->outputb(bt->io, 2, x)
/* Convenience routines for debugging. These are not multi-open safe!
Note the macros have hardcoded variables in them. */
static char *state2txt(unsigned char state)
{
switch (state) {
case BT_STATE_IDLE: return("IDLE");
case BT_STATE_XACTION_START: return("XACTION");
case BT_STATE_WRITE_BYTES: return("WR_BYTES");
case BT_STATE_WRITE_END: return("WR_END");
case BT_STATE_WRITE_CONSUME: return("WR_CONSUME");
case BT_STATE_B2H_WAIT: return("B2H_WAIT");
case BT_STATE_READ_END: return("RD_END");
case BT_STATE_RESET1: return("RESET1");
case BT_STATE_RESET2: return("RESET2");
case BT_STATE_RESET3: return("RESET3");
case BT_STATE_RESTART: return("RESTART");
case BT_STATE_HOSED: return("HOSED");
}
return("BAD STATE");
}
#define STATE2TXT state2txt(bt->state)
static char *status2txt(unsigned char status, char *buf)
{
strcpy(buf, "[ ");
if (status & BT_B_BUSY) strcat(buf, "B_BUSY ");
if (status & BT_H_BUSY) strcat(buf, "H_BUSY ");
if (status & BT_OEM0) strcat(buf, "OEM0 ");
if (status & BT_SMS_ATN) strcat(buf, "SMS ");
if (status & BT_B2H_ATN) strcat(buf, "B2H ");
if (status & BT_H2B_ATN) strcat(buf, "H2B ");
strcat(buf, "]");
return buf;
}
#define STATUS2TXT(buf) status2txt(status, buf)
/* This will be called from within this module on a hosed condition */
#define FIRST_SEQ 0
static unsigned int bt_init_data(struct si_sm_data *bt, struct si_sm_io *io)
{
bt->state = BT_STATE_IDLE;
bt->last_state = BT_STATE_IDLE;
bt->seq = FIRST_SEQ;
bt->io = io;
bt->write_count = 0;
bt->read_count = 0;
bt->error_retries = 0;
bt->nonzero_status = 0;
bt->truncated = 0;
bt->timeout = BT_NORMAL_TIMEOUT;
return 3; /* We claim 3 bytes of space; ought to check SPMI table */
}
static int bt_start_transaction(struct si_sm_data *bt,
unsigned char *data,
unsigned int size)
{
unsigned int i;
if ((size < 2) || (size > IPMI_MAX_MSG_LENGTH)) return -1;
if ((bt->state != BT_STATE_IDLE) && (bt->state != BT_STATE_HOSED))
return -2;
if (bt_debug & BT_DEBUG_MSG) {
printk(KERN_WARNING "+++++++++++++++++++++++++++++++++++++\n");
printk(KERN_WARNING "BT: write seq=0x%02X:", bt->seq);
for (i = 0; i < size; i ++) printk (" %02x", data[i]);
printk("\n");
}
bt->write_data[0] = size + 1; /* all data plus seq byte */
bt->write_data[1] = *data; /* NetFn/LUN */
bt->write_data[2] = bt->seq;
memcpy(bt->write_data + 3, data + 1, size - 1);
bt->write_count = size + 2;
bt->error_retries = 0;
bt->nonzero_status = 0;
bt->read_count = 0;
bt->truncated = 0;
bt->state = BT_STATE_XACTION_START;
bt->last_state = BT_STATE_IDLE;
bt->timeout = BT_NORMAL_TIMEOUT;
return 0;
}
/* After the upper state machine has been told SI_SM_TRANSACTION_COMPLETE
it calls this. Strip out the length and seq bytes. */
static int bt_get_result(struct si_sm_data *bt,
unsigned char *data,
unsigned int length)
{
int i, msg_len;
msg_len = bt->read_count - 2; /* account for length & seq */
/* Always NetFn, Cmd, cCode */
if (msg_len < 3 || msg_len > IPMI_MAX_MSG_LENGTH) {
printk(KERN_WARNING "BT results: bad msg_len = %d\n", msg_len);
data[0] = bt->write_data[1] | 0x4; /* Kludge a response */
data[1] = bt->write_data[3];
data[2] = IPMI_ERR_UNSPECIFIED;
msg_len = 3;
} else {
data[0] = bt->read_data[1];
data[1] = bt->read_data[3];
if (length < msg_len) bt->truncated = 1;
if (bt->truncated) { /* can be set in read_all_bytes() */
data[2] = IPMI_ERR_MSG_TRUNCATED;
msg_len = 3;
} else memcpy(data + 2, bt->read_data + 4, msg_len - 2);
if (bt_debug & BT_DEBUG_MSG) {
printk (KERN_WARNING "BT: res (raw)");
for (i = 0; i < msg_len; i++) printk(" %02x", data[i]);
printk ("\n");
}
}
bt->read_count = 0; /* paranoia */
return msg_len;
}
/* This bit's functionality is optional */
#define BT_BMC_HWRST 0x80
static void reset_flags(struct si_sm_data *bt)
{
if (BT_STATUS & BT_H_BUSY) BT_CONTROL(BT_H_BUSY);
if (BT_STATUS & BT_B_BUSY) BT_CONTROL(BT_B_BUSY);
BT_CONTROL(BT_CLR_WR_PTR);
BT_CONTROL(BT_SMS_ATN);
BT_INTMASK_W(BT_BMC_HWRST);
#ifdef DEVELOPMENT_ONLY_NOT_FOR_PRODUCTION
if (BT_STATUS & BT_B2H_ATN) {
int i;
BT_CONTROL(BT_H_BUSY);
BT_CONTROL(BT_B2H_ATN);
BT_CONTROL(BT_CLR_RD_PTR);
for (i = 0; i < IPMI_MAX_MSG_LENGTH + 2; i++) BMC2HOST;
BT_CONTROL(BT_H_BUSY);
}
#endif
}
static inline void write_all_bytes(struct si_sm_data *bt)
{
int i;
if (bt_debug & BT_DEBUG_MSG) {
printk(KERN_WARNING "BT: write %d bytes seq=0x%02X",
bt->write_count, bt->seq);
for (i = 0; i < bt->write_count; i++)
printk (" %02x", bt->write_data[i]);
printk ("\n");
}
for (i = 0; i < bt->write_count; i++) HOST2BMC(bt->write_data[i]);
}
static inline int read_all_bytes(struct si_sm_data *bt)
{
unsigned char i;
bt->read_data[0] = BMC2HOST;
bt->read_count = bt->read_data[0];
if (bt_debug & BT_DEBUG_MSG)
printk(KERN_WARNING "BT: read %d bytes:", bt->read_count);
/* minimum: length, NetFn, Seq, Cmd, cCode == 5 total, or 4 more
following the length byte. */
if (bt->read_count < 4 || bt->read_count >= IPMI_MAX_MSG_LENGTH) {
if (bt_debug & BT_DEBUG_MSG)
printk("bad length %d\n", bt->read_count);
bt->truncated = 1;
return 1; /* let next XACTION START clean it up */
}
for (i = 1; i <= bt->read_count; i++) bt->read_data[i] = BMC2HOST;
bt->read_count++; /* account for the length byte */
if (bt_debug & BT_DEBUG_MSG) {
for (i = 0; i < bt->read_count; i++)
printk (" %02x", bt->read_data[i]);
printk ("\n");
}
if (bt->seq != bt->write_data[2]) /* idiot check */
printk(KERN_WARNING "BT: internal error: sequence mismatch\n");
/* per the spec, the (NetFn, Seq, Cmd) tuples should match */
if ((bt->read_data[3] == bt->write_data[3]) && /* Cmd */
(bt->read_data[2] == bt->write_data[2]) && /* Sequence */
((bt->read_data[1] & 0xF8) == (bt->write_data[1] & 0xF8)))
return 1;
if (bt_debug & BT_DEBUG_MSG) printk(KERN_WARNING "BT: bad packet: "
"want 0x(%02X, %02X, %02X) got (%02X, %02X, %02X)\n",
bt->write_data[1], bt->write_data[2], bt->write_data[3],
bt->read_data[1], bt->read_data[2], bt->read_data[3]);
return 0;
}
/* Modifies bt->state appropriately, need to get into the bt_event() switch */
static void error_recovery(struct si_sm_data *bt, char *reason)
{
unsigned char status;
char buf[40]; /* For getting status */
bt->timeout = BT_NORMAL_TIMEOUT; /* various places want to retry */
status = BT_STATUS;
printk(KERN_WARNING "BT: %s in %s %s ", reason, STATE2TXT,
STATUS2TXT(buf));
(bt->error_retries)++;
if (bt->error_retries > BT_RETRY_LIMIT) {
printk("retry limit (%d) exceeded\n", BT_RETRY_LIMIT);
bt->state = BT_STATE_HOSED;
if (!bt->nonzero_status)
printk(KERN_ERR "IPMI: BT stuck, try power cycle\n");
else if (bt->seq == FIRST_SEQ + BT_RETRY_LIMIT) {
/* most likely during insmod */
printk(KERN_WARNING "IPMI: BT reset (takes 5 secs)\n");
bt->state = BT_STATE_RESET1;
}
return;
}
/* Sometimes the BMC queues get in an "off-by-one" state...*/
if ((bt->state == BT_STATE_B2H_WAIT) && (status & BT_B2H_ATN)) {
printk("retry B2H_WAIT\n");
return;
}
printk("restart command\n");
bt->state = BT_STATE_RESTART;
}
/* Check the status and (possibly) advance the BT state machine. The
default return is SI_SM_CALL_WITH_DELAY. */
static enum si_sm_result bt_event(struct si_sm_data *bt, long time)
{
unsigned char status;
char buf[40]; /* For getting status */
int i;
status = BT_STATUS;
bt->nonzero_status |= status;
if ((bt_debug & BT_DEBUG_STATES) && (bt->state != bt->last_state))
printk(KERN_WARNING "BT: %s %s TO=%ld - %ld \n",
STATE2TXT,
STATUS2TXT(buf),
bt->timeout,
time);
bt->last_state = bt->state;
if (bt->state == BT_STATE_HOSED) return SI_SM_HOSED;
if (bt->state != BT_STATE_IDLE) { /* do timeout test */
/* Certain states, on error conditions, can lock up a CPU
because they are effectively in an infinite loop with
CALL_WITHOUT_DELAY (right back here with time == 0).
Prevent infinite lockup by ALWAYS decrementing timeout. */
/* FIXME: bt_event is sometimes called with time > BT_NORMAL_TIMEOUT
(noticed in ipmi_smic_sm.c January 2004) */
if ((time <= 0) || (time >= BT_NORMAL_TIMEOUT)) time = 100;
bt->timeout -= time;
if ((bt->timeout < 0) && (bt->state < BT_STATE_RESET1)) {
error_recovery(bt, "timed out");
return SI_SM_CALL_WITHOUT_DELAY;
}
}
switch (bt->state) {
case BT_STATE_IDLE: /* check for asynchronous messages */
if (status & BT_SMS_ATN) {
BT_CONTROL(BT_SMS_ATN); /* clear it */
return SI_SM_ATTN;
}
return SI_SM_IDLE;
case BT_STATE_XACTION_START:
if (status & BT_H_BUSY) {
BT_CONTROL(BT_H_BUSY);
break;
}
if (status & BT_B2H_ATN) break;
bt->state = BT_STATE_WRITE_BYTES;
return SI_SM_CALL_WITHOUT_DELAY; /* for logging */
case BT_STATE_WRITE_BYTES:
if (status & (BT_B_BUSY | BT_H2B_ATN)) break;
BT_CONTROL(BT_CLR_WR_PTR);
write_all_bytes(bt);
BT_CONTROL(BT_H2B_ATN); /* clears too fast to catch? */
bt->state = BT_STATE_WRITE_CONSUME;
return SI_SM_CALL_WITHOUT_DELAY; /* it MIGHT sail through */
case BT_STATE_WRITE_CONSUME: /* BMCs usually blow right thru here */
if (status & (BT_H2B_ATN | BT_B_BUSY)) break;
bt->state = BT_STATE_B2H_WAIT;
/* fall through with status */
/* Stay in BT_STATE_B2H_WAIT until a packet matches. However, spinning
hard here, constantly reading status, seems to hold off the
generation of B2H_ATN so ALWAYS return CALL_WITH_DELAY. */
case BT_STATE_B2H_WAIT:
if (!(status & BT_B2H_ATN)) break;
/* Assume ordered, uncached writes: no need to wait */
if (!(status & BT_H_BUSY)) BT_CONTROL(BT_H_BUSY); /* set */
BT_CONTROL(BT_B2H_ATN); /* clear it, ACK to the BMC */
BT_CONTROL(BT_CLR_RD_PTR); /* reset the queue */
i = read_all_bytes(bt);
BT_CONTROL(BT_H_BUSY); /* clear */
if (!i) break; /* Try this state again */
bt->state = BT_STATE_READ_END;
return SI_SM_CALL_WITHOUT_DELAY; /* for logging */
case BT_STATE_READ_END:
/* I could wait on BT_H_BUSY to go clear for a truly clean
exit. However, this is already done in XACTION_START
and the (possible) extra loop/status/possible wait affects
performance. So, as long as it works, just ignore H_BUSY */
#ifdef MAKE_THIS_TRUE_IF_NECESSARY
if (status & BT_H_BUSY) break;
#endif
bt->seq++;
bt->state = BT_STATE_IDLE;
return SI_SM_TRANSACTION_COMPLETE;
case BT_STATE_RESET1:
reset_flags(bt);
bt->timeout = BT_RESET_DELAY;
bt->state = BT_STATE_RESET2;
break;
case BT_STATE_RESET2: /* Send a soft reset */
BT_CONTROL(BT_CLR_WR_PTR);
HOST2BMC(3); /* number of bytes following */
HOST2BMC(0x18); /* NetFn/LUN == Application, LUN 0 */
HOST2BMC(42); /* Sequence number */
HOST2BMC(3); /* Cmd == Soft reset */
BT_CONTROL(BT_H2B_ATN);
bt->state = BT_STATE_RESET3;
break;
case BT_STATE_RESET3:
if (bt->timeout > 0) return SI_SM_CALL_WITH_DELAY;
bt->state = BT_STATE_RESTART; /* printk in debug modes */
break;
case BT_STATE_RESTART: /* don't reset retries! */
bt->write_data[2] = ++bt->seq;
bt->read_count = 0;
bt->nonzero_status = 0;
bt->timeout = BT_NORMAL_TIMEOUT;
bt->state = BT_STATE_XACTION_START;
break;
default: /* HOSED is supposed to be caught much earlier */
error_recovery(bt, "internal logic error");
break;
}
return SI_SM_CALL_WITH_DELAY;
}
static int bt_detect(struct si_sm_data *bt)
{
/* It's impossible for the BT status and interrupt registers to be
all 1's, (assuming a properly functioning, self-initialized BMC)
but that's what you get from reading a bogus address, so we
test that first. The calling routine uses negative logic. */
if ((BT_STATUS == 0xFF) && (BT_INTMASK_R == 0xFF)) return 1;
reset_flags(bt);
return 0;
}
static void bt_cleanup(struct si_sm_data *bt)
{
}
static int bt_size(void)
{
return sizeof(struct si_sm_data);
}
struct si_sm_handlers bt_smi_handlers =
{
.version = IPMI_BT_VERSION,
.init_data = bt_init_data,
.start_transaction = bt_start_transaction,
.get_result = bt_get_result,
.event = bt_event,
.detect = bt_detect,
.cleanup = bt_cleanup,
.size = bt_size,
};

View File

@@ -0,0 +1,582 @@
/*
* ipmi_devintf.c
*
* Linux device interface for the IPMI message handler.
*
* Author: MontaVista Software, Inc.
* Corey Minyard <minyard@mvista.com>
* source@mvista.com
*
* Copyright 2002 MontaVista Software Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*
* THIS SOFTWARE IS PROVIDED ``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 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/errno.h>
#include <asm/system.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/ipmi.h>
#include <asm/semaphore.h>
#include <linux/init.h>
#define IPMI_DEVINTF_VERSION "v33"
struct ipmi_file_private
{
ipmi_user_t user;
spinlock_t recv_msg_lock;
struct list_head recv_msgs;
struct file *file;
struct fasync_struct *fasync_queue;
wait_queue_head_t wait;
struct semaphore recv_sem;
int default_retries;
unsigned int default_retry_time_ms;
};
static void file_receive_handler(struct ipmi_recv_msg *msg,
void *handler_data)
{
struct ipmi_file_private *priv = handler_data;
int was_empty;
unsigned long flags;
spin_lock_irqsave(&(priv->recv_msg_lock), flags);
was_empty = list_empty(&(priv->recv_msgs));
list_add_tail(&(msg->link), &(priv->recv_msgs));
if (was_empty) {
wake_up_interruptible(&priv->wait);
kill_fasync(&priv->fasync_queue, SIGIO, POLL_IN);
}
spin_unlock_irqrestore(&(priv->recv_msg_lock), flags);
}
static unsigned int ipmi_poll(struct file *file, poll_table *wait)
{
struct ipmi_file_private *priv = file->private_data;
unsigned int mask = 0;
unsigned long flags;
poll_wait(file, &priv->wait, wait);
spin_lock_irqsave(&priv->recv_msg_lock, flags);
if (! list_empty(&(priv->recv_msgs)))
mask |= (POLLIN | POLLRDNORM);
spin_unlock_irqrestore(&priv->recv_msg_lock, flags);
return mask;
}
static int ipmi_fasync(int fd, struct file *file, int on)
{
struct ipmi_file_private *priv = file->private_data;
int result;
result = fasync_helper(fd, file, on, &priv->fasync_queue);
return (result);
}
static struct ipmi_user_hndl ipmi_hndlrs =
{
.ipmi_recv_hndl = file_receive_handler,
};
static int ipmi_open(struct inode *inode, struct file *file)
{
int if_num = iminor(inode);
int rv;
struct ipmi_file_private *priv;
priv = kmalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->file = file;
rv = ipmi_create_user(if_num,
&ipmi_hndlrs,
priv,
&(priv->user));
if (rv) {
kfree(priv);
return rv;
}
file->private_data = priv;
spin_lock_init(&(priv->recv_msg_lock));
INIT_LIST_HEAD(&(priv->recv_msgs));
init_waitqueue_head(&priv->wait);
priv->fasync_queue = NULL;
sema_init(&(priv->recv_sem), 1);
/* Use the low-level defaults. */
priv->default_retries = -1;
priv->default_retry_time_ms = 0;
return 0;
}
static int ipmi_release(struct inode *inode, struct file *file)
{
struct ipmi_file_private *priv = file->private_data;
int rv;
rv = ipmi_destroy_user(priv->user);
if (rv)
return rv;
ipmi_fasync (-1, file, 0);
/* FIXME - free the messages in the list. */
kfree(priv);
return 0;
}
static int handle_send_req(ipmi_user_t user,
struct ipmi_req *req,
int retries,
unsigned int retry_time_ms)
{
int rv;
struct ipmi_addr addr;
struct kernel_ipmi_msg msg;
if (req->addr_len > sizeof(struct ipmi_addr))
return -EINVAL;
if (copy_from_user(&addr, req->addr, req->addr_len))
return -EFAULT;
msg.netfn = req->msg.netfn;
msg.cmd = req->msg.cmd;
msg.data_len = req->msg.data_len;
msg.data = kmalloc(IPMI_MAX_MSG_LENGTH, GFP_KERNEL);
if (!msg.data)
return -ENOMEM;
/* From here out we cannot return, we must jump to "out" for
error exits to free msgdata. */
rv = ipmi_validate_addr(&addr, req->addr_len);
if (rv)
goto out;
if (req->msg.data != NULL) {
if (req->msg.data_len > IPMI_MAX_MSG_LENGTH) {
rv = -EMSGSIZE;
goto out;
}
if (copy_from_user(msg.data,
req->msg.data,
req->msg.data_len))
{
rv = -EFAULT;
goto out;
}
} else {
msg.data_len = 0;
}
rv = ipmi_request_settime(user,
&addr,
req->msgid,
&msg,
NULL,
0,
retries,
retry_time_ms);
out:
kfree(msg.data);
return rv;
}
static int ipmi_ioctl(struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long data)
{
int rv = -EINVAL;
struct ipmi_file_private *priv = file->private_data;
void __user *arg = (void __user *)data;
switch (cmd)
{
case IPMICTL_SEND_COMMAND:
{
struct ipmi_req req;
if (copy_from_user(&req, arg, sizeof(req))) {
rv = -EFAULT;
break;
}
rv = handle_send_req(priv->user,
&req,
priv->default_retries,
priv->default_retry_time_ms);
break;
}
case IPMICTL_SEND_COMMAND_SETTIME:
{
struct ipmi_req_settime req;
if (copy_from_user(&req, arg, sizeof(req))) {
rv = -EFAULT;
break;
}
rv = handle_send_req(priv->user,
&req.req,
req.retries,
req.retry_time_ms);
break;
}
case IPMICTL_RECEIVE_MSG:
case IPMICTL_RECEIVE_MSG_TRUNC:
{
struct ipmi_recv rsp;
int addr_len;
struct list_head *entry;
struct ipmi_recv_msg *msg;
unsigned long flags;
rv = 0;
if (copy_from_user(&rsp, arg, sizeof(rsp))) {
rv = -EFAULT;
break;
}
/* We claim a semaphore because we don't want two
users getting something from the queue at a time.
Since we have to release the spinlock before we can
copy the data to the user, it's possible another
user will grab something from the queue, too. Then
the messages might get out of order if something
fails and the message gets put back onto the
queue. This semaphore prevents that problem. */
down(&(priv->recv_sem));
/* Grab the message off the list. */
spin_lock_irqsave(&(priv->recv_msg_lock), flags);
if (list_empty(&(priv->recv_msgs))) {
spin_unlock_irqrestore(&(priv->recv_msg_lock), flags);
rv = -EAGAIN;
goto recv_err;
}
entry = priv->recv_msgs.next;
msg = list_entry(entry, struct ipmi_recv_msg, link);
list_del(entry);
spin_unlock_irqrestore(&(priv->recv_msg_lock), flags);
addr_len = ipmi_addr_length(msg->addr.addr_type);
if (rsp.addr_len < addr_len)
{
rv = -EINVAL;
goto recv_putback_on_err;
}
if (copy_to_user(rsp.addr, &(msg->addr), addr_len)) {
rv = -EFAULT;
goto recv_putback_on_err;
}
rsp.addr_len = addr_len;
rsp.recv_type = msg->recv_type;
rsp.msgid = msg->msgid;
rsp.msg.netfn = msg->msg.netfn;
rsp.msg.cmd = msg->msg.cmd;
if (msg->msg.data_len > 0) {
if (rsp.msg.data_len < msg->msg.data_len) {
rv = -EMSGSIZE;
if (cmd == IPMICTL_RECEIVE_MSG_TRUNC) {
msg->msg.data_len = rsp.msg.data_len;
} else {
goto recv_putback_on_err;
}
}
if (copy_to_user(rsp.msg.data,
msg->msg.data,
msg->msg.data_len))
{
rv = -EFAULT;
goto recv_putback_on_err;
}
rsp.msg.data_len = msg->msg.data_len;
} else {
rsp.msg.data_len = 0;
}
if (copy_to_user(arg, &rsp, sizeof(rsp))) {
rv = -EFAULT;
goto recv_putback_on_err;
}
up(&(priv->recv_sem));
ipmi_free_recv_msg(msg);
break;
recv_putback_on_err:
/* If we got an error, put the message back onto
the head of the queue. */
spin_lock_irqsave(&(priv->recv_msg_lock), flags);
list_add(entry, &(priv->recv_msgs));
spin_unlock_irqrestore(&(priv->recv_msg_lock), flags);
up(&(priv->recv_sem));
break;
recv_err:
up(&(priv->recv_sem));
break;
}
case IPMICTL_REGISTER_FOR_CMD:
{
struct ipmi_cmdspec val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
rv = ipmi_register_for_cmd(priv->user, val.netfn, val.cmd);
break;
}
case IPMICTL_UNREGISTER_FOR_CMD:
{
struct ipmi_cmdspec val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
rv = ipmi_unregister_for_cmd(priv->user, val.netfn, val.cmd);
break;
}
case IPMICTL_SET_GETS_EVENTS_CMD:
{
int val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
rv = ipmi_set_gets_events(priv->user, val);
break;
}
case IPMICTL_SET_MY_ADDRESS_CMD:
{
unsigned int val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
ipmi_set_my_address(priv->user, val);
rv = 0;
break;
}
case IPMICTL_GET_MY_ADDRESS_CMD:
{
unsigned int val;
val = ipmi_get_my_address(priv->user);
if (copy_to_user(arg, &val, sizeof(val))) {
rv = -EFAULT;
break;
}
rv = 0;
break;
}
case IPMICTL_SET_MY_LUN_CMD:
{
unsigned int val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
ipmi_set_my_LUN(priv->user, val);
rv = 0;
break;
}
case IPMICTL_GET_MY_LUN_CMD:
{
unsigned int val;
val = ipmi_get_my_LUN(priv->user);
if (copy_to_user(arg, &val, sizeof(val))) {
rv = -EFAULT;
break;
}
rv = 0;
break;
}
case IPMICTL_SET_TIMING_PARMS_CMD:
{
struct ipmi_timing_parms parms;
if (copy_from_user(&parms, arg, sizeof(parms))) {
rv = -EFAULT;
break;
}
priv->default_retries = parms.retries;
priv->default_retry_time_ms = parms.retry_time_ms;
rv = 0;
break;
}
case IPMICTL_GET_TIMING_PARMS_CMD:
{
struct ipmi_timing_parms parms;
parms.retries = priv->default_retries;
parms.retry_time_ms = priv->default_retry_time_ms;
if (copy_to_user(arg, &parms, sizeof(parms))) {
rv = -EFAULT;
break;
}
rv = 0;
break;
}
}
return rv;
}
static struct file_operations ipmi_fops = {
.owner = THIS_MODULE,
.ioctl = ipmi_ioctl,
.open = ipmi_open,
.release = ipmi_release,
.fasync = ipmi_fasync,
.poll = ipmi_poll,
};
#define DEVICE_NAME "ipmidev"
static int ipmi_major = 0;
module_param(ipmi_major, int, 0);
MODULE_PARM_DESC(ipmi_major, "Sets the major number of the IPMI device. By"
" default, or if you set it to zero, it will choose the next"
" available device. Setting it to -1 will disable the"
" interface. Other values will set the major device number"
" to that value.");
static void ipmi_new_smi(int if_num)
{
devfs_mk_cdev(MKDEV(ipmi_major, if_num),
S_IFCHR | S_IRUSR | S_IWUSR,
"ipmidev/%d", if_num);
}
static void ipmi_smi_gone(int if_num)
{
devfs_remove("ipmidev/%d", if_num);
}
static struct ipmi_smi_watcher smi_watcher =
{
.owner = THIS_MODULE,
.new_smi = ipmi_new_smi,
.smi_gone = ipmi_smi_gone,
};
static __init int init_ipmi_devintf(void)
{
int rv;
if (ipmi_major < 0)
return -EINVAL;
printk(KERN_INFO "ipmi device interface version "
IPMI_DEVINTF_VERSION "\n");
rv = register_chrdev(ipmi_major, DEVICE_NAME, &ipmi_fops);
if (rv < 0) {
printk(KERN_ERR "ipmi: can't get major %d\n", ipmi_major);
return rv;
}
if (ipmi_major == 0) {
ipmi_major = rv;
}
devfs_mk_dir(DEVICE_NAME);
rv = ipmi_smi_watcher_register(&smi_watcher);
if (rv) {
unregister_chrdev(ipmi_major, DEVICE_NAME);
printk(KERN_WARNING "ipmi: can't register smi watcher\n");
return rv;
}
return 0;
}
module_init(init_ipmi_devintf);
static __exit void cleanup_ipmi(void)
{
ipmi_smi_watcher_unregister(&smi_watcher);
devfs_remove(DEVICE_NAME);
unregister_chrdev(ipmi_major, DEVICE_NAME);
}
module_exit(cleanup_ipmi);
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,500 @@
/*
* ipmi_kcs_sm.c
*
* State machine for handling IPMI KCS interfaces.
*
* Author: MontaVista Software, Inc.
* Corey Minyard <minyard@mvista.com>
* source@mvista.com
*
* Copyright 2002 MontaVista Software Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*
* THIS SOFTWARE IS PROVIDED ``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 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* This state machine is taken from the state machine in the IPMI spec,
* pretty much verbatim. If you have questions about the states, see
* that document.
*/
#include <linux/kernel.h> /* For printk. */
#include <linux/string.h>
#include <linux/ipmi_msgdefs.h> /* for completion codes */
#include "ipmi_si_sm.h"
#define IPMI_KCS_VERSION "v33"
/* Set this if you want a printout of why the state machine was hosed
when it gets hosed. */
#define DEBUG_HOSED_REASON
/* Print the state machine state on entry every time. */
#undef DEBUG_STATE
/* The states the KCS driver may be in. */
enum kcs_states {
KCS_IDLE, /* The KCS interface is currently
doing nothing. */
KCS_START_OP, /* We are starting an operation. The
data is in the output buffer, but
nothing has been done to the
interface yet. This was added to
the state machine in the spec to
wait for the initial IBF. */
KCS_WAIT_WRITE_START, /* We have written a write cmd to the
interface. */
KCS_WAIT_WRITE, /* We are writing bytes to the
interface. */
KCS_WAIT_WRITE_END, /* We have written the write end cmd
to the interface, and still need to
write the last byte. */
KCS_WAIT_READ, /* We are waiting to read data from
the interface. */
KCS_ERROR0, /* State to transition to the error
handler, this was added to the
state machine in the spec to be
sure IBF was there. */
KCS_ERROR1, /* First stage error handler, wait for
the interface to respond. */
KCS_ERROR2, /* The abort cmd has been written,
wait for the interface to
respond. */
KCS_ERROR3, /* We wrote some data to the
interface, wait for it to switch to
read mode. */
KCS_HOSED /* The hardware failed to follow the
state machine. */
};
#define MAX_KCS_READ_SIZE 80
#define MAX_KCS_WRITE_SIZE 80
/* Timeouts in microseconds. */
#define IBF_RETRY_TIMEOUT 1000000
#define OBF_RETRY_TIMEOUT 1000000
#define MAX_ERROR_RETRIES 10
struct si_sm_data
{
enum kcs_states state;
struct si_sm_io *io;
unsigned char write_data[MAX_KCS_WRITE_SIZE];
int write_pos;
int write_count;
int orig_write_count;
unsigned char read_data[MAX_KCS_READ_SIZE];
int read_pos;
int truncated;
unsigned int error_retries;
long ibf_timeout;
long obf_timeout;
};
static unsigned int init_kcs_data(struct si_sm_data *kcs,
struct si_sm_io *io)
{
kcs->state = KCS_IDLE;
kcs->io = io;
kcs->write_pos = 0;
kcs->write_count = 0;
kcs->orig_write_count = 0;
kcs->read_pos = 0;
kcs->error_retries = 0;
kcs->truncated = 0;
kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
kcs->obf_timeout = OBF_RETRY_TIMEOUT;
/* Reserve 2 I/O bytes. */
return 2;
}
static inline unsigned char read_status(struct si_sm_data *kcs)
{
return kcs->io->inputb(kcs->io, 1);
}
static inline unsigned char read_data(struct si_sm_data *kcs)
{
return kcs->io->inputb(kcs->io, 0);
}
static inline void write_cmd(struct si_sm_data *kcs, unsigned char data)
{
kcs->io->outputb(kcs->io, 1, data);
}
static inline void write_data(struct si_sm_data *kcs, unsigned char data)
{
kcs->io->outputb(kcs->io, 0, data);
}
/* Control codes. */
#define KCS_GET_STATUS_ABORT 0x60
#define KCS_WRITE_START 0x61
#define KCS_WRITE_END 0x62
#define KCS_READ_BYTE 0x68
/* Status bits. */
#define GET_STATUS_STATE(status) (((status) >> 6) & 0x03)
#define KCS_IDLE_STATE 0
#define KCS_READ_STATE 1
#define KCS_WRITE_STATE 2
#define KCS_ERROR_STATE 3
#define GET_STATUS_ATN(status) ((status) & 0x04)
#define GET_STATUS_IBF(status) ((status) & 0x02)
#define GET_STATUS_OBF(status) ((status) & 0x01)
static inline void write_next_byte(struct si_sm_data *kcs)
{
write_data(kcs, kcs->write_data[kcs->write_pos]);
(kcs->write_pos)++;
(kcs->write_count)--;
}
static inline void start_error_recovery(struct si_sm_data *kcs, char *reason)
{
(kcs->error_retries)++;
if (kcs->error_retries > MAX_ERROR_RETRIES) {
#ifdef DEBUG_HOSED_REASON
printk("ipmi_kcs_sm: kcs hosed: %s\n", reason);
#endif
kcs->state = KCS_HOSED;
} else {
kcs->state = KCS_ERROR0;
}
}
static inline void read_next_byte(struct si_sm_data *kcs)
{
if (kcs->read_pos >= MAX_KCS_READ_SIZE) {
/* Throw the data away and mark it truncated. */
read_data(kcs);
kcs->truncated = 1;
} else {
kcs->read_data[kcs->read_pos] = read_data(kcs);
(kcs->read_pos)++;
}
write_data(kcs, KCS_READ_BYTE);
}
static inline int check_ibf(struct si_sm_data *kcs, unsigned char status,
long time)
{
if (GET_STATUS_IBF(status)) {
kcs->ibf_timeout -= time;
if (kcs->ibf_timeout < 0) {
start_error_recovery(kcs, "IBF not ready in time");
kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
return 1;
}
return 0;
}
kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
return 1;
}
static inline int check_obf(struct si_sm_data *kcs, unsigned char status,
long time)
{
if (! GET_STATUS_OBF(status)) {
kcs->obf_timeout -= time;
if (kcs->obf_timeout < 0) {
start_error_recovery(kcs, "OBF not ready in time");
return 1;
}
return 0;
}
kcs->obf_timeout = OBF_RETRY_TIMEOUT;
return 1;
}
static void clear_obf(struct si_sm_data *kcs, unsigned char status)
{
if (GET_STATUS_OBF(status))
read_data(kcs);
}
static void restart_kcs_transaction(struct si_sm_data *kcs)
{
kcs->write_count = kcs->orig_write_count;
kcs->write_pos = 0;
kcs->read_pos = 0;
kcs->state = KCS_WAIT_WRITE_START;
kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
kcs->obf_timeout = OBF_RETRY_TIMEOUT;
write_cmd(kcs, KCS_WRITE_START);
}
static int start_kcs_transaction(struct si_sm_data *kcs, unsigned char *data,
unsigned int size)
{
if ((size < 2) || (size > MAX_KCS_WRITE_SIZE)) {
return -1;
}
if ((kcs->state != KCS_IDLE) && (kcs->state != KCS_HOSED)) {
return -2;
}
kcs->error_retries = 0;
memcpy(kcs->write_data, data, size);
kcs->write_count = size;
kcs->orig_write_count = size;
kcs->write_pos = 0;
kcs->read_pos = 0;
kcs->state = KCS_START_OP;
kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
kcs->obf_timeout = OBF_RETRY_TIMEOUT;
return 0;
}
static int get_kcs_result(struct si_sm_data *kcs, unsigned char *data,
unsigned int length)
{
if (length < kcs->read_pos) {
kcs->read_pos = length;
kcs->truncated = 1;
}
memcpy(data, kcs->read_data, kcs->read_pos);
if ((length >= 3) && (kcs->read_pos < 3)) {
/* Guarantee that we return at least 3 bytes, with an
error in the third byte if it is too short. */
data[2] = IPMI_ERR_UNSPECIFIED;
kcs->read_pos = 3;
}
if (kcs->truncated) {
/* Report a truncated error. We might overwrite
another error, but that's too bad, the user needs
to know it was truncated. */
data[2] = IPMI_ERR_MSG_TRUNCATED;
kcs->truncated = 0;
}
return kcs->read_pos;
}
/* This implements the state machine defined in the IPMI manual, see
that for details on how this works. Divide that flowchart into
sections delimited by "Wait for IBF" and this will become clear. */
static enum si_sm_result kcs_event(struct si_sm_data *kcs, long time)
{
unsigned char status;
unsigned char state;
status = read_status(kcs);
#ifdef DEBUG_STATE
printk(" State = %d, %x\n", kcs->state, status);
#endif
/* All states wait for ibf, so just do it here. */
if (!check_ibf(kcs, status, time))
return SI_SM_CALL_WITH_DELAY;
/* Just about everything looks at the KCS state, so grab that, too. */
state = GET_STATUS_STATE(status);
switch (kcs->state) {
case KCS_IDLE:
/* If there's and interrupt source, turn it off. */
clear_obf(kcs, status);
if (GET_STATUS_ATN(status))
return SI_SM_ATTN;
else
return SI_SM_IDLE;
case KCS_START_OP:
if (state != KCS_IDLE) {
start_error_recovery(kcs,
"State machine not idle at start");
break;
}
clear_obf(kcs, status);
write_cmd(kcs, KCS_WRITE_START);
kcs->state = KCS_WAIT_WRITE_START;
break;
case KCS_WAIT_WRITE_START:
if (state != KCS_WRITE_STATE) {
start_error_recovery(
kcs,
"Not in write state at write start");
break;
}
read_data(kcs);
if (kcs->write_count == 1) {
write_cmd(kcs, KCS_WRITE_END);
kcs->state = KCS_WAIT_WRITE_END;
} else {
write_next_byte(kcs);
kcs->state = KCS_WAIT_WRITE;
}
break;
case KCS_WAIT_WRITE:
if (state != KCS_WRITE_STATE) {
start_error_recovery(kcs,
"Not in write state for write");
break;
}
clear_obf(kcs, status);
if (kcs->write_count == 1) {
write_cmd(kcs, KCS_WRITE_END);
kcs->state = KCS_WAIT_WRITE_END;
} else {
write_next_byte(kcs);
}
break;
case KCS_WAIT_WRITE_END:
if (state != KCS_WRITE_STATE) {
start_error_recovery(kcs,
"Not in write state for write end");
break;
}
clear_obf(kcs, status);
write_next_byte(kcs);
kcs->state = KCS_WAIT_READ;
break;
case KCS_WAIT_READ:
if ((state != KCS_READ_STATE) && (state != KCS_IDLE_STATE)) {
start_error_recovery(
kcs,
"Not in read or idle in read state");
break;
}
if (state == KCS_READ_STATE) {
if (! check_obf(kcs, status, time))
return SI_SM_CALL_WITH_DELAY;
read_next_byte(kcs);
} else {
/* We don't implement this exactly like the state
machine in the spec. Some broken hardware
does not write the final dummy byte to the
read register. Thus obf will never go high
here. We just go straight to idle, and we
handle clearing out obf in idle state if it
happens to come in. */
clear_obf(kcs, status);
kcs->orig_write_count = 0;
kcs->state = KCS_IDLE;
return SI_SM_TRANSACTION_COMPLETE;
}
break;
case KCS_ERROR0:
clear_obf(kcs, status);
write_cmd(kcs, KCS_GET_STATUS_ABORT);
kcs->state = KCS_ERROR1;
break;
case KCS_ERROR1:
clear_obf(kcs, status);
write_data(kcs, 0);
kcs->state = KCS_ERROR2;
break;
case KCS_ERROR2:
if (state != KCS_READ_STATE) {
start_error_recovery(kcs,
"Not in read state for error2");
break;
}
if (! check_obf(kcs, status, time))
return SI_SM_CALL_WITH_DELAY;
clear_obf(kcs, status);
write_data(kcs, KCS_READ_BYTE);
kcs->state = KCS_ERROR3;
break;
case KCS_ERROR3:
if (state != KCS_IDLE_STATE) {
start_error_recovery(kcs,
"Not in idle state for error3");
break;
}
if (! check_obf(kcs, status, time))
return SI_SM_CALL_WITH_DELAY;
clear_obf(kcs, status);
if (kcs->orig_write_count) {
restart_kcs_transaction(kcs);
} else {
kcs->state = KCS_IDLE;
return SI_SM_TRANSACTION_COMPLETE;
}
break;
case KCS_HOSED:
break;
}
if (kcs->state == KCS_HOSED) {
init_kcs_data(kcs, kcs->io);
return SI_SM_HOSED;
}
return SI_SM_CALL_WITHOUT_DELAY;
}
static int kcs_size(void)
{
return sizeof(struct si_sm_data);
}
static int kcs_detect(struct si_sm_data *kcs)
{
/* It's impossible for the KCS status register to be all 1's,
(assuming a properly functioning, self-initialized BMC)
but that's what you get from reading a bogus address, so we
test that first. */
if (read_status(kcs) == 0xff)
return 1;
return 0;
}
static void kcs_cleanup(struct si_sm_data *kcs)
{
}
struct si_sm_handlers kcs_smi_handlers =
{
.version = IPMI_KCS_VERSION,
.init_data = init_kcs_data,
.start_transaction = start_kcs_transaction,
.get_result = get_kcs_result,
.event = kcs_event,
.detect = kcs_detect,
.cleanup = kcs_cleanup,
.size = kcs_size,
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,543 @@
/*
* ipmi_poweroff.c
*
* MontaVista IPMI Poweroff extension to sys_reboot
*
* Author: MontaVista Software, Inc.
* Steven Dake <sdake@mvista.com>
* Corey Minyard <cminyard@mvista.com>
* source@mvista.com
*
* Copyright 2002,2004 MontaVista Software Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*
* THIS SOFTWARE IS PROVIDED ``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 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <asm/semaphore.h>
#include <linux/kdev_t.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/ipmi.h>
#include <linux/ipmi_smi.h>
#define PFX "IPMI poweroff: "
#define IPMI_POWEROFF_VERSION "v33"
/* Where to we insert our poweroff function? */
extern void (*pm_power_off)(void);
/* Stuff from the get device id command. */
unsigned int mfg_id;
unsigned int prod_id;
unsigned char capabilities;
/* We use our own messages for this operation, we don't let the system
allocate them, since we may be in a panic situation. The whole
thing is single-threaded, anyway, so multiple messages are not
required. */
static void dummy_smi_free(struct ipmi_smi_msg *msg)
{
}
static void dummy_recv_free(struct ipmi_recv_msg *msg)
{
}
static struct ipmi_smi_msg halt_smi_msg =
{
.done = dummy_smi_free
};
static struct ipmi_recv_msg halt_recv_msg =
{
.done = dummy_recv_free
};
/*
* Code to send a message and wait for the reponse.
*/
static void receive_handler(struct ipmi_recv_msg *recv_msg, void *handler_data)
{
struct semaphore *sem = recv_msg->user_msg_data;
if (sem)
up(sem);
}
static struct ipmi_user_hndl ipmi_poweroff_handler =
{
.ipmi_recv_hndl = receive_handler
};
static int ipmi_request_wait_for_response(ipmi_user_t user,
struct ipmi_addr *addr,
struct kernel_ipmi_msg *send_msg)
{
int rv;
struct semaphore sem;
sema_init (&sem, 0);
rv = ipmi_request_supply_msgs(user, addr, 0, send_msg, &sem,
&halt_smi_msg, &halt_recv_msg, 0);
if (rv)
return rv;
down (&sem);
return halt_recv_msg.msg.data[0];
}
/* We are in run-to-completion mode, no semaphore is desired. */
static int ipmi_request_in_rc_mode(ipmi_user_t user,
struct ipmi_addr *addr,
struct kernel_ipmi_msg *send_msg)
{
int rv;
rv = ipmi_request_supply_msgs(user, addr, 0, send_msg, NULL,
&halt_smi_msg, &halt_recv_msg, 0);
if (rv)
return rv;
return halt_recv_msg.msg.data[0];
}
/*
* ATCA Support
*/
#define IPMI_NETFN_ATCA 0x2c
#define IPMI_ATCA_SET_POWER_CMD 0x11
#define IPMI_ATCA_GET_ADDR_INFO_CMD 0x01
#define IPMI_PICMG_ID 0
static int ipmi_atca_detect (ipmi_user_t user)
{
struct ipmi_system_interface_addr smi_addr;
struct kernel_ipmi_msg send_msg;
int rv;
unsigned char data[1];
/*
* Configure IPMI address for local access
*/
smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
smi_addr.channel = IPMI_BMC_CHANNEL;
smi_addr.lun = 0;
/*
* Use get address info to check and see if we are ATCA
*/
send_msg.netfn = IPMI_NETFN_ATCA;
send_msg.cmd = IPMI_ATCA_GET_ADDR_INFO_CMD;
data[0] = IPMI_PICMG_ID;
send_msg.data = data;
send_msg.data_len = sizeof(data);
rv = ipmi_request_wait_for_response(user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
return !rv;
}
static void ipmi_poweroff_atca (ipmi_user_t user)
{
struct ipmi_system_interface_addr smi_addr;
struct kernel_ipmi_msg send_msg;
int rv;
unsigned char data[4];
/*
* Configure IPMI address for local access
*/
smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
smi_addr.channel = IPMI_BMC_CHANNEL;
smi_addr.lun = 0;
printk(KERN_INFO PFX "Powering down via ATCA power command\n");
/*
* Power down
*/
send_msg.netfn = IPMI_NETFN_ATCA;
send_msg.cmd = IPMI_ATCA_SET_POWER_CMD;
data[0] = IPMI_PICMG_ID;
data[1] = 0; /* FRU id */
data[2] = 0; /* Power Level */
data[3] = 0; /* Don't change saved presets */
send_msg.data = data;
send_msg.data_len = sizeof (data);
rv = ipmi_request_in_rc_mode(user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
if (rv) {
printk(KERN_ERR PFX "Unable to send ATCA powerdown message,"
" IPMI error 0x%x\n", rv);
goto out;
}
out:
return;
}
/*
* CPI1 Support
*/
#define IPMI_NETFN_OEM_1 0xf8
#define OEM_GRP_CMD_SET_RESET_STATE 0x84
#define OEM_GRP_CMD_SET_POWER_STATE 0x82
#define IPMI_NETFN_OEM_8 0xf8
#define OEM_GRP_CMD_REQUEST_HOTSWAP_CTRL 0x80
#define OEM_GRP_CMD_GET_SLOT_GA 0xa3
#define IPMI_NETFN_SENSOR_EVT 0x10
#define IPMI_CMD_GET_EVENT_RECEIVER 0x01
#define IPMI_CPI1_PRODUCT_ID 0x000157
#define IPMI_CPI1_MANUFACTURER_ID 0x0108
static int ipmi_cpi1_detect (ipmi_user_t user)
{
return ((mfg_id == IPMI_CPI1_MANUFACTURER_ID)
&& (prod_id == IPMI_CPI1_PRODUCT_ID));
}
static void ipmi_poweroff_cpi1 (ipmi_user_t user)
{
struct ipmi_system_interface_addr smi_addr;
struct ipmi_ipmb_addr ipmb_addr;
struct kernel_ipmi_msg send_msg;
int rv;
unsigned char data[1];
int slot;
unsigned char hotswap_ipmb;
unsigned char aer_addr;
unsigned char aer_lun;
/*
* Configure IPMI address for local access
*/
smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
smi_addr.channel = IPMI_BMC_CHANNEL;
smi_addr.lun = 0;
printk(KERN_INFO PFX "Powering down via CPI1 power command\n");
/*
* Get IPMI ipmb address
*/
send_msg.netfn = IPMI_NETFN_OEM_8 >> 2;
send_msg.cmd = OEM_GRP_CMD_GET_SLOT_GA;
send_msg.data = NULL;
send_msg.data_len = 0;
rv = ipmi_request_in_rc_mode(user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
if (rv)
goto out;
slot = halt_recv_msg.msg.data[1];
hotswap_ipmb = (slot > 9) ? (0xb0 + 2 * slot) : (0xae + 2 * slot);
/*
* Get active event receiver
*/
send_msg.netfn = IPMI_NETFN_SENSOR_EVT >> 2;
send_msg.cmd = IPMI_CMD_GET_EVENT_RECEIVER;
send_msg.data = NULL;
send_msg.data_len = 0;
rv = ipmi_request_in_rc_mode(user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
if (rv)
goto out;
aer_addr = halt_recv_msg.msg.data[1];
aer_lun = halt_recv_msg.msg.data[2];
/*
* Setup IPMB address target instead of local target
*/
ipmb_addr.addr_type = IPMI_IPMB_ADDR_TYPE;
ipmb_addr.channel = 0;
ipmb_addr.slave_addr = aer_addr;
ipmb_addr.lun = aer_lun;
/*
* Send request hotswap control to remove blade from dpv
*/
send_msg.netfn = IPMI_NETFN_OEM_8 >> 2;
send_msg.cmd = OEM_GRP_CMD_REQUEST_HOTSWAP_CTRL;
send_msg.data = &hotswap_ipmb;
send_msg.data_len = 1;
ipmi_request_in_rc_mode(user,
(struct ipmi_addr *) &ipmb_addr,
&send_msg);
/*
* Set reset asserted
*/
send_msg.netfn = IPMI_NETFN_OEM_1 >> 2;
send_msg.cmd = OEM_GRP_CMD_SET_RESET_STATE;
send_msg.data = data;
data[0] = 1; /* Reset asserted state */
send_msg.data_len = 1;
rv = ipmi_request_in_rc_mode(user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
if (rv)
goto out;
/*
* Power down
*/
send_msg.netfn = IPMI_NETFN_OEM_1 >> 2;
send_msg.cmd = OEM_GRP_CMD_SET_POWER_STATE;
send_msg.data = data;
data[0] = 1; /* Power down state */
send_msg.data_len = 1;
rv = ipmi_request_in_rc_mode(user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
if (rv)
goto out;
out:
return;
}
/*
* Standard chassis support
*/
#define IPMI_NETFN_CHASSIS_REQUEST 0
#define IPMI_CHASSIS_CONTROL_CMD 0x02
static int ipmi_chassis_detect (ipmi_user_t user)
{
/* Chassis support, use it. */
return (capabilities & 0x80);
}
static void ipmi_poweroff_chassis (ipmi_user_t user)
{
struct ipmi_system_interface_addr smi_addr;
struct kernel_ipmi_msg send_msg;
int rv;
unsigned char data[1];
/*
* Configure IPMI address for local access
*/
smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
smi_addr.channel = IPMI_BMC_CHANNEL;
smi_addr.lun = 0;
printk(KERN_INFO PFX "Powering down via IPMI chassis control command\n");
/*
* Power down
*/
send_msg.netfn = IPMI_NETFN_CHASSIS_REQUEST;
send_msg.cmd = IPMI_CHASSIS_CONTROL_CMD;
data[0] = 0; /* Power down */
send_msg.data = data;
send_msg.data_len = sizeof(data);
rv = ipmi_request_in_rc_mode(user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
if (rv) {
printk(KERN_ERR PFX "Unable to send chassis powerdown message,"
" IPMI error 0x%x\n", rv);
goto out;
}
out:
return;
}
/* Table of possible power off functions. */
struct poweroff_function {
char *platform_type;
int (*detect)(ipmi_user_t user);
void (*poweroff_func)(ipmi_user_t user);
};
static struct poweroff_function poweroff_functions[] = {
{ "ATCA", ipmi_atca_detect, ipmi_poweroff_atca },
{ "CPI1", ipmi_cpi1_detect, ipmi_poweroff_cpi1 },
/* Chassis should generally be last, other things should override
it. */
{ "chassis", ipmi_chassis_detect, ipmi_poweroff_chassis },
};
#define NUM_PO_FUNCS (sizeof(poweroff_functions) \
/ sizeof(struct poweroff_function))
/* Our local state. */
static int ready = 0;
static ipmi_user_t ipmi_user;
static void (*specific_poweroff_func)(ipmi_user_t user) = NULL;
/* Holds the old poweroff function so we can restore it on removal. */
static void (*old_poweroff_func)(void);
/* Called on a powerdown request. */
static void ipmi_poweroff_function (void)
{
if (!ready)
return;
/* Use run-to-completion mode, since interrupts may be off. */
ipmi_user_set_run_to_completion(ipmi_user, 1);
specific_poweroff_func(ipmi_user);
ipmi_user_set_run_to_completion(ipmi_user, 0);
}
/* Wait for an IPMI interface to be installed, the first one installed
will be grabbed by this code and used to perform the powerdown. */
static void ipmi_po_new_smi(int if_num)
{
struct ipmi_system_interface_addr smi_addr;
struct kernel_ipmi_msg send_msg;
int rv;
int i;
if (ready)
return;
rv = ipmi_create_user(if_num, &ipmi_poweroff_handler, NULL, &ipmi_user);
if (rv) {
printk(KERN_ERR PFX "could not create IPMI user, error %d\n",
rv);
return;
}
/*
* Do a get device ide and store some results, since this is
* used by several functions.
*/
smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
smi_addr.channel = IPMI_BMC_CHANNEL;
smi_addr.lun = 0;
send_msg.netfn = IPMI_NETFN_APP_REQUEST;
send_msg.cmd = IPMI_GET_DEVICE_ID_CMD;
send_msg.data = NULL;
send_msg.data_len = 0;
rv = ipmi_request_wait_for_response(ipmi_user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
if (rv) {
printk(KERN_ERR PFX "Unable to send IPMI get device id info,"
" IPMI error 0x%x\n", rv);
goto out_err;
}
if (halt_recv_msg.msg.data_len < 12) {
printk(KERN_ERR PFX "(chassis) IPMI get device id info too,"
" short, was %d bytes, needed %d bytes\n",
halt_recv_msg.msg.data_len, 12);
goto out_err;
}
mfg_id = (halt_recv_msg.msg.data[7]
| (halt_recv_msg.msg.data[8] << 8)
| (halt_recv_msg.msg.data[9] << 16));
prod_id = (halt_recv_msg.msg.data[10]
| (halt_recv_msg.msg.data[11] << 8));
capabilities = halt_recv_msg.msg.data[6];
/* Scan for a poweroff method */
for (i=0; i<NUM_PO_FUNCS; i++) {
if (poweroff_functions[i].detect(ipmi_user))
goto found;
}
out_err:
printk(KERN_ERR PFX "Unable to find a poweroff function that"
" will work, giving up\n");
ipmi_destroy_user(ipmi_user);
return;
found:
printk(KERN_INFO PFX "Found a %s style poweroff function\n",
poweroff_functions[i].platform_type);
specific_poweroff_func = poweroff_functions[i].poweroff_func;
old_poweroff_func = pm_power_off;
pm_power_off = ipmi_poweroff_function;
ready = 1;
}
static void ipmi_po_smi_gone(int if_num)
{
/* This can never be called, because once poweroff driver is
registered, the interface can't go away until the power
driver is unregistered. */
}
static struct ipmi_smi_watcher smi_watcher =
{
.owner = THIS_MODULE,
.new_smi = ipmi_po_new_smi,
.smi_gone = ipmi_po_smi_gone
};
/*
* Startup and shutdown functions.
*/
static int ipmi_poweroff_init (void)
{
int rv;
printk ("Copyright (C) 2004 MontaVista Software -"
" IPMI Powerdown via sys_reboot version "
IPMI_POWEROFF_VERSION ".\n");
rv = ipmi_smi_watcher_register(&smi_watcher);
if (rv)
printk(KERN_ERR PFX "Unable to register SMI watcher: %d\n", rv);
return rv;
}
#ifdef MODULE
static __exit void ipmi_poweroff_cleanup(void)
{
int rv;
ipmi_smi_watcher_unregister(&smi_watcher);
if (ready) {
rv = ipmi_destroy_user(ipmi_user);
if (rv)
printk(KERN_ERR PFX "could not cleanup the IPMI"
" user: 0x%x\n", rv);
pm_power_off = old_poweroff_func;
}
}
module_exit(ipmi_poweroff_cleanup);
#endif
module_init(ipmi_poweroff_init);
MODULE_LICENSE("GPL");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,120 @@
/*
* ipmi_si_sm.h
*
* State machine interface for low-level IPMI system management
* interface state machines. This code is the interface between
* the ipmi_smi code (that handles the policy of a KCS, SMIC, or
* BT interface) and the actual low-level state machine.
*
* Author: MontaVista Software, Inc.
* Corey Minyard <minyard@mvista.com>
* source@mvista.com
*
* Copyright 2002 MontaVista Software Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*
* THIS SOFTWARE IS PROVIDED ``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 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* This is defined by the state machines themselves, it is an opaque
data type for them to use. */
struct si_sm_data;
/* The structure for doing I/O in the state machine. The state
machine doesn't have the actual I/O routines, they are done through
this interface. */
struct si_sm_io
{
unsigned char (*inputb)(struct si_sm_io *io, unsigned int offset);
void (*outputb)(struct si_sm_io *io,
unsigned int offset,
unsigned char b);
/* Generic info used by the actual handling routines, the
state machine shouldn't touch these. */
void *info;
void *addr;
int regspacing;
int regsize;
int regshift;
};
/* Results of SMI events. */
enum si_sm_result
{
SI_SM_CALL_WITHOUT_DELAY, /* Call the driver again immediately */
SI_SM_CALL_WITH_DELAY, /* Delay some before calling again. */
SI_SM_TRANSACTION_COMPLETE, /* A transaction is finished. */
SI_SM_IDLE, /* The SM is in idle state. */
SI_SM_HOSED, /* The hardware violated the state machine. */
SI_SM_ATTN /* The hardware is asserting attn and the
state machine is idle. */
};
/* Handlers for the SMI state machine. */
struct si_sm_handlers
{
/* Put the version number of the state machine here so the
upper layer can print it. */
char *version;
/* Initialize the data and return the amount of I/O space to
reserve for the space. */
unsigned int (*init_data)(struct si_sm_data *smi,
struct si_sm_io *io);
/* Start a new transaction in the state machine. This will
return -2 if the state machine is not idle, -1 if the size
is invalid (to large or too small), or 0 if the transaction
is successfully completed. */
int (*start_transaction)(struct si_sm_data *smi,
unsigned char *data, unsigned int size);
/* Return the results after the transaction. This will return
-1 if the buffer is too small, zero if no transaction is
present, or the actual length of the result data. */
int (*get_result)(struct si_sm_data *smi,
unsigned char *data, unsigned int length);
/* Call this periodically (for a polled interface) or upon
receiving an interrupt (for a interrupt-driven interface).
If interrupt driven, you should probably poll this
periodically when not in idle state. This should be called
with the time that passed since the last call, if it is
significant. Time is in microseconds. */
enum si_sm_result (*event)(struct si_sm_data *smi, long time);
/* Attempt to detect an SMI. Returns 0 on success or nonzero
on failure. */
int (*detect)(struct si_sm_data *smi);
/* The interface is shutting down, so clean it up. */
void (*cleanup)(struct si_sm_data *smi);
/* Return the size of the SMI structure in bytes. */
int (*size)(void);
};
/* Current state machines that we can use. */
extern struct si_sm_handlers kcs_smi_handlers;
extern struct si_sm_handlers smic_smi_handlers;
extern struct si_sm_handlers bt_smi_handlers;

View File

@@ -0,0 +1,599 @@
/*
* ipmi_smic_sm.c
*
* The state-machine driver for an IPMI SMIC driver
*
* It started as a copy of Corey Minyard's driver for the KSC interface
* and the kernel patch "mmcdev-patch-245" by HP
*
* modified by: Hannes Schulz <schulz@schwaar.com>
* ipmi@schwaar.com
*
*
* Corey Minyard's driver for the KSC interface has the following
* copyright notice:
* Copyright 2002 MontaVista Software Inc.
*
* the kernel patch "mmcdev-patch-245" by HP has the following
* copyright notice:
* (c) Copyright 2001 Grant Grundler (c) Copyright
* 2001 Hewlett-Packard Company
*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*
* THIS SOFTWARE IS PROVIDED ``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 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA. */
#include <linux/kernel.h> /* For printk. */
#include <linux/string.h>
#include <linux/ipmi_msgdefs.h> /* for completion codes */
#include "ipmi_si_sm.h"
#define IPMI_SMIC_VERSION "v33"
/* smic_debug is a bit-field
* SMIC_DEBUG_ENABLE - turned on for now
* SMIC_DEBUG_MSG - commands and their responses
* SMIC_DEBUG_STATES - state machine
*/
#define SMIC_DEBUG_STATES 4
#define SMIC_DEBUG_MSG 2
#define SMIC_DEBUG_ENABLE 1
static int smic_debug = 1;
enum smic_states {
SMIC_IDLE,
SMIC_START_OP,
SMIC_OP_OK,
SMIC_WRITE_START,
SMIC_WRITE_NEXT,
SMIC_WRITE_END,
SMIC_WRITE2READ,
SMIC_READ_START,
SMIC_READ_NEXT,
SMIC_READ_END,
SMIC_HOSED
};
#define MAX_SMIC_READ_SIZE 80
#define MAX_SMIC_WRITE_SIZE 80
#define SMIC_MAX_ERROR_RETRIES 3
/* Timeouts in microseconds. */
#define SMIC_RETRY_TIMEOUT 100000
/* SMIC Flags Register Bits */
#define SMIC_RX_DATA_READY 0x80
#define SMIC_TX_DATA_READY 0x40
#define SMIC_SMI 0x10
#define SMIC_EVM_DATA_AVAIL 0x08
#define SMIC_SMS_DATA_AVAIL 0x04
#define SMIC_FLAG_BSY 0x01
/* SMIC Error Codes */
#define EC_NO_ERROR 0x00
#define EC_ABORTED 0x01
#define EC_ILLEGAL_CONTROL 0x02
#define EC_NO_RESPONSE 0x03
#define EC_ILLEGAL_COMMAND 0x04
#define EC_BUFFER_FULL 0x05
struct si_sm_data
{
enum smic_states state;
struct si_sm_io *io;
unsigned char write_data[MAX_SMIC_WRITE_SIZE];
int write_pos;
int write_count;
int orig_write_count;
unsigned char read_data[MAX_SMIC_READ_SIZE];
int read_pos;
int truncated;
unsigned int error_retries;
long smic_timeout;
};
static unsigned int init_smic_data (struct si_sm_data *smic,
struct si_sm_io *io)
{
smic->state = SMIC_IDLE;
smic->io = io;
smic->write_pos = 0;
smic->write_count = 0;
smic->orig_write_count = 0;
smic->read_pos = 0;
smic->error_retries = 0;
smic->truncated = 0;
smic->smic_timeout = SMIC_RETRY_TIMEOUT;
/* We use 3 bytes of I/O. */
return 3;
}
static int start_smic_transaction(struct si_sm_data *smic,
unsigned char *data, unsigned int size)
{
unsigned int i;
if ((size < 2) || (size > MAX_SMIC_WRITE_SIZE)) {
return -1;
}
if ((smic->state != SMIC_IDLE) && (smic->state != SMIC_HOSED)) {
return -2;
}
if (smic_debug & SMIC_DEBUG_MSG) {
printk(KERN_INFO "start_smic_transaction -");
for (i = 0; i < size; i ++) {
printk (" %02x", (unsigned char) (data [i]));
}
printk ("\n");
}
smic->error_retries = 0;
memcpy(smic->write_data, data, size);
smic->write_count = size;
smic->orig_write_count = size;
smic->write_pos = 0;
smic->read_pos = 0;
smic->state = SMIC_START_OP;
smic->smic_timeout = SMIC_RETRY_TIMEOUT;
return 0;
}
static int smic_get_result(struct si_sm_data *smic,
unsigned char *data, unsigned int length)
{
int i;
if (smic_debug & SMIC_DEBUG_MSG) {
printk (KERN_INFO "smic_get result -");
for (i = 0; i < smic->read_pos; i ++) {
printk (" %02x", (smic->read_data [i]));
}
printk ("\n");
}
if (length < smic->read_pos) {
smic->read_pos = length;
smic->truncated = 1;
}
memcpy(data, smic->read_data, smic->read_pos);
if ((length >= 3) && (smic->read_pos < 3)) {
data[2] = IPMI_ERR_UNSPECIFIED;
smic->read_pos = 3;
}
if (smic->truncated) {
data[2] = IPMI_ERR_MSG_TRUNCATED;
smic->truncated = 0;
}
return smic->read_pos;
}
static inline unsigned char read_smic_flags(struct si_sm_data *smic)
{
return smic->io->inputb(smic->io, 2);
}
static inline unsigned char read_smic_status(struct si_sm_data *smic)
{
return smic->io->inputb(smic->io, 1);
}
static inline unsigned char read_smic_data(struct si_sm_data *smic)
{
return smic->io->inputb(smic->io, 0);
}
static inline void write_smic_flags(struct si_sm_data *smic,
unsigned char flags)
{
smic->io->outputb(smic->io, 2, flags);
}
static inline void write_smic_control(struct si_sm_data *smic,
unsigned char control)
{
smic->io->outputb(smic->io, 1, control);
}
static inline void write_si_sm_data (struct si_sm_data *smic,
unsigned char data)
{
smic->io->outputb(smic->io, 0, data);
}
static inline void start_error_recovery(struct si_sm_data *smic, char *reason)
{
(smic->error_retries)++;
if (smic->error_retries > SMIC_MAX_ERROR_RETRIES) {
if (smic_debug & SMIC_DEBUG_ENABLE) {
printk(KERN_WARNING
"ipmi_smic_drv: smic hosed: %s\n", reason);
}
smic->state = SMIC_HOSED;
} else {
smic->write_count = smic->orig_write_count;
smic->write_pos = 0;
smic->read_pos = 0;
smic->state = SMIC_START_OP;
smic->smic_timeout = SMIC_RETRY_TIMEOUT;
}
}
static inline void write_next_byte(struct si_sm_data *smic)
{
write_si_sm_data(smic, smic->write_data[smic->write_pos]);
(smic->write_pos)++;
(smic->write_count)--;
}
static inline void read_next_byte (struct si_sm_data *smic)
{
if (smic->read_pos >= MAX_SMIC_READ_SIZE) {
read_smic_data (smic);
smic->truncated = 1;
} else {
smic->read_data[smic->read_pos] = read_smic_data(smic);
(smic->read_pos)++;
}
}
/* SMIC Control/Status Code Components */
#define SMIC_GET_STATUS 0x00 /* Control form's name */
#define SMIC_READY 0x00 /* Status form's name */
#define SMIC_WR_START 0x01 /* Unified Control/Status names... */
#define SMIC_WR_NEXT 0x02
#define SMIC_WR_END 0x03
#define SMIC_RD_START 0x04
#define SMIC_RD_NEXT 0x05
#define SMIC_RD_END 0x06
#define SMIC_CODE_MASK 0x0f
#define SMIC_CONTROL 0x00
#define SMIC_STATUS 0x80
#define SMIC_CS_MASK 0x80
#define SMIC_SMS 0x40
#define SMIC_SMM 0x60
#define SMIC_STREAM_MASK 0x60
/* SMIC Control Codes */
#define SMIC_CC_SMS_GET_STATUS (SMIC_CONTROL|SMIC_SMS|SMIC_GET_STATUS)
#define SMIC_CC_SMS_WR_START (SMIC_CONTROL|SMIC_SMS|SMIC_WR_START)
#define SMIC_CC_SMS_WR_NEXT (SMIC_CONTROL|SMIC_SMS|SMIC_WR_NEXT)
#define SMIC_CC_SMS_WR_END (SMIC_CONTROL|SMIC_SMS|SMIC_WR_END)
#define SMIC_CC_SMS_RD_START (SMIC_CONTROL|SMIC_SMS|SMIC_RD_START)
#define SMIC_CC_SMS_RD_NEXT (SMIC_CONTROL|SMIC_SMS|SMIC_RD_NEXT)
#define SMIC_CC_SMS_RD_END (SMIC_CONTROL|SMIC_SMS|SMIC_RD_END)
#define SMIC_CC_SMM_GET_STATUS (SMIC_CONTROL|SMIC_SMM|SMIC_GET_STATUS)
#define SMIC_CC_SMM_WR_START (SMIC_CONTROL|SMIC_SMM|SMIC_WR_START)
#define SMIC_CC_SMM_WR_NEXT (SMIC_CONTROL|SMIC_SMM|SMIC_WR_NEXT)
#define SMIC_CC_SMM_WR_END (SMIC_CONTROL|SMIC_SMM|SMIC_WR_END)
#define SMIC_CC_SMM_RD_START (SMIC_CONTROL|SMIC_SMM|SMIC_RD_START)
#define SMIC_CC_SMM_RD_NEXT (SMIC_CONTROL|SMIC_SMM|SMIC_RD_NEXT)
#define SMIC_CC_SMM_RD_END (SMIC_CONTROL|SMIC_SMM|SMIC_RD_END)
/* SMIC Status Codes */
#define SMIC_SC_SMS_READY (SMIC_STATUS|SMIC_SMS|SMIC_READY)
#define SMIC_SC_SMS_WR_START (SMIC_STATUS|SMIC_SMS|SMIC_WR_START)
#define SMIC_SC_SMS_WR_NEXT (SMIC_STATUS|SMIC_SMS|SMIC_WR_NEXT)
#define SMIC_SC_SMS_WR_END (SMIC_STATUS|SMIC_SMS|SMIC_WR_END)
#define SMIC_SC_SMS_RD_START (SMIC_STATUS|SMIC_SMS|SMIC_RD_START)
#define SMIC_SC_SMS_RD_NEXT (SMIC_STATUS|SMIC_SMS|SMIC_RD_NEXT)
#define SMIC_SC_SMS_RD_END (SMIC_STATUS|SMIC_SMS|SMIC_RD_END)
#define SMIC_SC_SMM_READY (SMIC_STATUS|SMIC_SMM|SMIC_READY)
#define SMIC_SC_SMM_WR_START (SMIC_STATUS|SMIC_SMM|SMIC_WR_START)
#define SMIC_SC_SMM_WR_NEXT (SMIC_STATUS|SMIC_SMM|SMIC_WR_NEXT)
#define SMIC_SC_SMM_WR_END (SMIC_STATUS|SMIC_SMM|SMIC_WR_END)
#define SMIC_SC_SMM_RD_START (SMIC_STATUS|SMIC_SMM|SMIC_RD_START)
#define SMIC_SC_SMM_RD_NEXT (SMIC_STATUS|SMIC_SMM|SMIC_RD_NEXT)
#define SMIC_SC_SMM_RD_END (SMIC_STATUS|SMIC_SMM|SMIC_RD_END)
/* these are the control/status codes we actually use
SMIC_CC_SMS_GET_STATUS 0x40
SMIC_CC_SMS_WR_START 0x41
SMIC_CC_SMS_WR_NEXT 0x42
SMIC_CC_SMS_WR_END 0x43
SMIC_CC_SMS_RD_START 0x44
SMIC_CC_SMS_RD_NEXT 0x45
SMIC_CC_SMS_RD_END 0x46
SMIC_SC_SMS_READY 0xC0
SMIC_SC_SMS_WR_START 0xC1
SMIC_SC_SMS_WR_NEXT 0xC2
SMIC_SC_SMS_WR_END 0xC3
SMIC_SC_SMS_RD_START 0xC4
SMIC_SC_SMS_RD_NEXT 0xC5
SMIC_SC_SMS_RD_END 0xC6
*/
static enum si_sm_result smic_event (struct si_sm_data *smic, long time)
{
unsigned char status;
unsigned char flags;
unsigned char data;
if (smic->state == SMIC_HOSED) {
init_smic_data(smic, smic->io);
return SI_SM_HOSED;
}
if (smic->state != SMIC_IDLE) {
if (smic_debug & SMIC_DEBUG_STATES) {
printk(KERN_INFO
"smic_event - smic->smic_timeout = %ld,"
" time = %ld\n",
smic->smic_timeout, time);
}
/* FIXME: smic_event is sometimes called with time > SMIC_RETRY_TIMEOUT */
if (time < SMIC_RETRY_TIMEOUT) {
smic->smic_timeout -= time;
if (smic->smic_timeout < 0) {
start_error_recovery(smic, "smic timed out.");
return SI_SM_CALL_WITH_DELAY;
}
}
}
flags = read_smic_flags(smic);
if (flags & SMIC_FLAG_BSY)
return SI_SM_CALL_WITH_DELAY;
status = read_smic_status (smic);
if (smic_debug & SMIC_DEBUG_STATES)
printk(KERN_INFO
"smic_event - state = %d, flags = 0x%02x,"
" status = 0x%02x\n",
smic->state, flags, status);
switch (smic->state) {
case SMIC_IDLE:
/* in IDLE we check for available messages */
if (flags & (SMIC_SMI |
SMIC_EVM_DATA_AVAIL | SMIC_SMS_DATA_AVAIL))
{
return SI_SM_ATTN;
}
return SI_SM_IDLE;
case SMIC_START_OP:
/* sanity check whether smic is really idle */
write_smic_control(smic, SMIC_CC_SMS_GET_STATUS);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
smic->state = SMIC_OP_OK;
break;
case SMIC_OP_OK:
if (status != SMIC_SC_SMS_READY) {
/* this should not happen */
start_error_recovery(smic,
"state = SMIC_OP_OK,"
" status != SMIC_SC_SMS_READY");
return SI_SM_CALL_WITH_DELAY;
}
/* OK so far; smic is idle let us start ... */
write_smic_control(smic, SMIC_CC_SMS_WR_START);
write_next_byte(smic);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
smic->state = SMIC_WRITE_START;
break;
case SMIC_WRITE_START:
if (status != SMIC_SC_SMS_WR_START) {
start_error_recovery(smic,
"state = SMIC_WRITE_START, "
"status != SMIC_SC_SMS_WR_START");
return SI_SM_CALL_WITH_DELAY;
}
/* we must not issue WR_(NEXT|END) unless
TX_DATA_READY is set */
if (flags & SMIC_TX_DATA_READY) {
if (smic->write_count == 1) {
/* last byte */
write_smic_control(smic, SMIC_CC_SMS_WR_END);
smic->state = SMIC_WRITE_END;
} else {
write_smic_control(smic, SMIC_CC_SMS_WR_NEXT);
smic->state = SMIC_WRITE_NEXT;
}
write_next_byte(smic);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
}
else {
return SI_SM_CALL_WITH_DELAY;
}
break;
case SMIC_WRITE_NEXT:
if (status != SMIC_SC_SMS_WR_NEXT) {
start_error_recovery(smic,
"state = SMIC_WRITE_NEXT, "
"status != SMIC_SC_SMS_WR_NEXT");
return SI_SM_CALL_WITH_DELAY;
}
/* this is the same code as in SMIC_WRITE_START */
if (flags & SMIC_TX_DATA_READY) {
if (smic->write_count == 1) {
write_smic_control(smic, SMIC_CC_SMS_WR_END);
smic->state = SMIC_WRITE_END;
}
else {
write_smic_control(smic, SMIC_CC_SMS_WR_NEXT);
smic->state = SMIC_WRITE_NEXT;
}
write_next_byte(smic);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
}
else {
return SI_SM_CALL_WITH_DELAY;
}
break;
case SMIC_WRITE_END:
if (status != SMIC_SC_SMS_WR_END) {
start_error_recovery (smic,
"state = SMIC_WRITE_END, "
"status != SMIC_SC_SMS_WR_END");
return SI_SM_CALL_WITH_DELAY;
}
/* data register holds an error code */
data = read_smic_data(smic);
if (data != 0) {
if (smic_debug & SMIC_DEBUG_ENABLE) {
printk(KERN_INFO
"SMIC_WRITE_END: data = %02x\n", data);
}
start_error_recovery(smic,
"state = SMIC_WRITE_END, "
"data != SUCCESS");
return SI_SM_CALL_WITH_DELAY;
} else {
smic->state = SMIC_WRITE2READ;
}
break;
case SMIC_WRITE2READ:
/* we must wait for RX_DATA_READY to be set before we
can continue */
if (flags & SMIC_RX_DATA_READY) {
write_smic_control(smic, SMIC_CC_SMS_RD_START);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
smic->state = SMIC_READ_START;
} else {
return SI_SM_CALL_WITH_DELAY;
}
break;
case SMIC_READ_START:
if (status != SMIC_SC_SMS_RD_START) {
start_error_recovery(smic,
"state = SMIC_READ_START, "
"status != SMIC_SC_SMS_RD_START");
return SI_SM_CALL_WITH_DELAY;
}
if (flags & SMIC_RX_DATA_READY) {
read_next_byte(smic);
write_smic_control(smic, SMIC_CC_SMS_RD_NEXT);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
smic->state = SMIC_READ_NEXT;
} else {
return SI_SM_CALL_WITH_DELAY;
}
break;
case SMIC_READ_NEXT:
switch (status) {
/* smic tells us that this is the last byte to be read
--> clean up */
case SMIC_SC_SMS_RD_END:
read_next_byte(smic);
write_smic_control(smic, SMIC_CC_SMS_RD_END);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
smic->state = SMIC_READ_END;
break;
case SMIC_SC_SMS_RD_NEXT:
if (flags & SMIC_RX_DATA_READY) {
read_next_byte(smic);
write_smic_control(smic, SMIC_CC_SMS_RD_NEXT);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
smic->state = SMIC_READ_NEXT;
} else {
return SI_SM_CALL_WITH_DELAY;
}
break;
default:
start_error_recovery(
smic,
"state = SMIC_READ_NEXT, "
"status != SMIC_SC_SMS_RD_(NEXT|END)");
return SI_SM_CALL_WITH_DELAY;
}
break;
case SMIC_READ_END:
if (status != SMIC_SC_SMS_READY) {
start_error_recovery(smic,
"state = SMIC_READ_END, "
"status != SMIC_SC_SMS_READY");
return SI_SM_CALL_WITH_DELAY;
}
data = read_smic_data(smic);
/* data register holds an error code */
if (data != 0) {
if (smic_debug & SMIC_DEBUG_ENABLE) {
printk(KERN_INFO
"SMIC_READ_END: data = %02x\n", data);
}
start_error_recovery(smic,
"state = SMIC_READ_END, "
"data != SUCCESS");
return SI_SM_CALL_WITH_DELAY;
} else {
smic->state = SMIC_IDLE;
return SI_SM_TRANSACTION_COMPLETE;
}
case SMIC_HOSED:
init_smic_data(smic, smic->io);
return SI_SM_HOSED;
default:
if (smic_debug & SMIC_DEBUG_ENABLE) {
printk(KERN_WARNING "smic->state = %d\n", smic->state);
start_error_recovery(smic, "state = UNKNOWN");
return SI_SM_CALL_WITH_DELAY;
}
}
smic->smic_timeout = SMIC_RETRY_TIMEOUT;
return SI_SM_CALL_WITHOUT_DELAY;
}
static int smic_detect(struct si_sm_data *smic)
{
/* It's impossible for the SMIC fnags register to be all 1's,
(assuming a properly functioning, self-initialized BMC)
but that's what you get from reading a bogus address, so we
test that first. */
if (read_smic_flags(smic) == 0xff)
return 1;
return 0;
}
static void smic_cleanup(struct si_sm_data *kcs)
{
}
static int smic_size(void)
{
return sizeof(struct si_sm_data);
}
struct si_sm_handlers smic_smi_handlers =
{
.version = IPMI_SMIC_VERSION,
.init_data = init_smic_data,
.start_transaction = start_smic_transaction,
.get_result = smic_get_result,
.event = smic_event,
.detect = smic_detect,
.cleanup = smic_cleanup,
.size = smic_size,
};

File diff suppressed because it is too large Load Diff