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 h->iftype = IPMI_PLAT_IF_SI; 112 rv = parse_str(hotmod_ops, &ival, "operation", &curr); 113 if (rv) 114 return rv; 115 *op = ival; 116 117 rv = parse_str(hotmod_si, &ival, "interface type", &curr); 118 if (rv) 119 return rv; 120 h->type = ival; 121 122 rv = parse_str(hotmod_as, &ival, "address space", &curr); 123 if (rv) 124 return rv; 125 h->space = ival; 126 127 s = strchr(curr, ','); 128 if (s) { 129 *s = '\0'; 130 s++; 131 } 132 rv = kstrtoul(curr, 0, &h->addr); 133 if (rv) { 134 pr_warn("Invalid hotmod address '%s': %d\n", curr, rv); 135 return rv; 136 } 137 138 while (s) { 139 curr = s; 140 s = strchr(curr, ','); 141 if (s) { 142 *s = '\0'; 143 s++; 144 } 145 o = strchr(curr, '='); 146 if (o) { 147 *o = '\0'; 148 o++; 149 } 150 rv = check_hotmod_int_op(curr, o, "rsp", &h->regspacing); 151 if (rv < 0) 152 return rv; 153 else if (rv) 154 continue; 155 rv = check_hotmod_int_op(curr, o, "rsi", &h->regsize); 156 if (rv < 0) 157 return rv; 158 else if (rv) 159 continue; 160 rv = check_hotmod_int_op(curr, o, "rsh", &h->regshift); 161 if (rv < 0) 162 return rv; 163 else if (rv) 164 continue; 165 rv = check_hotmod_int_op(curr, o, "irq", &h->irq); 166 if (rv < 0) 167 return rv; 168 else if (rv) 169 continue; 170 rv = check_hotmod_int_op(curr, o, "ipmb", &h->slave_addr); 171 if (rv < 0) 172 return rv; 173 else if (rv) 174 continue; 175 176 pr_warn("Invalid hotmod option '%s'\n", curr); 177 return -EINVAL; 178 } 179 180 h->addr_source = SI_HOTMOD; 181 return 0; 182 } 183 184 static atomic_t hotmod_nr; 185 186 static int hotmod_handler(const char *val, const struct kernel_param *kp) 187 { 188 char *str = kstrdup(val, GFP_KERNEL), *curr, *next; 189 int rv; 190 struct ipmi_plat_data h; 191 unsigned int len; 192 int ival; 193 194 if (!str) 195 return -ENOMEM; 196 197 /* Kill any trailing spaces, as we can get a "\n" from echo. */ 198 len = strlen(str); 199 ival = len - 1; 200 while ((ival >= 0) && isspace(str[ival])) { 201 str[ival] = '\0'; 202 ival--; 203 } 204 205 for (curr = str; curr; curr = next) { 206 enum hotmod_op op; 207 208 next = strchr(curr, ':'); 209 if (next) { 210 *next = '\0'; 211 next++; 212 } 213 214 memset(&h, 0, sizeof(h)); 215 rv = parse_hotmod_str(curr, &op, &h); 216 if (rv) 217 goto out; 218 219 if (op == HM_ADD) { 220 ipmi_platform_add("hotmod-ipmi-si", 221 atomic_inc_return(&hotmod_nr), 222 &h); 223 } else { 224 struct device *dev; 225 226 dev = ipmi_si_remove_by_data(h.space, h.type, h.addr); 227 if (dev && dev_is_platform(dev)) { 228 struct platform_device *pdev; 229 230 pdev = to_platform_device(dev); 231 if (strcmp(pdev->name, "hotmod-ipmi-si") == 0) 232 platform_device_unregister(pdev); 233 } 234 if (dev) 235 put_device(dev); 236 } 237 } 238 rv = len; 239 out: 240 kfree(str); 241 return rv; 242 } 243 244 void ipmi_si_hotmod_exit(void) 245 { 246 ipmi_remove_platform_device_by_name("hotmod-ipmi-si"); 247 } 248