457 lines
11 KiB
C
457 lines
11 KiB
C
/*
|
|
* 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;
|
|
}
|