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
>
next prev parent 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