URL
https://opencores.org/ocsvn/openrisc/openrisc/trunk
Subversion Repositories openrisc
Compare Revisions
- This comparison shows the changes necessary to convert path
/openrisc/trunk/rtos/ecos-2.0/packages/io/serial/v2_0/src
- from Rev 27 to Rev 174
- ↔ Reverse comparison
Rev 27 → Rev 174
/common/serial.c
0,0 → 1,1175
//========================================================================== |
// |
// io/serial/common/serial.c |
// |
// High level serial driver |
// |
//========================================================================== |
//####ECOSGPLCOPYRIGHTBEGIN#### |
// ------------------------------------------- |
// This file is part of eCos, the Embedded Configurable Operating System. |
// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc. |
// |
// eCos 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 or (at your option) any later version. |
// |
// eCos 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 eCos; if not, write to the Free Software Foundation, Inc., |
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. |
// |
// As a special exception, if other files instantiate templates or use macros |
// or inline functions from this file, or you compile this file and link it |
// with other works to produce a work based on this file, this file does not |
// by itself cause the resulting work to be covered by the GNU General Public |
// License. However the source code for this file must still be made available |
// in accordance with section (3) of the GNU General Public License. |
// |
// This exception does not invalidate any other reasons why a work based on |
// this file might be covered by the GNU General Public License. |
// |
// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc. |
// at http://sources.redhat.com/ecos/ecos-license/ |
// ------------------------------------------- |
//####ECOSGPLCOPYRIGHTEND#### |
//========================================================================== |
//#####DESCRIPTIONBEGIN#### |
// |
// Author(s): gthomas |
// Contributors: gthomas, grante, jlarmour, jskov |
// Date: 1999-02-04 |
// Purpose: Top level serial driver |
// Description: |
// |
//####DESCRIPTIONEND#### |
// |
//========================================================================== |
|
#include <pkgconf/io.h> |
#include <pkgconf/io_serial.h> |
|
#include <cyg/io/io.h> |
#include <cyg/io/devtab.h> |
#include <cyg/io/serial.h> |
#include <cyg/infra/cyg_ass.h> // assertion support |
#include <cyg/infra/diag.h> // diagnostic output |
|
static Cyg_ErrNo serial_write(cyg_io_handle_t handle, const void *buf, cyg_uint32 *len); |
static Cyg_ErrNo serial_read(cyg_io_handle_t handle, void *buf, cyg_uint32 *len); |
static Cyg_ErrNo serial_select(cyg_io_handle_t handle, cyg_uint32 which, CYG_ADDRWORD info); |
static Cyg_ErrNo serial_get_config(cyg_io_handle_t handle, cyg_uint32 key, void *buf, cyg_uint32 *len); |
static Cyg_ErrNo serial_set_config(cyg_io_handle_t handle, cyg_uint32 key, const void *buf, cyg_uint32 *len); |
|
DEVIO_TABLE(cyg_io_serial_devio, |
serial_write, |
serial_read, |
serial_select, |
serial_get_config, |
serial_set_config |
); |
|
static void serial_init(serial_channel *chan); |
static void serial_xmt_char(serial_channel *chan); |
static void serial_rcv_char(serial_channel *chan, unsigned char c); |
#ifdef CYGOPT_IO_SERIAL_SUPPORT_LINE_STATUS |
static void serial_indicate_status(serial_channel *chan, |
cyg_serial_line_status_t *s); |
#endif |
#if CYGINT_IO_SERIAL_BLOCK_TRANSFER |
static rcv_req_reply_t serial_data_rcv_req(serial_channel *chan, int avail, |
int* space_avail, |
unsigned char** space); |
static void serial_data_rcv_done(serial_channel *chan, int chars_rcvd); |
static xmt_req_reply_t serial_data_xmt_req(serial_channel *chan, int space, |
int* chars_avail, |
unsigned char** chars); |
static void serial_data_xmt_done(serial_channel *chan, int chars_sent); |
# ifdef CYGOPT_IO_SERIAL_SUPPORT_LINE_STATUS |
SERIAL_CALLBACKS(cyg_io_serial_callbacks, |
serial_init, |
serial_xmt_char, |
serial_rcv_char, |
serial_data_rcv_req, |
serial_data_rcv_done, |
serial_data_xmt_req, |
serial_data_xmt_done, |
serial_indicate_status); |
|
# else |
SERIAL_CALLBACKS(cyg_io_serial_callbacks, |
serial_init, |
serial_xmt_char, |
serial_rcv_char, |
serial_data_rcv_req, |
serial_data_rcv_done, |
serial_data_xmt_req, |
serial_data_xmt_done); |
# endif |
#else |
# ifdef CYGOPT_IO_SERIAL_SUPPORT_LINE_STATUS |
SERIAL_CALLBACKS(cyg_io_serial_callbacks, |
serial_init, |
serial_xmt_char, |
serial_rcv_char, |
serial_indicate_status); |
# else |
SERIAL_CALLBACKS(cyg_io_serial_callbacks, |
serial_init, |
serial_xmt_char, |
serial_rcv_char); |
# endif |
#endif |
|
// --------------------------------------------------------------------------- |
|
#ifdef CYGPKG_IO_SERIAL_FLOW_CONTROL |
|
static __inline__ void |
throttle_tx( serial_channel *chan ) |
{ |
chan->flow_desc.flags |= CYG_SERIAL_FLOW_OUT_THROTTLED; |
// the throttling itself occurs in the serial_xmt_char() callback |
} |
|
static __inline__ void |
restart_tx( serial_channel *chan ) |
{ |
serial_funs *funs = chan->funs; |
|
chan->flow_desc.flags &= ~CYG_SERIAL_FLOW_OUT_THROTTLED; |
|
#ifdef CYGPKG_IO_SERIAL_SELECT_SUPPORT |
// See if there is now enough room to say it is available |
// for writing |
{ |
cbuf_t *cbuf = &chan->out_cbuf; |
int space; |
|
space = cbuf->len - cbuf->nb; |
if (space >= cbuf->low_water) |
cyg_selwakeup( &cbuf->selinfo ); |
} |
#endif |
if ( chan->out_cbuf.nb > 0 ) |
(funs->start_xmit)(chan); |
} |
|
static __inline__ void |
throttle_rx( serial_channel *chan, cyg_bool force ) |
{ |
serial_funs *funs = chan->funs; |
|
chan->flow_desc.flags |= CYG_SERIAL_FLOW_IN_THROTTLED; |
#ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_SOFTWARE |
// send an xoff |
if ( force || chan->config.flags & CYGNUM_SERIAL_FLOW_XONXOFF_RX ) { |
chan->flow_desc.xchar = CYGDAT_IO_SERIAL_FLOW_CONTROL_XOFF_CHAR; |
// Make sure xmit is running so we can send it |
(funs->start_xmit)(chan); |
} |
#endif |
#ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_HW |
{ |
cyg_uint32 i=1; |
cyg_uint32 len = sizeof(i); |
|
// set hardware flow control - don't care if it fails |
if ( force || (chan->config.flags & CYGNUM_SERIAL_FLOW_RTSCTS_RX) || |
(chan->config.flags & CYGNUM_SERIAL_FLOW_DSRDTR_RX) ) |
(funs->set_config)(chan, |
CYG_IO_SET_CONFIG_SERIAL_HW_RX_FLOW_THROTTLE, |
&i, &len); |
} |
#endif |
} |
|
static __inline__ void |
restart_rx( serial_channel *chan, cyg_bool force ) |
{ |
serial_funs *funs = chan->funs; |
|
chan->flow_desc.flags &= ~CYG_SERIAL_FLOW_IN_THROTTLED; |
#ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_SOFTWARE |
// send an xon |
if ( force || chan->config.flags & CYGNUM_SERIAL_FLOW_XONXOFF_RX ) { |
chan->flow_desc.xchar = CYGDAT_IO_SERIAL_FLOW_CONTROL_XON_CHAR; |
(funs->start_xmit)(chan); // Make sure xmit is running so we can send it |
} |
#endif |
#ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_HW |
{ |
cyg_uint32 i=0; |
cyg_uint32 len = sizeof(i); |
|
// set hardware flow control - don't care if it fails |
if ( force || (chan->config.flags & CYGNUM_SERIAL_FLOW_RTSCTS_RX) || |
(chan->config.flags & CYGNUM_SERIAL_FLOW_DSRDTR_RX) ) |
(funs->set_config)(chan, |
CYG_IO_SET_CONFIG_SERIAL_HW_RX_FLOW_THROTTLE, |
&i, &len); |
} |
#endif |
} |
|
#endif |
|
// --------------------------------------------------------------------------- |
|
static void |
serial_init(serial_channel *chan) |
{ |
if (chan->init) return; |
if (chan->out_cbuf.len != 0) { |
#ifdef CYGDBG_IO_INIT |
diag_printf("Set output buffer - buf: %x len: %d\n", chan->out_cbuf.data, chan->out_cbuf.len); |
#endif |
chan->out_cbuf.waiting = false; |
chan->out_cbuf.abort = false; |
#ifdef CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING |
chan->out_cbuf.blocking = true; |
#endif |
chan->out_cbuf.pending = 0; |
cyg_drv_mutex_init(&chan->out_cbuf.lock); |
cyg_drv_cond_init(&chan->out_cbuf.wait, &chan->out_cbuf.lock); |
chan->out_cbuf.low_water = chan->out_cbuf.len / 4; |
#ifdef CYGPKG_IO_SERIAL_SELECT_SUPPORT |
cyg_selinit( &chan->out_cbuf.selinfo ); |
#endif |
} |
if (chan->in_cbuf.len != 0) { |
cbuf_t *cbuf = &chan->in_cbuf; |
|
#ifdef CYGDBG_IO_INIT |
diag_printf("Set input buffer - buf: %x len: %d\n", cbuf->data, cbuf->len); |
#endif |
cbuf->waiting = false; |
cbuf->abort = false; |
#ifdef CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING |
cbuf->blocking = true; |
#endif |
#ifdef CYGPKG_IO_SERIAL_SELECT_SUPPORT |
cyg_selinit( &cbuf->selinfo ); |
#endif |
#ifdef CYGPKG_IO_SERIAL_FLOW_CONTROL |
cbuf->low_water = |
(CYGNUM_IO_SERIAL_FLOW_CONTROL_LOW_WATER_PERCENT * cbuf->len) / 100; |
cbuf->high_water = |
(CYGNUM_IO_SERIAL_FLOW_CONTROL_HIGH_WATER_PERCENT * cbuf->len) / 100; |
# ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_SOFTWARE |
// But make sure it is at least 35 below buffer size, to allow |
// for 16 byte fifos, twice, plus some latency before s/w flow |
// control can kick in. This doesn't apply to h/w flow control |
// as it is near-instaneous |
if ( (cbuf->len - cbuf->high_water) < 35 ) |
cbuf->high_water = cbuf->len - 35; |
// and just in case... |
if ( cbuf->high_water <= 0 ) |
cbuf->high_water = 1; |
if ( cbuf->low_water > cbuf->high_water ) |
cbuf->low_water = cbuf->high_water; |
# endif |
#endif |
cyg_drv_mutex_init(&cbuf->lock); |
cyg_drv_cond_init(&cbuf->wait, &cbuf->lock); |
} |
#ifdef CYGOPT_IO_SERIAL_SUPPORT_LINE_STATUS |
chan->status_callback = NULL; |
#endif |
|
#ifdef CYGDBG_USE_ASSERTS |
#if CYGINT_IO_SERIAL_BLOCK_TRANSFER |
chan->in_cbuf.block_mode_xfer_running = false; |
chan->out_cbuf.block_mode_xfer_running = false; |
#endif // CYGINT_IO_SERIAL_BLOCK_TRANSFER |
#endif // CYGDBG_USE_ASSERTS |
chan->init = true; |
} |
|
// --------------------------------------------------------------------------- |
// FIXME:@@@ Throughout this file there are uses of cyg_drv_cond_signal and |
// cyg_drv_cond_broadcast. Does it matter which? -Jifl |
|
static Cyg_ErrNo |
serial_write(cyg_io_handle_t handle, const void *_buf, cyg_uint32 *len) |
{ |
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *)handle; |
serial_channel *chan = (serial_channel *)t->priv; |
serial_funs *funs = chan->funs; |
cyg_int32 size = *len; |
cyg_uint8 *buf = (cyg_uint8 *)_buf; |
int next; |
cbuf_t *cbuf = &chan->out_cbuf; |
Cyg_ErrNo res = ENOERR; |
|
cyg_drv_mutex_lock(&cbuf->lock); |
cbuf->abort = false; |
|
if (cbuf->len == 0) { |
// Non interrupt driven (i.e. polled) operation |
while (size-- > 0) { |
#ifdef CYGPKG_IO_SERIAL_FLOW_CONTROL |
while ( ( 0 == (chan->flow_desc.flags & CYG_SERIAL_FLOW_OUT_THROTTLED) ) && |
((funs->putc)(chan, *buf) == false) ) |
; // Ignore full, keep trying |
#else |
while ((funs->putc)(chan, *buf) == false) |
; // Ignore full, keep trying |
#endif |
buf++; |
} |
} else { |
cyg_drv_dsr_lock(); // Avoid race condition testing pointers |
while (size > 0) { |
next = cbuf->put + 1; |
if (next == cbuf->len) next = 0; |
if (cbuf->nb == cbuf->len) { |
cbuf->waiting = true; |
// Buffer full - wait for space |
#ifdef CYGPKG_IO_SERIAL_FLOW_CONTROL |
if ( 0 == (chan->flow_desc.flags & CYG_SERIAL_FLOW_OUT_THROTTLED) ) |
#endif |
(funs->start_xmit)(chan); // Make sure xmit is running |
|
// Check flag: 'start_xmit' may have obviated the need |
// to wait :-) |
if (cbuf->waiting) { |
#ifdef CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING |
// Optionally return if configured for non-blocking mode. |
if (!cbuf->blocking) { |
*len -= size; // number of characters actually sent |
cbuf->waiting = false; |
res = -EAGAIN; |
break; |
} |
#endif // CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING |
cbuf->pending += size; // Have this much more to send [eventually] |
if( !cyg_drv_cond_wait(&cbuf->wait) ) |
cbuf->abort = true; |
cbuf->pending -= size; |
} |
if (cbuf->abort) { |
// Give up! |
*len -= size; // number of characters actually sent |
cbuf->abort = false; |
cbuf->waiting = false; |
res = -EINTR; |
break; |
} |
} else { |
cbuf->data[cbuf->put++] = *buf++; |
cbuf->put = next; |
cbuf->nb++; |
size--; // Only count if actually sent! |
} |
} |
#ifdef CYGPKG_IO_SERIAL_FLOW_CONTROL |
if ( 0 == (chan->flow_desc.flags & CYG_SERIAL_FLOW_OUT_THROTTLED) ) |
#endif |
(funs->start_xmit)(chan); // Start output as necessary |
cyg_drv_dsr_unlock(); |
} |
cyg_drv_mutex_unlock(&cbuf->lock); |
return res; |
} |
|
|
// --------------------------------------------------------------------------- |
|
static Cyg_ErrNo |
serial_read(cyg_io_handle_t handle, void *_buf, cyg_uint32 *len) |
{ |
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *)handle; |
serial_channel *chan = (serial_channel *)t->priv; |
serial_funs *funs = chan->funs; |
cyg_uint8 *buf = (cyg_uint8 *)_buf; |
cyg_int32 size = 0; |
cbuf_t *cbuf = &chan->in_cbuf; |
Cyg_ErrNo res = ENOERR; |
#ifdef XX_CYGDBG_DIAG_BUF |
extern int enable_diag_uart; |
int _enable = enable_diag_uart; |
int _time, _stime; |
externC cyg_tick_count_t cyg_current_time(void); |
#endif // CYGDBG_DIAG_BUF |
|
cyg_drv_mutex_lock(&cbuf->lock); |
cbuf->abort = false; |
|
if (cbuf->len == 0) { |
// Non interrupt driven (i.e. polled) operation |
while (size++ < *len) { |
cyg_uint8 c = (funs->getc)(chan); |
#ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_SOFTWARE |
// for software flow control, if the driver returns one of the |
// characters we act on it and then drop it (the app must not |
// see it) |
if ( chan->config.flags & CYGNUM_SERIAL_FLOW_XONXOFF_TX ) { |
if ( c == CYGDAT_IO_SERIAL_FLOW_CONTROL_XOFF_CHAR ) { |
throttle_tx( chan ); |
} else if ( c == CYGDAT_IO_SERIAL_FLOW_CONTROL_XON_CHAR ) { |
restart_tx( chan ); |
} |
else |
*buf++ = c; |
} |
else |
*buf++ = c; |
#else |
*buf++ = c; |
#endif |
} |
} else { |
cyg_drv_dsr_lock(); // Avoid races |
while (size < *len) { |
if (cbuf->nb > 0) { |
#ifdef CYGPKG_IO_SERIAL_FLOW_CONTROL |
if ( (cbuf->nb <= cbuf->low_water) && |
(chan->flow_desc.flags & CYG_SERIAL_FLOW_IN_THROTTLED) ) |
restart_rx( chan, false ); |
#endif |
*buf++ = cbuf->data[cbuf->get]; |
if (++cbuf->get == cbuf->len) cbuf->get = 0; |
cbuf->nb--; |
size++; |
} else { |
#ifdef CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING |
if (!cbuf->blocking) { |
*len = size; // characters actually read |
res = -EAGAIN; |
break; |
} |
#endif // CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING |
cbuf->waiting = true; |
#ifdef XX_CYGDBG_DIAG_BUF |
enable_diag_uart = 0; |
HAL_CLOCK_READ(&_time); |
_stime = (int)cyg_current_time(); |
diag_printf("READ wait - get: %d, put: %d, time: %x.%x\n", cbuf->get, cbuf->put, _stime, _time); |
enable_diag_uart = _enable; |
#endif // CYGDBG_DIAG_BUF |
if( !cyg_drv_cond_wait(&cbuf->wait) ) |
cbuf->abort = true; |
#ifdef XX_CYGDBG_DIAG_BUF |
enable_diag_uart = 0; |
HAL_CLOCK_READ(&_time); |
_stime = (int)cyg_current_time(); |
diag_printf("READ continue - get: %d, put: %d, time: %x.%x\n", cbuf->get, cbuf->put, _stime, _time); |
enable_diag_uart = _enable; |
#endif // CYGDBG_DIAG_BUF |
if (cbuf->abort) { |
// Give up! |
*len = size; // characters actually read |
cbuf->abort = false; |
cbuf->waiting = false; |
res = -EINTR; |
break; |
} |
} |
} |
cyg_drv_dsr_unlock(); |
} |
#ifdef XX_CYGDBG_DIAG_BUF |
cyg_drv_isr_lock(); |
enable_diag_uart = 0; |
HAL_CLOCK_READ(&_time); |
_stime = (int)cyg_current_time(); |
diag_printf("READ done - size: %d, len: %d, time: %x.%x\n", size, *len, _stime, _time); |
enable_diag_uart = _enable; |
cyg_drv_isr_unlock(); |
#endif // CYGDBG_DIAG_BUF |
cyg_drv_mutex_unlock(&cbuf->lock); |
return res; |
} |
|
|
// --------------------------------------------------------------------------- |
|
static cyg_bool |
serial_select(cyg_io_handle_t handle, cyg_uint32 which, CYG_ADDRWORD info) |
{ |
#ifdef CYGPKG_IO_SERIAL_SELECT_SUPPORT |
|
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *)handle; |
serial_channel *chan = (serial_channel *)t->priv; |
|
|
switch( which ) |
{ |
case CYG_FREAD: |
{ |
cbuf_t *cbuf = &chan->in_cbuf; |
|
// Check for data in the input buffer. If there is none, |
// register the select operation, otherwise return true. |
|
if( cbuf->nb == 0 ) |
cyg_selrecord( info, &cbuf->selinfo ); |
else return true; |
} |
break; |
|
case CYG_FWRITE: |
{ |
// Check for space in the output buffer. If there is none, |
// register the select operation, otherwise return true. |
|
cbuf_t *cbuf = &chan->out_cbuf; |
int space = cbuf->len - cbuf->nb; |
#ifdef CYGPKG_IO_SERIAL_FLOW_CONTROL |
if ( (space < cbuf->low_water) || |
(chan->flow_desc.flags & CYG_SERIAL_FLOW_OUT_THROTTLED) ) |
cyg_selrecord( info, &cbuf->selinfo ); |
#else |
if (space < cbuf->low_water) |
cyg_selrecord( info, &cbuf->selinfo ); |
#endif |
else return true; |
} |
break; |
|
case 0: // exceptions - none supported |
break; |
} |
return false; |
#else |
|
// With no select support, we simply return true. |
return true; |
#endif |
} |
|
|
// --------------------------------------------------------------------------- |
|
static Cyg_ErrNo |
serial_get_config(cyg_io_handle_t handle, cyg_uint32 key, void *xbuf, |
cyg_uint32 *len) |
{ |
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *)handle; |
serial_channel *chan = (serial_channel *)t->priv; |
cyg_serial_info_t *buf = (cyg_serial_info_t *)xbuf; |
Cyg_ErrNo res = ENOERR; |
cbuf_t *out_cbuf = &chan->out_cbuf; |
cbuf_t *in_cbuf = &chan->in_cbuf; |
serial_funs *funs = chan->funs; |
|
switch (key) { |
case CYG_IO_GET_CONFIG_SERIAL_INFO: |
if (*len < sizeof(cyg_serial_info_t)) { |
return -EINVAL; |
} |
*buf = chan->config; |
*len = sizeof(chan->config); |
break; |
|
case CYG_IO_GET_CONFIG_SERIAL_BUFFER_INFO: |
// return rx/tx buffer sizes and counts |
{ |
cyg_serial_buf_info_t *p; |
if (*len < sizeof(cyg_serial_buf_info_t)) |
return -EINVAL; |
|
*len = sizeof(cyg_serial_buf_info_t); |
p = (cyg_serial_buf_info_t *)xbuf; |
|
p->rx_bufsize = in_cbuf->len; |
if (p->rx_bufsize) |
p->rx_count = in_cbuf->nb; |
else |
p->rx_count = 0; |
|
p->tx_bufsize = out_cbuf->len; |
if (p->tx_bufsize) |
p->tx_count = out_cbuf->nb; |
else |
p->tx_count = 0; |
} |
break; |
|
case CYG_IO_GET_CONFIG_SERIAL_OUTPUT_DRAIN: |
// Wait for any pending output to complete |
if (out_cbuf->len == 0) break; // Nothing to do if not buffered |
cyg_drv_mutex_lock(&out_cbuf->lock); // Stop any further output processing |
cyg_drv_dsr_lock(); |
while (out_cbuf->pending || (out_cbuf->nb > 0)) { |
out_cbuf->waiting = true; |
if(!cyg_drv_cond_wait(&out_cbuf->wait) ) |
res = -EINTR; |
} |
cyg_drv_dsr_unlock(); |
cyg_drv_mutex_unlock(&out_cbuf->lock); |
break; |
|
case CYG_IO_GET_CONFIG_SERIAL_INPUT_FLUSH: |
// Flush any buffered input |
if (in_cbuf->len == 0) break; // Nothing to do if not buffered |
cyg_drv_mutex_lock(&in_cbuf->lock); // Stop any further input processing |
cyg_drv_dsr_lock(); |
if (in_cbuf->waiting) { |
in_cbuf->abort = true; |
cyg_drv_cond_signal(&in_cbuf->wait); |
in_cbuf->waiting = false; |
} |
in_cbuf->get = in_cbuf->put = in_cbuf->nb = 0; // Flush buffered input |
cyg_drv_dsr_unlock(); |
cyg_drv_mutex_unlock(&in_cbuf->lock); |
break; |
|
case CYG_IO_GET_CONFIG_SERIAL_ABORT: |
// Abort any outstanding I/O, including blocked reads |
// Caution - assumed to be called from 'timeout' (i.e. DSR) code |
if (in_cbuf->len != 0) { |
in_cbuf->abort = true; |
cyg_drv_cond_signal(&in_cbuf->wait); |
} |
if (out_cbuf->len != 0) { |
out_cbuf->abort = true; |
cyg_drv_cond_signal(&out_cbuf->wait); |
} |
break; |
|
case CYG_IO_GET_CONFIG_SERIAL_OUTPUT_FLUSH: |
// Throw away any pending output |
if (out_cbuf->len == 0) break; // Nothing to do if not buffered |
cyg_drv_mutex_lock(&out_cbuf->lock); // Stop any further output processing |
cyg_drv_dsr_lock(); |
if (out_cbuf->nb > 0) { |
out_cbuf->get = out_cbuf->put = out_cbuf->nb = 0; // Empties queue! |
(funs->stop_xmit)(chan); // Done with transmit |
} |
if (out_cbuf->waiting) { |
out_cbuf->abort = true; |
cyg_drv_cond_signal(&out_cbuf->wait); |
out_cbuf->waiting = false; |
} |
cyg_drv_dsr_unlock(); |
cyg_drv_mutex_unlock(&out_cbuf->lock); |
break; |
|
#ifdef CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING |
case CYG_IO_GET_CONFIG_READ_BLOCKING: |
if (*len < sizeof(cyg_uint32)) { |
return -EINVAL; |
} |
*(cyg_uint32*)xbuf = (in_cbuf->blocking) ? 1 : 0; |
break; |
|
case CYG_IO_GET_CONFIG_WRITE_BLOCKING: |
if (*len < sizeof(cyg_uint32)) { |
return -EINVAL; |
} |
*(cyg_uint32*)xbuf = (out_cbuf->blocking) ? 1 : 0; |
break; |
#endif // CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING |
|
default: |
res = -EINVAL; |
} |
return res; |
} |
|
|
// --------------------------------------------------------------------------- |
|
static Cyg_ErrNo |
serial_set_config(cyg_io_handle_t handle, cyg_uint32 key, const void *xbuf, |
cyg_uint32 *len) |
{ |
Cyg_ErrNo res = ENOERR; |
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *)handle; |
serial_channel *chan = (serial_channel *)t->priv; |
#ifdef CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING |
cbuf_t *out_cbuf = &chan->out_cbuf; |
cbuf_t *in_cbuf = &chan->in_cbuf; |
#endif |
serial_funs *funs = chan->funs; |
|
switch (key) { |
#ifdef CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING |
case CYG_IO_SET_CONFIG_READ_BLOCKING: |
if (*len < sizeof(cyg_uint32) || 0 == in_cbuf->len) { |
return -EINVAL; |
} |
in_cbuf->blocking = (1 == *(cyg_uint32*)xbuf) ? true : false; |
break; |
case CYG_IO_SET_CONFIG_WRITE_BLOCKING: |
if (*len < sizeof(cyg_uint32) || 0 == out_cbuf->len) { |
return -EINVAL; |
} |
out_cbuf->blocking = (1 == *(cyg_uint32*)xbuf) ? true : false; |
break; |
#endif // CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING |
|
#ifdef CYGPKG_IO_SERIAL_FLOW_CONTROL |
case CYG_IO_SET_CONFIG_SERIAL_FLOW_CONTROL_METHOD: |
{ |
cyg_uint32 *f = (cyg_uint32 *)xbuf; |
|
if (*len < sizeof(*f)) |
return -EINVAL; |
|
cyg_drv_dsr_lock(); |
|
chan->config.flags &= ~(CYGNUM_SERIAL_FLOW_XONXOFF_RX| |
CYGNUM_SERIAL_FLOW_XONXOFF_TX| |
CYGNUM_SERIAL_FLOW_RTSCTS_RX| |
CYGNUM_SERIAL_FLOW_RTSCTS_TX| |
CYGNUM_SERIAL_FLOW_DSRDTR_RX| |
CYGNUM_SERIAL_FLOW_DSRDTR_TX); |
chan->config.flags |= (*f & ( |
#ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_SOFTWARE |
CYGNUM_SERIAL_FLOW_XONXOFF_RX| |
CYGNUM_SERIAL_FLOW_XONXOFF_TX| |
#endif |
#ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_HW |
CYGNUM_SERIAL_FLOW_RTSCTS_RX| |
CYGNUM_SERIAL_FLOW_RTSCTS_TX| |
CYGNUM_SERIAL_FLOW_DSRDTR_RX| |
CYGNUM_SERIAL_FLOW_DSRDTR_TX| |
#endif |
0)); |
#ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_HW |
// up to hardware driver to clear flags if rejected |
res = (funs->set_config)(chan, |
CYG_IO_SET_CONFIG_SERIAL_HW_FLOW_CONFIG, |
NULL, NULL); |
#endif |
cyg_drv_dsr_unlock(); |
} |
break; |
|
case CYG_IO_SET_CONFIG_SERIAL_FLOW_CONTROL_FORCE: |
{ |
cyg_uint32 *f = (cyg_uint32 *)xbuf; |
|
if (*len < sizeof(*f)) |
return -EINVAL; |
|
cyg_drv_dsr_lock(); |
switch (*f) { |
case CYGNUM_SERIAL_FLOW_THROTTLE_RX: |
throttle_rx( chan, true ); |
break; |
case CYGNUM_SERIAL_FLOW_RESTART_RX: |
restart_rx( chan, true ); |
break; |
case CYGNUM_SERIAL_FLOW_THROTTLE_TX: |
throttle_tx( chan ); |
break; |
case CYGNUM_SERIAL_FLOW_RESTART_TX: |
restart_tx( chan ); |
break; |
default: |
res = -EINVAL; |
break; |
} |
cyg_drv_dsr_unlock(); |
} |
break; |
#endif // CYGPKG_IO_SERIAL_FLOW_CONTROL |
|
#ifdef CYGOPT_IO_SERIAL_SUPPORT_LINE_STATUS |
case CYG_IO_SET_CONFIG_SERIAL_STATUS_CALLBACK: |
{ |
cyg_serial_line_status_callback_fn_t newfn; |
CYG_ADDRWORD newpriv; |
cyg_serial_line_status_callback_t *tmp = |
(cyg_serial_line_status_callback_t *)xbuf; |
|
if ( *len < sizeof(*tmp) ) |
return -EINVAL; |
|
newfn = tmp->fn; |
newpriv = tmp->priv; |
|
// prevent callbacks while we do this |
cyg_drv_dsr_lock(); |
// store old callbacks in same structure |
tmp->fn = chan->status_callback; |
tmp->priv = chan->status_callback_priv; |
chan->status_callback = newfn; |
chan->status_callback_priv = newpriv; |
cyg_drv_dsr_unlock(); |
*len = sizeof(*tmp); |
} |
break; |
#endif |
|
default: |
// pass down to lower layers |
return (funs->set_config)(chan, key, xbuf, len); |
} |
return res; |
} |
|
// --------------------------------------------------------------------------- |
|
static void |
serial_xmt_char(serial_channel *chan) |
{ |
cbuf_t *cbuf = &chan->out_cbuf; |
serial_funs *funs = chan->funs; |
unsigned char c; |
int space; |
|
#if CYGINT_IO_SERIAL_BLOCK_TRANSFER |
CYG_ASSERT(false == cbuf->block_mode_xfer_running, |
"Attempting char xmt while block transfer is running"); |
#endif |
#ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_SOFTWARE |
// if we are required to send an XON/XOFF char, send it before |
// anything else |
// FIXME: what if XON gets corrupted in transit to the other end? |
// Should we resend XON even though the other end may not be wanting |
// to send us stuff at this point? |
if ( chan->config.flags & CYGNUM_SERIAL_FLOW_XONXOFF_RX ) { |
if ( chan->flow_desc.xchar ) { |
if ( (funs->putc)(chan, chan->flow_desc.xchar) ) { |
chan->flow_desc.xchar = '\0'; |
} else { // otherwise there's no space and we have to wait |
return; |
} |
} |
} |
#endif |
#ifdef CYGPKG_IO_SERIAL_FLOW_CONTROL |
// if we're meant to be throttled, just stop and leave |
if ( chan->flow_desc.flags & CYG_SERIAL_FLOW_OUT_THROTTLED ) { |
(funs->stop_xmit)(chan); // Stop transmitting for now |
return; |
} |
#endif |
while (cbuf->nb > 0) { |
c = cbuf->data[cbuf->get]; |
if ((funs->putc)(chan, c)) { |
cbuf->get++; |
if (cbuf->get == cbuf->len) cbuf->get = 0; |
cbuf->nb--; |
} else { |
// See if there is now enough room to restart writer |
space = cbuf->len - cbuf->nb; |
if (space >= cbuf->low_water) { |
if (cbuf->waiting) { |
cbuf->waiting = false; |
cyg_drv_cond_broadcast(&cbuf->wait); |
} |
#ifdef CYGPKG_IO_SERIAL_SELECT_SUPPORT |
cyg_selwakeup( &cbuf->selinfo ); |
#endif |
} |
return; // Need to wait for more space |
} |
} |
(funs->stop_xmit)(chan); // Done with transmit |
|
// must signal waiters, and wake up selecters for the case when |
// this was the last char to be sent and they hadn't been signalled |
// before (e.g. because of flow control) |
if (cbuf->waiting) { |
cbuf->waiting = false; |
cyg_drv_cond_signal(&cbuf->wait); |
} |
#ifdef CYGPKG_IO_SERIAL_SELECT_SUPPORT |
cyg_selwakeup( &cbuf->selinfo ); |
#endif |
} |
|
// --------------------------------------------------------------------------- |
|
static void |
serial_rcv_char(serial_channel *chan, unsigned char c) |
{ |
cbuf_t *cbuf = &chan->in_cbuf; |
|
#if CYGINT_IO_SERIAL_BLOCK_TRANSFER |
CYG_ASSERT(false == cbuf->block_mode_xfer_running, |
"Attempting char rcv while block transfer is running"); |
#endif |
#ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_SOFTWARE |
// for software flow control, if the driver returns one of the characters |
// we act on it and then drop it (the app must not see it) |
if ( chan->config.flags & CYGNUM_SERIAL_FLOW_XONXOFF_TX ) { |
if ( c == CYGDAT_IO_SERIAL_FLOW_CONTROL_XOFF_CHAR ) { |
throttle_tx( chan ); |
return; // it wasn't a "real" character |
} else if ( c == CYGDAT_IO_SERIAL_FLOW_CONTROL_XON_CHAR ) { |
restart_tx( chan ); |
return; // it wasn't a "real" character |
} |
} |
#endif |
#ifdef CYGPKG_IO_SERIAL_FLOW_CONTROL |
// If we've hit the high water mark, tell the other side to stop |
if ( cbuf->nb >= cbuf->high_water ) { |
throttle_rx( chan, false ); |
} |
#endif |
#ifdef CYGPKG_IO_SERIAL_SELECT_SUPPORT |
// Wake up any pending selectors if we are about to |
// put some data into a previously empty buffer. |
if( cbuf->nb == 0 ) |
cyg_selwakeup( &cbuf->selinfo ); |
#endif |
|
// If the flow control is not enabled/sufficient and the buffer is |
// already full, just throw new characters away. |
|
if ( cbuf->nb < cbuf->len ) { |
cbuf->data[cbuf->put++] = c; |
if (cbuf->put == cbuf->len) cbuf->put = 0; |
cbuf->nb++; |
} // note trailing else |
#ifdef CYGOPT_IO_SERIAL_SUPPORT_LINE_STATUS |
else { |
// Overrun. Report the error. |
cyg_serial_line_status_t stat; |
stat.which = CYGNUM_SERIAL_STATUS_OVERRUNERR; |
serial_indicate_status(chan, &stat); |
} |
#endif |
|
if (cbuf->waiting) { |
#ifdef XX_CYGDBG_DIAG_BUF |
extern int enable_diag_uart; |
int _enable = enable_diag_uart; |
int _time, _stime; |
externC cyg_tick_count_t cyg_current_time(void); |
enable_diag_uart = 0; |
HAL_CLOCK_READ(&_time); |
_stime = (int)cyg_current_time(); |
diag_printf("Signal reader - time: %x.%x\n", _stime, _time); |
enable_diag_uart = _enable; |
#endif // CYGDBG_DIAG_BUF |
cbuf->waiting = false; |
cyg_drv_cond_signal(&cbuf->wait); |
} |
} |
|
//---------------------------------------------------------------------------- |
// Flow control indication callback |
|
#ifdef CYGOPT_IO_SERIAL_SUPPORT_LINE_STATUS |
static void |
serial_indicate_status(serial_channel *chan, cyg_serial_line_status_t *s ) |
{ |
#ifdef CYGPKG_IO_SERIAL_FLOW_CONTROL |
if ( CYGNUM_SERIAL_STATUS_FLOW == s->which ) { |
if ( s->value ) |
restart_tx( chan ); |
else |
throttle_tx( chan ); |
} |
#endif |
if ( chan->status_callback ) |
(*chan->status_callback)(s, chan->status_callback_priv); |
} |
#endif // ifdef CYGOPT_IO_SERIAL_SUPPORT_LINE_STATUS |
|
//---------------------------------------------------------------------------- |
// Block transfer functions. Not all drivers require these. Those that |
// do must follow the required semantics: |
// |
// Attempt to transfer as much via the block transfer function as |
// possible, _but_ if that fails, do the remaining bytes via the |
// single-char function. That ensures that all policy decisions can be |
// made in this driver, and not in the device driver. |
// |
// Note: if the driver uses DMA for transmission, an initial failing |
// call to the xmt_req function must cause the start_xmit function to |
// fall-back to regular CPU-interrupt based single-character |
// transmission. |
|
#if CYGINT_IO_SERIAL_BLOCK_TRANSFER |
|
static rcv_req_reply_t |
serial_data_rcv_req(serial_channel *chan, int avail, |
int* space_avail, unsigned char** space) |
{ |
cbuf_t *cbuf = &chan->in_cbuf; |
int gap; |
|
#ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_SOFTWARE |
// When there is software flow-control, force the serial device |
// driver to use the single-char xmt/rcv functions, since these |
// have to make policy decision based on the data. Rcv function |
// may also have to transmit data to throttle the xmitter. |
if (chan->config.flags & (CYGNUM_SERIAL_FLOW_XONXOFF_TX|CYGNUM_SERIAL_FLOW_XONXOFF_RX)) |
return CYG_RCV_DISABLED; |
#endif |
|
CYG_ASSERT(false == cbuf->block_mode_xfer_running, |
"Attempting new block transfer while another is running"); |
// Check for space |
gap = cbuf->nb; |
if (gap == cbuf->len) |
return CYG_RCV_FULL; |
|
#ifdef CYGDBG_USE_ASSERTS |
cbuf->block_mode_xfer_running = true; |
#endif |
|
if (0 == gap) { |
// Buffer is empty. Reset put/get indexes to get max transfer in |
// one chunk. |
cbuf->get = 0; |
cbuf->put = 0; |
gap = cbuf->len; |
} else { |
// Free space (G = get, P = put, x = data, . = empty) |
// positive: xxxxP.....Gxxx |
// negative: ..GxxxxxP..... [offer last chunk only] |
|
// First try for a gap between put and get locations |
gap = cbuf->get - cbuf->put; |
if (gap < 0) { |
// If failed, the gap is between put and the end of buffer |
gap = cbuf->len - cbuf->put; |
} |
} |
|
if (avail < gap) gap = avail; // bound by what's available from hw |
|
*space_avail = gap; |
*space = &cbuf->data[cbuf->put]; |
|
CYG_ASSERT((gap+cbuf->nb) <= cbuf->len, "Buffer will overflow"); |
CYG_ASSERT(cbuf->put < cbuf->len, "Invalid put ptr"); |
CYG_ASSERT(cbuf->get < cbuf->len, "Invalid get ptr"); |
|
return CYG_RCV_OK; |
} |
|
static void |
serial_data_rcv_done(serial_channel *chan, int chars_rcvd) |
{ |
cbuf_t *cbuf = &chan->in_cbuf; |
|
cbuf->put += chars_rcvd; |
cbuf->nb += chars_rcvd; |
|
if (cbuf->put == cbuf->len) cbuf->put = 0; |
|
CYG_ASSERT(cbuf->nb <= cbuf->len, "Buffer overflow"); |
CYG_ASSERT(cbuf->put < cbuf->len, "Invalid put ptr"); |
CYG_ASSERT(cbuf->get < cbuf->len, "Invalid get ptr"); |
|
if (cbuf->waiting) { |
cbuf->waiting = false; |
cyg_drv_cond_signal(&cbuf->wait); |
} |
#ifdef CYGPKG_IO_SERIAL_FLOW_CONTROL |
// If we've hit the high water mark, tell the other side to stop |
if ( cbuf->nb >= cbuf->high_water ) { |
throttle_rx( chan, false ); |
} |
#endif |
#ifdef CYGPKG_IO_SERIAL_SELECT_SUPPORT |
// Wake up any pending selectors if we have |
// put some data into a previously empty buffer. |
if (chars_rcvd == cbuf->nb) |
cyg_selwakeup( &cbuf->selinfo ); |
#endif |
|
#ifdef CYGDBG_USE_ASSERTS |
cbuf->block_mode_xfer_running = false; |
#endif |
} |
|
static xmt_req_reply_t |
serial_data_xmt_req(serial_channel *chan, int space, |
int* chars_avail, unsigned char** chars) |
{ |
cbuf_t *cbuf = &chan->out_cbuf; |
int avail; |
|
#ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_SOFTWARE |
// When there is software flow-control, force the serial device |
// driver to use the single-char xmt/rcv functions, since these |
// have to make policy decision based on the data. Rcv function |
// may also have to transmit data to throttle the xmitter. |
if (chan->config.flags & (CYGNUM_SERIAL_FLOW_XONXOFF_TX|CYGNUM_SERIAL_FLOW_XONXOFF_RX)) |
return CYG_XMT_DISABLED; |
#endif |
|
CYG_ASSERT(false == cbuf->block_mode_xfer_running, |
"Attempting new block transfer while another is running"); |
|
// Available data (G = get, P = put, x = data, . = empty) |
// 0: no data |
// negative: xxxxP.....Gxxx [offer last chunk only] |
// positive: ..GxxxxxP..... |
if (0 == cbuf->nb) |
return CYG_XMT_EMPTY; |
|
#ifdef CYGDBG_USE_ASSERTS |
cbuf->block_mode_xfer_running = true; |
#endif |
|
if (cbuf->get >= cbuf->put) { |
avail = cbuf->len - cbuf->get; |
} else { |
avail = cbuf->put - cbuf->get; |
} |
|
if (avail > space) avail = space; // bound by space in hardware |
|
*chars_avail = avail; |
*chars = &cbuf->data[cbuf->get]; |
|
CYG_ASSERT(avail <= cbuf->len, "Avail overflow"); |
CYG_ASSERT(cbuf->nb <= cbuf->len, "Buffer overflow"); |
CYG_ASSERT(cbuf->put < cbuf->len, "Invalid put ptr"); |
CYG_ASSERT(cbuf->get < cbuf->len, "Invalid get ptr"); |
|
return CYG_XMT_OK; |
} |
|
static void |
serial_data_xmt_done(serial_channel *chan, int chars_sent) |
{ |
cbuf_t *cbuf = &chan->out_cbuf; |
serial_funs *funs = chan->funs; |
int space; |
|
cbuf->get += chars_sent; |
cbuf->nb -= chars_sent; |
|
if (cbuf->get == cbuf->len) cbuf->get = 0; |
|
CYG_ASSERT(cbuf->nb <= cbuf->len, "Buffer overflow"); |
CYG_ASSERT(cbuf->nb >= 0, "Buffer underflow"); |
CYG_ASSERT(cbuf->put < cbuf->len, "Invalid put ptr"); |
CYG_ASSERT(cbuf->get < cbuf->len, "Invalid get ptr"); |
|
if (0 == cbuf->nb) { |
(funs->stop_xmit)(chan); // Done with transmit |
cbuf->get = cbuf->put = 0; // reset ptrs if empty |
} |
|
// See if there is now enough room to restart writer |
space = cbuf->len - cbuf->nb; |
if (space >= cbuf->low_water) { |
if (cbuf->waiting) { |
cbuf->waiting = false; |
cyg_drv_cond_broadcast(&cbuf->wait); |
} |
#ifdef CYGPKG_IO_SERIAL_SELECT_SUPPORT |
cyg_selwakeup( &cbuf->selinfo ); |
#endif |
} |
|
#ifdef CYGDBG_USE_ASSERTS |
cbuf->block_mode_xfer_running = false; |
#endif |
} |
|
#endif // CYGINT_IO_SERIAL_BLOCK_TRANSFER |
|
// --------------------------------------------------------------------------- |
|
// EOF serial.c |
/common/tty.c
0,0 → 1,336
//========================================================================== |
// |
// io/serial/common/tty.c |
// |
// TTY (terminal-like interface) I/O driver |
// |
//========================================================================== |
//####ECOSGPLCOPYRIGHTBEGIN#### |
// ------------------------------------------- |
// This file is part of eCos, the Embedded Configurable Operating System. |
// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc. |
// |
// eCos 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 or (at your option) any later version. |
// |
// eCos 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 eCos; if not, write to the Free Software Foundation, Inc., |
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. |
// |
// As a special exception, if other files instantiate templates or use macros |
// or inline functions from this file, or you compile this file and link it |
// with other works to produce a work based on this file, this file does not |
// by itself cause the resulting work to be covered by the GNU General Public |
// License. However the source code for this file must still be made available |
// in accordance with section (3) of the GNU General Public License. |
// |
// This exception does not invalidate any other reasons why a work based on |
// this file might be covered by the GNU General Public License. |
// |
// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc. |
// at http://sources.redhat.com/ecos/ecos-license/ |
// ------------------------------------------- |
//####ECOSGPLCOPYRIGHTEND#### |
//========================================================================== |
//#####DESCRIPTIONBEGIN#### |
// |
// Author(s): gthomas |
// Contributors: gthomas |
// Date: 1999-02-04 |
// Purpose: Device driver for tty I/O, layered on top of serial I/O |
// Description: |
// |
//####DESCRIPTIONEND#### |
// |
//========================================================================== |
|
#include <pkgconf/io.h> |
#include <pkgconf/io_serial.h> |
#ifdef CYGPKG_IO_SERIAL_TTY |
#include <cyg/io/io.h> |
#include <cyg/io/devtab.h> |
#include <cyg/io/ttyio.h> |
#include <cyg/infra/diag.h> |
|
static bool tty_init(struct cyg_devtab_entry *tab); |
static Cyg_ErrNo tty_lookup(struct cyg_devtab_entry **tab, |
struct cyg_devtab_entry *sub_tab, |
const char *name); |
static Cyg_ErrNo tty_write(cyg_io_handle_t handle, const void *buf, cyg_uint32 *len); |
static Cyg_ErrNo tty_read(cyg_io_handle_t handle, void *buf, cyg_uint32 *len); |
static Cyg_ErrNo tty_select(cyg_io_handle_t handle, cyg_uint32 which, CYG_ADDRWORD info); |
static Cyg_ErrNo tty_get_config(cyg_io_handle_t handle, cyg_uint32 key, void *buf, cyg_uint32 *len); |
static Cyg_ErrNo tty_set_config(cyg_io_handle_t handle, cyg_uint32 key, const void *buf, cyg_uint32 *len); |
|
struct tty_private_info { |
cyg_tty_info_t dev_info; |
cyg_io_handle_t dev_handle; |
}; |
|
static DEVIO_TABLE(tty_devio, |
tty_write, |
tty_read, |
tty_select, |
tty_get_config, |
tty_set_config |
); |
|
#ifdef CYGPKG_IO_SERIAL_TTY_TTYDIAG |
static struct tty_private_info tty_private_info_diag; |
DEVTAB_ENTRY(tty_io_diag, |
// "/dev/console", |
// CYGDAT_IO_SERIAL_TTY_CONSOLE, // Based on driver for this device |
"/dev/ttydiag", |
"/dev/haldiag", |
&tty_devio, |
tty_init, |
tty_lookup, // Execute this when device is being looked up |
&tty_private_info_diag); |
#endif |
|
#ifdef CYGPKG_IO_SERIAL_TTY_TTY0 |
static struct tty_private_info tty_private_info0; |
DEVTAB_ENTRY(tty_io0, |
"/dev/tty0", |
CYGDAT_IO_SERIAL_TTY_TTY0_DEV, |
&tty_devio, |
tty_init, |
tty_lookup, // Execute this when device is being looked up |
&tty_private_info0); |
#endif |
|
#ifdef CYGPKG_IO_SERIAL_TTY_TTY1 |
static struct tty_private_info tty_private_info1; |
DEVTAB_ENTRY(tty_io1, |
"/dev/tty1", |
CYGDAT_IO_SERIAL_TTY_TTY1_DEV, |
&tty_devio, |
tty_init, |
tty_lookup, // Execute this when device is being looked up |
&tty_private_info1); |
#endif |
|
#ifdef CYGPKG_IO_SERIAL_TTY_TTY2 |
static struct tty_private_info tty_private_info2; |
DEVTAB_ENTRY(tty_io2, |
"/dev/tty2", |
CYGDAT_IO_SERIAL_TTY_TTY2_DEV, |
&tty_devio, |
tty_init, |
tty_lookup, // Execute this when device is being looked up |
&tty_private_info2); |
#endif |
|
static bool |
tty_init(struct cyg_devtab_entry *tab) |
{ |
struct tty_private_info *priv = (struct tty_private_info *)tab->priv; |
#ifdef CYGDBG_IO_INIT |
diag_printf("Init tty channel: %x\n", tab); |
#endif |
priv->dev_info.tty_out_flags = CYG_TTY_OUT_FLAGS_DEFAULT; |
priv->dev_info.tty_in_flags = CYG_TTY_IN_FLAGS_DEFAULT; |
return true; |
} |
|
static Cyg_ErrNo |
tty_lookup(struct cyg_devtab_entry **tab, |
struct cyg_devtab_entry *sub_tab, |
const char *name) |
{ |
cyg_io_handle_t chan = (cyg_io_handle_t)sub_tab; |
struct tty_private_info *priv = (struct tty_private_info *)(*tab)->priv; |
#if 0 |
cyg_int32 len; |
#endif |
priv->dev_handle = chan; |
#if 0 |
len = sizeof(cyg_serial_info_t); |
// Initialize configuration |
cyg_io_get_config(chan, CYG_SERIAL_GET_CONFIG, |
(void *)&priv->dev_info.serial_config, &len); |
#endif |
return ENOERR; |
} |
|
#define BUFSIZE 64 |
|
static Cyg_ErrNo |
tty_write(cyg_io_handle_t handle, const void *_buf, cyg_uint32 *len) |
{ |
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *)handle; |
struct tty_private_info *priv = (struct tty_private_info *)t->priv; |
cyg_io_handle_t chan = (cyg_io_handle_t)priv->dev_handle; |
cyg_int32 size, bytes_successful, actually_written; |
cyg_uint8 xbuf[BUFSIZE]; |
cyg_uint8 c; |
cyg_uint8 *buf = (cyg_uint8 *)_buf; |
Cyg_ErrNo res = -EBADF; |
// assert(chan) |
size = 0; |
bytes_successful = 0; |
actually_written = 0; |
while (bytes_successful++ < *len) { |
c = *buf++; |
if ((c == '\n') && |
(priv->dev_info.tty_out_flags & CYG_TTY_OUT_FLAGS_CRLF)) { |
xbuf[size++] = '\r'; |
} |
xbuf[size++] = c; |
// Always leave room for possible CR/LF expansion |
if ((size >= (BUFSIZE-1)) || |
(bytes_successful == *len)) { |
res = cyg_io_write(chan, xbuf, &size); |
if (res != ENOERR) { |
*len = actually_written; |
return res; |
} |
actually_written += size; |
size = 0; |
} |
} |
return res; |
} |
|
static Cyg_ErrNo |
tty_read(cyg_io_handle_t handle, void *_buf, cyg_uint32 *len) |
{ |
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *)handle; |
struct tty_private_info *priv = (struct tty_private_info *)t->priv; |
cyg_io_handle_t chan = (cyg_io_handle_t)priv->dev_handle; |
cyg_uint32 clen; |
cyg_int32 size; |
Cyg_ErrNo res; |
cyg_uint8 c; |
cyg_uint8 *buf = (cyg_uint8 *)_buf; |
// assert(chan) |
size = 0; |
while (size < *len) { |
clen = 1; |
res = cyg_io_read(chan, &c, &clen); |
if (res != ENOERR) { |
*len = size; |
return res; |
} |
buf[size++] = c; |
if ((priv->dev_info.tty_in_flags & CYG_TTY_IN_FLAGS_BINARY) == 0) { |
switch (c) { |
case '\b': /* drop through */ |
case 0x7f: |
size -= 2; // erase one character + 'backspace' char |
if (size < 0) { |
size = 0; |
} else if (priv->dev_info.tty_in_flags & CYG_TTY_IN_FLAGS_ECHO) { |
clen = 3; |
cyg_io_write(chan, "\b \b", &clen); |
} |
break; |
case '\r': |
if (priv->dev_info.tty_in_flags & CYG_TTY_IN_FLAGS_CRLF) { |
/* Don't do anything because a '\n' will come next */ |
break; |
} |
if (priv->dev_info.tty_in_flags & CYG_TTY_IN_FLAGS_CR) { |
c = '\n'; // Map CR -> LF |
} |
/* drop through */ |
case '\n': |
if (priv->dev_info.tty_in_flags & CYG_TTY_IN_FLAGS_ECHO) { |
if (priv->dev_info.tty_out_flags & CYG_TTY_OUT_FLAGS_CRLF) { |
clen = 2; |
cyg_io_write(chan, "\r\n", &clen); |
} else { |
clen = 1; |
cyg_io_write(chan, &c, &clen); |
} |
} |
buf[size-1] = c; |
*len = size; |
return ENOERR; |
default: |
if (priv->dev_info.tty_in_flags & CYG_TTY_IN_FLAGS_ECHO) { |
clen = 1; |
cyg_io_write(chan, &c, &clen); |
} |
break; |
} |
} |
} |
*len = size; |
return ENOERR; |
} |
|
static cyg_bool |
tty_select(cyg_io_handle_t handle, cyg_uint32 which, CYG_ADDRWORD info) |
{ |
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *)handle; |
struct tty_private_info *priv = (struct tty_private_info *)t->priv; |
cyg_io_handle_t chan = (cyg_io_handle_t)priv->dev_handle; |
|
// Just pass it on to next driver level |
return cyg_io_select( chan, which, info ); |
} |
|
static Cyg_ErrNo |
tty_get_config(cyg_io_handle_t handle, cyg_uint32 key, void *buf, cyg_uint32 *len) |
{ |
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *)handle; |
struct tty_private_info *priv = (struct tty_private_info *)t->priv; |
cyg_io_handle_t chan = (cyg_io_handle_t)priv->dev_handle; |
Cyg_ErrNo res = ENOERR; |
#if 0 |
cyg_int32 current_len; |
#endif |
// assert(chan) |
switch (key) { |
case CYG_IO_GET_CONFIG_TTY_INFO: |
if (*len < sizeof(cyg_tty_info_t)) { |
return -EINVAL; |
} |
#if 0 |
current_len = sizeof(cyg_serial_info_t); |
res = cyg_io_get_config(chan, CYG_SERIAL_GET_CONFIG, |
(void *)&priv->dev_info.serial_config, ¤t_len); |
if (res != ENOERR) { |
return res; |
} |
#endif |
*(cyg_tty_info_t *)buf = priv->dev_info; |
*len = sizeof(cyg_tty_info_t); |
break; |
default: // Assume this is a 'serial' driver control |
res = cyg_io_get_config(chan, key, buf, len); |
} |
return res; |
} |
|
static Cyg_ErrNo |
tty_set_config(cyg_io_handle_t handle, cyg_uint32 key, const void *buf, cyg_uint32 *len) |
{ |
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *)handle; |
struct tty_private_info *priv = (struct tty_private_info *)t->priv; |
cyg_io_handle_t chan = (cyg_io_handle_t)priv->dev_handle; |
#if 0 |
cyg_int32 current_len; |
#endif |
Cyg_ErrNo res = ENOERR; |
// assert(chan) |
switch (key) { |
case CYG_IO_SET_CONFIG_TTY_INFO: |
if (*len != sizeof(cyg_tty_info_t)) { |
return -EINVAL; |
} |
priv->dev_info = *(cyg_tty_info_t *)buf; |
break; |
default: // Pass on to serial driver |
res = cyg_io_set_config(chan, key, buf, len); |
} |
return res; |
} |
#endif // CYGPKG_IO_SERIAL_TTY |
/common/termios.c
0,0 → 1,402
/* ==================================================================== |
// |
// termios.c |
// |
// POSIX termios API implementation |
// |
// ==================================================================== |
//####ECOSGPLCOPYRIGHTBEGIN#### |
// ------------------------------------------- |
// This file is part of eCos, the Embedded Configurable Operating System. |
// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc. |
// |
// eCos 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 or (at your option) any later version. |
// |
// eCos 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 eCos; if not, write to the Free Software Foundation, Inc., |
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. |
// |
// As a special exception, if other files instantiate templates or use macros |
// or inline functions from this file, or you compile this file and link it |
// with other works to produce a work based on this file, this file does not |
// by itself cause the resulting work to be covered by the GNU General Public |
// License. However the source code for this file must still be made available |
// in accordance with section (3) of the GNU General Public License. |
// |
// This exception does not invalidate any other reasons why a work based on |
// this file might be covered by the GNU General Public License. |
// |
// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc. |
// at http://sources.redhat.com/ecos/ecos-license/ |
// ------------------------------------------- |
//####ECOSGPLCOPYRIGHTEND#### |
// ==================================================================== |
//#####DESCRIPTIONBEGIN#### |
// |
// Author(s): jlarmour |
// Contributors: |
// Date: 2000-07-22 |
// Purpose: POSIX termios API support |
// Description: Most of the real work happens in the POSIX termios tty |
// drivers |
// |
//####DESCRIPTIONEND#### |
// |
// ==================================================================*/ |
|
// CONFIGURATION |
|
#include <pkgconf/io_serial.h> |
|
#ifdef CYGPKG_IO_SERIAL_TERMIOS |
|
// INCLUDES |
|
#include <termios.h> // Header for this file |
#include <cyg/infra/cyg_type.h> // Common stuff |
#include <cyg/infra/cyg_ass.h> // Assertion support |
#include <cyg/infra/cyg_trac.h> // Tracing support |
#include <cyg/io/serialio.h> // eCos serial implementation |
#include <cyg/fileio/fileio.h> // file operations |
#include <cyg/io/io.h> |
#include <errno.h> // errno |
#include <unistd.h> // isatty() |
|
// TYPES |
|
typedef struct { |
const struct termios *termios_p; |
int optact; |
} setattr_struct; |
|
// FUNCTIONS |
|
extern speed_t |
cfgetospeed( const struct termios *termios_p ) |
{ |
CYG_REPORT_FUNCTYPE( "returning speed code %d" ); |
CYG_CHECK_DATA_PTRC( termios_p ); |
CYG_REPORT_FUNCARG1XV( termios_p ); |
CYG_REPORT_RETVAL( termios_p->c_ospeed ); |
return termios_p->c_ospeed; |
} // cfgetospeed() |
|
|
extern int |
cfsetospeed( struct termios *termios_p, speed_t speed ) |
{ |
CYG_REPORT_FUNCTYPE( "returning %d" ); |
CYG_CHECK_DATA_PTRC( termios_p ); |
CYG_REPORT_FUNCARG2( "termios_p=%08x, speed=%d", termios_p, speed ); |
CYG_REPORT_RETVAL( termios_p->c_ospeed ); |
if ( speed > B4000000 ) { |
errno = EINVAL; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
termios_p->c_ospeed = speed; |
CYG_REPORT_RETVAL( 0 ); |
return 0; |
} // cfsetospeed() |
|
|
extern speed_t |
cfgetispeed( const struct termios *termios_p ) |
{ |
CYG_REPORT_FUNCTYPE( "returning speed code %d" ); |
CYG_CHECK_DATA_PTRC( termios_p ); |
CYG_REPORT_FUNCARG1XV( termios_p ); |
CYG_REPORT_RETVAL( termios_p->c_ispeed ); |
return termios_p->c_ispeed; |
} // cfgetispeed() |
|
|
extern int |
cfsetispeed( struct termios *termios_p, speed_t speed ) |
{ |
CYG_REPORT_FUNCTYPE( "returning %d" ); |
CYG_CHECK_DATA_PTRC( termios_p ); |
CYG_REPORT_FUNCARG2( "termios_p=%08x, speed=%d", termios_p, speed ); |
if ( speed > B115200 ) { |
errno = EINVAL; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
termios_p->c_ispeed = speed; |
CYG_REPORT_RETVAL( 0 ); |
return 0; |
} // cfsetispeed() |
|
|
__externC cyg_file * |
cyg_fp_get( int fd ); |
|
__externC void |
cyg_fp_free( cyg_file *fp ); |
|
extern int |
tcgetattr( int fildes, struct termios *termios_p ) |
{ |
cyg_file *fp; |
int ret; |
int len = sizeof( *termios_p ); |
|
CYG_REPORT_FUNCTYPE( "returning %d" ); |
CYG_REPORT_FUNCARG2( "fildes=%d, termios_p=%08x", fildes, termios_p ); |
CYG_CHECK_DATA_PTRC( termios_p ); |
|
if ( !isatty(fildes) ) { |
errno = ENOTTY; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
|
fp = cyg_fp_get( fildes ); |
|
if ( NULL == fp ) { |
errno = EBADF; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
|
ret = fp->f_ops->fo_getinfo( fp, CYG_IO_GET_CONFIG_TERMIOS, termios_p, |
len); |
cyg_fp_free( fp ); |
|
if ( ret > 0 ) { |
errno = ret; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
CYG_REPORT_RETVAL( 0 ); |
return 0; |
} // tcgetattr() |
|
|
extern int |
tcsetattr( int fildes, int optact, const struct termios *termios_p ) |
{ |
cyg_file *fp; |
int ret; |
setattr_struct attr; |
int len = sizeof( attr ); |
|
CYG_REPORT_FUNCTYPE( "returning %d" ); |
CYG_REPORT_FUNCARG3( "fildes=%d, optact=%d, termios_p=%08x", |
fildes, optact, termios_p ); |
CYG_CHECK_DATA_PTRC( termios_p ); |
|
if ( !isatty(fildes) ) { |
errno = ENOTTY; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
|
if ( (optact != TCSANOW) && (optact != TCSADRAIN) && |
(optact != TCSAFLUSH) ) { |
errno = EINVAL; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
|
fp = cyg_fp_get( fildes ); |
|
if ( NULL == fp ) { |
errno = EBADF; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
|
attr.termios_p = termios_p; |
attr.optact = optact; |
|
ret = fp->f_ops->fo_setinfo( fp, CYG_IO_SET_CONFIG_TERMIOS, &attr, |
len); |
|
cyg_fp_free( fp ); |
|
if ( ret > 0 ) { |
errno = ret; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
CYG_REPORT_RETVAL( 0 ); |
return 0; |
} // tcsetattr() |
|
|
extern int |
tcsendbreak( int fildes, int duration ) |
{ |
// FIXME |
return -EINVAL; |
} // tcsendbreak() |
|
extern int |
tcdrain( int fildes ) |
{ |
cyg_file *fp; |
int ret; |
|
CYG_REPORT_FUNCTYPE( "returning %d" ); |
CYG_REPORT_FUNCARG1DV( fildes ); |
|
if ( !isatty(fildes) ) { |
errno = ENOTTY; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
|
fp = cyg_fp_get( fildes ); |
|
if ( NULL == fp ) { |
errno = EBADF; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
|
ret = fp->f_ops->fo_getinfo( fp, |
CYG_IO_GET_CONFIG_SERIAL_OUTPUT_DRAIN, |
NULL, 0 ); |
cyg_fp_free( fp ); |
|
if ( ret > 0 ) { |
errno = ret; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
CYG_REPORT_RETVAL( 0 ); |
return 0; |
} // tcdrain() |
|
extern int |
tcflush( int fildes, int queue_sel ) |
{ |
cyg_file *fp; |
int ret; |
|
CYG_REPORT_FUNCTYPE( "returning %d" ); |
CYG_REPORT_FUNCARG2DV( fildes, queue_sel ); |
|
if ( !isatty(fildes) ) { |
errno = ENOTTY; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
|
fp = cyg_fp_get( fildes ); |
|
if ( NULL == fp ) { |
errno = EBADF; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
|
switch( queue_sel ) { |
case TCIOFLUSH: |
ret = fp->f_ops->fo_getinfo( fp, |
CYG_IO_GET_CONFIG_SERIAL_OUTPUT_FLUSH, |
NULL, 0 ); |
// fallthrough |
case TCIFLUSH: |
ret = fp->f_ops->fo_getinfo( fp, |
CYG_IO_GET_CONFIG_SERIAL_INPUT_FLUSH, |
NULL, 0 ); |
break; |
case TCOFLUSH: |
ret = fp->f_ops->fo_getinfo( fp, |
CYG_IO_GET_CONFIG_SERIAL_OUTPUT_FLUSH, |
NULL, 0 ); |
break; |
default: |
ret = EINVAL; |
break; |
} |
|
cyg_fp_free( fp ); |
|
if ( ret > 0 ) { |
errno = ret; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
CYG_REPORT_RETVAL( 0 ); |
return 0; |
} // tcflush() |
|
extern int |
tcflow( int fildes, int action ) |
{ |
cyg_file *fp; |
int ret; |
cyg_uint32 forcearg; |
int len = sizeof(forcearg); |
|
CYG_REPORT_FUNCTYPE( "returning %d" ); |
CYG_REPORT_FUNCARG2DV( fildes, action ); |
|
if ( !isatty(fildes) ) { |
errno = ENOTTY; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
|
fp = cyg_fp_get( fildes ); |
|
if ( NULL == fp ) { |
errno = EBADF; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
|
switch( action ) { |
case TCOOFF: |
forcearg = CYGNUM_SERIAL_FLOW_THROTTLE_TX; |
ret = fp->f_ops->fo_setinfo( fp, |
CYG_IO_SET_CONFIG_SERIAL_FLOW_CONTROL_FORCE, |
&forcearg, len ); |
break; |
case TCOON: |
forcearg = CYGNUM_SERIAL_FLOW_RESTART_TX; |
ret = fp->f_ops->fo_setinfo( fp, |
CYG_IO_SET_CONFIG_SERIAL_FLOW_CONTROL_FORCE, |
&forcearg, len ); |
break; |
case TCIOFF: |
forcearg = CYGNUM_SERIAL_FLOW_THROTTLE_RX; |
ret = fp->f_ops->fo_setinfo( fp, |
CYG_IO_SET_CONFIG_SERIAL_FLOW_CONTROL_FORCE, |
&forcearg, len ); |
break; |
case TCION: |
forcearg = CYGNUM_SERIAL_FLOW_RESTART_RX; |
ret = fp->f_ops->fo_setinfo( fp, |
CYG_IO_SET_CONFIG_SERIAL_FLOW_CONTROL_FORCE, |
&forcearg, len ); |
break; |
default: |
ret = EINVAL; |
break; |
} |
|
cyg_fp_free( fp ); |
|
if ( ret > 0 ) { |
errno = ret; |
CYG_REPORT_RETVAL( -1 ); |
return -1; |
} |
CYG_REPORT_RETVAL( 0 ); |
return 0; |
} // tcflow() |
|
#endif // ifdef CYGPKG_IO_SERIAL_TERMIOS |
|
// EOF termios.c |
/common/termiostty.c
0,0 → 1,902
//========================================================================== |
// |
// termiostty.c |
// |
// POSIX Termios compatible TTY I/O driver |
// |
//========================================================================== |
//####ECOSGPLCOPYRIGHTBEGIN#### |
// ------------------------------------------- |
// This file is part of eCos, the Embedded Configurable Operating System. |
// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc. |
// Copyright (C) 2003 Jonathan Larmour |
// |
// eCos 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 or (at your option) any later version. |
// |
// eCos 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 eCos; if not, write to the Free Software Foundation, Inc., |
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. |
// |
// As a special exception, if other files instantiate templates or use macros |
// or inline functions from this file, or you compile this file and link it |
// with other works to produce a work based on this file, this file does not |
// by itself cause the resulting work to be covered by the GNU General Public |
// License. However the source code for this file must still be made available |
// in accordance with section (3) of the GNU General Public License. |
// |
// This exception does not invalidate any other reasons why a work based on |
// this file might be covered by the GNU General Public License. |
// |
// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc. |
// at http://sources.redhat.com/ecos/ecos-license/ |
// ------------------------------------------- |
//####ECOSGPLCOPYRIGHTEND#### |
//========================================================================== |
//#####DESCRIPTIONBEGIN#### |
// |
// Author(s): jlarmour |
// Contributors: gthomas |
// Date: 2000-07-22 |
// Purpose: Device driver for termios emulation tty I/O, layered on |
// top of serial I/O |
// Description: TODO: Add OPOST support for 80x25 (configurable) windows |
// TODO: Support _POSIX_VDISABLE |
// |
//####DESCRIPTIONEND#### |
// |
//========================================================================== |
|
// CONFIGURATION |
|
#include <pkgconf/io_serial.h> |
|
#ifdef CYGPKG_IO_SERIAL_TERMIOS |
|
// INCLUDES |
|
#include <cyg/infra/cyg_type.h> // Common types |
#include <cyg/infra/cyg_ass.h> // Assertion support |
#include <cyg/infra/cyg_trac.h> // Tracing support |
#include <cyg/io/io.h> |
#include <cyg/io/devtab.h> |
#include <cyg/io/serialio.h> // public serial API |
#include <termios.h> // Termios header |
#include <cyg/hal/drv_api.h> |
#include <stdlib.h> // malloc |
#include <string.h> |
#ifdef CYGSEM_IO_SERIAL_TERMIOS_USE_SIGNALS |
# include <signal.h> |
#endif |
|
//========================================================================== |
// FUNCTION PROTOTYPES |
|
static bool |
termios_init(struct cyg_devtab_entry *tab); |
|
static Cyg_ErrNo |
termios_lookup(struct cyg_devtab_entry **tab, |
struct cyg_devtab_entry *sub_tab, |
const char *name); |
static Cyg_ErrNo |
termios_write(cyg_io_handle_t handle, const void *buf, cyg_uint32 *len); |
static Cyg_ErrNo |
termios_read(cyg_io_handle_t handle, void *buf, cyg_uint32 *len); |
static Cyg_ErrNo |
termios_select(cyg_io_handle_t handle, cyg_uint32 which, CYG_ADDRWORD info); |
static Cyg_ErrNo |
termios_get_config(cyg_io_handle_t handle, cyg_uint32 key, void *buf, |
cyg_uint32 *len); |
static Cyg_ErrNo |
termios_set_config(cyg_io_handle_t handle, cyg_uint32 key, const void *buf, |
cyg_uint32 *len); |
|
//========================================================================== |
// TYPE DEFINITIONS |
|
struct termios_private_info { |
struct termios termios; |
cyg_io_handle_t dev_handle; |
cyg_drv_mutex_t lock; |
cyg_bool init; |
cyg_uint8 *errbuf; |
cyg_uint8 *errbufpos; |
cyg_uint32 errbufsize; |
}; |
|
typedef struct { |
struct termios *termios_p; |
int optact; |
} setattr_struct; |
|
|
//========================================================================== |
// STATIC OBJECTS |
|
static DEVIO_TABLE(termios_devio, |
termios_write, |
termios_read, |
termios_select, |
termios_get_config, |
termios_set_config); |
|
#ifdef CYGPKG_IO_SERIAL_TERMIOS_TERMIOS0 |
static struct termios_private_info termios_private_info0; |
DEVTAB_ENTRY(termios_io0, |
"/dev/termios0", |
CYGDAT_IO_SERIAL_TERMIOS_TERMIOS0_DEV, |
&termios_devio, |
termios_init, |
termios_lookup, |
&termios_private_info0); |
#endif |
|
#ifdef CYGPKG_IO_SERIAL_TERMIOS_TERMIOS1 |
static struct termios_private_info termios_private_info1; |
DEVTAB_ENTRY(termios_io1, |
"/dev/termios1", |
CYGDAT_IO_SERIAL_TERMIOS_TERMIOS1_DEV, |
&termios_devio, |
termios_init, |
termios_lookup, |
&termios_private_info1); |
#endif |
|
#ifdef CYGPKG_IO_SERIAL_TERMIOS_TERMIOS2 |
static struct termios_private_info termios_private_info2; |
DEVTAB_ENTRY(termios_io2, |
"/dev/termios2", |
CYGDAT_IO_SERIAL_TERMIOS_TERMIOS2_DEV, |
&termios_devio, |
termios_init, |
termios_lookup, |
&termios_private_info2); |
#endif |
|
static const cc_t c_cc_init[ NCCS ] = { |
0x04, /* EOF == ^D */ |
0, /* EOL */ |
0x08, /* ERASE = BS ; NB DEL=0x7f */ |
0x03, /* INTR = ^C */ |
0x15, /* KILL = ^U */ |
0, /* MIN = 0 */ |
0x1c, /* QUIT = ^\ */ |
0x1a, /* SUSP = ^Z ; NB ignored in this impl - no job control */ |
0, /* TIME = 0 */ |
#ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_SOFTWARE |
CYGDAT_IO_SERIAL_FLOW_CONTROL_XON_CHAR, |
CYGDAT_IO_SERIAL_FLOW_CONTROL_XOFF_CHAR, |
#else |
17, |
19, |
#endif |
}; |
|
// map eCos bitrates to POSIX bitrates. |
static speed_t ecosbaud2posixbaud[] = { |
0, B50, B75, B110, B134, B150, B200, B300, B600, B1200, B2400, B3600, |
B4800, B7200, B9600, B14400, B19200, B38400, B57600, B115200, B230400 }; |
|
// map POSIX bitrates to eCos bitrates. |
static cyg_serial_baud_rate_t posixbaud2ecosbaud[] = { |
0, CYGNUM_SERIAL_BAUD_50, CYGNUM_SERIAL_BAUD_75, CYGNUM_SERIAL_BAUD_110, |
CYGNUM_SERIAL_BAUD_134_5, CYGNUM_SERIAL_BAUD_150, CYGNUM_SERIAL_BAUD_200, |
CYGNUM_SERIAL_BAUD_300, CYGNUM_SERIAL_BAUD_600, CYGNUM_SERIAL_BAUD_1200, |
CYGNUM_SERIAL_BAUD_1800, CYGNUM_SERIAL_BAUD_2400, CYGNUM_SERIAL_BAUD_3600, |
CYGNUM_SERIAL_BAUD_4800, CYGNUM_SERIAL_BAUD_7200, CYGNUM_SERIAL_BAUD_9600, |
CYGNUM_SERIAL_BAUD_14400, CYGNUM_SERIAL_BAUD_19200, |
CYGNUM_SERIAL_BAUD_38400, CYGNUM_SERIAL_BAUD_57600, |
CYGNUM_SERIAL_BAUD_115200, CYGNUM_SERIAL_BAUD_230400 }; |
|
|
//========================================================================== |
// FUNCTIONS |
|
static __inline__ speed_t |
map_ecosbaud_to_posixbaud( cyg_serial_baud_rate_t ebaud ) |
{ |
if ( ebaud > (sizeof(ecosbaud2posixbaud) / sizeof(speed_t)) ) |
return 0; |
return ecosbaud2posixbaud[ ebaud ]; |
} |
|
static __inline__ cyg_serial_baud_rate_t |
map_posixbaud_to_ecosbaud( speed_t pbaud ) |
{ |
if ( pbaud > (sizeof(posixbaud2ecosbaud)/sizeof(cyg_serial_baud_rate_t)) ) |
return 0; |
return posixbaud2ecosbaud[ pbaud ]; |
} |
|
//========================================================================== |
// real_termios_init is used to initialize the termios structure. This is |
// called at lookup time, and not from termios_init() because it needs |
// to query the serial device which may not be set up yet at that point |
// in termios_init() |
|
#ifdef CYGSEM_IO_SERIAL_TERMIOS_USE_SIGNALS |
# define C_IFLAG_INIT (ICRNL|IGNBRK|BRKINT) |
#else |
# define C_IFLAG_INIT (ICRNL|IGNBRK) |
#endif |
#define C_OFLAG_INIT (ONLCR) |
#define C_CFLAG_INIT (CREAD) |
#define C_LFLAG_INIT (ECHO|ECHOE|ECHOK|ICANON) |
|
static Cyg_ErrNo |
real_termios_init( struct termios_private_info *priv ) |
{ |
Cyg_ErrNo res; |
struct termios *t; |
cyg_serial_info_t dev_conf; |
cyg_serial_buf_info_t dev_buf_conf; |
cyg_uint32 len = sizeof( dev_conf ); |
|
CYG_REPORT_FUNCTYPE("returning %d"); |
CYG_REPORT_FUNCARG1XV( priv ); |
CYG_CHECK_DATA_PTRC( priv ); |
|
t = &priv->termios; |
|
// Get info from driver |
res = cyg_io_get_config( priv->dev_handle, CYG_IO_GET_CONFIG_SERIAL_INFO, |
&dev_conf, &len ); |
if ( ENOERR == res ) { |
len = sizeof( dev_buf_conf ); |
res = cyg_io_get_config( priv->dev_handle, |
CYG_IO_GET_CONFIG_SERIAL_BUFFER_INFO, |
&dev_buf_conf, &len ); |
} |
|
priv->errbuf = (cyg_uint8 *)malloc( dev_buf_conf.rx_bufsize ); |
if ( NULL == priv->errbuf ) |
res = ENOMEM; // FIXME: Are we allowed to do this? |
priv->errbufpos = priv->errbuf; |
priv->errbufsize = dev_buf_conf.rx_bufsize; |
|
if ( ENOERR != res ) { |
CYG_REPORT_RETVAL( res ); |
return res; |
} |
|
// we only support symmetric baud rates |
t->c_ispeed = t->c_ospeed = map_ecosbaud_to_posixbaud( dev_conf.baud ); |
t->c_iflag = C_IFLAG_INIT; |
t->c_oflag = C_OFLAG_INIT; |
t->c_cflag = C_CFLAG_INIT; |
t->c_lflag = C_LFLAG_INIT; |
memcpy( t->c_cc, c_cc_init, sizeof( t->c_cc ) ); |
|
switch ( dev_conf.parity ) { |
case CYGNUM_SERIAL_PARITY_NONE: |
t->c_iflag |= IGNPAR; |
break; |
case CYGNUM_SERIAL_PARITY_ODD: |
t->c_cflag |= PARODD; |
// DROPTHROUGH |
case CYGNUM_SERIAL_PARITY_EVEN: |
t->c_iflag |= PARENB; |
break; |
default: |
CYG_FAIL( "Unsupported default parity" ); |
break; |
} |
|
switch( dev_conf.word_length ) { |
case CYGNUM_SERIAL_WORD_LENGTH_5: |
t->c_cflag |= CS5; |
break; |
case CYGNUM_SERIAL_WORD_LENGTH_6: |
t->c_cflag |= CS6; |
break; |
case CYGNUM_SERIAL_WORD_LENGTH_7: |
t->c_cflag |= CS7; |
break; |
case CYGNUM_SERIAL_WORD_LENGTH_8: |
t->c_cflag |= CS8; |
break; |
default: |
CYG_FAIL( "Unsupported word length" ); |
break; |
} |
|
switch ( dev_conf.stop ) { |
case CYGNUM_SERIAL_STOP_1: |
// Don't need to do anything |
break; |
case CYGNUM_SERIAL_STOP_2: |
t->c_cflag |= CSTOPB; |
break; |
default: |
CYG_FAIL( "Unsupported number of stop bits" ); |
break; |
} |
|
switch ( dev_conf.flags ) { |
case CYGNUM_SERIAL_FLOW_RTSCTS_RX: |
t->c_cflag |= CRTSCTS; |
// drop through |
case CYGNUM_SERIAL_FLOW_XONXOFF_RX: |
t->c_iflag |= IXOFF; |
break; |
case CYGNUM_SERIAL_FLOW_RTSCTS_TX: |
t->c_cflag |= CRTSCTS; |
// drop through |
case CYGNUM_SERIAL_FLOW_XONXOFF_TX: |
t->c_iflag |= IXON; |
break; |
default: |
// Ignore flags we don't grok |
break; |
} |
|
return ENOERR; |
} // real_termios_init() |
|
//========================================================================== |
// set_attr() actually enacts the termios config. We come in here with |
// the mutex in priv locked |
// |
// Possible deviation from standard: POSIX says we should enact which ever |
// bits we can and only return EINVAL when none of them can be performed |
// Rather than tracking whether *none* of them worked, instead we just |
// always claim success. At the very least, setting c_cc is never to |
// fail so I'm not sure if this is really non-standard or not! |
|
static Cyg_ErrNo |
set_attr( struct termios *t, struct termios_private_info *priv ) |
{ |
Cyg_ErrNo res = ENOERR; |
cyg_serial_info_t dev_conf, new_dev_conf; |
cyg_uint32 len = sizeof( dev_conf ); |
cc_t *tempcc = &priv->termios.c_cc[0]; |
struct termios *ptermios = &priv->termios; |
|
// Get info from driver |
res = cyg_io_get_config( priv->dev_handle, CYG_IO_GET_CONFIG_SERIAL_INFO, |
&dev_conf, &len ); |
|
if ( ENOERR != res ) |
return res; |
|
// We need to set up each facet of config to change one by one because |
// POSIX says we should try and change as much of the config as possible |
// This is tedious and has to be done by steam :-( |
|
if ( t->c_ospeed != ptermios->c_ospeed ) { |
new_dev_conf = dev_conf; |
new_dev_conf.baud = map_posixbaud_to_ecosbaud( t->c_ospeed ); |
if ( 0 != new_dev_conf.baud ) { |
len = sizeof( new_dev_conf ); |
res = cyg_io_set_config( priv->dev_handle, |
CYG_IO_SET_CONFIG_SERIAL_INFO, |
&new_dev_conf, &len ); |
if ( ENOERR == res ) { |
// It worked, so update dev_conf to reflect the new state |
dev_conf.baud = new_dev_conf.baud; |
// and termios |
ptermios->c_ispeed = t->c_ospeed; |
ptermios->c_ospeed = t->c_ospeed; |
} |
} |
} |
|
if ( (t->c_cflag & CSTOPB) != (ptermios->c_cflag & CSTOPB) ) { |
new_dev_conf = dev_conf; |
if ( t->c_cflag & CSTOPB ) |
new_dev_conf.stop = CYGNUM_SERIAL_STOP_2; |
else |
new_dev_conf.stop = CYGNUM_SERIAL_STOP_1; |
|
len = sizeof( new_dev_conf ); |
res = cyg_io_set_config( priv->dev_handle, |
CYG_IO_SET_CONFIG_SERIAL_INFO, |
&new_dev_conf, &len ); |
if ( ENOERR == res ) { |
// It worked, so update dev_conf to reflect the new state |
dev_conf.stop = new_dev_conf.stop; |
// and termios |
ptermios->c_cflag &= ~CSTOPB; |
ptermios->c_cflag |= t->c_cflag & CSTOPB; |
} |
} |
|
if ( ((t->c_cflag & PARENB) != (ptermios->c_cflag & PARENB)) || |
((t->c_cflag & PARODD) != (ptermios->c_cflag & PARODD)) ) { |
new_dev_conf = dev_conf; |
if ( t->c_cflag & PARENB ) |
if ( t->c_cflag & PARODD ) |
new_dev_conf.parity = CYGNUM_SERIAL_PARITY_ODD; |
else |
new_dev_conf.parity = CYGNUM_SERIAL_PARITY_EVEN; |
else |
new_dev_conf.parity = CYGNUM_SERIAL_PARITY_NONE; |
|
len = sizeof( new_dev_conf ); |
res = cyg_io_set_config( priv->dev_handle, |
CYG_IO_SET_CONFIG_SERIAL_INFO, |
&new_dev_conf, &len ); |
if ( ENOERR == res ) { |
// It worked, so update dev_conf to reflect the new state |
dev_conf.parity = new_dev_conf.parity; |
// and termios |
ptermios->c_cflag &= ~(PARENB|PARODD); |
ptermios->c_cflag |= t->c_cflag & (PARENB|PARODD); |
} |
} |
|
if ( (t->c_cflag & CSIZE) != (ptermios->c_cflag & CSIZE) ) { |
new_dev_conf = dev_conf; |
switch ( t->c_cflag & CSIZE ) { |
case CS5: |
new_dev_conf.word_length = CYGNUM_SERIAL_WORD_LENGTH_5; |
break; |
case CS6: |
new_dev_conf.word_length = CYGNUM_SERIAL_WORD_LENGTH_6; |
break; |
case CS7: |
new_dev_conf.word_length = CYGNUM_SERIAL_WORD_LENGTH_7; |
break; |
case CS8: |
new_dev_conf.word_length = CYGNUM_SERIAL_WORD_LENGTH_8; |
break; |
} |
|
len = sizeof( new_dev_conf ); |
res = cyg_io_set_config( priv->dev_handle, |
CYG_IO_SET_CONFIG_SERIAL_INFO, |
&new_dev_conf, &len ); |
if ( ENOERR == res ) { |
// It worked, so update dev_conf to reflect the new state |
dev_conf.word_length = new_dev_conf.word_length; |
// and termios |
ptermios->c_cflag &= ~CSIZE; |
ptermios->c_cflag |= t->c_cflag & CSIZE; |
} |
} |
|
if ( (t->c_cflag & IXOFF) != (ptermios->c_cflag & IXOFF) ) { |
new_dev_conf = dev_conf; |
new_dev_conf.flags &= |
~(CYGNUM_SERIAL_FLOW_XONXOFF_RX|CYGNUM_SERIAL_FLOW_RTSCTS_RX); |
if ( t->c_cflag & IXOFF ) |
if ( t->c_cflag & CRTSCTS) |
new_dev_conf.flags |= CYGNUM_SERIAL_FLOW_RTSCTS_RX; |
else |
new_dev_conf.flags |= CYGNUM_SERIAL_FLOW_XONXOFF_RX; |
else |
new_dev_conf.flags |= CYGNUM_SERIAL_FLOW_NONE; |
|
len = sizeof( new_dev_conf ); |
res = cyg_io_set_config( priv->dev_handle, |
CYG_IO_SET_CONFIG_SERIAL_INFO, |
&new_dev_conf, &len ); |
if ( ENOERR == res ) { |
// It worked, so update dev_conf to reflect the new state |
dev_conf.flags = new_dev_conf.flags; |
// and termios |
ptermios->c_cflag &= ~(IXOFF|CRTSCTS); |
ptermios->c_cflag |= t->c_cflag & (IXOFF|CRTSCTS); |
} |
} |
|
if ( (t->c_cflag & IXON) != (ptermios->c_cflag & IXON) ) { |
new_dev_conf = dev_conf; |
new_dev_conf.flags &= |
~(CYGNUM_SERIAL_FLOW_XONXOFF_TX|CYGNUM_SERIAL_FLOW_RTSCTS_TX); |
if ( t->c_cflag & IXON ) |
if ( t->c_cflag & CRTSCTS) |
new_dev_conf.flags |= CYGNUM_SERIAL_FLOW_RTSCTS_TX; |
else |
new_dev_conf.flags |= CYGNUM_SERIAL_FLOW_XONXOFF_TX; |
else |
new_dev_conf.flags |= CYGNUM_SERIAL_FLOW_NONE; |
|
len = sizeof( new_dev_conf ); |
res = cyg_io_set_config( priv->dev_handle, |
CYG_IO_SET_CONFIG_SERIAL_INFO, |
&new_dev_conf, &len ); |
if ( ENOERR == res ) { |
// It worked, so update dev_conf to reflect the new state |
dev_conf.flags = new_dev_conf.flags; |
// and termios |
ptermios->c_cflag &= ~(IXON|CRTSCTS); |
ptermios->c_cflag |= t->c_cflag & (IXON|CRTSCTS); |
} |
} |
|
// Input/Output processing flags can just be set as we grok them all |
// with few exceptions (see lflags below) |
ptermios->c_iflag &= ~(BRKINT|ICRNL|IGNBRK|IGNCR|IGNPAR|INLCR|INPCK| |
ISTRIP|PARMRK); |
ptermios->c_iflag |= t->c_iflag & ( |
#ifdef CYGSEM_IO_SERIAL_TERMIOS_USE_SIGNALS |
BRKINT| |
#endif |
ICRNL|IGNBRK|IGNCR|IGNPAR| |
INLCR|INPCK|ISTRIP|PARMRK ); |
|
ptermios->c_oflag &= ~(OPOST|ONLCR); |
ptermios->c_oflag |= t->c_oflag & (OPOST|ONLCR); |
|
ptermios->c_cflag &= ~(CLOCAL|CREAD|HUPCL); |
ptermios->c_cflag |= t->c_cflag & (CLOCAL|CREAD|HUPCL); |
|
ptermios->c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ICANON| |
IEXTEN|ISIG|NOFLSH|TOSTOP); |
// Note we don't support IEXTEN nor TOSTOP so we don't set them |
ptermios->c_lflag |= t->c_lflag & (ECHO|ECHOE|ECHOK|ECHONL|ICANON| |
#ifdef CYGSEM_IO_SERIAL_TERMIOS_USE_SIGNALS |
ISIG| |
#endif |
NOFLSH); |
|
// control characters. We don't support changing of VSTART, VSTOP, |
// VTIME or VSUSP though |
tempcc[VEOF] = t->c_cc[VEOF]; |
tempcc[VEOL] = t->c_cc[VEOL]; |
tempcc[VERASE] = t->c_cc[VERASE]; |
tempcc[VINTR] = t->c_cc[VINTR]; |
tempcc[VKILL] = t->c_cc[VKILL]; |
tempcc[VMIN] = t->c_cc[VMIN]; |
tempcc[VQUIT] = t->c_cc[VQUIT]; |
|
return res; |
} |
|
//========================================================================== |
|
static bool |
termios_init(struct cyg_devtab_entry *tab) |
{ |
// can't initialize the termios structure because we can't |
// query the serial driver yet. Wait until lookup time. |
|
return true; |
} // termios_init() |
|
//========================================================================== |
|
static Cyg_ErrNo |
termios_lookup(struct cyg_devtab_entry **tab, |
struct cyg_devtab_entry *sub_tab, |
const char *name) |
{ |
cyg_io_handle_t chan = (cyg_io_handle_t)sub_tab; |
struct termios_private_info *priv = |
(struct termios_private_info *)(*tab)->priv; |
Cyg_ErrNo err = ENOERR; |
|
if ( !priv->init ) { |
cyg_drv_mutex_lock( &priv->lock ); |
if ( !priv->init ) { // retest as we may have been pre-empted |
priv->dev_handle = chan; |
err = real_termios_init( priv ); |
} |
cyg_drv_mutex_unlock( &priv->lock ); |
} |
return err; |
} |
|
//========================================================================== |
|
#define WRITE_BUFSIZE 100 // FIXME: ->CDL |
// #define MAX_CANON 64 FIXME: relevance? |
|
|
static Cyg_ErrNo |
termios_write(cyg_io_handle_t handle, const void *_buf, cyg_uint32 *len) |
{ |
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *)handle; |
struct termios_private_info *priv = (struct termios_private_info *)t->priv; |
cyg_io_handle_t chan = (cyg_io_handle_t)priv->dev_handle; |
cyg_int32 xbufsize, input_bytes_read; |
cyg_uint8 xbuf[WRITE_BUFSIZE]; |
cyg_uint8 *buf = (cyg_uint8 *)_buf; |
Cyg_ErrNo res; |
|
xbufsize = input_bytes_read = 0; |
while (input_bytes_read++ < *len) { |
if ( (*buf == '\n') && (priv->termios.c_oflag & (OPOST|ONLCR)) ) { |
xbuf[xbufsize++] = '\r'; |
} |
xbuf[xbufsize++] = *buf; |
if ((xbufsize >= (WRITE_BUFSIZE-1)) || (input_bytes_read == *len) || |
(*buf == '\n')) |
{ |
cyg_int32 size = xbufsize; |
res = cyg_io_write(chan, xbuf, &size); |
if (res != ENOERR) { |
*len = input_bytes_read - (xbufsize - size); |
return res; |
} |
xbufsize = 0; |
} |
buf++; |
} |
// Everything sent, so *len is correct. |
return ENOERR; |
} |
|
|
//========================================================================== |
|
static Cyg_ErrNo |
termios_read(cyg_io_handle_t handle, void *_buf, cyg_uint32 *len) |
{ |
cyg_devtab_entry_t *dt = (cyg_devtab_entry_t *)handle; |
struct termios_private_info *priv = (struct termios_private_info *)dt->priv; |
cyg_io_handle_t chan = (cyg_io_handle_t)priv->dev_handle; |
struct termios *t = &priv->termios; |
cyg_uint32 clen; |
cyg_uint32 size; |
Cyg_ErrNo res; |
cyg_uint8 c; |
cyg_uint8 *buf = (cyg_uint8 *)_buf; |
cyg_bool discardc; // should c be discarded (not read, not printed) |
cyg_bool returnnow = false; // return back to user after this char |
|
// if receiver off |
if (0 == (t->c_cflag & CREAD) ) { |
*len = 0; |
return -EINVAL; |
} |
|
size = 0; |
if ( 0 == (t->c_lflag & ICANON) ) { |
// In non-canonical mode we return the min of *len and the |
// number of bytes available |
// So we query the driver for how many bytes are available - this |
// guarantees we won't block |
cyg_serial_buf_info_t dev_buf_conf; |
cyg_uint32 dbc_len = sizeof( dev_buf_conf ); |
res = cyg_io_get_config( chan, |
CYG_IO_GET_CONFIG_SERIAL_BUFFER_INFO, |
&dev_buf_conf, &dbc_len ); |
CYG_ASSERT( res == ENOERR, "Query buffer status failed!" ); |
*len = *len < dev_buf_conf.rx_count ? *len : dev_buf_conf.rx_count; |
} // if |
|
while (!returnnow && size < *len) { |
clen = 1; |
discardc = false; |
res = cyg_io_read(chan, &c, &clen); |
if (res != ENOERR) { |
*len = size; |
return res; |
} |
|
// lock to prevent termios getting corrupted while we read from it |
cyg_drv_mutex_lock( &priv->lock ); |
|
if ( t->c_iflag & ISTRIP ) |
c &= 0x7f; |
|
// canonical mode: erase, kill, and newline processing |
if ( t->c_lflag & ICANON ) { |
if ( t->c_cc[ VERASE ] == c ) { |
discardc = true; |
// erase on display? |
if ( (t->c_lflag & ECHO) && (t->c_lflag & ECHOE) ) { |
cyg_uint8 erasebuf[3]; |
erasebuf[0] = erasebuf[2] = t->c_cc[ VERASE ]; |
erasebuf[1] = ' '; |
clen = sizeof(erasebuf); |
// FIXME: what about error or non-blocking? |
cyg_io_write(chan, erasebuf, &clen); |
} |
if ( size ) |
size--; |
} // if |
else if ( t->c_cc[ VKILL ] == c ) { |
// kill line on display? |
if ( (t->c_lflag & ECHO) && (t->c_lflag & ECHOK) ) { |
|
// we could try and be more efficient here and |
// output a stream of erases, and then a stream |
// of spaces and then more erases. But this is poor |
// because on a slow terminal the user will see characters |
// delete from the middle forward in chunks! |
// But at least we try and chunk up sets of writes |
cyg_uint8 erasebuf[30]; |
cyg_uint8 erasechunks; |
cyg_uint8 i; |
|
erasechunks = size < (sizeof(erasebuf)/3) ? |
size : (sizeof(erasebuf)/3); |
|
for (i=0; i<erasechunks; i++) { |
erasebuf[i*3] = erasebuf[i*3+2] = t->c_cc[ VERASE ]; |
erasebuf[i*3+1] = ' '; |
} |
|
while( size ) { |
cyg_uint8 j; |
|
j = size < (sizeof(erasebuf)/3) ? |
size : (sizeof(erasebuf)/3); |
clen = (j*3); |
// FIXME: what about error or non-blocking? |
cyg_io_write( chan, erasebuf, &clen ); |
size -= j; |
} |
} else |
size = 0; |
discardc = true; |
} // else if |
// CR |
else if ( '\r' == c ) { |
if ( t->c_iflag & IGNCR ) |
discardc = true; |
else if ( t->c_iflag & ICRNL ) |
c = '\n'; |
} |
// newlines or similar. |
// Note: not an else if to catch CRNL conversion |
if ( (t->c_cc[ VEOF ] == c) || (t->c_cc[ VEOL ] == c) || |
('\n' == c) ) { |
if ( t->c_cc[ VEOF ] == c ) |
discardc = true; |
if ( t->c_lflag & ECHONL ) { // don't check ECHO in this case |
clen = 1; |
// FIXME: what about error or non-blocking? |
// FIXME: what if INLCR is set? |
cyg_io_write( chan, "\n", &clen ); |
} |
if ( t->c_iflag & INLCR ) |
c = '\r'; |
returnnow = true; // FIXME: true even for INLCR? |
} // else if |
} else { // non-canonical mode |
if ( t->c_cc[ VMIN ] && (size+1 >= t->c_cc[ VMIN ]) ) |
returnnow = true; |
} // else |
|
#ifdef CYGSEM_IO_SERIAL_TERMIOS_USE_SIGNALS |
if ( (t->c_lflag & ISIG) && (t->c_cc[ VINTR ] == c) ) { |
discardc = true; |
if ( 0 == (t->c_lflag & NOFLSH) ) |
size = 0; |
// raise could be a non-local jump - we should unlock mutex |
cyg_drv_mutex_unlock( &priv->lock ); |
|
// FIXME: what if raise returns != 0? |
raise( SIGINT ); |
cyg_drv_mutex_lock( &priv->lock ); |
} |
|
if ( (t->c_lflag & ISIG) && (t->c_cc[ VQUIT ] == c) ) { |
discardc = true; |
if ( 0 == (t->c_lflag & NOFLSH) ) |
size = 0; |
// raise could be a non-local jump - we should unlock mutex |
cyg_drv_mutex_unlock( &priv->lock ); |
|
// FIXME: what if raise returns != 0? |
raise( SIGQUIT ); |
cyg_drv_mutex_lock( &priv->lock ); |
} |
#endif |
if (!discardc) { |
buf[size++] = c; |
if ( t->c_lflag & ECHO ) { |
clen = 1; |
// FIXME: what about error or non-blocking? |
termios_write( handle, &c, &clen ); |
} |
} |
cyg_drv_mutex_unlock( &priv->lock ); |
} // while |
|
*len = size; |
return ENOERR; |
} |
|
|
//========================================================================== |
|
static cyg_bool |
termios_select(cyg_io_handle_t handle, cyg_uint32 which, CYG_ADDRWORD info) |
{ |
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *)handle; |
struct termios_private_info *priv = (struct termios_private_info *)t->priv; |
cyg_io_handle_t chan = (cyg_io_handle_t)priv->dev_handle; |
|
// Just pass it on to next driver level |
return cyg_io_select( chan, which, info ); |
} |
|
|
//========================================================================== |
|
static Cyg_ErrNo |
termios_get_config(cyg_io_handle_t handle, cyg_uint32 key, void *buf, |
cyg_uint32 *len) |
{ |
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *)handle; |
struct termios_private_info *priv = (struct termios_private_info *)t->priv; |
cyg_io_handle_t chan = (cyg_io_handle_t)priv->dev_handle; |
Cyg_ErrNo res = ENOERR; |
|
switch (key) { |
case CYG_IO_GET_CONFIG_TERMIOS: |
{ |
if ( *len < sizeof(struct termios) ) { |
return -EINVAL; |
} |
cyg_drv_mutex_lock( &priv->lock ); |
*(struct termios *)buf = priv->termios; |
cyg_drv_mutex_unlock( &priv->lock ); |
*len = sizeof(struct termios); |
} |
break; |
default: // Assume this is a 'serial' driver control |
res = cyg_io_get_config(chan, key, buf, len); |
} // switch |
return res; |
} |
|
|
//========================================================================== |
|
|
static Cyg_ErrNo |
termios_set_config(cyg_io_handle_t handle, cyg_uint32 key, const void *buf, cyg_uint32 *len) |
{ |
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *)handle; |
struct termios_private_info *priv = (struct termios_private_info *)t->priv; |
cyg_io_handle_t chan = (cyg_io_handle_t)priv->dev_handle; |
Cyg_ErrNo res = ENOERR; |
|
switch (key) { |
case CYG_IO_SET_CONFIG_TERMIOS: |
{ |
setattr_struct *attr = (setattr_struct *)buf; |
int optact = attr->optact; |
|
if ( *len != sizeof( *attr ) ) { |
return -EINVAL; |
} |
|
CYG_ASSERT( (optact == TCSAFLUSH) || (optact == TCSADRAIN) || |
(optact == TCSANOW), "Invalid optact" ); |
|
cyg_drv_mutex_lock( &priv->lock ); |
|
if ( ( TCSAFLUSH == optact ) || |
( TCSADRAIN == optact ) ) { |
res = cyg_io_get_config( chan, |
CYG_IO_GET_CONFIG_SERIAL_OUTPUT_DRAIN, |
NULL, NULL ); |
CYG_ASSERT( ENOERR == res, "Drain request failed" ); |
} |
if ( TCSAFLUSH == optact ) { |
res = cyg_io_get_config( chan, |
CYG_IO_GET_CONFIG_SERIAL_INPUT_FLUSH, |
NULL, NULL ); |
CYG_ASSERT( ENOERR == res, "Flush request failed" ); |
} |
|
res = set_attr( attr->termios_p, priv ); |
cyg_drv_mutex_unlock( &priv->lock ); |
return res; |
} |
default: // Pass on to serial driver |
res = cyg_io_set_config(chan, key, buf, len); |
} |
return res; |
} |
|
|
//========================================================================== |
|
#endif // ifdef CYGPKG_IO_SERIAL_TERMIOS |
|
// EOF termiostty.c |
/common/haldiag.c
0,0 → 1,138
//========================================================================== |
// |
// io/serial/common/haldiag.c |
// |
// Serial I/O interface module using HAL I/O routines |
// |
//========================================================================== |
//####ECOSGPLCOPYRIGHTBEGIN#### |
// ------------------------------------------- |
// This file is part of eCos, the Embedded Configurable Operating System. |
// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc. |
// |
// eCos 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 or (at your option) any later version. |
// |
// eCos 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 eCos; if not, write to the Free Software Foundation, Inc., |
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. |
// |
// As a special exception, if other files instantiate templates or use macros |
// or inline functions from this file, or you compile this file and link it |
// with other works to produce a work based on this file, this file does not |
// by itself cause the resulting work to be covered by the GNU General Public |
// License. However the source code for this file must still be made available |
// in accordance with section (3) of the GNU General Public License. |
// |
// This exception does not invalidate any other reasons why a work based on |
// this file might be covered by the GNU General Public License. |
// |
// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc. |
// at http://sources.redhat.com/ecos/ecos-license/ |
// ------------------------------------------- |
//####ECOSGPLCOPYRIGHTEND#### |
//========================================================================== |
//#####DESCRIPTIONBEGIN#### |
// |
// Author(s): gthomas |
// Contributors: gthomas |
// Date: 1999-02-04 |
// Purpose: HAL/diag serial driver |
// Description: |
// |
//####DESCRIPTIONEND#### |
// |
//========================================================================== |
|
#include <pkgconf/io.h> |
#include <pkgconf/io_serial.h> |
#ifdef CYGPKG_IO_SERIAL_HALDIAG |
#include <cyg/io/io.h> |
#include <cyg/io/devtab.h> |
#include <cyg/io/serial.h> |
#include <cyg/infra/diag.h> |
#include <cyg/hal/hal_diag.h> |
|
static bool haldiag_init(struct cyg_devtab_entry *tab); |
static bool haldiag_putc(serial_channel *chan, unsigned char c); |
static unsigned char haldiag_getc(serial_channel *chan); |
static Cyg_ErrNo haldiag_set_config(serial_channel *chan, cyg_uint32 key, |
const void *xbuf, cyg_uint32 *len); |
|
static SERIAL_FUNS(haldiag_funs, |
haldiag_putc, |
haldiag_getc, |
haldiag_set_config, |
0, // start xmit - not used |
0 // stop xmit - not used |
); |
|
static int _no_data; |
static SERIAL_CHANNEL(haldiag_channel0, |
haldiag_funs, |
_no_data, |
CYG_SERIAL_BAUD_DEFAULT, |
CYG_SERIAL_STOP_DEFAULT, |
CYG_SERIAL_PARITY_DEFAULT, |
CYG_SERIAL_WORD_LENGTH_DEFAULT, |
CYG_SERIAL_FLAGS_DEFAULT |
); |
DEVTAB_ENTRY(haldiag_io0, |
"/dev/haldiag", |
0, // Does not depend on a lower level interface |
&cyg_io_serial_devio, |
haldiag_init, |
0, // No initialization/lookup needed |
&haldiag_channel0); |
|
static void |
haldiag_config_port(serial_channel *chan) |
{ |
} |
|
static bool |
haldiag_init(struct cyg_devtab_entry *tab) |
{ |
serial_channel *chan = (serial_channel *)tab->priv; |
#ifdef CYGDBG_IO_INIT |
diag_printf("HAL/diag SERIAL init\n"); |
#endif |
haldiag_config_port(chan); |
return true; |
} |
|
// Return 'true' if character is sent to device |
static bool |
haldiag_putc(serial_channel *chan, unsigned char c) |
{ |
HAL_DIAG_WRITE_CHAR(c); |
return true; |
} |
|
static unsigned char |
haldiag_getc(serial_channel *chan) |
{ |
unsigned char c; |
HAL_DIAG_READ_CHAR(c); |
return c; |
} |
|
static Cyg_ErrNo |
haldiag_set_config(serial_channel *chan, cyg_uint32 key, const void *xbuf, |
cyg_uint32 *len) |
{ |
switch (key) { |
case CYG_IO_SET_CONFIG_SERIAL_INFO: |
diag_printf("%s\n", __FUNCTION__); |
return ENOERR; |
default: |
return -EINVAL; |
} |
} |
#endif // CYGPKG_IO_SERIAL_HALDIAG |