1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Deliver z/VM CP special messages (SMSG) as uevents. |
4 | * |
5 | * The driver registers for z/VM CP special messages with the |
6 | * "APP" prefix. Incoming messages are delivered to user space |
7 | * as uevents. |
8 | * |
9 | * Copyright IBM Corp. 2010 |
10 | * Author(s): Hendrik Brueckner <brueckner@linux.vnet.ibm.com> |
11 | * |
12 | */ |
13 | #define KMSG_COMPONENT "smsgiucv_app" |
14 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt |
15 | |
16 | #include <linux/ctype.h> |
17 | #include <linux/err.h> |
18 | #include <linux/device.h> |
19 | #include <linux/list.h> |
20 | #include <linux/kobject.h> |
21 | #include <linux/module.h> |
22 | #include <linux/slab.h> |
23 | #include <linux/spinlock.h> |
24 | #include <linux/workqueue.h> |
25 | #include <net/iucv/iucv.h> |
26 | #include "smsgiucv.h" |
27 | |
28 | /* prefix used for SMSG registration */ |
29 | #define SMSG_PREFIX "APP" |
30 | |
31 | /* SMSG related uevent environment variables */ |
32 | #define ENV_SENDER_STR "SMSG_SENDER=" |
33 | #define ENV_SENDER_LEN (strlen(ENV_SENDER_STR) + 8 + 1) |
34 | #define ENV_PREFIX_STR "SMSG_ID=" |
35 | #define ENV_PREFIX_LEN (strlen(ENV_PREFIX_STR) + \ |
36 | strlen(SMSG_PREFIX) + 1) |
37 | #define ENV_TEXT_STR "SMSG_TEXT=" |
38 | #define ENV_TEXT_LEN(msg) (strlen(ENV_TEXT_STR) + strlen((msg)) + 1) |
39 | |
40 | /* z/VM user ID which is permitted to send SMSGs |
41 | * If the value is undefined or empty (""), special messages are |
42 | * accepted from any z/VM user ID. */ |
43 | static char *sender; |
44 | module_param(sender, charp, 0400); |
45 | MODULE_PARM_DESC(sender, "z/VM user ID from which CP SMSGs are accepted" ); |
46 | |
47 | /* SMSG device representation */ |
48 | static struct device *smsg_app_dev; |
49 | |
50 | /* list element for queuing received messages for delivery */ |
51 | struct smsg_app_event { |
52 | struct list_head list; |
53 | char *buf; |
54 | char *envp[4]; |
55 | }; |
56 | |
57 | /* queue for outgoing uevents */ |
58 | static LIST_HEAD(smsg_event_queue); |
59 | static DEFINE_SPINLOCK(smsg_event_queue_lock); |
60 | |
61 | static void smsg_app_event_free(struct smsg_app_event *ev) |
62 | { |
63 | kfree(objp: ev->buf); |
64 | kfree(objp: ev); |
65 | } |
66 | |
67 | static struct smsg_app_event *smsg_app_event_alloc(const char *from, |
68 | const char *msg) |
69 | { |
70 | struct smsg_app_event *ev; |
71 | |
72 | ev = kzalloc(size: sizeof(*ev), GFP_ATOMIC); |
73 | if (!ev) |
74 | return NULL; |
75 | |
76 | ev->buf = kzalloc(ENV_SENDER_LEN + ENV_PREFIX_LEN + |
77 | ENV_TEXT_LEN(msg), GFP_ATOMIC); |
78 | if (!ev->buf) { |
79 | kfree(objp: ev); |
80 | return NULL; |
81 | } |
82 | |
83 | /* setting up environment pointers into buf */ |
84 | ev->envp[0] = ev->buf; |
85 | ev->envp[1] = ev->envp[0] + ENV_SENDER_LEN; |
86 | ev->envp[2] = ev->envp[1] + ENV_PREFIX_LEN; |
87 | ev->envp[3] = NULL; |
88 | |
89 | /* setting up environment: sender, prefix name, and message text */ |
90 | snprintf(buf: ev->envp[0], ENV_SENDER_LEN, ENV_SENDER_STR "%s" , from); |
91 | snprintf(buf: ev->envp[1], ENV_PREFIX_LEN, ENV_PREFIX_STR "%s" , SMSG_PREFIX); |
92 | snprintf(buf: ev->envp[2], ENV_TEXT_LEN(msg), ENV_TEXT_STR "%s" , msg); |
93 | |
94 | return ev; |
95 | } |
96 | |
97 | static void smsg_event_work_fn(struct work_struct *work) |
98 | { |
99 | LIST_HEAD(event_queue); |
100 | struct smsg_app_event *p, *n; |
101 | struct device *dev; |
102 | |
103 | dev = get_device(dev: smsg_app_dev); |
104 | if (!dev) |
105 | return; |
106 | |
107 | spin_lock_bh(lock: &smsg_event_queue_lock); |
108 | list_splice_init(list: &smsg_event_queue, head: &event_queue); |
109 | spin_unlock_bh(lock: &smsg_event_queue_lock); |
110 | |
111 | list_for_each_entry_safe(p, n, &event_queue, list) { |
112 | list_del(entry: &p->list); |
113 | kobject_uevent_env(kobj: &dev->kobj, action: KOBJ_CHANGE, envp: p->envp); |
114 | smsg_app_event_free(ev: p); |
115 | } |
116 | |
117 | put_device(dev); |
118 | } |
119 | static DECLARE_WORK(smsg_event_work, smsg_event_work_fn); |
120 | |
121 | static void smsg_app_callback(const char *from, char *msg) |
122 | { |
123 | struct smsg_app_event *se; |
124 | |
125 | /* check if the originating z/VM user ID matches |
126 | * the configured sender. */ |
127 | if (sender && strlen(sender) > 0 && strcmp(from, sender) != 0) |
128 | return; |
129 | |
130 | /* get start of message text (skip prefix and leading blanks) */ |
131 | msg += strlen(SMSG_PREFIX); |
132 | while (*msg && isspace(*msg)) |
133 | msg++; |
134 | if (*msg == '\0') |
135 | return; |
136 | |
137 | /* allocate event list element and its environment */ |
138 | se = smsg_app_event_alloc(from, msg); |
139 | if (!se) |
140 | return; |
141 | |
142 | /* queue event and schedule work function */ |
143 | spin_lock(lock: &smsg_event_queue_lock); |
144 | list_add_tail(new: &se->list, head: &smsg_event_queue); |
145 | spin_unlock(lock: &smsg_event_queue_lock); |
146 | |
147 | schedule_work(work: &smsg_event_work); |
148 | return; |
149 | } |
150 | |
151 | static int __init smsgiucv_app_init(void) |
152 | { |
153 | struct device_driver *smsgiucv_drv; |
154 | int rc; |
155 | |
156 | if (!MACHINE_IS_VM) |
157 | return -ENODEV; |
158 | |
159 | smsg_app_dev = kzalloc(size: sizeof(*smsg_app_dev), GFP_KERNEL); |
160 | if (!smsg_app_dev) |
161 | return -ENOMEM; |
162 | |
163 | smsgiucv_drv = driver_find(SMSGIUCV_DRV_NAME, bus: &iucv_bus); |
164 | if (!smsgiucv_drv) { |
165 | kfree(objp: smsg_app_dev); |
166 | return -ENODEV; |
167 | } |
168 | |
169 | rc = dev_set_name(dev: smsg_app_dev, KMSG_COMPONENT); |
170 | if (rc) { |
171 | kfree(objp: smsg_app_dev); |
172 | goto fail; |
173 | } |
174 | smsg_app_dev->bus = &iucv_bus; |
175 | smsg_app_dev->parent = iucv_root; |
176 | smsg_app_dev->release = (void (*)(struct device *)) kfree; |
177 | smsg_app_dev->driver = smsgiucv_drv; |
178 | rc = device_register(dev: smsg_app_dev); |
179 | if (rc) { |
180 | put_device(dev: smsg_app_dev); |
181 | goto fail; |
182 | } |
183 | |
184 | /* convert sender to uppercase characters */ |
185 | if (sender) { |
186 | int len = strlen(sender); |
187 | while (len--) |
188 | sender[len] = toupper(sender[len]); |
189 | } |
190 | |
191 | /* register with the smsgiucv device driver */ |
192 | rc = smsg_register_callback(SMSG_PREFIX, smsg_app_callback); |
193 | if (rc) { |
194 | device_unregister(dev: smsg_app_dev); |
195 | goto fail; |
196 | } |
197 | |
198 | rc = 0; |
199 | fail: |
200 | return rc; |
201 | } |
202 | module_init(smsgiucv_app_init); |
203 | |
204 | static void __exit smsgiucv_app_exit(void) |
205 | { |
206 | /* unregister callback */ |
207 | smsg_unregister_callback(SMSG_PREFIX, smsg_app_callback); |
208 | |
209 | /* cancel pending work and flush any queued event work */ |
210 | cancel_work_sync(work: &smsg_event_work); |
211 | smsg_event_work_fn(work: &smsg_event_work); |
212 | |
213 | device_unregister(dev: smsg_app_dev); |
214 | } |
215 | module_exit(smsgiucv_app_exit); |
216 | |
217 | MODULE_LICENSE("GPL v2" ); |
218 | MODULE_DESCRIPTION("Deliver z/VM CP SMSG as uevents" ); |
219 | MODULE_AUTHOR("Hendrik Brueckner <brueckner@linux.vnet.ibm.com>" ); |
220 | |