// SPDX-License-Identifier: MIT /* * Copyright © 2025 Intel Corporation */ #include "xe_mmio_gem.h" #include #include #include #include "xe_device_types.h" /** * DOC: Exposing MMIO regions to userspace * * In certain cases, the driver may allow userspace to mmap a portion of the hardware registers. * * This can be done as follows: * 1. Call xe_mmio_gem_create() to create a GEM object with an mmap-able fake offset. * 2. Use xe_mmio_gem_mmap_offset() on the created GEM object to retrieve the fake offset. * 3. Provide the fake offset to userspace. * 4. Userspace can call mmap with the fake offset. The length provided to mmap * must match the size of the GEM object. * 5. When the region is no longer needed, call xe_mmio_gem_destroy() to release the GEM object. * * NOTE: The exposed MMIO region must be page-aligned with regards to its BAR offset and size. * * WARNING: Exposing MMIO regions to userspace can have security and stability implications. * Make sure not to expose any sensitive registers. */ static void xe_mmio_gem_free(struct drm_gem_object *); static int xe_mmio_gem_mmap(struct drm_gem_object *, struct vm_area_struct *); static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *); struct xe_mmio_gem { struct drm_gem_object base; phys_addr_t phys_addr; }; static const struct vm_operations_struct vm_ops = { .open = drm_gem_vm_open, .close = drm_gem_vm_close, .fault = xe_mmio_gem_vm_fault, }; static const struct drm_gem_object_funcs xe_mmio_gem_funcs = { .free = xe_mmio_gem_free, .mmap = xe_mmio_gem_mmap, .vm_ops = &vm_ops, }; static inline struct xe_mmio_gem *to_xe_mmio_gem(struct drm_gem_object *obj) { return container_of(obj, struct xe_mmio_gem, base); } /** * xe_mmio_gem_create - Expose an MMIO region to userspace * @xe: The xe device * @file: DRM file descriptor * @phys_addr: Start of the exposed MMIO region * @size: The size of the exposed MMIO region * * This function creates a GEM object that exposes an MMIO region with an mmap-able * fake offset. * * See: "Exposing MMIO regions to userspace" */ struct xe_mmio_gem *xe_mmio_gem_create(struct xe_device *xe, struct drm_file *file, phys_addr_t phys_addr, size_t size) { struct xe_mmio_gem *obj; struct drm_gem_object *base; int err; if ((phys_addr % PAGE_SIZE != 0) || (size % PAGE_SIZE != 0)) return ERR_PTR(-EINVAL); obj = kzalloc(sizeof(*obj), GFP_KERNEL); if (!obj) return ERR_PTR(-ENOMEM); base = &obj->base; base->funcs = &xe_mmio_gem_funcs; obj->phys_addr = phys_addr; drm_gem_private_object_init(&xe->drm, base, size); err = drm_gem_create_mmap_offset(base); if (err) goto free_gem; err = drm_vma_node_allow(&base->vma_node, file); if (err) goto free_gem; return obj; free_gem: xe_mmio_gem_free(base); return ERR_PTR(err); } /** * xe_mmio_gem_mmap_offset - Return the mmap-able fake offset * @gem: the GEM object created with xe_mmio_gem_create() * * This function returns the mmap-able fake offset allocated during * xe_mmio_gem_create(). * * See: "Exposing MMIO regions to userspace" */ u64 xe_mmio_gem_mmap_offset(struct xe_mmio_gem *gem) { return drm_vma_node_offset_addr(&gem->base.vma_node); } static void xe_mmio_gem_free(struct drm_gem_object *base) { struct xe_mmio_gem *obj = to_xe_mmio_gem(base); drm_gem_object_release(base); kfree(obj); } /** * xe_mmio_gem_destroy - Destroy the GEM object that exposes an MMIO region * @gem: the GEM object to destroy * * This function releases resources associated with the GEM object created by * xe_mmio_gem_create(). * * See: "Exposing MMIO regions to userspace" */ void xe_mmio_gem_destroy(struct xe_mmio_gem *gem) { xe_mmio_gem_free(&gem->base); } static int xe_mmio_gem_mmap(struct drm_gem_object *base, struct vm_area_struct *vma) { if (vma->vm_end - vma->vm_start != base->size) return -EINVAL; if ((vma->vm_flags & VM_SHARED) == 0) return -EINVAL; /* Set vm_pgoff (used as a fake buffer offset by DRM) to 0 */ vma->vm_pgoff = 0; vma->vm_page_prot = pgprot_noncached(vm_get_page_prot(vma->vm_flags)); vm_flags_set(vma, VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP | VM_DONTCOPY | VM_NORESERVE); /* Defer actual mapping to the fault handler. */ return 0; } static void xe_mmio_gem_release_dummy_page(struct drm_device *dev, void *res) { __free_page((struct page *)res); } static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_area_struct *vma) { struct drm_gem_object *base = vma->vm_private_data; struct drm_device *dev = base->dev; vm_fault_t ret = VM_FAULT_NOPAGE; struct page *page; unsigned long pfn; unsigned long i; page = alloc_page(GFP_KERNEL | __GFP_ZERO); if (!page) return VM_FAULT_OOM; if (drmm_add_action_or_reset(dev, xe_mmio_gem_release_dummy_page, page)) return VM_FAULT_OOM; pfn = page_to_pfn(page); /* Map the entire VMA to the same dummy page */ for (i = 0; i < base->size; i += PAGE_SIZE) { unsigned long addr = vma->vm_start + i; ret = vmf_insert_pfn(vma, addr, pfn); if (ret & VM_FAULT_ERROR) break; } return ret; } static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) { struct vm_area_struct *vma = vmf->vma; struct drm_gem_object *base = vma->vm_private_data; struct xe_mmio_gem *obj = to_xe_mmio_gem(base); struct drm_device *dev = base->dev; vm_fault_t ret = VM_FAULT_NOPAGE; unsigned long i; int idx; if (!drm_dev_enter(dev, &idx)) { /* * Provide a dummy page to avoid SIGBUS for events such as hot-unplug. * This gives the userspace the option to recover instead of crashing. * It is assumed the userspace will receive the notification via some * other channel (e.g. drm uevent). */ return xe_mmio_gem_vm_fault_dummy_page(vma); } for (i = 0; i < base->size; i += PAGE_SIZE) { unsigned long addr = vma->vm_start + i; unsigned long phys_addr = obj->phys_addr + i; ret = vmf_insert_pfn(vma, addr, PHYS_PFN(phys_addr)); if (ret & VM_FAULT_ERROR) break; } drm_dev_exit(idx); return ret; }