The Linux Kernel Mailing List
 help / color / mirror / Atom feed
From: Zhi Wang <zhiw@nvidia.com>
To: John Hubbard <jhubbard@nvidia.com>
Cc: "Danilo Krummrich" <dakr@kernel.org>,
	"Alexandre Courbot" <acourbot@nvidia.com>,
	"Timur Tabi" <ttabi@nvidia.com>,
	"Alistair Popple" <apopple@nvidia.com>,
	"Eliot Courtney" <ecourtney@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>
Subject: Re: [PATCH 18/27] gpu: nova-core: separate the generic falcon bootloader from FWSEC
Date: Thu, 20 Aug 2026 16:02:54 +0300	[thread overview]
Message-ID: <20260820160254.3dbc9191@inno-dell> (raw)
In-Reply-To: <20260819035221.336390-19-jhubbard@nvidia.com>

On Tue, 18 Aug 2026 20:52:11 -0700
John Hubbard <jhubbard@nvidia.com> wrote:

Looking good to me.

Reviewed-by: Zhi Wang <zhiw@nvidia.com>

> The r000 GSP boot protocol sends a load-and-execute event for the
> generic falcon bootloader.
> 
> Nova-core stored the image and its load parameters in the FWSEC
> firmware wrapper, which kept the r000 boot path from loading it
> independently.
> 
> Separate the bootloader image and load parameters from the FWSEC
> wrapper. Place the bootloader at the end of the target falcon's
> HWCFG-reported IMEM rather than below a fixed 64 KiB ceiling, so its
> placement remains within bounds on falcons with less IMEM.
> 
> Assisted-by: Cursor:claude-opus-5
> Signed-off-by: John Hubbard <jhubbard@nvidia.com>
> ---
>  drivers/gpu/nova-core/falcon.rs               |  15 +++
>  drivers/gpu/nova-core/firmware.rs             |   1 +
>  .../nova-core/firmware/fwsec/bootloader.rs    |  64 ++---------
>  .../gpu/nova-core/firmware/gen_bootloader.rs  | 102
> ++++++++++++++++++ drivers/gpu/nova-core/gsp/hal/tu102.rs        |
> 6 +- drivers/gpu/nova-core/regs.rs                 |   5 +
>  6 files changed, 135 insertions(+), 58 deletions(-)
>  create mode 100644 drivers/gpu/nova-core/firmware/gen_bootloader.rs
> 
> diff --git a/drivers/gpu/nova-core/falcon.rs
> b/drivers/gpu/nova-core/falcon.rs index 20a288050c37..b8013d2eb582
> 100644 --- a/drivers/gpu/nova-core/falcon.rs
> +++ b/drivers/gpu/nova-core/falcon.rs
> @@ -387,6 +387,21 @@ pub(crate) fn new(
>          })
>      }
>  
> +    /// Returns the size of this falcon's IMEM, in bytes.
> +    ///
> +    /// `NV_PFALCON_FALCON_IMEMC` addresses IMEM with a 16-bit byte
> offset, so the result never
> +    /// exceeds 64KiB.
> +    pub(crate) fn imem_size(&self) -> usize {
> +        let blocks = usize::from_safe_cast(
> +            *self
> +                .bar
> +                .read(regs::NV_PFALCON_FALCON_HWCFG::of::<E>())
> +                .imem_size(),
> +        );
> +
> +        blocks * MEM_BLOCK_ALIGNMENT
> +    }
> +
>      /// Resets DMA-related registers.
>      pub(crate) fn dma_reset(&self) {
>          self.bar.update(regs::NV_PFALCON_FBIF_CTL::of::<E>(), |v| {
> diff --git a/drivers/gpu/nova-core/firmware.rs
> b/drivers/gpu/nova-core/firmware.rs index e0befe84aa3e..9ff764930bcd
> 100644 --- a/drivers/gpu/nova-core/firmware.rs
> +++ b/drivers/gpu/nova-core/firmware.rs
> @@ -26,6 +26,7 @@
>  pub(crate) mod booter;
>  pub(crate) mod fsp;
>  pub(crate) mod fwsec;
> +pub(crate) mod gen_bootloader;
>  pub(crate) mod gsp;
>  pub(crate) mod radix3;
>  pub(crate) mod riscv;
> diff --git a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
> b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs index
> ec4d92317a93..6670d17b4eeb 100644 ---
> a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs +++
> b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs @@ -14,18 +14,12
> @@ dma::Coherent,
>      io::{register::WithBase, Io},
>      prelude::*,
> -    ptr::{
> -        Alignable,
> -        Alignment, //
> -    },
> -    sizes,
>      transmute::AsBytes,
>  };
>  
>  use crate::{
>      driver::Bar0,
>      falcon::{
> -        self,
>          gsp::Gsp,
>          Falcon,
>          FalconBromParams,
> @@ -39,10 +33,7 @@
>      },
>      firmware::{
>          fwsec::FwsecFirmware,
> -        tlv::{
> -            request_tlv, //
> -            Tlv,
> -        },
> +        gen_bootloader::GenericBootloader, //
>      },
>      gpu::Chipset,
>      num::FromSafeCast, //
> @@ -101,16 +92,12 @@ unsafe impl AsBytes for BootloaderDmemDescV2 {}
>  pub(crate) struct FwsecFirmwareWithBl {
>      /// DMA object the bootloader will copy the firmware from.
>      _firmware_dma: Coherent<[u8]>,
> -    /// Code of the bootloader to be loaded into non-secure IMEM.
> -    ucode: KVec<u8>,
> +    /// Bootloader that performs the load.
> +    bootloader: GenericBootloader,
>      /// Descriptor to be loaded into DMEM for the bootloader to read.
>      dmem_desc: BootloaderDmemDescV2,
> -    /// Range-validated start offset of the firmware code in IMEM.
> -    imem_dst_start: u16,
>      /// BROM parameters of the loaded firmware.
>      brom_params: FalconBromParams,
> -    /// Range-validated `desc.start_tag`.
> -    start_tag: u16,
>  }
>  
>  impl FwsecFirmwareWithBl {
> @@ -120,29 +107,9 @@ pub(crate) fn new(
>          firmware: FwsecFirmware,
>          dev: &Device<device::Bound>,
>          chipset: Chipset,
> +        falcon: &Falcon<'_, Gsp>,
>      ) -> Result<Self> {
> -        let fw = request_tlv(dev, chipset, "gen_bootloader")?;
> -        let tlv = Tlv::new(fw.data())?;
> -        dev_dbg!(
> -            dev,
> -            "loaded generic bootloader firmware v{}\n",
> -            tlv.get_string(b"VERS")?
> -        );
> -
> -        let ucode = {
> -            let blob = tlv.get_bytes(b"BLOB")?;
> -            let code_size =
> usize::from_safe_cast(tlv.get_u32(b"CDSZ")?);
> -            let code = blob.get(..code_size).ok_or(EINVAL)?;
> -            let aligned_code_size = code_size
> -                .align_up(Alignment::new::<{
> falcon::MEM_BLOCK_ALIGNMENT }>())
> -                .ok_or(EINVAL)?;
> -
> -            let mut ucode = KVec::with_capacity(aligned_code_size,
> GFP_KERNEL)?;
> -            ucode.extend_from_slice(code, GFP_KERNEL)?;
> -            ucode.resize(aligned_code_size, 0, GFP_KERNEL)?;
> -
> -            ucode
> -        };
> +        let bootloader = GenericBootloader::new(dev, chipset,
> falcon)?; 
>          // `BootloaderDmemDescV2` expects the source to be a mirror
> image of the destination and // uses the same offset parameter for
> both. @@ -213,21 +180,11 @@ pub(crate) fn new(
>              }
>          };
>  
> -        // The bootloader's code must be loaded in the area right
> below the first 64K of IMEM.
> -        const BOOTLOADER_LOAD_CEILING: usize = sizes::SZ_64K;
> -        let imem_dst_start = BOOTLOADER_LOAD_CEILING
> -            .checked_sub(ucode.len())
> -            .ok_or(EOVERFLOW)?;
> -
> -        let start_tag = u16::try_from(tlv.get_u32(b"STRT")?)?;
> -
>          Ok(Self {
>              _firmware_dma: firmware_dma,
> -            ucode,
> +            bootloader,
>              dmem_desc,
>              brom_params: firmware.brom_params(),
> -            imem_dst_start: u16::try_from(imem_dst_start)?,
> -            start_tag,
>          })
>      }
>  
> @@ -282,7 +239,7 @@ fn brom_params(&self) -> FalconBromParams {
>      fn boot_addr(&self) -> u32 {
>          // On V2 platforms, the boot address is extracted from the
> generic bootloader, because the // gbl is what actually copies FWSEC
> into memory, so that is what needs to be booted.
> -        u32::from(self.start_tag) << 8
> +        self.bootloader.boot_addr()
>      }
>  }
>  
> @@ -292,12 +249,7 @@ fn imem_sec_load_params(&self) ->
> Option<FalconPioImemLoadTarget<'_>> { }
>  
>      fn imem_ns_load_params(&self) ->
> Option<FalconPioImemLoadTarget<'_>> {
> -        Some(FalconPioImemLoadTarget {
> -            data: self.ucode.as_ref(),
> -            dst_start: self.imem_dst_start,
> -            secure: false,
> -            start_tag: self.start_tag,
> -        })
> +        Some(self.bootloader.imem_load_params())
>      }
>  
>      fn dmem_load_params(&self) -> FalconPioDmemLoadTarget<'_> {
> diff --git a/drivers/gpu/nova-core/firmware/gen_bootloader.rs
> b/drivers/gpu/nova-core/firmware/gen_bootloader.rs new file mode
> 100644 index 000000000000..f949223af2d0
> --- /dev/null
> +++ b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
> @@ -0,0 +1,102 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +//! The generic falcon bootloader.
> +//!
> +//! A small program loaded into falcon IMEM using PIO, which then
> DMAs a larger image into IMEM and +//! DMEM from a descriptor the
> driver leaves in DMEM at offset 0. Open RM's +//!
> `ksec2GetGenericBlUcode` supplies the same image for both SEC2 and
> GSP. + +use kernel::{
> +    device,
> +    prelude::*,
> +    ptr::{
> +        Alignable,
> +        Alignment, //
> +    },
> +};
> +
> +use crate::{
> +    falcon::{
> +        self,
> +        Falcon,
> +        FalconEngine,
> +        FalconPioImemLoadTarget, //
> +    },
> +    firmware::tlv::{
> +        request_tlv, //
> +        Tlv,
> +    },
> +    gpu::Chipset,
> +    num::FromSafeCast, //
> +};
> +
> +/// The generic falcon bootloader image and the IMEM placement it
> was loaded for. +pub(crate) struct GenericBootloader {
> +    /// Bootloader code, zero-padded to a whole number of falcon
> memory blocks.
> +    ucode: KVec<u8>,
> +    /// Byte offset in IMEM the code is loaded at.
> +    imem_dst_start: u16,
> +    /// Tag the first code block is loaded under.
> +    start_tag: u16,
> +}
> +
> +impl GenericBootloader {
> +    /// Loads the generic bootloader image for `chipset`, placed in
> the last blocks of `falcon`'s
> +    /// IMEM so the image it goes on to load has the rest to itself.
> +    ///
> +    /// # Errors
> +    ///
> +    /// - `EINVAL` if a required TLV field is absent or the image
> does not fit in IMEM.
> +    /// - `ENOMEM` if the padded copy of the code cannot be
> allocated.
> +    pub(crate) fn new<E: FalconEngine + 'static>(
> +        dev: &device::Device<device::Bound>,
> +        chipset: Chipset,
> +        falcon: &Falcon<'_, E>,
> +    ) -> Result<Self> {
> +        let fw = request_tlv(dev, chipset, "gen_bootloader")?;
> +        let tlv = Tlv::new(fw.data())?;
> +        dev_dbg!(
> +            dev,
> +            "loaded generic bootloader firmware v{}\n",
> +            tlv.get_string(b"VERS")?
> +        );
> +
> +        let ucode = {
> +            let blob = tlv.get_bytes(b"BLOB")?;
> +            let code_size =
> usize::from_safe_cast(tlv.get_u32(b"CDSZ")?);
> +            let code = blob.get(..code_size).ok_or(EINVAL)?;
> +            let aligned_code_size = code_size
> +                .align_up(Alignment::new::<{
> falcon::MEM_BLOCK_ALIGNMENT }>())
> +                .ok_or(EINVAL)?;
> +
> +            let mut ucode = KVec::with_capacity(aligned_code_size,
> GFP_KERNEL)?;
> +            ucode.extend_from_slice(code, GFP_KERNEL)?;
> +            ucode.resize(aligned_code_size, 0, GFP_KERNEL)?;
> +
> +            ucode
> +        };
> +
> +        let imem_dst_start =
> falcon.imem_size().checked_sub(ucode.len()).ok_or(EINVAL)?; +
> +        Ok(Self {
> +            ucode,
> +            imem_dst_start: u16::try_from(imem_dst_start)?,
> +            start_tag: u16::try_from(tlv.get_u32(b"STRT")?)?,
> +        })
> +    }
> +
> +    /// Returns the address the falcon must boot from to run this
> bootloader.
> +    pub(crate) fn boot_addr(&self) -> u32 {
> +        u32::from(self.start_tag) << 8
> +    }
> +
> +    /// Returns the PIO parameters that place this bootloader in
> non-secure IMEM.
> +    pub(crate) fn imem_load_params(&self) ->
> FalconPioImemLoadTarget<'_> {
> +        FalconPioImemLoadTarget {
> +            data: self.ucode.as_ref(),
> +            dst_start: self.imem_dst_start,
> +            secure: false,
> +            start_tag: self.start_tag,
> +        }
> +    }
> +}
> diff --git a/drivers/gpu/nova-core/gsp/hal/tu102.rs
> b/drivers/gpu/nova-core/gsp/hal/tu102.rs index
> a5c0ca355493..68a48c882c0f 100644 ---
> a/drivers/gpu/nova-core/gsp/hal/tu102.rs +++
> b/drivers/gpu/nova-core/gsp/hal/tu102.rs @@ -166,7 +166,7 @@ fn
> run_fwsec_frts( )?;
>  
>          if self.needs_fwsec_bootloader {
> -            let fwsec_frts_bl = FwsecFirmwareWithBl::new(fwsec_frts,
> dev, chipset)?;
> +            let fwsec_frts_bl = FwsecFirmwareWithBl::new(fwsec_frts,
> dev, chipset, falcon)?; // Load and run the bootloader, which will
> load FWSEC-FRTS and run it. fwsec_frts_bl.run(dev, falcon, bar)?;
>          } else {
> @@ -224,7 +224,9 @@ fn build_unload_bundle(
>          // Load the FWSEC SB firmware, as well as its bootloader if
> required. let fwsec_sb = FwsecFirmware::new(dev, gsp_falcon, bios,
> FwsecCommand::Sb)?; let fwsec_sb = if self.needs_fwsec_bootloader {
> -
> FwsecUnloadFirmware::WithBl(FwsecFirmwareWithBl::new(fwsec_sb, dev,
> chipset)?)
> +            FwsecUnloadFirmware::WithBl(FwsecFirmwareWithBl::new(
> +                fwsec_sb, dev, chipset, gsp_falcon,
> +            )?)
>          } else {
>              FwsecUnloadFirmware::WithoutBl(fwsec_sb)
>          };
> diff --git a/drivers/gpu/nova-core/regs.rs
> b/drivers/gpu/nova-core/regs.rs index 5d265b5788e3..5501c36a56af
> 100644 --- a/drivers/gpu/nova-core/regs.rs
> +++ b/drivers/gpu/nova-core/regs.rs
> @@ -247,6 +247,11 @@ pub(crate) fn usable_fb_size(self) -> u64 {
>          31:0    value => u32;
>      }
>  
> +    pub(crate) NV_PFALCON_FALCON_HWCFG(u32) @ PFalconBase +
> 0x00000108 {
> +        /// Size of this falcon's IMEM, in blocks of
> [`crate::falcon::MEM_BLOCK_ALIGNMENT`] bytes.
> +        8:0     imem_size;
> +    }
> +
>      pub(crate) NV_PFALCON_FALCON_DMACTL(u32) @ PFalconBase +
> 0x0000010c { 7:7     secure_stat => bool;
>          6:3     dmaq_num;


  reply	other threads:[~2026-08-20 13:03 UTC|newest]

Thread overview: 64+ 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 17:37   ` Timur Tabi
2026-08-20  1:05     ` John Hubbard
2026-08-19  3:51 ` [PATCH 03/27] gpu: nova-core: set MCTP transport header version to 1 John Hubbard
2026-08-20  9:38   ` Zhi Wang
2026-08-19  3:51 ` [PATCH 04/27] gpu: nova-core: add Falcon helpers for r000 LOAD_EXEC events John Hubbard
2026-08-20  9:53   ` Zhi Wang
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 17:41   ` Timur Tabi
2026-08-20  2:18     ` 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-20 10:26   ` Zhi Wang
2026-08-19  3:52 ` [PATCH 07/27] gpu: nova-core: add optional ucodes firmware loading John Hubbard
2026-08-19 17:55   ` Timur Tabi
2026-08-20  2:22     ` John Hubbard
2026-08-19 18:41   ` Timur Tabi
2026-08-20  2:31     ` 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 18:12   ` Timur Tabi
2026-08-20  2:26     ` 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 18:20   ` Timur Tabi
2026-08-20  1:00     ` John Hubbard
2026-08-20  1:30       ` Timur Tabi
2026-08-20  2:03         ` 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 18:21   ` Timur Tabi
2026-08-20  1:01     ` John Hubbard
2026-08-19  3:52 ` [PATCH 11/27] gpu: nova-core: regs: add msgq v2 BAR0 register declarations John Hubbard
2026-08-20 10:39   ` Zhi Wang
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-20 12:47   ` Zhi Wang
2026-08-19  3:52 ` [PATCH 14/27] gpu: nova-core: add GMC API message types John Hubbard
2026-08-20 10:53   ` Zhi Wang
2026-08-19  3:52 ` [PATCH 15/27] gpu: nova-core: add GMC send path John Hubbard
2026-08-20 11:12   ` Zhi Wang
2026-08-19  3:52 ` [PATCH 16/27] gpu: nova-core: add GMC transport receive path John Hubbard
2026-08-20 12:00   ` Zhi Wang
2026-08-19  3:52 ` [PATCH 17/27] gpu: nova-core: gsp: add GMC dispatch on receive John Hubbard
2026-08-20 12:30   ` Zhi Wang
2026-08-19  3:52 ` [PATCH 18/27] gpu: nova-core: separate the generic falcon bootloader from FWSEC John Hubbard
2026-08-20 13:02   ` Zhi Wang [this message]
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-20 16:24   ` Zhi Wang
2026-08-19  3:52 ` [PATCH 21/27] gpu: nova-core: gsp: add the GMC boot event dispatcher John Hubbard
2026-08-20 17:28   ` Zhi Wang
2026-08-19  3:52 ` [PATCH 22/27] gpu: nova-core: gsp: add the GSP_INIT request builder John Hubbard
2026-08-20 18:16   ` Zhi Wang
2026-08-19  3:52 ` [PATCH 23/27] gpu: nova-core: gsp: send GSP_INIT and decode its reply John Hubbard
2026-08-20 18:36   ` Zhi Wang
2026-08-19  3:52 ` [PATCH 24/27] gpu: nova-core: gsp: pass the remaining log buffers to GSP-RM John Hubbard
2026-08-20 18:47   ` Zhi Wang
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-20 18:59   ` Zhi Wang
2026-08-19  3:52 ` [PATCH 27/27] gpu: nova-core: firmware: delete the r570 bindings John Hubbard
2026-08-20 19:00   ` Zhi Wang
     [not found] ` <20260819035221.336390-26-jhubbard@nvidia.com>
2026-08-19 18:58   ` [PATCH 25/27] gpu: nova-core: switch to the r000 GSP firmware Timur Tabi
2026-08-20  1:02     ` John Hubbard
2026-08-19 19:25 ` [PATCH 00/27] gpu: nova-core: boot on " Timur Tabi
2026-08-20 19:29   ` 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=20260820160254.3dbc9191@inno-dell \
    --to=zhiw@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=jhubbard@nvidia.com \
    --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 \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox