aboutsummaryrefslogtreecommitdiff
path: root/drivers/char/ipmi/ipmi_si_intf.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/ipmi/ipmi_si_intf.c')
-rw-r--r--drivers/char/ipmi/ipmi_si_intf.c2359
1 files changed, 2359 insertions, 0 deletions
diff --git a/drivers/char/ipmi/ipmi_si_intf.c b/drivers/char/ipmi/ipmi_si_intf.c
new file mode 100644
index 00000000000..29de259a981
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_si_intf.c
@@ -0,0 +1,2359 @@
+/*
+ * ipmi_si.c
+ *
+ * The interface to the IPMI driver for the system interfaces (KCS, SMIC,
+ * BT).
+ *
+ * Author: MontaVista Software, Inc.
+ * Corey Minyard <minyard@mvista.com>
+ * source@mvista.com
+ *
+ * Copyright 2002 MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * This file holds the "policy" for the interface to the SMI state
+ * machine. It does the configuration, handles timers and interrupts,
+ * and drives the real SMI state machine.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <asm/system.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/errno.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+#include <asm/irq.h>
+#ifdef CONFIG_HIGH_RES_TIMERS
+#include <linux/hrtime.h>
+# if defined(schedule_next_int)
+/* Old high-res timer code, do translations. */
+# define get_arch_cycles(a) quick_update_jiffies_sub(a)
+# define arch_cycles_per_jiffy cycles_per_jiffies
+# endif
+static inline void add_usec_to_timer(struct timer_list *t, long v)
+{
+ t->sub_expires += nsec_to_arch_cycle(v * 1000);
+ while (t->sub_expires >= arch_cycles_per_jiffy)
+ {
+ t->expires++;
+ t->sub_expires -= arch_cycles_per_jiffy;
+ }
+}
+#endif
+#include <linux/interrupt.h>
+#include <linux/rcupdate.h>
+#include <linux/ipmi_smi.h>
+#include <asm/io.h>
+#include "ipmi_si_sm.h"
+#include <linux/init.h>
+
+#define IPMI_SI_VERSION "v33"
+
+/* Measure times between events in the driver. */
+#undef DEBUG_TIMING
+
+/* Call every 10 ms. */
+#define SI_TIMEOUT_TIME_USEC 10000
+#define SI_USEC_PER_JIFFY (1000000/HZ)
+#define SI_TIMEOUT_JIFFIES (SI_TIMEOUT_TIME_USEC/SI_USEC_PER_JIFFY)
+#define SI_SHORT_TIMEOUT_USEC 250 /* .25ms when the SM request a
+ short timeout */
+
+enum si_intf_state {
+ SI_NORMAL,
+ SI_GETTING_FLAGS,
+ SI_GETTING_EVENTS,
+ SI_CLEARING_FLAGS,
+ SI_CLEARING_FLAGS_THEN_SET_IRQ,
+ SI_GETTING_MESSAGES,
+ SI_ENABLE_INTERRUPTS1,
+ SI_ENABLE_INTERRUPTS2
+ /* FIXME - add watchdog stuff. */
+};
+
+enum si_type {
+ SI_KCS, SI_SMIC, SI_BT
+};
+
+struct smi_info
+{
+ ipmi_smi_t intf;
+ struct si_sm_data *si_sm;
+ struct si_sm_handlers *handlers;
+ enum si_type si_type;
+ spinlock_t si_lock;
+ spinlock_t msg_lock;
+ struct list_head xmit_msgs;
+ struct list_head hp_xmit_msgs;
+ struct ipmi_smi_msg *curr_msg;
+ enum si_intf_state si_state;
+
+ /* Used to handle the various types of I/O that can occur with
+ IPMI */
+ struct si_sm_io io;
+ int (*io_setup)(struct smi_info *info);
+ void (*io_cleanup)(struct smi_info *info);
+ int (*irq_setup)(struct smi_info *info);
+ void (*irq_cleanup)(struct smi_info *info);
+ unsigned int io_size;
+
+ /* Flags from the last GET_MSG_FLAGS command, used when an ATTN
+ is set to hold the flags until we are done handling everything
+ from the flags. */
+#define RECEIVE_MSG_AVAIL 0x01
+#define EVENT_MSG_BUFFER_FULL 0x02
+#define WDT_PRE_TIMEOUT_INT 0x08
+ unsigned char msg_flags;
+
+ /* If set to true, this will request events the next time the
+ state machine is idle. */
+ atomic_t req_events;
+
+ /* If true, run the state machine to completion on every send
+ call. Generally used after a panic to make sure stuff goes
+ out. */
+ int run_to_completion;
+
+ /* The I/O port of an SI interface. */
+ int port;
+
+ /* The space between start addresses of the two ports. For
+ instance, if the first port is 0xca2 and the spacing is 4, then
+ the second port is 0xca6. */
+ unsigned int spacing;
+
+ /* zero if no irq; */
+ int irq;
+
+ /* The timer for this si. */
+ struct timer_list si_timer;
+
+ /* The time (in jiffies) the last timeout occurred at. */
+ unsigned long last_timeout_jiffies;
+
+ /* Used to gracefully stop the timer without race conditions. */
+ volatile int stop_operation;
+ volatile int timer_stopped;
+
+ /* The driver will disable interrupts when it gets into a
+ situation where it cannot handle messages due to lack of
+ memory. Once that situation clears up, it will re-enable
+ interrupts. */
+ int interrupt_disabled;
+
+ unsigned char ipmi_si_dev_rev;
+ unsigned char ipmi_si_fw_rev_major;
+ unsigned char ipmi_si_fw_rev_minor;
+ unsigned char ipmi_version_major;
+ unsigned char ipmi_version_minor;
+
+ /* Slave address, could be reported from DMI. */
+ unsigned char slave_addr;
+
+ /* Counters and things for the proc filesystem. */
+ spinlock_t count_lock;
+ unsigned long short_timeouts;
+ unsigned long long_timeouts;
+ unsigned long timeout_restarts;
+ unsigned long idles;
+ unsigned long interrupts;
+ unsigned long attentions;
+ unsigned long flag_fetches;
+ unsigned long hosed_count;
+ unsigned long complete_transactions;
+ unsigned long events;
+ unsigned long watchdog_pretimeouts;
+ unsigned long incoming_messages;
+};
+
+static void si_restart_short_timer(struct smi_info *smi_info);
+
+static void deliver_recv_msg(struct smi_info *smi_info,
+ struct ipmi_smi_msg *msg)
+{
+ /* Deliver the message to the upper layer with the lock
+ released. */
+ spin_unlock(&(smi_info->si_lock));
+ ipmi_smi_msg_received(smi_info->intf, msg);
+ spin_lock(&(smi_info->si_lock));
+}
+
+static void return_hosed_msg(struct smi_info *smi_info)
+{
+ struct ipmi_smi_msg *msg = smi_info->curr_msg;
+
+ /* Make it a reponse */
+ msg->rsp[0] = msg->data[0] | 4;
+ msg->rsp[1] = msg->data[1];
+ msg->rsp[2] = 0xFF; /* Unknown error. */
+ msg->rsp_size = 3;
+
+ smi_info->curr_msg = NULL;
+ deliver_recv_msg(smi_info, msg);
+}
+
+static enum si_sm_result start_next_msg(struct smi_info *smi_info)
+{
+ int rv;
+ struct list_head *entry = NULL;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+#endif
+
+ /* No need to save flags, we aleady have interrupts off and we
+ already hold the SMI lock. */
+ spin_lock(&(smi_info->msg_lock));
+
+ /* Pick the high priority queue first. */
+ if (! list_empty(&(smi_info->hp_xmit_msgs))) {
+ entry = smi_info->hp_xmit_msgs.next;
+ } else if (! list_empty(&(smi_info->xmit_msgs))) {
+ entry = smi_info->xmit_msgs.next;
+ }
+
+ if (!entry) {
+ smi_info->curr_msg = NULL;
+ rv = SI_SM_IDLE;
+ } else {
+ int err;
+
+ list_del(entry);
+ smi_info->curr_msg = list_entry(entry,
+ struct ipmi_smi_msg,
+ link);
+#ifdef DEBUG_TIMING
+ do_gettimeofday(&t);
+ printk("**Start2: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+ err = smi_info->handlers->start_transaction(
+ smi_info->si_sm,
+ smi_info->curr_msg->data,
+ smi_info->curr_msg->data_size);
+ if (err) {
+ return_hosed_msg(smi_info);
+ }
+
+ rv = SI_SM_CALL_WITHOUT_DELAY;
+ }
+ spin_unlock(&(smi_info->msg_lock));
+
+ return rv;
+}
+
+static void start_enable_irq(struct smi_info *smi_info)
+{
+ unsigned char msg[2];
+
+ /* If we are enabling interrupts, we have to tell the
+ BMC to use them. */
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_GET_BMC_GLOBAL_ENABLES_CMD;
+
+ smi_info->handlers->start_transaction(smi_info->si_sm, msg, 2);
+ smi_info->si_state = SI_ENABLE_INTERRUPTS1;
+}
+
+static void start_clear_flags(struct smi_info *smi_info)
+{
+ unsigned char msg[3];
+
+ /* Make sure the watchdog pre-timeout flag is not set at startup. */
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_CLEAR_MSG_FLAGS_CMD;
+ msg[2] = WDT_PRE_TIMEOUT_INT;
+
+ smi_info->handlers->start_transaction(smi_info->si_sm, msg, 3);
+ smi_info->si_state = SI_CLEARING_FLAGS;
+}
+
+/* When we have a situtaion where we run out of memory and cannot
+ allocate messages, we just leave them in the BMC and run the system
+ polled until we can allocate some memory. Once we have some
+ memory, we will re-enable the interrupt. */
+static inline void disable_si_irq(struct smi_info *smi_info)
+{
+ if ((smi_info->irq) && (!smi_info->interrupt_disabled)) {
+ disable_irq_nosync(smi_info->irq);
+ smi_info->interrupt_disabled = 1;
+ }
+}
+
+static inline void enable_si_irq(struct smi_info *smi_info)
+{
+ if ((smi_info->irq) && (smi_info->interrupt_disabled)) {
+ enable_irq(smi_info->irq);
+ smi_info->interrupt_disabled = 0;
+ }
+}
+
+static void handle_flags(struct smi_info *smi_info)
+{
+ if (smi_info->msg_flags & WDT_PRE_TIMEOUT_INT) {
+ /* Watchdog pre-timeout */
+ spin_lock(&smi_info->count_lock);
+ smi_info->watchdog_pretimeouts++;
+ spin_unlock(&smi_info->count_lock);
+
+ start_clear_flags(smi_info);
+ smi_info->msg_flags &= ~WDT_PRE_TIMEOUT_INT;
+ spin_unlock(&(smi_info->si_lock));
+ ipmi_smi_watchdog_pretimeout(smi_info->intf);
+ spin_lock(&(smi_info->si_lock));
+ } else if (smi_info->msg_flags & RECEIVE_MSG_AVAIL) {
+ /* Messages available. */
+ smi_info->curr_msg = ipmi_alloc_smi_msg();
+ if (!smi_info->curr_msg) {
+ disable_si_irq(smi_info);
+ smi_info->si_state = SI_NORMAL;
+ return;
+ }
+ enable_si_irq(smi_info);
+
+ smi_info->curr_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ smi_info->curr_msg->data[1] = IPMI_GET_MSG_CMD;
+ smi_info->curr_msg->data_size = 2;
+
+ smi_info->handlers->start_transaction(
+ smi_info->si_sm,
+ smi_info->curr_msg->data,
+ smi_info->curr_msg->data_size);
+ smi_info->si_state = SI_GETTING_MESSAGES;
+ } else if (smi_info->msg_flags & EVENT_MSG_BUFFER_FULL) {
+ /* Events available. */
+ smi_info->curr_msg = ipmi_alloc_smi_msg();
+ if (!smi_info->curr_msg) {
+ disable_si_irq(smi_info);
+ smi_info->si_state = SI_NORMAL;
+ return;
+ }
+ enable_si_irq(smi_info);
+
+ smi_info->curr_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ smi_info->curr_msg->data[1] = IPMI_READ_EVENT_MSG_BUFFER_CMD;
+ smi_info->curr_msg->data_size = 2;
+
+ smi_info->handlers->start_transaction(
+ smi_info->si_sm,
+ smi_info->curr_msg->data,
+ smi_info->curr_msg->data_size);
+ smi_info->si_state = SI_GETTING_EVENTS;
+ } else {
+ smi_info->si_state = SI_NORMAL;
+ }
+}
+
+static void handle_transaction_done(struct smi_info *smi_info)
+{
+ struct ipmi_smi_msg *msg;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+
+ do_gettimeofday(&t);
+ printk("**Done: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+ switch (smi_info->si_state) {
+ case SI_NORMAL:
+ if (!smi_info->curr_msg)
+ break;
+
+ smi_info->curr_msg->rsp_size
+ = smi_info->handlers->get_result(
+ smi_info->si_sm,
+ smi_info->curr_msg->rsp,
+ IPMI_MAX_MSG_LENGTH);
+
+ /* Do this here becase deliver_recv_msg() releases the
+ lock, and a new message can be put in during the
+ time the lock is released. */
+ msg = smi_info->curr_msg;
+ smi_info->curr_msg = NULL;
+ deliver_recv_msg(smi_info, msg);
+ break;
+
+ case SI_GETTING_FLAGS:
+ {
+ unsigned char msg[4];
+ unsigned int len;
+
+ /* We got the flags from the SMI, now handle them. */
+ len = smi_info->handlers->get_result(smi_info->si_sm, msg, 4);
+ if (msg[2] != 0) {
+ /* Error fetching flags, just give up for
+ now. */
+ smi_info->si_state = SI_NORMAL;
+ } else if (len < 4) {
+ /* Hmm, no flags. That's technically illegal, but
+ don't use uninitialized data. */
+ smi_info->si_state = SI_NORMAL;
+ } else {
+ smi_info->msg_flags = msg[3];
+ handle_flags(smi_info);
+ }
+ break;
+ }
+
+ case SI_CLEARING_FLAGS:
+ case SI_CLEARING_FLAGS_THEN_SET_IRQ:
+ {
+ unsigned char msg[3];
+
+ /* We cleared the flags. */
+ smi_info->handlers->get_result(smi_info->si_sm, msg, 3);
+ if (msg[2] != 0) {
+ /* Error clearing flags */
+ printk(KERN_WARNING
+ "ipmi_si: Error clearing flags: %2.2x\n",
+ msg[2]);
+ }
+ if (smi_info->si_state == SI_CLEARING_FLAGS_THEN_SET_IRQ)
+ start_enable_irq(smi_info);
+ else
+ smi_info->si_state = SI_NORMAL;
+ break;
+ }
+
+ case SI_GETTING_EVENTS:
+ {
+ smi_info->curr_msg->rsp_size
+ = smi_info->handlers->get_result(
+ smi_info->si_sm,
+ smi_info->curr_msg->rsp,
+ IPMI_MAX_MSG_LENGTH);
+
+ /* Do this here becase deliver_recv_msg() releases the
+ lock, and a new message can be put in during the
+ time the lock is released. */
+ msg = smi_info->curr_msg;
+ smi_info->curr_msg = NULL;
+ if (msg->rsp[2] != 0) {
+ /* Error getting event, probably done. */
+ msg->done(msg);
+
+ /* Take off the event flag. */
+ smi_info->msg_flags &= ~EVENT_MSG_BUFFER_FULL;
+ handle_flags(smi_info);
+ } else {
+ spin_lock(&smi_info->count_lock);
+ smi_info->events++;
+ spin_unlock(&smi_info->count_lock);
+
+ /* Do this before we deliver the message
+ because delivering the message releases the
+ lock and something else can mess with the
+ state. */
+ handle_flags(smi_info);
+
+ deliver_recv_msg(smi_info, msg);
+ }
+ break;
+ }
+
+ case SI_GETTING_MESSAGES:
+ {
+ smi_info->curr_msg->rsp_size
+ = smi_info->handlers->get_result(
+ smi_info->si_sm,
+ smi_info->curr_msg->rsp,
+ IPMI_MAX_MSG_LENGTH);
+
+ /* Do this here becase deliver_recv_msg() releases the
+ lock, and a new message can be put in during the
+ time the lock is released. */
+ msg = smi_info->curr_msg;
+ smi_info->curr_msg = NULL;
+ if (msg->rsp[2] != 0) {
+ /* Error getting event, probably done. */
+ msg->done(msg);
+
+ /* Take off the msg flag. */
+ smi_info->msg_flags &= ~RECEIVE_MSG_AVAIL;
+ handle_flags(smi_info);
+ } else {
+ spin_lock(&smi_info->count_lock);
+ smi_info->incoming_messages++;
+ spin_unlock(&smi_info->count_lock);
+
+ /* Do this before we deliver the message
+ because delivering the message releases the
+ lock and something else can mess with the
+ state. */
+ handle_flags(smi_info);
+
+ deliver_recv_msg(smi_info, msg);
+ }
+ break;
+ }
+
+ case SI_ENABLE_INTERRUPTS1:
+ {
+ unsigned char msg[4];
+
+ /* We got the flags from the SMI, now handle them. */
+ smi_info->handlers->get_result(smi_info->si_sm, msg, 4);
+ if (msg[2] != 0) {
+ printk(KERN_WARNING
+ "ipmi_si: Could not enable interrupts"
+ ", failed get, using polled mode.\n");
+ smi_info->si_state = SI_NORMAL;
+ } else {
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_SET_BMC_GLOBAL_ENABLES_CMD;
+ msg[2] = msg[3] | 1; /* enable msg queue int */
+ smi_info->handlers->start_transaction(
+ smi_info->si_sm, msg, 3);
+ smi_info->si_state = SI_ENABLE_INTERRUPTS2;
+ }
+ break;
+ }
+
+ case SI_ENABLE_INTERRUPTS2:
+ {
+ unsigned char msg[4];
+
+ /* We got the flags from the SMI, now handle them. */
+ smi_info->handlers->get_result(smi_info->si_sm, msg, 4);
+ if (msg[2] != 0) {
+ printk(KERN_WARNING
+ "ipmi_si: Could not enable interrupts"
+ ", failed set, using polled mode.\n");
+ }
+ smi_info->si_state = SI_NORMAL;
+ break;
+ }
+ }
+}
+
+/* Called on timeouts and events. Timeouts should pass the elapsed
+ time, interrupts should pass in zero. */
+static enum si_sm_result smi_event_handler(struct smi_info *smi_info,
+ int time)
+{
+ enum si_sm_result si_sm_result;
+
+ restart:
+ /* There used to be a loop here that waited a little while
+ (around 25us) before giving up. That turned out to be
+ pointless, the minimum delays I was seeing were in the 300us
+ range, which is far too long to wait in an interrupt. So
+ we just run until the state machine tells us something
+ happened or it needs a delay. */
+ si_sm_result = smi_info->handlers->event(smi_info->si_sm, time);
+ time = 0;
+ while (si_sm_result == SI_SM_CALL_WITHOUT_DELAY)
+ {
+ si_sm_result = smi_info->handlers->event(smi_info->si_sm, 0);
+ }
+
+ if (si_sm_result == SI_SM_TRANSACTION_COMPLETE)
+ {
+ spin_lock(&smi_info->count_lock);
+ smi_info->complete_transactions++;
+ spin_unlock(&smi_info->count_lock);
+
+ handle_transaction_done(smi_info);
+ si_sm_result = smi_info->handlers->event(smi_info->si_sm, 0);
+ }
+ else if (si_sm_result == SI_SM_HOSED)
+ {
+ spin_lock(&smi_info->count_lock);
+ smi_info->hosed_count++;
+ spin_unlock(&smi_info->count_lock);
+
+ /* Do the before return_hosed_msg, because that
+ releases the lock. */
+ smi_info->si_state = SI_NORMAL;
+ if (smi_info->curr_msg != NULL) {
+ /* If we were handling a user message, format
+ a response to send to the upper layer to
+ tell it about the error. */
+ return_hosed_msg(smi_info);
+ }
+ si_sm_result = smi_info->handlers->event(smi_info->si_sm, 0);
+ }
+
+ /* We prefer handling attn over new messages. */
+ if (si_sm_result == SI_SM_ATTN)
+ {
+ unsigned char msg[2];
+
+ spin_lock(&smi_info->count_lock);
+ smi_info->attentions++;
+ spin_unlock(&smi_info->count_lock);
+
+ /* Got a attn, send down a get message flags to see
+ what's causing it. It would be better to handle
+ this in the upper layer, but due to the way
+ interrupts work with the SMI, that's not really
+ possible. */
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_GET_MSG_FLAGS_CMD;
+
+ smi_info->handlers->start_transaction(
+ smi_info->si_sm, msg, 2);
+ smi_info->si_state = SI_GETTING_FLAGS;
+ goto restart;
+ }
+
+ /* If we are currently idle, try to start the next message. */
+ if (si_sm_result == SI_SM_IDLE) {
+ spin_lock(&smi_info->count_lock);
+ smi_info->idles++;
+ spin_unlock(&smi_info->count_lock);
+
+ si_sm_result = start_next_msg(smi_info);
+ if (si_sm_result != SI_SM_IDLE)
+ goto restart;
+ }
+
+ if ((si_sm_result == SI_SM_IDLE)
+ && (atomic_read(&smi_info->req_events)))
+ {
+ /* We are idle and the upper layer requested that I fetch
+ events, so do so. */
+ unsigned char msg[2];
+
+ spin_lock(&smi_info->count_lock);
+ smi_info->flag_fetches++;
+ spin_unlock(&smi_info->count_lock);
+
+ atomic_set(&smi_info->req_events, 0);
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_GET_MSG_FLAGS_CMD;
+
+ smi_info->handlers->start_transaction(
+ smi_info->si_sm, msg, 2);
+ smi_info->si_state = SI_GETTING_FLAGS;
+ goto restart;
+ }
+
+ return si_sm_result;
+}
+
+static void sender(void *send_info,
+ struct ipmi_smi_msg *msg,
+ int priority)
+{
+ struct smi_info *smi_info = send_info;
+ enum si_sm_result result;
+ unsigned long flags;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+#endif
+
+ spin_lock_irqsave(&(smi_info->msg_lock), flags);
+#ifdef DEBUG_TIMING
+ do_gettimeofday(&t);
+ printk("**Enqueue: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+
+ if (smi_info->run_to_completion) {
+ /* If we are running to completion, then throw it in
+ the list and run transactions until everything is
+ clear. Priority doesn't matter here. */
+ list_add_tail(&(msg->link), &(smi_info->xmit_msgs));
+
+ /* We have to release the msg lock and claim the smi
+ lock in this case, because of race conditions. */
+ spin_unlock_irqrestore(&(smi_info->msg_lock), flags);
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+ result = smi_event_handler(smi_info, 0);
+ while (result != SI_SM_IDLE) {
+ udelay(SI_SHORT_TIMEOUT_USEC);
+ result = smi_event_handler(smi_info,
+ SI_SHORT_TIMEOUT_USEC);
+ }
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+ return;
+ } else {
+ if (priority > 0) {
+ list_add_tail(&(msg->link), &(smi_info->hp_xmit_msgs));
+ } else {
+ list_add_tail(&(msg->link), &(smi_info->xmit_msgs));
+ }
+ }
+ spin_unlock_irqrestore(&(smi_info->msg_lock), flags);
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+ if ((smi_info->si_state == SI_NORMAL)
+ && (smi_info->curr_msg == NULL))
+ {
+ start_next_msg(smi_info);
+ si_restart_short_timer(smi_info);
+ }
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+}
+
+static void set_run_to_completion(void *send_info, int i_run_to_completion)
+{
+ struct smi_info *smi_info = send_info;
+ enum si_sm_result result;
+ unsigned long flags;
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+
+ smi_info->run_to_completion = i_run_to_completion;
+ if (i_run_to_completion) {
+ result = smi_event_handler(smi_info, 0);
+ while (result != SI_SM_IDLE) {
+ udelay(SI_SHORT_TIMEOUT_USEC);
+ result = smi_event_handler(smi_info,
+ SI_SHORT_TIMEOUT_USEC);
+ }
+ }
+
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+}
+
+static void poll(void *send_info)
+{
+ struct smi_info *smi_info = send_info;
+
+ smi_event_handler(smi_info, 0);
+}
+
+static void request_events(void *send_info)
+{
+ struct smi_info *smi_info = send_info;
+
+ atomic_set(&smi_info->req_events, 1);
+}
+
+static int initialized = 0;
+
+/* Must be called with interrupts off and with the si_lock held. */
+static void si_restart_short_timer(struct smi_info *smi_info)
+{
+#if defined(CONFIG_HIGH_RES_TIMERS)
+ unsigned long flags;
+ unsigned long jiffies_now;
+
+ if (del_timer(&(smi_info->si_timer))) {
+ /* If we don't delete the timer, then it will go off
+ immediately, anyway. So we only process if we
+ actually delete the timer. */
+
+ /* We already have irqsave on, so no need for it
+ here. */
+ read_lock(&xtime_lock);
+ jiffies_now = jiffies;
+ smi_info->si_timer.expires = jiffies_now;
+ smi_info->si_timer.sub_expires = get_arch_cycles(jiffies_now);
+
+ add_usec_to_timer(&smi_info->si_timer, SI_SHORT_TIMEOUT_USEC);
+
+ add_timer(&(smi_info->si_timer));
+ spin_lock_irqsave(&smi_info->count_lock, flags);
+ smi_info->timeout_restarts++;
+ spin_unlock_irqrestore(&smi_info->count_lock, flags);
+ }
+#endif
+}
+
+static void smi_timeout(unsigned long data)
+{
+ struct smi_info *smi_info = (struct smi_info *) data;
+ enum si_sm_result smi_result;
+ unsigned long flags;
+ unsigned long jiffies_now;
+ unsigned long time_diff;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+#endif
+
+ if (smi_info->stop_operation) {
+ smi_info->timer_stopped = 1;
+ return;
+ }
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+#ifdef DEBUG_TIMING
+ do_gettimeofday(&t);
+ printk("**Timer: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+ jiffies_now = jiffies;
+ time_diff = ((jiffies_now - smi_info->last_timeout_jiffies)
+ * SI_USEC_PER_JIFFY);
+ smi_result = smi_event_handler(smi_info, time_diff);
+
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+
+ smi_info->last_timeout_jiffies = jiffies_now;
+
+ if ((smi_info->irq) && (! smi_info->interrupt_disabled)) {
+ /* Running with interrupts, only do long timeouts. */
+ smi_info->si_timer.expires = jiffies + SI_TIMEOUT_JIFFIES;
+ spin_lock_irqsave(&smi_info->count_lock, flags);
+ smi_info->long_timeouts++;
+ spin_unlock_irqrestore(&smi_info->count_lock, flags);
+ goto do_add_timer;
+ }
+
+ /* If the state machine asks for a short delay, then shorten
+ the timer timeout. */
+ if (smi_result == SI_SM_CALL_WITH_DELAY) {
+ spin_lock_irqsave(&smi_info->count_lock, flags);
+ smi_info->short_timeouts++;
+ spin_unlock_irqrestore(&smi_info->count_lock, flags);
+#if defined(CONFIG_HIGH_RES_TIMERS)
+ read_lock(&xtime_lock);
+ smi_info->si_timer.expires = jiffies;
+ smi_info->si_timer.sub_expires
+ = get_arch_cycles(smi_info->si_timer.expires);
+ read_unlock(&xtime_lock);
+ add_usec_to_timer(&smi_info->si_timer, SI_SHORT_TIMEOUT_USEC);
+#else
+ smi_info->si_timer.expires = jiffies + 1;
+#endif
+ } else {
+ spin_lock_irqsave(&smi_info->count_lock, flags);
+ smi_info->long_timeouts++;
+ spin_unlock_irqrestore(&smi_info->count_lock, flags);
+ smi_info->si_timer.expires = jiffies + SI_TIMEOUT_JIFFIES;
+#if defined(CONFIG_HIGH_RES_TIMERS)
+ smi_info->si_timer.sub_expires = 0;
+#endif
+ }
+
+ do_add_timer:
+ add_timer(&(smi_info->si_timer));
+}
+
+static irqreturn_t si_irq_handler(int irq, void *data, struct pt_regs *regs)
+{
+ struct smi_info *smi_info = data;
+ unsigned long flags;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+#endif
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+
+ spin_lock(&smi_info->count_lock);
+ smi_info->interrupts++;
+ spin_unlock(&smi_info->count_lock);
+
+ if (smi_info->stop_operation)
+ goto out;
+
+#ifdef DEBUG_TIMING
+ do_gettimeofday(&t);
+ printk("**Interrupt: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+ smi_event_handler(smi_info, 0);
+ out:
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+ return IRQ_HANDLED;
+}
+
+static struct ipmi_smi_handlers handlers =
+{
+ .owner = THIS_MODULE,
+ .sender = sender,
+ .request_events = request_events,
+ .set_run_to_completion = set_run_to_completion,
+ .poll = poll,
+};
+
+/* There can be 4 IO ports passed in (with or without IRQs), 4 addresses,
+ a default IO port, and 1 ACPI/SPMI address. That sets SI_MAX_DRIVERS */
+
+#define SI_MAX_PARMS 4
+#define SI_MAX_DRIVERS ((SI_MAX_PARMS * 2) + 2)
+static struct smi_info *smi_infos[SI_MAX_DRIVERS] =
+{ NULL, NULL, NULL, NULL };
+
+#define DEVICE_NAME "ipmi_si"
+
+#define DEFAULT_KCS_IO_PORT 0xca2
+#define DEFAULT_SMIC_IO_PORT 0xca9
+#define DEFAULT_BT_IO_PORT 0xe4
+#define DEFAULT_REGSPACING 1
+
+static int si_trydefaults = 1;
+static char *si_type[SI_MAX_PARMS];
+#define MAX_SI_TYPE_STR 30
+static char si_type_str[MAX_SI_TYPE_STR];
+static unsigned long addrs[SI_MAX_PARMS];
+static int num_addrs;
+static unsigned int ports[SI_MAX_PARMS];
+static int num_ports;
+static int irqs[SI_MAX_PARMS];
+static int num_irqs;
+static int regspacings[SI_MAX_PARMS];
+static int num_regspacings = 0;
+static int regsizes[SI_MAX_PARMS];
+static int num_regsizes = 0;
+static int regshifts[SI_MAX_PARMS];
+static int num_regshifts = 0;
+static int slave_addrs[SI_MAX_PARMS];
+static int num_slave_addrs = 0;
+
+
+module_param_named(trydefaults, si_trydefaults, bool, 0);
+MODULE_PARM_DESC(trydefaults, "Setting this to 'false' will disable the"
+ " default scan of the KCS and SMIC interface at the standard"
+ " address");
+module_param_string(type, si_type_str, MAX_SI_TYPE_STR, 0);
+MODULE_PARM_DESC(type, "Defines the type of each interface, each"
+ " interface separated by commas. The types are 'kcs',"
+ " 'smic', and 'bt'. For example si_type=kcs,bt will set"
+ " the first interface to kcs and the second to bt");
+module_param_array(addrs, long, &num_addrs, 0);
+MODULE_PARM_DESC(addrs, "Sets the memory address of each interface, the"
+ " addresses separated by commas. Only use if an interface"
+ " is in memory. Otherwise, set it to zero or leave"
+ " it blank.");
+module_param_array(ports, int, &num_ports, 0);
+MODULE_PARM_DESC(ports, "Sets the port address of each interface, the"
+ " addresses separated by commas. Only use if an interface"
+ " is a port. Otherwise, set it to zero or leave"
+ " it blank.");
+module_param_array(irqs, int, &num_irqs, 0);
+MODULE_PARM_DESC(irqs, "Sets the interrupt of each interface, the"
+ " addresses separated by commas. Only use if an interface"
+ " has an interrupt. Otherwise, set it to zero or leave"
+ " it blank.");
+module_param_array(regspacings, int, &num_regspacings, 0);
+MODULE_PARM_DESC(regspacings, "The number of bytes between the start address"
+ " and each successive register used by the interface. For"
+ " instance, if the start address is 0xca2 and the spacing"
+ " is 2, then the second address is at 0xca4. Defaults"
+ " to 1.");
+module_param_array(regsizes, int, &num_regsizes, 0);
+MODULE_PARM_DESC(regsizes, "The size of the specific IPMI register in bytes."
+ " This should generally be 1, 2, 4, or 8 for an 8-bit,"
+ " 16-bit, 32-bit, or 64-bit register. Use this if you"
+ " the 8-bit IPMI register has to be read from a larger"
+ " register.");
+module_param_array(regshifts, int, &num_regshifts, 0);
+MODULE_PARM_DESC(regshifts, "The amount to shift the data read from the."
+ " IPMI register, in bits. For instance, if the data"
+ " is read from a 32-bit word and the IPMI data is in"
+ " bit 8-15, then the shift would be 8");
+module_param_array(slave_addrs, int, &num_slave_addrs, 0);
+MODULE_PARM_DESC(slave_addrs, "Set the default IPMB slave address for"
+ " the controller. Normally this is 0x20, but can be"
+ " overridden by this parm. This is an array indexed"
+ " by interface number.");
+
+
+#define IPMI_MEM_ADDR_SPACE 1
+#define IPMI_IO_ADDR_SPACE 2
+
+#if defined(CONFIG_ACPI_INTERPRETER) || defined(CONFIG_X86) || defined(CONFIG_PCI)
+static int is_new_interface(int intf, u8 addr_space, unsigned long base_addr)
+{
+ int i;
+
+ for (i = 0; i < SI_MAX_PARMS; ++i) {
+ /* Don't check our address. */
+ if (i == intf)
+ continue;
+ if (si_type[i] != NULL) {
+ if ((addr_space == IPMI_MEM_ADDR_SPACE &&
+ base_addr == addrs[i]) ||
+ (addr_space == IPMI_IO_ADDR_SPACE &&
+ base_addr == ports[i]))
+ return 0;
+ }
+ else
+ break;
+ }
+
+ return 1;
+}
+#endif
+
+static int std_irq_setup(struct smi_info *info)
+{
+ int rv;
+
+ if (!info->irq)
+ return 0;
+
+ rv = request_irq(info->irq,
+ si_irq_handler,
+ SA_INTERRUPT,
+ DEVICE_NAME,
+ info);
+ if (rv) {
+ printk(KERN_WARNING
+ "ipmi_si: %s unable to claim interrupt %d,"
+ " running polled\n",
+ DEVICE_NAME, info->irq);
+ info->irq = 0;
+ } else {
+ printk(" Using irq %d\n", info->irq);
+ }
+
+ return rv;
+}
+
+static void std_irq_cleanup(struct smi_info *info)
+{
+ if (!info->irq)
+ return;
+
+ free_irq(info->irq, info);
+}
+
+static unsigned char port_inb(struct si_sm_io *io, unsigned int offset)
+{
+ unsigned int *addr = io->info;
+
+ return inb((*addr)+(offset*io->regspacing));
+}
+
+static void port_outb(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ unsigned int *addr = io->info;
+
+ outb(b, (*addr)+(offset * io->regspacing));
+}
+
+static unsigned char port_inw(struct si_sm_io *io, unsigned int offset)
+{
+ unsigned int *addr = io->info;
+
+ return (inw((*addr)+(offset * io->regspacing)) >> io->regshift) & 0xff;
+}
+
+static void port_outw(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ unsigned int *addr = io->info;
+
+ outw(b << io->regshift, (*addr)+(offset * io->regspacing));
+}
+
+static unsigned char port_inl(struct si_sm_io *io, unsigned int offset)
+{
+ unsigned int *addr = io->info;
+
+ return (inl((*addr)+(offset * io->regspacing)) >> io->regshift) & 0xff;
+}
+
+static void port_outl(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ unsigned int *addr = io->info;
+
+ outl(b << io->regshift, (*addr)+(offset * io->regspacing));
+}
+
+static void port_cleanup(struct smi_info *info)
+{
+ unsigned int *addr = info->io.info;
+ int mapsize;
+
+ if (addr && (*addr)) {
+ mapsize = ((info->io_size * info->io.regspacing)
+ - (info->io.regspacing - info->io.regsize));
+
+ release_region (*addr, mapsize);
+ }
+ kfree(info);
+}
+
+static int port_setup(struct smi_info *info)
+{
+ unsigned int *addr = info->io.info;
+ int mapsize;
+
+ if (!addr || (!*addr))
+ return -ENODEV;
+
+ info->io_cleanup = port_cleanup;
+
+ /* Figure out the actual inb/inw/inl/etc routine to use based
+ upon the register size. */
+ switch (info->io.regsize) {
+ case 1:
+ info->io.inputb = port_inb;
+ info->io.outputb = port_outb;
+ break;
+ case 2:
+ info->io.inputb = port_inw;
+ info->io.outputb = port_outw;
+ break;
+ case 4:
+ info->io.inputb = port_inl;
+ info->io.outputb = port_outl;
+ break;
+ default:
+ printk("ipmi_si: Invalid register size: %d\n",
+ info->io.regsize);
+ return -EINVAL;
+ }
+
+ /* Calculate the total amount of memory to claim. This is an
+ * unusual looking calculation, but it avoids claiming any
+ * more memory than it has to. It will claim everything
+ * between the first address to the end of the last full
+ * register. */
+ mapsize = ((info->io_size * info->io.regspacing)
+ - (info->io.regspacing - info->io.regsize));
+
+ if (request_region(*addr, mapsize, DEVICE_NAME) == NULL)
+ return -EIO;
+ return 0;
+}
+
+static int try_init_port(i