1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * ipmi_si_hotmod.c
4 *
5 * Handling for dynamically adding/removing IPMI devices through
6 * a module parameter (and thus sysfs).
7 */
8
9#define pr_fmt(fmt) "ipmi_hotmod: " fmt
10
11#include <linux/moduleparam.h>
12#include <linux/ipmi.h>
13#include <linux/atomic.h>
14#include "ipmi_si.h"
15#include "ipmi_plat_data.h"
16
17static int hotmod_handler(const char *val, const struct kernel_param *kp);
18
19module_param_call(hotmod, hotmod_handler, NULL, NULL, 0200);
20MODULE_PARM_DESC(hotmod,
21 "Add and remove interfaces. See Documentation/driver-api/ipmi.rst in the kernel sources for the gory details.");
22
23/*
24 * Parms come in as <op1>[:op2[:op3...]]. ops are:
25 * add|remove,kcs|bt|smic,mem|i/o,<address>[,<opt1>[,<opt2>[,...]]]
26 * Options are:
27 * rsp=<regspacing>
28 * rsi=<regsize>
29 * rsh=<regshift>
30 * irq=<irq>
31 * ipmb=<ipmb addr>
32 */
33enum hotmod_op { HM_ADD, HM_REMOVE };
34struct hotmod_vals {
35 const char *name;
36 const int val;
37};
38
39static const struct hotmod_vals hotmod_ops[] = {
40 { "add", HM_ADD },
41 { "remove", HM_REMOVE },
42 { NULL }
43};
44
45static const struct hotmod_vals hotmod_si[] = {
46 { "kcs", SI_KCS },
47 { "smic", SI_SMIC },
48 { "bt", SI_BT },
49 { NULL }
50};
51
52static const struct hotmod_vals hotmod_as[] = {
53 { "mem", IPMI_MEM_ADDR_SPACE },
54 { "i/o", IPMI_IO_ADDR_SPACE },
55 { NULL }
56};
57
58static int parse_str(const struct hotmod_vals *v, unsigned int *val, char *name,
59 const char **curr)
60{
61 char *s;
62 int i;
63
64 s = strchr(*curr, ',');
65 if (!s) {
66 pr_warn("No hotmod %s given\n", name);
67 return -EINVAL;
68 }
69 *s = '\0';
70 s++;
71 for (i = 0; v[i].name; i++) {
72 if (strcmp(*curr, v[i].name) == 0) {
73 *val = v[i].val;
74 *curr = s;
75 return 0;
76 }
77 }
78
79 pr_warn("Invalid hotmod %s '%s'\n", name, *curr);
80 return -EINVAL;
81}
82
83static int check_hotmod_int_op(const char *curr, const char *option,
84 const char *name, unsigned int *val)
85{
86 char *n;
87
88 if (strcmp(curr, name) == 0) {
89 if (!option) {
90 pr_warn("No option given for '%s'\n", curr);
91 return -EINVAL;
92 }
93 *val = simple_strtoul(option, &n, 0);
94 if ((*n != '\0') || (*option == '\0')) {
95 pr_warn("Bad option given for '%s'\n", curr);
96 return -EINVAL;
97 }
98 return 1;
99 }
100 return 0;
101}
102
103static int parse_hotmod_str(const char *curr, enum hotmod_op *op,
104 struct ipmi_plat_data *h)
105{
106 char *s, *o;
107 int rv;
108 unsigned int ival;
109
110 h->iftype = IPMI_PLAT_IF_SI;
111 rv = parse_str(v: hotmod_ops, val: &ival, name: "operation", curr: &curr);
112 if (rv)
113 return rv;
114 *op = ival;
115
116 rv = parse_str(v: hotmod_si, val: &ival, name: "interface type", curr: &curr);
117 if (rv)
118 return rv;
119 h->type = ival;
120
121 rv = parse_str(v: hotmod_as, val: &ival, name: "address space", curr: &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(s: curr, base: 0, res: &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, option: o, name: "rsp", val: &h->regspacing);
150 if (rv < 0)
151 return rv;
152 else if (rv)
153 continue;
154 rv = check_hotmod_int_op(curr, option: o, name: "rsi", val: &h->regsize);
155 if (rv < 0)
156 return rv;
157 else if (rv)
158 continue;
159 rv = check_hotmod_int_op(curr, option: o, name: "rsh", val: &h->regshift);
160 if (rv < 0)
161 return rv;
162 else if (rv)
163 continue;
164 rv = check_hotmod_int_op(curr, option: o, name: "irq", val: &h->irq);
165 if (rv < 0)
166 return rv;
167 else if (rv)
168 continue;
169 rv = check_hotmod_int_op(curr, option: o, name: "ipmb", val: &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
185static int hotmod_handler(const char *val, const struct kernel_param *kp)
186{
187 int rv;
188 struct ipmi_plat_data h;
189 char *str, *curr, *next;
190
191 str = kstrdup(s: val, GFP_KERNEL);
192 if (!str)
193 return -ENOMEM;
194
195 /* Kill any trailing spaces, as we can get a "\n" from echo. */
196 for (curr = strstrip(str); curr; curr = next) {
197 enum hotmod_op op;
198
199 next = strchr(curr, ':');
200 if (next) {
201 *next = '\0';
202 next++;
203 }
204
205 memset(&h, 0, sizeof(h));
206 rv = parse_hotmod_str(curr, op: &op, h: &h);
207 if (rv)
208 goto out;
209
210 if (op == HM_ADD) {
211 ipmi_platform_add(name: "hotmod-ipmi-si",
212 inst: atomic_inc_return(v: &hotmod_nr),
213 p: &h);
214 } else {
215 struct device *dev;
216
217 dev = ipmi_si_remove_by_data(addr_space: h.space, si_type: h.type, addr: h.addr);
218 if (dev && dev_is_platform(dev)) {
219 struct platform_device *pdev;
220
221 pdev = to_platform_device(dev);
222 if (strcmp(pdev->name, "hotmod-ipmi-si") == 0)
223 platform_device_unregister(pdev);
224 }
225 put_device(dev);
226 }
227 }
228 rv = strlen(val);
229out:
230 kfree(objp: str);
231 return rv;
232}
233
234void ipmi_si_hotmod_exit(void)
235{
236 ipmi_remove_platform_device_by_name(name: "hotmod-ipmi-si");
237}
238

source code of linux/drivers/char/ipmi/ipmi_si_hotmod.c