currently, nuttx implements readv/writev on the top of read/write. while it might work for the simplest cases, it's broken by design. for example, it's impossible to make it work correctly for files which need to preserve data boundaries without allocating a single contiguous buffer. (udp socket, some character devices, etc) this change is a start of the migration to a better design. that is, implement read/write on the top of readv/writev. to avoid a single huge change, following things will NOT be done in this commit: * fix actual bugs caused by the original readv-based-on-read design. (cf. https://github.com/apache/nuttx/pull/12674) * adapt filesystems/drivers to actually benefit from the new interface. (except a few trivial examples) * eventually retire the old interface. * retire read/write syscalls. implement them in libc instead. * pread/pwrite/preadv/pwritev (except the introduction of struct uio, which is a preparation to back these variations with the new interface.)
1162 lines
30 KiB
C
1162 lines
30 KiB
C
/****************************************************************************
|
|
* drivers/serial/pty.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <stdbool.h>
|
|
#include <unistd.h>
|
|
#include <sched.h>
|
|
#include <termios.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <poll.h>
|
|
#include <fcntl.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
|
|
#include <nuttx/ascii.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/mutex.h>
|
|
#include <nuttx/semaphore.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/serial/pty.h>
|
|
#include <nuttx/signal.h>
|
|
|
|
#include "pty.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
/* Maximum number of threads than can be waiting for POLL events */
|
|
|
|
#ifndef CONFIG_DEV_PTY_NPOLLWAITERS
|
|
# define CONFIG_DEV_PTY_NPOLLWAITERS 2
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct pty_poll_s
|
|
{
|
|
FAR void *src;
|
|
FAR void *sink;
|
|
};
|
|
|
|
/* This device structure describes on memory of the PTY device pair */
|
|
|
|
struct pty_devpair_s;
|
|
struct pty_dev_s
|
|
{
|
|
FAR struct pty_devpair_s *pd_devpair;
|
|
struct file pd_src; /* Provides data to read() method (pipe output) */
|
|
struct file pd_sink; /* Accepts data from write() method (pipe input) */
|
|
bool pd_master; /* True: this is the master */
|
|
uint8_t pd_escape; /* Number of the character to be escaped */
|
|
#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP)
|
|
pid_t pd_pid; /* Thread PID to receive signals (-1 if none) */
|
|
#endif
|
|
tcflag_t pd_iflag; /* Terminal input modes */
|
|
tcflag_t pd_lflag; /* Terminal local modes */
|
|
tcflag_t pd_oflag; /* Terminal output modes */
|
|
struct pty_poll_s pd_poll[CONFIG_DEV_PTY_NPOLLWAITERS];
|
|
};
|
|
|
|
/* This structure describes the pipe pair */
|
|
|
|
struct pty_devpair_s
|
|
{
|
|
struct pty_dev_s pp_master; /* Maseter device */
|
|
struct pty_dev_s pp_slave; /* Slave device */
|
|
|
|
bool pp_susv1; /* SUSv1 or BSD style */
|
|
bool pp_locked; /* Slave is locked */
|
|
bool pp_unlinked; /* File has been unlinked */
|
|
uint8_t pp_minor; /* Minor device number */
|
|
uint16_t pp_nopen; /* Open file count */
|
|
sem_t pp_slavesem; /* Slave lock semaphore */
|
|
mutex_t pp_lock; /* Mutual exclusion */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
static void pty_destroy(FAR struct pty_devpair_s *devpair);
|
|
static int pty_pipe(FAR struct pty_devpair_s *devpair);
|
|
static int pty_open(FAR struct file *filep);
|
|
static int pty_close(FAR struct file *filep);
|
|
static ssize_t pty_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen);
|
|
static ssize_t pty_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen);
|
|
static int pty_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
|
|
static int pty_poll(FAR struct file *filep, FAR struct pollfd *fds,
|
|
bool setup);
|
|
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
|
|
static int pty_unlink(FAR struct inode *inode);
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static const struct file_operations g_pty_fops =
|
|
{
|
|
pty_open, /* open */
|
|
pty_close, /* close */
|
|
pty_read, /* read */
|
|
pty_write, /* write */
|
|
NULL, /* seek */
|
|
pty_ioctl, /* ioctl */
|
|
NULL, /* mmap */
|
|
NULL, /* truncate */
|
|
pty_poll, /* poll */
|
|
NULL, /* readv */
|
|
NULL /* writev */
|
|
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
|
|
, pty_unlink /* unlink */
|
|
#endif
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: pty_destroy
|
|
****************************************************************************/
|
|
|
|
static void pty_destroy(FAR struct pty_devpair_s *devpair)
|
|
{
|
|
char devname[16];
|
|
|
|
if (devpair->pp_susv1)
|
|
{
|
|
/* Free this minor number so that it can be reused */
|
|
|
|
ptmx_minor_free(devpair->pp_minor);
|
|
|
|
/* Un-register the slave device */
|
|
|
|
snprintf(devname, sizeof(devname), "/dev/pts/%u", devpair->pp_minor);
|
|
}
|
|
else
|
|
{
|
|
/* Un-register the master device (/dev/ptyN may have already been
|
|
* unlinked).
|
|
*/
|
|
|
|
snprintf(devname, sizeof(devname), "/dev/pty%u", devpair->pp_minor);
|
|
unregister_driver(devname);
|
|
|
|
/* Un-register the slave device */
|
|
|
|
snprintf(devname, sizeof(devname), "/dev/ttyp%u", devpair->pp_minor);
|
|
}
|
|
|
|
unregister_driver(devname);
|
|
|
|
/* And free the device structure */
|
|
|
|
nxmutex_destroy(&devpair->pp_lock);
|
|
nxsem_destroy(&devpair->pp_slavesem);
|
|
kmm_free(devpair);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pty_pipe
|
|
****************************************************************************/
|
|
|
|
static int pty_pipe(FAR struct pty_devpair_s *devpair)
|
|
{
|
|
FAR struct file *pipe_a[2];
|
|
FAR struct file *pipe_b[2];
|
|
int ret;
|
|
|
|
/* Create two pipes:
|
|
*
|
|
* pipe_a: Master source, slave sink (TX, slave-to-master)
|
|
* pipe_b: Master sink, slave source (RX, master-to-slave)
|
|
*/
|
|
|
|
pipe_a[0] = &devpair->pp_master.pd_src;
|
|
pipe_a[1] = &devpair->pp_slave.pd_sink;
|
|
|
|
ret = file_pipe(pipe_a, CONFIG_PSEUDOTERM_TXBUFSIZE, O_CLOEXEC);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
pipe_b[0] = &devpair->pp_slave.pd_src;
|
|
pipe_b[1] = &devpair->pp_master.pd_sink;
|
|
|
|
ret = file_pipe(pipe_b, CONFIG_PSEUDOTERM_RXBUFSIZE, O_CLOEXEC);
|
|
if (ret < 0)
|
|
{
|
|
file_close(pipe_a[0]);
|
|
file_close(pipe_a[1]);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pty_open
|
|
****************************************************************************/
|
|
|
|
static int pty_open(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct pty_dev_s *dev;
|
|
FAR struct pty_devpair_s *devpair;
|
|
int ret = OK;
|
|
|
|
inode = filep->f_inode;
|
|
dev = inode->i_private;
|
|
DEBUGASSERT(dev != NULL && dev->pd_devpair != NULL);
|
|
devpair = dev->pd_devpair;
|
|
|
|
/* Get exclusive access to the device structure */
|
|
|
|
ret = nxmutex_lock(&devpair->pp_lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Wait if this is an attempt to open the slave device and the slave
|
|
* device is locked.
|
|
*/
|
|
|
|
if (!dev->pd_master)
|
|
{
|
|
/* Slave... Check if the slave driver is locked. */
|
|
|
|
while (devpair->pp_locked)
|
|
{
|
|
/* Release the exclusive access before wait */
|
|
|
|
nxmutex_unlock(&devpair->pp_lock);
|
|
|
|
/* Wait until unlocked.
|
|
* We will also most certainly suspend here.
|
|
*/
|
|
|
|
ret = nxsem_wait(&devpair->pp_slavesem);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Restore the semaphore count */
|
|
|
|
DEBUGVERIFY(nxsem_post(&devpair->pp_slavesem));
|
|
|
|
/* Get exclusive access to the device structure. This might also
|
|
* cause suspension.
|
|
*/
|
|
|
|
ret = nxmutex_lock(&devpair->pp_lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* First open? */
|
|
|
|
if (devpair->pp_nopen == 0)
|
|
{
|
|
/* Yes, create the internal pipe */
|
|
|
|
ret = pty_pipe(devpair);
|
|
}
|
|
|
|
/* Increment the count of open references on the driver */
|
|
|
|
if (ret >= 0)
|
|
{
|
|
devpair->pp_nopen++;
|
|
DEBUGASSERT(devpair->pp_nopen > 0);
|
|
}
|
|
|
|
nxmutex_unlock(&devpair->pp_lock);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pty_close
|
|
****************************************************************************/
|
|
|
|
static int pty_close(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct pty_dev_s *dev;
|
|
FAR struct pty_devpair_s *devpair;
|
|
int ret;
|
|
|
|
inode = filep->f_inode;
|
|
dev = inode->i_private;
|
|
DEBUGASSERT(dev != NULL && dev->pd_devpair != NULL);
|
|
devpair = dev->pd_devpair;
|
|
|
|
/* Get exclusive access */
|
|
|
|
ret = nxmutex_lock(&devpair->pp_lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Check if the decremented inode reference count would go to zero */
|
|
|
|
if (atomic_load(&inode->i_crefs) == 1)
|
|
{
|
|
/* Did the (single) master just close its reference? */
|
|
|
|
if (dev->pd_master && devpair->pp_susv1)
|
|
{
|
|
/* Yes, then we are essentially unlinked and when all of the
|
|
* slaves close there references, then the PTY should be
|
|
* destroyed.
|
|
*/
|
|
|
|
devpair->pp_unlinked = true;
|
|
}
|
|
|
|
/* Close the contained file structures */
|
|
|
|
file_close(&dev->pd_src);
|
|
file_close(&dev->pd_sink);
|
|
}
|
|
|
|
/* Is this the last open reference? If so, was the driver previously
|
|
* unlinked?
|
|
*/
|
|
|
|
DEBUGASSERT(devpair->pp_nopen > 0);
|
|
if (devpair->pp_nopen <= 1 && devpair->pp_unlinked)
|
|
{
|
|
/* Yes.. Free the device pair now (without freeing the semaphore) */
|
|
|
|
nxmutex_unlock(&devpair->pp_lock);
|
|
pty_destroy(devpair);
|
|
return OK;
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise just decrement the open count */
|
|
|
|
devpair->pp_nopen--;
|
|
}
|
|
|
|
nxmutex_unlock(&devpair->pp_lock);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pty_read
|
|
****************************************************************************/
|
|
|
|
static ssize_t pty_read(FAR struct file *filep, FAR char *buffer, size_t len)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct pty_dev_s *dev;
|
|
ssize_t ntotal;
|
|
ssize_t i;
|
|
ssize_t j;
|
|
char ch;
|
|
|
|
inode = filep->f_inode;
|
|
dev = inode->i_private;
|
|
DEBUGASSERT(dev != NULL);
|
|
|
|
/* Do input processing if any is enabled
|
|
*
|
|
* Specifically not handled:
|
|
*
|
|
* All of the local modes; echo, line editing, etc.
|
|
* Anything to do with break or parity errors.
|
|
* ISTRIP - We should be 8-bit clean.
|
|
* IUCLC - Not Posix
|
|
* IXON/OXOFF - No xon/xoff flow control.
|
|
*/
|
|
|
|
if (dev->pd_iflag & (INLCR | IGNCR | ICRNL))
|
|
{
|
|
while ((ntotal = file_read(&dev->pd_src, buffer, len)) > 0)
|
|
{
|
|
for (i = j = 0; i < ntotal; i++)
|
|
{
|
|
/* Perform input processing */
|
|
|
|
ch = buffer[i];
|
|
|
|
/* \n -> \r or \r -> \n translation? */
|
|
|
|
if (ch == '\n' && (dev->pd_iflag & INLCR) != 0)
|
|
{
|
|
ch = '\r';
|
|
}
|
|
else if (ch == '\r' && (dev->pd_iflag & ICRNL) != 0)
|
|
{
|
|
ch = '\n';
|
|
}
|
|
|
|
/* Discarding \r ? Print character if (1) character is not \r
|
|
* or if (2) we were not asked to ignore \r.
|
|
*/
|
|
|
|
if (ch != '\r' || (dev->pd_iflag & IGNCR) == 0)
|
|
{
|
|
buffer[j++] = ch;
|
|
}
|
|
}
|
|
|
|
/* Is the buffer not empty after process? */
|
|
|
|
if (j != 0)
|
|
{
|
|
/* Yes, we are done. */
|
|
|
|
ntotal = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* NOTE: the source pipe will block if no data is available in
|
|
* the pipe. Otherwise, it will return data from the pipe. If
|
|
* there are fewer than 'len' bytes in the, it will return with
|
|
* ntotal < len.
|
|
*
|
|
* REVISIT: Should not block if the oflags include O_NONBLOCK.
|
|
* How would we ripple the O_NONBLOCK characteristic to the
|
|
* contained source pipe? file_fcntl()? Or FIONREAD? See the
|
|
* TODO comment at the top of this file.
|
|
*/
|
|
|
|
ntotal = file_read(&dev->pd_src, buffer, len);
|
|
}
|
|
|
|
if ((dev->pd_lflag & ECHO) && (ntotal > 0))
|
|
{
|
|
size_t n = 0;
|
|
|
|
for (i = j = 0; i < ntotal; i++)
|
|
{
|
|
ch = buffer[i];
|
|
|
|
/* Check for the beginning of a VT100 escape sequence, 3 byte */
|
|
|
|
if (ch == ASCII_ESC)
|
|
{
|
|
/* Mark that we should skip 2 more bytes */
|
|
|
|
dev->pd_escape = 2;
|
|
continue;
|
|
}
|
|
else if (dev->pd_escape == 2 && ch != ASCII_LBRACKET)
|
|
{
|
|
/* It's not an <esc>[x 3 byte sequence, show it */
|
|
|
|
dev->pd_escape = 0;
|
|
}
|
|
else if (dev->pd_escape > 0)
|
|
{
|
|
/* Skipping character count down */
|
|
|
|
if (--dev->pd_escape > 0)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Echo if the character in batch */
|
|
|
|
if (ch == '\n' || (n != 0 && j + n != i))
|
|
{
|
|
if (n != 0)
|
|
{
|
|
pty_write(filep, buffer + j, n);
|
|
n = 0;
|
|
}
|
|
|
|
if (ch == '\n')
|
|
{
|
|
pty_write(filep, "\r\n", 2);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Record the character can be echo */
|
|
|
|
if (!iscntrl(ch & 0xff) && n++ == 0)
|
|
{
|
|
j = i;
|
|
}
|
|
}
|
|
|
|
if (n != 0)
|
|
{
|
|
pty_write(filep, buffer + j, n);
|
|
}
|
|
}
|
|
|
|
return ntotal;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pty_write
|
|
****************************************************************************/
|
|
|
|
static ssize_t pty_write(FAR struct file *filep,
|
|
FAR const char *buffer, size_t len)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct pty_dev_s *dev;
|
|
ssize_t ntotal;
|
|
ssize_t nwritten;
|
|
#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP)
|
|
pid_t pid;
|
|
#endif
|
|
size_t i;
|
|
char ch;
|
|
|
|
inode = filep->f_inode;
|
|
dev = inode->i_private;
|
|
DEBUGASSERT(dev != NULL);
|
|
|
|
#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP)
|
|
pid = dev->pd_devpair->pp_master.pd_pid;
|
|
if (dev->pd_master)
|
|
{
|
|
pid = dev->pd_devpair->pp_slave.pd_pid;
|
|
}
|
|
#endif
|
|
|
|
/* Do output post-processing */
|
|
|
|
if ((dev->pd_oflag & OPOST) != 0)
|
|
{
|
|
/* We will transfer one byte at a time, making the appropriae
|
|
* translations. Specifically not handled:
|
|
*
|
|
* OXTABS - primarily a full-screen terminal optimisation
|
|
* ONOEOT - Unix interoperability hack
|
|
* OLCUC - Not specified by POSIX
|
|
* ONOCR - low-speed interactive optimisation
|
|
*/
|
|
|
|
ntotal = 0;
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
ch = *buffer++;
|
|
|
|
/* Mapping CR to NL? */
|
|
|
|
if (ch == '\r' && (dev->pd_oflag & OCRNL) != 0)
|
|
{
|
|
ch = '\n';
|
|
}
|
|
|
|
/* Are we interested in newline processing? */
|
|
|
|
if ((ch == '\n') && (dev->pd_oflag & (ONLCR | ONLRET)) != 0)
|
|
{
|
|
char cr = '\r';
|
|
|
|
/* Transfer the carriage return. This will block if the
|
|
* sink pipe is full.
|
|
*
|
|
* REVISIT: Should not block if the oflags include O_NONBLOCK.
|
|
* How would we ripple the O_NONBLOCK characteristic to the
|
|
* contained sink pipe? file_fcntl()? Or FIONSPACE? See the
|
|
* TODO comment at the top of this file.
|
|
*
|
|
* NOTE: The newline is not included in total number of bytes
|
|
* written. Otherwise, we would return more than the
|
|
* requested number of bytes.
|
|
*/
|
|
|
|
nwritten = file_write(&dev->pd_sink, &cr, 1);
|
|
if (nwritten < 0)
|
|
{
|
|
ntotal = nwritten;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_TTY_SIGINT
|
|
if (pid > 0 && ch == CONFIG_TTY_SIGINT_CHAR)
|
|
{
|
|
nxsig_kill(pid, SIGINT);
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_TTY_SIGTSTP
|
|
if (pid > 0 && ch == CONFIG_TTY_SIGTSTP_CHAR)
|
|
{
|
|
nxsig_kill(pid, SIGTSTP);
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
/* Transfer the (possibly translated) character.. This will block
|
|
* if the sink pipe is full
|
|
*
|
|
* REVISIT: Should not block if the oflags include O_NONBLOCK.
|
|
* How would we ripple the O_NONBLOCK characteristic to the
|
|
* contained sink pipe? file_fcntl()? Or FIONSPACe? See the
|
|
* TODO comment at the top of this file.
|
|
*/
|
|
|
|
nwritten = file_write(&dev->pd_sink, &ch, 1);
|
|
if (nwritten < 0)
|
|
{
|
|
ntotal = nwritten;
|
|
break;
|
|
}
|
|
|
|
/* Update the count of bytes transferred */
|
|
|
|
ntotal++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Write the 'len' bytes to the sink pipe. This will block until all
|
|
* 'len' bytes have been written to the pipe.
|
|
*
|
|
* REVISIT: Should not block if the oflags include O_NONBLOCK.
|
|
* How would we ripple the O_NONBLOCK characteristic to the
|
|
* contained sink pipe? file_fcntl()? Or FIONSPACE? See the
|
|
* TODO comment at the top of this file.
|
|
*/
|
|
|
|
ntotal = file_write(&dev->pd_sink, buffer, len);
|
|
}
|
|
|
|
return ntotal;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pty_ioctl
|
|
*
|
|
* Description:
|
|
* The standard ioctl method. This is where ALL of the PWM work is done.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int pty_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct pty_dev_s *dev;
|
|
FAR struct pty_devpair_s *devpair;
|
|
int ret;
|
|
|
|
inode = filep->f_inode;
|
|
dev = inode->i_private;
|
|
DEBUGASSERT(dev != NULL && dev->pd_devpair != NULL);
|
|
devpair = dev->pd_devpair;
|
|
|
|
/* Get exclusive access */
|
|
|
|
ret = nxmutex_lock(&devpair->pp_lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Handle IOCTL commands */
|
|
|
|
switch (cmd)
|
|
{
|
|
/* PTY IOCTL commands would be handled here */
|
|
|
|
case TIOCGPTN: /* Get Pty Number (of pty-mux device): FAR int* */
|
|
{
|
|
FAR int *ptyno = (FAR int *)((uintptr_t)arg);
|
|
if (ptyno == NULL)
|
|
{
|
|
ret = -EINVAL;
|
|
}
|
|
else
|
|
{
|
|
*ptyno = devpair->pp_minor;
|
|
ret = OK;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TIOCSPTLCK: /* Lock/unlock Pty: int */
|
|
{
|
|
if (arg == 0)
|
|
{
|
|
if (devpair->pp_locked)
|
|
{
|
|
/* Release any waiting threads */
|
|
|
|
ret = nxsem_post(&devpair->pp_slavesem);
|
|
if (ret >= 0)
|
|
{
|
|
devpair->pp_locked = false;
|
|
}
|
|
}
|
|
}
|
|
else if (!devpair->pp_locked)
|
|
{
|
|
/* Locking */
|
|
|
|
ret = nxsem_wait(&devpair->pp_slavesem);
|
|
if (ret >= 0)
|
|
{
|
|
devpair->pp_locked = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TIOCGPTLCK: /* Get Pty lock state: FAR int* */
|
|
{
|
|
FAR int *ptr = (FAR int *)((uintptr_t)arg);
|
|
if (ptr == NULL)
|
|
{
|
|
ret = -EINVAL;
|
|
}
|
|
else
|
|
{
|
|
*ptr = devpair->pp_locked;
|
|
ret = OK;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TCGETS:
|
|
{
|
|
FAR struct termios *termiosp = (FAR struct termios *)arg;
|
|
|
|
if (!termiosp)
|
|
{
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
/* And update with flags from this layer */
|
|
|
|
termiosp->c_iflag = dev->pd_iflag;
|
|
termiosp->c_oflag = dev->pd_oflag;
|
|
termiosp->c_lflag = dev->pd_lflag;
|
|
ret = OK;
|
|
}
|
|
break;
|
|
|
|
case TCSETS:
|
|
{
|
|
FAR struct termios *termiosp = (FAR struct termios *)arg;
|
|
|
|
if (!termiosp)
|
|
{
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
/* Update the flags we keep at this layer */
|
|
|
|
dev->pd_iflag = termiosp->c_iflag;
|
|
dev->pd_oflag = termiosp->c_oflag;
|
|
dev->pd_lflag = termiosp->c_lflag;
|
|
ret = OK;
|
|
}
|
|
break;
|
|
|
|
/* Get the number of bytes that are immediately available for reading
|
|
* from the source pipe.
|
|
*/
|
|
|
|
case FIONREAD:
|
|
{
|
|
ret = file_ioctl(&dev->pd_src, cmd, arg);
|
|
}
|
|
break;
|
|
|
|
/* Get the number of bytes waiting in the sink pipe (FIONWRITE) or the
|
|
* number of unused bytes in the sink pipe (FIONSPACE).
|
|
*/
|
|
|
|
case FIONWRITE:
|
|
case FIONSPACE:
|
|
{
|
|
ret = file_ioctl(&dev->pd_sink, cmd, arg);
|
|
}
|
|
break;
|
|
|
|
case FIONBIO:
|
|
{
|
|
ret = file_ioctl(&dev->pd_src, cmd, arg);
|
|
if (ret >= 0)
|
|
{
|
|
ret = file_ioctl(&dev->pd_sink, cmd, arg);
|
|
}
|
|
}
|
|
break;
|
|
|
|
#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP)
|
|
/* Make the controlling terminal of the calling process */
|
|
|
|
case TIOCSCTTY:
|
|
{
|
|
/* Save the PID of the recipient of the SIGINT signal. */
|
|
|
|
if ((int)arg < 0 || dev->pd_pid >= 0)
|
|
{
|
|
ret = -EINVAL;
|
|
}
|
|
else
|
|
{
|
|
dev->pd_pid = (pid_t)arg;
|
|
ret = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TIOCNOTTY:
|
|
{
|
|
dev->pd_pid = INVALID_PROCESS_ID;
|
|
ret = 0;
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
/* Any unrecognized IOCTL commands will be passed to the contained
|
|
* pipe driver.
|
|
*
|
|
* REVISIT: We know for a fact that the pipe driver only supports
|
|
* FIONREAD, FIONWRITE, FIONSPACE and PIPEIOC_POLICY. The first two
|
|
* are handled above and PIPEIOC_POLICY should not be managed by
|
|
* applications -- it can break the PTY!
|
|
*/
|
|
|
|
default:
|
|
{
|
|
#if 0
|
|
ret = file_ioctl(&dev->pd_src, cmd, arg);
|
|
if (ret >= 0 || ret == -ENOTTY)
|
|
{
|
|
ret = file_ioctl(&dev->pd_sink, cmd, arg);
|
|
}
|
|
#else
|
|
ret = -ENOTTY;
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
|
|
nxmutex_unlock(&devpair->pp_lock);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pty_poll
|
|
****************************************************************************/
|
|
|
|
static int pty_poll(FAR struct file *filep, FAR struct pollfd *fds,
|
|
bool setup)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct pty_dev_s *dev;
|
|
FAR struct pty_devpair_s *devpair;
|
|
FAR struct pty_poll_s *pollp = NULL;
|
|
int ret;
|
|
int i;
|
|
|
|
inode = filep->f_inode;
|
|
dev = inode->i_private;
|
|
devpair = dev->pd_devpair;
|
|
|
|
ret = nxmutex_lock(&devpair->pp_lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if (setup)
|
|
{
|
|
for (i = 0; i < CONFIG_DEV_PTY_NPOLLWAITERS; i++)
|
|
{
|
|
if (dev->pd_poll[i].src == NULL && dev->pd_poll[i].sink == NULL)
|
|
{
|
|
pollp = &dev->pd_poll[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i >= CONFIG_DEV_PTY_NPOLLWAITERS)
|
|
{
|
|
ret = -EBUSY;
|
|
goto errout;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pollp = (FAR struct pty_poll_s *)fds->priv;
|
|
}
|
|
|
|
/* POLLIN: Data may be read without blocking. */
|
|
|
|
if ((fds->events & POLLIN) != 0)
|
|
{
|
|
fds->priv = pollp->src;
|
|
ret = file_poll(&dev->pd_src, fds, setup);
|
|
if (ret < 0)
|
|
{
|
|
goto errout;
|
|
}
|
|
|
|
pollp->src = fds->priv;
|
|
}
|
|
|
|
/* POLLOUT: Normal data may be written without blocking. */
|
|
|
|
if ((fds->events & POLLOUT) != 0)
|
|
{
|
|
fds->priv = pollp->sink;
|
|
ret = file_poll(&dev->pd_sink, fds, setup);
|
|
if (ret < 0)
|
|
{
|
|
if (pollp->src)
|
|
{
|
|
fds->priv = pollp->src;
|
|
file_poll(&dev->pd_src, fds, false);
|
|
pollp->src = NULL;
|
|
}
|
|
|
|
goto errout;
|
|
}
|
|
|
|
pollp->sink = fds->priv;
|
|
}
|
|
|
|
if (setup)
|
|
{
|
|
fds->priv = pollp;
|
|
}
|
|
|
|
errout:
|
|
nxmutex_unlock(&devpair->pp_lock);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pty_unlink
|
|
****************************************************************************/
|
|
|
|
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
|
|
static int pty_unlink(FAR struct inode *inode)
|
|
{
|
|
FAR struct pty_dev_s *dev;
|
|
FAR struct pty_devpair_s *devpair;
|
|
int ret;
|
|
|
|
DEBUGASSERT(inode->i_private != NULL);
|
|
dev = inode->i_private;
|
|
devpair = dev->pd_devpair;
|
|
DEBUGASSERT(dev->pd_devpair != NULL);
|
|
|
|
/* Get exclusive access */
|
|
|
|
ret = nxmutex_lock(&devpair->pp_lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Indicate that the driver has been unlinked */
|
|
|
|
devpair->pp_unlinked = true;
|
|
|
|
/* If there are no further open references to the driver, then commit
|
|
* Hara-Kiri now.
|
|
*/
|
|
|
|
if (devpair->pp_nopen == 0)
|
|
{
|
|
nxmutex_unlock(&devpair->pp_lock);
|
|
pty_destroy(devpair);
|
|
return OK;
|
|
}
|
|
|
|
nxmutex_unlock(&devpair->pp_lock);
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: pty_register2
|
|
*
|
|
* Description:
|
|
* Create and register PTY master and slave devices. The slave side of
|
|
* the interface is always locked initially. The master must call
|
|
* unlockpt() before the slave device can be opened.
|
|
*
|
|
* Input Parameters:
|
|
* minor - The number that qualifies the naming of the created devices.
|
|
* susv1 - select SUSv1 or BSD behaviour
|
|
*
|
|
* Returned Value:
|
|
* 0 is returned on success; otherwise, the negative error code return
|
|
* appropriately.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int pty_register2(int minor, bool susv1)
|
|
{
|
|
FAR struct pty_devpair_s *devpair;
|
|
char devname[16];
|
|
int ret;
|
|
|
|
/* Allocate a device instance */
|
|
|
|
devpair = kmm_zalloc(sizeof(struct pty_devpair_s));
|
|
if (devpair == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Initialize semaphores & mutex */
|
|
|
|
nxsem_init(&devpair->pp_slavesem, 0, 0);
|
|
nxmutex_init(&devpair->pp_lock);
|
|
|
|
/* Map CR -> NL from terminal input (master)
|
|
* For some usage like adb shell:
|
|
* adb shell write \r -> nsh read \n and echo input
|
|
* nsh write \n -> adb shell read \r\n
|
|
*/
|
|
|
|
devpair->pp_susv1 = susv1;
|
|
devpair->pp_minor = minor;
|
|
devpair->pp_locked = true;
|
|
devpair->pp_master.pd_devpair = devpair;
|
|
devpair->pp_master.pd_master = true;
|
|
devpair->pp_master.pd_oflag = OPOST | OCRNL;
|
|
devpair->pp_slave.pd_devpair = devpair;
|
|
devpair->pp_slave.pd_oflag = OPOST | ONLCR;
|
|
devpair->pp_slave.pd_lflag = ECHO | ICANON;
|
|
#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP)
|
|
/* Initialize of the task that will receive SIGINT signals. */
|
|
|
|
devpair->pp_master.pd_pid = INVALID_PROCESS_ID;
|
|
devpair->pp_slave.pd_pid = INVALID_PROCESS_ID;
|
|
#endif
|
|
|
|
/* Register the master device
|
|
*
|
|
* BSD style (deprecated): /dev/ptyN
|
|
* SUSv1 style: Master: /dev/ptmx (multiplexor, see ptmx.c)
|
|
*
|
|
* Where N is the minor number
|
|
*/
|
|
|
|
snprintf(devname, sizeof(devname), "/dev/pty%d", minor);
|
|
|
|
ret = register_driver(devname, &g_pty_fops, 0666, &devpair->pp_master);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_devpair;
|
|
}
|
|
|
|
/* Register the slave device
|
|
*
|
|
* BSD style (deprecated): /dev/ttypN
|
|
* SUSv1 style: /dev/pts/N
|
|
*
|
|
* Where N is the minor number
|
|
*/
|
|
|
|
if (susv1)
|
|
{
|
|
snprintf(devname, sizeof(devname), "/dev/pts/%d", minor);
|
|
}
|
|
else
|
|
{
|
|
snprintf(devname, sizeof(devname), "/dev/ttyp%d", minor);
|
|
}
|
|
|
|
ret = register_driver(devname, &g_pty_fops, 0666, &devpair->pp_slave);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_with_master;
|
|
}
|
|
|
|
return OK;
|
|
|
|
errout_with_master:
|
|
snprintf(devname, sizeof(devname), "/dev/pty%d", minor);
|
|
unregister_driver(devname);
|
|
|
|
errout_with_devpair:
|
|
nxmutex_destroy(&devpair->pp_lock);
|
|
nxsem_destroy(&devpair->pp_slavesem);
|
|
kmm_free(devpair);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pty_register
|
|
*
|
|
* Description:
|
|
* Create and register PTY master and slave devices. The master device
|
|
* will be registered at /dev/ptyN and slave at /dev/ttypN where N is
|
|
* the provided minor number.
|
|
*
|
|
* The slave side of the interface is always locked initially. The
|
|
* master must call unlockpt() before the slave device can be opened.
|
|
*
|
|
* Input Parameters:
|
|
* minor - The number that qualifies the naming of the created devices.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success; a negated errno value is returned on
|
|
* any failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int pty_register(int minor)
|
|
{
|
|
return pty_register2(minor, false);
|
|
}
|