#include <linux/list.h>
#include <linux/pci.h>
#include <linux/ioport.h>
-#include <linux/irq.h>
+#include <asm/irq.h>
#ifdef CONFIG_HIGH_RES_TIMERS
#include <linux/hrtime.h>
# if defined(schedule_next_int)
#include "ipmi_si_sm.h"
#include <linux/init.h>
-#define IPMI_SI_VERSION "v31"
+#define IPMI_SI_VERSION "v33"
/* Measure times between events in the driver. */
#undef DEBUG_TIMING
/* 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;
/* 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);
}
- handle_flags(smi_info);
break;
}
/* 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);
}
- handle_flags(smi_info);
break;
}
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
return_hosed_msg(smi_info);
}
si_sm_result = smi_info->handlers->event(smi_info->si_sm, 0);
- smi_info->si_state = SI_NORMAL;
}
/* We prefer handling attn over new messages. */
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;
.owner = THIS_MODULE,
.sender = sender,
.request_events = request_events,
- .set_run_to_completion = set_run_to_completion
+ .set_run_to_completion = set_run_to_completion,
+ .poll = poll,
};
/* There can be 4 IO ports passed in (with or without IRQs), 4 addresses,
#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_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] = { NULL, NULL, NULL, NULL };
static int num_ports = 0;
static int irqs[SI_MAX_PARMS] = { 0, 0, 0, 0 };
static int num_irqs = 0;
+static int regspacings[SI_MAX_PARMS] = { 0, 0, 0, 0 };
+static int num_regspacings = 0;
+static int regsizes[SI_MAX_PARMS] = { 0, 0, 0, 0 };
+static int num_regsizes = 0;
+static int regshifts[SI_MAX_PARMS] = { 0, 0, 0, 0 };
+static int num_regshifts = 0;
module_param_named(trydefaults, si_trydefaults, bool, 0);
" 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");
#define IPMI_MEM_ADDR_SPACE 1
#define IPMI_IO_ADDR_SPACE 2
{
unsigned int *addr = io->info;
- return inb((*addr)+offset);
+ return inb((*addr)+(offset*io->regspacing));
}
static void port_outb(struct si_sm_io *io, unsigned int offset,
{
unsigned int *addr = io->info;
- outb(b, (*addr)+offset);
+ outb(b, (*addr)+(offset * io->regspacing));
}
-static int port_setup(struct smi_info *info)
+static unsigned char port_inw(struct si_sm_io *io, unsigned int offset)
{
- unsigned int *addr = info->io.info;
+ unsigned int *addr = io->info;
- if (!addr || (!*addr))
- return -ENODEV;
+ return (inw((*addr)+(offset * io->regspacing)) >> io->regshift) & 0xff;
+}
- if (request_region(*addr, info->io_size, DEVICE_NAME) == NULL)
- return -EIO;
- return 0;
+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));
- if (addr && (*addr))
- release_region (*addr, info->io_size);
+ release_region (*addr, mapsize);
+ }
kfree(info);
}
+static int port_setup(struct smi_info *info)
+{
+ unsigned int *addr = info->io.info;
+ int mapsize;
+
+ if (!addr || (!*addr))
+ return -ENODEV;
+
+ info->io_cleanup = port_cleanup;
+
+ /* Figure out the actual inb/inw/inl/etc routine to use based
+ upon the register size. */
+ switch (info->io.regsize) {
+ case 1:
+ info->io.inputb = port_inb;
+ info->io.outputb = port_outb;
+ break;
+ case 2:
+ info->io.inputb = port_inw;
+ info->io.outputb = port_outw;
+ break;
+ case 4:
+ info->io.inputb = port_inl;
+ info->io.outputb = port_outl;
+ break;
+ default:
+ printk("ipmi_si: Invalid register size: %d\n",
+ info->io.regsize);
+ return -EINVAL;
+ }
+
+ /* Calculate the total amount of memory to claim. This is an
+ * unusual looking calculation, but it avoids claiming any
+ * more memory than it has to. It will claim everything
+ * between the first address to the end of the last full
+ * register. */
+ mapsize = ((info->io_size * info->io.regspacing)
+ - (info->io.regspacing - info->io.regsize));
+
+ if (request_region(*addr, mapsize, DEVICE_NAME) == NULL)
+ return -EIO;
+ return 0;
+}
+
static int try_init_port(int intf_num, struct smi_info **new_info)
{
struct smi_info *info;
memset(info, 0, sizeof(*info));
info->io_setup = port_setup;
- info->io_cleanup = port_cleanup;
- info->io.inputb = port_inb;
- info->io.outputb = port_outb;
info->io.info = &(ports[intf_num]);
info->io.addr = NULL;
+ info->io.regspacing = regspacings[intf_num];
+ if (!info->io.regspacing)
+ info->io.regspacing = DEFAULT_REGSPACING;
+ info->io.regsize = regsizes[intf_num];
+ if (!info->io.regsize)
+ info->io.regsize = DEFAULT_REGSPACING;
+ info->io.regshift = regshifts[intf_num];
info->irq = 0;
info->irq_setup = NULL;
*new_info = info;
static unsigned char mem_inb(struct si_sm_io *io, unsigned int offset)
{
- return readb((io->addr)+offset);
+ return readb((io->addr)+(offset * io->regspacing));
}
static void mem_outb(struct si_sm_io *io, unsigned int offset,
unsigned char b)
{
- writeb(b, (io->addr)+offset);
+ writeb(b, (io->addr)+(offset * io->regspacing));
}
-static int mem_setup(struct smi_info *info)
+static unsigned char mem_inw(struct si_sm_io *io, unsigned int offset)
{
- unsigned long *addr = info->io.info;
+ return (readw((io->addr)+(offset * io->regspacing)) >> io->regshift)
+ && 0xff;
+}
- if (!addr || (!*addr))
- return -ENODEV;
+static void mem_outw(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ writeb(b << io->regshift, (io->addr)+(offset * io->regspacing));
+}
- if (request_mem_region(*addr, info->io_size, DEVICE_NAME) == NULL)
- return -EIO;
+static unsigned char mem_inl(struct si_sm_io *io, unsigned int offset)
+{
+ return (readl((io->addr)+(offset * io->regspacing)) >> io->regshift)
+ && 0xff;
+}
- info->io.addr = ioremap(*addr, info->io_size);
- if (info->io.addr == NULL) {
- release_mem_region(*addr, info->io_size);
- return -EIO;
- }
- return 0;
+static void mem_outl(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ writel(b << io->regshift, (io->addr)+(offset * io->regspacing));
+}
+
+#ifdef readq
+static unsigned char mem_inq(struct si_sm_io *io, unsigned int offset)
+{
+ return (readq((io->addr)+(offset * io->regspacing)) >> io->regshift)
+ && 0xff;
+}
+
+static void mem_outq(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ writeq(b << io->regshift, (io->addr)+(offset * io->regspacing));
}
+#endif
static void mem_cleanup(struct smi_info *info)
{
unsigned long *addr = info->io.info;
+ int mapsize;
if (info->io.addr) {
iounmap(info->io.addr);
- release_mem_region(*addr, info->io_size);
+
+ mapsize = ((info->io_size * info->io.regspacing)
+ - (info->io.regspacing - info->io.regsize));
+
+ release_mem_region(*addr, mapsize);
}
kfree(info);
}
+static int mem_setup(struct smi_info *info)
+{
+ unsigned long *addr = info->io.info;
+ int mapsize;
+
+ if (!addr || (!*addr))
+ return -ENODEV;
+
+ info->io_cleanup = mem_cleanup;
+
+ /* Figure out the actual readb/readw/readl/etc routine to use based
+ upon the register size. */
+ switch (info->io.regsize) {
+ case 1:
+ info->io.inputb = mem_inb;
+ info->io.outputb = mem_outb;
+ break;
+ case 2:
+ info->io.inputb = mem_inw;
+ info->io.outputb = mem_outw;
+ break;
+ case 4:
+ info->io.inputb = mem_inl;
+ info->io.outputb = mem_outl;
+ break;
+#ifdef readq
+ case 8:
+ info->io.inputb = mem_inq;
+ info->io.outputb = mem_outq;
+ break;
+#endif
+ default:
+ printk("ipmi_si: Invalid register size: %d\n",
+ info->io.regsize);
+ return -EINVAL;
+ }
+
+ /* Calculate the total amount of memory to claim. This is an
+ * unusual looking calculation, but it avoids claiming any
+ * more memory than it has to. It will claim everything
+ * between the first address to the end of the last full
+ * register. */
+ mapsize = ((info->io_size * info->io.regspacing)
+ - (info->io.regspacing - info->io.regsize));
+
+ if (request_mem_region(*addr, mapsize, DEVICE_NAME) == NULL)
+ return -EIO;
+
+ info->io.addr = ioremap(*addr, mapsize);
+ if (info->io.addr == NULL) {
+ release_mem_region(*addr, mapsize);
+ return -EIO;
+ }
+ return 0;
+}
+
static int try_init_mem(int intf_num, struct smi_info **new_info)
{
struct smi_info *info;
memset(info, 0, sizeof(*info));
info->io_setup = mem_setup;
- info->io_cleanup = mem_cleanup;
- info->io.inputb = mem_inb;
- info->io.outputb = mem_outb;
info->io.info = (void *) addrs[intf_num];
info->io.addr = NULL;
+ info->io.regspacing = regspacings[intf_num];
+ if (!info->io.regspacing)
+ info->io.regspacing = DEFAULT_REGSPACING;
+ info->io.regsize = regsizes[intf_num];
+ if (!info->io.regsize)
+ info->io.regsize = DEFAULT_REGSPACING;
+ info->io.regshift = regshifts[intf_num];
info->irq = 0;
info->irq_setup = NULL;
*new_info = info;
static int acpi_failure = 0;
/* For GPE-type interrupts. */
-void ipmi_acpi_gpe(void *context)
+u32 ipmi_acpi_gpe(void *context)
{
struct smi_info *smi_info = context;
unsigned long flags;
smi_event_handler(smi_info, 0);
out:
spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+
+ return ACPI_INTERRUPT_HANDLED;
}
static int acpi_gpe_irq_setup(struct smi_info *info)
printk(" Using ACPI GPE %d\n", info->irq);
return 0;
}
-
}
static void acpi_gpe_irq_cleanup(struct smi_info *info)
info->irq_setup = NULL;
}
+ regspacings[intf_num] = spmi->addr.register_bit_width / 8;
+ info->io.regspacing = spmi->addr.register_bit_width / 8;
+ regsizes[intf_num] = regspacings[intf_num];
+ info->io.regsize = regsizes[intf_num];
+ regshifts[intf_num] = spmi->addr.register_bit_offset;
+ info->io.regshift = regshifts[intf_num];
+
if (spmi->addr.address_space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
io_type = "memory";
info->io_setup = mem_setup;
- info->io_cleanup = mem_cleanup;
addrs[intf_num] = spmi->addr.address;
- info->io.inputb = mem_inb;
- info->io.outputb = mem_outb;
info->io.info = &(addrs[intf_num]);
} else if (spmi->addr.address_space_id == ACPI_ADR_SPACE_SYSTEM_IO) {
io_type = "I/O";
info->io_setup = port_setup;
- info->io_cleanup = port_cleanup;
ports[intf_num] = spmi->addr.address;
- info->io.inputb = port_inb;
- info->io.outputb = port_outb;
info->io.info = &(ports[intf_num]);
} else {
kfree(info);
u8 addr_space;
unsigned long base_addr;
u8 irq;
+ u8 offset;
}dmi_ipmi_data_t;
typedef struct dmi_header
{
u8 *data = (u8 *)dm;
unsigned long base_addr;
+ u8 reg_spacing;
ipmi_data->type = data[0x04];
ipmi_data->addr_space = IPMI_MEM_ADDR_SPACE;
}
- ipmi_data->base_addr = base_addr;
+ /* The top two bits of byte 0x10 hold the register spacing. */
+ reg_spacing = (data[0x10] & 0xC0) >> 6;
+ switch(reg_spacing){
+ case 0x00: /* Byte boundaries */
+ ipmi_data->offset = 1;
+ break;
+ case 0x01: /* 32-bit boundaries */
+ ipmi_data->offset = 4;
+ break;
+ case 0x02: /* 16-bit boundaries */
+ ipmi_data->offset = 2;
+ default:
+ printk("ipmi_si: Unknown SMBIOS IPMI Base Addr"
+ " Modifier: 0x%x\n", reg_spacing);
+ return -EIO;
+ }
+
+ /* If bit 4 of byte 0x10 is set, then the lsb for the address
+ is odd. */
+ ipmi_data->base_addr = base_addr | ((data[0x10] & 0x10) >> 4);
+
ipmi_data->irq = data[0x11];
if (is_new_interface(-1, ipmi_data->addr_space,ipmi_data->base_addr))
if (ipmi_data.addr_space == 1) {
io_type = "memory";
info->io_setup = mem_setup;
- info->io_cleanup = mem_cleanup;
addrs[intf_num] = ipmi_data.base_addr;
- info->io.inputb = mem_inb;
- info->io.outputb = mem_outb;
info->io.info = &(addrs[intf_num]);
} else if (ipmi_data.addr_space == 2) {
io_type = "I/O";
info->io_setup = port_setup;
- info->io_cleanup = port_cleanup;
ports[intf_num] = ipmi_data.base_addr;
- info->io.inputb = port_inb;
- info->io.outputb = port_outb;
info->io.info = &(ports[intf_num]);
} else {
kfree(info);
return -EIO;
}
+ regspacings[intf_num] = ipmi_data.offset;
+ info->io.regspacing = regspacings[intf_num];
+ if (!info->io.regspacing)
+ info->io.regspacing = DEFAULT_REGSPACING;
+ info->io.regsize = DEFAULT_REGSPACING;
+ info->io.regshift = regshifts[intf_num];
+
irqs[intf_num] = ipmi_data.irq;
*new_info = info;
memset(info, 0, sizeof(*info));
info->io_setup = port_setup;
- info->io_cleanup = port_cleanup;
ports[intf_num] = base_addr;
- info->io.inputb = port_inb;
- info->io.outputb = port_outb;
info->io.info = &(ports[intf_num]);
+ info->io.regspacing = regspacings[intf_num];
+ if (!info->io.regspacing)
+ info->io.regspacing = DEFAULT_REGSPACING;
+ info->io.regsize = DEFAULT_REGSPACING;
+ info->io.regshift = regshifts[intf_num];
*new_info = info;
/* So we know not to free it unless we have allocated one. */
new_smi->intf = NULL;
new_smi->si_sm = NULL;
- new_smi->handlers = 0;
+ new_smi->handlers = NULL;
if (!new_smi->irq_setup) {
new_smi->irq = irqs[intf_num];
atomic_set(&new_smi->req_events, 0);
new_smi->run_to_completion = 0;
+ new_smi->interrupt_disabled = 0;
+ new_smi->timer_stopped = 0;
+ new_smi->stop_operation = 0;
+
+ /* Start clearing the flags before we enable interrupts or the
+ timer to avoid racing with the timer. */
+ start_clear_flags(new_smi);
+ /* IRQ is defined to be set when non-zero. */
+ if (new_smi->irq)
+ new_smi->si_state = SI_CLEARING_FLAGS_THEN_SET_IRQ;
+
+ /* The ipmi_register_smi() code does some operations to
+ determine the channel information, so we must be ready to
+ handle operations before it is called. This means we have
+ to stop the timer if we get an error after this point. */
+ init_timer(&(new_smi->si_timer));
+ new_smi->si_timer.data = (long) new_smi;
+ new_smi->si_timer.function = smi_timeout;
+ new_smi->last_timeout_jiffies = jiffies;
+ new_smi->si_timer.expires = jiffies + SI_TIMEOUT_JIFFIES;
+ add_timer(&(new_smi->si_timer));
+
rv = ipmi_register_smi(&handlers,
new_smi,
new_smi->ipmi_version_major,
printk(KERN_ERR
"ipmi_si: Unable to register device: error %d\n",
rv);
- goto out_err;
+ goto out_err_stop_timer;
}
rv = ipmi_smi_add_proc_entry(new_smi->intf, "type",
printk(KERN_ERR
"ipmi_si: Unable to create proc entry: %d\n",
rv);
- goto out_err;
+ goto out_err_stop_timer;
}
rv = ipmi_smi_add_proc_entry(new_smi->intf, "si_stats",
printk(KERN_ERR
"ipmi_si: Unable to create proc entry: %d\n",
rv);
- goto out_err;
+ goto out_err_stop_timer;
}
- start_clear_flags(new_smi);
-
- /* IRQ is defined to be set when non-zero. */
- if (new_smi->irq)
- new_smi->si_state = SI_CLEARING_FLAGS_THEN_SET_IRQ;
-
- new_smi->interrupt_disabled = 0;
- new_smi->timer_stopped = 0;
- new_smi->stop_operation = 0;
-
- init_timer(&(new_smi->si_timer));
- new_smi->si_timer.data = (long) new_smi;
- new_smi->si_timer.function = smi_timeout;
- new_smi->last_timeout_jiffies = jiffies;
- new_smi->si_timer.expires = jiffies + SI_TIMEOUT_JIFFIES;
- add_timer(&(new_smi->si_timer));
-
*smi = new_smi;
printk(" IPMI %s interface initialized\n", si_type[intf_num]);
return 0;
+ out_err_stop_timer:
+ new_smi->stop_operation = 1;
+
+ /* Wait for the timer to stop. This avoids problems with race
+ conditions removing the timer here. */
+ while (!new_smi->timer_stopped) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ }
+
out_err:
if (new_smi->intf)
ipmi_unregister_smi(new_smi->intf);
new_smi->irq_cleanup(new_smi);
+
+ /* Wait until we know that we are out of any interrupt
+ handlers might have been running before we freed the
+ interrupt. */
+ synchronize_kernel();
+
if (new_smi->si_sm) {
if (new_smi->handlers)
new_smi->handlers->cleanup(new_smi->si_sm);
kfree(new_smi->si_sm);
}
new_smi->io_cleanup(new_smi);
+
return rv;
}
schedule_timeout(1);
}
+ /* Interrupts and timeouts are stopped, now make sure the
+ interface is in a clean state. */
+ while ((to_clean->curr_msg) || (to_clean->si_state != SI_NORMAL)) {
+ poll(to_clean);
+ schedule_timeout(1);
+ }
+
rv = ipmi_unregister_smi(to_clean->intf);
if (rv) {
printk(KERN_ERR