/* * libfb - FreeBASIC's runtime library * Copyright (C) 2004-2007 The FreeBASIC development team. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * As a special exception, the copyright holders of this library give * you permission to link this library with independent modules to * produce an executable, regardless of the license terms of these * independent modules, and to copy and distribute the resulting * executable under terms of your choice, provided that you also meet, * for each linked independent module, the terms and conditions of the * license of that module. An independent module is a module which is * not derived from or based on this library. If you modify this library, * you may extend this exception to your version of the library, but * you are not obligated to do so. If you do not wish to do so, delete * this exception statement from your version. */ /* * io_serial.c -- serial port driver for Dos * * chng: feb/2007 written [jeffm] * */ #include "fb.h" /* PIC port addresses. */ #define ICU_BASE 0x20 #define ICU_OCW2 (ICU_BASE + 0) #define ICU_MASK (ICU_BASE + 1) /* UART port registers */ #define UART_RBR 0x00 /* DLAB = 0 - Receive Buffer*/ #define UART_THR 0x00 /* DLAB = 0 - Transmit Hold */ #define UART_IER 0x01 /* interrupt enable register */ #define UART_IIR 0x02 /* interrupt ID register */ #define UART_FCR 0x02 /* FIFO control register */ #define UART_LCR 0x03 /* line control / data format */ #define UART_MCR 0x04 /* modem control register */ #define UART_LSR 0x05 /* line status register */ #define UART_MSR 0x06 /* modem status register */ #define UART_SCR 0x07 /* scratch register */ #define UART_DL_LSB 0x00 /* DLAB = 1 */ #define UART_DL_MSB 0x01 /* DLAB = 1 */ #define LCR_DLAB 0x80 #define MCR_OUT2 8 #define MCR_OUT1 4 #define MCR_RTS 2 #define MCR_DTR 1 #define IER_SINP 8 #define IER_EBRK 4 #define IER_TBE 2 #define IER_RXRD 1 /* FLAGS */ #define FLAG_KEEP_DTR 1 #define FLAG_SUPPRESS_RTS 2 /* UART types */ #define UART_IS_NONE 0 #define UART_IS_8250 1 #define UART_IS_16450 2 #define UART_IS_16550 3 #define UART_IS_16550A 4 #define DEFAULT_BUFFERSIZE 2048 #define COMM_CHECKED_CHIPTYPE 1 #define COMM_HAVE_BUFFER 2 #define FIRST_IRQ 3 #define IRQ_COUNT 2 #define MAX_COMM 4 #define COMM_PROPS( n ) &comm_props[n-1] typedef struct { int usecount; /* irq usage count for IRQ sharing */ int old_pic_mask; /* old pic mask */ int first; /* first comm in shared IRQ chain */ } irq_props_t; typedef struct { unsigned char * data; /* pointer to buffer */ int size; /* size of buffer */ int head; /* next char position to get */ int tail; /* next char position to put */ int length; /* length of chars to get in the buffer */ } buffer_t; typedef struct { int baseaddr; /* base port address */ int irq; /* irq number */ int initflags; /* initialization flags */ int inuse; /* in use flag */ int chiptype; /* chiptype */ int counter; /* interrupt counter */ int next; /* next comm in shared IRQ */ buffer_t rxbuf; /* receive buffer */ buffer_t txbuf; /* transmit buffer */ int txclear; /* transmit buffer cleared */ int flags; /* additional flags */ } comm_props_t; typedef struct { int com_num; FB_SERIAL_OPTIONS * pOptions; } DOS_SERIAL_INFO; /* private local procs */ static int UART_detect_chiptype( unsigned int baseaddr ); static int UART_set_baud( unsigned int baseaddr, int baud ); static int UART_set_data_format( unsigned int baseaddr, int parity, int data, int stop ); static int comm_isr( unsigned irq ); static int comm_init( int com_num, unsigned int baseaddr, int irq ); static int comm_exit( int com_num ); /* public procs */ int comm_open( int com_num, int baud, int parity, int data, int stop, int txbufsize, int rxbufsize, int flags ); int comm_close( int com_num ); int comm_putc( int com_num, int ch ); int comm_getc( int com_num ); int comm_bytes_remaining( int com_num ); /* local state */ static comm_init_count = 0; static irq_props_t irq_props[IRQ_COUNT] = { { 0, 0, 0 }, { 0, 0, 0 } }; static comm_props_t comm_props[MAX_COMM] = { { 0x3f8, 4, 0, 0, 0, 0, 0 }, { 0x2f8, 3, 0, 0, 0, 0, 0 }, { 0x3e8, 4, 0, 0, 0, 0, 0 }, { 0x2e8, 3, 0, 0, 0, 0, 0 } }; /*:::::*/ static int BUFFER_reset( buffer_t * buf ) { memset( buf->data, 0, buf->size); buf->head = buf->tail = buf->length = 0; } /*:::::*/ static int BUFFER_alloc( buffer_t * buf, int size ) { /* TODO: optimize for power of 2 buffer sizes */ buf->size = 0; buf->data = malloc( size ); if( buf->data ) { buf->size = size; fb_dos_lock_data( buf->data, buf->size); } buf->head = buf->tail = buf->length = 0; /* TRUE = success */ return( buf->size != 0 ); } /*:::::*/ static void BUFFER_free( buffer_t * buf ) { if( buf->data ) { memset( buf->data, 0, buf->size); fb_dos_unlock_data( buf->data, buf->size); free( buf->data ); buf->data = NULL; } buf->head = buf->tail = buf->size = buf->length = 0; } /*:::::*/ static int BUFFER_putc( buffer_t * buf, int ch ) { if( buf->length == buf->size ) return -1; buf->data[ buf->tail ] = ch; /* TODO: optimize for power of 2 buffer sizes */ buf->tail = (buf->tail + 1) % buf->size;; buf->length++; return( ch ); } static void end_BUFFER_putc( void ) { } /*:::::*/ static int BUFFER_getc( buffer_t * buf ) { int ch = -1; if( buf->length ) { ch = buf->data[ buf->head ]; /* TODO: optimize for power of 2 buffer sizes */ buf->head = (buf->head + 1) % buf->size; buf->length--; } return ch; } static void end_BUFFER_getc( void ) { } /*:::::*/ static int UART_detect_chiptype( unsigned int baseaddr ) { int tmp; /* test for LCR */ outportb( baseaddr + UART_LCR ,0x1b ); if ( inportb( baseaddr + UART_LCR ) != 0x1b ) return UART_IS_NONE; outportb( baseaddr + UART_LCR, 0x3 ); if ( inportb( baseaddr + UART_LCR ) != UART_LCR ) return UART_IS_NONE; /* test for scratch register */ outportb( baseaddr + UART_SCR, 0x55 ); if ( inportb( baseaddr + UART_SCR ) != 0x55 ) return UART_IS_8250; outportb( baseaddr + UART_SCR, 0xAA ); if ( inportb( baseaddr + UART_SCR ) != 0xAA ) return UART_IS_8250; /* check for FIFO */ outportb(baseaddr + UART_FCR, 1); tmp = inportb( baseaddr + UART_IIR); if ( ( tmp & 0x80 ) == 0 ) return UART_IS_16450; if ( ( tmp & 0x40 ) == 0 ) return UART_IS_16550; /* clear FCR */ outportb( baseaddr + UART_FCR, 0x0 ); return UART_IS_16550A; } static int UART_set_baud( unsigned int baseaddr, int baud ) { int divisor, lsb, msb, tmp; if(( baud < 50 ) || ( baud > 115200 )) return( FALSE ); divisor = 115200 / baud; lsb = divisor & 0xff; msb = divisor >> 8; tmp = inportb( baseaddr + UART_LCR ); tmp |= LCR_DLAB; outportb( baseaddr + UART_LCR, tmp ); outportb( baseaddr + UART_DL_LSB, lsb ); outportb( baseaddr + UART_DL_MSB, msb ); tmp = inportb( baseaddr + UART_LCR ); tmp &= ~LCR_DLAB; outportb( baseaddr + UART_LCR, tmp ); } /*:::::*/ static int UART_set_data_format( unsigned int baseaddr, int parity, int data, int stop ) { int fmt = 0; switch( data ) { case 5: break; case 6: fmt |= 1; break; case 7: fmt |= 2; break; case 8: fmt |= 3; break; default: return( FALSE ); } switch( parity ) { case FB_SERIAL_PARITY_NONE: break; case FB_SERIAL_PARITY_ODD: fmt |= 1 << 3; break; case FB_SERIAL_PARITY_EVEN: fmt |= 3 << 3; break; case FB_SERIAL_PARITY_MARK: fmt |= 5 << 3; break; case FB_SERIAL_PARITY_SPACE: fmt |= 7 << 3; break; default: return( FALSE ); } switch( stop ) { case FB_SERIAL_STOP_BITS_1: break; case FB_SERIAL_STOP_BITS_1_5: if( data != 5 ) return( FALSE ); break; case FB_SERIAL_STOP_BITS_2: fmt |= 4; break; default: return( FALSE ); } /* set data format - assumes DLAB is clear */ outportb( baseaddr + UART_LCR, fmt ); return( TRUE ); } /*:::::*/ static int comm_isr( unsigned irq ) { comm_props_t *cp; irq_props_t *ip; int ret, i, ch; disable (); outportb (ICU_OCW2, 0x20); ip = &irq_props[irq - FIRST_IRQ]; if( ip->usecount == 0) return; i = ip->first; /* TODO: FIFO Support on UARTs that support it */ while(i) { cp = COMM_PROPS(i); while(1) { ret = inportb( cp->baseaddr + UART_IIR ) & 0xf; if( ret == 1) break; //printf("comm_isr = %d\n", ret ); switch( ret ) { case 4: /* receive data */ ch = inportb( cp->baseaddr + UART_RBR ); BUFFER_putc( &cp->rxbuf, ch ); break; case 2: /* tx buffer empty */ ch = BUFFER_getc( &cp->txbuf ); if( ch >= 0 ) { cp->txclear = FALSE; outportb( cp->baseaddr + UART_THR, ch ); /* TODO: check CTS if CS option set and start timer */ } else { ret = inportb( cp->baseaddr + UART_IIR ); cp->txclear = TRUE; } break; case 0: /* change in input signal */ ret = inportb( cp->baseaddr + UART_MSR ); /* TODO: start DSR low timer if DS option set */ /* TODO: start DCD low timer if CD option set */ /* TODO: check errors */ break; case 6: /* reception error or break */ ret = inportb( cp->baseaddr + UART_LSR ); /* TODO: check errors */ break; default: break; } } /* next comm port in a shared IRQ chain */ i = cp->next; } return(0); } static void end_comm_isr( void ) { }; /*:::::*/ static int comm_init( int com_num, unsigned int baseaddr, int irq ) { comm_props_t *cp; irq_props_t *ip; int tmp, i; if( com_num < 1 || com_num > MAX_COMM ) return( FALSE ); cp = COMM_PROPS(com_num); if( comm_init_count == 0 ) { lock_var( comm_init_count ); lock_array( irq_props ); lock_array( comm_props ); lock_proc( comm_isr ); lock_proc( BUFFER_getc ); lock_proc( BUFFER_putc ); comm_init_count++; } if( baseaddr ) cp->baseaddr = baseaddr; if( irq ) cp->irq = irq; ip = &irq_props[cp->irq - FIRST_IRQ]; outportb( cp->baseaddr + UART_FCR, 0 ); /* TODO: FIFO setup on UARTs that support it */ /* flush ints pending */ for( i = 0; i < 17; i++ ) { tmp = inportb( cp->baseaddr + UART_IIR ); if( tmp & 0x38 == 0 ) break; tmp = inportb( cp->baseaddr + UART_RBR ); } /* disable interrupts on the UART */ outportb( cp->baseaddr + UART_IER, 0 ); /* clear status ints */ tmp = inportb( cp->baseaddr + UART_LSR ); tmp = inportb( cp->baseaddr + UART_MSR ); /* enable interrupt gate */ outportb( cp->baseaddr + UART_MCR, MCR_OUT2 ); if( ip->usecount == 0 ) { if( !fb_isr_set( cp->irq, comm_isr, 0, 1024 )) return( FALSE ); /* set the interrupt controller mask */ fb_dos_cli(); /* TODO: Support for slave PIC? */ ip->old_pic_mask = inportb (ICU_MASK); outportb (ICU_MASK, ip->old_pic_mask & ~(1 << cp->irq)); ip->usecount++; fb_dos_sti(); } fb_dos_cli(); /* chain comm ports for IRQ sharing */ if( ip->first ) comm_props[ip->first - 1].next = ip->first; ip->first = com_num; /* enable the UART's interrupts */ tmp = inportb( cp->baseaddr + UART_IER ); tmp |= IER_SINP | IER_EBRK | IER_TBE | IER_RXRD; outportb( cp->baseaddr + UART_IER, tmp ); /* enable interrupt gate */ tmp = inportb( cp->baseaddr + UART_MCR ); tmp |= MCR_OUT2 | MCR_RTS | MCR_DTR; if( cp->flags & FLAG_SUPPRESS_RTS ) tmp ^= MCR_RTS; outportb( cp->baseaddr + UART_MCR, tmp ); /* TODO: MCR_RTS should be set only after waiting for DSR if the OP/CS protocol option is set */ fb_dos_sti(); return( TRUE ); } static int comm_exit( int com_num ) { comm_props_t *cp; irq_props_t *ip; int tmp, irq, i; if( com_num < 1 || com_num > MAX_COMM ) return( FALSE ); cp = COMM_PROPS(com_num); /* disable interrupt gate on the UART */ tmp = inportb( cp->baseaddr + UART_MCR ); if( cp->flags & FLAG_KEEP_DTR ) tmp &= ~( MCR_OUT2 | MCR_OUT1 | MCR_RTS ); else tmp &= ~( MCR_OUT2 | MCR_OUT1 | MCR_RTS | MCR_DTR ); outportb( cp->baseaddr + UART_MCR, tmp ); /* disable interrupts on the UART */ tmp = inportb( cp->baseaddr + UART_IER ); tmp &= ~( IER_SINP | IER_EBRK | IER_TBE | IER_RXRD ); outportb( cp->baseaddr + UART_IER, tmp ); ip = &irq_props[cp->irq - FIRST_IRQ]; /* un-chain comm ports for IRQ sharing */ fb_dos_cli(); if( ip->first == com_num ) { ip->first = cp->next; } else { for( i = 0; i < MAX_COMM; i ++ ) { if( comm_props[i].next == com_num ) { comm_props[i].next = cp->next; break; } } } cp->next = 0; fb_dos_sti(); /* restore interrupt controller mask */ if( ip->usecount == 1 ) { disable (); /* TODO: Support for slave PIC? */ outportb (ICU_MASK, inportb (ICU_MASK) | (ip->old_pic_mask & (1 << cp->irq))); enable (); fb_isr_reset( cp->irq ); } if( ip->usecount != 0 ) ip->usecount--; /* no more com ports in use? */ if( comm_init_count == 1 ) { unlock_proc( BUFFER_getc ); unlock_proc( BUFFER_putc ); unlock_proc( comm_isr ); unlock_array( comm_props ); unlock_array( irq_props ); unlock_var( comm_init_count ); } if( comm_init_count != 0 ) comm_init_count--; return( TRUE ); } /*:::::*/ int comm_open( int com_num, int baud, int parity, int data, int stop, int txbufsize, int rxbufsize, int flags ) { comm_props_t *cp; int tmp, ret; if( com_num < 1 || com_num > MAX_COMM ) return( FALSE ); cp = COMM_PROPS(com_num); if( cp->inuse ) return( FALSE ); if( (cp->initflags & COMM_CHECKED_CHIPTYPE) == 0 ) { cp->chiptype = UART_detect_chiptype( cp->baseaddr ); cp->initflags |= COMM_CHECKED_CHIPTYPE; } /* TODO: Autodetect IRQ in use by baseaddr */ if( cp->chiptype == 0 ) return( FALSE ); cp->txbuf.data = NULL; cp->txbuf.size = cp->txbuf.head = cp->txbuf.tail = 0; cp->rxbuf.data = NULL; cp->rxbuf.size = cp->rxbuf.head = cp->rxbuf.tail = 0; cp->txclear = TRUE; cp->flags = flags; if( BUFFER_alloc( &cp->txbuf, txbufsize )) if( BUFFER_alloc( &cp->rxbuf, rxbufsize )) if( UART_set_baud( cp->baseaddr, baud )) if( UART_set_data_format( cp->baseaddr, parity, data, stop )) if( comm_init( com_num, 0, 0 )) ret = TRUE; /* TODO: wait for DSR if OP/DS option is set */ /* TODO: Set RTS if RS option not set */ /* TODO: wait for DCD if OP/CD option is set */ /* TODO: Clean-up on TIMEOUT */ if( ret ) { cp->inuse = TRUE; } else { BUFFER_free( &cp->rxbuf); BUFFER_free( &cp->txbuf); } /* TRUE == success */ return( cp->inuse ); } /*:::::*/ int comm_close( int com_num ) { comm_props_t *cp; int tmp, ret; if( com_num < 1 || com_num > MAX_COMM ) return( FALSE ); cp = COMM_PROPS(com_num); if( cp->inuse == FALSE ) return( FALSE ); ret = comm_exit( com_num ); cp->inuse = FALSE; BUFFER_free( &cp->rxbuf); BUFFER_free( &cp->txbuf); return( TRUE ); } /*:::::*/ int comm_putc( int com_num, int ch ) { comm_props_t *cp; cp = COMM_PROPS(com_num); fb_dos_cli(); if( cp->txclear ) { cp->txclear = FALSE; outportb( cp->baseaddr + UART_THR, ch ); } else { ch = BUFFER_putc( &cp->txbuf, ch ); } fb_dos_sti(); return(ch); } /*:::::*/ int comm_getc( int com_num ) { int ch; comm_props_t *cp; cp = COMM_PROPS(com_num); fb_dos_cli(); ch = BUFFER_getc( &cp->rxbuf ); fb_dos_sti(); return(ch); } /*:::::*/ int comm_bytes_remaining( int com_num ) { int bytes; comm_props_t *cp; cp = COMM_PROPS(com_num); fb_dos_cli(); bytes = cp->rxbuf.length; fb_dos_sti(); return(bytes); } /*:::::*/ int fb_SerialOpen( struct _FB_FILE *handle, int iPort, FB_SERIAL_OPTIONS *options, const char *pszDevice, void **ppvHandle ) { int ret, flags = 0; if( options->TransmitBuffer == 0 ) options->TransmitBuffer = DEFAULT_BUFFERSIZE; else if( options->TransmitBuffer < 0 ) return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL ); if( options->ReceiveBuffer == 0 ) options->ReceiveBuffer = DEFAULT_BUFFERSIZE; else if( options->ReceiveBuffer < 0 ) return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL ); if( options->KeepDTREnabled ) flags |= FLAG_KEEP_DTR; if( options->SuppressRTS ) flags |= FLAG_SUPPRESS_RTS; ret = comm_open( iPort, options->uiSpeed, options->Parity, options->uiDataBits, options->StopBits, options->TransmitBuffer, options->ReceiveBuffer, flags ); if( ret ) { DOS_SERIAL_INFO *pInfo = (DOS_SERIAL_INFO *) calloc( 1, sizeof(DOS_SERIAL_INFO) ); DBG_ASSERT( ppvHandle!=NULL ); *ppvHandle = pInfo; pInfo->com_num = iPort; pInfo->pOptions = options; return fb_ErrorSetNum( FB_RTERROR_OK ); } return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL ); // [x] unsigned uiSpeed; // [x] unsigned uiDataBits; // [x] FB_SERIAL_PARITY Parity; // [x] FB_SERIAL_STOP_BITS StopBits; // [ ] unsigned DurationCTS; /* CS[msec] */ // [ ] unsigned DurationDSR; /* DS[msec] */ // [ ] unsigned DurationCD; /* CD[msec] */ // [ ] unsigned OpenTimeout; /* OP[msec] */ // [ ] int SuppressRTS; /* RS */ // [ ] int AddLF; /* LF, or ASC, or BIN */ // [ ] int CheckParity; /* PE */ // [x] int KeepDTREnabled; /* DT */ // [ ] int DiscardOnError; /* FE */ // [ ] int IgnoreAllErrors; /* ME */ // [ ] unsigned IRQNumber; /* IR2..IR15 */ // [x] unsigned TransmitBuffer; /* TBn - a value 0 means: default value */ // [x] unsigned ReceiveBuffer; /* RBn - a value 0 means: default value */ } /*:::::*/ int fb_SerialGetRemaining( struct _FB_FILE *handle, void *pvHandle, long *pLength ) { int bytes; DOS_SERIAL_INFO *pInfo = (DOS_SERIAL_INFO *) pvHandle; bytes = comm_bytes_remaining( pInfo->com_num ); if( bytes < 0 ) return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL ); if( pLength ) *pLength = (long) bytes; return fb_ErrorSetNum( FB_RTERROR_OK ); } /*:::::*/ int fb_SerialWrite( struct _FB_FILE *handle, void *pvHandle, const void *data, size_t length ) { DOS_SERIAL_INFO *pInfo = (DOS_SERIAL_INFO *) pvHandle; unsigned char * p = (unsigned char *)data; int ch, i; /* TODO: Support for ASC/LF options */ for( i=0; icom_num, p[i]); if( ch < 0 ) return fb_ErrorSetNum( FB_RTERROR_FILEIO ); } return fb_ErrorSetNum( FB_RTERROR_OK ); } /*:::::*/ int fb_SerialRead( struct _FB_FILE *handle, void *pvHandle, void *data, size_t *pLength ) { DOS_SERIAL_INFO *pInfo = (DOS_SERIAL_INFO *) pvHandle; int n = *pLength, ch, i, count = 0; unsigned char * p = (unsigned char *)data; int res = FB_RTERROR_OK; for( i = 0; i < n; i++ ) { ch = comm_getc( pInfo->com_num ); if( ch < 0 ) { res = FB_RTERROR_FILEIO; break; } p[i] = ch; count++; } *pLength = (size_t) count; return fb_ErrorSetNum( res ); } /*:::::*/ int fb_SerialClose( struct _FB_FILE *handle, void *pvHandle ) { DOS_SERIAL_INFO *pInfo = (DOS_SERIAL_INFO *) pvHandle; comm_close( pInfo->com_num ); free(pInfo); return fb_ErrorSetNum( FB_RTERROR_OK ); }