| // 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); | |
| } |