1
0
mirror of https://github.com/UzixLS/picocom.git synced 2025-07-19 07:21:18 +03:00

Moved native tty term_ops to ttylocal.c

Also, for better separation, the modem_XXX operations now communicate
using term.[ch] pin names (MCTL_xxx), not system pin names (TIOCM_xxx).
This commit is contained in:
Nick Patavalis
2018-03-01 17:51:34 +02:00
parent 3e1a610acb
commit cbcf13133d
4 changed files with 360 additions and 299 deletions

View File

@ -48,18 +48,20 @@ linenoise-1.0/linenoise.o : linenoise-1.0/linenoise.c linenoise-1.0/linenoise.h
## Comment these in to enable RFC2217 support
#CPPFLAGS += -DUSE_RFC2217
#OBJS += tn2217.o
#tn2217.o : tn2217.c tn2217.h tncomport.h fdio.h termint.h
#tn2217.o : tn2217.c tn2217.h tncomport.h fdio.h termint.h term.h
## Comment this IN to remove help strings (saves ~ 4-6 Kb).
#CPPFLAGS += -DNO_HELP
OBJS += picocom.o term.o fdio.o split.o custbaud.o termios2.o custbaud_bsd.o
OBJS += picocom.o term.o ttylocal.o fdio.o split.o \
custbaud.o termios2.o custbaud_bsd.o
picocom : $(OBJS)
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)
picocom.o : picocom.c term.h fdio.h split.h custbaud.h tn2217.h
term.o : term.c term.h termint.h termios2.h custbaud_bsd.h custbaud.h
ttylocal.o : ttylocal.c term.h termint.h termios2.h custbaud_bsd.h custbaud.h
split.o : split.c split.h
fdio.o : fdio.c fdio.h
termios2.o : termios2.c termios2.h termbits2.h custbaud.h

280
term.c
View File

@ -56,26 +56,11 @@
#define CMSPAR 0
#endif
/* On these systems, use the TIOCM[BIS|BIC|GET] ioctls to manipulate
* the modem control lines (DTR / RTS) */
#if defined(__linux__) || \
defined(__FreeBSD__) || defined(__OpenBSD__) || \
defined(__NetBSD__) || defined(__DragonFly__) || \
defined(__APPLE__)
#define USE_IOCTL
#endif
#ifdef USE_IOCTL
#include <sys/ioctl.h>
#endif
#include "custbaud.h"
#ifdef USE_CUSTOM_BAUD
#include CUSTOM_BAUD_HEAD
#endif
/* Time to wait for UART to clear after a drain (in usec). */
#define DRAIN_DELAY 200000
#include "term.h"
#include "termint.h"
@ -85,223 +70,8 @@ static int term_initted;
static struct term_s term[MAX_TERMS];
/***************************************************************************/
static int
local_init(struct term_s *t)
{
int rval = 0;
const char *n;
do { /* dummy */
if ( ! isatty(t->fd) ) {
term_errno = TERM_EISATTY;
rval = -1;
break;
}
if ( ! t->name ) {
n = ttyname(t->fd);
if ( n ) t->name = strdup(n);
}
} while (0);
return rval;
}
static int
local_tcgetattr(struct term_s *t, struct termios *termios_out)
{
int r;
r = tcgetattr(t->fd, termios_out);
if ( r < 0 ) term_errno = TERM_EGETATTR;
return r;
}
static int
local_tcsetattr(struct term_s *t, int when, const struct termios *termios)
{
int r;
do {
r = tcsetattr(t->fd, when, termios);
} while ( r < 0 && errno == EINTR );
if ( r < 0 ) term_errno = TERM_ESETATTR;
return r;
}
#ifdef USE_IOCTL
static int
local_modem_get(struct term_s *t, int *modem_out)
{
int r;
r = ioctl(t->fd, TIOCMGET, modem_out);
if ( r < 0 ) term_errno = TERM_EGETMCTL;
return r;
}
static int
local_modem_bis(struct term_s *t, const int *modem)
{
int r;
r = ioctl(t->fd, TIOCMBIS, modem);
if ( r < 0 ) term_errno = TERM_ESETMCTL;
return r;
}
static int
local_modem_bic(struct term_s *t, const int *modem)
{
int r;
r = ioctl(t->fd, TIOCMBIC, modem);
if ( r < 0 ) term_errno = TERM_ESETMCTL;
return r;
}
#endif /* of USE_IOCTL */
static int
local_send_break(struct term_s *t)
{
int r;
do {
r = tcsendbreak(t->fd, 0);
} while (r < 0 && errno == EINTR );
if ( r < 0 ) term_errno = TERM_EBREAK;
return r;
}
static int
local_flush(struct term_s *t, int selector)
{
int r;
r = tcflush(t->fd, selector);
if ( r < 0 ) term_errno = TERM_EFLUSH;
return r;
}
static int
local_fake_flush(struct term_s *t)
{
struct termios tio, tio_orig;
int term_errno_hold = 0, errno_hold = 0;
int r, rval = 0;
do { /* dummy */
/* Get current termios */
r = t->ops->tcgetattr(t, &tio);
if ( r < 0 ) {
rval = -1;
break;
}
tio_orig = tio;
/* Set flow-control to none */
tio.c_cflag &= ~(CRTSCTS);
tio.c_iflag &= ~(IXON | IXOFF | IXANY);
/* Apply termios */
r = t->ops->tcsetattr(t, TCSANOW, &tio);
if ( r < 0 ) {
rval = -1;
break;
}
/* Wait for output to drain. Without flow-control this should
complete in finite time. */
r = t->ops->drain(t);
if ( r < 0 ) {
term_errno_hold = term_errno;
errno_hold = errno;
rval = -1;
/* continue */
}
/* Reset flow-control to original setting. */
r = t->ops->tcsetattr(t, TCSANOW, &tio_orig);
if ( r < 0 ) {
rval = -1;
break;
}
} while(0);
if ( term_errno_hold ) {
term_errno = term_errno_hold;
errno = errno_hold;
}
return rval;
}
static int
local_drain(struct term_s *t)
{
int r;
do {
#ifdef __BIONIC__
/* See: http://dan.drown.org/android/src/gdb/no-tcdrain */
r = ioctl(t->fd, TCSBRK, 1);
#else
r = tcdrain(t->fd);
#endif
} while ( r < 0 && errno == EINTR);
if ( r < 0 ) {
term_errno = TERM_EDRAIN;
return r;
}
/* Give some time to the UART to transmit everything. Some
systems and / or drivers corrupt the last character(s) if
the port is immediately reconfigured, even after a
drain. (I guess, drain does not wait for everything to
actually be transitted on the wire). */
if ( DRAIN_DELAY ) usleep(DRAIN_DELAY);
return 0;
}
int
local_read(struct term_s *t, void *buf, unsigned bufsz)
{
int r;
r = read(t->fd, buf, bufsz);
if ( r < 0 ) term_errno = TERM_EINPUT;
return r;
}
int
local_write(struct term_s *t, const void *buf, unsigned bufsz)
{
int r;
r = write(t->fd, buf, bufsz);
if ( r < 0 ) term_errno = TERM_EOUTPUT;
return r;
}
static const struct term_ops local_term_ops = {
.init = local_init,
.fini = NULL,
.tcgetattr = local_tcgetattr,
.tcsetattr = local_tcsetattr,
#ifdef USE_IOCTL
.modem_get = local_modem_get,
.modem_bis = local_modem_bis,
.modem_bic = local_modem_bic,
#else
.modem_get = NULL,
.modem_bis = NULL,
.modem_bic = NULL,
#endif
.send_break = local_send_break,
.flush = local_flush,
.fake_flush = local_fake_flush,
.drain = local_drain,
.read = local_read,
.write = local_write,
};
/* defined and initialized in ttylocal.c */
extern struct term_ops ttylocal_term_ops;
/***************************************************************************/
@ -772,7 +542,7 @@ term_add (int fd, const char *name, const struct term_ops *ops)
}
if ( ! ops )
ops = &local_term_ops;
ops = &ttylocal_term_ops;
t = term_new(fd, name, ops);
if ( ! t ) {
@ -1569,7 +1339,7 @@ term_pulse_dtr (int fd)
}
if ( t->ops->modem_bic && t->ops->modem_bis ) {
int opins = TIOCM_DTR;
int opins = MCTL_DTR;
r = t->ops->modem_bic(t, &opins);
if ( r < 0 ) {
@ -1636,46 +1406,28 @@ set_pins (int fd, int raise, int pins)
return rol(t, &pins);
}
int term_raise_dtr(int fd) { return set_pins(fd, 1, TIOCM_DTR); }
int term_lower_dtr(int fd) { return set_pins(fd, 0, TIOCM_DTR); }
int term_raise_rts(int fd) { return set_pins(fd, 1, TIOCM_RTS); }
int term_lower_rts(int fd) { return set_pins(fd, 0, TIOCM_RTS); }
int term_raise_dtr(int fd) { return set_pins(fd, 1, MCTL_DTR); }
int term_lower_dtr(int fd) { return set_pins(fd, 0, MCTL_DTR); }
int term_raise_rts(int fd) { return set_pins(fd, 1, MCTL_RTS); }
int term_lower_rts(int fd) { return set_pins(fd, 0, MCTL_RTS); }
/***************************************************************************/
int
term_get_mctl (int fd)
{
int mctl;
int r, mctl;
struct term_s *t;
do { /* dummy */
t = term_find(fd);
if ( ! t ) return -1;
t = term_find(fd);
if ( ! t ) {
mctl = -1;
break;
}
if ( ! t->ops->modem_get ) {
return MCTL_UNAVAIL;
}
if ( t->ops->modem_get ) {
int r, pmctl;
r = t->ops->modem_get(t, &pmctl);
if (r < 0) {
mctl = -1;
break;
}
mctl = 0;
if (pmctl & TIOCM_DTR) mctl |= MCTL_DTR;
if (pmctl & TIOCM_DSR) mctl |= MCTL_DSR;
if (pmctl & TIOCM_CD) mctl |= MCTL_DCD;
if (pmctl & TIOCM_RTS) mctl |= MCTL_RTS;
if (pmctl & TIOCM_CTS) mctl |= MCTL_CTS;
if (pmctl & TIOCM_RI) mctl |= MCTL_RI;
} else {
mctl = MCTL_UNAVAIL;
}
} while(0);
r = t->ops->modem_get(t, &mctl);
if (r < 0) return -1;
return mctl;
}

View File

@ -55,10 +55,9 @@
#include "tncomport.h"
#include "tn2217.h"
#include <sys/ioctl.h>
#include <arpa/telnet.h>
#if 1
#if 0
/* Disable debugging code altogether */
#define DEBUG 0
#define DB(fl, ...) /* nothing */
@ -230,17 +229,13 @@ repr_modem(int m)
static char out[256];
snprintf(out, sizeof out,
"<%s%s%s%s%s%s%s%s%s>",
(m & TIOCM_LE) ? ",dsr": "",
(m & TIOCM_DTR) ? ",dtr": "",
(m & TIOCM_RTS) ? ",rts": "",
(m & TIOCM_ST) ? ",st" : "",
(m & TIOCM_SR) ? ",sr" : "",
(m & TIOCM_CTS) ? ",cts": "",
(m & TIOCM_CD) ? ",cd" : "",
(m & TIOCM_RI) ? ",ri" : "",
(m & TIOCM_DSR) ? ",dsr": ""
);
"<%s%s%s%s%s%s>",
(m & MCTL_DTR) ? ",dtr":"",
(m & MCTL_DSR) ? ",dsr":"",
(m & MCTL_DCD) ? ",dcd":"",
(m & MCTL_RTS) ? ",rts":"",
(m & MCTL_CTS) ? ",cts":"",
(m & MCTL_RI) ? ",ri":"");
if (out[1] == ',') {
out[1] = '<';
return &out[1];
@ -740,7 +735,7 @@ comport_send_set_dtr(struct term_s *t, int modem)
{
unsigned char val;
if (modem & TIOCM_DTR)
if (modem & MCTL_DTR)
val = COMPORT_CONTROL_DTR_ON;
else
val = COMPORT_CONTROL_DTR_OFF;
@ -753,7 +748,7 @@ comport_send_set_rts(struct term_s *t, int modem)
{
unsigned char val;
if (modem & TIOCM_RTS)
if (modem & MCTL_RTS)
val = COMPORT_CONTROL_RTS_ON;
else
val = COMPORT_CONTROL_RTS_OFF;
@ -965,8 +960,8 @@ comport_recv_cmd(struct term_s *t, unsigned char cmd,
/* DTR changes and COMPORT_CONTROL_DTR_REQUEST reply */
case COMPORT_CONTROL_DTR_ON:
case COMPORT_CONTROL_DTR_OFF:
val = (data[0] == COMPORT_CONTROL_DTR_ON) ? TIOCM_DTR : 0;
*modem &= ~TIOCM_DTR;
val = (data[0] == COMPORT_CONTROL_DTR_ON) ? MCTL_DTR : 0;
*modem &= ~MCTL_DTR;
*modem |= val;
DB(DB_CMP, "[received: COMPORT SET_CONTROL: %d: dtr=%u]\r\n",
data[0], !!val);
@ -974,8 +969,8 @@ comport_recv_cmd(struct term_s *t, unsigned char cmd,
/* RTS changes and COMPORT_CONTROL_RTS_REQUEST reply */
case COMPORT_CONTROL_RTS_ON:
case COMPORT_CONTROL_RTS_OFF:
val = (data[0] == COMPORT_CONTROL_RTS_ON) ? TIOCM_RTS : 0;
*modem &= ~TIOCM_RTS;
val = (data[0] == COMPORT_CONTROL_RTS_ON) ? MCTL_RTS : 0;
*modem &= ~MCTL_RTS;
*modem |= val;
DB(DB_CMP, "[received: COMPORT SET_CONTROL: %d: rts=%u]\r\n",
data[0], !!val);
@ -991,12 +986,12 @@ comport_recv_cmd(struct term_s *t, unsigned char cmd,
/* Updates are masked by COMPORT_SET_MODEMSTATE_MASK elsewhere */
if (datalen >= 1) {
val = 0;
if (data[0] & COMPORT_MODEM_CD) val |= TIOCM_CD;
if (data[0] & COMPORT_MODEM_RI) val |= TIOCM_RI;
if (data[0] & COMPORT_MODEM_DSR) val |= TIOCM_DSR;
if (data[0] & COMPORT_MODEM_CTS) val |= TIOCM_CTS;
if (data[0] & COMPORT_MODEM_CD) val |= MCTL_DCD;
if (data[0] & COMPORT_MODEM_RI) val |= MCTL_RI;
if (data[0] & COMPORT_MODEM_DSR) val |= MCTL_DSR;
if (data[0] & COMPORT_MODEM_CTS) val |= MCTL_CTS;
DB(DB_CMP, "[received: COMPORT MODEMSTATE: %s]\r\n", repr_modem(val));
*modem &= ~(TIOCM_CD|TIOCM_RI|TIOCM_DSR|TIOCM_CTS);
*modem &= ~(MCTL_DCD|MCTL_RI|MCTL_DSR|MCTL_CTS);
*modem |= val;
}
break;
@ -1037,7 +1032,7 @@ tn2217_init(struct term_s *t)
cfsetispeed(&s->termios, B0); /* This means "same as ospeed" */
/* Normally DTR and RTS are asserted, but an update can change that */
s->modem = TIOCM_DTR | TIOCM_RTS;
s->modem = MCTL_DTR | MCTL_RTS;
/* Start the negotiations. */
r = opt_will(t, TELOPT_BINARY);
@ -1106,15 +1101,15 @@ tn2217_modem_bis(struct term_s *t, const int *modem)
s->modem |= *modem;
if (s->can_comport) {
if (*modem & TIOCM_DTR) {
r = comport_send_set_dtr(t, TIOCM_DTR);
if (*modem & MCTL_DTR) {
r = comport_send_set_dtr(t, MCTL_DTR);
if (r < 0) return r;
}
if (*modem & TIOCM_RTS) {
r = comport_send_set_rts(t, TIOCM_RTS);
if (*modem & MCTL_RTS) {
r = comport_send_set_rts(t, MCTL_RTS);
if (r < 0) return r;
}
} else if (*modem & (TIOCM_DTR|TIOCM_RTS))
} else if (*modem & (MCTL_DTR|MCTL_RTS))
s->set_modem = 1;
return 0;
@ -1131,15 +1126,15 @@ tn2217_modem_bic(struct term_s *t, const int *modem)
s->modem &= ~*modem;
if (s->can_comport) {
if (*modem & TIOCM_DTR) {
if (*modem & MCTL_DTR) {
r = comport_send_set_dtr(t, 0);
if ( r < 0 ) return r;
}
if (*modem & TIOCM_RTS) {
if (*modem & MCTL_RTS) {
r = comport_send_set_rts(t, 0);
if ( r < 0 ) return r;
}
} else if (*modem & (TIOCM_DTR|TIOCM_RTS))
} else if (*modem & (MCTL_DTR|MCTL_RTS))
s->set_modem = 1;
return 0;

312
ttylocal.c Normal file
View File

@ -0,0 +1,312 @@
/* vi: set sw=4 ts=4:
*
* ttylocal.c
*
* General purpose terminal handling library. Local (native) tty
* handling.
*
* by Nick Patavalis (npat@efault.net)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* $Id$
*/
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <termios.h>
/* On these systems, use the TIOCM[BIS|BIC|GET] ioctls to manipulate
* the modem control lines (DTR / RTS) */
#if defined(__linux__) || \
defined(__FreeBSD__) || defined(__OpenBSD__) || \
defined(__NetBSD__) || defined(__DragonFly__) || \
defined(__APPLE__)
#define USE_IOCTL
#endif
#ifdef USE_IOCTL
#include <sys/ioctl.h>
#endif
#include "custbaud.h"
#ifdef USE_CUSTOM_BAUD
#include CUSTOM_BAUD_HEAD
#endif
/* Time to wait for UART to clear after a drain (in usec). */
#define DRAIN_DELAY 200000
#include "term.h"
#include "termint.h"
/***************************************************************************/
static int
ttylocal_init(struct term_s *t)
{
int rval = 0;
const char *n;
do { /* dummy */
if ( ! isatty(t->fd) ) {
term_errno = TERM_EISATTY;
rval = -1;
break;
}
if ( ! t->name ) {
n = ttyname(t->fd);
if ( n ) t->name = strdup(n);
}
} while (0);
return rval;
}
static int
ttylocal_tcgetattr(struct term_s *t, struct termios *termios_out)
{
int r;
r = tcgetattr(t->fd, termios_out);
if ( r < 0 ) term_errno = TERM_EGETATTR;
return r;
}
static int
ttylocal_tcsetattr(struct term_s *t, int when, const struct termios *termios)
{
int r;
do {
r = tcsetattr(t->fd, when, termios);
} while ( r < 0 && errno == EINTR );
if ( r < 0 ) term_errno = TERM_ESETATTR;
return r;
}
#ifdef USE_IOCTL
static int
sys2term (int sys)
{
int term = 0;
if (sys & TIOCM_DTR) term |= MCTL_DTR;
if (sys & TIOCM_DSR) term |= MCTL_DSR;
if (sys & TIOCM_CD) term |= MCTL_DCD;
if (sys & TIOCM_RTS) term |= MCTL_RTS;
if (sys & TIOCM_CTS) term |= MCTL_CTS;
if (sys & TIOCM_RI) term |= MCTL_RI;
return term;
}
static int
term2sys (int term)
{
int sys = 0;
if (term & MCTL_DTR) sys |= TIOCM_DTR;
if (term & MCTL_DSR) sys |= TIOCM_DSR;
if (term & MCTL_DCD) sys |= TIOCM_CD;
if (term & MCTL_RTS) sys |= TIOCM_RTS;
if (term & MCTL_CTS) sys |= TIOCM_CTS;
if (term & MCTL_RI) sys |= TIOCM_RI;
return sys;
}
static int
ttylocal_modem_get(struct term_s *t, int *pins)
{
int r, syspins;
r = ioctl(t->fd, TIOCMGET, &syspins);
if ( r < 0 ) { term_errno = TERM_EGETMCTL; return r; }
*pins = sys2term(syspins);
return 0;
}
static int
ttylocal_modem_bis(struct term_s *t, const int *pins)
{
int r, syspins;
syspins = term2sys(*pins);
r = ioctl(t->fd, TIOCMBIS, &syspins);
if ( r < 0 ) term_errno = TERM_ESETMCTL;
return r;
}
static int
ttylocal_modem_bic(struct term_s *t, const int *pins)
{
int r, syspins;
syspins = term2sys(*pins);
r = ioctl(t->fd, TIOCMBIC, &syspins);
if ( r < 0 ) term_errno = TERM_ESETMCTL;
return r;
}
#endif /* of USE_IOCTL */
static int
ttylocal_send_break(struct term_s *t)
{
int r;
do {
r = tcsendbreak(t->fd, 0);
} while (r < 0 && errno == EINTR );
if ( r < 0 ) term_errno = TERM_EBREAK;
return r;
}
static int
ttylocal_flush(struct term_s *t, int selector)
{
int r;
r = tcflush(t->fd, selector);
if ( r < 0 ) term_errno = TERM_EFLUSH;
return r;
}
static int
ttylocal_fake_flush(struct term_s *t)
{
struct termios tio, tio_orig;
int term_errno_hold = 0, errno_hold = 0;
int r, rval = 0;
do { /* dummy */
/* Get current termios */
r = t->ops->tcgetattr(t, &tio);
if ( r < 0 ) {
rval = -1;
break;
}
tio_orig = tio;
/* Set flow-control to none */
tio.c_cflag &= ~(CRTSCTS);
tio.c_iflag &= ~(IXON | IXOFF | IXANY);
/* Apply termios */
r = t->ops->tcsetattr(t, TCSANOW, &tio);
if ( r < 0 ) {
rval = -1;
break;
}
/* Wait for output to drain. Without flow-control this should
complete in finite time. */
r = t->ops->drain(t);
if ( r < 0 ) {
term_errno_hold = term_errno;
errno_hold = errno;
rval = -1;
/* continue */
}
/* Reset flow-control to original setting. */
r = t->ops->tcsetattr(t, TCSANOW, &tio_orig);
if ( r < 0 ) {
rval = -1;
break;
}
} while(0);
if ( term_errno_hold ) {
term_errno = term_errno_hold;
errno = errno_hold;
}
return rval;
}
static int
ttylocal_drain(struct term_s *t)
{
int r;
do {
#ifdef __BIONIC__
/* See: http://dan.drown.org/android/src/gdb/no-tcdrain */
r = ioctl(t->fd, TCSBRK, 1);
#else
r = tcdrain(t->fd);
#endif
} while ( r < 0 && errno == EINTR);
if ( r < 0 ) {
term_errno = TERM_EDRAIN;
return r;
}
/* Give some time to the UART to transmit everything. Some
systems and / or drivers corrupt the last character(s) if
the port is immediately reconfigured, even after a
drain. (I guess, drain does not wait for everything to
actually be transitted on the wire). */
if ( DRAIN_DELAY ) usleep(DRAIN_DELAY);
return 0;
}
int
ttylocal_read(struct term_s *t, void *buf, unsigned bufsz)
{
int r;
r = read(t->fd, buf, bufsz);
if ( r < 0 ) term_errno = TERM_EINPUT;
return r;
}
int
ttylocal_write(struct term_s *t, const void *buf, unsigned bufsz)
{
int r;
r = write(t->fd, buf, bufsz);
if ( r < 0 ) term_errno = TERM_EOUTPUT;
return r;
}
const struct term_ops ttylocal_term_ops = {
.init = ttylocal_init,
.fini = NULL,
.tcgetattr = ttylocal_tcgetattr,
.tcsetattr = ttylocal_tcsetattr,
#ifdef USE_IOCTL
.modem_get = ttylocal_modem_get,
.modem_bis = ttylocal_modem_bis,
.modem_bic = ttylocal_modem_bic,
#else
.modem_get = NULL,
.modem_bis = NULL,
.modem_bic = NULL,
#endif
.send_break = ttylocal_send_break,
.flush = ttylocal_flush,
.fake_flush = ttylocal_fake_flush,
.drain = ttylocal_drain,
.read = ttylocal_read,
.write = ttylocal_write,
};
/***************************************************************************/
/*
* Local Variables:
* mode:c
* tab-width: 4
* c-basic-offset: 4
* End:
*/