Merge "Aligns UART API with Rust Read and Write"

GitOrigin-RevId: df407ae4bddb75b1bc43c35062947679c2b10c3a
This commit is contained in:
Matt Harvey 2021-09-30 00:54:28 +00:00 committed by Sam Leffler
parent d6f8c7bced
commit d70003982c
9 changed files with 213 additions and 121 deletions

View File

@ -10,12 +10,10 @@ component DebugConsole {
control; control;
dataport Buf tx_dataport; dataport Buf tx_dataport;
uses dataport_io_inf uart_tx; uses rust_write_inf uart_write;
has mutex tx_mutex;
dataport Buf rx_dataport; dataport Buf rx_dataport;
uses dataport_io_inf uart_rx; uses rust_read_inf uart_read;
has mutex rx_mutex;
provides LoggerInterface logger; provides LoggerInterface logger;
uses ProcessControlInterface proc_ctrl; uses ProcessControlInterface proc_ctrl;

View File

@ -44,7 +44,7 @@ pub extern "C" fn pre_init() {
#[no_mangle] #[no_mangle]
pub extern "C" fn run() -> ! { pub extern "C" fn run() -> ! {
trace!("run"); trace!("run");
let mut tx = kata_uart_client::Tx {}; let mut tx = kata_uart_client::Tx::new();
let mut rx = kata_uart_client::Rx {}; let mut rx = kata_uart_client::Rx::new();
kata_shell::repl(&mut tx, &mut rx); kata_shell::repl(&mut tx, &mut rx);
} }

View File

@ -4,19 +4,6 @@ use core::fmt::Write;
use cstr_core::CStr; use cstr_core::CStr;
use kata_io as io; use kata_io as io;
// C interface to external UART driver.
extern "C" {
static rx_dataport: *mut cty::c_uchar;
fn uart_rx_update(n: cty::size_t);
fn rx_mutex_lock();
fn rx_mutex_unlock();
static tx_dataport: *mut cty::c_uchar;
fn uart_tx_update(n: cty::size_t);
fn tx_mutex_lock();
fn tx_mutex_unlock();
}
// Console logging interface. // Console logging interface.
#[no_mangle] #[no_mangle]
pub extern "C" fn logger_log(level: u8, msg: *const cstr_core::c_char) { pub extern "C" fn logger_log(level: u8, msg: *const cstr_core::c_char) {
@ -30,44 +17,83 @@ pub extern "C" fn logger_log(level: u8, msg: *const cstr_core::c_char) {
}; };
if l <= log::max_level() { if l <= log::max_level() {
// TODO(sleffler): is the uart driver ok w/ multiple writers? // TODO(sleffler): is the uart driver ok w/ multiple writers?
let output: &mut dyn io::Write = &mut self::Tx {}; let output: &mut dyn io::Write = &mut self::Tx::new();
unsafe { unsafe {
let _ = writeln!(output, "{}", CStr::from_ptr(msg).to_str().unwrap()); let _ = writeln!(output, "{}", CStr::from_ptr(msg).to_str().unwrap());
} }
} }
} }
pub struct Rx {} const DATAPORT_SIZE: usize = 4096;
pub struct Rx {
dataport: &'static [u8],
}
impl Rx {
pub fn new() -> Rx {
extern "C" {
static rx_dataport: *mut cty::c_uchar;
}
Rx {
dataport: unsafe { core::slice::from_raw_parts(rx_dataport, DATAPORT_SIZE) },
}
}
}
impl io::Read for Rx { impl io::Read for Rx {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
unsafe { extern "C" {
rx_mutex_lock(); fn uart_read_read(limit: cty::size_t) -> cty::c_int;
uart_rx_update(buf.len()); }
let port = core::slice::from_raw_parts(rx_dataport, buf.len()); let n = unsafe { uart_read_read(buf.len()) };
buf.copy_from_slice(&port); if n >= 0 {
rx_mutex_unlock(); let s = n as usize;
buf[..s].copy_from_slice(&self.dataport[..s]);
Ok(s)
} else {
Err(io::Error)
} }
Ok(buf.len())
} }
} }
pub struct Tx {} pub struct Tx {
dataport: &'static mut [u8],
}
impl Tx {
pub fn new() -> Tx {
extern "C" {
static tx_dataport: *mut cty::c_uchar;
}
Tx {
dataport: unsafe { core::slice::from_raw_parts_mut(tx_dataport, DATAPORT_SIZE) },
}
}
}
impl io::Write for Tx { impl io::Write for Tx {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
unsafe { extern "C" {
tx_mutex_lock(); fn uart_write_write(available: cty::size_t) -> cty::c_int;
let port = core::slice::from_raw_parts_mut(tx_dataport, buf.len()); }
port.copy_from_slice(buf); self.dataport[..buf.len()].copy_from_slice(buf);
uart_tx_update(buf.len()); let n = unsafe { uart_write_write(buf.len()) };
tx_mutex_unlock(); if n >= 0 {
Ok(n as usize)
} else {
Err(io::Error)
} }
Ok(buf.len())
} }
fn flush(&mut self) -> io::Result<()> { fn flush(&mut self) -> io::Result<()> {
// Do nothing. This implementation has no internal buffering. extern "C" {
fn uart_write_flush() -> cty::c_int;
}
if unsafe { uart_write_flush() } == 0 {
Ok(()) Ok(())
} else {
Err(io::Error)
}
} }
} }

View File

@ -1,27 +1,23 @@
/* /*
* Copyright 2017, Data61 * CAmkES component accessing an OpenTitan UART.
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
* *
* This software may be distributed and modified according to the terms of * Copyright 2021, Google LLC
* the BSD 2-Clause license. Note that NO WARRANTY is provided. * Apache License 2.0
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(DATA61_BSD)
*/ */
import "../../interfaces/RustIO.idl4";
component OpenTitanUARTDriver { component OpenTitanUARTDriver {
dataport Buf mmio_region; dataport Buf mmio_region;
dataport Buf tx_dataport; dataport Buf tx_dataport;
provides dataport_io_inf tx; provides rust_write_inf write;
consumes Interrupt tx_watermark; consumes Interrupt tx_watermark;
consumes Interrupt tx_empty; consumes Interrupt tx_empty;
has semaphore tx_semaphore;
has mutex tx_mutex; has mutex tx_mutex;
dataport Buf rx_dataport; dataport Buf rx_dataport;
provides dataport_io_inf rx; provides rust_read_inf read;
consumes Interrupt rx_watermark; consumes Interrupt rx_watermark;
has semaphore rx_semaphore; has semaphore rx_semaphore;
has mutex rx_mutex; has mutex rx_mutex;

View File

@ -0,0 +1,19 @@
/*
* Copyright 2021, Google LLC
*
* Error codes for the OpenTitanUARTDriver.
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
// Return codes for errors on read() and write().
//
// Normally these functions return the number of bytes actually read or written,
// with 0 indicating the end of the stream. If something goes wrong, the
// functions will return one of these negative values.
typedef enum UARTDriverError {
UARTDriver_AssertionFailed = -1,
UARTDriver_OutOfDataportBounds = -2,
} uart_driver_error_t;

View File

@ -15,6 +15,7 @@
#include "circular_buffer.h" #include "circular_buffer.h"
#include "opentitan/uart.h" #include "opentitan/uart.h"
#include "uart_driver_error.h"
// Referenced by macros in the generated file opentitan/uart.h. // Referenced by macros in the generated file opentitan/uart.h.
#define UART0_BASE_ADDR (void *)mmio_region #define UART0_BASE_ADDR (void *)mmio_region
@ -46,6 +47,13 @@
((value & UART_##regname##_##subfield##_MASK) \ ((value & UART_##regname##_##subfield##_MASK) \
<< UART_##regname##_##subfield##_OFFSET) << UART_##regname##_##subfield##_OFFSET)
#define LOCK(lockname) seL4_Assert(lockname##_lock() == 0);
#define UNLOCK(lockname) seL4_Assert(lockname##_unlock() == 0);
#define ASSERT_OR_RETURN(x) \
if (!(bool)(x)) { \
return UARTDriver_AssertionFailed; \
}
// Driver-owned buffer to receive more than the FIFO size before the received // Driver-owned buffer to receive more than the FIFO size before the received
// data is consumed by rx_update. // data is consumed by rx_update.
static circular_buffer rx_buf; // guarded by rx_mutex static circular_buffer rx_buf; // guarded by rx_mutex
@ -84,7 +92,7 @@ static void uart_putchar(char c) {
// This stops when the transmit FIFO is full or when tx_buf is empty, whichever // This stops when the transmit FIFO is full or when tx_buf is empty, whichever
// comes first. // comes first.
static void fill_tx_fifo() { static void fill_tx_fifo() {
seL4_Assert(tx_mutex_lock() == 0); LOCK(tx_mutex);
while (tx_fifo_level() < UART_FIFO_CAPACITY) { while (tx_fifo_level() < UART_FIFO_CAPACITY) {
char c; char c;
if (!circular_buffer_pop_front(&tx_buf, &c)) { if (!circular_buffer_pop_front(&tx_buf, &c)) {
@ -93,10 +101,7 @@ static void fill_tx_fifo() {
} }
uart_putchar(c); uart_putchar(c);
} }
if (circular_buffer_remaining(&tx_buf) > 0) { UNLOCK(tx_mutex);
seL4_Assert(tx_semaphore_post() == 0);
}
seL4_Assert(tx_mutex_unlock() == 0);
} }
// CAmkES initialization hook. // CAmkES initialization hook.
@ -157,62 +162,86 @@ void pre_init() {
BIT(UART_INTR_COMMON_TX_EMPTY)); BIT(UART_INTR_COMMON_TX_EMPTY));
} }
// Implements the update method of the CAmkES dataport_inf rx. // Implements Rust Read::read().
// //
// Reads a given number of bytes from rx_buf into the CAmkES rx_dataport, // Reads up to a given limit of bytes into the CAmkES rx_dataport, blocking
// blocking the RPC until the entire requested byte count has been read. // until at least one byte is available.
void rx_update(uint32_t num_to_read) { int read_read(size_t limit) {
// TODO(mattharvey): Error return value for num_to_read > if (limit > TX_RX_DATAPORT_CAPACITY) {
// TX_RX_DATAPORT_CAPACITY. return UARTDriver_OutOfDataportBounds;
seL4_Assert(num_to_read <= TX_RX_DATAPORT_CAPACITY);
char *dataport_cursor = (char *)rx_dataport;
char *const dataport_end = dataport_cursor + num_to_read;
while (dataport_cursor < dataport_end) {
seL4_Assert(rx_mutex_lock() == 0);
while (circular_buffer_empty(&rx_buf)) {
seL4_Assert(rx_mutex_unlock() == 0);
seL4_Assert(rx_semaphore_wait() == 0);
seL4_Assert(rx_mutex_lock() == 0);
} }
for (; dataport_cursor < dataport_end; ++dataport_cursor) { char *cursor = (char *)rx_dataport;
if (!circular_buffer_pop_front(&rx_buf, dataport_cursor)) { char *const cursor_begin = cursor;
char *const cursor_limit = cursor_begin + limit;
LOCK(rx_mutex);
{
while (circular_buffer_empty(&rx_buf)) {
UNLOCK(rx_mutex);
seL4_Assert(rx_semaphore_wait() == 0);
LOCK(rx_mutex);
}
while (cursor < cursor_limit) {
if (!circular_buffer_pop_front(&rx_buf, cursor)) {
// The buffer is empty. // The buffer is empty.
break; break;
} }
++cursor;
} }
seL4_Assert(rx_mutex_unlock() == 0);
} }
UNLOCK(rx_mutex);
int num_read = cursor - cursor_begin;
ASSERT_OR_RETURN(num_read > 0);
return num_read;
} }
// Implements the update method of the CAmkES dataport_inf tx. // Implements Rust Write::write().
// //
// Writes the contents of the CAmkES tx_dataport to the UART, one at a time, // Writes as many bytes from tx_dataport as the hardware will accept, but not
// blocking the RPC until the entire requested number of bytes has been written. // more than the number available (specified by the argument). Returns the
void tx_update(uint32_t num_valid_dataport_bytes) { // number of bytes written or a negative value if there is any error.
// TODO(mattharvey): Error return value for num_valid_dataport_bytes > int write_write(size_t available) {
// TX_RX_DATAPORT_CAPACITY. if (available > TX_RX_DATAPORT_CAPACITY) {
seL4_Assert(num_valid_dataport_bytes <= TX_RX_DATAPORT_CAPACITY); return UARTDriver_OutOfDataportBounds;
const char *dataport_cursor = (const char *)tx_dataport;
const char *const dataport_end = dataport_cursor + num_valid_dataport_bytes;
while (dataport_cursor < dataport_end) {
seL4_Assert(tx_mutex_lock() == 0);
while (circular_buffer_remaining(&tx_buf) == 0) {
seL4_Assert(tx_mutex_unlock() == 0);
seL4_Assert(tx_semaphore_wait() == 0);
seL4_Assert(tx_mutex_lock() == 0);
} }
for (; dataport_cursor < dataport_end; ++dataport_cursor) { const char *cursor = (const char *)tx_dataport;
if (!circular_buffer_push_back(&tx_buf, *dataport_cursor)) { const char *const cursor_begin = cursor;
const char *const cursor_limit = cursor_begin + available;
while (cursor < cursor_limit) {
LOCK(tx_mutex);
{
if (circular_buffer_remaining(&tx_buf) == 0) {
break;
}
for (; cursor < cursor_limit; ++cursor) {
if (!circular_buffer_push_back(&tx_buf, *cursor)) {
// The buffer is full. // The buffer is full.
break; break;
} }
} }
seL4_Assert(tx_mutex_unlock() == 0); }
UNLOCK(tx_mutex);
} }
fill_tx_fifo(); fill_tx_fifo();
int num_written = cursor - cursor_begin;
ASSERT_OR_RETURN(num_written > 0);
return num_written;
}
// Implements Rust Write::flush().
//
// Drains tx_buf and TX_FIFO. Returns a negative value if there is any error.
int write_flush() {
LOCK(tx_mutex);
while (circular_buffer_remaining(&tx_buf)) {
fill_tx_fifo();
}
UNLOCK(tx_mutex);
return 0;
} }
// Handles a tx_watermark interrupt. // Handles a tx_watermark interrupt.
@ -247,7 +276,7 @@ void rx_watermark_handle(void) {
} }
uint32_t num_read = 0; uint32_t num_read = 0;
seL4_Assert(rx_mutex_lock() == 0); LOCK(rx_mutex);
while (num_read < num_to_read) { while (num_read < num_to_read) {
if (!circular_buffer_push_back(&rx_buf, uart_getchar())) { if (!circular_buffer_push_back(&rx_buf, uart_getchar())) {
// The buffer is full. // The buffer is full.
@ -255,7 +284,7 @@ void rx_watermark_handle(void) {
} }
++num_read; ++num_read;
} }
seL4_Assert(rx_mutex_unlock() == 0); UNLOCK(rx_mutex);
if (num_read > 0) { if (num_read > 0) {
seL4_Assert(rx_semaphore_post() == 0); seL4_Assert(rx_semaphore_post() == 0);
@ -275,14 +304,16 @@ void rx_watermark_handle(void) {
void tx_empty_handle(void) { void tx_empty_handle(void) {
fill_tx_fifo(); fill_tx_fifo();
seL4_Assert(tx_mutex_lock() == 0); LOCK(tx_mutex);
{
if (circular_buffer_empty(&tx_buf)) { if (circular_buffer_empty(&tx_buf)) {
// Clears INTR_STATE for tx_empty. (INTR_STATE is write-1-to-clear.) We only // Clears INTR_STATE for tx_empty. (INTR_STATE is write-1-to-clear.) We
// do this if tx_buf is empty, since the TX FIFO might have become empty in // only do this if tx_buf is empty, since the TX FIFO might have become
// the time from fill_tx_fifo having sent the last character until here. In // empty in the time from fill_tx_fifo having sent the last character
// that case, we want the interrupt to reassert. // until here. In that case, we want the interrupt to reassert.
REG(INTR_STATE) = BIT(UART_INTR_STATE_TX_EMPTY); REG(INTR_STATE) = BIT(UART_INTR_STATE_TX_EMPTY);
} }
seL4_Assert(tx_empty_acknowledge() == 0); seL4_Assert(tx_empty_acknowledge() == 0);
seL4_Assert(tx_mutex_unlock() == 0); }
UNLOCK(tx_mutex);
} }

View File

@ -0,0 +1,38 @@
/*
* CAmkES backing for Rust Read and Write traits.
*
* Copyright 2021, Google LLC
* Apache License 2.0
*
* These CAmkES interfaces express the standard Rust read() and write()
* signatures, assuming two separate, externally defined, and implicitly
* associated dataports for each of read and write.
*
* It is intended that Rust code be able to use extern "C" declarations
* referencing the camkes.h that this will generate as the core of
* implementations of the Read and Write traits.
*/
procedure rust_read_inf {
// Reads up to limit bytes into the read dataport.
//
// Returns the number of bytes read or a negative value if there is any
// error.
int read(in size_t limit);
};
procedure rust_write_inf {
// Writes up to a given number of bytes from the write dataport.
//
// Returns the number of bytes actually written or a negative value if there
// is any error. For non-negative return values < available, the caller is
// reponsible for retrying with the remaining bytes at the beginning of the
// write dataport.
int write(in size_t available);
// Blocks until all bytes so far written have been pushed to the real sink.
//
// The semantics are the same as Rust's Write::flush. Returns 0 on success
// and a negative value if there is any error.
int flush();
}

View File

@ -1,15 +0,0 @@
/*
* Copyright 2017, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the BSD 2-Clause license. Note that NO WARRANTY is provided.
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(DATA61_BSD)
*/
procedure dataport_io_inf {
void update(in size_t n);
};

View File

@ -13,7 +13,6 @@
import <std_connector.camkes>; import <std_connector.camkes>;
import <global-connectors.camkes>; import <global-connectors.camkes>;
import "interfaces/dataport_io.idl4";
import "interfaces/VectorCoreInterface.camkes"; import "interfaces/VectorCoreInterface.camkes";
import "interfaces/VectorCoreReturnInterface.camkes"; import "interfaces/VectorCoreReturnInterface.camkes";
import "components/OpenTitanUARTDriver/OpenTitanUARTDriver.camkes"; import "components/OpenTitanUARTDriver/OpenTitanUARTDriver.camkes";
@ -108,12 +107,12 @@ assembly {
// Connect the DebugConsole to the OpenTitanUARTDriver. // Connect the DebugConsole to the OpenTitanUARTDriver.
connection seL4SharedData tx_channel( connection seL4SharedData tx_channel(
from debug_console.tx_dataport, to uart_driver.tx_dataport); from debug_console.tx_dataport, to uart_driver.tx_dataport);
connection seL4RPCCall tx_call( connection seL4RPCCall write_call(
from debug_console.uart_tx, to uart_driver.tx); from debug_console.uart_write, to uart_driver.write);
connection seL4SharedData rx_channel( connection seL4SharedData rx_channel(
from debug_console.rx_dataport, to uart_driver.rx_dataport); from debug_console.rx_dataport, to uart_driver.rx_dataport);
connection seL4RPCCall rx_call( connection seL4RPCCall read_call(
from debug_console.uart_rx, to uart_driver.rx); from debug_console.uart_read, to uart_driver.read);
// Connect the LoggerInterface to each component that needs to log // Connect the LoggerInterface to each component that needs to log
// to the console. Note this allocates a 4KB shared memory region to // to the console. Note this allocates a 4KB shared memory region to