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