253 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			253 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
| 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<length;i++) {
 | |
| 		byte = serial_read(unit);
 | |
| 		if (byte==-1) break;
 | |
| 		cdata[i] = (char)byte; 
 | |
| 	}
 | |
| 	return length;
 | |
| }
 | |
| 
 | |
| int serial_device_read_nonblock( int unit, void *data, int length, int offset )
 | |
| {
 | |
| 	int i,byte;
 | |
| 	char *cdata = data;
 | |
| 	for(i=0;i<length;i++) {
 | |
| 		byte = serial_read_nonblock(unit);
 | |
| 		if (byte==-1) break;
 | |
| 		cdata[i] = (char)byte;
 | |
| 	}
 | |
| 	return length;
 | |
| }
 | |
| 
 | |
| int serial_device_write( int unit, const void *data, int length, int offset )
 | |
| {
 | |
| 	int i;
 | |
| 	const char *cdata = data;
 | |
| 	for(i=0;i<length;i++) {
 | |
| 		serial_write(unit,cdata[i]);
 | |
| 	}
 | |
| 	return length;
 | |
| }
 | |
| 
 | |
| 
 | |
| static struct device_driver serial_driver = {
 | |
|        .name           = "serial",
 | |
|        .probe          = serial_device_probe,
 | |
|        .read           = serial_device_read,
 | |
|        .read_nonblock  = serial_device_read_nonblock,
 | |
|        .write          = serial_device_write
 | |
| };
 | |
| 
 | |
| void serial_init()
 | |
| {
 | |
| 	int i;
 | |
| 	for(i = 0; i < sizeof(serial_ports) / sizeof(int); i++) {
 | |
| 		int status = serial_init_port(i);
 | |
| 	}
 | |
| 	device_driver_register(&serial_driver);
 | |
| }
 | |
| 
 | |
| 
 |