drivers/net/telnet.c: Add NAWS and poll interface to telnet for screen size negotation used with termcurses.

arch/arm/src/samv7/sam_serial.c: Fix case where TTYS0 is not defined.
This commit is contained in:
Ken Pettit 2019-01-05 12:14:05 -06:00 committed by Gregory Nutt
parent 7127df3e38
commit a97c4d8644
3 changed files with 259 additions and 47 deletions

View File

@ -1409,7 +1409,9 @@ void up_earlyserialinit(void)
/* Disable all USARTS */
#ifdef TTYS0_DEV
sam_disableallints(TTYS0_DEV.priv, NULL);
#endif
#ifdef TTYS1_DEV
sam_disableallints(TTYS1_DEV.priv, NULL);
#endif
@ -1460,7 +1462,9 @@ void up_serialinit(void)
/* Register all USARTs */
#ifdef TTYS0_DEV
(void)uart_register("/dev/ttyS0", &TTYS0_DEV);
#endif
#ifdef TTYS1_DEV
(void)uart_register("/dev/ttyS1", &TTYS1_DEV);
#endif

View File

@ -49,6 +49,10 @@ config TELNET_CHARACTER_MODE
will echo a character which telnet client sent. By default, it works
in line mode.
config TELNET_SUPPORT_NAWS
bool "Support NAWS (Negotiate About Window Size)"
default n
endif # NETDEV_TELNET
config ARCH_HAVE_NETDEV_STATISTICS

View File

@ -1,7 +1,8 @@
/****************************************************************************
* drivers/net/telnet.c
*
* Copyright (C) 2007, 2009, 2011-2013, 2017 Gregory Nutt. All rights reserved.
* Copyright (C) 2007, 2009, 2011-2013, 2017, 2019 Gregory Nutt. All
* rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* This is a leverage of similar logic from uIP which has a compatible BSD
@ -20,9 +21,9 @@
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute, NuttX nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* 3. Neither the name of the Institute, NuttX nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
@ -69,6 +70,7 @@
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Configuration ************************************************************/
#ifndef CONFIG_TELNET_RXBUFFER_SIZE
@ -81,17 +83,30 @@
/* Telnet protocol stuff ****************************************************/
#define ISO_nl 0x0a
#define ISO_cr 0x0d
#define ISO_nl 0x0a
#define ISO_cr 0x0d
#define TELNET_SGA 0x03 /* Suppress Go Ahead */
#define TELNET_ECHO 0x01
/* Telnet commands */
#define TELNET_IAC 255
#define TELNET_WILL 251
#define TELNET_WONT 252
#define TELNET_DO 253
#define TELNET_DONT 254
#define TELNET_ECHO 1
#define TELNET_SGA 3 /* Suppress Go Ahead */
#define TELNET_NAWS 31 /* Negotiate about window size */
/* Telnet control */
#define TELNET_IAC 255
#define TELNET_WILL 251
#define TELNET_WONT 252
#define TELNET_DO 253
#define TELNET_DONT 254
#define TELNET_SB 250
#define TELNET_SE 240
/* Linemode sub options */
#define TELNET_LM_MODE 1
#define TELNET_LM_FORWARDMASK 2
#define TELNET_LM_SLC 3
/* Device stuff *************************************************************/
@ -100,6 +115,7 @@
/****************************************************************************
* Private Types
****************************************************************************/
/* The state of the telnet parser */
enum telnet_state_e
@ -109,37 +125,46 @@ enum telnet_state_e
STATE_WILL,
STATE_WONT,
STATE_DO,
STATE_DONT
STATE_DONT,
STATE_SB,
STATE_SB_NAWS,
STATE_SE
};
/* This structure describes the internal state of the driver */
struct telnet_dev_s
{
sem_t td_exclsem; /* Enforces mutually exclusive access */
uint8_t td_state; /* (See telnet_state_e) */
uint8_t td_pending; /* Number of valid, pending bytes in the rxbuffer */
uint8_t td_offset; /* Offset to the valid, pending bytes in the rxbuffer */
uint8_t td_crefs; /* The number of open references to the session */
int td_minor; /* Minor device number */
FAR struct socket td_psock; /* A clone of the internal socket structure */
sem_t td_exclsem; /* Enforces mutually exclusive access */
uint8_t td_state; /* (See telnet_state_e) */
uint8_t td_pending; /* Number of valid, pending bytes in the rxbuffer */
uint8_t td_offset; /* Offset to the valid, pending bytes in the rxbuffer */
uint8_t td_crefs; /* The number of open references to the session */
int td_minor; /* Minor device number */
#ifdef CONFIG_TELNET_SUPPORT_NAWS
uint16_t td_rows; /* Number of NAWS rows */
uint16_t td_cols; /* Number of NAWS cols */
int td_sb_count; /* Count of TELNET_SB bytes received */
#endif
FAR struct socket td_psock; /* A clone of the internal socket structure */
char td_rxbuffer[CONFIG_TELNET_RXBUFFER_SIZE];
char td_txbuffer[CONFIG_TELNET_TXBUFFER_SIZE];
};
/* This structure contains global information visable to all telnet driver
/* This structure contains global information visible to all telnet driver
* instances.
*/
struct telnet_common_s
{
sem_t tc_exclsem; /* Enforces exclusive access to 'minor' */
uint16_t tc_minor; /* The next minor number to use */
sem_t tc_exclsem; /* Enforces exclusive access to 'minor' */
uint16_t tc_minor; /* The next minor number to use */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Support functions */
#ifdef CONFIG_TELNET_DUMPBUFFER
@ -162,8 +187,14 @@ static void telnet_sendopt(FAR struct telnet_dev_s *priv, uint8_t option,
static int telnet_open(FAR struct file *filep);
static int telnet_close(FAR struct file *filep);
static ssize_t telnet_read(FAR struct file *, FAR char *, size_t);
static ssize_t telnet_write(FAR struct file *, FAR const char *, size_t);
static ssize_t telnet_read(FAR struct file *filep, FAR char *buffer,
size_t len);
static ssize_t telnet_write(FAR struct file *filep, FAR const char *buffer,
size_t len);
#ifndef CONFIG_DISABLE_POLL
static int telnet_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup);
#endif
/* Telnet session creation */
@ -191,7 +222,7 @@ static const struct file_operations g_telnet_fops =
0, /* seek */
common_ioctl /* ioctl */
#ifndef CONFIG_DISABLE_POLL
, 0 /* poll */
, telnet_poll /* poll */
#endif
};
@ -204,7 +235,7 @@ static const struct file_operations g_factory_fops =
0, /* seek */
common_ioctl /* ioctl */
#ifndef CONFIG_DISABLE_POLL
, 0 /* poll */
, telnet_poll /* poll */
#endif
};
@ -234,10 +265,10 @@ static inline void telnet_dumpbuffer(FAR const char *msg,
unsigned int nbytes)
{
/* CONFIG_DEBUG_FEATURES, CONFIG_DEBUG_INFO, and CONFIG_DEBUG_NET have to be
* defined or the following does nothing.
*/
* defined or the following does nothing.
*/
ninfodumpbuffer(msg, (FAR const uint8_t*)buffer, nbytes);
ninfodumpbuffer(msg, (FAR const uint8_t *)buffer, nbytes);
}
#endif
@ -276,8 +307,9 @@ static void telnet_getchar(FAR struct telnet_dev_s *priv, uint8_t ch,
*
****************************************************************************/
static ssize_t telnet_receive(FAR struct telnet_dev_s *priv, FAR const char *src,
size_t srclen, FAR char *dest, size_t destlen)
static ssize_t telnet_receive(FAR struct telnet_dev_s *priv,
FAR const char *src, size_t srclen,
FAR char *dest, size_t destlen)
{
int nread;
uint8_t ch;
@ -317,6 +349,17 @@ static ssize_t telnet_receive(FAR struct telnet_dev_s *priv, FAR const char *src
priv->td_state = STATE_DONT;
break;
#ifdef CONFIG_TELNET_SUPPORT_NAWS
case TELNET_SB:
priv->td_state = STATE_SB;
priv->td_sb_count = 0;
break;
case TELNET_SE:
priv->td_state = STATE_NORMAL;
break;
#endif
default:
priv->td_state = STATE_NORMAL;
break;
@ -325,15 +368,27 @@ static ssize_t telnet_receive(FAR struct telnet_dev_s *priv, FAR const char *src
break;
case STATE_WILL:
#ifdef CONFIG_TELNET_SUPPORT_NAWS
/* For NAWS, Reply with a DO */
if (ch == TELNET_NAWS)
{
telnet_sendopt(priv, TELNET_DO, ch);
}
/* Reply with a DONT */
telnet_sendopt(priv, TELNET_DONT, ch);
else
#endif
{
telnet_sendopt(priv, TELNET_DONT, ch);
ninfo("Suppress: 0x%02X (%d)\n", ch, ch);
}
priv->td_state = STATE_NORMAL;
break;
case STATE_WONT:
/* Reply with a DONT */
telnet_sendopt(priv, TELNET_DONT, ch);
priv->td_state = STATE_NORMAL;
break;
@ -359,6 +414,7 @@ static ssize_t telnet_receive(FAR struct telnet_dev_s *priv, FAR const char *src
/* Reply with a WONT */
telnet_sendopt(priv, TELNET_WONT, ch);
ninfo("WONT: 0x%02X\n", ch);
}
#else
/* Reply with a WONT */
@ -385,6 +441,57 @@ static ssize_t telnet_receive(FAR struct telnet_dev_s *priv, FAR const char *src
telnet_getchar(priv, ch, dest, &nread);
}
break;
#ifdef CONFIG_TELNET_SUPPORT_NAWS
/* Handle Telnet Sub negotation request */
case STATE_SB:
switch (ch)
{
case TELNET_NAWS:
priv->td_state = STATE_SB_NAWS;
break;
default:
priv->td_state = STATE_NORMAL;
break;
}
break;
/* Handle NAWS sub-option negotiation */
case STATE_SB_NAWS:
/* Update cols / rows based on received byte count */
switch (priv->td_sb_count)
{
case 0:
priv->td_cols = (priv->td_cols & 0x00FF) | (ch << 8);
break;
case 1:
priv->td_cols = (priv->td_cols & 0xFF00) | ch;
break;
case 2:
priv->td_rows = (priv->td_rows & 0x00FF) | (ch << 8);
break;
case 3:
priv->td_rows = (priv->td_rows & 0xFF00) | ch;
ninfo("NAWS: %d,%d", priv->td_cols, priv->td_rows);
break;
}
/* Increment SB count and switch to NORMAL when complete */
if (++priv->td_sb_count == 4)
{
priv->td_state = STATE_NORMAL;
}
break;
#endif
}
}
@ -612,6 +719,13 @@ static int telnet_close(FAR struct file *filep)
psock_close(&priv->td_psock);
#ifdef CONFIG_TERMCURSES
if (priv->tcurs != NULL)
{
free(priv->tcurs);
}
#endif
/* Release the driver memory. What if there are threads waiting on
* td_exclsem? They will never be awakened! How could this happen?
* crefs == 1 so there are no other open references to the driver.
@ -636,7 +750,8 @@ errout:
* Name: telnet_read
****************************************************************************/
static ssize_t telnet_read(FAR struct file *filep, FAR char *buffer, size_t len)
static ssize_t telnet_read(FAR struct file *filep, FAR char *buffer,
size_t len)
{
FAR struct inode *inode = filep->f_inode;
FAR struct telnet_dev_s *priv = inode->i_private;
@ -665,8 +780,15 @@ static ssize_t telnet_read(FAR struct file *filep, FAR char *buffer, size_t len)
else
{
/* Test for non-blocking read */
if (filep->f_oflags & O_NONBLOCK)
{
return 0;
}
ret = psock_recv(&priv->td_psock, priv->td_rxbuffer,
CONFIG_TELNET_RXBUFFER_SIZE, 0);
CONFIG_TELNET_RXBUFFER_SIZE, 0);
/* Did we receive anything? */
@ -704,7 +826,8 @@ static ssize_t telnet_read(FAR struct file *filep, FAR char *buffer, size_t len)
* Name: telnet_write
****************************************************************************/
static ssize_t telnet_write(FAR struct file *filep, FAR const char *buffer, size_t len)
static ssize_t telnet_write(FAR struct file *filep, FAR const char *buffer,
size_t len)
{
FAR struct inode *inode = filep->f_inode;
FAR struct telnet_dev_s *priv = inode->i_private;
@ -740,7 +863,8 @@ static ssize_t telnet_write(FAR struct file *filep, FAR const char *buffer, size
ret = psock_send(&priv->td_psock, priv->td_txbuffer, ncopied, 0);
if (ret < 0)
{
nerr("ERROR: psock_send failed '%s': %d\n", priv->td_txbuffer, ret);
nerr("ERROR: psock_send failed '%s': %d\n",
priv->td_txbuffer, ret);
return ret;
}
@ -781,7 +905,8 @@ static ssize_t telnet_write(FAR struct file *filep, FAR const char *buffer, size
*
* Input Parameters:
* session - On input, contains the socket descriptor that represents the
* new telnet connection. On output, it holds the path to the new Telnet driver.
* new telnet connection. On output, it holds the path to the new Telnet
* driver.
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
@ -798,7 +923,7 @@ static int telnet_session(FAR struct telnet_session_s *session)
/* Allocate instance data for this driver */
priv = (FAR struct telnet_dev_s*)malloc(sizeof(struct telnet_dev_s));
priv = (FAR struct telnet_dev_s *)malloc(sizeof(struct telnet_dev_s));
if (!priv)
{
nerr("ERROR: Failed to allocate the driver data structure\n");
@ -809,10 +934,18 @@ static int telnet_session(FAR struct telnet_session_s *session)
nxsem_init(&priv->td_exclsem, 0, 1);
priv->td_state = STATE_NORMAL;
priv->td_crefs = 0;
priv->td_pending = 0;
priv->td_offset = 0;
priv->td_state = STATE_NORMAL;
priv->td_crefs = 0;
priv->td_pending = 0;
priv->td_offset = 0;
#ifdef CONFIG_TELNET_SUPPORT_NAWS
priv->td_rows = 25;
priv->td_cols = 80;
priv->td_sb_count = 0;
#endif
#ifdef CONFIG_TERMCURSES
priv->tcurs = NULL;
#endif
/* Clone the internal socket structure. We do this so that it will be
* independent of threads and of socket descriptors (the original socket
@ -822,7 +955,8 @@ static int telnet_session(FAR struct telnet_session_s *session)
psock = sockfd_socket(session->ts_sd);
if (!psock)
{
nerr("ERROR: Failed to convert sd=%d to a socket structure\n", session->ts_sd);
nerr("ERROR: Failed to convert sd=%d to a socket structure\n",
session->ts_sd);
ret = -EINVAL;
goto errout_with_dev;
}
@ -888,6 +1022,8 @@ static int telnet_session(FAR struct telnet_session_s *session)
psock_close(psock);
telnet_sendopt(priv, TELNET_DO, TELNET_NAWS);
/* Return the path to the new telnet driver */
nxsem_post(&g_telnet_common.tc_exclsem);
@ -924,12 +1060,65 @@ static ssize_t factory_write(FAR struct file *filep, FAR const char *buffer,
return len; /* Say that everything was written */
}
/****************************************************************************
* Name: telnet_poll
*
* Description:
* The standard poll() operation redirects operations on socket descriptors
* to this function.
*
* Input Parameters:
* fd - The socket descriptor of interest
* fds - The structure describing the events to be monitored, OR NULL if
* this is a request to stop monitoring events.
* setup - true: Setup up the poll; false: Teardown the poll
*
* Returned Value:
* 0: Success; Negated errno on failure
*
****************************************************************************/
static int telnet_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup)
{
FAR struct inode *inode = filep->f_inode;
FAR struct telnet_dev_s *priv = inode->i_private;
FAR struct socket *psock;
DEBUGASSERT(fds != NULL);
/* Get the underlying socket structure and verify that the sockfd
* corresponds to valid, allocated socket
*/
psock = &priv->td_psock;
if (!psock || psock->s_crefs <= 0)
{
return -EBADF;
}
/* Test if we have cached data waiting to be read */
if (priv->td_pending > 0)
{
/* Yes.. then signal the poll logic */
nxsem_post(fds->sem);
}
/* Then let psock_poll() do the heavy lifting */
return psock_poll(psock, fds, setup);
}
/****************************************************************************
* Name: common_ioctl
****************************************************************************/
static int common_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
FAR struct inode *inode = filep->f_inode;
FAR struct telnet_dev_s *priv = inode->i_private;
int ret;
switch (cmd)
@ -957,6 +1146,21 @@ static int common_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
}
break;
#ifdef CONFIG_TELNET_SUPPORT_NAWS
case TIOCGWINSZ:
{
FAR struct winsize *pW = (FAR struct winsize *)((uintptr_t)arg);
/* Get row/col from the private data */
pW->ws_row = priv->td_rows;
pW->ws_col = priv->td_cols;
ret = OK;
}
break;
#endif
default:
ret = -ENOTTY;
break;