1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
//! Memory //! //! Low-level helpers to assist memory mapping, MMIOs and DMAs. use sunrise_libutils::{align_down, align_up}; use crate::syscalls; use crate::error::{KernelError, LibuserError, Error}; /// The size of page. Used to interface with the kernel. pub const PAGE_SIZE: usize = 4096; /// Finds a free memory zone of the given size and alignment in the current /// process's virtual address space. Note that the address space is not reserved, /// a call to map_memory to that address space might fail if another thread /// maps to it first. It is recommended to use this function and the map syscall /// in a loop. /// /// # Panics /// /// Panics on underflow when size = 0. /// /// Panics on underflow when align = 0. pub fn find_free_address(size: usize, align: usize) -> Result<usize, Error> { // TODO: Use svcGetInfo to get the address space in find_free_address // BODY: We should use svcGetInfo to get the address space in // BODY: `find_free_address`. This is extremely important as the low // BODY: addresses (from 0 to ADDRESS_SPACE_MIN) are not usable, but are // BODY: marked as available by QueryMemory. // BODY: // BODY: Here's a sample implementation for when we'll have GetInfo impl'd. // BODY: // BODY: ```rust // BODY: lazy_static! { // BODY: static ref ADDRESS_SPACE: (usize, usize) = { // BODY: let addr_space_base = syscalls::get_info(Process::current(), 12, 0).unwrap(); // BODY: let addr_space_size = syscalls::get_info(Process::current(), 13, 0).unwrap(); // BODY: (addr_space_base, addr_space_base + addr_space_size) // BODY: }; // BODY: } // BODY: ``` let mut addr = 0x00200000; // Go over the address space. loop { let (meminfo, _) = syscalls::query_memory(addr)?; if meminfo.memtype.ty() == sunrise_libkern::MemoryType::Unmapped { let alignedbaseaddr = sunrise_libutils::align_up_checked(meminfo.baseaddr, align).ok_or(LibuserError::AddressSpaceExhausted)?; let alignment = alignedbaseaddr - meminfo.baseaddr; if alignment.checked_add(size - 1).ok_or(LibuserError::AddressSpaceExhausted)? < meminfo.size { return Ok(alignedbaseaddr) } } addr = meminfo.baseaddr.checked_add(meminfo.size).ok_or(LibuserError::AddressSpaceExhausted)?; } } /// Maps a Mmio struct in the virtual memory of this process. /// /// This function preserves the offset relative to `PAGE_SIZE`. /// /// # Example /// // no_run because map_mmio will return an error on linux /// ```no_run /// use sunrise_libutils::io::{Io, Mmio}; /// use sunrise_libuser::mem::map_mmio; /// /// Found at physical address 0xabc00030 /// #[repr(packed)] /// struct DeviceFoo { /// header: Mmio<u32>, /// version: Mmio<u32>, /// field_a: Mmio<u16>, /// field_b: Mmio<u16>, /// } /// /// let mapped_data: *mut DeviceFoo = map_mmio::<DeviceFoo>(0xabc00030).unwrap(); // = virtual address 0x7030 /// unsafe { /// assert_eq!((*mapped_data).version.read(), 0x010200); /// } /// ``` pub fn map_mmio<T>(physical_address: usize) -> Result<*mut T, KernelError> { let aligned_phys_addr = align_down(physical_address, PAGE_SIZE); let full_size = align_up(aligned_phys_addr + ::core::mem::size_of::<T>(), PAGE_SIZE) - aligned_phys_addr; let virt_addr = find_free_address(full_size as _, 1).unwrap(); syscalls::map_mmio_region(aligned_phys_addr as _, full_size as _, virt_addr, true)?; Ok((virt_addr + (physical_address % PAGE_SIZE)) as *mut T) } /// Gets the physical address of a structure from its virtual address, preserving offset in the page. /// /// # Panics /// /// * query_physical_address failed. pub fn virt_to_phys<T>(virtual_address: *const T) -> usize { let (phys_region_start, base_addr, _) = syscalls::query_physical_address(virtual_address as usize) .expect("syscall query_physical_memory failed"); let offset = virtual_address as usize - base_addr; phys_region_start + offset }