summaryrefslogtreecommitdiffstats
path: root/drivers/char/ipmi
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/ipmi')
-rw-r--r--drivers/char/ipmi/Kconfig67
-rw-r--r--drivers/char/ipmi/Makefile15
-rw-r--r--drivers/char/ipmi/ipmi_bt_sm.c513
-rw-r--r--drivers/char/ipmi/ipmi_devintf.c582
-rw-r--r--drivers/char/ipmi/ipmi_kcs_sm.c500
-rw-r--r--drivers/char/ipmi/ipmi_msghandler.c3174
-rw-r--r--drivers/char/ipmi/ipmi_poweroff.c549
-rw-r--r--drivers/char/ipmi/ipmi_si_intf.c2359
-rw-r--r--drivers/char/ipmi/ipmi_si_sm.h120
-rw-r--r--drivers/char/ipmi/ipmi_smic_sm.c599
-rw-r--r--drivers/char/ipmi/ipmi_watchdog.c1068
11 files changed, 9546 insertions, 0 deletions
diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig
new file mode 100644
index 0000000..a6dcb29
--- /dev/null
+++ b/drivers/char/ipmi/Kconfig
@@ -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 <file: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
diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
new file mode 100644
index 0000000..553f0a4
--- /dev/null
+++ b/drivers/char/ipmi/Makefile
@@ -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)
+
diff --git a/drivers/char/ipmi/ipmi_bt_sm.c b/drivers/char/ipmi/ipmi_bt_sm.c
new file mode 100644
index 0000000..225b3301
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_bt_sm.c
@@ -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,
+};
diff --git a/drivers/char/ipmi/ipmi_devintf.c b/drivers/char/ipmi/ipmi_devintf.c
new file mode 100644
index 0000000..49d67f5
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_devintf.c
@@ -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");
diff --git a/drivers/char/ipmi/ipmi_kcs_sm.c b/drivers/char/ipmi/ipmi_kcs_sm.c
new file mode 100644
index 0000000..48cce24
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_kcs_sm.c
@@ -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,
+};
diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c
new file mode 100644
index 0000000..a6606a1
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_msghandler.c
@@ -0,0 +1,3174 @@
+/*
+ * ipmi_msghandler.c
+ *
+ * Incoming and outgoing message routing for an IPMI interface.
+ *
+ * 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/errno.h>
+#include <asm/system.h>
+#include <linux/sched.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <linux/ipmi.h>
+#include <linux/ipmi_smi.h>
+#include <linux/notifier.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+
+#define PFX "IPMI message handler: "
+#define IPMI_MSGHANDLER_VERSION "v33"
+
+static struct ipmi_recv_msg *ipmi_alloc_recv_msg(void);
+static int ipmi_init_msghandler(void);
+
+static int initialized = 0;
+
+static struct proc_dir_entry *proc_ipmi_root = NULL;
+
+#define MAX_EVENTS_IN_QUEUE 25
+
+/* Don't let a message sit in a queue forever, always time it with at lest
+ the max message timer. This is in milliseconds. */
+#define MAX_MSG_TIMEOUT 60000
+
+struct ipmi_user
+{
+ struct list_head link;
+
+ /* The upper layer that handles receive messages. */
+ struct ipmi_user_hndl *handler;
+ void *handler_data;
+
+ /* The interface this user is bound to. */
+ ipmi_smi_t intf;
+
+ /* Does this interface receive IPMI events? */
+ int gets_events;
+};
+
+struct cmd_rcvr
+{
+ struct list_head link;
+
+ ipmi_user_t user;
+ unsigned char netfn;
+ unsigned char cmd;
+};
+
+struct seq_table
+{
+ unsigned int inuse : 1;
+ unsigned int broadcast : 1;
+
+ unsigned long timeout;
+ unsigned long orig_timeout;
+ unsigned int retries_left;
+
+ /* To verify on an incoming send message response that this is
+ the message that the response is for, we keep a sequence id
+ and increment it every time we send a message. */
+ long seqid;
+
+ /* This is held so we can properly respond to the message on a
+ timeout, and it is used to hold the temporary data for
+ retransmission, too. */
+ struct ipmi_recv_msg *recv_msg;
+};
+
+/* Store the information in a msgid (long) to allow us to find a
+ sequence table entry from the msgid. */
+#define STORE_SEQ_IN_MSGID(seq, seqid) (((seq&0xff)<<26) | (seqid&0x3ffffff))
+
+#define GET_SEQ_FROM_MSGID(msgid, seq, seqid) \
+ do { \
+ seq = ((msgid >> 26) & 0x3f); \
+ seqid = (msgid & 0x3fffff); \
+ } while(0)
+
+#define NEXT_SEQID(seqid) (((seqid) + 1) & 0x3fffff)
+
+struct ipmi_channel
+{
+ unsigned char medium;
+ unsigned char protocol;
+};
+
+struct ipmi_proc_entry
+{
+ char *name;
+ struct ipmi_proc_entry *next;
+};
+
+#define IPMI_IPMB_NUM_SEQ 64
+#define IPMI_MAX_CHANNELS 8
+struct ipmi_smi
+{
+ /* What interface number are we? */
+ int intf_num;
+
+ /* The list of upper layers that are using me. We read-lock
+ this when delivering messages to the upper layer to keep
+ the user from going away while we are processing the
+ message. This means that you cannot add or delete a user
+ from the receive callback. */
+ rwlock_t users_lock;
+ struct list_head users;
+
+ /* Used for wake ups at startup. */
+ wait_queue_head_t waitq;
+
+ /* The IPMI version of the BMC on the other end. */
+ unsigned char version_major;
+ unsigned char version_minor;
+
+ /* This is the lower-layer's sender routine. */
+ struct ipmi_smi_handlers *handlers;
+ void *send_info;
+
+ /* A list of proc entries for this interface. This does not
+ need a lock, only one thread creates it and only one thread
+ destroys it. */
+ struct ipmi_proc_entry *proc_entries;
+
+ /* A table of sequence numbers for this interface. We use the
+ sequence numbers for IPMB messages that go out of the
+ interface to match them up with their responses. A routine
+ is called periodically to time the items in this list. */
+ spinlock_t seq_lock;
+ struct seq_table seq_table[IPMI_IPMB_NUM_SEQ];
+ int curr_seq;
+
+ /* Messages that were delayed for some reason (out of memory,
+ for instance), will go in here to be processed later in a
+ periodic timer interrupt. */
+ spinlock_t waiting_msgs_lock;
+ struct list_head waiting_msgs;
+
+ /* The list of command receivers that are registered for commands
+ on this interface. */
+ rwlock_t cmd_rcvr_lock;
+ struct list_head cmd_rcvrs;
+
+ /* Events that were queues because no one was there to receive
+ them. */
+ spinlock_t events_lock; /* For dealing with event stuff. */
+ struct list_head waiting_events;
+ unsigned int waiting_events_count; /* How many events in queue? */
+
+ /* This will be non-null if someone registers to receive all
+ IPMI commands (this is for interface emulation). There
+ may not be any things in the cmd_rcvrs list above when
+ this is registered. */
+ ipmi_user_t all_cmd_rcvr;
+
+ /* My slave address. This is initialized to IPMI_BMC_SLAVE_ADDR,
+ but may be changed by the user. */
+ unsigned char my_address;
+
+ /* My LUN. This should generally stay the SMS LUN, but just in
+ case... */
+ unsigned char my_lun;
+
+ /* The event receiver for my BMC, only really used at panic
+ shutdown as a place to store this. */
+ unsigned char event_receiver;
+ unsigned char event_receiver_lun;
+ unsigned char local_sel_device;
+ unsigned char local_event_generator;
+
+ /* A cheap hack, if this is non-null and a message to an
+ interface comes in with a NULL user, call this routine with
+ it. Note that the message will still be freed by the
+ caller. This only works on the system interface. */
+ void (*null_user_handler)(ipmi_smi_t intf, struct ipmi_smi_msg *msg);
+
+ /* When we are scanning the channels for an SMI, this will
+ tell which channel we are scanning. */
+ int curr_channel;
+
+ /* Channel information */
+ struct ipmi_channel channels[IPMI_MAX_CHANNELS];
+
+ /* Proc FS stuff. */
+ struct proc_dir_entry *proc_dir;
+ char proc_dir_name[10];
+
+ spinlock_t counter_lock; /* For making counters atomic. */
+
+ /* Commands we got that were invalid. */
+ unsigned int sent_invalid_commands;
+
+ /* Commands we sent to the MC. */
+ unsigned int sent_local_commands;
+ /* Responses from the MC that were delivered to a user. */
+ unsigned int handled_local_responses;
+ /* Responses from the MC that were not delivered to a user. */
+ unsigned int unhandled_local_responses;
+
+ /* Commands we sent out to the IPMB bus. */
+ unsigned int sent_ipmb_commands;
+ /* Commands sent on the IPMB that had errors on the SEND CMD */
+ unsigned int sent_ipmb_command_errs;
+ /* Each retransmit increments this count. */
+ unsigned int retransmitted_ipmb_commands;
+ /* When a message times out (runs out of retransmits) this is
+ incremented. */
+ unsigned int timed_out_ipmb_commands;
+
+ /* This is like above, but for broadcasts. Broadcasts are
+ *not* included in the above count (they are expected to
+ time out). */
+ unsigned int timed_out_ipmb_broadcasts;
+
+ /* Responses I have sent to the IPMB bus. */
+ unsigned int sent_ipmb_responses;
+
+ /* The response was delivered to the user. */
+ unsigned int handled_ipmb_responses;
+ /* The response had invalid data in it. */
+ unsigned int invalid_ipmb_responses;
+ /* The response didn't have anyone waiting for it. */
+ unsigned int unhandled_ipmb_responses;
+
+ /* Commands we sent out to the IPMB bus. */
+ unsigned int sent_lan_commands;
+ /* Commands sent on the IPMB that had errors on the SEND CMD */
+ unsigned int sent_lan_command_errs;
+ /* Each retransmit increments this count. */
+ unsigned int retransmitted_lan_commands;
+ /* When a message times out (runs out of retransmits) this is
+ incremented. */
+ unsigned int timed_out_lan_commands;
+
+ /* Responses I have sent to the IPMB bus. */
+ unsigned int sent_lan_responses;
+
+ /* The response was delivered to the user. */
+ unsigned int handled_lan_responses;
+ /* The response had invalid data in it. */
+ unsigned int invalid_lan_responses;
+ /* The response didn't have anyone waiting for it. */
+ unsigned int unhandled_lan_responses;
+
+ /* The command was delivered to the user. */
+ unsigned int handled_commands;
+ /* The command had invalid data in it. */
+ unsigned int invalid_commands;
+ /* The command didn't have anyone waiting for it. */
+ unsigned int unhandled_commands;
+
+ /* Invalid data in an event. */
+ unsigned int invalid_events;
+ /* Events that were received with the proper format. */
+ unsigned int events;
+};
+
+#define MAX_IPMI_INTERFACES 4
+static ipmi_smi_t ipmi_interfaces[MAX_IPMI_INTERFACES];
+
+/* Used to keep interfaces from going away while operations are
+ operating on interfaces. Grab read if you are not modifying the
+ interfaces, write if you are. */
+static DECLARE_RWSEM(interfaces_sem);
+
+/* Directly protects the ipmi_interfaces data structure. This is
+ claimed in the timer interrupt. */
+static DEFINE_SPINLOCK(interfaces_lock);
+
+/* List of watchers that want to know when smi's are added and
+ deleted. */
+static struct list_head smi_watchers = LIST_HEAD_INIT(smi_watchers);
+static DECLARE_RWSEM(smi_watchers_sem);
+
+int ipmi_smi_watcher_register(struct ipmi_smi_watcher *watcher)
+{
+ int i;
+
+ down_read(&interfaces_sem);
+ down_write(&smi_watchers_sem);
+ list_add(&(watcher->link), &smi_watchers);
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ if (ipmi_interfaces[i] != NULL) {
+ watcher->new_smi(i);
+ }
+ }
+ up_write(&smi_watchers_sem);
+ up_read(&interfaces_sem);
+ return 0;
+}
+
+int ipmi_smi_watcher_unregister(struct ipmi_smi_watcher *watcher)
+{
+ down_write(&smi_watchers_sem);
+ list_del(&(watcher->link));
+ up_write(&smi_watchers_sem);
+ return 0;
+}
+
+static void
+call_smi_watchers(int i)
+{
+ struct ipmi_smi_watcher *w;
+
+ down_read(&smi_watchers_sem);
+ list_for_each_entry(w, &smi_watchers, link) {
+ if (try_module_get(w->owner)) {
+ w->new_smi(i);
+ module_put(w->owner);
+ }
+ }
+ up_read(&smi_watchers_sem);
+}
+
+static int
+ipmi_addr_equal(struct ipmi_addr *addr1, struct ipmi_addr *addr2)
+{
+ if (addr1->addr_type != addr2->addr_type)
+ return 0;
+
+ if (addr1->channel != addr2->channel)
+ return 0;
+
+ if (addr1->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) {
+ struct ipmi_system_interface_addr *smi_addr1
+ = (struct ipmi_system_interface_addr *) addr1;
+ struct ipmi_system_interface_addr *smi_addr2
+ = (struct ipmi_system_interface_addr *) addr2;
+ return (smi_addr1->lun == smi_addr2->lun);
+ }
+
+ if ((addr1->addr_type == IPMI_IPMB_ADDR_TYPE)
+ || (addr1->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE))
+ {
+ struct ipmi_ipmb_addr *ipmb_addr1
+ = (struct ipmi_ipmb_addr *) addr1;
+ struct ipmi_ipmb_addr *ipmb_addr2
+ = (struct ipmi_ipmb_addr *) addr2;
+
+ return ((ipmb_addr1->slave_addr == ipmb_addr2->slave_addr)
+ && (ipmb_addr1->lun == ipmb_addr2->lun));
+ }
+
+ if (addr1->addr_type == IPMI_LAN_ADDR_TYPE) {
+ struct ipmi_lan_addr *lan_addr1
+ = (struct ipmi_lan_addr *) addr1;
+ struct ipmi_lan_addr *lan_addr2
+ = (struct ipmi_lan_addr *) addr2;
+
+ return ((lan_addr1->remote_SWID == lan_addr2->remote_SWID)
+ && (lan_addr1->local_SWID == lan_addr2->local_SWID)
+ && (lan_addr1->session_handle
+ == lan_addr2->session_handle)
+ && (lan_addr1->lun == lan_addr2->lun));
+ }
+
+ return 1;
+}
+
+int ipmi_validate_addr(struct ipmi_addr *addr, int len)
+{
+ if (len < sizeof(struct ipmi_system_interface_addr)) {
+ return -EINVAL;
+ }
+
+ if (addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) {
+ if (addr->channel != IPMI_BMC_CHANNEL)
+ return -EINVAL;
+ return 0;
+ }
+
+ if ((addr->channel == IPMI_BMC_CHANNEL)
+ || (addr->channel >= IPMI_NUM_CHANNELS)
+ || (addr->channel < 0))
+ return -EINVAL;
+
+ if ((addr->addr_type == IPMI_IPMB_ADDR_TYPE)
+ || (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE))
+ {
+ if (len < sizeof(struct ipmi_ipmb_addr)) {
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ if (addr->addr_type == IPMI_LAN_ADDR_TYPE) {
+ if (len < sizeof(struct ipmi_lan_addr)) {
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+unsigned int ipmi_addr_length(int addr_type)
+{
+ if (addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE)
+ return sizeof(struct ipmi_system_interface_addr);
+
+ if ((addr_type == IPMI_IPMB_ADDR_TYPE)
+ || (addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE))
+ {
+ return sizeof(struct ipmi_ipmb_addr);
+ }
+
+ if (addr_type == IPMI_LAN_ADDR_TYPE)
+ return sizeof(struct ipmi_lan_addr);
+
+ return 0;
+}
+
+static void deliver_response(struct ipmi_recv_msg *msg)
+{
+ msg->user->handler->ipmi_recv_hndl(msg, msg->user->handler_data);
+}
+
+/* Find the next sequence number not being used and add the given
+ message with the given timeout to the sequence table. This must be
+ called with the interface's seq_lock held. */
+static int intf_next_seq(ipmi_smi_t intf,
+ struct ipmi_recv_msg *recv_msg,
+ unsigned long timeout,
+ int retries,
+ int broadcast,
+ unsigned char *seq,
+ long *seqid)
+{
+ int rv = 0;
+ unsigned int i;
+
+ for (i=intf->curr_seq;
+ (i+1)%IPMI_IPMB_NUM_SEQ != intf->curr_seq;
+ i=(i+1)%IPMI_IPMB_NUM_SEQ)
+ {
+ if (! intf->seq_table[i].inuse)
+ break;
+ }
+
+ if (! intf->seq_table[i].inuse) {
+ intf->seq_table[i].recv_msg = recv_msg;
+
+ /* Start with the maximum timeout, when the send response
+ comes in we will start the real timer. */
+ intf->seq_table[i].timeout = MAX_MSG_TIMEOUT;
+ intf->seq_table[i].orig_timeout = timeout;
+ intf->seq_table[i].retries_left = retries;
+ intf->seq_table[i].broadcast = broadcast;
+ intf->seq_table[i].inuse = 1;
+ intf->seq_table[i].seqid = NEXT_SEQID(intf->seq_table[i].seqid);
+ *seq = i;
+ *seqid = intf->seq_table[i].seqid;
+ intf->curr_seq = (i+1)%IPMI_IPMB_NUM_SEQ;
+ } else {
+ rv = -EAGAIN;
+ }
+
+ return rv;
+}
+
+/* Return the receive message for the given sequence number and
+ release the sequence number so it can be reused. Some other data
+ is passed in to be sure the message matches up correctly (to help
+ guard against message coming in after their timeout and the
+ sequence number being reused). */
+static int intf_find_seq(ipmi_smi_t intf,
+ unsigned char seq,
+ short channel,
+ unsigned char cmd,
+ unsigned char netfn,
+ struct ipmi_addr *addr,
+ struct ipmi_recv_msg **recv_msg)
+{
+ int rv = -ENODEV;
+ unsigned long flags;
+
+ if (seq >= IPMI_IPMB_NUM_SEQ)
+ return -EINVAL;
+
+ spin_lock_irqsave(&(intf->seq_lock), flags);
+ if (intf->seq_table[seq].inuse) {
+ struct ipmi_recv_msg *msg = intf->seq_table[seq].recv_msg;
+
+ if ((msg->addr.channel == channel)
+ && (msg->msg.cmd == cmd)
+ && (msg->msg.netfn == netfn)
+ && (ipmi_addr_equal(addr, &(msg->addr))))
+ {
+ *recv_msg = msg;
+ intf->seq_table[seq].inuse = 0;
+ rv = 0;
+ }
+ }
+ spin_unlock_irqrestore(&(intf->seq_lock), flags);
+
+ return rv;
+}
+
+
+/* Start the timer for a specific sequence table entry. */
+static int intf_start_seq_timer(ipmi_smi_t intf,
+ long msgid)
+{
+ int rv = -ENODEV;
+ unsigned long flags;
+ unsigned char seq;
+ unsigned long seqid;
+
+
+ GET_SEQ_FROM_MSGID(msgid, seq, seqid);
+
+ spin_lock_irqsave(&(intf->seq_lock), flags);
+ /* We do this verification because the user can be deleted
+ while a message is outstanding. */
+ if ((intf->seq_table[seq].inuse)
+ && (intf->seq_table[seq].seqid == seqid))
+ {
+ struct seq_table *ent = &(intf->seq_table[seq]);
+ ent->timeout = ent->orig_timeout;
+ rv = 0;
+ }
+ spin_unlock_irqrestore(&(intf->seq_lock), flags);
+
+ return rv;
+}
+
+/* Got an error for the send message for a specific sequence number. */
+static int intf_err_seq(ipmi_smi_t intf,
+ long msgid,
+ unsigned int err)
+{
+ int rv = -ENODEV;
+ unsigned long flags;
+ unsigned char seq;
+ unsigned long seqid;
+ struct ipmi_recv_msg *msg = NULL;
+
+
+ GET_SEQ_FROM_MSGID(msgid, seq, seqid);
+
+ spin_lock_irqsave(&(intf->seq_lock), flags);
+ /* We do this verification because the user can be deleted
+ while a message is outstanding. */
+ if ((intf->seq_table[seq].inuse)
+ && (intf->seq_table[seq].seqid == seqid))
+ {
+ struct seq_table *ent = &(intf->seq_table[seq]);
+
+ ent->inuse = 0;
+ msg = ent->recv_msg;
+ rv = 0;
+ }
+ spin_unlock_irqrestore(&(intf->seq_lock), flags);
+
+ if (msg) {
+ msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+ msg->msg_data[0] = err;
+ msg->msg.netfn |= 1; /* Convert to a response. */
+ msg->msg.data_len = 1;
+ msg->msg.data = msg->msg_data;
+ deliver_response(msg);
+ }
+
+ return rv;
+}
+
+
+int ipmi_create_user(unsigned int if_num,
+ struct ipmi_user_hndl *handler,
+ void *handler_data,
+ ipmi_user_t *user)
+{
+ unsigned long flags;
+ ipmi_user_t new_user;
+ int rv = 0;
+ ipmi_smi_t intf;
+
+ /* There is no module usecount here, because it's not
+ required. Since this can only be used by and called from
+ other modules, they will implicitly use this module, and
+ thus this can't be removed unless the other modules are
+ removed. */
+
+ if (handler == NULL)
+ return -EINVAL;
+
+ /* Make sure the driver is actually initialized, this handles
+ problems with initialization order. */
+ if (!initialized) {
+ rv = ipmi_init_msghandler();
+ if (rv)
+ return rv;
+
+ /* The init code doesn't return an error if it was turned
+ off, but it won't initialize. Check that. */
+ if (!initialized)
+ return -ENODEV;
+ }
+
+ new_user = kmalloc(sizeof(*new_user), GFP_KERNEL);
+ if (! new_user)
+ return -ENOMEM;
+
+ down_read(&interfaces_sem);
+ if ((if_num > MAX_IPMI_INTERFACES) || ipmi_interfaces[if_num] == NULL)
+ {
+ rv = -EINVAL;
+ goto out_unlock;
+ }
+
+ intf = ipmi_interfaces[if_num];
+
+ new_user->handler = handler;
+ new_user->handler_data = handler_data;
+ new_user->intf = intf;
+ new_user->gets_events = 0;
+
+ if (!try_module_get(intf->handlers->owner)) {
+ rv = -ENODEV;
+ goto out_unlock;
+ }
+
+ if (intf->handlers->inc_usecount) {
+ rv = intf->handlers->inc_usecount(intf->send_info);
+ if (rv) {
+ module_put(intf->handlers->owner);
+ goto out_unlock;
+ }
+ }
+
+ write_lock_irqsave(&intf->users_lock, flags);
+ list_add_tail(&new_user->link, &intf->users);
+ write_unlock_irqrestore(&intf->users_lock, flags);
+
+ out_unlock:
+ if (rv) {
+ kfree(new_user);
+ } else {
+ *user = new_user;
+ }
+
+ up_read(&interfaces_sem);
+ return rv;
+}
+
+static int ipmi_destroy_user_nolock(ipmi_user_t user)
+{
+ int rv = -ENODEV;
+ ipmi_user_t t_user;
+ struct cmd_rcvr *rcvr, *rcvr2;
+ int i;
+ unsigned long flags;
+
+ /* Find the user and delete them from the list. */
+ list_for_each_entry(t_user, &(user->intf->users), link) {
+ if (t_user == user) {
+ list_del(&t_user->link);
+ rv = 0;
+ break;
+ }
+ }
+
+ if (rv) {
+ goto out_unlock;
+ }
+
+ /* Remove the user from the interfaces sequence table. */
+ spin_lock_irqsave(&(user->intf->seq_lock), flags);
+ for (i=0; i<IPMI_IPMB_NUM_SEQ; i++) {
+ if (user->intf->seq_table[i].inuse
+ && (user->intf->seq_table[i].recv_msg->user == user))
+ {
+ user->intf->seq_table[i].inuse = 0;
+ }
+ }
+ spin_unlock_irqrestore(&(user->intf->seq_lock), flags);
+
+ /* Remove the user from the command receiver's table. */
+ write_lock_irqsave(&(user->intf->cmd_rcvr_lock), flags);
+ list_for_each_entry_safe(rcvr, rcvr2, &(user->intf->cmd_rcvrs), link) {
+ if (rcvr->user == user) {
+ list_del(&rcvr->link);
+ kfree(rcvr);
+ }
+ }
+ write_unlock_irqrestore(&(user->intf->cmd_rcvr_lock), flags);
+
+ kfree(user);
+
+ out_unlock:
+
+ return rv;
+}
+
+int ipmi_destroy_user(ipmi_user_t user)
+{
+ int rv;
+ ipmi_smi_t intf = user->intf;
+ unsigned long flags;
+
+ down_read(&interfaces_sem);
+ write_lock_irqsave(&intf->users_lock, flags);
+ rv = ipmi_destroy_user_nolock(user);
+ if (!rv) {
+ module_put(intf->handlers->owner);
+ if (intf->handlers->dec_usecount)
+ intf->handlers->dec_usecount(intf->send_info);
+ }
+
+ write_unlock_irqrestore(&intf->users_lock, flags);
+ up_read(&interfaces_sem);
+ return rv;
+}
+
+void ipmi_get_version(ipmi_user_t user,
+ unsigned char *major,
+ unsigned char *minor)
+{
+ *major = user->intf->version_major;
+ *minor = user->intf->version_minor;
+}
+
+void ipmi_set_my_address(ipmi_user_t user,
+ unsigned char address)
+{
+ user->intf->my_address = address;
+}
+
+unsigned char ipmi_get_my_address(ipmi_user_t user)
+{
+ return user->intf->my_address;
+}
+
+void ipmi_set_my_LUN(ipmi_user_t user,
+ unsigned char LUN)
+{
+ user->intf->my_lun = LUN & 0x3;
+}
+
+unsigned char ipmi_get_my_LUN(ipmi_user_t user)
+{
+ return user->intf->my_lun;
+}
+
+int ipmi_set_gets_events(ipmi_user_t user, int val)
+{
+ unsigned long flags;
+ struct ipmi_recv_msg *msg, *msg2;
+
+ read_lock(&(user->intf->users_lock));
+ spin_lock_irqsave(&(user->intf->events_lock), flags);
+ user->gets_events = val;
+
+ if (val) {
+ /* Deliver any queued events. */
+ list_for_each_entry_safe(msg, msg2, &(user->intf->waiting_events), link) {
+ list_del(&msg->link);
+ msg->user = user;
+ deliver_response(msg);
+ }
+ }
+
+ spin_unlock_irqrestore(&(user->intf->events_lock), flags);
+ read_unlock(&(user->intf->users_lock));
+
+ return 0;
+}
+
+int ipmi_register_for_cmd(ipmi_user_t user,
+ unsigned char netfn,
+ unsigned char cmd)
+{
+ struct cmd_rcvr *cmp;
+ unsigned long flags;
+ struct cmd_rcvr *rcvr;
+ int rv = 0;
+
+
+ rcvr = kmalloc(sizeof(*rcvr), GFP_KERNEL);
+ if (! rcvr)
+ return -ENOMEM;
+
+ read_lock(&(user->intf->users_lock));
+ write_lock_irqsave(&(user->intf->cmd_rcvr_lock), flags);
+ if (user->intf->all_cmd_rcvr != NULL) {
+ rv = -EBUSY;
+ goto out_unlock;
+ }
+
+ /* Make sure the command/netfn is not already registered. */
+ list_for_each_entry(cmp, &(user->intf->cmd_rcvrs), link) {
+ if ((cmp->netfn == netfn) && (cmp->cmd == cmd)) {
+ rv = -EBUSY;
+ break;
+ }
+ }
+
+ if (! rv) {
+ rcvr->cmd = cmd;
+ rcvr->netfn = netfn;
+ rcvr->user = user;
+ list_add_tail(&(rcvr->link), &(user->intf->cmd_rcvrs));
+ }
+ out_unlock:
+ write_unlock_irqrestore(&(user->intf->cmd_rcvr_lock), flags);
+ read_unlock(&(user->intf->users_lock));
+
+ if (rv)
+ kfree(rcvr);
+
+ return rv;
+}
+
+int ipmi_unregister_for_cmd(ipmi_user_t user,
+ unsigned char netfn,
+ unsigned char cmd)
+{
+ unsigned long flags;
+ struct cmd_rcvr *rcvr;
+ int rv = -ENOENT;
+
+ read_lock(&(user->intf->users_lock));
+ write_lock_irqsave(&(user->intf->cmd_rcvr_lock), flags);
+ /* Make sure the command/netfn is not already registered. */
+ list_for_each_entry(rcvr, &(user->intf->cmd_rcvrs), link) {
+ if ((rcvr->netfn == netfn) && (rcvr->cmd == cmd)) {
+ rv = 0;
+ list_del(&rcvr->link);
+ kfree(rcvr);
+ break;
+ }
+ }
+ write_unlock_irqrestore(&(user->intf->cmd_rcvr_lock), flags);
+ read_unlock(&(user->intf->users_lock));
+
+ return rv;
+}
+
+void ipmi_user_set_run_to_completion(ipmi_user_t user, int val)
+{
+ user->intf->handlers->set_run_to_completion(user->intf->send_info,
+ val);
+}
+
+static unsigned char
+ipmb_checksum(unsigned char *data, int size)
+{
+ unsigned char csum = 0;
+
+ for (; size > 0; size--, data++)
+ csum += *data;
+
+ return -csum;
+}
+
+static inline void format_ipmb_msg(struct ipmi_smi_msg *smi_msg,
+ struct kernel_ipmi_msg *msg,
+ struct ipmi_ipmb_addr *ipmb_addr,
+ long msgid,
+ unsigned char ipmb_seq,
+ int broadcast,
+ unsigned char source_address,
+ unsigned char source_lun)
+{
+ int i = broadcast;
+
+ /* Format the IPMB header data. */
+ smi_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ smi_msg->data[1] = IPMI_SEND_MSG_CMD;
+ smi_msg->data[2] = ipmb_addr->channel;
+ if (broadcast)
+ smi_msg->data[3] = 0;
+ smi_msg->data[i+3] = ipmb_addr->slave_addr;
+ smi_msg->data[i+4] = (msg->netfn << 2) | (ipmb_addr->lun & 0x3);
+ smi_msg->data[i+5] = ipmb_checksum(&(smi_msg->data[i+3]), 2);
+ smi_msg->data[i+6] = source_address;
+ smi_msg->data[i+7] = (ipmb_seq << 2) | source_lun;
+ smi_msg->data[i+8] = msg->cmd;
+
+ /* Now tack on the data to the message. */
+ if (msg->data_len > 0)
+ memcpy(&(smi_msg->data[i+9]), msg->data,
+ msg->data_len);
+ smi_msg->data_size = msg->data_len + 9;
+
+ /* Now calculate the checksum and tack it on. */
+ smi_msg->data[i+smi_msg->data_size]
+ = ipmb_checksum(&(smi_msg->data[i+6]),
+ smi_msg->data_size-6);
+
+ /* Add on the checksum size and the offset from the
+ broadcast. */
+ smi_msg->data_size += 1 + i;
+
+ smi_msg->msgid = msgid;
+}
+
+static inline void format_lan_msg(struct ipmi_smi_msg *smi_msg,
+ struct kernel_ipmi_msg *msg,
+ struct ipmi_lan_addr *lan_addr,
+ long msgid,
+ unsigned char ipmb_seq,
+ unsigned char source_lun)
+{
+ /* Format the IPMB header data. */
+ smi_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ smi_msg->data[1] = IPMI_SEND_MSG_CMD;
+ smi_msg->data[2] = lan_addr->channel;
+ smi_msg->data[3] = lan_addr->session_handle;
+ smi_msg->data[4] = lan_addr->remote_SWID;
+ smi_msg->data[5] = (msg->netfn << 2) | (lan_addr->lun & 0x3);
+ smi_msg->data[6] = ipmb_checksum(&(smi_msg->data[4]), 2);
+ smi_msg->data[7] = lan_addr->local_SWID;
+ smi_msg->data[8] = (ipmb_seq << 2) | source_lun;
+ smi_msg->data[9] = msg->cmd;
+
+ /* Now tack on the data to the message. */
+ if (msg->data_len > 0)
+ memcpy(&(smi_msg->data[10]), msg->data,
+ msg->data_len);
+ smi_msg->data_size = msg->data_len + 10;
+
+ /* Now calculate the checksum and tack it on. */
+ smi_msg->data[smi_msg->data_size]
+ = ipmb_checksum(&(smi_msg->data[7]),
+ smi_msg->data_size-7);
+
+ /* Add on the checksum size and the offset from the
+ broadcast. */
+ smi_msg->data_size += 1;
+
+ smi_msg->msgid = msgid;
+}
+
+/* Separate from ipmi_request so that the user does not have to be
+ supplied in certain circumstances (mainly at panic time). If
+ messages are supplied, they will be freed, even if an error
+ occurs. */
+static inline int i_ipmi_request(ipmi_user_t user,
+ ipmi_smi_t intf,
+ struct ipmi_addr *addr,
+ long msgid,
+ struct kernel_ipmi_msg *msg,
+ void *user_msg_data,
+ void *supplied_smi,
+ struct ipmi_recv_msg *supplied_recv,
+ int priority,
+ unsigned char source_address,
+ unsigned char source_lun,
+ int retries,
+ unsigned int retry_time_ms)
+{
+ int rv = 0;
+ struct ipmi_smi_msg *smi_msg;
+ struct ipmi_recv_msg *recv_msg;
+ unsigned long flags;
+
+
+ if (supplied_recv) {
+ recv_msg = supplied_recv;
+ } else {
+ recv_msg = ipmi_alloc_recv_msg();
+ if (recv_msg == NULL) {
+ return -ENOMEM;
+ }
+ }
+ recv_msg->user_msg_data = user_msg_data;
+
+ if (supplied_smi) {
+ smi_msg = (struct ipmi_smi_msg *) supplied_smi;
+ } else {
+ smi_msg = ipmi_alloc_smi_msg();
+ if (smi_msg == NULL) {
+ ipmi_free_recv_msg(recv_msg);
+ return -ENOMEM;
+ }
+ }
+
+ recv_msg->user = user;
+ recv_msg->msgid = msgid;
+ /* Store the message to send in the receive message so timeout
+ responses can get the proper response data. */
+ recv_msg->msg = *msg;
+
+ if (addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) {
+ struct ipmi_system_interface_addr *smi_addr;
+
+ if (msg->netfn & 1) {
+ /* Responses are not allowed to the SMI. */
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ smi_addr = (struct ipmi_system_interface_addr *) addr;
+ if (smi_addr->lun > 3) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ memcpy(&recv_msg->addr, smi_addr, sizeof(*smi_addr));
+
+ if ((msg->netfn == IPMI_NETFN_APP_REQUEST)
+ && ((msg->cmd == IPMI_SEND_MSG_CMD)
+ || (msg->cmd == IPMI_GET_MSG_CMD)
+ || (msg->cmd == IPMI_READ_EVENT_MSG_BUFFER_CMD)))
+ {
+ /* We don't let the user do these, since we manage
+ the sequence numbers. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ if ((msg->data_len + 2) > IPMI_MAX_MSG_LENGTH) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EMSGSIZE;
+ goto out_err;
+ }
+
+ smi_msg->data[0] = (msg->netfn << 2) | (smi_addr->lun & 0x3);
+ smi_msg->data[1] = msg->cmd;
+ smi_msg->msgid = msgid;
+ smi_msg->user_data = recv_msg;
+ if (msg->data_len > 0)
+ memcpy(&(smi_msg->data[2]), msg->data, msg->data_len);
+ smi_msg->data_size = msg->data_len + 2;
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_local_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ } else if ((addr->addr_type == IPMI_IPMB_ADDR_TYPE)
+ || (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE))
+ {
+ struct ipmi_ipmb_addr *ipmb_addr;
+ unsigned char ipmb_seq;
+ long seqid;
+ int broadcast = 0;
+
+ if (addr->channel > IPMI_NUM_CHANNELS) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ if (intf->channels[addr->channel].medium
+ != IPMI_CHANNEL_MEDIUM_IPMB)
+ {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ if (retries < 0) {
+ if (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE)
+ retries = 0; /* Don't retry broadcasts. */
+ else
+ retries = 4;
+ }
+ if (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE) {
+ /* Broadcasts add a zero at the beginning of the
+ message, but otherwise is the same as an IPMB
+ address. */
+ addr->addr_type = IPMI_IPMB_ADDR_TYPE;
+ broadcast = 1;
+ }
+
+
+ /* Default to 1 second retries. */
+ if (retry_time_ms == 0)
+ retry_time_ms = 1000;
+
+ /* 9 for the header and 1 for the checksum, plus
+ possibly one for the broadcast. */
+ if ((msg->data_len + 10 + broadcast) > IPMI_MAX_MSG_LENGTH) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EMSGSIZE;
+ goto out_err;
+ }
+
+ ipmb_addr = (struct ipmi_ipmb_addr *) addr;
+ if (ipmb_addr->lun > 3) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ memcpy(&recv_msg->addr, ipmb_addr, sizeof(*ipmb_addr));
+
+ if (recv_msg->msg.netfn & 0x1) {
+ /* It's a response, so use the user's sequence
+ from msgid. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_ipmb_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ format_ipmb_msg(smi_msg, msg, ipmb_addr, msgid,
+ msgid, broadcast,
+ source_address, source_lun);
+
+ /* Save the receive message so we can use it
+ to deliver the response. */
+ smi_msg->user_data = recv_msg;
+ } else {
+ /* It's a command, so get a sequence for it. */
+
+ spin_lock_irqsave(&(intf->seq_lock), flags);
+
+ spin_lock(&intf->counter_lock);
+ intf->sent_ipmb_commands++;
+ spin_unlock(&intf->counter_lock);
+
+ /* Create a sequence number with a 1 second
+ timeout and 4 retries. */
+ rv = intf_next_seq(intf,
+ recv_msg,
+ retry_time_ms,
+ retries,
+ broadcast,
+ &ipmb_seq,
+ &seqid);
+ if (rv) {
+ /* We have used up all the sequence numbers,
+ probably, so abort. */
+ spin_unlock_irqrestore(&(intf->seq_lock),
+ flags);
+ goto out_err;
+ }
+
+ /* Store the sequence number in the message,
+ so that when the send message response
+ comes back we can start the timer. */
+ format_ipmb_msg(smi_msg, msg, ipmb_addr,
+ STORE_SEQ_IN_MSGID(ipmb_seq, seqid),
+ ipmb_seq, broadcast,
+ source_address, source_lun);
+
+ /* Copy the message into the recv message data, so we
+ can retransmit it later if necessary. */
+ memcpy(recv_msg->msg_data, smi_msg->data,
+ smi_msg->data_size);
+ recv_msg->msg.data = recv_msg->msg_data;
+ recv_msg->msg.data_len = smi_msg->data_size;
+
+ /* We don't unlock until here, because we need
+ to copy the completed message into the
+ recv_msg before we release the lock.
+ Otherwise, race conditions may bite us. I
+ know that's pretty paranoid, but I prefer
+ to be correct. */
+ spin_unlock_irqrestore(&(intf->seq_lock), flags);
+ }
+ } else if (addr->addr_type == IPMI_LAN_ADDR_TYPE) {
+ struct ipmi_lan_addr *lan_addr;
+ unsigned char ipmb_seq;
+ long seqid;
+
+ if (addr->channel > IPMI_NUM_CHANNELS) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ if ((intf->channels[addr->channel].medium
+ != IPMI_CHANNEL_MEDIUM_8023LAN)
+ && (intf->channels[addr->channel].medium
+ != IPMI_CHANNEL_MEDIUM_ASYNC))
+ {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ retries = 4;
+
+ /* Default to 1 second retries. */
+ if (retry_time_ms == 0)
+ retry_time_ms = 1000;
+
+ /* 11 for the header and 1 for the checksum. */
+ if ((msg->data_len + 12) > IPMI_MAX_MSG_LENGTH) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EMSGSIZE;
+ goto out_err;
+ }
+
+ lan_addr = (struct ipmi_lan_addr *) addr;
+ if (lan_addr->lun > 3) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ memcpy(&recv_msg->addr, lan_addr, sizeof(*lan_addr));
+
+ if (recv_msg->msg.netfn & 0x1) {
+ /* It's a response, so use the user's sequence
+ from msgid. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_lan_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ format_lan_msg(smi_msg, msg, lan_addr, msgid,
+ msgid, source_lun);
+
+ /* Save the receive message so we can use it
+ to deliver the response. */
+ smi_msg->user_data = recv_msg;
+ } else {
+ /* It's a command, so get a sequence for it. */
+
+ spin_lock_irqsave(&(intf->seq_lock), flags);
+
+ spin_lock(&intf->counter_lock);
+ intf->sent_lan_commands++;
+ spin_unlock(&intf->counter_lock);
+
+ /* Create a sequence number with a 1 second
+ timeout and 4 retries. */
+ rv = intf_next_seq(intf,
+ recv_msg,
+ retry_time_ms,
+ retries,
+ 0,
+ &ipmb_seq,
+ &seqid);
+ if (rv) {
+ /* We have used up all the sequence numbers,
+ probably, so abort. */
+ spin_unlock_irqrestore(&(intf->seq_lock),
+ flags);
+ goto out_err;
+ }
+
+ /* Store the sequence number in the message,
+ so that when the send message response
+ comes back we can start the timer. */
+ format_lan_msg(smi_msg, msg, lan_addr,
+ STORE_SEQ_IN_MSGID(ipmb_seq, seqid),
+ ipmb_seq, source_lun);
+
+ /* Copy the message into the recv message data, so we
+ can retransmit it later if necessary. */
+ memcpy(recv_msg->msg_data, smi_msg->data,
+ smi_msg->data_size);
+ recv_msg->msg.data = recv_msg->msg_data;
+ recv_msg->msg.data_len = smi_msg->data_size;
+
+ /* We don't unlock until here, because we need
+ to copy the completed message into the
+ recv_msg before we release the lock.
+ Otherwise, race conditions may bite us. I
+ know that's pretty paranoid, but I prefer
+ to be correct. */
+ spin_unlock_irqrestore(&(intf->seq_lock), flags);
+ }
+ } else {
+ /* Unknown address type. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+#ifdef DEBUG_MSGING
+ {
+ int m;
+ for (m=0; m<smi_msg->data_size; m++)
+ printk(" %2.2x", smi_msg->data[m]);
+ printk("\n");
+ }
+#endif
+ intf->handlers->sender(intf->send_info, smi_msg, priority);
+
+ return 0;
+
+ out_err:
+ ipmi_free_smi_msg(smi_msg);
+ ipmi_free_recv_msg(recv_msg);
+ return rv;
+}
+
+int ipmi_request_settime(ipmi_user_t user,
+ struct ipmi_addr *addr,
+ long msgid,
+ struct kernel_ipmi_msg *msg,
+ void *user_msg_data,
+ int priority,
+ int retries,
+ unsigned int retry_time_ms)
+{
+ return i_ipmi_request(user,
+ user->intf,
+ addr,
+ msgid,
+ msg,
+ user_msg_data,
+ NULL, NULL,
+ priority,
+ user->intf->my_address,
+ user->intf->my_lun,
+ retries,
+ retry_time_ms);
+}
+
+int ipmi_request_supply_msgs(ipmi_user_t user,
+ struct ipmi_addr *addr,
+ long msgid,
+ struct kernel_ipmi_msg *msg,
+ void *user_msg_data,
+ void *supplied_smi,
+ struct ipmi_recv_msg *supplied_recv,
+ int priority)
+{
+ return i_ipmi_request(user,
+ user->intf,
+ addr,
+ msgid,
+ msg,
+ user_msg_data,
+ supplied_smi,
+ supplied_recv,
+ priority,
+ user->intf->my_address,
+ user->intf->my_lun,
+ -1, 0);
+}
+
+static int ipmb_file_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char *out = (char *) page;
+ ipmi_smi_t intf = data;
+
+ return sprintf(out, "%x\n", intf->my_address);
+}
+
+static int version_file_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char *out = (char *) page;
+ ipmi_smi_t intf = data;
+
+ return sprintf(out, "%d.%d\n",
+ intf->version_major, intf->version_minor);
+}
+
+static int stat_file_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char *out = (char *) page;
+ ipmi_smi_t intf = data;
+
+ out += sprintf(out, "sent_invalid_commands: %d\n",
+ intf->sent_invalid_commands);
+ out += sprintf(out, "sent_local_commands: %d\n",
+ intf->sent_local_commands);
+ out += sprintf(out, "handled_local_responses: %d\n",
+ intf->handled_local_responses);
+ out += sprintf(out, "unhandled_local_responses: %d\n",
+ intf->unhandled_local_responses);
+ out += sprintf(out, "sent_ipmb_commands: %d\n",
+ intf->sent_ipmb_commands);
+ out += sprintf(out, "sent_ipmb_command_errs: %d\n",
+ intf->sent_ipmb_command_errs);
+ out += sprintf(out, "retransmitted_ipmb_commands: %d\n",
+ intf->retransmitted_ipmb_commands);
+ out += sprintf(out, "timed_out_ipmb_commands: %d\n",
+ intf->timed_out_ipmb_commands);
+ out += sprintf(out, "timed_out_ipmb_broadcasts: %d\n",
+ intf->timed_out_ipmb_broadcasts);
+ out += sprintf(out, "sent_ipmb_responses: %d\n",
+ intf->sent_ipmb_responses);
+ out += sprintf(out, "handled_ipmb_responses: %d\n",
+ intf->handled_ipmb_responses);
+ out += sprintf(out, "invalid_ipmb_responses: %d\n",
+ intf->invalid_ipmb_responses);
+ out += sprintf(out, "unhandled_ipmb_responses: %d\n",
+ intf->unhandled_ipmb_responses);
+ out += sprintf(out, "sent_lan_commands: %d\n",
+ intf->sent_lan_commands);
+ out += sprintf(out, "sent_lan_command_errs: %d\n",
+ intf->sent_lan_command_errs);
+ out += sprintf(out, "retransmitted_lan_commands: %d\n",
+ intf->retransmitted_lan_commands);
+ out += sprintf(out, "timed_out_lan_commands: %d\n",
+ intf->timed_out_lan_commands);
+ out += sprintf(out, "sent_lan_responses: %d\n",
+ intf->sent_lan_responses);
+ out += sprintf(out, "handled_lan_responses: %d\n",
+ intf->handled_lan_responses);
+ out += sprintf(out, "invalid_lan_responses: %d\n",
+ intf->invalid_lan_responses);
+ out += sprintf(out, "unhandled_lan_responses: %d\n",
+ intf->unhandled_lan_responses);
+ out += sprintf(out, "handled_commands: %d\n",
+ intf->handled_commands);
+ out += sprintf(out, "invalid_commands: %d\n",
+ intf->invalid_commands);
+ out += sprintf(out, "unhandled_commands: %d\n",
+ intf->unhandled_commands);
+ out += sprintf(out, "invalid_events: %d\n",
+ intf->invalid_events);
+ out += sprintf(out, "events: %d\n",
+ intf->events);
+
+ return (out - ((char *) page));
+}
+
+int ipmi_smi_add_proc_entry(ipmi_smi_t smi, char *name,
+ read_proc_t *read_proc, write_proc_t *write_proc,
+ void *data, struct module *owner)
+{
+ struct proc_dir_entry *file;
+ int rv = 0;
+ struct ipmi_proc_entry *entry;
+
+ /* Create a list element. */
+ entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+ entry->name = kmalloc(strlen(name)+1, GFP_KERNEL);
+ if (!entry->name) {
+ kfree(entry);
+ return -ENOMEM;
+ }
+ strcpy(entry->name, name);
+
+ file = create_proc_entry(name, 0, smi->proc_dir);
+ if (!file) {
+ kfree(entry->name);
+ kfree(entry);
+ rv = -ENOMEM;
+ } else {
+ file->nlink = 1;
+ file->data = data;
+ file->read_proc = read_proc;
+ file->write_proc = write_proc;
+ file->owner = owner;
+
+ /* Stick it on the list. */
+ entry->next = smi->proc_entries;
+ smi->proc_entries = entry;
+ }
+
+ return rv;
+}
+
+static int add_proc_entries(ipmi_smi_t smi, int num)
+{
+ int rv = 0;
+
+ sprintf(smi->proc_dir_name, "%d", num);
+ smi->proc_dir = proc_mkdir(smi->proc_dir_name, proc_ipmi_root);
+ if (!smi->proc_dir)
+ rv = -ENOMEM;
+ else {
+ smi->proc_dir->owner = THIS_MODULE;
+ }
+
+ if (rv == 0)
+ rv = ipmi_smi_add_proc_entry(smi, "stats",
+ stat_file_read_proc, NULL,
+ smi, THIS_MODULE);
+
+ if (rv == 0)
+ rv = ipmi_smi_add_proc_entry(smi, "ipmb",
+ ipmb_file_read_proc, NULL,
+ smi, THIS_MODULE);
+
+ if (rv == 0)
+ rv = ipmi_smi_add_proc_entry(smi, "version",
+ version_file_read_proc, NULL,
+ smi, THIS_MODULE);
+
+ return rv;
+}
+
+static void remove_proc_entries(ipmi_smi_t smi)
+{
+ struct ipmi_proc_entry *entry;
+
+ while (smi->proc_entries) {
+ entry = smi->proc_entries;
+ smi->proc_entries = entry->next;
+
+ remove_proc_entry(entry->name, smi->proc_dir);
+ kfree(entry->name);
+ kfree(entry);
+ }
+ remove_proc_entry(smi->proc_dir_name, proc_ipmi_root);
+}
+
+static int
+send_channel_info_cmd(ipmi_smi_t intf, int chan)
+{
+ struct kernel_ipmi_msg msg;
+ unsigned char data[1];
+ struct ipmi_system_interface_addr si;
+
+ si.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ si.channel = IPMI_BMC_CHANNEL;
+ si.lun = 0;
+
+ msg.netfn = IPMI_NETFN_APP_REQUEST;
+ msg.cmd = IPMI_GET_CHANNEL_INFO_CMD;
+ msg.data = data;
+ msg.data_len = 1;
+ data[0] = chan;
+ return i_ipmi_request(NULL,
+ intf,
+ (struct ipmi_addr *) &si,
+ 0,
+ &msg,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ intf->my_address,
+ intf->my_lun,
+ -1, 0);
+}
+
+static void
+channel_handler(ipmi_smi_t intf, struct ipmi_smi_msg *msg)
+{
+ int rv = 0;
+ int chan;
+
+ if ((msg->rsp[0] == (IPMI_NETFN_APP_RESPONSE << 2))
+ && (msg->rsp[1] == IPMI_GET_CHANNEL_INFO_CMD))
+ {
+ /* It's the one we want */
+ if (msg->rsp[2] != 0) {
+ /* Got an error from the channel, just go on. */
+
+ if (msg->rsp[2] == IPMI_INVALID_COMMAND_ERR) {
+ /* If the MC does not support this
+ command, that is legal. We just
+ assume it has one IPMB at channel
+ zero. */
+ intf->channels[0].medium
+ = IPMI_CHANNEL_MEDIUM_IPMB;
+ intf->channels[0].protocol
+ = IPMI_CHANNEL_PROTOCOL_IPMB;
+ rv = -ENOSYS;
+
+ intf->curr_channel = IPMI_MAX_CHANNELS;
+ wake_up(&intf->waitq);
+ goto out;
+ }
+ goto next_channel;
+ }
+ if (msg->rsp_size < 6) {
+ /* Message not big enough, just go on. */
+ goto next_channel;
+ }
+ chan = intf->curr_channel;
+ intf->channels[chan].medium = msg->rsp[4] & 0x7f;
+ intf->channels[chan].protocol = msg->rsp[5] & 0x1f;
+
+ next_channel:
+ intf->curr_channel++;
+ if (intf->curr_channel >= IPMI_MAX_CHANNELS)
+ wake_up(&intf->waitq);
+ else
+ rv = send_channel_info_cmd(intf, intf->curr_channel);
+
+ if (rv) {
+ /* Got an error somehow, just give up. */
+ intf->curr_channel = IPMI_MAX_CHANNELS;
+ wake_up(&intf->waitq);
+
+ printk(KERN_WARNING PFX
+ "Error sending channel information: %d\n",
+ rv);
+ }
+ }
+ out:
+ return;
+}
+
+int ipmi_register_smi(struct ipmi_smi_handlers *handlers,
+ void *send_info,
+ unsigned char version_major,
+ unsigned char version_minor,
+ unsigned char slave_addr,
+ ipmi_smi_t *intf)
+{
+ int i, j;
+ int rv;
+ ipmi_smi_t new_intf;
+ unsigned long flags;
+
+
+ /* Make sure the driver is actually initialized, this handles
+ problems with initialization order. */
+ if (!initialized) {
+ rv = ipmi_init_msghandler();
+ if (rv)
+ return rv;
+ /* The init code doesn't return an error if it was turned
+ off, but it won't initialize. Check that. */
+ if (!initialized)
+ return -ENODEV;
+ }
+
+ new_intf = kmalloc(sizeof(*new_intf), GFP_KERNEL);
+ if (!new_intf)
+ return -ENOMEM;
+ memset(new_intf, 0, sizeof(*new_intf));
+
+ new_intf->proc_dir = NULL;
+
+ rv = -ENOMEM;
+
+ down_write(&interfaces_sem);
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ if (ipmi_interfaces[i] == NULL) {
+ new_intf->intf_num = i;
+ new_intf->version_major = version_major;
+ new_intf->version_minor = version_minor;
+ if (slave_addr == 0)
+ new_intf->my_address = IPMI_BMC_SLAVE_ADDR;
+ else
+ new_intf->my_address = slave_addr;
+ new_intf->my_lun = 2; /* the SMS LUN. */
+ rwlock_init(&(new_intf->users_lock));
+ INIT_LIST_HEAD(&(new_intf->users));
+ new_intf->handlers = handlers;
+ new_intf->send_info = send_info;
+ spin_lock_init(&(new_intf->seq_lock));
+ for (j=0; j<IPMI_IPMB_NUM_SEQ; j++) {
+ new_intf->seq_table[j].inuse = 0;
+ new_intf->seq_table[j].seqid = 0;
+ }
+ new_intf->curr_seq = 0;
+ spin_lock_init(&(new_intf->waiting_msgs_lock));
+ INIT_LIST_HEAD(&(new_intf->waiting_msgs));
+ spin_lock_init(&(new_intf->events_lock));
+ INIT_LIST_HEAD(&(new_intf->waiting_events));
+ new_intf->waiting_events_count = 0;
+ rwlock_init(&(new_intf->cmd_rcvr_lock));
+ init_waitqueue_head(&new_intf->waitq);
+ INIT_LIST_HEAD(&(new_intf->cmd_rcvrs));
+ new_intf->all_cmd_rcvr = NULL;
+
+ spin_lock_init(&(new_intf->counter_lock));
+
+ spin_lock_irqsave(&interfaces_lock, flags);
+ ipmi_interfaces[i] = new_intf;
+ spin_unlock_irqrestore(&interfaces_lock, flags);
+
+ rv = 0;
+ *intf = new_intf;
+ break;
+ }
+ }
+
+ downgrade_write(&interfaces_sem);
+
+ if (rv == 0)
+ rv = add_proc_entries(*intf, i);
+
+ if (rv == 0) {
+ if ((version_major > 1)
+ || ((version_major == 1) && (version_minor >= 5)))
+ {
+ /* Start scanning the channels to see what is
+ available. */
+ (*intf)->null_user_handler = channel_handler;
+ (*intf)->curr_channel = 0;
+ rv = send_channel_info_cmd(*intf, 0);
+ if (rv)
+ goto out;
+
+ /* Wait for the channel info to be read. */
+ up_read(&interfaces_sem);
+ wait_event((*intf)->waitq,
+ ((*intf)->curr_channel>=IPMI_MAX_CHANNELS));
+ down_read(&interfaces_sem);
+
+ if (ipmi_interfaces[i] != new_intf)
+ /* Well, it went away. Just return. */
+ goto out;
+ } else {
+ /* Assume a single IPMB channel at zero. */
+ (*intf)->channels[0].medium = IPMI_CHANNEL_MEDIUM_IPMB;
+ (*intf)->channels[0].protocol
+ = IPMI_CHANNEL_PROTOCOL_IPMB;
+ }
+
+ /* Call all the watcher interfaces to tell
+ them that a new interface is available. */
+ call_smi_watchers(i);
+ }
+
+ out:
+ up_read(&interfaces_sem);
+
+ if (rv) {
+ if (new_intf->proc_dir)
+ remove_proc_entries(new_intf);
+ kfree(new_intf);
+ }
+
+ return rv;
+}
+
+static void free_recv_msg_list(struct list_head *q)
+{
+ struct ipmi_recv_msg *msg, *msg2;
+
+ list_for_each_entry_safe(msg, msg2, q, link) {
+ list_del(&msg->link);
+ ipmi_free_recv_msg(msg);
+ }
+}
+
+static void free_cmd_rcvr_list(struct list_head *q)
+{
+ struct cmd_rcvr *rcvr, *rcvr2;
+
+ list_for_each_entry_safe(rcvr, rcvr2, q, link) {
+ list_del(&rcvr->link);
+ kfree(rcvr);
+ }
+}
+
+static void clean_up_interface_data(ipmi_smi_t intf)
+{
+ int i;
+
+ free_recv_msg_list(&(intf->waiting_msgs));
+ free_recv_msg_list(&(intf->waiting_events));
+ free_cmd_rcvr_list(&(intf->cmd_rcvrs));
+
+ for (i=0; i<IPMI_IPMB_NUM_SEQ; i++) {
+ if ((intf->seq_table[i].inuse)
+ && (intf->seq_table[i].recv_msg))
+ {
+ ipmi_free_recv_msg(intf->seq_table[i].recv_msg);
+ }
+ }
+}
+
+int ipmi_unregister_smi(ipmi_smi_t intf)
+{
+ int rv = -ENODEV;
+ int i;
+ struct ipmi_smi_watcher *w;
+ unsigned long flags;
+
+ down_write(&interfaces_sem);
+ if (list_empty(&(intf->users)))
+ {
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ if (ipmi_interfaces[i] == intf) {
+ remove_proc_entries(intf);
+ spin_lock_irqsave(&interfaces_lock, flags);
+ ipmi_interfaces[i] = NULL;
+ clean_up_interface_data(intf);
+ spin_unlock_irqrestore(&interfaces_lock,flags);
+ kfree(intf);
+ rv = 0;
+ goto out_call_watcher;
+ }
+ }
+ } else {
+ rv = -EBUSY;
+ }
+ up_write(&interfaces_sem);
+
+ return rv;
+
+ out_call_watcher:
+ downgrade_write(&interfaces_sem);
+
+ /* Call all the watcher interfaces to tell them that
+ an interface is gone. */
+ down_read(&smi_watchers_sem);
+ list_for_each_entry(w, &smi_watchers, link) {
+ w->smi_gone(i);
+ }
+ up_read(&smi_watchers_sem);
+ up_read(&interfaces_sem);
+ return 0;
+}
+
+static int handle_ipmb_get_msg_rsp(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ struct ipmi_ipmb_addr ipmb_addr;
+ struct ipmi_recv_msg *recv_msg;
+ unsigned long flags;
+
+
+ /* This is 11, not 10, because the response must contain a
+ * completion code. */
+ if (msg->rsp_size < 11) {
+ /* Message not big enough, just ignore it. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->invalid_ipmb_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ return 0;
+ }
+
+ if (msg->rsp[2] != 0) {
+ /* An error getting the response, just ignore it. */
+ return 0;
+ }
+
+ ipmb_addr.addr_type = IPMI_IPMB_ADDR_TYPE;
+ ipmb_addr.slave_addr = msg->rsp[6];
+ ipmb_addr.channel = msg->rsp[3] & 0x0f;
+ ipmb_addr.lun = msg->rsp[7] & 3;
+
+ /* It's a response from a remote entity. Look up the sequence
+ number and handle the response. */
+ if (intf_find_seq(intf,
+ msg->rsp[7] >> 2,
+ msg->rsp[3] & 0x0f,
+ msg->rsp[8],
+ (msg->rsp[4] >> 2) & (~1),
+ (struct ipmi_addr *) &(ipmb_addr),
+ &recv_msg))
+ {
+ /* We were unable to find the sequence number,
+ so just nuke the message. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->unhandled_ipmb_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ return 0;
+ }
+
+ memcpy(recv_msg->msg_data,
+ &(msg->rsp[9]),
+ msg->rsp_size - 9);
+ /* THe other fields matched, so no need to set them, except
+ for netfn, which needs to be the response that was
+ returned, not the request value. */
+ recv_msg->msg.netfn = msg->rsp[4] >> 2;
+ recv_msg->msg.data = recv_msg->msg_data;
+ recv_msg->msg.data_len = msg->rsp_size - 10;
+ recv_msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->handled_ipmb_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ deliver_response(recv_msg);
+
+ return 0;
+}
+
+static int handle_ipmb_get_msg_cmd(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ struct cmd_rcvr *rcvr;
+ int rv = 0;
+ unsigned char netfn;
+ unsigned char cmd;
+ ipmi_user_t user = NULL;
+ struct ipmi_ipmb_addr *ipmb_addr;
+ struct ipmi_recv_msg *recv_msg;
+ unsigned long flags;
+
+ if (msg->rsp_size < 10) {
+ /* Message not big enough, just ignore it. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ return 0;
+ }
+
+ if (msg->rsp[2] != 0) {
+ /* An error getting the response, just ignore it. */
+ return 0;
+ }
+
+ netfn = msg->rsp[4] >> 2;
+ cmd = msg->rsp[8];
+
+ read_lock(&(intf->cmd_rcvr_lock));
+
+ if (intf->all_cmd_rcvr) {
+ user = intf->all_cmd_rcvr;
+ } else {
+ /* Find the command/netfn. */
+ list_for_each_entry(rcvr, &(intf->cmd_rcvrs), link) {
+ if ((rcvr->netfn == netfn) && (rcvr->cmd == cmd)) {
+ user = rcvr->user;
+ break;
+ }
+ }
+ }
+ read_unlock(&(intf->cmd_rcvr_lock));
+
+ if (user == NULL) {
+ /* We didn't find a user, deliver an error response. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->unhandled_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+
+ msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg->data[1] = IPMI_SEND_MSG_CMD;
+ msg->data[2] = msg->rsp[3];
+ msg->data[3] = msg->rsp[6];
+ msg->data[4] = ((netfn + 1) << 2) | (msg->rsp[7] & 0x3);
+ msg->data[5] = ipmb_checksum(&(msg->data[3]), 2);
+ msg->data[6] = intf->my_address;
+ /* rqseq/lun */
+ msg->data[7] = (msg->rsp[7] & 0xfc) | (msg->rsp[4] & 0x3);
+ msg->data[8] = msg->rsp[8]; /* cmd */
+ msg->data[9] = IPMI_INVALID_CMD_COMPLETION_CODE;
+ msg->data[10] = ipmb_checksum(&(msg->data[6]), 4);
+ msg->data_size = 11;
+
+#ifdef DEBUG_MSGING
+ {
+ int m;
+ printk("Invalid command:");
+ for (m=0; m<msg->data_size; m++)
+ printk(" %2.2x", msg->data[m]);
+ printk("\n");
+ }
+#endif
+ intf->handlers->sender(intf->send_info, msg, 0);
+
+ rv = -1; /* We used the message, so return the value that
+ causes it to not be freed or queued. */
+ } else {
+ /* Deliver the message to the user. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->handled_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+
+ recv_msg = ipmi_alloc_recv_msg();
+ if (! recv_msg) {
+ /* We couldn't allocate memory for the
+ message, so requeue it for handling
+ later. */
+ rv = 1;
+ } else {
+ /* Extract the source address from the data. */
+ ipmb_addr = (struct ipmi_ipmb_addr *) &recv_msg->addr;
+ ipmb_addr->addr_type = IPMI_IPMB_ADDR_TYPE;
+ ipmb_addr->slave_addr = msg->rsp[6];
+ ipmb_addr->lun = msg->rsp[7] & 3;
+ ipmb_addr->channel = msg->rsp[3] & 0xf;
+
+ /* Extract the rest of the message information
+ from the IPMB header.*/
+ recv_msg->user = user;
+ recv_msg->recv_type = IPMI_CMD_RECV_TYPE;
+ recv_msg->msgid = msg->rsp[7] >> 2;
+ recv_msg->msg.netfn = msg->rsp[4] >> 2;
+ recv_msg->msg.cmd = msg->rsp[8];
+ recv_msg->msg.data = recv_msg->msg_data;
+
+ /* We chop off 10, not 9 bytes because the checksum
+ at the end also needs to be removed. */
+ recv_msg->msg.data_len = msg->rsp_size - 10;
+ memcpy(recv_msg->msg_data,
+ &(msg->rsp[9]),
+ msg->rsp_size - 10);
+ deliver_response(recv_msg);
+ }
+ }
+
+ return rv;
+}
+
+static int handle_lan_get_msg_rsp(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ struct ipmi_lan_addr lan_addr;
+ struct ipmi_recv_msg *recv_msg;
+ unsigned long flags;
+
+
+ /* This is 13, not 12, because the response must contain a
+ * completion code. */
+ if (msg->rsp_size < 13) {
+ /* Message not big enough, just ignore it. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->invalid_lan_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ return 0;
+ }
+
+ if (msg->rsp[2] != 0) {
+ /* An error getting the response, just ignore it. */
+ return 0;
+ }
+
+ lan_addr.addr_type = IPMI_LAN_ADDR_TYPE;
+ lan_addr.session_handle = msg->rsp[4];
+ lan_addr.remote_SWID = msg->rsp[8];
+ lan_addr.local_SWID = msg->rsp[5];
+ lan_addr.channel = msg->rsp[3] & 0x0f;
+ lan_addr.privilege = msg->rsp[3] >> 4;
+ lan_addr.lun = msg->rsp[9] & 3;
+
+ /* It's a response from a remote entity. Look up the sequence
+ number and handle the response. */
+ if (intf_find_seq(intf,
+ msg->rsp[9] >> 2,
+ msg->rsp[3] & 0x0f,
+ msg->rsp[10],
+ (msg->rsp[6] >> 2) & (~1),
+ (struct ipmi_addr *) &(lan_addr),
+ &recv_msg))
+ {
+ /* We were unable to find the sequence number,
+ so just nuke the message. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->unhandled_lan_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ return 0;
+ }
+
+ memcpy(recv_msg->msg_data,
+ &(msg->rsp[11]),
+ msg->rsp_size - 11);
+ /* The other fields matched, so no need to set them, except
+ for netfn, which needs to be the response that was
+ returned, not the request value. */
+ recv_msg->msg.netfn = msg->rsp[6] >> 2;
+ recv_msg->msg.data = recv_msg->msg_data;
+ recv_msg->msg.data_len = msg->rsp_size - 12;
+ recv_msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->handled_lan_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ deliver_response(recv_msg);
+
+ return 0;
+}
+
+static int handle_lan_get_msg_cmd(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ struct cmd_rcvr *rcvr;
+ int rv = 0;
+ unsigned char netfn;
+ unsigned char cmd;
+ ipmi_user_t user = NULL;
+ struct ipmi_lan_addr *lan_addr;
+ struct ipmi_recv_msg *recv_msg;
+ unsigned long flags;
+
+ if (msg->rsp_size < 12) {
+ /* Message not big enough, just ignore it. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ return 0;
+ }
+
+ if (msg->rsp[2] != 0) {
+ /* An error getting the response, just ignore it. */
+ return 0;
+ }
+
+ netfn = msg->rsp[6] >> 2;
+ cmd = msg->rsp[10];
+
+ read_lock(&(intf->cmd_rcvr_lock));
+
+ if (intf->all_cmd_rcvr) {
+ user = intf->all_cmd_rcvr;
+ } else {
+ /* Find the command/netfn. */
+ list_for_each_entry(rcvr, &(intf->cmd_rcvrs), link) {
+ if ((rcvr->netfn == netfn) && (rcvr->cmd == cmd)) {
+ user = rcvr->user;
+ break;
+ }
+ }
+ }
+ read_unlock(&(intf->cmd_rcvr_lock));
+
+ if (user == NULL) {
+ /* We didn't find a user, deliver an error response. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->unhandled_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+
+ rv = 0; /* Don't do anything with these messages, just
+ allow them to be freed. */
+ } else {
+ /* Deliver the message to the user. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->handled_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+
+ recv_msg = ipmi_alloc_recv_msg();
+ if (! recv_msg) {
+ /* We couldn't allocate memory for the
+ message, so requeue it for handling
+ later. */
+ rv = 1;
+ } else {
+ /* Extract the source address from the data. */
+ lan_addr = (struct ipmi_lan_addr *) &recv_msg->addr;
+ lan_addr->addr_type = IPMI_LAN_ADDR_TYPE;
+ lan_addr->session_handle = msg->rsp[4];
+ lan_addr->remote_SWID = msg->rsp[8];
+ lan_addr->local_SWID = msg->rsp[5];
+ lan_addr->lun = msg->rsp[9] & 3;
+ lan_addr->channel = msg->rsp[3] & 0xf;
+ lan_addr->privilege = msg->rsp[3] >> 4;
+
+ /* Extract the rest of the message information
+ from the IPMB header.*/
+ recv_msg->user = user;
+ recv_msg->recv_type = IPMI_CMD_RECV_TYPE;
+ recv_msg->msgid = msg->rsp[9] >> 2;
+ recv_msg->msg.netfn = msg->rsp[6] >> 2;
+ recv_msg->msg.cmd = msg->rsp[10];
+ recv_msg->msg.data = recv_msg->msg_data;
+
+ /* We chop off 12, not 11 bytes because the checksum
+ at the end also needs to be removed. */
+ recv_msg->msg.data_len = msg->rsp_size - 12;
+ memcpy(recv_msg->msg_data,
+ &(msg->rsp[11]),
+ msg->rsp_size - 12);
+ deliver_response(recv_msg);
+ }
+ }
+
+ return rv;
+}
+
+static void copy_event_into_recv_msg(struct ipmi_recv_msg *recv_msg,
+ struct ipmi_smi_msg *msg)
+{
+ struct ipmi_system_interface_addr *smi_addr;
+
+ recv_msg->msgid = 0;
+ smi_addr = (struct ipmi_system_interface_addr *) &(recv_msg->addr);
+ smi_addr->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ smi_addr->channel = IPMI_BMC_CHANNEL;
+ smi_addr->lun = msg->rsp[0] & 3;
+ recv_msg->recv_type = IPMI_ASYNC_EVENT_RECV_TYPE;
+ recv_msg->msg.netfn = msg->rsp[0] >> 2;
+ recv_msg->msg.cmd = msg->rsp[1];
+ memcpy(recv_msg->msg_data, &(msg->rsp[3]), msg->rsp_size - 3);
+ recv_msg->msg.data = recv_msg->msg_data;
+ recv_msg->msg.data_len = msg->rsp_size - 3;
+}
+
+/* This will be called with the intf->users_lock read-locked, so no need
+ to do that here. */
+static int handle_read_event_rsp(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ struct ipmi_recv_msg *recv_msg, *recv_msg2;
+ struct list_head msgs;
+ ipmi_user_t user;
+ int rv = 0;
+ int deliver_count = 0;
+ unsigned long flags;
+
+ if (msg->rsp_size < 19) {
+ /* Message is too small to be an IPMB event. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->invalid_events++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ return 0;
+ }
+
+ if (msg->rsp[2] != 0) {
+ /* An error getting the event, just ignore it. */
+ return 0;
+ }
+
+ INIT_LIST_HEAD(&msgs);
+
+ spin_lock_irqsave(&(intf->events_lock), flags);
+
+ spin_lock(&intf->counter_lock);
+ intf->events++;
+ spin_unlock(&intf->counter_lock);
+
+ /* Allocate and fill in one message for every user that is getting
+ events. */
+ list_for_each_entry(user, &(intf->users), link) {
+ if (! user->gets_events)
+ continue;
+
+ recv_msg = ipmi_alloc_recv_msg();
+ if (! recv_msg) {
+ list_for_each_entry_safe(recv_msg, recv_msg2, &msgs, link) {
+ list_del(&recv_msg->link);
+ ipmi_free_recv_msg(recv_msg);
+ }
+ /* We couldn't allocate memory for the
+ message, so requeue it for handling
+ later. */
+ rv = 1;
+ goto out;
+ }
+
+ deliver_count++;
+
+ copy_event_into_recv_msg(recv_msg, msg);
+ recv_msg->user = user;
+ list_add_tail(&(recv_msg->link), &msgs);
+ }
+
+ if (deliver_count) {
+ /* Now deliver all the messages. */
+ list_for_each_entry_safe(recv_msg, recv_msg2, &msgs, link) {
+ list_del(&recv_msg->link);
+ deliver_response(recv_msg);
+ }
+ } else if (intf->waiting_events_count < MAX_EVENTS_IN_QUEUE) {
+ /* No one to receive the message, put it in queue if there's
+ not already too many things in the queue. */
+ recv_msg = ipmi_alloc_recv_msg();
+ if (! recv_msg) {
+ /* We couldn't allocate memory for the
+ message, so requeue it for handling
+ later. */
+ rv = 1;
+ goto out;
+ }
+
+ copy_event_into_recv_msg(recv_msg, msg);
+ list_add_tail(&(recv_msg->link), &(intf->waiting_events));
+ } else {
+ /* There's too many things in the queue, discard this
+ message. */
+ printk(KERN_WARNING PFX "Event queue full, discarding an"
+ " incoming event\n");
+ }
+
+ out:
+ spin_unlock_irqrestore(&(intf->events_lock), flags);
+
+ return rv;
+}
+
+static int handle_bmc_rsp(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ struct ipmi_recv_msg *recv_msg;
+ int found = 0;
+ struct ipmi_user *user;
+ unsigned long flags;
+
+ recv_msg = (struct ipmi_recv_msg *) msg->user_data;
+
+ /* Make sure the user still exists. */
+ list_for_each_entry(user, &(intf->users), link) {
+ if (user == recv_msg->user) {
+ /* Found it, so we can deliver it */
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ /* Special handling for NULL users. */
+ if (!recv_msg->user && intf->null_user_handler){
+ intf->null_user_handler(intf, msg);
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->handled_local_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ }else{
+ /* The user for the message went away, so give up. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->unhandled_local_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ }
+ ipmi_free_recv_msg(recv_msg);
+ } else {
+ struct ipmi_system_interface_addr *smi_addr;
+
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->handled_local_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ recv_msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+ recv_msg->msgid = msg->msgid;
+ smi_addr = ((struct ipmi_system_interface_addr *)
+ &(recv_msg->addr));
+ smi_addr->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ smi_addr->channel = IPMI_BMC_CHANNEL;
+ smi_addr->lun = msg->rsp[0] & 3;
+ recv_msg->msg.netfn = msg->rsp[0] >> 2;
+ recv_msg->msg.cmd = msg->rsp[1];
+ memcpy(recv_msg->msg_data,
+ &(msg->rsp[2]),
+ msg->rsp_size - 2);
+ recv_msg->msg.data = recv_msg->msg_data;
+ recv_msg->msg.data_len = msg->rsp_size - 2;
+ deliver_response(recv_msg);
+ }
+
+ return 0;
+}
+
+/* Handle a new message. Return 1 if the message should be requeued,
+ 0 if the message should be freed, or -1 if the message should not
+ be freed or requeued. */
+static int handle_new_recv_msg(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ int requeue;
+ int chan;
+
+#ifdef DEBUG_MSGING
+ int m;
+ printk("Recv:");
+ for (m=0; m<msg->rsp_size; m++)
+ printk(" %2.2x", msg->rsp[m]);
+ printk("\n");
+#endif
+ if (msg->rsp_size < 2) {
+ /* Message is too small to be correct. */
+ printk(KERN_WARNING PFX "BMC returned to small a message"
+ " for netfn %x cmd %x, got %d bytes\n",
+ (msg->data[0] >> 2) | 1, msg->data[1], msg->rsp_size);
+
+ /* Generate an error response for the message. */
+ msg->rsp[0] = msg->data[0] | (1 << 2);
+ msg->rsp[1] = msg->data[1];
+ msg->rsp[2] = IPMI_ERR_UNSPECIFIED;
+ msg->rsp_size = 3;
+ } else if (((msg->rsp[0] >> 2) != ((msg->data[0] >> 2) | 1))/* Netfn */
+ || (msg->rsp[1] != msg->data[1])) /* Command */
+ {
+ /* The response is not even marginally correct. */
+ printk(KERN_WARNING PFX "BMC returned incorrect response,"
+ " expected netfn %x cmd %x, got netfn %x cmd %x\n",
+ (msg->data[0] >> 2) | 1, msg->data[1],
+ msg->rsp[0] >> 2, msg->rsp[1]);
+
+ /* Generate an error response for the message. */
+ msg->rsp[0] = msg->data[0] | (1 << 2);
+ msg->rsp[1] = msg->data[1];
+ msg->rsp[2] = IPMI_ERR_UNSPECIFIED;
+ msg->rsp_size = 3;
+ }
+
+ if ((msg->rsp[0] == ((IPMI_NETFN_APP_REQUEST|1) << 2))
+ && (msg->rsp[1] == IPMI_SEND_MSG_CMD)
+ && (msg->user_data != NULL))
+ {
+ /* It's a response to a response we sent. For this we
+ deliver a send message response to the user. */
+ struct ipmi_recv_msg *recv_msg = msg->user_data;
+
+ requeue = 0;
+ if (msg->rsp_size < 2)
+ /* Message is too small to be correct. */
+ goto out;
+
+ chan = msg->data[2] & 0x0f;
+ if (chan >= IPMI_MAX_CHANNELS)
+ /* Invalid channel number */
+ goto out;
+
+ if (recv_msg) {
+ recv_msg->recv_type = IPMI_RESPONSE_RESPONSE_TYPE;
+ recv_msg->msg.data = recv_msg->msg_data;
+ recv_msg->msg.data_len = 1;
+ recv_msg->msg_data[0] = msg->rsp[2];
+ deliver_response(recv_msg);
+ }
+ } else if ((msg->rsp[0] == ((IPMI_NETFN_APP_REQUEST|1) << 2))
+ && (msg->rsp[1] == IPMI_GET_MSG_CMD))
+ {
+ /* It's from the receive queue. */
+ chan = msg->rsp[3] & 0xf;
+ if (chan >= IPMI_MAX_CHANNELS) {
+ /* Invalid channel number */
+ requeue = 0;
+ goto out;
+ }
+
+ switch (intf->channels[chan].medium) {
+ case IPMI_CHANNEL_MEDIUM_IPMB:
+ if (msg->rsp[4] & 0x04) {
+ /* It's a response, so find the
+ requesting message and send it up. */
+ requeue = handle_ipmb_get_msg_rsp(intf, msg);
+ } else {
+ /* It's a command to the SMS from some other
+ entity. Handle that. */
+ requeue = handle_ipmb_get_msg_cmd(intf, msg);
+ }
+ break;
+
+ case IPMI_CHANNEL_MEDIUM_8023LAN:
+ case IPMI_CHANNEL_MEDIUM_ASYNC:
+ if (msg->rsp[6] & 0x04) {
+ /* It's a response, so find the
+ requesting message and send it up. */
+ requeue = handle_lan_get_msg_rsp(intf, msg);
+ } else {
+ /* It's a command to the SMS from some other
+ entity. Handle that. */
+ requeue = handle_lan_get_msg_cmd(intf, msg);
+ }
+ break;
+
+ default:
+ /* We don't handle the channel type, so just
+ * free the message. */
+ requeue = 0;
+ }
+
+ } else if ((msg->rsp[0] == ((IPMI_NETFN_APP_REQUEST|1) << 2))
+ && (msg->rsp[1] == IPMI_READ_EVENT_MSG_BUFFER_CMD))
+ {
+ /* It's an asyncronous event. */
+ requeue = handle_read_event_rsp(intf, msg);
+ } else {
+ /* It's a response from the local BMC. */
+ requeue = handle_bmc_rsp(intf, msg);
+ }
+
+ out:
+ return requeue;
+}
+
+/* Handle a new message from the lower layer. */
+void ipmi_smi_msg_received(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ unsigned long flags;
+ int rv;
+
+
+ /* Lock the user lock so the user can't go away while we are
+ working on it. */
+ read_lock(&(intf->users_lock));
+
+ if ((msg->data_size >= 2)
+ && (msg->data[0] == (IPMI_NETFN_APP_REQUEST << 2))
+ && (msg->data[1] == IPMI_SEND_MSG_CMD)
+ && (msg->user_data == NULL)) {
+ /* This is the local response to a command send, start
+ the timer for these. The user_data will not be
+ NULL if this is a response send, and we will let
+ response sends just go through. */
+
+ /* Check for errors, if we get certain errors (ones
+ that mean basically we can try again later), we
+ ignore them and start the timer. Otherwise we
+ report the error immediately. */
+ if ((msg->rsp_size >= 3) && (msg->rsp[2] != 0)
+ && (msg->rsp[2] != IPMI_NODE_BUSY_ERR)
+ && (msg->rsp[2] != IPMI_LOST_ARBITRATION_ERR))
+ {
+ int chan = msg->rsp[3] & 0xf;
+
+ /* Got an error sending the message, handle it. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ if (chan >= IPMI_MAX_CHANNELS)
+ ; /* This shouldn't happen */
+ else if ((intf->channels[chan].medium
+ == IPMI_CHANNEL_MEDIUM_8023LAN)
+ || (intf->channels[chan].medium
+ == IPMI_CHANNEL_MEDIUM_ASYNC))
+ intf->sent_lan_command_errs++;
+ else
+ intf->sent_ipmb_command_errs++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ intf_err_seq(intf, msg->msgid, msg->rsp[2]);
+ } else {
+ /* The message was sent, start the timer. */
+ intf_start_seq_timer(intf, msg->msgid);
+ }
+
+ ipmi_free_smi_msg(msg);
+ goto out_unlock;
+ }
+
+ /* To preserve message order, if the list is not empty, we
+ tack this message onto the end of the list. */
+ spin_lock_irqsave(&(intf->waiting_msgs_lock), flags);
+ if (!list_empty(&(intf->waiting_msgs))) {
+ list_add_tail(&(msg->link), &(intf->waiting_msgs));
+ spin_unlock(&(intf->waiting_msgs_lock));
+ goto out_unlock;
+ }
+ spin_unlock_irqrestore(&(intf->waiting_msgs_lock), flags);
+
+ rv = handle_new_recv_msg(intf, msg);
+ if (rv > 0) {
+ /* Could not handle the message now, just add it to a
+ list to handle later. */
+ spin_lock(&(intf->waiting_msgs_lock));
+ list_add_tail(&(msg->link), &(intf->waiting_msgs));
+ spin_unlock(&(intf->waiting_msgs_lock));
+ } else if (rv == 0) {
+ ipmi_free_smi_msg(msg);
+ }
+
+ out_unlock:
+ read_unlock(&(intf->users_lock));
+}
+
+void ipmi_smi_watchdog_pretimeout(ipmi_smi_t intf)
+{
+ ipmi_user_t user;
+
+ read_lock(&(intf->users_lock));
+ list_for_each_entry(user, &(intf->users), link) {
+ if (! user->handler->ipmi_watchdog_pretimeout)
+ continue;
+
+ user->handler->ipmi_watchdog_pretimeout(user->handler_data);
+ }
+ read_unlock(&(intf->users_lock));
+}
+
+static void
+handle_msg_timeout(struct ipmi_recv_msg *msg)
+{
+ msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+ msg->msg_data[0] = IPMI_TIMEOUT_COMPLETION_CODE;
+ msg->msg.netfn |= 1; /* Convert to a response. */
+ msg->msg.data_len = 1;
+ msg->msg.data = msg->msg_data;
+ deliver_response(msg);
+}
+
+static void
+send_from_recv_msg(ipmi_smi_t intf, struct ipmi_recv_msg *recv_msg,
+ struct ipmi_smi_msg *smi_msg,
+ unsigned char seq, long seqid)
+{
+ if (!smi_msg)
+ smi_msg = ipmi_alloc_smi_msg();
+ if (!smi_msg)
+ /* If we can't allocate the message, then just return, we
+ get 4 retries, so this should be ok. */
+ return;
+
+ memcpy(smi_msg->data, recv_msg->msg.data, recv_msg->msg.data_len);
+ smi_msg->data_size = recv_msg->msg.data_len;
+ smi_msg->msgid = STORE_SEQ_IN_MSGID(seq, seqid);
+
+ /* Send the new message. We send with a zero priority. It
+ timed out, I doubt time is that critical now, and high
+ priority messages are really only for messages to the local
+ MC, which don't get resent. */
+ intf->handlers->sender(intf->send_info, smi_msg, 0);
+
+#ifdef DEBUG_MSGING
+ {
+ int m;
+ printk("Resend: ");
+ for (m=0; m<smi_msg->data_size; m++)
+ printk(" %2.2x", smi_msg->data[m]);
+ printk("\n");
+ }
+#endif
+}
+
+static void
+ipmi_timeout_handler(long timeout_period)
+{
+ ipmi_smi_t intf;
+ struct list_head timeouts;
+ struct ipmi_recv_msg *msg, *msg2;
+ struct ipmi_smi_msg *smi_msg, *smi_msg2;
+ unsigned long flags;
+ int i, j;
+
+ INIT_LIST_HEAD(&timeouts);
+
+ spin_lock(&interfaces_lock);
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ intf = ipmi_interfaces[i];
+ if (intf == NULL)
+ continue;
+
+ read_lock(&(intf->users_lock));
+
+ /* See if any waiting messages need to be processed. */
+ spin_lock_irqsave(&(intf->waiting_msgs_lock), flags);
+ list_for_each_entry_safe(smi_msg, smi_msg2, &(intf->waiting_msgs), link) {
+ if (! handle_new_recv_msg(intf, smi_msg)) {
+ list_del(&smi_msg->link);
+ ipmi_free_smi_msg(smi_msg);
+ } else {
+ /* To preserve message order, quit if we
+ can't handle a message. */
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&(intf->waiting_msgs_lock), flags);
+
+ /* Go through the seq table and find any messages that
+ have timed out, putting them in the timeouts
+ list. */
+ spin_lock_irqsave(&(intf->seq_lock), flags);
+ for (j=0; j<IPMI_IPMB_NUM_SEQ; j++) {
+ struct seq_table *ent = &(intf->seq_table[j]);
+ if (!ent->inuse)
+ continue;
+
+ ent->timeout -= timeout_period;
+ if (ent->timeout > 0)
+ continue;
+
+ if (ent->retries_left == 0) {
+ /* The message has used all its retries. */
+ ent->inuse = 0;
+ msg = ent->recv_msg;
+ list_add_tail(&(msg->link), &timeouts);
+ spin_lock(&intf->counter_lock);
+ if (ent->broadcast)
+ intf->timed_out_ipmb_broadcasts++;
+ else if (ent->recv_msg->addr.addr_type
+ == IPMI_LAN_ADDR_TYPE)
+ intf->timed_out_lan_commands++;
+ else
+ intf->timed_out_ipmb_commands++;
+ spin_unlock(&intf->counter_lock);
+ } else {
+ /* More retries, send again. */
+
+ /* Start with the max timer, set to normal
+ timer after the message is sent. */
+ ent->timeout = MAX_MSG_TIMEOUT;
+ ent->retries_left--;
+ send_from_recv_msg(intf, ent->recv_msg, NULL,
+ j, ent->seqid);
+ spin_lock(&intf->counter_lock);
+ if (ent->recv_msg->addr.addr_type
+ == IPMI_LAN_ADDR_TYPE)
+ intf->retransmitted_lan_commands++;
+ else
+ intf->retransmitted_ipmb_commands++;
+ spin_unlock(&intf->counter_lock);
+ }
+ }
+ spin_unlock_irqrestore(&(intf->seq_lock), flags);
+
+ list_for_each_entry_safe(msg, msg2, &timeouts, link) {
+ handle_msg_timeout(msg);
+ }
+
+ read_unlock(&(intf->users_lock));
+ }
+ spin_unlock(&interfaces_lock);
+}
+
+static void ipmi_request_event(void)
+{
+ ipmi_smi_t intf;
+ int i;
+
+ spin_lock(&interfaces_lock);
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ intf = ipmi_interfaces[i];
+ if (intf == NULL)
+ continue;
+
+ intf->handlers->request_events(intf->send_info);
+ }
+ spin_unlock(&interfaces_lock);
+}
+
+static struct timer_list ipmi_timer;
+
+/* Call every ~100 ms. */
+#define IPMI_TIMEOUT_TIME 100
+
+/* How many jiffies does it take to get to the timeout time. */
+#define IPMI_TIMEOUT_JIFFIES ((IPMI_TIMEOUT_TIME * HZ) / 1000)
+
+/* Request events from the queue every second (this is the number of
+ IPMI_TIMEOUT_TIMES between event requests). Hopefully, in the
+ future, IPMI will add a way to know immediately if an event is in
+ the queue and this silliness can go away. */
+#define IPMI_REQUEST_EV_TIME (1000 / (IPMI_TIMEOUT_TIME))
+
+static volatile int stop_operation = 0;
+static volatile int timer_stopped = 0;
+static unsigned int ticks_to_req_ev = IPMI_REQUEST_EV_TIME;
+
+static void ipmi_timeout(unsigned long data)
+{
+ if (stop_operation) {
+ timer_stopped = 1;
+ return;
+ }
+
+ ticks_to_req_ev--;
+ if (ticks_to_req_ev == 0) {
+ ipmi_request_event();
+ ticks_to_req_ev = IPMI_REQUEST_EV_TIME;
+ }
+
+ ipmi_timeout_handler(IPMI_TIMEOUT_TIME);
+
+ ipmi_timer.expires += IPMI_TIMEOUT_JIFFIES;
+ add_timer(&ipmi_timer);
+}
+
+
+static atomic_t smi_msg_inuse_count = ATOMIC_INIT(0);
+static atomic_t recv_msg_inuse_count = ATOMIC_INIT(0);
+
+/* FIXME - convert these to slabs. */
+static void free_smi_msg(struct ipmi_smi_msg *msg)
+{
+ atomic_dec(&smi_msg_inuse_count);
+ kfree(msg);
+}
+
+struct ipmi_smi_msg *ipmi_alloc_smi_msg(void)
+{
+ struct ipmi_smi_msg *rv;
+ rv = kmalloc(sizeof(struct ipmi_smi_msg), GFP_ATOMIC);
+ if (rv) {
+ rv->done = free_smi_msg;
+ rv->user_data = NULL;
+ atomic_inc(&smi_msg_inuse_count);
+ }
+ return rv;
+}
+
+static void free_recv_msg(struct ipmi_recv_msg *msg)
+{
+ atomic_dec(&recv_msg_inuse_count);
+ kfree(msg);
+}
+
+struct ipmi_recv_msg *ipmi_alloc_recv_msg(void)
+{
+ struct ipmi_recv_msg *rv;
+
+ rv = kmalloc(sizeof(struct ipmi_recv_msg), GFP_ATOMIC);
+ if (rv) {
+ rv->done = free_recv_msg;
+ atomic_inc(&recv_msg_inuse_count);
+ }
+ return rv;
+}
+
+#ifdef CONFIG_IPMI_PANIC_EVENT
+
+static void dummy_smi_done_handler(struct ipmi_smi_msg *msg)
+{
+}
+
+static void dummy_recv_done_handler(struct ipmi_recv_msg *msg)
+{
+}
+
+#ifdef CONFIG_IPMI_PANIC_STRING
+static void event_receiver_fetcher(ipmi_smi_t intf, struct ipmi_smi_msg *msg)
+{
+ if ((msg->rsp[0] == (IPMI_NETFN_SENSOR_EVENT_RESPONSE << 2))
+ && (msg->rsp[1] == IPMI_GET_EVENT_RECEIVER_CMD)
+ && (msg->rsp[2] == IPMI_CC_NO_ERROR))
+ {
+ /* A get event receiver command, save it. */
+ intf->event_receiver = msg->rsp[3];
+ intf->event_receiver_lun = msg->rsp[4] & 0x3;
+ }
+}
+
+static void device_id_fetcher(ipmi_smi_t intf, struct ipmi_smi_msg *msg)
+{
+ if ((msg->rsp[0] == (IPMI_NETFN_APP_RESPONSE << 2))
+ && (msg->rsp[1] == IPMI_GET_DEVICE_ID_CMD)
+ && (msg->rsp[2] == IPMI_CC_NO_ERROR))
+ {
+ /* A get device id command, save if we are an event
+ receiver or generator. */
+ intf->local_sel_device = (msg->rsp[8] >> 2) & 1;
+ intf->local_event_generator = (msg->rsp[8] >> 5) & 1;
+ }
+}
+#endif
+
+static void send_panic_events(char *str)
+{
+ struct kernel_ipmi_msg msg;
+ ipmi_smi_t intf;
+ unsigned char data[16];
+ int i;
+ struct ipmi_system_interface_addr *si;
+ struct ipmi_addr addr;
+ struct ipmi_smi_msg smi_msg;
+ struct ipmi_recv_msg recv_msg;
+
+ si = (struct ipmi_system_interface_addr *) &addr;
+ si->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ si->channel = IPMI_BMC_CHANNEL;
+ si->lun = 0;
+
+ /* Fill in an event telling that we have failed. */
+ msg.netfn = 0x04; /* Sensor or Event. */
+ msg.cmd = 2; /* Platform event command. */
+ msg.data = data;
+ msg.data_len = 8;
+ data[0] = 0x21; /* Kernel generator ID, IPMI table 5-4 */
+ data[1] = 0x03; /* This is for IPMI 1.0. */
+ data[2] = 0x20; /* OS Critical Stop, IPMI table 36-3 */
+ data[4] = 0x6f; /* Sensor specific, IPMI table 36-1 */
+ data[5] = 0xa1; /* Runtime stop OEM bytes 2 & 3. */
+
+ /* Put a few breadcrumbs in. Hopefully later we can add more things
+ to make the panic events more useful. */
+ if (str) {
+ data[3] = str[0];
+ data[6] = str[1];
+ data[7] = str[2];
+ }
+
+ smi_msg.done = dummy_smi_done_handler;
+ recv_msg.done = dummy_recv_done_handler;
+
+ /* For every registered interface, send the event. */
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ intf = ipmi_interfaces[i];
+ if (intf == NULL)
+ continue;
+
+ /* Send the event announcing the panic. */
+ intf->handlers->set_run_to_completion(intf->send_info, 1);
+ i_ipmi_request(NULL,
+ intf,
+ &addr,
+ 0,
+ &msg,
+ NULL,
+ &smi_msg,
+ &recv_msg,
+ 0,
+ intf->my_address,
+ intf->my_lun,
+ 0, 1); /* Don't retry, and don't wait. */
+ }
+
+#ifdef CONFIG_IPMI_PANIC_STRING
+ /* On every interface, dump a bunch of OEM event holding the
+ string. */
+ if (!str)
+ return;
+
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ char *p = str;
+ struct ipmi_ipmb_addr *ipmb;
+ int j;
+
+ intf = ipmi_interfaces[i];
+ if (intf == NULL)
+ continue;
+
+ /* First job here is to figure out where to send the
+ OEM events. There's no way in IPMI to send OEM
+ events using an event send command, so we have to
+ find the SEL to put them in and stick them in
+ there. */
+
+ /* Get capabilities from the get device id. */
+ intf->local_sel_device = 0;
+ intf->local_event_generator = 0;
+ intf->event_receiver = 0;
+
+ /* Request the device info from the local MC. */
+ msg.netfn = IPMI_NETFN_APP_REQUEST;
+ msg.cmd = IPMI_GET_DEVICE_ID_CMD;
+ msg.data = NULL;
+ msg.data_len = 0;
+ intf->null_user_handler = device_id_fetcher;
+ i_ipmi_request(NULL,
+ intf,
+ &addr,
+ 0,
+ &msg,
+ NULL,
+ &smi_msg,
+ &recv_msg,
+ 0,
+ intf->my_address,
+ intf->my_lun,
+ 0, 1); /* Don't retry, and don't wait. */
+
+ if (intf->local_event_generator) {
+ /* Request the event receiver from the local MC. */
+ msg.netfn = IPMI_NETFN_SENSOR_EVENT_REQUEST;
+ msg.cmd = IPMI_GET_EVENT_RECEIVER_CMD;
+ msg.data = NULL;
+ msg.data_len = 0;
+ intf->null_user_handler = event_receiver_fetcher;
+ i_ipmi_request(NULL,
+ intf,
+ &addr,
+ 0,
+ &msg,
+ NULL,
+ &smi_msg,
+ &recv_msg,
+ 0,
+ intf->my_address,
+ intf->my_lun,
+ 0, 1); /* no retry, and no wait. */
+ }
+ intf->null_user_handler = NULL;
+
+ /* Validate the event receiver. The low bit must not
+ be 1 (it must be a valid IPMB address), it cannot
+ be zero, and it must not be my address. */
+ if (((intf->event_receiver & 1) == 0)
+ && (intf->event_receiver != 0)
+ && (intf->event_receiver != intf->my_address))
+ {
+ /* The event receiver is valid, send an IPMB
+ message. */
+ ipmb = (struct ipmi_ipmb_addr *) &addr;
+ ipmb->addr_type = IPMI_IPMB_ADDR_TYPE;
+ ipmb->channel = 0; /* FIXME - is this right? */
+ ipmb->lun = intf->event_receiver_lun;
+ ipmb->slave_addr = intf->event_receiver;
+ } else if (intf->local_sel_device) {
+ /* The event receiver was not valid (or was
+ me), but I am an SEL device, just dump it
+ in my SEL. */
+ si = (struct ipmi_system_interface_addr *) &addr;
+ si->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ si->channel = IPMI_BMC_CHANNEL;
+ si->lun = 0;
+ } else
+ continue; /* No where to send the event. */
+
+
+ msg.netfn = IPMI_NETFN_STORAGE_REQUEST; /* Storage. */
+ msg.cmd = IPMI_ADD_SEL_ENTRY_CMD;
+ msg.data = data;
+ msg.data_len = 16;
+
+ j = 0;
+ while (*p) {
+ int size = strlen(p);
+
+ if (size > 11)
+ size = 11;
+ data[0] = 0;
+ data[1] = 0;
+ data[2] = 0xf0; /* OEM event without timestamp. */
+ data[3] = intf->my_address;
+ data[4] = j++; /* sequence # */
+ /* Always give 11 bytes, so strncpy will fill
+ it with zeroes for me. */
+ strncpy(data+5, p, 11);
+ p += size;
+
+ i_ipmi_request(NULL,
+ intf,
+ &addr,
+ 0,
+ &msg,
+ NULL,
+ &smi_msg,
+ &recv_msg,
+ 0,
+ intf->my_address,
+ intf->my_lun,
+ 0, 1); /* no retry, and no wait. */
+ }
+ }
+#endif /* CONFIG_IPMI_PANIC_STRING */
+}
+#endif /* CONFIG_IPMI_PANIC_EVENT */
+
+static int has_paniced = 0;
+
+static int panic_event(struct notifier_block *this,
+ unsigned long event,
+ void *ptr)
+{
+ int i;
+ ipmi_smi_t intf;
+
+ if (has_paniced)
+ return NOTIFY_DONE;
+ has_paniced = 1;
+
+ /* For every registered interface, set it to run to completion. */
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ intf = ipmi_interfaces[i];
+ if (intf == NULL)
+ continue;
+
+ intf->handlers->set_run_to_completion(intf->send_info, 1);
+ }
+
+#ifdef CONFIG_IPMI_PANIC_EVENT
+ send_panic_events(ptr);
+#endif
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block panic_block = {
+ .notifier_call = panic_event,
+ .next = NULL,
+ .priority = 200 /* priority: INT_MAX >= x >= 0 */
+};
+
+static int ipmi_init_msghandler(void)
+{
+ int i;
+
+ if (initialized)
+ return 0;
+
+ printk(KERN_INFO "ipmi message handler version "
+ IPMI_MSGHANDLER_VERSION "\n");
+
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ ipmi_interfaces[i] = NULL;
+ }
+
+ proc_ipmi_root = proc_mkdir("ipmi", NULL);
+ if (!proc_ipmi_root) {
+ printk(KERN_ERR PFX "Unable to create IPMI proc dir");
+ return -ENOMEM;
+ }
+
+ proc_ipmi_root->owner = THIS_MODULE;
+
+ init_timer(&ipmi_timer);
+ ipmi_timer.data = 0;
+ ipmi_timer.function = ipmi_timeout;
+ ipmi_timer.expires = jiffies + IPMI_TIMEOUT_JIFFIES;
+ add_timer(&ipmi_timer);
+
+ notifier_chain_register(&panic_notifier_list, &panic_block);
+
+ initialized = 1;
+
+ return 0;
+}
+
+static __init int ipmi_init_msghandler_mod(void)
+{
+ ipmi_init_msghandler();
+ return 0;
+}
+
+static __exit void cleanup_ipmi(void)
+{
+ int count;
+
+ if (!initialized)
+ return;
+
+ notifier_chain_unregister(&panic_notifier_list, &panic_block);
+
+ /* This can't be called if any interfaces exist, so no worry about
+ shutting down the interfaces. */
+
+ /* Tell the timer to stop, then wait for it to stop. This avoids
+ problems with race conditions removing the timer here. */
+ stop_operation = 1;
+ while (!timer_stopped) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ }
+
+ remove_proc_entry(proc_ipmi_root->name, &proc_root);
+
+ initialized = 0;
+
+ /* Check for buffer leaks. */
+ count = atomic_read(&smi_msg_inuse_count);
+ if (count != 0)
+ printk(KERN_WARNING PFX "SMI message count %d at exit\n",
+ count);
+ count = atomic_read(&recv_msg_inuse_count);
+ if (count != 0)
+ printk(KERN_WARNING PFX "recv message count %d at exit\n",
+ count);
+}
+module_exit(cleanup_ipmi);
+
+module_init(ipmi_init_msghandler_mod);
+MODULE_LICENSE("GPL");
+
+EXPORT_SYMBOL(ipmi_create_user);
+EXPORT_SYMBOL(ipmi_destroy_user);
+EXPORT_SYMBOL(ipmi_get_version);
+EXPORT_SYMBOL(ipmi_request_settime);
+EXPORT_SYMBOL(ipmi_request_supply_msgs);
+EXPORT_SYMBOL(ipmi_register_smi);
+EXPORT_SYMBOL(ipmi_unregister_smi);
+EXPORT_SYMBOL(ipmi_register_for_cmd);
+EXPORT_SYMBOL(ipmi_unregister_for_cmd);
+EXPORT_SYMBOL(ipmi_smi_msg_received);
+EXPORT_SYMBOL(ipmi_smi_watchdog_pretimeout);
+EXPORT_SYMBOL(ipmi_alloc_smi_msg);
+EXPORT_SYMBOL(ipmi_addr_length);
+EXPORT_SYMBOL(ipmi_validate_addr);
+EXPORT_SYMBOL(ipmi_set_gets_events);
+EXPORT_SYMBOL(ipmi_smi_watcher_register);
+EXPORT_SYMBOL(ipmi_smi_watcher_unregister);
+EXPORT_SYMBOL(ipmi_set_my_address);
+EXPORT_SYMBOL(ipmi_get_my_address);
+EXPORT_SYMBOL(ipmi_set_my_LUN);
+EXPORT_SYMBOL(ipmi_get_my_LUN);
+EXPORT_SYMBOL(ipmi_smi_add_proc_entry);
+EXPORT_SYMBOL(ipmi_user_set_run_to_completion);
diff --git a/drivers/char/ipmi/ipmi_poweroff.c b/drivers/char/ipmi/ipmi_poweroff.c
new file mode 100644
index 0000000..cb5cdc6
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_poweroff.c
@@ -0,0 +1,549 @@
+/*
+ * 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. */
+static unsigned int mfg_id;
+static unsigned int prod_id;
+static 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[] = {
+ { .platform_type = "ATCA",
+ .detect = ipmi_atca_detect,
+ .poweroff_func = ipmi_poweroff_atca },
+ { .platform_type = "CPI1",
+ .detect = ipmi_cpi1_detect,
+ .poweroff_func = ipmi_poweroff_cpi1 },
+ /* Chassis should generally be last, other things should override
+ it. */
+ { .platform_type = "chassis",
+ .detect = ipmi_chassis_detect,
+ .poweroff_func = 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");
diff --git a/drivers/char/ipmi/ipmi_si_intf.c b/drivers/char/ipmi/ipmi_si_intf.c
new file mode 100644
index 0000000..29de259
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_si_intf.c
@@ -0,0 +1,2359 @@
+/*
+ * ipmi_si.c
+ *
+ * The interface to the IPMI driver for the system interfaces (KCS, SMIC,
+ * BT).
+ *
+ * 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 file holds the "policy" for the interface to the SMI state
+ * machine. It does the configuration, handles timers and interrupts,
+ * and drives the real SMI state machine.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <asm/system.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/errno.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+#include <asm/irq.h>
+#ifdef CONFIG_HIGH_RES_TIMERS
+#include <linux/hrtime.h>
+# if defined(schedule_next_int)
+/* Old high-res timer code, do translations. */
+# define get_arch_cycles(a) quick_update_jiffies_sub(a)
+# define arch_cycles_per_jiffy cycles_per_jiffies
+# endif
+static inline void add_usec_to_timer(struct timer_list *t, long v)
+{
+ t->sub_expires += nsec_to_arch_cycle(v * 1000);
+ while (t->sub_expires >= arch_cycles_per_jiffy)
+ {
+ t->expires++;
+ t->sub_expires -= arch_cycles_per_jiffy;
+ }
+}
+#endif
+#include <linux/interrupt.h>
+#include <linux/rcupdate.h>
+#include <linux/ipmi_smi.h>
+#include <asm/io.h>
+#include "ipmi_si_sm.h"
+#include <linux/init.h>
+
+#define IPMI_SI_VERSION "v33"
+
+/* Measure times between events in the driver. */
+#undef DEBUG_TIMING
+
+/* Call every 10 ms. */
+#define SI_TIMEOUT_TIME_USEC 10000
+#define SI_USEC_PER_JIFFY (1000000/HZ)
+#define SI_TIMEOUT_JIFFIES (SI_TIMEOUT_TIME_USEC/SI_USEC_PER_JIFFY)
+#define SI_SHORT_TIMEOUT_USEC 250 /* .25ms when the SM request a
+ short timeout */
+
+enum si_intf_state {
+ SI_NORMAL,
+ SI_GETTING_FLAGS,
+ SI_GETTING_EVENTS,
+ SI_CLEARING_FLAGS,
+ SI_CLEARING_FLAGS_THEN_SET_IRQ,
+ SI_GETTING_MESSAGES,
+ SI_ENABLE_INTERRUPTS1,
+ SI_ENABLE_INTERRUPTS2
+ /* FIXME - add watchdog stuff. */
+};
+
+enum si_type {
+ SI_KCS, SI_SMIC, SI_BT
+};
+
+struct smi_info
+{
+ ipmi_smi_t intf;
+ struct si_sm_data *si_sm;
+ struct si_sm_handlers *handlers;
+ enum si_type si_type;
+ spinlock_t si_lock;
+ spinlock_t msg_lock;
+ struct list_head xmit_msgs;
+ struct list_head hp_xmit_msgs;
+ struct ipmi_smi_msg *curr_msg;
+ enum si_intf_state si_state;
+
+ /* Used to handle the various types of I/O that can occur with
+ IPMI */
+ struct si_sm_io io;
+ int (*io_setup)(struct smi_info *info);
+ void (*io_cleanup)(struct smi_info *info);
+ int (*irq_setup)(struct smi_info *info);
+ void (*irq_cleanup)(struct smi_info *info);
+ unsigned int io_size;
+
+ /* Flags from the last GET_MSG_FLAGS command, used when an ATTN
+ is set to hold the flags until we are done handling everything
+ from the flags. */
+#define RECEIVE_MSG_AVAIL 0x01
+#define EVENT_MSG_BUFFER_FULL 0x02
+#define WDT_PRE_TIMEOUT_INT 0x08
+ unsigned char msg_flags;
+
+ /* If set to true, this will request events the next time the
+ state machine is idle. */
+ atomic_t req_events;
+
+ /* If true, run the state machine to completion on every send
+ call. Generally used after a panic to make sure stuff goes
+ out. */
+ int run_to_completion;
+
+ /* The I/O port of an SI interface. */
+ int port;
+
+ /* The space between start addresses of the two ports. For
+ instance, if the first port is 0xca2 and the spacing is 4, then
+ the second port is 0xca6. */
+ unsigned int spacing;
+
+ /* zero if no irq; */
+ int irq;
+
+ /* The timer for this si. */
+ struct timer_list si_timer;
+
+ /* The time (in jiffies) the last timeout occurred at. */
+ unsigned long last_timeout_jiffies;
+
+ /* Used to gracefully stop the timer without race conditions. */
+ volatile int stop_operation;
+ volatile int timer_stopped;
+
+ /* The driver will disable interrupts when it gets into a
+ situation where it cannot handle messages due to lack of
+ memory. Once that situation clears up, it will re-enable
+ interrupts. */
+ int interrupt_disabled;
+
+ unsigned char ipmi_si_dev_rev;
+ unsigned char ipmi_si_fw_rev_major;
+ unsigned char ipmi_si_fw_rev_minor;
+ unsigned char ipmi_version_major;
+ unsigned char ipmi_version_minor;
+
+ /* Slave address, could be reported from DMI. */
+ unsigned char slave_addr;
+
+ /* Counters and things for the proc filesystem. */
+ spinlock_t count_lock;
+ unsigned long short_timeouts;
+ unsigned long long_timeouts;
+ unsigned long timeout_restarts;
+ unsigned long idles;
+ unsigned long interrupts;
+ unsigned long attentions;
+ unsigned long flag_fetches;
+ unsigned long hosed_count;
+ unsigned long complete_transactions;
+ unsigned long events;
+ unsigned long watchdog_pretimeouts;
+ unsigned long incoming_messages;
+};
+
+static void si_restart_short_timer(struct smi_info *smi_info);
+
+static void deliver_recv_msg(struct smi_info *smi_info,
+ struct ipmi_smi_msg *msg)
+{
+ /* Deliver the message to the upper layer with the lock
+ released. */
+ spin_unlock(&(smi_info->si_lock));
+ ipmi_smi_msg_received(smi_info->intf, msg);
+ spin_lock(&(smi_info->si_lock));
+}
+
+static void return_hosed_msg(struct smi_info *smi_info)
+{
+ struct ipmi_smi_msg *msg = smi_info->curr_msg;
+
+ /* Make it a reponse */
+ msg->rsp[0] = msg->data[0] | 4;
+ msg->rsp[1] = msg->data[1];
+ msg->rsp[2] = 0xFF; /* Unknown error. */
+ msg->rsp_size = 3;
+
+ smi_info->curr_msg = NULL;
+ deliver_recv_msg(smi_info, msg);
+}
+
+static enum si_sm_result start_next_msg(struct smi_info *smi_info)
+{
+ int rv;
+ struct list_head *entry = NULL;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+#endif
+
+ /* No need to save flags, we aleady have interrupts off and we
+ already hold the SMI lock. */
+ spin_lock(&(smi_info->msg_lock));
+
+ /* Pick the high priority queue first. */
+ if (! list_empty(&(smi_info->hp_xmit_msgs))) {
+ entry = smi_info->hp_xmit_msgs.next;
+ } else if (! list_empty(&(smi_info->xmit_msgs))) {
+ entry = smi_info->xmit_msgs.next;
+ }
+
+ if (!entry) {
+ smi_info->curr_msg = NULL;
+ rv = SI_SM_IDLE;
+ } else {
+ int err;
+
+ list_del(entry);
+ smi_info->curr_msg = list_entry(entry,
+ struct ipmi_smi_msg,
+ link);
+#ifdef DEBUG_TIMING
+ do_gettimeofday(&t);
+ printk("**Start2: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+ err = smi_info->handlers->start_transaction(
+ smi_info->si_sm,
+ smi_info->curr_msg->data,
+ smi_info->curr_msg->data_size);
+ if (err) {
+ return_hosed_msg(smi_info);
+ }
+
+ rv = SI_SM_CALL_WITHOUT_DELAY;
+ }
+ spin_unlock(&(smi_info->msg_lock));
+
+ return rv;
+}
+
+static void start_enable_irq(struct smi_info *smi_info)
+{
+ unsigned char msg[2];
+
+ /* If we are enabling interrupts, we have to tell the
+ BMC to use them. */
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_GET_BMC_GLOBAL_ENABLES_CMD;
+
+ smi_info->handlers->start_transaction(smi_info->si_sm, msg, 2);
+ smi_info->si_state = SI_ENABLE_INTERRUPTS1;
+}
+
+static void start_clear_flags(struct smi_info *smi_info)
+{
+ unsigned char msg[3];
+
+ /* Make sure the watchdog pre-timeout flag is not set at startup. */
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_CLEAR_MSG_FLAGS_CMD;
+ msg[2] = WDT_PRE_TIMEOUT_INT;
+
+ smi_info->handlers->start_transaction(smi_info->si_sm, msg, 3);
+ smi_info->si_state = SI_CLEARING_FLAGS;
+}
+
+/* When we have a situtaion where we run out of memory and cannot
+ allocate messages, we just leave them in the BMC and run the system
+ polled until we can allocate some memory. Once we have some
+ memory, we will re-enable the interrupt. */
+static inline void disable_si_irq(struct smi_info *smi_info)
+{
+ if ((smi_info->irq) && (!smi_info->interrupt_disabled)) {
+ disable_irq_nosync(smi_info->irq);
+ smi_info->interrupt_disabled = 1;
+ }
+}
+
+static inline void enable_si_irq(struct smi_info *smi_info)
+{
+ if ((smi_info->irq) && (smi_info->interrupt_disabled)) {
+ enable_irq(smi_info->irq);
+ smi_info->interrupt_disabled = 0;
+ }
+}
+
+static void handle_flags(struct smi_info *smi_info)
+{
+ if (smi_info->msg_flags & WDT_PRE_TIMEOUT_INT) {
+ /* Watchdog pre-timeout */
+ spin_lock(&smi_info->count_lock);
+ smi_info->watchdog_pretimeouts++;
+ spin_unlock(&smi_info->count_lock);
+
+ start_clear_flags(smi_info);
+ smi_info->msg_flags &= ~WDT_PRE_TIMEOUT_INT;
+ spin_unlock(&(smi_info->si_lock));
+ ipmi_smi_watchdog_pretimeout(smi_info->intf);
+ spin_lock(&(smi_info->si_lock));
+ } else if (smi_info->msg_flags & RECEIVE_MSG_AVAIL) {
+ /* Messages available. */
+ smi_info->curr_msg = ipmi_alloc_smi_msg();
+ if (!smi_info->curr_msg) {
+ disable_si_irq(smi_info);
+ smi_info->si_state = SI_NORMAL;
+ return;
+ }
+ enable_si_irq(smi_info);
+
+ smi_info->curr_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ smi_info->curr_msg->data[1] = IPMI_GET_MSG_CMD;
+ smi_info->curr_msg->data_size = 2;
+
+ smi_info->handlers->start_transaction(
+ smi_info->si_sm,
+ smi_info->curr_msg->data,
+ smi_info->curr_msg->data_size);
+ smi_info->si_state = SI_GETTING_MESSAGES;
+ } else if (smi_info->msg_flags & EVENT_MSG_BUFFER_FULL) {
+ /* Events available. */
+ smi_info->curr_msg = ipmi_alloc_smi_msg();
+ if (!smi_info->curr_msg) {
+ disable_si_irq(smi_info);
+ smi_info->si_state = SI_NORMAL;
+ return;
+ }
+ enable_si_irq(smi_info);
+
+ smi_info->curr_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ smi_info->curr_msg->data[1] = IPMI_READ_EVENT_MSG_BUFFER_CMD;
+ smi_info->curr_msg->data_size = 2;
+
+ smi_info->handlers->start_transaction(
+ smi_info->si_sm,
+ smi_info->curr_msg->data,
+ smi_info->curr_msg->data_size);
+ smi_info->si_state = SI_GETTING_EVENTS;
+ } else {
+ smi_info->si_state = SI_NORMAL;
+ }
+}
+
+static void handle_transaction_done(struct smi_info *smi_info)
+{
+ struct ipmi_smi_msg *msg;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+
+ do_gettimeofday(&t);
+ printk("**Done: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+ switch (smi_info->si_state) {
+ case SI_NORMAL:
+ if (!smi_info->curr_msg)
+ break;
+
+ smi_info->curr_msg->rsp_size
+ = smi_info->handlers->get_result(
+ smi_info->si_sm,
+ smi_info->curr_msg->rsp,
+ IPMI_MAX_MSG_LENGTH);
+
+ /* Do this here becase deliver_recv_msg() releases the
+ lock, and a new message can be put in during the
+ time the lock is released. */
+ msg = smi_info->curr_msg;
+ smi_info->curr_msg = NULL;
+ deliver_recv_msg(smi_info, msg);
+ break;
+
+ case SI_GETTING_FLAGS:
+ {
+ unsigned char msg[4];
+ unsigned int len;
+
+ /* We got the flags from the SMI, now handle them. */
+ len = smi_info->handlers->get_result(smi_info->si_sm, msg, 4);
+ if (msg[2] != 0) {
+ /* Error fetching flags, just give up for
+ now. */
+ smi_info->si_state = SI_NORMAL;
+ } else if (len < 4) {
+ /* Hmm, no flags. That's technically illegal, but
+ don't use uninitialized data. */
+ smi_info->si_state = SI_NORMAL;
+ } else {
+ smi_info->msg_flags = msg[3];
+ handle_flags(smi_info);
+ }
+ break;
+ }
+
+ case SI_CLEARING_FLAGS:
+ case SI_CLEARING_FLAGS_THEN_SET_IRQ:
+ {
+ unsigned char msg[3];
+
+ /* We cleared the flags. */
+ smi_info->handlers->get_result(smi_info->si_sm, msg, 3);
+ if (msg[2] != 0) {
+ /* Error clearing flags */
+ printk(KERN_WARNING
+ "ipmi_si: Error clearing flags: %2.2x\n",
+ msg[2]);
+ }
+ if (smi_info->si_state == SI_CLEARING_FLAGS_THEN_SET_IRQ)
+ start_enable_irq(smi_info);
+ else
+ smi_info->si_state = SI_NORMAL;
+ break;
+ }
+
+ case SI_GETTING_EVENTS:
+ {
+ smi_info->curr_msg->rsp_size
+ = smi_info->handlers->get_result(
+ smi_info->si_sm,
+ smi_info->curr_msg->rsp,
+ IPMI_MAX_MSG_LENGTH);
+
+ /* Do this here becase deliver_recv_msg() releases the
+ lock, and a new message can be put in during the
+ time the lock is released. */
+ msg = smi_info->curr_msg;
+ smi_info->curr_msg = NULL;
+ if (msg->rsp[2] != 0) {
+ /* Error getting event, probably done. */
+ msg->done(msg);
+
+ /* Take off the event flag. */
+ smi_info->msg_flags &= ~EVENT_MSG_BUFFER_FULL;
+ handle_flags(smi_info);
+ } else {
+ spin_lock(&smi_info->count_lock);
+ smi_info->events++;
+ spin_unlock(&smi_info->count_lock);
+
+ /* Do this before we deliver the message
+ because delivering the message releases the
+ lock and something else can mess with the
+ state. */
+ handle_flags(smi_info);
+
+ deliver_recv_msg(smi_info, msg);
+ }
+ break;
+ }
+
+ case SI_GETTING_MESSAGES:
+ {
+ smi_info->curr_msg->rsp_size
+ = smi_info->handlers->get_result(
+ smi_info->si_sm,
+ smi_info->curr_msg->rsp,
+ IPMI_MAX_MSG_LENGTH);
+
+ /* Do this here becase deliver_recv_msg() releases the
+ lock, and a new message can be put in during the
+ time the lock is released. */
+ msg = smi_info->curr_msg;
+ smi_info->curr_msg = NULL;
+ if (msg->rsp[2] != 0) {
+ /* Error getting event, probably done. */
+ msg->done(msg);
+
+ /* Take off the msg flag. */
+ smi_info->msg_flags &= ~RECEIVE_MSG_AVAIL;
+ handle_flags(smi_info);
+ } else {
+ spin_lock(&smi_info->count_lock);
+ smi_info->incoming_messages++;
+ spin_unlock(&smi_info->count_lock);
+
+ /* Do this before we deliver the message
+ because delivering the message releases the
+ lock and something else can mess with the
+ state. */
+ handle_flags(smi_info);
+
+ deliver_recv_msg(smi_info, msg);
+ }
+ break;
+ }
+
+ case SI_ENABLE_INTERRUPTS1:
+ {
+ unsigned char msg[4];
+
+ /* We got the flags from the SMI, now handle them. */
+ smi_info->handlers->get_result(smi_info->si_sm, msg, 4);
+ if (msg[2] != 0) {
+ printk(KERN_WARNING
+ "ipmi_si: Could not enable interrupts"
+ ", failed get, using polled mode.\n");
+ smi_info->si_state = SI_NORMAL;
+ } else {
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_SET_BMC_GLOBAL_ENABLES_CMD;
+ msg[2] = msg[3] | 1; /* enable msg queue int */
+ smi_info->handlers->start_transaction(
+ smi_info->si_sm, msg, 3);
+ smi_info->si_state = SI_ENABLE_INTERRUPTS2;
+ }
+ break;
+ }
+
+ case SI_ENABLE_INTERRUPTS2:
+ {
+ unsigned char msg[4];
+
+ /* We got the flags from the SMI, now handle them. */
+ smi_info->handlers->get_result(smi_info->si_sm, msg, 4);
+ if (msg[2] != 0) {
+ printk(KERN_WARNING
+ "ipmi_si: Could not enable interrupts"
+ ", failed set, using polled mode.\n");
+ }
+ smi_info->si_state = SI_NORMAL;
+ break;
+ }
+ }
+}
+
+/* Called on timeouts and events. Timeouts should pass the elapsed
+ time, interrupts should pass in zero. */
+static enum si_sm_result smi_event_handler(struct smi_info *smi_info,
+ int time)
+{
+ enum si_sm_result si_sm_result;
+
+ restart:
+ /* There used to be a loop here that waited a little while
+ (around 25us) before giving up. That turned out to be
+ pointless, the minimum delays I was seeing were in the 300us
+ range, which is far too long to wait in an interrupt. So
+ we just run until the state machine tells us something
+ happened or it needs a delay. */
+ si_sm_result = smi_info->handlers->event(smi_info->si_sm, time);
+ time = 0;
+ while (si_sm_result == SI_SM_CALL_WITHOUT_DELAY)
+ {
+ si_sm_result = smi_info->handlers->event(smi_info->si_sm, 0);
+ }
+
+ if (si_sm_result == SI_SM_TRANSACTION_COMPLETE)
+ {
+ spin_lock(&smi_info->count_lock);
+ smi_info->complete_transactions++;
+ spin_unlock(&smi_info->count_lock);
+
+ handle_transaction_done(smi_info);
+ si_sm_result = smi_info->handlers->event(smi_info->si_sm, 0);
+ }
+ else if (si_sm_result == SI_SM_HOSED)
+ {
+ spin_lock(&smi_info->count_lock);
+ smi_info->hosed_count++;
+ spin_unlock(&smi_info->count_lock);
+
+ /* Do the before return_hosed_msg, because that
+ releases the lock. */
+ smi_info->si_state = SI_NORMAL;
+ if (smi_info->curr_msg != NULL) {
+ /* If we were handling a user message, format
+ a response to send to the upper layer to
+ tell it about the error. */
+ return_hosed_msg(smi_info);
+ }
+ si_sm_result = smi_info->handlers->event(smi_info->si_sm, 0);
+ }
+
+ /* We prefer handling attn over new messages. */
+ if (si_sm_result == SI_SM_ATTN)
+ {
+ unsigned char msg[2];
+
+ spin_lock(&smi_info->count_lock);
+ smi_info->attentions++;
+ spin_unlock(&smi_info->count_lock);
+
+ /* Got a attn, send down a get message flags to see
+ what's causing it. It would be better to handle
+ this in the upper layer, but due to the way
+ interrupts work with the SMI, that's not really
+ possible. */
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_GET_MSG_FLAGS_CMD;
+
+ smi_info->handlers->start_transaction(
+ smi_info->si_sm, msg, 2);
+ smi_info->si_state = SI_GETTING_FLAGS;
+ goto restart;
+ }
+
+ /* If we are currently idle, try to start the next message. */
+ if (si_sm_result == SI_SM_IDLE) {
+ spin_lock(&smi_info->count_lock);
+ smi_info->idles++;
+ spin_unlock(&smi_info->count_lock);
+
+ si_sm_result = start_next_msg(smi_info);
+ if (si_sm_result != SI_SM_IDLE)
+ goto restart;
+ }
+
+ if ((si_sm_result == SI_SM_IDLE)
+ && (atomic_read(&smi_info->req_events)))
+ {
+ /* We are idle and the upper layer requested that I fetch
+ events, so do so. */
+ unsigned char msg[2];
+
+ spin_lock(&smi_info->count_lock);
+ smi_info->flag_fetches++;
+ spin_unlock(&smi_info->count_lock);
+
+ atomic_set(&smi_info->req_events, 0);
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_GET_MSG_FLAGS_CMD;
+
+ smi_info->handlers->start_transaction(
+ smi_info->si_sm, msg, 2);
+ smi_info->si_state = SI_GETTING_FLAGS;
+ goto restart;
+ }
+
+ return si_sm_result;
+}
+
+static void sender(void *send_info,
+ struct ipmi_smi_msg *msg,
+ int priority)
+{
+ struct smi_info *smi_info = send_info;
+ enum si_sm_result result;
+ unsigned long flags;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+#endif
+
+ spin_lock_irqsave(&(smi_info->msg_lock), flags);
+#ifdef DEBUG_TIMING
+ do_gettimeofday(&t);
+ printk("**Enqueue: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+
+ if (smi_info->run_to_completion) {
+ /* If we are running to completion, then throw it in
+ the list and run transactions until everything is
+ clear. Priority doesn't matter here. */
+ list_add_tail(&(msg->link), &(smi_info->xmit_msgs));
+
+ /* We have to release the msg lock and claim the smi
+ lock in this case, because of race conditions. */
+ spin_unlock_irqrestore(&(smi_info->msg_lock), flags);
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+ result = smi_event_handler(smi_info, 0);
+ while (result != SI_SM_IDLE) {
+ udelay(SI_SHORT_TIMEOUT_USEC);
+ result = smi_event_handler(smi_info,
+ SI_SHORT_TIMEOUT_USEC);
+ }
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+ return;
+ } else {
+ if (priority > 0) {
+ list_add_tail(&(msg->link), &(smi_info->hp_xmit_msgs));
+ } else {
+ list_add_tail(&(msg->link), &(smi_info->xmit_msgs));
+ }
+ }
+ spin_unlock_irqrestore(&(smi_info->msg_lock), flags);
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+ if ((smi_info->si_state == SI_NORMAL)
+ && (smi_info->curr_msg == NULL))
+ {
+ start_next_msg(smi_info);
+ si_restart_short_timer(smi_info);
+ }
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+}
+
+static void set_run_to_completion(void *send_info, int i_run_to_completion)
+{
+ struct smi_info *smi_info = send_info;
+ enum si_sm_result result;
+ unsigned long flags;
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+
+ smi_info->run_to_completion = i_run_to_completion;
+ if (i_run_to_completion) {
+ result = smi_event_handler(smi_info, 0);
+ while (result != SI_SM_IDLE) {
+ udelay(SI_SHORT_TIMEOUT_USEC);
+ result = smi_event_handler(smi_info,
+ SI_SHORT_TIMEOUT_USEC);
+ }
+ }
+
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+}
+
+static void poll(void *send_info)
+{
+ struct smi_info *smi_info = send_info;
+
+ smi_event_handler(smi_info, 0);
+}
+
+static void request_events(void *send_info)
+{
+ struct smi_info *smi_info = send_info;
+
+ atomic_set(&smi_info->req_events, 1);
+}
+
+static int initialized = 0;
+
+/* Must be called with interrupts off and with the si_lock held. */
+static void si_restart_short_timer(struct smi_info *smi_info)
+{
+#if defined(CONFIG_HIGH_RES_TIMERS)
+ unsigned long flags;
+ unsigned long jiffies_now;
+
+ if (del_timer(&(smi_info->si_timer))) {
+ /* If we don't delete the timer, then it will go off
+ immediately, anyway. So we only process if we
+ actually delete the timer. */
+
+ /* We already have irqsave on, so no need for it
+ here. */
+ read_lock(&xtime_lock);
+ jiffies_now = jiffies;
+ smi_info->si_timer.expires = jiffies_now;
+ smi_info->si_timer.sub_expires = get_arch_cycles(jiffies_now);
+
+ add_usec_to_timer(&smi_info->si_timer, SI_SHORT_TIMEOUT_USEC);
+
+ add_timer(&(smi_info->si_timer));
+ spin_lock_irqsave(&smi_info->count_lock, flags);
+ smi_info->timeout_restarts++;
+ spin_unlock_irqrestore(&smi_info->count_lock, flags);
+ }
+#endif
+}
+
+static void smi_timeout(unsigned long data)
+{
+ struct smi_info *smi_info = (struct smi_info *) data;
+ enum si_sm_result smi_result;
+ unsigned long flags;
+ unsigned long jiffies_now;
+ unsigned long time_diff;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+#endif
+
+ if (smi_info->stop_operation) {
+ smi_info->timer_stopped = 1;
+ return;
+ }
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+#ifdef DEBUG_TIMING
+ do_gettimeofday(&t);
+ printk("**Timer: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+ jiffies_now = jiffies;
+ time_diff = ((jiffies_now - smi_info->last_timeout_jiffies)
+ * SI_USEC_PER_JIFFY);
+ smi_result = smi_event_handler(smi_info, time_diff);
+
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+
+ smi_info->last_timeout_jiffies = jiffies_now;
+
+ if ((smi_info->irq) && (! smi_info->interrupt_disabled)) {
+ /* Running with interrupts, only do long timeouts. */
+ smi_info->si_timer.expires = jiffies + SI_TIMEOUT_JIFFIES;
+ spin_lock_irqsave(&smi_info->count_lock, flags);
+ smi_info->long_timeouts++;
+ spin_unlock_irqrestore(&smi_info->count_lock, flags);
+ goto do_add_timer;
+ }
+
+ /* If the state machine asks for a short delay, then shorten
+ the timer timeout. */
+ if (smi_result == SI_SM_CALL_WITH_DELAY) {
+ spin_lock_irqsave(&smi_info->count_lock, flags);
+ smi_info->short_timeouts++;
+ spin_unlock_irqrestore(&smi_info->count_lock, flags);
+#if defined(CONFIG_HIGH_RES_TIMERS)
+ read_lock(&xtime_lock);
+ smi_info->si_timer.expires = jiffies;
+ smi_info->si_timer.sub_expires
+ = get_arch_cycles(smi_info->si_timer.expires);
+ read_unlock(&xtime_lock);
+ add_usec_to_timer(&smi_info->si_timer, SI_SHORT_TIMEOUT_USEC);
+#else
+ smi_info->si_timer.expires = jiffies + 1;
+#endif
+ } else {
+ spin_lock_irqsave(&smi_info->count_lock, flags);
+ smi_info->long_timeouts++;
+ spin_unlock_irqrestore(&smi_info->count_lock, flags);
+ smi_info->si_timer.expires = jiffies + SI_TIMEOUT_JIFFIES;
+#if defined(CONFIG_HIGH_RES_TIMERS)
+ smi_info->si_timer.sub_expires = 0;
+#endif
+ }
+
+ do_add_timer:
+ add_timer(&(smi_info->si_timer));
+}
+
+static irqreturn_t si_irq_handler(int irq, void *data, struct pt_regs *regs)
+{
+ struct smi_info *smi_info = data;
+ unsigned long flags;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+#endif
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+
+ spin_lock(&smi_info->count_lock);
+ smi_info->interrupts++;
+ spin_unlock(&smi_info->count_lock);
+
+ if (smi_info->stop_operation)
+ goto out;
+
+#ifdef DEBUG_TIMING
+ do_gettimeofday(&t);
+ printk("**Interrupt: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+ smi_event_handler(smi_info, 0);
+ out:
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+ return IRQ_HANDLED;
+}
+
+static struct ipmi_smi_handlers handlers =
+{
+ .owner = THIS_MODULE,
+ .sender = sender,
+ .request_events = request_events,
+ .set_run_to_completion = set_run_to_completion,
+ .poll = poll,
+};
+
+/* There can be 4 IO ports passed in (with or without IRQs), 4 addresses,
+ a default IO port, and 1 ACPI/SPMI address. That sets SI_MAX_DRIVERS */
+
+#define SI_MAX_PARMS 4
+#define SI_MAX_DRIVERS ((SI_MAX_PARMS * 2) + 2)
+static struct smi_info *smi_infos[SI_MAX_DRIVERS] =
+{ NULL, NULL, NULL, NULL };
+
+#define DEVICE_NAME "ipmi_si"
+
+#define DEFAULT_KCS_IO_PORT 0xca2
+#define DEFAULT_SMIC_IO_PORT 0xca9
+#define DEFAULT_BT_IO_PORT 0xe4
+#define DEFAULT_REGSPACING 1
+
+static int si_trydefaults = 1;
+static char *si_type[SI_MAX_PARMS];
+#define MAX_SI_TYPE_STR 30
+static char si_type_str[MAX_SI_TYPE_STR];
+static unsigned long addrs[SI_MAX_PARMS];
+static int num_addrs;
+static unsigned int ports[SI_MAX_PARMS];
+static int num_ports;
+static int irqs[SI_MAX_PARMS];
+static int num_irqs;
+static int regspacings[SI_MAX_PARMS];
+static int num_regspacings = 0;
+static int regsizes[SI_MAX_PARMS];
+static int num_regsizes = 0;
+static int regshifts[SI_MAX_PARMS];
+static int num_regshifts = 0;
+static int slave_addrs[SI_MAX_PARMS];
+static int num_slave_addrs = 0;
+
+
+module_param_named(trydefaults, si_trydefaults, bool, 0);
+MODULE_PARM_DESC(trydefaults, "Setting this to 'false' will disable the"
+ " default scan of the KCS and SMIC interface at the standard"
+ " address");
+module_param_string(type, si_type_str, MAX_SI_TYPE_STR, 0);
+MODULE_PARM_DESC(type, "Defines the type of each interface, each"
+ " interface separated by commas. The types are 'kcs',"
+ " 'smic', and 'bt'. For example si_type=kcs,bt will set"
+ " the first interface to kcs and the second to bt");
+module_param_array(addrs, long, &num_addrs, 0);
+MODULE_PARM_DESC(addrs, "Sets the memory address of each interface, the"
+ " addresses separated by commas. Only use if an interface"
+ " is in memory. Otherwise, set it to zero or leave"
+ " it blank.");
+module_param_array(ports, int, &num_ports, 0);
+MODULE_PARM_DESC(ports, "Sets the port address of each interface, the"
+ " addresses separated by commas. Only use if an interface"
+ " is a port. Otherwise, set it to zero or leave"
+ " it blank.");
+module_param_array(irqs, int, &num_irqs, 0);
+MODULE_PARM_DESC(irqs, "Sets the interrupt of each interface, the"
+ " addresses separated by commas. Only use if an interface"
+ " has an interrupt. Otherwise, set it to zero or leave"
+ " it blank.");
+module_param_array(regspacings, int, &num_regspacings, 0);
+MODULE_PARM_DESC(regspacings, "The number of bytes between the start address"
+ " and each successive register used by the interface. For"
+ " instance, if the start address is 0xca2 and the spacing"
+ " is 2, then the second address is at 0xca4. Defaults"
+ " to 1.");
+module_param_array(regsizes, int, &num_regsizes, 0);
+MODULE_PARM_DESC(regsizes, "The size of the specific IPMI register in bytes."
+ " This should generally be 1, 2, 4, or 8 for an 8-bit,"
+ " 16-bit, 32-bit, or 64-bit register. Use this if you"
+ " the 8-bit IPMI register has to be read from a larger"
+ " register.");
+module_param_array(regshifts, int, &num_regshifts, 0);
+MODULE_PARM_DESC(regshifts, "The amount to shift the data read from the."
+ " IPMI register, in bits. For instance, if the data"
+ " is read from a 32-bit word and the IPMI data is in"
+ " bit 8-15, then the shift would be 8");
+module_param_array(slave_addrs, int, &num_slave_addrs, 0);
+MODULE_PARM_DESC(slave_addrs, "Set the default IPMB slave address for"
+ " the controller. Normally this is 0x20, but can be"
+ " overridden by this parm. This is an array indexed"
+ " by interface number.");
+
+
+#define IPMI_MEM_ADDR_SPACE 1
+#define IPMI_IO_ADDR_SPACE 2
+
+#if defined(CONFIG_ACPI_INTERPRETER) || defined(CONFIG_X86) || defined(CONFIG_PCI)
+static int is_new_interface(int intf, u8 addr_space, unsigned long base_addr)
+{
+ int i;
+
+ for (i = 0; i < SI_MAX_PARMS; ++i) {
+ /* Don't check our address. */
+ if (i == intf)
+ continue;
+ if (si_type[i] != NULL) {
+ if ((addr_space == IPMI_MEM_ADDR_SPACE &&
+ base_addr == addrs[i]) ||
+ (addr_space == IPMI_IO_ADDR_SPACE &&
+ base_addr == ports[i]))
+ return 0;
+ }
+ else
+ break;
+ }
+
+ return 1;
+}
+#endif
+
+static int std_irq_setup(struct smi_info *info)
+{
+ int rv;
+
+ if (!info->irq)
+ return 0;
+
+ rv = request_irq(info->irq,
+ si_irq_handler,
+ SA_INTERRUPT,
+ DEVICE_NAME,
+ info);
+ if (rv) {
+ printk(KERN_WARNING
+ "ipmi_si: %s unable to claim interrupt %d,"
+ " running polled\n",
+ DEVICE_NAME, info->irq);
+ info->irq = 0;
+ } else {
+ printk(" Using irq %d\n", info->irq);
+ }
+
+ return rv;
+}
+
+static void std_irq_cleanup(struct smi_info *info)
+{
+ if (!info->irq)
+ return;
+
+ free_irq(info->irq, info);
+}
+
+static unsigned char port_inb(struct si_sm_io *io, unsigned int offset)
+{
+ unsigned int *addr = io->info;
+
+ return inb((*addr)+(offset*io->regspacing));
+}
+
+static void port_outb(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ unsigned int *addr = io->info;
+
+ outb(b, (*addr)+(offset * io->regspacing));
+}
+
+static unsigned char port_inw(struct si_sm_io *io, unsigned int offset)
+{
+ unsigned int *addr = io->info;
+
+ return (inw((*addr)+(offset * io->regspacing)) >> io->regshift) & 0xff;
+}
+
+static void port_outw(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ unsigned int *addr = io->info;
+
+ outw(b << io->regshift, (*addr)+(offset * io->regspacing));
+}
+
+static unsigned char port_inl(struct si_sm_io *io, unsigned int offset)
+{
+ unsigned int *addr = io->info;
+
+ return (inl((*addr)+(offset * io->regspacing)) >> io->regshift) & 0xff;
+}
+
+static void port_outl(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ unsigned int *addr = io->info;
+
+ outl(b << io->regshift, (*addr)+(offset * io->regspacing));
+}
+
+static void port_cleanup(struct smi_info *info)
+{
+ unsigned int *addr = info->io.info;
+ int mapsize;
+
+ if (addr && (*addr)) {
+ mapsize = ((info->io_size * info->io.regspacing)
+ - (info->io.regspacing - info->io.regsize));
+
+ release_region (*addr, mapsize);
+ }
+ kfree(info);
+}
+
+static int port_setup(struct smi_info *info)
+{
+ unsigned int *addr = info->io.info;
+ int mapsize;
+
+ if (!addr || (!*addr))
+ return -ENODEV;
+
+ info->io_cleanup = port_cleanup;
+
+ /* Figure out the actual inb/inw/inl/etc routine to use based
+ upon the register size. */
+ switch (info->io.regsize) {
+ case 1:
+ info->io.inputb = port_inb;
+ info->io.outputb = port_outb;
+ break;
+ case 2:
+ info->io.inputb = port_inw;
+ info->io.outputb = port_outw;
+ break;
+ case 4:
+ info->io.inputb = port_inl;
+ info->io.outputb = port_outl;
+ break;
+ default:
+ printk("ipmi_si: Invalid register size: %d\n",
+ info->io.regsize);
+ return -EINVAL;
+ }
+
+ /* Calculate the total amount of memory to claim. This is an
+ * unusual looking calculation, but it avoids claiming any
+ * more memory than it has to. It will claim everything
+ * between the first address to the end of the last full
+ * register. */
+ mapsize = ((info->io_size * info->io.regspacing)
+ - (info->io.regspacing - info->io.regsize));
+
+ if (request_region(*addr, mapsize, DEVICE_NAME) == NULL)
+ return -EIO;
+ return 0;
+}
+
+static int try_init_port(int intf_num, struct smi_info **new_info)
+{
+ struct smi_info *info;
+
+ if (!ports[intf_num])
+ return -ENODEV;
+
+ if (!is_new_interface(intf_num, IPMI_IO_ADDR_SPACE,
+ ports[intf_num]))
+ return -ENODEV;
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ printk(KERN_ERR "ipmi_si: Could not allocate SI data (1)\n");
+ return -ENOMEM;
+ }
+ memset(info, 0, sizeof(*info));
+
+ info->io_setup = port_setup;
+ info->io.info = &(ports[intf_num]);
+ info->io.addr = NULL;
+ info->io.regspacing = regspacings[intf_num];
+ if (!info->io.regspacing)
+ info->io.regspacing = DEFAULT_REGSPACING;
+ info->io.regsize = regsizes[intf_num];
+ if (!info->io.regsize)
+ info->io.regsize = DEFAULT_REGSPACING;
+ info->io.regshift = regshifts[intf_num];
+ info->irq = 0;
+ info->irq_setup = NULL;
+ *new_info = info;
+
+ if (si_type[intf_num] == NULL)
+ si_type[intf_num] = "kcs";
+
+ printk("ipmi_si: Trying \"%s\" at I/O port 0x%x\n",
+ si_type[intf_num], ports[intf_num]);
+ return 0;
+}
+
+static unsigned char mem_inb(struct si_sm_io *io, unsigned int offset)
+{
+ return readb((io->addr)+(offset * io->regspacing));
+}
+
+static void mem_outb(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ writeb(b, (io->addr)+(offset * io->regspacing));
+}
+
+static unsigned char mem_inw(struct si_sm_io *io, unsigned int offset)
+{
+ return (readw((io->addr)+(offset * io->regspacing)) >> io->regshift)
+ && 0xff;
+}
+
+static void mem_outw(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ writeb(b << io->regshift, (io->addr)+(offset * io->regspacing));
+}
+
+static unsigned char mem_inl(struct si_sm_io *io, unsigned int offset)
+{
+ return (readl((io->addr)+(offset * io->regspacing)) >> io->regshift)
+ && 0xff;
+}
+
+static void mem_outl(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ writel(b << io->regshift, (io->addr)+(offset * io->regspacing));
+}
+
+#ifdef readq
+static unsigned char mem_inq(struct si_sm_io *io, unsigned int offset)
+{
+ return (readq((io->addr)+(offset * io->regspacing)) >> io->regshift)
+ && 0xff;
+}
+
+static void mem_outq(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ writeq(b << io->regshift, (io->addr)+(offset * io->regspacing));
+}
+#endif
+
+static void mem_cleanup(struct smi_info *info)
+{
+ unsigned long *addr = info->io.info;
+ int mapsize;
+
+ if (info->io.addr) {
+ iounmap(info->io.addr);
+
+ mapsize = ((info->io_size * info->io.regspacing)
+ - (info->io.regspacing - info->io.regsize));
+
+ release_mem_region(*addr, mapsize);
+ }
+ kfree(info);
+}
+
+static int mem_setup(struct smi_info *info)
+{
+ unsigned long *addr = info->io.info;
+ int mapsize;
+
+ if (!addr || (!*addr))
+ return -ENODEV;
+
+ info->io_cleanup = mem_cleanup;
+
+ /* Figure out the actual readb/readw/readl/etc routine to use based
+ upon the register size. */
+ switch (info->io.regsize) {
+ case 1:
+ info->io.inputb = mem_inb;
+ info->io.outputb = mem_outb;
+ break;
+ case 2:
+ info->io.inputb = mem_inw;
+ info->io.outputb = mem_outw;
+ break;
+ case 4:
+ info->io.inputb = mem_inl;
+ info->io.outputb = mem_outl;
+ break;
+#ifdef readq
+ case 8:
+ info->io.inputb = mem_inq;
+ info->io.outputb = mem_outq;
+ break;
+#endif
+ default:
+ printk("ipmi_si: Invalid register size: %d\n",
+ info->io.regsize);
+ return -EINVAL;
+ }
+
+ /* Calculate the total amount of memory to claim. This is an
+ * unusual looking calculation, but it avoids claiming any
+ * more memory than it has to. It will claim everything
+ * between the first address to the end of the last full
+ * register. */
+ mapsize = ((info->io_size * info->io.regspacing)
+ - (info->io.regspacing - info->io.regsize));
+
+ if (request_mem_region(*addr, mapsize, DEVICE_NAME) == NULL)
+ return -EIO;
+
+ info->io.addr = ioremap(*addr, mapsize);
+ if (info->io.addr == NULL) {
+ release_mem_region(*addr, mapsize);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int try_init_mem(int intf_num, struct smi_info **new_info)
+{
+ struct smi_info *info;
+
+ if (!addrs[intf_num])
+ return -ENODEV;
+
+ if (!is_new_interface(intf_num, IPMI_MEM_ADDR_SPACE,
+ addrs[intf_num]))
+ return -ENODEV;
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ printk(KERN_ERR "ipmi_si: Could not allocate SI data (2)\n");
+ return -ENOMEM;
+ }
+ memset(info, 0, sizeof(*info));
+
+ info->io_setup = mem_setup;
+ info->io.info = &addrs[intf_num];
+ info->io.addr = NULL;
+ info->io.regspacing = regspacings[intf_num];
+ if (!info->io.regspacing)
+ info->io.regspacing = DEFAULT_REGSPACING;
+ info->io.regsize = regsizes[intf_num];
+ if (!info->io.regsize)
+ info->io.regsize = DEFAULT_REGSPACING;
+ info->io.regshift = regshifts[intf_num];
+ info->irq = 0;
+ info->irq_setup = NULL;
+ *new_info = info;
+
+ if (si_type[intf_num] == NULL)
+ si_type[intf_num] = "kcs";
+
+ printk("ipmi_si: Trying \"%s\" at memory address 0x%lx\n",
+ si_type[intf_num], addrs[intf_num]);
+ return 0;
+}
+
+
+#ifdef CONFIG_ACPI_INTERPRETER
+
+#include <linux/acpi.h>
+
+/* Once we get an ACPI failure, we don't try any more, because we go
+ through the tables sequentially. Once we don't find a table, there
+ are no more. */
+static int acpi_failure = 0;
+
+/* For GPE-type interrupts. */
+static u32 ipmi_acpi_gpe(void *context)
+{
+ struct smi_info *smi_info = context;
+ unsigned long flags;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+#endif
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+
+ spin_lock(&smi_info->count_lock);
+ smi_info->interrupts++;
+ spin_unlock(&smi_info->count_lock);
+
+ if (smi_info->stop_operation)
+ goto out;
+
+#ifdef DEBUG_TIMING
+ do_gettimeofday(&t);
+ printk("**ACPI_GPE: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+ smi_event_handler(smi_info, 0);
+ out:
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+
+ return ACPI_INTERRUPT_HANDLED;
+}
+
+static int acpi_gpe_irq_setup(struct smi_info *info)
+{
+ acpi_status status;
+
+ if (!info->irq)
+ return 0;
+
+ /* FIXME - is level triggered right? */
+ status = acpi_install_gpe_handler(NULL,
+ info->irq,
+ ACPI_GPE_LEVEL_TRIGGERED,
+ &ipmi_acpi_gpe,
+ info);
+ if (status != AE_OK) {
+ printk(KERN_WARNING
+ "ipmi_si: %s unable to claim ACPI GPE %d,"
+ " running polled\n",
+ DEVICE_NAME, info->irq);
+ info->irq = 0;
+ return -EINVAL;
+ } else {
+ printk(" Using ACPI GPE %d\n", info->irq);
+ return 0;
+ }
+}
+
+static void acpi_gpe_irq_cleanup(struct smi_info *info)
+{
+ if (!info->irq)
+ return;
+
+ acpi_remove_gpe_handler(NULL, info->irq, &ipmi_acpi_gpe);
+}
+
+/*
+ * Defined at
+ * http://h21007.www2.hp.com/dspp/files/unprotected/devresource/Docs/TechPapers/IA64/hpspmi.pdf
+ */
+struct SPMITable {
+ s8 Signature[4];
+ u32 Length;
+ u8 Revision;
+ u8 Checksum;
+ s8 OEMID[6];
+ s8 OEMTableID[8];
+ s8 OEMRevision[4];
+ s8 CreatorID[4];
+ s8 CreatorRevision[4];
+ u8 InterfaceType;
+ u8 IPMIlegacy;
+ s16 SpecificationRevision;
+
+ /*
+ * Bit 0 - SCI interrupt supported
+ * Bit 1 - I/O APIC/SAPIC
+ */
+ u8 InterruptType;
+
+ /* If bit 0 of InterruptType is set, then this is the SCI
+ interrupt in the GPEx_STS register. */
+ u8 GPE;
+
+ s16 Reserved;
+
+ /* If bit 1 of InterruptType is set, then this is the I/O
+ APIC/SAPIC interrupt. */
+ u32 GlobalSystemInterrupt;
+
+ /* The actual register address. */
+ struct acpi_generic_address addr;
+
+ u8 UID[4];
+
+ s8 spmi_id[1]; /* A '\0' terminated array starts here. */
+};
+
+static int try_init_acpi(int intf_num, struct smi_info **new_info)
+{
+ struct smi_info *info;
+ acpi_status status;
+ struct SPMITable *spmi;
+ char *io_type;
+ u8 addr_space;
+
+ if (acpi_failure)
+ return -ENODEV;
+
+ status = acpi_get_firmware_table("SPMI", intf_num+1,
+ ACPI_LOGICAL_ADDRESSING,
+ (struct acpi_table_header **) &spmi);
+ if (status != AE_OK) {
+ acpi_failure = 1;
+ return -ENODEV;
+ }
+
+ if (spmi->IPMIlegacy != 1) {
+ printk(KERN_INFO "IPMI: Bad SPMI legacy %d\n", spmi->IPMIlegacy);
+ return -ENODEV;
+ }
+
+ if (spmi->addr.address_space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY)
+ addr_space = IPMI_MEM_ADDR_SPACE;
+ else
+ addr_space = IPMI_IO_ADDR_SPACE;
+ if (!is_new_interface(-1, addr_space, spmi->addr.address))
+ return -ENODEV;
+
+ if (!spmi->addr.register_bit_width) {
+ acpi_failure = 1;
+ return -ENODEV;
+ }
+
+ /* Figure out the interface type. */
+ switch (spmi->InterfaceType)
+ {
+ case 1: /* KCS */
+ si_type[intf_num] = "kcs";
+ break;
+
+ case 2: /* SMIC */
+ si_type[intf_num] = "smic";
+ break;
+
+ case 3: /* BT */
+ si_type[intf_num] = "bt";
+ break;
+
+ default:
+ printk(KERN_INFO "ipmi_si: Unknown ACPI/SPMI SI type %d\n",
+ spmi->InterfaceType);
+ return -EIO;
+ }
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ printk(KERN_ERR "ipmi_si: Could not allocate SI data (3)\n");
+ return -ENOMEM;
+ }
+ memset(info, 0, sizeof(*info));
+
+ if (spmi->InterruptType & 1) {
+ /* We've got a GPE interrupt. */
+ info->irq = spmi->GPE;
+ info->irq_setup = acpi_gpe_irq_setup;
+ info->irq_cleanup = acpi_gpe_irq_cleanup;
+ } else if (spmi->InterruptType & 2) {
+ /* We've got an APIC/SAPIC interrupt. */
+ info->irq = spmi->GlobalSystemInterrupt;
+ info->irq_setup = std_irq_setup;
+ info->irq_cleanup = std_irq_cleanup;
+ } else {
+ /* Use the default interrupt setting. */
+ info->irq = 0;
+ info->irq_setup = NULL;
+ }
+
+ regspacings[intf_num] = spmi->addr.register_bit_width / 8;
+ info->io.regspacing = spmi->addr.register_bit_width / 8;
+ regsizes[intf_num] = regspacings[intf_num];
+ info->io.regsize = regsizes[intf_num];
+ regshifts[intf_num] = spmi->addr.register_bit_offset;
+ info->io.regshift = regshifts[intf_num];
+
+ if (spmi->addr.address_space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
+ io_type = "memory";
+ info->io_setup = mem_setup;
+ addrs[intf_num] = spmi->addr.address;
+ info->io.info = &(addrs[intf_num]);
+ } else if (spmi->addr.address_space_id == ACPI_ADR_SPACE_SYSTEM_IO) {
+ io_type = "I/O";
+ info->io_setup = port_setup;
+ ports[intf_num] = spmi->addr.address;
+ info->io.info = &(ports[intf_num]);
+ } else {
+ kfree(info);
+ printk("ipmi_si: Unknown ACPI I/O Address type\n");
+ return -EIO;
+ }
+
+ *new_info = info;
+
+ printk("ipmi_si: ACPI/SPMI specifies \"%s\" %s SI @ 0x%lx\n",
+ si_type[intf_num], io_type, (unsigned long) spmi->addr.address);
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_X86
+typedef struct dmi_ipmi_data
+{
+ u8 type;
+ u8 addr_space;
+ unsigned long base_addr;
+ u8 irq;
+ u8 offset;
+ u8 slave_addr;
+} dmi_ipmi_data_t;
+
+static dmi_ipmi_data_t dmi_data[SI_MAX_DRIVERS];
+static int dmi_data_entries;
+
+typedef struct dmi_header
+{
+ u8 type;
+ u8 length;
+ u16 handle;
+} dmi_header_t;
+
+static int decode_dmi(dmi_header_t *dm, int intf_num)
+{
+ u8 *data = (u8 *)dm;
+ unsigned long base_addr;
+ u8 reg_spacing;
+ u8 len = dm->length;
+ dmi_ipmi_data_t *ipmi_data = dmi_data+intf_num;
+
+ ipmi_data->type = data[4];
+
+ memcpy(&base_addr, data+8, sizeof(unsigned long));
+ if (len >= 0x11) {
+ if (base_addr & 1) {
+ /* I/O */
+ base_addr &= 0xFFFE;
+ ipmi_data->addr_space = IPMI_IO_ADDR_SPACE;
+ }
+ else {
+ /* Memory */
+ ipmi_data->addr_space = IPMI_MEM_ADDR_SPACE;
+ }
+ /* If bit 4 of byte 0x10 is set, then the lsb for the address
+ is odd. */
+ ipmi_data->base_addr = base_addr | ((data[0x10] & 0x10) >> 4);
+
+ ipmi_data->irq = data[0x11];
+
+ /* The top two bits of byte 0x10 hold the register spacing. */
+ reg_spacing = (data[0x10] & 0xC0) >> 6;
+ switch(reg_spacing){
+ case 0x00: /* Byte boundaries */
+ ipmi_data->offset = 1;
+ break;
+ case 0x01: /* 32-bit boundaries */
+ ipmi_data->offset = 4;
+ break;
+ case 0x02: /* 16-byte boundaries */
+ ipmi_data->offset = 16;
+ break;
+ default:
+ /* Some other interface, just ignore it. */
+ return -EIO;
+ }
+ } else {
+ /* Old DMI spec. */
+ ipmi_data->base_addr = base_addr;
+ ipmi_data->addr_space = IPMI_IO_ADDR_SPACE;
+ ipmi_data->offset = 1;
+ }
+
+ ipmi_data->slave_addr = data[6];
+
+ if (is_new_interface(-1, ipmi_data->addr_space,ipmi_data->base_addr)) {
+ dmi_data_entries++;
+ return 0;
+ }
+
+ memset(ipmi_data, 0, sizeof(dmi_ipmi_data_t));
+
+ return -1;
+}
+
+static int dmi_table(u32 base, int len, int num)
+{
+ u8 *buf;
+ struct dmi_header *dm;
+ u8 *data;
+ int i=1;
+ int status=-1;
+ int intf_num = 0;
+
+ buf = ioremap(base, len);
+ if(buf==NULL)
+ return -1;
+
+ data = buf;
+
+ while(i<num && (data - buf) < len)
+ {
+ dm=(dmi_header_t *)data;
+
+ if((data-buf+dm->length) >= len)
+ break;
+
+ if (dm->type == 38) {
+ if (decode_dmi(dm, intf_num) == 0) {
+ intf_num++;
+ if (intf_num >= SI_MAX_DRIVERS)
+ break;
+ }
+ }
+
+ data+=dm->length;
+ while((data-buf) < len && (*data || data[1]))
+ data++;
+ data+=2;
+ i++;
+ }
+ iounmap(buf);
+
+ return status;
+}
+
+inline static int dmi_checksum(u8 *buf)
+{
+ u8 sum=0;
+ int a;
+
+ for(a=0; a<15; a++)
+ sum+=buf[a];
+ return (sum==0);
+}
+
+static int dmi_decode(void)
+{
+ u8 buf[15];
+ u32 fp=0xF0000;
+
+#ifdef CONFIG_SIMNOW
+ return -1;
+#endif
+
+ while(fp < 0xFFFFF)
+ {
+ isa_memcpy_fromio(buf, fp, 15);
+ if(memcmp(buf, "_DMI_", 5)==0 && dmi_checksum(buf))
+ {
+ u16 num=buf[13]<<8|buf[12];
+ u16 len=buf[7]<<8|buf[6];
+ u32 base=buf[11]<<24|buf[10]<<16|buf[9]<<8|buf[8];
+
+ if(dmi_table(base, len, num) == 0)
+ return 0;
+ }
+ fp+=16;
+ }
+
+ return -1;
+}
+
+static int try_init_smbios(int intf_num, struct smi_info **new_info)
+{
+ struct smi_info *info;
+ dmi_ipmi_data_t *ipmi_data = dmi_data+intf_num;
+ char *io_type;
+
+ if (intf_num >= dmi_data_entries)
+ return -ENODEV;
+
+ switch(ipmi_data->type) {
+ case 0x01: /* KCS */
+ si_type[intf_num] = "kcs";
+ break;
+ case 0x02: /* SMIC */
+ si_type[intf_num] = "smic";
+ break;
+ case 0x03: /* BT */
+ si_type[intf_num] = "bt";
+ break;
+ default:
+ return -EIO;
+ }
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ printk(KERN_ERR "ipmi_si: Could not allocate SI data (4)\n");
+ return -ENOMEM;
+ }
+ memset(info, 0, sizeof(*info));
+
+ if (ipmi_data->addr_space == 1) {
+ io_type = "memory";
+ info->io_setup = mem_setup;
+ addrs[intf_num] = ipmi_data->base_addr;
+ info->io.info = &(addrs[intf_num]);
+ } else if (ipmi_data->addr_space == 2) {
+ io_type = "I/O";
+ info->io_setup = port_setup;
+ ports[intf_num] = ipmi_data->base_addr;
+ info->io.info = &(ports[intf_num]);
+ } else {
+ kfree(info);
+ printk("ipmi_si: Unknown SMBIOS I/O Address type.\n");
+ return -EIO;
+ }
+
+ regspacings[intf_num] = ipmi_data->offset;
+ info->io.regspacing = regspacings[intf_num];
+ if (!info->io.regspacing)
+ info->io.regspacing = DEFAULT_REGSPACING;
+ info->io.regsize = DEFAULT_REGSPACING;
+ info->io.regshift = regshifts[intf_num];
+
+ info->slave_addr = ipmi_data->slave_addr;
+
+ irqs[intf_num] = ipmi_data->irq;
+
+ *new_info = info;
+
+ printk("ipmi_si: Found SMBIOS-specified state machine at %s"
+ " address 0x%lx, slave address 0x%x\n",
+ io_type, (unsigned long)ipmi_data->base_addr,
+ ipmi_data->slave_addr);
+ return 0;
+}
+#endif /* CONFIG_X86 */
+
+#ifdef CONFIG_PCI
+
+#define PCI_ERMC_CLASSCODE 0x0C0700
+#define PCI_HP_VENDOR_ID 0x103C
+#define PCI_MMC_DEVICE_ID 0x121A
+#define PCI_MMC_ADDR_CW 0x10
+
+/* Avoid more than one attempt to probe pci smic. */
+static int pci_smic_checked = 0;
+
+static int find_pci_smic(int intf_num, struct smi_info **new_info)
+{
+ struct smi_info *info;
+ int error;
+ struct pci_dev *pci_dev = NULL;
+ u16 base_addr;
+ int fe_rmc = 0;
+
+ if (pci_smic_checked)
+ return -ENODEV;
+
+ pci_smic_checked = 1;
+
+ if ((pci_dev = pci_get_device(PCI_HP_VENDOR_ID, PCI_MMC_DEVICE_ID,
+ NULL)))
+ ;
+ else if ((pci_dev = pci_get_class(PCI_ERMC_CLASSCODE, NULL)) &&
+ pci_dev->subsystem_vendor == PCI_HP_VENDOR_ID)
+ fe_rmc = 1;
+ else
+ return -ENODEV;
+
+ error = pci_read_config_word(pci_dev, PCI_MMC_ADDR_CW, &base_addr);
+ if (error)
+ {
+ pci_dev_put(pci_dev);
+ printk(KERN_ERR
+ "ipmi_si: pci_read_config_word() failed (%d).\n",
+ error);
+ return -ENODEV;
+ }
+
+ /* Bit 0: 1 specifies programmed I/O, 0 specifies memory mapped I/O */
+ if (!(base_addr & 0x0001))
+ {
+ pci_dev_put(pci_dev);
+ printk(KERN_ERR
+ "ipmi_si: memory mapped I/O not supported for PCI"
+ " smic.\n");
+ return -ENODEV;
+ }
+
+ base_addr &= 0xFFFE;
+ if (!fe_rmc)
+ /* Data register starts at base address + 1 in eRMC */
+ ++base_addr;
+
+ if (!is_new_interface(-1, IPMI_IO_ADDR_SPACE, base_addr)) {
+ pci_dev_put(pci_dev);
+ return -ENODEV;
+ }
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ pci_dev_put(pci_dev);
+ printk(KERN_ERR "ipmi_si: Could not allocate SI data (5)\n");
+ return -ENOMEM;
+ }
+ memset(info, 0, sizeof(*info));
+
+ info->io_setup = port_setup;
+ ports[intf_num] = base_addr;
+ info->io.info = &(ports[intf_num]);
+ info->io.regspacing = regspacings[intf_num];
+ if (!info->io.regspacing)
+ info->io.regspacing = DEFAULT_REGSPACING;
+ info->io.regsize = DEFAULT_REGSPACING;
+ info->io.regshift = regshifts[intf_num];
+
+ *new_info = info;
+
+ irqs[intf_num] = pci_dev->irq;
+ si_type[intf_num] = "smic";
+
+ printk("ipmi_si: Found PCI SMIC at I/O address 0x%lx\n",
+ (long unsigned int) base_addr);
+
+ pci_dev_put(pci_dev);
+ return 0;
+}
+#endif /* CONFIG_PCI */
+
+static int try_init_plug_and_play(int intf_num, struct smi_info **new_info)
+{
+#ifdef CONFIG_PCI
+ if (find_pci_smic(intf_num, new_info)==0)
+ return 0;
+#endif
+ /* Include other methods here. */
+
+ return -ENODEV;
+}
+
+
+static int try_get_dev_id(struct smi_info *smi_info)
+{
+ unsigned char msg[2];
+ unsigned char *resp;
+ unsigned long resp_len;
+ enum si_sm_result smi_result;
+ int rv = 0;
+
+ resp = kmalloc(IPMI_MAX_MSG_LENGTH, GFP_KERNEL);
+ if (!resp)
+ return -ENOMEM;
+
+ /* Do a Get Device ID command, since it comes back with some
+ useful info. */
+ msg[0] = IPMI_NETFN_APP_REQUEST << 2;
+ msg[1] = IPMI_GET_DEVICE_ID_CMD;
+ smi_info->handlers->start_transaction(smi_info->si_sm, msg, 2);
+
+ smi_result = smi_info->handlers->event(smi_info->si_sm, 0);
+ for (;;)
+ {
+ if (smi_result == SI_SM_CALL_WITH_DELAY) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ smi_result = smi_info->handlers->event(
+ smi_info->si_sm, 100);
+ }
+ else if (smi_result == SI_SM_CALL_WITHOUT_DELAY)
+ {
+ smi_result = smi_info->handlers->event(
+ smi_info->si_sm, 0);
+ }
+ else
+ break;
+ }
+ if (smi_result == SI_SM_HOSED) {
+ /* We couldn't get the state machine to run, so whatever's at
+ the port is probably not an IPMI SMI interface. */
+ rv = -ENODEV;
+ goto out;
+ }
+
+ /* Otherwise, we got some data. */
+ resp_len = smi_info->handlers->get_result(smi_info->si_sm,
+ resp, IPMI_MAX_MSG_LENGTH);
+ if (resp_len < 6) {
+ /* That's odd, it should be longer. */
+ rv = -EINVAL;
+ goto out;
+ }
+
+ if ((resp[1] != IPMI_GET_DEVICE_ID_CMD) || (resp[2] != 0)) {
+ /* That's odd, it shouldn't be able to fail. */
+ rv = -EINVAL;
+ goto out;
+ }
+
+ /* Record info from the get device id, in case we need it. */
+ smi_info->ipmi_si_dev_rev = resp[4] & 0xf;
+ smi_info->ipmi_si_fw_rev_major = resp[5] & 0x7f;
+ smi_info->ipmi_si_fw_rev_minor = resp[6];
+ smi_info->ipmi_version_major = resp[7] & 0xf;
+ smi_info->ipmi_version_minor = resp[7] >> 4;
+
+ out:
+ kfree(resp);
+ return rv;
+}
+
+static int type_file_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char *out = (char *) page;
+ struct smi_info *smi = data;
+
+ switch (smi->si_type) {
+ case SI_KCS:
+ return sprintf(out, "kcs\n");
+ case SI_SMIC:
+ return sprintf(out, "smic\n");
+ case SI_BT:
+ return sprintf(out, "bt\n");
+ default:
+ return 0;
+ }
+}
+
+static int stat_file_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char *out = (char *) page;
+ struct smi_info *smi = data;
+
+ out += sprintf(out, "interrupts_enabled: %d\n",
+ smi->irq && !smi->interrupt_disabled);
+ out += sprintf(out, "short_timeouts: %ld\n",
+ smi->short_timeouts);
+ out += sprintf(out, "long_timeouts: %ld\n",
+ smi->long_timeouts);
+ out += sprintf(out, "timeout_restarts: %ld\n",
+ smi->timeout_restarts);
+ out += sprintf(out, "idles: %ld\n",
+ smi->idles);
+ out += sprintf(out, "interrupts: %ld\n",
+ smi->interrupts);
+ out += sprintf(out, "attentions: %ld\n",
+ smi->attentions);
+ out += sprintf(out, "flag_fetches: %ld\n",
+ smi->flag_fetches);
+ out += sprintf(out, "hosed_count: %ld\n",
+ smi->hosed_count);
+ out += sprintf(out, "complete_transactions: %ld\n",
+ smi->complete_transactions);
+ out += sprintf(out, "events: %ld\n",
+ smi->events);
+ out += sprintf(out, "watchdog_pretimeouts: %ld\n",
+ smi->watchdog_pretimeouts);
+ out += sprintf(out, "incoming_messages: %ld\n",
+ smi->incoming_messages);
+
+ return (out - ((char *) page));
+}
+
+/* Returns 0 if initialized, or negative on an error. */
+static int init_one_smi(int intf_num, struct smi_info **smi)
+{
+ int rv;
+ struct smi_info *new_smi;
+
+
+ rv = try_init_mem(intf_num, &new_smi);
+ if (rv)
+ rv = try_init_port(intf_num, &new_smi);
+#ifdef CONFIG_ACPI_INTERPRETER
+ if ((rv) && (si_trydefaults)) {
+ rv = try_init_acpi(intf_num, &new_smi);
+ }
+#endif
+#ifdef CONFIG_X86
+ if ((rv) && (si_trydefaults)) {
+ rv = try_init_smbios(intf_num, &new_smi);
+ }
+#endif
+ if ((rv) && (si_trydefaults)) {
+ rv = try_init_plug_and_play(intf_num, &new_smi);
+ }
+
+
+ if (rv)
+ return rv;
+
+ /* So we know not to free it unless we have allocated one. */
+ new_smi->intf = NULL;
+ new_smi->si_sm = NULL;
+ new_smi->handlers = NULL;
+
+ if (!new_smi->irq_setup) {
+ new_smi->irq = irqs[intf_num];
+ new_smi->irq_setup = std_irq_setup;
+ new_smi->irq_cleanup = std_irq_cleanup;
+ }
+
+ /* Default to KCS if no type is specified. */
+ if (si_type[intf_num] == NULL) {
+ if (si_trydefaults)
+ si_type[intf_num] = "kcs";
+ else {
+ rv = -EINVAL;
+ goto out_err;
+ }
+ }
+
+ /* Set up the state machine to use. */
+ if (strcmp(si_type[intf_num], "kcs") == 0) {
+ new_smi->handlers = &kcs_smi_handlers;
+ new_smi->si_type = SI_KCS;
+ } else if (strcmp(si_type[intf_num], "smic") == 0) {
+ new_smi->handlers = &smic_smi_handlers;
+ new_smi->si_type = SI_SMIC;
+ } else if (strcmp(si_type[intf_num], "bt") == 0) {
+ new_smi->handlers = &bt_smi_handlers;
+ new_smi->si_type = SI_BT;
+ } else {
+ /* No support for anything else yet. */
+ rv = -EIO;
+ goto out_err;
+ }
+
+ /* Allocate the state machine's data and initialize it. */
+ new_smi->si_sm = kmalloc(new_smi->handlers->size(), GFP_KERNEL);
+ if (!new_smi->si_sm) {
+ printk(" Could not allocate state machine memory\n");
+ rv = -ENOMEM;
+ goto out_err;
+ }
+ new_smi->io_size = new_smi->handlers->init_data(new_smi->si_sm,
+ &new_smi->io);
+
+ /* Now that we know the I/O size, we can set up the I/O. */
+ rv = new_smi->io_setup(new_smi);
+ if (rv) {
+ printk(" Could not set up I/O space\n");
+ goto out_err;
+ }
+
+ spin_lock_init(&(new_smi->si_lock));
+ spin_lock_init(&(new_smi->msg_lock));
+ spin_lock_init(&(new_smi->count_lock));
+
+ /* Do low-level detection first. */
+ if (new_smi->handlers->detect(new_smi->si_sm)) {
+ rv = -ENODEV;
+ goto out_err;
+ }
+
+ /* Attempt a get device id command. If it fails, we probably
+ don't have a SMI here. */
+ rv = try_get_dev_id(new_smi);
+ if (rv)
+ goto out_err;
+
+ /* Try to claim any interrupts. */
+ new_smi->irq_setup(new_smi);
+
+ INIT_LIST_HEAD(&(new_smi->xmit_msgs));
+ INIT_LIST_HEAD(&(new_smi->hp_xmit_msgs));
+ new_smi->curr_msg = NULL;
+ atomic_set(&new_smi->req_events, 0);
+ new_smi->run_to_completion = 0;
+
+ new_smi->interrupt_disabled = 0;
+ new_smi->timer_stopped = 0;
+ new_smi->stop_operation = 0;
+
+ /* Start clearing the flags before we enable interrupts or the
+ timer to avoid racing with the timer. */
+ start_clear_flags(new_smi);
+ /* IRQ is defined to be set when non-zero. */
+ if (new_smi->irq)
+ new_smi->si_state = SI_CLEARING_FLAGS_THEN_SET_IRQ;
+
+ /* The ipmi_register_smi() code does some operations to
+ determine the channel information, so we must be ready to
+ handle operations before it is called. This means we have
+ to stop the timer if we get an error after this point. */
+ init_timer(&(new_smi->si_timer));
+ new_smi->si_timer.data = (long) new_smi;
+ new_smi->si_timer.function = smi_timeout;
+ new_smi->last_timeout_jiffies = jiffies;
+ new_smi->si_timer.expires = jiffies + SI_TIMEOUT_JIFFIES;
+ add_timer(&(new_smi->si_timer));
+
+ rv = ipmi_register_smi(&handlers,
+ new_smi,
+ new_smi->ipmi_version_major,
+ new_smi->ipmi_version_minor,
+ new_smi->slave_addr,
+ &(new_smi->intf));
+ if (rv) {
+ printk(KERN_ERR
+ "ipmi_si: Unable to register device: error %d\n",
+ rv);
+ goto out_err_stop_timer;
+ }
+
+ rv = ipmi_smi_add_proc_entry(new_smi->intf, "type",
+ type_file_read_proc, NULL,
+ new_smi, THIS_MODULE);
+ if (rv) {
+ printk(KERN_ERR
+ "ipmi_si: Unable to create proc entry: %d\n",
+ rv);
+ goto out_err_stop_timer;
+ }
+
+ rv = ipmi_smi_add_proc_entry(new_smi->intf, "si_stats",
+ stat_file_read_proc, NULL,
+ new_smi, THIS_MODULE);
+ if (rv) {
+ printk(KERN_ERR
+ "ipmi_si: Unable to create proc entry: %d\n",
+ rv);
+ goto out_err_stop_timer;
+ }
+
+ *smi = new_smi;
+
+ printk(" IPMI %s interface initialized\n", si_type[intf_num]);
+
+ return 0;
+
+ out_err_stop_timer:
+ new_smi->stop_operation = 1;
+
+ /* Wait for the timer to stop. This avoids problems with race
+ conditions removing the timer here. */
+ while (!new_smi->timer_stopped) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ }
+
+ out_err:
+ if (new_smi->intf)
+ ipmi_unregister_smi(new_smi->intf);
+
+ new_smi->irq_cleanup(new_smi);
+
+ /* Wait until we know that we are out of any interrupt
+ handlers might have been running before we freed the
+ interrupt. */
+ synchronize_kernel();
+
+ if (new_smi->si_sm) {
+ if (new_smi->handlers)
+ new_smi->handlers->cleanup(new_smi->si_sm);
+ kfree(new_smi->si_sm);
+ }
+ new_smi->io_cleanup(new_smi);
+
+ return rv;
+}
+
+static __init int init_ipmi_si(void)
+{
+ int rv = 0;
+ int pos = 0;
+ int i;
+ char *str;
+
+ if (initialized)
+ return 0;
+ initialized = 1;
+
+ /* Parse out the si_type string into its components. */
+ str = si_type_str;
+ if (*str != '\0') {
+ for (i=0; (i<SI_MAX_PARMS) && (*str != '\0'); i++) {
+ si_type[i] = str;
+ str = strchr(str, ',');
+ if (str) {
+ *str = '\0';
+ str++;
+ } else {
+ break;
+ }
+ }
+ }
+
+ printk(KERN_INFO "IPMI System Interface driver version "
+ IPMI_SI_VERSION);
+ if (kcs_smi_handlers.version)
+ printk(", KCS version %s", kcs_smi_handlers.version);
+ if (smic_smi_handlers.version)
+ printk(", SMIC version %s", smic_smi_handlers.version);
+ if (bt_smi_handlers.version)
+ printk(", BT version %s", bt_smi_handlers.version);
+ printk("\n");
+
+#ifdef CONFIG_X86
+ dmi_decode();
+#endif
+
+ rv = init_one_smi(0, &(smi_infos[pos]));
+ if (rv && !ports[0] && si_trydefaults) {
+ /* If we are trying defaults and the initial port is
+ not set, then set it. */
+ si_type[0] = "kcs";
+ ports[0] = DEFAULT_KCS_IO_PORT;
+ rv = init_one_smi(0, &(smi_infos[pos]));
+ if (rv) {
+ /* No KCS - try SMIC */
+ si_type[0] = "smic";
+ ports[0] = DEFAULT_SMIC_IO_PORT;
+ rv = init_one_smi(0, &(smi_infos[pos]));
+ }
+ if (rv) {
+ /* No SMIC - try BT */
+ si_type[0] = "bt";
+ ports[0] = DEFAULT_BT_IO_PORT;
+ rv = init_one_smi(0, &(smi_infos[pos]));
+ }
+ }
+ if (rv == 0)
+ pos++;
+
+ for (i=1; i < SI_MAX_PARMS; i++) {
+ rv = init_one_smi(i, &(smi_infos[pos]));
+ if (rv == 0)
+ pos++;
+ }
+
+ if (smi_infos[0] == NULL) {
+ printk("ipmi_si: Unable to find any System Interface(s)\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+module_init(init_ipmi_si);
+
+static void __exit cleanup_one_si(struct smi_info *to_clean)
+{
+ int rv;
+ unsigned long flags;
+
+ if (! to_clean)
+ return;
+
+ /* Tell the timer and interrupt handlers that we are shutting
+ down. */
+ spin_lock_irqsave(&(to_clean->si_lock), flags);
+ spin_lock(&(to_clean->msg_lock));
+
+ to_clean->stop_operation = 1;
+
+ to_clean->irq_cleanup(to_clean);
+
+ spin_unlock(&(to_clean->msg_lock));
+ spin_unlock_irqrestore(&(to_clean->si_lock), flags);
+
+ /* Wait until we know that we are out of any interrupt
+ handlers might have been running before we freed the
+ interrupt. */
+ synchronize_kernel();
+
+ /* Wait for the timer to stop. This avoids problems with race
+ conditions removing the timer here. */
+ while (!to_clean->timer_stopped) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ }
+
+ /* Interrupts and timeouts are stopped, now make sure the
+ interface is in a clean state. */
+ while ((to_clean->curr_msg) || (to_clean->si_state != SI_NORMAL)) {
+ poll(to_clean);
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ }
+
+ rv = ipmi_unregister_smi(to_clean->intf);
+ if (rv) {
+ printk(KERN_ERR
+ "ipmi_si: Unable to unregister device: errno=%d\n",
+ rv);
+ }
+
+ to_clean->handlers->cleanup(to_clean->si_sm);
+
+ kfree(to_clean->si_sm);
+
+ to_clean->io_cleanup(to_clean);
+}
+
+static __exit void cleanup_ipmi_si(void)
+{
+ int i;
+
+ if (!initialized)
+ return;
+
+ for (i=0; i<SI_MAX_DRIVERS; i++) {
+ cleanup_one_si(smi_infos[i]);
+ }
+}
+module_exit(cleanup_ipmi_si);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/ipmi/ipmi_si_sm.h b/drivers/char/ipmi/ipmi_si_sm.h
new file mode 100644
index 0000000..a0212b0
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_si_sm.h
@@ -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;
+
diff --git a/drivers/char/ipmi/ipmi_smic_sm.c b/drivers/char/ipmi/ipmi_smic_sm.c
new file mode 100644
index 0000000..ae18747
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_smic_sm.c
@@ -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,
+};
diff --git a/drivers/char/ipmi/ipmi_watchdog.c b/drivers/char/ipmi/ipmi_watchdog.c
new file mode 100644
index 0000000..fd70938
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_watchdog.c
@@ -0,0 +1,1068 @@
+/*
+ * ipmi_watchdog.c
+ *
+ * A watchdog timer based upon the IPMI interface.
+ *
+ * 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/ipmi.h>
+#include <linux/ipmi_smi.h>
+#include <linux/watchdog.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+#include <linux/rwsem.h>
+#include <linux/errno.h>
+#include <asm/uaccess.h>
+#include <linux/notifier.h>
+#include <linux/nmi.h>
+#include <linux/reboot.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#ifdef CONFIG_X86_LOCAL_APIC
+#include <asm/apic.h>
+#endif
+
+#define PFX "IPMI Watchdog: "
+
+#define IPMI_WATCHDOG_VERSION "v33"
+
+/*
+ * The IPMI command/response information for the watchdog timer.
+ */
+
+/* values for byte 1 of the set command, byte 2 of the get response. */
+#define WDOG_DONT_LOG (1 << 7)
+#define WDOG_DONT_STOP_ON_SET (1 << 6)
+#define WDOG_SET_TIMER_USE(byte, use) \
+ byte = ((byte) & 0xf8) | ((use) & 0x7)
+#define WDOG_GET_TIMER_USE(byte) ((byte) & 0x7)
+#define WDOG_TIMER_USE_BIOS_FRB2 1
+#define WDOG_TIMER_USE_BIOS_POST 2
+#define WDOG_TIMER_USE_OS_LOAD 3
+#define WDOG_TIMER_USE_SMS_OS 4
+#define WDOG_TIMER_USE_OEM 5
+
+/* values for byte 2 of the set command, byte 3 of the get response. */
+#define WDOG_SET_PRETIMEOUT_ACT(byte, use) \
+ byte = ((byte) & 0x8f) | (((use) & 0x7) << 4)
+#define WDOG_GET_PRETIMEOUT_ACT(byte) (((byte) >> 4) & 0x7)
+#define WDOG_PRETIMEOUT_NONE 0
+#define WDOG_PRETIMEOUT_SMI 1
+#define WDOG_PRETIMEOUT_NMI 2
+#define WDOG_PRETIMEOUT_MSG_INT 3
+
+/* Operations that can be performed on a pretimout. */
+#define WDOG_PREOP_NONE 0
+#define WDOG_PREOP_PANIC 1
+#define WDOG_PREOP_GIVE_DATA 2 /* Cause data to be available to
+ read. Doesn't work in NMI
+ mode. */
+
+/* Actions to perform on a full timeout. */
+#define WDOG_SET_TIMEOUT_ACT(byte, use) \
+ byte = ((byte) & 0xf8) | ((use) & 0x7)
+#define WDOG_GET_TIMEOUT_ACT(byte) ((byte) & 0x7)
+#define WDOG_TIMEOUT_NONE 0
+#define WDOG_TIMEOUT_RESET 1
+#define WDOG_TIMEOUT_POWER_DOWN 2
+#define WDOG_TIMEOUT_POWER_CYCLE 3
+
+/* Byte 3 of the get command, byte 4 of the get response is the
+ pre-timeout in seconds. */
+
+/* Bits for setting byte 4 of the set command, byte 5 of the get response. */
+#define WDOG_EXPIRE_CLEAR_BIOS_FRB2 (1 << 1)
+#define WDOG_EXPIRE_CLEAR_BIOS_POST (1 << 2)
+#define WDOG_EXPIRE_CLEAR_OS_LOAD (1 << 3)
+#define WDOG_EXPIRE_CLEAR_SMS_OS (1 << 4)
+#define WDOG_EXPIRE_CLEAR_OEM (1 << 5)
+
+/* Setting/getting the watchdog timer value. This is for bytes 5 and
+ 6 (the timeout time) of the set command, and bytes 6 and 7 (the
+ timeout time) and 8 and 9 (the current countdown value) of the
+ response. The timeout value is given in seconds (in the command it
+ is 100ms intervals). */
+#define WDOG_SET_TIMEOUT(byte1, byte2, val) \
+ (byte1) = (((val) * 10) & 0xff), (byte2) = (((val) * 10) >> 8)
+#define WDOG_GET_TIMEOUT(byte1, byte2) \
+ (((byte1) | ((byte2) << 8)) / 10)
+
+#define IPMI_WDOG_RESET_TIMER 0x22
+#define IPMI_WDOG_SET_TIMER 0x24
+#define IPMI_WDOG_GET_TIMER 0x25
+
+/* These are here until the real ones get into the watchdog.h interface. */
+#ifndef WDIOC_GETTIMEOUT
+#define WDIOC_GETTIMEOUT _IOW(WATCHDOG_IOCTL_BASE, 20, int)
+#endif
+#ifndef WDIOC_SET_PRETIMEOUT
+#define WDIOC_SET_PRETIMEOUT _IOW(WATCHDOG_IOCTL_BASE, 21, int)
+#endif
+#ifndef WDIOC_GET_PRETIMEOUT
+#define WDIOC_GET_PRETIMEOUT _IOW(WATCHDOG_IOCTL_BASE, 22, int)
+#endif
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout;
+#endif
+
+static ipmi_user_t watchdog_user = NULL;
+
+/* Default the timeout to 10 seconds. */
+static int timeout = 10;
+
+/* The pre-timeout is disabled by default. */
+static int pretimeout = 0;
+
+/* Default action is to reset the board on a timeout. */
+static unsigned char action_val = WDOG_TIMEOUT_RESET;
+
+static char action[16] = "reset";
+
+static unsigned char preaction_val = WDOG_PRETIMEOUT_NONE;
+
+static char preaction[16] = "pre_none";
+
+static unsigned char preop_val = WDOG_PREOP_NONE;
+
+static char preop[16] = "preop_none";
+static DEFINE_SPINLOCK(ipmi_read_lock);
+static char data_to_read = 0;
+static DECLARE_WAIT_QUEUE_HEAD(read_q);
+static struct fasync_struct *fasync_q = NULL;
+static char pretimeout_since_last_heartbeat = 0;
+static char expect_close;
+
+/* If true, the driver will start running as soon as it is configured
+ and ready. */
+static int start_now = 0;
+
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Timeout value in seconds.");
+module_param(pretimeout, int, 0);
+MODULE_PARM_DESC(pretimeout, "Pretimeout value in seconds.");
+module_param_string(action, action, sizeof(action), 0);
+MODULE_PARM_DESC(action, "Timeout action. One of: "
+ "reset, none, power_cycle, power_off.");
+module_param_string(preaction, preaction, sizeof(preaction), 0);
+MODULE_PARM_DESC(preaction, "Pretimeout action. One of: "
+ "pre_none, pre_smi, pre_nmi, pre_int.");
+module_param_string(preop, preop, sizeof(preop), 0);
+MODULE_PARM_DESC(preop, "Pretimeout driver operation. One of: "
+ "preop_none, preop_panic, preop_give_data.");
+module_param(start_now, int, 0);
+MODULE_PARM_DESC(start_now, "Set to 1 to start the watchdog as"
+ "soon as the driver is loaded.");
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/* Default state of the timer. */
+static unsigned char ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
+
+/* If shutting down via IPMI, we ignore the heartbeat. */
+static int ipmi_ignore_heartbeat = 0;
+
+/* Is someone using the watchdog? Only one user is allowed. */
+static unsigned long ipmi_wdog_open = 0;
+
+/* If set to 1, the heartbeat command will set the state to reset and
+ start the timer. The timer doesn't normally run when the driver is
+ first opened until the heartbeat is set the first time, this
+ variable is used to accomplish this. */
+static int ipmi_start_timer_on_heartbeat = 0;
+
+/* IPMI version of the BMC. */
+static unsigned char ipmi_version_major;
+static unsigned char ipmi_version_minor;
+
+
+static int ipmi_heartbeat(void);
+static void panic_halt_ipmi_heartbeat(void);
+
+
+/* We use a semaphore to make sure that only one thing can send a set
+ timeout at one time, because we only have one copy of the data.
+ The semaphore is claimed when the set_timeout is sent and freed
+ when both messages are free. */
+static atomic_t set_timeout_tofree = ATOMIC_INIT(0);
+static DECLARE_MUTEX(set_timeout_lock);
+static void set_timeout_free_smi(struct ipmi_smi_msg *msg)
+{
+ if (atomic_dec_and_test(&set_timeout_tofree))
+ up(&set_timeout_lock);
+}
+static void set_timeout_free_recv(struct ipmi_recv_msg *msg)
+{
+ if (atomic_dec_and_test(&set_timeout_tofree))
+ up(&set_timeout_lock);
+}
+static struct ipmi_smi_msg set_timeout_smi_msg =
+{
+ .done = set_timeout_free_smi
+};
+static struct ipmi_recv_msg set_timeout_recv_msg =
+{
+ .done = set_timeout_free_recv
+};
+
+static int i_ipmi_set_timeout(struct ipmi_smi_msg *smi_msg,
+ struct ipmi_recv_msg *recv_msg,
+ int *send_heartbeat_now)
+{
+ struct kernel_ipmi_msg msg;
+ unsigned char data[6];
+ int rv;
+ struct ipmi_system_interface_addr addr;
+ int hbnow = 0;
+
+
+ data[0] = 0;
+ WDOG_SET_TIMER_USE(data[0], WDOG_TIMER_USE_SMS_OS);
+
+ if ((ipmi_version_major > 1)
+ || ((ipmi_version_major == 1) && (ipmi_version_minor >= 5)))
+ {
+ /* This is an IPMI 1.5-only feature. */
+ data[0] |= WDOG_DONT_STOP_ON_SET;
+ } else if (ipmi_watchdog_state != WDOG_TIMEOUT_NONE) {
+ /* In ipmi 1.0, setting the timer stops the watchdog, we
+ need to start it back up again. */
+ hbnow = 1;
+ }
+
+ data[1] = 0;
+ WDOG_SET_TIMEOUT_ACT(data[1], ipmi_watchdog_state);
+ if (pretimeout > 0) {
+ WDOG_SET_PRETIMEOUT_ACT(data[1], preaction_val);
+ data[2] = pretimeout;
+ } else {
+ WDOG_SET_PRETIMEOUT_ACT(data[1], WDOG_PRETIMEOUT_NONE);
+ data[2] = 0; /* No pretimeout. */
+ }
+ data[3] = 0;
+ WDOG_SET_TIMEOUT(data[4], data[5], timeout);
+
+ addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ addr.channel = IPMI_BMC_CHANNEL;
+ addr.lun = 0;
+
+ msg.netfn = 0x06;
+ msg.cmd = IPMI_WDOG_SET_TIMER;
+ msg.data = data;
+ msg.data_len = sizeof(data);
+ rv = ipmi_request_supply_msgs(watchdog_user,
+ (struct ipmi_addr *) &addr,
+ 0,
+ &msg,
+ NULL,
+ smi_msg,
+ recv_msg,
+ 1);
+ if (rv) {
+ printk(KERN_WARNING PFX "set timeout error: %d\n",
+ rv);
+ }
+
+ if (send_heartbeat_now)
+ *send_heartbeat_now = hbnow;
+
+ return rv;
+}
+
+/* Parameters to ipmi_set_timeout */
+#define IPMI_SET_TIMEOUT_NO_HB 0
+#define IPMI_SET_TIMEOUT_HB_IF_NECESSARY 1
+#define IPMI_SET_TIMEOUT_FORCE_HB 2
+
+static int ipmi_set_timeout(int do_heartbeat)
+{
+ int send_heartbeat_now;
+ int rv;
+
+
+ /* We can only send one of these at a time. */
+ down(&set_timeout_lock);
+
+ atomic_set(&set_timeout_tofree, 2);
+
+ rv = i_ipmi_set_timeout(&set_timeout_smi_msg,
+ &set_timeout_recv_msg,
+ &send_heartbeat_now);
+ if (rv) {
+ up(&set_timeout_lock);
+ } else {
+ if ((do_heartbeat == IPMI_SET_TIMEOUT_FORCE_HB)
+ || ((send_heartbeat_now)
+ && (do_heartbeat == IPMI_SET_TIMEOUT_HB_IF_NECESSARY)))
+ {
+ rv = ipmi_heartbeat();
+ }
+ }
+
+ return rv;
+}
+
+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 panic_halt_smi_msg =
+{
+ .done = dummy_smi_free
+};
+static struct ipmi_recv_msg panic_halt_recv_msg =
+{
+ .done = dummy_recv_free
+};
+
+/* Special call, doesn't claim any locks. This is only to be called
+ at panic or halt time, in run-to-completion mode, when the caller
+ is the only CPU and the only thing that will be going is these IPMI
+ calls. */
+static void panic_halt_ipmi_set_timeout(void)
+{
+ int send_heartbeat_now;
+ int rv;
+
+ rv = i_ipmi_set_timeout(&panic_halt_smi_msg,
+ &panic_halt_recv_msg,
+ &send_heartbeat_now);
+ if (!rv) {
+ if (send_heartbeat_now)
+ panic_halt_ipmi_heartbeat();
+ }
+}
+
+/* We use a semaphore to make sure that only one thing can send a
+ heartbeat at one time, because we only have one copy of the data.
+ The semaphore is claimed when the set_timeout is sent and freed
+ when both messages are free. */
+static atomic_t heartbeat_tofree = ATOMIC_INIT(0);
+static DECLARE_MUTEX(heartbeat_lock);
+static DECLARE_MUTEX_LOCKED(heartbeat_wait_lock);
+static void heartbeat_free_smi(struct ipmi_smi_msg *msg)
+{
+ if (atomic_dec_and_test(&heartbeat_tofree))
+ up(&heartbeat_wait_lock);
+}
+static void heartbeat_free_recv(struct ipmi_recv_msg *msg)
+{
+ if (atomic_dec_and_test(&heartbeat_tofree))
+ up(&heartbeat_wait_lock);
+}
+static struct ipmi_smi_msg heartbeat_smi_msg =
+{
+ .done = heartbeat_free_smi
+};
+static struct ipmi_recv_msg heartbeat_recv_msg =
+{
+ .done = heartbeat_free_recv
+};
+
+static struct ipmi_smi_msg panic_halt_heartbeat_smi_msg =
+{
+ .done = dummy_smi_free
+};
+static struct ipmi_recv_msg panic_halt_heartbeat_recv_msg =
+{
+ .done = dummy_recv_free
+};
+
+static int ipmi_heartbeat(void)
+{
+ struct kernel_ipmi_msg msg;
+ int rv;
+ struct ipmi_system_interface_addr addr;
+
+ if (ipmi_ignore_heartbeat) {
+ return 0;
+ }
+
+ if (ipmi_start_timer_on_heartbeat) {
+ ipmi_start_timer_on_heartbeat = 0;
+ ipmi_watchdog_state = action_val;
+ return ipmi_set_timeout(IPMI_SET_TIMEOUT_FORCE_HB);
+ } else if (pretimeout_since_last_heartbeat) {
+ /* A pretimeout occurred, make sure we set the timeout.
+ We don't want to set the action, though, we want to
+ leave that alone (thus it can't be combined with the
+ above operation. */
+ pretimeout_since_last_heartbeat = 0;
+ return ipmi_set_timeout(IPMI_SET_TIMEOUT_HB_IF_NECESSARY);
+ }
+
+ down(&heartbeat_lock);
+
+ atomic_set(&heartbeat_tofree, 2);
+
+ /* Don't reset the timer if we have the timer turned off, that
+ re-enables the watchdog. */
+ if (ipmi_watchdog_state == WDOG_TIMEOUT_NONE) {
+ up(&heartbeat_lock);
+ return 0;
+ }
+
+ addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ addr.channel = IPMI_BMC_CHANNEL;
+ addr.lun = 0;
+
+ msg.netfn = 0x06;
+ msg.cmd = IPMI_WDOG_RESET_TIMER;
+ msg.data = NULL;
+ msg.data_len = 0;
+ rv = ipmi_request_supply_msgs(watchdog_user,
+ (struct ipmi_addr *) &addr,
+ 0,
+ &msg,
+ NULL,
+ &heartbeat_smi_msg,
+ &heartbeat_recv_msg,
+ 1);
+ if (rv) {
+ up(&heartbeat_lock);
+ printk(KERN_WARNING PFX "heartbeat failure: %d\n",
+ rv);
+ return rv;
+ }
+
+ /* Wait for the heartbeat to be sent. */
+ down(&heartbeat_wait_lock);
+
+ if (heartbeat_recv_msg.msg.data[0] != 0) {
+ /* Got an error in the heartbeat response. It was already
+ reported in ipmi_wdog_msg_handler, but we should return
+ an error here. */
+ rv = -EINVAL;
+ }
+
+ up(&heartbeat_lock);
+
+ return rv;
+}
+
+static void panic_halt_ipmi_heartbeat(void)
+{
+ struct kernel_ipmi_msg msg;
+ struct ipmi_system_interface_addr addr;
+
+
+ /* Don't reset the timer if we have the timer turned off, that
+ re-enables the watchdog. */
+ if (ipmi_watchdog_state == WDOG_TIMEOUT_NONE)
+ return;
+
+ addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ addr.channel = IPMI_BMC_CHANNEL;
+ addr.lun = 0;
+
+ msg.netfn = 0x06;
+ msg.cmd = IPMI_WDOG_RESET_TIMER;
+ msg.data = NULL;
+ msg.data_len = 0;
+ ipmi_request_supply_msgs(watchdog_user,
+ (struct ipmi_addr *) &addr,
+ 0,
+ &msg,
+ NULL,
+ &panic_halt_heartbeat_smi_msg,
+ &panic_halt_heartbeat_recv_msg,
+ 1);
+}
+
+static struct watchdog_info ident=
+{
+ .options = 0, /* WDIOF_SETTIMEOUT, */
+ .firmware_version = 1,
+ .identity = "IPMI"
+};
+
+static int ipmi_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int i;
+ int val;
+
+ switch(cmd) {
+ case WDIOC_GETSUPPORT:
+ i = copy_to_user(argp, &ident, sizeof(ident));
+ return i ? -EFAULT : 0;
+
+ case WDIOC_SETTIMEOUT:
+ i = copy_from_user(&val, argp, sizeof(int));
+ if (i)
+ return -EFAULT;
+ timeout = val;
+ return ipmi_set_timeout(IPMI_SET_TIMEOUT_HB_IF_NECESSARY);
+
+ case WDIOC_GETTIMEOUT:
+ i = copy_to_user(argp, &timeout, sizeof(timeout));
+ if (i)
+ return -EFAULT;
+ return 0;
+
+ case WDIOC_SET_PRETIMEOUT:
+ i = copy_from_user(&val, argp, sizeof(int));
+ if (i)
+ return -EFAULT;
+ pretimeout = val;
+ return ipmi_set_timeout(IPMI_SET_TIMEOUT_HB_IF_NECESSARY);
+
+ case WDIOC_GET_PRETIMEOUT:
+ i = copy_to_user(argp, &pretimeout, sizeof(pretimeout));
+ if (i)
+ return -EFAULT;
+ return 0;
+
+ case WDIOC_KEEPALIVE:
+ return ipmi_heartbeat();
+
+ case WDIOC_SETOPTIONS:
+ i = copy_from_user(&val, argp, sizeof(int));
+ if (i)
+ return -EFAULT;
+ if (val & WDIOS_DISABLECARD)
+ {
+ ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
+ ipmi_set_timeout(IPMI_SET_TIMEOUT_NO_HB);
+ ipmi_start_timer_on_heartbeat = 0;
+ }
+
+ if (val & WDIOS_ENABLECARD)
+ {
+ ipmi_watchdog_state = action_val;
+ ipmi_set_timeout(IPMI_SET_TIMEOUT_FORCE_HB);
+ }
+ return 0;
+
+ case WDIOC_GETSTATUS:
+ val = 0;
+ i = copy_to_user(argp, &val, sizeof(val));
+ if (i)
+ return -EFAULT;
+ return 0;
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static ssize_t ipmi_write(struct file *file,
+ const char __user *buf,
+ size_t len,
+ loff_t *ppos)
+{
+ int rv;
+
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ rv = ipmi_heartbeat();
+ if (rv)
+ return rv;
+ return 1;
+ }
+ return 0;
+}
+
+static ssize_t ipmi_read(struct file *file,
+ char __user *buf,
+ size_t count,
+ loff_t *ppos)
+{
+ int rv = 0;
+ wait_queue_t wait;
+
+ if (count <= 0)
+ return 0;
+
+ /* Reading returns if the pretimeout has gone off, and it only does
+ it once per pretimeout. */
+ spin_lock(&ipmi_read_lock);
+ if (!data_to_read) {
+ if (file->f_flags & O_NONBLOCK) {
+ rv = -EAGAIN;
+ goto out;
+ }
+
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&read_q, &wait);
+ while (!data_to_read) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock(&ipmi_read_lock);
+ schedule();
+ spin_lock(&ipmi_read_lock);
+ }
+ remove_wait_queue(&read_q, &wait);
+
+ if (signal_pending(current)) {
+ rv = -ERESTARTSYS;
+ goto out;
+ }
+ }
+ data_to_read = 0;
+
+ out:
+ spin_unlock(&ipmi_read_lock);
+
+ if (rv == 0) {
+ if (copy_to_user(buf, &data_to_read, 1))
+ rv = -EFAULT;
+ else
+ rv = 1;
+ }
+
+ return rv;
+}
+
+static int ipmi_open(struct inode *ino, struct file *filep)
+{
+ switch (iminor(ino))
+ {
+ case WATCHDOG_MINOR:
+ if(test_and_set_bit(0, &ipmi_wdog_open))
+ return -EBUSY;
+
+ /* Don't start the timer now, let it start on the
+ first heartbeat. */
+ ipmi_start_timer_on_heartbeat = 1;
+ return nonseekable_open(ino, filep);
+
+ default:
+ return (-ENODEV);
+ }
+}
+
+static unsigned int ipmi_poll(struct file *file, poll_table *wait)
+{
+ unsigned int mask = 0;
+
+ poll_wait(file, &read_q, wait);
+
+ spin_lock(&ipmi_read_lock);
+ if (data_to_read)
+ mask |= (POLLIN | POLLRDNORM);
+ spin_unlock(&ipmi_read_lock);
+
+ return mask;
+}
+
+static int ipmi_fasync(int fd, struct file *file, int on)
+{
+ int result;
+
+ result = fasync_helper(fd, file, on, &fasync_q);
+
+ return (result);
+}
+
+static int ipmi_close(struct inode *ino, struct file *filep)
+{
+ if (iminor(ino)==WATCHDOG_MINOR)
+ {
+ if (expect_close == 42) {
+ ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
+ ipmi_set_timeout(IPMI_SET_TIMEOUT_NO_HB);
+ clear_bit(0, &ipmi_wdog_open);
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+ ipmi_heartbeat();
+ }
+ }
+
+ ipmi_fasync (-1, filep, 0);
+ expect_close = 0;
+
+ return 0;
+}
+
+static struct file_operations ipmi_wdog_fops = {
+ .owner = THIS_MODULE,
+ .read = ipmi_read,
+ .poll = ipmi_poll,
+ .write = ipmi_write,
+ .ioctl = ipmi_ioctl,
+ .open = ipmi_open,
+ .release = ipmi_close,
+ .fasync = ipmi_fasync,
+};
+
+static struct miscdevice ipmi_wdog_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &ipmi_wdog_fops
+};
+
+static DECLARE_RWSEM(register_sem);
+
+static void ipmi_wdog_msg_handler(struct ipmi_recv_msg *msg,
+ void *handler_data)
+{
+ if (msg->msg.data[0] != 0) {
+ printk(KERN_ERR PFX "response: Error %x on cmd %x\n",
+ msg->msg.data[0],
+ msg->msg.cmd);
+ }
+
+ ipmi_free_recv_msg(msg);
+}
+
+static void ipmi_wdog_pretimeout_handler(void *handler_data)
+{
+ if (preaction_val != WDOG_PRETIMEOUT_NONE) {
+ if (preop_val == WDOG_PREOP_PANIC)
+ panic("Watchdog pre-timeout");
+ else if (preop_val == WDOG_PREOP_GIVE_DATA) {
+ spin_lock(&ipmi_read_lock);
+ data_to_read = 1;
+ wake_up_interruptible(&read_q);
+ kill_fasync(&fasync_q, SIGIO, POLL_IN);
+
+ spin_unlock(&ipmi_read_lock);
+ }
+ }
+
+ /* On some machines, the heartbeat will give
+ an error and not work unless we re-enable
+ the timer. So do so. */
+ pretimeout_since_last_heartbeat = 1;
+}
+
+static struct ipmi_user_hndl ipmi_hndlrs =
+{
+ .ipmi_recv_hndl = ipmi_wdog_msg_handler,
+ .ipmi_watchdog_pretimeout = ipmi_wdog_pretimeout_handler
+};
+
+static void ipmi_register_watchdog(int ipmi_intf)
+{
+ int rv = -EBUSY;
+
+ down_write(&register_sem);
+ if (watchdog_user)
+ goto out;
+
+ rv = ipmi_create_user(ipmi_intf, &ipmi_hndlrs, NULL, &watchdog_user);
+ if (rv < 0) {
+ printk(KERN_CRIT PFX "Unable to register with ipmi\n");
+ goto out;
+ }
+
+ ipmi_get_version(watchdog_user,
+ &ipmi_version_major,
+ &ipmi_version_minor);
+
+ rv = misc_register(&ipmi_wdog_miscdev);
+ if (rv < 0) {
+ ipmi_destroy_user(watchdog_user);
+ watchdog_user = NULL;
+ printk(KERN_CRIT PFX "Unable to register misc device\n");
+ }
+
+ out:
+ up_write(&register_sem);
+
+ if ((start_now) && (rv == 0)) {
+ /* Run from startup, so start the timer now. */
+ start_now = 0; /* Disable this function after first startup. */
+ ipmi_watchdog_state = action_val;
+ ipmi_set_timeout(IPMI_SET_TIMEOUT_FORCE_HB);
+ printk(KERN_INFO PFX "Starting now!\n");
+ }
+}
+
+#ifdef HAVE_NMI_HANDLER
+static int
+ipmi_nmi(void *dev_id, struct pt_regs *regs, int cpu, int handled)
+{
+ /* If no one else handled the NMI, we assume it was the IPMI
+ watchdog. */
+ if ((!handled) && (preop_val == WDOG_PREOP_PANIC))
+ panic(PFX "pre-timeout");
+
+ /* On some machines, the heartbeat will give
+ an error and not work unless we re-enable
+ the timer. So do so. */
+ pretimeout_since_last_heartbeat = 1;
+
+ return NOTIFY_DONE;
+}
+
+static struct nmi_handler ipmi_nmi_handler =
+{
+ .link = LIST_HEAD_INIT(ipmi_nmi_handler.link),
+ .dev_name = "ipmi_watchdog",
+ .dev_id = NULL,
+ .handler = ipmi_nmi,
+ .priority = 0, /* Call us last. */
+};
+#endif
+
+static int wdog_reboot_handler(struct notifier_block *this,
+ unsigned long code,
+ void *unused)
+{
+ static int reboot_event_handled = 0;
+
+ if ((watchdog_user) && (!reboot_event_handled)) {
+ /* Make sure we only do this once. */
+ reboot_event_handled = 1;
+
+ if (code == SYS_DOWN || code == SYS_HALT) {
+ /* Disable the WDT if we are shutting down. */
+ ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
+ panic_halt_ipmi_set_timeout();
+ } else {
+ /* Set a long timer to let the reboot happens, but
+ reboot if it hangs. */
+ timeout = 120;
+ pretimeout = 0;
+ ipmi_watchdog_state = WDOG_TIMEOUT_RESET;
+ panic_halt_ipmi_set_timeout();
+ }
+ }
+ return NOTIFY_OK;
+}
+
+static struct notifier_block wdog_reboot_notifier = {
+ .notifier_call = wdog_reboot_handler,
+ .next = NULL,
+ .priority = 0
+};
+
+static int wdog_panic_handler(struct notifier_block *this,
+ unsigned long event,
+ void *unused)
+{
+ static int panic_event_handled = 0;
+
+ /* On a panic, if we have a panic timeout, make sure that the thing
+ reboots, even if it hangs during that panic. */
+ if (watchdog_user && !panic_event_handled) {
+ /* Make sure the panic doesn't hang, and make sure we
+ do this only once. */
+ panic_event_handled = 1;
+
+ timeout = 255;
+ pretimeout = 0;
+ ipmi_watchdog_state = WDOG_TIMEOUT_RESET;
+ panic_halt_ipmi_set_timeout();
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block wdog_panic_notifier = {
+ .notifier_call = wdog_panic_handler,
+ .next = NULL,
+ .priority = 150 /* priority: INT_MAX >= x >= 0 */
+};
+
+
+static void ipmi_new_smi(int if_num)
+{
+ ipmi_register_watchdog(if_num);
+}
+
+static void ipmi_smi_gone(int if_num)
+{
+ /* This can never be called, because once the watchdog is
+ registered, the interface can't go away until the watchdog
+ is unregistered. */
+}
+
+static struct ipmi_smi_watcher smi_watcher =
+{
+ .owner = THIS_MODULE,
+ .new_smi = ipmi_new_smi,
+ .smi_gone = ipmi_smi_gone
+};
+
+static int __init ipmi_wdog_init(void)
+{
+ int rv;
+
+ printk(KERN_INFO PFX "driver version "
+ IPMI_WATCHDOG_VERSION "\n");
+
+ if (strcmp(action, "reset") == 0) {
+ action_val = WDOG_TIMEOUT_RESET;
+ } else if (strcmp(action, "none") == 0) {
+ action_val = WDOG_TIMEOUT_NONE;
+ } else if (strcmp(action, "power_cycle") == 0) {
+ action_val = WDOG_TIMEOUT_POWER_CYCLE;
+ } else if (strcmp(action, "power_off") == 0) {
+ action_val = WDOG_TIMEOUT_POWER_DOWN;
+ } else {
+ action_val = WDOG_TIMEOUT_RESET;
+ printk(KERN_INFO PFX "Unknown action '%s', defaulting to"
+ " reset\n", action);
+ }
+
+ if (strcmp(preaction, "pre_none") == 0) {
+ preaction_val = WDOG_PRETIMEOUT_NONE;
+ } else if (strcmp(preaction, "pre_smi") == 0) {
+ preaction_val = WDOG_PRETIMEOUT_SMI;
+#ifdef HAVE_NMI_HANDLER
+ } else if (strcmp(preaction, "pre_nmi") == 0) {
+ preaction_val = WDOG_PRETIMEOUT_NMI;
+#endif
+ } else if (strcmp(preaction, "pre_int") == 0) {
+ preaction_val = WDOG_PRETIMEOUT_MSG_INT;
+ } else {
+ preaction_val = WDOG_PRETIMEOUT_NONE;
+ printk(KERN_INFO PFX "Unknown preaction '%s', defaulting to"
+ " none\n", preaction);
+ }
+
+ if (strcmp(preop, "preop_none") == 0) {
+ preop_val = WDOG_PREOP_NONE;
+ } else if (strcmp(preop, "preop_panic") == 0) {
+ preop_val = WDOG_PREOP_PANIC;
+ } else if (strcmp(preop, "preop_give_data") == 0) {
+ preop_val = WDOG_PREOP_GIVE_DATA;
+ } else {
+ preop_val = WDOG_PREOP_NONE;
+ printk(KERN_INFO PFX "Unknown preop '%s', defaulting to"
+ " none\n", preop);
+ }
+
+#ifdef HAVE_NMI_HANDLER
+ if (preaction_val == WDOG_PRETIMEOUT_NMI) {
+ if (preop_val == WDOG_PREOP_GIVE_DATA) {
+ printk(KERN_WARNING PFX "Pretimeout op is to give data"
+ " but NMI pretimeout is enabled, setting"
+ " pretimeout op to none\n");
+ preop_val = WDOG_PREOP_NONE;
+ }
+#ifdef CONFIG_X86_LOCAL_APIC
+ if (nmi_watchdog == NMI_IO_APIC) {
+ printk(KERN_WARNING PFX "nmi_watchdog is set to IO APIC"
+ " mode (value is %d), that is incompatible"
+ " with using NMI in the IPMI watchdog."
+ " Disabling IPMI nmi pretimeout.\n",
+ nmi_watchdog);
+ preaction_val = WDOG_PRETIMEOUT_NONE;
+ } else {
+#endif
+ rv = request_nmi(&ipmi_nmi_handler);
+ if (rv) {
+ printk(KERN_WARNING PFX "Can't register nmi handler\n");
+ return rv;
+ }
+#ifdef CONFIG_X86_LOCAL_APIC
+ }
+#endif
+ }
+#endif
+
+ rv = ipmi_smi_watcher_register(&smi_watcher);
+ if (rv) {
+#ifdef HAVE_NMI_HANDLER
+ if (preaction_val == WDOG_PRETIMEOUT_NMI)
+ release_nmi(&ipmi_nmi_handler);
+#endif
+ printk(KERN_WARNING PFX "can't register smi watcher\n");
+ return rv;
+ }
+
+ register_reboot_notifier(&wdog_reboot_notifier);
+ notifier_chain_register(&panic_notifier_list, &wdog_panic_notifier);
+
+ return 0;
+}
+
+static __exit void ipmi_unregister_watchdog(void)
+{
+ int rv;
+
+ down_write(&register_sem);
+
+#ifdef HAVE_NMI_HANDLER
+ if (preaction_val == WDOG_PRETIMEOUT_NMI)
+ release_nmi(&ipmi_nmi_handler);
+#endif
+
+ notifier_chain_unregister(&panic_notifier_list, &wdog_panic_notifier);
+ unregister_reboot_notifier(&wdog_reboot_notifier);
+
+ if (! watchdog_user)
+ goto out;
+
+ /* Make sure no one can call us any more. */
+ misc_deregister(&ipmi_wdog_miscdev);
+
+ /* Wait to make sure the message makes it out. The lower layer has
+ pointers to our buffers, we want to make sure they are done before
+ we release our memory. */
+ while (atomic_read(&set_timeout_tofree)) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ }
+
+ /* Disconnect from IPMI. */
+ rv = ipmi_destroy_user(watchdog_user);
+ if (rv) {
+ printk(KERN_WARNING PFX "error unlinking from IPMI: %d\n",
+ rv);
+ }
+ watchdog_user = NULL;
+
+ out:
+ up_write(&register_sem);
+}
+
+static void __exit ipmi_wdog_exit(void)
+{
+ ipmi_smi_watcher_unregister(&smi_watcher);
+ ipmi_unregister_watchdog();
+}
+module_exit(ipmi_wdog_exit);
+module_init(ipmi_wdog_init);
+MODULE_LICENSE("GPL");
OpenPOWER on IntegriCloud