/* Copyright (C) 2016-2019 The University of Notre Dame This software is distributed under the GNU General Public License. See the file LICENSE for details. */ /* Derived from: https://wiki.osdev.org/Serial_Ports#Example_Code For more info: https://wiki.osdev.org/Serial_Ports http://www.webcitation.org/5ugQv5JOw TODO: Interrupt support; polling IO is not a good idea for carbon free air */ #include "kernel/types.h" #include "ioports.h" #include "interrupt.h" #include "string.h" #include "device.h" #include "event.h" #include "event_queue.h" #define COM1 0x3f8 #define COM2 0x2F8 #define COM3 0x3E8 #define COM4 0x2E8 #define SERIAL_DATA 0 // If DLAB disabled in LCR #define SERIAL_IER 1 #define SERIAL_IRQ_DATA_AVAILABILE (0x01 << 0) #define SERIAL_IRQ_TRASMITTER_EMPTY (0x01 << 1) #define SERIAL_IRQ_ERROR (0x01 << 2) #define SERIAL_IRQ_STATUS_CHANGE (0x01 << 3) #define SERIAL_DIVISOR_LO 0 // If DLAB enabled in LCR #define SERIAL_DIVISOR_HI 1 // If DLAB enabled in LCR #define SERIAL_FCR 2 #define SERIAL_FIFO_ENABLE (0x01 << 0) #define SERIAL_FIFO_CLEAR_RECEIVER (0x01 << 1) #define SERIAL_FIFO_CLEAR_TRANSMITTER (0x01 << 2) #define SERIAL_FIFO_DMA_MODE (0x01 << 3) #define SERIAL_TRIGGER_LEVEL0 (0x00 << 6) // 1 byte in FIFO #define SERIAL_TRIGGER_LEVEL1 (0x01 << 6) // 4 bytes #define SERIAL_TRIGGER_LEVEL2 (0x10 << 6) // 8 bytes #define SERIAL_TRIGGER_LEVEL3 (0x11 << 6) // 14 bytes #define SERIAL_LCR 3 #define SERIAL_CHARLEN_START (0x01 << 0) #define SERIAL_STOP_BITS (0x01 << 2) #define SERIAL_DLAB_ENABLE (0x01 << 3) #define SERIAL_MCR 4 #define SERIAL_DATA_TERMINAL_READY (0x01 << 0) #define SERIAL_REQUEST_TO_SEND (0x01 << 1) #define SERIAL_AUX_OUT1 (0x01 << 2) #define SERIAL_AUX_OUT2 (0x01 << 3) #define SERIAL_LSR 5 #define SERIAL_DATA_AVAILABLE (0x01 << 0) #define SERIAL_TRANSMIT_EMPTY (0x01 << 5) #define SERIAL_MSR 6 #define SERIAL_SCRATCH 7 // units 0,1,2,3 static const int serial_ports[2] = { COM1, COM2 }; static const int serial_ports_irq[2] = { 4, 3 }; static struct event_queue *queue[2]; static void serial_interrupt(int intr, int intr_code) { intr=intr-32; // reduce by IRQ table offset; IRQ is not unique! We may only support 2 COMs! int port_no=intr==4?0:1, port = serial_ports[port_no]; while(inb(port + SERIAL_LSR) & SERIAL_DATA_AVAILABLE) { char ch=inb(port); struct event e; e.type = EVENT_DATA; e.code = ch; event_queue_post(queue[port_no],&e); } } static int serial_init_port(int unit) { int port = serial_ports[unit]; // probe for hardware if(inb(port+SERIAL_LSR)==0xff) return 0; //Disable interrupts outb(0x00, port + SERIAL_IER); //Enable DLAB(set baud rate divisor) outb(SERIAL_DLAB_ENABLE, port + SERIAL_LCR); //Set divisor to 3(lo byte) 38400 baud outb(0x03, port + SERIAL_DIVISOR_LO); //(hi byte) outb(0x00, port + SERIAL_DIVISOR_HI); //8 bits, no parity, one stop bit outb(SERIAL_CHARLEN_START * 3, port + SERIAL_LCR); #ifdef SERIALFIFO //Enable FIFO, clear them, with 14 - byte threshold outb(SERIAL_FIFO_ENABLE | SERIAL_FIFO_CLEAR_RECEIVER | SERIAL_FIFO_CLEAR_TRANSMITTER | SERIAL_TRIGGER_LEVEL0 , port + SERIAL_FCR); #else outb(0 , port + SERIAL_FCR); #endif //IRQs enabled, RTS / DSR set outb(SERIAL_DATA_TERMINAL_READY | SERIAL_REQUEST_TO_SEND | SERIAL_AUX_OUT2, port + SERIAL_MCR); // setup interrupt support (need to add 32 to IRQ number!!!!) interrupt_register(32+serial_ports_irq[unit], serial_interrupt); interrupt_enable(32+serial_ports_irq[unit]); // + received data available outb(SERIAL_IRQ_DATA_AVAILABILE, port + SERIAL_IER); queue[unit]=event_queue_create(); printf("[COM%d] serial_init_port %x: ready [IRQ=%d QUE=%x].\n",unit,port,serial_ports_irq[unit],queue[unit]); return 1; } static int serial_received(int port) { return inb(port + SERIAL_LSR) & SERIAL_DATA_AVAILABLE; } static int is_transmit_empty(int port) { return inb(port + SERIAL_LSR) & SERIAL_TRANSMIT_EMPTY; } static int is_valid_port(uint8_t port_no) { return port_no < sizeof(serial_ports) / sizeof(int); } /* Polling read */ int serial_read(uint8_t port_no) { if(!is_valid_port(port_no)) return -1; while(serial_received(serial_ports[port_no]) == 0); printf("[COM%d]\n",port_no); return inb(serial_ports[port_no]); } /* Interrupt driven event queue (blocks process if no dat ais available) */ int serial_getchar(uint8_t port_no) { struct event e; if(!is_valid_port(port_no)) return -1; event_queue_read(queue[port_no],&e,sizeof(e)); if(e.type==EVENT_DATA) return (int)e.code; return -1; } int serial_read_nonblock(uint8_t port_no) { if(!is_valid_port(port_no)) return -1; if (serial_received(serial_ports[port_no]) == 0) return -1; printf("[COM%d]\n",port_no); return inb(serial_ports[port_no]); } int serial_write(uint8_t port_no, char a) { if(!is_valid_port(port_no)) return -1; while(is_transmit_empty(serial_ports[port_no]) == 0); outb(a, serial_ports[port_no]); return 0; } int serial_device_probe( int unit, int *blocksize, int *nblocks, char *info ) { if(unit<0 || unit>3) return 0; // TODO probing for real hardware // reinit, second time (first time in serial_init, but needed to register the device) int status=serial_init_port(unit); if (!status) return -1; *blocksize = 1; *nblocks = 0; strcpy(info,"serial"); return 1; } int serial_device_read( int unit, void *data, int length, int offset ) { int i,byte; char *cdata = data; for(i=0;i