X-Git-Url: https://codewiz.org/gitweb?a=blobdiff_plain;f=drv%2Fser.c;h=0838bc1538855e6c58575d0c6107d16b2981c485;hb=2535cb94ec2183791128f8bbd109ca69a960cf78;hp=289551d06ff60b78e1208e2ab010a0c685dfe760;hpb=018b68ae17c81e82f8a1991f518c08107657bc9b;p=bertos.git diff --git a/drv/ser.c b/drv/ser.c old mode 100755 new mode 100644 index 289551d0..0838bc15 --- a/drv/ser.c +++ b/drv/ser.c @@ -1,9 +1,34 @@ -/*! +/** * \file * * * \brief Buffered serial I/O driver @@ -26,112 +51,74 @@ * \author Bernardo Innocenti */ -/*#* - *#* $Log$ - *#* Revision 1.18 2004/09/20 03:31:15 bernie - *#* Sanitize for C++. - *#* - *#* Revision 1.17 2004/09/14 21:06:07 bernie - *#* Use debug.h instead of kdebug.h; Spelling fixes. - *#* - *#* Revision 1.16 2004/09/06 21:40:50 bernie - *#* Move buffer handling in chip-specific driver. - *#* - *#* Revision 1.15 2004/08/25 14:12:08 rasky - *#* Aggiornato il comment block dei log RCS - *#* - *#* Revision 1.14 2004/08/24 16:22:57 bernie - *#* Thinkos; Doxygen fixes - *#* - *#* Revision 1.13 2004/08/24 16:20:48 bernie - *#* ser_read(): Make buffer argument void *#* for consistency with ANSI C and ser_write() - *#* - *#* Revision 1.12 2004/08/24 13:49:39 bernie - *#* Fix thinko. - *#* - *#* Revision 1.11 2004/08/15 05:32:22 bernie - *#* ser_resync(): New function. - *#* - *#* Revision 1.10 2004/08/10 06:29:50 bernie - *#* Rename timer_gettick() to timer_ticks(). - *#* - *#* Revision 1.9 2004/08/08 06:06:20 bernie - *#* Use new-style CONFIG_ idiom; Fix module-wide documentation. - *#* - *#* Revision 1.8 2004/07/29 22:57:09 bernie - *#* ser_drain(): New function; Make Serial::is_open a debug-only feature; Switch to new-style CONFIG_* macros. - *#* - *#* Revision 1.7 2004/07/18 21:49:03 bernie - *#* Make CONFIG_SER_DEFBAUDRATE optional. - *#* - *#* Revision 1.6 2004/06/07 15:56:28 aleph - *#* Remove cast-as-lvalue extension abuse - *#* - *#* Revision 1.5 2004/06/06 16:41:44 bernie - *#* ser_putchar(): Use fifo_push_locked() to fix potential race on 8bit processors. - *#* - *#* Revision 1.4 2004/06/03 11:27:09 bernie - *#* Add dual-license information. - *#* - *#* Revision 1.3 2004/06/02 21:35:24 aleph - *#* Serial enhancements: interruptible receive handler and 8 bit serial status for AVR; remove volatile attribute to FIFOBuffer, useless for new fifobuf routens - *#* - *#* 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 + +#include /* memset */ -#ifdef CONFIG_KERNEL +/* + * 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_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 + #if CONFIG_SER_TXTIMEOUT != -1 || CONFIG_SER_RXTIMEOUT != -1 #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) */ - - 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) { - //ASSERT_VALID_FIFO(&port->txfifo); if (fifo_isfull_locked(&port->txfifo)) { #if CONFIG_SER_TXTIMEOUT != -1 - time_t start_time = timer_ticks(); + /* 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 { -#if defined(CONFIG_KERN_SCHED) && CONFIG_KERN_SCHED + wdt_reset(); +#if CONFIG_KERNEL && CONFIG_KERN_SCHED /* Give up timeslice to other processes. */ proc_switch(); #endif #if CONFIG_SER_TXTIMEOUT != -1 - if (timer_ticks() - start_time >= port->txtimeout) + if (timer_clock() - start_time >= port->txtimeout) { - port->status |= SERRF_TXTIMEOUT; + ATOMIC(port->status |= SERRF_TXTIMEOUT); return EOF; } #endif /* CONFIG_SER_TXTIMEOUT */ @@ -142,211 +129,133 @@ int ser_putchar(int c, struct Serial *port) 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)) { #if CONFIG_SER_RXTIMEOUT != -1 - time_t start_time = timer_ticks(); + /* 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 { -#if defined(CONFIG_KERN_SCHED) && CONFIG_KERN_SCHED + wdt_reset(); +#if CONFIG_KERNEL && CONFIG_KERN_SCHED /* Give up timeslice to other processes. */ proc_switch(); #endif #if CONFIG_SER_RXTIMEOUT != -1 - if (timer_ticks() - start_time >= port->rxtimeout) + 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_locked(&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_locked(&port->rxfifo); + return (int)(unsigned char)fifo_pop_locked(&fd->ser->rxfifo); } -#if CONFIG_SER_GETS -/*! - * 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 put it - * in buf, with optional echo. - * - * \return number of chars read, or EOF in case - * of error. - */ -int ser_gets_echo(struct Serial *port, char *buf, int size, bool echo) -{ - int i = 0; - int c; - - for (;;) - { - if ((c = ser_getchar(port)) == EOF) - { - buf[i] = '\0'; - return -1; - } - - /* FIXME */ - if (c == '\r' || c == '\n' || i >= size-1) - { - buf[i] = '\0'; - if (echo) - ser_print(port, "\r\n"); - break; - } - buf[i++] = c; - if (echo) - ser_putchar(c, port); - } - - return i; -} -#endif /* !CONFIG_SER_GETS */ - -/*! +/** * Read at most \a size bytes from \a port and put them in \a buf * - * \return number of bytes actually read, or EOF in - * case of error. + * \return number of bytes actually read. */ -int ser_read(struct Serial *port, void *buf, size_t size) +static size_t ser_read(struct KFile *fd, void *_buf, size_t size) { + KFileSerial *fds = KFILESERIAL(fd); + size_t i = 0; - char *_buf = (char *)buf; + char *buf = (char *)_buf; int c; while (i < size) { - if ((c = ser_getchar(port)) == EOF) - return EOF; - _buf[i++] = c; + if ((c = ser_getchar(fds->ser)) == EOF) + break; + buf[i++] = c; } 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) -{ - while (*s) - { - if (ser_putchar(*s++, port) == EOF) - return EOF; - } - return 0; -} - - -/*! +/** * \brief Write a buffer to serial. * * \return 0 if OK, EOF in case of error. * * \todo Optimize with fifo_pushblock() */ -int ser_write(struct Serial *port, const void *_buf, size_t len) +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; - while (len--) + while (size--) { - if (ser_putchar(*buf++, port) == EOF) - return EOF; + if (ser_putchar(*buf++, fds->ser) == EOF) + break; + i++; } - return 0; -} - - -#if CONFIG_PRINTF -/*! - * Formatted write - */ -int ser_printf(struct Serial *port, const char *format, ...) -{ - va_list ap; - int len; - - ser_setstatus(port, 0); - va_start(ap, format); - len = _formatted_write(format, (void (*)(char, void *))ser_putchar, port, ap); - va_end(ap); - - return len; + return i; } -#endif /* CONFIG_PRINTF */ #if CONFIG_SER_RXTIMEOUT != -1 || CONFIG_SER_TXTIMEOUT != -1 -void ser_settimeouts(struct Serial *port, time_t rxtimeout, time_t txtimeout) +void ser_settimeouts(struct KFileSerial *fd, mtime_t rxtimeout, mtime_t txtimeout) { - port->rxtimeout = rxtimeout; - port->txtimeout = txtimeout; + fd->ser->rxtimeout = ms_to_ticks(rxtimeout); + fd->ser->txtimeout = ms_to_ticks(txtimeout); } #endif /* CONFIG_SER_RXTIMEOUT || CONFIG_SER_TXTIMEOUT */ #if CONFIG_SER_RXTIMEOUT != -1 -/*! +/** * Discard input to resynchronize with remote end. * * Discard incoming data until the port stops receiving @@ -354,48 +263,77 @@ void ser_settimeouts(struct Serial *port, time_t rxtimeout, time_t txtimeout) * * \note Serial errors are reset before and after executing the purge. */ -void ser_resync(struct Serial *port, time_t delay) +void ser_resync(struct KFileSerial *fd, mtime_t delay) { - time_t old_rxtimeout = port->rxtimeout; + mtime_t old_rxtimeout = ticks_to_ms(fd->ser->rxtimeout); - ser_settimeouts(port, delay, port->txtimeout); + ser_settimeouts(fd, delay, ticks_to_ms(fd->ser->txtimeout)); do { - ser_setstatus(port, 0); - ser_getchar(port); + ser_setstatus(fd->ser, 0); + ser_getchar(fd->ser); } - while (!(ser_getstatus(port) & SERRF_RXTIMEOUT)); + while (!(ser_getstatus(fd->ser) & SERRF_RXTIMEOUT)); /* Restore port to an usable status */ - ser_setstatus(port, 0); - ser_settimeouts(port, old_rxtimeout, port->txtimeout); + ser_setstatus(fd->ser, 0); + ser_settimeouts(fd, old_rxtimeout, ticks_to_ms(fd->ser->txtimeout)); } #endif /* CONFIG_SER_RXTIMEOUT */ -void ser_setbaudrate(struct Serial *port, unsigned long rate) +void ser_setbaudrate(struct KFileSerial *fd, unsigned long rate) { - port->hw->table->setbaudrate(port->hw, rate); + fd->ser->hw->table->setBaudrate(fd->ser->hw, rate); } -void ser_setparity(struct Serial *port, int parity) +void ser_setparity(struct KFileSerial *fd, int parity) { - port->hw->table->setparity(port->hw, parity); + fd->ser->hw->table->setParity(fd->ser->hw, parity); } +static int ser_error(struct KFile *fd) +{ + KFileSerial *fds = KFILESERIAL(fd); + return ser_getstatus(fds->ser); +} -/*! +static void ser_clearerr(struct KFile *fd) +{ + KFileSerial *fds = KFILESERIAL(fd); + ser_setstatus(fds->ser, 0); +} + + + +/** * Flush both the RX and TX buffers. */ -void ser_purge(struct Serial *port) +void ser_purge(struct KFileSerial *fd) { - fifo_flush_locked(&port->rxfifo); - fifo_flush_locked(&port->txfifo); + ser_purgeRx(fd); + ser_purgeTx(fd); } +/** + * Flush RX buffer. + */ +void ser_purgeRx(struct KFileSerial *fd) +{ + fifo_flush_locked(&fd->ser->rxfifo); +} -/*! +/** + * Flush TX buffer. + */ +void ser_purgeTx(struct KFileSerial *fd) +{ + fifo_flush_locked(&fd->ser->txfifo); +} + + +/** * Wait until all pending output is completely * transmitted to the other end. * @@ -403,22 +341,34 @@ void ser_purge(struct Serial *port) * software transmission queue. Any hardware * FIFOs are ignored. */ -void ser_drain(struct Serial *ser) +static int ser_flush(struct KFile *fd) { - while (!fifo_isempty(&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 defined(CONFIG_KERN_SCHED) && CONFIG_KERN_SCHED + #if CONFIG_KERNEL && CONFIG_KERN_SCHED /* Give up timeslice to other processes. */ proc_switch(); -#endif + #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; @@ -426,38 +376,154 @@ struct Serial *ser_open(unsigned int unit) port = &ser_handles[unit]; ASSERT(!port->is_open); - DB(port->is_open = true;) + DB(port->is_open = true); port->unit = unit; 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 CONFIG_SER_RXTIMEOUT != -1 || CONFIG_SER_TXTIMEOUT != -1 - ser_settimeouts(port, CONFIG_SER_RXTIMEOUT, CONFIG_SER_TXTIMEOUT); + ser_settimeouts(fd, CONFIG_SER_RXTIMEOUT, CONFIG_SER_TXTIMEOUT); #endif #if CONFIG_SER_DEFBAUDRATE - ser_setbaudrate(port, CONFIG_SER_DEFBAUDRATE); + ser_setbaudrate(fd, CONFIG_SER_DEFBAUDRATE); #endif + /* 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;) + DB(port->is_open = false); + + // Wait until we finish sending everything + ser_flush(fd); 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; +} + +