hc
2024-12-19 9370bb92b2d16684ee45cf24e879c93c509162da
kernel/drivers/tty/rpmsg_tty.c
....@@ -0,0 +1,287 @@
1
+// SPDX-License-Identifier: GPL-2.0
2
+/*
3
+ * Copyright (C) 2021 STMicroelectronics - All Rights Reserved
4
+ *
5
+ * The rpmsg tty driver implements serial communication on the RPMsg bus to makes
6
+ * possible for user-space programs to send and receive rpmsg messages as a standard
7
+ * tty protocol.
8
+ *
9
+ * The remote processor can instantiate a new tty by requesting a "rpmsg-tty" RPMsg service.
10
+ * The "rpmsg-tty" service is directly used for data exchange. No flow control is implemented yet.
11
+ */
12
+
13
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
14
+
15
+#include <linux/module.h>
16
+#include <linux/rpmsg.h>
17
+#include <linux/slab.h>
18
+#include <linux/tty.h>
19
+#include <linux/tty_flip.h>
20
+
21
+#define RPMSG_TTY_NAME "ttyRPMSG"
22
+#define MAX_TTY_RPMSG 32
23
+
24
+static DEFINE_IDR(tty_idr); /* tty instance id */
25
+static DEFINE_MUTEX(idr_lock); /* protects tty_idr */
26
+
27
+static struct tty_driver *rpmsg_tty_driver;
28
+
29
+struct rpmsg_tty_port {
30
+ struct tty_port port; /* TTY port data */
31
+ int id; /* TTY rpmsg index */
32
+ struct rpmsg_device *rpdev; /* rpmsg device */
33
+};
34
+
35
+static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src)
36
+{
37
+ struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
38
+ int copied;
39
+
40
+ if (!len)
41
+ return -EINVAL;
42
+ copied = tty_insert_flip_string(&cport->port, data, len);
43
+ if (copied != len)
44
+ dev_err_ratelimited(&rpdev->dev, "Trunc buffer: available space is %d\n", copied);
45
+ tty_flip_buffer_push(&cport->port);
46
+
47
+ return 0;
48
+}
49
+
50
+static int rpmsg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
51
+{
52
+ struct rpmsg_tty_port *cport = idr_find(&tty_idr, tty->index);
53
+ struct tty_port *port;
54
+
55
+ tty->driver_data = cport;
56
+
57
+ port = tty_port_get(&cport->port);
58
+ return tty_port_install(port, driver, tty);
59
+}
60
+
61
+static void rpmsg_tty_cleanup(struct tty_struct *tty)
62
+{
63
+ tty_port_put(tty->port);
64
+}
65
+
66
+static int rpmsg_tty_open(struct tty_struct *tty, struct file *filp)
67
+{
68
+ return tty_port_open(tty->port, tty, filp);
69
+}
70
+
71
+static void rpmsg_tty_close(struct tty_struct *tty, struct file *filp)
72
+{
73
+ return tty_port_close(tty->port, tty, filp);
74
+}
75
+
76
+static int rpmsg_tty_write(struct tty_struct *tty, const u8 *buf, int len)
77
+{
78
+ struct rpmsg_tty_port *cport = tty->driver_data;
79
+ struct rpmsg_device *rpdev;
80
+ int msg_max_size, msg_size;
81
+ int ret;
82
+
83
+ rpdev = cport->rpdev;
84
+
85
+ msg_max_size = rpmsg_get_mtu(rpdev->ept);
86
+ if (msg_max_size < 0)
87
+ return msg_max_size;
88
+
89
+ msg_size = min(len, msg_max_size);
90
+
91
+ /*
92
+ * Use rpmsg_trysend instead of rpmsg_send to send the message so the caller is not
93
+ * hung until a rpmsg buffer is available. In such case rpmsg_trysend returns -ENOMEM.
94
+ */
95
+ ret = rpmsg_trysend(rpdev->ept, (void *)buf, msg_size);
96
+ if (ret) {
97
+ dev_dbg_ratelimited(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
98
+ return ret;
99
+ }
100
+
101
+ return msg_size;
102
+}
103
+
104
+static int rpmsg_tty_write_room(struct tty_struct *tty)
105
+{
106
+ struct rpmsg_tty_port *cport = tty->driver_data;
107
+ int size;
108
+
109
+ size = rpmsg_get_mtu(cport->rpdev->ept);
110
+ if (size < 0)
111
+ return 0;
112
+
113
+ return size;
114
+}
115
+
116
+static void rpmsg_tty_hangup(struct tty_struct *tty)
117
+{
118
+ tty_port_hangup(tty->port);
119
+}
120
+
121
+static const struct tty_operations rpmsg_tty_ops = {
122
+ .install = rpmsg_tty_install,
123
+ .open = rpmsg_tty_open,
124
+ .close = rpmsg_tty_close,
125
+ .write = rpmsg_tty_write,
126
+ .write_room = rpmsg_tty_write_room,
127
+ .hangup = rpmsg_tty_hangup,
128
+ .cleanup = rpmsg_tty_cleanup,
129
+};
130
+
131
+static struct rpmsg_tty_port *rpmsg_tty_alloc_cport(void)
132
+{
133
+ struct rpmsg_tty_port *cport;
134
+ int ret;
135
+
136
+ cport = kzalloc(sizeof(*cport), GFP_KERNEL);
137
+ if (!cport)
138
+ return ERR_PTR(-ENOMEM);
139
+
140
+ mutex_lock(&idr_lock);
141
+ ret = idr_alloc(&tty_idr, cport, 0, MAX_TTY_RPMSG, GFP_KERNEL);
142
+ mutex_unlock(&idr_lock);
143
+
144
+ if (ret < 0) {
145
+ kfree(cport);
146
+ return ERR_PTR(ret);
147
+ }
148
+
149
+ cport->id = ret;
150
+
151
+ return cport;
152
+}
153
+
154
+static void rpmsg_tty_destruct_port(struct tty_port *port)
155
+{
156
+ struct rpmsg_tty_port *cport = container_of(port, struct rpmsg_tty_port, port);
157
+
158
+ mutex_lock(&idr_lock);
159
+ idr_remove(&tty_idr, cport->id);
160
+ mutex_unlock(&idr_lock);
161
+
162
+ kfree(cport);
163
+}
164
+
165
+static const struct tty_port_operations rpmsg_tty_port_ops = {
166
+ .destruct = rpmsg_tty_destruct_port,
167
+};
168
+
169
+
170
+static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
171
+{
172
+ struct rpmsg_tty_port *cport;
173
+ struct device *dev = &rpdev->dev;
174
+ struct device *tty_dev;
175
+ int ret;
176
+
177
+ cport = rpmsg_tty_alloc_cport();
178
+ if (IS_ERR(cport))
179
+ return dev_err_probe(dev, PTR_ERR(cport), "Failed to alloc tty port\n");
180
+
181
+ tty_port_init(&cport->port);
182
+ cport->port.ops = &rpmsg_tty_port_ops;
183
+
184
+ tty_dev = tty_port_register_device(&cport->port, rpmsg_tty_driver,
185
+ cport->id, dev);
186
+ if (IS_ERR(tty_dev)) {
187
+ ret = dev_err_probe(dev, PTR_ERR(tty_dev), "Failed to register tty port\n");
188
+ tty_port_put(&cport->port);
189
+ return ret;
190
+ }
191
+
192
+ cport->rpdev = rpdev;
193
+
194
+ dev_set_drvdata(dev, cport);
195
+
196
+ dev_dbg(dev, "New channel: 0x%x -> 0x%x: " RPMSG_TTY_NAME "%d\n",
197
+ rpdev->src, rpdev->dst, cport->id);
198
+
199
+ return 0;
200
+}
201
+
202
+static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
203
+{
204
+ struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
205
+
206
+ dev_dbg(&rpdev->dev, "Removing rpmsg tty device %d\n", cport->id);
207
+
208
+ /* User hang up to release the tty */
209
+ tty_port_tty_hangup(&cport->port, false);
210
+
211
+ tty_unregister_device(rpmsg_tty_driver, cport->id);
212
+
213
+ tty_port_put(&cport->port);
214
+}
215
+
216
+static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = {
217
+ { .name = "rpmsg-tty" },
218
+ { },
219
+};
220
+MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_tty_id_table);
221
+
222
+static struct rpmsg_driver rpmsg_tty_rpmsg_drv = {
223
+ .drv.name = KBUILD_MODNAME,
224
+ .id_table = rpmsg_driver_tty_id_table,
225
+ .probe = rpmsg_tty_probe,
226
+ .callback = rpmsg_tty_cb,
227
+ .remove = rpmsg_tty_remove,
228
+};
229
+
230
+static int __init rpmsg_tty_init(void)
231
+{
232
+ int ret;
233
+
234
+ rpmsg_tty_driver = tty_alloc_driver(MAX_TTY_RPMSG, TTY_DRIVER_REAL_RAW |
235
+ TTY_DRIVER_DYNAMIC_DEV);
236
+ if (IS_ERR(rpmsg_tty_driver))
237
+ return PTR_ERR(rpmsg_tty_driver);
238
+
239
+ rpmsg_tty_driver->driver_name = "rpmsg_tty";
240
+ rpmsg_tty_driver->name = RPMSG_TTY_NAME;
241
+ rpmsg_tty_driver->major = 0;
242
+ rpmsg_tty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
243
+
244
+ /* Disable unused mode by default */
245
+ rpmsg_tty_driver->init_termios = tty_std_termios;
246
+ rpmsg_tty_driver->init_termios.c_lflag &= ~(ECHO | ICANON);
247
+ rpmsg_tty_driver->init_termios.c_oflag &= ~(OPOST | ONLCR);
248
+
249
+ tty_set_operations(rpmsg_tty_driver, &rpmsg_tty_ops);
250
+
251
+ ret = tty_register_driver(rpmsg_tty_driver);
252
+ if (ret < 0) {
253
+ pr_err("Couldn't install driver: %d\n", ret);
254
+ goto error_put;
255
+ }
256
+
257
+ ret = register_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
258
+ if (ret < 0) {
259
+ pr_err("Couldn't register driver: %d\n", ret);
260
+ goto error_unregister;
261
+ }
262
+
263
+ return 0;
264
+
265
+error_unregister:
266
+ tty_unregister_driver(rpmsg_tty_driver);
267
+
268
+error_put:
269
+ tty_driver_kref_put(rpmsg_tty_driver);
270
+
271
+ return ret;
272
+}
273
+
274
+static void __exit rpmsg_tty_exit(void)
275
+{
276
+ unregister_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
277
+ tty_unregister_driver(rpmsg_tty_driver);
278
+ tty_driver_kref_put(rpmsg_tty_driver);
279
+ idr_destroy(&tty_idr);
280
+}
281
+
282
+module_init(rpmsg_tty_init);
283
+module_exit(rpmsg_tty_exit);
284
+
285
+MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>");
286
+MODULE_DESCRIPTION("remote processor messaging tty driver");
287
+MODULE_LICENSE("GPL v2");