]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/char/ipmi/ipmi_si_intf.c
Merge branch 'linux-2.6'
[linux-2.6-omap-h63xx.git] / drivers / char / ipmi / ipmi_si_intf.c
index 0baa094961a81e6b99cfcf56810e02cde912105b..1dd3a065f57ba50b40bd365aeb35c5d942f00fac 100644 (file)
@@ -9,6 +9,7 @@
  *         source@mvista.com
  *
  * Copyright 2002 MontaVista Software Inc.
+ * Copyright 2006 IBM Corp., Christian Krafft <krafft@de.ibm.com>
  *
  *  This program is free software; you can redistribute it and/or modify it
  *  under the terms of the GNU General Public License as published by the
 #include <linux/string.h>
 #include <linux/ctype.h>
 
+#ifdef CONFIG_PPC_OF
+#include <asm/of_device.h>
+#include <asm/of_platform.h>
+#endif
+
 #define PFX "ipmi_si: "
 
 /* Measure times between events in the driver. */
 #define SI_SHORT_TIMEOUT_USEC  250 /* .25ms when the SM request a
                                        short timeout */
 
+/* Bit for BMC global enables. */
+#define IPMI_BMC_RCV_MSG_INTR     0x01
+#define IPMI_BMC_EVT_MSG_INTR     0x02
+#define IPMI_BMC_EVT_MSG_BUFF     0x04
+#define IPMI_BMC_SYS_LOG          0x08
+
 enum si_intf_state {
        SI_NORMAL,
        SI_GETTING_FLAGS,
@@ -84,7 +96,9 @@ enum si_intf_state {
        SI_CLEARING_FLAGS_THEN_SET_IRQ,
        SI_GETTING_MESSAGES,
        SI_ENABLE_INTERRUPTS1,
-       SI_ENABLE_INTERRUPTS2
+       SI_ENABLE_INTERRUPTS2,
+       SI_DISABLE_INTERRUPTS1,
+       SI_DISABLE_INTERRUPTS2
        /* FIXME - add watchdog stuff. */
 };
 
@@ -333,6 +347,17 @@ static void start_enable_irq(struct smi_info *smi_info)
        smi_info->si_state = SI_ENABLE_INTERRUPTS1;
 }
 
+static void start_disable_irq(struct smi_info *smi_info)
+{
+       unsigned char msg[2];
+
+       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_DISABLE_INTERRUPTS1;
+}
+
 static void start_clear_flags(struct smi_info *smi_info)
 {
        unsigned char msg[3];
@@ -353,7 +378,7 @@ static void start_clear_flags(struct smi_info *smi_info)
 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);
+               start_disable_irq(smi_info);
                smi_info->interrupt_disabled = 1;
        }
 }
@@ -361,7 +386,7 @@ static inline void disable_si_irq(struct smi_info *smi_info)
 static inline void enable_si_irq(struct smi_info *smi_info)
 {
        if ((smi_info->irq) && (smi_info->interrupt_disabled)) {
-               enable_irq(smi_info->irq);
+               start_enable_irq(smi_info);
                smi_info->interrupt_disabled = 0;
        }
 }
@@ -583,7 +608,9 @@ static void handle_transaction_done(struct smi_info *smi_info)
                } 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 */
+                       msg[2] = (msg[3] |
+                                 IPMI_BMC_RCV_MSG_INTR |
+                                 IPMI_BMC_EVT_MSG_INTR);
                        smi_info->handlers->start_transaction(
                                smi_info->si_sm, msg, 3);
                        smi_info->si_state = SI_ENABLE_INTERRUPTS2;
@@ -605,6 +632,45 @@ static void handle_transaction_done(struct smi_info *smi_info)
                smi_info->si_state = SI_NORMAL;
                break;
        }
+
+       case SI_DISABLE_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 disable interrupts"
+                              ", failed get.\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] &
+                                 ~(IPMI_BMC_RCV_MSG_INTR |
+                                   IPMI_BMC_EVT_MSG_INTR));
+                       smi_info->handlers->start_transaction(
+                               smi_info->si_sm, msg, 3);
+                       smi_info->si_state = SI_DISABLE_INTERRUPTS2;
+               }
+               break;
+       }
+
+       case SI_DISABLE_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 disable interrupts"
+                              ", failed set.\n");
+               }
+               smi_info->si_state = SI_NORMAL;
+               break;
+       }
        }
 }
 
@@ -858,9 +924,6 @@ static void smi_timeout(unsigned long data)
        struct timeval    t;
 #endif
 
-       if (atomic_read(&smi_info->stop_operation))
-               return;
-
        spin_lock_irqsave(&(smi_info->si_lock), flags);
 #ifdef DEBUG_TIMING
        do_gettimeofday(&t);
@@ -916,15 +979,11 @@ static irqreturn_t si_irq_handler(int irq, void *data)
        smi_info->interrupts++;
        spin_unlock(&smi_info->count_lock);
 
-       if (atomic_read(&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;
 }
@@ -1006,6 +1065,7 @@ static DEFINE_MUTEX(smi_infos_lock);
 static int smi_num; /* Used to sequence the SMIs */
 
 #define DEFAULT_REGSPACING     1
+#define DEFAULT_REGSIZE                1
 
 static int           si_trydefaults = 1;
 static char          *si_type[SI_MAX_PARMS];
@@ -1028,7 +1088,7 @@ static int num_slave_addrs;
 
 #define IPMI_IO_ADDR_SPACE  0
 #define IPMI_MEM_ADDR_SPACE 1
-static char *addr_space_to_str[] = { "I/O", "mem" };
+static char *addr_space_to_str[] = { "i/o", "mem" };
 
 static int hotmod_handler(const char *val, struct kernel_param *kp);
 
@@ -1111,7 +1171,7 @@ static int std_irq_setup(struct smi_info *info)
        if (info->si_type == SI_BT) {
                rv = request_irq(info->irq,
                                 si_bt_irq_handler,
-                                IRQF_DISABLED,
+                                IRQF_SHARED | IRQF_DISABLED,
                                 DEVICE_NAME,
                                 info);
                if (!rv)
@@ -1121,7 +1181,7 @@ static int std_irq_setup(struct smi_info *info)
        } else
                rv = request_irq(info->irq,
                                 si_irq_handler,
-                                IRQF_DISABLED,
+                                IRQF_SHARED | IRQF_DISABLED,
                                 DEVICE_NAME,
                                 info);
        if (rv) {
@@ -1397,20 +1457,7 @@ static struct hotmod_vals hotmod_as[] = {
        { "i/o",        IPMI_IO_ADDR_SPACE },
        { NULL }
 };
-static int ipmi_strcasecmp(const char *s1, const char *s2)
-{
-       while (*s1 || *s2) {
-               if (!*s1)
-                       return -1;
-               if (!*s2)
-                       return 1;
-               if (*s1 != *s2)
-                       return *s1 - *s2;
-               s1++;
-               s2++;
-       }
-       return 0;
-}
+
 static int parse_str(struct hotmod_vals *v, int *val, char *name, char **curr)
 {
        char *s;
@@ -1424,7 +1471,7 @@ static int parse_str(struct hotmod_vals *v, int *val, char *name, char **curr)
        *s = '\0';
        s++;
        for (i = 0; hotmod_ops[i].name; i++) {
-               if (ipmi_strcasecmp(*curr, v[i].name) == 0) {
+               if (strcmp(*curr, v[i].name) == 0) {
                        *val = v[i].val;
                        *curr = s;
                        return 0;
@@ -1435,10 +1482,34 @@ static int parse_str(struct hotmod_vals *v, int *val, char *name, char **curr)
        return -EINVAL;
 }
 
+static int check_hotmod_int_op(const char *curr, const char *option,
+                              const char *name, int *val)
+{
+       char *n;
+
+       if (strcmp(curr, name) == 0) {
+               if (!option) {
+                       printk(KERN_WARNING PFX
+                              "No option given for '%s'\n",
+                              curr);
+                       return -EINVAL;
+               }
+               *val = simple_strtoul(option, &n, 0);
+               if ((*n != '\0') || (*option == '\0')) {
+                       printk(KERN_WARNING PFX
+                              "Bad option given for '%s'\n",
+                              curr);
+                       return -EINVAL;
+               }
+               return 1;
+       }
+       return 0;
+}
+
 static int hotmod_handler(const char *val, struct kernel_param *kp)
 {
        char *str = kstrdup(val, GFP_KERNEL);
-       int  rv = -EINVAL;
+       int  rv;
        char *next, *curr, *s, *n, *o;
        enum hotmod_op op;
        enum si_type si_type;
@@ -1450,13 +1521,15 @@ static int hotmod_handler(const char *val, struct kernel_param *kp)
        int irq;
        int ipmb;
        int ival;
+       int len;
        struct smi_info *info;
 
        if (!str)
                return -ENOMEM;
 
        /* Kill any trailing spaces, as we can get a "\n" from echo. */
-       ival = strlen(str) - 1;
+       len = strlen(str);
+       ival = len - 1;
        while ((ival >= 0) && isspace(str[ival])) {
                str[ival] = '\0';
                ival--;
@@ -1513,35 +1586,37 @@ static int hotmod_handler(const char *val, struct kernel_param *kp)
                                *o = '\0';
                                o++;
                        }
-#define HOTMOD_INT_OPT(name, val) \
-                       if (ipmi_strcasecmp(curr, name) == 0) {         \
-                               if (!o) {                               \
-                                       printk(KERN_WARNING PFX         \
-                                              "No option given for '%s'\n", \
-                                               curr);                  \
-                                       goto out;                       \
-                               }                                       \
-                               val = simple_strtoul(o, &n, 0);         \
-                               if ((*n != '\0') || (*o == '\0')) {     \
-                                       printk(KERN_WARNING PFX         \
-                                              "Bad option given for '%s'\n", \
-                                              curr);                   \
-                                       goto out;                       \
-                               }                                       \
-                       }
-
-                       HOTMOD_INT_OPT("rsp", regspacing)
-                       else HOTMOD_INT_OPT("rsi", regsize)
-                       else HOTMOD_INT_OPT("rsh", regshift)
-                       else HOTMOD_INT_OPT("irq", irq)
-                       else HOTMOD_INT_OPT("ipmb", ipmb)
-                       else {
-                               printk(KERN_WARNING PFX
-                                      "Invalid hotmod option '%s'\n",
-                                      curr);
+                       rv = check_hotmod_int_op(curr, o, "rsp", &regspacing);
+                       if (rv < 0)
                                goto out;
-                       }
-#undef HOTMOD_INT_OPT
+                       else if (rv)
+                               continue;
+                       rv = check_hotmod_int_op(curr, o, "rsi", &regsize);
+                       if (rv < 0)
+                               goto out;
+                       else if (rv)
+                               continue;
+                       rv = check_hotmod_int_op(curr, o, "rsh", &regshift);
+                       if (rv < 0)
+                               goto out;
+                       else if (rv)
+                               continue;
+                       rv = check_hotmod_int_op(curr, o, "irq", &irq);
+                       if (rv < 0)
+                               goto out;
+                       else if (rv)
+                               continue;
+                       rv = check_hotmod_int_op(curr, o, "ipmb", &ipmb);
+                       if (rv < 0)
+                               goto out;
+                       else if (rv)
+                               continue;
+
+                       rv = -EINVAL;
+                       printk(KERN_WARNING PFX
+                              "Invalid hotmod option '%s'\n",
+                              curr);
+                       goto out;
                }
 
                if (op == HM_ADD) {
@@ -1590,6 +1665,7 @@ static int hotmod_handler(const char *val, struct kernel_param *kp)
                        mutex_unlock(&smi_infos_lock);
                }
        }
+       rv = len;
  out:
        kfree(str);
        return rv;
@@ -1610,11 +1686,11 @@ static __devinit void hardcode_find_bmc(void)
 
                info->addr_source = "hardcoded";
 
-               if (!si_type[i] || ipmi_strcasecmp(si_type[i], "kcs") == 0) {
+               if (!si_type[i] || strcmp(si_type[i], "kcs") == 0) {
                        info->si_type = SI_KCS;
-               } else if (ipmi_strcasecmp(si_type[i], "smic") == 0) {
+               } else if (strcmp(si_type[i], "smic") == 0) {
                        info->si_type = SI_SMIC;
-               } else if (ipmi_strcasecmp(si_type[i], "bt") == 0) {
+               } else if (strcmp(si_type[i], "bt") == 0) {
                        info->si_type = SI_BT;
                } else {
                        printk(KERN_WARNING
@@ -1685,15 +1761,11 @@ static u32 ipmi_acpi_gpe(void *context)
        smi_info->interrupts++;
        spin_unlock(&smi_info->count_lock);
 
-       if (atomic_read(&smi_info->stop_operation))
-               goto out;
-
 #ifdef DEBUG_TIMING
        do_gettimeofday(&t);
        printk("**ACPI_GPE: %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 ACPI_INTERRUPT_HANDLED;
@@ -1779,7 +1851,6 @@ struct SPMITable {
 static __devinit int try_init_acpi(struct SPMITable *spmi)
 {
        struct smi_info  *info;
-       char             *io_type;
        u8               addr_space;
 
        if (spmi->IPMIlegacy != 1) {
@@ -1787,7 +1858,7 @@ static __devinit int try_init_acpi(struct SPMITable *spmi)
            return -ENODEV;
        }
 
-       if (spmi->addr.address_space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY)
+       if (spmi->addr.space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY)
                addr_space = IPMI_MEM_ADDR_SPACE;
        else
                addr_space = IPMI_IO_ADDR_SPACE;
@@ -1833,23 +1904,21 @@ static __devinit int try_init_acpi(struct SPMITable *spmi)
                info->irq_setup = NULL;
        }
 
-       if (spmi->addr.register_bit_width) {
+       if (spmi->addr.bit_width) {
                /* A (hopefully) properly formed register bit width. */
-               info->io.regspacing = spmi->addr.register_bit_width / 8;
+               info->io.regspacing = spmi->addr.bit_width / 8;
        } else {
                info->io.regspacing = DEFAULT_REGSPACING;
        }
        info->io.regsize = info->io.regspacing;
-       info->io.regshift = spmi->addr.register_bit_offset;
+       info->io.regshift = spmi->addr.bit_offset;
 
-       if (spmi->addr.address_space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
-               io_type = "memory";
+       if (spmi->addr.space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
                info->io_setup = mem_setup;
-               info->io.addr_type = IPMI_IO_ADDR_SPACE;
-       } else if (spmi->addr.address_space_id == ACPI_ADR_SPACE_SYSTEM_IO) {
-               io_type = "I/O";
-               info->io_setup = port_setup;
                info->io.addr_type = IPMI_MEM_ADDR_SPACE;
+       } else if (spmi->addr.space_id == ACPI_ADR_SPACE_SYSTEM_IO) {
+               info->io_setup = port_setup;
+               info->io.addr_type = IPMI_IO_ADDR_SPACE;
        } else {
                kfree(info);
                printk("ipmi_si: Unknown ACPI I/O Address type\n");
@@ -1875,10 +1944,8 @@ static __devinit void acpi_find_bmc(void)
                return;
 
        for (i = 0; ; i++) {
-               status = acpi_get_firmware_table("SPMI", i+1,
-                                                ACPI_LOGICAL_ADDRESSING,
-                                                (struct acpi_table_header **)
-                                                &spmi);
+               status = acpi_get_table(ACPI_SIG_SPMI, i+1,
+                                       (struct acpi_table_header **)&spmi);
                if (status != AE_OK)
                        return;
 
@@ -1983,6 +2050,7 @@ static __devinit void try_init_dmi(struct dmi_ipmi_data *ipmi_data)
                info->si_type = SI_BT;
                break;
        default:
+               kfree(info);
                return;
        }
 
@@ -2122,12 +2190,15 @@ static int __devinit ipmi_pci_probe(struct pci_dev *pdev,
                info->irq_setup = std_irq_setup;
 
        info->dev = &pdev->dev;
+       pci_set_drvdata(pdev, info);
 
        return try_smi_init(info);
 }
 
 static void __devexit ipmi_pci_remove(struct pci_dev *pdev)
 {
+       struct smi_info *info = pci_get_drvdata(pdev);
+       cleanup_one_si(info);
 }
 
 #ifdef CONFIG_PM
@@ -2144,7 +2215,8 @@ static int ipmi_pci_resume(struct pci_dev *pdev)
 
 static struct pci_device_id ipmi_pci_devices[] = {
        { PCI_DEVICE(PCI_HP_VENDOR_ID, PCI_MMC_DEVICE_ID) },
-       { PCI_DEVICE_CLASS(PCI_ERMC_CLASSCODE, PCI_ERMC_CLASSCODE_MASK) }
+       { PCI_DEVICE_CLASS(PCI_ERMC_CLASSCODE, PCI_ERMC_CLASSCODE_MASK) },
+       { 0, }
 };
 MODULE_DEVICE_TABLE(pci, ipmi_pci_devices);
 
@@ -2161,6 +2233,99 @@ static struct pci_driver ipmi_pci_driver = {
 #endif /* CONFIG_PCI */
 
 
+#ifdef CONFIG_PPC_OF
+static int __devinit ipmi_of_probe(struct of_device *dev,
+                        const struct of_device_id *match)
+{
+       struct smi_info *info;
+       struct resource resource;
+       const int *regsize, *regspacing, *regshift;
+       struct device_node *np = dev->node;
+       int ret;
+       int proplen;
+
+       dev_info(&dev->dev, PFX "probing via device tree\n");
+
+       ret = of_address_to_resource(np, 0, &resource);
+       if (ret) {
+               dev_warn(&dev->dev, PFX "invalid address from OF\n");
+               return ret;
+       }
+
+       regsize = of_get_property(np, "reg-size", &proplen);
+       if (regsize && proplen != 4) {
+               dev_warn(&dev->dev, PFX "invalid regsize from OF\n");
+               return -EINVAL;
+       }
+
+       regspacing = of_get_property(np, "reg-spacing", &proplen);
+       if (regspacing && proplen != 4) {
+               dev_warn(&dev->dev, PFX "invalid regspacing from OF\n");
+               return -EINVAL;
+       }
+
+       regshift = of_get_property(np, "reg-shift", &proplen);
+       if (regshift && proplen != 4) {
+               dev_warn(&dev->dev, PFX "invalid regshift from OF\n");
+               return -EINVAL;
+       }
+
+       info = kzalloc(sizeof(*info), GFP_KERNEL);
+
+       if (!info) {
+               dev_err(&dev->dev,
+                       PFX "could not allocate memory for OF probe\n");
+               return -ENOMEM;
+       }
+
+       info->si_type           = (enum si_type) match->data;
+       info->addr_source       = "device-tree";
+       info->io_setup          = mem_setup;
+       info->irq_setup         = std_irq_setup;
+
+       info->io.addr_type      = IPMI_MEM_ADDR_SPACE;
+       info->io.addr_data      = resource.start;
+
+       info->io.regsize        = regsize ? *regsize : DEFAULT_REGSIZE;
+       info->io.regspacing     = regspacing ? *regspacing : DEFAULT_REGSPACING;
+       info->io.regshift       = regshift ? *regshift : 0;
+
+       info->irq               = irq_of_parse_and_map(dev->node, 0);
+       info->dev               = &dev->dev;
+
+       dev_dbg(&dev->dev, "addr 0x%lx regsize %d spacing %d irq %x\n",
+               info->io.addr_data, info->io.regsize, info->io.regspacing,
+               info->irq);
+
+       dev->dev.driver_data = (void*) info;
+
+       return try_smi_init(info);
+}
+
+static int __devexit ipmi_of_remove(struct of_device *dev)
+{
+       cleanup_one_si(dev->dev.driver_data);
+       return 0;
+}
+
+static struct of_device_id ipmi_match[] =
+{
+       { .type = "ipmi", .compatible = "ipmi-kcs",  .data = (void *)(unsigned long) SI_KCS },
+       { .type = "ipmi", .compatible = "ipmi-smic", .data = (void *)(unsigned long) SI_SMIC },
+       { .type = "ipmi", .compatible = "ipmi-bt",   .data = (void *)(unsigned long) SI_BT },
+       {},
+};
+
+static struct of_platform_driver ipmi_of_platform_driver =
+{
+       .name           = "ipmi",
+       .match_table    = ipmi_match,
+       .probe          = ipmi_of_probe,
+       .remove         = __devexit_p(ipmi_of_remove),
+};
+#endif /* CONFIG_PPC_OF */
+
+
 static int try_get_dev_id(struct smi_info *smi_info)
 {
        unsigned char         msg[2];
@@ -2467,6 +2632,11 @@ static __devinit void default_find_bmc(void)
                if (!info)
                        return;
 
+#ifdef CONFIG_PPC_MERGE
+               if (check_legacy_ioport(ipmi_defaults[i].port))
+                       continue;
+#endif
+
                info->addr_source = NULL;
 
                info->si_type = ipmi_defaults[i].type;
@@ -2689,7 +2859,7 @@ static int try_smi_init(struct smi_info *new_smi)
 
        mutex_unlock(&smi_infos_lock);
 
-       printk(IPMI %s interface initialized\n",si_to_str[new_smi->si_type]);
+       printk(KERN_INFO "IPMI %s interface initialized\n",si_to_str[new_smi->si_type]);
 
        return 0;
 
@@ -2773,8 +2943,7 @@ static __devinit int init_ipmi_si(void)
 #endif
 
 #ifdef CONFIG_ACPI
-       if (si_trydefaults)
-               acpi_find_bmc();
+       acpi_find_bmc();
 #endif
 
 #ifdef CONFIG_PCI
@@ -2786,6 +2955,10 @@ static __devinit int init_ipmi_si(void)
        }
 #endif
 
+#ifdef CONFIG_PPC_OF
+       of_register_platform_driver(&ipmi_of_platform_driver);
+#endif
+
        if (si_trydefaults) {
                mutex_lock(&smi_infos_lock);
                if (list_empty(&smi_infos)) {
@@ -2803,6 +2976,10 @@ static __devinit int init_ipmi_si(void)
 #ifdef CONFIG_PCI
                pci_unregister_driver(&ipmi_pci_driver);
 #endif
+
+#ifdef CONFIG_PPC_OF
+               of_unregister_platform_driver(&ipmi_of_platform_driver);
+#endif
                driver_unregister(&ipmi_driver);
                printk("ipmi_si: Unable to find any System Interface(s)\n");
                return -ENODEV;
@@ -2823,28 +3000,33 @@ static void cleanup_one_si(struct smi_info *to_clean)
 
        list_del(&to_clean->link);
 
-       /* Tell the timer and interrupt handlers that we are shutting
-          down. */
-       spin_lock_irqsave(&(to_clean->si_lock), flags);
-       spin_lock(&(to_clean->msg_lock));
-
+       /* Tell the driver that we are shutting down. */
        atomic_inc(&to_clean->stop_operation);
 
-       if (to_clean->irq_cleanup)
-               to_clean->irq_cleanup(to_clean);
-
-       spin_unlock(&(to_clean->msg_lock));
-       spin_unlock_irqrestore(&(to_clean->si_lock), flags);
-
-       /* Wait until we know that we are out of any interrupt
-          handlers might have been running before we freed the
-          interrupt. */
-       synchronize_sched();
-
+       /* Make sure the timer and thread are stopped and will not run
+          again. */
        wait_for_timer_and_thread(to_clean);
 
-       /* Interrupts and timeouts are stopped, now make sure the
-          interface is in a clean state. */
+       /* Timeouts are stopped, now make sure the interrupts are off
+          for the device.  A little tricky with locks to make sure
+          there are no races. */
+       spin_lock_irqsave(&to_clean->si_lock, flags);
+       while (to_clean->curr_msg || (to_clean->si_state != SI_NORMAL)) {
+               spin_unlock_irqrestore(&to_clean->si_lock, flags);
+               poll(to_clean);
+               schedule_timeout_uninterruptible(1);
+               spin_lock_irqsave(&to_clean->si_lock, flags);
+       }
+       disable_si_irq(to_clean);
+       spin_unlock_irqrestore(&to_clean->si_lock, flags);
+       while (to_clean->curr_msg || (to_clean->si_state != SI_NORMAL)) {
+               poll(to_clean);
+               schedule_timeout_uninterruptible(1);
+       }
+
+       /* Clean up interrupts and make sure that everything is done. */
+       if (to_clean->irq_cleanup)
+               to_clean->irq_cleanup(to_clean);
        while (to_clean->curr_msg || (to_clean->si_state != SI_NORMAL)) {
                poll(to_clean);
                schedule_timeout_uninterruptible(1);
@@ -2883,6 +3065,10 @@ static __exit void cleanup_ipmi_si(void)
        pci_unregister_driver(&ipmi_pci_driver);
 #endif
 
+#ifdef CONFIG_PPC_OF
+       of_unregister_platform_driver(&ipmi_of_platform_driver);
+#endif
+
        mutex_lock(&smi_infos_lock);
        list_for_each_entry_safe(e, tmp_e, &smi_infos, link)
                cleanup_one_si(e);