457 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			457 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
| /*
 | |
|  * Copyright (c) 2006-2023, RT-Thread Development Team
 | |
|  *
 | |
|  * SPDX-License-Identifier: Apache-2.0
 | |
|  *
 | |
|  * Change Logs:
 | |
|  * Date           Author       Notes
 | |
|  * 2023-11-13     Shell        init ver.
 | |
|  */
 | |
| 
 | |
| #define DBG_TAG "lwp.tty"
 | |
| #define DBG_LVL DBG_INFO
 | |
| #include <rtdbg.h>
 | |
| 
 | |
| #define TTY_CONF_INCLUDE_CCHARS
 | |
| #include "tty_config.h"
 | |
| #include "tty_internal.h"
 | |
| #include "terminal.h"
 | |
| 
 | |
| /* configure option: timeout of tty drain wait */
 | |
| static int tty_drainwait = 5 * 60;
 | |
| 
 | |
| #define TTY_NAME_PREFIX "tty"
 | |
| static char *alloc_device_name(const char *name)
 | |
| {
 | |
|     char *tty_dev_name;
 | |
|     long name_buf_len = (sizeof(TTY_NAME_PREFIX) - 1) /* raw prefix */
 | |
|                         + rt_strlen(name)             /* custom name */
 | |
|                         + 1;                          /* tailing \0 */
 | |
| 
 | |
|     tty_dev_name = rt_malloc(name_buf_len);
 | |
|     if (tty_dev_name)
 | |
|         sprintf(tty_dev_name, "%s%s", TTY_NAME_PREFIX, name);
 | |
|     return tty_dev_name;
 | |
| }
 | |
| 
 | |
| /* character device for tty */
 | |
| #ifdef RT_USING_DEVICE_OPS
 | |
| const static struct rt_device_ops tty_dev_ops = {
 | |
|     /* IO directly through device is not allowed */
 | |
| };
 | |
| #else
 | |
| #error Must enable RT_USING_DEVICE_OPS in Kconfig
 | |
| #endif
 | |
| 
 | |
| static int tty_fops_open(struct dfs_file *file)
 | |
| {
 | |
|     int rc;
 | |
|     lwp_tty_t tp;
 | |
|     rt_device_t device;
 | |
|     int devtype = 0; /* unused */
 | |
| 
 | |
|     if (file->vnode && file->vnode->data)
 | |
|     {
 | |
|         if (file->vnode->ref_count != 1)
 | |
|         {
 | |
|             rc = 0;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             device = (rt_device_t)file->vnode->data;
 | |
|             tp = rt_container_of(device, struct lwp_tty, parent);
 | |
|             rc = bsd_ttydev_methods.d_open(tp, file->flags, devtype,
 | |
|                                            rt_thread_self());
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         rc = -EINVAL;
 | |
|     }
 | |
| 
 | |
|     return rc;
 | |
| }
 | |
| 
 | |
| static int tty_fops_close(struct dfs_file *file)
 | |
| {
 | |
|     int rc;
 | |
|     lwp_tty_t tp;
 | |
|     rt_device_t device;
 | |
|     int fflags = FFLAGS(file->flags);
 | |
|     int devtype = 0; /* unused */
 | |
| 
 | |
|     if (file->vnode && file->vnode->data)
 | |
|     {
 | |
|         if (file->vnode->ref_count != 1)
 | |
|         {
 | |
|             rc = 0;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             device = (rt_device_t)file->vnode->data;
 | |
|             tp = rt_container_of(device, struct lwp_tty, parent);
 | |
|             rc = bsd_ttydev_methods.d_close(tp, fflags, devtype, rt_thread_self());
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         rc = -EINVAL;
 | |
|     }
 | |
| 
 | |
|     return rc;
 | |
| }
 | |
| 
 | |
| static int tty_fops_ioctl(struct dfs_file *file, int cmd, void *arg)
 | |
| {
 | |
|     int rc;
 | |
|     lwp_tty_t tp;
 | |
|     rt_device_t device;
 | |
| 
 | |
|     if (file->vnode && file->vnode->data)
 | |
|     {
 | |
|         device = (rt_device_t)file->vnode->data;
 | |
|         tp = rt_container_of(device, struct lwp_tty, parent);
 | |
|         rc = lwp_tty_ioctl_adapter(tp, cmd, file->flags, arg, rt_thread_self());
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         rc = -EINVAL;
 | |
|     }
 | |
| 
 | |
|     return rc;
 | |
| }
 | |
| 
 | |
| static ssize_t tty_fops_read(struct dfs_file *file, void *buf, size_t count,
 | |
|                              off_t *pos)
 | |
| {
 | |
|     ssize_t rc = 0;
 | |
|     int error;
 | |
|     struct uio uio;
 | |
|     struct iovec iov;
 | |
|     rt_device_t device;
 | |
|     struct lwp_tty *tp;
 | |
|     int ioflags;
 | |
|     int oflags = file->flags;
 | |
| 
 | |
|     if (file->vnode && file->vnode->data)
 | |
|     {
 | |
|         device = (rt_device_t)file->vnode->data;
 | |
|         tp = rt_container_of(device, struct lwp_tty, parent);
 | |
| 
 | |
|         /* setup ioflags */
 | |
|         ioflags = 0;
 | |
|         if (oflags & O_NONBLOCK)
 | |
|             ioflags |= IO_NDELAY;
 | |
| 
 | |
|         /* setup uio parameters */
 | |
|         iov.iov_base = (void *)buf;
 | |
|         iov.iov_len = count;
 | |
|         uio.uio_offset = file->fpos;
 | |
|         uio.uio_resid = count;
 | |
|         uio.uio_iov = &iov;
 | |
|         uio.uio_iovcnt = 1;
 | |
|         uio.uio_rw = UIO_READ;
 | |
| 
 | |
|         rc = count;
 | |
|         error = bsd_ttydev_methods.d_read(tp, &uio, ioflags);
 | |
|         rc -= uio.uio_resid;
 | |
|         if (error)
 | |
|         {
 | |
|             LOG_D("%s: failed to write %d bytes of data. error code %d",
 | |
|                   __func__, uio.uio_resid, error);
 | |
|             rc = error;
 | |
|         }
 | |
| 
 | |
|         /* reset file context */
 | |
|         file->fpos = uio.uio_offset;
 | |
|     }
 | |
| 
 | |
|     if (rc)
 | |
|         LOG_D("%s(len=%d, buf=%c \"%d\")", __func__, rc, *((char *)buf),
 | |
|               *((char *)buf));
 | |
|     return rc;
 | |
| }
 | |
| 
 | |
| static ssize_t tty_fops_write(struct dfs_file *file, const void *buf,
 | |
|                               size_t count, off_t *pos)
 | |
| {
 | |
|     ssize_t rc = 0;
 | |
|     int error;
 | |
|     struct uio uio;
 | |
|     struct iovec iov;
 | |
|     rt_device_t device;
 | |
|     struct lwp_tty *tp;
 | |
|     int ioflags;
 | |
|     int oflags = file->flags;
 | |
| 
 | |
|     if (file->vnode && file->vnode->data)
 | |
|     {
 | |
|         device = (rt_device_t)file->vnode->data;
 | |
|         tp = rt_container_of(device, struct lwp_tty, parent);
 | |
| 
 | |
|         /* setup ioflags */
 | |
|         ioflags = 0;
 | |
|         if (oflags & O_NONBLOCK)
 | |
|             ioflags |= IO_NDELAY;
 | |
| 
 | |
|         /* setup uio parameters */
 | |
|         iov.iov_base = (void *)buf;
 | |
|         iov.iov_len = count;
 | |
|         uio.uio_offset = file->fpos;
 | |
|         uio.uio_resid = count;
 | |
|         uio.uio_iov = &iov;
 | |
|         uio.uio_iovcnt = 1;
 | |
|         uio.uio_rw = UIO_WRITE;
 | |
| 
 | |
|         rc = count;
 | |
|         error = bsd_ttydev_methods.d_write(tp, &uio, ioflags);
 | |
|         if (error)
 | |
|         {
 | |
|             rc = error;
 | |
|             LOG_D("%s: failed to write %d bytes of data. error code %d",
 | |
|                   __func__, uio.uio_resid, error);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             rc -= uio.uio_resid;
 | |
|         }
 | |
| 
 | |
|         /* reset file context */
 | |
|         file->fpos = uio.uio_offset;
 | |
|     }
 | |
|     return rc;
 | |
| }
 | |
| 
 | |
| static int tty_fops_flush(struct dfs_file *file)
 | |
| {
 | |
|     return -EINVAL;
 | |
| }
 | |
| 
 | |
| static off_t tty_fops_lseek(struct dfs_file *file, off_t offset, int wherece)
 | |
| {
 | |
|     return -EINVAL;
 | |
| }
 | |
| 
 | |
| static int tty_fops_truncate(struct dfs_file *file, off_t offset)
 | |
| {
 | |
|     /**
 | |
|      * regarding to POSIX.1, TRUNC is not supported for tty device.
 | |
|      * return 0 always to make filesystem happy
 | |
|      */
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static int tty_fops_poll(struct dfs_file *file, struct rt_pollreq *req)
 | |
| {
 | |
|     int rc;
 | |
|     rt_device_t device;
 | |
|     struct lwp_tty *tp;
 | |
| 
 | |
|     if (file->vnode && file->vnode->data)
 | |
|     {
 | |
|         device = (rt_device_t)file->vnode->data;
 | |
|         tp = rt_container_of(device, struct lwp_tty, parent);
 | |
| 
 | |
|         rc = bsd_ttydev_methods.d_poll(tp, req, rt_thread_self());
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         rc = -1;
 | |
|     }
 | |
| 
 | |
|     return rc;
 | |
| }
 | |
| 
 | |
| static int tty_fops_mmap(struct dfs_file *file, struct lwp_avl_struct *mmap)
 | |
| {
 | |
|     return -EINVAL;
 | |
| }
 | |
| 
 | |
| static int tty_fops_lock(struct dfs_file *file, struct file_lock *flock)
 | |
| {
 | |
|     return -EINVAL;
 | |
| }
 | |
| 
 | |
| static int tty_fops_flock(struct dfs_file *file, int operation, struct file_lock *flock)
 | |
| {
 | |
|     return -EINVAL;
 | |
| }
 | |
| 
 | |
| static struct dfs_file_ops tty_file_ops = {
 | |
|     .open = tty_fops_open,
 | |
|     .close = tty_fops_close,
 | |
|     .ioctl = tty_fops_ioctl,
 | |
|     .read = tty_fops_read,
 | |
|     .write = tty_fops_write,
 | |
|     .flush = tty_fops_flush,
 | |
|     .lseek = tty_fops_lseek,
 | |
|     .truncate = tty_fops_truncate,
 | |
|     .poll = tty_fops_poll,
 | |
|     .mmap = tty_fops_mmap,
 | |
|     .lock = tty_fops_lock,
 | |
|     .flock = tty_fops_flock,
 | |
| };
 | |
| 
 | |
| rt_inline void device_setup(lwp_tty_t terminal)
 | |
| {
 | |
|     terminal->parent.type = RT_Device_Class_Char;
 | |
| #ifdef RT_USING_DEVICE_OPS
 | |
|     terminal->parent.ops = &tty_dev_ops;
 | |
| #else
 | |
| #error Must enable RT_USING_DEVICE_OPS in Kconfig
 | |
| #endif
 | |
| }
 | |
| 
 | |
| /* register TTY device */
 | |
| rt_err_t lwp_tty_register(lwp_tty_t terminal, const char *name)
 | |
| {
 | |
|     rt_err_t rc = -RT_ENOMEM;
 | |
|     const char *tty_name;
 | |
|     char *alloc_name;
 | |
| 
 | |
|     if (terminal->t_devsw->tsw_flags & TF_NOPREFIX)
 | |
|     {
 | |
|         alloc_name = RT_NULL;
 | |
|         tty_name = name;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         alloc_name = alloc_device_name(name);
 | |
|         tty_name = alloc_name;
 | |
|     }
 | |
| 
 | |
|     if (tty_name)
 | |
|     {
 | |
|         device_setup(terminal);
 | |
|         rc = rt_device_register(&terminal->parent, tty_name, 0);
 | |
|         if (rc == RT_EOK)
 | |
|         {
 | |
|             terminal->parent.fops = &tty_file_ops;
 | |
| 
 | |
|             LOG_D("%s() /dev/%s device registered", __func__, tty_name);
 | |
|         }
 | |
| 
 | |
|         rt_free(alloc_name);
 | |
|     }
 | |
|     return rc;
 | |
| }
 | |
| 
 | |
| static void tty_init_termios(lwp_tty_t tp)
 | |
| {
 | |
|     struct termios *t = &tp->t_termios_init_in;
 | |
| 
 | |
|     t->c_cflag = TTYDEF_CFLAG;
 | |
|     t->c_iflag = TTYDEF_IFLAG;
 | |
|     t->c_lflag = TTYDEF_LFLAG;
 | |
|     t->c_oflag = TTYDEF_OFLAG;
 | |
|     t->__c_ispeed = TTYDEF_SPEED;
 | |
|     t->__c_ospeed = TTYDEF_SPEED;
 | |
| 
 | |
|     memcpy(&t->c_cc, tty_ctrl_charset,
 | |
|            sizeof(tty_ctrl_charset) / sizeof(tty_ctrl_charset[0]));
 | |
| 
 | |
| #ifdef USING_BSD_INIT_LOCK_DEVICE
 | |
|     tp->t_termios_init_out = *t;
 | |
| #endif /* USING_BSD_INIT_LOCK_DEVICE */
 | |
| }
 | |
| 
 | |
| lwp_tty_t lwp_tty_create_ext(lwp_ttydevsw_t handle, void *softc,
 | |
|                              rt_mutex_t custom_mtx)
 | |
| {
 | |
|     lwp_tty_t tp;
 | |
| 
 | |
|     tp = rt_calloc(1, sizeof(struct lwp_tty)
 | |
|     #ifdef USING_BSD_SIGINFO
 | |
|             + LWP_TTY_PRBUF_SIZE
 | |
|     #endif
 | |
|             );
 | |
| 
 | |
|     if (!tp)
 | |
|         return tp;
 | |
| 
 | |
|     bsd_devsw_init(handle);
 | |
| 
 | |
| #ifdef USING_BSD_SIGINFO
 | |
|     tp->t_prbufsz = LWP_TTY_PRBUF_SIZE;
 | |
| #endif
 | |
|     tp->t_devsw = handle;
 | |
|     tp->t_devswsoftc = softc;
 | |
|     tp->t_flags = handle->tsw_flags;
 | |
|     tp->t_drainwait = tty_drainwait;
 | |
| 
 | |
|     tty_init_termios(tp);
 | |
| 
 | |
|     cv_init(&tp->t_inwait, "ttyin");
 | |
|     cv_init(&tp->t_outwait, "ttyout");
 | |
|     cv_init(&tp->t_outserwait, "ttyosr");
 | |
|     cv_init(&tp->t_bgwait, "ttybg");
 | |
|     cv_init(&tp->t_dcdwait, "ttydcd");
 | |
| 
 | |
|     rt_wqueue_init(&tp->t_inpoll);
 | |
|     rt_wqueue_init(&tp->t_outpoll);
 | |
| 
 | |
|     /* Allow drivers to use a custom mutex to lock the TTY. */
 | |
|     if (custom_mtx != NULL)
 | |
|     {
 | |
|         tp->t_mtx = custom_mtx;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         tp->t_mtx = &tp->t_mtxobj;
 | |
|         rt_mutex_init(&tp->t_mtxobj, "ttydev", RT_IPC_FLAG_PRIO);
 | |
|     }
 | |
| 
 | |
| #ifdef USING_BSD_POLL
 | |
|     knlist_init_mtx(&tp->t_inpoll.si_note, tp->t_mtx);
 | |
|     knlist_init_mtx(&tp->t_outpoll.si_note, tp->t_mtx);
 | |
| #endif
 | |
| 
 | |
|     return tp;
 | |
| }
 | |
| 
 | |
| lwp_tty_t lwp_tty_create(lwp_ttydevsw_t handle, void *softc)
 | |
| {
 | |
|     return lwp_tty_create_ext(handle, softc, NULL);
 | |
| }
 | |
| 
 | |
| void lwp_tty_delete(lwp_tty_t tp)
 | |
| {
 | |
|     /*
 | |
|      * ttyydev_leave() usually frees the i/o queues earlier, but it is
 | |
|      * not always called between queue allocation and here.  The queues
 | |
|      * may be allocated by ioctls on a pty control device without the
 | |
|      * corresponding pty slave device ever being open, or after it is
 | |
|      * closed.
 | |
|      */
 | |
|     ttyinq_free(&tp->t_inq);
 | |
|     ttyoutq_free(&tp->t_outq);
 | |
|     rt_wqueue_wakeup_all(&tp->t_inpoll, (void *)POLLHUP);
 | |
|     rt_wqueue_wakeup_all(&tp->t_outpoll, (void *)POLLHUP);
 | |
| 
 | |
| #ifdef USING_BSD_POLL
 | |
|     knlist_destroy(&tp->t_inpoll.si_note);
 | |
|     knlist_destroy(&tp->t_outpoll.si_note);
 | |
| #endif
 | |
| 
 | |
|     cv_destroy(&tp->t_inwait);
 | |
|     cv_destroy(&tp->t_outwait);
 | |
|     cv_destroy(&tp->t_bgwait);
 | |
|     cv_destroy(&tp->t_dcdwait);
 | |
|     cv_destroy(&tp->t_outserwait);
 | |
| 
 | |
|     if (tp->t_mtx == &tp->t_mtxobj)
 | |
|         rt_mutex_detach(&tp->t_mtxobj);
 | |
|     ttydevsw_free(tp);
 | |
|     rt_device_unregister(&tp->parent);
 | |
|     rt_free(tp);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Report on state of foreground process group.
 | |
|  */
 | |
| void tty_info(struct lwp_tty *tp)
 | |
| {
 | |
|     /* TODO */
 | |
|     return;
 | |
| }
 |