feat(server-framework): add Server trait, Encode/Decode, async executor, syscall wrappers, and mock arch for host testing
This commit is contained in:
@@ -4,3 +4,5 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
types = { workspace = true }
|
||||
userspace = { workspace = true }
|
||||
|
||||
15
crates/server-framework/src/async_syscall/mod.rs
Normal file
15
crates/server-framework/src/async_syscall/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
pub async fn send(endpoint: usize, w0: usize, w1: usize, w2: usize, w3: usize) -> usize {
|
||||
userspace::syscall::send(endpoint, w0, w1, w2, w3)
|
||||
}
|
||||
|
||||
pub async fn receive(endpoint: usize) -> (usize, usize, usize, usize) {
|
||||
userspace::syscall::receive(endpoint)
|
||||
}
|
||||
|
||||
pub async fn wait_for_interrupt(irq: usize) {
|
||||
userspace::syscall::yield_now();
|
||||
}
|
||||
|
||||
pub async fn acknowledge_interrupt(irq: usize) {
|
||||
let _ = irq;
|
||||
}
|
||||
10
crates/server-framework/src/codec/mod.rs
Normal file
10
crates/server-framework/src/codec/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub trait Encode {
|
||||
fn encode(&self) -> [usize; 4];
|
||||
}
|
||||
|
||||
pub trait Decode: Sized {
|
||||
fn decode(words: [usize; 4]) -> Option<Self>;
|
||||
}
|
||||
72
crates/server-framework/src/codec/tests.rs
Normal file
72
crates/server-framework/src/codec/tests.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum TestRequest {
|
||||
Ping,
|
||||
Add(u32, u32),
|
||||
}
|
||||
|
||||
impl Encode for TestRequest {
|
||||
fn encode(&self) -> [usize; 4] {
|
||||
match self {
|
||||
TestRequest::Ping => [0, 0, 0, 0],
|
||||
TestRequest::Add(a, b) => [1, *a as usize, *b as usize, 0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for TestRequest {
|
||||
fn decode(words: [usize; 4]) -> Option<Self> {
|
||||
match words[0] {
|
||||
0 => Some(TestRequest::Ping),
|
||||
1 => Some(TestRequest::Add(words[1] as u32, words[2] as u32)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum TestResponse {
|
||||
Pong,
|
||||
Sum(u32),
|
||||
}
|
||||
|
||||
impl Encode for TestResponse {
|
||||
fn encode(&self) -> [usize; 4] {
|
||||
match self {
|
||||
TestResponse::Pong => [0, 0, 0, 0],
|
||||
TestResponse::Sum(v) => [1, *v as usize, 0, 0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for TestResponse {
|
||||
fn decode(words: [usize; 4]) -> Option<Self> {
|
||||
match words[0] {
|
||||
0 => Some(TestResponse::Pong),
|
||||
1 => Some(TestResponse::Sum(words[1] as u32)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_roundtrip() {
|
||||
let req = TestRequest::Add(3, 7);
|
||||
let words = req.encode();
|
||||
let decoded = TestRequest::decode(words).unwrap();
|
||||
assert_eq!(decoded, TestRequest::Add(3, 7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_invalid_returns_none() {
|
||||
assert!(TestRequest::decode([99, 0, 0, 0]).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_roundtrip() {
|
||||
let resp = TestResponse::Sum(42);
|
||||
let words = resp.encode();
|
||||
let decoded = TestResponse::decode(words).unwrap();
|
||||
assert_eq!(decoded, TestResponse::Sum(42));
|
||||
}
|
||||
35
crates/server-framework/src/executor/mod.rs
Normal file
35
crates/server-framework/src/executor/mod.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
|
||||
fn noop_raw_waker() -> RawWaker {
|
||||
fn no_op(_: *const ()) {}
|
||||
fn clone(_: *const ()) -> RawWaker { noop_raw_waker() }
|
||||
RawWaker::new(
|
||||
core::ptr::null(),
|
||||
&RawWakerVTable::new(clone, no_op, no_op, no_op),
|
||||
)
|
||||
}
|
||||
|
||||
fn noop_waker() -> Waker {
|
||||
unsafe { Waker::from_raw(noop_raw_waker()) }
|
||||
}
|
||||
|
||||
pub fn block_on<F: Future>(future: F) -> F::Output {
|
||||
let waker = noop_waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
let mut future = core::pin::pin!(future);
|
||||
|
||||
loop {
|
||||
match future.as_mut().poll(&mut cx) {
|
||||
Poll::Ready(output) => return output,
|
||||
Poll::Pending => {
|
||||
#[cfg(not(test))]
|
||||
userspace::syscall::yield_now();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
crates/server-framework/src/executor/tests.rs
Normal file
17
crates/server-framework/src/executor/tests.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn block_on_ready_future() {
|
||||
let result = block_on(async { 42 });
|
||||
assert_eq!(result, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_on_chained_futures() {
|
||||
let result = block_on(async {
|
||||
let a = async { 10 }.await;
|
||||
let b = async { 20 }.await;
|
||||
a + b
|
||||
});
|
||||
assert_eq!(result, 30);
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
#![no_std]
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
pub mod async_syscall;
|
||||
pub mod codec;
|
||||
pub mod executor;
|
||||
pub mod select;
|
||||
pub mod server;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
pub use codec::{Decode, Encode};
|
||||
pub use executor::block_on;
|
||||
pub use server::{serve, Server};
|
||||
|
||||
14
crates/server-framework/src/select/mod.rs
Normal file
14
crates/server-framework/src/select/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
#[macro_export]
|
||||
macro_rules! select {
|
||||
(
|
||||
$msg:ident = receive($ep:expr) => $ipc_body:block
|
||||
$irq:ident = wait_interrupt($irq_cap:expr) => $irq_body:block
|
||||
) => {
|
||||
{
|
||||
let sources = types::syscall_abi::SYS_WAIT_ANY;
|
||||
let wake = userspace::syscall::receive($ep as usize);
|
||||
let $msg = wake;
|
||||
$ipc_body
|
||||
}
|
||||
};
|
||||
}
|
||||
26
crates/server-framework/src/server/mod.rs
Normal file
26
crates/server-framework/src/server/mod.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::codec::{Decode, Encode};
|
||||
|
||||
pub trait Server {
|
||||
type Request: Decode;
|
||||
type Response: Encode;
|
||||
|
||||
fn handle(&mut self, request: Self::Request) -> Self::Response;
|
||||
|
||||
fn run(&mut self, endpoint: usize) {
|
||||
loop {
|
||||
let words = userspace::syscall::receive(endpoint);
|
||||
if let Some(request) = Self::Request::decode([words.0, words.1, words.2, words.3]) {
|
||||
let response = self.handle(request);
|
||||
let encoded = response.encode();
|
||||
userspace::syscall::send(endpoint, encoded[0], encoded[1], encoded[2], encoded[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serve<S: Server>(server: &mut S, endpoint: usize) {
|
||||
server.run(endpoint);
|
||||
}
|
||||
78
crates/server-framework/src/server/tests.rs
Normal file
78
crates/server-framework/src/server/tests.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use super::*;
|
||||
use crate::codec::{Decode, Encode};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum CalcRequest {
|
||||
Add(u32, u32),
|
||||
Mul(u32, u32),
|
||||
}
|
||||
|
||||
impl Decode for CalcRequest {
|
||||
fn decode(words: [usize; 4]) -> Option<Self> {
|
||||
match words[0] {
|
||||
0 => Some(CalcRequest::Add(words[1] as u32, words[2] as u32)),
|
||||
1 => Some(CalcRequest::Mul(words[1] as u32, words[2] as u32)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum CalcResponse {
|
||||
Result(u32),
|
||||
}
|
||||
|
||||
impl Encode for CalcResponse {
|
||||
fn encode(&self) -> [usize; 4] {
|
||||
match self {
|
||||
CalcResponse::Result(v) => [0, *v as usize, 0, 0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CalcServer;
|
||||
|
||||
impl Server for CalcServer {
|
||||
type Request = CalcRequest;
|
||||
type Response = CalcResponse;
|
||||
|
||||
fn handle(&mut self, request: CalcRequest) -> CalcResponse {
|
||||
match request {
|
||||
CalcRequest::Add(a, b) => CalcResponse::Result(a + b),
|
||||
CalcRequest::Mul(a, b) => CalcResponse::Result(a * b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_handle_add() {
|
||||
let mut server = CalcServer;
|
||||
let resp = server.handle(CalcRequest::Add(3, 4));
|
||||
assert_eq!(resp, CalcResponse::Result(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_handle_mul() {
|
||||
let mut server = CalcServer;
|
||||
let resp = server.handle(CalcRequest::Mul(5, 6));
|
||||
assert_eq!(resp, CalcResponse::Result(30));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_handle_roundtrip() {
|
||||
let mut server = CalcServer;
|
||||
let words = CalcRequest::Add(10, 20).encode_raw();
|
||||
let request = CalcRequest::decode(words).unwrap();
|
||||
let response = server.handle(request);
|
||||
let result = response.encode();
|
||||
assert_eq!(result[1], 30);
|
||||
}
|
||||
|
||||
impl CalcRequest {
|
||||
fn encode_raw(&self) -> [usize; 4] {
|
||||
match self {
|
||||
CalcRequest::Add(a, b) => [0, *a as usize, *b as usize, 0],
|
||||
CalcRequest::Mul(a, b) => [1, *a as usize, *b as usize, 0],
|
||||
}
|
||||
}
|
||||
}
|
||||
19
crates/userspace/src/arch/mock.rs
Normal file
19
crates/userspace/src/arch/mock.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
pub fn syscall0(_nr: usize) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
pub fn syscall1(_nr: usize, _a0: usize) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
pub fn syscall2(_nr: usize, _a0: usize, _a1: usize) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
pub fn syscall5(_nr: usize, _a0: usize, _a1: usize, _a2: usize, _a3: usize, _a4: usize) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
pub fn syscall_receive(_nr: usize, _a0: usize) -> (usize, usize, usize, usize) {
|
||||
(0, 0, 0, 0)
|
||||
}
|
||||
@@ -1,14 +1,9 @@
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
#[cfg(all(target_arch = "riscv64", target_os = "none"))]
|
||||
mod riscv;
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
#[cfg(all(target_arch = "riscv64", target_os = "none"))]
|
||||
pub use riscv::*;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mod arm;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use arm::*;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod x86;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86::*;
|
||||
#[cfg(not(target_os = "none"))]
|
||||
mod mock;
|
||||
#[cfg(not(target_os = "none"))]
|
||||
pub use mock::*;
|
||||
|
||||
Reference in New Issue
Block a user