1 // SPDX-License-Identifier: GPL-2.0 2 3 //! Support for splitting large GSP commands across continuation records. 4 5 use core::convert::Infallible; 6 7 use kernel::prelude::*; 8 9 use super::{ 10 CommandToGsp, 11 NoReply, // 12 }; 13 14 use crate::{ 15 gsp::fw::{ 16 GspMsgElement, 17 MsgFunction, 18 GSP_MSG_QUEUE_ELEMENT_SIZE_MAX, // 19 }, 20 sbuffer::SBufferIter, 21 }; 22 23 /// Maximum command size that fits in a single queue element. 24 const MAX_CMD_SIZE: usize = GSP_MSG_QUEUE_ELEMENT_SIZE_MAX - size_of::<GspMsgElement>(); 25 26 /// Acts as an iterator over the continuation records for a split command. 27 pub(super) struct ContinuationRecords { 28 payload: KVVec<u8>, 29 offset: usize, 30 } 31 32 impl ContinuationRecords { 33 /// Creates a new iterator over continuation records for the given payload. 34 fn new(payload: KVVec<u8>) -> Self { 35 Self { payload, offset: 0 } 36 } 37 38 /// Returns the next continuation record, or [`None`] if there are no more. 39 pub(super) fn next(&mut self) -> Option<ContinuationRecord<'_>> { 40 let remaining = self.payload.len() - self.offset; 41 42 if remaining > 0 { 43 let chunk_size = remaining.min(MAX_CMD_SIZE); 44 let record = 45 ContinuationRecord::new(&self.payload[self.offset..(self.offset + chunk_size)]); 46 self.offset += chunk_size; 47 Some(record) 48 } else { 49 None 50 } 51 } 52 } 53 54 /// The [`ContinuationRecord`] command. 55 pub(super) struct ContinuationRecord<'a> { 56 data: &'a [u8], 57 } 58 59 impl<'a> ContinuationRecord<'a> { 60 /// Creates a new [`ContinuationRecord`] command with the given data. 61 fn new(data: &'a [u8]) -> Self { 62 Self { data } 63 } 64 } 65 66 impl<'a> CommandToGsp for ContinuationRecord<'a> { 67 const FUNCTION: MsgFunction = MsgFunction::ContinuationRecord; 68 type Command = (); 69 type Reply = NoReply; 70 type InitError = Infallible; 71 72 fn init(&self) -> impl Init<Self::Command, Self::InitError> { 73 <()>::init_zeroed() 74 } 75 76 fn variable_payload_len(&self) -> usize { 77 self.data.len() 78 } 79 80 fn init_variable_payload( 81 &self, 82 dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>, 83 ) -> Result { 84 dst.write_all(self.data) 85 } 86 } 87 88 /// Whether a command needs to be split across continuation records or not. 89 pub(super) enum SplitState<C: CommandToGsp> { 90 /// A command that fits in a single queue element. 91 Single(C), 92 /// A command split across continuation records. 93 Split(SplitCommand<C>, ContinuationRecords), 94 } 95 96 impl<C: CommandToGsp> SplitState<C> { 97 /// Maximum variable payload size that fits in the first command alongside the command header. 98 const MAX_FIRST_PAYLOAD: usize = MAX_CMD_SIZE - size_of::<C::Command>(); 99 100 /// Creates a new [`SplitState`] for the given command. 101 /// 102 /// If the command is too large, it will be split into a main command and some number of 103 /// continuation records. 104 pub(super) fn new(command: C) -> Result<Self> { 105 let payload_len = command.variable_payload_len(); 106 107 if command.size() > MAX_CMD_SIZE { 108 let mut command_payload = 109 KVVec::<u8>::from_elem(0u8, payload_len.min(Self::MAX_FIRST_PAYLOAD), GFP_KERNEL)?; 110 let mut continuation_payload = 111 KVVec::<u8>::from_elem(0u8, payload_len - command_payload.len(), GFP_KERNEL)?; 112 let mut sbuffer = SBufferIter::new_writer([ 113 command_payload.as_mut_slice(), 114 continuation_payload.as_mut_slice(), 115 ]); 116 117 command.init_variable_payload(&mut sbuffer)?; 118 if !sbuffer.is_empty() { 119 return Err(EIO); 120 } 121 drop(sbuffer); 122 123 Ok(Self::Split( 124 SplitCommand::new(command, command_payload), 125 ContinuationRecords::new(continuation_payload), 126 )) 127 } else { 128 Ok(Self::Single(command)) 129 } 130 } 131 } 132 133 /// A command that has been truncated to maximum accepted length of the command queue. 134 /// 135 /// The remainder of its payload is expected to be sent using [`ContinuationRecords`]. 136 pub(super) struct SplitCommand<C: CommandToGsp> { 137 command: C, 138 payload: KVVec<u8>, 139 } 140 141 impl<C: CommandToGsp> SplitCommand<C> { 142 /// Creates a new [`SplitCommand`] wrapping `command` with the given truncated payload. 143 fn new(command: C, payload: KVVec<u8>) -> Self { 144 Self { command, payload } 145 } 146 } 147 148 impl<C: CommandToGsp> CommandToGsp for SplitCommand<C> { 149 const FUNCTION: MsgFunction = C::FUNCTION; 150 type Command = C::Command; 151 type Reply = C::Reply; 152 type InitError = C::InitError; 153 154 fn init(&self) -> impl Init<Self::Command, Self::InitError> { 155 self.command.init() 156 } 157 158 fn variable_payload_len(&self) -> usize { 159 self.payload.len() 160 } 161 162 fn init_variable_payload( 163 &self, 164 dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>, 165 ) -> Result { 166 dst.write_all(&self.payload) 167 } 168 } 169 170 #[kunit_tests(nova_core_gsp_continuation)] 171 mod tests { 172 use super::*; 173 174 use kernel::transmute::{ 175 AsBytes, 176 FromBytes, // 177 }; 178 179 /// Non-zero-sized command header for testing. 180 #[repr(C)] 181 #[derive(Clone, Copy, Zeroable)] 182 struct TestHeader([u8; 64]); 183 184 // SAFETY: `TestHeader` is a plain array of bytes for which all bit patterns are valid. 185 unsafe impl FromBytes for TestHeader {} 186 187 // SAFETY: `TestHeader` is a plain array of bytes for which all bit patterns are valid. 188 unsafe impl AsBytes for TestHeader {} 189 190 struct TestPayload { 191 data: KVVec<u8>, 192 } 193 194 impl TestPayload { 195 fn generate_pattern(len: usize) -> Result<KVVec<u8>> { 196 let mut data = KVVec::with_capacity(len, GFP_KERNEL)?; 197 for i in 0..len { 198 // Mix in higher bits so the pattern does not repeat every 256 bytes. 199 data.push((i ^ (i >> 8)) as u8, GFP_KERNEL)?; 200 } 201 Ok(data) 202 } 203 204 fn new(len: usize) -> Result<Self> { 205 Ok(Self { 206 data: Self::generate_pattern(len)?, 207 }) 208 } 209 } 210 211 impl CommandToGsp for TestPayload { 212 const FUNCTION: MsgFunction = MsgFunction::Nop; 213 type Command = TestHeader; 214 type Reply = NoReply; 215 type InitError = Infallible; 216 217 fn init(&self) -> impl Init<Self::Command, Self::InitError> { 218 TestHeader::init_zeroed() 219 } 220 221 fn variable_payload_len(&self) -> usize { 222 self.data.len() 223 } 224 225 fn init_variable_payload( 226 &self, 227 dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>, 228 ) -> Result { 229 dst.write_all(self.data.as_slice()) 230 } 231 } 232 233 /// Maximum variable payload size that fits in the first command alongside the header. 234 const MAX_FIRST_PAYLOAD: usize = SplitState::<TestPayload>::MAX_FIRST_PAYLOAD; 235 236 fn read_payload(cmd: impl CommandToGsp) -> Result<KVVec<u8>> { 237 let len = cmd.variable_payload_len(); 238 let mut buf = KVVec::from_elem(0u8, len, GFP_KERNEL)?; 239 let mut sbuf = SBufferIter::new_writer([buf.as_mut_slice(), &mut []]); 240 cmd.init_variable_payload(&mut sbuf)?; 241 drop(sbuf); 242 Ok(buf) 243 } 244 245 struct SplitTest { 246 payload_size: usize, 247 num_continuations: usize, 248 } 249 250 fn check_split(t: SplitTest) -> Result { 251 let payload = TestPayload::new(t.payload_size)?; 252 let mut num_continuations = 0; 253 254 let buf = match SplitState::new(payload)? { 255 SplitState::Single(cmd) => read_payload(cmd)?, 256 SplitState::Split(cmd, mut continuations) => { 257 let mut buf = read_payload(cmd)?; 258 assert!(size_of::<TestHeader>() + buf.len() <= MAX_CMD_SIZE); 259 260 while let Some(cont) = continuations.next() { 261 let payload = read_payload(cont)?; 262 assert!(payload.len() <= MAX_CMD_SIZE); 263 buf.extend_from_slice(&payload, GFP_KERNEL)?; 264 num_continuations += 1; 265 } 266 267 buf 268 } 269 }; 270 271 assert_eq!(num_continuations, t.num_continuations); 272 assert_eq!( 273 buf.as_slice(), 274 TestPayload::generate_pattern(t.payload_size)?.as_slice() 275 ); 276 Ok(()) 277 } 278 279 #[test] 280 fn split_command() -> Result { 281 check_split(SplitTest { 282 payload_size: 0, 283 num_continuations: 0, 284 })?; 285 check_split(SplitTest { 286 payload_size: MAX_FIRST_PAYLOAD, 287 num_continuations: 0, 288 })?; 289 check_split(SplitTest { 290 payload_size: MAX_FIRST_PAYLOAD + 1, 291 num_continuations: 1, 292 })?; 293 check_split(SplitTest { 294 payload_size: MAX_FIRST_PAYLOAD + MAX_CMD_SIZE, 295 num_continuations: 1, 296 })?; 297 check_split(SplitTest { 298 payload_size: MAX_FIRST_PAYLOAD + MAX_CMD_SIZE + 1, 299 num_continuations: 2, 300 })?; 301 check_split(SplitTest { 302 payload_size: MAX_FIRST_PAYLOAD + MAX_CMD_SIZE * 3 + MAX_CMD_SIZE / 2, 303 num_continuations: 4, 304 })?; 305 Ok(()) 306 } 307 } 308