[6s] Add 6s shell, make channels full-duplex

This commit adds the 6s shell, and a bunch of supporting work for it.
Major changes include:

- New shell.yaml manifest to give 6s control of the TTY instead of
  srv.logger
- Changes to mailbox syscalls to add max handles array size separate
  from input size. Also reversed the meaning of the similar data size
  argument in those syscalls. (Using the second arg as the max array
  size and the first as the current valid size allows for the auto
  verify code to verify handles properly, and simplifies user-side
  code.)
- New util::unique_ptr smart pointer class similar to std::unique_ptr
- New ipc::message format that uses util::unique_ptr to manage ownership
  and lifetimes and avoid extra copying.
- The service locator protocol now supports multiple handles per entry
- Channels got a major overhaul. They are now split into two VMAs, each
  containing a mutex, a condition, and a util::bip_buffer. The order of
  the VMAs determines which end of the pipe you're on. (ie, the creator
  swaps them before handing them to the other thread.) Their API also
  changed to be similar to that of util::bip_buffer, to avoid extra
  copies.
- util::bip_buffer now keeps its state and its buffer together, so that
  there are no pointers. This allows multiple processes to share them in
  shared memory, like in channels.
- The UART driver changed from keeping buffers for the serial ports to
  just keeping a channel, and the serial port objects read/write
  directly from/to the channel.

Known issues:

- The shell doesn't actually do anything yet. It echos its input back to
  the serial line and injects a prompt on new lines.
- The shell is one character behind in printing back to the serial line.
This commit is contained in:
Justin C. Miller
2024-04-23 23:32:28 -07:00
parent d8a21608c3
commit e725795a17
30 changed files with 727 additions and 323 deletions

View File

@@ -12,138 +12,177 @@
#include <j6/syscalls.h>
#include <j6/syslog.hh>
#include <util/spinlock.h>
#include <util/bip_buffer.h>
#include <util/new.h>
namespace j6 {
static uintptr_t channel_addr = 0x6000'0000;
static util::spinlock addr_spinlock;
struct channel::header
struct channel_memory_area
{
size_t size;
size_t read_index;
size_t write_index;
mutex mutex;
condition read_waiting;
condition write_waiting;
uint8_t data[0];
inline const void* read_at() const { return &data[read_index & (size - 1)]; }
inline void* write_at() { return &data[write_index & (size - 1)]; }
inline size_t read_avail() const { return write_index - read_index; }
inline size_t write_avail() const { return size - read_avail(); }
inline void consume(size_t n) { read_index += n; }
inline void commit(size_t n) { write_index += n; }
j6::mutex mutex;
j6::condition waiting;
util::bip_buffer buf;
};
channel *
channel::create(size_t size)
static bool
check_channel_size(size_t s)
{
j6_status_t result;
j6_handle_t vma = j6_handle_invalid;
if (size < arch::frame_size || (size & (size - 1)) != 0) {
syslog(j6::logs::ipc, j6::log_level::error, "Bad channel size: %lx", size);
return nullptr;
if (s < arch::frame_size || (s & (s - 1)) != 0) {
syslog(j6::logs::ipc, j6::log_level::error, "Bad channel size: %lx", s);
return false;
}
return true;
}
util::scoped_lock lock {addr_spinlock};
uintptr_t addr = channel_addr;
channel_addr += size * 2; // account for ring buffer virtual space doubling
lock.release();
result = j6_vma_create_map(&vma, size, &addr, j6_vm_flag_write|j6_vm_flag_ring);
static uintptr_t
create_channel_vma(j6_handle_t &vma, size_t size)
{
uintptr_t addr = 0;
j6_status_t result = j6_vma_create_map(&vma, size, &addr, j6_vm_flag_write);
if (result != j6_status_ok) {
syslog(j6::logs::ipc, j6::log_level::error, "Failed to create channel VMA. Error: %lx", result);
return 0;
}
syslog(j6::logs::ipc, j6::log_level::verbose, "Created channel VMA at 0x%lx size 0x%lx", addr, size);
return addr;
}
static void
init_channel_memory_area(channel_memory_area *area, size_t size)
{
new (&area->mutex) j6::mutex;
new (&area->waiting) j6::condition;
new (&area->buf) util::bip_buffer {size - sizeof(*area)};
}
channel *
channel::create(size_t tx_size, size_t rx_size)
{
if (!rx_size)
rx_size = tx_size;
if (!check_channel_size(tx_size) || !check_channel_size(rx_size))
return nullptr;
j6_status_t result;
j6_handle_t tx_vma = j6_handle_invalid;
j6_handle_t rx_vma = j6_handle_invalid;
uintptr_t tx_addr = create_channel_vma(tx_vma, tx_size);
if (!tx_addr)
return nullptr;
uintptr_t rx_addr = create_channel_vma(rx_vma, rx_size);
if (!rx_addr) {
j6_vma_unmap(tx_vma, 0);
// TODO: Close TX handle
return nullptr;
}
header *h = reinterpret_cast<header*>(addr);
memset(h, 0, sizeof(*h));
h->size = size;
channel_memory_area *tx = reinterpret_cast<channel_memory_area*>(tx_addr);
channel_memory_area *rx = reinterpret_cast<channel_memory_area*>(rx_addr);
init_channel_memory_area(tx, tx_size);
init_channel_memory_area(rx, rx_size);
return new channel {vma, h};
j6::syslog(logs::ipc, log_level::info, "Created new channel with handles {%x, %x}", tx_vma, rx_vma);
return new channel {{tx_vma, rx_vma}, *tx, *rx};
}
channel *
channel::open(j6_handle_t vma)
channel::open(const channel_def &def)
{
j6_status_t result;
util::scoped_lock lock {addr_spinlock};
uintptr_t addr = channel_addr;
result = j6_vma_map(vma, 0, &addr, 0);
uintptr_t tx_addr = 0;
result = j6_vma_map(def.tx, 0, &tx_addr, 0);
if (result != j6_status_ok) {
syslog(j6::logs::ipc, j6::log_level::error, "Failed to map channel VMA. Error: %lx", result);
return nullptr;
}
header *h = reinterpret_cast<header*>(addr);
channel_addr += h->size;
lock.release();
uintptr_t rx_addr = 0;
result = j6_vma_map(def.rx, 0, &rx_addr, 0);
if (result != j6_status_ok) {
j6_vma_unmap(def.tx, 0);
// TODO: Close TX handle
syslog(j6::logs::ipc, j6::log_level::error, "Failed to map channel VMA. Error: %lx", result);
return nullptr;
}
return new channel {vma, h};
channel_memory_area *tx = reinterpret_cast<channel_memory_area*>(tx_addr);
channel_memory_area *rx = reinterpret_cast<channel_memory_area*>(rx_addr);
j6::syslog(logs::ipc, log_level::info, "Opening existing channel with handles {%x:0x%lx, %x:0x%lx}", def.tx, tx, def.rx, rx);
return new channel { def, *tx, *rx };
}
channel::channel(j6_handle_t vma, header *h) :
m_vma {vma},
m_size {h->size},
m_header {h}
channel::channel(
const channel_def &def,
channel_memory_area &tx,
channel_memory_area &rx) :
m_def {def},
m_tx {tx},
m_rx {rx}
{
}
j6_status_t
channel::send(const void *buffer, size_t len, bool block)
size_t
channel::reserve(size_t size, uint8_t **area, bool block)
{
if (len > m_header->size)
if (size > m_tx.buf.buffer_size())
return j6_err_insufficient;
j6::scoped_lock lock {m_header->mutex};
while (m_header->write_avail() < len) {
if (!block)
return j6_status_would_block;
j6::scoped_lock lock {m_tx.mutex};
while (m_tx.buf.write_available() < size) {
if (!block) return 0;
lock.release();
m_header->write_waiting.wait();
m_tx.waiting.wait();
lock.acquire();
}
memcpy(m_header->write_at(), buffer, len);
m_header->commit(len);
m_header->read_waiting.wake();
return j6_status_ok;
return m_tx.buf.reserve(size, area);
}
j6_status_t
channel::receive(void *buffer, size_t *size, bool block)
void
channel::commit(size_t size)
{
j6::scoped_lock lock {m_header->mutex};
while (!m_header->read_avail()) {
if (!block) {
*size = 0;
return j6_status_would_block;
}
j6::syslog(logs::ipc, log_level::spam,
"Sending %d bytes to channel on {%x}", size, m_def.tx);
j6::scoped_lock lock {m_tx.mutex};
m_tx.buf.commit(size);
if (size)
m_tx.waiting.wake();
}
size_t
channel::get_block(uint8_t const **area, bool block) const
{
j6::scoped_lock lock {m_rx.mutex};
while (!m_rx.buf.size()) {
if (!block) return 0;
lock.release();
m_header->read_waiting.wait();
m_rx.waiting.wait();
lock.acquire();
}
size_t avail = m_header->read_avail();
size_t read = *size > avail ? avail : *size;
return m_rx.buf.get_block(area);
}
memcpy(buffer, m_header->read_at(), read);
m_header->consume(read);
m_header->write_waiting.wake();
*size = read;
return j6_status_ok;
void
channel::consume(size_t size)
{
j6::syslog(logs::ipc, log_level::spam,
"Read %d bytes from channel on {%x}", size, m_def.rx);
m_rx.buf.consume(size);
if (size)
m_rx.waiting.wake();
}
} // namespace j6

View File

@@ -10,41 +10,66 @@
#include <j6/types.h>
#include <util/api.h>
namespace util {
class bip_buffer;
}
namespace j6 {
/// Descriptor of VMAs for a channel.
struct channel_def
{
j6_handle_t tx;
j6_handle_t rx;
};
struct channel_memory_area;
class API channel
{
public:
/// Create a new channel of the given size.
static channel * create(size_t size);
/// Create a new channel.
/// \arg tx_size Size of the transmit buffer (sending from this thread)
/// \arg rx_size Size of the receive buffer (sending from remote thread),
/// or 0 for equal sized buffers.
static channel * create(size_t tx_size, size_t rx_size = 0);
/// Open an existing channel for which we have a VMA handle
static channel * open(j6_handle_t vma);
/// Open an existing channel for which we have VMA handles
static channel * open(const channel_def &def);
/// Send data into the channel.
/// \arg buffer The buffer from which to read data
/// \arg len The number of bytes to read from `buffer`
/// \arg block If true, block this thread if there aren't `len` bytes of space available
j6_status_t send(const void *buffer, size_t len, bool block = true);
/// Reserve an area of the output buffer for a write.
/// \arg size Requested size, in bytes
/// \arg area [out] Pointer to returned area
/// \arg block If true, block this thread until there are size bytes free
/// \returns Size of returned area, in bytes, or 0 on failure
size_t reserve(size_t size, uint8_t **area, bool block = true);
/// Read data out of the channel.
/// \arg buffer The buffer to receive channel data
/// \arg size [in] The size of `buffer` [out] the amount read into `buffer`
/// \arg block If true, block this thread if there is no data to read yet
j6_status_t receive(void *buffer, size_t *size, bool block = true);
/// Commit a pending write started by reserve()
/// \arg size Amount of data used, in bytes
void commit(size_t size);
/// Get the VMA handle for sharing with other processes
j6_handle_t handle() const { return m_vma; }
/// Get a pointer to a block of data in the buffer.
/// \arg area [out] Pointer to the retuned area
/// \arg block If true, block this thread until there is data available
/// \returns Size of the returned area, in bytes
size_t get_block(uint8_t const **area, bool block = true) const;
/// Mark a number of bytes as consumed, freeing buffer space
/// \arg size Number of bytes to consume
void consume(size_t size);
/// Get the channel_def for creating the remote end
inline channel_def remote_def() const { return {m_def.rx, m_def.tx}; }
private:
struct header;
channel(
const channel_def &def,
channel_memory_area &tx,
channel_memory_area &rx);
channel(j6_handle_t vma, header *h);
j6_handle_t m_vma;
size_t m_size;
header *m_header;
channel_def m_def;
channel_memory_area &m_tx;
channel_memory_area &m_rx;
};
} // namespace j6

View File

@@ -1,6 +1,7 @@
#include <j6/protocols/service_locator.h>
#include <j6/types.h>
#include <util/api.h>
#include <util/counted.h>
namespace j6::proto::sl {
@@ -13,16 +14,16 @@ public:
/// Register a handle as a service with the locator.
/// \arg proto_id The protocol this handle supports
/// \arg handle The mailbox or channel handle
j6_status_t register_service(uint64_t proto_id, j6_handle_t handle);
/// \arg handles The mailbox or channel handles
j6_status_t register_service(uint64_t proto_id, util::counted<j6_handle_t> handles);
/// Look up a handle with the locator service.
/// \arg proto_id The protocol to look for
/// \arg handle [out] The mailbox or channel handle
j6_status_t lookup_service(uint64_t proto_id, j6_handle_t &handle);
/// \arg handles [out] The mailbox or channel handles
j6_status_t lookup_service(uint64_t proto_id, util::counted<j6_handle_t> &handles);
private:
j6_handle_t m_service;
};
} // namespace j6::proto::sl
} // namespace j6::proto::sl

View File

@@ -1,15 +1,15 @@
LOG(apic, info)
LOG(boot, info)
LOG(device, spam)
LOG(device, info)
LOG(irq, info)
LOG(memory, verbose)
LOG(objs, spam)
LOG(memory, info)
LOG(objs, info)
LOG(paging, info)
LOG(sched, verbose)
LOG(syscall,verbose)
LOG(sched, info)
LOG(syscall,info)
LOG(task, verbose)
LOG(timer, info)
LOG(ipc, spam)
LOG(app, spam)
LOG(proto, spam)
LOG(srv, spam)
LOG(proto, spam)
LOG(srv, info)

View File

@@ -13,16 +13,15 @@ client::client(j6_handle_t slp_mb) :
}
j6_status_t
client::register_service(uint64_t proto_id, j6_handle_t handle)
client::register_service(uint64_t proto_id, util::counted<j6_handle_t> handles)
{
uint64_t tag = j6_proto_sl_register;
size_t handle_count = 1;
size_t data = proto_id;
size_t data_size = sizeof(proto_id);
j6_status_t s = j6_mailbox_call(m_service, &tag,
&data, &data_size, data_size,
&handle, &handle_count);
handles.pointer, &handles.count, handles.count);
if (s != j6_status_ok)
return s;
@@ -34,31 +33,35 @@ client::register_service(uint64_t proto_id, j6_handle_t handle)
}
j6_status_t
client::lookup_service(uint64_t proto_id, j6_handle_t &handle)
client::lookup_service(uint64_t proto_id, util::counted<j6_handle_t> &handles)
{
uint64_t tag = j6_proto_sl_find;
size_t handle_count = 1;
size_t data = proto_id;
size_t data_size = sizeof(proto_id);
handle = j6_handle_invalid;
size_t handles_size = handles.count;
handles.count = 0;
j6::syslog(j6::logs::proto, j6::log_level::verbose, "Looking up service for %x", proto_id);
j6_status_t s = j6_mailbox_call(m_service, &tag,
&data, &data_size, data_size,
&handle, &handle_count);
handles.pointer, &handles.count, handles_size);
if (s != j6_status_ok)
if (s != j6_status_ok) {
j6::syslog(j6::logs::proto, j6::log_level::error, "Received error %lx trying to call service lookup", s);
return s;
}
if (tag == j6_proto_sl_result)
return j6_status_ok; // handle is already in `handle`
return j6_status_ok; // handles are already in `handles`
else if (tag == j6_proto_base_status)
else if (tag == j6_proto_base_status) {
j6::syslog(j6::logs::proto, j6::log_level::warn, "Received status %lx from service lookup", data);
return data; // contains a status
}
return j6_err_unexpected;
}
} // namespace j6::proto::sl
#endif // __j6kernel
#endif // __j6kernel

View File

@@ -21,7 +21,7 @@ client::load_file(char *path, j6_handle_t &vma, size_t &size)
return j6_err_invalid_arg;
uint64_t tag = j6_proto_vfs_load;
size_t handle_count = 1;
size_t handle_count = 0;
vma = j6_handle_invalid;
// Always need to send a big enough buffer for a status code
@@ -38,12 +38,12 @@ client::load_file(char *path, j6_handle_t &vma, size_t &size)
j6_status_t s = j6_mailbox_call(m_service, &tag,
data, &data_len, path_len,
&vma, &handle_count);
&vma, &handle_count, 1);
if (s != j6_status_ok)
return s;
if (tag == j6_proto_vfs_file) {
if (tag == j6_proto_vfs_file && handle_count == 1) {
size = 0;
// Get the size into `size`