mirror of
https://github.com/justinian/jsix.git
synced 2025-12-10 00:14:32 -08:00
[uart] Add first pass UART driver and logger
First attempt at a UART driver. I'm not sure it's the most stable. Now that userspace is handling displaying logs, also removed serial and log output support from the kernel.
This commit is contained in:
22
src/user/drv.uart/io.cpp
Normal file
22
src/user/drv.uart/io.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "io.h"
|
||||
|
||||
uint8_t
|
||||
inb(uint16_t port)
|
||||
{
|
||||
uint8_t val;
|
||||
__asm__ __volatile__ ( "inb %1, %0" : "=a"(val) : "Nd"(port) );
|
||||
return val;
|
||||
}
|
||||
|
||||
void
|
||||
outb(uint16_t port, uint8_t val)
|
||||
{
|
||||
__asm__ __volatile__ ( "outb %0, %1" :: "a"(val), "Nd"(port) );
|
||||
}
|
||||
|
||||
void
|
||||
io_wait(unsigned times)
|
||||
{
|
||||
for (unsigned i = 0; i < times; ++i)
|
||||
outb(0x80, 0);
|
||||
}
|
||||
24
src/user/drv.uart/io.h
Normal file
24
src/user/drv.uart/io.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
/// Read a byte from an IO port.
|
||||
/// \arg port The address of the IO port
|
||||
/// \returns One byte read from the port
|
||||
uint8_t inb(uint16_t port);
|
||||
|
||||
/// Write a byte to an IO port.
|
||||
/// \arg port The addres of the IO port
|
||||
/// \arg val The byte to write
|
||||
void outb(uint16_t port, uint8_t val);
|
||||
|
||||
/// Pause briefly by doing IO to port 0x80
|
||||
/// \arg times Number of times to delay by writing
|
||||
void io_wait(unsigned times = 1);
|
||||
|
||||
}
|
||||
|
||||
constexpr uint16_t COM1 = 0x03f8;
|
||||
constexpr uint16_t COM2 = 0x02f8;
|
||||
196
src/user/drv.uart/main.cpp
Normal file
196
src/user/drv.uart/main.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
#include <new>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <j6/errors.h>
|
||||
#include <j6/flags.h>
|
||||
#include <j6/signals.h>
|
||||
#include <j6/syscalls.h>
|
||||
#include <j6/sysconf.h>
|
||||
#include <j6/types.h>
|
||||
#include <util/constexpr_hash.h>
|
||||
|
||||
#include "io.h"
|
||||
#include "serial.h"
|
||||
|
||||
extern "C" {
|
||||
int main(int, const char **);
|
||||
}
|
||||
|
||||
constexpr j6_handle_t handle_self = 1;
|
||||
constexpr j6_handle_t handle_sys = 2;
|
||||
|
||||
struct entry
|
||||
{
|
||||
uint8_t bytes;
|
||||
uint8_t area;
|
||||
uint8_t severity;
|
||||
uint8_t sequence;
|
||||
char message[0];
|
||||
};
|
||||
|
||||
static const uint8_t level_colors[] = {0x07, 0x07, 0x0f, 0x0b, 0x09};
|
||||
const char *level_names[] = {"", "debug", "info", "warn", "error", "fatal"};
|
||||
const char *area_names[] = {
|
||||
#define LOG(name, lvl) #name ,
|
||||
#include <j6/tables/log_areas.inc>
|
||||
#undef LOG
|
||||
nullptr
|
||||
};
|
||||
|
||||
constexpr size_t in_buf_size = 512;
|
||||
constexpr size_t out_buf_size = 128 * 1024;
|
||||
|
||||
uint8_t com1_in[in_buf_size];
|
||||
uint8_t com2_in[in_buf_size];
|
||||
uint8_t com1_out[out_buf_size];
|
||||
uint8_t com2_out[out_buf_size];
|
||||
|
||||
serial_port *g_com1;
|
||||
serial_port *g_com2;
|
||||
|
||||
constexpr size_t stack_size = 0x10000;
|
||||
constexpr uintptr_t stack_top = 0xf80000000;
|
||||
|
||||
void
|
||||
print_header()
|
||||
{
|
||||
char stringbuf[150];
|
||||
|
||||
unsigned version_major = j6_sysconf(j6sc_version_major);
|
||||
unsigned version_minor = j6_sysconf(j6sc_version_minor);
|
||||
unsigned version_patch = j6_sysconf(j6sc_version_patch);
|
||||
unsigned version_git = j6_sysconf(j6sc_version_gitsha);
|
||||
|
||||
size_t len = snprintf(stringbuf, sizeof(stringbuf),
|
||||
"\e[38;5;21mjsix OS\e[38;5;8m %d.%d.%d (%07x) booting...\e[0m\r\n",
|
||||
version_major, version_minor, version_patch, version_git);
|
||||
g_com1->write(stringbuf, len);
|
||||
}
|
||||
|
||||
void
|
||||
log_pump_proc()
|
||||
{
|
||||
size_t buffer_size = 0;
|
||||
void *message_buffer = nullptr;
|
||||
char stringbuf[300];
|
||||
|
||||
j6_status_t result = j6_system_request_iopl(handle_sys, 3);
|
||||
if (result != j6_status_ok)
|
||||
return;
|
||||
|
||||
while (true) {
|
||||
size_t size = buffer_size;
|
||||
j6_status_t s = j6_system_get_log(handle_sys, message_buffer, &size);
|
||||
|
||||
if (s == j6_err_insufficient) {
|
||||
free(message_buffer);
|
||||
buffer_size = size * 2;
|
||||
message_buffer = malloc(buffer_size);
|
||||
continue;
|
||||
} else if (s != j6_status_ok) {
|
||||
j6_log("uart driver got error from get_log");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
j6_signal_t sigs = 0;
|
||||
j6_kobject_wait(handle_sys, j6_signal_system_has_log, &sigs);
|
||||
continue;
|
||||
}
|
||||
|
||||
const entry *e = reinterpret_cast<entry*>(message_buffer);
|
||||
|
||||
const char *area_name = area_names[e->area];
|
||||
const char *level_name = level_names[e->severity];
|
||||
uint8_t level_color = level_colors[e->severity];
|
||||
|
||||
size_t len = snprintf(stringbuf, sizeof(stringbuf),
|
||||
"\e[38;5;%dm%7s %5s: %s\e[38;5;0m\r\n",
|
||||
level_color, area_name, level_name, e->message);
|
||||
g_com1->write(stringbuf, len);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, const char **argv)
|
||||
{
|
||||
j6_log("uart driver starting");
|
||||
|
||||
j6_handle_t endp = j6_handle_invalid;
|
||||
j6_status_t result = j6_status_ok;
|
||||
|
||||
result = j6_system_request_iopl(handle_sys, 3);
|
||||
if (result != j6_status_ok)
|
||||
return result;
|
||||
|
||||
result = j6_endpoint_create(&endp);
|
||||
if (result != j6_status_ok)
|
||||
return result;
|
||||
|
||||
result = j6_system_bind_irq(handle_sys, endp, 3);
|
||||
if (result != j6_status_ok)
|
||||
return result;
|
||||
|
||||
result = j6_system_bind_irq(handle_sys, endp, 4);
|
||||
if (result != j6_status_ok)
|
||||
return result;
|
||||
|
||||
serial_port com1 {COM1, in_buf_size, com1_in, out_buf_size, com1_out};
|
||||
serial_port com2 {COM2, in_buf_size, com2_in, out_buf_size, com2_out};
|
||||
g_com1 = &com1;
|
||||
g_com2 = &com2;
|
||||
|
||||
print_header();
|
||||
|
||||
j6_handle_t child_stack_vma = j6_handle_invalid;
|
||||
result = j6_vma_create_map(&child_stack_vma, stack_size, stack_top-stack_size, j6_vm_flag_write);
|
||||
if (result != j6_status_ok)
|
||||
return result;
|
||||
|
||||
uint64_t *sp = reinterpret_cast<uint64_t*>(stack_top - 0x10);
|
||||
sp[0] = sp[1] = 0;
|
||||
|
||||
j6_handle_t child = j6_handle_invalid;
|
||||
result = j6_thread_create(&child, handle_self, stack_top - 0x10, reinterpret_cast<uintptr_t>(&log_pump_proc));
|
||||
if (result != j6_status_ok)
|
||||
return result;
|
||||
|
||||
size_t len = 0;
|
||||
while (true) {
|
||||
uint64_t tag = 0;
|
||||
result = j6_endpoint_receive(endp, &tag, nullptr, &len, 10);
|
||||
if (result == j6_err_timed_out) {
|
||||
com1.handle_interrupt();
|
||||
com2.handle_interrupt();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result != j6_status_ok) {
|
||||
j6_log("uart driver got error waiting for irq");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!j6_tag_is_irq(tag)) {
|
||||
j6_log("uart driver got non-irq waiting for irq");
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned irq = j6_tag_to_irq(tag);
|
||||
switch (irq) {
|
||||
case 3:
|
||||
com2.handle_interrupt();
|
||||
break;
|
||||
case 4:
|
||||
com1.handle_interrupt();
|
||||
break;
|
||||
default:
|
||||
j6_log("uart driver got unknown irq waiting for irq");
|
||||
}
|
||||
}
|
||||
|
||||
j6_log("uart driver somehow got to the end of main");
|
||||
return 0;
|
||||
}
|
||||
|
||||
152
src/user/drv.uart/serial.cpp
Normal file
152
src/user/drv.uart/serial.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#include <string.h>
|
||||
#include "io.h"
|
||||
#include "serial.h"
|
||||
|
||||
constexpr size_t fifo_size = 64;
|
||||
|
||||
// register offsets
|
||||
constexpr uint16_t THR = 0; // Write
|
||||
constexpr uint16_t RBR = 0; // Read
|
||||
constexpr uint16_t IER = 1;
|
||||
constexpr uint16_t FCR = 2; // Write
|
||||
constexpr uint16_t IIR = 2; // Read
|
||||
constexpr uint16_t LCR = 3;
|
||||
constexpr uint16_t MCR = 4;
|
||||
constexpr uint16_t LSR = 5;
|
||||
constexpr uint16_t MSR = 6;
|
||||
|
||||
constexpr uint16_t DLL = 0; // DLAB == 1
|
||||
constexpr uint16_t DLH = 1; // DLAB == 1
|
||||
|
||||
serial_port::serial_port(uint16_t port,
|
||||
size_t in_buffer_len, uint8_t *in_buffer,
|
||||
size_t out_buffer_len, uint8_t *out_buffer) :
|
||||
m_writing(false),
|
||||
m_port(port),
|
||||
m_out_buffer(out_buffer, out_buffer_len),
|
||||
m_in_buffer(in_buffer, in_buffer_len)
|
||||
{
|
||||
outb(port + IER, 0x00); // Disable all interrupts
|
||||
outb(port + LCR, 0x80); // Enable the Divisor Latch Access Bit
|
||||
outb(port + DLL, 0x01); // Divisor low byte: 1 (115200 baud)
|
||||
outb(port + DLH, 0x00); // Divisor high byte
|
||||
outb(port + LCR, 0x03); // 8-N-1, diable DLAB
|
||||
outb(port + FCR, 0xe7); // Clear and enable FIFO, enable 64 byte, 56 byte trigger
|
||||
outb(port + MCR, 0x0b); // Data terminal ready, Request to send, aux output 2 (irq enable)
|
||||
outb(port + IER, 0x03); // Enable interrupts
|
||||
|
||||
// Clear out pending interrupts
|
||||
handle_interrupt();
|
||||
}
|
||||
|
||||
inline bool read_ready(uint16_t port) { return (inb(port + LSR) & 0x01) != 0; }
|
||||
inline bool write_ready(uint16_t port) { return (inb(port + LSR) & 0x20) != 0; }
|
||||
|
||||
void
|
||||
serial_port::handle_interrupt()
|
||||
{
|
||||
uint8_t iir = inb(m_port + IIR);
|
||||
|
||||
while ((iir & 1) == 0) {
|
||||
uint8_t reg = 0;
|
||||
switch ((iir>>1) & 0x3) {
|
||||
case 0: // Modem status change
|
||||
reg = inb(m_port + MSR);
|
||||
handle_error(MSR, reg);
|
||||
break;
|
||||
|
||||
case 1: // Transmit buffer empty
|
||||
case 2: // Received data available
|
||||
if (read_ready(m_port)) do_read();
|
||||
if (write_ready(m_port)) do_write();
|
||||
break;
|
||||
|
||||
case 3: // Line status change
|
||||
reg = inb(m_port + LSR);
|
||||
handle_error(LSR, reg);
|
||||
break;
|
||||
}
|
||||
|
||||
iir = inb(m_port + IIR);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
serial_port::do_read()
|
||||
{
|
||||
size_t used = 0;
|
||||
uint8_t *data = nullptr;
|
||||
size_t avail = m_in_buffer.reserve(fifo_size, reinterpret_cast<void**>(&data));
|
||||
|
||||
while (used < avail && read_ready(m_port)) {
|
||||
*data++ = inb(m_port);
|
||||
used++;
|
||||
}
|
||||
|
||||
m_in_buffer.commit(used);
|
||||
}
|
||||
|
||||
void
|
||||
serial_port::do_write()
|
||||
{
|
||||
// If another thread is writing data, just give up and
|
||||
// try again later
|
||||
util::scoped_trylock lock {m_lock};
|
||||
if (!lock.locked())
|
||||
return;
|
||||
|
||||
uint8_t *data = nullptr;
|
||||
size_t n = m_out_buffer.get_block(reinterpret_cast<void**>(&data));
|
||||
|
||||
m_writing = (n > 0);
|
||||
if (!m_writing) {
|
||||
m_out_buffer.consume(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (n > fifo_size)
|
||||
n = fifo_size;
|
||||
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (!write_ready(m_port)) {
|
||||
n = i;
|
||||
break;
|
||||
}
|
||||
outb(m_port, data[i]);
|
||||
}
|
||||
|
||||
m_out_buffer.consume(n);
|
||||
}
|
||||
|
||||
void
|
||||
serial_port::handle_error(uint16_t reg, uint8_t value)
|
||||
{
|
||||
while (1) asm ("hlt");
|
||||
}
|
||||
|
||||
char
|
||||
serial_port::read()
|
||||
{
|
||||
uint8_t *data = nullptr;
|
||||
size_t n = m_in_buffer.get_block(reinterpret_cast<void**>(&data));
|
||||
if (!n) return 0;
|
||||
char c = *data;
|
||||
m_in_buffer.consume(1);
|
||||
return c;
|
||||
}
|
||||
|
||||
size_t
|
||||
serial_port::write(const char *c, size_t len)
|
||||
{
|
||||
uint8_t *data = nullptr;
|
||||
size_t avail = m_out_buffer.reserve(len, reinterpret_cast<void**>(&data));
|
||||
|
||||
memcpy(data, c, avail);
|
||||
m_out_buffer.commit(avail);
|
||||
|
||||
if (!m_writing)
|
||||
do_write();
|
||||
|
||||
return avail;
|
||||
}
|
||||
|
||||
33
src/user/drv.uart/serial.h
Normal file
33
src/user/drv.uart/serial.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
/// \file serial.h
|
||||
/// Declarations related to serial ports.
|
||||
#include <stdint.h>
|
||||
|
||||
#include <util/bip_buffer.h>
|
||||
#include <util/spinlock.h>
|
||||
|
||||
class serial_port
|
||||
{
|
||||
public:
|
||||
/// Constructor.
|
||||
/// \arg port The IO address of the serial port
|
||||
serial_port(uint16_t port,
|
||||
size_t in_buffer_len, uint8_t *in_buffer,
|
||||
size_t out_buffer_len, uint8_t *out_buffer);
|
||||
|
||||
size_t write(const char *str, size_t len);
|
||||
char read();
|
||||
|
||||
void handle_interrupt();
|
||||
|
||||
private:
|
||||
bool m_writing;
|
||||
uint16_t m_port;
|
||||
util::bip_buffer m_out_buffer;
|
||||
util::bip_buffer m_in_buffer;
|
||||
util::spinlock m_lock;
|
||||
|
||||
void do_read();
|
||||
void do_write();
|
||||
void handle_error(uint16_t reg, uint8_t value);
|
||||
};
|
||||
12
src/user/drv.uart/uart.module
Normal file
12
src/user/drv.uart/uart.module
Normal file
@@ -0,0 +1,12 @@
|
||||
# vim: ft=python
|
||||
|
||||
module("drv.uart",
|
||||
targets = [ "user" ],
|
||||
deps = [ "libc", "util" ],
|
||||
description = "UART driver",
|
||||
sources = [
|
||||
"io.cpp",
|
||||
"main.cpp",
|
||||
"serial.cpp",
|
||||
])
|
||||
|
||||
@@ -55,6 +55,12 @@ load_program(const module_program &prog, char *err_msg)
|
||||
return false;
|
||||
}
|
||||
|
||||
res = j6_process_give_handle(proc, handle_system, nullptr);
|
||||
if (res != j6_status_ok) {
|
||||
sprintf(err_msg, " ** error loading program '%s': giving system handle: %lx", prog.filename, res);
|
||||
return false;
|
||||
}
|
||||
|
||||
uintptr_t load_addr = load_addr_base;
|
||||
|
||||
for (auto &seg : progelf.programs()) {
|
||||
|
||||
Reference in New Issue
Block a user