| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2026, Advanced Micro Devices, Inc. |
| */ |
| |
| #include <drm/drm_mm.h> |
| #include <drm/drm_prime.h> |
| |
| #include "amdxdna_cbuf.h" |
| #include "amdxdna_pci_drv.h" |
| |
| /* |
| * Carveout memory is a chunk of memory which is physically contiguous and |
| * is reserved during early boot time. There is only one chunk of such memory |
| * per device. Once available, all BOs accessible from device should be |
| * allocated from this memory. This is a platform debug/bringup feature. |
| */ |
| struct amdxdna_carveout { |
| u64 addr; |
| u64 size; |
| struct drm_mm mm; |
| struct mutex lock; /* protect mm */ |
| }; |
| |
| bool amdxdna_use_carveout(struct amdxdna_dev *xdna) |
| { |
| return !!xdna->carveout; |
| } |
| |
| void amdxdna_get_carveout_conf(struct amdxdna_dev *xdna, u64 *addr, u64 *size) |
| { |
| if (amdxdna_use_carveout(xdna)) { |
| *addr = xdna->carveout->addr; |
| *size = xdna->carveout->size; |
| } else { |
| *addr = 0; |
| *size = 0; |
| } |
| } |
| |
| int amdxdna_carveout_init(struct amdxdna_dev *xdna, u64 carveout_addr, u64 carveout_size) |
| { |
| struct amdxdna_carveout *carveout; |
| |
| /* Only allow carveout memory to be set up once. */ |
| if (amdxdna_use_carveout(xdna)) { |
| XDNA_ERR(xdna, "Carveout memory has already been set up."); |
| return -EBUSY; |
| } |
| |
| carveout = kzalloc_obj(*carveout); |
| if (!carveout) |
| return -ENOMEM; |
| |
| carveout->addr = carveout_addr; |
| carveout->size = carveout_size; |
| mutex_init(&carveout->lock); |
| drm_mm_init(&carveout->mm, carveout->addr, carveout->size); |
| |
| xdna->carveout = carveout; |
| XDNA_INFO(xdna, "Use carveout mem: 0x%llx@0x%llx\n", carveout->size, carveout->addr); |
| return 0; |
| } |
| |
| void amdxdna_carveout_fini(struct amdxdna_dev *xdna) |
| { |
| struct amdxdna_carveout *carveout = xdna->carveout; |
| |
| if (!amdxdna_use_carveout(xdna)) |
| return; |
| |
| XDNA_INFO(xdna, "Cleanup carveout mem: 0x%llx@0x%llx\n", carveout->size, carveout->addr); |
| drm_mm_takedown(&carveout->mm); |
| mutex_destroy(&carveout->lock); |
| kfree(carveout); |
| xdna->carveout = NULL; |
| } |
| |
| struct amdxdna_cbuf_priv { |
| struct amdxdna_dev *xdna; |
| struct drm_mm_node node; |
| }; |
| |
| static struct sg_table *amdxdna_cbuf_map(struct dma_buf_attachment *attach, |
| enum dma_data_direction direction) |
| { |
| struct amdxdna_cbuf_priv *cbuf = attach->dmabuf->priv; |
| struct device *dev = attach->dev; |
| struct scatterlist *sgl, *sg; |
| int ret, n_entries, i; |
| struct sg_table *sgt; |
| dma_addr_t dma_addr; |
| size_t dma_size; |
| size_t max_seg; |
| |
| sgt = kzalloc_obj(*sgt); |
| if (!sgt) |
| return ERR_PTR(-ENOMEM); |
| |
| max_seg = min_t(size_t, UINT_MAX, dma_max_mapping_size(dev)); |
| n_entries = (cbuf->node.size + max_seg - 1) / max_seg; |
| sgl = kzalloc_objs(*sg, n_entries); |
| if (!sgl) { |
| ret = -ENOMEM; |
| goto free_sgt; |
| } |
| sg_init_table(sgl, n_entries); |
| sgt->orig_nents = n_entries; |
| sgt->nents = n_entries; |
| sgt->sgl = sgl; |
| |
| dma_size = cbuf->node.size; |
| dma_addr = dma_map_resource(dev, cbuf->node.start, dma_size, |
| direction, DMA_ATTR_SKIP_CPU_SYNC); |
| ret = dma_mapping_error(dev, dma_addr); |
| if (ret) { |
| pr_err("Failed to dma_map_resource carveout dma buf, ret %d\n", ret); |
| goto free_sgl; |
| } |
| |
| for_each_sgtable_dma_sg(sgt, sg, i) { |
| size_t len = min_t(size_t, max_seg, dma_size); |
| |
| sg_dma_address(sg) = dma_addr; |
| sg_dma_len(sg) = len; |
| dma_addr += len; |
| dma_size -= len; |
| } |
| |
| return sgt; |
| |
| free_sgl: |
| kfree(sgl); |
| free_sgt: |
| kfree(sgt); |
| return ERR_PTR(ret); |
| } |
| |
| static void amdxdna_cbuf_unmap(struct dma_buf_attachment *attach, |
| struct sg_table *sgt, |
| enum dma_data_direction direction) |
| { |
| dma_unmap_resource(attach->dev, sg_dma_address(sgt->sgl), |
| drm_prime_get_contiguous_size(sgt), direction, |
| DMA_ATTR_SKIP_CPU_SYNC); |
| sg_free_table(sgt); |
| kfree(sgt); |
| } |
| |
| static void amdxdna_cbuf_release(struct dma_buf *dbuf) |
| { |
| struct amdxdna_cbuf_priv *cbuf = dbuf->priv; |
| struct amdxdna_carveout *carveout; |
| |
| carveout = cbuf->xdna->carveout; |
| mutex_lock(&carveout->lock); |
| drm_mm_remove_node(&cbuf->node); |
| mutex_unlock(&carveout->lock); |
| |
| kfree(cbuf); |
| } |
| |
| static vm_fault_t amdxdna_cbuf_vm_fault(struct vm_fault *vmf) |
| { |
| struct vm_area_struct *vma = vmf->vma; |
| struct amdxdna_cbuf_priv *cbuf; |
| unsigned long pfn; |
| pgoff_t pgoff; |
| |
| cbuf = vma->vm_private_data; |
| pgoff = (vmf->address - vma->vm_start) >> PAGE_SHIFT; |
| pfn = (cbuf->node.start >> PAGE_SHIFT) + pgoff; |
| |
| return vmf_insert_pfn(vma, vmf->address, pfn); |
| } |
| |
| static const struct vm_operations_struct amdxdna_cbuf_vm_ops = { |
| .fault = amdxdna_cbuf_vm_fault, |
| }; |
| |
| static int amdxdna_cbuf_mmap(struct dma_buf *dbuf, struct vm_area_struct *vma) |
| { |
| struct amdxdna_cbuf_priv *cbuf = dbuf->priv; |
| |
| vma->vm_ops = &amdxdna_cbuf_vm_ops; |
| vma->vm_private_data = cbuf; |
| vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP); |
| |
| return 0; |
| } |
| |
| static int amdxdna_cbuf_vmap(struct dma_buf *dbuf, struct iosys_map *map) |
| { |
| struct amdxdna_cbuf_priv *cbuf = dbuf->priv; |
| void *kva; |
| |
| kva = memremap(cbuf->node.start, cbuf->node.size, MEMREMAP_WB); |
| if (!kva) { |
| pr_err("Failed to vmap carveout dma buf\n"); |
| return -ENOMEM; |
| } |
| |
| iosys_map_set_vaddr(map, kva); |
| return 0; |
| } |
| |
| static void amdxdna_cbuf_vunmap(struct dma_buf *dbuf, struct iosys_map *map) |
| { |
| memunmap(map->vaddr); |
| } |
| |
| static const struct dma_buf_ops amdxdna_cbuf_dmabuf_ops = { |
| .map_dma_buf = amdxdna_cbuf_map, |
| .unmap_dma_buf = amdxdna_cbuf_unmap, |
| .release = amdxdna_cbuf_release, |
| .mmap = amdxdna_cbuf_mmap, |
| .vmap = amdxdna_cbuf_vmap, |
| .vunmap = amdxdna_cbuf_vunmap, |
| }; |
| |
| static int amdxdna_cbuf_clear(struct dma_buf *dbuf) |
| { |
| struct iosys_map vmap = IOSYS_MAP_INIT_VADDR(NULL); |
| |
| dma_buf_vmap(dbuf, &vmap); |
| if (!vmap.vaddr) |
| return -EFAULT; |
| |
| memset(vmap.vaddr, 0, dbuf->size); |
| dma_buf_vunmap(dbuf, &vmap); |
| |
| return 0; |
| } |
| |
| struct dma_buf *amdxdna_get_cbuf(struct drm_device *dev, size_t size, u64 alignment) |
| { |
| struct amdxdna_dev *xdna = to_xdna_dev(dev); |
| DEFINE_DMA_BUF_EXPORT_INFO(exp_info); |
| struct amdxdna_carveout *carveout; |
| struct amdxdna_cbuf_priv *cbuf; |
| struct dma_buf *dbuf; |
| int ret; |
| |
| cbuf = kzalloc_obj(*cbuf); |
| if (!cbuf) |
| return ERR_PTR(-ENOMEM); |
| cbuf->xdna = xdna; |
| |
| carveout = xdna->carveout; |
| mutex_lock(&carveout->lock); |
| ret = drm_mm_insert_node_generic(&carveout->mm, &cbuf->node, size, |
| alignment, 0, DRM_MM_INSERT_BEST); |
| mutex_unlock(&carveout->lock); |
| if (ret) |
| goto free_cbuf; |
| |
| exp_info.size = size; |
| exp_info.ops = &amdxdna_cbuf_dmabuf_ops; |
| exp_info.priv = cbuf; |
| exp_info.flags = O_RDWR; |
| dbuf = dma_buf_export(&exp_info); |
| if (IS_ERR(dbuf)) { |
| ret = PTR_ERR(dbuf); |
| goto remove_node; |
| } |
| |
| ret = amdxdna_cbuf_clear(dbuf); |
| if (ret) { |
| dma_buf_put(dbuf); |
| goto out; |
| } |
| return dbuf; |
| |
| remove_node: |
| drm_mm_remove_node(&cbuf->node); |
| free_cbuf: |
| kfree(cbuf); |
| out: |
| return ERR_PTR(ret); |
| } |