mirror of
https://github.com/torvalds/linux.git
synced 2026-09-28 12:02:03 +02:00
Add support for allocating buffers from reserved carveout memory when IOMMU is not available. This is useful during debugging or bring-up. In this configuration, the device uses physical addresses and does not support scatter-gather lists, requiring physically contiguous buffers. Implement carveout-backed allocation and integrate it into buffer management to support operation in physical address mode. Signed-off-by: Max Zhen <max.zhen@amd.com> Reviewed-by: Mario Limonciello (AMD) <superm1@kernel.org> Signed-off-by: Lizhi Hou <lizhi.hou@amd.com> Link: https://patch.msgid.link/20260427170949.2666601-1-lizhi.hou@amd.com
281 lines
6.4 KiB
C
281 lines
6.4 KiB
C
// 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);
|
|
}
|