// SPDX-License-Identifier: GPL-2.0
/*
* Virtio driver for the paravirtualized IOMMU
*
* Copyright (C) 2019 Arm Limited
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/amba/bus.h>
#include <linux/delay.h>
#include <linux/dma-iommu.h>
#include <linux/freezer.h>
#include <linux/interval_tree.h>
#include <linux/iommu.h>
#include <linux/module.h>
#include <linux/of_iommu.h>
#include <linux/of_platform.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <linux/virtio_ids.h>
#include <linux/wait.h>
#include <uapi/linux/virtio_iommu.h>
#define MSI_IOVA_BASE 0x8000000
#define MSI_IOVA_LENGTH 0x100000
#define VIOMMU_REQUEST_VQ 0
#define VIOMMU_EVENT_VQ 1
#define VIOMMU_NR_VQS 2
struct viommu_dev {
struct iommu_device iommu;
struct device *dev;
struct virtio_device *vdev;
struct ida domain_ids;
struct virtqueue *vqs[VIOMMU_NR_VQS];
spinlock_t request_lock;
struct list_head requests;
void *evts;
/* Device configuration */
struct iommu_domain_geometry geometry;
u64 pgsize_bitmap;
u32 first_domain;
u32 last_domain;
/* Supported MAP flags */
u32 map_flags;
u32 probe_size;
};
struct viommu_mapping {
phys_addr_t paddr;
struct interval_tree_node iova;
u32 flags;
};
struct viommu_domain {
struct iommu_domain domain;
struct viommu_dev *viommu;
struct mutex mutex; /* protects viommu pointer */
unsigned int id;
u32 map_flags;
spinlock_t mappings_lock;
struct rb_root_cached mappings;
unsigned long nr_endpoints;
};
struct viommu_endpoint {
struct device *dev;
struct viommu_dev *viommu;
struct viommu_domain *vdomain;
struct list_head resv_regions;
};
struct viommu_request {
struct list_head list;
void *writeback;
unsigned int write_offset;
unsigned int len;
char buf[];
};
#define VIOMMU_FAULT_RESV_MASK 0xffffff00
struct viommu_event {
union {
u32 head;
struct virtio_iommu_fault fault;
};
};
#define to_viommu_domain(domain) \
container_of(domain, struct viommu_domain, domain)
static int viommu_get_req_errno(void *buf, size_t len)
{
struct virtio_iommu_req_tail *tail = buf + len - sizeof(*tail);
switch (tail->status) {
case VIRTIO_IOMMU_S_OK:
return 0;
case VIRTIO_IOMMU_S_UNSUPP:
return -ENOSYS;
case VIRTIO_IOMMU_S_INVAL:
return -EINVAL;
case VIRTIO_IOMMU_S_RANGE:
return -ERANGE;
case VIRTIO_IOMMU_S_NOENT:
return -ENOENT;
case VIRTIO_IOMMU_S_FAULT:
return -EFAULT;
case VIRTIO_IOMMU_S_NOMEM:
return -ENOMEM;
case VIRTIO_IOMMU_S_IOERR:
case VIRTIO_IOMMU_S_DEVERR:
default:
return -EIO;
}
}
static void viommu_set_req_status(void *buf, size_t len, int status)
{
struct virtio_iommu_req_tail *tail = buf + len - sizeof(*tail);
tail->status = status;
}
static off_t viommu_get_write_desc_offset(struct viommu_dev *viommu,
struct virtio_iommu_req_head *req,
size_t len)
{
size_t tail_size = sizeof(struct virtio_iommu_req_tail);
if (req->type == VIRTIO_IOMMU_T_PROBE)
return len - viommu->probe_size - tail_size;
return len - tail_size;
}
/*
* __viommu_sync_req - Complete all in-flight requests
*
* Wait for all added requests to complete. When this function returns, all
* requests that were in-flight at the time of the call have completed.
*/
static int __viommu_sync_req(struct viommu_dev *viommu)
{
unsigned int len;
size_t write_len;
struct viommu_request *req;
struct virtqueue *vq = viommu->vqs[VIOMMU_REQUEST_VQ];
assert_spin_locked(&viommu->request_lock);
virtqueue_kick(vq);
while (!list_empty(&viommu->requests)) {
len = 0;
req = virtqueue_get_buf(vq, &len);
if (!req)
continue;
if (!len)
viommu_set_req_status(req->buf, req->len,
VIRTIO_IOMMU_S_IOERR);
write_len = req->len - req->write_offset;
if (req->writeback && len