242 lines
6.2 KiB
C
242 lines
6.2 KiB
C
/* vim: tabstop=4 shiftwidth=4 noexpandtab
|
|
* This file is part of ToaruOS and is released under the terms
|
|
* of the NCSA / University of Illinois License - see LICENSE.md
|
|
* Copyright (C) 2014-2018 K. Lange
|
|
*
|
|
* Serial communication device
|
|
*
|
|
*/
|
|
|
|
#include <kernel/system.h>
|
|
#include <kernel/fs.h>
|
|
#include <kernel/pipe.h>
|
|
#include <kernel/logging.h>
|
|
#include <kernel/args.h>
|
|
#include <kernel/module.h>
|
|
|
|
#define SERIAL_PORT_A 0x3F8
|
|
#define SERIAL_PORT_B 0x2F8
|
|
#define SERIAL_PORT_C 0x3E8
|
|
#define SERIAL_PORT_D 0x2E8
|
|
|
|
#define SERIAL_IRQ_AC 4
|
|
#define SERIAL_IRQ_BD 3
|
|
|
|
static char serial_recv(int device);
|
|
|
|
static fs_node_t * _serial_port_a = NULL;
|
|
static fs_node_t * _serial_port_b = NULL;
|
|
static fs_node_t * _serial_port_c = NULL;
|
|
static fs_node_t * _serial_port_d = NULL;
|
|
|
|
static uint8_t convert(uint8_t in) {
|
|
switch (in) {
|
|
case 0x7F:
|
|
return 0x08;
|
|
case 0x0D:
|
|
return '\n';
|
|
default:
|
|
return in;
|
|
}
|
|
}
|
|
|
|
static fs_node_t ** pipe_for_port(int port) {
|
|
switch (port) {
|
|
case SERIAL_PORT_A: return &_serial_port_a;
|
|
case SERIAL_PORT_B: return &_serial_port_b;
|
|
case SERIAL_PORT_C: return &_serial_port_c;
|
|
case SERIAL_PORT_D: return &_serial_port_d;
|
|
}
|
|
__builtin_unreachable();
|
|
}
|
|
|
|
static int serial_handler_ac(struct regs *r) {
|
|
char serial;
|
|
int port = 0;
|
|
if (inportb(SERIAL_PORT_A+1) & 0x01) {
|
|
port = SERIAL_PORT_A;
|
|
} else {
|
|
port = SERIAL_PORT_C;
|
|
}
|
|
serial = serial_recv(port);
|
|
irq_ack(SERIAL_IRQ_AC);
|
|
uint8_t buf[] = {convert(serial), 0};
|
|
write_fs(*pipe_for_port(port), 0, 1, buf);
|
|
return 1;
|
|
}
|
|
|
|
static int serial_handler_bd(struct regs *r) {
|
|
char serial;
|
|
int port = 0;
|
|
debug_print(NOTICE, "Received something on secondary port");
|
|
if (inportb(SERIAL_PORT_B+1) & 0x01) {
|
|
port = SERIAL_PORT_B;
|
|
} else {
|
|
port = SERIAL_PORT_D;
|
|
}
|
|
serial = serial_recv(port);
|
|
irq_ack(SERIAL_IRQ_BD);
|
|
uint8_t buf[] = {convert(serial), 0};
|
|
write_fs(*pipe_for_port(port), 0, 1, buf);
|
|
return 1;
|
|
}
|
|
|
|
static void serial_enable(int port) {
|
|
outportb(port + 1, 0x00); /* Disable interrupts */
|
|
outportb(port + 3, 0x80); /* Enable divisor mode */
|
|
outportb(port + 0, 0x01); /* Div Low: 01 Set the port to 115200 bps */
|
|
outportb(port + 1, 0x00); /* Div High: 00 */
|
|
outportb(port + 3, 0x03); /* Disable divisor mode, set parity */
|
|
outportb(port + 2, 0xC7); /* Enable FIFO and clear */
|
|
outportb(port + 4, 0x0B); /* Enable interrupts */
|
|
outportb(port + 1, 0x01); /* Enable interrupts */
|
|
}
|
|
|
|
static int serial_rcvd(int device) {
|
|
return inportb(device + 5) & 1;
|
|
}
|
|
|
|
static char serial_recv(int device) {
|
|
while (serial_rcvd(device) == 0) ;
|
|
return inportb(device);
|
|
}
|
|
|
|
static int serial_transmit_empty(int device) {
|
|
return inportb(device + 5) & 0x20;
|
|
}
|
|
|
|
static void serial_send(int device, char out) {
|
|
while (serial_transmit_empty(device) == 0);
|
|
outportb(device, out);
|
|
}
|
|
|
|
static void serial_string(char * out) {
|
|
for (uint32_t i = 0; i < strlen(out); ++i) {
|
|
serial_send(SERIAL_PORT_A, out[i]);
|
|
}
|
|
}
|
|
|
|
static uint32_t read_serial(fs_node_t *node, uint32_t offset, uint32_t size, uint8_t *buffer);
|
|
static uint32_t write_serial(fs_node_t *node, uint32_t offset, uint32_t size, uint8_t *buffer);
|
|
static void open_serial(fs_node_t *node, unsigned int flags);
|
|
static void close_serial(fs_node_t *node);
|
|
|
|
static uint32_t read_serial(fs_node_t *node, uint32_t offset, uint32_t size, uint8_t *buffer) {
|
|
return read_fs(*pipe_for_port((int)node->device), offset, size, buffer);
|
|
}
|
|
|
|
static uint32_t write_serial(fs_node_t *node, uint32_t offset, uint32_t size, uint8_t *buffer) {
|
|
uint32_t sent = 0;
|
|
while (sent < size) {
|
|
serial_send((int)node->device, buffer[sent]);
|
|
sent++;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
static void open_serial(fs_node_t * node, unsigned int flags) {
|
|
return;
|
|
}
|
|
|
|
static void close_serial(fs_node_t * node) {
|
|
return;
|
|
}
|
|
|
|
static int wait_serial(fs_node_t * node, void * process) {
|
|
return selectwait_fs(*pipe_for_port((int)node->device), process);
|
|
}
|
|
static int check_serial(fs_node_t * node) {
|
|
return selectcheck_fs(*pipe_for_port((int)node->device));
|
|
}
|
|
|
|
static int have_installed_ac = 0;
|
|
static int have_installed_bd = 0;
|
|
|
|
static fs_node_t * serial_device_create(int device) {
|
|
fs_node_t * fnode = malloc(sizeof(fs_node_t));
|
|
memset(fnode, 0x00, sizeof(fs_node_t));
|
|
fnode->device= (void *)device;
|
|
strcpy(fnode->name, "serial");
|
|
fnode->uid = 0;
|
|
fnode->gid = 0;
|
|
fnode->mask = 0660;
|
|
fnode->flags = FS_CHARDEVICE;
|
|
fnode->read = read_serial;
|
|
fnode->write = write_serial;
|
|
fnode->open = open_serial;
|
|
fnode->close = close_serial;
|
|
fnode->readdir = NULL;
|
|
fnode->finddir = NULL;
|
|
fnode->ioctl = NULL; /* TODO ioctls for raw serial devices */
|
|
|
|
fnode->selectcheck = check_serial;
|
|
fnode->selectwait = wait_serial;
|
|
|
|
fnode->atime = now();
|
|
fnode->mtime = fnode->atime;
|
|
fnode->ctime = fnode->atime;
|
|
|
|
serial_enable(device);
|
|
|
|
if (device == SERIAL_PORT_A || device == SERIAL_PORT_C) {
|
|
if (!have_installed_ac) {
|
|
irq_install_handler(SERIAL_IRQ_AC, serial_handler_ac, "serial ac");
|
|
have_installed_ac = 1;
|
|
}
|
|
} else {
|
|
if (!have_installed_bd) {
|
|
irq_install_handler(SERIAL_IRQ_BD, serial_handler_bd, "serial bd");
|
|
have_installed_bd = 1;
|
|
}
|
|
}
|
|
|
|
*pipe_for_port(device) = make_pipe(128);
|
|
|
|
return fnode;
|
|
}
|
|
|
|
static int serial_mount_devices(void) {
|
|
|
|
fs_node_t * ttyS0 = serial_device_create(SERIAL_PORT_A);
|
|
vfs_mount("/dev/ttyS0", ttyS0);
|
|
|
|
fs_node_t * ttyS1 = serial_device_create(SERIAL_PORT_B);
|
|
vfs_mount("/dev/ttyS1", ttyS1);
|
|
|
|
fs_node_t * ttyS2 = serial_device_create(SERIAL_PORT_C);
|
|
vfs_mount("/dev/ttyS2", ttyS2);
|
|
|
|
fs_node_t * ttyS3 = serial_device_create(SERIAL_PORT_D);
|
|
vfs_mount("/dev/ttyS3", ttyS3);
|
|
|
|
char * c;
|
|
if ((c = args_value("logtoserial"))) {
|
|
debug_file = ttyS0;
|
|
if (!strcmp(c,"INFO") || !strcmp(c,"info")) {
|
|
debug_level = INFO;
|
|
} else if (!strcmp(c,"NOTICE") || !strcmp(c,"notice")) {
|
|
debug_level = NOTICE;
|
|
} else if (!strcmp(c,"WARNING") || !strcmp(c,"warning")) {
|
|
debug_level = WARNING;
|
|
} else if (!strcmp(c,"ERROR") || !strcmp(c,"error")) {
|
|
debug_level = ERROR;
|
|
} else if (!strcmp(c,"CRITICAL") || !strcmp(c,"critical")) {
|
|
debug_level = CRITICAL;
|
|
} else if (!strcmp(c,"INSANE") || !strcmp(c,"insane")) {
|
|
debug_level = INSANE;
|
|
} else {
|
|
debug_level = atoi(c);
|
|
}
|
|
debug_print(NOTICE, "Serial logging enabled at level %d.", debug_level);
|
|
}
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int serial_finalize(void) {
|
|
return 0;
|
|
}
|
|
|
|
MODULE_DEF(serial, serial_mount_devices, serial_finalize);
|