public inbox for rust-for-linux@vger.kernel.org
 help / color / mirror / Atom feed
From: "Onur Özkan" <work@onurozkan.dev>
To: Deborah Brouwer <deborah.brouwer@collabora.com>
Cc: "Daniel Almeida" <daniel.almeida@collabora.com>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Danilo Krummrich" <dakr@kernel.org>,
	"David Airlie" <airlied@gmail.com>,
	"Simona Vetter" <simona@ffwll.ch>,
	"Benno Lossin" <lossin@kernel.org>, "Gary Guo" <gary@garyguo.net>,
	"Miguel Ojeda" <ojeda@kernel.org>,
	"Boqun Feng" <boqun@kernel.org>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Trevor Gross" <tmgross@umich.edu>,
	"FUJITA Tomonori" <fujita.tomonori@gmail.com>,
	"Frederic Weisbecker" <frederic@kernel.org>,
	"Thomas Gleixner" <tglx@kernel.org>,
	"Anna-Maria Behnsen" <anna-maria@linutronix.de>,
	"John Stultz" <jstultz@google.com>,
	"Stephen Boyd" <sboyd@kernel.org>,
	dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org,
	rust-for-linux@vger.kernel.org, boris.brezillon@collabora.com,
	beata.michalska@arm.com, lyude@redhat.com, acourbot@nvidia.com,
	alvin.sun@linux.dev
Subject: Re: [PATCH v4 12/20] drm/tyr: add parser for firmware binary
Date: Mon, 27 Apr 2026 11:09:25 +0300	[thread overview]
Message-ID: <20260427080926.73950-1-work@onurozkan.dev> (raw)
In-Reply-To: <20260424-b4-fw-boot-v4-v4-12-a5d91050789d@collabora.com>

On Fri, 24 Apr 2026 16:39:06 -0700
Deborah Brouwer <deborah.brouwer@collabora.com> wrote:

> From: Daniel Almeida <daniel.almeida@collabora.com>
> 
> Add a parser for the Mali CSF GPU firmware binary format. The firmware
> consists of a header followed by entries describing how to load firmware
> sections into the MCU's memory.
> 
> The parser extracts section metadata including virtual address ranges,
> data byte offsets within the binary, and section flags controlling
> permissions and cache modes. It validates the basic firmware structure
> and alignment and ignores protected-mode sections for now.
> 
> Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
> Co-developed-by: Beata Michalska <beata.michalska@arm.com>
> Signed-off-by: Beata Michalska <beata.michalska@arm.com>
> Co-developed-by: Boris Brezillon <boris.brezillon@collabora.com>
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> Co-developed-by: Deborah Brouwer <deborah.brouwer@collabora.com>
> Signed-off-by: Deborah Brouwer <deborah.brouwer@collabora.com>
> ---
>  drivers/gpu/drm/tyr/fw/parser.rs | 519 +++++++++++++++++++++++++++++++++++++++
>  1 file changed, 519 insertions(+)
> 
> diff --git a/drivers/gpu/drm/tyr/fw/parser.rs b/drivers/gpu/drm/tyr/fw/parser.rs
> new file mode 100644
> index 000000000000..638707430701
> --- /dev/null
> +++ b/drivers/gpu/drm/tyr/fw/parser.rs
> @@ -0,0 +1,519 @@
> +// SPDX-License-Identifier: GPL-2.0 or MIT
> +
> +//! Firmware binary parser for Mali CSF (Command Stream Frontend) GPU.
> +//!
> +//! This module implements a parser for the Mali GPU firmware binary format. The firmware
> +//! file contains a header followed by a sequence of entries, each describing how to load
> +//! firmware sections into the MCU (Microcontroller Unit) memory. The parser extracts section metadata including:
> +//! - Virtual address ranges where sections should be mapped
> +//! - Data ranges (byte offsets) within the firmware binary
> +//! - Section flags (permissions, cache modes)
> +
> +use core::{
> +    mem::size_of,
> +    ops::Range, //
> +};
> +
> +use kernel::{
> +    bits::bit_u32,
> +    prelude::*,
> +    str::CString, //
> +};
> +
> +use crate::{
> +    fw::{
> +        SectionFlag,
> +        SectionFlags,
> +        CSF_MCU_SHARED_REGION_START, //
> +    },
> +    vm::{
> +        VmFlag,
> +        VmMapFlags, //
> +    }, //
> +};
> +
> +/// A parsed firmware section ready for loading into MCU memory.
> +///
> +/// Represents a single firmware section extracted from the firmware binary, containing
> +/// all information needed to map the section's data into the MCU's virtual address space.
> +pub(super) struct ParsedSection {
> +    /// Byte offset range within the firmware binary where this section's data resides.
> +    pub(super) data_range: Range<u32>,
> +    /// MCU virtual address range where this section should be mapped.
> +    pub(super) va: Range<u32>,
> +    /// Memory protection and caching flags for the mapping.
> +    pub(super) vm_map_flags: VmMapFlags,
> +}
> +
> +/// A bare-bones `std::io::Cursor<[u8]>` clone to keep track of the current position in the firmware binary.
> +///
> +/// Provides methods to sequentially read primitive types and byte arrays from the firmware
> +/// binary while maintaining the current read position.
> +struct Cursor<'a> {
> +    data: &'a [u8],
> +    pos: usize,
> +}
> +
> +impl<'a> Cursor<'a> {
> +    fn new(data: &'a [u8]) -> Self {
> +        Self { data, pos: 0 }
> +    }
> +
> +    fn len(&self) -> usize {
> +        self.data.len()
> +    }
> +
> +    fn pos(&self) -> usize {
> +        self.pos
> +    }
> +
> +    /// Returns a view into the cursor's data.
> +    ///
> +    /// This spawns a new cursor, leaving the current cursor unchanged.
> +    fn view(&self, range: Range<usize>) -> Result<Cursor<'_>> {
> +        if range.start < self.pos || range.end > self.data.len() {
> +            pr_err!(
> +                "Invalid cursor range {:?} for data of length {}",
> +                range,
> +                self.data.len()
> +            );
> +
> +            Err(EINVAL)
> +        } else {
> +            Ok(Self {
> +                data: &self.data[range],
> +                pos: 0,
> +            })
> +        }
> +    }
> +
> +    /// Reads a slice of bytes from the current position and advances the cursor.
> +    ///
> +    /// Returns an error if the read would exceed the data bounds.
> +    fn read(&mut self, nbytes: usize) -> Result<&[u8]> {
> +        let start = self.pos;
> +        let end = start + nbytes;
> +
> +        if end > self.data.len() {
> +            pr_err!(
> +                "Invalid firmware file: read of size {} at position {} is out of bounds",
> +                nbytes,
> +                start,
> +            );
> +            return Err(EINVAL);
> +        }
> +
> +        self.pos += nbytes;
> +        Ok(&self.data[start..end])
> +    }
> +
> +    /// Reads a little-endian `u8` from the current position and advances the cursor.
> +    fn read_u8(&mut self) -> Result<u8> {
> +        let bytes = self.read(size_of::<u8>())?;
> +        Ok(bytes[0])
> +    }
> +
> +    /// Reads a little-endian `u16` from the current position and advances the cursor.
> +    fn read_u16(&mut self) -> Result<u16> {
> +        let bytes = self.read(size_of::<u16>())?;
> +        Ok(u16::from_le_bytes(bytes.try_into().unwrap()))
> +    }
> +
> +    /// Reads a little-endian `u32` from the current position and advances the cursor.
> +    fn read_u32(&mut self) -> Result<u32> {
> +        let bytes = self.read(size_of::<u32>())?;
> +        Ok(u32::from_le_bytes(bytes.try_into().unwrap()))
> +    }
> +
> +    /// Advances the cursor position by the specified number of bytes.
> +    ///
> +    /// Returns an error if the advance would exceed the data bounds.
> +    fn advance(&mut self, nbytes: usize) -> Result {
> +        if self.pos + nbytes > self.data.len() {
> +            pr_err!(
> +                "Invalid firmware file: advance of size {} at position {} is out of bounds",
> +                nbytes,
> +                self.pos,
> +            );
> +            return Err(EINVAL);
> +        }
> +        self.pos += nbytes;
> +        Ok(())
> +    }
> +}
> +
> +/// Parser for Mali CSF GPU firmware binaries.
> +///
> +/// Parses the firmware binary format, extracting section metadata including virtual
> +/// address ranges, data offsets, and memory protection flags needed to load firmware
> +/// into the MCU's memory.
> +pub(super) struct FwParser<'a> {
> +    cursor: Cursor<'a>,
> +}
> +
> +impl<'a> FwParser<'a> {
> +    /// Creates a new firmware parser for the given firmware binary data.
> +    pub(super) fn new(data: &'a [u8]) -> Self {
> +        Self {
> +            cursor: Cursor::new(data),
> +        }
> +    }
> +
> +    /// Parses the firmware binary and returns a collection of parsed sections.
> +    ///
> +    /// This method validates the firmware header and iterates through all entries
> +    /// in the binary, extracting section information needed for loading.
> +    pub(super) fn parse(&mut self) -> Result<KVec<ParsedSection>> {
> +        let fw_header = self.parse_fw_header()?;
> +
> +        let mut parsed_sections = KVec::new();
> +        while (self.cursor.pos() as u32) < fw_header.size {
> +            let entry_section = self.parse_entry()?;
> +
> +            if let Some(inner) = entry_section.inner {
> +                parsed_sections.push(inner, GFP_KERNEL)?;
> +            }
> +        }
> +
> +        Ok(parsed_sections)
> +    }
> +
> +    fn parse_fw_header(&mut self) -> Result<FirmwareHeader> {
> +        let fw_header: FirmwareHeader = match FirmwareHeader::new(&mut self.cursor) {
> +            Ok(fw_header) => fw_header,
> +            Err(e) => {
> +                pr_err!("Invalid firmware file: {}", e.to_errno());
> +                return Err(e);
> +            }
> +        };
> +
> +        if fw_header.size > self.cursor.len() as u32 {
> +            pr_err!("Firmware image is truncated");
> +            return Err(EINVAL);
> +        }
> +        Ok(fw_header)
> +    }
> +
> +    fn parse_entry(&mut self) -> Result<EntrySection> {
> +        let entry_section = EntrySection {
> +            entry_hdr: EntryHeader(self.cursor.read_u32()?),
> +            inner: None,
> +        };
> +
> +        if self.cursor.pos() % size_of::<u32>() != 0
> +            || entry_section.entry_hdr.size() as usize % size_of::<u32>() != 0
> +        {
> +            pr_err!(
> +                "Firmware entry isn't 32 bit aligned, offset={:#x} size={:#x}\n",
> +                self.cursor.pos() - size_of::<u32>(),
> +                entry_section.entry_hdr.size()
> +            );
> +            return Err(EINVAL);
> +        }
> +
> +        let section_hdr_size = entry_section.entry_hdr.size() as usize - size_of::<EntryHeader>();
> +
> +        let entry_section = {
> +            let mut entry_cursor = self
> +                .cursor
> +                .view(self.cursor.pos()..self.cursor.pos() + section_hdr_size)?;
> +
> +            match entry_section.entry_hdr.entry_type() {
> +                Ok(EntryType::Iface) => Ok(EntrySection {
> +                    entry_hdr: entry_section.entry_hdr,
> +                    inner: Self::parse_section_entry(&mut entry_cursor)?,
> +                }),
> +                Ok(
> +                    EntryType::Config
> +                    | EntryType::FutfTest
> +                    | EntryType::TraceBuffer
> +                    | EntryType::TimelineMetadata
> +                    | EntryType::BuildInfoMetadata,
> +                ) => Ok(entry_section),
> +
> +                entry_type => {
> +                    if entry_type.is_err() || !entry_section.entry_hdr.optional() {
> +                        if !entry_section.entry_hdr.optional() {
> +                            pr_err!(
> +                                "Failed to handle firmware entry type: {}\n",
> +                                entry_type
> +                                    .map_or(entry_section.entry_hdr.entry_type_raw(), |e| e as u8)
> +                            );
> +                            Err(EINVAL)
> +                        } else {
> +                            Ok(entry_section)
> +                        }
> +                    } else {
> +                        Ok(entry_section)
> +                    }
> +                }
> +            }
> +        };
> +
> +        if entry_section.is_ok() {
> +            self.cursor.advance(section_hdr_size)?;
> +        }
> +
> +        entry_section
> +    }
> +
> +    fn parse_section_entry(entry_cursor: &mut Cursor<'_>) -> Result<Option<ParsedSection>> {
> +        let section_hdr: SectionHeader = SectionHeader::new(entry_cursor)?;
> +
> +        if section_hdr.data.end < section_hdr.data.start {
> +            pr_err!(
> +                "Firmware corrupted, data.end < data.start (0x{:x} < 0x{:x})\n",
> +                section_hdr.data.end,
> +                section_hdr.data.start
> +            );
> +            return Err(EINVAL);
> +        }
> +
> +        if section_hdr.va.end < section_hdr.va.start {
> +            pr_err!(
> +                "Firmware corrupted, section_hdr.va.end < section_hdr.va.start (0x{:x} < 0x{:x})\n",
> +                section_hdr.va.end,
> +                section_hdr.va.start
> +            );
> +            return Err(EINVAL);
> +        }
> +
> +        if section_hdr.section_flags.contains(SectionFlag::Prot) {
> +            pr_info!("Firmware protected mode entry not supported, ignoring");
> +            return Ok(None);
> +        }
> +
> +        if section_hdr.va.start == CSF_MCU_SHARED_REGION_START
> +            && !section_hdr.section_flags.contains(SectionFlag::Shared)
> +        {
> +            pr_err!(
> +                "Interface at 0x{:x} must be shared",
> +                CSF_MCU_SHARED_REGION_START
> +            );
> +            return Err(EINVAL);
> +        }
> +
> +        let name_len = entry_cursor.len() - entry_cursor.pos();
> +        let name_bytes = entry_cursor.read(name_len)?;
> +
> +        let mut name = KVec::with_capacity(name_bytes.len() + 1, GFP_KERNEL)?;
> +        name.extend_from_slice(name_bytes, GFP_KERNEL)?;
> +        name.push(0, GFP_KERNEL)?;
> +
> +        let _name = CStr::from_bytes_with_nul(&name)
> +            .ok()
> +            .and_then(|name| CString::try_from(name).ok());
> +
> +        let cache_mode = section_hdr.section_flags.cache_mode();
> +        let mut vm_map_flags = VmMapFlags::empty();
> +
> +        if !section_hdr.section_flags.contains(SectionFlag::Write) {
> +            vm_map_flags |= VmFlag::Readonly;
> +        }
> +        if !section_hdr.section_flags.contains(SectionFlag::Exec) {
> +            vm_map_flags |= VmFlag::Noexec;
> +        }
> +        if cache_mode != SectionFlag::CacheModeCached.into() {
> +            vm_map_flags |= VmFlag::Uncached;
> +        }
> +
> +        Ok(Some(ParsedSection {
> +            data_range: section_hdr.data.clone(),
> +            va: section_hdr.va,
> +            vm_map_flags,
> +        }))
> +    }
> +}
> +
> +/// Firmware binary header containing version and size information.
> +///
> +/// The header is located at the beginning of the firmware binary and contains
> +/// a magic value for validation, version information, and the total size of
> +/// all structured headers that follow.
> +#[expect(dead_code)]
> +struct FirmwareHeader {
> +    /// Magic value to check binary validity.
> +    magic: u32,
> +
> +    /// Minor firmware version.
> +    minor: u8,
> +
> +    /// Major firmware version.
> +    major: u8,
> +
> +    /// Padding. Must be set to zero.
> +    _padding1: u16,
> +
> +    /// Firmware version hash.
> +    version_hash: u32,
> +
> +    /// Padding. Must be set to zero.
> +    _padding2: u32,
> +
> +    /// Total size of all the structured data headers at beginning of firmware binary.
> +    size: u32,
> +}
> +
> +impl FirmwareHeader {
> +    const FW_BINARY_MAGIC: u32 = 0xc3f13a6e;
> +    const FW_BINARY_MAJOR_MAX: u8 = 0;
> +
> +    /// Reads and validates a firmware header from the cursor.
> +    ///
> +    /// Verifies the magic value, version compatibility, and padding fields.
> +    fn new(cursor: &mut Cursor<'_>) -> Result<Self> {
> +        let magic = cursor.read_u32()?;
> +        if magic != Self::FW_BINARY_MAGIC {
> +            pr_err!("Invalid firmware magic");
> +            return Err(EINVAL);
> +        }
> +
> +        let minor = cursor.read_u8()?;
> +        let major = cursor.read_u8()?;
> +
> +        if major > Self::FW_BINARY_MAJOR_MAX {
> +            pr_err!(
> +                "Unsupported firmware binary header version {}.{} (expected {}.x)\n",
> +                major,
> +                minor,
> +                Self::FW_BINARY_MAJOR_MAX
> +            );
> +            return Err(EINVAL);
> +        }
> +
> +        let padding1 = cursor.read_u16()?;
> +        let version_hash = cursor.read_u32()?;
> +        let padding2 = cursor.read_u32()?;
> +        let size = cursor.read_u32()?;
> +
> +        if padding1 != 0 || padding2 != 0 {
> +            pr_err!("Invalid firmware file: header padding is not zero");
> +            return Err(EINVAL);
> +        }
> +
> +        let fw_header = Self {
> +            magic,
> +            minor,
> +            major,
> +            _padding1: padding1,
> +            version_hash,
> +            _padding2: padding2,

nit: I would write it like this:

	_padding1: 0,
	version_hash,
	_padding2: 0,

to be more explicit.

> +            size,
> +        };
> +
> +        Ok(fw_header)
> +    }
> +}
> +
> +/// Firmware section header for loading binary sections into MCU memory.
> +#[derive(Debug)]
> +struct SectionHeader {
> +    section_flags: SectionFlags,
> +    /// MCU virtual range to map this binary section to.
> +    va: Range<u32>,
> +    /// References the data in the FW binary.
> +    data: Range<u32>,
> +}
> +
> +impl SectionHeader {
> +    /// Reads and validates a section header from the cursor.
> +    ///
> +    /// Parses section flags, virtual address range, and data range from the firmware binary.
> +    fn new(cursor: &mut Cursor<'_>) -> Result<Self> {
> +        let section_flags = cursor.read_u32()?;
> +        let section_flags = SectionFlags::try_from(section_flags)?;
> +
> +        let va_start = cursor.read_u32()?;
> +        let va_end = cursor.read_u32()?;
> +
> +        let va = va_start..va_end;
> +
> +        if va.is_empty() {
> +            pr_err!(
> +                "Invalid firmware file: empty VA range at pos {}\n",
> +                cursor.pos(),
> +            );
> +            return Err(EINVAL);
> +        }
> +
> +        let data_start = cursor.read_u32()?;
> +        let data_end = cursor.read_u32()?;
> +        let data = data_start..data_end;
> +
> +        Ok(Self {
> +            section_flags,
> +            va,
> +            data,
> +        })
> +    }
> +}
> +
> +/// A firmware entry containing a header and optional parsed section data.
> +///
> +/// Represents a single entry in the firmware binary, which may contain loadable
> +/// section data or metadata that doesn't require loading.
> +struct EntrySection {
> +    entry_hdr: EntryHeader,
> +    inner: Option<ParsedSection>,
> +}
> +
> +/// Header for a firmware entry, packed into a single u32.
> +///
> +/// The entry header encodes the entry type, size, and optional flag in a
> +/// 32-bit value with the following layout:
> +/// - Bits 0-7: Entry type
> +/// - Bits 8-15: Size in bytes
> +/// - Bit 31: Optional flag
> +struct EntryHeader(u32);
> +
> +impl EntryHeader {
> +    fn entry_type_raw(&self) -> u8 {
> +        (self.0 & 0xff) as u8
> +    }
> +
> +    fn entry_type(&self) -> Result<EntryType> {
> +        let v = self.entry_type_raw();
> +        EntryType::try_from(v)
> +    }
> +
> +    fn optional(&self) -> bool {
> +        self.0 & bit_u32(31) != 0
> +    }
> +
> +    fn size(&self) -> u32 {
> +        self.0 >> 8 & 0xff
> +    }
> +}
> +
> +#[derive(Clone, Copy, Debug)]
> +#[repr(u8)]
> +enum EntryType {
> +    /// Host <-> FW interface.
> +    Iface = 0,
> +    /// FW config.
> +    Config = 1,
> +    /// Unit tests.
> +    FutfTest = 2,
> +    /// Trace buffer interface.
> +    TraceBuffer = 3,
> +    /// Timeline metadata interface.
> +    TimelineMetadata = 4,
> +    /// Metadata about how the FW binary was built.
> +    BuildInfoMetadata = 6,
> +}
> +
> +impl TryFrom<u8> for EntryType {
> +    type Error = Error;
> +
> +    fn try_from(value: u8) -> Result<Self, Self::Error> {
> +        match value {
> +            0 => Ok(EntryType::Iface),
> +            1 => Ok(EntryType::Config),
> +            2 => Ok(EntryType::FutfTest),
> +            3 => Ok(EntryType::TraceBuffer),
> +            4 => Ok(EntryType::TimelineMetadata),
> +            6 => Ok(EntryType::BuildInfoMetadata),
> +            _ => Err(EINVAL),
> +        }
> +    }
> +}
> 
> -- 
> 2.53.0
> 

  reply	other threads:[~2026-04-27  8:09 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-24 23:38 [PATCH v4 00/20] drm/tyr: firmware loading and MCU boot support Deborah Brouwer
2026-04-24 23:38 ` [PATCH v4 01/20] drm/tyr: remove unused device from platform data Deborah Brouwer
2026-04-24 23:38 ` [PATCH v4 02/20] drm/tyr: select required dependencies in Kconfig Deborah Brouwer
2026-04-27  7:23   ` Boris Brezillon
2026-04-24 23:38 ` [PATCH v4 03/20] drm/tyr: move clock cleanup into Clocks Drop impl Deborah Brouwer
2026-04-24 23:38 ` [PATCH v4 04/20] drm/tyr: rename TyrObject to BoData Deborah Brouwer
2026-04-24 23:38 ` [PATCH v4 05/20] drm/tyr: use shmem GEM object type in TyrDrmDriver Deborah Brouwer
2026-04-24 23:39 ` [PATCH v4 06/20] drm/tyr: set DMA mask using GPU physical address Deborah Brouwer
2026-04-24 23:39 ` [PATCH v4 07/20] drm/tyr: add shmem backing for GEM objects Deborah Brouwer
2026-04-24 23:39 ` [PATCH v4 08/20] drm/tyr: Add generic slot manager Deborah Brouwer
2026-04-24 23:39 ` [PATCH v4 09/20] drm/tyr: add MMU module Deborah Brouwer
2026-04-24 23:39 ` [PATCH v4 10/20] drm/tyr: add GPU virtual memory module Deborah Brouwer
2026-04-24 23:39 ` [PATCH v4 11/20] drm/tyr: add a kernel buffer object Deborah Brouwer
2026-04-24 23:39 ` [PATCH v4 12/20] drm/tyr: add parser for firmware binary Deborah Brouwer
2026-04-27  8:09   ` Onur Özkan [this message]
2026-04-27  8:20     ` Boris Brezillon
2026-04-24 23:39 ` [PATCH v4 13/20] drm/tyr: add firmware loading and MCU boot support Deborah Brouwer
2026-04-24 23:39 ` [PATCH v4 14/20] drm/tyr: add Wait type for GPU events Deborah Brouwer
2026-04-24 23:39 ` [PATCH v4 15/20] drm/tyr: add Job IRQ handling Deborah Brouwer
2026-04-24 23:39 ` [PATCH v4 16/20] drm/tyr: wait for global interface readiness Deborah Brouwer
2026-04-24 23:39 ` [PATCH v4 17/20] drm/tyr: validate presence of CSF shared section Deborah Brouwer
2026-04-24 23:39 ` [PATCH v4 18/20] drm/tyr: add CSF firmware interface support Deborah Brouwer
2026-04-27  9:08   ` Onur Özkan
2026-04-24 23:39 ` [PATCH v4 19/20] rust: time: add arch_timer_get_rate wrapper Deborah Brouwer
2026-04-27  7:42   ` Andreas Hindborg
2026-04-27  7:53   ` Alice Ryhl
2026-04-27  8:59   ` Onur Özkan
2026-04-24 23:39 ` [PATCH v4 20/20] drm/tyr: program CSF global interface Deborah Brouwer
2026-04-27  8:07 ` [PATCH v4 00/20] drm/tyr: firmware loading and MCU boot support Boris Brezillon

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=20260427080926.73950-1-work@onurozkan.dev \
    --to=work@onurozkan.dev \
    --cc=a.hindborg@kernel.org \
    --cc=acourbot@nvidia.com \
    --cc=airlied@gmail.com \
    --cc=aliceryhl@google.com \
    --cc=alvin.sun@linux.dev \
    --cc=anna-maria@linutronix.de \
    --cc=beata.michalska@arm.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun@kernel.org \
    --cc=boris.brezillon@collabora.com \
    --cc=dakr@kernel.org \
    --cc=daniel.almeida@collabora.com \
    --cc=deborah.brouwer@collabora.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=frederic@kernel.org \
    --cc=fujita.tomonori@gmail.com \
    --cc=gary@garyguo.net \
    --cc=jstultz@google.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lossin@kernel.org \
    --cc=lyude@redhat.com \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=sboyd@kernel.org \
    --cc=simona@ffwll.ch \
    --cc=tglx@kernel.org \
    --cc=tmgross@umich.edu \
    /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