1 /*
2  * Copyright (c) 2006-2023, RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author       Notes
8  * 2023-11-13     Shell        init ver.
9  */
10 
11 #define DBG_TAG "lwp.tty"
12 #define DBG_LVL DBG_INFO
13 #include <rtdbg.h>
14 
15 #define TTY_CONF_INCLUDE_CCHARS
16 #include "tty_config.h"
17 #include "tty_internal.h"
18 #include "terminal.h"
19 
20 /* configure option: timeout of tty drain wait */
21 static int tty_drainwait = 5 * 60;
22 
23 #define TTY_NAME_PREFIX "tty"
alloc_device_name(const char * name)24 static char *alloc_device_name(const char *name)
25 {
26     char *tty_dev_name;
27     long name_buf_len = (sizeof(TTY_NAME_PREFIX) - 1) /* raw prefix */
28                         + rt_strlen(name)             /* custom name */
29                         + 1;                          /* tailing \0 */
30 
31     tty_dev_name = rt_malloc(name_buf_len);
32     if (tty_dev_name)
33         sprintf(tty_dev_name, "%s%s", TTY_NAME_PREFIX, name);
34     return tty_dev_name;
35 }
36 
37 /* character device for tty */
38 #ifdef RT_USING_DEVICE_OPS
39 const static struct rt_device_ops tty_dev_ops = {
40     /* IO directly through device is not allowed */
41 };
42 #else
43 #error Must enable RT_USING_DEVICE_OPS in Kconfig
44 #endif
45 
tty_fops_open(struct dfs_file * file)46 static int tty_fops_open(struct dfs_file *file)
47 {
48     int rc;
49     lwp_tty_t tp;
50     rt_device_t device;
51     int devtype = 0; /* unused */
52 
53     if (file->vnode && file->vnode->data)
54     {
55         if (file->vnode->ref_count != 1)
56         {
57             rc = 0;
58         }
59         else
60         {
61             device = (rt_device_t)file->vnode->data;
62             tp = rt_container_of(device, struct lwp_tty, parent);
63             rc = bsd_ttydev_methods.d_open(tp, file->flags, devtype,
64                                            rt_thread_self());
65         }
66     }
67     else
68     {
69         rc = -EINVAL;
70     }
71 
72     return rc;
73 }
74 
tty_fops_close(struct dfs_file * file)75 static int tty_fops_close(struct dfs_file *file)
76 {
77     int rc;
78     lwp_tty_t tp;
79     rt_device_t device;
80     int fflags = FFLAGS(file->flags);
81     int devtype = 0; /* unused */
82 
83     if (file->vnode && file->vnode->data)
84     {
85         if (file->vnode->ref_count != 1)
86         {
87             rc = 0;
88         }
89         else
90         {
91             device = (rt_device_t)file->vnode->data;
92             tp = rt_container_of(device, struct lwp_tty, parent);
93             rc = bsd_ttydev_methods.d_close(tp, fflags, devtype, rt_thread_self());
94         }
95     }
96     else
97     {
98         rc = -EINVAL;
99     }
100 
101     return rc;
102 }
103 
tty_fops_ioctl(struct dfs_file * file,int cmd,void * arg)104 static int tty_fops_ioctl(struct dfs_file *file, int cmd, void *arg)
105 {
106     int rc;
107     lwp_tty_t tp;
108     rt_device_t device;
109 
110     if (file->vnode && file->vnode->data)
111     {
112         device = (rt_device_t)file->vnode->data;
113         tp = rt_container_of(device, struct lwp_tty, parent);
114         rc = lwp_tty_ioctl_adapter(tp, cmd, file->flags, arg, rt_thread_self());
115     }
116     else
117     {
118         rc = -EINVAL;
119     }
120 
121     return rc;
122 }
123 
tty_fops_read(struct dfs_file * file,void * buf,size_t count,off_t * pos)124 static ssize_t tty_fops_read(struct dfs_file *file, void *buf, size_t count,
125                              off_t *pos)
126 {
127     ssize_t rc = 0;
128     int error;
129     struct uio uio;
130     struct iovec iov;
131     rt_device_t device;
132     struct lwp_tty *tp;
133     int ioflags;
134     int oflags = file->flags;
135 
136     if (file->vnode && file->vnode->data)
137     {
138         device = (rt_device_t)file->vnode->data;
139         tp = rt_container_of(device, struct lwp_tty, parent);
140 
141         /* setup ioflags */
142         ioflags = 0;
143         if (oflags & O_NONBLOCK)
144             ioflags |= IO_NDELAY;
145 
146         /* setup uio parameters */
147         iov.iov_base = (void *)buf;
148         iov.iov_len = count;
149         uio.uio_offset = file->fpos;
150         uio.uio_resid = count;
151         uio.uio_iov = &iov;
152         uio.uio_iovcnt = 1;
153         uio.uio_rw = UIO_READ;
154 
155         rc = count;
156         error = bsd_ttydev_methods.d_read(tp, &uio, ioflags);
157         rc -= uio.uio_resid;
158         if (error)
159         {
160             LOG_D("%s: failed to write %d bytes of data. error code %d",
161                   __func__, uio.uio_resid, error);
162             rc = error;
163         }
164 
165         /* reset file context */
166         file->fpos = uio.uio_offset;
167     }
168 
169     if (rc)
170         LOG_D("%s(len=%d, buf=%c \"%d\")", __func__, rc, *((char *)buf),
171               *((char *)buf));
172     return rc;
173 }
174 
tty_fops_write(struct dfs_file * file,const void * buf,size_t count,off_t * pos)175 static ssize_t tty_fops_write(struct dfs_file *file, const void *buf,
176                               size_t count, off_t *pos)
177 {
178     ssize_t rc = 0;
179     int error;
180     struct uio uio;
181     struct iovec iov;
182     rt_device_t device;
183     struct lwp_tty *tp;
184     int ioflags;
185     int oflags = file->flags;
186 
187     if (file->vnode && file->vnode->data)
188     {
189         device = (rt_device_t)file->vnode->data;
190         tp = rt_container_of(device, struct lwp_tty, parent);
191 
192         /* setup ioflags */
193         ioflags = 0;
194         if (oflags & O_NONBLOCK)
195             ioflags |= IO_NDELAY;
196 
197         /* setup uio parameters */
198         iov.iov_base = (void *)buf;
199         iov.iov_len = count;
200         uio.uio_offset = file->fpos;
201         uio.uio_resid = count;
202         uio.uio_iov = &iov;
203         uio.uio_iovcnt = 1;
204         uio.uio_rw = UIO_WRITE;
205 
206         rc = count;
207         error = bsd_ttydev_methods.d_write(tp, &uio, ioflags);
208         if (error)
209         {
210             rc = error;
211             LOG_D("%s: failed to write %d bytes of data. error code %d",
212                   __func__, uio.uio_resid, error);
213         }
214         else
215         {
216             rc -= uio.uio_resid;
217         }
218 
219         /* reset file context */
220         file->fpos = uio.uio_offset;
221     }
222     return rc;
223 }
224 
tty_fops_flush(struct dfs_file * file)225 static int tty_fops_flush(struct dfs_file *file)
226 {
227     return -EINVAL;
228 }
229 
tty_fops_lseek(struct dfs_file * file,off_t offset,int wherece)230 static off_t tty_fops_lseek(struct dfs_file *file, off_t offset, int wherece)
231 {
232     return -EINVAL;
233 }
234 
tty_fops_truncate(struct dfs_file * file,off_t offset)235 static int tty_fops_truncate(struct dfs_file *file, off_t offset)
236 {
237     /**
238      * regarding to POSIX.1, TRUNC is not supported for tty device.
239      * return 0 always to make filesystem happy
240      */
241     return 0;
242 }
243 
tty_fops_poll(struct dfs_file * file,struct rt_pollreq * req)244 static int tty_fops_poll(struct dfs_file *file, struct rt_pollreq *req)
245 {
246     int rc;
247     rt_device_t device;
248     struct lwp_tty *tp;
249 
250     if (file->vnode && file->vnode->data)
251     {
252         device = (rt_device_t)file->vnode->data;
253         tp = rt_container_of(device, struct lwp_tty, parent);
254 
255         rc = bsd_ttydev_methods.d_poll(tp, req, rt_thread_self());
256     }
257     else
258     {
259         rc = -1;
260     }
261 
262     return rc;
263 }
264 
tty_fops_mmap(struct dfs_file * file,struct lwp_avl_struct * mmap)265 static int tty_fops_mmap(struct dfs_file *file, struct lwp_avl_struct *mmap)
266 {
267     return -EINVAL;
268 }
269 
tty_fops_lock(struct dfs_file * file,struct file_lock * flock)270 static int tty_fops_lock(struct dfs_file *file, struct file_lock *flock)
271 {
272     return -EINVAL;
273 }
274 
tty_fops_flock(struct dfs_file * file,int operation,struct file_lock * flock)275 static int tty_fops_flock(struct dfs_file *file, int operation, struct file_lock *flock)
276 {
277     return -EINVAL;
278 }
279 
280 static struct dfs_file_ops tty_file_ops = {
281     .open = tty_fops_open,
282     .close = tty_fops_close,
283     .ioctl = tty_fops_ioctl,
284     .read = tty_fops_read,
285     .write = tty_fops_write,
286     .flush = tty_fops_flush,
287     .lseek = tty_fops_lseek,
288     .truncate = tty_fops_truncate,
289     .poll = tty_fops_poll,
290     .mmap = tty_fops_mmap,
291     .lock = tty_fops_lock,
292     .flock = tty_fops_flock,
293 };
294 
device_setup(lwp_tty_t terminal)295 rt_inline void device_setup(lwp_tty_t terminal)
296 {
297     terminal->parent.type = RT_Device_Class_Char;
298 #ifdef RT_USING_DEVICE_OPS
299     terminal->parent.ops = &tty_dev_ops;
300 #else
301 #error Must enable RT_USING_DEVICE_OPS in Kconfig
302 #endif
303 }
304 
305 /* register TTY device */
lwp_tty_register(lwp_tty_t terminal,const char * name)306 rt_err_t lwp_tty_register(lwp_tty_t terminal, const char *name)
307 {
308     rt_err_t rc = -RT_ENOMEM;
309     const char *tty_name;
310     char *alloc_name;
311 
312     if (terminal->t_devsw->tsw_flags & TF_NOPREFIX)
313     {
314         alloc_name = RT_NULL;
315         tty_name = name;
316     }
317     else
318     {
319         alloc_name = alloc_device_name(name);
320         tty_name = alloc_name;
321     }
322 
323     if (tty_name)
324     {
325         device_setup(terminal);
326         rc = rt_device_register(&terminal->parent, tty_name, 0);
327         if (rc == RT_EOK)
328         {
329             terminal->parent.fops = &tty_file_ops;
330 
331             LOG_D("%s() /dev/%s device registered", __func__, tty_name);
332         }
333 
334         rt_free(alloc_name);
335     }
336     return rc;
337 }
338 
tty_init_termios(lwp_tty_t tp)339 static void tty_init_termios(lwp_tty_t tp)
340 {
341     struct termios *t = &tp->t_termios_init_in;
342 
343     t->c_cflag = TTYDEF_CFLAG;
344     t->c_iflag = TTYDEF_IFLAG;
345     t->c_lflag = TTYDEF_LFLAG;
346     t->c_oflag = TTYDEF_OFLAG;
347     t->__c_ispeed = TTYDEF_SPEED;
348     t->__c_ospeed = TTYDEF_SPEED;
349 
350     memcpy(&t->c_cc, tty_ctrl_charset,
351            sizeof(tty_ctrl_charset) / sizeof(tty_ctrl_charset[0]));
352 
353 #ifdef USING_BSD_INIT_LOCK_DEVICE
354     tp->t_termios_init_out = *t;
355 #endif /* USING_BSD_INIT_LOCK_DEVICE */
356 }
357 
lwp_tty_create_ext(lwp_ttydevsw_t handle,void * softc,rt_mutex_t custom_mtx)358 lwp_tty_t lwp_tty_create_ext(lwp_ttydevsw_t handle, void *softc,
359                              rt_mutex_t custom_mtx)
360 {
361     lwp_tty_t tp;
362 
363     tp = rt_calloc(1, sizeof(struct lwp_tty)
364     #ifdef USING_BSD_SIGINFO
365             + LWP_TTY_PRBUF_SIZE
366     #endif
367             );
368 
369     if (!tp)
370         return tp;
371 
372     bsd_devsw_init(handle);
373 
374 #ifdef USING_BSD_SIGINFO
375     tp->t_prbufsz = LWP_TTY_PRBUF_SIZE;
376 #endif
377     tp->t_devsw = handle;
378     tp->t_devswsoftc = softc;
379     tp->t_flags = handle->tsw_flags;
380     tp->t_drainwait = tty_drainwait;
381 
382     tty_init_termios(tp);
383 
384     cv_init(&tp->t_inwait, "ttyin");
385     cv_init(&tp->t_outwait, "ttyout");
386     cv_init(&tp->t_outserwait, "ttyosr");
387     cv_init(&tp->t_bgwait, "ttybg");
388     cv_init(&tp->t_dcdwait, "ttydcd");
389 
390     rt_wqueue_init(&tp->t_inpoll);
391     rt_wqueue_init(&tp->t_outpoll);
392 
393     /* Allow drivers to use a custom mutex to lock the TTY. */
394     if (custom_mtx != NULL)
395     {
396         tp->t_mtx = custom_mtx;
397     }
398     else
399     {
400         tp->t_mtx = &tp->t_mtxobj;
401         rt_mutex_init(&tp->t_mtxobj, "ttydev", RT_IPC_FLAG_PRIO);
402     }
403 
404 #ifdef USING_BSD_POLL
405     knlist_init_mtx(&tp->t_inpoll.si_note, tp->t_mtx);
406     knlist_init_mtx(&tp->t_outpoll.si_note, tp->t_mtx);
407 #endif
408 
409     return tp;
410 }
411 
lwp_tty_create(lwp_ttydevsw_t handle,void * softc)412 lwp_tty_t lwp_tty_create(lwp_ttydevsw_t handle, void *softc)
413 {
414     return lwp_tty_create_ext(handle, softc, NULL);
415 }
416 
lwp_tty_delete(lwp_tty_t tp)417 void lwp_tty_delete(lwp_tty_t tp)
418 {
419     /*
420      * ttyydev_leave() usually frees the i/o queues earlier, but it is
421      * not always called between queue allocation and here.  The queues
422      * may be allocated by ioctls on a pty control device without the
423      * corresponding pty slave device ever being open, or after it is
424      * closed.
425      */
426     ttyinq_free(&tp->t_inq);
427     ttyoutq_free(&tp->t_outq);
428     rt_wqueue_wakeup_all(&tp->t_inpoll, (void *)POLLHUP);
429     rt_wqueue_wakeup_all(&tp->t_outpoll, (void *)POLLHUP);
430 
431 #ifdef USING_BSD_POLL
432     knlist_destroy(&tp->t_inpoll.si_note);
433     knlist_destroy(&tp->t_outpoll.si_note);
434 #endif
435 
436     cv_destroy(&tp->t_inwait);
437     cv_destroy(&tp->t_outwait);
438     cv_destroy(&tp->t_bgwait);
439     cv_destroy(&tp->t_dcdwait);
440     cv_destroy(&tp->t_outserwait);
441 
442     if (tp->t_mtx == &tp->t_mtxobj)
443         rt_mutex_detach(&tp->t_mtxobj);
444     ttydevsw_free(tp);
445     rt_device_unregister(&tp->parent);
446     rt_free(tp);
447 }
448 
449 /*
450  * Report on state of foreground process group.
451  */
tty_info(struct lwp_tty * tp)452 void tty_info(struct lwp_tty *tp)
453 {
454     /* TODO */
455     return;
456 }
457