解剖Tyr:Linux首个Rust GPU驱动的代码实战分析

2025年9月,Linux内核合并了首个Rust GPU驱动Tyr(commit cf4fd52e3236),标志着Rust在内核图形子系统的正式落地。本文通过剖析Tyr的实际代码,展示Rust GPU驱动的架构设计、DRM抽象层的具体实现,以及从Panthor(C)移植到Tyr(Rust)的关键挑战。这是Rust在Linux内核从抽象到实战的完整技术案例。

引言:从理论到代码

在前两篇文章中,我们分析了Rust在Linux内核的整体状态和ABI稳定性12。这些讨论主要停留在宏观层面:代码统计、政策争议、技术保证。但实际的Rust内核代码长什么样?如何与C内核交互?遇到了哪些具体挑战?

本文通过解剖Tyr项目——Linux内核首个合并的Rust GPU驱动——来回答这些问题。我们将:

  1. 分析实际代码:基于commit cf4fd52e3236的真实代码
  2. 对比C/Rust实现:Panthor(C)vs Tyr(Rust)
  3. 揭示技术挑战:为何上游代码如此精简?
  4. 理解DRM抽象层rust/kernel/drm/如何工作?

这不是一篇科普文章,而是代码级的技术剖析


背景知识:GPU驱动与DRM子系统

GPU驱动的双层架构

在Linux中,GPU驱动分为两个部分:

1. 内核模式驱动(Kernel-mode Driver)

2. 用户模式驱动(Userspace Driver)

┌─────────────────────────────┐
│   游戏/应用程序              │
└──────────┬──────────────────┘
           │ OpenGL/Vulkan API
           ↓
┌─────────────────────────────┐
│   Mesa (用户模式驱动)        │
│   - panfrost_dri.so (Panthor)│
└──────────┬──────────────────┘
           │ ioctl系统调用
           ↓
┌─────────────────────────────┐
│   Tyr (内核模式驱动)         │ ← 本文重点
│   drivers/gpu/drm/tyr/      │
└──────────┬──────────────────┘
           │ 硬件寄存器操作
           ↓
┌─────────────────────────────┐
│   Mali GPU 硬件              │
└─────────────────────────────┘

什么是DRM子系统?

DRM(Direct Rendering Manager) 是Linux内核的图形子系统,管理所有GPU驱动。

核心组件

  1. DRM Coredrivers/gpu/drm/drm_*.c
    • 提供通用GPU管理框架
    • 处理显示模式设置(KMS)
    • 管理图形内存(GEM)
  2. GEM(Graphics Execution Manager)
    • GPU内存对象管理
    • 处理CPU/GPU内存共享
    • 管理用户空间映射(mmap)
  3. GPUVM(GPU Virtual Address Management)
    • GPU虚拟地址空间管理
    • 类似CPU的虚拟内存
    • 支持多进程GPU内存隔离
  4. GPU调度器(drm_gpu_scheduler)
    • 管理GPU任务队列
    • 处理任务依赖关系
    • 实现公平调度

学习资源

ARM Mali GPU架构

Mali GPU家族

架构 代表型号 特点 Tyr支持
Midgard Mali-T760 早期架构
Bifrost Mali-G71, G52 引入四边形着色器
Valhall Mali-G77, G78 超标量引擎
Valhall CSF Mali-G610, G710 命令流前端 Tyr目标

CSF(Command Stream Frontend)架构

Mali GPU硬件结构

┌─────────────────────────────────────┐
│  MCU (Microcontroller Unit)        │
│  - Cortex-M7核心 @ GHz             │
│  - 运行固件,管理GPU调度            │
└──────────┬──────────────────────────┘
           │ 内部总线
┌──────────┴──────────────────────────┐
│  Shader Cores (着色器核心)          │
│  - 执行计算/图形任务                │
│  - 多核并行(8-32核心不等)          │
└──────────┬──────────────────────────┘
           │
┌──────────┴──────────────────────────┐
│  L2 Cache + Memory System           │
│  - 共享L2缓存                       │
│  - MMU(内存管理单元)               │
└─────────────────────────────────────┘

MCU固件的关键作用

学习资源

为什么要用Rust重写GPU驱动?

GPU驱动的复杂性

  1. 海量内存操作
    • CPU/GPU共享内存
    • 用户空间映射(mmap)
    • DMA传输
    • 常见bug:use-after-free、double-free
  2. 并发密集
    • 多进程同时访问GPU
    • 中断处理
    • 任务队列管理
    • 常见bug:数据竞争、死锁
  3. 用户空间交互频繁
    • ioctl暴露大量攻击面
    • 需要严格验证用户输入
    • 常见bug:权限提升漏洞

历史数据(来自前文1):

Rust的解决方案

问题类别 C的困境 Rust的保证
内存安全 手动管理,易出错 所有权系统,编译时检查
并发安全 锁靠约定 借用检查器,编译时防数据竞争
资源泄漏 手动cleanup RAII自动管理
空指针 运行时崩溃 Option<T>编译时消除

Greg Kroah-Hartman(内核维护者)的评价1

“The majority of bugs we have are due to the stupid little corner cases in C that are totally gone in Rust.”

Panthor vs Tyr:移植关系

Panthor是Mali CSF GPU的C驱动(已上游):

TyrPanthor的Rust移植

为什么不直接用Panthor?

  1. 技术演进:验证Rust在GPU驱动的可行性
  2. 安全提升:消除Panthor的潜在内存安全bug
  3. 生态建设:为其他GPU驱动提供Rust参考

快速入门:如何学习GPU驱动开发

前置知识

必备基础

  1. ✅ C语言(指针、结构体、位操作)
  2. ✅ Linux系统编程(系统调用、设备驱动基础)
  3. ✅ 计算机体系结构(虚拟内存、DMA、中断)

Rust特有

  1. ✅ 所有权和借用
  2. ✅ 生命周期
  3. ✅ unsafe Rust(FFI互操作)

学习路径(推荐顺序)

第1步:DRM基础(2-3周)

第2步:Rust内核编程(3-4周)

第3步:阅读现有代码(持续)

第4步:理解GPU硬件(按需)

关键资源汇总

官方文档

代码仓库

社区资源

书籍推荐

从哪里开始贡献?

难度递增的任务

  1. ⭐ 初级
    • 为Rust抽象添加文档注释
    • 修复编译警告
    • 添加单元测试
  2. ⭐⭐ 中级
    • 实现缺失的寄存器定义
    • 添加新的GPU型号支持
    • 改进错误处理
  3. ⭐⭐⭐ 高级
    • 开发GPUVM Rust抽象
    • 实现GPU调度器
    • 移植其他GPU驱动到Rust

如何参与

  1. 订阅Rust for Linux邮件列表
  2. 在GitLab上关注DRM Rust项目
  3. 参与代码审查(学习最快的方式!)
  4. 从小patch开始提交

Tyr项目概览:第一手资料

Git Commit信息

提交哈希cf4fd52e3236 作者:Daniel Almeida daniel.almeida@collabora.com 日期:2025年9月10日 合作方:Collabora、Arm、Google

Commit message核心摘录(原文)3

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 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.

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.

关键信息解读

  1. 下游分支功能完整
    • ✅ MCU启动(Mali GPU的微控制器)
    • ✅ 同步VM_BINDS(虚拟内存绑定)
    • ✅ 基础任务提交
  2. 上游代码受限
    • ❌ 无法启动MCU
    • ❌ GPUVM抽象缺失
    • ❌ 只能查询GPU信息
  3. 战略转变
    • 之前尝试C+Rust混合(失败)
    • 现在改为纯Rust,分阶段上游

Tyr代码结构:实际文件布局

代码树(基于commit cf4fd52e3236)

drivers/gpu/drm/tyr/
├── tyr.rs        # 模块入口,platform_driver声明
├── driver.rs     # 驱动核心,TyrDriver和TyrData实现
├── file.rs       # DRM file操作,处理用户空间连接
├── gem.rs        # GEM对象管理
├── gpu.rs        # GPU信息查询(GpuInfo结构体)
├── regs.rs       # GPU寄存器定义和访问
├── Kconfig       # 内核配置选项
└── Makefile      # 构建配置

对比Panthor(C驱动)

$ cd /Users/weli/works/linux
$ ls drivers/gpu/drm/panthor/
panthor_devfreq.c  panthor_fw.c   panthor_gem.c  panthor_gpu.c
panthor_device.c   panthor_fw.h   panthor_gem.h  panthor_gpu.h
panthor_device.h   panthor_heap.c panthor_mmu.c  panthor_regs.h
...(共24个文件)

Tyr更精简:8个文件 vs Panthor的24个文件。但这并非优势,而是功能缺失的体现。


代码分析1:Tyr驱动入口

文件:drivers/gpu/drm/tyr/tyr.rs

// SPDX-License-Identifier: GPL-2.0 or MIT

//! Arm Mali Tyr DRM driver.
//!
//! The name "Tyr" is inspired by Norse mythology, reflecting Arm's tradition of
//! naming their GPUs after Nordic mythological figures and places.

use crate::driver::TyrDriver;

mod driver;
mod file;
mod gem;
mod gpu;
mod regs;

kernel::module_platform_driver! {
    type: TyrDriver,
    name: "tyr",
    authors: ["The Tyr driver authors"],
    description: "Arm Mali Tyr DRM driver",
    license: "Dual MIT/GPL",
}

关键点

  1. module_platform_driver!
    • 自动生成平台驱动注册代码
    • 等价于C中的module_platform_driver(tyr_driver)
  2. 模块组织
    • 清晰的模块划分(driver、file、gem、gpu、regs)
    • 私有模块,不暴露内部细节

对比C版本panthor_drv.c):

static struct platform_driver panthor_driver = {
    .probe = panthor_probe,
    .remove = panthor_remove,
    .driver = {
        .name = "panthor",
        .pm = &panthor_pm_ops,
        .of_match_table = dt_match,
    },
};
module_platform_driver(panthor_driver);

Rust的优势


代码分析2:驱动核心实现

文件:drivers/gpu/drm/tyr/driver.rs(部分)

2.1 设备树匹配

kernel::of_device_table!(
    OF_TABLE,
    MODULE_OF_TABLE,
    <TyrDriver as platform::Driver>::IdInfo,
    [
        (of::DeviceId::new(c_str!("rockchip,rk3588-mali")), ()),
        (of::DeviceId::new(c_str!("arm,mali-valhall-csf")), ())
    ]
);

解释

对比C版本

static const struct of_device_id dt_match[] = {
    { .compatible = "arm,mali-valhall-csf" },
    { .compatible = "rockchip,rk3588-mali" },
    {}
};
MODULE_DEVICE_TABLE(of, dt_match);

Rust的类型安全

2.2 驱动数据结构

#[pin_data(PinnedDrop)]
pub(crate) struct TyrData {
    pub(crate) pdev: ARef<platform::Device>,

    #[pin]
    clks: Mutex<Clocks>,

    #[pin]
    regulators: Mutex<Regulators>,

    /// Some information on the GPU.
    ///
    /// This is mainly queried by userspace, i.e.: Mesa.
    pub(crate) gpu_info: GpuInfo,
}

关键设计

  1. #[pin_data] 属性
    • 保证内存不移动(pin到堆上)
    • 必需,因为C代码可能持有指针
  2. ARef<platform::Device>
    • 引用计数的平台设备
    • 等价于C中的struct platform_device *
  3. Mutex<Clocks>Mutex<Regulators>
    • 内核互斥锁,保护共享资源
    • #[pin]:这些字段不能移动

2.3 初始化流程(probe函数)

impl platform::Driver for TyrDriver {
    type IdInfo = ();
    const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);

    fn probe(
        pdev: &platform::Device<Core>,
        _info: Option<&Self::IdInfo>,
    ) -> Result<Pin<KBox<Self>>> {
        // 1. 获取时钟
        let core_clk = Clk::get(pdev.as_ref(), Some(c_str!("core")))?;
        let stacks_clk = OptionalClk::get(pdev.as_ref(), Some(c_str!("stacks")))?;
        let coregroup_clk = OptionalClk::get(pdev.as_ref(), Some(c_str!("coregroup")))?;

        // 2. 启用时钟
        core_clk.prepare_enable()?;
        stacks_clk.prepare_enable()?;
        coregroup_clk.prepare_enable()?;

        // 3. 获取并启用电源调节器
        let mali_regulator = Regulator::<regulator::Enabled>::get(pdev.as_ref(), c_str!("mali"))?;
        let sram_regulator = Regulator::<regulator::Enabled>::get(pdev.as_ref(), c_str!("sram"))?;

        // 4. 映射MMIO寄存器
        let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
        let iomem = Arc::pin_init(request.iomap_sized::<SZ_2M>(), GFP_KERNEL)?;

        // 5. 软复位GPU
        issue_soft_reset(pdev.as_ref(), &iomem)?;

        // 6. L2缓存上电
        gpu::l2_power_on(pdev.as_ref(), &iomem)?;

        // 7. 读取GPU信息
        let gpu_info = GpuInfo::new(pdev.as_ref(), &iomem)?;
        gpu_info.log(pdev);

        // 8. 创建DRM设备
        let data = try_pin_init!(TyrData {
            pdev: platform.clone(),
            clks <- new_mutex!(Clocks { ... }),
            regulators <- new_mutex!(Regulators { ... }),
            gpu_info,
        });

        let tdev: ARef<TyrDevice> = drm::Device::new(pdev.as_ref(), data)?;
        drm::driver::Registration::new_foreign_owned(&tdev, pdev.as_ref(), 0)?;

        // 9. 返回驱动实例
        let driver = KBox::pin_init(try_pin_init!(TyrDriver { device: tdev }), GFP_KERNEL)?;

        dev_info!(pdev.as_ref(), "Tyr initialized correctly.\n");
        Ok(driver)
    }
}

详细分析

步骤1-2:时钟管理

Rust的Clk::get + prepare_enable自动管理生命周期

let core_clk = Clk::get(pdev.as_ref(), Some(c_str!("core")))?;
core_clk.prepare_enable()?;
// 当core_clk离开作用域时,自动disable + unprepare

对比C版本:

core_clk = devm_clk_get(dev, "core");
if (IS_ERR(core_clk))
    return PTR_ERR(core_clk);

ret = clk_prepare_enable(core_clk);
if (ret)
    return ret;

// ...
// 忘记disable?内存泄漏!
// clk_disable_unprepare(core_clk);  // 必须手动

步骤3:电源调节器的类型状态

let mali_regulator = Regulator::<regulator::Enabled>::get(pdev.as_ref(), c_str!("mali"))?;

类型系统保证

C中无此保证,完全依赖运行时检查。

步骤4:MMIO映射的大小检查

let iomem = Arc::pin_init(request.iomap_sized::<SZ_2M>(), GFP_KERNEL)?;

C版本:

iomem = devm_ioremap_resource(dev, res);
// 没有大小检查,运行时越界访问可能!

步骤5:软复位实现

fn issue_soft_reset(dev: &Device<Bound>, iomem: &Devres<IoMem>) -> Result {
    regs::GPU_CMD.write(dev, iomem, regs::GPU_CMD_SOFT_RESET)?;

    // 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::GPU_IRQ_RAWSTAT.read(dev, iomem)? & regs::GPU_IRQ_RAWSTAT_RESET_COMPLETED == 0 {
        dev_err!(dev, "GPU reset failed with errno\n");
        dev_err!(
            dev,
            "GPU_INT_RAWSTAT is {}\n",
            regs::GPU_IRQ_RAWSTAT.read(dev, iomem)?
        );

        return Err(EIO);
    }

    Ok(())
}

TODO注释揭示的问题

步骤7:GPU信息查询

这是当前Tyr唯一能做的事情。详见下一节。


代码分析3:GPU信息查询

文件:drivers/gpu/drm/tyr/gpu.rs

/// 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,
}

关键设计

  1. #[repr(C)]
    • 保证与C结构体drm_panthor_gpu_info内存布局完全相同
    • 用户空间通过ioctl读取这个结构体
  2. Invariants注释
    • Rust文档化不变量
    • 编译器无法检查(需要人工审查)

GpuInfo初始化

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)?;

        // 64位寄存器,分两次读取
        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,
        })
    }
}

技术细节

  1. 错误传播
    • 每次regs::XXX.read()?都可能失败
    • ?运算符自动传播错误
    • 无需手动if (ret < 0) return ret;
  2. 64位寄存器读取
    • Mali GPU的64位寄存器分成LO/HI两个32位寄存器
    • Rust明确显示位运算:| u64::from(...) << 32
    • C中容易出错(符号扩展问题)
  3. TODO注释
    • texture_features只读取了第一个
    • 其余3个硬编码为0
    • 说明这是WIP(Work-in-Progress)

代码分析4:DRM抽象层

Tyr依赖rust/kernel/drm/提供的抽象层。让我们深入分析。

文件:rust/kernel/drm/gem/mod.rs

4.1 BaseDriverObject trait

/// GEM object functions, which must be implemented by drivers.
pub trait BaseDriverObject<T: BaseObject>: Sync + Send + Sized {
    /// Create a new driver data object for a GEM object of a given size.
    fn new(dev: &drm::Device<T::Driver>, size: usize) -> impl PinInit<Self, Error>;

    /// Open a new handle to an existing object, associated with a File.
    fn open(
        _obj: &<<T as IntoGEMObject>::Driver as drm::Driver>::Object,
        _file: &drm::File<<<T as IntoGEMObject>::Driver as drm::Driver>::File>,
    ) -> Result {
        Ok(())
    }

    /// Close a handle to an existing object, associated with a File.
    fn close(
        _obj: &<<T as IntoGEMObject>::Driver as drm::Driver>::Object,
        _file: &drm::File<<<T as IntoGEMObject>::Driver as drm::Driver>::File>,
    ) {
    }
}

设计解析

  1. PinInit<Self, Error>
    • 就地初始化(in-place init)
    • 避免在栈上构造后移动到堆
    • 关键:C指针可能指向这块内存
  2. open/close回调
    • 默认实现为空
    • 驱动可选择性覆盖
    • 对比C:必须提供函数指针或NULL
  3. 类型约束
    • Sync + Send:可安全跨线程
    • Sized:大小已知(非trait object)

4.2 引用计数机制

// SAFETY: All gem objects are refcounted.
unsafe impl<T: IntoGEMObject> AlwaysRefCounted for T {
    fn inc_ref(&self) {
        // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero.
        unsafe { bindings::drm_gem_object_get(self.as_raw()) };
    }

    unsafe fn dec_ref(obj: NonNull<Self>) {
        // SAFETY: We either hold the only refcount on `obj`, or one of many - meaning that no one
        // else could possibly hold a mutable reference to `obj` and thus this immutable reference
        // is safe.
        let obj = unsafe { obj.as_ref() }.as_raw();

        // SAFETY:
        // - The safety requirements guarantee that the refcount is non-zero.
        // - We hold no references to `obj` now, making it safe for us to potentially deallocate it.
        unsafe { bindings::drm_gem_object_put(obj) };
    }
}

SAFETY注释的重要性

  1. inc_ref
    • 调用C函数drm_gem_object_get
    • 假设:已有&self,所以refcount非零
    • 这是不变量,违反=UB(未定义行为)
  2. dec_ref
    • 详细的SAFETY论证:
      • 持有唯一或多个引用之一
      • 没有可变引用冲突
      • refcount非零(由调用者保证)
    • 可能释放内存(refcount降到0)

对比C版本

static inline void drm_gem_object_get(struct drm_gem_object *obj)
{
    kref_get(&obj->refcount);
}

static inline void drm_gem_object_put(struct drm_gem_object *obj)
{
    kref_put(&obj->refcount, drm_gem_object_free);
}

C中完全没有安全论证

4.3 open/close回调的FFI桥接

extern "C" fn open_callback<T: BaseDriverObject<U>, U: BaseObject>(
    raw_obj: *mut bindings::drm_gem_object,
    raw_file: *mut bindings::drm_file,
) -> core::ffi::c_int {
    // SAFETY: `open_callback` is only ever called with a valid pointer to a `struct drm_file`.
    let file = unsafe {
        drm::File::<<<U as IntoGEMObject>::Driver as drm::Driver>::File>::as_ref(raw_file)
    };
    // SAFETY: `open_callback` is specified in the AllocOps structure for `Object<T>`, ensuring that
    // `raw_obj` is indeed contained within a `Object<T>`.
    let obj = unsafe {
        <<<U as IntoGEMObject>::Driver as drm::Driver>::Object as IntoGEMObject>::as_ref(raw_obj)
    };

    match T::open(obj, file) {
        Err(e) => e.to_errno(),
        Ok(()) => 0,
    }
}

FFI桥接技巧

  1. extern "C"
    • 使用C ABI(调用约定)
    • C代码可以调用这个函数
  2. unsafe转换
    • raw_objraw_file是C指针
    • 转换为Rust引用需要unsafe
    • SAFETY注释论证为何安全
  3. 错误处理
    • Rust的Result<T>转换为C的int
    • Err(e) => e.to_errno():错误码映射

这是Rust/C互操作的经典模式

C kernel → extern "C" fn → unsafe转换 → 安全Rust trait方法 → Result → C错误码

代码分析5:Nova驱动对比

Nova是另一个Rust GPU驱动(Nvidia GSP),结构与Tyr类似。

文件:drivers/gpu/drm/nova/driver.rs(部分)

#[vtable]
impl drm::Driver for NovaDriver {
    type Data = NovaData;
    type File = File;
    type Object = gem::Object<NovaObject>;

    const INFO: drm::DriverInfo = INFO;

    kernel::declare_drm_ioctls! {
        (NOVA_GETPARAM, drm_nova_getparam, ioctl::RENDER_ALLOW, File::get_param),
        (NOVA_GEM_CREATE, drm_nova_gem_create, ioctl::AUTH | ioctl::RENDER_ALLOW, File::gem_create),
        (NOVA_GEM_INFO, drm_nova_gem_info, ioctl::AUTH | ioctl::RENDER_ALLOW, File::gem_info),
    }
}

declare_drm_ioctls!宏分析

// 宏展开后(简化版)
const IOCTLS: &'static [drm::ioctl::DrmIoctlDescriptor] = &[
    drm::ioctl::DrmIoctlDescriptor {
        cmd: drm::ioctl::IOWR::<drm_nova_getparam>(DRM_COMMAND_BASE + 0),
        flags: ioctl::RENDER_ALLOW,
        func: nova_get_param_wrapper,  // 自动生成的C包装器
    },
    // ...
];

自动生成的工作

  1. 计算ioctl号(_IOWR宏)
  2. 生成C→Rust的包装函数
  3. 类型安全检查(编译时)

对比C版本(手动):

#define DRM_NOVA_GETPARAM 0x00
#define DRM_IOCTL_NOVA_GETPARAM \
    DRM_IOWR(DRM_COMMAND_BASE + DRM_NOVA_GETPARAM, struct drm_nova_getparam)

static const struct drm_ioctl_desc nova_ioctls[] = {
    DRM_IOCTL_DEF_DRV(NOVA_GETPARAM, nova_get_param, DRM_RENDER_ALLOW),
    // 魔数0x00容易重复或冲突
};

Rust的宏:


为何上游代码如此精简?GPUVM抽象缺失

回到最核心的问题:为何Tyr上游只能查询GPU信息,无法启动MCU?

Commit message的关键解释3

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.

技术分解

启动MCU需要什么?

  1. 分配GPU内存:存放MCU固件(数百KB)
  2. 映射到GPU虚拟地址:MCU通过VA访问内存
  3. 配置MCU寄存器:设置入口地址
  4. 启动MCU:发送启动命令

当前Tyr能做什么?

GPUVM抽象是什么?

C实现drivers/gpu/drm/drm_gpuvm.c):

/**
 * DOC: Overview
 *
 * The GPU VA Manager, represented by struct drm_gpuvm, keeps track of a
 * GPU's virtual address (VA) space and manages the corresponding virtual
 * mappings represented by &drm_gpuva objects.
 *
 * The DRM GPUVM tracks GPU VA space with &drm_gpuva objects backed by a
 * &drm_gem_object representing the actual memory backing the VA range.
 */
struct drm_gpuvm {
    struct drm_gem_object *r_obj;
    struct drm_device *drm;
    const char *name;

    struct rb_root_cached rb;  // 红黑树,存储VA映射
    // ...
};

Rust需要什么?

// 理想的GPUVM Rust API(概念性)
pub struct GpuVm<T: drm::Driver> {
    inner: Opaque<bindings::drm_gpuvm>,
    _phantom: PhantomData<T>,
}

impl<T: drm::Driver> GpuVm<T> {
    /// 映射GEM对象到GPU虚拟地址
    pub fn map(
        &self,
        gem_obj: &gem::Object<...>,
        va: u64,
        size: usize,
    ) -> Result<GpuVa> {
        // 调用C的drm_gpuva_insert()
    }

    /// 取消映射
    pub fn unmap(&self, va: &GpuVa) -> Result {
        // 调用C的drm_gpuva_remove()
    }
}

问题

Alice Ryhl的工作

根据新闻报道和commit message,Alice Ryhl正在开发GPUVM的Rust抽象,基于Asahi Lina的前期工作。

挑战

  1. 生命周期管理:GEM对象和VA映射的关系
  2. 锁顺序:避免死锁(C代码有隐式锁顺序)
  3. 红黑树抽象:Rust需要安全的树操作

这是高难度的内核Rust工作,需要深入理解C实现和Rust所有权模型。


技术洞察:从Tyr学到的经验

1. 类型状态模式的威力

电源调节器示例

pub struct Regulator<S: State> {
    inner: *mut bindings::regulator,
    _state: PhantomData<S>,
}

pub struct Enabled;
pub struct Disabled;

impl Regulator<Disabled> {
    pub fn enable(self) -> Result<Regulator<Enabled>> {
        // unsafe调用C API
        // 转换到Enabled状态
    }
}

impl Regulator<Enabled> {
    pub fn set_voltage(&self, min_uV: i32, max_uV: i32) -> Result {
        // 只有Enabled状态才能设置电压
    }

    pub fn disable(self) -> Result<Regulator<Disabled>> {
        // 转换回Disabled状态
    }
}

// 编译错误:Disabled状态没有set_voltage方法
let reg = Regulator::<Disabled>::get(...)?;
reg.set_voltage(1000000, 1000000)?;  // ❌ 编译失败!

// 正确用法
let reg = reg.enable()?;  // 转换到Enabled
reg.set_voltage(1000000, 1000000)?;  // ✅ 编译通过

优势

C中完全没有这种保证:

struct regulator *reg = regulator_get(...);
// 忘记enable
regulator_set_voltage(reg, 1000000, 1000000);  // 运行时错误或崩溃!

2. RAII消除资源泄漏

时钟管理示例

{
    let clk = Clk::get(dev, Some(c_str!("core")))?;
    clk.prepare_enable()?;

    do_work()?;  // 即使这里失败提前返回

    // clk离开作用域,自动调用Drop
} // <- 这里自动disable+unprepare

Drop trait实现(简化):

impl Drop for Clk {
    fn drop(&mut self) {
        unsafe {
            bindings::clk_disable_unprepare(self.inner);
        }
    }
}

C版本的问题

ret = clk_prepare_enable(clk);
if (ret)
    return ret;

ret = do_work();
if (ret) {
    // 忘记cleanup!
    return ret;  // 时钟泄漏
}

clk_disable_unprepare(clk);  // 只有成功路径执行

统计数据(来自前文):

3. 错误传播的简洁性

Rust的?运算符

fn initialize() -> Result {
    let clk = Clk::get(dev, Some(c_str!("core")))?;  // 失败则返回
    let reg = Regulator::get(dev, c_str!("mali"))?;  // 失败则返回
    let iomem = iomap()?;  // 失败则返回

    // 全部成功才继续
    Ok(())
}

C版本

int initialize(void) {
    clk = clk_get(dev, "core");
    if (IS_ERR(clk)) {
        ret = PTR_ERR(clk);
        goto err_clk;
    }

    reg = regulator_get(dev, "mali");
    if (IS_ERR(reg)) {
        ret = PTR_ERR(reg);
        goto err_reg;
    }

    iomem = ioremap(...);
    if (!iomem) {
        ret = -ENOMEM;
        goto err_iomem;
    }

    return 0;

err_iomem:
    regulator_put(reg);
err_reg:
    clk_put(clk);
err_clk:
    return ret;
}

差异

4. FFI安全边界的明确化

Tyr代码中,所有unsafe都在特定位置

  1. 寄存器读写regs::XXX.read()内部
  2. C结构体转换as_ref()方法
  3. 引用计数操作drm_gem_object_get/put

驱动代码本身几乎全是安全Rust

// drivers/gpu/drm/tyr/driver.rs - probe函数
// 没有任何unsafe!
fn probe(pdev: &platform::Device<Core>, ...) -> Result<Pin<KBox<Self>>> {
    let core_clk = Clk::get(pdev.as_ref(), Some(c_str!("core")))?;
    core_clk.prepare_enable()?;
    // ... 全部安全代码
}

unsafe集中在抽象层

// rust/kernel/drm/gem/mod.rs
unsafe impl<T: IntoGEMObject> AlwaysRefCounted for T {
    fn inc_ref(&self) {
        unsafe { bindings::drm_gem_object_get(self.as_raw()) };
        // ^^^ unsafe在这里,驱动无需接触
    }
}

这是Rust在内核的核心价值


与已有Blog的体系关联

Blog1:Rust in the Linux Kernel - Reality Check1

该文关注

本文补充

Blog2:Rust and Linux Kernel ABI Stability2

该文关注

本文补充

形成的知识体系

Blog1 (宏观) → Blog2 (ABI) → Blog3 (代码实战)
     ↓              ↓                ↓
  数据统计      技术保证        具体实现
  政策争议      接口规范        挑战分析
  整体趋势      系统设计        代码细节

三篇文章从不同角度完整覆盖了Rust在Linux内核的状态。


未来展望:Tyr的Roadmap

短期(2026年上半年)

依赖的抽象层(根据commit message):

  1. ✅ GEM shmem(Lyude Paul负责)
  2. ✅ GPUVM(Alice Ryhl负责)
  3. ✅ io-pgtable(Alice Ryhl负责)

期望效果(原文)3

Once we can handle those items, we expect to quickly become able to boot the GPU firmware and then progress unhindered until it is time to discuss job submission.

中期(2026-2027)

整合Nova的贡献

完善功能

长期(2027+)

JobQueue架构


结论:代码层面的洞察

通过解剖Tyr项目的实际代码,我们得到了超越宏观讨论的具体认识

技术层面

  1. Rust的类型系统价值
    • 类型状态模式(Regulator
    • 编译时状态机(设备初始化)
    • RAII资源管理(时钟、锁)
  2. FFI互操作的实践
    • extern "C"的C ABI桥接
    • #[repr(C)]的ABI兼容
    • SAFETY注释的严格论证
  3. 抽象层的分层设计
    • 驱动层:安全Rust
    • 抽象层:处理unsafe
    • C层:bindings自动生成

挑战层面

  1. 基础设施缺失的实际影响
    • GPUVM抽象→无法启动MCU
    • read_poll_timeout()缺失→用固定延迟
    • 工具链不成熟→Send/Sync workaround
  2. 上游策略的务实性
    • 不再C+Rust混合(失败过)
    • 分阶段上游(避免下游分叉)
    • 与Nova/rvkms协同演进

对开发者的启示

  1. 学习路径
    • 先掌握Rust基础(所有权、生命周期)
    • 学习内核概念(DRM、GEM、GPUVM)
    • 阅读实际代码(Tyr、Nova、Asahi)
  2. 贡献机会
    • GPUVM抽象开发
    • 其他DRM抽象补全
    • Tyr驱动功能实现
  3. 技术趋势
    • Rust在DRM子系统的采用不可逆
    • 基础设施建设是当前瓶颈
    • 2027年可能禁止新C驱动4

Rust在Linux内核已经从”实验”进入”生产”,Tyr项目是这一转变的代码级见证。

参考资料

代码仓库

相关项目

  1. Rust in the Linux Kernel: A Reality Check from Code to Controversy - 本系列第一篇  2 3 4

  2. Rust and Linux Kernel ABI Stability: A Technical Deep Dive - 本系列第二篇  2

  3. Linux Kernel Git Commit cf4fd52e3236 - “rust: drm: Introduce the Tyr driver for Arm Mali GPUs”, Daniel Almeida, 2025-09-10. 可通过git show cf4fd52e3236查看完整commit message。  2 3

  4. Dave Airlie在2025 Maintainers Summit的声明,报道来源: 

My Github Page: https://github.com/liweinan

Powered by Jekyll and Theme by solid

If you have any question want to ask or find bugs regarding with my blog posts, please report it here:
https://github.com/liweinan/liweinan.github.io/issues