| use core::arch::asm; | |
| use crate::io; | |
| use crate::num::NonZero; | |
| use crate::os::xous::ffi::{ | |
| MemoryFlags, Syscall, ThreadId, blocking_scalar, create_thread, do_yield, join_thread, | |
| map_memory, update_memory_flags, | |
| }; | |
| use crate::os::xous::services::{TicktimerScalar, ticktimer_server}; | |
| use crate::thread::ThreadInit; | |
| use crate::time::Duration; | |
| pub struct Thread { | |
| tid: ThreadId, | |
| } | |
| pub const DEFAULT_MIN_STACK_SIZE: usize = 131072; | |
| const MIN_STACK_SIZE: usize = 4096; | |
| pub const GUARD_PAGE_SIZE: usize = 4096; | |
| impl Thread { | |
| // unsafe: see thread::Builder::spawn_unchecked for safety requirements | |
| pub unsafe fn new(stack: usize, init: Box<ThreadInit>) -> io::Result<Thread> { | |
| let data = Box::into_raw(init); | |
| let mut stack_size = crate::cmp::max(stack, MIN_STACK_SIZE); | |
| if (stack_size & 4095) != 0 { | |
| stack_size = (stack_size + 4095) & !4095; | |
| } | |
| // Allocate the whole thing, then divide it up after the fact. This ensures that | |
| // even if there's a context switch during this function, the whole stack plus | |
| // guard pages will remain contiguous. | |
| let stack_plus_guard_pages: &mut [u8] = unsafe { | |
| map_memory( | |
| None, | |
| None, | |
| GUARD_PAGE_SIZE + stack_size + GUARD_PAGE_SIZE, | |
| MemoryFlags::R | MemoryFlags::W | MemoryFlags::X, | |
| ) | |
| } | |
| .map_err(|code| io::Error::from_raw_os_error(code as i32))?; | |
| // No access to this page. Note: Write-only pages are illegal, and will | |
| // cause an access violation. | |
| unsafe { | |
| update_memory_flags(&mut stack_plus_guard_pages[0..GUARD_PAGE_SIZE], MemoryFlags::W) | |
| .map_err(|code| io::Error::from_raw_os_error(code as i32))? | |
| }; | |
| // No access to this page. Note: Write-only pages are illegal, and will | |
| // cause an access violation. | |
| unsafe { | |
| update_memory_flags( | |
| &mut stack_plus_guard_pages[(GUARD_PAGE_SIZE + stack_size)..], | |
| MemoryFlags::W, | |
| ) | |
| .map_err(|code| io::Error::from_raw_os_error(code as i32))? | |
| }; | |
| let guard_page_pre = stack_plus_guard_pages.as_ptr() as usize; | |
| let tid = create_thread( | |
| thread_start as *mut usize, | |
| &mut stack_plus_guard_pages[GUARD_PAGE_SIZE..(stack_size + GUARD_PAGE_SIZE)], | |
| data as usize, | |
| guard_page_pre, | |
| stack_size, | |
| 0, | |
| ) | |
| .map_err(|code| io::Error::from_raw_os_error(code as i32))?; | |
| #[inline(never)] | |
| fn rust_main_thread_not_inlined(init: Box<ThreadInit>) { | |
| let rust_start = init.init(); | |
| rust_start(); | |
| } | |
| extern "C" fn thread_start( | |
| data: *mut usize, | |
| guard_page_pre: usize, | |
| stack_size: usize, | |
| ) -> ! { | |
| // SAFETY: we are simply recreating the box that was leaked earlier. | |
| let init = unsafe { Box::from_raw(data as *mut ThreadInit) }; | |
| // Run the main thread with an inline(never) barrier to prevent | |
| // dealloc calls from being reordered to after the TLS has been destroyed. | |
| // See https://github.com/rust-lang/rust/pull/144465#pullrequestreview-3289729950 | |
| // for more context. | |
| rust_main_thread_not_inlined(init); | |
| // Destroy TLS, which will free the TLS page and call the destructor for | |
| // any thread local storage (if any). | |
| unsafe { | |
| crate::sys::thread_local::key::destroy_tls(); | |
| } | |
| // Deallocate the stack memory, along with the guard pages. Afterwards, | |
| // exit the thread by returning to the magic address 0xff80_3000usize, | |
| // which tells the kernel to deallocate this thread. | |
| let mapped_memory_base = guard_page_pre; | |
| let mapped_memory_length = GUARD_PAGE_SIZE + stack_size + GUARD_PAGE_SIZE; | |
| unsafe { | |
| asm!( | |
| "ecall", | |
| "ret", | |
| in("a0") Syscall::UnmapMemory as usize, | |
| in("a1") mapped_memory_base, | |
| in("a2") mapped_memory_length, | |
| in("ra") 0xff80_3000usize, | |
| options(nomem, nostack, noreturn) | |
| ); | |
| } | |
| } | |
| Ok(Thread { tid }) | |
| } | |
| pub fn join(self) { | |
| join_thread(self.tid).unwrap(); | |
| } | |
| } | |
| pub fn available_parallelism() -> io::Result<NonZero<usize>> { | |
| // We're unicore right now. | |
| Ok(unsafe { NonZero::new_unchecked(1) }) | |
| } | |
| pub fn yield_now() { | |
| do_yield(); | |
| } | |
| pub fn sleep(dur: Duration) { | |
| // Because the sleep server works on units of `usized milliseconds`, split | |
| // the messages up into these chunks. This means we may run into issues | |
| // if you try to sleep a thread for more than 49 days on a 32-bit system. | |
| let mut millis = dur.as_millis(); | |
| while millis > 0 { | |
| let sleep_duration = if millis > (usize::MAX as _) { usize::MAX } else { millis as usize }; | |
| blocking_scalar(ticktimer_server(), TicktimerScalar::SleepMs(sleep_duration).into()) | |
| .expect("failed to send message to ticktimer server"); | |
| millis -= sleep_duration as u128; | |
| } | |
| } |