1e8f4f9aeSEliot Courtney // SPDX-License-Identifier: GPL-2.0 2e8f4f9aeSEliot Courtney 3e8f4f9aeSEliot Courtney //! Support for splitting large GSP commands across continuation records. 4e8f4f9aeSEliot Courtney 5e8f4f9aeSEliot Courtney use core::convert::Infallible; 6e8f4f9aeSEliot Courtney 7e8f4f9aeSEliot Courtney use kernel::prelude::*; 8e8f4f9aeSEliot Courtney 9*c3bd240fSEliot Courtney use super::{ 10*c3bd240fSEliot Courtney CommandToGsp, 11*c3bd240fSEliot Courtney NoReply, // 12*c3bd240fSEliot Courtney }; 13e8f4f9aeSEliot Courtney 14e8f4f9aeSEliot Courtney use crate::{ 15e8f4f9aeSEliot Courtney gsp::fw::{ 16e8f4f9aeSEliot Courtney GspMsgElement, 17e8f4f9aeSEliot Courtney MsgFunction, 18e8f4f9aeSEliot Courtney GSP_MSG_QUEUE_ELEMENT_SIZE_MAX, // 19e8f4f9aeSEliot Courtney }, 20e8f4f9aeSEliot Courtney sbuffer::SBufferIter, 21e8f4f9aeSEliot Courtney }; 22e8f4f9aeSEliot Courtney 23e8f4f9aeSEliot Courtney /// Maximum command size that fits in a single queue element. 24e8f4f9aeSEliot Courtney const MAX_CMD_SIZE: usize = GSP_MSG_QUEUE_ELEMENT_SIZE_MAX - size_of::<GspMsgElement>(); 25e8f4f9aeSEliot Courtney 26e8f4f9aeSEliot Courtney /// Acts as an iterator over the continuation records for a split command. 27e8f4f9aeSEliot Courtney pub(super) struct ContinuationRecords { 28e8f4f9aeSEliot Courtney payload: KVVec<u8>, 29e8f4f9aeSEliot Courtney offset: usize, 30e8f4f9aeSEliot Courtney } 31e8f4f9aeSEliot Courtney 32e8f4f9aeSEliot Courtney impl ContinuationRecords { 33e8f4f9aeSEliot Courtney /// Creates a new iterator over continuation records for the given payload. 34e8f4f9aeSEliot Courtney fn new(payload: KVVec<u8>) -> Self { 35e8f4f9aeSEliot Courtney Self { payload, offset: 0 } 36e8f4f9aeSEliot Courtney } 37e8f4f9aeSEliot Courtney 38e8f4f9aeSEliot Courtney /// Returns the next continuation record, or [`None`] if there are no more. 39e8f4f9aeSEliot Courtney pub(super) fn next(&mut self) -> Option<ContinuationRecord<'_>> { 40e8f4f9aeSEliot Courtney let remaining = self.payload.len() - self.offset; 41e8f4f9aeSEliot Courtney 42e8f4f9aeSEliot Courtney if remaining > 0 { 43e8f4f9aeSEliot Courtney let chunk_size = remaining.min(MAX_CMD_SIZE); 44e8f4f9aeSEliot Courtney let record = 45e8f4f9aeSEliot Courtney ContinuationRecord::new(&self.payload[self.offset..(self.offset + chunk_size)]); 46e8f4f9aeSEliot Courtney self.offset += chunk_size; 47e8f4f9aeSEliot Courtney Some(record) 48e8f4f9aeSEliot Courtney } else { 49e8f4f9aeSEliot Courtney None 50e8f4f9aeSEliot Courtney } 51e8f4f9aeSEliot Courtney } 52e8f4f9aeSEliot Courtney } 53e8f4f9aeSEliot Courtney 54e8f4f9aeSEliot Courtney /// The [`ContinuationRecord`] command. 55e8f4f9aeSEliot Courtney pub(super) struct ContinuationRecord<'a> { 56e8f4f9aeSEliot Courtney data: &'a [u8], 57e8f4f9aeSEliot Courtney } 58e8f4f9aeSEliot Courtney 59e8f4f9aeSEliot Courtney impl<'a> ContinuationRecord<'a> { 60e8f4f9aeSEliot Courtney /// Creates a new [`ContinuationRecord`] command with the given data. 61e8f4f9aeSEliot Courtney fn new(data: &'a [u8]) -> Self { 62e8f4f9aeSEliot Courtney Self { data } 63e8f4f9aeSEliot Courtney } 64e8f4f9aeSEliot Courtney } 65e8f4f9aeSEliot Courtney 66e8f4f9aeSEliot Courtney impl<'a> CommandToGsp for ContinuationRecord<'a> { 67e8f4f9aeSEliot Courtney const FUNCTION: MsgFunction = MsgFunction::ContinuationRecord; 68e8f4f9aeSEliot Courtney type Command = (); 69*c3bd240fSEliot Courtney type Reply = NoReply; 70e8f4f9aeSEliot Courtney type InitError = Infallible; 71e8f4f9aeSEliot Courtney 72e8f4f9aeSEliot Courtney fn init(&self) -> impl Init<Self::Command, Self::InitError> { 73e8f4f9aeSEliot Courtney <()>::init_zeroed() 74e8f4f9aeSEliot Courtney } 75e8f4f9aeSEliot Courtney 76e8f4f9aeSEliot Courtney fn variable_payload_len(&self) -> usize { 77e8f4f9aeSEliot Courtney self.data.len() 78e8f4f9aeSEliot Courtney } 79e8f4f9aeSEliot Courtney 80e8f4f9aeSEliot Courtney fn init_variable_payload( 81e8f4f9aeSEliot Courtney &self, 82e8f4f9aeSEliot Courtney dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>, 83e8f4f9aeSEliot Courtney ) -> Result { 84e8f4f9aeSEliot Courtney dst.write_all(self.data) 85e8f4f9aeSEliot Courtney } 86e8f4f9aeSEliot Courtney } 87e8f4f9aeSEliot Courtney 88e8f4f9aeSEliot Courtney /// Whether a command needs to be split across continuation records or not. 89e8f4f9aeSEliot Courtney pub(super) enum SplitState<C: CommandToGsp> { 90e8f4f9aeSEliot Courtney /// A command that fits in a single queue element. 91e8f4f9aeSEliot Courtney Single(C), 92e8f4f9aeSEliot Courtney /// A command split across continuation records. 93e8f4f9aeSEliot Courtney Split(SplitCommand<C>, ContinuationRecords), 94e8f4f9aeSEliot Courtney } 95e8f4f9aeSEliot Courtney 96e8f4f9aeSEliot Courtney impl<C: CommandToGsp> SplitState<C> { 97e8f4f9aeSEliot Courtney /// Maximum variable payload size that fits in the first command alongside the command header. 98e8f4f9aeSEliot Courtney const MAX_FIRST_PAYLOAD: usize = MAX_CMD_SIZE - size_of::<C::Command>(); 99e8f4f9aeSEliot Courtney 100e8f4f9aeSEliot Courtney /// Creates a new [`SplitState`] for the given command. 101e8f4f9aeSEliot Courtney /// 102e8f4f9aeSEliot Courtney /// If the command is too large, it will be split into a main command and some number of 103e8f4f9aeSEliot Courtney /// continuation records. 104e8f4f9aeSEliot Courtney pub(super) fn new(command: C) -> Result<Self> { 105e8f4f9aeSEliot Courtney let payload_len = command.variable_payload_len(); 106e8f4f9aeSEliot Courtney 107e8f4f9aeSEliot Courtney if command.size() > MAX_CMD_SIZE { 108e8f4f9aeSEliot Courtney let mut command_payload = 109e8f4f9aeSEliot Courtney KVVec::<u8>::from_elem(0u8, payload_len.min(Self::MAX_FIRST_PAYLOAD), GFP_KERNEL)?; 110e8f4f9aeSEliot Courtney let mut continuation_payload = 111e8f4f9aeSEliot Courtney KVVec::<u8>::from_elem(0u8, payload_len - command_payload.len(), GFP_KERNEL)?; 112e8f4f9aeSEliot Courtney let mut sbuffer = SBufferIter::new_writer([ 113e8f4f9aeSEliot Courtney command_payload.as_mut_slice(), 114e8f4f9aeSEliot Courtney continuation_payload.as_mut_slice(), 115e8f4f9aeSEliot Courtney ]); 116e8f4f9aeSEliot Courtney 117e8f4f9aeSEliot Courtney command.init_variable_payload(&mut sbuffer)?; 118e8f4f9aeSEliot Courtney if !sbuffer.is_empty() { 119e8f4f9aeSEliot Courtney return Err(EIO); 120e8f4f9aeSEliot Courtney } 121e8f4f9aeSEliot Courtney drop(sbuffer); 122e8f4f9aeSEliot Courtney 123e8f4f9aeSEliot Courtney Ok(Self::Split( 124e8f4f9aeSEliot Courtney SplitCommand::new(command, command_payload), 125e8f4f9aeSEliot Courtney ContinuationRecords::new(continuation_payload), 126e8f4f9aeSEliot Courtney )) 127e8f4f9aeSEliot Courtney } else { 128e8f4f9aeSEliot Courtney Ok(Self::Single(command)) 129e8f4f9aeSEliot Courtney } 130e8f4f9aeSEliot Courtney } 131e8f4f9aeSEliot Courtney } 132e8f4f9aeSEliot Courtney 133e8f4f9aeSEliot Courtney /// A command that has been truncated to maximum accepted length of the command queue. 134e8f4f9aeSEliot Courtney /// 135e8f4f9aeSEliot Courtney /// The remainder of its payload is expected to be sent using [`ContinuationRecords`]. 136e8f4f9aeSEliot Courtney pub(super) struct SplitCommand<C: CommandToGsp> { 137e8f4f9aeSEliot Courtney command: C, 138e8f4f9aeSEliot Courtney payload: KVVec<u8>, 139e8f4f9aeSEliot Courtney } 140e8f4f9aeSEliot Courtney 141e8f4f9aeSEliot Courtney impl<C: CommandToGsp> SplitCommand<C> { 142e8f4f9aeSEliot Courtney /// Creates a new [`SplitCommand`] wrapping `command` with the given truncated payload. 143e8f4f9aeSEliot Courtney fn new(command: C, payload: KVVec<u8>) -> Self { 144e8f4f9aeSEliot Courtney Self { command, payload } 145e8f4f9aeSEliot Courtney } 146e8f4f9aeSEliot Courtney } 147e8f4f9aeSEliot Courtney 148e8f4f9aeSEliot Courtney impl<C: CommandToGsp> CommandToGsp for SplitCommand<C> { 149e8f4f9aeSEliot Courtney const FUNCTION: MsgFunction = C::FUNCTION; 150e8f4f9aeSEliot Courtney type Command = C::Command; 151*c3bd240fSEliot Courtney type Reply = C::Reply; 152e8f4f9aeSEliot Courtney type InitError = C::InitError; 153e8f4f9aeSEliot Courtney 154e8f4f9aeSEliot Courtney fn init(&self) -> impl Init<Self::Command, Self::InitError> { 155e8f4f9aeSEliot Courtney self.command.init() 156e8f4f9aeSEliot Courtney } 157e8f4f9aeSEliot Courtney 158e8f4f9aeSEliot Courtney fn variable_payload_len(&self) -> usize { 159e8f4f9aeSEliot Courtney self.payload.len() 160e8f4f9aeSEliot Courtney } 161e8f4f9aeSEliot Courtney 162e8f4f9aeSEliot Courtney fn init_variable_payload( 163e8f4f9aeSEliot Courtney &self, 164e8f4f9aeSEliot Courtney dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>, 165e8f4f9aeSEliot Courtney ) -> Result { 166e8f4f9aeSEliot Courtney dst.write_all(&self.payload) 167e8f4f9aeSEliot Courtney } 168e8f4f9aeSEliot Courtney } 1690499a382SEliot Courtney 1700499a382SEliot Courtney #[kunit_tests(nova_core_gsp_continuation)] 1710499a382SEliot Courtney mod tests { 1720499a382SEliot Courtney use super::*; 1730499a382SEliot Courtney 1740499a382SEliot Courtney use kernel::transmute::{ 1750499a382SEliot Courtney AsBytes, 1760499a382SEliot Courtney FromBytes, // 1770499a382SEliot Courtney }; 1780499a382SEliot Courtney 1790499a382SEliot Courtney /// Non-zero-sized command header for testing. 1800499a382SEliot Courtney #[repr(C)] 1810499a382SEliot Courtney #[derive(Clone, Copy, Zeroable)] 1820499a382SEliot Courtney struct TestHeader([u8; 64]); 1830499a382SEliot Courtney 1840499a382SEliot Courtney // SAFETY: `TestHeader` is a plain array of bytes for which all bit patterns are valid. 1850499a382SEliot Courtney unsafe impl FromBytes for TestHeader {} 1860499a382SEliot Courtney 1870499a382SEliot Courtney // SAFETY: `TestHeader` is a plain array of bytes for which all bit patterns are valid. 1880499a382SEliot Courtney unsafe impl AsBytes for TestHeader {} 1890499a382SEliot Courtney 1900499a382SEliot Courtney struct TestPayload { 1910499a382SEliot Courtney data: KVVec<u8>, 1920499a382SEliot Courtney } 1930499a382SEliot Courtney 1940499a382SEliot Courtney impl TestPayload { 1950499a382SEliot Courtney fn generate_pattern(len: usize) -> Result<KVVec<u8>> { 1960499a382SEliot Courtney let mut data = KVVec::with_capacity(len, GFP_KERNEL)?; 1970499a382SEliot Courtney for i in 0..len { 1980499a382SEliot Courtney // Mix in higher bits so the pattern does not repeat every 256 bytes. 1990499a382SEliot Courtney data.push((i ^ (i >> 8)) as u8, GFP_KERNEL)?; 2000499a382SEliot Courtney } 2010499a382SEliot Courtney Ok(data) 2020499a382SEliot Courtney } 2030499a382SEliot Courtney 2040499a382SEliot Courtney fn new(len: usize) -> Result<Self> { 2050499a382SEliot Courtney Ok(Self { 2060499a382SEliot Courtney data: Self::generate_pattern(len)?, 2070499a382SEliot Courtney }) 2080499a382SEliot Courtney } 2090499a382SEliot Courtney } 2100499a382SEliot Courtney 2110499a382SEliot Courtney impl CommandToGsp for TestPayload { 2120499a382SEliot Courtney const FUNCTION: MsgFunction = MsgFunction::Nop; 2130499a382SEliot Courtney type Command = TestHeader; 214*c3bd240fSEliot Courtney type Reply = NoReply; 2150499a382SEliot Courtney type InitError = Infallible; 2160499a382SEliot Courtney 2170499a382SEliot Courtney fn init(&self) -> impl Init<Self::Command, Self::InitError> { 2180499a382SEliot Courtney TestHeader::init_zeroed() 2190499a382SEliot Courtney } 2200499a382SEliot Courtney 2210499a382SEliot Courtney fn variable_payload_len(&self) -> usize { 2220499a382SEliot Courtney self.data.len() 2230499a382SEliot Courtney } 2240499a382SEliot Courtney 2250499a382SEliot Courtney fn init_variable_payload( 2260499a382SEliot Courtney &self, 2270499a382SEliot Courtney dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>, 2280499a382SEliot Courtney ) -> Result { 2290499a382SEliot Courtney dst.write_all(self.data.as_slice()) 2300499a382SEliot Courtney } 2310499a382SEliot Courtney } 2320499a382SEliot Courtney 2330499a382SEliot Courtney /// Maximum variable payload size that fits in the first command alongside the header. 2340499a382SEliot Courtney const MAX_FIRST_PAYLOAD: usize = SplitState::<TestPayload>::MAX_FIRST_PAYLOAD; 2350499a382SEliot Courtney 2360499a382SEliot Courtney fn read_payload(cmd: impl CommandToGsp) -> Result<KVVec<u8>> { 2370499a382SEliot Courtney let len = cmd.variable_payload_len(); 2380499a382SEliot Courtney let mut buf = KVVec::from_elem(0u8, len, GFP_KERNEL)?; 2390499a382SEliot Courtney let mut sbuf = SBufferIter::new_writer([buf.as_mut_slice(), &mut []]); 2400499a382SEliot Courtney cmd.init_variable_payload(&mut sbuf)?; 2410499a382SEliot Courtney drop(sbuf); 2420499a382SEliot Courtney Ok(buf) 2430499a382SEliot Courtney } 2440499a382SEliot Courtney 2450499a382SEliot Courtney struct SplitTest { 2460499a382SEliot Courtney payload_size: usize, 2470499a382SEliot Courtney num_continuations: usize, 2480499a382SEliot Courtney } 2490499a382SEliot Courtney 2500499a382SEliot Courtney fn check_split(t: SplitTest) -> Result { 2510499a382SEliot Courtney let payload = TestPayload::new(t.payload_size)?; 2520499a382SEliot Courtney let mut num_continuations = 0; 2530499a382SEliot Courtney 2540499a382SEliot Courtney let buf = match SplitState::new(payload)? { 2550499a382SEliot Courtney SplitState::Single(cmd) => read_payload(cmd)?, 2560499a382SEliot Courtney SplitState::Split(cmd, mut continuations) => { 2570499a382SEliot Courtney let mut buf = read_payload(cmd)?; 2580499a382SEliot Courtney assert!(size_of::<TestHeader>() + buf.len() <= MAX_CMD_SIZE); 2590499a382SEliot Courtney 2600499a382SEliot Courtney while let Some(cont) = continuations.next() { 2610499a382SEliot Courtney let payload = read_payload(cont)?; 2620499a382SEliot Courtney assert!(payload.len() <= MAX_CMD_SIZE); 2630499a382SEliot Courtney buf.extend_from_slice(&payload, GFP_KERNEL)?; 2640499a382SEliot Courtney num_continuations += 1; 2650499a382SEliot Courtney } 2660499a382SEliot Courtney 2670499a382SEliot Courtney buf 2680499a382SEliot Courtney } 2690499a382SEliot Courtney }; 2700499a382SEliot Courtney 2710499a382SEliot Courtney assert_eq!(num_continuations, t.num_continuations); 2720499a382SEliot Courtney assert_eq!( 2730499a382SEliot Courtney buf.as_slice(), 2740499a382SEliot Courtney TestPayload::generate_pattern(t.payload_size)?.as_slice() 2750499a382SEliot Courtney ); 2760499a382SEliot Courtney Ok(()) 2770499a382SEliot Courtney } 2780499a382SEliot Courtney 2790499a382SEliot Courtney #[test] 2800499a382SEliot Courtney fn split_command() -> Result { 2810499a382SEliot Courtney check_split(SplitTest { 2820499a382SEliot Courtney payload_size: 0, 2830499a382SEliot Courtney num_continuations: 0, 2840499a382SEliot Courtney })?; 2850499a382SEliot Courtney check_split(SplitTest { 2860499a382SEliot Courtney payload_size: MAX_FIRST_PAYLOAD, 2870499a382SEliot Courtney num_continuations: 0, 2880499a382SEliot Courtney })?; 2890499a382SEliot Courtney check_split(SplitTest { 2900499a382SEliot Courtney payload_size: MAX_FIRST_PAYLOAD + 1, 2910499a382SEliot Courtney num_continuations: 1, 2920499a382SEliot Courtney })?; 2930499a382SEliot Courtney check_split(SplitTest { 2940499a382SEliot Courtney payload_size: MAX_FIRST_PAYLOAD + MAX_CMD_SIZE, 2950499a382SEliot Courtney num_continuations: 1, 2960499a382SEliot Courtney })?; 2970499a382SEliot Courtney check_split(SplitTest { 2980499a382SEliot Courtney payload_size: MAX_FIRST_PAYLOAD + MAX_CMD_SIZE + 1, 2990499a382SEliot Courtney num_continuations: 2, 3000499a382SEliot Courtney })?; 3010499a382SEliot Courtney check_split(SplitTest { 3020499a382SEliot Courtney payload_size: MAX_FIRST_PAYLOAD + MAX_CMD_SIZE * 3 + MAX_CMD_SIZE / 2, 3030499a382SEliot Courtney num_continuations: 4, 3040499a382SEliot Courtney })?; 3050499a382SEliot Courtney Ok(()) 3060499a382SEliot Courtney } 3070499a382SEliot Courtney } 308