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