Commit b361e27b authored by Corey Minyard's avatar Corey Minyard Committed by Linus Torvalds
Browse files

[PATCH] IPMI: system interface hotplug



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: default avatarCorey Minyard <minyard@acm.org>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 15c62e10
......@@ -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
----------------
......
......@@ -61,6 +61,10 @@
#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;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment