xref: /linux/drivers/gpu/nova-core/gsp/cmdq/continuation.rs (revision 5ea5880764cbb164afb17a62e76ca75dc371409d)
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