mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
76
drivers/char/ipmi/Kconfig
Normal file
76
drivers/char/ipmi/Kconfig
Normal file
|
@ -0,0 +1,76 @@
|
|||
#
|
||||
# IPMI device configuration
|
||||
#
|
||||
|
||||
menuconfig IPMI_HANDLER
|
||||
tristate 'IPMI top-level message handler'
|
||||
depends on HAS_IOMEM
|
||||
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.
|
||||
|
||||
if IPMI_HANDLER
|
||||
|
||||
config IPMI_PANIC_EVENT
|
||||
bool 'Generate a panic event to all BMCs on a panic'
|
||||
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'
|
||||
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'
|
||||
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_SI_PROBE_DEFAULTS
|
||||
bool 'Probe for all possible IPMI system interfaces by default'
|
||||
default n
|
||||
depends on IPMI_SI
|
||||
help
|
||||
Modern systems will usually expose IPMI interfaces via a discoverable
|
||||
firmware mechanism such as ACPI or DMI. Older systems do not, and so
|
||||
the driver is forced to probe hardware manually. This may cause boot
|
||||
delays. Say "n" here to disable this manual probing. IPMI will then
|
||||
only be available on older systems if the "ipmi_si_intf.trydefaults=1"
|
||||
boot argument is passed.
|
||||
|
||||
config IPMI_WATCHDOG
|
||||
tristate 'IPMI Watchdog Timer'
|
||||
help
|
||||
This enables the IPMI watchdog timer.
|
||||
|
||||
config IPMI_POWEROFF
|
||||
tristate 'IPMI Poweroff'
|
||||
help
|
||||
This enables a function to power off the system with IPMI if
|
||||
the IPMI management controller is capable of this.
|
||||
|
||||
endif # IPMI_HANDLER
|
11
drivers/char/ipmi/Makefile
Normal file
11
drivers/char/ipmi/Makefile
Normal file
|
@ -0,0 +1,11 @@
|
|||
#
|
||||
# Makefile for the ipmi drivers.
|
||||
#
|
||||
|
||||
ipmi_si-y := 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
|
705
drivers/char/ipmi/ipmi_bt_sm.c
Normal file
705
drivers/char/ipmi/ipmi_bt_sm.c
Normal file
|
@ -0,0 +1,705 @@
|
|||
/*
|
||||
* 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/projects/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/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/ipmi_msgdefs.h> /* for completion codes */
|
||||
#include "ipmi_si_sm.h"
|
||||
|
||||
#define BT_DEBUG_OFF 0 /* Used in production */
|
||||
#define BT_DEBUG_ENABLE 1 /* Generic messages */
|
||||
#define BT_DEBUG_MSG 2 /* Prints all request/response buffers */
|
||||
#define BT_DEBUG_STATES 4 /* Verbose look at state changes */
|
||||
/*
|
||||
* BT_DEBUG_OFF must be zero to correspond to the default uninitialized
|
||||
* value
|
||||
*/
|
||||
|
||||
static int bt_debug; /* 0 == BT_DEBUG_OFF */
|
||||
|
||||
module_param(bt_debug, int, 0644);
|
||||
MODULE_PARM_DESC(bt_debug, "debug bitmask, 1=enable, 2=messages, 4=states");
|
||||
|
||||
/*
|
||||
* 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 5 /* seconds */
|
||||
#define BT_NORMAL_RETRY_LIMIT 2
|
||||
#define BT_RESET_DELAY 6 /* seconds after warm reset */
|
||||
|
||||
/*
|
||||
* States are written in chronological order and usually cover
|
||||
* multiple rows of the state table discussion in the IPMI spec.
|
||||
*/
|
||||
|
||||
enum bt_states {
|
||||
BT_STATE_IDLE = 0, /* Order is critical in this list */
|
||||
BT_STATE_XACTION_START,
|
||||
BT_STATE_WRITE_BYTES,
|
||||
BT_STATE_WRITE_CONSUME,
|
||||
BT_STATE_READ_WAIT,
|
||||
BT_STATE_CLEAR_B2H,
|
||||
BT_STATE_READ_BYTES,
|
||||
BT_STATE_RESET1, /* These must come last */
|
||||
BT_STATE_RESET2,
|
||||
BT_STATE_RESET3,
|
||||
BT_STATE_RESTART,
|
||||
BT_STATE_PRINTME,
|
||||
BT_STATE_CAPABILITIES_BEGIN,
|
||||
BT_STATE_CAPABILITIES_END,
|
||||
BT_STATE_LONG_BUSY /* BT doesn't get hosed :-) */
|
||||
};
|
||||
|
||||
/*
|
||||
* Macros seen at the end of state "case" blocks. They help with legibility
|
||||
* and debugging.
|
||||
*/
|
||||
|
||||
#define BT_STATE_CHANGE(X, Y) { bt->state = X; return Y; }
|
||||
|
||||
#define BT_SI_SM_RETURN(Y) { last_printed = BT_STATE_PRINTME; return Y; }
|
||||
|
||||
struct si_sm_data {
|
||||
enum bt_states state;
|
||||
unsigned char seq; /* BT sequence number */
|
||||
struct si_sm_io *io;
|
||||
unsigned char write_data[IPMI_MAX_MSG_LENGTH + 2]; /* +2 for memcpy */
|
||||
int write_count;
|
||||
unsigned char read_data[IPMI_MAX_MSG_LENGTH + 2]; /* +2 for memcpy */
|
||||
int read_count;
|
||||
int truncated;
|
||||
long timeout; /* microseconds countdown */
|
||||
int error_retries; /* end of "common" fields */
|
||||
int nonzero_status; /* hung BMCs stay all 0 */
|
||||
enum bt_states complete; /* to divert the state machine */
|
||||
int BT_CAP_outreqs;
|
||||
long BT_CAP_req2rsp;
|
||||
int BT_CAP_retries; /* Recommended retries */
|
||||
};
|
||||
|
||||
#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_CONSUME: return("WR_CONSUME");
|
||||
case BT_STATE_READ_WAIT: return("RD_WAIT");
|
||||
case BT_STATE_CLEAR_B2H: return("CLEAR_B2H");
|
||||
case BT_STATE_READ_BYTES: return("RD_BYTES");
|
||||
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_LONG_BUSY: return("LONG_BUSY");
|
||||
case BT_STATE_CAPABILITIES_BEGIN: return("CAP_BEGIN");
|
||||
case BT_STATE_CAPABILITIES_END: return("CAP_END");
|
||||
}
|
||||
return("BAD STATE");
|
||||
}
|
||||
#define STATE2TXT state2txt(bt->state)
|
||||
|
||||
static char *status2txt(unsigned char status)
|
||||
{
|
||||
/*
|
||||
* This cannot be called by two threads at the same time and
|
||||
* the buffer is always consumed immediately, so the static is
|
||||
* safe to use.
|
||||
*/
|
||||
static char buf[40];
|
||||
|
||||
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 status2txt(status)
|
||||
|
||||
/* called externally at insmod time, and internally on cleanup */
|
||||
|
||||
static unsigned int bt_init_data(struct si_sm_data *bt, struct si_sm_io *io)
|
||||
{
|
||||
memset(bt, 0, sizeof(struct si_sm_data));
|
||||
if (bt->io != io) {
|
||||
/* external: one-time only things */
|
||||
bt->io = io;
|
||||
bt->seq = 0;
|
||||
}
|
||||
bt->state = BT_STATE_IDLE; /* start here */
|
||||
bt->complete = BT_STATE_IDLE; /* end here */
|
||||
bt->BT_CAP_req2rsp = BT_NORMAL_TIMEOUT * USEC_PER_SEC;
|
||||
bt->BT_CAP_retries = BT_NORMAL_RETRY_LIMIT;
|
||||
/* BT_CAP_outreqs == zero is a flag to read BT Capabilities */
|
||||
return 3; /* We claim 3 bytes of space; ought to check SPMI table */
|
||||
}
|
||||
|
||||
/* Jam a completion code (probably an error) into a response */
|
||||
|
||||
static void force_result(struct si_sm_data *bt, unsigned char completion_code)
|
||||
{
|
||||
bt->read_data[0] = 4; /* # following bytes */
|
||||
bt->read_data[1] = bt->write_data[1] | 4; /* Odd NetFn/LUN */
|
||||
bt->read_data[2] = bt->write_data[2]; /* seq (ignored) */
|
||||
bt->read_data[3] = bt->write_data[3]; /* Command */
|
||||
bt->read_data[4] = completion_code;
|
||||
bt->read_count = 5;
|
||||
}
|
||||
|
||||
/* The upper state machine starts here */
|
||||
|
||||
static int bt_start_transaction(struct si_sm_data *bt,
|
||||
unsigned char *data,
|
||||
unsigned int size)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
if (size < 2)
|
||||
return IPMI_REQ_LEN_INVALID_ERR;
|
||||
if (size > IPMI_MAX_MSG_LENGTH)
|
||||
return IPMI_REQ_LEN_EXCEEDED_ERR;
|
||||
|
||||
if (bt->state == BT_STATE_LONG_BUSY)
|
||||
return IPMI_NODE_BUSY_ERR;
|
||||
|
||||
if (bt->state != BT_STATE_IDLE)
|
||||
return IPMI_NOT_IN_MY_STATE_ERR;
|
||||
|
||||
if (bt_debug & BT_DEBUG_MSG) {
|
||||
printk(KERN_WARNING "BT: +++++++++++++++++ New command\n");
|
||||
printk(KERN_WARNING "BT: NetFn/LUN CMD [%d data]:", size - 2);
|
||||
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->truncated = 0;
|
||||
bt->state = BT_STATE_XACTION_START;
|
||||
bt->timeout = bt->BT_CAP_req2rsp;
|
||||
force_result(bt, IPMI_ERR_UNSPECIFIED);
|
||||
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 */
|
||||
if (msg_len < 3 || msg_len > IPMI_MAX_MSG_LENGTH) {
|
||||
force_result(bt, IPMI_ERR_UNSPECIFIED);
|
||||
msg_len = 3;
|
||||
}
|
||||
data[0] = bt->read_data[1];
|
||||
data[1] = bt->read_data[3];
|
||||
if (length < msg_len || bt->truncated) {
|
||||
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: result %d bytes:", msg_len);
|
||||
for (i = 0; i < msg_len; i++)
|
||||
printk(" %02x", data[i]);
|
||||
printk("\n");
|
||||
}
|
||||
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_debug)
|
||||
printk(KERN_WARNING "IPMI BT: flag reset %s\n",
|
||||
status2txt(BT_STATUS));
|
||||
if (BT_STATUS & BT_H_BUSY)
|
||||
BT_CONTROL(BT_H_BUSY); /* force clear */
|
||||
BT_CONTROL(BT_CLR_WR_PTR); /* always reset */
|
||||
BT_CONTROL(BT_SMS_ATN); /* always clear */
|
||||
BT_INTMASK_W(BT_BMC_HWRST);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get rid of an unwanted/stale response. This should only be needed for
|
||||
* BMCs that support multiple outstanding requests.
|
||||
*/
|
||||
|
||||
static void drain_BMC2HOST(struct si_sm_data *bt)
|
||||
{
|
||||
int i, size;
|
||||
|
||||
if (!(BT_STATUS & BT_B2H_ATN)) /* Not signalling a response */
|
||||
return;
|
||||
|
||||
BT_CONTROL(BT_H_BUSY); /* now set */
|
||||
BT_CONTROL(BT_B2H_ATN); /* always clear */
|
||||
BT_STATUS; /* pause */
|
||||
BT_CONTROL(BT_B2H_ATN); /* some BMCs are stubborn */
|
||||
BT_CONTROL(BT_CLR_RD_PTR); /* always reset */
|
||||
if (bt_debug)
|
||||
printk(KERN_WARNING "IPMI BT: stale response %s; ",
|
||||
status2txt(BT_STATUS));
|
||||
size = BMC2HOST;
|
||||
for (i = 0; i < size ; i++)
|
||||
BMC2HOST;
|
||||
BT_CONTROL(BT_H_BUSY); /* now clear */
|
||||
if (bt_debug)
|
||||
printk("drained %d bytes\n", size + 1);
|
||||
}
|
||||
|
||||
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 int i;
|
||||
|
||||
/*
|
||||
* length is "framing info", minimum = 4: NetFn, Seq, Cmd, cCode.
|
||||
* Keep layout of first four bytes aligned with write_data[]
|
||||
*/
|
||||
|
||||
bt->read_data[0] = BMC2HOST;
|
||||
bt->read_count = bt->read_data[0];
|
||||
|
||||
if (bt->read_count < 4 || bt->read_count >= IPMI_MAX_MSG_LENGTH) {
|
||||
if (bt_debug & BT_DEBUG_MSG)
|
||||
printk(KERN_WARNING "BT: bad raw rsp len=%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 internally for length byte */
|
||||
|
||||
if (bt_debug & BT_DEBUG_MSG) {
|
||||
int max = bt->read_count;
|
||||
|
||||
printk(KERN_WARNING "BT: got %d bytes seq=0x%02X",
|
||||
max, bt->read_data[2]);
|
||||
if (max > 16)
|
||||
max = 16;
|
||||
for (i = 0; i < max; i++)
|
||||
printk(KERN_CONT " %02x", bt->read_data[i]);
|
||||
printk(KERN_CONT "%s\n", bt->read_count == max ? "" : " ...");
|
||||
}
|
||||
|
||||
/* per the spec, the (NetFn[1], Seq[2], Cmd[3]) tuples must match */
|
||||
if ((bt->read_data[3] == bt->write_data[3]) &&
|
||||
(bt->read_data[2] == bt->write_data[2]) &&
|
||||
((bt->read_data[1] & 0xF8) == (bt->write_data[1] & 0xF8)))
|
||||
return 1;
|
||||
|
||||
if (bt_debug & BT_DEBUG_MSG)
|
||||
printk(KERN_WARNING "IPMI BT: bad packet: "
|
||||
"want 0x(%02X, %02X, %02X) got (%02X, %02X, %02X)\n",
|
||||
bt->write_data[1] | 0x04, bt->write_data[2], bt->write_data[3],
|
||||
bt->read_data[1], bt->read_data[2], bt->read_data[3]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Restart if retries are left, or return an error completion code */
|
||||
|
||||
static enum si_sm_result error_recovery(struct si_sm_data *bt,
|
||||
unsigned char status,
|
||||
unsigned char cCode)
|
||||
{
|
||||
char *reason;
|
||||
|
||||
bt->timeout = bt->BT_CAP_req2rsp;
|
||||
|
||||
switch (cCode) {
|
||||
case IPMI_TIMEOUT_ERR:
|
||||
reason = "timeout";
|
||||
break;
|
||||
default:
|
||||
reason = "internal error";
|
||||
break;
|
||||
}
|
||||
|
||||
printk(KERN_WARNING "IPMI BT: %s in %s %s ", /* open-ended line */
|
||||
reason, STATE2TXT, STATUS2TXT);
|
||||
|
||||
/*
|
||||
* Per the IPMI spec, retries are based on the sequence number
|
||||
* known only to this module, so manage a restart here.
|
||||
*/
|
||||
(bt->error_retries)++;
|
||||
if (bt->error_retries < bt->BT_CAP_retries) {
|
||||
printk("%d retries left\n",
|
||||
bt->BT_CAP_retries - bt->error_retries);
|
||||
bt->state = BT_STATE_RESTART;
|
||||
return SI_SM_CALL_WITHOUT_DELAY;
|
||||
}
|
||||
|
||||
printk(KERN_WARNING "failed %d retries, sending error response\n",
|
||||
bt->BT_CAP_retries);
|
||||
if (!bt->nonzero_status)
|
||||
printk(KERN_ERR "IPMI BT: stuck, try power cycle\n");
|
||||
|
||||
/* this is most likely during insmod */
|
||||
else if (bt->seq <= (unsigned char)(bt->BT_CAP_retries & 0xFF)) {
|
||||
printk(KERN_WARNING "IPMI: BT reset (takes 5 secs)\n");
|
||||
bt->state = BT_STATE_RESET1;
|
||||
return SI_SM_CALL_WITHOUT_DELAY;
|
||||
}
|
||||
|
||||
/*
|
||||
* Concoct a useful error message, set up the next state, and
|
||||
* be done with this sequence.
|
||||
*/
|
||||
|
||||
bt->state = BT_STATE_IDLE;
|
||||
switch (cCode) {
|
||||
case IPMI_TIMEOUT_ERR:
|
||||
if (status & BT_B_BUSY) {
|
||||
cCode = IPMI_NODE_BUSY_ERR;
|
||||
bt->state = BT_STATE_LONG_BUSY;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
force_result(bt, cCode);
|
||||
return SI_SM_TRANSACTION_COMPLETE;
|
||||
}
|
||||
|
||||
/* Check status and (usually) take action and change this state machine. */
|
||||
|
||||
static enum si_sm_result bt_event(struct si_sm_data *bt, long time)
|
||||
{
|
||||
unsigned char status, BT_CAP[8];
|
||||
static enum bt_states last_printed = BT_STATE_PRINTME;
|
||||
int i;
|
||||
|
||||
status = BT_STATUS;
|
||||
bt->nonzero_status |= status;
|
||||
if ((bt_debug & BT_DEBUG_STATES) && (bt->state != last_printed)) {
|
||||
printk(KERN_WARNING "BT: %s %s TO=%ld - %ld \n",
|
||||
STATE2TXT,
|
||||
STATUS2TXT,
|
||||
bt->timeout,
|
||||
time);
|
||||
last_printed = bt->state;
|
||||
}
|
||||
|
||||
/*
|
||||
* Commands that time out may still (eventually) provide a response.
|
||||
* This stale response will get in the way of a new response so remove
|
||||
* it if possible (hopefully during IDLE). Even if it comes up later
|
||||
* it will be rejected by its (now-forgotten) seq number.
|
||||
*/
|
||||
|
||||
if ((bt->state < BT_STATE_WRITE_BYTES) && (status & BT_B2H_ATN)) {
|
||||
drain_BMC2HOST(bt);
|
||||
BT_SI_SM_RETURN(SI_SM_CALL_WITH_DELAY);
|
||||
}
|
||||
|
||||
if ((bt->state != BT_STATE_IDLE) &&
|
||||
(bt->state < BT_STATE_PRINTME)) {
|
||||
/* check timeout */
|
||||
bt->timeout -= time;
|
||||
if ((bt->timeout < 0) && (bt->state < BT_STATE_RESET1))
|
||||
return error_recovery(bt,
|
||||
status,
|
||||
IPMI_TIMEOUT_ERR);
|
||||
}
|
||||
|
||||
switch (bt->state) {
|
||||
|
||||
/*
|
||||
* Idle state first checks for asynchronous messages from another
|
||||
* channel, then does some opportunistic housekeeping.
|
||||
*/
|
||||
|
||||
case BT_STATE_IDLE:
|
||||
if (status & BT_SMS_ATN) {
|
||||
BT_CONTROL(BT_SMS_ATN); /* clear it */
|
||||
return SI_SM_ATTN;
|
||||
}
|
||||
|
||||
if (status & BT_H_BUSY) /* clear a leftover H_BUSY */
|
||||
BT_CONTROL(BT_H_BUSY);
|
||||
|
||||
/* Read BT capabilities if it hasn't been done yet */
|
||||
if (!bt->BT_CAP_outreqs)
|
||||
BT_STATE_CHANGE(BT_STATE_CAPABILITIES_BEGIN,
|
||||
SI_SM_CALL_WITHOUT_DELAY);
|
||||
bt->timeout = bt->BT_CAP_req2rsp;
|
||||
BT_SI_SM_RETURN(SI_SM_IDLE);
|
||||
|
||||
case BT_STATE_XACTION_START:
|
||||
if (status & (BT_B_BUSY | BT_H2B_ATN))
|
||||
BT_SI_SM_RETURN(SI_SM_CALL_WITH_DELAY);
|
||||
if (BT_STATUS & BT_H_BUSY)
|
||||
BT_CONTROL(BT_H_BUSY); /* force clear */
|
||||
BT_STATE_CHANGE(BT_STATE_WRITE_BYTES,
|
||||
SI_SM_CALL_WITHOUT_DELAY);
|
||||
|
||||
case BT_STATE_WRITE_BYTES:
|
||||
if (status & BT_H_BUSY)
|
||||
BT_CONTROL(BT_H_BUSY); /* clear */
|
||||
BT_CONTROL(BT_CLR_WR_PTR);
|
||||
write_all_bytes(bt);
|
||||
BT_CONTROL(BT_H2B_ATN); /* can clear too fast to catch */
|
||||
BT_STATE_CHANGE(BT_STATE_WRITE_CONSUME,
|
||||
SI_SM_CALL_WITHOUT_DELAY);
|
||||
|
||||
case BT_STATE_WRITE_CONSUME:
|
||||
if (status & (BT_B_BUSY | BT_H2B_ATN))
|
||||
BT_SI_SM_RETURN(SI_SM_CALL_WITH_DELAY);
|
||||
BT_STATE_CHANGE(BT_STATE_READ_WAIT,
|
||||
SI_SM_CALL_WITHOUT_DELAY);
|
||||
|
||||
/* Spinning hard can suppress B2H_ATN and force a timeout */
|
||||
|
||||
case BT_STATE_READ_WAIT:
|
||||
if (!(status & BT_B2H_ATN))
|
||||
BT_SI_SM_RETURN(SI_SM_CALL_WITH_DELAY);
|
||||
BT_CONTROL(BT_H_BUSY); /* set */
|
||||
|
||||
/*
|
||||
* Uncached, ordered writes should just proceed serially but
|
||||
* some BMCs don't clear B2H_ATN with one hit. Fast-path a
|
||||
* workaround without too much penalty to the general case.
|
||||
*/
|
||||
|
||||
BT_CONTROL(BT_B2H_ATN); /* clear it to ACK the BMC */
|
||||
BT_STATE_CHANGE(BT_STATE_CLEAR_B2H,
|
||||
SI_SM_CALL_WITHOUT_DELAY);
|
||||
|
||||
case BT_STATE_CLEAR_B2H:
|
||||
if (status & BT_B2H_ATN) {
|
||||
/* keep hitting it */
|
||||
BT_CONTROL(BT_B2H_ATN);
|
||||
BT_SI_SM_RETURN(SI_SM_CALL_WITH_DELAY);
|
||||
}
|
||||
BT_STATE_CHANGE(BT_STATE_READ_BYTES,
|
||||
SI_SM_CALL_WITHOUT_DELAY);
|
||||
|
||||
case BT_STATE_READ_BYTES:
|
||||
if (!(status & BT_H_BUSY))
|
||||
/* check in case of retry */
|
||||
BT_CONTROL(BT_H_BUSY);
|
||||
BT_CONTROL(BT_CLR_RD_PTR); /* start of BMC2HOST buffer */
|
||||
i = read_all_bytes(bt); /* true == packet seq match */
|
||||
BT_CONTROL(BT_H_BUSY); /* NOW clear */
|
||||
if (!i) /* Not my message */
|
||||
BT_STATE_CHANGE(BT_STATE_READ_WAIT,
|
||||
SI_SM_CALL_WITHOUT_DELAY);
|
||||
bt->state = bt->complete;
|
||||
return bt->state == BT_STATE_IDLE ? /* where to next? */
|
||||
SI_SM_TRANSACTION_COMPLETE : /* normal */
|
||||
SI_SM_CALL_WITHOUT_DELAY; /* Startup magic */
|
||||
|
||||
case BT_STATE_LONG_BUSY: /* For example: after FW update */
|
||||
if (!(status & BT_B_BUSY)) {
|
||||
reset_flags(bt); /* next state is now IDLE */
|
||||
bt_init_data(bt, bt->io);
|
||||
}
|
||||
return SI_SM_CALL_WITH_DELAY; /* No repeat printing */
|
||||
|
||||
case BT_STATE_RESET1:
|
||||
reset_flags(bt);
|
||||
drain_BMC2HOST(bt);
|
||||
BT_STATE_CHANGE(BT_STATE_RESET2,
|
||||
SI_SM_CALL_WITH_DELAY);
|
||||
|
||||
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->timeout = BT_RESET_DELAY * USEC_PER_SEC;
|
||||
BT_STATE_CHANGE(BT_STATE_RESET3,
|
||||
SI_SM_CALL_WITH_DELAY);
|
||||
|
||||
case BT_STATE_RESET3: /* Hold off everything for a bit */
|
||||
if (bt->timeout > 0)
|
||||
return SI_SM_CALL_WITH_DELAY;
|
||||
drain_BMC2HOST(bt);
|
||||
BT_STATE_CHANGE(BT_STATE_RESTART,
|
||||
SI_SM_CALL_WITH_DELAY);
|
||||
|
||||
case BT_STATE_RESTART: /* don't reset retries or seq! */
|
||||
bt->read_count = 0;
|
||||
bt->nonzero_status = 0;
|
||||
bt->timeout = bt->BT_CAP_req2rsp;
|
||||
BT_STATE_CHANGE(BT_STATE_XACTION_START,
|
||||
SI_SM_CALL_WITH_DELAY);
|
||||
|
||||
/*
|
||||
* Get BT Capabilities, using timing of upper level state machine.
|
||||
* Set outreqs to prevent infinite loop on timeout.
|
||||
*/
|
||||
case BT_STATE_CAPABILITIES_BEGIN:
|
||||
bt->BT_CAP_outreqs = 1;
|
||||
{
|
||||
unsigned char GetBT_CAP[] = { 0x18, 0x36 };
|
||||
bt->state = BT_STATE_IDLE;
|
||||
bt_start_transaction(bt, GetBT_CAP, sizeof(GetBT_CAP));
|
||||
}
|
||||
bt->complete = BT_STATE_CAPABILITIES_END;
|
||||
BT_STATE_CHANGE(BT_STATE_XACTION_START,
|
||||
SI_SM_CALL_WITH_DELAY);
|
||||
|
||||
case BT_STATE_CAPABILITIES_END:
|
||||
i = bt_get_result(bt, BT_CAP, sizeof(BT_CAP));
|
||||
bt_init_data(bt, bt->io);
|
||||
if ((i == 8) && !BT_CAP[2]) {
|
||||
bt->BT_CAP_outreqs = BT_CAP[3];
|
||||
bt->BT_CAP_req2rsp = BT_CAP[6] * USEC_PER_SEC;
|
||||
bt->BT_CAP_retries = BT_CAP[7];
|
||||
} else
|
||||
printk(KERN_WARNING "IPMI BT: using default values\n");
|
||||
if (!bt->BT_CAP_outreqs)
|
||||
bt->BT_CAP_outreqs = 1;
|
||||
printk(KERN_WARNING "IPMI BT: req2rsp=%ld secs retries=%d\n",
|
||||
bt->BT_CAP_req2rsp / USEC_PER_SEC, bt->BT_CAP_retries);
|
||||
bt->timeout = bt->BT_CAP_req2rsp;
|
||||
return SI_SM_CALL_WITHOUT_DELAY;
|
||||
|
||||
default: /* should never occur */
|
||||
return error_recovery(bt,
|
||||
status,
|
||||
IPMI_ERR_UNSPECIFIED);
|
||||
}
|
||||
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 = {
|
||||
.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,
|
||||
};
|
988
drivers/char/ipmi/ipmi_devintf.c
Normal file
988
drivers/char/ipmi/ipmi_devintf.c
Normal file
|
@ -0,0 +1,988 @@
|
|||
/*
|
||||
* 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/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/ipmi.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/compat.h>
|
||||
|
||||
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 mutex recv_mutex;
|
||||
int default_retries;
|
||||
unsigned int default_retry_time_ms;
|
||||
};
|
||||
|
||||
static DEFINE_MUTEX(ipmi_mutex);
|
||||
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;
|
||||
|
||||
mutex_lock(&ipmi_mutex); /* could race against open() otherwise */
|
||||
result = fasync_helper(fd, file, on, &priv->fasync_queue);
|
||||
mutex_unlock(&ipmi_mutex);
|
||||
|
||||
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;
|
||||
|
||||
mutex_lock(&ipmi_mutex);
|
||||
priv->file = file;
|
||||
|
||||
rv = ipmi_create_user(if_num,
|
||||
&ipmi_hndlrs,
|
||||
priv,
|
||||
&(priv->user));
|
||||
if (rv) {
|
||||
kfree(priv);
|
||||
goto out;
|
||||
}
|
||||
|
||||
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;
|
||||
mutex_init(&priv->recv_mutex);
|
||||
|
||||
/* Use the low-level defaults. */
|
||||
priv->default_retries = -1;
|
||||
priv->default_retry_time_ms = 0;
|
||||
|
||||
out:
|
||||
mutex_unlock(&ipmi_mutex);
|
||||
return rv;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/* 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 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 mutex 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 mutex prevents that problem. */
|
||||
mutex_lock(&priv->recv_mutex);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
mutex_unlock(&priv->recv_mutex);
|
||||
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);
|
||||
mutex_unlock(&priv->recv_mutex);
|
||||
break;
|
||||
|
||||
recv_err:
|
||||
mutex_unlock(&priv->recv_mutex);
|
||||
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,
|
||||
IPMI_CHAN_ALL);
|
||||
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,
|
||||
IPMI_CHAN_ALL);
|
||||
break;
|
||||
}
|
||||
|
||||
case IPMICTL_REGISTER_FOR_CMD_CHANS:
|
||||
{
|
||||
struct ipmi_cmdspec_chans val;
|
||||
|
||||
if (copy_from_user(&val, arg, sizeof(val))) {
|
||||
rv = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
rv = ipmi_register_for_cmd(priv->user, val.netfn, val.cmd,
|
||||
val.chans);
|
||||
break;
|
||||
}
|
||||
|
||||
case IPMICTL_UNREGISTER_FOR_CMD_CHANS:
|
||||
{
|
||||
struct ipmi_cmdspec_chans val;
|
||||
|
||||
if (copy_from_user(&val, arg, sizeof(val))) {
|
||||
rv = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
rv = ipmi_unregister_for_cmd(priv->user, val.netfn, val.cmd,
|
||||
val.chans);
|
||||
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;
|
||||
}
|
||||
|
||||
/* The next four are legacy, not per-channel. */
|
||||
case IPMICTL_SET_MY_ADDRESS_CMD:
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
if (copy_from_user(&val, arg, sizeof(val))) {
|
||||
rv = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
rv = ipmi_set_my_address(priv->user, 0, val);
|
||||
break;
|
||||
}
|
||||
|
||||
case IPMICTL_GET_MY_ADDRESS_CMD:
|
||||
{
|
||||
unsigned int val;
|
||||
unsigned char rval;
|
||||
|
||||
rv = ipmi_get_my_address(priv->user, 0, &rval);
|
||||
if (rv)
|
||||
break;
|
||||
|
||||
val = rval;
|
||||
|
||||
if (copy_to_user(arg, &val, sizeof(val))) {
|
||||
rv = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case IPMICTL_SET_MY_LUN_CMD:
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
if (copy_from_user(&val, arg, sizeof(val))) {
|
||||
rv = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
rv = ipmi_set_my_LUN(priv->user, 0, val);
|
||||
break;
|
||||
}
|
||||
|
||||
case IPMICTL_GET_MY_LUN_CMD:
|
||||
{
|
||||
unsigned int val;
|
||||
unsigned char rval;
|
||||
|
||||
rv = ipmi_get_my_LUN(priv->user, 0, &rval);
|
||||
if (rv)
|
||||
break;
|
||||
|
||||
val = rval;
|
||||
|
||||
if (copy_to_user(arg, &val, sizeof(val))) {
|
||||
rv = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case IPMICTL_SET_MY_CHANNEL_ADDRESS_CMD:
|
||||
{
|
||||
struct ipmi_channel_lun_address_set val;
|
||||
|
||||
if (copy_from_user(&val, arg, sizeof(val))) {
|
||||
rv = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
return ipmi_set_my_address(priv->user, val.channel, val.value);
|
||||
break;
|
||||
}
|
||||
|
||||
case IPMICTL_GET_MY_CHANNEL_ADDRESS_CMD:
|
||||
{
|
||||
struct ipmi_channel_lun_address_set val;
|
||||
|
||||
if (copy_from_user(&val, arg, sizeof(val))) {
|
||||
rv = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
rv = ipmi_get_my_address(priv->user, val.channel, &val.value);
|
||||
if (rv)
|
||||
break;
|
||||
|
||||
if (copy_to_user(arg, &val, sizeof(val))) {
|
||||
rv = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case IPMICTL_SET_MY_CHANNEL_LUN_CMD:
|
||||
{
|
||||
struct ipmi_channel_lun_address_set val;
|
||||
|
||||
if (copy_from_user(&val, arg, sizeof(val))) {
|
||||
rv = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
rv = ipmi_set_my_LUN(priv->user, val.channel, val.value);
|
||||
break;
|
||||
}
|
||||
|
||||
case IPMICTL_GET_MY_CHANNEL_LUN_CMD:
|
||||
{
|
||||
struct ipmi_channel_lun_address_set val;
|
||||
|
||||
if (copy_from_user(&val, arg, sizeof(val))) {
|
||||
rv = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
rv = ipmi_get_my_LUN(priv->user, val.channel, &val.value);
|
||||
if (rv)
|
||||
break;
|
||||
|
||||
if (copy_to_user(arg, &val, sizeof(val))) {
|
||||
rv = -EFAULT;
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
case IPMICTL_GET_MAINTENANCE_MODE_CMD:
|
||||
{
|
||||
int mode;
|
||||
|
||||
mode = ipmi_get_maintenance_mode(priv->user);
|
||||
if (copy_to_user(arg, &mode, sizeof(mode))) {
|
||||
rv = -EFAULT;
|
||||
break;
|
||||
}
|
||||
rv = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case IPMICTL_SET_MAINTENANCE_MODE_CMD:
|
||||
{
|
||||
int mode;
|
||||
|
||||
if (copy_from_user(&mode, arg, sizeof(mode))) {
|
||||
rv = -EFAULT;
|
||||
break;
|
||||
}
|
||||
rv = ipmi_set_maintenance_mode(priv->user, mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: it doesn't make sense to take the BKL here but
|
||||
* not in compat_ipmi_ioctl. -arnd
|
||||
*/
|
||||
static long ipmi_unlocked_ioctl(struct file *file,
|
||||
unsigned int cmd,
|
||||
unsigned long data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&ipmi_mutex);
|
||||
ret = ipmi_ioctl(file, cmd, data);
|
||||
mutex_unlock(&ipmi_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
|
||||
/*
|
||||
* The following code contains code for supporting 32-bit compatible
|
||||
* ioctls on 64-bit kernels. This allows running 32-bit apps on the
|
||||
* 64-bit kernel
|
||||
*/
|
||||
#define COMPAT_IPMICTL_SEND_COMMAND \
|
||||
_IOR(IPMI_IOC_MAGIC, 13, struct compat_ipmi_req)
|
||||
#define COMPAT_IPMICTL_SEND_COMMAND_SETTIME \
|
||||
_IOR(IPMI_IOC_MAGIC, 21, struct compat_ipmi_req_settime)
|
||||
#define COMPAT_IPMICTL_RECEIVE_MSG \
|
||||
_IOWR(IPMI_IOC_MAGIC, 12, struct compat_ipmi_recv)
|
||||
#define COMPAT_IPMICTL_RECEIVE_MSG_TRUNC \
|
||||
_IOWR(IPMI_IOC_MAGIC, 11, struct compat_ipmi_recv)
|
||||
|
||||
struct compat_ipmi_msg {
|
||||
u8 netfn;
|
||||
u8 cmd;
|
||||
u16 data_len;
|
||||
compat_uptr_t data;
|
||||
};
|
||||
|
||||
struct compat_ipmi_req {
|
||||
compat_uptr_t addr;
|
||||
compat_uint_t addr_len;
|
||||
compat_long_t msgid;
|
||||
struct compat_ipmi_msg msg;
|
||||
};
|
||||
|
||||
struct compat_ipmi_recv {
|
||||
compat_int_t recv_type;
|
||||
compat_uptr_t addr;
|
||||
compat_uint_t addr_len;
|
||||
compat_long_t msgid;
|
||||
struct compat_ipmi_msg msg;
|
||||
};
|
||||
|
||||
struct compat_ipmi_req_settime {
|
||||
struct compat_ipmi_req req;
|
||||
compat_int_t retries;
|
||||
compat_uint_t retry_time_ms;
|
||||
};
|
||||
|
||||
/*
|
||||
* Define some helper functions for copying IPMI data
|
||||
*/
|
||||
static long get_compat_ipmi_msg(struct ipmi_msg *p64,
|
||||
struct compat_ipmi_msg __user *p32)
|
||||
{
|
||||
compat_uptr_t tmp;
|
||||
|
||||
if (!access_ok(VERIFY_READ, p32, sizeof(*p32)) ||
|
||||
__get_user(p64->netfn, &p32->netfn) ||
|
||||
__get_user(p64->cmd, &p32->cmd) ||
|
||||
__get_user(p64->data_len, &p32->data_len) ||
|
||||
__get_user(tmp, &p32->data))
|
||||
return -EFAULT;
|
||||
p64->data = compat_ptr(tmp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long put_compat_ipmi_msg(struct ipmi_msg *p64,
|
||||
struct compat_ipmi_msg __user *p32)
|
||||
{
|
||||
if (!access_ok(VERIFY_WRITE, p32, sizeof(*p32)) ||
|
||||
__put_user(p64->netfn, &p32->netfn) ||
|
||||
__put_user(p64->cmd, &p32->cmd) ||
|
||||
__put_user(p64->data_len, &p32->data_len))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long get_compat_ipmi_req(struct ipmi_req *p64,
|
||||
struct compat_ipmi_req __user *p32)
|
||||
{
|
||||
|
||||
compat_uptr_t tmp;
|
||||
|
||||
if (!access_ok(VERIFY_READ, p32, sizeof(*p32)) ||
|
||||
__get_user(tmp, &p32->addr) ||
|
||||
__get_user(p64->addr_len, &p32->addr_len) ||
|
||||
__get_user(p64->msgid, &p32->msgid) ||
|
||||
get_compat_ipmi_msg(&p64->msg, &p32->msg))
|
||||
return -EFAULT;
|
||||
p64->addr = compat_ptr(tmp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long get_compat_ipmi_req_settime(struct ipmi_req_settime *p64,
|
||||
struct compat_ipmi_req_settime __user *p32)
|
||||
{
|
||||
if (!access_ok(VERIFY_READ, p32, sizeof(*p32)) ||
|
||||
get_compat_ipmi_req(&p64->req, &p32->req) ||
|
||||
__get_user(p64->retries, &p32->retries) ||
|
||||
__get_user(p64->retry_time_ms, &p32->retry_time_ms))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long get_compat_ipmi_recv(struct ipmi_recv *p64,
|
||||
struct compat_ipmi_recv __user *p32)
|
||||
{
|
||||
compat_uptr_t tmp;
|
||||
|
||||
if (!access_ok(VERIFY_READ, p32, sizeof(*p32)) ||
|
||||
__get_user(p64->recv_type, &p32->recv_type) ||
|
||||
__get_user(tmp, &p32->addr) ||
|
||||
__get_user(p64->addr_len, &p32->addr_len) ||
|
||||
__get_user(p64->msgid, &p32->msgid) ||
|
||||
get_compat_ipmi_msg(&p64->msg, &p32->msg))
|
||||
return -EFAULT;
|
||||
p64->addr = compat_ptr(tmp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long put_compat_ipmi_recv(struct ipmi_recv *p64,
|
||||
struct compat_ipmi_recv __user *p32)
|
||||
{
|
||||
if (!access_ok(VERIFY_WRITE, p32, sizeof(*p32)) ||
|
||||
__put_user(p64->recv_type, &p32->recv_type) ||
|
||||
__put_user(p64->addr_len, &p32->addr_len) ||
|
||||
__put_user(p64->msgid, &p32->msgid) ||
|
||||
put_compat_ipmi_msg(&p64->msg, &p32->msg))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle compatibility ioctls
|
||||
*/
|
||||
static long compat_ipmi_ioctl(struct file *filep, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
int rc;
|
||||
struct ipmi_file_private *priv = filep->private_data;
|
||||
|
||||
switch(cmd) {
|
||||
case COMPAT_IPMICTL_SEND_COMMAND:
|
||||
{
|
||||
struct ipmi_req rp;
|
||||
|
||||
if (get_compat_ipmi_req(&rp, compat_ptr(arg)))
|
||||
return -EFAULT;
|
||||
|
||||
return handle_send_req(priv->user, &rp,
|
||||
priv->default_retries,
|
||||
priv->default_retry_time_ms);
|
||||
}
|
||||
case COMPAT_IPMICTL_SEND_COMMAND_SETTIME:
|
||||
{
|
||||
struct ipmi_req_settime sp;
|
||||
|
||||
if (get_compat_ipmi_req_settime(&sp, compat_ptr(arg)))
|
||||
return -EFAULT;
|
||||
|
||||
return handle_send_req(priv->user, &sp.req,
|
||||
sp.retries, sp.retry_time_ms);
|
||||
}
|
||||
case COMPAT_IPMICTL_RECEIVE_MSG:
|
||||
case COMPAT_IPMICTL_RECEIVE_MSG_TRUNC:
|
||||
{
|
||||
struct ipmi_recv __user *precv64;
|
||||
struct ipmi_recv recv64;
|
||||
|
||||
memset(&recv64, 0, sizeof(recv64));
|
||||
if (get_compat_ipmi_recv(&recv64, compat_ptr(arg)))
|
||||
return -EFAULT;
|
||||
|
||||
precv64 = compat_alloc_user_space(sizeof(recv64));
|
||||
if (copy_to_user(precv64, &recv64, sizeof(recv64)))
|
||||
return -EFAULT;
|
||||
|
||||
rc = ipmi_ioctl(filep,
|
||||
((cmd == COMPAT_IPMICTL_RECEIVE_MSG)
|
||||
? IPMICTL_RECEIVE_MSG
|
||||
: IPMICTL_RECEIVE_MSG_TRUNC),
|
||||
(unsigned long) precv64);
|
||||
if (rc != 0)
|
||||
return rc;
|
||||
|
||||
if (copy_from_user(&recv64, precv64, sizeof(recv64)))
|
||||
return -EFAULT;
|
||||
|
||||
if (put_compat_ipmi_recv(&recv64, compat_ptr(arg)))
|
||||
return -EFAULT;
|
||||
|
||||
return rc;
|
||||
}
|
||||
default:
|
||||
return ipmi_ioctl(filep, cmd, arg);
|
||||
}
|
||||
}
|
||||
|
||||
static long unlocked_compat_ipmi_ioctl(struct file *filep, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&ipmi_mutex);
|
||||
ret = compat_ipmi_ioctl(filep, cmd, arg);
|
||||
mutex_unlock(&ipmi_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct file_operations ipmi_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = ipmi_unlocked_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = unlocked_compat_ipmi_ioctl,
|
||||
#endif
|
||||
.open = ipmi_open,
|
||||
.release = ipmi_release,
|
||||
.fasync = ipmi_fasync,
|
||||
.poll = ipmi_poll,
|
||||
.llseek = noop_llseek,
|
||||
};
|
||||
|
||||
#define DEVICE_NAME "ipmidev"
|
||||
|
||||
static int ipmi_major;
|
||||
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.");
|
||||
|
||||
/* Keep track of the devices that are registered. */
|
||||
struct ipmi_reg_list {
|
||||
dev_t dev;
|
||||
struct list_head link;
|
||||
};
|
||||
static LIST_HEAD(reg_list);
|
||||
static DEFINE_MUTEX(reg_list_mutex);
|
||||
|
||||
static struct class *ipmi_class;
|
||||
|
||||
static void ipmi_new_smi(int if_num, struct device *device)
|
||||
{
|
||||
dev_t dev = MKDEV(ipmi_major, if_num);
|
||||
struct ipmi_reg_list *entry;
|
||||
|
||||
entry = kmalloc(sizeof(*entry), GFP_KERNEL);
|
||||
if (!entry) {
|
||||
printk(KERN_ERR "ipmi_devintf: Unable to create the"
|
||||
" ipmi class device link\n");
|
||||
return;
|
||||
}
|
||||
entry->dev = dev;
|
||||
|
||||
mutex_lock(®_list_mutex);
|
||||
device_create(ipmi_class, device, dev, NULL, "ipmi%d", if_num);
|
||||
list_add(&entry->link, ®_list);
|
||||
mutex_unlock(®_list_mutex);
|
||||
}
|
||||
|
||||
static void ipmi_smi_gone(int if_num)
|
||||
{
|
||||
dev_t dev = MKDEV(ipmi_major, if_num);
|
||||
struct ipmi_reg_list *entry;
|
||||
|
||||
mutex_lock(®_list_mutex);
|
||||
list_for_each_entry(entry, ®_list, link) {
|
||||
if (entry->dev == dev) {
|
||||
list_del(&entry->link);
|
||||
kfree(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
device_destroy(ipmi_class, dev);
|
||||
mutex_unlock(®_list_mutex);
|
||||
}
|
||||
|
||||
static struct ipmi_smi_watcher smi_watcher =
|
||||
{
|
||||
.owner = THIS_MODULE,
|
||||
.new_smi = ipmi_new_smi,
|
||||
.smi_gone = ipmi_smi_gone,
|
||||
};
|
||||
|
||||
static int __init init_ipmi_devintf(void)
|
||||
{
|
||||
int rv;
|
||||
|
||||
if (ipmi_major < 0)
|
||||
return -EINVAL;
|
||||
|
||||
printk(KERN_INFO "ipmi device interface\n");
|
||||
|
||||
ipmi_class = class_create(THIS_MODULE, "ipmi");
|
||||
if (IS_ERR(ipmi_class)) {
|
||||
printk(KERN_ERR "ipmi: can't register device class\n");
|
||||
return PTR_ERR(ipmi_class);
|
||||
}
|
||||
|
||||
rv = register_chrdev(ipmi_major, DEVICE_NAME, &ipmi_fops);
|
||||
if (rv < 0) {
|
||||
class_destroy(ipmi_class);
|
||||
printk(KERN_ERR "ipmi: can't get major %d\n", ipmi_major);
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (ipmi_major == 0) {
|
||||
ipmi_major = rv;
|
||||
}
|
||||
|
||||
rv = ipmi_smi_watcher_register(&smi_watcher);
|
||||
if (rv) {
|
||||
unregister_chrdev(ipmi_major, DEVICE_NAME);
|
||||
class_destroy(ipmi_class);
|
||||
printk(KERN_WARNING "ipmi: can't register smi watcher\n");
|
||||
return rv;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(init_ipmi_devintf);
|
||||
|
||||
static void __exit cleanup_ipmi(void)
|
||||
{
|
||||
struct ipmi_reg_list *entry, *entry2;
|
||||
mutex_lock(®_list_mutex);
|
||||
list_for_each_entry_safe(entry, entry2, ®_list, link) {
|
||||
list_del(&entry->link);
|
||||
device_destroy(ipmi_class, entry->dev);
|
||||
kfree(entry);
|
||||
}
|
||||
mutex_unlock(®_list_mutex);
|
||||
class_destroy(ipmi_class);
|
||||
ipmi_smi_watcher_unregister(&smi_watcher);
|
||||
unregister_chrdev(ipmi_major, DEVICE_NAME);
|
||||
}
|
||||
module_exit(cleanup_ipmi);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Corey Minyard <minyard@mvista.com>");
|
||||
MODULE_DESCRIPTION("Linux device interface for the IPMI message handler.");
|
||||
MODULE_ALIAS("platform:ipmi_si");
|
551
drivers/char/ipmi/ipmi_kcs_sm.c
Normal file
551
drivers/char/ipmi/ipmi_kcs_sm.c
Normal file
|
@ -0,0 +1,551 @@
|
|||
/*
|
||||
* 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/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/ipmi_msgdefs.h> /* for completion codes */
|
||||
#include "ipmi_si_sm.h"
|
||||
|
||||
/* kcs_debug is a bit-field
|
||||
* KCS_DEBUG_ENABLE - turned on for now
|
||||
* KCS_DEBUG_MSG - commands and their responses
|
||||
* KCS_DEBUG_STATES - state machine
|
||||
*/
|
||||
#define KCS_DEBUG_STATES 4
|
||||
#define KCS_DEBUG_MSG 2
|
||||
#define KCS_DEBUG_ENABLE 1
|
||||
|
||||
static int kcs_debug;
|
||||
module_param(kcs_debug, int, 0644);
|
||||
MODULE_PARM_DESC(kcs_debug, "debug bitmask, 1=enable, 2=messages, 4=states");
|
||||
|
||||
/* The states the KCS driver may be in. */
|
||||
enum kcs_states {
|
||||
/* The KCS interface is currently doing nothing. */
|
||||
KCS_IDLE,
|
||||
|
||||
/*
|
||||
* 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_START_OP,
|
||||
|
||||
/* We have written a write cmd to the interface. */
|
||||
KCS_WAIT_WRITE_START,
|
||||
|
||||
/* We are writing bytes to the interface. */
|
||||
KCS_WAIT_WRITE,
|
||||
|
||||
/*
|
||||
* We have written the write end cmd to the interface, and
|
||||
* still need to write the last byte.
|
||||
*/
|
||||
KCS_WAIT_WRITE_END,
|
||||
|
||||
/* We are waiting to read data from the interface. */
|
||||
KCS_WAIT_READ,
|
||||
|
||||
/*
|
||||
* State to transition to the error handler, this was added to
|
||||
* the state machine in the spec to be sure IBF was there.
|
||||
*/
|
||||
KCS_ERROR0,
|
||||
|
||||
/*
|
||||
* First stage error handler, wait for the interface to
|
||||
* respond.
|
||||
*/
|
||||
KCS_ERROR1,
|
||||
|
||||
/*
|
||||
* The abort cmd has been written, wait for the interface to
|
||||
* respond.
|
||||
*/
|
||||
KCS_ERROR2,
|
||||
|
||||
/*
|
||||
* We wrote some data to the interface, wait for it to switch
|
||||
* to read mode.
|
||||
*/
|
||||
KCS_ERROR3,
|
||||
|
||||
/* The hardware failed to follow the state machine. */
|
||||
KCS_HOSED
|
||||
};
|
||||
|
||||
#define MAX_KCS_READ_SIZE IPMI_MAX_MSG_LENGTH
|
||||
#define MAX_KCS_WRITE_SIZE IPMI_MAX_MSG_LENGTH
|
||||
|
||||
/* Timeouts in microseconds. */
|
||||
#define IBF_RETRY_TIMEOUT (5*USEC_PER_SEC)
|
||||
#define OBF_RETRY_TIMEOUT (5*USEC_PER_SEC)
|
||||
#define MAX_ERROR_RETRIES 10
|
||||
#define ERROR0_OBF_WAIT_JIFFIES (2*HZ)
|
||||
|
||||
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;
|
||||
unsigned long error0_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) {
|
||||
if (kcs_debug & KCS_DEBUG_ENABLE)
|
||||
printk(KERN_DEBUG "ipmi_kcs_sm: kcs hosed: %s\n",
|
||||
reason);
|
||||
kcs->state = KCS_HOSED;
|
||||
} else {
|
||||
kcs->error0_timeout = jiffies + ERROR0_OBF_WAIT_JIFFIES;
|
||||
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) {
|
||||
kcs->obf_timeout = OBF_RETRY_TIMEOUT;
|
||||
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)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
if (size < 2)
|
||||
return IPMI_REQ_LEN_INVALID_ERR;
|
||||
if (size > MAX_KCS_WRITE_SIZE)
|
||||
return IPMI_REQ_LEN_EXCEEDED_ERR;
|
||||
|
||||
if ((kcs->state != KCS_IDLE) && (kcs->state != KCS_HOSED))
|
||||
return IPMI_NOT_IN_MY_STATE_ERR;
|
||||
|
||||
if (kcs_debug & KCS_DEBUG_MSG) {
|
||||
printk(KERN_DEBUG "start_kcs_transaction -");
|
||||
for (i = 0; i < size; i++)
|
||||
printk(" %02x", (unsigned char) (data [i]));
|
||||
printk("\n");
|
||||
}
|
||||
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);
|
||||
|
||||
if (kcs_debug & KCS_DEBUG_STATES)
|
||||
printk(KERN_DEBUG "KCS: State = %d, %x\n", kcs->state, status);
|
||||
|
||||
/* 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_STATE) {
|
||||
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);
|
||||
status = read_status(kcs);
|
||||
if (GET_STATUS_OBF(status))
|
||||
/* controller isn't responding */
|
||||
if (time_before(jiffies, kcs->error0_timeout))
|
||||
return SI_SM_CALL_WITH_TICK_DELAY;
|
||||
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 = {
|
||||
.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,
|
||||
};
|
4619
drivers/char/ipmi/ipmi_msghandler.c
Normal file
4619
drivers/char/ipmi/ipmi_msghandler.c
Normal file
File diff suppressed because it is too large
Load diff
749
drivers/char/ipmi/ipmi_poweroff.c
Normal file
749
drivers/char/ipmi/ipmi_poweroff.c
Normal file
|
@ -0,0 +1,749 @@
|
|||
/*
|
||||
* 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 <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/kdev_t.h>
|
||||
#include <linux/ipmi.h>
|
||||
#include <linux/ipmi_smi.h>
|
||||
|
||||
#define PFX "IPMI poweroff: "
|
||||
|
||||
static void ipmi_po_smi_gone(int if_num);
|
||||
static void ipmi_po_new_smi(int if_num, struct device *device);
|
||||
|
||||
/* Definitions for controlling power off (if the system supports it). It
|
||||
* conveniently matches the IPMI chassis control values. */
|
||||
#define IPMI_CHASSIS_POWER_DOWN 0 /* power down, the default. */
|
||||
#define IPMI_CHASSIS_POWER_CYCLE 0x02 /* power cycle */
|
||||
|
||||
/* the IPMI data command */
|
||||
static int poweroff_powercycle;
|
||||
|
||||
/* Which interface to use, -1 means the first we see. */
|
||||
static int ifnum_to_use = -1;
|
||||
|
||||
/* Our local state. */
|
||||
static int ready;
|
||||
static ipmi_user_t ipmi_user;
|
||||
static int ipmi_ifnum;
|
||||
static void (*specific_poweroff_func)(ipmi_user_t user);
|
||||
|
||||
/* Holds the old poweroff function so we can restore it on removal. */
|
||||
static void (*old_poweroff_func)(void);
|
||||
|
||||
static int set_param_ifnum(const char *val, struct kernel_param *kp)
|
||||
{
|
||||
int rv = param_set_int(val, kp);
|
||||
if (rv)
|
||||
return rv;
|
||||
if ((ifnum_to_use < 0) || (ifnum_to_use == ipmi_ifnum))
|
||||
return 0;
|
||||
|
||||
ipmi_po_smi_gone(ipmi_ifnum);
|
||||
ipmi_po_new_smi(ifnum_to_use, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_param_call(ifnum_to_use, set_param_ifnum, param_get_int,
|
||||
&ifnum_to_use, 0644);
|
||||
MODULE_PARM_DESC(ifnum_to_use, "The interface number to use for the watchdog "
|
||||
"timer. Setting to -1 defaults to the first registered "
|
||||
"interface");
|
||||
|
||||
/* parameter definition to allow user to flag power cycle */
|
||||
module_param(poweroff_powercycle, int, 0644);
|
||||
MODULE_PARM_DESC(poweroff_powercycle,
|
||||
" Set to non-zero to enable power cycle instead of power"
|
||||
" down. Power cycle is contingent on hardware support,"
|
||||
" otherwise it defaults back to power down.");
|
||||
|
||||
/* Stuff from the get device id command. */
|
||||
static unsigned int mfg_id;
|
||||
static unsigned int prod_id;
|
||||
static unsigned char capabilities;
|
||||
static unsigned char ipmi_version;
|
||||
|
||||
/*
|
||||
* 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 atomic_t dummy_count = ATOMIC_INIT(0);
|
||||
static void dummy_smi_free(struct ipmi_smi_msg *msg)
|
||||
{
|
||||
atomic_dec(&dummy_count);
|
||||
}
|
||||
static void dummy_recv_free(struct ipmi_recv_msg *msg)
|
||||
{
|
||||
atomic_dec(&dummy_count);
|
||||
}
|
||||
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 response.
|
||||
*/
|
||||
|
||||
static void receive_handler(struct ipmi_recv_msg *recv_msg, void *handler_data)
|
||||
{
|
||||
struct completion *comp = recv_msg->user_msg_data;
|
||||
|
||||
if (comp)
|
||||
complete(comp);
|
||||
}
|
||||
|
||||
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 completion comp;
|
||||
|
||||
init_completion(&comp);
|
||||
|
||||
rv = ipmi_request_supply_msgs(user, addr, 0, send_msg, &comp,
|
||||
&halt_smi_msg, &halt_recv_msg, 0);
|
||||
if (rv)
|
||||
return rv;
|
||||
|
||||
wait_for_completion(&comp);
|
||||
|
||||
return halt_recv_msg.msg.data[0];
|
||||
}
|
||||
|
||||
/* Wait for message to complete, spinning. */
|
||||
static int ipmi_request_in_rc_mode(ipmi_user_t user,
|
||||
struct ipmi_addr *addr,
|
||||
struct kernel_ipmi_msg *send_msg)
|
||||
{
|
||||
int rv;
|
||||
|
||||
atomic_set(&dummy_count, 2);
|
||||
rv = ipmi_request_supply_msgs(user, addr, 0, send_msg, NULL,
|
||||
&halt_smi_msg, &halt_recv_msg, 0);
|
||||
if (rv) {
|
||||
atomic_set(&dummy_count, 0);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* Spin until our message is done.
|
||||
*/
|
||||
while (atomic_read(&dummy_count) > 0) {
|
||||
ipmi_poll_interface(user);
|
||||
cpu_relax();
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
#define IPMI_NETFN_OEM 0x2e
|
||||
#define IPMI_ATCA_PPS_GRACEFUL_RESTART 0x11
|
||||
#define IPMI_ATCA_PPS_IANA "\x00\x40\x0A"
|
||||
#define IPMI_MOTOROLA_MANUFACTURER_ID 0x0000A1
|
||||
#define IPMI_MOTOROLA_PPS_IPMC_PRODUCT_ID 0x0051
|
||||
|
||||
static void (*atca_oem_poweroff_hook)(ipmi_user_t user);
|
||||
|
||||
static void pps_poweroff_atca(ipmi_user_t user)
|
||||
{
|
||||
struct ipmi_system_interface_addr smi_addr;
|
||||
struct kernel_ipmi_msg send_msg;
|
||||
int rv;
|
||||
/*
|
||||
* 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 "PPS powerdown hook used");
|
||||
|
||||
send_msg.netfn = IPMI_NETFN_OEM;
|
||||
send_msg.cmd = IPMI_ATCA_PPS_GRACEFUL_RESTART;
|
||||
send_msg.data = IPMI_ATCA_PPS_IANA;
|
||||
send_msg.data_len = 3;
|
||||
rv = ipmi_request_in_rc_mode(user,
|
||||
(struct ipmi_addr *) &smi_addr,
|
||||
&send_msg);
|
||||
if (rv && rv != IPMI_UNKNOWN_ERR_COMPLETION_CODE) {
|
||||
printk(KERN_ERR PFX "Unable to send ATCA ,"
|
||||
" IPMI error 0x%x\n", rv);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
printk(KERN_INFO PFX "ATCA Detect mfg 0x%X prod 0x%X\n",
|
||||
mfg_id, prod_id);
|
||||
if ((mfg_id == IPMI_MOTOROLA_MANUFACTURER_ID)
|
||||
&& (prod_id == IPMI_MOTOROLA_PPS_IPMC_PRODUCT_ID)) {
|
||||
printk(KERN_INFO PFX
|
||||
"Installing Pigeon Point Systems Poweroff Hook\n");
|
||||
atca_oem_poweroff_hook = pps_poweroff_atca;
|
||||
}
|
||||
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);
|
||||
/*
|
||||
* At this point, the system may be shutting down, and most
|
||||
* serial drivers (if used) will have interrupts turned off
|
||||
* it may be better to ignore IPMI_UNKNOWN_ERR_COMPLETION_CODE
|
||||
* return code
|
||||
*/
|
||||
if (rv && rv != IPMI_UNKNOWN_ERR_COMPLETION_CODE) {
|
||||
printk(KERN_ERR PFX "Unable to send ATCA powerdown message,"
|
||||
" IPMI error 0x%x\n", rv);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (atca_oem_poweroff_hook)
|
||||
atca_oem_poweroff_hook(user);
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* ipmi_dell_chassis_detect()
|
||||
* Dell systems with IPMI < 1.5 don't set the chassis capability bit
|
||||
* but they can handle a chassis poweroff or powercycle command.
|
||||
*/
|
||||
|
||||
#define DELL_IANA_MFR_ID {0xA2, 0x02, 0x00}
|
||||
static int ipmi_dell_chassis_detect(ipmi_user_t user)
|
||||
{
|
||||
const char ipmi_version_major = ipmi_version & 0xF;
|
||||
const char ipmi_version_minor = (ipmi_version >> 4) & 0xF;
|
||||
const char mfr[3] = DELL_IANA_MFR_ID;
|
||||
if (!memcmp(mfr, &mfg_id, sizeof(mfr)) &&
|
||||
ipmi_version_major <= 1 &&
|
||||
ipmi_version_minor < 5)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
powercyclefailed:
|
||||
printk(KERN_INFO PFX "Powering %s via IPMI chassis control command\n",
|
||||
(poweroff_powercycle ? "cycle" : "down"));
|
||||
|
||||
/*
|
||||
* Power down
|
||||
*/
|
||||
send_msg.netfn = IPMI_NETFN_CHASSIS_REQUEST;
|
||||
send_msg.cmd = IPMI_CHASSIS_CONTROL_CMD;
|
||||
if (poweroff_powercycle)
|
||||
data[0] = IPMI_CHASSIS_POWER_CYCLE;
|
||||
else
|
||||
data[0] = IPMI_CHASSIS_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) {
|
||||
if (poweroff_powercycle) {
|
||||
/* power cycle failed, default to power down */
|
||||
printk(KERN_ERR PFX "Unable to send chassis power " \
|
||||
"cycle message, IPMI error 0x%x\n", rv);
|
||||
poweroff_powercycle = 0;
|
||||
goto powercyclefailed;
|
||||
}
|
||||
|
||||
printk(KERN_ERR PFX "Unable to send chassis power " \
|
||||
"down message, IPMI error 0x%x\n", rv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 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 },
|
||||
{ .platform_type = "chassis",
|
||||
.detect = ipmi_dell_chassis_detect,
|
||||
.poweroff_func = ipmi_poweroff_chassis },
|
||||
/* 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))
|
||||
|
||||
|
||||
/* Called on a powerdown request. */
|
||||
static void ipmi_poweroff_function(void)
|
||||
{
|
||||
if (!ready)
|
||||
return;
|
||||
|
||||
/* Use run-to-completion mode, since interrupts may be off. */
|
||||
specific_poweroff_func(ipmi_user);
|
||||
}
|
||||
|
||||
/* 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 device *device)
|
||||
{
|
||||
struct ipmi_system_interface_addr smi_addr;
|
||||
struct kernel_ipmi_msg send_msg;
|
||||
int rv;
|
||||
int i;
|
||||
|
||||
if (ready)
|
||||
return;
|
||||
|
||||
if ((ifnum_to_use >= 0) && (ifnum_to_use != if_num))
|
||||
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;
|
||||
}
|
||||
|
||||
ipmi_ifnum = if_num;
|
||||
|
||||
/*
|
||||
* 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];
|
||||
ipmi_version = halt_recv_msg.msg.data[5];
|
||||
|
||||
|
||||
/* 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)
|
||||
{
|
||||
if (!ready)
|
||||
return;
|
||||
|
||||
if (ipmi_ifnum != if_num)
|
||||
return;
|
||||
|
||||
ready = 0;
|
||||
ipmi_destroy_user(ipmi_user);
|
||||
pm_power_off = old_poweroff_func;
|
||||
}
|
||||
|
||||
static struct ipmi_smi_watcher smi_watcher = {
|
||||
.owner = THIS_MODULE,
|
||||
.new_smi = ipmi_po_new_smi,
|
||||
.smi_gone = ipmi_po_smi_gone
|
||||
};
|
||||
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
#include <linux/sysctl.h>
|
||||
|
||||
static struct ctl_table ipmi_table[] = {
|
||||
{ .procname = "poweroff_powercycle",
|
||||
.data = &poweroff_powercycle,
|
||||
.maxlen = sizeof(poweroff_powercycle),
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct ctl_table ipmi_dir_table[] = {
|
||||
{ .procname = "ipmi",
|
||||
.mode = 0555,
|
||||
.child = ipmi_table },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct ctl_table ipmi_root_table[] = {
|
||||
{ .procname = "dev",
|
||||
.mode = 0555,
|
||||
.child = ipmi_dir_table },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct ctl_table_header *ipmi_table_header;
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
|
||||
/*
|
||||
* Startup and shutdown functions.
|
||||
*/
|
||||
static int __init ipmi_poweroff_init(void)
|
||||
{
|
||||
int rv;
|
||||
|
||||
printk(KERN_INFO "Copyright (C) 2004 MontaVista Software -"
|
||||
" IPMI Powerdown via sys_reboot.\n");
|
||||
|
||||
if (poweroff_powercycle)
|
||||
printk(KERN_INFO PFX "Power cycle is enabled.\n");
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
ipmi_table_header = register_sysctl_table(ipmi_root_table);
|
||||
if (!ipmi_table_header) {
|
||||
printk(KERN_ERR PFX "Unable to register powercycle sysctl\n");
|
||||
rv = -ENOMEM;
|
||||
goto out_err;
|
||||
}
|
||||
#endif
|
||||
|
||||
rv = ipmi_smi_watcher_register(&smi_watcher);
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
if (rv) {
|
||||
unregister_sysctl_table(ipmi_table_header);
|
||||
printk(KERN_ERR PFX "Unable to register SMI watcher: %d\n", rv);
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
out_err:
|
||||
#endif
|
||||
return rv;
|
||||
}
|
||||
|
||||
#ifdef MODULE
|
||||
static void __exit ipmi_poweroff_cleanup(void)
|
||||
{
|
||||
int rv;
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
unregister_sysctl_table(ipmi_table_header);
|
||||
#endif
|
||||
|
||||
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");
|
||||
MODULE_AUTHOR("Corey Minyard <minyard@mvista.com>");
|
||||
MODULE_DESCRIPTION("IPMI Poweroff extension to sys_reboot");
|
3758
drivers/char/ipmi/ipmi_si_intf.c
Normal file
3758
drivers/char/ipmi/ipmi_si_intf.c
Normal file
File diff suppressed because it is too large
Load diff
141
drivers/char/ipmi/ipmi_si_sm.h
Normal file
141
drivers/char/ipmi/ipmi_si_sm.h
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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 __iomem *addr;
|
||||
int regspacing;
|
||||
int regsize;
|
||||
int regshift;
|
||||
int addr_type;
|
||||
long addr_data;
|
||||
};
|
||||
|
||||
/* 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_CALL_WITH_TICK_DELAY,/* Delay >=1 tick 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. */
|
||||
|
||||
/*
|
||||
* The hardware is asserting attn and the state machine is
|
||||
* idle.
|
||||
*/
|
||||
SI_SM_ATTN
|
||||
};
|
||||
|
||||
/* 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;
|
||||
|
600
drivers/char/ipmi/ipmi_smic_sm.c
Normal file
600
drivers/char/ipmi/ipmi_smic_sm.c
Normal file
|
@ -0,0 +1,600 @@
|
|||
/*
|
||||
* 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/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/ipmi_msgdefs.h> /* for completion codes */
|
||||
#include "ipmi_si_sm.h"
|
||||
|
||||
/* 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;
|
||||
module_param(smic_debug, int, 0644);
|
||||
MODULE_PARM_DESC(smic_debug, "debug bitmask, 1=enable, 2=messages, 4=states");
|
||||
|
||||
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 (2*USEC_PER_SEC)
|
||||
|
||||
/* SMIC Flags Register Bits */
|
||||
#define SMIC_RX_DATA_READY 0x80
|
||||
#define SMIC_TX_DATA_READY 0x40
|
||||
|
||||
/*
|
||||
* SMIC_SMI and SMIC_EVM_DATA_AVAIL are only used by
|
||||
* a few systems, and then only by Systems Management
|
||||
* Interrupts, not by the OS. Always ignore these bits.
|
||||
*
|
||||
*/
|
||||
#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)
|
||||
return IPMI_REQ_LEN_INVALID_ERR;
|
||||
if (size > MAX_SMIC_WRITE_SIZE)
|
||||
return IPMI_REQ_LEN_EXCEEDED_ERR;
|
||||
|
||||
if ((smic->state != SMIC_IDLE) && (smic->state != SMIC_HOSED))
|
||||
return IPMI_NOT_IN_MY_STATE_ERR;
|
||||
|
||||
if (smic_debug & SMIC_DEBUG_MSG) {
|
||||
printk(KERN_DEBUG "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_DEBUG "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_DEBUG
|
||||
"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_DEBUG
|
||||
"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_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_DEBUG
|
||||
"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_DEBUG
|
||||
"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_DEBUG "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 = {
|
||||
.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,
|
||||
};
|
1385
drivers/char/ipmi/ipmi_watchdog.c
Normal file
1385
drivers/char/ipmi/ipmi_watchdog.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue