capsules_extra/wifi/
driver.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright OxidOS Automotive 2025.
4
5use crate::wifi::{len, Client, Device, Passphrase, Security, Ssid};
6use enum_primitive::cast::FromPrimitive;
7use kernel::errorcode::into_statuscode;
8use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
9use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer};
10use kernel::syscall::{self, CommandReturn, SyscallDriver};
11use kernel::{process, utilities::cells::OptionalCell, ErrorCode, ProcessId};
12
13/// Ids for read-only allow buffers
14mod ro_allow {
15    pub const SSID: usize = 0;
16    pub const PASS: usize = 1;
17
18    /// The number of RO allow buffers the kernel stores for this grant
19    pub const COUNT: u8 = 2;
20}
21
22/// Ids for read-write allow buffers
23mod rw_allow {
24    pub const MAC: usize = 0;
25    pub const SCAN_SSID: usize = 1;
26
27    /// The length for the MAC address buffer
28    pub const MAC_LEN: usize = 6;
29    /// The number of RW allow buffers the kernel stores for this grant
30    pub const COUNT: u8 = 2;
31}
32
33/// Ids for upcalls
34mod upcall {
35    pub const INIT: usize = 0;
36    pub const JOIN: usize = 1;
37    pub const LEAVE: usize = 2;
38    pub const AP: usize = 3;
39    pub const STA: usize = 4;
40    pub const SCAN: usize = 5;
41    pub const STOP_SCAN: usize = 6;
42
43    // Scan result/scan done
44    pub const SCAN_RES: usize = 7;
45
46    /// The number of upcalls the kernel stores for this grant
47    pub const COUNT: u8 = 8;
48}
49
50/// Wifi security options encodings
51mod security {
52    pub const OPEN: usize = 0;
53    pub const WPA: usize = 1;
54    pub const WPA2: usize = 2;
55    pub const WPA2_WPA3: usize = 3;
56    pub const WPA3: usize = 4;
57}
58
59#[repr(usize)]
60#[derive(Clone, Copy, Debug)]
61pub enum Command {
62    Init = upcall::INIT,
63    Join = upcall::JOIN,
64    Leave = upcall::LEAVE,
65    Ap = upcall::AP,
66    Sta = upcall::STA,
67    Scan = upcall::SCAN,
68    StopScan = upcall::STOP_SCAN,
69}
70
71impl Command {
72    #[inline]
73    fn to_upcall(self) -> usize {
74        self as _
75    }
76}
77
78#[derive(Default)]
79pub struct App;
80
81pub struct WifiDriver<'a, D: Device<'a>> {
82    device: &'a D,
83    process_id: OptionalCell<ProcessId>,
84    command: OptionalCell<Command>,
85    grants: Grant<
86        App,
87        UpcallCount<{ upcall::COUNT }>,
88        AllowRoCount<{ ro_allow::COUNT }>,
89        AllowRwCount<{ rw_allow::COUNT }>,
90    >,
91}
92
93impl<'a, D: Device<'a>> WifiDriver<'a, D> {
94    pub fn new(
95        device: &'a D,
96        grants: Grant<
97            App,
98            UpcallCount<{ upcall::COUNT }>,
99            AllowRoCount<{ ro_allow::COUNT }>,
100            AllowRwCount<{ rw_allow::COUNT }>,
101        >,
102    ) -> Self {
103        WifiDriver {
104            device,
105            process_id: OptionalCell::empty(),
106            command: OptionalCell::empty(),
107            grants,
108        }
109    }
110
111    fn credentials(
112        &self,
113        process_id: ProcessId,
114        security: usize,
115    ) -> Result<(Ssid, Option<(Security, Passphrase)>), ErrorCode> {
116        self.grants.enter(process_id, |_, kernel_data| {
117            let ssid = kernel_data
118                .get_readonly_processbuffer(ro_allow::SSID)
119                .and_then(|buf| {
120                    buf.enter(|buf| -> Result<Ssid, ErrorCode> {
121                        let mut ssid = Ssid::try_new(buf.len() as _)?;
122                        buf.copy_to_slice(&mut ssid.buf[..buf.len()]);
123                        Ok(ssid)
124                    })
125                })
126                .map_err(ErrorCode::from)??;
127            let security = match security {
128                security::OPEN => None,
129                wpa @ (security::WPA | security::WPA3 | security::WPA2 | security::WPA2_WPA3) => {
130                    Some(
131                        kernel_data
132                            .get_readonly_processbuffer(ro_allow::PASS)
133                            .and_then(|buf| {
134                                buf.enter(|buf| {
135                                    if buf.len() < len::WPA_PASSPHRASE_MIN {
136                                        return Err(ErrorCode::INVAL);
137                                    }
138                                    let mut passphrase = Passphrase::try_new(buf.len() as _)?;
139                                    buf.copy_to_slice(&mut passphrase.buf[..buf.len()]);
140                                    let security =
141                                        Security::from_usize(wpa).ok_or(ErrorCode::INVAL)?;
142
143                                    Ok((security, passphrase))
144                                })
145                            })??,
146                    )
147                }
148                _ => Err(ErrorCode::INVAL)?,
149            };
150            Ok((ssid, security))
151        })?
152    }
153}
154
155impl<'a, D: Device<'a>> SyscallDriver for WifiDriver<'a, D> {
156    fn command(
157        &self,
158        command_num: usize,
159        security: usize,
160        channel: usize,
161        process_id: ProcessId,
162    ) -> syscall::CommandReturn {
163        match command_num {
164            0 => CommandReturn::success(),
165            // Initialize the device
166            1 => {
167                let rval = self.device.init();
168                if rval.is_ok() {
169                    self.command.set(Command::Init);
170                    self.process_id.set(process_id);
171                }
172                rval.into()
173            }
174            // Get the MAC address of the device.
175            // Should be available if the initialisation is successful
176            2 => self.device.mac().map_or_else(
177                |err| Err(err).into(),
178                |mac| {
179                    self.grants
180                        .enter(process_id, |_, kernel_data| {
181                            kernel_data
182                                .get_readwrite_processbuffer(rw_allow::MAC)
183                                .and_then(|buf| {
184                                    buf.mut_enter(|buf| {
185                                        let len = usize::min(rw_allow::MAC_LEN, buf.len());
186                                        buf[..len].copy_from_slice(&mac[..len]);
187                                    })
188                                })
189                                .map_err(ErrorCode::from)
190                        })
191                        .unwrap_or_else(|err| Err(err.into()))
192                        .into()
193                },
194            ),
195            // Configure the device as an access point
196            3 => {
197                if channel > u8::MAX as _ {
198                    return Err(ErrorCode::INVAL).into();
199                }
200                let rval = self
201                    .credentials(process_id, security)
202                    .and_then(|(ssid, security)| {
203                        self.device.access_point(ssid, security, channel as _)
204                    });
205                if rval.is_ok() {
206                    self.command.set(Command::Ap);
207                    self.process_id.set(process_id);
208                }
209                rval.into()
210            }
211            // Configure the device as station
212            4 => {
213                let rval = self.device.station();
214                if rval.is_ok() {
215                    self.command.set(Command::Sta);
216                    self.process_id.set(process_id);
217                }
218                rval.into()
219            }
220            // Join a network
221            5 => {
222                let rval = self
223                    .credentials(process_id, security)
224                    .and_then(|(ssid, security)| self.device.join(ssid, security));
225                if rval.is_ok() {
226                    self.command.set(Command::Join);
227                    self.process_id.set(process_id);
228                }
229                rval.into()
230            }
231            // Leave the current connected network
232            6 => {
233                let rval = self.device.leave();
234                if rval.is_ok() {
235                    self.command.set(Command::Leave);
236                    self.process_id.set(process_id);
237                }
238                rval.into()
239            }
240            // Start scanning for networks
241            7 => {
242                let rval = self.device.scan();
243                if rval.is_ok() {
244                    self.command.set(Command::Scan);
245                    self.process_id.set(process_id);
246                }
247                rval.into()
248            }
249            // Stop scanning
250            8 => {
251                let rval = self.device.stop_scan();
252                if rval.is_ok() {
253                    self.command.set(Command::StopScan);
254                    self.process_id.set(process_id);
255                }
256                rval.into()
257            }
258            _ => syscall::CommandReturn::failure(ErrorCode::INVAL),
259        }
260    }
261
262    fn allocate_grant(&self, process_id: ProcessId) -> Result<(), process::Error> {
263        self.grants.enter(process_id, |_, _| {})
264    }
265}
266
267impl<'a, D: Device<'a>> Client for WifiDriver<'a, D> {
268    fn command_done(&self, rval: Result<(), ErrorCode>) {
269        Option::zip(self.process_id.get(), self.command.take()).map(|(process_id, command)| {
270            let _ = self.grants.enter(process_id, |_, kernel_data| {
271                let _ =
272                    kernel_data.schedule_upcall(command.to_upcall(), (into_statuscode(rval), 0, 0));
273                if let Command::Scan = command {
274                    self.process_id.set(process_id);
275                }
276            });
277        });
278    }
279
280    fn scan_done(&self) {
281        self.process_id.map(|process_id| {
282            self.grants.enter(process_id, |_, kernel_data| {
283                let _ = kernel_data.schedule_upcall(upcall::SCAN_RES, (0, 0, 0));
284            })
285        });
286    }
287
288    fn scanned_network(&self, ssid: Ssid) {
289        self.process_id.get().map(|process_id| {
290            self.grants
291                .enter(process_id, |_, kernel_data| {
292                    let _ = kernel_data
293                        .get_readwrite_processbuffer(rw_allow::SCAN_SSID)
294                        .and_then(|buf| {
295                            buf.mut_enter(|buf| {
296                                let len = usize::min(ssid.len.get() as _, buf.len());
297                                buf[..len].copy_from_slice(&ssid.buf[..len]);
298                            })
299                        });
300                    let _ =
301                        kernel_data.schedule_upcall(upcall::SCAN_RES, (ssid.len.get() as _, 0, 0));
302                })
303                .unwrap();
304        });
305    }
306}