From: Deborah Brouwer <deborah.brouwer@collabora.com>
To: Boris Brezillon <boris.brezillon@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>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Trevor Gross" <tmgross@umich.edu>,
linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org,
rust-for-linux@vger.kernel.org, laura.nao@collabora.com,
samitolvanen@google.com
Subject: Re: [PATCH v2] drm/tyr: move probe resources into registration data
Date: Fri, 5 Jun 2026 09:43:50 -0700 [thread overview]
Message-ID: <aiL8xtohSEDZ7Lbx@um790> (raw)
In-Reply-To: <20260605090714.2c4c7945@fedora-2.home>
On Fri, Jun 05, 2026 at 09:07:14AM +0200, Boris Brezillon wrote:
> On Thu, 04 Jun 2026 16:26:12 -0700
> Deborah Brouwer <deborah.brouwer@collabora.com> wrote:
>
> > Introduce TyrDrmRegistrationData to hold resources that need to stay alive
> > while the DRM device is registered. This moves the parent platform device,
> > clocks, regulators, MMIO mapping, firmware state, and GPU info into data
> > owned by the DRM registration.
> >
> > The registration data is tied to the platform device binding lifetime, so
> > use Registration::new_with_lt() and store the returned Registration in the
> > platform driver data.
> >
> > Add initial stubs for firmware, MMU, VM, and address-space handling, to
> > show the intended ownership model.
>
> I get that it's useful to prove that things work as intended, but I'd
> prefer if were submitting the working implementation instead of the
> stub, since we have it. Anyway, this should at least be done in separate
> commits (first the RegData with just the things we need today, then
> commits to show what it would look like with real components).
It's true that we don't need to merge the stubs, I would just like Danilo's
changes to tyr/driver.rs in "rust: drm: Add RegistrationData to drm::Driver"
to match what this patch does in tyr/driver.rs
>
> >
> > Signed-off-by: Deborah Brouwer <deborah.brouwer@collabora.com>
> > ---
> > Changes in v2:
> > - Store the parent platform device as a bound reference instead of as an ARef.
> > - Store Firmware directly instead of in an Arc.
> > - Replace manual ForLt implementation with the ForLt!() macro.
> > - Remove TyrDrmDeviceData and just pass Ok(()) to UnregisteredDevice::new().
> > - Complete firmware boot() as an example of it using iomem.
> > - Add more content to the MMU stub as well as VM, and address-space stubs
> > to show how iomem is shared.
> >
> > - Link to v1: https://lore.kernel.org/r/20260603-use_tyr_reg_data-v1-1-97f64e951cf6@collabora.com
> >
> > This patch applies on top of the drm lifetime series [1] and the
> > full branch [2].
> >
> > [1] https://lore.kernel.org/rust-for-linux/20260603011711.2077361-1-dakr@kernel.org/
> > [2] https://git.kernel.org/pub/scm/linux/kernel/git/dakr/linux.git/log/?h=drm-lifetime
> > ---
> > drivers/gpu/drm/tyr/driver.rs | 49 ++++++++++++++----
> > drivers/gpu/drm/tyr/file.rs | 11 ++--
> > drivers/gpu/drm/tyr/fw.rs | 88 ++++++++++++++++++++++++++++++++
> > drivers/gpu/drm/tyr/mmu.rs | 56 ++++++++++++++++++++
> > drivers/gpu/drm/tyr/mmu/address_space.rs | 63 +++++++++++++++++++++++
> > drivers/gpu/drm/tyr/tyr.rs | 3 ++
> > drivers/gpu/drm/tyr/vm.rs | 62 ++++++++++++++++++++++
> > 7 files changed, 316 insertions(+), 16 deletions(-)
> >
> > diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs
> > index 819f64a7573d..7f2d2e8eb7de 100644
> > --- a/drivers/gpu/drm/tyr/driver.rs
> > +++ b/drivers/gpu/drm/tyr/driver.rs
> > @@ -6,6 +6,7 @@
> > OptionalClk, //
> > },
> > device::{
> > + Bound,
> > Core,
> > Device,
> > DeviceContext, //
> > @@ -29,6 +30,7 @@
> > sizes::SZ_2M,
> > sync::{
> > aref::ARef,
> > + Arc,
> > Mutex, //
> > },
> > time,
> > @@ -37,9 +39,11 @@
> >
> > use crate::{
> > file::TyrDrmFileData,
> > + fw::Firmware,
> > gem::BoData,
> > gpu,
> > gpu::GpuInfo,
> > + mmu::Mmu,
> > regs::gpu_control::*, //
> > };
> >
> > @@ -49,7 +53,6 @@
> >
> > /// Convenience type alias for the DRM device type for this driver.
> > pub(crate) type TyrDrmDevice<Ctx = drm::Registered> = drm::Device<TyrDrmDriver, Ctx>;
> > -
> > pub(crate) struct TyrPlatformDriver;
> >
> > #[pin_data(PinnedDrop)]
> > @@ -58,9 +61,14 @@ pub(crate) struct TyrPlatformDriverData<'bound> {
> > _reg: drm::Registration<'bound, TyrDrmDriver>,
> > }
> >
> > +/// Resources kept alive by the DRM registration.
> > #[pin_data]
> > -pub(crate) struct TyrDrmDeviceData {
> > - pub(crate) pdev: ARef<platform::Device>,
> > +pub(crate) struct TyrDrmRegistrationData<'bound> {
> > + /// Parent platform device.
> > + pub(crate) pdev: &'bound platform::Device<Bound>,
> > +
> > + /// Firmware sections.
> > + pub(crate) fw: Firmware<'bound>,
> >
> > #[pin]
> > clks: Mutex<Clocks>,
> > @@ -68,6 +76,9 @@ pub(crate) struct TyrDrmDeviceData {
> > #[pin]
> > regulators: Mutex<Regulators>,
> >
> > + /// GPU MMIO register mapping.
> > + pub(crate) iomem: Arc<IoMem<'bound>>,
> > +
> > /// Some information on the GPU.
> > ///
> > /// This is mainly queried by userspace, i.e.: Mesa.
> > @@ -119,7 +130,7 @@ fn probe<'bound>(
> > let sram_regulator = Regulator::<regulator::Enabled>::get(pdev.as_ref(), c"sram")?;
> >
> > let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
> > - let iomem = request.iomap_sized::<SZ_2M>()?;
> > + let iomem = Arc::new(request.iomap_sized::<SZ_2M>()?, GFP_KERNEL)?;
> >
> > issue_soft_reset(pdev.as_ref(), &iomem)?;
> > gpu::l2_power_on(pdev.as_ref(), &iomem)?;
> > @@ -135,10 +146,23 @@ fn probe<'bound>(
> > // other threads of execution.
> > unsafe { pdev.dma_set_mask_and_coherent(DmaMask::try_new(pa_bits)?)? };
> >
> > - let platform: ARef<platform::Device> = pdev.into();
> > + let unreg_dev = drm::UnregisteredDevice::<TyrDrmDriver>::new(pdev, Ok(()))?;
> > +
> > + let mmu = Mmu::new(iomem.as_arc_borrow(), &gpu_info)?;
> > +
> > + let firmware = Firmware::new(
> > + pdev,
> > + iomem.clone(),
> > + &unreg_dev,
> > + mmu.as_arc_borrow(),
> > + &gpu_info,
> > + )?;
> > +
> > + firmware.boot()?;
> >
> > - let data = try_pin_init!(TyrDrmDeviceData {
> > - pdev: platform.clone(),
> > + let reg_data = try_pin_init!(TyrDrmRegistrationData {
> > + pdev,
> > + fw: firmware,
> > clks <- new_mutex!(Clocks {
> > core: core_clk,
> > stacks: stacks_clk,
> > @@ -148,11 +172,14 @@ fn probe<'bound>(
> > _mali: mali_regulator,
> > _sram: sram_regulator,
> > }),
> > + iomem,
> > gpu_info,
> > });
> >
> > - let tdev = drm::UnregisteredDevice::<TyrDrmDriver>::new(pdev, data)?;
> > - let reg = drm::Registration::new(pdev.as_ref(), tdev, (), 0)?;
> > + // SAFETY: `reg` is stored in the platform driver data and is not leaked or
> > + // forgotten, so it is dropped before the `'bound` registration data can become
> > + // invalid.
> > + let reg = unsafe { drm::Registration::new_with_lt(pdev.as_ref(), unreg_dev, reg_data, 0)? };
> >
> > let driver = TyrPlatformDriverData {
> > _device: reg.device().into(),
> > @@ -183,8 +210,8 @@ fn drop(self: Pin<&mut Self>) {}
> >
> > #[vtable]
> > impl drm::Driver for TyrDrmDriver {
> > - type Data = TyrDrmDeviceData;
> > - type RegistrationData = ForLt!(());
> > + type Data = ();
> > + type RegistrationData = ForLt!(TyrDrmRegistrationData<'_>);
> > type File = TyrDrmFileData;
> > type Object<R: drm::DeviceContext> = drm::gem::shmem::Object<BoData, R>;
> > type ParentDevice<Ctx: DeviceContext> = platform::Device<Ctx>;
> > diff --git a/drivers/gpu/drm/tyr/file.rs b/drivers/gpu/drm/tyr/file.rs
> > index 9f53da7633ab..9baec67febc9 100644
> > --- a/drivers/gpu/drm/tyr/file.rs
> > +++ b/drivers/gpu/drm/tyr/file.rs
> > @@ -11,7 +11,8 @@
> >
> > use crate::driver::{
> > TyrDrmDevice,
> > - TyrDrmDriver, //
> > + TyrDrmDriver,
> > + TyrDrmRegistrationData, //
> > };
> >
> > #[pin_data]
> > @@ -30,16 +31,16 @@ fn open(_dev: &drm::Device<Self::Driver>) -> Result<Pin<KBox<Self>>> {
> >
> > impl TyrDrmFileData {
> > pub(crate) fn dev_query(
> > - ddev: &TyrDrmDevice,
> > + _ddev: &TyrDrmDevice,
> > _pdev: &platform::Device<device::Bound>,
> > - _reg_data: &(),
> > + reg_data: &TyrDrmRegistrationData<'_>,
> > devquery: &mut uapi::drm_panthor_dev_query,
> > _file: &TyrDrmFile,
> > ) -> Result<u32> {
> > if devquery.pointer == 0 {
> > match devquery.type_ {
> > uapi::drm_panthor_dev_query_type_DRM_PANTHOR_DEV_QUERY_GPU_INFO => {
> > - devquery.size = core::mem::size_of_val(&ddev.gpu_info) as u32;
> > + devquery.size = core::mem::size_of_val(®_data.gpu_info) as u32;
> > Ok(0)
> > }
> > _ => Err(EINVAL),
> > @@ -53,7 +54,7 @@ pub(crate) fn dev_query(
> > )
> > .writer();
> >
> > - writer.write(&ddev.gpu_info)?;
> > + writer.write(®_data.gpu_info)?;
> >
> > Ok(0)
> > }
> > diff --git a/drivers/gpu/drm/tyr/fw.rs b/drivers/gpu/drm/tyr/fw.rs
> > new file mode 100644
> > index 000000000000..fb4b6709bce1
> > --- /dev/null
> > +++ b/drivers/gpu/drm/tyr/fw.rs
> > @@ -0,0 +1,88 @@
> > +// SPDX-License-Identifier: GPL-2.0 or MIT
> > +
> > +//! Firmware loading and management for Mali CSF GPUs.
> > +//!
> > +//! This is currently only a lifetime/resource stub. The actual firmware parser,
> > +//! section loading, MCU VM mapping, and boot sequence will be added later.
> > +#![allow(dead_code)]
> > +
> > +use kernel::{
> > + device::Bound,
> > + drm::Uninit,
> > + io::{
> > + poll,
> > + Io, //
> > + },
> > + platform,
> > + prelude::*,
> > + sync::{
> > + Arc,
> > + ArcBorrow, //
> > + },
> > + time::Delta, //
> > +};
> > +
> > +use crate::{
> > + driver::{
> > + IoMem,
> > + TyrDrmDevice, //
> > + },
> > + gpu::GpuInfo,
> > + mmu::Mmu,
> > + regs::gpu_control::{
> > + McuControlMode,
> > + McuStatus,
> > + MCU_CONTROL,
> > + MCU_STATUS, //
> > + },
> > + vm::Vm, //
> > +};
> > +
> > +/// Firmware state for a bound Tyr device.
> > +///
> > +/// For now this only keeps the device resources alive. Later this will own the
> > +/// loaded firmware sections, MCU VM mappings, and boot state.
> > +pub(crate) struct Firmware<'bound> {
> > + /// Parent platform device.
> > + _pdev: &'bound platform::Device<Bound>,
> > +
> > + /// MMIO mapping used for firmware/MCU register access.
> > + iomem: Arc<IoMem<'bound>>,
> > +}
> > +
> > +impl<'bound> Firmware<'bound> {
> > + /// Create firmware state for this device.
> > + ///
> > + /// This stub only records the resources that future firmware loading code
> > + /// will need.
> > + pub(crate) fn new(
> > + pdev: &'bound platform::Device<Bound>,
> > + iomem: Arc<IoMem<'bound>>,
> > + ddev: &TyrDrmDevice<Uninit>,
> > + mmu: ArcBorrow<'_, Mmu<'bound>>,
> > + gpu_info: &GpuInfo,
> > + ) -> Result<Firmware<'bound>> {
> > + let vm = Vm::new(pdev, ddev, mmu, gpu_info)?;
> > + vm.activate()?;
> > +
> > + Ok(Firmware { _pdev: pdev, iomem })
> > + }
> > +
> > + /// Boot the firmware.
> > + pub(crate) fn boot(&self) -> Result {
> > + let io = &self.iomem;
> > + io.write_reg(MCU_CONTROL::zeroed().with_req(McuControlMode::Auto));
> > +
> > + if let Err(e) = poll::read_poll_timeout(
> > + || Ok(io.read(MCU_STATUS)),
> > + |status| status.value() == McuStatus::Enabled,
> > + Delta::from_millis(1),
> > + Delta::from_millis(100),
> > + ) {
> > + let status = io.read(MCU_STATUS);
> > + pr_err!("MCU failed to boot, status: {:?}", status.value());
> > + return Err(e);
> > + }
> > + Ok(())
> > + }
> > +}
> > diff --git a/drivers/gpu/drm/tyr/mmu.rs b/drivers/gpu/drm/tyr/mmu.rs
> > new file mode 100644
> > index 000000000000..9e2efe342df3
> > --- /dev/null
> > +++ b/drivers/gpu/drm/tyr/mmu.rs
> > @@ -0,0 +1,56 @@
> > +// SPDX-License-Identifier: GPL-2.0 or MIT
> > +
> > +//! Memory Management Unit (MMU) driver for the Tyr GPU.
> > +//!
> > +//! This is just a lifetime/resource stub.
> > +//! The actual MMU initialization, page table management,
> > +//! and GPU VM mapping will be added later.
> > +
> > +use kernel::{
> > + new_mutex,
> > + prelude::*,
> > + sync::{
> > + Arc,
> > + ArcBorrow,
> > + Mutex, //
> > + }, //
> > +};
> > +
> > +use crate::{
> > + driver::IoMem,
> > + gpu::GpuInfo,
> > + mmu::address_space::AddressSpaceManager,
> > + regs::gpu_control::AS_PRESENT, //
> > +};
> > +
> > +pub(crate) mod address_space;
> > +
> > +#[pin_data]
> > +pub(super) struct Mmu<'bound> {
> > + /// Manages the allocation of hardware MMU slots to GPU address spaces.
> > + #[pin]
> > + pub(crate) as_manager: Mutex<AddressSpaceManager<'bound>>,
> > +}
> > +
> > +impl<'bound> Mmu<'bound> {
> > + /// Create an MMU component for this device.
> > + pub(super) fn new(
> > + iomem: ArcBorrow<'_, IoMem<'bound>>,
> > + gpu_info: &GpuInfo,
> > + ) -> Result<Arc<Mmu<'bound>>> {
> > + let present = AS_PRESENT::from_raw(gpu_info.as_present).present().get();
> > + let slot_count: usize = present.count_ones().try_into()?;
> > + pr_info!("MMU: {} address space slots present", slot_count);
> > +
> > + let as_manager = AddressSpaceManager::new(iomem)?;
> > + let mmu_init = try_pin_init!(Self{
> > + as_manager <- new_mutex!(as_manager),
> > + });
> > + Arc::pin_init(mmu_init, GFP_KERNEL)
> > + }
> > +
> > + /// Make a VM active.
> > + pub(crate) fn activate_vm(&self) -> Result {
> > + self.as_manager.lock().activate_vm()
> > + }
> > +}
> > diff --git a/drivers/gpu/drm/tyr/mmu/address_space.rs b/drivers/gpu/drm/tyr/mmu/address_space.rs
> > new file mode 100644
> > index 000000000000..47534944b458
> > --- /dev/null
> > +++ b/drivers/gpu/drm/tyr/mmu/address_space.rs
> > @@ -0,0 +1,63 @@
> > +// SPDX-License-Identifier: GPL-2.0 or MIT
> > +
> > +//! GPU address space management and hardware operations.
> > +//!
> > +//! This is just a lifetime/resource stub.
> > +//! The actual GPU hardware address spaces (AS), including configuration,
> > +//! command submission, and page table update regions will be added later.
> > +
> > +use kernel::{
> > + io::{
> > + poll,
> > + register::Array,
> > + Io, //
> > + },
> > + prelude::*,
> > + sync::{
> > + Arc, //
> > + ArcBorrow,
> > + },
> > + time::Delta, //
> > +};
> > +
> > +use crate::{
> > + driver::IoMem,
> > + regs::mmu_control::mmu_as_control::STATUS, //
> > +};
> > +
> > +/// Manages GPU hardware address spaces via MMIO register operations.
> > +pub(crate) struct AddressSpaceManager<'bound> {
> > + /// Memory-mapped I/O region for GPU register access.
> > + iomem: Arc<IoMem<'bound>>,
> > +}
> > +
> > +impl<'bound> AddressSpaceManager<'bound> {
> > + /// Creates a new address space manager.
> > + ///
> > + /// Initializes the manager with references to the platform device and
> > + /// I/O memory region, along with the bitmask of available AS slots.
> > + pub(super) fn new(iomem: ArcBorrow<'_, IoMem<'bound>>) -> Result<AddressSpaceManager<'bound>> {
> > + Ok(Self {
> > + iomem: iomem.into(),
> > + })
> > + }
> > +
> > + pub(super) fn activate_vm(&mut self) -> Result {
> > + self.as_wait_ready(0)
> > + }
> > +
> > + /// Waits for an AS slot to become ready (not active).
> > + ///
> > + /// Returns an error if polling times out after 10ms or if register access fails.
> > + fn as_wait_ready(&self, as_nr: usize) -> Result {
> > + let io = &*self.iomem;
> > + let op = || {
> > + let status_reg = STATUS::try_at(as_nr).ok_or(EINVAL)?;
> > + Ok(io.read(status_reg))
> > + };
> > + let cond = |status: &STATUS| -> bool { !status.active_ext() };
> > + poll::read_poll_timeout(op, cond, Delta::from_millis(0), Delta::from_millis(10))?;
> > +
> > + Ok(())
> > + }
> > +}
> > diff --git a/drivers/gpu/drm/tyr/tyr.rs b/drivers/gpu/drm/tyr/tyr.rs
> > index 95cda7b0962f..ace2764c6059 100644
> > --- a/drivers/gpu/drm/tyr/tyr.rs
> > +++ b/drivers/gpu/drm/tyr/tyr.rs
> > @@ -9,9 +9,12 @@
> >
> > mod driver;
> > mod file;
> > +mod fw;
> > mod gem;
> > mod gpu;
> > +mod mmu;
> > mod regs;
> > +mod vm;
> >
> > kernel::module_platform_driver! {
> > type: TyrPlatformDriver,
> > diff --git a/drivers/gpu/drm/tyr/vm.rs b/drivers/gpu/drm/tyr/vm.rs
> > new file mode 100644
> > index 000000000000..5ca1b773df92
> > --- /dev/null
> > +++ b/drivers/gpu/drm/tyr/vm.rs
> > @@ -0,0 +1,62 @@
> > +// SPDX-License-Identifier: GPL-2.0 or MIT
> > +
> > +//! GPU virtual memory management.
> > +//!
> > +//! This is just a lifetime/resource stub. The actual vm
> > +//! implementation using the DRM GPUVM framework will be added later.
> > +
> > +use kernel::{
> > + device::Bound,
> > + drm::DeviceContext,
> > + platform,
> > + prelude::*,
> > + sync::{
> > + aref::ARef,
> > + Arc,
> > + ArcBorrow, //
> > + }, //
> > +};
> > +
> > +use crate::{
> > + driver::TyrDrmDevice,
> > + gpu::GpuInfo,
> > + mmu::Mmu, //
> > +};
> > +
> > +/// GPU virtual address space.
> > +///
> > +/// Each VM can be mapped into a hardware address space slot.
> > +#[pin_data]
> > +pub(crate) struct Vm<'bound> {
> > + /// MMU manager.
> > + mmu: Arc<Mmu<'bound>>,
> > + /// Platform device reference (needed to access the page table via devres).
> > + pdev: ARef<platform::Device>,
> > +}
> > +
> > +impl<'bound> Vm<'bound> {
> > + /// Creates a new GPU virtual address space.
> > + pub(crate) fn new<Ctx: DeviceContext>(
> > + pdev: &'bound platform::Device<Bound>,
> > + _ddev: &TyrDrmDevice<Ctx>,
> > + mmu: ArcBorrow<'_, Mmu<'bound>>,
> > + _gpu_info: &GpuInfo,
> > + ) -> Result<Arc<Vm<'bound>>> {
> > + let vm = Arc::pin_init(
> > + pin_init!(Self {
> > + pdev: pdev.into(),
> > + mmu: mmu.into(),
> > + }),
> > + GFP_KERNEL,
> > + )?;
> > +
> > + Ok(vm)
> > + }
> > +
> > + /// Activate the VM in a hardware address space slot.
> > + pub(crate) fn activate(&self) -> Result {
> > + self.mmu.activate_vm().inspect_err(|e| {
> > + pr_err!("Failed to activate VM: {:?}\n", e);
> > + })
> > + }
> > +}
> >
> > ---
> > base-commit: 305b04e26f0cdff2c8c0db208dbec0d9c9984a48
> > change-id: 20260603-use_tyr_reg_data-32720e66b2dd
> >
> > Best regards,
>
prev parent reply other threads:[~2026-06-05 16:44 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-04 23:26 [PATCH v2] drm/tyr: move probe resources into registration data Deborah Brouwer
2026-06-05 7:07 ` Boris Brezillon
2026-06-05 16:43 ` Deborah Brouwer [this message]
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=aiL8xtohSEDZ7Lbx@um790 \
--to=deborah.brouwer@collabora.com \
--cc=a.hindborg@kernel.org \
--cc=airlied@gmail.com \
--cc=aliceryhl@google.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=dri-devel@lists.freedesktop.org \
--cc=gary@garyguo.net \
--cc=laura.nao@collabora.com \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=samitolvanen@google.com \
--cc=simona@ffwll.ch \
--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