/** * \file * * * \brief Buffered serial I/O driver * * The serial rx interrupt buffers incoming data in a software FIFO * to decouple the higher level protocols from the line speed. * Outgoing data is buffered as well for better performance. * This driver is not optimized for best performance, but it * has proved to be fast enough to handle transfer rates up to * 38400bps on a 16MHz 80196. * * MODULE CONFIGURATION * * \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 * * * \author Bernie Innocenti */ #include "ser.h" #include "wdt.h" #include "timer.h" #include "ser_p.h" #include "cfg/cfg_ser.h" #include "cfg/cfg_proc.h" #include #include #include /* cpu_relax() */ #include /* memset() */ /* * Sanity check for config parameters required by this module. */ #if !defined(CONFIG_KERN) || ((CONFIG_KERN != 0) && CONFIG_KERN != 1) #error CONFIG_KERN must be set to either 0 or 1 in cfg_kern.h #endif #if !defined(CONFIG_SER_RXTIMEOUT) #error CONFIG_SER_TXTIMEOUT missing in cfg_ser.h #endif #if !defined(CONFIG_SER_RXTIMEOUT) #error CONFIG_SER_RXTIMEOUT missing in cfg_ser.h #endif #if !defined(CONFIG_SER_DEFBAUDRATE) #error CONFIG_SER_DEFBAUDRATE missing in cfg_ser.h #endif struct Serial *ser_handles[SER_CNT]; /** * 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 on error or timeout, \a c otherwise. */ static int ser_putchar(int c, struct Serial *port) { if (fifo_isfull_locked(&port->txfifo)) { #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 /* Wait while buffer is full... */ do { cpu_relax(); #if CONFIG_SER_TXTIMEOUT != -1 if (timer_clock() - start_time >= port->txtimeout) { ATOMIC(port->status |= SERRF_TXTIMEOUT); return EOF; } #endif /* CONFIG_SER_TXTIMEOUT */ } while (fifo_isfull_locked(&port->txfifo)); } fifo_push_locked(&port->txfifo, (unsigned char)c); /* (re)trigger tx interrupt */ port->hw->table->txStart(port->hw); /* Avoid returning signed extended char */ return (int)((unsigned char)c); } /** * 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 on error or timeout, \a c otherwise. */ static int ser_getchar(struct Serial *port) { if (fifo_isempty_locked(&port->rxfifo)) { #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 { cpu_relax(); #if CONFIG_SER_RXTIMEOUT != -1 if (timer_clock() - start_time >= port->rxtimeout) { ATOMIC(port->status |= SERRF_RXTIMEOUT); return EOF; } #endif /* CONFIG_SER_RXTIMEOUT */ } 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. */ if (ser_getstatus(port) & SERRF_RX) return EOF; return (int)(unsigned char)fifo_pop_locked(&port->rxfifo); } /** * 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 *fd) { if (fifo_isempty_locked(&fd->rxfifo)) return EOF; /* NOTE: the double cast prevents unwanted sign extension */ return (int)(unsigned char)fifo_pop_locked(&fd->rxfifo); } bool ser_available(struct Serial *fd) { if (fifo_isempty_locked(&fd->rxfifo)) { return false; } else { return true; } } /** * Read at most \a size bytes from \a port and put them in \a buf * * \return number of bytes actually read. */ static size_t ser_read(struct KFile *fd, void *_buf, size_t size) { Serial *fds = SERIAL_CAST(fd); size_t i = 0; char *buf = (char *)_buf; int c; while (i < size) { if ((c = ser_getchar(fds)) == EOF) break; buf[i++] = c; } return i; } /** * \brief Write a buffer to serial. * * \return 0 if OK, EOF in case of error. * * \todo Optimize with fifo_pushblock() */ static size_t ser_write(struct KFile *fd, const void *_buf, size_t size) { Serial *fds = SERIAL_CAST(fd); const char *buf = (const char *)_buf; size_t i = 0; while (size--) { if (ser_putchar(*buf++, fds) == EOF) break; i++; } return i; } #if CONFIG_SER_RXTIMEOUT != -1 || CONFIG_SER_TXTIMEOUT != -1 void ser_settimeouts(struct Serial *fd, mtime_t rxtimeout, mtime_t txtimeout) { #if CONFIG_SER_RXTIMEOUT != -1 fd->rxtimeout = ms_to_ticks(rxtimeout); #else (void)rxtimeout; #endif #if CONFIG_SER_TXTIMEOUT != -1 fd->txtimeout = ms_to_ticks(txtimeout); #else (void)txtimeout; #endif } #endif /* CONFIG_SER_RXTIMEOUT || CONFIG_SER_TXTIMEOUT */ /** * Set the baudrate for the serial port */ void ser_setbaudrate(struct Serial *fd, unsigned long rate) { fd->hw->table->setBaudrate(fd->hw, rate); } /** * Set the parity for the \a fd serial port */ void ser_setparity(struct Serial *fd, int parity) { fd->hw->table->setParity(fd->hw, parity); } static int ser_error(struct KFile *fd) { Serial *fds = SERIAL_CAST(fd); return ser_getstatus(fds); } static void ser_clearerr(struct KFile *fd) { Serial *fds = SERIAL_CAST(fd); ser_setstatus(fds, 0); } /** * Flush both the RX and TX buffers. */ void ser_purge(struct Serial *fd) { ser_purgeRx(fd); ser_purgeTx(fd); } /** * Flush RX buffer. */ void ser_purgeRx(struct Serial *fd) { fifo_flush_locked(&fd->rxfifo); } /** * Flush TX buffer. */ void ser_purgeTx(struct Serial *fd) { fifo_flush_locked(&fd->txfifo); } /** * 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. */ static int ser_flush(struct KFile *fd) { Serial *fds = SERIAL_CAST(fd); /* * Wait until the FIFO becomes empty, and then until the byte currently in * the hardware register gets shifted out. */ while (!fifo_isempty(&fds->txfifo) || fds->hw->table->txSending(fds->hw)) cpu_relax(); return 0; } /** * Initialize a serial port. * * \param fd KFile Serial struct interface. * \param unit Serial unit to open. Possible values are architecture dependant. */ static struct Serial *ser_open(struct Serial *fd, unsigned int unit) { ASSERT(unit < countof(ser_handles)); ser_handles[unit] = fd; ASSERT(!fd->is_open); DB(fd->is_open = true); fd->unit = unit; fd->hw = ser_hw_getdesc(unit); /* Initialize circular buffers */ ASSERT(fd->hw->txbuffer); ASSERT(fd->hw->rxbuffer); fifo_init(&fd->txfifo, fd->hw->txbuffer, fd->hw->txbuffer_size); fifo_init(&fd->rxfifo, fd->hw->rxbuffer, fd->hw->rxbuffer_size); fd->hw->table->init(fd->hw, fd); /* Set default values */ #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 /* Clear error flags */ ser_setstatus(fd, 0); return fd; } /** * Clean up serial port, disabling the associated hardware. */ static int ser_close(struct KFile *fd) { Serial *fds = SERIAL_CAST(fd); Serial *port = fds; ASSERT(port->is_open); DB(port->is_open = false); // Wait until we finish sending everything ser_flush(fd); port->hw->table->cleanup(port->hw); 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) { Serial *fds = SERIAL_CAST(fd); ser_close(fd); ser_open(fds, fds->unit); return (KFile *)fds; } /** * Init serial driver for \a unit. * * Use values SER_UARTn as values for \a unit. */ void ser_init(struct Serial *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) { Serial *fd_spi = SERIAL_CAST(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); if ((c = ser_getchar(fd_spi)) == 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) { Serial *fd_spi = SERIAL_CAST(fd); ser_purgeRx(fd_spi); return ser_write(&fd_spi->fd, buf, size); } /** * Init SPI serial driver \a unit in master mode. * * Use SER_SPIn for \a unit parameter. * * 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(Serial *fds, unsigned int unit) { ser_init(fds, unit); fds->fd.read = spimaster_read; fds->fd.write = spimaster_write; }