blob: 32c86f2e924566e4e36c502301e9deadd44a9353 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* GXP DMA implemented via IOMMU.
*
* Copyright (C) 2021 Google LLC
*/
#include <linux/bits.h>
#include <linux/dma-mapping.h>
#include <linux/iommu.h>
#include <linux/platform_device.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <gcip/gcip-iommu.h>
#include "gxp-config.h"
#include "gxp-dma.h"
#include "gxp-mailbox.h"
#include "gxp-mapping.h"
#include "gxp-pm.h"
#include "gxp.h"
#include "mobile-soc.h"
#include <trace/events/gxp.h>
struct gxp_dma_iommu_manager {
struct gxp_dma_manager dma_mgr;
struct gcip_iommu_domain *default_domain;
};
/* Fault handler */
static int sysmmu_fault_handler(struct iommu_fault *fault, void *token)
{
struct gxp_dev *gxp = (struct gxp_dev *)token;
switch (fault->type) {
case IOMMU_FAULT_DMA_UNRECOV:
dev_err(gxp->dev, "Unrecoverable IOMMU fault!\n");
break;
case IOMMU_FAULT_PAGE_REQ:
dev_err(gxp->dev, "IOMMU page request fault!\n");
break;
default:
dev_err(gxp->dev, "Unexpected IOMMU fault type (%d)\n",
fault->type);
return -EAGAIN;
}
/*
* Normally the iommu driver should fill out the `event` struct for
* unrecoverable errors, and the `prm` struct for page request faults.
* The SysMMU driver, instead, always fills out the `event` struct.
*
* Note that the `fetch_addr` and `perm` fields are never filled out,
* so we skip printing them.
*/
dev_err(gxp->dev, "reason = %08X\n", fault->event.reason);
dev_err(gxp->dev, "flags = %08X\n", fault->event.flags);
dev_err(gxp->dev, "pasid = %08X\n", fault->event.pasid);
dev_err(gxp->dev, "addr = %llX\n", fault->event.addr);
// Tell the IOMMU driver to carry on
return -EAGAIN;
}
#if GXP_HAS_LAP
/* No need to map CSRs when local access path exists. */
#define gxp_map_csrs(...) 0
#define gxp_unmap_csrs(...)
#else /* !GXP_HAS_LAP */
#define SYNC_BARRIERS_SIZE 0x100000
static int gxp_map_csrs(struct gxp_dev *gxp, struct gcip_iommu_domain *gdomain,
struct gxp_mapped_resource *regs)
{
int ret = gcip_iommu_map(gdomain, GXP_IOVA_AURORA_TOP, gxp->regs.paddr, gxp->regs.size,
GCIP_MAP_FLAGS_DMA_RW);
if (ret)
return ret;
/*
* Firmware expects to access the sync barriers at a separate
* address, lower than the rest of the AURORA_TOP registers.
*/
ret = gcip_iommu_map(gdomain, GXP_IOVA_SYNC_BARRIERS,
gxp->regs.paddr + GXP_IOVA_SYNC_BARRIERS, SYNC_BARRIERS_SIZE,
GCIP_MAP_FLAGS_DMA_RW);
if (ret) {
gcip_iommu_unmap(gdomain, GXP_IOVA_AURORA_TOP, gxp->regs.size);
return ret;
}
return 0;
}
static void gxp_unmap_csrs(struct gxp_dev *gxp, struct gcip_iommu_domain *gdomain,
struct gxp_mapped_resource *regs)
{
gcip_iommu_unmap(gdomain, GXP_IOVA_SYNC_BARRIERS, SYNC_BARRIERS_SIZE);
gcip_iommu_unmap(gdomain, GXP_IOVA_AURORA_TOP, gxp->regs.size);
}
#endif /* GXP_HAS_LAP */
/* gxp-dma.h Interface */
struct gcip_iommu_domain *gxp_iommu_get_domain_for_dev(struct gxp_dev *gxp)
{
struct gcip_iommu_domain *gdomain = gxp->default_domain;
if (IS_ERR_OR_NULL(gdomain)) {
gdomain = gcip_iommu_get_domain_for_dev(gxp->dev);
if (IS_ERR_OR_NULL(gdomain))
return gdomain;
gxp->default_domain = gdomain;
}
return gdomain;
}
int gxp_dma_init(struct gxp_dev *gxp)
{
struct gxp_dma_iommu_manager *mgr;
int ret;
/* Remove the limit of DMA ranges. */
ret = dma_set_mask_and_coherent(gxp->dev, DMA_BIT_MASK(64));
if (ret) {
dev_err(gxp->dev, "Failed to set DMA mask\n");
return ret;
}
mgr = devm_kzalloc(gxp->dev, sizeof(*mgr), GFP_KERNEL);
if (!mgr)
return -ENOMEM;
mgr->default_domain = gxp_iommu_get_domain_for_dev(gxp);
if (IS_ERR(mgr->default_domain)) {
dev_err(gxp->dev, "Failed to find default IOMMU domain\n");
return PTR_ERR(mgr->default_domain);
}
if (iommu_register_device_fault_handler(gxp->dev, sysmmu_fault_handler,
gxp)) {
dev_err(gxp->dev, "Failed to register iommu fault handler\n");
return -EIO;
}
gxp->dma_mgr = &(mgr->dma_mgr);
return 0;
}
void gxp_dma_exit(struct gxp_dev *gxp)
{
if (iommu_unregister_device_fault_handler(gxp->dev))
dev_err(gxp->dev,
"Failed to unregister SysMMU fault handler\n");
}
#define EXT_TPU_MBX_SIZE 0x2000
void gxp_dma_init_default_resources(struct gxp_dev *gxp)
{
unsigned int core;
int i;
for (i = 0; i < GXP_NUM_MAILBOXES; i++)
gxp->mbx[i].daddr = GXP_IOVA_MAILBOX(i);
for (core = 0; core < GXP_NUM_CORES; core++)
gxp->fwbufs[core].daddr = GXP_IOVA_FIRMWARE(core);
}
int gxp_dma_domain_attach_device(struct gxp_dev *gxp, struct gcip_iommu_domain *gdomain,
uint core_list)
{
int pasid;
if (gdomain == gxp_iommu_get_domain_for_dev(gxp))
return 0;
pasid = gcip_iommu_domain_pool_attach_domain(gxp->domain_pool, gdomain);
if (pasid < 0) {
dev_err(gxp->dev, "Attach IOMMU domain failed: %d", pasid);
return pasid;
}
gxp_soc_activate_context(gxp, gdomain, core_list);
return 0;
}
void gxp_dma_domain_detach_device(struct gxp_dev *gxp, struct gcip_iommu_domain *gdomain,
uint core_list)
{
if (gdomain == gxp_iommu_get_domain_for_dev(gxp))
return;
gxp_soc_deactivate_context(gxp, gdomain, core_list);
gcip_iommu_domain_pool_detach_domain(gxp->domain_pool, gdomain);
}
int gxp_dma_map_core_resources(struct gxp_dev *gxp, struct gcip_iommu_domain *gdomain,
uint core_list, u8 slice_index)
{
int ret;
uint i;
if (!gxp_is_direct_mode(gxp))
return 0;
ret = gxp_map_csrs(gxp, gdomain, &gxp->regs);
if (ret)
goto err;
for (i = 0; i < GXP_NUM_CORES; i++) {
if (!(BIT(i) & core_list))
continue;
ret = gcip_iommu_map(gdomain, gxp->mbx[i].daddr,
gxp->mbx[i].paddr + MAILBOX_DEVICE_INTERFACE_OFFSET,
gxp->mbx[i].size, GCIP_MAP_FLAGS_DMA_RW);
if (ret)
goto err;
}
/* Only map the TPU mailboxes if they were found on probe */
if (gxp->tpu_dev.mbx_paddr) {
for (i = 0; i < GXP_NUM_CORES; i++) {
if (!(BIT(i) & core_list))
continue;
ret = gcip_iommu_map(gdomain, GXP_IOVA_EXT_TPU_MBX + i * EXT_TPU_MBX_SIZE,
gxp->tpu_dev.mbx_paddr + i * EXT_TPU_MBX_SIZE,
EXT_TPU_MBX_SIZE, GCIP_MAP_FLAGS_DMA_RW);
if (ret)
goto err;
}
}
return ret;
err:
/*
* Attempt to unmap all resources.
* Any resource that hadn't been mapped yet will cause `iommu_unmap()`
* to return immediately, so its safe to try to unmap everything.
*/
gxp_dma_unmap_core_resources(gxp, gdomain, core_list);
return ret;
}
void gxp_dma_unmap_core_resources(struct gxp_dev *gxp, struct gcip_iommu_domain *gdomain,
uint core_list)
{
uint i;
if (!gxp_is_direct_mode(gxp))
return;
/* Only unmap the TPU mailboxes if they were found on probe */
if (gxp->tpu_dev.mbx_paddr) {
for (i = 0; i < GXP_NUM_CORES; i++) {
if (!(BIT(i) & core_list))
continue;
gcip_iommu_unmap(gdomain, GXP_IOVA_EXT_TPU_MBX + i * EXT_TPU_MBX_SIZE,
EXT_TPU_MBX_SIZE);
}
}
for (i = 0; i < GXP_NUM_CORES; i++) {
if (!(BIT(i) & core_list))
continue;
gcip_iommu_unmap(gdomain, gxp->mbx[i].daddr, gxp->mbx[i].size);
}
gxp_unmap_csrs(gxp, gdomain, &gxp->regs);
}
static inline struct sg_table *alloc_sgt_for_buffer(void *ptr, size_t size,
struct iommu_domain *domain,
dma_addr_t daddr)
{
struct sg_table *sgt;
ulong offset;
uint num_ents;
int ret;
struct scatterlist *next;
size_t size_in_page;
struct page *page;
void *va_base = ptr;
/* Calculate the number of entries needed in the table */
offset = offset_in_page(va_base);
if (unlikely((size + offset) / PAGE_SIZE >= UINT_MAX - 1 ||
size + offset < size))
return ERR_PTR(-EINVAL);
num_ents = (size + offset) / PAGE_SIZE;
if ((size + offset) % PAGE_SIZE)
num_ents++;
/* Allocate and setup the table for filling out */
sgt = kmalloc(sizeof(*sgt), GFP_KERNEL);
if (!sgt)
return ERR_PTR(-ENOMEM);
ret = sg_alloc_table(sgt, num_ents, GFP_KERNEL);
if (ret) {
kfree(sgt);
return ERR_PTR(ret);
}
next = sgt->sgl;
/*
* Fill in the first scatterlist entry.
* This is the only one which may start at a non-page-aligned address.
*/
size_in_page = size > (PAGE_SIZE - offset_in_page(ptr)) ?
PAGE_SIZE - offset_in_page(ptr) :
size;
page = phys_to_page(iommu_iova_to_phys(domain, daddr));
sg_set_page(next, page, size_in_page, offset_in_page(ptr));
size -= size_in_page;
ptr += size_in_page;
next = sg_next(next);
while (size > 0) {
/*
* Fill in and link the next scatterlist entry.
* `ptr` is now page-aligned, so it is only necessary to check
* if this entire page is part of the buffer, or if the buffer
* ends part way through the page (which means this is the last
* entry in the list).
*/
size_in_page = size > PAGE_SIZE ? PAGE_SIZE : size;
page = phys_to_page(iommu_iova_to_phys(
domain, daddr + (unsigned long long)(ptr - va_base)));
sg_set_page(next, page, size_in_page, 0);
size -= size_in_page;
ptr += size_in_page;
next = sg_next(next);
}
return sgt;
}
#if HAS_TPU_EXT
int gxp_dma_map_tpu_buffer(struct gxp_dev *gxp,
struct gcip_iommu_domain *gdomain, uint core_list,
struct edgetpu_ext_mailbox_info *mbx_info)
{
uint orig_core_list = core_list;
u64 queue_iova;
int core;
int ret;
int i = 0;
while (core_list) {
phys_addr_t cmdq_pa = mbx_info->mailboxes[i].cmdq_pa;
phys_addr_t respq_pa = mbx_info->mailboxes[i++].respq_pa;
core = ffs(core_list) - 1;
queue_iova = GXP_IOVA_TPU_MBX_BUFFER(core);
ret = gcip_iommu_map(gdomain, queue_iova, cmdq_pa, mbx_info->cmdq_size,
GCIP_MAP_FLAGS_DMA_RW);
if (ret)
goto error;
ret = gcip_iommu_map(gdomain, queue_iova + mbx_info->cmdq_size, respq_pa,
mbx_info->respq_size, GCIP_MAP_FLAGS_DMA_RO);
if (ret) {
gcip_iommu_unmap(gdomain, queue_iova, mbx_info->cmdq_size);
goto error;
}
core_list &= ~BIT(core);
}
return 0;
error:
core_list ^= orig_core_list;
while (core_list) {
core = ffs(core_list) - 1;
core_list &= ~BIT(core);
queue_iova = GXP_IOVA_TPU_MBX_BUFFER(core);
gcip_iommu_unmap(gdomain, queue_iova, mbx_info->cmdq_size);
gcip_iommu_unmap(gdomain, queue_iova + mbx_info->cmdq_size, mbx_info->respq_size);
}
return ret;
}
void gxp_dma_unmap_tpu_buffer(struct gxp_dev *gxp, struct gcip_iommu_domain *gdomain,
struct gxp_tpu_mbx_desc mbx_desc)
{
uint core_list = mbx_desc.phys_core_list;
u64 queue_iova;
int core;
while (core_list) {
core = ffs(core_list) - 1;
core_list &= ~BIT(core);
queue_iova = GXP_IOVA_TPU_MBX_BUFFER(core);
gcip_iommu_unmap(gdomain, queue_iova, mbx_desc.cmdq_size);
gcip_iommu_unmap(gdomain, queue_iova + mbx_desc.cmdq_size, mbx_desc.respq_size);
}
}
#endif /* HAS_TPU_EXT */
int gxp_dma_map_allocated_coherent_buffer(struct gxp_dev *gxp,
struct gxp_coherent_buf *buf,
struct gcip_iommu_domain *gdomain,
uint gxp_dma_flags)
{
struct gxp_dma_iommu_manager *mgr = container_of(
gxp->dma_mgr, struct gxp_dma_iommu_manager, dma_mgr);
struct sg_table *sgt;
unsigned int nents_mapped;
int ret = 0;
u64 gcip_map_flags = GCIP_MAP_FLAGS_DMA_RW;
if (gdomain == gxp_iommu_get_domain_for_dev(gxp))
return 0;
sgt = alloc_sgt_for_buffer(buf->vaddr, buf->size,
mgr->default_domain->domain, buf->dma_addr);
if (IS_ERR(sgt)) {
dev_err(gxp->dev,
"Failed to allocate sgt for coherent buffer\n");
return PTR_ERR(sgt);
}
nents_mapped =
gcip_iommu_domain_map_sgt_to_iova(gdomain, sgt, buf->dsp_addr, &gcip_map_flags);
if (!nents_mapped)
ret = -ENOSPC;
sg_free_table(sgt);
kfree(sgt);
return ret;
}
int gxp_dma_alloc_coherent_buf(struct gxp_dev *gxp,
struct gcip_iommu_domain *gdomain, size_t size,
gfp_t flag, uint gxp_dma_flags,
struct gxp_coherent_buf *buffer)
{
void *buf;
dma_addr_t daddr;
int ret;
size = size < PAGE_SIZE ? PAGE_SIZE : size;
/* Allocate a coherent buffer in the default domain */
buf = dma_alloc_coherent(gxp->dev, size, &daddr, flag);
if (!buf) {
dev_err(gxp->dev, "Failed to allocate coherent buffer\n");
return -ENOMEM;
}
buffer->vaddr = buf;
buffer->size = size;
buffer->dma_addr = daddr;
if (!gdomain)
return 0;
buffer->dsp_addr = gcip_iommu_alloc_iova(gdomain, size, 0);
if (!buffer->dsp_addr) {
ret = -ENOSPC;
goto err_free_coherent;
}
ret = gxp_dma_map_allocated_coherent_buffer(gxp, buffer, gdomain, gxp_dma_flags);
if (ret)
goto err_free_iova;
return 0;
err_free_iova:
gcip_iommu_free_iova(gdomain, buffer->dsp_addr, size);
err_free_coherent:
buffer->vaddr = NULL;
buffer->size = 0;
dma_free_coherent(gxp->dev, size, buf, daddr);
return ret;
}
void gxp_dma_unmap_allocated_coherent_buffer(struct gxp_dev *gxp,
struct gcip_iommu_domain *gdomain,
struct gxp_coherent_buf *buf)
{
if (gdomain == gxp_iommu_get_domain_for_dev(gxp))
return;
gcip_iommu_unmap(gdomain, buf->dsp_addr, buf->size);
}
void gxp_dma_free_coherent_buf(struct gxp_dev *gxp,
struct gcip_iommu_domain *gdomain,
struct gxp_coherent_buf *buf)
{
if (gdomain) {
gxp_dma_unmap_allocated_coherent_buffer(gxp, gdomain, buf);
gcip_iommu_free_iova(gdomain, buf->dsp_addr, buf->size);
}
dma_free_coherent(gxp->dev, buf->size, buf->vaddr, buf->dma_addr);
}
void gxp_dma_sync_sg_for_cpu(struct gxp_dev *gxp, struct scatterlist *sg,
int nents, enum dma_data_direction direction)
{
/*
* Syncing is not domain specific. Just call through to DMA API.
*
* This works even for buffers not mapped via the DMA API, since the
* dma-iommu implementation syncs buffers by their physical address
* ranges, taken from the scatterlist, without using the IOVA.
*/
dma_sync_sg_for_cpu(gxp->dev, sg, nents, direction);
}
void gxp_dma_sync_sg_for_device(struct gxp_dev *gxp, struct scatterlist *sg,
int nents, enum dma_data_direction direction)
{
/*
* Syncing is not domain specific. Just call through to DMA API.
*
* This works even for buffers not mapped via the DMA API, since the
* dma-iommu implementation syncs buffers by their physical address
* ranges, taken from the scatterlist, without using the IOVA.
*/
dma_sync_sg_for_device(gxp->dev, sg, nents, direction);
}
u64 gxp_dma_encode_gcip_map_flags(uint gxp_dma_flags, unsigned long dma_attrs)
{
enum dma_data_direction dir = gxp_dma_flags & GXP_MAP_DIR_MASK;
bool coherent = false;
bool restrict_iova = false;
#ifdef GXP_IS_DMA_COHERENT
coherent = gxp_dma_flags & GXP_MAP_COHERENT;
#endif
return gcip_iommu_encode_gcip_map_flags(dir, coherent, dma_attrs, restrict_iova);
}