|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only OR MIT |
| 2 | + |
| 3 | +//! Support for Apple RTKit coprocessors. |
| 4 | +//! |
| 5 | +//! C header: [`include/linux/soc/apple/rtkit.h`](../../../../include/linux/gpio/driver.h) |
| 6 | +
|
| 7 | +use crate::{ |
| 8 | + alloc::flags::*, |
| 9 | + bindings, |
| 10 | + device, |
| 11 | + error::{ |
| 12 | + from_err_ptr, |
| 13 | + from_result, |
| 14 | + to_result, // |
| 15 | + }, |
| 16 | + iosys_map::IoSysMapRef, |
| 17 | + prelude::*, |
| 18 | + str::CStrExt, |
| 19 | + types::{ |
| 20 | + ForeignOwnable, |
| 21 | + ScopeGuard, // |
| 22 | + }, // |
| 23 | +}; |
| 24 | + |
| 25 | +use core::marker::PhantomData; |
| 26 | +use core::ptr; |
| 27 | +use macros::vtable; |
| 28 | + |
| 29 | +/// Trait to represent allocatable buffers for the RTKit core. |
| 30 | +/// |
| 31 | +/// Users must implement this trait for their own representation of those allocations. |
| 32 | +pub trait Buffer { |
| 33 | + /// Returns the IOVA (virtual address) of the buffer from RTKit's point of view, or an error if |
| 34 | + /// unavailable. |
| 35 | + fn iova(&self) -> Result<usize>; |
| 36 | + |
| 37 | + /// Returns a mutable byte slice of the buffer contents, or an |
| 38 | + /// error if unavailable. |
| 39 | + fn buf(&mut self) -> Result<IoSysMapRef<'_, u8>>; |
| 40 | +} |
| 41 | + |
| 42 | +/// Callback operations for an RTKit client. |
| 43 | +#[vtable] |
| 44 | +pub trait Operations { |
| 45 | + /// Arbitrary user context type. |
| 46 | + type Data: ForeignOwnable + Send + Sync; |
| 47 | + |
| 48 | + /// Type representing an allocated buffer for RTKit. |
| 49 | + type Buffer: Buffer; |
| 50 | + |
| 51 | + /// Called when RTKit crashes. |
| 52 | + fn crashed(_data: <Self::Data as ForeignOwnable>::Borrowed<'_>, _crashlog: Option<&[u8]>) {} |
| 53 | + |
| 54 | + /// Called when a message was received on a non-system endpoint. Called in non-IRQ context. |
| 55 | + fn recv_message( |
| 56 | + _data: <Self::Data as ForeignOwnable>::Borrowed<'_>, |
| 57 | + _endpoint: u8, |
| 58 | + _message: u64, |
| 59 | + ) { |
| 60 | + } |
| 61 | + |
| 62 | + /// Called in IRQ context when a message was received on a non-system endpoint. |
| 63 | + /// |
| 64 | + /// Must return `true` if the message is handled, or `false` to process it in |
| 65 | + /// the handling thread. |
| 66 | + fn recv_message_early( |
| 67 | + _data: <Self::Data as ForeignOwnable>::Borrowed<'_>, |
| 68 | + _endpoint: u8, |
| 69 | + _message: u64, |
| 70 | + ) -> bool { |
| 71 | + false |
| 72 | + } |
| 73 | + |
| 74 | + /// Allocate a buffer for use by RTKit. |
| 75 | + fn shmem_alloc( |
| 76 | + _data: <Self::Data as ForeignOwnable>::Borrowed<'_>, |
| 77 | + _size: usize, |
| 78 | + ) -> Result<Self::Buffer> { |
| 79 | + Err(EINVAL) |
| 80 | + } |
| 81 | + |
| 82 | + /// Map an existing buffer used by RTKit at a device-specified virtual address. |
| 83 | + fn shmem_map( |
| 84 | + _data: <Self::Data as ForeignOwnable>::Borrowed<'_>, |
| 85 | + _iova: usize, |
| 86 | + _size: usize, |
| 87 | + ) -> Result<Self::Buffer> { |
| 88 | + Err(EINVAL) |
| 89 | + } |
| 90 | +} |
| 91 | + |
| 92 | +/// Represents `struct apple_rtkit *`. |
| 93 | +/// |
| 94 | +/// # Invariants |
| 95 | +/// |
| 96 | +/// The rtk pointer is valid. |
| 97 | +/// The data pointer is a valid pointer from T::Data::into_foreign(). |
| 98 | +pub struct RtKit<T: Operations> { |
| 99 | + rtk: *mut bindings::apple_rtkit, |
| 100 | + data: *mut core::ffi::c_void, |
| 101 | + _p: PhantomData<T>, |
| 102 | +} |
| 103 | + |
| 104 | +unsafe extern "C" fn crashed_callback<T: Operations>( |
| 105 | + cookie: *mut core::ffi::c_void, |
| 106 | + crashlog: *const core::ffi::c_void, |
| 107 | + crashlog_size: usize, |
| 108 | +) { |
| 109 | + let crashlog = if !crashlog.is_null() && crashlog_size > 0 { |
| 110 | + // SAFETY: The crashlog is either missing or a byte buffer of the specified size |
| 111 | + Some(unsafe { core::slice::from_raw_parts(crashlog as *const u8, crashlog_size) }) |
| 112 | + } else { |
| 113 | + None |
| 114 | + }; |
| 115 | + // SAFETY: cookie is always a T::Data in this API |
| 116 | + T::crashed(unsafe { T::Data::borrow(cookie.cast()) }, crashlog); |
| 117 | +} |
| 118 | + |
| 119 | +unsafe extern "C" fn recv_message_callback<T: Operations>( |
| 120 | + cookie: *mut core::ffi::c_void, |
| 121 | + endpoint: u8, |
| 122 | + message: u64, |
| 123 | +) { |
| 124 | + // SAFETY: cookie is always a T::Data in this API |
| 125 | + T::recv_message(unsafe { T::Data::borrow(cookie.cast()) }, endpoint, message); |
| 126 | +} |
| 127 | + |
| 128 | +unsafe extern "C" fn recv_message_early_callback<T: Operations>( |
| 129 | + cookie: *mut core::ffi::c_void, |
| 130 | + endpoint: u8, |
| 131 | + message: u64, |
| 132 | +) -> bool { |
| 133 | + // SAFETY: cookie is always a T::Data in this API |
| 134 | + T::recv_message_early(unsafe { T::Data::borrow(cookie.cast()) }, endpoint, message) |
| 135 | +} |
| 136 | + |
| 137 | +unsafe extern "C" fn shmem_setup_callback<T: Operations>( |
| 138 | + cookie: *mut core::ffi::c_void, |
| 139 | + bfr: *mut bindings::apple_rtkit_shmem, |
| 140 | +) -> core::ffi::c_int { |
| 141 | + // SAFETY: `bfr` is a valid buffer |
| 142 | + let bfr_mut = unsafe { &mut *bfr }; |
| 143 | + |
| 144 | + from_result(|| { |
| 145 | + let mut buf = if bfr_mut.iova != 0 { |
| 146 | + bfr_mut.is_mapped = true; |
| 147 | + T::shmem_map( |
| 148 | + // SAFETY: `cookie` came from a previous call to `into_foreign`. |
| 149 | + unsafe { T::Data::borrow(cookie.cast()) }, |
| 150 | + bfr_mut.iova as usize, |
| 151 | + bfr_mut.size, |
| 152 | + )? |
| 153 | + } else { |
| 154 | + bfr_mut.is_mapped = false; |
| 155 | + // SAFETY: `cookie` came from a previous call to `into_foreign`. |
| 156 | + T::shmem_alloc(unsafe { T::Data::borrow(cookie.cast()) }, bfr_mut.size)? |
| 157 | + }; |
| 158 | + |
| 159 | + let iova = buf.iova()?; |
| 160 | + let iosys_map = buf.buf()?; |
| 161 | + |
| 162 | + if iosys_map.size() < bfr_mut.size { |
| 163 | + return Err(ENOMEM); |
| 164 | + } |
| 165 | + |
| 166 | + bfr_mut.iova = iova as u64; |
| 167 | + bfr_mut.buffer = iosys_map.as_mut_ptr() as *mut _; |
| 168 | + |
| 169 | + // Now box the returned buffer type and stash it in the private pointer of the |
| 170 | + // `apple_rtkit_shmem` struct for safekeeping. |
| 171 | + let boxed = KBox::new(buf, GFP_KERNEL)?; |
| 172 | + bfr_mut.private = KBox::into_raw(boxed) as *mut _; |
| 173 | + Ok(0) |
| 174 | + }) |
| 175 | +} |
| 176 | + |
| 177 | +unsafe extern "C" fn shmem_destroy_callback<T: Operations>( |
| 178 | + _cookie: *mut core::ffi::c_void, |
| 179 | + bfr: *mut bindings::apple_rtkit_shmem, |
| 180 | +) { |
| 181 | + // SAFETY: `bfr` is a valid buffer |
| 182 | + let bfr_mut = unsafe { &mut *bfr }; |
| 183 | + if !bfr_mut.private.is_null() { |
| 184 | + // SAFETY: Per shmem_setup_callback, this has to be a pointer to a Buffer if it is set. |
| 185 | + unsafe { |
| 186 | + core::mem::drop(KBox::from_raw(bfr_mut.private as *mut T::Buffer)); |
| 187 | + } |
| 188 | + bfr_mut.private = core::ptr::null_mut(); |
| 189 | + } |
| 190 | +} |
| 191 | + |
| 192 | +impl<T: Operations> RtKit<T> { |
| 193 | + const VTABLE: bindings::apple_rtkit_ops = bindings::apple_rtkit_ops { |
| 194 | + crashed: Some(crashed_callback::<T>), |
| 195 | + recv_message: Some(recv_message_callback::<T>), |
| 196 | + recv_message_early: Some(recv_message_early_callback::<T>), |
| 197 | + shmem_setup: if T::HAS_SHMEM_ALLOC || T::HAS_SHMEM_MAP { |
| 198 | + Some(shmem_setup_callback::<T>) |
| 199 | + } else { |
| 200 | + None |
| 201 | + }, |
| 202 | + shmem_destroy: if T::HAS_SHMEM_ALLOC || T::HAS_SHMEM_MAP { |
| 203 | + Some(shmem_destroy_callback::<T>) |
| 204 | + } else { |
| 205 | + None |
| 206 | + }, |
| 207 | + }; |
| 208 | + |
| 209 | + /// Creates a new RTKit client for a given device and optional mailbox name or index. |
| 210 | + pub fn new( |
| 211 | + dev: &device::Device, |
| 212 | + mbox_name: Option<&'static CStr>, |
| 213 | + mbox_idx: usize, |
| 214 | + data: T::Data, |
| 215 | + ) -> Result<Self> { |
| 216 | + let ptr: *mut crate::ffi::c_void = data.into_foreign().cast(); |
| 217 | + let guard = ScopeGuard::new(|| { |
| 218 | + // SAFETY: `ptr` came from a previous call to `into_foreign`. |
| 219 | + unsafe { T::Data::from_foreign(ptr.cast()) }; |
| 220 | + }); |
| 221 | + // SAFETY: `dev` is valid by its type invarants and otherwise his just |
| 222 | + // calls the C init function. |
| 223 | + let rtk = unsafe { |
| 224 | + from_err_ptr(bindings::apple_rtkit_init( |
| 225 | + dev.as_raw(), |
| 226 | + ptr, |
| 227 | + match mbox_name { |
| 228 | + Some(s) => s.as_char_ptr(), |
| 229 | + None => ptr::null(), |
| 230 | + }, |
| 231 | + mbox_idx.try_into()?, |
| 232 | + &Self::VTABLE, |
| 233 | + )) |
| 234 | + }?; |
| 235 | + |
| 236 | + guard.dismiss(); |
| 237 | + // INVARIANT: `rtk` and `data` are valid here. |
| 238 | + Ok(Self { |
| 239 | + rtk, |
| 240 | + data: ptr, |
| 241 | + _p: PhantomData, |
| 242 | + }) |
| 243 | + } |
| 244 | + |
| 245 | + /// Boots (wakes up) the RTKit coprocessor. |
| 246 | + pub fn wake(self: Pin<&mut Self>) -> Result { |
| 247 | + // SAFETY: `rtk` is valid per the type invariant. |
| 248 | + to_result(unsafe { bindings::apple_rtkit_wake(self.rtk) }) |
| 249 | + } |
| 250 | + |
| 251 | + /// Waits for the RTKit coprocessor to finish booting. |
| 252 | + pub fn boot(self: Pin<&mut Self>) -> Result { |
| 253 | + // SAFETY: `rtk` is valid per the type invariant. |
| 254 | + to_result(unsafe { bindings::apple_rtkit_boot(self.rtk) }) |
| 255 | + } |
| 256 | + |
| 257 | + /// Starts a non-system endpoint. |
| 258 | + pub fn start_endpoint(self: Pin<&mut Self>, endpoint: u8) -> Result { |
| 259 | + // SAFETY: `rtk` is valid per the type invariant. |
| 260 | + to_result(unsafe { bindings::apple_rtkit_start_ep(self.rtk, endpoint) }) |
| 261 | + } |
| 262 | + |
| 263 | + /// Sends a message to a given endpoint. |
| 264 | + pub fn send_message(self: Pin<&mut Self>, endpoint: u8, message: u64) -> Result { |
| 265 | + // SAFETY: `rtk` is valid per the type invariant. |
| 266 | + to_result(unsafe { |
| 267 | + bindings::apple_rtkit_send_message(self.rtk, endpoint, message, ptr::null_mut(), false) |
| 268 | + }) |
| 269 | + } |
| 270 | +} |
| 271 | + |
| 272 | +// SAFETY: `RtKit` operations require a mutable reference |
| 273 | +unsafe impl<T: Operations> Sync for RtKit<T> {} |
| 274 | + |
| 275 | +// SAFETY: `RtKit` operations require a mutable reference |
| 276 | +unsafe impl<T: Operations> Send for RtKit<T> {} |
| 277 | + |
| 278 | +impl<T: Operations> Drop for RtKit<T> { |
| 279 | + fn drop(&mut self) { |
| 280 | + // SAFETY: The pointer is valid by the type invariant. |
| 281 | + unsafe { bindings::apple_rtkit_free(self.rtk) }; |
| 282 | + |
| 283 | + // Free context data. |
| 284 | + // |
| 285 | + // SAFETY: This matches the call to `into_foreign` from `new` in the success case. |
| 286 | + unsafe { T::Data::from_foreign(self.data.cast()) }; |
| 287 | + } |
| 288 | +} |
0 commit comments