1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. */ |
3 | |
4 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
5 | |
6 | #include <linux/kernel.h> |
7 | #include <linux/errno.h> |
8 | #include <linux/tty.h> |
9 | #include <linux/tty_driver.h> |
10 | #include <linux/tty_flip.h> |
11 | #include <linux/module.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/usb/cdc.h> |
14 | #include <linux/serial.h> |
15 | #include "gdm_tty.h" |
16 | |
17 | #define GDM_TTY_MAJOR 0 |
18 | #define GDM_TTY_MINOR 32 |
19 | |
20 | #define WRITE_SIZE 2048 |
21 | |
22 | #define MUX_TX_MAX_SIZE 2048 |
23 | |
24 | static inline bool gdm_tty_ready(struct gdm *gdm) |
25 | { |
26 | return gdm && gdm->tty_dev && gdm->port.count; |
27 | } |
28 | |
29 | static struct tty_driver *gdm_driver[TTY_MAX_COUNT]; |
30 | static struct gdm *gdm_table[TTY_MAX_COUNT][GDM_TTY_MINOR]; |
31 | static DEFINE_MUTEX(gdm_table_lock); |
32 | |
33 | static const char *DRIVER_STRING[TTY_MAX_COUNT] = {"GCTATC" , "GCTDM" }; |
34 | static char *DEVICE_STRING[TTY_MAX_COUNT] = {"GCT-ATC" , "GCT-DM" }; |
35 | |
36 | static void gdm_port_destruct(struct tty_port *port) |
37 | { |
38 | struct gdm *gdm = container_of(port, struct gdm, port); |
39 | |
40 | mutex_lock(&gdm_table_lock); |
41 | gdm_table[gdm->index][gdm->minor] = NULL; |
42 | mutex_unlock(lock: &gdm_table_lock); |
43 | |
44 | kfree(objp: gdm); |
45 | } |
46 | |
47 | static const struct tty_port_operations gdm_port_ops = { |
48 | .destruct = gdm_port_destruct, |
49 | }; |
50 | |
51 | static int gdm_tty_install(struct tty_driver *driver, struct tty_struct *tty) |
52 | { |
53 | struct gdm *gdm = NULL; |
54 | int ret; |
55 | |
56 | ret = match_string(array: DRIVER_STRING, TTY_MAX_COUNT, |
57 | string: tty->driver->driver_name); |
58 | if (ret < 0) |
59 | return -ENODEV; |
60 | |
61 | mutex_lock(&gdm_table_lock); |
62 | gdm = gdm_table[ret][tty->index]; |
63 | if (!gdm) { |
64 | mutex_unlock(lock: &gdm_table_lock); |
65 | return -ENODEV; |
66 | } |
67 | |
68 | tty_port_get(port: &gdm->port); |
69 | |
70 | ret = tty_standard_install(driver, tty); |
71 | if (ret) { |
72 | tty_port_put(port: &gdm->port); |
73 | mutex_unlock(lock: &gdm_table_lock); |
74 | return ret; |
75 | } |
76 | |
77 | tty->driver_data = gdm; |
78 | mutex_unlock(lock: &gdm_table_lock); |
79 | |
80 | return 0; |
81 | } |
82 | |
83 | static int gdm_tty_open(struct tty_struct *tty, struct file *filp) |
84 | { |
85 | struct gdm *gdm = tty->driver_data; |
86 | |
87 | return tty_port_open(port: &gdm->port, tty, filp); |
88 | } |
89 | |
90 | static void gdm_tty_cleanup(struct tty_struct *tty) |
91 | { |
92 | struct gdm *gdm = tty->driver_data; |
93 | |
94 | tty_port_put(port: &gdm->port); |
95 | } |
96 | |
97 | static void gdm_tty_hangup(struct tty_struct *tty) |
98 | { |
99 | struct gdm *gdm = tty->driver_data; |
100 | |
101 | tty_port_hangup(port: &gdm->port); |
102 | } |
103 | |
104 | static void gdm_tty_close(struct tty_struct *tty, struct file *filp) |
105 | { |
106 | struct gdm *gdm = tty->driver_data; |
107 | |
108 | tty_port_close(port: &gdm->port, tty, filp); |
109 | } |
110 | |
111 | static int gdm_tty_recv_complete(void *data, |
112 | int len, |
113 | int index, |
114 | struct tty_dev *tty_dev, |
115 | int complete) |
116 | { |
117 | struct gdm *gdm = tty_dev->gdm[index]; |
118 | |
119 | if (!gdm_tty_ready(gdm)) { |
120 | if (complete == RECV_PACKET_PROCESS_COMPLETE) |
121 | gdm->tty_dev->recv_func(gdm->tty_dev->priv_dev, |
122 | gdm_tty_recv_complete); |
123 | return TO_HOST_PORT_CLOSE; |
124 | } |
125 | |
126 | if (data && len) { |
127 | if (tty_buffer_request_room(port: &gdm->port, size: len) == len) { |
128 | tty_insert_flip_string(port: &gdm->port, chars: data, size: len); |
129 | tty_flip_buffer_push(port: &gdm->port); |
130 | } else { |
131 | return TO_HOST_BUFFER_REQUEST_FAIL; |
132 | } |
133 | } |
134 | |
135 | if (complete == RECV_PACKET_PROCESS_COMPLETE) |
136 | gdm->tty_dev->recv_func(gdm->tty_dev->priv_dev, |
137 | gdm_tty_recv_complete); |
138 | |
139 | return 0; |
140 | } |
141 | |
142 | static void gdm_tty_send_complete(void *arg) |
143 | { |
144 | struct gdm *gdm = arg; |
145 | |
146 | if (!gdm_tty_ready(gdm)) |
147 | return; |
148 | |
149 | tty_port_tty_wakeup(port: &gdm->port); |
150 | } |
151 | |
152 | static ssize_t gdm_tty_write(struct tty_struct *tty, const u8 *buf, size_t len) |
153 | { |
154 | struct gdm *gdm = tty->driver_data; |
155 | size_t remain = len; |
156 | size_t sent_len = 0; |
157 | |
158 | if (!gdm_tty_ready(gdm)) |
159 | return -ENODEV; |
160 | |
161 | while (remain) { |
162 | size_t sending_len = min_t(size_t, MUX_TX_MAX_SIZE, remain); |
163 | |
164 | gdm->tty_dev->send_func(gdm->tty_dev->priv_dev, |
165 | (void *)(buf + sent_len), |
166 | sending_len, |
167 | gdm->index, |
168 | gdm_tty_send_complete, |
169 | gdm); |
170 | sent_len += sending_len; |
171 | remain -= sending_len; |
172 | } |
173 | |
174 | return len; |
175 | } |
176 | |
177 | static unsigned int gdm_tty_write_room(struct tty_struct *tty) |
178 | { |
179 | struct gdm *gdm = tty->driver_data; |
180 | |
181 | if (!gdm_tty_ready(gdm)) |
182 | return 0; |
183 | |
184 | return WRITE_SIZE; |
185 | } |
186 | |
187 | int register_lte_tty_device(struct tty_dev *tty_dev, struct device *device) |
188 | { |
189 | struct gdm *gdm; |
190 | int i; |
191 | int j; |
192 | |
193 | for (i = 0; i < TTY_MAX_COUNT; i++) { |
194 | gdm = kmalloc(size: sizeof(*gdm), GFP_KERNEL); |
195 | if (!gdm) |
196 | return -ENOMEM; |
197 | |
198 | mutex_lock(&gdm_table_lock); |
199 | for (j = 0; j < GDM_TTY_MINOR; j++) { |
200 | if (!gdm_table[i][j]) |
201 | break; |
202 | } |
203 | |
204 | if (j == GDM_TTY_MINOR) { |
205 | kfree(objp: gdm); |
206 | mutex_unlock(lock: &gdm_table_lock); |
207 | return -EINVAL; |
208 | } |
209 | |
210 | gdm_table[i][j] = gdm; |
211 | mutex_unlock(lock: &gdm_table_lock); |
212 | |
213 | tty_dev->gdm[i] = gdm; |
214 | tty_port_init(port: &gdm->port); |
215 | |
216 | gdm->port.ops = &gdm_port_ops; |
217 | gdm->index = i; |
218 | gdm->minor = j; |
219 | gdm->tty_dev = tty_dev; |
220 | |
221 | tty_port_register_device(port: &gdm->port, driver: gdm_driver[i], |
222 | index: gdm->minor, device); |
223 | } |
224 | |
225 | for (i = 0; i < MAX_ISSUE_NUM; i++) |
226 | gdm->tty_dev->recv_func(gdm->tty_dev->priv_dev, |
227 | gdm_tty_recv_complete); |
228 | |
229 | return 0; |
230 | } |
231 | |
232 | void unregister_lte_tty_device(struct tty_dev *tty_dev) |
233 | { |
234 | struct gdm *gdm; |
235 | struct tty_struct *tty; |
236 | int i; |
237 | |
238 | for (i = 0; i < TTY_MAX_COUNT; i++) { |
239 | gdm = tty_dev->gdm[i]; |
240 | if (!gdm) |
241 | continue; |
242 | |
243 | mutex_lock(&gdm_table_lock); |
244 | gdm_table[gdm->index][gdm->minor] = NULL; |
245 | mutex_unlock(lock: &gdm_table_lock); |
246 | |
247 | tty = tty_port_tty_get(port: &gdm->port); |
248 | if (tty) { |
249 | tty_vhangup(tty); |
250 | tty_kref_put(tty); |
251 | } |
252 | |
253 | tty_unregister_device(driver: gdm_driver[i], index: gdm->minor); |
254 | tty_port_put(port: &gdm->port); |
255 | } |
256 | } |
257 | |
258 | static const struct tty_operations gdm_tty_ops = { |
259 | .install = gdm_tty_install, |
260 | .open = gdm_tty_open, |
261 | .close = gdm_tty_close, |
262 | .cleanup = gdm_tty_cleanup, |
263 | .hangup = gdm_tty_hangup, |
264 | .write = gdm_tty_write, |
265 | .write_room = gdm_tty_write_room, |
266 | }; |
267 | |
268 | int register_lte_tty_driver(void) |
269 | { |
270 | struct tty_driver *tty_driver; |
271 | int i; |
272 | int ret; |
273 | |
274 | for (i = 0; i < TTY_MAX_COUNT; i++) { |
275 | tty_driver = tty_alloc_driver(GDM_TTY_MINOR, |
276 | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV); |
277 | if (IS_ERR(ptr: tty_driver)) |
278 | return PTR_ERR(ptr: tty_driver); |
279 | |
280 | tty_driver->owner = THIS_MODULE; |
281 | tty_driver->driver_name = DRIVER_STRING[i]; |
282 | tty_driver->name = DEVICE_STRING[i]; |
283 | tty_driver->major = GDM_TTY_MAJOR; |
284 | tty_driver->type = TTY_DRIVER_TYPE_SERIAL; |
285 | tty_driver->subtype = SERIAL_TYPE_NORMAL; |
286 | tty_driver->init_termios = tty_std_termios; |
287 | tty_driver->init_termios.c_cflag = B9600 | CS8 | HUPCL | CLOCAL; |
288 | tty_driver->init_termios.c_lflag = ISIG | ICANON | IEXTEN; |
289 | tty_set_operations(driver: tty_driver, op: &gdm_tty_ops); |
290 | |
291 | ret = tty_register_driver(driver: tty_driver); |
292 | if (ret) { |
293 | tty_driver_kref_put(driver: tty_driver); |
294 | return ret; |
295 | } |
296 | |
297 | gdm_driver[i] = tty_driver; |
298 | } |
299 | |
300 | return ret; |
301 | } |
302 | |
303 | void unregister_lte_tty_driver(void) |
304 | { |
305 | struct tty_driver *tty_driver; |
306 | int i; |
307 | |
308 | for (i = 0; i < TTY_MAX_COUNT; i++) { |
309 | tty_driver = gdm_driver[i]; |
310 | if (tty_driver) { |
311 | tty_unregister_driver(driver: tty_driver); |
312 | tty_driver_kref_put(driver: tty_driver); |
313 | } |
314 | } |
315 | } |
316 | |
317 | |