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,
  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