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