238 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * ipmi_si_hotmod.c4 *5 * Handling for dynamically adding/removing IPMI devices through6 * a module parameter (and thus sysfs).7 */8 9#define pr_fmt(fmt) "ipmi_hotmod: " fmt10 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 17static int hotmod_handler(const char *val, const struct kernel_param *kp);18 19module_param_call(hotmod, hotmod_handler, NULL, NULL, 0200);20MODULE_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 */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 58static 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 83static 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 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 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 183static atomic_t hotmod_nr;184 185static 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);229out:230 kfree(str);231 return rv;232}233 234void ipmi_si_hotmod_exit(void)235{236 ipmi_remove_platform_device_by_name("hotmod-ipmi-si");237}238