162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * ipmi_si_hotmod.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Handling for dynamically adding/removing IPMI devices through 662306a36Sopenharmony_ci * a module parameter (and thus sysfs). 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#define pr_fmt(fmt) "ipmi_hotmod: " fmt 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/moduleparam.h> 1262306a36Sopenharmony_ci#include <linux/ipmi.h> 1362306a36Sopenharmony_ci#include <linux/atomic.h> 1462306a36Sopenharmony_ci#include "ipmi_si.h" 1562306a36Sopenharmony_ci#include "ipmi_plat_data.h" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cistatic int hotmod_handler(const char *val, const struct kernel_param *kp); 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cimodule_param_call(hotmod, hotmod_handler, NULL, NULL, 0200); 2062306a36Sopenharmony_ciMODULE_PARM_DESC(hotmod, 2162306a36Sopenharmony_ci "Add and remove interfaces. See Documentation/driver-api/ipmi.rst in the kernel sources for the gory details."); 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci/* 2462306a36Sopenharmony_ci * Parms come in as <op1>[:op2[:op3...]]. ops are: 2562306a36Sopenharmony_ci * add|remove,kcs|bt|smic,mem|i/o,<address>[,<opt1>[,<opt2>[,...]]] 2662306a36Sopenharmony_ci * Options are: 2762306a36Sopenharmony_ci * rsp=<regspacing> 2862306a36Sopenharmony_ci * rsi=<regsize> 2962306a36Sopenharmony_ci * rsh=<regshift> 3062306a36Sopenharmony_ci * irq=<irq> 3162306a36Sopenharmony_ci * ipmb=<ipmb addr> 3262306a36Sopenharmony_ci */ 3362306a36Sopenharmony_cienum hotmod_op { HM_ADD, HM_REMOVE }; 3462306a36Sopenharmony_cistruct hotmod_vals { 3562306a36Sopenharmony_ci const char *name; 3662306a36Sopenharmony_ci const int val; 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic const struct hotmod_vals hotmod_ops[] = { 4062306a36Sopenharmony_ci { "add", HM_ADD }, 4162306a36Sopenharmony_ci { "remove", HM_REMOVE }, 4262306a36Sopenharmony_ci { NULL } 4362306a36Sopenharmony_ci}; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistatic const struct hotmod_vals hotmod_si[] = { 4662306a36Sopenharmony_ci { "kcs", SI_KCS }, 4762306a36Sopenharmony_ci { "smic", SI_SMIC }, 4862306a36Sopenharmony_ci { "bt", SI_BT }, 4962306a36Sopenharmony_ci { NULL } 5062306a36Sopenharmony_ci}; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic const struct hotmod_vals hotmod_as[] = { 5362306a36Sopenharmony_ci { "mem", IPMI_MEM_ADDR_SPACE }, 5462306a36Sopenharmony_ci { "i/o", IPMI_IO_ADDR_SPACE }, 5562306a36Sopenharmony_ci { NULL } 5662306a36Sopenharmony_ci}; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic int parse_str(const struct hotmod_vals *v, unsigned int *val, char *name, 5962306a36Sopenharmony_ci const char **curr) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci char *s; 6262306a36Sopenharmony_ci int i; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci s = strchr(*curr, ','); 6562306a36Sopenharmony_ci if (!s) { 6662306a36Sopenharmony_ci pr_warn("No hotmod %s given\n", name); 6762306a36Sopenharmony_ci return -EINVAL; 6862306a36Sopenharmony_ci } 6962306a36Sopenharmony_ci *s = '\0'; 7062306a36Sopenharmony_ci s++; 7162306a36Sopenharmony_ci for (i = 0; v[i].name; i++) { 7262306a36Sopenharmony_ci if (strcmp(*curr, v[i].name) == 0) { 7362306a36Sopenharmony_ci *val = v[i].val; 7462306a36Sopenharmony_ci *curr = s; 7562306a36Sopenharmony_ci return 0; 7662306a36Sopenharmony_ci } 7762306a36Sopenharmony_ci } 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci pr_warn("Invalid hotmod %s '%s'\n", name, *curr); 8062306a36Sopenharmony_ci return -EINVAL; 8162306a36Sopenharmony_ci} 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic int check_hotmod_int_op(const char *curr, const char *option, 8462306a36Sopenharmony_ci const char *name, unsigned int *val) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci char *n; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci if (strcmp(curr, name) == 0) { 8962306a36Sopenharmony_ci if (!option) { 9062306a36Sopenharmony_ci pr_warn("No option given for '%s'\n", curr); 9162306a36Sopenharmony_ci return -EINVAL; 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci *val = simple_strtoul(option, &n, 0); 9462306a36Sopenharmony_ci if ((*n != '\0') || (*option == '\0')) { 9562306a36Sopenharmony_ci pr_warn("Bad option given for '%s'\n", curr); 9662306a36Sopenharmony_ci return -EINVAL; 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci return 1; 9962306a36Sopenharmony_ci } 10062306a36Sopenharmony_ci return 0; 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic int parse_hotmod_str(const char *curr, enum hotmod_op *op, 10462306a36Sopenharmony_ci struct ipmi_plat_data *h) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci char *s, *o; 10762306a36Sopenharmony_ci int rv; 10862306a36Sopenharmony_ci unsigned int ival; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci h->iftype = IPMI_PLAT_IF_SI; 11162306a36Sopenharmony_ci rv = parse_str(hotmod_ops, &ival, "operation", &curr); 11262306a36Sopenharmony_ci if (rv) 11362306a36Sopenharmony_ci return rv; 11462306a36Sopenharmony_ci *op = ival; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci rv = parse_str(hotmod_si, &ival, "interface type", &curr); 11762306a36Sopenharmony_ci if (rv) 11862306a36Sopenharmony_ci return rv; 11962306a36Sopenharmony_ci h->type = ival; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci rv = parse_str(hotmod_as, &ival, "address space", &curr); 12262306a36Sopenharmony_ci if (rv) 12362306a36Sopenharmony_ci return rv; 12462306a36Sopenharmony_ci h->space = ival; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci s = strchr(curr, ','); 12762306a36Sopenharmony_ci if (s) { 12862306a36Sopenharmony_ci *s = '\0'; 12962306a36Sopenharmony_ci s++; 13062306a36Sopenharmony_ci } 13162306a36Sopenharmony_ci rv = kstrtoul(curr, 0, &h->addr); 13262306a36Sopenharmony_ci if (rv) { 13362306a36Sopenharmony_ci pr_warn("Invalid hotmod address '%s': %d\n", curr, rv); 13462306a36Sopenharmony_ci return rv; 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci while (s) { 13862306a36Sopenharmony_ci curr = s; 13962306a36Sopenharmony_ci s = strchr(curr, ','); 14062306a36Sopenharmony_ci if (s) { 14162306a36Sopenharmony_ci *s = '\0'; 14262306a36Sopenharmony_ci s++; 14362306a36Sopenharmony_ci } 14462306a36Sopenharmony_ci o = strchr(curr, '='); 14562306a36Sopenharmony_ci if (o) { 14662306a36Sopenharmony_ci *o = '\0'; 14762306a36Sopenharmony_ci o++; 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci rv = check_hotmod_int_op(curr, o, "rsp", &h->regspacing); 15062306a36Sopenharmony_ci if (rv < 0) 15162306a36Sopenharmony_ci return rv; 15262306a36Sopenharmony_ci else if (rv) 15362306a36Sopenharmony_ci continue; 15462306a36Sopenharmony_ci rv = check_hotmod_int_op(curr, o, "rsi", &h->regsize); 15562306a36Sopenharmony_ci if (rv < 0) 15662306a36Sopenharmony_ci return rv; 15762306a36Sopenharmony_ci else if (rv) 15862306a36Sopenharmony_ci continue; 15962306a36Sopenharmony_ci rv = check_hotmod_int_op(curr, o, "rsh", &h->regshift); 16062306a36Sopenharmony_ci if (rv < 0) 16162306a36Sopenharmony_ci return rv; 16262306a36Sopenharmony_ci else if (rv) 16362306a36Sopenharmony_ci continue; 16462306a36Sopenharmony_ci rv = check_hotmod_int_op(curr, o, "irq", &h->irq); 16562306a36Sopenharmony_ci if (rv < 0) 16662306a36Sopenharmony_ci return rv; 16762306a36Sopenharmony_ci else if (rv) 16862306a36Sopenharmony_ci continue; 16962306a36Sopenharmony_ci rv = check_hotmod_int_op(curr, o, "ipmb", &h->slave_addr); 17062306a36Sopenharmony_ci if (rv < 0) 17162306a36Sopenharmony_ci return rv; 17262306a36Sopenharmony_ci else if (rv) 17362306a36Sopenharmony_ci continue; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci pr_warn("Invalid hotmod option '%s'\n", curr); 17662306a36Sopenharmony_ci return -EINVAL; 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci h->addr_source = SI_HOTMOD; 18062306a36Sopenharmony_ci return 0; 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_cistatic atomic_t hotmod_nr; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_cistatic int hotmod_handler(const char *val, const struct kernel_param *kp) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci int rv; 18862306a36Sopenharmony_ci struct ipmi_plat_data h; 18962306a36Sopenharmony_ci char *str, *curr, *next; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci str = kstrdup(val, GFP_KERNEL); 19262306a36Sopenharmony_ci if (!str) 19362306a36Sopenharmony_ci return -ENOMEM; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci /* Kill any trailing spaces, as we can get a "\n" from echo. */ 19662306a36Sopenharmony_ci for (curr = strstrip(str); curr; curr = next) { 19762306a36Sopenharmony_ci enum hotmod_op op; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci next = strchr(curr, ':'); 20062306a36Sopenharmony_ci if (next) { 20162306a36Sopenharmony_ci *next = '\0'; 20262306a36Sopenharmony_ci next++; 20362306a36Sopenharmony_ci } 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci memset(&h, 0, sizeof(h)); 20662306a36Sopenharmony_ci rv = parse_hotmod_str(curr, &op, &h); 20762306a36Sopenharmony_ci if (rv) 20862306a36Sopenharmony_ci goto out; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci if (op == HM_ADD) { 21162306a36Sopenharmony_ci ipmi_platform_add("hotmod-ipmi-si", 21262306a36Sopenharmony_ci atomic_inc_return(&hotmod_nr), 21362306a36Sopenharmony_ci &h); 21462306a36Sopenharmony_ci } else { 21562306a36Sopenharmony_ci struct device *dev; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci dev = ipmi_si_remove_by_data(h.space, h.type, h.addr); 21862306a36Sopenharmony_ci if (dev && dev_is_platform(dev)) { 21962306a36Sopenharmony_ci struct platform_device *pdev; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci pdev = to_platform_device(dev); 22262306a36Sopenharmony_ci if (strcmp(pdev->name, "hotmod-ipmi-si") == 0) 22362306a36Sopenharmony_ci platform_device_unregister(pdev); 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci put_device(dev); 22662306a36Sopenharmony_ci } 22762306a36Sopenharmony_ci } 22862306a36Sopenharmony_ci rv = strlen(val); 22962306a36Sopenharmony_ciout: 23062306a36Sopenharmony_ci kfree(str); 23162306a36Sopenharmony_ci return rv; 23262306a36Sopenharmony_ci} 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_civoid ipmi_si_hotmod_exit(void) 23562306a36Sopenharmony_ci{ 23662306a36Sopenharmony_ci ipmi_remove_platform_device_by_name("hotmod-ipmi-si"); 23762306a36Sopenharmony_ci} 238