18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * ipmi_si_hotmod.c 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Handling for dynamically adding/removing IPMI devices through 68c2ecf20Sopenharmony_ci * a module parameter (and thus sysfs). 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "ipmi_hotmod: " fmt 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/moduleparam.h> 128c2ecf20Sopenharmony_ci#include <linux/ipmi.h> 138c2ecf20Sopenharmony_ci#include <linux/atomic.h> 148c2ecf20Sopenharmony_ci#include "ipmi_si.h" 158c2ecf20Sopenharmony_ci#include "ipmi_plat_data.h" 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_cistatic int hotmod_handler(const char *val, const struct kernel_param *kp); 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cimodule_param_call(hotmod, hotmod_handler, NULL, NULL, 0200); 208c2ecf20Sopenharmony_ciMODULE_PARM_DESC(hotmod, "Add and remove interfaces. See" 218c2ecf20Sopenharmony_ci " Documentation/driver-api/ipmi.rst in the kernel sources for the" 228c2ecf20Sopenharmony_ci " gory details."); 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* 258c2ecf20Sopenharmony_ci * Parms come in as <op1>[:op2[:op3...]]. ops are: 268c2ecf20Sopenharmony_ci * add|remove,kcs|bt|smic,mem|i/o,<address>[,<opt1>[,<opt2>[,...]]] 278c2ecf20Sopenharmony_ci * Options are: 288c2ecf20Sopenharmony_ci * rsp=<regspacing> 298c2ecf20Sopenharmony_ci * rsi=<regsize> 308c2ecf20Sopenharmony_ci * rsh=<regshift> 318c2ecf20Sopenharmony_ci * irq=<irq> 328c2ecf20Sopenharmony_ci * ipmb=<ipmb addr> 338c2ecf20Sopenharmony_ci */ 348c2ecf20Sopenharmony_cienum hotmod_op { HM_ADD, HM_REMOVE }; 358c2ecf20Sopenharmony_cistruct hotmod_vals { 368c2ecf20Sopenharmony_ci const char *name; 378c2ecf20Sopenharmony_ci const int val; 388c2ecf20Sopenharmony_ci}; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistatic const struct hotmod_vals hotmod_ops[] = { 418c2ecf20Sopenharmony_ci { "add", HM_ADD }, 428c2ecf20Sopenharmony_ci { "remove", HM_REMOVE }, 438c2ecf20Sopenharmony_ci { NULL } 448c2ecf20Sopenharmony_ci}; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistatic const struct hotmod_vals hotmod_si[] = { 478c2ecf20Sopenharmony_ci { "kcs", SI_KCS }, 488c2ecf20Sopenharmony_ci { "smic", SI_SMIC }, 498c2ecf20Sopenharmony_ci { "bt", SI_BT }, 508c2ecf20Sopenharmony_ci { NULL } 518c2ecf20Sopenharmony_ci}; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic const struct hotmod_vals hotmod_as[] = { 548c2ecf20Sopenharmony_ci { "mem", IPMI_MEM_ADDR_SPACE }, 558c2ecf20Sopenharmony_ci { "i/o", IPMI_IO_ADDR_SPACE }, 568c2ecf20Sopenharmony_ci { NULL } 578c2ecf20Sopenharmony_ci}; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic int parse_str(const struct hotmod_vals *v, unsigned int *val, char *name, 608c2ecf20Sopenharmony_ci const char **curr) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci char *s; 638c2ecf20Sopenharmony_ci int i; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci s = strchr(*curr, ','); 668c2ecf20Sopenharmony_ci if (!s) { 678c2ecf20Sopenharmony_ci pr_warn("No hotmod %s given\n", name); 688c2ecf20Sopenharmony_ci return -EINVAL; 698c2ecf20Sopenharmony_ci } 708c2ecf20Sopenharmony_ci *s = '\0'; 718c2ecf20Sopenharmony_ci s++; 728c2ecf20Sopenharmony_ci for (i = 0; v[i].name; i++) { 738c2ecf20Sopenharmony_ci if (strcmp(*curr, v[i].name) == 0) { 748c2ecf20Sopenharmony_ci *val = v[i].val; 758c2ecf20Sopenharmony_ci *curr = s; 768c2ecf20Sopenharmony_ci return 0; 778c2ecf20Sopenharmony_ci } 788c2ecf20Sopenharmony_ci } 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci pr_warn("Invalid hotmod %s '%s'\n", name, *curr); 818c2ecf20Sopenharmony_ci return -EINVAL; 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic int check_hotmod_int_op(const char *curr, const char *option, 858c2ecf20Sopenharmony_ci const char *name, unsigned int *val) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci char *n; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci if (strcmp(curr, name) == 0) { 908c2ecf20Sopenharmony_ci if (!option) { 918c2ecf20Sopenharmony_ci pr_warn("No option given for '%s'\n", curr); 928c2ecf20Sopenharmony_ci return -EINVAL; 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci *val = simple_strtoul(option, &n, 0); 958c2ecf20Sopenharmony_ci if ((*n != '\0') || (*option == '\0')) { 968c2ecf20Sopenharmony_ci pr_warn("Bad option given for '%s'\n", curr); 978c2ecf20Sopenharmony_ci return -EINVAL; 988c2ecf20Sopenharmony_ci } 998c2ecf20Sopenharmony_ci return 1; 1008c2ecf20Sopenharmony_ci } 1018c2ecf20Sopenharmony_ci return 0; 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic int parse_hotmod_str(const char *curr, enum hotmod_op *op, 1058c2ecf20Sopenharmony_ci struct ipmi_plat_data *h) 1068c2ecf20Sopenharmony_ci{ 1078c2ecf20Sopenharmony_ci char *s, *o; 1088c2ecf20Sopenharmony_ci int rv; 1098c2ecf20Sopenharmony_ci unsigned int ival; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci h->iftype = IPMI_PLAT_IF_SI; 1128c2ecf20Sopenharmony_ci rv = parse_str(hotmod_ops, &ival, "operation", &curr); 1138c2ecf20Sopenharmony_ci if (rv) 1148c2ecf20Sopenharmony_ci return rv; 1158c2ecf20Sopenharmony_ci *op = ival; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci rv = parse_str(hotmod_si, &ival, "interface type", &curr); 1188c2ecf20Sopenharmony_ci if (rv) 1198c2ecf20Sopenharmony_ci return rv; 1208c2ecf20Sopenharmony_ci h->type = ival; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci rv = parse_str(hotmod_as, &ival, "address space", &curr); 1238c2ecf20Sopenharmony_ci if (rv) 1248c2ecf20Sopenharmony_ci return rv; 1258c2ecf20Sopenharmony_ci h->space = ival; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci s = strchr(curr, ','); 1288c2ecf20Sopenharmony_ci if (s) { 1298c2ecf20Sopenharmony_ci *s = '\0'; 1308c2ecf20Sopenharmony_ci s++; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci rv = kstrtoul(curr, 0, &h->addr); 1338c2ecf20Sopenharmony_ci if (rv) { 1348c2ecf20Sopenharmony_ci pr_warn("Invalid hotmod address '%s': %d\n", curr, rv); 1358c2ecf20Sopenharmony_ci return rv; 1368c2ecf20Sopenharmony_ci } 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci while (s) { 1398c2ecf20Sopenharmony_ci curr = s; 1408c2ecf20Sopenharmony_ci s = strchr(curr, ','); 1418c2ecf20Sopenharmony_ci if (s) { 1428c2ecf20Sopenharmony_ci *s = '\0'; 1438c2ecf20Sopenharmony_ci s++; 1448c2ecf20Sopenharmony_ci } 1458c2ecf20Sopenharmony_ci o = strchr(curr, '='); 1468c2ecf20Sopenharmony_ci if (o) { 1478c2ecf20Sopenharmony_ci *o = '\0'; 1488c2ecf20Sopenharmony_ci o++; 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci rv = check_hotmod_int_op(curr, o, "rsp", &h->regspacing); 1518c2ecf20Sopenharmony_ci if (rv < 0) 1528c2ecf20Sopenharmony_ci return rv; 1538c2ecf20Sopenharmony_ci else if (rv) 1548c2ecf20Sopenharmony_ci continue; 1558c2ecf20Sopenharmony_ci rv = check_hotmod_int_op(curr, o, "rsi", &h->regsize); 1568c2ecf20Sopenharmony_ci if (rv < 0) 1578c2ecf20Sopenharmony_ci return rv; 1588c2ecf20Sopenharmony_ci else if (rv) 1598c2ecf20Sopenharmony_ci continue; 1608c2ecf20Sopenharmony_ci rv = check_hotmod_int_op(curr, o, "rsh", &h->regshift); 1618c2ecf20Sopenharmony_ci if (rv < 0) 1628c2ecf20Sopenharmony_ci return rv; 1638c2ecf20Sopenharmony_ci else if (rv) 1648c2ecf20Sopenharmony_ci continue; 1658c2ecf20Sopenharmony_ci rv = check_hotmod_int_op(curr, o, "irq", &h->irq); 1668c2ecf20Sopenharmony_ci if (rv < 0) 1678c2ecf20Sopenharmony_ci return rv; 1688c2ecf20Sopenharmony_ci else if (rv) 1698c2ecf20Sopenharmony_ci continue; 1708c2ecf20Sopenharmony_ci rv = check_hotmod_int_op(curr, o, "ipmb", &h->slave_addr); 1718c2ecf20Sopenharmony_ci if (rv < 0) 1728c2ecf20Sopenharmony_ci return rv; 1738c2ecf20Sopenharmony_ci else if (rv) 1748c2ecf20Sopenharmony_ci continue; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci pr_warn("Invalid hotmod option '%s'\n", curr); 1778c2ecf20Sopenharmony_ci return -EINVAL; 1788c2ecf20Sopenharmony_ci } 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci h->addr_source = SI_HOTMOD; 1818c2ecf20Sopenharmony_ci return 0; 1828c2ecf20Sopenharmony_ci} 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_cistatic atomic_t hotmod_nr; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cistatic int hotmod_handler(const char *val, const struct kernel_param *kp) 1878c2ecf20Sopenharmony_ci{ 1888c2ecf20Sopenharmony_ci char *str = kstrdup(val, GFP_KERNEL), *curr, *next; 1898c2ecf20Sopenharmony_ci int rv; 1908c2ecf20Sopenharmony_ci struct ipmi_plat_data h; 1918c2ecf20Sopenharmony_ci unsigned int len; 1928c2ecf20Sopenharmony_ci int ival; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci if (!str) 1958c2ecf20Sopenharmony_ci return -ENOMEM; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci /* Kill any trailing spaces, as we can get a "\n" from echo. */ 1988c2ecf20Sopenharmony_ci len = strlen(str); 1998c2ecf20Sopenharmony_ci ival = len - 1; 2008c2ecf20Sopenharmony_ci while ((ival >= 0) && isspace(str[ival])) { 2018c2ecf20Sopenharmony_ci str[ival] = '\0'; 2028c2ecf20Sopenharmony_ci ival--; 2038c2ecf20Sopenharmony_ci } 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci for (curr = str; curr; curr = next) { 2068c2ecf20Sopenharmony_ci enum hotmod_op op; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci next = strchr(curr, ':'); 2098c2ecf20Sopenharmony_ci if (next) { 2108c2ecf20Sopenharmony_ci *next = '\0'; 2118c2ecf20Sopenharmony_ci next++; 2128c2ecf20Sopenharmony_ci } 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci memset(&h, 0, sizeof(h)); 2158c2ecf20Sopenharmony_ci rv = parse_hotmod_str(curr, &op, &h); 2168c2ecf20Sopenharmony_ci if (rv) 2178c2ecf20Sopenharmony_ci goto out; 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci if (op == HM_ADD) { 2208c2ecf20Sopenharmony_ci ipmi_platform_add("hotmod-ipmi-si", 2218c2ecf20Sopenharmony_ci atomic_inc_return(&hotmod_nr), 2228c2ecf20Sopenharmony_ci &h); 2238c2ecf20Sopenharmony_ci } else { 2248c2ecf20Sopenharmony_ci struct device *dev; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci dev = ipmi_si_remove_by_data(h.space, h.type, h.addr); 2278c2ecf20Sopenharmony_ci if (dev && dev_is_platform(dev)) { 2288c2ecf20Sopenharmony_ci struct platform_device *pdev; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci pdev = to_platform_device(dev); 2318c2ecf20Sopenharmony_ci if (strcmp(pdev->name, "hotmod-ipmi-si") == 0) 2328c2ecf20Sopenharmony_ci platform_device_unregister(pdev); 2338c2ecf20Sopenharmony_ci } 2348c2ecf20Sopenharmony_ci if (dev) 2358c2ecf20Sopenharmony_ci put_device(dev); 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci } 2388c2ecf20Sopenharmony_ci rv = len; 2398c2ecf20Sopenharmony_ciout: 2408c2ecf20Sopenharmony_ci kfree(str); 2418c2ecf20Sopenharmony_ci return rv; 2428c2ecf20Sopenharmony_ci} 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_civoid ipmi_si_hotmod_exit(void) 2458c2ecf20Sopenharmony_ci{ 2468c2ecf20Sopenharmony_ci ipmi_remove_platform_device_by_name("hotmod-ipmi-si"); 2478c2ecf20Sopenharmony_ci} 248