All of lore.kernel.org
 help / color / mirror / Atom feed
From: John Hubbard <jhubbard@nvidia.com>
To: Danilo Krummrich <dakr@kernel.org>,
	Alexandre Courbot <acourbot@nvidia.com>
Cc: "Timur Tabi" <ttabi@nvidia.com>,
	"Alistair Popple" <apopple@nvidia.com>,
	"Eliot Courtney" <ecourtney@nvidia.com>,
	"Zhi Wang" <zhiw@nvidia.com>, "David Airlie" <airlied@gmail.com>,
	"Simona Vetter" <simona@ffwll.ch>,
	"Bjorn Helgaas" <bhelgaas@google.com>,
	"Miguel Ojeda" <ojeda@kernel.org>,
	"Alex Gaynor" <alex.gaynor@gmail.com>,
	"Boqun Feng" <boqun.feng@gmail.com>,
	"Gary Guo" <gary@garyguo.net>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Benno Lossin" <lossin@kernel.org>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Trevor Gross" <tmgross@umich.edu>,
	nova-gpu@lists.linux.dev, LKML <linux-kernel@vger.kernel.org>,
	"John Hubbard" <jhubbard@nvidia.com>
Subject: [PATCH 23/27] gpu: nova-core: gsp: send GSP_INIT and decode its reply
Date: Tue, 18 Aug 2026 20:52:16 -0700	[thread overview]
Message-ID: <20260819035221.336390-24-jhubbard@nvidia.com> (raw)
In-Reply-To: <20260819035221.336390-1-jhubbard@nvidia.com>

GSP-RM answers a GSP_INIT request with the static GPU configuration, and
that reply is also what signals it has finished starting. It raises
load-and-execute events in the meantime, so a caller must handle them
rather than wait through them.

Nova-core can build the request but cannot send it: the GMC sender is
private, and the receive path drops the field carrying the reply status.

Add the sender and the receive-side status field it needs, and decode
the reply into the static-info type the RPC path already produces.

Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
 drivers/gpu/nova-core/gsp/cmdq.rs        |  49 ++++++--
 drivers/gpu/nova-core/gsp/commands.rs    | 140 ++++++++++++++++++++++-
 drivers/gpu/nova-core/gsp/fw.rs          |   4 +
 drivers/gpu/nova-core/gsp/fw/commands.rs |  35 +++++-
 4 files changed, 213 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 7da61bc9ad92..82ff46620911 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -790,22 +790,43 @@ fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
         self.inner.lock().receive_msg(timeout, None)
     }
 
-    /// Receives one GMC event from the GSP and passes its command id and raw payload slices to
-    /// `handler`.
+    /// Receives one GMC element from the GSP and passes its command id, the `max_resp_or_status`
+    /// field, and the raw payload slices to `handler`.
     ///
     /// This method may sleep while waiting. The [`CmdqInner`] mutex stays locked across the wait
     /// and across the `handler` call, so `handler` must not call back into this [`Cmdq`].
     ///
     /// See [`CmdqInner::receive_gmc_and_dispatch`] for return values, queue state, and errors.
-    #[expect(dead_code)]
     pub(crate) fn receive_gmc_and_dispatch<R>(
         &self,
         timeout: Delta,
-        handler: impl FnOnce(u32, &[u8], &[u8]) -> Option<R>,
+        handler: impl FnOnce(u32, u32, &[u8], &[u8]) -> Option<R>,
     ) -> Result<Option<R>> {
         self.inner.lock().receive_gmc_and_dispatch(timeout, handler)
     }
 
+    /// Sends a GMC API command to the GSP without waiting for its response.
+    ///
+    /// A caller that expects a response reads it with [`Self::receive_gmc_and_dispatch`], which
+    /// lets it handle the events GSP-RM interleaves before the response arrives.
+    ///
+    /// # Errors
+    ///
+    /// - `EMSGSIZE` if the command exceeds the maximum queue element size.
+    /// - `ETIMEDOUT` if space does not become available within the timeout.
+    /// - `EIO` if the command header is not properly aligned.
+    pub(crate) fn send_gmc_no_wait(
+        &self,
+        bar: Bar0<'_>,
+        command_id: u32,
+        payload: &[u8],
+        max_response_size: u32,
+    ) -> Result {
+        self.inner
+            .lock()
+            .send_gmc(bar, command_id, payload, max_response_size)
+    }
+
     /// Waits for an unsolicited GSP event of type `M`, dispatching any other event that arrives
     /// first.
     ///
@@ -1005,7 +1026,6 @@ fn send_command<M>(&mut self, bar: Bar0<'_>, command: M) -> Result<u32>
     /// - `EMSGSIZE` if the command exceeds the maximum queue element size.
     /// - `ETIMEDOUT` if space does not become available within the timeout.
     /// - `EIO` if the command header is not properly aligned.
-    #[expect(dead_code)]
     fn send_gmc(
         &mut self,
         bar: Bar0<'_>,
@@ -1383,9 +1403,13 @@ fn wait_for_gmc_msg(&self, timeout: Delta) -> Result<GmcMessage<'_>> {
 
     /// Receive the next GMC event from the GSP and dispatch it through a handler.
     ///
-    /// The handler receives the GMC command id and the raw payload slices that follow the
-    /// [`super::fw::GmcApiHeader`] (two slices because the circular buffer may wrap). It returns
-    /// `None` for an event it does not handle.
+    /// The handler receives the GMC command id, the header's `max_resp_or_status` field, and the
+    /// raw payload slices that follow the [`super::fw::GmcApiHeader`] (two slices because the
+    /// circular buffer may wrap). It returns `None` for an element it does not handle.
+    ///
+    /// `max_resp_or_status` is a union: GSP-RM writes an `NV_STATUS` there when the element is a
+    /// response, and the maximum response size when it is a request. Only a handler that knows
+    /// which one it asked for can read it.
     ///
     /// Where [`Self::receive_msg`] keys on [`MsgFunction`], this keys on the GMC command id,
     /// which is the form the r000 firmware uses for boot events.
@@ -1401,7 +1425,7 @@ fn wait_for_gmc_msg(&self, timeout: Delta) -> Result<GmcMessage<'_>> {
     fn receive_gmc_and_dispatch<R>(
         &mut self,
         timeout: Delta,
-        handler: impl FnOnce(u32, &[u8], &[u8]) -> Option<R>,
+        handler: impl FnOnce(u32, u32, &[u8], &[u8]) -> Option<R>,
     ) -> Result<Option<R>> {
         let message = self.wait_for_gmc_msg(timeout)?;
         let header = message.header;
@@ -1420,7 +1444,12 @@ fn receive_gmc_and_dispatch<R>(
                 length,
             );
 
-            handler(command_id, message.contents.0, message.contents.1)
+            handler(
+                command_id,
+                header.gmc.max_resp_or_status,
+                message.contents.0,
+                message.contents.1,
+            )
         } else {
             dev_warn!(&self.dev, "GSP GMC: dropping non-GMC queue element\n");
             None
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 0c3832ccf726..d55faf1a4e04 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -20,6 +20,7 @@
 };
 
 use crate::{
+    driver::Bar0,
     gpu::Chipset,
     gsp::{
         cmdq::{
@@ -32,13 +33,18 @@
             self,
             commands::{
                 GspInitRequest,
+                GspInitResponse,
+                GspInitResponseSchema,
                 RegKey, //
             },
-            MsgFunction, //
+            MsgFunction,
+            GMCAPI_CMD_GSP_INIT, //
         },
         nvkv::{
+            Decoder,
             Encodeable,
-            Encoder, //
+            Encoder,
+            UnknownKeyPolicy, //
         },
     },
     sbuffer::SBufferIter,
@@ -311,6 +317,136 @@ pub(crate) fn build_gsp_init_payload(
     Ok(encoder.finish())
 }
 
+/// Size of the buffer GSP-RM may fill with static configuration, matching the allocation Open RM
+/// makes in `kgspSendInitRpcs`.
+const GSP_INIT_MAX_RESPONSE_SIZE: u32 = 48 * 1024;
+
+/// Sends `GSP_INIT` and returns the static configuration its reply carries.
+///
+/// GSP-RM interleaves load-and-execute events between the request and the reply, and those events
+/// drive the falcon loads that let it finish starting, so each one is passed to `on_boot_event`
+/// rather than skipped. The reply arrives only once GSP-RM is up, which is what makes it the
+/// signal that boot is complete.
+///
+/// `payload` is the blob from [`build_gsp_init_payload`].
+///
+/// # Errors
+///
+/// - `EIO` if GSP-RM reports a failure status, or if the reply is not a whole number of NVKV
+///   words.
+/// - `ETIMEDOUT` if neither the reply nor another element arrives within
+///   [`Cmdq::RECEIVE_TIMEOUT`].
+///
+/// Errors from `on_boot_event` and from decoding the reply are propagated as-is.
+#[expect(dead_code)]
+pub(crate) fn gsp_init(
+    cmdq: &Cmdq,
+    bar: Bar0<'_>,
+    payload: &[u64],
+    mut on_boot_event: impl FnMut(u32, &[u8]) -> Result,
+) -> Result<GetGspStaticInfoReply> {
+    // Qualified because `zerocopy::IntoBytes` also gives `[T]` an `as_bytes`.
+    let payload = AsBytes::as_bytes(payload);
+
+    cmdq.send_gmc_no_wait(
+        bar,
+        GMCAPI_CMD_GSP_INIT,
+        payload,
+        GSP_INIT_MAX_RESPONSE_SIZE,
+    )?;
+
+    loop {
+        let reply = cmdq.receive_gmc_and_dispatch(
+            Cmdq::RECEIVE_TIMEOUT,
+            |command_id, max_resp_or_status, payload_0, payload_1| {
+                if command_id == GMCAPI_CMD_GSP_INIT {
+                    Some(decode_gsp_init_reply(
+                        max_resp_or_status,
+                        payload_0,
+                        payload_1,
+                    ))
+                } else {
+                    // A boot event. Keep waiting for the reply unless handling it failed.
+                    match on_boot_event(command_id, payload_0) {
+                        Ok(()) => None,
+                        Err(e) => Some(Err(e)),
+                    }
+                }
+            },
+        )?;
+
+        if let Some(reply) = reply {
+            return reply;
+        }
+    }
+}
+
+/// Decodes the `GSP_INIT` reply, whose `max_resp_or_status` field carries an `NV_STATUS`.
+fn decode_gsp_init_reply(
+    status: u32,
+    payload_0: &[u8],
+    payload_1: &[u8],
+) -> Result<GetGspStaticInfoReply> {
+    if status != 0 {
+        return Err(EIO);
+    }
+
+    decode_gsp_info(&nvkv_words(payload_0, payload_1)?)
+}
+
+/// Joins the two halves of a wrapped payload into the `u64` words an NVKV stream is made of.
+///
+/// # Errors
+///
+/// - `EIO` if the combined length is not a whole number of words.
+/// - `ENOMEM` if the buffer cannot be allocated.
+fn nvkv_words(payload_0: &[u8], payload_1: &[u8]) -> Result<KVVec<u64>> {
+    let bytes = SBufferIter::new_reader([payload_0, payload_1]).flush_into_kvec(GFP_KERNEL)?;
+    let words = bytes.chunks_exact(size_of::<u64>());
+    if !words.remainder().is_empty() {
+        return Err(EIO);
+    }
+
+    let mut out = KVVec::with_capacity(bytes.len() / size_of::<u64>(), GFP_KERNEL)?;
+    for word in words {
+        let word: [u8; size_of::<u64>()] = word.try_into().map_err(|_| EIO)?;
+        out.push(u64::from_le_bytes(word), GFP_KERNEL)?;
+    }
+
+    Ok(out)
+}
+
+/// Decodes the static GPU configuration from an NVKV stream.
+///
+/// # Errors
+///
+/// - `EINVAL` if the stream is malformed or omits a required key.
+/// - `ENOMEM` if the decoded regions cannot be allocated.
+fn decode_gsp_info(words: &[u64]) -> Result<GetGspStaticInfoReply> {
+    let decoder = Decoder::new(words, UnknownKeyPolicy::Ignore);
+    let decoded = KBox::try_init(
+        decoder.decode(GspInitResponseSchema::default())?,
+        GFP_KERNEL,
+    )?;
+
+    let mut gpu_name = [0u8; GspInitResponse::MAX_GPU_NAME_LEN];
+    let name = decoded.gpu_name();
+    gpu_name
+        .get_mut(..name.len())
+        .ok_or(EINVAL)?
+        .copy_from_slice(name);
+
+    let mut usable_fb_regions = KVec::new();
+    for region in decoded.usable_fb_regions() {
+        usable_fb_regions.push(region, GFP_KERNEL)?;
+    }
+
+    Ok(GetGspStaticInfoReply {
+        gpu_name,
+        usable_fb_regions,
+    })
+}
+
 pub(crate) use fw::commands::PowerStateLevel;
 
 /// The `UnloadingGuestDriver` command, used to shut down the GSP.
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 4772af362117..6958ee3a2e4e 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -979,6 +979,10 @@ pub(crate) struct GmcApiHeader {
 /// `GMCAPI_HEADER_COMMAND_ID_MASK`. The remaining byte carries flags.
 const GMCAPI_COMMAND_ID_MASK: u32 = 0x00ff_ffff;
 
+/// GMC command that hands GSP-RM its system information and registry keys and returns the static
+/// GPU configuration. Its reply is also what signals that GSP-RM has finished starting.
+pub(crate) const GMCAPI_CMD_GSP_INIT: u32 = r000_00::GMCAPI_COMMANDS_GMCAPI_CMD_GSP_INIT;
+
 /// GMC command asking the driver to run the generic falcon bootloader against a descriptor the
 /// GSP supplies.
 pub(crate) const GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER: u32 =
diff --git a/drivers/gpu/nova-core/gsp/fw/commands.rs b/drivers/gpu/nova-core/gsp/fw/commands.rs
index bfd756813c64..00e40a435053 100644
--- a/drivers/gpu/nova-core/gsp/fw/commands.rs
+++ b/drivers/gpu/nova-core/gsp/fw/commands.rs
@@ -402,7 +402,7 @@ pub(crate) fn new(
     /// Schema for the `GSP_INIT` response.
     #[cfg_attr(not(CONFIG_KUNIT), allow(dead_code))]
     #[derive(Default)]
-    struct GspInitResponseSchema => GspInitResponse {
+    pub(crate) struct GspInitResponseSchema => GspInitResponse {
         gpu_name:
             Array<u8, { GspInitResponse::MAX_GPU_NAME_LEN }, { Self::GPU_NAME_STRING_KEY }>,
         fb_regions: Accumulated<FbRegionSchema>,
@@ -420,7 +420,7 @@ impl GspInitResponseSchema {
 
 /// Payload of the `GSP_INIT` response.
 #[cfg_attr(not(CONFIG_KUNIT), allow(dead_code))]
-struct GspInitResponse {
+pub(crate) struct GspInitResponse {
     gpu_name: ArrayVec<u8, { Self::MAX_GPU_NAME_LEN }>,
     fb_regions: KVVec<FbRegion>,
     bar1_pde_base: u64,
@@ -428,7 +428,36 @@ struct GspInitResponse {
 }
 
 impl GspInitResponse {
-    const MAX_GPU_NAME_LEN: usize = 64;
+    pub(crate) const MAX_GPU_NAME_LEN: usize = 64;
+
+    /// A region with no tag is general-purpose memory. A tagged region is reserved for a
+    /// firmware-internal use that the tag identifies.
+    const FB_REGION_TAG_NONE: u32 = 0;
+
+    /// Returns the GPU name, which GSP-RM sends with its NULL terminator.
+    pub(crate) fn gpu_name(&self) -> &[u8] {
+        self.gpu_name.as_slice()
+    }
+
+    /// Iterates over the FB regions the driver may allocate from.
+    ///
+    /// A region qualifies when it is untagged, unprotected, and supports both compression and
+    /// isochronous access, which is the same set the RPC path selects from
+    /// [`GspStaticConfigInfo::usable_fb_regions`].
+    pub(crate) fn usable_fb_regions(&self) -> impl Iterator<Item = Range<u64>> + '_ {
+        self.fb_regions.iter().filter_map(|region| {
+            if region.limit >= region.base
+                && region.tag == Self::FB_REGION_TAG_NONE
+                && !region.flags.protected()
+                && region.flags.support_compressed()
+                && region.flags.support_iso()
+            {
+                region.limit.checked_add(1).map(|end| region.base..end)
+            } else {
+                None
+            }
+        })
+    }
 }
 
 nvkv_decode! {
-- 
2.55.0


  parent reply	other threads:[~2026-08-19  3:53 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-19  3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
2026-08-19  3:51 ` [PATCH 01/27] gpu: nova-core: firmware: add r000 bindings John Hubbard
2026-08-19  3:51 ` [PATCH 02/27] gpu: nova-core: extract radix3 page table into its own module John Hubbard
2026-08-19  3:51 ` [PATCH 03/27] gpu: nova-core: set MCTP transport header version to 1 John Hubbard
2026-08-19  3:51 ` [PATCH 04/27] gpu: nova-core: add Falcon helpers for r000 LOAD_EXEC events John Hubbard
2026-08-19  3:51 ` [PATCH 05/27] gpu: nova-core: zero-pad radix3 page table levels to page boundary John Hubbard
2026-08-19  3:51 ` [PATCH 06/27] gpu: nova-core: distinguish async GSP RPC traffic in debug logs John Hubbard
2026-08-19  3:52 ` [PATCH 07/27] gpu: nova-core: add optional ucodes firmware loading John Hubbard
2026-08-19  3:52 ` [PATCH 08/27] gpu: nova-core: add LIBOS3 log buffers and state monitor buffer John Hubbard
2026-08-19  3:52 ` [PATCH 09/27] gpu: nova-core: add build ID headers to debugfs log buffer dumps John Hubbard
2026-08-19  3:52 ` [PATCH 10/27] gpu: nova-core: rename the FbRanges elf field to fw_image John Hubbard
2026-08-19  3:52 ` [PATCH 11/27] gpu: nova-core: regs: add msgq v2 BAR0 register declarations John Hubbard
2026-08-19  3:52 ` [PATCH 12/27] gpu: nova-core: gsp: add msgq v2 internals John Hubbard
2026-08-19  3:52 ` [PATCH 13/27] gpu: nova-core: generalize allocate_command() for variable headers John Hubbard
2026-08-19  3:52 ` [PATCH 14/27] gpu: nova-core: add GMC API message types John Hubbard
2026-08-19  3:52 ` [PATCH 15/27] gpu: nova-core: add GMC send path John Hubbard
2026-08-19  3:52 ` [PATCH 16/27] gpu: nova-core: add GMC transport receive path John Hubbard
2026-08-19  3:52 ` [PATCH 17/27] gpu: nova-core: gsp: add GMC dispatch on receive John Hubbard
2026-08-19  3:52 ` [PATCH 18/27] gpu: nova-core: separate the generic falcon bootloader from FWSEC John Hubbard
2026-08-19  3:52 ` [PATCH 19/27] gpu: nova-core: handle the r000 load-and-execute HS binary event John Hubbard
2026-08-19  3:52 ` [PATCH 20/27] gpu: nova-core: handle the r000 load-and-execute bootloader event John Hubbard
2026-08-19  3:52 ` [PATCH 21/27] gpu: nova-core: gsp: add the GMC boot event dispatcher John Hubbard
2026-08-19  3:52 ` [PATCH 22/27] gpu: nova-core: gsp: add the GSP_INIT request builder John Hubbard
2026-08-19  3:52 ` John Hubbard [this message]
2026-08-19  3:52 ` [PATCH 24/27] gpu: nova-core: gsp: pass the remaining log buffers to GSP-RM John Hubbard
2026-08-19  3:52 ` [PATCH 25/27] gpu: nova-core: switch to the r000 GSP firmware John Hubbard
2026-08-19  3:52 ` [PATCH 26/27] gpu: nova-core: gsp: remove the retired system-info and static-info RPCs John Hubbard
2026-08-19  3:52 ` [PATCH 27/27] gpu: nova-core: firmware: delete the r570 bindings John Hubbard

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260819035221.336390-24-jhubbard@nvidia.com \
    --to=jhubbard@nvidia.com \
    --cc=a.hindborg@kernel.org \
    --cc=acourbot@nvidia.com \
    --cc=airlied@gmail.com \
    --cc=alex.gaynor@gmail.com \
    --cc=aliceryhl@google.com \
    --cc=apopple@nvidia.com \
    --cc=bhelgaas@google.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun.feng@gmail.com \
    --cc=dakr@kernel.org \
    --cc=ecourtney@nvidia.com \
    --cc=gary@garyguo.net \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lossin@kernel.org \
    --cc=nova-gpu@lists.linux.dev \
    --cc=ojeda@kernel.org \
    --cc=simona@ffwll.ch \
    --cc=tmgross@umich.edu \
    --cc=ttabi@nvidia.com \
    --cc=zhiw@nvidia.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.