summaryrefslogtreecommitdiffstats
path: root/drivers/char/ipmi/ipmi_si_hotmod.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/ipmi/ipmi_si_hotmod.c')
-rw-r--r--drivers/char/ipmi/ipmi_si_hotmod.c237
1 files changed, 237 insertions, 0 deletions
diff --git a/drivers/char/ipmi/ipmi_si_hotmod.c b/drivers/char/ipmi/ipmi_si_hotmod.c
new file mode 100644
index 000000000..6b12a83cc
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_si_hotmod.c
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ipmi_si_hotmod.c
+ *
+ * Handling for dynamically adding/removing IPMI devices through
+ * a module parameter (and thus sysfs).
+ */
+
+#define pr_fmt(fmt) "ipmi_hotmod: " fmt
+
+#include <linux/moduleparam.h>
+#include <linux/ipmi.h>
+#include <linux/atomic.h>
+#include "ipmi_si.h"
+#include "ipmi_plat_data.h"
+
+static int hotmod_handler(const char *val, const struct kernel_param *kp);
+
+module_param_call(hotmod, hotmod_handler, NULL, NULL, 0200);
+MODULE_PARM_DESC(hotmod,
+ "Add and remove interfaces. See Documentation/driver-api/ipmi.rst in the kernel sources for the gory details.");
+
+/*
+ * Parms come in as <op1>[:op2[:op3...]]. ops are:
+ * add|remove,kcs|bt|smic,mem|i/o,<address>[,<opt1>[,<opt2>[,...]]]
+ * Options are:
+ * rsp=<regspacing>
+ * rsi=<regsize>
+ * rsh=<regshift>
+ * irq=<irq>
+ * ipmb=<ipmb addr>
+ */
+enum hotmod_op { HM_ADD, HM_REMOVE };
+struct hotmod_vals {
+ const char *name;
+ const int val;
+};
+
+static const struct hotmod_vals hotmod_ops[] = {
+ { "add", HM_ADD },
+ { "remove", HM_REMOVE },
+ { NULL }
+};
+
+static const struct hotmod_vals hotmod_si[] = {
+ { "kcs", SI_KCS },
+ { "smic", SI_SMIC },
+ { "bt", SI_BT },
+ { NULL }
+};
+
+static const struct hotmod_vals hotmod_as[] = {
+ { "mem", IPMI_MEM_ADDR_SPACE },
+ { "i/o", IPMI_IO_ADDR_SPACE },
+ { NULL }
+};
+
+static int parse_str(const struct hotmod_vals *v, unsigned int *val, char *name,
+ const char **curr)
+{
+ char *s;
+ int i;
+
+ s = strchr(*curr, ',');
+ if (!s) {
+ pr_warn("No hotmod %s given\n", name);
+ return -EINVAL;
+ }
+ *s = '\0';
+ s++;
+ for (i = 0; v[i].name; i++) {
+ if (strcmp(*curr, v[i].name) == 0) {
+ *val = v[i].val;
+ *curr = s;
+ return 0;
+ }
+ }
+
+ pr_warn("Invalid hotmod %s '%s'\n", name, *curr);
+ return -EINVAL;
+}
+
+static int check_hotmod_int_op(const char *curr, const char *option,
+ const char *name, unsigned int *val)
+{
+ char *n;
+
+ if (strcmp(curr, name) == 0) {
+ if (!option) {
+ pr_warn("No option given for '%s'\n", curr);
+ return -EINVAL;
+ }
+ *val = simple_strtoul(option, &n, 0);
+ if ((*n != '\0') || (*option == '\0')) {
+ pr_warn("Bad option given for '%s'\n", curr);
+ return -EINVAL;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+static int parse_hotmod_str(const char *curr, enum hotmod_op *op,
+ struct ipmi_plat_data *h)
+{
+ char *s, *o;
+ int rv;
+ unsigned int ival;
+
+ h->iftype = IPMI_PLAT_IF_SI;
+ rv = parse_str(hotmod_ops, &ival, "operation", &curr);
+ if (rv)
+ return rv;
+ *op = ival;
+
+ rv = parse_str(hotmod_si, &ival, "interface type", &curr);
+ if (rv)
+ return rv;
+ h->type = ival;
+
+ rv = parse_str(hotmod_as, &ival, "address space", &curr);
+ if (rv)
+ return rv;
+ h->space = ival;
+
+ s = strchr(curr, ',');
+ if (s) {
+ *s = '\0';
+ s++;
+ }
+ rv = kstrtoul(curr, 0, &h->addr);
+ if (rv) {
+ pr_warn("Invalid hotmod address '%s': %d\n", curr, rv);
+ return rv;
+ }
+
+ while (s) {
+ curr = s;
+ s = strchr(curr, ',');
+ if (s) {
+ *s = '\0';
+ s++;
+ }
+ o = strchr(curr, '=');
+ if (o) {
+ *o = '\0';
+ o++;
+ }
+ rv = check_hotmod_int_op(curr, o, "rsp", &h->regspacing);
+ if (rv < 0)
+ return rv;
+ else if (rv)
+ continue;
+ rv = check_hotmod_int_op(curr, o, "rsi", &h->regsize);
+ if (rv < 0)
+ return rv;
+ else if (rv)
+ continue;
+ rv = check_hotmod_int_op(curr, o, "rsh", &h->regshift);
+ if (rv < 0)
+ return rv;
+ else if (rv)
+ continue;
+ rv = check_hotmod_int_op(curr, o, "irq", &h->irq);
+ if (rv < 0)
+ return rv;
+ else if (rv)
+ continue;
+ rv = check_hotmod_int_op(curr, o, "ipmb", &h->slave_addr);
+ if (rv < 0)
+ return rv;
+ else if (rv)
+ continue;
+
+ pr_warn("Invalid hotmod option '%s'\n", curr);
+ return -EINVAL;
+ }
+
+ h->addr_source = SI_HOTMOD;
+ return 0;
+}
+
+static atomic_t hotmod_nr;
+
+static int hotmod_handler(const char *val, const struct kernel_param *kp)
+{
+ int rv;
+ struct ipmi_plat_data h;
+ char *str, *curr, *next;
+
+ str = kstrdup(val, GFP_KERNEL);
+ if (!str)
+ return -ENOMEM;
+
+ /* Kill any trailing spaces, as we can get a "\n" from echo. */
+ for (curr = strstrip(str); curr; curr = next) {
+ enum hotmod_op op;
+
+ next = strchr(curr, ':');
+ if (next) {
+ *next = '\0';
+ next++;
+ }
+
+ memset(&h, 0, sizeof(h));
+ rv = parse_hotmod_str(curr, &op, &h);
+ if (rv)
+ goto out;
+
+ if (op == HM_ADD) {
+ ipmi_platform_add("hotmod-ipmi-si",
+ atomic_inc_return(&hotmod_nr),
+ &h);
+ } else {
+ struct device *dev;
+
+ dev = ipmi_si_remove_by_data(h.space, h.type, h.addr);
+ if (dev && dev_is_platform(dev)) {
+ struct platform_device *pdev;
+
+ pdev = to_platform_device(dev);
+ if (strcmp(pdev->name, "hotmod-ipmi-si") == 0)
+ platform_device_unregister(pdev);
+ }
+ put_device(dev);
+ }
+ }
+ rv = strlen(val);
+out:
+ kfree(str);
+ return rv;
+}
+
+void ipmi_si_hotmod_exit(void)
+{
+ ipmi_remove_platform_device_by_name("hotmod-ipmi-si");
+}