Mercurial > flash_v2
diff packages/io/serial/current/src/common/serial.c @ 115:6ed91473a1cd ecos-sw-2000-08-21
Merge from eCos master repository on 2000-08-21-22:40:54-BST
| author | jlarmour |
|---|---|
| date | Fri, 25 Aug 2000 17:32:38 +0000 |
| parents | 7f461c3a372c |
| children | 0c2b7be0d798 |
line wrap: on
line diff
--- a/packages/io/serial/current/src/common/serial.c +++ b/packages/io/serial/current/src/common/serial.c @@ -32,7 +32,7 @@ //#####DESCRIPTIONBEGIN#### // // Author(s): gthomas -// Contributors: gthomas, grante +// Contributors: gthomas, grante, jlarmour, jskov // Date: 1999-02-04 // Purpose: Top level serial driver // Description: @@ -51,12 +51,14 @@ 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 ); @@ -64,13 +66,29 @@ DEVIO_TABLE(cyg_io_serial_devio, 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 CYGINT_IO_SERIAL_BLOCK_TRANSFER +#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 bool serial_data_rcv_req(serial_channel *chan, int avail, int* space_avail, unsigned char** space); static void serial_data_rcv_done(serial_channel *chan); static bool serial_data_xmt_req(serial_channel *chan, int space, int* chars_avail, unsigned char** chars); static void serial_data_xmt_done(serial_channel *chan); +# ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_HW +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, @@ -79,13 +97,116 @@ SERIAL_CALLBACKS(cyg_io_serial_callbacks 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) @@ -104,22 +225,57 @@ serial_init(serial_channel *chan) 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", chan->in_cbuf.data, chan->in_cbuf.len); + diag_printf("Set input buffer - buf: %x len: %d\n", cbuf->data, cbuf->len); #endif - chan->in_cbuf.waiting = false; - chan->in_cbuf.abort = false; + cbuf->waiting = false; + cbuf->abort = false; #ifdef CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING - chan->in_cbuf.blocking = true; + cbuf->blocking = true; +#endif +#ifdef CYGPKG_IO_SERIAL_SELECT_SUPPORT + cyg_selinit( &cbuf->selinfo ); #endif - cyg_drv_mutex_init(&chan->in_cbuf.lock); - cyg_drv_cond_init(&chan->in_cbuf.wait, &chan->in_cbuf.lock); +#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 + 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) { @@ -132,13 +288,19 @@ serial_write(cyg_io_handle_t handle, con cbuf_t *cbuf = &chan->out_cbuf; Cyg_ErrNo res = ENOERR; + cyg_drv_mutex_lock(&cbuf->lock); cbuf->abort = false; - cyg_drv_mutex_lock(&cbuf->lock); 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 { @@ -146,10 +308,14 @@ serial_write(cyg_io_handle_t handle, con while (size > 0) { next = cbuf->put + 1; if (next == cbuf->len) next = 0; - if (next == cbuf->get) { + if (cbuf->nb == cbuf->len) { cbuf->waiting = true; // Buffer full - wait for space - (funs->start_xmit)(chan); // Make sure xmit is running +#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 @@ -162,7 +328,8 @@ serial_write(cyg_io_handle_t handle, con } #endif // CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING cbuf->pending += size; // Have this much more to send [eventually] - cyg_drv_cond_wait(&cbuf->wait); + if( !cyg_drv_cond_wait(&cbuf->wait) ) + cbuf->abort = true; cbuf->pending -= size; } if (cbuf->abort) { @@ -176,16 +343,23 @@ serial_write(cyg_io_handle_t handle, con } else { cbuf->data[cbuf->put++] = *buf++; cbuf->put = next; + cbuf->nb++; size--; // Only count if actually sent! } } - (funs->start_xmit)(chan); // Start output as necessary +#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) { @@ -203,20 +377,44 @@ serial_read(cyg_io_handle_t handle, void externC cyg_tick_count_t cyg_current_time(void); #endif // CYGDBG_DIAG_BUF + cyg_drv_mutex_lock(&cbuf->lock); cbuf->abort = false; - cyg_drv_mutex_lock(&cbuf->lock); if (cbuf->len == 0) { // Non interrupt driven (i.e. polled) operation while (size++ < *len) { - *buf++ = (funs->getc)(chan); + 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->get != cbuf->put) { + 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 @@ -228,19 +426,20 @@ serial_read(cyg_io_handle_t handle, void #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; + 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 - cyg_drv_cond_wait(&cbuf->wait); + 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; + 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! @@ -267,6 +466,66 @@ serial_read(cyg_io_handle_t handle, void 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) { @@ -298,20 +557,14 @@ serial_get_config(cyg_io_handle_t handle p = (cyg_serial_buf_info_t *)xbuf; p->rx_bufsize = in_cbuf->len; - if (p->rx_bufsize) { - p->rx_count = in_cbuf->put - in_cbuf->get; - if (p->rx_count < 0) - p->rx_count += p->rx_bufsize; - } + 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->put - out_cbuf->get; - if (p->tx_count < 0) - p->tx_count += p->tx_bufsize; - } + if (p->tx_bufsize) + p->tx_count = out_cbuf->nb; else p->tx_count = 0; } @@ -322,9 +575,10 @@ serial_get_config(cyg_io_handle_t handle 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->get != out_cbuf->put)) { + while (out_cbuf->pending || (out_cbuf->nb > 0)) { out_cbuf->waiting = true; - cyg_drv_cond_wait(&out_cbuf->wait); + if(!cyg_drv_cond_wait(&out_cbuf->wait) ) + res = -EINTR; } cyg_drv_dsr_unlock(); cyg_drv_mutex_unlock(&out_cbuf->lock); @@ -340,7 +594,7 @@ serial_get_config(cyg_io_handle_t handle cyg_drv_cond_signal(&in_cbuf->wait); in_cbuf->waiting = false; } - in_cbuf->get = in_cbuf->put; // Flush buffered input + 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; @@ -363,8 +617,8 @@ serial_get_config(cyg_io_handle_t handle 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->get != out_cbuf->put) { - out_cbuf->get = out_cbuf->put; // Empties queue! + 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) { @@ -377,14 +631,14 @@ serial_get_config(cyg_io_handle_t handle break; #ifdef CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING - case CYG_IO_GET_CONFIG_SERIAL_READ_BLOCKING: + 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_SERIAL_WRITE_BLOCKING: + case CYG_IO_GET_CONFIG_WRITE_BLOCKING: if (*len < sizeof(cyg_uint32)) { return -EINVAL; } @@ -398,6 +652,9 @@ serial_get_config(cyg_io_handle_t handle return res; } + +// --------------------------------------------------------------------------- + static Cyg_ErrNo serial_set_config(cyg_io_handle_t handle, cyg_uint32 key, const void *xbuf, cyg_uint32 *len) { @@ -409,38 +666,127 @@ serial_set_config(cyg_io_handle_t handle cbuf_t *in_cbuf = &chan->in_cbuf; #endif serial_funs *funs = chan->funs; - cyg_serial_info_t *buf = (cyg_serial_info_t *)xbuf; switch (key) { - case CYG_IO_SET_CONFIG_SERIAL_INFO: - if (*len != sizeof(cyg_serial_info_t)) { - return -EINVAL; - } - if ((funs->set_config)(chan, buf) != true) { - // Device is not happy with paramters or they cannot be set - res = -EINVAL; - } - break; #ifdef CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING - case CYG_IO_SET_CONFIG_SERIAL_READ_BLOCKING: + 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_SERIAL_WRITE_BLOCKING: + 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: - res = -EINVAL; + // pass down to lower layers + return (funs->set_config)(chan, key, xbuf, len); } return res; } +// --------------------------------------------------------------------------- + static void serial_xmt_char(serial_channel *chan) { @@ -449,47 +795,105 @@ serial_xmt_char(serial_channel *chan) unsigned char c; int space; - while (cbuf->get != cbuf->put) { +#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; - if (cbuf->waiting) { - // See if there is now enough room to restart writer - space = (cbuf->len + cbuf->get) - cbuf->put; - if (space > cbuf->len) space -= cbuf->len; - if (space >= cbuf->low_water) { + 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 } - } else { 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 0 - // FIXME: add error handling - int space; - // Check for overflow - space = (cbuf->len + cbuf->put - cbuf->get) % cbuf->len; - if (space == (cbuf->len - 1)) - ; // full since put==get means empty +#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 - cbuf->data[cbuf->put++] = c; - if (cbuf->put == cbuf->len) cbuf->put = 0; + // 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++; + } if (cbuf->waiting) { #ifdef XX_CYGDBG_DIAG_BUF extern int enable_diag_uart; @@ -508,8 +912,28 @@ serial_rcv_char(serial_channel *chan, un } //---------------------------------------------------------------------------- +// 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. -#ifdef CYGINT_IO_SERIAL_BLOCK_TRANSFER +#if CYGINT_IO_SERIAL_BLOCK_TRANSFER static bool serial_data_rcv_req(serial_channel *chan, int avail, @@ -519,33 +943,26 @@ serial_data_rcv_req(serial_channel *chan int gap; // Check for space - gap = (cbuf->len + cbuf->put - cbuf->get) % cbuf->len; - if (gap == (cbuf->len - 1)) - return false; // full since put==get means empty + gap = cbuf->nb; + if (gap == cbuf->len) + return false; // full 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 - 1; + 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 - 1; + 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 get is at the beginning of the buffer, we cannot - // fill the last character, since that would make put==get - // implying empty. In this case we must offer last chunk-1 only. - // GxxxxxP....... - if (cbuf->get == 0) - gap--; } } @@ -554,6 +971,7 @@ serial_data_rcv_req(serial_channel *chan *space_avail = gap; *space = &cbuf->data[cbuf->put]; cbuf->put += gap; + cbuf->nb += gap; if (cbuf->put == cbuf->len) cbuf->put = 0; @@ -568,6 +986,11 @@ serial_data_rcv_done(serial_channel *cha cbuf->waiting = false; cyg_drv_cond_signal(&cbuf->wait); } +#ifdef CYGPKG_IO_SERIAL_SELECT_SUPPORT + // Wake up any pending selectors if we have + // put some data into a previously empty buffer. + cyg_selwakeup( &cbuf->selinfo ); +#endif } static bool @@ -581,11 +1004,12 @@ serial_data_xmt_req(serial_channel *chan // 0: no data // negative: xxxxP.....Gxxx [offer last chunk only] // positive: ..GxxxxxP..... - avail = cbuf->put - cbuf->get; - if (0 == avail) + if (0 == cbuf->nb) return false; - if (avail < 0) { + 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 @@ -593,6 +1017,7 @@ serial_data_xmt_req(serial_channel *chan *chars_avail = avail; *chars = &cbuf->data[cbuf->get]; cbuf->get += avail; + cbuf->nb -= avail; if (cbuf->get == cbuf->len) cbuf->get = 0; @@ -608,15 +1033,21 @@ serial_data_xmt_done(serial_channel *cha (funs->stop_xmit)(chan); // Done with transmit - if (cbuf->waiting) { - // See if there is now enough room to restart writer - space = (cbuf->len + cbuf->get) - cbuf->put; - if (space > cbuf->len) space -= cbuf->len; - if (space >= cbuf->low_water) { + // 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 } } #endif // CYGINT_IO_SERIAL_BLOCK_TRANSFER + +// --------------------------------------------------------------------------- + +// EOF serial.c
