Merge tag 'sound-5.13-rc6' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai...
[linux-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);
07cbd87b
AS
20MODULE_PARM_DESC(hotmod,
21 "Add and remove interfaces. See Documentation/driver-api/ipmi.rst in the kernel sources for the gory details.");
44814ec9
CM
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
3bb8ea40
CM
58static int parse_str(const struct hotmod_vals *v, unsigned int *val, char *name,
59 const char **curr)
44814ec9
CM
60{
61 char *s;
62 int i;
63
64 s = strchr(*curr, ',');
65 if (!s) {
25880f7d 66 pr_warn("No hotmod %s given\n", name);
44814ec9
CM
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
25880f7d 79 pr_warn("Invalid hotmod %s '%s'\n", name, *curr);
44814ec9
CM
80 return -EINVAL;
81}
82
83static int check_hotmod_int_op(const char *curr, const char *option,
3bb8ea40 84 const char *name, unsigned int *val)
44814ec9
CM
85{
86 char *n;
87
88 if (strcmp(curr, name) == 0) {
89 if (!option) {
25880f7d 90 pr_warn("No option given for '%s'\n", curr);
44814ec9
CM
91 return -EINVAL;
92 }
93 *val = simple_strtoul(option, &n, 0);
94 if ((*n != '\0') || (*option == '\0')) {
25880f7d 95 pr_warn("Bad option given for '%s'\n", curr);
44814ec9
CM
96 return -EINVAL;
97 }
98 return 1;
99 }
100 return 0;
101}
102
3bb8ea40
CM
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
d7323638 110 h->iftype = IPMI_PLAT_IF_SI;
3bb8ea40
CM
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{
44814ec9 187 int rv;
3bb8ea40 188 struct ipmi_plat_data h;
d14ce8c7 189 char *str, *curr, *next;
44814ec9 190
d14ce8c7 191 str = kstrdup(val, GFP_KERNEL);
44814ec9
CM
192 if (!str)
193 return -ENOMEM;
194
195 /* Kill any trailing spaces, as we can get a "\n" from echo. */
d14ce8c7 196 for (curr = strstrip(str); curr; curr = next) {
3bb8ea40 197 enum hotmod_op op;
44814ec9
CM
198
199 next = strchr(curr, ':');
200 if (next) {
201 *next = '\0';
202 next++;
203 }
204
3bb8ea40
CM
205 memset(&h, 0, sizeof(h));
206 rv = parse_hotmod_str(curr, &op, &h);
44814ec9 207 if (rv)
44814ec9 208 goto out;
44814ec9
CM
209
210 if (op == HM_ADD) {
3bb8ea40
CM
211 ipmi_platform_add("hotmod-ipmi-si",
212 atomic_inc_return(&hotmod_nr),
213 &h);
44814ec9 214 } else {
bdb57b7b
CM
215 struct device *dev;
216
217 dev = ipmi_si_remove_by_data(h.space, h.type, 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 }
25f314db 225 put_device(dev);
44814ec9
CM
226 }
227 }
d14ce8c7 228 rv = strlen(val);
44814ec9
CM
229out:
230 kfree(str);
231 return rv;
232}
bdb57b7b 233
bdb57b7b
CM
234void ipmi_si_hotmod_exit(void)
235{
e17c6571 236 ipmi_remove_platform_device_by_name("hotmod-ipmi-si");
bdb57b7b 237}