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 "ipmi_si.h" 14 15 static int hotmod_handler(const char *val, const struct kernel_param *kp); 16 17 module_param_call(hotmod, hotmod_handler, NULL, NULL, 0200); 18 MODULE_PARM_DESC(hotmod, "Add and remove interfaces. See" 19 " Documentation/IPMI.txt in the kernel sources for the" 20 " gory details."); 21 22 /* 23 * Parms come in as <op1>[:op2[:op3...]]. ops are: 24 * add|remove,kcs|bt|smic,mem|i/o,<address>[,<opt1>[,<opt2>[,...]]] 25 * Options are: 26 * rsp=<regspacing> 27 * rsi=<regsize> 28 * rsh=<regshift> 29 * irq=<irq> 30 * ipmb=<ipmb addr> 31 */ 32 enum hotmod_op { HM_ADD, HM_REMOVE }; 33 struct hotmod_vals { 34 const char *name; 35 const int val; 36 }; 37 38 static const struct hotmod_vals hotmod_ops[] = { 39 { "add", HM_ADD }, 40 { "remove", HM_REMOVE }, 41 { NULL } 42 }; 43 44 static const struct hotmod_vals hotmod_si[] = { 45 { "kcs", SI_KCS }, 46 { "smic", SI_SMIC }, 47 { "bt", SI_BT }, 48 { NULL } 49 }; 50 51 static const struct hotmod_vals hotmod_as[] = { 52 { "mem", IPMI_MEM_ADDR_SPACE }, 53 { "i/o", IPMI_IO_ADDR_SPACE }, 54 { NULL } 55 }; 56 57 static int parse_str(const struct hotmod_vals *v, int *val, char *name, 58 char **curr) 59 { 60 char *s; 61 int i; 62 63 s = strchr(*curr, ','); 64 if (!s) { 65 pr_warn("No hotmod %s given\n", name); 66 return -EINVAL; 67 } 68 *s = '\0'; 69 s++; 70 for (i = 0; v[i].name; i++) { 71 if (strcmp(*curr, v[i].name) == 0) { 72 *val = v[i].val; 73 *curr = s; 74 return 0; 75 } 76 } 77 78 pr_warn("Invalid hotmod %s '%s'\n", name, *curr); 79 return -EINVAL; 80 } 81 82 static int check_hotmod_int_op(const char *curr, const char *option, 83 const char *name, int *val) 84 { 85 char *n; 86 87 if (strcmp(curr, name) == 0) { 88 if (!option) { 89 pr_warn("No option given for '%s'\n", curr); 90 return -EINVAL; 91 } 92 *val = simple_strtoul(option, &n, 0); 93 if ((*n != '\0') || (*option == '\0')) { 94 pr_warn("Bad option given for '%s'\n", curr); 95 return -EINVAL; 96 } 97 return 1; 98 } 99 return 0; 100 } 101 102 static int hotmod_handler(const char *val, const struct kernel_param *kp) 103 { 104 char *str = kstrdup(val, GFP_KERNEL); 105 int rv; 106 char *next, *curr, *s, *n, *o; 107 enum hotmod_op op; 108 enum si_type si_type; 109 int addr_space; 110 unsigned long addr; 111 int regspacing; 112 int regsize; 113 int regshift; 114 int irq; 115 int ipmb; 116 int ival; 117 int len; 118 119 if (!str) 120 return -ENOMEM; 121 122 /* Kill any trailing spaces, as we can get a "\n" from echo. */ 123 len = strlen(str); 124 ival = len - 1; 125 while ((ival >= 0) && isspace(str[ival])) { 126 str[ival] = '\0'; 127 ival--; 128 } 129 130 for (curr = str; curr; curr = next) { 131 regspacing = 1; 132 regsize = 1; 133 regshift = 0; 134 irq = 0; 135 ipmb = 0; /* Choose the default if not specified */ 136 137 next = strchr(curr, ':'); 138 if (next) { 139 *next = '\0'; 140 next++; 141 } 142 143 rv = parse_str(hotmod_ops, &ival, "operation", &curr); 144 if (rv) 145 break; 146 op = ival; 147 148 rv = parse_str(hotmod_si, &ival, "interface type", &curr); 149 if (rv) 150 break; 151 si_type = ival; 152 153 rv = parse_str(hotmod_as, &addr_space, "address space", &curr); 154 if (rv) 155 break; 156 157 s = strchr(curr, ','); 158 if (s) { 159 *s = '\0'; 160 s++; 161 } 162 addr = simple_strtoul(curr, &n, 0); 163 if ((*n != '\0') || (*curr == '\0')) { 164 pr_warn("Invalid hotmod address '%s'\n", curr); 165 break; 166 } 167 168 while (s) { 169 curr = s; 170 s = strchr(curr, ','); 171 if (s) { 172 *s = '\0'; 173 s++; 174 } 175 o = strchr(curr, '='); 176 if (o) { 177 *o = '\0'; 178 o++; 179 } 180 rv = check_hotmod_int_op(curr, o, "rsp", ®spacing); 181 if (rv < 0) 182 goto out; 183 else if (rv) 184 continue; 185 rv = check_hotmod_int_op(curr, o, "rsi", ®size); 186 if (rv < 0) 187 goto out; 188 else if (rv) 189 continue; 190 rv = check_hotmod_int_op(curr, o, "rsh", ®shift); 191 if (rv < 0) 192 goto out; 193 else if (rv) 194 continue; 195 rv = check_hotmod_int_op(curr, o, "irq", &irq); 196 if (rv < 0) 197 goto out; 198 else if (rv) 199 continue; 200 rv = check_hotmod_int_op(curr, o, "ipmb", &ipmb); 201 if (rv < 0) 202 goto out; 203 else if (rv) 204 continue; 205 206 rv = -EINVAL; 207 pr_warn("Invalid hotmod option '%s'\n", curr); 208 goto out; 209 } 210 211 if (op == HM_ADD) { 212 struct si_sm_io io; 213 214 memset(&io, 0, sizeof(io)); 215 io.addr_source = SI_HOTMOD; 216 io.si_type = si_type; 217 io.addr_data = addr; 218 io.addr_type = addr_space; 219 220 io.addr = NULL; 221 io.regspacing = regspacing; 222 if (!io.regspacing) 223 io.regspacing = DEFAULT_REGSPACING; 224 io.regsize = regsize; 225 if (!io.regsize) 226 io.regsize = DEFAULT_REGSIZE; 227 io.regshift = regshift; 228 io.irq = irq; 229 if (io.irq) 230 io.irq_setup = ipmi_std_irq_setup; 231 io.slave_addr = ipmb; 232 233 rv = ipmi_si_add_smi(&io); 234 if (rv) 235 goto out; 236 } else { 237 ipmi_si_remove_by_data(addr_space, si_type, addr); 238 } 239 } 240 rv = len; 241 out: 242 kfree(str); 243 return rv; 244 } 245