[uart] Fix UART driver hangs

The UART driver would constantly hang in unpredictable spots. Turns out
it could get into a situation where it was stuck in a loop unable to
read more from the receive channel, and/or write to the serial port
buffer. Now we use a ring buffer to read as much as possible from the
receive channel, write as much as possible to the serial port buffer,
and move on without looping.
This commit is contained in:
Justin C. Miller
2024-03-04 19:48:16 -08:00
parent 9f54927a82
commit 40130076b2
5 changed files with 112 additions and 16 deletions

View File

@@ -125,8 +125,10 @@ channel::receive(void *buffer, size_t *size, bool block)
{
j6::scoped_lock lock {m_header->mutex};
while (!m_header->read_avail()) {
if (!block)
if (!block) {
*size = 0;
return j6_status_would_block;
}
lock.release();
m_header->read_waiting.wait();

View File

@@ -0,0 +1,55 @@
#pragma once
/// \file j6/ring_buffer.hh
/// Helper class for managing ring buffers in doubly-mapped VMAs
// The kernel depends on libj6 for some shared code,
// but should not include the user-specific code.
#ifndef __j6kernel
#include <stddef.h>
#include <j6/types.h>
#include <util/api.h>
namespace j6 {
API class ring_buffer
{
public:
ring_buffer(size_t pages);
inline bool valid() const { return m_data != nullptr; }
inline size_t size() const { return 1<<m_bits; }
inline size_t used() const { return m_write-m_read; }
inline size_t free() const { return size()-used(); }
inline const char * read_ptr() const { return get(m_read); }
inline char * write_ptr() { return get(m_write); }
inline void commit(size_t &n) {
if (n > free()) n = free();
m_write += n;
}
inline const char * consume(size_t &n) {
if (n > used()) n = used();
size_t i = m_read;
m_read += n;
return get(i);
}
private:
inline size_t mask() const { return (1<<m_bits)-1;}
inline size_t index(size_t i) const { return i & mask(); }
inline char * get(size_t i) { return m_data + index(i); }
inline const char * get(size_t i) const { return m_data + index(i); }
size_t m_bits;
size_t m_write;
size_t m_read;
j6_handle_t m_vma;
char *m_data;
};
} // namespace j6
#endif // __j6kernel

View File

@@ -13,6 +13,7 @@ j6 = module("j6",
"protocol_ids.cpp",
"protocols/service_locator.cpp",
"protocols/vfs.cpp",
"ring_buffer.cpp",
"syscalls.s.cog",
"sysconf.cpp.cog",
"syslog.cpp",
@@ -29,6 +30,7 @@ j6 = module("j6",
"j6/protocols.h",
"j6/protocols/service_locator.h",
"j6/protocols/service_locator.hh",
"j6/ring_buffer.hh",
"j6/syscalls.h.cog",
"j6/sysconf.h.cog",
"j6/syslog.hh",

View File

@@ -0,0 +1,38 @@
// The kernel depends on libj6 for some shared code,
// but should not include the user-specific code.
#ifndef __j6kernel
#include <arch/memory.h>
#include <j6/errors.h>
#include <j6/flags.h>
#include <j6/ring_buffer.hh>
#include <j6/syscalls.h>
#include <j6/types.h>
#include <util/util.h>
namespace j6 {
API
ring_buffer::ring_buffer(size_t pages) :
m_bits {util::log2(pages) + arch::frame_bits},
m_write {0},
m_read {0},
m_vma {j6_handle_invalid},
m_data {nullptr}
{
// Must be a power of 2
if (!util::is_pow2(pages))
return;
uintptr_t buffer_addr = 0;
size_t size = 1<<m_bits;
j6_status_t result = j6_vma_create_map(&m_vma, size, &buffer_addr, j6_vm_flag_ring|j6_vm_flag_write);
if (result != j6_status_ok)
return;
m_data = reinterpret_cast<char*>(buffer_addr);
}
} // namespace j6
#endif // __j6kernel

View File

@@ -2,12 +2,14 @@
#include <stdlib.h>
#include <stdio.h>
#include <arch/memory.h>
#include <j6/cap_flags.h>
#include <j6/channel.hh>
#include <j6/errors.h>
#include <j6/flags.h>
#include <j6/init.h>
#include <j6/protocols/service_locator.hh>
#include <j6/ring_buffer.hh>
#include <j6/syscalls.h>
#include <j6/sysconf.h>
#include <j6/syslog.hh>
@@ -64,8 +66,10 @@ main(int argc, const char **argv)
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};
static constexpr size_t buffer_size = 512;
char buffer[buffer_size];
static constexpr size_t buffer_pages = 1;
j6::ring_buffer buffer {buffer_pages};
if (!buffer.valid())
return 128;
j6_handle_t slp = j6_find_init_handle(j6::proto::sl::id);
if (slp == j6_handle_invalid)
@@ -87,17 +91,13 @@ main(int argc, const char **argv)
return 6;
while (true) {
size_t size = buffer_size;
while (true) {
result = cout->receive(buffer, &size, 0);
if (result != j6_status_ok)
break;
size_t size = buffer.free();
cout->receive(buffer.write_ptr(), &size, 0);
buffer.commit(size);
//j6::syslog(j6::logs::srv, j6::log_level::spam, "uart driver: got %d bytes from channel", size);
j6::syslog(j6::logs::srv, j6::log_level::spam, "uart driver: got %d bytes from channel", size);
com1.write(buffer, size);
}
if (result != j6_status_would_block)
j6::syslog(j6::logs::srv, j6::log_level::error, "uart driver: error %lx receiving from channel", result);
size = com1.write(buffer.read_ptr(), buffer.used());
buffer.consume(size);
uint64_t signals = 0;
result = j6_event_wait(event, &signals, 500);
@@ -110,7 +110,7 @@ main(int argc, const char **argv)
j6::syslog(j6::logs::srv, j6::log_level::error, "uart driver: error %lx waiting for irq", result);
continue;
} else {
j6::syslog(j6::logs::srv, j6::log_level::verbose, "uart driver: irq signals: %lx", signals);
j6::syslog(j6::logs::srv, j6::log_level::spam, "uart driver: irq signals: %lx", signals);
}
if (signals & (1<<0))
@@ -121,5 +121,4 @@ main(int argc, const char **argv)
j6::syslog(j6::logs::srv, j6::log_level::error, "uart driver somehow got to the end of main");
return 0;
}
}