* [PATCH] Add the IPMI SMBus driver
@ 2005-02-01 22:33 Corey Minyard
0 siblings, 0 replies; only message in thread
From: Corey Minyard @ 2005-02-01 22:33 UTC (permalink / raw)
To: lkml, Andrew Morton
[-- Attachment #1: Type: text/plain, Size: 171 bytes --]
The IPMI SMBus driver has long been languishing because I needed some
changes to the I2C code to make it work right. I've posted those
changes. This driver uses them.
[-- Attachment #2: ipmi_smb.diff --]
[-- Type: text/plain, Size: 47668 bytes --]
This patch adds the SMBus interface to the IPMI driver.
It is a rework of the previous driver, which was lacking
functions because it needed an async interface to the I2C
code. Thus, this depends on the async I2C interface
patches.
Signed-off-by: Corey Minyard <minyard@acm.org>
Index: linux-2.6.11-rc2/drivers/char/ipmi/ipmi_smb.c
===================================================================
--- /dev/null
+++ linux-2.6.11-rc2/drivers/char/ipmi/ipmi_smb.c
@@ -0,0 +1,1572 @@
+/*
+ * ipmi_smb.c
+ *
+ * The interface to the IPMI driver for SMBus access to a SMBus
+ * compliant device.
+ *
+ * Author: Intel Corporation
+ * Todd Davis <todd.c.davis@intel.com>
+ *
+ * Rewritten by Corey Minyard <minyard@acm.org> to support the
+ * non-blocking I2C interface, add support for multi-part
+ * transactions, add PEC support, and general clenaup.
+ *
+ * Copyright 2003 Intel Corporation
+ * Copyright 2005 MontaVista Software
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * This file holds the "policy" for the interface to the SMB state
+ * machine. It does the configuration, handles timers and interrupts,
+ * and drives the real SMB state machine.
+ */
+
+/*
+ * TODO: Figure out how to use SMB alerts. This will require a new
+ * interface into the I2C driver, I believe.
+ */
+
+#include <linux/config.h>
+#include <linux/version.h>
+#if defined(MODVERSIONS)
+#include <linux/modversions.h>
+#endif
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <asm/system.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/errno.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/i2c.h>
+#include <linux/ipmi_smi.h>
+#include <linux/init.h>
+#include <asm/io.h>
+
+
+#define IPMI_SMB_VERSION "v33"
+
+#define IPMI_GET_SYSTEM_INTERFACE_CAPABILITIES_CMD 0x57
+
+#define SMB_IPMI_REQUEST 2
+#define SMB_IPMI_MULTI_PART_REQUEST_START 6
+#define SMB_IPMI_MULTI_PART_REQUEST_MIDDLE 7
+#define SMB_IPMI_RESPONSE 3
+#define SMB_IPMI_MULTI_PART_RESPONSE_MIDDLE 9
+
+/* smb_debug is a bit-field
+ * SMB_DEBUG_MSG - commands and their responses
+ * SMB_DEBUG_STATES - message states
+ * SMB_DEBUG_TIMING - Measure times between events in the driver
+ */
+#define SMB_DEBUG_TIMING 4
+#define SMB_DEBUG_STATE 2
+#define SMB_DEBUG_MSG 1
+#define SMB_NODEBUG 0
+#define SMB_DEFAULT_DEBUG (SMB_NODEBUG)
+
+#ifdef CONFIG_IPMI_SMB
+/* This forces a dependency to the config file for this option. */
+#endif
+
+enum smb_intf_state {
+ SMB_NORMAL,
+ SMB_GETTING_FLAGS,
+ SMB_GETTING_EVENTS,
+ SMB_CLEARING_FLAGS,
+ SMB_GETTING_MESSAGES,
+ /* FIXME - add watchdog stuff. */
+};
+
+#define SMB_IDLE(smb) ((smb)->smb_state == SMB_NORMAL \
+ && (smb)->curr_msg == NULL)
+
+/* How many times to we retry the message. */
+#define SMB_MSG_RETRIES 24
+
+/* At what retry interval to we try to rewrite the command. */
+#define SMB_MSG_RETRY_WRITE_COUNT 8
+
+struct smb_info
+{
+ int pos;
+ ipmi_smi_t intf;
+ spinlock_t msg_lock;
+ struct list_head xmit_msgs;
+ struct list_head hp_xmit_msgs;
+ struct ipmi_smi_msg *curr_msg;
+ enum smb_intf_state smb_state;
+ unsigned long smb_debug;
+
+ /* Flags from the last GET_MSG_FLAGS command, used when an ATTN
+ is set to hold the flags until we are done handling everything
+ from the flags. */
+#define RECEIVE_MSG_AVAIL 0x01
+#define EVENT_MSG_BUFFER_FULL 0x02
+#define WDT_PRE_TIMEOUT_INT 0x08
+ unsigned char msg_flags;
+
+ /* If set to true, this will request events the next time the
+ state machine is idle. */
+ int req_events;
+
+ /* If true, run the state machine to completion on every send
+ call. Generally used after a panic or shutdown to make
+ sure stuff goes out. */
+ int run_to_completion;
+
+ /* Used for sending/receiving data. +1 for the length. */
+ unsigned char data[IPMI_MAX_MSG_LENGTH + 1];
+ unsigned int data_len;
+
+ /* Temp receive buffer, gets copied into data. */
+ unsigned char recv[I2C_SMBUS_BLOCK_MAX];
+
+ struct i2c_client client;
+ struct i2c_op_q_entry i2c_q_entry;
+ unsigned char ipmi_smb_dev_rev;
+ unsigned char ipmi_smb_fw_rev_major;
+ unsigned char ipmi_smb_fw_rev_minor;
+ unsigned char ipmi_version_major;
+ unsigned char ipmi_version_minor;
+
+ /* Is the driver trying to stop? */
+ int stopping;
+
+ /* Is the driver running a command? */
+ int running;
+
+ struct timer_list retry_timer;
+ int retries_left;
+
+ /* Info from SSIF cmd */
+ unsigned char max_xmit_msg_size;
+ unsigned char max_recv_msg_size;
+ unsigned int multi_support;
+ int supports_pec;
+
+#define SMB_NO_MULTI 0
+#define SMB_MULTI_2_PART 1
+#define SMB_MULTI_n_PART 2
+ unsigned char *multi_data;
+ unsigned int multi_len;
+ unsigned int multi_pos;
+};
+
+static int initialized = 0;
+static int smb_dbg_probe = 0;
+
+static void return_hosed_msg(struct smb_info *smb_info,
+ struct ipmi_smi_msg *msg);
+static void start_next_msg(struct smb_info *smb_info, unsigned long *flags);
+static int start_send(struct smb_info *smb_info,
+ unsigned char *data,
+ unsigned int len);
+
+
+static void deliver_recv_msg(struct smb_info *smb_info,
+ struct ipmi_smi_msg *msg)
+{
+ if (msg->rsp_size < 0) {
+ return_hosed_msg(smb_info, msg);
+ printk(KERN_ERR
+ "malformed message in deliver_recv_msg:"
+ " rsp_size = %d\n", msg->rsp_size);
+ ipmi_free_smi_msg(msg);
+ } else {
+ ipmi_smi_msg_received(smb_info->intf, msg);
+ }
+}
+
+static void return_hosed_msg(struct smb_info *smb_info,
+ struct ipmi_smi_msg *msg)
+{
+ /* Make it a reponse */
+ msg->rsp[0] = msg->data[0] | 4;
+ msg->rsp[1] = msg->data[1];
+ msg->rsp[2] = 0xFF; /* Unknown error. */
+ msg->rsp_size = 3;
+
+ deliver_recv_msg(smb_info, msg);
+}
+
+/*
+ * Must be called with the message lock held. This will release the
+ * message lock. Note that the caller will check SMB_IDLE and start a
+ * new operation, so there is no need to check for new messages to
+ * start in here.
+ */
+static void start_clear_flags(struct smb_info *smb_info, unsigned long *flags)
+{
+ unsigned char *msg = smb_info->data;
+
+ smb_info->smb_state = SMB_CLEARING_FLAGS;
+ smb_info->running = 1;
+ spin_unlock_irqrestore(&smb_info->msg_lock, *flags);
+
+ /* Make sure the watchdog pre-timeout flag is not set at startup. */
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_CLEAR_MSG_FLAGS_CMD;
+ msg[2] = WDT_PRE_TIMEOUT_INT;
+
+ if (start_send(smb_info, msg, 3) != 0) {
+ /* Error, just go to normal state. */
+ smb_info->smb_state = SMB_NORMAL;
+ smb_info->running = 0;
+ }
+}
+
+/*
+ * Must be called with the message lock held. This will release the
+ * message lock. Note that the caller will check SMB_IDLE and start a
+ * new operation, so there is no need to check for new messages to
+ * start in here.
+ */
+static void handle_flags(struct smb_info *smb_info, unsigned long *flags)
+{
+ if (smb_info->msg_flags & WDT_PRE_TIMEOUT_INT) {
+ /* Watchdog pre-timeout */
+ smb_info->msg_flags &= ~WDT_PRE_TIMEOUT_INT;
+ start_clear_flags(smb_info, flags);
+ ipmi_smi_watchdog_pretimeout(smb_info->intf);
+ } else if (smb_info->msg_flags & RECEIVE_MSG_AVAIL) {
+ /* Messages available. */
+ struct ipmi_smi_msg *msg;
+
+ msg = ipmi_alloc_smi_msg();
+ if (!msg) {
+ smb_info->smb_state = SMB_NORMAL;
+ smb_info->running = 0;
+ spin_unlock_irqrestore(&smb_info->msg_lock, *flags);
+ return;
+ }
+
+ smb_info->curr_msg = msg;
+ smb_info->smb_state = SMB_GETTING_MESSAGES;
+ smb_info->running = 1;
+ spin_unlock_irqrestore(&smb_info->msg_lock, *flags);
+
+ msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg->data[1] = IPMI_GET_MSG_CMD;
+ msg->data_size = 2;
+
+ if (start_send(smb_info, msg->data, msg->data_size) != 0) {
+ spin_lock_irqsave(&smb_info->msg_lock, *flags);
+ smb_info->curr_msg = NULL;
+ smb_info->smb_state = SMB_NORMAL;
+ smb_info->running = 0;
+ spin_unlock_irqrestore(&smb_info->msg_lock, *flags);
+ ipmi_free_smi_msg(msg);
+ }
+ } else if (smb_info->msg_flags & EVENT_MSG_BUFFER_FULL) {
+ /* Events available. */
+ struct ipmi_smi_msg *msg = ipmi_alloc_smi_msg();
+ if (!msg) {
+ smb_info->smb_state = SMB_NORMAL;
+ smb_info->running = 0;
+ return;
+ }
+
+ smb_info->curr_msg = msg;
+ smb_info->smb_state = SMB_GETTING_EVENTS;
+ smb_info->running = 1;
+ spin_unlock_irqrestore(&smb_info->msg_lock, *flags);
+
+ msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg->data[1] = IPMI_READ_EVENT_MSG_BUFFER_CMD;
+ msg->data_size = 2;
+
+ if (start_send(smb_info, msg->data, msg->data_size) != 0) {
+ spin_lock_irqsave(&smb_info->msg_lock, *flags);
+ smb_info->curr_msg = NULL;
+ smb_info->smb_state = SMB_NORMAL;
+ smb_info->running = 0;
+ spin_unlock_irqrestore(&smb_info->msg_lock, *flags);
+ ipmi_free_smi_msg(msg);
+ }
+ } else {
+ smb_info->smb_state = SMB_NORMAL;
+ smb_info->running = 0;
+ spin_unlock_irqrestore(&smb_info->msg_lock, *flags);
+ }
+}
+
+static void msg_done_handler(struct i2c_op_q_entry *i2ce);
+static void retry_timeout(unsigned long data)
+{
+ struct smb_info *smb_info = (void *) data;
+ struct i2c_op_q_entry *i2ce;
+
+ i2ce = &smb_info->i2c_q_entry;
+ i2ce->xfer_type = I2C_OP_SMBUS;
+ i2ce->handler = msg_done_handler;
+ i2ce->handler_data = smb_info;
+ i2ce->smbus.read_write = I2C_SMBUS_READ;
+ i2ce->smbus.command = SMB_IPMI_RESPONSE;
+ i2ce->smbus.data = (union i2c_smbus_data *) smb_info->recv;
+ i2ce->smbus.size = I2C_SMBUS_BLOCK_DATA;
+
+ if (i2c_non_blocking_op(&smb_info->client, i2ce)) {
+ /* request failed, just return the error. */
+ if (smb_info->smb_debug & SMB_DEBUG_MSG) {
+ printk(KERN_INFO
+ "Error from i2c_non_blocking_op(5)\n");
+ }
+ i2ce->result = -EIO;
+ msg_done_handler(i2ce);
+ }
+}
+
+static int start_resend(struct smb_info *smb_info);
+static void msg_done_handler(struct i2c_op_q_entry *i2ce)
+{
+ struct smb_info *smb_info = i2ce->handler_data;
+ unsigned char *data = smb_info->recv;
+ int len;
+ int result = i2ce->result;
+ struct ipmi_smi_msg *msg;
+ unsigned long flags;
+
+ if (smb_info->stopping) {
+ smb_info->running = 0;
+ return;
+ }
+
+ /* We are single-threaded here, so no need for a lock until we
+ start messing with driver states or the queues. */
+
+ if (result < 0) {
+ smb_info->retries_left--;
+ if (smb_info->retries_left > 0) {
+ if ((smb_info->retries_left
+ % SMB_MSG_RETRY_WRITE_COUNT) == 0)
+ {
+ /* Every once in a while re-request the
+ write. */
+ if (! start_resend(smb_info))
+ return;
+ /* If start_resend fails, just restart
+ the timer. */
+ }
+ struct timer_list *t = &smb_info->retry_timer;
+ t->expires = jiffies + 10;
+ t->data = (unsigned long) smb_info;
+ t->function = retry_timeout;
+ add_timer(t);
+ return;
+ }
+ if (smb_info->smb_debug & SMB_DEBUG_MSG)
+ printk(KERN_INFO
+ "Error in msg_done_handler: %d\n", i2ce->result);
+ len = 0;
+ goto continue_op;
+ }
+
+ len = data[0]; /* Number of bytes *after* data[0]. */
+ data++;
+ if ((len > 1) && (smb_info->multi_pos == 0)
+ && (data[0] == 0x00) && (data[1] == 0x01))
+ {
+ /* Start of multi-part read. Start the next transaction. */
+ int i;
+
+ /* Remove the multi-part read marker. */
+ for (i=0; i<len-2; i++)
+ smb_info->data[i] = data[i+2];
+ len -= 2;
+ smb_info->multi_len = len;
+ smb_info->multi_pos = 1;
+
+ i2ce->xfer_type = I2C_OP_SMBUS;
+ i2ce->handler = msg_done_handler;
+ i2ce->handler_data = smb_info;
+ i2ce->smbus.read_write = I2C_SMBUS_READ;
+ i2ce->smbus.command = SMB_IPMI_MULTI_PART_RESPONSE_MIDDLE;
+ i2ce->smbus.data = ((union i2c_smbus_data *) smb_info->recv);
+ i2ce->smbus.size = I2C_SMBUS_BLOCK_DATA;
+ if (i2c_non_blocking_op(&smb_info->client, i2ce)) {
+ if (smb_info->smb_debug & SMB_DEBUG_MSG) {
+ printk(KERN_INFO
+ "Error from i2c_non_blocking_op(1)\n");
+ }
+ result = -EIO;
+ } else
+ return;
+ } else if (smb_info->multi_pos) {
+ /* Middle of multi-part read. Start the next transaction. */
+ int i;
+ unsigned char blocknum;
+
+ if (len == 0) {
+ result = -EIO;
+ if (smb_info->smb_debug & SMB_DEBUG_MSG) {
+ printk(KERN_INFO
+ "Received middle message with no"
+ " data\n");
+ }
+ goto continue_op;
+ }
+
+ blocknum = data[smb_info->multi_len];
+
+ if (smb_info->multi_len+len-1 > IPMI_MAX_MSG_LENGTH) {
+ /* Received message too big, abort the operation. */
+ result = -E2BIG;
+ if (smb_info->smb_debug & SMB_DEBUG_MSG) {
+ printk(KERN_INFO
+ "Received message too big\n");
+ }
+ goto continue_op;
+ }
+
+ /* Remove the blocknum from the data. */
+ for (i=0; i<len-1; i++)
+ smb_info->data[i+smb_info->multi_len] = data[i+1];
+ len--;
+ smb_info->multi_len += len;
+ if (blocknum == 0xff) {
+ /* End of read */
+ len = smb_info->multi_len;
+ data = smb_info->data;
+ } else if ((blocknum+1) != smb_info->multi_pos) {
+ /* Out of sequence block, just abort. Block
+ numbers start at zero for the second block,
+ but multi_pos starts at one, so the +1. */
+ result = -EIO;
+ } else {
+ smb_info->multi_pos++;
+ i2ce->xfer_type = I2C_OP_SMBUS;
+ i2ce->handler = msg_done_handler;
+ i2ce->handler_data = smb_info;
+ i2ce->smbus.read_write = I2C_SMBUS_READ;
+ i2ce->smbus.command
+ = SMB_IPMI_MULTI_PART_RESPONSE_MIDDLE;
+ i2ce->smbus.data = ((union i2c_smbus_data *)
+ smb_info->recv);
+ i2ce->smbus.size = I2C_SMBUS_BLOCK_DATA;
+
+ if (i2c_non_blocking_op(&smb_info->client, i2ce)) {
+ if (smb_info->smb_debug & SMB_DEBUG_MSG) {
+ printk(KERN_INFO
+ "Error fromo i2c_non_blocking_op(2)\n");
+ }
+ result = -EIO;
+ } else
+ return;
+ }
+ }
+
+ continue_op:
+ if (smb_info->smb_debug & SMB_DEBUG_STATE)
+ printk(KERN_INFO "DONE 1: state = %d, result=%d.\n",
+ smb_info->smb_state, result);
+
+ spin_lock_irqsave(&(smb_info->msg_lock), flags);
+ msg = smb_info->curr_msg;
+ if (msg) {
+ msg->rsp_size = len;
+ if (msg->rsp_size > IPMI_MAX_MSG_LENGTH)
+ msg->rsp_size = IPMI_MAX_MSG_LENGTH;
+ memcpy(msg->rsp, data, msg->rsp_size);
+ smb_info->curr_msg = NULL;
+ }
+
+ switch (smb_info->smb_state) {
+ case SMB_NORMAL:
+ spin_unlock_irqrestore(&smb_info->msg_lock, flags);
+ if (!msg)
+ break;
+
+ if (result < 0)
+ return_hosed_msg(smb_info, msg);
+ else
+ deliver_recv_msg(smb_info, msg);
+ break;
+
+ case SMB_GETTING_FLAGS:
+ /* We got the flags from the SMB, now handle them. */
+ if ((result < 0) || (len < 4) || (data[2] != 0)) {
+ /* Error fetching flags, or invalid length,
+ just give up for now. */
+ smb_info->smb_state = SMB_NORMAL;
+ smb_info->running = 0;
+ spin_unlock_irqrestore(&smb_info->msg_lock, flags);
+ printk(KERN_WARNING
+ "ipmi_smb:Error getting flags: %d %d, %2.2x\n",
+ result, len, data[2]);
+ } else {
+ smb_info->msg_flags = data[3];
+ handle_flags(smb_info, &flags);
+ }
+ break;
+
+ case SMB_CLEARING_FLAGS:
+ /* We cleared the flags. */
+ if ((result < 0) || (len < 3) || (data[2] != 0)) {
+ /* Error clearing flags */
+ printk(KERN_WARNING
+ "ipmi_smb:Error clearing flags: %d %d, %2.2x\n",
+ result, len, data[2]);
+ }
+ smb_info->smb_state = SMB_NORMAL;
+ smb_info->running = 0;
+ spin_unlock_irqrestore(&smb_info->msg_lock, flags);
+ break;
+
+ case SMB_GETTING_EVENTS:
+ if ((result < 0) || (len < 3) || (msg->rsp[2] != 0)) {
+ /* Error getting event, probably done. */
+ msg->done(msg);
+
+ /* Take off the event flag. */
+ smb_info->msg_flags &= ~EVENT_MSG_BUFFER_FULL;
+ handle_flags(smb_info, &flags);
+ } else {
+ handle_flags(smb_info, &flags);
+ deliver_recv_msg(smb_info, msg);
+ }
+ break;
+
+ case SMB_GETTING_MESSAGES:
+ if ((result < 0) || (len < 3) || (msg->rsp[2] != 0)) {
+ /* Error getting event, probably done. */
+ msg->done(msg);
+
+ /* Take off the msg flag. */
+ smb_info->msg_flags &= ~RECEIVE_MSG_AVAIL;
+ handle_flags(smb_info, &flags);
+ } else {
+ handle_flags(smb_info, &flags);
+ deliver_recv_msg(smb_info, msg);
+ }
+ break;
+ }
+
+ spin_lock_irqsave(&(smb_info->msg_lock), flags);
+ if (SMB_IDLE(smb_info)) {
+ if (smb_info->req_events) {
+ unsigned char mb[2];
+
+ smb_info->req_events = 0;
+ smb_info->smb_state = SMB_GETTING_FLAGS;
+ smb_info->running = 1;
+ spin_unlock_irqrestore(&smb_info->msg_lock, flags);
+
+ mb[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ mb[1] = IPMI_GET_MSG_FLAGS_CMD;
+ if (start_send(smb_info, mb, 2) != 0) {
+ smb_info->smb_state = SMB_NORMAL;
+ smb_info->running = 0;
+ }
+ } else {
+ start_next_msg(smb_info, &flags);
+ }
+ } else
+ spin_unlock_irqrestore(&smb_info->msg_lock, flags);
+
+ if (smb_info->smb_debug & SMB_DEBUG_STATE)
+ printk(KERN_INFO "DONE 2: state = %d.\n", smb_info->smb_state);
+}
+
+static void msg_written_handler(struct i2c_op_q_entry *i2ce)
+{
+ struct smb_info *smb_info = i2ce->handler_data;
+
+ if (smb_info->stopping) {
+ smb_info->running = 0;
+ return;
+ }
+
+ /* We are single-threaded here, so no need for a lock. */
+ if (i2ce->result < 0) {
+ smb_info->retries_left--;
+ if (smb_info->retries_left > 0) {
+ if (! start_resend(smb_info))
+ return;
+ /* request failed, just return the error. */
+ if (smb_info->smb_debug & SMB_DEBUG_MSG) {
+ printk(KERN_INFO
+ "Error from i2c_non_blocking_op(3)\n");
+ }
+ i2ce->result = -EIO;
+ msg_done_handler(i2ce);
+ return;
+ }
+
+ /* Got an error on transmit, let the done routine
+ handle it. */
+ if (smb_info->smb_debug & SMB_DEBUG_MSG) {
+ printk(KERN_INFO
+ "Error in msg_written_handler: %d\n",
+ i2ce->result);
+ }
+ msg_done_handler(i2ce);
+ return;
+ }
+
+ if (smb_info->multi_data) {
+ /* In the middle of a multi-data write. */
+ int left;
+
+ if (left > 32)
+ left = 32;
+ left = smb_info->multi_len - smb_info->multi_pos;
+ i2ce->xfer_type = I2C_OP_SMBUS;
+ i2ce->handler = msg_written_handler;
+ i2ce->handler_data = smb_info;
+ i2ce->smbus.read_write = I2C_SMBUS_WRITE;
+ i2ce->smbus.data
+ = ((union i2c_smbus_data *)
+ (smb_info->multi_data + smb_info->multi_pos));
+ /* Length byte. */
+ smb_info->multi_data[smb_info->multi_pos] = left;
+ i2ce->smbus.size = I2C_SMBUS_BLOCK_DATA;
+ smb_info->multi_pos += left;
+ i2ce->smbus.command = SMB_IPMI_MULTI_PART_REQUEST_MIDDLE;
+ if (left < 32)
+ /* Write is finished. Note that we must end
+ with a write of less than 32 bytes to
+ complete the transaction, even if it is
+ zero bytes. */
+ smb_info->multi_data = NULL;
+ } else {
+ /* Write done, start a read. */
+ i2ce->xfer_type = I2C_OP_SMBUS;
+ i2ce->handler = msg_done_handler;
+ i2ce->handler_data = smb_info;
+ i2ce->smbus.read_write = I2C_SMBUS_READ;
+ i2ce->smbus.command = SMB_IPMI_RESPONSE;
+ i2ce->smbus.data = (union i2c_smbus_data *) smb_info->recv;
+ i2ce->smbus.size = I2C_SMBUS_BLOCK_DATA;
+ smb_info->multi_pos = 0;
+ }
+
+ if (i2c_non_blocking_op(&smb_info->client, i2ce)) {
+ /* request failed, just return the error. */
+ if (smb_info->smb_debug & SMB_DEBUG_MSG) {
+ printk(KERN_INFO
+ "Error from i2c_non_blocking_op(3)\n");
+ }
+ i2ce->result = -EIO;
+ msg_done_handler(i2ce);
+ }
+}
+
+static int start_resend(struct smb_info *smb_info)
+{
+ struct i2c_op_q_entry *i2ce;
+ int rv;
+
+ i2ce = &smb_info->i2c_q_entry;
+ i2ce->xfer_type = I2C_OP_SMBUS;
+ i2ce->handler = msg_written_handler;
+ i2ce->handler_data = smb_info;
+ i2ce->smbus.read_write = I2C_SMBUS_WRITE;
+
+ i2ce->smbus.data = (union i2c_smbus_data *) smb_info->data;
+ i2ce->smbus.size = I2C_SMBUS_BLOCK_DATA;
+
+ if (smb_info->data_len > 32) {
+ i2ce->smbus.command = SMB_IPMI_MULTI_PART_REQUEST_START;
+ smb_info->multi_data = smb_info->data;
+ smb_info->multi_len = smb_info->data_len;
+ /* Subtle thing, this is 32, not 33, because we will
+ overwrite the thing at position 32 (which was just
+ transmitted) with the new length. */
+ smb_info->multi_pos = 32;
+ smb_info->data[0] = 32;
+ } else {
+ smb_info->multi_data = NULL;
+ i2ce->smbus.command = SMB_IPMI_REQUEST;
+ smb_info->data[0] = smb_info->data_len;
+ }
+
+ rv = i2c_non_blocking_op(&smb_info->client, i2ce);
+ if (rv && (smb_info->smb_debug & SMB_DEBUG_MSG)) {
+ printk(KERN_INFO
+ "Error from i2c_non_blocking_op(4)\n");
+ }
+ return rv;
+}
+
+static int start_send(struct smb_info *smb_info,
+ unsigned char *data,
+ unsigned int len)
+{
+ if (len > IPMI_MAX_MSG_LENGTH)
+ return -E2BIG;
+ if (len > smb_info->max_xmit_msg_size)
+ return -E2BIG;
+
+ smb_info->retries_left = SMB_MSG_RETRIES;
+ memcpy(smb_info->data+1, data, len);
+ smb_info->data_len = len;
+ return start_resend(smb_info);
+}
+
+/* Must be called with the message lock held. */
+static void start_next_msg(struct smb_info *smb_info, unsigned long *flags)
+{
+ struct list_head *entry = NULL;
+ struct ipmi_smi_msg *msg;
+
+ restart:
+ if (!SMB_IDLE(smb_info)) {
+ spin_unlock_irqrestore(&smb_info->msg_lock, *flags);
+ return;
+ }
+
+ /* Pick the high priority queue first. */
+ if (! list_empty(&(smb_info->hp_xmit_msgs))) {
+ entry = smb_info->hp_xmit_msgs.next;
+ } else if (! list_empty(&(smb_info->xmit_msgs))) {
+ entry = smb_info->xmit_msgs.next;
+ }
+
+ if (!entry) {
+ smb_info->curr_msg = NULL;
+ spin_unlock_irqrestore(&smb_info->msg_lock, *flags);
+ } else {
+ int rv;
+
+ list_del(entry);
+ msg = list_entry(entry, struct ipmi_smi_msg, link);
+ smb_info->curr_msg = msg;
+ spin_unlock_irqrestore(&smb_info->msg_lock, *flags);
+ rv = start_send(smb_info,
+ smb_info->curr_msg->data,
+ smb_info->curr_msg->data_size);
+ if (rv) {
+ smb_info->curr_msg = NULL;
+ return_hosed_msg(smb_info, msg);
+ spin_lock_irqsave(&smb_info->msg_lock, *flags);
+ goto restart;
+ }
+ }
+}
+
+static void sender(void *send_info,
+ struct ipmi_smi_msg *msg,
+ int priority)
+{
+ struct smb_info *smb_info = (struct smb_info *) send_info;
+ unsigned long flags;
+
+ spin_lock_irqsave(&smb_info->msg_lock, flags);
+ if (smb_info->run_to_completion) {
+ /* If we are running to completion, then throw it in
+ the list and run transactions until everything is
+ clear. Priority doesn't matter here. */
+ list_add_tail(&(msg->link), &(smb_info->xmit_msgs));
+ start_next_msg(smb_info, &flags);
+
+ i2c_poll(&smb_info->client, 0);
+ while (! SMB_IDLE(smb_info)) {
+ udelay(500);
+ i2c_poll(&smb_info->client, 500);
+ }
+ return;
+ }
+
+
+ if (priority > 0) {
+ list_add_tail(&(msg->link), &(smb_info->hp_xmit_msgs));
+ } else {
+ list_add_tail(&(msg->link), &(smb_info->xmit_msgs));
+ }
+ start_next_msg(smb_info, &flags);
+
+ if (smb_info->smb_debug & SMB_DEBUG_TIMING) {
+ struct timeval t;
+ do_gettimeofday(&t);
+ printk(KERN_INFO
+ "**Enqueue %02x %02x: %ld.%6.6ld\n",
+ msg->data[0], msg->data[1], t.tv_sec, t.tv_usec);
+ }
+}
+
+/*
+ * Instead of having our own timer to periodically check the message
+ * flags, we let the message handler drive us.
+ */
+static void request_events(void *send_info)
+{
+ struct smb_info *smb_info = (struct smb_info *) send_info;
+ unsigned long flags;
+
+ spin_lock_irqsave(&smb_info->msg_lock, flags);
+ if (SMB_IDLE(smb_info)) {
+ unsigned char mb[2];
+
+ smb_info->smb_state = SMB_GETTING_FLAGS;
+ smb_info->running = 1;
+ spin_unlock_irqrestore(&smb_info->msg_lock, flags);
+
+ mb[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ mb[1] = IPMI_GET_MSG_FLAGS_CMD;
+ if (start_send(smb_info, mb, 2)) {
+ spin_lock_irqsave(&smb_info->msg_lock, flags);
+ smb_info->smb_state = SMB_NORMAL;
+ smb_info->running = 0;
+ /* Msgs could have been queued while we were
+ * unlocked. */
+ start_next_msg(smb_info, &flags);
+ }
+ } else {
+ smb_info->req_events = 1;
+ spin_unlock_irqrestore(&smb_info->msg_lock, flags);
+ }
+}
+
+static void set_run_to_completion(void *send_info, int i_run_to_completion)
+{
+ struct smb_info *smb_info = (struct smb_info *) send_info;
+
+ smb_info->run_to_completion = i_run_to_completion;
+ /* Note that if this does not compile, there are some I2C
+ changes that you need to handle this properly. */
+ if (i_run_to_completion) {
+ i2c_poll(&smb_info->client, 0);
+ while (! SMB_IDLE(smb_info)) {
+ udelay(500);
+ i2c_poll(&smb_info->client, 500);
+ }
+ }
+}
+
+static void poll(void *send_info)
+{
+ struct smb_info *smb_info = send_info;
+ i2c_poll(&smb_info->client, 10);
+}
+
+static struct ipmi_smi_handlers handlers =
+{
+ .owner = THIS_MODULE,
+ .sender = sender,
+ .request_events = request_events,
+ .set_run_to_completion = set_run_to_completion,
+ .poll = poll,
+};
+
+static int attach_adapter(struct i2c_adapter *adapter);
+static int detach_client(struct i2c_client *client);
+static struct i2c_driver smb_i2c_driver =
+{
+ .owner = THIS_MODULE,
+ .name = "IPMI",
+ .flags = I2C_DF_NOTIFY,
+ .attach_adapter = attach_adapter,
+ .detach_client = detach_client,
+};
+
+static int ipmi_smb_detect_hardware(struct i2c_adapter *adapter, int addr,
+ int debug, struct smb_info **smb_info)
+{
+ unsigned char msg[3];
+ unsigned char *resp;
+ s32 ret;
+ struct smb_info *info;
+ int rv = 0;
+ struct i2c_client *client;
+ int retry_cnt;
+
+ resp = kmalloc(IPMI_MAX_MSG_LENGTH, GFP_KERNEL);
+ if (!resp)
+ return -ENOMEM;
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ kfree(resp);
+ return -ENOMEM;
+ }
+ memset(info, 0, sizeof(*info));
+
+ client = &info->client;
+ strlcpy(client->name, "IPMI", I2C_NAME_SIZE);
+ client->addr = addr;
+ client->adapter = adapter;
+ client->driver = &smb_i2c_driver;
+
+ /* Do a Get Device ID command, since it comes back with some
+ useful info. */
+ msg[0] = IPMI_NETFN_APP_REQUEST << 2;
+ msg[1] = IPMI_GET_DEVICE_ID_CMD;
+
+ retry_cnt = SMB_MSG_RETRIES;
+ retry1:
+ ret = i2c_smbus_write_block_data(client, SMB_IPMI_REQUEST, 2, msg);
+ if (ret) {
+ retry_cnt--;
+ if (retry_cnt > 0)
+ goto retry1;
+ rv = -ENODEV;
+ goto out;
+ }
+
+ ret = -ENODEV;
+ while (retry_cnt > 0) {
+ ret = i2c_smbus_read_block_data(client, SMB_IPMI_RESPONSE,
+ resp);
+ if (ret >= 6)
+ break;
+ msleep(10);
+ retry_cnt--;
+ if (retry_cnt <= 0)
+ break;
+ if ((retry_cnt % SMB_MSG_RETRY_WRITE_COUNT) == 0)
+ goto retry1;
+
+ }
+ if (ret < 6) {
+ /* That's odd, it should be longer. */
+ rv = -ENODEV;
+ goto out;
+ }
+
+ if ((resp[1] != IPMI_GET_DEVICE_ID_CMD) || (resp[2] != 0)) {
+ /* That's odd, it shouldn't be able to fail. */
+ rv = -ENODEV;
+ goto out;
+ }
+
+ info->ipmi_smb_dev_rev = resp[4] & 0xf;
+ info->ipmi_smb_fw_rev_major = resp[5] & 0x7f;
+ info->ipmi_smb_fw_rev_minor = resp[6];
+ info->ipmi_version_major = resp[7] & 0xf;
+ info->ipmi_version_minor = resp[7] >> 4;
+ info->client = *client;
+ i2c_set_clientdata(&info->client, info);
+ info->smb_debug = debug;
+
+ /* Now check for system interface capabilities */
+ msg[0] = IPMI_NETFN_APP_REQUEST << 2;
+ msg[1] = IPMI_GET_SYSTEM_INTERFACE_CAPABILITIES_CMD;
+ msg[2] = 0; /* SSIF */
+ retry_cnt = SMB_MSG_RETRIES;
+ retry2:
+ ret = i2c_smbus_write_block_data(client, SMB_IPMI_REQUEST, 3, msg);
+ if (ret) {
+ retry_cnt--;
+ if (retry_cnt > 0)
+ goto retry2;
+ rv = -ENODEV;
+ goto out;
+ }
+
+ ret = -ENODEV;
+ while (retry_cnt > 0) {
+ ret = i2c_smbus_read_block_data(client, SMB_IPMI_RESPONSE,
+ resp);
+ if (ret >= 6)
+ break;
+ msleep(10);
+ retry_cnt--;
+ if (retry_cnt <= 0)
+ break;
+ if ((retry_cnt % SMB_MSG_RETRY_WRITE_COUNT) == 0)
+ goto retry2;
+ }
+ if ((ret >= 3) && (resp[2] == 0)) {
+ if (ret < 7) {
+ if (smb_dbg_probe) {
+ printk(KERN_INFO
+ "ipmi_smb: SSIF info too short: %d\n",
+ ret);
+ }
+ goto no_support;
+ }
+
+ /* Got a good SSIF response, handle it. */
+ info->max_xmit_msg_size = resp[5];
+ info->max_recv_msg_size = resp[6];
+ info->multi_support = (resp[4] >> 6) & 0x3;
+ info->supports_pec = (resp[4] >> 3) & 0x1;
+
+ /* Sanitize the data */
+ switch (info->multi_support)
+ {
+ case SMB_NO_MULTI:
+ if (info->max_xmit_msg_size > 32)
+ info->max_xmit_msg_size = 32;
+ if (info->max_recv_msg_size > 32)
+ info->max_recv_msg_size = 32;
+ break;
+
+ case SMB_MULTI_2_PART:
+ if (info->max_xmit_msg_size > 64)
+ info->max_xmit_msg_size = 64;
+ if (info->max_recv_msg_size > 62)
+ info->max_recv_msg_size = 62;
+ break;
+
+ case SMB_MULTI_n_PART:
+ break;
+
+ default:
+ /* Data is not sane, just give up. */
+ goto no_support;
+ }
+ } else {
+ no_support:
+ /* Assume no multi-part or PEC support */
+ info->max_xmit_msg_size = 32;
+ info->max_recv_msg_size = 32;
+ info->multi_support = SMB_NO_MULTI;
+ info->supports_pec = 0;
+ }
+
+ *smb_info = info;
+
+ out:
+ if (rv)
+ kfree(info);
+ kfree(resp);
+ return rv;
+}
+
+#define MAX_SMB_BMCS 4
+/* no expressions allowed in __MODULE_STRING */
+#define MAX_SMB_ADDR_PAIRS 8
+
+/* An array of SMB interfaces. */
+static struct smb_info *smb_infos[MAX_SMB_BMCS];
+
+static unsigned short __initdata addr[MAX_SMB_BMCS*2];
+static int num_addrs = 0;
+
+module_param_array(addr, ushort, &num_addrs, 0);
+MODULE_PARM_DESC(addr, "Sets the addresses to scan for IPMI BMCs on the SMBus."
+ " By default the driver will scan for anything it finds in"
+ " DMI or ACPI tables. Otherwise you have to hand-specify"
+ " the address. This is a list of pairs (ie a,b,c,d) where"
+ " each pair gives an adapter and an address. In the"
+ " previous example it will scan address b on adapter a"
+ " and address d on adapter c."
+ " The first pair is for the first interface, etc. If you"
+ " don't provide this and don't have DMI/ACPI, it probably"
+ " won't work.");
+
+static int smb_defaultprobe = 0;
+module_param_named(defaultprobe, smb_defaultprobe, int, 0);
+MODULE_PARM_DESC(defaultprobe, "Normally the driver will not scan anything"
+ " but the specified values and DMI/ACPI values. If you set"
+ " this to non-zero it will scan all addresses except for"
+ " certain dangerous ones.");
+
+static int slave_addrs[MAX_SMB_BMCS];
+static int num_slave_addrs = 0;
+module_param_array(slave_addrs, int, &num_slave_addrs, 0);
+MODULE_PARM_DESC(slave_addrs, "Set the default IPMB slave address for"
+ " the controller. Normally this is 0x20, but can be"
+ " overridden by this parm. This is an array indexed"
+ " by interface number.");
+
+static int dbg[MAX_SMB_BMCS];
+static int num_dbg = 0;
+module_param_array(dbg, int, &num_dbg, 0);
+MODULE_PARM_DESC(dbg, "Turn on debugging. Bit 0 enables message debugging,"
+ " bit 1 enables state debugging, and bit 2 enables timing"
+ " debugging. This is an array indexed by interface number");
+
+module_param_named(dbg_probe, smb_dbg_probe, int, 0);
+MODULE_PARM_DESC(dbg_probe, "Enable debugging of probing of adapters.");
+
+/* force list has 3 entries - 0:bus/adapter no 1: i2c addr 2: unknown/unused */
+#define FORCE_LIST_ENTRIES 3
+static unsigned short smb_force_list[MAX_SMB_BMCS*FORCE_LIST_ENTRIES + FORCE_LIST_ENTRIES];
+
+#define SMB_I2C_START_ADDR 0x20
+#define SMB_I2C_END_ADDR 0x4f
+static unsigned short normal_i2c[] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short normal_i2c_range[] = { SMB_I2C_START_ADDR,
+ SMB_I2C_END_ADDR,
+ I2C_CLIENT_END };
+/*
+static unsigned int normal_isa[] = { SENSORS_ISA_END };
+static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
+*/
+static unsigned short reserved[] =
+{
+/* As defined by SMBus Spec. Appendix C */
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x28,
+ 0x37,
+/* As defined by SMBus Spec. Sect. 5.2 */
+ 0x01, 0x02, 0x03, 0x04, 0x05,
+ 0x06, 0x07, 0x78, 0x79, 0x7a, 0x7b,
+ 0x7c, 0x7d, 0x7e, 0x7f,
+/* Common PC addresses (bad idea) */
+ 0x2d, 0x48, 0x49, /* sensors */
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, /* eeproms */
+ 0x69, /* clock chips */
+
+ I2C_CLIENT_END
+};
+
+static unsigned short smb_empty_list[] = { I2C_CLIENT_END, I2C_CLIENT_END };
+
+static struct i2c_client_address_data smb_address_data = {
+ .normal_i2c = normal_i2c,
+ .normal_i2c_range = normal_i2c_range,
+ .probe = smb_empty_list,
+ .probe_range = smb_empty_list,
+ .ignore = reserved,
+ .ignore_range = smb_empty_list,
+ .force = smb_force_list,
+};
+
+static unsigned int pos_reserved_as(int pos)
+{
+ if (addr[pos*2+1] != 0)
+ return (addr[pos*2] << 16) | addr[pos*2+1];
+
+ return 0;
+}
+
+static int smb_found_addr_proc(struct i2c_adapter *adapter, int addr, int kind)
+{
+ int id = i2c_adapter_id(adapter);
+ int debug = dbg[id];
+ int rv;
+ int i;
+ int next_pos;
+ struct smb_info *smb_info;
+
+ if( id >= MAX_SMB_BMCS )
+ return 0;
+ rv = ipmi_smb_detect_hardware(adapter, addr, debug, &smb_info);
+ if (rv) {
+ if (smb_dbg_probe) {
+ printk(KERN_INFO
+ "smb_found_addr_proc:No IPMI client 0x%x: %d\n",
+ addr, rv);
+ }
+ return 0;
+ }
+
+ if (smb_dbg_probe) {
+ printk(KERN_INFO
+ "smb_found_addr_proc: i2c_probe found device at"
+ " i2c address %x\n", addr);
+ }
+
+ spin_lock_init(&(smb_info->msg_lock));
+ INIT_LIST_HEAD(&(smb_info->xmit_msgs));
+ INIT_LIST_HEAD(&(smb_info->hp_xmit_msgs));
+ smb_info->curr_msg = NULL;
+ smb_info->req_events = 0;
+ smb_info->run_to_completion = 0;
+ smb_info->smb_state = SMB_NORMAL;
+ smb_info->running = 0;
+ init_timer(&smb_info->retry_timer);
+
+ next_pos = -1;
+ for (i=0; i < MAX_SMB_BMCS; i++) {
+ unsigned int res = pos_reserved_as(i);
+
+ if (res == ((id << 16) | addr)) {
+ /* We have a reserved position, use it. */
+ next_pos = i;
+ break;
+ }
+
+ /* Claim the first unused position */
+ if (!res && (next_pos == -1) && (smb_infos[next_pos] == NULL))
+ next_pos = i;
+ }
+ if (next_pos == -1) {
+ rv = -EBUSY;
+ goto out_err;
+ }
+
+ if (smb_info->supports_pec)
+ smb_info->client.flags |= I2C_CLIENT_PEC;
+
+ rv = i2c_attach_client(&smb_info->client);
+ if (rv) {
+ printk(KERN_ERR
+ "smb_found_one_addr_proc:"
+ " Unable to attach i2c client: error %d\n",
+ rv);
+ goto out_err;
+ }
+
+ smb_info->pos = next_pos;
+ smb_infos[next_pos] = smb_info;
+
+ rv = ipmi_register_smi(&handlers,
+ smb_info,
+ smb_info->ipmi_version_major,
+ smb_info->ipmi_version_minor,
+ slave_addrs[next_pos],
+ &(smb_info->intf));
+ if (rv) {
+ i2c_detach_client(&smb_info->client);
+ smb_infos[next_pos] = NULL;
+ printk(KERN_ERR
+ "ipmi_smb: Unable to register device: error %d\n",
+ rv);
+ goto out_err;
+ }
+
+ return addr;
+
+ out_err:
+ kfree(smb_info);
+ return 0;
+}
+
+static int attach_adapter(struct i2c_adapter *adapter)
+{
+ int id = i2c_adapter_id(adapter);
+
+ if (smb_dbg_probe)
+ printk(KERN_INFO "init_one_smb: Checking SMBus adapter %d:"
+ " %s\n", id, adapter->name);
+
+ if (!(i2c_get_functionality(adapter) & (I2C_FUNC_SMBUS_BLOCK_DATA)))
+ return 0;
+
+ if (smb_dbg_probe)
+ printk(KERN_INFO "init_one_smb: found SMBus adapter:"
+ " %s\n", adapter->name);
+
+ if (!i2c_non_blocking_capable(adapter)) {
+ if (smb_dbg_probe)
+ printk(KERN_INFO
+ "init_one_smb: SMBus adapter not non_blocking capable: %s\n",
+ adapter->name);
+ return 0;
+ }
+
+ i2c_probe(adapter, &smb_address_data, smb_found_addr_proc);
+
+ return 0;
+}
+
+void cleanup_one_smb(struct smb_info *to_clean)
+{
+ int rv;
+
+ if (! to_clean)
+ return;
+
+ /* make sure the driver is not doing anything. */
+ to_clean->stopping = 1;
+ while (to_clean->running)
+ msleep(1);
+
+ rv = ipmi_unregister_smi(to_clean->intf);
+ if (rv) {
+ printk(KERN_ERR
+ "ipmi_smb: Unable to unregister device: errno=%d\n",
+ rv);
+ }
+
+ rv = i2c_detach_client(&to_clean->client);
+ if (rv) {
+ printk(KERN_ERR
+ "ipmi_smb: Unable to detach SMBUS client: errno=%d\n",
+ rv);
+ }
+
+ smb_infos[to_clean->pos] = NULL;
+ kfree(to_clean);
+}
+
+static int detach_client(struct i2c_client *client)
+{
+ struct smb_info *smb_info = i2c_get_clientdata(client);
+
+ cleanup_one_smb(smb_info);
+
+ return(0);
+}
+
+#ifdef CONFIG_ACPI_INTERPRETER
+
+#include <linux/acpi.h>
+
+/*
+ * Defined in the IPMI 2.0 spec.
+ */
+struct SPMITable {
+ s8 Signature[4];
+ u32 Length;
+ u8 Revision;
+ u8 Checksum;
+ s8 OEMID[6];
+ s8 OEMTableID[8];
+ s8 OEMRevision[4];
+ s8 CreatorID[4];
+ s8 CreatorRevision[4];
+ u8 InterfaceType;
+ u8 IPMIlegacy;
+ s16 SpecificationRevision;
+
+ /*
+ * Bit 0 - SCI interrupt supported
+ * Bit 1 - I/O APIC/SAPIC
+ */
+ u8 InterruptType;
+
+ /* If bit 0 of InterruptType is set, then this is the SCI
+ interrupt in the GPEx_STS register. */
+ u8 GPE;
+
+ s16 Reserved;
+
+ /* If bit 1 of InterruptType is set, then this is the I/O
+ APIC/SAPIC interrupt. */
+ u32 GlobalSystemInterrupt;
+
+ /* The actual register address. */
+ struct acpi_generic_address addr;
+
+ u8 UID[4];
+
+ s8 spmi_id[1]; /* A '\0' terminated array starts here. */
+};
+
+static int decode_acpi(int intf_num)
+{
+ acpi_status status;
+ struct SPMITable *spmi;
+
+ status = acpi_get_firmware_table("SPMI", intf_num+1,
+ ACPI_LOGICAL_ADDRESSING,
+ (struct acpi_table_header **) &spmi);
+ if (status != AE_OK)
+ return -ENODEV;
+
+ if (spmi->IPMIlegacy != 1) {
+ printk(KERN_WARNING "IPMI: Bad SPMI legacy %d\n",
+ spmi->IPMIlegacy);
+ return -ENODEV;
+ }
+
+ if (spmi->InterfaceType != 4)
+ return -ENODEV;
+
+ if (spmi->addr.address_space_id != 4) {
+ printk(KERN_WARNING
+ "ipmi_smb: Invalid ACPI SSIF I/O Address type\n");
+ return -EIO;
+ }
+
+ if (addr[intf_num*2+1] == 0) {
+ /* User didn't specify, so use this one. */
+ addr[intf_num*2] = 0; /* Assume adapter 0 */
+ addr[intf_num*2+1] = spmi->addr.address >> 1;
+ printk(KERN_INFO
+ "ipmi_smb: ACPI/SPMI specifies SSIF @ 0x%x\n",
+ addr[intf_num*2+1]);
+ }
+
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_X86
+typedef struct dmi_header
+{
+ u8 type;
+ u8 length;
+ u16 handle;
+} dmi_header_t;
+
+static int decode_dmi(dmi_header_t *dm, int intf_num)
+{
+ u8 *data = (u8 *)dm;
+
+ if (data[0x04] != 4) /* Not SSIF */
+ return -1;
+
+ if (addr[intf_num*2+1] == 0) {
+ /* User didn't specify, so use this one. */
+ addr[intf_num*2] = 0; /* Assume adapter 0 */
+ addr[intf_num*2+1] = data[8] >> 1;
+ printk(KERN_INFO
+ "ipmi_smb: DMI specifies SSIF @ 0x%x\n", data[8] >> 1);
+ }
+
+ if (slave_addrs[intf_num] == 0) {
+ slave_addrs[intf_num] = data[6];
+ printk(KERN_INFO
+ "ipmi_smb: DMI specifies slave address at 0x%x\n",
+ slave_addrs[intf_num]);
+ }
+
+ return -1;
+}
+
+static int dmi_table(u32 base, int len, int num)
+{
+ u8 *buf;
+ struct dmi_header *dm;
+ u8 *data;
+ int i=1;
+ int status=-1;
+ int intf_num = 0;
+
+ buf = ioremap(base, len);
+ if(buf==NULL)
+ return -1;
+
+ data = buf;
+
+ while(i<num && (data - buf) < len)
+ {
+ dm=(dmi_header_t *)data;
+
+ if((data-buf+dm->length) >= len)
+ break;
+
+ if (dm->type == 38) {
+ if (decode_dmi(dm, intf_num) == 0) {
+ intf_num++;
+ if (intf_num >= MAX_SMB_BMCS)
+ break;
+ }
+ }
+
+ data+=dm->length;
+ while((data-buf) < len && (*data || data[1]))
+ data++;
+ data+=2;
+ i++;
+ }
+ iounmap(buf);
+
+ return status;
+}
+
+inline static int dmi_checksum(u8 *buf)
+{
+ u8 sum=0;
+ int a;
+
+ for(a=0; a<15; a++)
+ sum+=buf[a];
+ return (sum==0);
+}
+
+static int dmi_iterator(void)
+{
+ u8 buf[15];
+ u32 fp=0xF0000;
+
+#ifdef CONFIG_SIMNOW
+ return -1;
+#endif
+
+ while(fp < 0xFFFFF)
+ {
+ isa_memcpy_fromio(buf, fp, 15);
+ if(memcmp(buf, "_DMI_", 5)==0 && dmi_checksum(buf))
+ {
+ u16 num=buf[13]<<8|buf[12];
+ u16 len=buf[7]<<8|buf[6];
+ u32 base=buf[11]<<24|buf[10]<<16|buf[9]<<8|buf[8];
+
+ if(dmi_table(base, len, num) == 0)
+ return 0;
+ }
+ fp+=16;
+ }
+
+ return -1;
+}
+#endif
+
+static __init int init_ipmi_smb(void)
+{
+ int i;
+ int rv;
+
+ if (initialized)
+ return 0;
+
+ printk(KERN_INFO "IPMI SMB Interface driver version "
+ IPMI_SMB_VERSION "\n");
+
+#ifdef CONFIG_X86
+ dmi_iterator();
+#endif
+#ifdef CONFIG_ACPI_INTERPRETER
+ for (i=0; i<MAX_SMB_BMCS; i++)
+ decode_acpi(i);
+#endif
+
+ /* build force list from addr list */
+ for (i=0; i<MAX_SMB_BMCS; i++) {
+ if (addr[i*2+1] == 0)
+ break;
+ smb_force_list[i*FORCE_LIST_ENTRIES] = addr[i*2];
+ smb_force_list[i*FORCE_LIST_ENTRIES+1] = addr[i*2+1];
+ }
+ smb_force_list[i*FORCE_LIST_ENTRIES] = I2C_CLIENT_END;
+ smb_force_list[i*FORCE_LIST_ENTRIES+1] = I2C_CLIENT_END;
+
+ /* If the default probing is turned off, then disable the
+ * range scanning. */
+ if (!smb_defaultprobe)
+ normal_i2c_range[0] = I2C_CLIENT_END;
+
+ rv = i2c_add_driver(&smb_i2c_driver);
+ if (!rv)
+ initialized = 1;
+
+ return rv;
+}
+module_init(init_ipmi_smb);
+
+static __exit void cleanup_ipmi_smb(void)
+{
+ int i;
+ int rv;
+
+ if (!initialized)
+ return;
+
+ for (i=0; i<MAX_SMB_BMCS; i++) {
+ cleanup_one_smb(smb_infos[i]);
+ }
+
+ initialized = 0;
+
+ rv = i2c_del_driver(&smb_i2c_driver);
+ if (!rv)
+ initialized = 0;
+}
+module_exit(cleanup_ipmi_smb);
+
+MODULE_AUTHOR("Todd C Davis <todd.c.davis@intel.com>, "
+ "Corey Minyard <minyard@acm.org>");
+MODULE_DESCRIPTION("IPMI system interface driver for management controllers on a SMBus");
+MODULE_LICENSE("GPL");
Index: linux-2.6.11-rc2/drivers/char/ipmi/Kconfig
===================================================================
--- linux-2.6.11-rc2.orig/drivers/char/ipmi/Kconfig
+++ linux-2.6.11-rc2/drivers/char/ipmi/Kconfig
@@ -51,6 +51,17 @@
Currently, only KCS and SMIC are supported. If
you are using IPMI, you should probably say "y" here.
+config IPMI_SMB
+ tristate 'IPMI SMBus handler'
+ depends on IPMI_HANDLER && I2C
+ help
+ Provides a driver for a SMBus interface to a BMC, meaning that you
+ have a driver that must be accessed over an I2C bus instead of a
+ standard interface. This module requires I2C support. Note that
+ you might need some I2C changes if CONFIG_IPMI_PANIC_EVENT is
+ enabled along with this, so the I2C driver knows to run to
+ completion during sending a panic event.
+
config IPMI_WATCHDOG
tristate 'IPMI Watchdog Timer'
depends on IPMI_HANDLER
Index: linux-2.6.11-rc2/drivers/char/ipmi/Makefile
===================================================================
--- linux-2.6.11-rc2.orig/drivers/char/ipmi/Makefile
+++ linux-2.6.11-rc2/drivers/char/ipmi/Makefile
@@ -7,6 +7,7 @@
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_SMB) += ipmi_smb.o
obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o
obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o
Index: linux-2.6.11-rc2/Documentation/IPMI.txt
===================================================================
--- linux-2.6.11-rc2.orig/Documentation/IPMI.txt
+++ linux-2.6.11-rc2/Documentation/IPMI.txt
@@ -417,14 +417,15 @@
----------------
The SMBus driver allows up to 4 SMBus devices to be configured in the
-system. By default, the driver will register any SMBus interfaces it finds
-in the I2C address range of 0x20 to 0x4f on any adapter. You can change this
+system. By default, the driver will only register with something it
+finds in DMI or ACPI tables. You can change this
at module load time (for a module) with:
modprobe ipmi_smb.o
addr=<adapter1>,<i2caddr1>[,<adapter2>,<i2caddr2>[,...]]
dbg=<flags1>,<flags2>...
- [defaultprobe=0] [dbg_probe=1]
+ slave_addrs=<addr1>,<addr2>,...
+ [defaultprobe=1] [dbg_probe=1]
The addresses are specified in pairs, the first is the adapter ID and the
second is the I2C address on that adapter.
@@ -432,20 +433,26 @@
The debug flags are bit flags for each BMC found, they are:
IPMI messages: 1, driver state: 2, timing: 4, I2C probe: 8
-Setting smb_defaultprobe to zero disabled the default probing of SMBus
-interfaces at address range 0x20 to 0x4f. This means that only the
-BMCs specified on the smb_addr line will be detected.
+Setting defaultprobe to one will enable probing of SMBus interfaces at
+address ranging from 0x20 to 0x4f. This means any BMCs found in that
+area on any adapter will be detected. Note that if something else is
+in this address range, the scan operation can clobber it, so be
+careful.
-Setting smb_dbg_probe to 1 will enable debugging of the probing and
+Setting dbg_probe to 1 will enable debugging of the probing and
detection process for BMCs on the SMBusses.
-Discovering the IPMI compilant BMC on the SMBus can cause devices
-on the I2C bus to fail. The SMBus driver writes a "Get Device ID" IPMI
+The slave_addrs specifies the IPMI address of the local BMC. This is
+usually 0x20 and the driver defaults to that, but in case it's not, it
+can be specified when the driver starts up.
+
+Discovering the IPMI compilant BMC on the SMBus can cause devices on
+the I2C bus to fail. The SMBus driver writes a "Get Device ID" IPMI
message as a block write to the I2C bus and waits for a response.
-This action can be detrimental to some I2C devices. It is highly recommended
-that the known I2c address be given to the SMBus driver in the smb_addr
-parameter. The default adrress range will not be used when a smb_addr
-parameter is provided.
+This action can be detrimental to some I2C devices. It is highly
+recommended that the known I2C address be given to the SMBus driver in
+the smb_addr parameter unless you have DMI or ACPI data to tell the
+driver what to use.
When compiled into the kernel, the addresses can be specified on the
kernel command line as:
@@ -453,6 +460,7 @@
ipmb_smb.addr=<adapter1>,<i2caddr1>[,<adapter2>,<i2caddr2>[,...]]
ipmi_smb.dbg=<flags1>,<flags2>...
ipmi_smb.defaultprobe=0 ipmi_smb.dbg_probe=1
+ ipmi_smb.slave_addrs=<addr1>,<addr2>,...
These are the same options as on the module command line.
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2005-02-01 22:43 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2005-02-01 22:33 [PATCH] Add the IPMI SMBus driver Corey Minyard
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).