linux/drivers/gpu/drm/tyr/gpu.rs
Daniel Almeida cf4fd52e32 rust: drm: Introduce the Tyr driver for Arm Mali GPUs
Add a Rust driver for ARM Mali CSF-based GPUs. It is a port of Panthor
and therefore exposes Panthor's uAPI and name to userspace, and the
product of a joint effort between Collabora, Arm and Google engineers.

The aim is to incrementally develop Tyr with the abstractions that are
currently available until it is consider to be in parity with Panthor
feature-wise.

The development of Tyr itself started in January, after a few failed
attempts of converting Panthor piecewise through a mix of Rust and C
code. There is a downstream branch that's much further ahead in terms of
capabilities than this initial patch.

The downstream code is capable of booting the MCU, doing sync VM_BINDS
through the work-in-progress GPUVM abstraction and also doing (trivial)
submits through Asahi's drm_scheduler and dma_fence abstractions. So
basically, most of what one would expect a modern GPU driver to do,
except for power management and some other very important adjacent
pieces. It is not at the point where submits can correctly deal with
dependencies, or at the point where it can rotate access to the GPU
hardware fairly through a software scheduler, but that is simply a
matter of writing more code.

This first patch, however, only implements a subset of the current
features available downstream, as the rest is not implementable without
pulling in even more abstractions. In particular, a lot of things depend
on properly mapping memory on a given VA range, which itself depends on
the GPUVM abstraction that is currently work-in-progress. For this
reason, we still cannot boot the MCU and thus, cannot do much for the
moment.

This constitutes a change in the overall strategy that we have been
using to develop Tyr so far. By submitting small parts of the driver
upstream iteratively, we aim to:

a) evolve together with Nova and rvkms, hopefully reducing regressions
due to upstream changes (that may break us because we were not there, in
the first place)

b) prove any work-in-progress abstractions by having them run on a real
driver and hardware and,

c) provide a reason to work on and review said abstractions by providing
a user, which would be tyr itself.

Despite its limited feature-set, we offer IGT tests. It is only tested
on the rk3588, so any other SoC is probably not going to work at all for
now.

The skeleton is basically taken from Nova and also
rust_platform_driver.rs.

Lastly, the name "Tyr" is inspired by Norse mythology, reflecting ARM's
tradition of naming their GPUs after Nordic mythological figures and
places.

Co-developed-by: Beata Michalska <beata.michalska@arm.com>
Signed-off-by: Beata Michalska <beata.michalska@arm.com>
Co-developed-by: Carsten Haitzler <carsten.haitzler@foss.arm.com>
Signed-off-by: Carsten Haitzler <carsten.haitzler@foss.arm.com>
Co-developed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Rob Herring <robh@kernel.org>
Link: https://www.collabora.com/news-and-blog/news-and-events/introducing-tyr-a-new-rust-drm-driver.html
Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
Acked-by: Boris Brezillon <boris.brezillon@collabora.com>
[aliceryhl: minor Kconfig update on apply]
[aliceryhl: s/drm::device::/drm::/]
Link: https://lore.kernel.org/r/20250910-tyr-v3-1-dba3bc2ae623@collabora.com
Co-developed-by: Alice Ryhl <aliceryhl@google.com>
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
2025-09-11 12:20:03 +00:00

219 lines
6.9 KiB
Rust

// SPDX-License-Identifier: GPL-2.0 or MIT
use kernel::bits::genmask_u32;
use kernel::device::Bound;
use kernel::device::Device;
use kernel::devres::Devres;
use kernel::platform;
use kernel::prelude::*;
use kernel::time;
use kernel::transmute::AsBytes;
use crate::driver::IoMem;
use crate::regs;
/// Struct containing information that can be queried by userspace. This is read from
/// the GPU's registers.
///
/// # Invariants
///
/// - The layout of this struct identical to the C `struct drm_panthor_gpu_info`.
#[repr(C)]
pub(crate) struct GpuInfo {
pub(crate) gpu_id: u32,
pub(crate) gpu_rev: u32,
pub(crate) csf_id: u32,
pub(crate) l2_features: u32,
pub(crate) tiler_features: u32,
pub(crate) mem_features: u32,
pub(crate) mmu_features: u32,
pub(crate) thread_features: u32,
pub(crate) max_threads: u32,
pub(crate) thread_max_workgroup_size: u32,
pub(crate) thread_max_barrier_size: u32,
pub(crate) coherency_features: u32,
pub(crate) texture_features: [u32; 4],
pub(crate) as_present: u32,
pub(crate) pad0: u32,
pub(crate) shader_present: u64,
pub(crate) l2_present: u64,
pub(crate) tiler_present: u64,
pub(crate) core_features: u32,
pub(crate) pad: u32,
}
impl GpuInfo {
pub(crate) fn new(dev: &Device<Bound>, iomem: &Devres<IoMem>) -> Result<Self> {
let gpu_id = regs::GPU_ID.read(dev, iomem)?;
let csf_id = regs::GPU_CSF_ID.read(dev, iomem)?;
let gpu_rev = regs::GPU_REVID.read(dev, iomem)?;
let core_features = regs::GPU_CORE_FEATURES.read(dev, iomem)?;
let l2_features = regs::GPU_L2_FEATURES.read(dev, iomem)?;
let tiler_features = regs::GPU_TILER_FEATURES.read(dev, iomem)?;
let mem_features = regs::GPU_MEM_FEATURES.read(dev, iomem)?;
let mmu_features = regs::GPU_MMU_FEATURES.read(dev, iomem)?;
let thread_features = regs::GPU_THREAD_FEATURES.read(dev, iomem)?;
let max_threads = regs::GPU_THREAD_MAX_THREADS.read(dev, iomem)?;
let thread_max_workgroup_size = regs::GPU_THREAD_MAX_WORKGROUP_SIZE.read(dev, iomem)?;
let thread_max_barrier_size = regs::GPU_THREAD_MAX_BARRIER_SIZE.read(dev, iomem)?;
let coherency_features = regs::GPU_COHERENCY_FEATURES.read(dev, iomem)?;
let texture_features = regs::GPU_TEXTURE_FEATURES0.read(dev, iomem)?;
let as_present = regs::GPU_AS_PRESENT.read(dev, iomem)?;
let shader_present = u64::from(regs::GPU_SHADER_PRESENT_LO.read(dev, iomem)?);
let shader_present =
shader_present | u64::from(regs::GPU_SHADER_PRESENT_HI.read(dev, iomem)?) << 32;
let tiler_present = u64::from(regs::GPU_TILER_PRESENT_LO.read(dev, iomem)?);
let tiler_present =
tiler_present | u64::from(regs::GPU_TILER_PRESENT_HI.read(dev, iomem)?) << 32;
let l2_present = u64::from(regs::GPU_L2_PRESENT_LO.read(dev, iomem)?);
let l2_present = l2_present | u64::from(regs::GPU_L2_PRESENT_HI.read(dev, iomem)?) << 32;
Ok(Self {
gpu_id,
gpu_rev,
csf_id,
l2_features,
tiler_features,
mem_features,
mmu_features,
thread_features,
max_threads,
thread_max_workgroup_size,
thread_max_barrier_size,
coherency_features,
// TODO: Add texture_features_{1,2,3}.
texture_features: [texture_features, 0, 0, 0],
as_present,
pad0: 0,
shader_present,
l2_present,
tiler_present,
core_features,
pad: 0,
})
}
pub(crate) fn log(&self, pdev: &platform::Device) {
let major = (self.gpu_id >> 16) & 0xff;
let minor = (self.gpu_id >> 8) & 0xff;
let status = self.gpu_id & 0xff;
let model_name = if let Some(model) = GPU_MODELS
.iter()
.find(|&f| f.major == major && f.minor == minor)
{
model.name
} else {
"unknown"
};
dev_info!(
pdev.as_ref(),
"mali-{} id 0x{:x} major 0x{:x} minor 0x{:x} status 0x{:x}",
model_name,
self.gpu_id >> 16,
major,
minor,
status
);
dev_info!(
pdev.as_ref(),
"Features: L2:{:#x} Tiler:{:#x} Mem:{:#x} MMU:{:#x} AS:{:#x}",
self.l2_features,
self.tiler_features,
self.mem_features,
self.mmu_features,
self.as_present
);
dev_info!(
pdev.as_ref(),
"shader_present=0x{:016x} l2_present=0x{:016x} tiler_present=0x{:016x}",
self.shader_present,
self.l2_present,
self.tiler_present
);
}
/// Returns the number of virtual address bits supported by the GPU.
#[expect(dead_code)]
pub(crate) fn va_bits(&self) -> u32 {
self.mmu_features & genmask_u32(0..=7)
}
/// Returns the number of physical address bits supported by the GPU.
#[expect(dead_code)]
pub(crate) fn pa_bits(&self) -> u32 {
(self.mmu_features >> 8) & genmask_u32(0..=7)
}
}
// SAFETY: `GpuInfo`'s invariant guarantees that it is the same type that is
// already exposed to userspace by the C driver. This implies that it fulfills
// the requirements for `AsBytes`.
//
// This means:
//
// - No implicit padding,
// - No kernel pointers,
// - No interior mutability.
unsafe impl AsBytes for GpuInfo {}
struct GpuModels {
name: &'static str,
major: u32,
minor: u32,
}
const GPU_MODELS: [GpuModels; 1] = [GpuModels {
name: "g610",
major: 10,
minor: 7,
}];
#[allow(dead_code)]
pub(crate) struct GpuId {
pub(crate) arch_major: u32,
pub(crate) arch_minor: u32,
pub(crate) arch_rev: u32,
pub(crate) prod_major: u32,
pub(crate) ver_major: u32,
pub(crate) ver_minor: u32,
pub(crate) ver_status: u32,
}
impl From<u32> for GpuId {
fn from(value: u32) -> Self {
GpuId {
arch_major: (value & genmask_u32(28..=31)) >> 28,
arch_minor: (value & genmask_u32(24..=27)) >> 24,
arch_rev: (value & genmask_u32(20..=23)) >> 20,
prod_major: (value & genmask_u32(16..=19)) >> 16,
ver_major: (value & genmask_u32(12..=15)) >> 12,
ver_minor: (value & genmask_u32(4..=11)) >> 4,
ver_status: value & genmask_u32(0..=3),
}
}
}
/// Powers on the l2 block.
pub(crate) fn l2_power_on(dev: &Device<Bound>, iomem: &Devres<IoMem>) -> Result {
regs::L2_PWRON_LO.write(dev, iomem, 1)?;
// TODO: We cannot poll, as there is no support in Rust currently, so we
// sleep. Change this when read_poll_timeout() is implemented in Rust.
kernel::time::delay::fsleep(time::Delta::from_millis(100));
if regs::L2_READY_LO.read(dev, iomem)? != 1 {
dev_err!(dev, "Failed to power on the GPU\n");
return Err(EIO);
}
Ok(())
}