Mercurial > ecos-v3_0-branch
view packages/io/serial/current/src/common/serial.c @ 88:afdeb44241de ecos-sw-2000-05-12
Merge from eCos master repository on 2000-05-12-07:47:04-BST
| author | jlarmour |
|---|---|
| date | Fri, 12 May 2000 17:53:32 +0000 |
| parents | 6512add4f5d6 |
| children | 7f461c3a372c |
line wrap: on
line source
//========================================================================== // // io/serial/common/serial.c // // High level serial driver // //========================================================================== //####COPYRIGHTBEGIN#### // // ------------------------------------------- // The contents of this file are subject to the Red Hat eCos Public License // Version 1.1 (the "License"); you may not use this file except in // compliance with the License. You may obtain a copy of the License at // http://www.redhat.com/ // // Software distributed under the License is distributed on an "AS IS" // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the // License for the specific language governing rights and limitations under // the License. // // The Original Code is eCos - Embedded Configurable Operating System, // released September 30, 1998. // // The Initial Developer of the Original Code is Red Hat. // Portions created by Red Hat are // Copyright (C) 1998, 1999, 2000 Red Hat, Inc. // All Rights Reserved. // ------------------------------------------- // //####COPYRIGHTEND#### //========================================================================== //#####DESCRIPTIONBEGIN#### // // Author(s): gthomas // Contributors: gthomas, grante // 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/diag.h> 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_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_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 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); 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); #else SERIAL_CALLBACKS(cyg_io_serial_callbacks, serial_init, serial_xmt_char, serial_rcv_char); #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; } if (chan->in_cbuf.len != 0) { #ifdef CYGDBG_IO_INIT diag_printf("Set input buffer - buf: %x len: %d\n", chan->in_cbuf.data, chan->in_cbuf.len); #endif chan->in_cbuf.waiting = false; chan->in_cbuf.abort = false; #ifdef CYGOPT_IO_SERIAL_SUPPORT_NONBLOCKING chan->in_cbuf.blocking = true; #endif cyg_drv_mutex_init(&chan->in_cbuf.lock); cyg_drv_cond_init(&chan->in_cbuf.wait, &chan->in_cbuf.lock); } chan->init = true; } 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; cbuf->abort = false; cyg_drv_mutex_lock(&cbuf->lock); if (cbuf->len == 0) { // Non interrupt driven (i.e. polled) operation while (size-- > 0) { while ((funs->putc)(chan, *buf) == false) ; // Ignore full, keep trying 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 (next == cbuf->get) { cbuf->waiting = true; // Buffer full - wait for space (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] cyg_drv_cond_wait(&cbuf->wait); 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; size--; // Only count if actually sent! } } (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 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); } } else { cyg_drv_dsr_lock(); // Avoid races while (size < *len) { if (cbuf->get != cbuf->put) { *buf++ = cbuf->data[cbuf->get]; if (++cbuf->get == cbuf->len) cbuf->get = 0; 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 cyg_drv_cond_wait(&cbuf->wait); #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_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->put - in_cbuf->get; if (p->rx_count < 0) p->rx_count += p->rx_bufsize; } 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; } 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->get != out_cbuf->put)) { out_cbuf->waiting = true; cyg_drv_cond_wait(&out_cbuf->wait); } 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; // 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->get != out_cbuf->put) { out_cbuf->get = out_cbuf->put; // 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_SERIAL_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: 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; 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: 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: 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 default: res = -EINVAL; } 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; while (cbuf->get != cbuf->put) { 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->waiting = false; cyg_drv_cond_broadcast(&cbuf->wait); } } } else { return; // Need to wait for more space } } (funs->stop_xmit)(chan); // Done with transmit if (cbuf->waiting) { cbuf->waiting = false; cyg_drv_cond_signal(&cbuf->wait); } } 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 #endif cbuf->data[cbuf->put++] = c; if (cbuf->put == cbuf->len) cbuf->put = 0; 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); } } //---------------------------------------------------------------------------- // Block transfer functions. Not all drivers require these. #ifdef CYGINT_IO_SERIAL_BLOCK_TRANSFER static bool serial_data_rcv_req(serial_channel *chan, int avail, int* space_avail, unsigned char** space) { cbuf_t *cbuf = &chan->in_cbuf; 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 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; } 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; if (gap < 0) { // If failed, the gap is between put and the end of buffer gap = cbuf->len - cbuf->put - 1; } } if (avail < gap) gap = avail; // bound by what's available from hw *space_avail = gap; *space = &cbuf->data[cbuf->put]; cbuf->put += gap; if (cbuf->put == cbuf->len) cbuf->put = 0; return true; } static void serial_data_rcv_done(serial_channel *chan) { cbuf_t *cbuf = &chan->in_cbuf; if (cbuf->waiting) { cbuf->waiting = false; cyg_drv_cond_signal(&cbuf->wait); } } static bool serial_data_xmt_req(serial_channel *chan, int space, int* chars_avail, unsigned char** chars) { cbuf_t *cbuf = &chan->out_cbuf; int avail; // Available data (G = get, P = put, x = data, . = empty) // 0: no data // negative: xxxxP.....Gxxx [offer last chunk only] // positive: ..GxxxxxP..... avail = cbuf->put - cbuf->get; if (0 == avail) return false; if (avail < 0) { avail = cbuf->len - cbuf->get; } if (avail > space) avail = space; // bound by space in hardware *chars_avail = avail; *chars = &cbuf->data[cbuf->get]; cbuf->get += avail; if (cbuf->get == cbuf->len) cbuf->get = 0; return true; } static void serial_data_xmt_done(serial_channel *chan) { cbuf_t *cbuf = &chan->out_cbuf; serial_funs *funs = chan->funs; int space; (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) { cbuf->waiting = false; cyg_drv_cond_broadcast(&cbuf->wait); } } } #endif // CYGINT_IO_SERIAL_BLOCK_TRANSFER
