Module drone_cortexm::proc_loop [−][src]
This module provides interface to wrap a stackful synchronous code into an asynchronous command loop.
NOTE This module documentation should be viewed as a continuation of
the drone_core
documentation.
To provide an example, imagine a C library for FAT32 file system. Here is how it could be wrapped:
use core::{future::Future, pin::Pin, slice}; use drone_core::ffi::{c_char, CString}; use drone_cortexm::{ proc_loop::{self, Context as _, ProcLoop, Sess as _}, sv, }; use futures::prelude::*; use drone_cortexm::sv::{SwitchBackService, SwitchContextService}; // Stackful fibers need a supervisor. sv::pool! { pool => SERVICES; supervisor => pub Sv; services => { // These services are required for stackful fibers. SwitchContextService; SwitchBackService; } } // Here is the library API. extern "C" { fn f_read(name: *const c_char, buf: *mut u8, count: u32) -> u32; fn f_write(name: *const c_char, buf: *const u8, count: u32) -> u32; } // The library is expecting to be linked with the following two function. #[no_mangle] pub extern "C" fn disk_read(buf: *mut u8, sector: u32, count: u32) -> u32 { // We need to recreate the Yielder with correct type parameters. let yielder = unsafe { proc_loop::Yielder::<Sv, FatfsRes>::new() }; // Redirect the request to the command loop. This call is blocking. let req_res = yielder.req(Req::DiskRead { buf, sector, count }); // The result variant must correspond to the request variant. unsafe { req_res.disk_read } } #[no_mangle] pub extern "C" fn disk_write(buf: *const u8, sector: u32, count: u32) -> u32 { let yielder = unsafe { proc_loop::Yielder::<Sv, FatfsRes>::new() }; let req_res = yielder.req(Req::DiskWrite { buf, sector, count }); // The result variant must correspond to the request variant. unsafe { req_res.disk_write } } // We need to map the two functions above to the corresponding functions below. pub async fn disk_read_async(buf: &mut [u8], sector: u32) -> u32 { // Serve `disk_read` asynchronously. unimplemented!() } pub async fn disk_write_async(buf: &[u8], sector: u32) -> u32 { // Serve `disk_write` asynchronously. unimplemented!() } // All possible commands. We can use only `'static` lifetimes here. That is // why we use `*const str`, `*const [u8]`, `*mut [u8]` instead of `&str`, // `&[u8]`, `&mut [u8]`. pub enum Cmd { Read { name: *const str, buf: *mut [u8] }, Write { name: *const str, buf: *const [u8] }, } // Results for each of the commands above. pub union CmdRes { pub read: u32, pub write: u32, } // All possible requests used by `disk_read` and `disk_write` functions above. pub enum Req { DiskRead { buf: *mut u8, sector: u32, count: u32 }, DiskWrite { buf: *const u8, sector: u32, count: u32 }, } // Results for each of the requests above. pub union ReqRes { pub disk_read: u32, pub disk_write: u32, } // The use of raw pointers requires this. unsafe impl Send for Cmd {} unsafe impl Send for Req {} // This type will never be instantiated. It is used only to define associated // items with `ProcLoop` trait. pub struct FatfsRes; // This is the type that implements the high-level API of our library. pub struct FatfsSess<'sess>(&'sess mut proc_loop::Fiber<Sv, FatfsRes>); impl ProcLoop for FatfsRes { type Cmd = Cmd; type CmdRes = CmdRes; type Context = proc_loop::Yielder<Sv, FatfsRes>; type Req = Req; type ReqRes = ReqRes; const STACK_SIZE: usize = 0x800; fn run_cmd(cmd: Cmd, _context: Self::Context) -> CmdRes { match cmd { Cmd::Read { name, buf } => { // Rebind lifetimes for the raw pointers. let name = unsafe { &*buf }; let buf = unsafe { &mut *buf }; // Call the library function synchronously. This will block. let read = unsafe { f_read( CString::new(name).unwrap().as_ptr(), buf.as_mut_ptr(), buf.len() as u32, ) }; // The result variant must correspond to the command variant. CmdRes { read } } Cmd::Write { name, buf } => { let name = unsafe { &*buf }; let buf = unsafe { &*buf }; let write = unsafe { f_write( CString::new(name).unwrap().as_ptr(), buf.as_ptr(), buf.len() as u32, ) }; // The result variant must correspond to the command variant. CmdRes { write } } } } } impl proc_loop::Sess for FatfsSess<'_> { type Error = !; type Fiber = proc_loop::Fiber<Sv, FatfsRes>; type ProcLoop = FatfsRes; fn fib(&mut self) -> Pin<&mut Self::Fiber> { Pin::new(self.0) } fn run_req( &mut self, req: <Self::ProcLoop as ProcLoop>::Req, ) -> Pin< Box< dyn Future<Output = Result<<Self::ProcLoop as ProcLoop>::ReqRes, Self::Error>> + Send + '_, >, > { match req { Req::DiskRead { buf, sector, count } => { let slice = unsafe { slice::from_raw_parts_mut(buf, count as usize) }; Box::pin(disk_read_async(slice, sector).map(|disk_read| { // The result variant must correspond to the request variant. Ok(ReqRes { disk_read }) })) } Req::DiskWrite { buf, sector, count } => { let slice = unsafe { slice::from_raw_parts(buf, count as usize) }; Box::pin(disk_write_async(slice, sector).map(|disk_write| { // The result variant must correspond to the request variant. Ok(ReqRes { disk_write }) })) } } } } // The high-level API to our library. impl FatfsSess<'_> { pub async fn read<'a>(&'a mut self, name: &'a str, buf: &'a mut [u8]) -> Result<u32, !> { let res = self.cmd(Cmd::Read { name, buf }).await?; // The result variant must correspond to the command variant. Ok(unsafe { res.read }) } pub async fn write<'a>(&'a mut self, name: &'a str, buf: &'a [u8]) -> Result<u32, !> { let res = self.cmd(Cmd::Write { name, buf }).await?; // The result variant must correspond to the command variant. Ok(unsafe { res.write }) } } // Here is how we use the defined command loop in asynchronous context. async { let mut fatfs_proc = proc_loop::Fiber::<Sv, FatfsRes>::new(); let mut fatfs_sess = FatfsSess(&mut fatfs_proc); let mut buf = [0; 10]; fatfs_sess.read("file1.txt", &mut buf).await?; fatfs_sess.write("file2.txt", b"hello there!\n").await?; Ok::<(), !>(()) };
Re-exports
pub use drone_core::proc_loop::*; |
Structs
Fiber | A wrapper for |
Yielder | Yielder for |