? fifofix.txt Index: ChangeLog =================================================================== RCS file: /cvs/ecos/ecos/packages/devs/serial/arm/at91/current/ChangeLog,v retrieving revision 1.11 diff -w -u -r1.11 ChangeLog --- ChangeLog 26 Jan 2004 08:38:22 -0000 1.11 +++ ChangeLog 1 Nov 2004 09:33:25 -0000 @@ -1,3 +1,11 @@ +2004-10-25 Oyvind Harboe + + * src/at91_serial.c: changed how hardware fifos are handled + + added >2 ISR buffers. This is required to make sure that data is + not lost when lots of interrupts are happening in the system. + This applies even at low speeds(38400). Typically this can be seen + when both serial ports are used simultaneously. + 2004-01-26 Andrew Lunn * src/at91_serial.c (at91_serial_getc_polled) Index: src/at91_serial.c =================================================================== RCS file: /cvs/ecos/ecos/packages/devs/serial/arm/at91/current/src/at91_serial.c,v retrieving revision 1.9 diff -w -u -r1.9 at91_serial.c --- src/at91_serial.c 26 Jan 2004 08:38:24 -0000 1.9 +++ src/at91_serial.c 1 Nov 2004 09:33:26 -0000 @@ -80,6 +80,12 @@ #define SIFLG_XMIT_BUSY 0x02 #define SIFLG_XMIT_CONTINUE 0x04 +/* via empirical testing this has been found to be the required number of buffers. + * Note that 6 interrupts can happen between each char received, so the number + * isn't entirely surprising. + */ +#define BUFNUM 8 + typedef struct at91_serial_info { CYG_ADDRWORD base; CYG_WORD int_num; @@ -87,10 +93,12 @@ int transmit_size; cyg_interrupt serial_interrupt; cyg_handle_t serial_interrupt_handle; - cyg_uint8 *rcv_buffer[2]; + cyg_uint8 rcv_buffer[BUFNUM][CYGNUM_IO_SERIAL_ARM_AT91_SERIAL1_RCV_CHUNK_SIZE + RCVBUF_EXTRA]; cyg_uint16 rcv_chunk_size; - cyg_uint8 curbuf; + cyg_int32 curbufwrite; + cyg_int32 curbufread; cyg_uint8 flags; + cyg_uint8 *end[BUFNUM]; } at91_serial_info; static bool at91_serial_init(struct cyg_devtab_entry *tab); @@ -138,13 +146,10 @@ #endif #ifdef CYGPKG_IO_SERIAL_ARM_AT91_SERIAL0 -static cyg_uint8 at91_serial_rcv_buffer_0 - [2][CYGNUM_IO_SERIAL_ARM_AT91_SERIAL0_RCV_CHUNK_SIZE + RCVBUF_EXTRA]; static at91_serial_info at91_serial_info0 = { base : (CYG_ADDRWORD) AT91_USART0, int_num : CYGNUM_HAL_INTERRUPT_USART0, rcv_chunk_size : CYGNUM_IO_SERIAL_ARM_AT91_SERIAL0_RCV_CHUNK_SIZE, - rcv_buffer : {at91_serial_rcv_buffer_0[0], at91_serial_rcv_buffer_0[1]} }; #if CYGNUM_IO_SERIAL_ARM_AT91_SERIAL0_BUFSIZE > 0 @@ -185,13 +190,10 @@ #endif // CYGPKG_IO_SERIAL_ARM_AT91_SERIAL1 #ifdef CYGPKG_IO_SERIAL_ARM_AT91_SERIAL1 -static cyg_uint8 at91_serial_rcv_buffer_1 - [2][CYGNUM_IO_SERIAL_ARM_AT91_SERIAL1_RCV_CHUNK_SIZE + RCVBUF_EXTRA]; static at91_serial_info at91_serial_info1 = { base : (CYG_ADDRWORD) AT91_USART1, int_num : CYGNUM_HAL_INTERRUPT_USART1, rcv_chunk_size : CYGNUM_IO_SERIAL_ARM_AT91_SERIAL1_RCV_CHUNK_SIZE, - rcv_buffer : {at91_serial_rcv_buffer_1[0], at91_serial_rcv_buffer_1[1]} }; #if CYGNUM_IO_SERIAL_ARM_AT91_SERIAL1_BUFSIZE > 0 @@ -260,7 +262,13 @@ HAL_WRITE_UINT32(base + AT91_US_IDR, 0xFFFFFFFF); // Start receiver - at91_chan->curbuf = 0; + at91_chan->curbufwrite = 0; + at91_chan->curbufread = 0; + int i; + for (i=0; iend[i] = NULL; + } HAL_WRITE_UINT32(base + AT91_US_RPR, (CYG_ADDRESS) at91_chan->rcv_buffer[0]); HAL_WRITE_UINT32(base + AT91_US_RTO, RCV_TIMEOUT); HAL_WRITE_UINT32(base + AT91_US_IER, AT91_US_IER_ENDRX | AT91_US_IER_TIMEOUT); @@ -290,7 +298,13 @@ #ifdef CYGDBG_IO_INIT diag_printf("AT91 SERIAL init - dev: %x.%d\n", at91_chan->base, at91_chan->int_num); #endif - at91_chan->curbuf = 0; + at91_chan->curbufwrite = 0; + at91_chan->curbufread = 0; + int i; + for (i=0; iend[i] = NULL; + } at91_chan->flags = SIFLG_NONE; at91_chan->stat = 0; (chan->callbacks->serial_init)(chan); // Really only required for interrupt driven devices @@ -448,8 +462,25 @@ at91_chan->flags &= ~SIFLG_XMIT_CONTINUE; } -// Serial I/O - low level interrupt handler (ISR) +/* Serial I/O - low level interrupt handler (ISR) + * + * This serial driver will not work correctly if the interrupt + * latency is greater than the time it takes for one char to arrive + * on the serial port. + * + * NOTE!!! There are normally two serial ports on an AT91, and + * in the time one char arrives, 6 serial interrupts can happen. For + * each port the interrupts are: + * + * - AT91_US_IER_TIMEOUT + * - AT91_US_IER_ENDRX + * - AT91_US_IER_TxRDY | AT91_US_IER_ENDTX + * + * NOTE!!! To reduce amount of time spent in the ISR, the routine has been put + * in SRAM which is much faster than FLASH + */ static cyg_uint32 +__attribute__ ((section (".2ram.text"))) at91_serial_ISR(cyg_vector_t vector, cyg_addrword_t data) { serial_channel * const chan = (serial_channel *) data; @@ -461,17 +492,37 @@ HAL_READ_UINT32(base + AT91_US_IMR, mask); stat &= mask; - if (stat & (AT91_US_IER_ENDRX | AT91_US_IER_TIMEOUT)) { - cyg_uint32 x; - HAL_WRITE_UINT32(base + AT91_US_IDR, AT91_US_IER_ENDRX | AT91_US_IER_TIMEOUT); + if (stat & AT91_US_IER_TIMEOUT) { + /* + * the FIFO is still running, we have to stop feeding it + * and wait for it to become idle + * + * Note that while the FIFO is idle, a char can be received in + * the normal recieve register. Once the FIFO is restarted, it will + * fish out any char already in the receive register. + * + */ + HAL_WRITE_UINT32(base + AT91_US_IDR, AT91_US_IER_TIMEOUT); HAL_WRITE_UINT32(base + AT91_US_RCR, 0); HAL_WRITE_UINT32(base + AT91_US_RTO, 0); - HAL_READ_UINT32(base + AT91_US_RPR, x); - HAL_WRITE_UINT32( - base + AT91_US_RCR, - (CYG_ADDRESS) at91_chan->rcv_buffer[at91_chan->curbuf] - + at91_chan->rcv_chunk_size + RCVBUF_EXTRA - x - ); + } + + if (stat & AT91_US_IER_ENDRX) { + /* the FIFO is currently stopped, restart it. This needs to happen + * at the highest interrupt priority since the AT91 serial hardware has + * a very tight window for doing this. + */ + const cyg_uint32 cb = at91_chan->curbufwrite, nb = (cb+1)%BUFNUM; + at91_chan->curbufwrite = nb; + + cyg_uint8 *end; + HAL_READ_UINT32(base + AT91_US_RPR, (CYG_ADDRESS) end); + CYG_ASSERT(at91_chan->end[cb]==NULL, ("serial receive DSR was not able to process quickly enough\n")); + at91_chan->end[cb]=end; + + HAL_WRITE_UINT32(base + AT91_US_CR, AT91_US_CR_RSTATUS | AT91_US_CR_STTTO); + HAL_WRITE_UINT32(base + AT91_US_RPR, (CYG_ADDRESS) at91_chan->rcv_buffer[nb]); + HAL_WRITE_UINT32(base + AT91_US_RCR, at91_chan->rcv_chunk_size); } if (stat & (AT91_US_IER_TxRDY | AT91_US_IER_ENDTX)) @@ -479,6 +530,7 @@ at91_chan->stat |= stat; cyg_drv_interrupt_acknowledge(vector); + return CYG_ISR_CALL_DSR; } @@ -496,21 +548,19 @@ at91_chan->stat = 0; cyg_drv_interrupt_unmask(vector); - if (stat & (AT91_US_IER_ENDRX | AT91_US_IER_TIMEOUT)) { - const cyg_uint8 cb = at91_chan->curbuf, nb = cb ^ 0x01; - const cyg_uint8 * p = at91_chan->rcv_buffer[cb], * end; - - at91_chan->curbuf = nb; - HAL_WRITE_UINT32(base + AT91_US_RCR, 0); - HAL_READ_UINT32(base + AT91_US_RPR, (CYG_ADDRESS) end); - HAL_WRITE_UINT32(base + AT91_US_RTO, RCV_TIMEOUT); - HAL_WRITE_UINT32(base + AT91_US_CR, AT91_US_CR_RSTATUS | AT91_US_CR_STTTO); - HAL_WRITE_UINT32(base + AT91_US_RPR, (CYG_ADDRESS) at91_chan->rcv_buffer[nb]); - HAL_WRITE_UINT32(base + AT91_US_RCR, at91_chan->rcv_chunk_size); - HAL_WRITE_UINT32( - base + AT91_US_IER, - AT91_US_IER_ENDRX | AT91_US_IER_TIMEOUT - ); + if (stat & AT91_US_IER_ENDRX) { + while (at91_chan->curbufread!=at91_chan->curbufwrite) + { + cyg_uint8 *end; + const cyg_uint8 *p; + const cyg_uint32 cb = at91_chan->curbufread; + at91_chan->curbufread=(at91_chan->curbufread+1)%BUFNUM; + + CYG_ASSERT((at91_chan->end[cb]==NULL),("serial DSR received interrupt without data\n")); + + end=at91_chan->end[cb]; + at91_chan->end[cb]=NULL; + p=at91_chan->rcv_buffer[cb]; while (p < end) { rcv_req_reply_t res; @@ -543,6 +593,10 @@ } } } + // now we want timeout interrupts again. + HAL_WRITE_UINT32(base + AT91_US_RTO, RCV_TIMEOUT); + HAL_WRITE_UINT32(base + AT91_US_IER, AT91_US_IER_TIMEOUT); + } if (stat & AT91_US_IER_TxRDY) { at91_chan->flags &= ~SIFLG_XMIT_BUSY;