244 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			244 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0+
 | |
| /*
 | |
|  * ipmi_si_hotmod.c
 | |
|  *
 | |
|  * Handling for dynamically adding/removing IPMI devices through
 | |
|  * a module parameter (and thus sysfs).
 | |
|  */
 | |
| #include <linux/moduleparam.h>
 | |
| #include <linux/ipmi.h>
 | |
| #include "ipmi_si.h"
 | |
| 
 | |
| #define PFX "ipmi_hotmod: "
 | |
| 
 | |
| static int hotmod_handler(const char *val, const 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.");
 | |
| 
 | |
| /*
 | |
|  * 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 {
 | |
| 	const char *name;
 | |
| 	const int  val;
 | |
| };
 | |
| 
 | |
| static const struct hotmod_vals hotmod_ops[] = {
 | |
| 	{ "add",	HM_ADD },
 | |
| 	{ "remove",	HM_REMOVE },
 | |
| 	{ NULL }
 | |
| };
 | |
| 
 | |
| static const struct hotmod_vals hotmod_si[] = {
 | |
| 	{ "kcs",	SI_KCS },
 | |
| 	{ "smic",	SI_SMIC },
 | |
| 	{ "bt",		SI_BT },
 | |
| 	{ NULL }
 | |
| };
 | |
| 
 | |
| static const struct hotmod_vals hotmod_as[] = {
 | |
| 	{ "mem",	IPMI_MEM_ADDR_SPACE },
 | |
| 	{ "i/o",	IPMI_IO_ADDR_SPACE },
 | |
| 	{ NULL }
 | |
| };
 | |
| 
 | |
| static int parse_str(const struct hotmod_vals *v, int *val, char *name,
 | |
| 		     char **curr)
 | |
| {
 | |
| 	char *s;
 | |
| 	int  i;
 | |
| 
 | |
| 	s = strchr(*curr, ',');
 | |
| 	if (!s) {
 | |
| 		pr_warn(PFX "No hotmod %s given.\n", name);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 	*s = '\0';
 | |
| 	s++;
 | |
| 	for (i = 0; v[i].name; i++) {
 | |
| 		if (strcmp(*curr, v[i].name) == 0) {
 | |
| 			*val = v[i].val;
 | |
| 			*curr = s;
 | |
| 			return 0;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	pr_warn(PFX "Invalid hotmod %s '%s'\n", name, *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) {
 | |
| 			pr_warn(PFX "No option given for '%s'\n", curr);
 | |
| 			return -EINVAL;
 | |
| 		}
 | |
| 		*val = simple_strtoul(option, &n, 0);
 | |
| 		if ((*n != '\0') || (*option == '\0')) {
 | |
| 			pr_warn(PFX "Bad option given for '%s'\n", curr);
 | |
| 			return -EINVAL;
 | |
| 		}
 | |
| 		return 1;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int hotmod_handler(const char *val, const struct kernel_param *kp)
 | |
| {
 | |
| 	char *str = kstrdup(val, GFP_KERNEL);
 | |
| 	int  rv;
 | |
| 	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;
 | |
| 	int len;
 | |
| 
 | |
| 	if (!str)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	/* Kill any trailing spaces, as we can get a "\n" from echo. */
 | |
| 	len = strlen(str);
 | |
| 	ival = len - 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 = 0; /* Choose the default if not specified */
 | |
| 
 | |
| 		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')) {
 | |
| 			pr_warn(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++;
 | |
| 			}
 | |
| 			rv = check_hotmod_int_op(curr, o, "rsp", ®spacing);
 | |
| 			if (rv < 0)
 | |
| 				goto out;
 | |
| 			else if (rv)
 | |
| 				continue;
 | |
| 			rv = check_hotmod_int_op(curr, o, "rsi", ®size);
 | |
| 			if (rv < 0)
 | |
| 				goto out;
 | |
| 			else if (rv)
 | |
| 				continue;
 | |
| 			rv = check_hotmod_int_op(curr, o, "rsh", ®shift);
 | |
| 			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;
 | |
| 			pr_warn(PFX "Invalid hotmod option '%s'\n", curr);
 | |
| 			goto out;
 | |
| 		}
 | |
| 
 | |
| 		if (op == HM_ADD) {
 | |
| 			struct si_sm_io io;
 | |
| 
 | |
| 			memset(&io, 0, sizeof(io));
 | |
| 			io.addr_source = SI_HOTMOD;
 | |
| 			io.si_type = si_type;
 | |
| 			io.addr_data = addr;
 | |
| 			io.addr_type = addr_space;
 | |
| 
 | |
| 			io.addr = NULL;
 | |
| 			io.regspacing = regspacing;
 | |
| 			if (!io.regspacing)
 | |
| 				io.regspacing = DEFAULT_REGSPACING;
 | |
| 			io.regsize = regsize;
 | |
| 			if (!io.regsize)
 | |
| 				io.regsize = DEFAULT_REGSIZE;
 | |
| 			io.regshift = regshift;
 | |
| 			io.irq = irq;
 | |
| 			if (io.irq)
 | |
| 				io.irq_setup = ipmi_std_irq_setup;
 | |
| 			io.slave_addr = ipmb;
 | |
| 
 | |
| 			rv = ipmi_si_add_smi(&io);
 | |
| 			if (rv)
 | |
| 				goto out;
 | |
| 		} else {
 | |
| 			ipmi_si_remove_by_data(addr_space, si_type, addr);
 | |
| 		}
 | |
| 	}
 | |
| 	rv = len;
 | |
| out:
 | |
| 	kfree(str);
 | |
| 	return rv;
 | |
| }
 | 
