#include <drm/drm_mm.h>
#include <drm/drm_prime.h>
#include "amdxdna_cbuf.h"
#include "amdxdna_pci_drv.h"
struct amdxdna_carveout {
u64 addr;
u64 size;
struct drm_mm mm;
struct mutex lock;
};
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;
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);
}