host_bindings/
graphics.rs

1use std::{
2    cell::OnceCell,
3    collections::{BTreeMap, btree_map::Entry},
4    os::raw::c_void,
5};
6
7use futures::block_on;
8pub use graphics::lvgl;
9
10use task::TaskIdentifier;
11use virtual_machine::{
12    Environment, EnvironmentPointer, FunctionDescriptor, Registrable, WasmPointer, WasmUsize,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum Error {
17    InvalidArgumentsCount,
18    InvalidPointer,
19    NativePointerNotFound,
20    WasmPointerNotFound,
21    PointerTableFull,
22    EnvironmentRetrievalFailed,
23}
24
25pub type Result<T> = core::result::Result<T, Error>;
26
27mod generated_bindings {
28    use super::{Error, PointerTable, Result, TaskIdentifier, lvgl::*};
29    use virtual_machine::{Environment, WasmPointer, WasmUsize};
30
31    unsafe fn convert_to_native_pointer<T>(
32        environment: &Environment,
33        pointer: WasmPointer,
34    ) -> Result<*mut T> {
35        unsafe {
36            environment
37                .convert_to_native_pointer(pointer)
38                .ok_or(Error::InvalidPointer)
39        }
40    }
41
42    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
43}
44
45pub struct GraphicsBindings;
46
47impl Registrable for GraphicsBindings {
48    fn get_functions(&self) -> &[FunctionDescriptor] {
49        &GRAPHICS_BINDINGS_FUNCTIONS
50    }
51
52    #[cfg(not(target_arch = "x86_64"))]
53    fn is_XIP(&self) -> bool {
54        true
55    }
56
57    fn get_name(&self) -> &'static str {
58        "Xila_graphics\0"
59    }
60}
61
62pub(crate) struct PointerTable {
63    to_native_pointer: BTreeMap<usize, *mut c_void>,
64    to_wasm_pointer: BTreeMap<*mut c_void, u16>,
65}
66
67impl PointerTable {
68    pub fn new() -> Self {
69        Self {
70            to_native_pointer: BTreeMap::new(),
71            to_wasm_pointer: BTreeMap::new(),
72        }
73    }
74
75    const fn get_identifier(task: TaskIdentifier, identifier: u16) -> usize {
76        (task.into_inner() as usize) << 32 | identifier as usize
77    }
78
79    pub fn insert(&mut self, task: TaskIdentifier, pointer: *mut c_void) -> Result<u16> {
80        for i in u16::MIN..u16::MAX {
81            let identifier = Self::get_identifier(task, i);
82
83            match self.to_native_pointer.entry(identifier) {
84                Entry::Vacant(entry) => {
85                    entry.insert(pointer);
86                    self.to_wasm_pointer.insert(pointer, i);
87                    return Ok(i);
88                }
89                Entry::Occupied(entry_pointer) => {
90                    if *entry_pointer.get() == pointer {
91                        return Ok(i);
92                    }
93                }
94            }
95        }
96
97        Err(Error::PointerTableFull)
98    }
99
100    pub fn get_native_pointer<T>(&self, task: TaskIdentifier, identifier: u16) -> Result<*mut T> {
101        let identifier = Self::get_identifier(task, identifier);
102
103        self.to_native_pointer
104            .get(&identifier)
105            .map(|pointer| *pointer as *mut T)
106            .ok_or(Error::NativePointerNotFound)
107    }
108
109    pub fn get_wasm_pointer<T>(&self, pointer: *mut T) -> Result<u16> {
110        self.to_wasm_pointer
111            .get(&(pointer as *mut c_void))
112            .cloned()
113            .ok_or(Error::WasmPointerNotFound)
114    }
115
116    pub fn remove<T>(&mut self, task: TaskIdentifier, identifier: u16) -> Result<*mut T> {
117        let identifier = Self::get_identifier(task, identifier);
118
119        let pointer = self
120            .to_native_pointer
121            .remove(&identifier)
122            .map(|pointer| pointer as *mut T)
123            .ok_or(Error::NativePointerNotFound)?;
124
125        self.to_wasm_pointer.remove(&(pointer as *mut _));
126
127        Ok(pointer)
128    }
129}
130
131static mut POINTER_TABLE: OnceCell<PointerTable> = OnceCell::new();
132
133/// Call to graphics API
134///
135/// # Safety
136///
137/// This function is unsafe because it may dereference raw pointers (e.g. `Environment`, `Result` or `Arguments`).
138/// The pointer must be valid and properly aligned (ensured by the virtual machine).
139#[allow(clippy::too_many_arguments)]
140pub unsafe fn call(
141    environment: EnvironmentPointer,
142    function: generated_bindings::FunctionCall,
143    argument_0: WasmUsize,
144    argument_1: WasmUsize,
145    argument_2: WasmUsize,
146    argument_3: WasmUsize,
147    argument_4: WasmUsize,
148    argument_5: WasmUsize,
149    argument_6: WasmUsize,
150    arguments_count: u8,
151    result: WasmPointer,
152) {
153    unsafe {
154        let environment = Environment::from_raw_pointer(environment).unwrap();
155
156        let instance = graphics::get_instance();
157
158        let _lock = block_on(instance.lock());
159
160        let pointer_table_reference = &raw mut POINTER_TABLE;
161
162        let _ = (*pointer_table_reference).get_or_init(PointerTable::new);
163
164        let pointer_table_reference = (*pointer_table_reference).get_mut().unwrap();
165
166        if let Err(error) = generated_bindings::call_function(
167            environment,
168            pointer_table_reference,
169            function,
170            argument_0,
171            argument_1,
172            argument_2,
173            argument_3,
174            argument_4,
175            argument_5,
176            argument_6,
177            arguments_count,
178            result,
179        ) {
180            log::Error!(
181                "Error {error:?} durring graphics call: {function:?} with arguments: {argument_0:x}, {argument_1:x}, {argument_2:x}, {argument_3:x}, {argument_4:x}, {argument_5:x}, {argument_6:x}",
182            );
183        }
184
185        // Lock is automatically released here.
186    }
187}
188
189const GRAPHICS_BINDINGS_FUNCTIONS: [FunctionDescriptor; 1] = [FunctionDescriptor {
190    name: "Xila_graphics_call",
191    pointer: call as *mut _,
192}];