1 /*
2  * ipmi_si_hotmod.c
3  *
4  * Handling for dynamically adding/removing IPMI devices through
5  * a module parameter (and thus sysfs).
6  */
7 #include <linux/moduleparam.h>
8 #include <linux/ipmi.h>
9 #include "ipmi_si.h"
10 
11 #define PFX "ipmi_hotmod: "
12 
13 static int hotmod_handler(const char *val, const struct kernel_param *kp);
14 
15 module_param_call(hotmod, hotmod_handler, NULL, NULL, 0200);
16 MODULE_PARM_DESC(hotmod, "Add and remove interfaces.  See"
17 		 " Documentation/IPMI.txt in the kernel sources for the"
18 		 " gory details.");
19 
20 /*
21  * Parms come in as <op1>[:op2[:op3...]].  ops are:
22  *   add|remove,kcs|bt|smic,mem|i/o,<address>[,<opt1>[,<opt2>[,...]]]
23  * Options are:
24  *   rsp=<regspacing>
25  *   rsi=<regsize>
26  *   rsh=<regshift>
27  *   irq=<irq>
28  *   ipmb=<ipmb addr>
29  */
30 enum hotmod_op { HM_ADD, HM_REMOVE };
31 struct hotmod_vals {
32 	const char *name;
33 	const int  val;
34 };
35 
36 static const struct hotmod_vals hotmod_ops[] = {
37 	{ "add",	HM_ADD },
38 	{ "remove",	HM_REMOVE },
39 	{ NULL }
40 };
41 
42 static const struct hotmod_vals hotmod_si[] = {
43 	{ "kcs",	SI_KCS },
44 	{ "smic",	SI_SMIC },
45 	{ "bt",		SI_BT },
46 	{ NULL }
47 };
48 
49 static const struct hotmod_vals hotmod_as[] = {
50 	{ "mem",	IPMI_MEM_ADDR_SPACE },
51 	{ "i/o",	IPMI_IO_ADDR_SPACE },
52 	{ NULL }
53 };
54 
55 static int parse_str(const struct hotmod_vals *v, int *val, char *name,
56 		     char **curr)
57 {
58 	char *s;
59 	int  i;
60 
61 	s = strchr(*curr, ',');
62 	if (!s) {
63 		pr_warn(PFX "No hotmod %s given.\n", name);
64 		return -EINVAL;
65 	}
66 	*s = '\0';
67 	s++;
68 	for (i = 0; v[i].name; i++) {
69 		if (strcmp(*curr, v[i].name) == 0) {
70 			*val = v[i].val;
71 			*curr = s;
72 			return 0;
73 		}
74 	}
75 
76 	pr_warn(PFX "Invalid hotmod %s '%s'\n", name, *curr);
77 	return -EINVAL;
78 }
79 
80 static int check_hotmod_int_op(const char *curr, const char *option,
81 			       const char *name, int *val)
82 {
83 	char *n;
84 
85 	if (strcmp(curr, name) == 0) {
86 		if (!option) {
87 			pr_warn(PFX "No option given for '%s'\n", curr);
88 			return -EINVAL;
89 		}
90 		*val = simple_strtoul(option, &n, 0);
91 		if ((*n != '\0') || (*option == '\0')) {
92 			pr_warn(PFX "Bad option given for '%s'\n", curr);
93 			return -EINVAL;
94 		}
95 		return 1;
96 	}
97 	return 0;
98 }
99 
100 static int hotmod_handler(const char *val, const struct kernel_param *kp)
101 {
102 	char *str = kstrdup(val, GFP_KERNEL);
103 	int  rv;
104 	char *next, *curr, *s, *n, *o;
105 	enum hotmod_op op;
106 	enum si_type si_type;
107 	int  addr_space;
108 	unsigned long addr;
109 	int regspacing;
110 	int regsize;
111 	int regshift;
112 	int irq;
113 	int ipmb;
114 	int ival;
115 	int len;
116 
117 	if (!str)
118 		return -ENOMEM;
119 
120 	/* Kill any trailing spaces, as we can get a "\n" from echo. */
121 	len = strlen(str);
122 	ival = len - 1;
123 	while ((ival >= 0) && isspace(str[ival])) {
124 		str[ival] = '\0';
125 		ival--;
126 	}
127 
128 	for (curr = str; curr; curr = next) {
129 		regspacing = 1;
130 		regsize = 1;
131 		regshift = 0;
132 		irq = 0;
133 		ipmb = 0; /* Choose the default if not specified */
134 
135 		next = strchr(curr, ':');
136 		if (next) {
137 			*next = '\0';
138 			next++;
139 		}
140 
141 		rv = parse_str(hotmod_ops, &ival, "operation", &curr);
142 		if (rv)
143 			break;
144 		op = ival;
145 
146 		rv = parse_str(hotmod_si, &ival, "interface type", &curr);
147 		if (rv)
148 			break;
149 		si_type = ival;
150 
151 		rv = parse_str(hotmod_as, &addr_space, "address space", &curr);
152 		if (rv)
153 			break;
154 
155 		s = strchr(curr, ',');
156 		if (s) {
157 			*s = '\0';
158 			s++;
159 		}
160 		addr = simple_strtoul(curr, &n, 0);
161 		if ((*n != '\0') || (*curr == '\0')) {
162 			pr_warn(PFX "Invalid hotmod address '%s'\n", curr);
163 			break;
164 		}
165 
166 		while (s) {
167 			curr = s;
168 			s = strchr(curr, ',');
169 			if (s) {
170 				*s = '\0';
171 				s++;
172 			}
173 			o = strchr(curr, '=');
174 			if (o) {
175 				*o = '\0';
176 				o++;
177 			}
178 			rv = check_hotmod_int_op(curr, o, "rsp", &regspacing);
179 			if (rv < 0)
180 				goto out;
181 			else if (rv)
182 				continue;
183 			rv = check_hotmod_int_op(curr, o, "rsi", &regsize);
184 			if (rv < 0)
185 				goto out;
186 			else if (rv)
187 				continue;
188 			rv = check_hotmod_int_op(curr, o, "rsh", &regshift);
189 			if (rv < 0)
190 				goto out;
191 			else if (rv)
192 				continue;
193 			rv = check_hotmod_int_op(curr, o, "irq", &irq);
194 			if (rv < 0)
195 				goto out;
196 			else if (rv)
197 				continue;
198 			rv = check_hotmod_int_op(curr, o, "ipmb", &ipmb);
199 			if (rv < 0)
200 				goto out;
201 			else if (rv)
202 				continue;
203 
204 			rv = -EINVAL;
205 			pr_warn(PFX "Invalid hotmod option '%s'\n", curr);
206 			goto out;
207 		}
208 
209 		if (op == HM_ADD) {
210 			struct si_sm_io io;
211 
212 			memset(&io, 0, sizeof(io));
213 			io.addr_source = SI_HOTMOD;
214 			io.si_type = si_type;
215 			io.addr_data = addr;
216 			io.addr_type = addr_space;
217 
218 			io.addr = NULL;
219 			io.regspacing = regspacing;
220 			if (!io.regspacing)
221 				io.regspacing = DEFAULT_REGSPACING;
222 			io.regsize = regsize;
223 			if (!io.regsize)
224 				io.regsize = DEFAULT_REGSIZE;
225 			io.regshift = regshift;
226 			io.irq = irq;
227 			if (io.irq)
228 				io.irq_setup = ipmi_std_irq_setup;
229 			io.slave_addr = ipmb;
230 
231 			rv = ipmi_si_add_smi(&io);
232 			if (rv)
233 				goto out;
234 		} else {
235 			ipmi_si_remove_by_data(addr_space, si_type, addr);
236 		}
237 	}
238 	rv = len;
239 out:
240 	kfree(str);
241 	return rv;
242 }
243