Hi,
On Mon, Oct 26, 2026 at 12:40:02PM +0100, Konrad Dybcio wrote:
> From: Konrad Dybcio <[email protected]>
>
> Much of the VM handling code is a headache to read and maintain.
> There's a soup of mixed-signedness/size types which need to be compared
> and added and getting that wrong in C is very easy.
>
> The "(range + obj_offset) > obj->size" check could wrap for a large
> obj_offset, which let an out-of-range map be accepted and then mapped
> as nothing. Oxidize it and use checked_add() to fix that.
>
> This commit is a "get the foot in the door" type change to make sure
> that folks building kernels for Qualcomm SoCs have their Rust setup
> ready. It may/should be extended to the wider mm code once the
> appropriate abstractions are finalized.
>
> Worth noting, the C UERR() macro expands to dev_dbg() and the Rust
> implementation for that currently doesn't support dynamic_debug.
>
> The DRM_BRIDGE dependency is dropped because including RUST exposed
> some spaghetti dependencies ending at DEBUGFS and it's enabled anyway.
>
> Signed-off-by: Konrad Dybcio <[email protected]>
> ---
> (if this is rejected we may just patch the C code).
>
> Notably, this will regress some ancient configurations:
> -- arch/arm/Kconfig
> select HAVE_RUST if CPU_LITTLE_ENDIAN && CPU_32v7 && !KASAN
>
> Smoke-tested on Glymur
> ---
> drivers/gpu/drm/msm/Kconfig | 2 +-
> drivers/gpu/drm/msm/Makefile | 1 +
> drivers/gpu/drm/msm/msm_gem_vma.c | 66 +++------------
> drivers/gpu/drm/msm/msm_vm_bind.h | 16 ++++
> drivers/gpu/drm/msm/msm_vm_bind.rs | 160
> +++++++++++++++++++++++++++++++++++++
> rust/uapi/uapi_helper.h | 1 +
> 6 files changed, 188 insertions(+), 58 deletions(-)
>
[…]
> diff --git a/drivers/gpu/drm/msm/msm_vm_bind.rs
> b/drivers/gpu/drm/msm/msm_vm_bind.rs
> new file mode 100644
> index 000000000000..164208e0a1b4
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/msm_vm_bind.rs
> @@ -0,0 +1,160 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +//! VM_BIND operation validation for the MSM driver.
> +
> +use kernel::{
> + bindings,
> + device::Device,
> + error::code::EINVAL,
> + prelude::*,
> + uapi, //
> +};
> +
> +/// Validates a single VM_BIND operation.
> +///
> +/// Returns 0 if the operation is valid. Otherwise logs the reason and
> returns
> +/// `-EINVAL`.
> +///
> +/// `page_size` is the granularity that `iova`, `obj_offset` and `range`
> must be
> +/// aligned to. It is the GPU's alignment rather than the CPU's, but no
> known qcom
> +/// SMMU lacks support for the CPU's smallest page size.
> +/// `range_valid` is the result of `drm_gpuvm_range_valid()` for this
> operation.
> +/// `prr_supported` is only consulted for `MSM_VM_BIND_OP_MAP_NULL`.
> +///
> +/// # Safety
> +///
> +/// `dev` must point to a live `struct device`.
> +#[no_mangle]
> +pub unsafe extern "C" fn msm_vm_bind_check_op(
> + dev: *mut bindings::device,
> + op: u32,
> + handle: u32,
> + obj_offset: u64,
> + iova: u64,
> + range: u64,
> + flags: u32,
> + page_size: u64,
> + range_valid: bool,
> + prr_supported: bool,
> +) -> c_int {
> + // SAFETY: The caller guarantees that `dev` is a live `struct device`.
> + let dev: &Device = unsafe { Device::from_raw(dev) };
> +
> + match check_op(
> + dev,
> + op,
> + handle,
> + obj_offset,
> + iova,
> + range,
> + flags,
> + page_size,
> + range_valid,
> + prr_supported,
> + ) {
> + Ok(()) => 0,
> + Err(e) => e.to_errno(),
> + }
I think we usually use kernel::error::from_result() instead of a match
block in this situation.
> +}
> +
> +fn check_op(
> + dev: &Device,
> + op: u32,
> + handle: u32,
> + obj_offset: u64,
> + iova: u64,
> + range: u64,
> + flags: u32,
> + page_size: u64,
> + range_valid: bool,
> + prr_supported: bool,
> +) -> Result {
> + let aligned = |addr: u64| page_size.is_power_of_two() && (addr &
> (page_size - 1)) == 0;
> +
> + if flags & !uapi::MSM_VM_BIND_OP_FLAGS != 0 {
> + dev_dbg!(dev, "invalid flags: {:x}\n", flags);
> + return Err(EINVAL);
> + }
> +
> + if !aligned(iova) {
> + dev_dbg!(dev, "invalid address: {:016x}\n", iova);
> + return Err(EINVAL);
> + }
> +
> + if !aligned(obj_offset) {
> + dev_dbg!(dev, "invalid bo_offset: {:016x}\n", obj_offset);
> + return Err(EINVAL);
> + }
> +
> + if !aligned(range) {
> + dev_dbg!(dev, "invalid range: {:016x}\n", range);
> + return Err(EINVAL);
> + }
> +
> + if !range_valid {
> + dev_dbg!(dev, "invalid range: {:016x}, {:016x}\n", iova, range);
> + return Err(EINVAL);
> + }
> +
> + // MAP must name a handle; UNMAP and MAP_NULL must not.
> + match op {
> + uapi::MSM_VM_BIND_OP_MAP => {
> + if handle == 0 {
> + dev_dbg!(dev, "invalid handle\n");
> + return Err(EINVAL);
> + }
> + }
> + uapi::MSM_VM_BIND_OP_UNMAP | uapi::MSM_VM_BIND_OP_MAP_NULL => {
> + if handle != 0 {
> + dev_dbg!(dev, "handle must be zero\n");
> + return Err(EINVAL);
> + }
> + }
> + _ => {
> + dev_dbg!(dev, "invalid op: {}\n", op);
> + return Err(EINVAL);
> + }
> + }
> +
> + if op == uapi::MSM_VM_BIND_OP_MAP_NULL && !prr_supported {
> + dev_dbg!(dev, "PRR not supported\n");
> + return Err(EINVAL);
> + }
> +
> + Ok(())
> +}
> +
> +/// Checks that `[obj_offset, obj_offset + range)` lies within an object of
> +/// `obj_size` bytes.
> +///
> +/// Returns 0 if it does. Otherwise logs the reason and returns `-EINVAL`.
> +///
> +/// # Safety
> +///
> +/// `dev` must point to a live `struct device`.
> +#[no_mangle]
> +pub unsafe extern "C" fn msm_vm_bind_check_bo_range(
> + dev: *mut bindings::device,
> + obj_offset: u64,
> + range: u64,
> + obj_size: u64,
> +) -> c_int {
> + // SAFETY: The caller guarantees that `dev` is a live `struct device`.
> + let dev: &Device = unsafe { Device::from_raw(dev) };
> +
> + let fits = obj_offset
> + .checked_add(range)
> + .is_some_and(|end| end <= obj_size);
> +
> + if fits {
> + return 0;
> + }
> +
> + dev_dbg!(
> + dev,
> + "invalid range: {:016x} + {:016x} > {:016x}\n",
> + range,
> + obj_offset,
> + obj_size
> + );
> + EINVAL.to_errno()
Same here, the whole content of this function could be wrapped in a
from_result().
> +}
[…]
Thanks,
--
Link Mauve