[PATCH] IPMI: system interface hotplug
authorCorey Minyard <minyard@acm.org>
Thu, 7 Dec 2006 04:41:07 +0000 (20:41 -0800)
committerLinus Torvalds <torvalds@woody.osdl.org>
Thu, 7 Dec 2006 16:39:47 +0000 (08:39 -0800)
Add the ability to hot add and remove interfaces in the ipmi_si driver.  Any
users who have the device open will get errors if they try to send a message.

Signed-off-by: Corey Minyard <minyard@acm.org>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Documentation/IPMI.txt
drivers/char/ipmi/ipmi_si_intf.c

index 9101cbf..24dc3fc 100644 (file)
@@ -365,6 +365,7 @@ You can change this at module load time (for a module) with:
        regshifts=<shift1>,<shift2>,...
        slave_addrs=<addr1>,<addr2>,...
        force_kipmid=<enable1>,<enable2>,...
+       unload_when_empty=[0|1]
 
 Each of these except si_trydefaults is a list, the first item for the
 first interface, second item for the second interface, etc.
@@ -416,6 +417,11 @@ by the driver, but systems with broken interrupts might need an enable,
 or users that don't want the daemon (don't need the performance, don't
 want the CPU hit) can disable it.
 
+If unload_when_empty is set to 1, the driver will be unloaded if it
+doesn't find any interfaces or all the interfaces fail to work.  The
+default is one.  Setting to 0 is useful with the hotmod, but is
+obviously only useful for modules.
+
 When compiled into the kernel, the parameters can be specified on the
 kernel command line as:
 
@@ -441,6 +447,25 @@ have high-res timers enabled in the kernel and you don't have
 interrupts enabled, the driver will run VERY slowly.  Don't blame me,
 these interfaces suck.
 
+The driver supports a hot add and remove of interfaces.  This way,
+interfaces can be added or removed after the kernel is up and running.
+This is done using /sys/modules/ipmi_si/hotmod, which is a write-only
+parameter.  You write a string to this interface.  The string has the
+format:
+   <op1>[:op2[:op3...]]
+The "op"s are:
+   add|remove,kcs|bt|smic,mem|i/o,<address>[,<opt1>[,<opt2>[,...]]]
+You can specify more than one interface on the line.  The "opt"s are:
+   rsp=<regspacing>
+   rsi=<regsize>
+   rsh=<regshift>
+   irq=<irq>
+   ipmb=<ipmb slave addr>
+and these have the same meanings as discussed above.  Note that you
+can also use this on the kernel command line for a more compact format
+for specifying an interface.  Note that when removing an interface,
+only the first three parameters (si type, address type, and address)
+are used for the comparison.  Any options are ignored for removing.
 
 The SMBus Driver
 ----------------
index bedd763..8b36b50 100644 (file)
 #include "ipmi_si_sm.h"
 #include <linux/init.h>
 #include <linux/dmi.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+
+#define PFX "ipmi_si: "
 
 /* Measure times between events in the driver. */
 #undef DEBUG_TIMING
@@ -92,7 +96,7 @@ enum si_intf_state {
 enum si_type {
     SI_KCS, SI_SMIC, SI_BT
 };
-static char *si_to_str[] = { "KCS", "SMIC", "BT" };
+static char *si_to_str[] = { "kcs", "smic", "bt" };
 
 #define DEVICE_NAME "ipmi_si"
 
@@ -222,7 +226,10 @@ struct smi_info
 static int force_kipmid[SI_MAX_PARMS];
 static int num_force_kipmid;
 
+static int unload_when_empty = 1;
+
 static int try_smi_init(struct smi_info *smi);
+static void cleanup_one_si(struct smi_info *to_clean);
 
 static ATOMIC_NOTIFIER_HEAD(xaction_notifier_list);
 static int register_xaction_notifier(struct notifier_block * nb)
@@ -247,7 +254,7 @@ static void return_hosed_msg(struct smi_info *smi_info)
        /* Make it a reponse */
        msg->rsp[0] = msg->data[0] | 4;
        msg->rsp[1] = msg->data[1];
-       msg->rsp[2] = 0xFF; /* Unknown error. */
+       msg->rsp[2] = IPMI_ERR_UNSPECIFIED;
        msg->rsp_size = 3;
 
        smi_info->curr_msg = NULL;
@@ -716,6 +723,15 @@ static void sender(void                *send_info,
        struct timeval    t;
 #endif
 
+       if (atomic_read(&smi_info->stop_operation)) {
+               msg->rsp[0] = msg->data[0] | 4;
+               msg->rsp[1] = msg->data[1];
+               msg->rsp[2] = IPMI_ERR_UNSPECIFIED;
+               msg->rsp_size = 3;
+               deliver_recv_msg(smi_info, msg);
+               return;
+       }
+
        spin_lock_irqsave(&(smi_info->msg_lock), flags);
 #ifdef DEBUG_TIMING
        do_gettimeofday(&t);
@@ -819,6 +835,9 @@ static void request_events(void *send_info)
 {
        struct smi_info *smi_info = send_info;
 
+       if (atomic_read(&smi_info->stop_operation))
+               return;
+
        atomic_set(&smi_info->req_events, 1);
 }
 
@@ -1003,6 +1022,16 @@ static int num_regshifts = 0;
 static int slave_addrs[SI_MAX_PARMS];
 static int num_slave_addrs = 0;
 
+#define IPMI_IO_ADDR_SPACE  0
+#define IPMI_MEM_ADDR_SPACE 1
+static char *addr_space_to_str[] = { "I/O", "mem" };
+
+static int hotmod_handler(const char *val, struct kernel_param *kp);
+
+module_param_call(hotmod, hotmod_handler, NULL, NULL, 0200);
+MODULE_PARM_DESC(hotmod, "Add and remove interfaces.  See"
+                " Documentation/IPMI.txt in the kernel sources for the"
+                " gory details.");
 
 module_param_named(trydefaults, si_trydefaults, bool, 0);
 MODULE_PARM_DESC(trydefaults, "Setting this to 'false' will disable the"
@@ -1054,12 +1083,12 @@ module_param_array(force_kipmid, int, &num_force_kipmid, 0);
 MODULE_PARM_DESC(force_kipmid, "Force the kipmi daemon to be enabled (1) or"
                 " disabled(0).  Normally the IPMI driver auto-detects"
                 " this, but the value may be overridden by this parm.");
+module_param(unload_when_empty, int, 0);
+MODULE_PARM_DESC(unload_when_empty, "Unload the module if no interfaces are"
+                " specified or found, default is 1.  Setting to 0"
+                " is useful for hot add of devices using hotmod.");
 
 
-#define IPMI_IO_ADDR_SPACE  0
-#define IPMI_MEM_ADDR_SPACE 1
-static char *addr_space_to_str[] = { "I/O", "memory" };
-
 static void std_irq_cleanup(struct smi_info *info)
 {
        if (info->si_type == SI_BT)
@@ -1333,6 +1362,234 @@ static int mem_setup(struct smi_info *info)
        return 0;
 }
 
+/*
+ * Parms come in as <op1>[:op2[:op3...]].  ops are:
+ *   add|remove,kcs|bt|smic,mem|i/o,<address>[,<opt1>[,<opt2>[,...]]]
+ * Options are:
+ *   rsp=<regspacing>
+ *   rsi=<regsize>
+ *   rsh=<regshift>
+ *   irq=<irq>
+ *   ipmb=<ipmb addr>
+ */
+enum hotmod_op { HM_ADD, HM_REMOVE };
+struct hotmod_vals {
+       char *name;
+       int  val;
+};
+static struct hotmod_vals hotmod_ops[] = {
+       { "add",        HM_ADD },
+       { "remove",     HM_REMOVE },
+       { NULL }
+};
+static struct hotmod_vals hotmod_si[] = {
+       { "kcs",        SI_KCS },
+       { "smic",       SI_SMIC },
+       { "bt",         SI_BT },
+       { NULL }
+};
+static struct hotmod_vals hotmod_as[] = {
+       { "mem",        IPMI_MEM_ADDR_SPACE },
+       { "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;
+       int  i;
+
+       s = strchr(*curr, ',');
+       if (!s) {
+               printk(KERN_WARNING PFX "No hotmod %s given.\n", name);
+               return -EINVAL;
+       }
+       *s = '\0';
+       s++;
+       for (i = 0; hotmod_ops[i].name; i++) {
+               if (ipmi_strcasecmp(*curr, v[i].name) == 0) {
+                       *val = v[i].val;
+                       *curr = s;
+                       return 0;
+               }
+       }
+
+       printk(KERN_WARNING PFX "Invalid hotmod %s '%s'\n", name, *curr);
+       return -EINVAL;
+}
+
+static int hotmod_handler(const char *val, struct kernel_param *kp)
+{
+       char *str = kstrdup(val, GFP_KERNEL);
+       int  rv = -EINVAL;
+       char *next, *curr, *s, *n, *o;
+       enum hotmod_op op;
+       enum si_type si_type;
+       int  addr_space;
+       unsigned long addr;
+       int regspacing;
+       int regsize;
+       int regshift;
+       int irq;
+       int ipmb;
+       int ival;
+       struct smi_info *info;
+
+       if (!str)
+               return -ENOMEM;
+
+       /* Kill any trailing spaces, as we can get a "\n" from echo. */
+       ival = strlen(str) - 1;
+       while ((ival >= 0) && isspace(str[ival])) {
+               str[ival] = '\0';
+               ival--;
+       }
+
+       for (curr = str; curr; curr = next) {
+               regspacing = 1;
+               regsize = 1;
+               regshift = 0;
+               irq = 0;
+               ipmb = 0x20;
+
+               next = strchr(curr, ':');
+               if (next) {
+                       *next = '\0';
+                       next++;
+               }
+
+               rv = parse_str(hotmod_ops, &ival, "operation", &curr);
+               if (rv)
+                       break;
+               op = ival;
+
+               rv = parse_str(hotmod_si, &ival, "interface type", &curr);
+               if (rv)
+                       break;
+               si_type = ival;
+
+               rv = parse_str(hotmod_as, &addr_space, "address space", &curr);
+               if (rv)
+                       break;
+
+               s = strchr(curr, ',');
+               if (s) {
+                       *s = '\0';
+                       s++;
+               }
+               addr = simple_strtoul(curr, &n, 0);
+               if ((*n != '\0') || (*curr == '\0')) {
+                       printk(KERN_WARNING PFX "Invalid hotmod address"
+                              " '%s'\n", curr);
+                       break;
+               }
+
+               while (s) {
+                       curr = s;
+                       s = strchr(curr, ',');
+                       if (s) {
+                               *s = '\0';
+                               s++;
+                       }
+                       o = strchr(curr, '=');
+                       if (o) {
+                               *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);
+                               goto out;
+                       }
+#undef HOTMOD_INT_OPT
+               }
+
+               if (op == HM_ADD) {
+                       info = kzalloc(sizeof(*info), GFP_KERNEL);
+                       if (!info) {
+                               rv = -ENOMEM;
+                               goto out;
+                       }
+
+                       info->addr_source = "hotmod";
+                       info->si_type = si_type;
+                       info->io.addr_data = addr;
+                       info->io.addr_type = addr_space;
+                       if (addr_space == IPMI_MEM_ADDR_SPACE)
+                               info->io_setup = mem_setup;
+                       else
+                               info->io_setup = port_setup;
+
+                       info->io.addr = NULL;
+                       info->io.regspacing = regspacing;
+                       if (!info->io.regspacing)
+                               info->io.regspacing = DEFAULT_REGSPACING;
+                       info->io.regsize = regsize;
+                       if (!info->io.regsize)
+                               info->io.regsize = DEFAULT_REGSPACING;
+                       info->io.regshift = regshift;
+                       info->irq = irq;
+                       if (info->irq)
+                               info->irq_setup = std_irq_setup;
+                       info->slave_addr = ipmb;
+
+                       try_smi_init(info);
+               } else {
+                       /* remove */
+                       struct smi_info *e, *tmp_e;
+
+                       mutex_lock(&smi_infos_lock);
+                       list_for_each_entry_safe(e, tmp_e, &smi_infos, link) {
+                               if (e->io.addr_type != addr_space)
+                                       continue;
+                               if (e->si_type != si_type)
+                                       continue;
+                               if (e->io.addr_data == addr)
+                                       cleanup_one_si(e);
+                       }
+                       mutex_unlock(&smi_infos_lock);
+               }
+       }
+ out:
+       kfree(str);
+       return rv;
+}
 
 static __devinit void hardcode_find_bmc(void)
 {
@@ -1349,11 +1606,11 @@ static __devinit void hardcode_find_bmc(void)
 
                info->addr_source = "hardcoded";
 
-               if (!si_type[i] || strcmp(si_type[i], "kcs") == 0) {
+               if (!si_type[i] || ipmi_strcasecmp(si_type[i], "kcs") == 0) {
                        info->si_type = SI_KCS;
-               } else if (strcmp(si_type[i], "smic") == 0) {
+               } else if (ipmi_strcasecmp(si_type[i], "smic") == 0) {
                        info->si_type = SI_SMIC;
-               } else if (strcmp(si_type[i], "bt") == 0) {
+               } else if (ipmi_strcasecmp(si_type[i], "bt") == 0) {
                        info->si_type = SI_BT;
                } else {
                        printk(KERN_WARNING
@@ -1968,19 +2225,9 @@ static int try_get_dev_id(struct smi_info *smi_info)
 static int type_file_read_proc(char *page, char **start, off_t off,
                               int count, int *eof, void *data)
 {
-       char            *out = (char *) page;
        struct smi_info *smi = data;
 
-       switch (smi->si_type) {
-           case SI_KCS:
-               return sprintf(out, "kcs\n");
-           case SI_SMIC:
-               return sprintf(out, "smic\n");
-           case SI_BT:
-               return sprintf(out, "bt\n");
-           default:
-               return 0;
-       }
+       return sprintf(page, "%s\n", si_to_str[smi->si_type]);
 }
 
 static int stat_file_read_proc(char *page, char **start, off_t off,
@@ -2016,7 +2263,24 @@ static int stat_file_read_proc(char *page, char **start, off_t off,
        out += sprintf(out, "incoming_messages:     %ld\n",
                       smi->incoming_messages);
 
-       return (out - ((char *) page));
+       return out - page;
+}
+
+static int param_read_proc(char *page, char **start, off_t off,
+                          int count, int *eof, void *data)
+{
+       struct smi_info *smi = data;
+
+       return sprintf(page,
+                      "%s,%s,0x%lx,rsp=%d,rsi=%d,rsh=%d,irq=%d,ipmb=%d\n",
+                      si_to_str[smi->si_type],
+                      addr_space_to_str[smi->io.addr_type],
+                      smi->io.addr_data,
+                      smi->io.regspacing,
+                      smi->io.regsize,
+                      smi->io.regshift,
+                      smi->irq,
+                      smi->slave_addr);
 }
 
 /*
@@ -2407,6 +2671,16 @@ static int try_smi_init(struct smi_info *new_smi)
                goto out_err_stop_timer;
        }
 
+       rv = ipmi_smi_add_proc_entry(new_smi->intf, "params",
+                                    param_read_proc, NULL,
+                                    new_smi, THIS_MODULE);
+       if (rv) {
+               printk(KERN_ERR
+                      "ipmi_si: Unable to create proc entry: %d\n",
+                      rv);
+               goto out_err_stop_timer;
+       }
+
        list_add_tail(&new_smi->link, &smi_infos);
 
        mutex_unlock(&smi_infos_lock);
@@ -2515,7 +2789,7 @@ static __devinit int init_ipmi_si(void)
        }
 
        mutex_lock(&smi_infos_lock);
-       if (list_empty(&smi_infos)) {
+       if (unload_when_empty && list_empty(&smi_infos)) {
                mutex_unlock(&smi_infos_lock);
 #ifdef CONFIG_PCI
                pci_unregister_driver(&ipmi_pci_driver);
@@ -2530,7 +2804,7 @@ static __devinit int init_ipmi_si(void)
 }
 module_init(init_ipmi_si);
 
-static void __devexit cleanup_one_si(struct smi_info *to_clean)
+static void cleanup_one_si(struct smi_info *to_clean)
 {
        int           rv;
        unsigned long flags;