2024-10-14 23:07:43 +02:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2024-12-09 20:42:06 +01:00
|
|
|
/* 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
|
|
|
|
*/
|
2024-10-14 23:07:43 +02:00
|
|
|
|
|
|
|
#include "kernel/types.h"
|
|
|
|
#include "ioports.h"
|
2024-12-09 20:42:06 +01:00
|
|
|
#include "interrupt.h"
|
2024-10-14 23:07:43 +02:00
|
|
|
#include "string.h"
|
|
|
|
#include "device.h"
|
|
|
|
|
|
|
|
#define COM1 0x3f8
|
|
|
|
#define COM2 0x2F8
|
|
|
|
#define COM3 0x3E8
|
|
|
|
#define COM4 0x2E8
|
|
|
|
|
|
|
|
#define SERIAL_DATA 0 // If DLAB disabled in LCR
|
|
|
|
|
|
|
|
#define SERIAL_IRQ_ENABLE 1 // If DLAB disabled in LCR
|
|
|
|
#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)
|
2024-12-09 20:42:06 +01:00
|
|
|
#define SERIAL_FIFO_CLEAR_RECEIVER (0x01 << 1)
|
2024-10-14 23:07:43 +02:00
|
|
|
#define SERIAL_FIFO_CLEAR_TRANSMITTER (0x01 << 2)
|
|
|
|
#define SERIAL_FIFO_DMA_MODE (0x01 << 3)
|
2024-12-09 20:42:06 +01:00
|
|
|
#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
|
2024-10-14 23:07:43 +02:00
|
|
|
|
|
|
|
#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
|
|
|
|
|
2024-12-09 20:42:06 +01:00
|
|
|
// units 0,1,2,3
|
2024-10-14 23:07:43 +02:00
|
|
|
static const int serial_ports[4] = { COM1, COM2, COM3, COM4 };
|
2024-12-09 20:42:06 +01:00
|
|
|
static const int serial_ports_irq[4] = { 4, 3, 4, 3 };
|
|
|
|
|
|
|
|
static void serial_interrupt(int i, int intr_code)
|
|
|
|
{
|
|
|
|
printf("serial_interrupt %d %d\n",i,intr_code);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-10-14 23:07:43 +02:00
|
|
|
|
2024-12-09 20:42:06 +01:00
|
|
|
static void serial_init_port(int unit)
|
2024-10-14 23:07:43 +02:00
|
|
|
{
|
2024-12-09 20:42:06 +01:00
|
|
|
int port = serial_ports[unit];
|
|
|
|
//Disable interrupts
|
2024-10-14 23:07:43 +02:00
|
|
|
outb(0x00, port + SERIAL_IRQ_ENABLE);
|
|
|
|
//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);
|
|
|
|
//Enable FIFO, clear them, with 14 - byte threshold
|
2024-12-09 20:42:06 +01:00
|
|
|
outb(SERIAL_FIFO_ENABLE | SERIAL_FIFO_CLEAR_RECEIVER | SERIAL_FIFO_CLEAR_TRANSMITTER | SERIAL_TRIGGER_LEVEL0 , port + SERIAL_FCR);
|
2024-10-14 23:07:43 +02:00
|
|
|
//IRQs enabled, RTS / DSR set
|
|
|
|
outb(SERIAL_DATA_TERMINAL_READY | SERIAL_REQUEST_TO_SEND | SERIAL_AUX_OUT2, port + SERIAL_MCR);
|
2024-12-09 20:42:06 +01:00
|
|
|
|
|
|
|
// setup interrupt support
|
|
|
|
interrupt_register(serial_ports_irq[unit], serial_interrupt);
|
|
|
|
interrupt_enable(serial_ports_irq[unit]);
|
|
|
|
// + received data available
|
|
|
|
outb(0x01, port + SERIAL_IRQ_ENABLE);
|
|
|
|
|
|
|
|
printf("[COM%d] serial_init_port %x: ready.\n",unit,port);
|
2024-10-14 23:07:43 +02:00
|
|
|
}
|
|
|
|
|
2024-12-09 20:42:06 +01:00
|
|
|
|
2024-10-14 23:07:43 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-12-09 20:42:06 +01:00
|
|
|
int serial_read(uint8_t port_no)
|
2024-10-14 23:07:43 +02:00
|
|
|
{
|
|
|
|
if(!is_valid_port(port_no))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
while(serial_received(serial_ports[port_no]) == 0);
|
2024-12-09 20:42:06 +01:00
|
|
|
printf("[COM%d]\n",port_no);
|
|
|
|
return inb(serial_ports[port_no]);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2024-10-14 23:07:43 +02:00
|
|
|
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;
|
2024-12-09 20:42:06 +01:00
|
|
|
// TODO probing for real hardware
|
|
|
|
// reinit, second time (first time in serial_init, but needed to register the device)
|
|
|
|
serial_init_port(unit);
|
2024-10-14 23:07:43 +02:00
|
|
|
*blocksize = 1;
|
|
|
|
*nblocks = 0;
|
|
|
|
strcpy(info,"serial");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int serial_device_read( int unit, void *data, int length, int offset )
|
|
|
|
{
|
2024-12-09 20:42:06 +01:00
|
|
|
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;
|
2024-10-14 23:07:43 +02:00
|
|
|
char *cdata = data;
|
|
|
|
for(i=0;i<length;i++) {
|
2024-12-09 20:42:06 +01:00
|
|
|
byte = serial_read_nonblock(unit);
|
|
|
|
if (byte==-1) break;
|
|
|
|
cdata[i] = (char)byte;
|
2024-10-14 23:07:43 +02:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-12-09 20:42:06 +01:00
|
|
|
|
2024-10-14 23:07:43 +02:00
|
|
|
static struct device_driver serial_driver = {
|
|
|
|
.name = "serial",
|
|
|
|
.probe = serial_device_probe,
|
|
|
|
.read = serial_device_read,
|
2024-12-09 20:42:06 +01:00
|
|
|
.read_nonblock = serial_device_read_nonblock,
|
2024-10-14 23:07:43 +02:00
|
|
|
.write = serial_device_write
|
|
|
|
};
|
|
|
|
|
|
|
|
void serial_init()
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for(i = 0; i < sizeof(serial_ports) / sizeof(int); i++) {
|
2024-12-09 20:42:06 +01:00
|
|
|
serial_init_port(i);
|
2024-10-14 23:07:43 +02:00
|
|
|
}
|
|
|
|
device_driver_register(&serial_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
|