vserver 1.9.5.x5
[linux-2.6.git] / drivers / char / ipmi / ipmi_si_intf.c
index ea330f3..82f0ed4 100644 (file)
@@ -76,7 +76,7 @@ static inline void add_usec_to_timer(struct timer_list *t, long v)
 #include "ipmi_si_sm.h"
 #include <linux/init.h>
 
-#define IPMI_SI_VERSION "v32"
+#define IPMI_SI_VERSION "v33"
 
 /* Measure times between events in the driver. */
 #undef DEBUG_TIMING
@@ -146,6 +146,11 @@ struct smi_info
        /* 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;
 
@@ -452,14 +457,20 @@ static void handle_transaction_done(struct smi_info *smi_info)
 
                        /* 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;
        }
 
@@ -482,14 +493,20 @@ static void handle_transaction_done(struct smi_info *smi_info)
 
                        /* 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;
        }
 
@@ -568,6 +585,9 @@ static enum si_sm_result smi_event_handler(struct smi_info *smi_info,
                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
@@ -575,7 +595,6 @@ static enum si_sm_result smi_event_handler(struct smi_info *smi_info,
                        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. */
@@ -872,9 +891,10 @@ static struct smi_info *smi_infos[SI_MAX_DRIVERS] =
 
 #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 };
@@ -886,6 +906,12 @@ static unsigned int  ports[SI_MAX_PARMS] = { 0, 0, 0, 0 };
 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);
@@ -897,26 +923,43 @@ 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_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_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_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");
 
 #define IPMI_MEM_ADDR_SPACE 1
 #define IPMI_IO_ADDR_SPACE  2
 
-#if defined(CONFIG_ACPI_INTERPETER) || defined(CONFIG_X86) || defined(CONFIG_PCI)
+#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;
@@ -977,7 +1020,7 @@ static unsigned char port_inb(struct si_sm_io *io, unsigned int offset)
 {
        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,
@@ -985,30 +1028,97 @@ 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;
@@ -1028,11 +1138,15 @@ static int try_init_port(int intf_num, struct smi_info **new_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;
@@ -1047,44 +1161,125 @@ static int try_init_port(int intf_num, struct smi_info **new_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;
@@ -1104,11 +1299,15 @@ static int try_init_mem(int intf_num, struct smi_info **new_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.info = &addrs[intf_num];
        info->io.addr = NULL;
+       info->io.regspacing = regspacings[intf_num];
+       if (!info->io.regspacing)
+               info->io.regspacing = DEFAULT_REGSPACING;
+       info->io.regsize = regsizes[intf_num];
+       if (!info->io.regsize)
+               info->io.regsize = DEFAULT_REGSPACING;
+       info->io.regshift = regshifts[intf_num];
        info->irq = 0;
        info->irq_setup = NULL;
        *new_info = info;
@@ -1132,7 +1331,7 @@ static int try_init_mem(int intf_num, struct smi_info **new_info)
 static int acpi_failure = 0;
 
 /* For GPE-type interrupts. */
-void ipmi_acpi_gpe(void *context)
+static u32 ipmi_acpi_gpe(void *context)
 {
        struct smi_info *smi_info = context;
        unsigned long   flags;
@@ -1156,6 +1355,8 @@ void ipmi_acpi_gpe(void *context)
        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)
@@ -1169,7 +1370,7 @@ static int acpi_gpe_irq_setup(struct smi_info *info)
        status = acpi_install_gpe_handler(NULL,
                                          info->irq,
                                          ACPI_GPE_LEVEL_TRIGGERED,
-                                         ipmi_acpi_gpe,
+                                         &ipmi_acpi_gpe,
                                          info);
        if (status != AE_OK) {
                printk(KERN_WARNING
@@ -1182,7 +1383,6 @@ 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)
@@ -1190,7 +1390,7 @@ static void acpi_gpe_irq_cleanup(struct smi_info *info)
        if (!info->irq)
                return;
 
-       acpi_remove_gpe_handler(NULL, info->irq, ipmi_acpi_gpe);
+       acpi_remove_gpe_handler(NULL, info->irq, &ipmi_acpi_gpe);
 }
 
 /*
@@ -1310,21 +1510,22 @@ static int try_init_acpi(int intf_num, struct smi_info **new_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);
@@ -1348,6 +1549,7 @@ typedef struct dmi_ipmi_data
        u8              addr_space;
        unsigned long   base_addr;
        u8              irq;
+       u8              offset;
 }dmi_ipmi_data_t;
 
 typedef struct dmi_header
@@ -1361,27 +1563,55 @@ static int decode_dmi(dmi_header_t *dm, dmi_ipmi_data_t *ipmi_data)
 {
        u8              *data = (u8 *)dm;
        unsigned long   base_addr;
+       u8              reg_spacing;
+       u8              len = dm->length;
 
-       ipmi_data->type = data[0x04];
+       ipmi_data->type = data[4];
 
-       memcpy(&base_addr,&data[0x08],sizeof(unsigned long));
-       if (base_addr & 1) {
-               /* I/O */
-               base_addr &= 0xFFFE;
+       memcpy(&base_addr, data+8, sizeof(unsigned long));
+       if (len >= 0x11) {
+               if (base_addr & 1) {
+                       /* I/O */
+                       base_addr &= 0xFFFE;
+                       ipmi_data->addr_space = IPMI_IO_ADDR_SPACE;
+               }
+               else {
+                       /* Memory */
+                       ipmi_data->addr_space = IPMI_MEM_ADDR_SPACE;
+               }
+               /* If bit 4 of byte 0x10 is set, then the lsb for the address
+                  is odd. */
+               ipmi_data->base_addr = base_addr | ((data[0x10] & 0x10) >> 4);
+
+               ipmi_data->irq = data[0x11];
+
+               /* The top two bits of byte 0x10 hold the register spacing. */
+               reg_spacing = (data[0x10] & 0xC0) >> 6;
+               switch(reg_spacing){
+               case 0x00: /* Byte boundaries */
+                   ipmi_data->offset = 1;
+                   break;
+               case 0x01: /* 32-bit boundaries */
+                   ipmi_data->offset = 4;
+                   break;
+               case 0x02: /* 16-byte boundaries */
+                   ipmi_data->offset = 16;
+                   break;
+               default:
+                   /* Some other interface, just ignore it. */
+                   return -EIO;
+               }
+       } else {
+               /* Old DMI spec. */
+               ipmi_data->base_addr = base_addr;
                ipmi_data->addr_space = IPMI_IO_ADDR_SPACE;
-       }
-       else {
-               /* Memory */
-               ipmi_data->addr_space = IPMI_MEM_ADDR_SPACE;
+               ipmi_data->offset = 1;
        }
 
-       ipmi_data->base_addr = base_addr;
-       ipmi_data->irq = data[0x11];
-
        if (is_new_interface(-1, ipmi_data->addr_space,ipmi_data->base_addr))
-           return 0;
+               return 0;
 
-       memset(ipmi_data,0,sizeof(dmi_ipmi_data_t));
+       memset(ipmi_data, 0, sizeof(dmi_ipmi_data_t));
 
        return -1;
 }
@@ -1500,18 +1730,12 @@ static int try_init_smbios(int intf_num, struct smi_info **new_info)
        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);
@@ -1519,6 +1743,13 @@ static int try_init_smbios(int intf_num, struct smi_info **new_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;
@@ -1553,10 +1784,10 @@ static int find_pci_smic(int intf_num, struct smi_info **new_info)
 
        pci_smic_checked = 1;
 
-       if ((pci_dev = pci_find_device(PCI_HP_VENDOR_ID, PCI_MMC_DEVICE_ID,
+       if ((pci_dev = pci_get_device(PCI_HP_VENDOR_ID, PCI_MMC_DEVICE_ID,
                                       NULL)))
                ;
-       else if ((pci_dev = pci_find_class(PCI_ERMC_CLASSCODE, NULL)) &&
+       else if ((pci_dev = pci_get_class(PCI_ERMC_CLASSCODE, NULL)) &&
                 pci_dev->subsystem_vendor == PCI_HP_VENDOR_ID)
                fe_rmc = 1;
        else
@@ -1565,6 +1796,7 @@ static int find_pci_smic(int intf_num, struct smi_info **new_info)
        error = pci_read_config_word(pci_dev, PCI_MMC_ADDR_CW, &base_addr);
        if (error)
        {
+               pci_dev_put(pci_dev);
                printk(KERN_ERR
                       "ipmi_si: pci_read_config_word() failed (%d).\n",
                       error);
@@ -1574,6 +1806,7 @@ static int find_pci_smic(int intf_num, struct smi_info **new_info)
        /* Bit 0: 1 specifies programmed I/O, 0 specifies memory mapped I/O */
        if (!(base_addr & 0x0001))
        {
+               pci_dev_put(pci_dev);
                printk(KERN_ERR
                       "ipmi_si: memory mapped I/O not supported for PCI"
                       " smic.\n");
@@ -1585,22 +1818,27 @@ static int find_pci_smic(int intf_num, struct smi_info **new_info)
                /* Data register starts at base address + 1 in eRMC */
                ++base_addr;
 
-       if (!is_new_interface(-1, IPMI_IO_ADDR_SPACE, base_addr))
-           return -ENODEV;
+       if (!is_new_interface(-1, IPMI_IO_ADDR_SPACE, base_addr)) {
+               pci_dev_put(pci_dev);
+               return -ENODEV;
+       }
 
        info = kmalloc(sizeof(*info), GFP_KERNEL);
        if (!info) {
+               pci_dev_put(pci_dev);
                printk(KERN_ERR "ipmi_si: Could not allocate SI data (5)\n");
                return -ENOMEM;
        }
        memset(info, 0, sizeof(*info));
 
        info->io_setup = port_setup;
-       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;
 
@@ -1610,6 +1848,7 @@ static int find_pci_smic(int intf_num, struct smi_info **new_info)
        printk("ipmi_si: Found PCI SMIC at I/O address 0x%lx\n",
                (long unsigned int) base_addr);
 
+       pci_dev_put(pci_dev);
        return 0;
 }
 #endif /* CONFIG_PCI */
@@ -1860,6 +2099,13 @@ static int init_one_smi(int intf_num, struct smi_info **smi)
        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
@@ -1903,12 +2149,6 @@ static int init_one_smi(int intf_num, struct smi_info **smi)
                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;
-
        *smi = new_smi;
 
        printk(" IPMI %s interface initialized\n", si_type[intf_num]);
@@ -2020,7 +2260,7 @@ static __init int init_ipmi_si(void)
 }
 module_init(init_ipmi_si);
 
-void __exit cleanup_one_si(struct smi_info *to_clean)
+static void __exit cleanup_one_si(struct smi_info *to_clean)
 {
        int           rv;
        unsigned long flags;
@@ -2052,6 +2292,14 @@ void __exit cleanup_one_si(struct smi_info *to_clean)
                schedule_timeout(1);
        }
 
+       /* Interrupts and timeouts are stopped, now make sure the
+          interface is in a clean state. */
+       while ((to_clean->curr_msg) || (to_clean->si_state != SI_NORMAL)) {
+               poll(to_clean);
+               set_current_state(TASK_UNINTERRUPTIBLE);
+               schedule_timeout(1);
+       }
+
        rv = ipmi_unregister_smi(to_clean->intf);
        if (rv) {
                printk(KERN_ERR