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 17 static int hotmod_handler(const char *val, const struct kernel_param *kp); 18 19 module_param_call(hotmod, hotmod_handler, NULL, NULL, 0200); 20 MODULE_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 */ 34 enum hotmod_op { HM_ADD, HM_REMOVE }; 35 struct hotmod_vals { 36 const char *name; 37 const int val; 38 }; 39 40 static const struct hotmod_vals hotmod_ops[] = { 41 { "add", HM_ADD }, 42 { "remove", HM_REMOVE }, 43 { NULL } 44 }; 45 46 static const struct hotmod_vals hotmod_si[] = { 47 { "kcs", SI_KCS }, 48 { "smic", SI_SMIC }, 49 { "bt", SI_BT }, 50 { NULL } 51 }; 52 53 static const struct hotmod_vals hotmod_as[] = { 54 { "mem", IPMI_MEM_ADDR_SPACE }, 55 { "i/o", IPMI_IO_ADDR_SPACE }, 56 { NULL } 57 }; 58 59 static int parse_str(const struct hotmod_vals *v, unsigned int *val, char *name, 60 const char **curr) 61 { 62 char *s; 63 int i; 64 65 s = strchr(*curr, ','); 66 if (!s) { 67 pr_warn("No hotmod %s given\n", name); 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 80 pr_warn("Invalid hotmod %s '%s'\n", name, *curr); 81 return -EINVAL; 82 } 83 84 static int check_hotmod_int_op(const char *curr, const char *option, 85 const char *name, unsigned int *val) 86 { 87 char *n; 88 89 if (strcmp(curr, name) == 0) { 90 if (!option) { 91 pr_warn("No option given for '%s'\n", curr); 92 return -EINVAL; 93 } 94 *val = simple_strtoul(option, &n, 0); 95 if ((*n != '\0') || (*option == '\0')) { 96 pr_warn("Bad option given for '%s'\n", curr); 97 return -EINVAL; 98 } 99 return 1; 100 } 101 return 0; 102 } 103 104 static 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 183 static atomic_t hotmod_nr; 184 185 static int hotmod_handler(const char *val, const struct kernel_param *kp) 186 { 187 char *str = kstrdup(val, GFP_KERNEL), *curr, *next; 188 int rv; 189 struct ipmi_plat_data h; 190 unsigned int len; 191 int ival; 192 193 if (!str) 194 return -ENOMEM; 195 196 /* Kill any trailing spaces, as we can get a "\n" from echo. */ 197 len = strlen(str); 198 ival = len - 1; 199 while ((ival >= 0) && isspace(str[ival])) { 200 str[ival] = '\0'; 201 ival--; 202 } 203 204 for (curr = str; curr; curr = next) { 205 enum hotmod_op op; 206 207 next = strchr(curr, ':'); 208 if (next) { 209 *next = '\0'; 210 next++; 211 } 212 213 memset(&h, 0, sizeof(h)); 214 rv = parse_hotmod_str(curr, &op, &h); 215 if (rv) 216 goto out; 217 218 if (op == HM_ADD) { 219 ipmi_platform_add("hotmod-ipmi-si", 220 atomic_inc_return(&hotmod_nr), 221 &h); 222 } else { 223 struct device *dev; 224 225 dev = ipmi_si_remove_by_data(h.space, h.type, h.addr); 226 if (dev && dev_is_platform(dev)) { 227 struct platform_device *pdev; 228 229 pdev = to_platform_device(dev); 230 if (strcmp(pdev->name, "hotmod-ipmi-si") == 0) 231 platform_device_unregister(pdev); 232 } 233 if (dev) 234 put_device(dev); 235 } 236 } 237 rv = len; 238 out: 239 kfree(str); 240 return rv; 241 } 242 243 void ipmi_si_hotmod_exit(void) 244 { 245 ipmi_remove_platform_device_by_name("hotmod-ipmi-si"); 246 } 247