X-Git-Url: https://codewiz.org/gitweb?a=blobdiff_plain;f=drv%2Fser.c;h=0838bc1538855e6c58575d0c6107d16b2981c485;hb=2535cb94ec2183791128f8bbd109ca69a960cf78;hp=6d16b93c1b5dc685b4d0df0c9885e625efad42b1;hpb=7ee3b3c3da60b181d18336e892d8d0920166f5ae;p=bertos.git diff --git a/drv/ser.c b/drv/ser.c old mode 100755 new mode 100644 index 6d16b93c..0838bc15 --- a/drv/ser.c +++ b/drv/ser.c @@ -1,9 +1,34 @@ -/*! +/** * \file * * * \brief Buffered serial I/O driver @@ -16,9 +41,9 @@ * 38400bps on a 16MHz 80196. * * MODULE CONFIGURATION - * \li \c CONFIG_SER_HWHANDSHAKE define this preprocessor symbol to enable - * support for RTS/CTS handshake. Support is incomplete/untested - * for 80196. + * + * \li \c CONFIG_SER_HWHANDSHAKE - set to 1 to enable RTS/CTS handshake. + * Support is incomplete/untested. * \li \c CONFIG_SER_TXTIMEOUT - Enable software serial transmission timeouts * * @@ -26,63 +51,74 @@ * \author Bernardo Innocenti */ -/* - * $Log$ - * Revision 1.2 2004/05/23 18:21:53 bernie - * Trim CVS logs and cleanup header info. - * - */ - -#include -#include #include "ser.h" +#include "wdt.h" #include "ser_p.h" -#include "hw.h" +#include +#include +#include -#ifdef CONFIG_KERNEL - #include +#include /* memset */ + +/* + * Sanity check for config parameters required by this module. + */ +#if !defined(CONFIG_KERNEL) || ((CONFIG_KERNEL != 0) && CONFIG_KERNEL != 1) + #error CONFIG_KERNEL must be set to either 0 or 1 in config.h #endif -#if defined(CONFIG_SER_TXTIMEOUT) || defined(CONFIG_SER_RXTIMEOUT) - #include +#if !defined(CONFIG_SER_RXTIMEOUT) + #error CONFIG_SER_TXTIMEOUT missing in config.h +#endif +#if !defined(CONFIG_SER_RXTIMEOUT) + #error CONFIG_SER_RXTIMEOUT missing in config.h +#endif +#if !defined(CONFIG_SER_DEFBAUDRATE) + #error CONFIG_SER_DEFBAUDRATE missing in config.h #endif +#if CONFIG_KERNEL + #include +#endif -/* Serial configuration parameters */ -#define SER_CTSDELAY 70 /*!< CTS line retry interval (ms) */ -#define SER_TXPOLLDELAY 2 /*!< Transmit buffer full retry interval (ms) */ -#define SER_RXPOLLDELAY 2 /*!< Receive buffer empty retry interval (ms) */ +#if CONFIG_SER_TXTIMEOUT != -1 || CONFIG_SER_RXTIMEOUT != -1 + #include +#endif struct Serial ser_handles[SER_CNT]; - -/*! - * Inserisce il carattere c nel buffer di trasmissione. - * Questa funzione mette il processo chiamante in attesa - * quando il buffer e' pieno. +/** + * Insert \a c in tx FIFO buffer. + * \note This function will switch out the calling process + * if the tx buffer is full. If the buffer is full + * and \a port->txtimeout is 0 return EOF immediatly. * - * \return EOF in caso di errore o timeout, altrimenti - * il carattere inviato. + * \return EOF on error or timeout, \a c otherwise. */ -int ser_putchar(int c, struct Serial *port) +static int ser_putchar(int c, struct Serial *port) { if (fifo_isfull_locked(&port->txfifo)) { -#ifdef CONFIG_SER_TXTIMEOUT - time_t start_time = timer_gettick(); +#if CONFIG_SER_TXTIMEOUT != -1 + /* If timeout == 0 we don't want to wait */ + if (port->txtimeout == 0) + return EOF; + + ticks_t start_time = timer_clock(); #endif - /* Attende finche' il buffer e' pieno... */ + /* Wait while buffer is full... */ do { -#ifdef CONFIG_KERN_SCHED + wdt_reset(); +#if CONFIG_KERNEL && CONFIG_KERN_SCHED /* Give up timeslice to other processes. */ proc_switch(); #endif -#ifdef CONFIG_SER_TXTIMEOUT - if (timer_gettick() - start_time >= port->txtimeout) +#if CONFIG_SER_TXTIMEOUT != -1 + if (timer_clock() - start_time >= port->txtimeout) { - port->status |= SERRF_TXTIMEOUT; + ATOMIC(port->status |= SERRF_TXTIMEOUT); return EOF; } #endif /* CONFIG_SER_TXTIMEOUT */ @@ -90,261 +126,404 @@ int ser_putchar(int c, struct Serial *port) while (fifo_isfull_locked(&port->txfifo)); } - fifo_push(&port->txfifo, (unsigned char)c); + fifo_push_locked(&port->txfifo, (unsigned char)c); /* (re)trigger tx interrupt */ - port->hw->table->enabletxirq(port->hw); + port->hw->table->txStart(port->hw); - /* Avoid returning signed estended char */ + /* Avoid returning signed extended char */ return (int)((unsigned char)c); } -/*! - * Preleva un carattere dal buffer di ricezione. - * Questa funzione mette il processo chiamante in attesa - * quando il buffer e' vuoto. L'attesa ha un timeout - * di ser_rxtimeout millisecondi. +/** + * Fetch a character from the rx FIFO buffer. + * \note This function will switch out the calling process + * if the rx buffer is empty. If the buffer is empty + * and \a port->rxtimeout is 0 return EOF immediatly. * - * \return EOF in caso di errore o timeout, altrimenti - * il carattere ricevuto. + * \return EOF on error or timeout, \a c otherwise. */ -int ser_getchar(struct Serial *port) +static int ser_getchar(struct Serial *port) { - int result; - if (fifo_isempty_locked(&port->rxfifo)) { -#ifdef CONFIG_SER_RXTIMEOUT - time_t start_time = timer_gettick(); +#if CONFIG_SER_RXTIMEOUT != -1 + /* If timeout == 0 we don't want to wait for chars */ + if (port->rxtimeout == 0) + return EOF; + + ticks_t start_time = timer_clock(); #endif /* Wait while buffer is empty */ do { -#ifdef CONFIG_KERN_SCHED + wdt_reset(); +#if CONFIG_KERNEL && CONFIG_KERN_SCHED /* Give up timeslice to other processes. */ proc_switch(); #endif -#ifdef CONFIG_SER_RXTIMEOUT - if (timer_gettick() - start_time >= port->rxtimeout) +#if CONFIG_SER_RXTIMEOUT != -1 + if (timer_clock() - start_time >= port->rxtimeout) { - port->status |= SERRF_RXTIMEOUT; + ATOMIC(port->status |= SERRF_RXTIMEOUT); return EOF; } #endif /* CONFIG_SER_RXTIMEOUT */ } - while (fifo_isempty_locked(&port->rxfifo)); + while (fifo_isempty_locked(&port->rxfifo) && (ser_getstatus(port) & SERRF_RX) == 0); } /* * Get a byte from the FIFO (avoiding sign-extension), * re-enable RTS, then return result. */ - result = (int)(unsigned char)fifo_pop(&port->rxfifo); - return port->status ? EOF : result; + if (ser_getstatus(port) & SERRF_RX) + return EOF; + return (int)(unsigned char)fifo_pop_locked(&port->rxfifo); } - -/*! - * Preleva un carattere dal buffer di ricezione. - * Se il buffer e' vuoto, ser_getchar_nowait() ritorna - * immediatamente EOF. +/** + * Fetch a character from the rx FIFO buffer. + * If the buffer is empty, ser_getchar_nowait() returns + * EOF immediatly. + * \note Deprecated, use ser_getchar with rx_timeout set to 0. */ -int ser_getchar_nowait(struct Serial *port) +int ser_getchar_nowait(struct KFileSerial *fd) { - if (fifo_isempty_locked(&port->rxfifo)) + if (fifo_isempty_locked(&fd->ser->rxfifo)) return EOF; /* NOTE: the double cast prevents unwanted sign extension */ - return (int)(unsigned char)fifo_pop(&port->rxfifo); + return (int)(unsigned char)fifo_pop_locked(&fd->ser->rxfifo); } -/*! - * Read a line long at most as size and puts it - * in buf. - * \return number of chars read or EOF in case - * of error. - */ -int ser_gets(struct Serial *port, char *buf, int size) -{ - return ser_gets_echo(port, buf, size, false); -} - -/*! - * Read a line long at most as size and puts it - * in buf, with optional echo. - * \return number of chars read or EOF in case - * of error. +/** + * Read at most \a size bytes from \a port and put them in \a buf + * + * \return number of bytes actually read. */ -int ser_gets_echo(struct Serial *port, char *buf, int size, bool echo) +static size_t ser_read(struct KFile *fd, void *_buf, size_t size) { - int i = 0; + KFileSerial *fds = KFILESERIAL(fd); + + size_t i = 0; + char *buf = (char *)_buf; int c; - for (;;) + while (i < size) { - if ((c = ser_getchar(port)) == EOF) - return -1; - /* FIXME */ - if (c == '\r' || c == '\n' || i >= size-1) - { - buf[i] = '\0'; - if (echo) - ser_print(port, "\r\n"); + if ((c = ser_getchar(fds->ser)) == EOF) break; - } buf[i++] = c; - if (echo) - ser_putchar(c, port); } return i; } - -/*! - * Read at most size bytes and puts them - * in buf. - * \return number of bytes read or EOF in case - * of error. +/** + * \brief Write a buffer to serial. + * + * \return 0 if OK, EOF in case of error. + * + * \todo Optimize with fifo_pushblock() */ -int ser_read(struct Serial *port, char *buf, size_t size) +static size_t ser_write(struct KFile *fd, const void *_buf, size_t size) { + KFileSerial *fds = KFILESERIAL(fd); + const char *buf = (const char *)_buf; size_t i = 0; - int c; - while (i < size) + while (size--) { - if ((c = ser_getchar(port)) == EOF) - return EOF; - buf[i++] = c; + if (ser_putchar(*buf++, fds->ser) == EOF) + break; + i++; } - return i; } -/*! - * Write a string to serial. - * \return 0 if OK, EOF in case of error. - */ -int ser_print(struct Serial *port, const char *s) +#if CONFIG_SER_RXTIMEOUT != -1 || CONFIG_SER_TXTIMEOUT != -1 +void ser_settimeouts(struct KFileSerial *fd, mtime_t rxtimeout, mtime_t txtimeout) { - while (*s) - { - if (ser_putchar(*s++, port) == EOF) - return EOF; - } - return 0; + fd->ser->rxtimeout = ms_to_ticks(rxtimeout); + fd->ser->txtimeout = ms_to_ticks(txtimeout); } +#endif /* CONFIG_SER_RXTIMEOUT || CONFIG_SER_TXTIMEOUT */ - -/*! - * \brief Write a buffer to serial. +#if CONFIG_SER_RXTIMEOUT != -1 +/** + * Discard input to resynchronize with remote end. * - * \return 0 if OK, EOF in case of error. + * Discard incoming data until the port stops receiving + * characters for at least \a delay milliseconds. + * + * \note Serial errors are reset before and after executing the purge. */ -int ser_write(struct Serial *port, const void *buf, size_t len) +void ser_resync(struct KFileSerial *fd, mtime_t delay) { - while (len--) + mtime_t old_rxtimeout = ticks_to_ms(fd->ser->rxtimeout); + + ser_settimeouts(fd, delay, ticks_to_ms(fd->ser->txtimeout)); + do { - if (ser_putchar(*((const char *)buf)++, port) == EOF) - return EOF; + ser_setstatus(fd->ser, 0); + ser_getchar(fd->ser); } - return 0; + while (!(ser_getstatus(fd->ser) & SERRF_RXTIMEOUT)); + + /* Restore port to an usable status */ + ser_setstatus(fd->ser, 0); + ser_settimeouts(fd, old_rxtimeout, ticks_to_ms(fd->ser->txtimeout)); } +#endif /* CONFIG_SER_RXTIMEOUT */ -/*! - * Formatted write - */ -int ser_printf(struct Serial *port, const char *format, ...) +void ser_setbaudrate(struct KFileSerial *fd, unsigned long rate) { - va_list ap; - int len; + fd->ser->hw->table->setBaudrate(fd->ser->hw, rate); +} - ser_setstatus(port, 0); - va_start(ap, format); - len = _formatted_write(format, (void (*)(char, void *))ser_putchar, port, ap); - va_end(ap); - return len; +void ser_setparity(struct KFileSerial *fd, int parity) +{ + fd->ser->hw->table->setParity(fd->ser->hw, parity); } -#if defined(CONFIG_SER_RXTIMEOUT) || defined(CONFIG_SER_TXTIMEOUT) -void ser_settimeouts(struct Serial *port, time_t rxtimeout, time_t txtimeout) +static int ser_error(struct KFile *fd) { - port->rxtimeout = rxtimeout; - port->txtimeout = txtimeout; + KFileSerial *fds = KFILESERIAL(fd); + return ser_getstatus(fds->ser); } -#endif /* defined(CONFIG_SER_RXTIMEOUT) || defined(CONFIG_SER_TXTIMEOUT) */ + +static void ser_clearerr(struct KFile *fd) +{ + KFileSerial *fds = KFILESERIAL(fd); + ser_setstatus(fds->ser, 0); +} + -void ser_setbaudrate(struct Serial *port, unsigned long rate) +/** + * Flush both the RX and TX buffers. + */ +void ser_purge(struct KFileSerial *fd) { - port->hw->table->setbaudrate(port->hw, rate); + ser_purgeRx(fd); + ser_purgeTx(fd); } +/** + * Flush RX buffer. + */ +void ser_purgeRx(struct KFileSerial *fd) +{ + fifo_flush_locked(&fd->ser->rxfifo); +} -void ser_setparity(struct Serial *port, int parity) +/** + * Flush TX buffer. + */ +void ser_purgeTx(struct KFileSerial *fd) { - port->hw->table->setparity(port->hw, parity); + fifo_flush_locked(&fd->ser->txfifo); } -/*! - * Flush both the RX and TX buffers. +/** + * Wait until all pending output is completely + * transmitted to the other end. + * + * \note The current implementation only checks the + * software transmission queue. Any hardware + * FIFOs are ignored. */ -void ser_purge(struct Serial *ser) +static int ser_flush(struct KFile *fd) { - fifo_flush(&ser->rxfifo); - fifo_flush(&ser->txfifo); + KFileSerial *fds = KFILESERIAL(fd); + + /* + * Wait until the FIFO becomes empty, and then until the byte currently in + * the hardware register gets shifted out. + */ + while (!fifo_isempty(&fds->ser->txfifo) + || fds->ser->hw->table->txSending(fds->ser->hw)) + { + #if CONFIG_KERNEL && CONFIG_KERN_SCHED + /* Give up timeslice to other processes. */ + proc_switch(); + #endif + wdt_reset(); + } + return 0; } -/*! - * Initialize serial +/** + * Initialize a serial port. + * + * \param fd KFile Serial struct interface. + * \param unit Serial unit to open. Possible values are architecture dependant. */ -struct Serial *ser_open(unsigned int unit) +static struct Serial *ser_open(struct KFileSerial *fd, unsigned int unit) { struct Serial *port; ASSERT(unit < countof(ser_handles)); - port = &ser_handles[unit]; ASSERT(!port->is_open); + DB(port->is_open = true); port->unit = unit; - port->is_open = true; - - /* Initialize circular buffer */ - fifo_init(&port->rxfifo, port->rxbuffer, sizeof(port->rxbuffer)); - fifo_init(&port->txfifo, port->txbuffer, sizeof(port->txbuffer)); port->hw = ser_hw_getdesc(unit); + + /* Initialize circular buffers */ + ASSERT(port->hw->txbuffer); + ASSERT(port->hw->rxbuffer); + fifo_init(&port->txfifo, port->hw->txbuffer, port->hw->txbuffer_size); + fifo_init(&port->rxfifo, port->hw->rxbuffer, port->hw->rxbuffer_size); + port->hw->table->init(port->hw, port); + fd->ser = port; /* Set default values */ -#if defined(CONFIG_SER_RXTIMEOUT) || defined(CONFIG_SER_TXTIMEOUT) - ser_settimeouts(port, CONFIG_SER_RXTIMEOUT, CONFIG_SER_TXTIMEOUT); +#if CONFIG_SER_RXTIMEOUT != -1 || CONFIG_SER_TXTIMEOUT != -1 + ser_settimeouts(fd, CONFIG_SER_RXTIMEOUT, CONFIG_SER_TXTIMEOUT); +#endif +#if CONFIG_SER_DEFBAUDRATE + ser_setbaudrate(fd, CONFIG_SER_DEFBAUDRATE); #endif - ser_setbaudrate(port, CONFIG_SER_DEFBAUDRATE); + + /* Clear error flags */ + ser_setstatus(port, 0); return port; } -/*! +/** * Clean up serial port, disabling the associated hardware. */ -void ser_close(struct Serial *port) +static int ser_close(struct KFile *fd) { + KFileSerial *fds = KFILESERIAL(fd); + Serial *port = fds->ser; + ASSERT(port->is_open); + DB(port->is_open = false); + + // Wait until we finish sending everything + ser_flush(fd); - port->is_open = false; port->hw->table->cleanup(port->hw); - port->hw = NULL; + DB(port->hw = NULL); + + /* + * We purge the FIFO buffer only after the low-level cleanup, so that + * we are sure that there are no more interrupts. + */ + ser_purge(fds); + return 0; +} + +/** + * Reopen serial port. + */ +static struct KFile *ser_reopen(struct KFile *fd) +{ + KFileSerial *fds = KFILESERIAL(fd); + + ser_close(fd); + ser_open(fds, fds->ser->unit); + return (KFile *)fds; +} + +/** + * Init serial driver for \a unit. + */ +void ser_init(struct KFileSerial *fds, unsigned int unit) +{ + memset(fds, 0, sizeof(*fds)); + + DB(fds->fd._type = KFT_SERIAL); + fds->fd.reopen = ser_reopen; + fds->fd.close = ser_close; + fds->fd.read = ser_read; + fds->fd.write = ser_write; + fds->fd.flush = ser_flush; + fds->fd.error = ser_error; + fds->fd.clearerr = ser_clearerr; + ser_open(fds, unit); +} + + +/** + * Read data from SPI bus. + * Since we are master, we have to trigger slave by sending + * fake chars on the bus. + */ +static size_t spimaster_read(struct KFile *fd, void *_buf, size_t size) +{ + KFileSerial *fd_spi = KFILESERIAL(fd); + + ser_flush(&fd_spi->fd); + ser_purgeRx(fd_spi); + + size_t total_rd = 0; + uint8_t *buf = (uint8_t *)_buf; + int c; + + while (size--) + { + /* + * Send and receive chars 1 by 1, otherwise the rxfifo + * will overrun. + */ + ser_putchar(0, fd_spi->ser); + + if ((c = ser_getchar(fd_spi->ser)) == EOF) + break; + + *buf++ = c; + total_rd++; + } + return total_rd; +} + +/** + * Write data to SPI bus. + */ +static size_t spimaster_write(struct KFile *fd, const void *buf, size_t size) +{ + KFileSerial *fd_spi = KFILESERIAL(fd); + + ser_purgeRx(fd_spi); + + return ser_write(&fd_spi->fd, buf, size); } + + +/** + * Init SPI serial driver \a unit in master mode. + * + * This interface implements the SPI master protocol over a serial SPI + * driver. This is needed because normal serial driver send/receive data + * at the same time. SPI slaves like memories and other peripherals + * first receive and *then* send response back instead. + * To achieve this, when we are master and we are *sending*, + * we have to discard all incoming data. Then, when we want to + * receive, we must write fake data to SPI to trigger slave devices. + */ +void spimaster_init(KFileSerial *fds, unsigned int unit) +{ + ser_init(fds, unit); + fds->fd.read = spimaster_read; + fds->fd.write = spimaster_write; +} + +