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