vserver 1.9.3
[linux-2.6.git] / drivers / char / ipmi / ipmi_si_intf.c
index ea330f3..835d6a4 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);
@@ -912,6 +938,23 @@ 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
@@ -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))
-               release_region (*addr, info->io_size);
+       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(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.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)
+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)
@@ -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)
@@ -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,6 +1563,7 @@ 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;
 
        ipmi_data->type = data[0x04];
 
@@ -1375,7 +1578,27 @@ static int decode_dmi(dmi_header_t *dm, dmi_ipmi_data_t *ipmi_data)
                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))
@@ -1500,18 +1723,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 +1736,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;
@@ -1596,11 +1820,13 @@ static int find_pci_smic(int intf_num, struct smi_info **new_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;
 
@@ -1860,6 +2086,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 +2136,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]);
@@ -2052,6 +2279,13 @@ 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);
+               schedule_timeout(1);
+       }
+
        rv = ipmi_unregister_smi(to_clean->intf);
        if (rv) {
                printk(KERN_ERR