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, 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 */ 33 enum hotmod_op { HM_ADD, HM_REMOVE }; 34 struct hotmod_vals { 35 const char *name; 36 const int val; 37 }; 38 39 static const struct hotmod_vals hotmod_ops[] = { 40 { "add", HM_ADD }, 41 { "remove", HM_REMOVE }, 42 { NULL } 43 }; 44 45 static const struct hotmod_vals hotmod_si[] = { 46 { "kcs", SI_KCS }, 47 { "smic", SI_SMIC }, 48 { "bt", SI_BT }, 49 { NULL } 50 }; 51 52 static const struct hotmod_vals hotmod_as[] = { 53 { "mem", IPMI_MEM_ADDR_SPACE }, 54 { "i/o", IPMI_IO_ADDR_SPACE }, 55 { NULL } 56 }; 57 58 static 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 83 static 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 103 static 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(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 int rv; 188 struct ipmi_plat_data h; 189 char *str, *curr, *next; 190 191 str = kstrdup(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, &h); 207 if (rv) 208 goto out; 209 210 if (op == HM_ADD) { 211 ipmi_platform_add("hotmod-ipmi-si", 212 atomic_inc_return(&hotmod_nr), 213 &h); 214 } else { 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 } 225 put_device(dev); 226 } 227 } 228 rv = strlen(val); 229 out: 230 kfree(str); 231 return rv; 232 } 233 234 void ipmi_si_hotmod_exit(void) 235 { 236 ipmi_remove_platform_device_by_name("hotmod-ipmi-si"); 237 } 238