Introduce the `CommandElement` trait and `RpcCommandElement` wrapper type to define how messages (independently of their type) are sent through the transport layer. `send_single_command` becomes `send_command_element`, which allocates the queue slots, writes the element header, and delegates the writing of the message itself to the implementation of `CommandElement` before computing the checksum, advancing the write pointer and ringing the doorbell.
The RPC part of `send_single_command` (writing the RPC header and the command payload) is now part of the `CommandElement` implementation. This sets things up for moving the RPC code into its own sub-module, leaving the transport agnostic of the message type. No functional change intended. Suggested-by: Eliot Courtney <[email protected]> Signed-off-by: Alexandre Courbot <[email protected]> --- drivers/gpu/nova-core/gsp/cmdq.rs | 133 +++++++++++++++++++++++--------------- 1 file changed, 80 insertions(+), 53 deletions(-) diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs index bda79f23626d..d8a7716fc500 100644 --- a/drivers/gpu/nova-core/gsp/cmdq.rs +++ b/drivers/gpu/nova-core/gsp/cmdq.rs @@ -35,10 +35,7 @@ }, }; -use continuation::{ - ContinuationRecord, - SplitState, // -}; +use continuation::SplitState; use pin_init::pin_init_scope; @@ -67,6 +64,19 @@ /// reply type are sent using [`Cmdq::send_command_no_wait`]. pub(crate) struct NoReply; +/// Trait implemented by types that can be sent as a single command queue element. +/// +/// The command queue allocates `size()` bytes after the `GspMsgElement` header and calls `write()` +/// to fill them. +trait CommandElement { + /// Size in bytes of the element, not including the `GspMsgElement` header. + fn size(&self) -> usize; + + /// Writes the contents of the command into `dst`. `dev` is the queue's device (to be used for + /// logging), `seq` is the sequence number of the element. + fn write(&self, dev: &device::Device, seq: u32, dst: &mut GspCommand<'_>) -> Result; +} + /// Trait implemented by types representing a command to send to the GSP. /// /// The main purpose of this trait is to provide [`Cmdq`] with the information it needs to send @@ -129,6 +139,62 @@ fn size(&self) -> usize { } } +/// Wrapper type for sending a RPC command as a command queue element. +/// +/// [`CommandElement`] cannot be directly implemented for all [`CommandToGsp`] with a blanket +/// implementation as it would conflict with other future command types. +struct RpcCommandElement<M>(M); + +impl<M> CommandElement for RpcCommandElement<M> +where + M: CommandToGsp, + Error: From<M::InitError>, +{ + fn size(&self) -> usize { + self.0.size() + } + + fn write(&self, dev: &device::Device, seq: u32, dst: &mut GspCommand<'_>) -> Result { + let command = &self.0; + let size_in_bytes = command.size(); + // Extract area for the command itself. The GSP message header and the command header + // together are guaranteed to fit entirely into a single page, so it's ok to only look + // at `dst.contents.0` here. + let (cmd, payload_1) = M::Command::from_bytes_mut_prefix(dst.contents.0).ok_or(EIO)?; + let rpc_header_init = RpcMessageHeader::init(size_in_bytes, M::FUNCTION); + // SAFETY: `dst.header.rpc_header_mut()` is a valid reference, and is not touched if the + // initializer fails. + unsafe { + pin_init::raw_try_init( + core::ptr::from_mut(dst.header.rpc_header_mut()), + rpc_header_init, + )?; + } + // SAFETY: `cmd` is a valid reference, and is not touched if the initializer fails. + unsafe { + pin_init::raw_try_init(core::ptr::from_mut(cmd), command.init())?; + } + + // Fill the variable-length payload, which may be empty. + let mut sbuffer = SBufferIter::new_writer([&mut payload_1[..], &mut dst.contents.1[..]]); + command.init_variable_payload(&mut sbuffer)?; + + if !sbuffer.is_empty() { + return Err(EIO); + } + + dev_dbg!( + dev, + "GSP RPC: send: seq# {}, function={:?}, length=0x{:x}\n", + seq, + M::FUNCTION, + size_in_bytes, + ); + + Ok(()) + } +} + /// Trait representing messages received from the GSP. /// /// This trait tells [`Cmdq::receive_msg`] how it can receive a given type of message. @@ -638,24 +704,18 @@ impl CmdqInner<'_> { /// Timeout for waiting for space on the command queue. const ALLOCATE_TIMEOUT: Delta = Delta::from_secs(1); - /// Sends `command` to the GSP, without splitting it. + /// Allocate enough send slots to store `command`, initialize them using + /// [`CommandElement::write`], and send the command to the GSP. /// /// # Errors /// /// - `EMSGSIZE` if the command exceeds the maximum queue element size. /// - `ETIMEDOUT` if space does not become available within the timeout. - /// - `EIO` if the variable payload requested by the command has not been entirely - /// written to by its [`CommandToGsp::init_variable_payload`] method. /// - /// Error codes returned by the command initializers are propagated as-is. - fn send_single_command<M>(&mut self, command: M) -> Result - where - M: CommandToGsp, - // This allows all error types, including `Infallible`, to be used for `M::InitError`. - Error: From<M::InitError>, - { + /// Error codes returned by [`CommandElement::write`] are returned as-is. + fn send_command_element<E: CommandElement>(&mut self, command: E) -> Result { let size_in_bytes = command.size(); - let dst = self + let mut dst = self .gsp_mem .allocate_command(size_in_bytes, Self::ALLOCATE_TIMEOUT)?; @@ -666,40 +726,8 @@ fn send_single_command<M>(&mut self, command: M) -> Result pin_init::raw_try_init(core::ptr::from_mut(dst.header), msg_element_init)?; } - // Extract area for the command itself. The GSP message header and the command header - // together are guaranteed to fit entirely into a single page, so it's ok to only look - // at `dst.contents.0` here. - let (cmd, payload_1) = M::Command::from_bytes_mut_prefix(dst.contents.0).ok_or(EIO)?; - let rpc_header_init = RpcMessageHeader::init(size_in_bytes, M::FUNCTION); - // SAFETY: `dst.header.rpc_header_mut()` is a valid reference, and is not touched if the - // initializer fails. - unsafe { - pin_init::raw_try_init( - core::ptr::from_mut(dst.header.rpc_header_mut()), - rpc_header_init, - )?; - } - // SAFETY: `cmd` is a valid reference, and is not touched if the initializer fails. - unsafe { - pin_init::raw_try_init(core::ptr::from_mut(cmd), command.init())?; - } - - // Fill the variable-length payload, which may be empty. - let mut sbuffer = SBufferIter::new_writer([&mut payload_1[..], &mut dst.contents.1[..]]); - command.init_variable_payload(&mut sbuffer)?; - - if !sbuffer.is_empty() { - return Err(EIO); - } - drop(sbuffer); - - dev_dbg!( - &self.dev, - "GSP RPC: send: seq# {}, function={:?}, length=0x{:x}\n", - self.seq, - M::FUNCTION, - size_in_bytes, - ); + // Write the message payload. + command.write(self.dev, self.seq, &mut dst)?; // Compute checksum now that the whole message is ready. dst.header @@ -735,13 +763,12 @@ fn send_command<M>(&mut self, command: M) -> Result Error: From<M::InitError>, { match SplitState::new(command)? { - SplitState::Single(command) => self.send_single_command(command), + SplitState::Single(command) => self.send_command_element(RpcCommandElement(command)), SplitState::Split(command, mut continuations) => { - self.send_single_command(command)?; + self.send_command_element(RpcCommandElement(command))?; while let Some(continuation) = continuations.next() { - // Turbofish needed because the compiler cannot infer M here. - self.send_single_command::<ContinuationRecord<'_>>(continuation)?; + self.send_command_element(RpcCommandElement(continuation))?; } Ok(()) -- 2.55.0
