18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Deliver z/VM CP special messages (SMSG) as uevents.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * The driver registers for z/VM CP special messages with the
68c2ecf20Sopenharmony_ci * "APP" prefix. Incoming messages are delivered to user space
78c2ecf20Sopenharmony_ci * as uevents.
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * Copyright IBM Corp. 2010
108c2ecf20Sopenharmony_ci * Author(s): Hendrik Brueckner <brueckner@linux.vnet.ibm.com>
118c2ecf20Sopenharmony_ci *
128c2ecf20Sopenharmony_ci */
138c2ecf20Sopenharmony_ci#define KMSG_COMPONENT		"smsgiucv_app"
148c2ecf20Sopenharmony_ci#define pr_fmt(fmt)		KMSG_COMPONENT ": " fmt
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include <linux/ctype.h>
178c2ecf20Sopenharmony_ci#include <linux/err.h>
188c2ecf20Sopenharmony_ci#include <linux/device.h>
198c2ecf20Sopenharmony_ci#include <linux/list.h>
208c2ecf20Sopenharmony_ci#include <linux/kobject.h>
218c2ecf20Sopenharmony_ci#include <linux/module.h>
228c2ecf20Sopenharmony_ci#include <linux/slab.h>
238c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
248c2ecf20Sopenharmony_ci#include <linux/workqueue.h>
258c2ecf20Sopenharmony_ci#include <net/iucv/iucv.h>
268c2ecf20Sopenharmony_ci#include "smsgiucv.h"
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci/* prefix used for SMSG registration */
298c2ecf20Sopenharmony_ci#define SMSG_PREFIX		"APP"
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci/* SMSG related uevent environment variables */
328c2ecf20Sopenharmony_ci#define ENV_SENDER_STR		"SMSG_SENDER="
338c2ecf20Sopenharmony_ci#define ENV_SENDER_LEN		(strlen(ENV_SENDER_STR) + 8 + 1)
348c2ecf20Sopenharmony_ci#define ENV_PREFIX_STR		"SMSG_ID="
358c2ecf20Sopenharmony_ci#define ENV_PREFIX_LEN		(strlen(ENV_PREFIX_STR) + \
368c2ecf20Sopenharmony_ci				 strlen(SMSG_PREFIX) + 1)
378c2ecf20Sopenharmony_ci#define ENV_TEXT_STR		"SMSG_TEXT="
388c2ecf20Sopenharmony_ci#define ENV_TEXT_LEN(msg)	(strlen(ENV_TEXT_STR) + strlen((msg)) + 1)
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci/* z/VM user ID which is permitted to send SMSGs
418c2ecf20Sopenharmony_ci * If the value is undefined or empty (""), special messages are
428c2ecf20Sopenharmony_ci * accepted from any z/VM user ID. */
438c2ecf20Sopenharmony_cistatic char *sender;
448c2ecf20Sopenharmony_cimodule_param(sender, charp, 0400);
458c2ecf20Sopenharmony_ciMODULE_PARM_DESC(sender, "z/VM user ID from which CP SMSGs are accepted");
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci/* SMSG device representation */
488c2ecf20Sopenharmony_cistatic struct device *smsg_app_dev;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci/* list element for queuing received messages for delivery */
518c2ecf20Sopenharmony_cistruct smsg_app_event {
528c2ecf20Sopenharmony_ci	struct list_head list;
538c2ecf20Sopenharmony_ci	char *buf;
548c2ecf20Sopenharmony_ci	char *envp[4];
558c2ecf20Sopenharmony_ci};
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci/* queue for outgoing uevents */
588c2ecf20Sopenharmony_cistatic LIST_HEAD(smsg_event_queue);
598c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(smsg_event_queue_lock);
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cistatic void smsg_app_event_free(struct smsg_app_event *ev)
628c2ecf20Sopenharmony_ci{
638c2ecf20Sopenharmony_ci	kfree(ev->buf);
648c2ecf20Sopenharmony_ci	kfree(ev);
658c2ecf20Sopenharmony_ci}
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cistatic struct smsg_app_event *smsg_app_event_alloc(const char *from,
688c2ecf20Sopenharmony_ci						   const char *msg)
698c2ecf20Sopenharmony_ci{
708c2ecf20Sopenharmony_ci	struct smsg_app_event *ev;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	ev = kzalloc(sizeof(*ev), GFP_ATOMIC);
738c2ecf20Sopenharmony_ci	if (!ev)
748c2ecf20Sopenharmony_ci		return NULL;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	ev->buf = kzalloc(ENV_SENDER_LEN + ENV_PREFIX_LEN +
778c2ecf20Sopenharmony_ci			  ENV_TEXT_LEN(msg), GFP_ATOMIC);
788c2ecf20Sopenharmony_ci	if (!ev->buf) {
798c2ecf20Sopenharmony_ci		kfree(ev);
808c2ecf20Sopenharmony_ci		return NULL;
818c2ecf20Sopenharmony_ci	}
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	/* setting up environment pointers into buf */
848c2ecf20Sopenharmony_ci	ev->envp[0] = ev->buf;
858c2ecf20Sopenharmony_ci	ev->envp[1] = ev->envp[0] + ENV_SENDER_LEN;
868c2ecf20Sopenharmony_ci	ev->envp[2] = ev->envp[1] + ENV_PREFIX_LEN;
878c2ecf20Sopenharmony_ci	ev->envp[3] = NULL;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	/* setting up environment: sender, prefix name, and message text */
908c2ecf20Sopenharmony_ci	snprintf(ev->envp[0], ENV_SENDER_LEN, ENV_SENDER_STR "%s", from);
918c2ecf20Sopenharmony_ci	snprintf(ev->envp[1], ENV_PREFIX_LEN, ENV_PREFIX_STR "%s", SMSG_PREFIX);
928c2ecf20Sopenharmony_ci	snprintf(ev->envp[2], ENV_TEXT_LEN(msg), ENV_TEXT_STR "%s", msg);
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	return ev;
958c2ecf20Sopenharmony_ci}
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_cistatic void smsg_event_work_fn(struct work_struct *work)
988c2ecf20Sopenharmony_ci{
998c2ecf20Sopenharmony_ci	LIST_HEAD(event_queue);
1008c2ecf20Sopenharmony_ci	struct smsg_app_event *p, *n;
1018c2ecf20Sopenharmony_ci	struct device *dev;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	dev = get_device(smsg_app_dev);
1048c2ecf20Sopenharmony_ci	if (!dev)
1058c2ecf20Sopenharmony_ci		return;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	spin_lock_bh(&smsg_event_queue_lock);
1088c2ecf20Sopenharmony_ci	list_splice_init(&smsg_event_queue, &event_queue);
1098c2ecf20Sopenharmony_ci	spin_unlock_bh(&smsg_event_queue_lock);
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	list_for_each_entry_safe(p, n, &event_queue, list) {
1128c2ecf20Sopenharmony_ci		list_del(&p->list);
1138c2ecf20Sopenharmony_ci		kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, p->envp);
1148c2ecf20Sopenharmony_ci		smsg_app_event_free(p);
1158c2ecf20Sopenharmony_ci	}
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	put_device(dev);
1188c2ecf20Sopenharmony_ci}
1198c2ecf20Sopenharmony_cistatic DECLARE_WORK(smsg_event_work, smsg_event_work_fn);
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic void smsg_app_callback(const char *from, char *msg)
1228c2ecf20Sopenharmony_ci{
1238c2ecf20Sopenharmony_ci	struct smsg_app_event *se;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	/* check if the originating z/VM user ID matches
1268c2ecf20Sopenharmony_ci	 * the configured sender. */
1278c2ecf20Sopenharmony_ci	if (sender && strlen(sender) > 0 && strcmp(from, sender) != 0)
1288c2ecf20Sopenharmony_ci		return;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	/* get start of message text (skip prefix and leading blanks) */
1318c2ecf20Sopenharmony_ci	msg += strlen(SMSG_PREFIX);
1328c2ecf20Sopenharmony_ci	while (*msg && isspace(*msg))
1338c2ecf20Sopenharmony_ci		msg++;
1348c2ecf20Sopenharmony_ci	if (*msg == '\0')
1358c2ecf20Sopenharmony_ci		return;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	/* allocate event list element and its environment */
1388c2ecf20Sopenharmony_ci	se = smsg_app_event_alloc(from, msg);
1398c2ecf20Sopenharmony_ci	if (!se)
1408c2ecf20Sopenharmony_ci		return;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	/* queue event and schedule work function */
1438c2ecf20Sopenharmony_ci	spin_lock(&smsg_event_queue_lock);
1448c2ecf20Sopenharmony_ci	list_add_tail(&se->list, &smsg_event_queue);
1458c2ecf20Sopenharmony_ci	spin_unlock(&smsg_event_queue_lock);
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	schedule_work(&smsg_event_work);
1488c2ecf20Sopenharmony_ci	return;
1498c2ecf20Sopenharmony_ci}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_cistatic int __init smsgiucv_app_init(void)
1528c2ecf20Sopenharmony_ci{
1538c2ecf20Sopenharmony_ci	struct device_driver *smsgiucv_drv;
1548c2ecf20Sopenharmony_ci	int rc;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	if (!MACHINE_IS_VM)
1578c2ecf20Sopenharmony_ci		return -ENODEV;
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	smsg_app_dev = kzalloc(sizeof(*smsg_app_dev), GFP_KERNEL);
1608c2ecf20Sopenharmony_ci	if (!smsg_app_dev)
1618c2ecf20Sopenharmony_ci		return -ENOMEM;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	smsgiucv_drv = driver_find(SMSGIUCV_DRV_NAME, &iucv_bus);
1648c2ecf20Sopenharmony_ci	if (!smsgiucv_drv) {
1658c2ecf20Sopenharmony_ci		kfree(smsg_app_dev);
1668c2ecf20Sopenharmony_ci		return -ENODEV;
1678c2ecf20Sopenharmony_ci	}
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	rc = dev_set_name(smsg_app_dev, KMSG_COMPONENT);
1708c2ecf20Sopenharmony_ci	if (rc) {
1718c2ecf20Sopenharmony_ci		kfree(smsg_app_dev);
1728c2ecf20Sopenharmony_ci		goto fail;
1738c2ecf20Sopenharmony_ci	}
1748c2ecf20Sopenharmony_ci	smsg_app_dev->bus = &iucv_bus;
1758c2ecf20Sopenharmony_ci	smsg_app_dev->parent = iucv_root;
1768c2ecf20Sopenharmony_ci	smsg_app_dev->release = (void (*)(struct device *)) kfree;
1778c2ecf20Sopenharmony_ci	smsg_app_dev->driver = smsgiucv_drv;
1788c2ecf20Sopenharmony_ci	rc = device_register(smsg_app_dev);
1798c2ecf20Sopenharmony_ci	if (rc) {
1808c2ecf20Sopenharmony_ci		put_device(smsg_app_dev);
1818c2ecf20Sopenharmony_ci		goto fail;
1828c2ecf20Sopenharmony_ci	}
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	/* convert sender to uppercase characters */
1858c2ecf20Sopenharmony_ci	if (sender) {
1868c2ecf20Sopenharmony_ci		int len = strlen(sender);
1878c2ecf20Sopenharmony_ci		while (len--)
1888c2ecf20Sopenharmony_ci			sender[len] = toupper(sender[len]);
1898c2ecf20Sopenharmony_ci	}
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	/* register with the smsgiucv device driver */
1928c2ecf20Sopenharmony_ci	rc = smsg_register_callback(SMSG_PREFIX, smsg_app_callback);
1938c2ecf20Sopenharmony_ci	if (rc) {
1948c2ecf20Sopenharmony_ci		device_unregister(smsg_app_dev);
1958c2ecf20Sopenharmony_ci		goto fail;
1968c2ecf20Sopenharmony_ci	}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	rc = 0;
1998c2ecf20Sopenharmony_cifail:
2008c2ecf20Sopenharmony_ci	return rc;
2018c2ecf20Sopenharmony_ci}
2028c2ecf20Sopenharmony_cimodule_init(smsgiucv_app_init);
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_cistatic void __exit smsgiucv_app_exit(void)
2058c2ecf20Sopenharmony_ci{
2068c2ecf20Sopenharmony_ci	/* unregister callback */
2078c2ecf20Sopenharmony_ci	smsg_unregister_callback(SMSG_PREFIX, smsg_app_callback);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	/* cancel pending work and flush any queued event work */
2108c2ecf20Sopenharmony_ci	cancel_work_sync(&smsg_event_work);
2118c2ecf20Sopenharmony_ci	smsg_event_work_fn(&smsg_event_work);
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	device_unregister(smsg_app_dev);
2148c2ecf20Sopenharmony_ci}
2158c2ecf20Sopenharmony_cimodule_exit(smsgiucv_app_exit);
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
2188c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Deliver z/VM CP SMSG as uevents");
2198c2ecf20Sopenharmony_ciMODULE_AUTHOR("Hendrik Brueckner <brueckner@linux.vnet.ibm.com>");
220