18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Virtio driver for the paravirtualized IOMMU
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2019 Arm Limited
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/amba/bus.h>
118c2ecf20Sopenharmony_ci#include <linux/delay.h>
128c2ecf20Sopenharmony_ci#include <linux/dma-iommu.h>
138c2ecf20Sopenharmony_ci#include <linux/freezer.h>
148c2ecf20Sopenharmony_ci#include <linux/interval_tree.h>
158c2ecf20Sopenharmony_ci#include <linux/iommu.h>
168c2ecf20Sopenharmony_ci#include <linux/module.h>
178c2ecf20Sopenharmony_ci#include <linux/of_iommu.h>
188c2ecf20Sopenharmony_ci#include <linux/of_platform.h>
198c2ecf20Sopenharmony_ci#include <linux/pci.h>
208c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
218c2ecf20Sopenharmony_ci#include <linux/virtio.h>
228c2ecf20Sopenharmony_ci#include <linux/virtio_config.h>
238c2ecf20Sopenharmony_ci#include <linux/virtio_ids.h>
248c2ecf20Sopenharmony_ci#include <linux/wait.h>
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci#include <uapi/linux/virtio_iommu.h>
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci#define MSI_IOVA_BASE			0x8000000
298c2ecf20Sopenharmony_ci#define MSI_IOVA_LENGTH			0x100000
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci#define VIOMMU_REQUEST_VQ		0
328c2ecf20Sopenharmony_ci#define VIOMMU_EVENT_VQ			1
338c2ecf20Sopenharmony_ci#define VIOMMU_NR_VQS			2
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistruct viommu_dev {
368c2ecf20Sopenharmony_ci	struct iommu_device		iommu;
378c2ecf20Sopenharmony_ci	struct device			*dev;
388c2ecf20Sopenharmony_ci	struct virtio_device		*vdev;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	struct ida			domain_ids;
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	struct virtqueue		*vqs[VIOMMU_NR_VQS];
438c2ecf20Sopenharmony_ci	spinlock_t			request_lock;
448c2ecf20Sopenharmony_ci	struct list_head		requests;
458c2ecf20Sopenharmony_ci	void				*evts;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	/* Device configuration */
488c2ecf20Sopenharmony_ci	struct iommu_domain_geometry	geometry;
498c2ecf20Sopenharmony_ci	u64				pgsize_bitmap;
508c2ecf20Sopenharmony_ci	u32				first_domain;
518c2ecf20Sopenharmony_ci	u32				last_domain;
528c2ecf20Sopenharmony_ci	/* Supported MAP flags */
538c2ecf20Sopenharmony_ci	u32				map_flags;
548c2ecf20Sopenharmony_ci	u32				probe_size;
558c2ecf20Sopenharmony_ci};
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_cistruct viommu_mapping {
588c2ecf20Sopenharmony_ci	phys_addr_t			paddr;
598c2ecf20Sopenharmony_ci	struct interval_tree_node	iova;
608c2ecf20Sopenharmony_ci	u32				flags;
618c2ecf20Sopenharmony_ci};
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_cistruct viommu_domain {
648c2ecf20Sopenharmony_ci	struct iommu_domain		domain;
658c2ecf20Sopenharmony_ci	struct viommu_dev		*viommu;
668c2ecf20Sopenharmony_ci	struct mutex			mutex; /* protects viommu pointer */
678c2ecf20Sopenharmony_ci	unsigned int			id;
688c2ecf20Sopenharmony_ci	u32				map_flags;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	spinlock_t			mappings_lock;
718c2ecf20Sopenharmony_ci	struct rb_root_cached		mappings;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	unsigned long			nr_endpoints;
748c2ecf20Sopenharmony_ci};
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_cistruct viommu_endpoint {
778c2ecf20Sopenharmony_ci	struct device			*dev;
788c2ecf20Sopenharmony_ci	struct viommu_dev		*viommu;
798c2ecf20Sopenharmony_ci	struct viommu_domain		*vdomain;
808c2ecf20Sopenharmony_ci	struct list_head		resv_regions;
818c2ecf20Sopenharmony_ci};
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_cistruct viommu_request {
848c2ecf20Sopenharmony_ci	struct list_head		list;
858c2ecf20Sopenharmony_ci	void				*writeback;
868c2ecf20Sopenharmony_ci	unsigned int			write_offset;
878c2ecf20Sopenharmony_ci	unsigned int			len;
888c2ecf20Sopenharmony_ci	char				buf[];
898c2ecf20Sopenharmony_ci};
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci#define VIOMMU_FAULT_RESV_MASK		0xffffff00
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_cistruct viommu_event {
948c2ecf20Sopenharmony_ci	union {
958c2ecf20Sopenharmony_ci		u32			head;
968c2ecf20Sopenharmony_ci		struct virtio_iommu_fault fault;
978c2ecf20Sopenharmony_ci	};
988c2ecf20Sopenharmony_ci};
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci#define to_viommu_domain(domain)	\
1018c2ecf20Sopenharmony_ci	container_of(domain, struct viommu_domain, domain)
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_cistatic int viommu_get_req_errno(void *buf, size_t len)
1048c2ecf20Sopenharmony_ci{
1058c2ecf20Sopenharmony_ci	struct virtio_iommu_req_tail *tail = buf + len - sizeof(*tail);
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	switch (tail->status) {
1088c2ecf20Sopenharmony_ci	case VIRTIO_IOMMU_S_OK:
1098c2ecf20Sopenharmony_ci		return 0;
1108c2ecf20Sopenharmony_ci	case VIRTIO_IOMMU_S_UNSUPP:
1118c2ecf20Sopenharmony_ci		return -ENOSYS;
1128c2ecf20Sopenharmony_ci	case VIRTIO_IOMMU_S_INVAL:
1138c2ecf20Sopenharmony_ci		return -EINVAL;
1148c2ecf20Sopenharmony_ci	case VIRTIO_IOMMU_S_RANGE:
1158c2ecf20Sopenharmony_ci		return -ERANGE;
1168c2ecf20Sopenharmony_ci	case VIRTIO_IOMMU_S_NOENT:
1178c2ecf20Sopenharmony_ci		return -ENOENT;
1188c2ecf20Sopenharmony_ci	case VIRTIO_IOMMU_S_FAULT:
1198c2ecf20Sopenharmony_ci		return -EFAULT;
1208c2ecf20Sopenharmony_ci	case VIRTIO_IOMMU_S_NOMEM:
1218c2ecf20Sopenharmony_ci		return -ENOMEM;
1228c2ecf20Sopenharmony_ci	case VIRTIO_IOMMU_S_IOERR:
1238c2ecf20Sopenharmony_ci	case VIRTIO_IOMMU_S_DEVERR:
1248c2ecf20Sopenharmony_ci	default:
1258c2ecf20Sopenharmony_ci		return -EIO;
1268c2ecf20Sopenharmony_ci	}
1278c2ecf20Sopenharmony_ci}
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_cistatic void viommu_set_req_status(void *buf, size_t len, int status)
1308c2ecf20Sopenharmony_ci{
1318c2ecf20Sopenharmony_ci	struct virtio_iommu_req_tail *tail = buf + len - sizeof(*tail);
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	tail->status = status;
1348c2ecf20Sopenharmony_ci}
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_cistatic off_t viommu_get_write_desc_offset(struct viommu_dev *viommu,
1378c2ecf20Sopenharmony_ci					  struct virtio_iommu_req_head *req,
1388c2ecf20Sopenharmony_ci					  size_t len)
1398c2ecf20Sopenharmony_ci{
1408c2ecf20Sopenharmony_ci	size_t tail_size = sizeof(struct virtio_iommu_req_tail);
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	if (req->type == VIRTIO_IOMMU_T_PROBE)
1438c2ecf20Sopenharmony_ci		return len - viommu->probe_size - tail_size;
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	return len - tail_size;
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci/*
1498c2ecf20Sopenharmony_ci * __viommu_sync_req - Complete all in-flight requests
1508c2ecf20Sopenharmony_ci *
1518c2ecf20Sopenharmony_ci * Wait for all added requests to complete. When this function returns, all
1528c2ecf20Sopenharmony_ci * requests that were in-flight at the time of the call have completed.
1538c2ecf20Sopenharmony_ci */
1548c2ecf20Sopenharmony_cistatic int __viommu_sync_req(struct viommu_dev *viommu)
1558c2ecf20Sopenharmony_ci{
1568c2ecf20Sopenharmony_ci	unsigned int len;
1578c2ecf20Sopenharmony_ci	size_t write_len;
1588c2ecf20Sopenharmony_ci	struct viommu_request *req;
1598c2ecf20Sopenharmony_ci	struct virtqueue *vq = viommu->vqs[VIOMMU_REQUEST_VQ];
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	assert_spin_locked(&viommu->request_lock);
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	virtqueue_kick(vq);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	while (!list_empty(&viommu->requests)) {
1668c2ecf20Sopenharmony_ci		len = 0;
1678c2ecf20Sopenharmony_ci		req = virtqueue_get_buf(vq, &len);
1688c2ecf20Sopenharmony_ci		if (!req)
1698c2ecf20Sopenharmony_ci			continue;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci		if (!len)
1728c2ecf20Sopenharmony_ci			viommu_set_req_status(req->buf, req->len,
1738c2ecf20Sopenharmony_ci					      VIRTIO_IOMMU_S_IOERR);
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci		write_len = req->len - req->write_offset;
1768c2ecf20Sopenharmony_ci		if (req->writeback && len == write_len)
1778c2ecf20Sopenharmony_ci			memcpy(req->writeback, req->buf + req->write_offset,
1788c2ecf20Sopenharmony_ci			       write_len);
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci		list_del(&req->list);
1818c2ecf20Sopenharmony_ci		kfree(req);
1828c2ecf20Sopenharmony_ci	}
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	return 0;
1858c2ecf20Sopenharmony_ci}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_cistatic int viommu_sync_req(struct viommu_dev *viommu)
1888c2ecf20Sopenharmony_ci{
1898c2ecf20Sopenharmony_ci	int ret;
1908c2ecf20Sopenharmony_ci	unsigned long flags;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	spin_lock_irqsave(&viommu->request_lock, flags);
1938c2ecf20Sopenharmony_ci	ret = __viommu_sync_req(viommu);
1948c2ecf20Sopenharmony_ci	if (ret)
1958c2ecf20Sopenharmony_ci		dev_dbg(viommu->dev, "could not sync requests (%d)\n", ret);
1968c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&viommu->request_lock, flags);
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	return ret;
1998c2ecf20Sopenharmony_ci}
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci/*
2028c2ecf20Sopenharmony_ci * __viommu_add_request - Add one request to the queue
2038c2ecf20Sopenharmony_ci * @buf: pointer to the request buffer
2048c2ecf20Sopenharmony_ci * @len: length of the request buffer
2058c2ecf20Sopenharmony_ci * @writeback: copy data back to the buffer when the request completes.
2068c2ecf20Sopenharmony_ci *
2078c2ecf20Sopenharmony_ci * Add a request to the queue. Only synchronize the queue if it's already full.
2088c2ecf20Sopenharmony_ci * Otherwise don't kick the queue nor wait for requests to complete.
2098c2ecf20Sopenharmony_ci *
2108c2ecf20Sopenharmony_ci * When @writeback is true, data written by the device, including the request
2118c2ecf20Sopenharmony_ci * status, is copied into @buf after the request completes. This is unsafe if
2128c2ecf20Sopenharmony_ci * the caller allocates @buf on stack and drops the lock between add_req() and
2138c2ecf20Sopenharmony_ci * sync_req().
2148c2ecf20Sopenharmony_ci *
2158c2ecf20Sopenharmony_ci * Return 0 if the request was successfully added to the queue.
2168c2ecf20Sopenharmony_ci */
2178c2ecf20Sopenharmony_cistatic int __viommu_add_req(struct viommu_dev *viommu, void *buf, size_t len,
2188c2ecf20Sopenharmony_ci			    bool writeback)
2198c2ecf20Sopenharmony_ci{
2208c2ecf20Sopenharmony_ci	int ret;
2218c2ecf20Sopenharmony_ci	off_t write_offset;
2228c2ecf20Sopenharmony_ci	struct viommu_request *req;
2238c2ecf20Sopenharmony_ci	struct scatterlist top_sg, bottom_sg;
2248c2ecf20Sopenharmony_ci	struct scatterlist *sg[2] = { &top_sg, &bottom_sg };
2258c2ecf20Sopenharmony_ci	struct virtqueue *vq = viommu->vqs[VIOMMU_REQUEST_VQ];
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	assert_spin_locked(&viommu->request_lock);
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	write_offset = viommu_get_write_desc_offset(viommu, buf, len);
2308c2ecf20Sopenharmony_ci	if (write_offset <= 0)
2318c2ecf20Sopenharmony_ci		return -EINVAL;
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	req = kzalloc(sizeof(*req) + len, GFP_ATOMIC);
2348c2ecf20Sopenharmony_ci	if (!req)
2358c2ecf20Sopenharmony_ci		return -ENOMEM;
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	req->len = len;
2388c2ecf20Sopenharmony_ci	if (writeback) {
2398c2ecf20Sopenharmony_ci		req->writeback = buf + write_offset;
2408c2ecf20Sopenharmony_ci		req->write_offset = write_offset;
2418c2ecf20Sopenharmony_ci	}
2428c2ecf20Sopenharmony_ci	memcpy(&req->buf, buf, write_offset);
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	sg_init_one(&top_sg, req->buf, write_offset);
2458c2ecf20Sopenharmony_ci	sg_init_one(&bottom_sg, req->buf + write_offset, len - write_offset);
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci	ret = virtqueue_add_sgs(vq, sg, 1, 1, req, GFP_ATOMIC);
2488c2ecf20Sopenharmony_ci	if (ret == -ENOSPC) {
2498c2ecf20Sopenharmony_ci		/* If the queue is full, sync and retry */
2508c2ecf20Sopenharmony_ci		if (!__viommu_sync_req(viommu))
2518c2ecf20Sopenharmony_ci			ret = virtqueue_add_sgs(vq, sg, 1, 1, req, GFP_ATOMIC);
2528c2ecf20Sopenharmony_ci	}
2538c2ecf20Sopenharmony_ci	if (ret)
2548c2ecf20Sopenharmony_ci		goto err_free;
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci	list_add_tail(&req->list, &viommu->requests);
2578c2ecf20Sopenharmony_ci	return 0;
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_cierr_free:
2608c2ecf20Sopenharmony_ci	kfree(req);
2618c2ecf20Sopenharmony_ci	return ret;
2628c2ecf20Sopenharmony_ci}
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_cistatic int viommu_add_req(struct viommu_dev *viommu, void *buf, size_t len)
2658c2ecf20Sopenharmony_ci{
2668c2ecf20Sopenharmony_ci	int ret;
2678c2ecf20Sopenharmony_ci	unsigned long flags;
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	spin_lock_irqsave(&viommu->request_lock, flags);
2708c2ecf20Sopenharmony_ci	ret = __viommu_add_req(viommu, buf, len, false);
2718c2ecf20Sopenharmony_ci	if (ret)
2728c2ecf20Sopenharmony_ci		dev_dbg(viommu->dev, "could not add request: %d\n", ret);
2738c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&viommu->request_lock, flags);
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	return ret;
2768c2ecf20Sopenharmony_ci}
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_ci/*
2798c2ecf20Sopenharmony_ci * Send a request and wait for it to complete. Return the request status (as an
2808c2ecf20Sopenharmony_ci * errno)
2818c2ecf20Sopenharmony_ci */
2828c2ecf20Sopenharmony_cistatic int viommu_send_req_sync(struct viommu_dev *viommu, void *buf,
2838c2ecf20Sopenharmony_ci				size_t len)
2848c2ecf20Sopenharmony_ci{
2858c2ecf20Sopenharmony_ci	int ret;
2868c2ecf20Sopenharmony_ci	unsigned long flags;
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	spin_lock_irqsave(&viommu->request_lock, flags);
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_ci	ret = __viommu_add_req(viommu, buf, len, true);
2918c2ecf20Sopenharmony_ci	if (ret) {
2928c2ecf20Sopenharmony_ci		dev_dbg(viommu->dev, "could not add request (%d)\n", ret);
2938c2ecf20Sopenharmony_ci		goto out_unlock;
2948c2ecf20Sopenharmony_ci	}
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci	ret = __viommu_sync_req(viommu);
2978c2ecf20Sopenharmony_ci	if (ret) {
2988c2ecf20Sopenharmony_ci		dev_dbg(viommu->dev, "could not sync requests (%d)\n", ret);
2998c2ecf20Sopenharmony_ci		/* Fall-through (get the actual request status) */
3008c2ecf20Sopenharmony_ci	}
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci	ret = viommu_get_req_errno(buf, len);
3038c2ecf20Sopenharmony_ciout_unlock:
3048c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&viommu->request_lock, flags);
3058c2ecf20Sopenharmony_ci	return ret;
3068c2ecf20Sopenharmony_ci}
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci/*
3098c2ecf20Sopenharmony_ci * viommu_add_mapping - add a mapping to the internal tree
3108c2ecf20Sopenharmony_ci *
3118c2ecf20Sopenharmony_ci * On success, return the new mapping. Otherwise return NULL.
3128c2ecf20Sopenharmony_ci */
3138c2ecf20Sopenharmony_cistatic int viommu_add_mapping(struct viommu_domain *vdomain, unsigned long iova,
3148c2ecf20Sopenharmony_ci			      phys_addr_t paddr, size_t size, u32 flags)
3158c2ecf20Sopenharmony_ci{
3168c2ecf20Sopenharmony_ci	unsigned long irqflags;
3178c2ecf20Sopenharmony_ci	struct viommu_mapping *mapping;
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	mapping = kzalloc(sizeof(*mapping), GFP_ATOMIC);
3208c2ecf20Sopenharmony_ci	if (!mapping)
3218c2ecf20Sopenharmony_ci		return -ENOMEM;
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_ci	mapping->paddr		= paddr;
3248c2ecf20Sopenharmony_ci	mapping->iova.start	= iova;
3258c2ecf20Sopenharmony_ci	mapping->iova.last	= iova + size - 1;
3268c2ecf20Sopenharmony_ci	mapping->flags		= flags;
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_ci	spin_lock_irqsave(&vdomain->mappings_lock, irqflags);
3298c2ecf20Sopenharmony_ci	interval_tree_insert(&mapping->iova, &vdomain->mappings);
3308c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&vdomain->mappings_lock, irqflags);
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci	return 0;
3338c2ecf20Sopenharmony_ci}
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_ci/*
3368c2ecf20Sopenharmony_ci * viommu_del_mappings - remove mappings from the internal tree
3378c2ecf20Sopenharmony_ci *
3388c2ecf20Sopenharmony_ci * @vdomain: the domain
3398c2ecf20Sopenharmony_ci * @iova: start of the range
3408c2ecf20Sopenharmony_ci * @size: size of the range. A size of 0 corresponds to the entire address
3418c2ecf20Sopenharmony_ci *	space.
3428c2ecf20Sopenharmony_ci *
3438c2ecf20Sopenharmony_ci * On success, returns the number of unmapped bytes (>= size)
3448c2ecf20Sopenharmony_ci */
3458c2ecf20Sopenharmony_cistatic size_t viommu_del_mappings(struct viommu_domain *vdomain,
3468c2ecf20Sopenharmony_ci				  unsigned long iova, size_t size)
3478c2ecf20Sopenharmony_ci{
3488c2ecf20Sopenharmony_ci	size_t unmapped = 0;
3498c2ecf20Sopenharmony_ci	unsigned long flags;
3508c2ecf20Sopenharmony_ci	unsigned long last = iova + size - 1;
3518c2ecf20Sopenharmony_ci	struct viommu_mapping *mapping = NULL;
3528c2ecf20Sopenharmony_ci	struct interval_tree_node *node, *next;
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci	spin_lock_irqsave(&vdomain->mappings_lock, flags);
3558c2ecf20Sopenharmony_ci	next = interval_tree_iter_first(&vdomain->mappings, iova, last);
3568c2ecf20Sopenharmony_ci	while (next) {
3578c2ecf20Sopenharmony_ci		node = next;
3588c2ecf20Sopenharmony_ci		mapping = container_of(node, struct viommu_mapping, iova);
3598c2ecf20Sopenharmony_ci		next = interval_tree_iter_next(node, iova, last);
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci		/* Trying to split a mapping? */
3628c2ecf20Sopenharmony_ci		if (mapping->iova.start < iova)
3638c2ecf20Sopenharmony_ci			break;
3648c2ecf20Sopenharmony_ci
3658c2ecf20Sopenharmony_ci		/*
3668c2ecf20Sopenharmony_ci		 * Virtio-iommu doesn't allow UNMAP to split a mapping created
3678c2ecf20Sopenharmony_ci		 * with a single MAP request, so remove the full mapping.
3688c2ecf20Sopenharmony_ci		 */
3698c2ecf20Sopenharmony_ci		unmapped += mapping->iova.last - mapping->iova.start + 1;
3708c2ecf20Sopenharmony_ci
3718c2ecf20Sopenharmony_ci		interval_tree_remove(node, &vdomain->mappings);
3728c2ecf20Sopenharmony_ci		kfree(mapping);
3738c2ecf20Sopenharmony_ci	}
3748c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&vdomain->mappings_lock, flags);
3758c2ecf20Sopenharmony_ci
3768c2ecf20Sopenharmony_ci	return unmapped;
3778c2ecf20Sopenharmony_ci}
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_ci/*
3808c2ecf20Sopenharmony_ci * viommu_replay_mappings - re-send MAP requests
3818c2ecf20Sopenharmony_ci *
3828c2ecf20Sopenharmony_ci * When reattaching a domain that was previously detached from all endpoints,
3838c2ecf20Sopenharmony_ci * mappings were deleted from the device. Re-create the mappings available in
3848c2ecf20Sopenharmony_ci * the internal tree.
3858c2ecf20Sopenharmony_ci */
3868c2ecf20Sopenharmony_cistatic int viommu_replay_mappings(struct viommu_domain *vdomain)
3878c2ecf20Sopenharmony_ci{
3888c2ecf20Sopenharmony_ci	int ret = 0;
3898c2ecf20Sopenharmony_ci	unsigned long flags;
3908c2ecf20Sopenharmony_ci	struct viommu_mapping *mapping;
3918c2ecf20Sopenharmony_ci	struct interval_tree_node *node;
3928c2ecf20Sopenharmony_ci	struct virtio_iommu_req_map map;
3938c2ecf20Sopenharmony_ci
3948c2ecf20Sopenharmony_ci	spin_lock_irqsave(&vdomain->mappings_lock, flags);
3958c2ecf20Sopenharmony_ci	node = interval_tree_iter_first(&vdomain->mappings, 0, -1UL);
3968c2ecf20Sopenharmony_ci	while (node) {
3978c2ecf20Sopenharmony_ci		mapping = container_of(node, struct viommu_mapping, iova);
3988c2ecf20Sopenharmony_ci		map = (struct virtio_iommu_req_map) {
3998c2ecf20Sopenharmony_ci			.head.type	= VIRTIO_IOMMU_T_MAP,
4008c2ecf20Sopenharmony_ci			.domain		= cpu_to_le32(vdomain->id),
4018c2ecf20Sopenharmony_ci			.virt_start	= cpu_to_le64(mapping->iova.start),
4028c2ecf20Sopenharmony_ci			.virt_end	= cpu_to_le64(mapping->iova.last),
4038c2ecf20Sopenharmony_ci			.phys_start	= cpu_to_le64(mapping->paddr),
4048c2ecf20Sopenharmony_ci			.flags		= cpu_to_le32(mapping->flags),
4058c2ecf20Sopenharmony_ci		};
4068c2ecf20Sopenharmony_ci
4078c2ecf20Sopenharmony_ci		ret = viommu_send_req_sync(vdomain->viommu, &map, sizeof(map));
4088c2ecf20Sopenharmony_ci		if (ret)
4098c2ecf20Sopenharmony_ci			break;
4108c2ecf20Sopenharmony_ci
4118c2ecf20Sopenharmony_ci		node = interval_tree_iter_next(node, 0, -1UL);
4128c2ecf20Sopenharmony_ci	}
4138c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&vdomain->mappings_lock, flags);
4148c2ecf20Sopenharmony_ci
4158c2ecf20Sopenharmony_ci	return ret;
4168c2ecf20Sopenharmony_ci}
4178c2ecf20Sopenharmony_ci
4188c2ecf20Sopenharmony_cistatic int viommu_add_resv_mem(struct viommu_endpoint *vdev,
4198c2ecf20Sopenharmony_ci			       struct virtio_iommu_probe_resv_mem *mem,
4208c2ecf20Sopenharmony_ci			       size_t len)
4218c2ecf20Sopenharmony_ci{
4228c2ecf20Sopenharmony_ci	size_t size;
4238c2ecf20Sopenharmony_ci	u64 start64, end64;
4248c2ecf20Sopenharmony_ci	phys_addr_t start, end;
4258c2ecf20Sopenharmony_ci	struct iommu_resv_region *region = NULL;
4268c2ecf20Sopenharmony_ci	unsigned long prot = IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_MMIO;
4278c2ecf20Sopenharmony_ci
4288c2ecf20Sopenharmony_ci	start = start64 = le64_to_cpu(mem->start);
4298c2ecf20Sopenharmony_ci	end = end64 = le64_to_cpu(mem->end);
4308c2ecf20Sopenharmony_ci	size = end64 - start64 + 1;
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_ci	/* Catch any overflow, including the unlikely end64 - start64 + 1 = 0 */
4338c2ecf20Sopenharmony_ci	if (start != start64 || end != end64 || size < end64 - start64)
4348c2ecf20Sopenharmony_ci		return -EOVERFLOW;
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_ci	if (len < sizeof(*mem))
4378c2ecf20Sopenharmony_ci		return -EINVAL;
4388c2ecf20Sopenharmony_ci
4398c2ecf20Sopenharmony_ci	switch (mem->subtype) {
4408c2ecf20Sopenharmony_ci	default:
4418c2ecf20Sopenharmony_ci		dev_warn(vdev->dev, "unknown resv mem subtype 0x%x\n",
4428c2ecf20Sopenharmony_ci			 mem->subtype);
4438c2ecf20Sopenharmony_ci		fallthrough;
4448c2ecf20Sopenharmony_ci	case VIRTIO_IOMMU_RESV_MEM_T_RESERVED:
4458c2ecf20Sopenharmony_ci		region = iommu_alloc_resv_region(start, size, 0,
4468c2ecf20Sopenharmony_ci						 IOMMU_RESV_RESERVED);
4478c2ecf20Sopenharmony_ci		break;
4488c2ecf20Sopenharmony_ci	case VIRTIO_IOMMU_RESV_MEM_T_MSI:
4498c2ecf20Sopenharmony_ci		region = iommu_alloc_resv_region(start, size, prot,
4508c2ecf20Sopenharmony_ci						 IOMMU_RESV_MSI);
4518c2ecf20Sopenharmony_ci		break;
4528c2ecf20Sopenharmony_ci	}
4538c2ecf20Sopenharmony_ci	if (!region)
4548c2ecf20Sopenharmony_ci		return -ENOMEM;
4558c2ecf20Sopenharmony_ci
4568c2ecf20Sopenharmony_ci	list_add(&region->list, &vdev->resv_regions);
4578c2ecf20Sopenharmony_ci	return 0;
4588c2ecf20Sopenharmony_ci}
4598c2ecf20Sopenharmony_ci
4608c2ecf20Sopenharmony_cistatic int viommu_probe_endpoint(struct viommu_dev *viommu, struct device *dev)
4618c2ecf20Sopenharmony_ci{
4628c2ecf20Sopenharmony_ci	int ret;
4638c2ecf20Sopenharmony_ci	u16 type, len;
4648c2ecf20Sopenharmony_ci	size_t cur = 0;
4658c2ecf20Sopenharmony_ci	size_t probe_len;
4668c2ecf20Sopenharmony_ci	struct virtio_iommu_req_probe *probe;
4678c2ecf20Sopenharmony_ci	struct virtio_iommu_probe_property *prop;
4688c2ecf20Sopenharmony_ci	struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
4698c2ecf20Sopenharmony_ci	struct viommu_endpoint *vdev = dev_iommu_priv_get(dev);
4708c2ecf20Sopenharmony_ci
4718c2ecf20Sopenharmony_ci	if (!fwspec->num_ids)
4728c2ecf20Sopenharmony_ci		return -EINVAL;
4738c2ecf20Sopenharmony_ci
4748c2ecf20Sopenharmony_ci	probe_len = sizeof(*probe) + viommu->probe_size +
4758c2ecf20Sopenharmony_ci		    sizeof(struct virtio_iommu_req_tail);
4768c2ecf20Sopenharmony_ci	probe = kzalloc(probe_len, GFP_KERNEL);
4778c2ecf20Sopenharmony_ci	if (!probe)
4788c2ecf20Sopenharmony_ci		return -ENOMEM;
4798c2ecf20Sopenharmony_ci
4808c2ecf20Sopenharmony_ci	probe->head.type = VIRTIO_IOMMU_T_PROBE;
4818c2ecf20Sopenharmony_ci	/*
4828c2ecf20Sopenharmony_ci	 * For now, assume that properties of an endpoint that outputs multiple
4838c2ecf20Sopenharmony_ci	 * IDs are consistent. Only probe the first one.
4848c2ecf20Sopenharmony_ci	 */
4858c2ecf20Sopenharmony_ci	probe->endpoint = cpu_to_le32(fwspec->ids[0]);
4868c2ecf20Sopenharmony_ci
4878c2ecf20Sopenharmony_ci	ret = viommu_send_req_sync(viommu, probe, probe_len);
4888c2ecf20Sopenharmony_ci	if (ret)
4898c2ecf20Sopenharmony_ci		goto out_free;
4908c2ecf20Sopenharmony_ci
4918c2ecf20Sopenharmony_ci	prop = (void *)probe->properties;
4928c2ecf20Sopenharmony_ci	type = le16_to_cpu(prop->type) & VIRTIO_IOMMU_PROBE_T_MASK;
4938c2ecf20Sopenharmony_ci
4948c2ecf20Sopenharmony_ci	while (type != VIRTIO_IOMMU_PROBE_T_NONE &&
4958c2ecf20Sopenharmony_ci	       cur < viommu->probe_size) {
4968c2ecf20Sopenharmony_ci		len = le16_to_cpu(prop->length) + sizeof(*prop);
4978c2ecf20Sopenharmony_ci
4988c2ecf20Sopenharmony_ci		switch (type) {
4998c2ecf20Sopenharmony_ci		case VIRTIO_IOMMU_PROBE_T_RESV_MEM:
5008c2ecf20Sopenharmony_ci			ret = viommu_add_resv_mem(vdev, (void *)prop, len);
5018c2ecf20Sopenharmony_ci			break;
5028c2ecf20Sopenharmony_ci		default:
5038c2ecf20Sopenharmony_ci			dev_err(dev, "unknown viommu prop 0x%x\n", type);
5048c2ecf20Sopenharmony_ci		}
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_ci		if (ret)
5078c2ecf20Sopenharmony_ci			dev_err(dev, "failed to parse viommu prop 0x%x\n", type);
5088c2ecf20Sopenharmony_ci
5098c2ecf20Sopenharmony_ci		cur += len;
5108c2ecf20Sopenharmony_ci		if (cur >= viommu->probe_size)
5118c2ecf20Sopenharmony_ci			break;
5128c2ecf20Sopenharmony_ci
5138c2ecf20Sopenharmony_ci		prop = (void *)probe->properties + cur;
5148c2ecf20Sopenharmony_ci		type = le16_to_cpu(prop->type) & VIRTIO_IOMMU_PROBE_T_MASK;
5158c2ecf20Sopenharmony_ci	}
5168c2ecf20Sopenharmony_ci
5178c2ecf20Sopenharmony_ciout_free:
5188c2ecf20Sopenharmony_ci	kfree(probe);
5198c2ecf20Sopenharmony_ci	return ret;
5208c2ecf20Sopenharmony_ci}
5218c2ecf20Sopenharmony_ci
5228c2ecf20Sopenharmony_cistatic int viommu_fault_handler(struct viommu_dev *viommu,
5238c2ecf20Sopenharmony_ci				struct virtio_iommu_fault *fault)
5248c2ecf20Sopenharmony_ci{
5258c2ecf20Sopenharmony_ci	char *reason_str;
5268c2ecf20Sopenharmony_ci
5278c2ecf20Sopenharmony_ci	u8 reason	= fault->reason;
5288c2ecf20Sopenharmony_ci	u32 flags	= le32_to_cpu(fault->flags);
5298c2ecf20Sopenharmony_ci	u32 endpoint	= le32_to_cpu(fault->endpoint);
5308c2ecf20Sopenharmony_ci	u64 address	= le64_to_cpu(fault->address);
5318c2ecf20Sopenharmony_ci
5328c2ecf20Sopenharmony_ci	switch (reason) {
5338c2ecf20Sopenharmony_ci	case VIRTIO_IOMMU_FAULT_R_DOMAIN:
5348c2ecf20Sopenharmony_ci		reason_str = "domain";
5358c2ecf20Sopenharmony_ci		break;
5368c2ecf20Sopenharmony_ci	case VIRTIO_IOMMU_FAULT_R_MAPPING:
5378c2ecf20Sopenharmony_ci		reason_str = "page";
5388c2ecf20Sopenharmony_ci		break;
5398c2ecf20Sopenharmony_ci	case VIRTIO_IOMMU_FAULT_R_UNKNOWN:
5408c2ecf20Sopenharmony_ci	default:
5418c2ecf20Sopenharmony_ci		reason_str = "unknown";
5428c2ecf20Sopenharmony_ci		break;
5438c2ecf20Sopenharmony_ci	}
5448c2ecf20Sopenharmony_ci
5458c2ecf20Sopenharmony_ci	/* TODO: find EP by ID and report_iommu_fault */
5468c2ecf20Sopenharmony_ci	if (flags & VIRTIO_IOMMU_FAULT_F_ADDRESS)
5478c2ecf20Sopenharmony_ci		dev_err_ratelimited(viommu->dev, "%s fault from EP %u at %#llx [%s%s%s]\n",
5488c2ecf20Sopenharmony_ci				    reason_str, endpoint, address,
5498c2ecf20Sopenharmony_ci				    flags & VIRTIO_IOMMU_FAULT_F_READ ? "R" : "",
5508c2ecf20Sopenharmony_ci				    flags & VIRTIO_IOMMU_FAULT_F_WRITE ? "W" : "",
5518c2ecf20Sopenharmony_ci				    flags & VIRTIO_IOMMU_FAULT_F_EXEC ? "X" : "");
5528c2ecf20Sopenharmony_ci	else
5538c2ecf20Sopenharmony_ci		dev_err_ratelimited(viommu->dev, "%s fault from EP %u\n",
5548c2ecf20Sopenharmony_ci				    reason_str, endpoint);
5558c2ecf20Sopenharmony_ci	return 0;
5568c2ecf20Sopenharmony_ci}
5578c2ecf20Sopenharmony_ci
5588c2ecf20Sopenharmony_cistatic void viommu_event_handler(struct virtqueue *vq)
5598c2ecf20Sopenharmony_ci{
5608c2ecf20Sopenharmony_ci	int ret;
5618c2ecf20Sopenharmony_ci	unsigned int len;
5628c2ecf20Sopenharmony_ci	struct scatterlist sg[1];
5638c2ecf20Sopenharmony_ci	struct viommu_event *evt;
5648c2ecf20Sopenharmony_ci	struct viommu_dev *viommu = vq->vdev->priv;
5658c2ecf20Sopenharmony_ci
5668c2ecf20Sopenharmony_ci	while ((evt = virtqueue_get_buf(vq, &len)) != NULL) {
5678c2ecf20Sopenharmony_ci		if (len > sizeof(*evt)) {
5688c2ecf20Sopenharmony_ci			dev_err(viommu->dev,
5698c2ecf20Sopenharmony_ci				"invalid event buffer (len %u != %zu)\n",
5708c2ecf20Sopenharmony_ci				len, sizeof(*evt));
5718c2ecf20Sopenharmony_ci		} else if (!(evt->head & VIOMMU_FAULT_RESV_MASK)) {
5728c2ecf20Sopenharmony_ci			viommu_fault_handler(viommu, &evt->fault);
5738c2ecf20Sopenharmony_ci		}
5748c2ecf20Sopenharmony_ci
5758c2ecf20Sopenharmony_ci		sg_init_one(sg, evt, sizeof(*evt));
5768c2ecf20Sopenharmony_ci		ret = virtqueue_add_inbuf(vq, sg, 1, evt, GFP_ATOMIC);
5778c2ecf20Sopenharmony_ci		if (ret)
5788c2ecf20Sopenharmony_ci			dev_err(viommu->dev, "could not add event buffer\n");
5798c2ecf20Sopenharmony_ci	}
5808c2ecf20Sopenharmony_ci
5818c2ecf20Sopenharmony_ci	virtqueue_kick(vq);
5828c2ecf20Sopenharmony_ci}
5838c2ecf20Sopenharmony_ci
5848c2ecf20Sopenharmony_ci/* IOMMU API */
5858c2ecf20Sopenharmony_ci
5868c2ecf20Sopenharmony_cistatic struct iommu_domain *viommu_domain_alloc(unsigned type)
5878c2ecf20Sopenharmony_ci{
5888c2ecf20Sopenharmony_ci	struct viommu_domain *vdomain;
5898c2ecf20Sopenharmony_ci
5908c2ecf20Sopenharmony_ci	if (type != IOMMU_DOMAIN_UNMANAGED && type != IOMMU_DOMAIN_DMA)
5918c2ecf20Sopenharmony_ci		return NULL;
5928c2ecf20Sopenharmony_ci
5938c2ecf20Sopenharmony_ci	vdomain = kzalloc(sizeof(*vdomain), GFP_KERNEL);
5948c2ecf20Sopenharmony_ci	if (!vdomain)
5958c2ecf20Sopenharmony_ci		return NULL;
5968c2ecf20Sopenharmony_ci
5978c2ecf20Sopenharmony_ci	mutex_init(&vdomain->mutex);
5988c2ecf20Sopenharmony_ci	spin_lock_init(&vdomain->mappings_lock);
5998c2ecf20Sopenharmony_ci	vdomain->mappings = RB_ROOT_CACHED;
6008c2ecf20Sopenharmony_ci
6018c2ecf20Sopenharmony_ci	if (type == IOMMU_DOMAIN_DMA &&
6028c2ecf20Sopenharmony_ci	    iommu_get_dma_cookie(&vdomain->domain)) {
6038c2ecf20Sopenharmony_ci		kfree(vdomain);
6048c2ecf20Sopenharmony_ci		return NULL;
6058c2ecf20Sopenharmony_ci	}
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_ci	return &vdomain->domain;
6088c2ecf20Sopenharmony_ci}
6098c2ecf20Sopenharmony_ci
6108c2ecf20Sopenharmony_cistatic int viommu_domain_finalise(struct viommu_endpoint *vdev,
6118c2ecf20Sopenharmony_ci				  struct iommu_domain *domain)
6128c2ecf20Sopenharmony_ci{
6138c2ecf20Sopenharmony_ci	int ret;
6148c2ecf20Sopenharmony_ci	unsigned long viommu_page_size;
6158c2ecf20Sopenharmony_ci	struct viommu_dev *viommu = vdev->viommu;
6168c2ecf20Sopenharmony_ci	struct viommu_domain *vdomain = to_viommu_domain(domain);
6178c2ecf20Sopenharmony_ci
6188c2ecf20Sopenharmony_ci	viommu_page_size = 1UL << __ffs(viommu->pgsize_bitmap);
6198c2ecf20Sopenharmony_ci	if (viommu_page_size > PAGE_SIZE) {
6208c2ecf20Sopenharmony_ci		dev_err(vdev->dev,
6218c2ecf20Sopenharmony_ci			"granule 0x%lx larger than system page size 0x%lx\n",
6228c2ecf20Sopenharmony_ci			viommu_page_size, PAGE_SIZE);
6238c2ecf20Sopenharmony_ci		return -EINVAL;
6248c2ecf20Sopenharmony_ci	}
6258c2ecf20Sopenharmony_ci
6268c2ecf20Sopenharmony_ci	ret = ida_alloc_range(&viommu->domain_ids, viommu->first_domain,
6278c2ecf20Sopenharmony_ci			      viommu->last_domain, GFP_KERNEL);
6288c2ecf20Sopenharmony_ci	if (ret < 0)
6298c2ecf20Sopenharmony_ci		return ret;
6308c2ecf20Sopenharmony_ci
6318c2ecf20Sopenharmony_ci	vdomain->id		= (unsigned int)ret;
6328c2ecf20Sopenharmony_ci
6338c2ecf20Sopenharmony_ci	domain->pgsize_bitmap	= viommu->pgsize_bitmap;
6348c2ecf20Sopenharmony_ci	domain->geometry	= viommu->geometry;
6358c2ecf20Sopenharmony_ci
6368c2ecf20Sopenharmony_ci	vdomain->map_flags	= viommu->map_flags;
6378c2ecf20Sopenharmony_ci	vdomain->viommu		= viommu;
6388c2ecf20Sopenharmony_ci
6398c2ecf20Sopenharmony_ci	return 0;
6408c2ecf20Sopenharmony_ci}
6418c2ecf20Sopenharmony_ci
6428c2ecf20Sopenharmony_cistatic void viommu_domain_free(struct iommu_domain *domain)
6438c2ecf20Sopenharmony_ci{
6448c2ecf20Sopenharmony_ci	struct viommu_domain *vdomain = to_viommu_domain(domain);
6458c2ecf20Sopenharmony_ci
6468c2ecf20Sopenharmony_ci	iommu_put_dma_cookie(domain);
6478c2ecf20Sopenharmony_ci
6488c2ecf20Sopenharmony_ci	/* Free all remaining mappings (size 2^64) */
6498c2ecf20Sopenharmony_ci	viommu_del_mappings(vdomain, 0, 0);
6508c2ecf20Sopenharmony_ci
6518c2ecf20Sopenharmony_ci	if (vdomain->viommu)
6528c2ecf20Sopenharmony_ci		ida_free(&vdomain->viommu->domain_ids, vdomain->id);
6538c2ecf20Sopenharmony_ci
6548c2ecf20Sopenharmony_ci	kfree(vdomain);
6558c2ecf20Sopenharmony_ci}
6568c2ecf20Sopenharmony_ci
6578c2ecf20Sopenharmony_cistatic int viommu_attach_dev(struct iommu_domain *domain, struct device *dev)
6588c2ecf20Sopenharmony_ci{
6598c2ecf20Sopenharmony_ci	int i;
6608c2ecf20Sopenharmony_ci	int ret = 0;
6618c2ecf20Sopenharmony_ci	struct virtio_iommu_req_attach req;
6628c2ecf20Sopenharmony_ci	struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
6638c2ecf20Sopenharmony_ci	struct viommu_endpoint *vdev = dev_iommu_priv_get(dev);
6648c2ecf20Sopenharmony_ci	struct viommu_domain *vdomain = to_viommu_domain(domain);
6658c2ecf20Sopenharmony_ci
6668c2ecf20Sopenharmony_ci	mutex_lock(&vdomain->mutex);
6678c2ecf20Sopenharmony_ci	if (!vdomain->viommu) {
6688c2ecf20Sopenharmony_ci		/*
6698c2ecf20Sopenharmony_ci		 * Properly initialize the domain now that we know which viommu
6708c2ecf20Sopenharmony_ci		 * owns it.
6718c2ecf20Sopenharmony_ci		 */
6728c2ecf20Sopenharmony_ci		ret = viommu_domain_finalise(vdev, domain);
6738c2ecf20Sopenharmony_ci	} else if (vdomain->viommu != vdev->viommu) {
6748c2ecf20Sopenharmony_ci		dev_err(dev, "cannot attach to foreign vIOMMU\n");
6758c2ecf20Sopenharmony_ci		ret = -EXDEV;
6768c2ecf20Sopenharmony_ci	}
6778c2ecf20Sopenharmony_ci	mutex_unlock(&vdomain->mutex);
6788c2ecf20Sopenharmony_ci
6798c2ecf20Sopenharmony_ci	if (ret)
6808c2ecf20Sopenharmony_ci		return ret;
6818c2ecf20Sopenharmony_ci
6828c2ecf20Sopenharmony_ci	/*
6838c2ecf20Sopenharmony_ci	 * In the virtio-iommu device, when attaching the endpoint to a new
6848c2ecf20Sopenharmony_ci	 * domain, it is detached from the old one and, if as as a result the
6858c2ecf20Sopenharmony_ci	 * old domain isn't attached to any endpoint, all mappings are removed
6868c2ecf20Sopenharmony_ci	 * from the old domain and it is freed.
6878c2ecf20Sopenharmony_ci	 *
6888c2ecf20Sopenharmony_ci	 * In the driver the old domain still exists, and its mappings will be
6898c2ecf20Sopenharmony_ci	 * recreated if it gets reattached to an endpoint. Otherwise it will be
6908c2ecf20Sopenharmony_ci	 * freed explicitly.
6918c2ecf20Sopenharmony_ci	 *
6928c2ecf20Sopenharmony_ci	 * vdev->vdomain is protected by group->mutex
6938c2ecf20Sopenharmony_ci	 */
6948c2ecf20Sopenharmony_ci	if (vdev->vdomain)
6958c2ecf20Sopenharmony_ci		vdev->vdomain->nr_endpoints--;
6968c2ecf20Sopenharmony_ci
6978c2ecf20Sopenharmony_ci	req = (struct virtio_iommu_req_attach) {
6988c2ecf20Sopenharmony_ci		.head.type	= VIRTIO_IOMMU_T_ATTACH,
6998c2ecf20Sopenharmony_ci		.domain		= cpu_to_le32(vdomain->id),
7008c2ecf20Sopenharmony_ci	};
7018c2ecf20Sopenharmony_ci
7028c2ecf20Sopenharmony_ci	for (i = 0; i < fwspec->num_ids; i++) {
7038c2ecf20Sopenharmony_ci		req.endpoint = cpu_to_le32(fwspec->ids[i]);
7048c2ecf20Sopenharmony_ci
7058c2ecf20Sopenharmony_ci		ret = viommu_send_req_sync(vdomain->viommu, &req, sizeof(req));
7068c2ecf20Sopenharmony_ci		if (ret)
7078c2ecf20Sopenharmony_ci			return ret;
7088c2ecf20Sopenharmony_ci	}
7098c2ecf20Sopenharmony_ci
7108c2ecf20Sopenharmony_ci	if (!vdomain->nr_endpoints) {
7118c2ecf20Sopenharmony_ci		/*
7128c2ecf20Sopenharmony_ci		 * This endpoint is the first to be attached to the domain.
7138c2ecf20Sopenharmony_ci		 * Replay existing mappings (e.g. SW MSI).
7148c2ecf20Sopenharmony_ci		 */
7158c2ecf20Sopenharmony_ci		ret = viommu_replay_mappings(vdomain);
7168c2ecf20Sopenharmony_ci		if (ret)
7178c2ecf20Sopenharmony_ci			return ret;
7188c2ecf20Sopenharmony_ci	}
7198c2ecf20Sopenharmony_ci
7208c2ecf20Sopenharmony_ci	vdomain->nr_endpoints++;
7218c2ecf20Sopenharmony_ci	vdev->vdomain = vdomain;
7228c2ecf20Sopenharmony_ci
7238c2ecf20Sopenharmony_ci	return 0;
7248c2ecf20Sopenharmony_ci}
7258c2ecf20Sopenharmony_ci
7268c2ecf20Sopenharmony_cistatic int viommu_map(struct iommu_domain *domain, unsigned long iova,
7278c2ecf20Sopenharmony_ci		      phys_addr_t paddr, size_t size, int prot, gfp_t gfp)
7288c2ecf20Sopenharmony_ci{
7298c2ecf20Sopenharmony_ci	int ret;
7308c2ecf20Sopenharmony_ci	u32 flags;
7318c2ecf20Sopenharmony_ci	struct virtio_iommu_req_map map;
7328c2ecf20Sopenharmony_ci	struct viommu_domain *vdomain = to_viommu_domain(domain);
7338c2ecf20Sopenharmony_ci
7348c2ecf20Sopenharmony_ci	flags = (prot & IOMMU_READ ? VIRTIO_IOMMU_MAP_F_READ : 0) |
7358c2ecf20Sopenharmony_ci		(prot & IOMMU_WRITE ? VIRTIO_IOMMU_MAP_F_WRITE : 0) |
7368c2ecf20Sopenharmony_ci		(prot & IOMMU_MMIO ? VIRTIO_IOMMU_MAP_F_MMIO : 0);
7378c2ecf20Sopenharmony_ci
7388c2ecf20Sopenharmony_ci	if (flags & ~vdomain->map_flags)
7398c2ecf20Sopenharmony_ci		return -EINVAL;
7408c2ecf20Sopenharmony_ci
7418c2ecf20Sopenharmony_ci	ret = viommu_add_mapping(vdomain, iova, paddr, size, flags);
7428c2ecf20Sopenharmony_ci	if (ret)
7438c2ecf20Sopenharmony_ci		return ret;
7448c2ecf20Sopenharmony_ci
7458c2ecf20Sopenharmony_ci	map = (struct virtio_iommu_req_map) {
7468c2ecf20Sopenharmony_ci		.head.type	= VIRTIO_IOMMU_T_MAP,
7478c2ecf20Sopenharmony_ci		.domain		= cpu_to_le32(vdomain->id),
7488c2ecf20Sopenharmony_ci		.virt_start	= cpu_to_le64(iova),
7498c2ecf20Sopenharmony_ci		.phys_start	= cpu_to_le64(paddr),
7508c2ecf20Sopenharmony_ci		.virt_end	= cpu_to_le64(iova + size - 1),
7518c2ecf20Sopenharmony_ci		.flags		= cpu_to_le32(flags),
7528c2ecf20Sopenharmony_ci	};
7538c2ecf20Sopenharmony_ci
7548c2ecf20Sopenharmony_ci	if (!vdomain->nr_endpoints)
7558c2ecf20Sopenharmony_ci		return 0;
7568c2ecf20Sopenharmony_ci
7578c2ecf20Sopenharmony_ci	ret = viommu_send_req_sync(vdomain->viommu, &map, sizeof(map));
7588c2ecf20Sopenharmony_ci	if (ret)
7598c2ecf20Sopenharmony_ci		viommu_del_mappings(vdomain, iova, size);
7608c2ecf20Sopenharmony_ci
7618c2ecf20Sopenharmony_ci	return ret;
7628c2ecf20Sopenharmony_ci}
7638c2ecf20Sopenharmony_ci
7648c2ecf20Sopenharmony_cistatic size_t viommu_unmap(struct iommu_domain *domain, unsigned long iova,
7658c2ecf20Sopenharmony_ci			   size_t size, struct iommu_iotlb_gather *gather)
7668c2ecf20Sopenharmony_ci{
7678c2ecf20Sopenharmony_ci	int ret = 0;
7688c2ecf20Sopenharmony_ci	size_t unmapped;
7698c2ecf20Sopenharmony_ci	struct virtio_iommu_req_unmap unmap;
7708c2ecf20Sopenharmony_ci	struct viommu_domain *vdomain = to_viommu_domain(domain);
7718c2ecf20Sopenharmony_ci
7728c2ecf20Sopenharmony_ci	unmapped = viommu_del_mappings(vdomain, iova, size);
7738c2ecf20Sopenharmony_ci	if (unmapped < size)
7748c2ecf20Sopenharmony_ci		return 0;
7758c2ecf20Sopenharmony_ci
7768c2ecf20Sopenharmony_ci	/* Device already removed all mappings after detach. */
7778c2ecf20Sopenharmony_ci	if (!vdomain->nr_endpoints)
7788c2ecf20Sopenharmony_ci		return unmapped;
7798c2ecf20Sopenharmony_ci
7808c2ecf20Sopenharmony_ci	unmap = (struct virtio_iommu_req_unmap) {
7818c2ecf20Sopenharmony_ci		.head.type	= VIRTIO_IOMMU_T_UNMAP,
7828c2ecf20Sopenharmony_ci		.domain		= cpu_to_le32(vdomain->id),
7838c2ecf20Sopenharmony_ci		.virt_start	= cpu_to_le64(iova),
7848c2ecf20Sopenharmony_ci		.virt_end	= cpu_to_le64(iova + unmapped - 1),
7858c2ecf20Sopenharmony_ci	};
7868c2ecf20Sopenharmony_ci
7878c2ecf20Sopenharmony_ci	ret = viommu_add_req(vdomain->viommu, &unmap, sizeof(unmap));
7888c2ecf20Sopenharmony_ci	return ret ? 0 : unmapped;
7898c2ecf20Sopenharmony_ci}
7908c2ecf20Sopenharmony_ci
7918c2ecf20Sopenharmony_cistatic phys_addr_t viommu_iova_to_phys(struct iommu_domain *domain,
7928c2ecf20Sopenharmony_ci				       dma_addr_t iova)
7938c2ecf20Sopenharmony_ci{
7948c2ecf20Sopenharmony_ci	u64 paddr = 0;
7958c2ecf20Sopenharmony_ci	unsigned long flags;
7968c2ecf20Sopenharmony_ci	struct viommu_mapping *mapping;
7978c2ecf20Sopenharmony_ci	struct interval_tree_node *node;
7988c2ecf20Sopenharmony_ci	struct viommu_domain *vdomain = to_viommu_domain(domain);
7998c2ecf20Sopenharmony_ci
8008c2ecf20Sopenharmony_ci	spin_lock_irqsave(&vdomain->mappings_lock, flags);
8018c2ecf20Sopenharmony_ci	node = interval_tree_iter_first(&vdomain->mappings, iova, iova);
8028c2ecf20Sopenharmony_ci	if (node) {
8038c2ecf20Sopenharmony_ci		mapping = container_of(node, struct viommu_mapping, iova);
8048c2ecf20Sopenharmony_ci		paddr = mapping->paddr + (iova - mapping->iova.start);
8058c2ecf20Sopenharmony_ci	}
8068c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&vdomain->mappings_lock, flags);
8078c2ecf20Sopenharmony_ci
8088c2ecf20Sopenharmony_ci	return paddr;
8098c2ecf20Sopenharmony_ci}
8108c2ecf20Sopenharmony_ci
8118c2ecf20Sopenharmony_cistatic void viommu_iotlb_sync(struct iommu_domain *domain,
8128c2ecf20Sopenharmony_ci			      struct iommu_iotlb_gather *gather)
8138c2ecf20Sopenharmony_ci{
8148c2ecf20Sopenharmony_ci	struct viommu_domain *vdomain = to_viommu_domain(domain);
8158c2ecf20Sopenharmony_ci
8168c2ecf20Sopenharmony_ci	viommu_sync_req(vdomain->viommu);
8178c2ecf20Sopenharmony_ci}
8188c2ecf20Sopenharmony_ci
8198c2ecf20Sopenharmony_cistatic void viommu_get_resv_regions(struct device *dev, struct list_head *head)
8208c2ecf20Sopenharmony_ci{
8218c2ecf20Sopenharmony_ci	struct iommu_resv_region *entry, *new_entry, *msi = NULL;
8228c2ecf20Sopenharmony_ci	struct viommu_endpoint *vdev = dev_iommu_priv_get(dev);
8238c2ecf20Sopenharmony_ci	int prot = IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_MMIO;
8248c2ecf20Sopenharmony_ci
8258c2ecf20Sopenharmony_ci	list_for_each_entry(entry, &vdev->resv_regions, list) {
8268c2ecf20Sopenharmony_ci		if (entry->type == IOMMU_RESV_MSI)
8278c2ecf20Sopenharmony_ci			msi = entry;
8288c2ecf20Sopenharmony_ci
8298c2ecf20Sopenharmony_ci		new_entry = kmemdup(entry, sizeof(*entry), GFP_KERNEL);
8308c2ecf20Sopenharmony_ci		if (!new_entry)
8318c2ecf20Sopenharmony_ci			return;
8328c2ecf20Sopenharmony_ci		list_add_tail(&new_entry->list, head);
8338c2ecf20Sopenharmony_ci	}
8348c2ecf20Sopenharmony_ci
8358c2ecf20Sopenharmony_ci	/*
8368c2ecf20Sopenharmony_ci	 * If the device didn't register any bypass MSI window, add a
8378c2ecf20Sopenharmony_ci	 * software-mapped region.
8388c2ecf20Sopenharmony_ci	 */
8398c2ecf20Sopenharmony_ci	if (!msi) {
8408c2ecf20Sopenharmony_ci		msi = iommu_alloc_resv_region(MSI_IOVA_BASE, MSI_IOVA_LENGTH,
8418c2ecf20Sopenharmony_ci					      prot, IOMMU_RESV_SW_MSI);
8428c2ecf20Sopenharmony_ci		if (!msi)
8438c2ecf20Sopenharmony_ci			return;
8448c2ecf20Sopenharmony_ci
8458c2ecf20Sopenharmony_ci		list_add_tail(&msi->list, head);
8468c2ecf20Sopenharmony_ci	}
8478c2ecf20Sopenharmony_ci
8488c2ecf20Sopenharmony_ci	iommu_dma_get_resv_regions(dev, head);
8498c2ecf20Sopenharmony_ci}
8508c2ecf20Sopenharmony_ci
8518c2ecf20Sopenharmony_cistatic struct iommu_ops viommu_ops;
8528c2ecf20Sopenharmony_cistatic struct virtio_driver virtio_iommu_drv;
8538c2ecf20Sopenharmony_ci
8548c2ecf20Sopenharmony_cistatic int viommu_match_node(struct device *dev, const void *data)
8558c2ecf20Sopenharmony_ci{
8568c2ecf20Sopenharmony_ci	return dev->parent->fwnode == data;
8578c2ecf20Sopenharmony_ci}
8588c2ecf20Sopenharmony_ci
8598c2ecf20Sopenharmony_cistatic struct viommu_dev *viommu_get_by_fwnode(struct fwnode_handle *fwnode)
8608c2ecf20Sopenharmony_ci{
8618c2ecf20Sopenharmony_ci	struct device *dev = driver_find_device(&virtio_iommu_drv.driver, NULL,
8628c2ecf20Sopenharmony_ci						fwnode, viommu_match_node);
8638c2ecf20Sopenharmony_ci	put_device(dev);
8648c2ecf20Sopenharmony_ci
8658c2ecf20Sopenharmony_ci	return dev ? dev_to_virtio(dev)->priv : NULL;
8668c2ecf20Sopenharmony_ci}
8678c2ecf20Sopenharmony_ci
8688c2ecf20Sopenharmony_cistatic struct iommu_device *viommu_probe_device(struct device *dev)
8698c2ecf20Sopenharmony_ci{
8708c2ecf20Sopenharmony_ci	int ret;
8718c2ecf20Sopenharmony_ci	struct viommu_endpoint *vdev;
8728c2ecf20Sopenharmony_ci	struct viommu_dev *viommu = NULL;
8738c2ecf20Sopenharmony_ci	struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
8748c2ecf20Sopenharmony_ci
8758c2ecf20Sopenharmony_ci	if (!fwspec || fwspec->ops != &viommu_ops)
8768c2ecf20Sopenharmony_ci		return ERR_PTR(-ENODEV);
8778c2ecf20Sopenharmony_ci
8788c2ecf20Sopenharmony_ci	viommu = viommu_get_by_fwnode(fwspec->iommu_fwnode);
8798c2ecf20Sopenharmony_ci	if (!viommu)
8808c2ecf20Sopenharmony_ci		return ERR_PTR(-ENODEV);
8818c2ecf20Sopenharmony_ci
8828c2ecf20Sopenharmony_ci	vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
8838c2ecf20Sopenharmony_ci	if (!vdev)
8848c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
8858c2ecf20Sopenharmony_ci
8868c2ecf20Sopenharmony_ci	vdev->dev = dev;
8878c2ecf20Sopenharmony_ci	vdev->viommu = viommu;
8888c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&vdev->resv_regions);
8898c2ecf20Sopenharmony_ci	dev_iommu_priv_set(dev, vdev);
8908c2ecf20Sopenharmony_ci
8918c2ecf20Sopenharmony_ci	if (viommu->probe_size) {
8928c2ecf20Sopenharmony_ci		/* Get additional information for this endpoint */
8938c2ecf20Sopenharmony_ci		ret = viommu_probe_endpoint(viommu, dev);
8948c2ecf20Sopenharmony_ci		if (ret)
8958c2ecf20Sopenharmony_ci			goto err_free_dev;
8968c2ecf20Sopenharmony_ci	}
8978c2ecf20Sopenharmony_ci
8988c2ecf20Sopenharmony_ci	return &viommu->iommu;
8998c2ecf20Sopenharmony_ci
9008c2ecf20Sopenharmony_cierr_free_dev:
9018c2ecf20Sopenharmony_ci	generic_iommu_put_resv_regions(dev, &vdev->resv_regions);
9028c2ecf20Sopenharmony_ci	kfree(vdev);
9038c2ecf20Sopenharmony_ci
9048c2ecf20Sopenharmony_ci	return ERR_PTR(ret);
9058c2ecf20Sopenharmony_ci}
9068c2ecf20Sopenharmony_ci
9078c2ecf20Sopenharmony_cistatic void viommu_release_device(struct device *dev)
9088c2ecf20Sopenharmony_ci{
9098c2ecf20Sopenharmony_ci	struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
9108c2ecf20Sopenharmony_ci	struct viommu_endpoint *vdev;
9118c2ecf20Sopenharmony_ci
9128c2ecf20Sopenharmony_ci	if (!fwspec || fwspec->ops != &viommu_ops)
9138c2ecf20Sopenharmony_ci		return;
9148c2ecf20Sopenharmony_ci
9158c2ecf20Sopenharmony_ci	vdev = dev_iommu_priv_get(dev);
9168c2ecf20Sopenharmony_ci
9178c2ecf20Sopenharmony_ci	generic_iommu_put_resv_regions(dev, &vdev->resv_regions);
9188c2ecf20Sopenharmony_ci	kfree(vdev);
9198c2ecf20Sopenharmony_ci}
9208c2ecf20Sopenharmony_ci
9218c2ecf20Sopenharmony_cistatic struct iommu_group *viommu_device_group(struct device *dev)
9228c2ecf20Sopenharmony_ci{
9238c2ecf20Sopenharmony_ci	if (dev_is_pci(dev))
9248c2ecf20Sopenharmony_ci		return pci_device_group(dev);
9258c2ecf20Sopenharmony_ci	else
9268c2ecf20Sopenharmony_ci		return generic_device_group(dev);
9278c2ecf20Sopenharmony_ci}
9288c2ecf20Sopenharmony_ci
9298c2ecf20Sopenharmony_cistatic int viommu_of_xlate(struct device *dev, struct of_phandle_args *args)
9308c2ecf20Sopenharmony_ci{
9318c2ecf20Sopenharmony_ci	return iommu_fwspec_add_ids(dev, args->args, 1);
9328c2ecf20Sopenharmony_ci}
9338c2ecf20Sopenharmony_ci
9348c2ecf20Sopenharmony_cistatic struct iommu_ops viommu_ops = {
9358c2ecf20Sopenharmony_ci	.domain_alloc		= viommu_domain_alloc,
9368c2ecf20Sopenharmony_ci	.domain_free		= viommu_domain_free,
9378c2ecf20Sopenharmony_ci	.attach_dev		= viommu_attach_dev,
9388c2ecf20Sopenharmony_ci	.map			= viommu_map,
9398c2ecf20Sopenharmony_ci	.unmap			= viommu_unmap,
9408c2ecf20Sopenharmony_ci	.iova_to_phys		= viommu_iova_to_phys,
9418c2ecf20Sopenharmony_ci	.iotlb_sync		= viommu_iotlb_sync,
9428c2ecf20Sopenharmony_ci	.probe_device		= viommu_probe_device,
9438c2ecf20Sopenharmony_ci	.release_device		= viommu_release_device,
9448c2ecf20Sopenharmony_ci	.device_group		= viommu_device_group,
9458c2ecf20Sopenharmony_ci	.get_resv_regions	= viommu_get_resv_regions,
9468c2ecf20Sopenharmony_ci	.put_resv_regions	= generic_iommu_put_resv_regions,
9478c2ecf20Sopenharmony_ci	.of_xlate		= viommu_of_xlate,
9488c2ecf20Sopenharmony_ci};
9498c2ecf20Sopenharmony_ci
9508c2ecf20Sopenharmony_cistatic int viommu_init_vqs(struct viommu_dev *viommu)
9518c2ecf20Sopenharmony_ci{
9528c2ecf20Sopenharmony_ci	struct virtio_device *vdev = dev_to_virtio(viommu->dev);
9538c2ecf20Sopenharmony_ci	const char *names[] = { "request", "event" };
9548c2ecf20Sopenharmony_ci	vq_callback_t *callbacks[] = {
9558c2ecf20Sopenharmony_ci		NULL, /* No async requests */
9568c2ecf20Sopenharmony_ci		viommu_event_handler,
9578c2ecf20Sopenharmony_ci	};
9588c2ecf20Sopenharmony_ci
9598c2ecf20Sopenharmony_ci	return virtio_find_vqs(vdev, VIOMMU_NR_VQS, viommu->vqs, callbacks,
9608c2ecf20Sopenharmony_ci			       names, NULL);
9618c2ecf20Sopenharmony_ci}
9628c2ecf20Sopenharmony_ci
9638c2ecf20Sopenharmony_cistatic int viommu_fill_evtq(struct viommu_dev *viommu)
9648c2ecf20Sopenharmony_ci{
9658c2ecf20Sopenharmony_ci	int i, ret;
9668c2ecf20Sopenharmony_ci	struct scatterlist sg[1];
9678c2ecf20Sopenharmony_ci	struct viommu_event *evts;
9688c2ecf20Sopenharmony_ci	struct virtqueue *vq = viommu->vqs[VIOMMU_EVENT_VQ];
9698c2ecf20Sopenharmony_ci	size_t nr_evts = vq->num_free;
9708c2ecf20Sopenharmony_ci
9718c2ecf20Sopenharmony_ci	viommu->evts = evts = devm_kmalloc_array(viommu->dev, nr_evts,
9728c2ecf20Sopenharmony_ci						 sizeof(*evts), GFP_KERNEL);
9738c2ecf20Sopenharmony_ci	if (!evts)
9748c2ecf20Sopenharmony_ci		return -ENOMEM;
9758c2ecf20Sopenharmony_ci
9768c2ecf20Sopenharmony_ci	for (i = 0; i < nr_evts; i++) {
9778c2ecf20Sopenharmony_ci		sg_init_one(sg, &evts[i], sizeof(*evts));
9788c2ecf20Sopenharmony_ci		ret = virtqueue_add_inbuf(vq, sg, 1, &evts[i], GFP_KERNEL);
9798c2ecf20Sopenharmony_ci		if (ret)
9808c2ecf20Sopenharmony_ci			return ret;
9818c2ecf20Sopenharmony_ci	}
9828c2ecf20Sopenharmony_ci
9838c2ecf20Sopenharmony_ci	return 0;
9848c2ecf20Sopenharmony_ci}
9858c2ecf20Sopenharmony_ci
9868c2ecf20Sopenharmony_cistatic int viommu_probe(struct virtio_device *vdev)
9878c2ecf20Sopenharmony_ci{
9888c2ecf20Sopenharmony_ci	struct device *parent_dev = vdev->dev.parent;
9898c2ecf20Sopenharmony_ci	struct viommu_dev *viommu = NULL;
9908c2ecf20Sopenharmony_ci	struct device *dev = &vdev->dev;
9918c2ecf20Sopenharmony_ci	u64 input_start = 0;
9928c2ecf20Sopenharmony_ci	u64 input_end = -1UL;
9938c2ecf20Sopenharmony_ci	int ret;
9948c2ecf20Sopenharmony_ci
9958c2ecf20Sopenharmony_ci	if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1) ||
9968c2ecf20Sopenharmony_ci	    !virtio_has_feature(vdev, VIRTIO_IOMMU_F_MAP_UNMAP))
9978c2ecf20Sopenharmony_ci		return -ENODEV;
9988c2ecf20Sopenharmony_ci
9998c2ecf20Sopenharmony_ci	viommu = devm_kzalloc(dev, sizeof(*viommu), GFP_KERNEL);
10008c2ecf20Sopenharmony_ci	if (!viommu)
10018c2ecf20Sopenharmony_ci		return -ENOMEM;
10028c2ecf20Sopenharmony_ci
10038c2ecf20Sopenharmony_ci	spin_lock_init(&viommu->request_lock);
10048c2ecf20Sopenharmony_ci	ida_init(&viommu->domain_ids);
10058c2ecf20Sopenharmony_ci	viommu->dev = dev;
10068c2ecf20Sopenharmony_ci	viommu->vdev = vdev;
10078c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&viommu->requests);
10088c2ecf20Sopenharmony_ci
10098c2ecf20Sopenharmony_ci	ret = viommu_init_vqs(viommu);
10108c2ecf20Sopenharmony_ci	if (ret)
10118c2ecf20Sopenharmony_ci		return ret;
10128c2ecf20Sopenharmony_ci
10138c2ecf20Sopenharmony_ci	virtio_cread_le(vdev, struct virtio_iommu_config, page_size_mask,
10148c2ecf20Sopenharmony_ci			&viommu->pgsize_bitmap);
10158c2ecf20Sopenharmony_ci
10168c2ecf20Sopenharmony_ci	if (!viommu->pgsize_bitmap) {
10178c2ecf20Sopenharmony_ci		ret = -EINVAL;
10188c2ecf20Sopenharmony_ci		goto err_free_vqs;
10198c2ecf20Sopenharmony_ci	}
10208c2ecf20Sopenharmony_ci
10218c2ecf20Sopenharmony_ci	viommu->map_flags = VIRTIO_IOMMU_MAP_F_READ | VIRTIO_IOMMU_MAP_F_WRITE;
10228c2ecf20Sopenharmony_ci	viommu->last_domain = ~0U;
10238c2ecf20Sopenharmony_ci
10248c2ecf20Sopenharmony_ci	/* Optional features */
10258c2ecf20Sopenharmony_ci	virtio_cread_le_feature(vdev, VIRTIO_IOMMU_F_INPUT_RANGE,
10268c2ecf20Sopenharmony_ci				struct virtio_iommu_config, input_range.start,
10278c2ecf20Sopenharmony_ci				&input_start);
10288c2ecf20Sopenharmony_ci
10298c2ecf20Sopenharmony_ci	virtio_cread_le_feature(vdev, VIRTIO_IOMMU_F_INPUT_RANGE,
10308c2ecf20Sopenharmony_ci				struct virtio_iommu_config, input_range.end,
10318c2ecf20Sopenharmony_ci				&input_end);
10328c2ecf20Sopenharmony_ci
10338c2ecf20Sopenharmony_ci	virtio_cread_le_feature(vdev, VIRTIO_IOMMU_F_DOMAIN_RANGE,
10348c2ecf20Sopenharmony_ci				struct virtio_iommu_config, domain_range.start,
10358c2ecf20Sopenharmony_ci				&viommu->first_domain);
10368c2ecf20Sopenharmony_ci
10378c2ecf20Sopenharmony_ci	virtio_cread_le_feature(vdev, VIRTIO_IOMMU_F_DOMAIN_RANGE,
10388c2ecf20Sopenharmony_ci				struct virtio_iommu_config, domain_range.end,
10398c2ecf20Sopenharmony_ci				&viommu->last_domain);
10408c2ecf20Sopenharmony_ci
10418c2ecf20Sopenharmony_ci	virtio_cread_le_feature(vdev, VIRTIO_IOMMU_F_PROBE,
10428c2ecf20Sopenharmony_ci				struct virtio_iommu_config, probe_size,
10438c2ecf20Sopenharmony_ci				&viommu->probe_size);
10448c2ecf20Sopenharmony_ci
10458c2ecf20Sopenharmony_ci	viommu->geometry = (struct iommu_domain_geometry) {
10468c2ecf20Sopenharmony_ci		.aperture_start	= input_start,
10478c2ecf20Sopenharmony_ci		.aperture_end	= input_end,
10488c2ecf20Sopenharmony_ci		.force_aperture	= true,
10498c2ecf20Sopenharmony_ci	};
10508c2ecf20Sopenharmony_ci
10518c2ecf20Sopenharmony_ci	if (virtio_has_feature(vdev, VIRTIO_IOMMU_F_MMIO))
10528c2ecf20Sopenharmony_ci		viommu->map_flags |= VIRTIO_IOMMU_MAP_F_MMIO;
10538c2ecf20Sopenharmony_ci
10548c2ecf20Sopenharmony_ci	viommu_ops.pgsize_bitmap = viommu->pgsize_bitmap;
10558c2ecf20Sopenharmony_ci
10568c2ecf20Sopenharmony_ci	virtio_device_ready(vdev);
10578c2ecf20Sopenharmony_ci
10588c2ecf20Sopenharmony_ci	/* Populate the event queue with buffers */
10598c2ecf20Sopenharmony_ci	ret = viommu_fill_evtq(viommu);
10608c2ecf20Sopenharmony_ci	if (ret)
10618c2ecf20Sopenharmony_ci		goto err_free_vqs;
10628c2ecf20Sopenharmony_ci
10638c2ecf20Sopenharmony_ci	ret = iommu_device_sysfs_add(&viommu->iommu, dev, NULL, "%s",
10648c2ecf20Sopenharmony_ci				     virtio_bus_name(vdev));
10658c2ecf20Sopenharmony_ci	if (ret)
10668c2ecf20Sopenharmony_ci		goto err_free_vqs;
10678c2ecf20Sopenharmony_ci
10688c2ecf20Sopenharmony_ci	iommu_device_set_ops(&viommu->iommu, &viommu_ops);
10698c2ecf20Sopenharmony_ci	iommu_device_set_fwnode(&viommu->iommu, parent_dev->fwnode);
10708c2ecf20Sopenharmony_ci
10718c2ecf20Sopenharmony_ci	iommu_device_register(&viommu->iommu);
10728c2ecf20Sopenharmony_ci
10738c2ecf20Sopenharmony_ci#ifdef CONFIG_PCI
10748c2ecf20Sopenharmony_ci	if (pci_bus_type.iommu_ops != &viommu_ops) {
10758c2ecf20Sopenharmony_ci		ret = bus_set_iommu(&pci_bus_type, &viommu_ops);
10768c2ecf20Sopenharmony_ci		if (ret)
10778c2ecf20Sopenharmony_ci			goto err_unregister;
10788c2ecf20Sopenharmony_ci	}
10798c2ecf20Sopenharmony_ci#endif
10808c2ecf20Sopenharmony_ci#ifdef CONFIG_ARM_AMBA
10818c2ecf20Sopenharmony_ci	if (amba_bustype.iommu_ops != &viommu_ops) {
10828c2ecf20Sopenharmony_ci		ret = bus_set_iommu(&amba_bustype, &viommu_ops);
10838c2ecf20Sopenharmony_ci		if (ret)
10848c2ecf20Sopenharmony_ci			goto err_unregister;
10858c2ecf20Sopenharmony_ci	}
10868c2ecf20Sopenharmony_ci#endif
10878c2ecf20Sopenharmony_ci	if (platform_bus_type.iommu_ops != &viommu_ops) {
10888c2ecf20Sopenharmony_ci		ret = bus_set_iommu(&platform_bus_type, &viommu_ops);
10898c2ecf20Sopenharmony_ci		if (ret)
10908c2ecf20Sopenharmony_ci			goto err_unregister;
10918c2ecf20Sopenharmony_ci	}
10928c2ecf20Sopenharmony_ci
10938c2ecf20Sopenharmony_ci	vdev->priv = viommu;
10948c2ecf20Sopenharmony_ci
10958c2ecf20Sopenharmony_ci	dev_info(dev, "input address: %u bits\n",
10968c2ecf20Sopenharmony_ci		 order_base_2(viommu->geometry.aperture_end));
10978c2ecf20Sopenharmony_ci	dev_info(dev, "page mask: %#llx\n", viommu->pgsize_bitmap);
10988c2ecf20Sopenharmony_ci
10998c2ecf20Sopenharmony_ci	return 0;
11008c2ecf20Sopenharmony_ci
11018c2ecf20Sopenharmony_cierr_unregister:
11028c2ecf20Sopenharmony_ci	iommu_device_sysfs_remove(&viommu->iommu);
11038c2ecf20Sopenharmony_ci	iommu_device_unregister(&viommu->iommu);
11048c2ecf20Sopenharmony_cierr_free_vqs:
11058c2ecf20Sopenharmony_ci	vdev->config->del_vqs(vdev);
11068c2ecf20Sopenharmony_ci
11078c2ecf20Sopenharmony_ci	return ret;
11088c2ecf20Sopenharmony_ci}
11098c2ecf20Sopenharmony_ci
11108c2ecf20Sopenharmony_cistatic void viommu_remove(struct virtio_device *vdev)
11118c2ecf20Sopenharmony_ci{
11128c2ecf20Sopenharmony_ci	struct viommu_dev *viommu = vdev->priv;
11138c2ecf20Sopenharmony_ci
11148c2ecf20Sopenharmony_ci	iommu_device_sysfs_remove(&viommu->iommu);
11158c2ecf20Sopenharmony_ci	iommu_device_unregister(&viommu->iommu);
11168c2ecf20Sopenharmony_ci
11178c2ecf20Sopenharmony_ci	/* Stop all virtqueues */
11188c2ecf20Sopenharmony_ci	vdev->config->reset(vdev);
11198c2ecf20Sopenharmony_ci	vdev->config->del_vqs(vdev);
11208c2ecf20Sopenharmony_ci
11218c2ecf20Sopenharmony_ci	dev_info(&vdev->dev, "device removed\n");
11228c2ecf20Sopenharmony_ci}
11238c2ecf20Sopenharmony_ci
11248c2ecf20Sopenharmony_cistatic void viommu_config_changed(struct virtio_device *vdev)
11258c2ecf20Sopenharmony_ci{
11268c2ecf20Sopenharmony_ci	dev_warn(&vdev->dev, "config changed\n");
11278c2ecf20Sopenharmony_ci}
11288c2ecf20Sopenharmony_ci
11298c2ecf20Sopenharmony_cistatic unsigned int features[] = {
11308c2ecf20Sopenharmony_ci	VIRTIO_IOMMU_F_MAP_UNMAP,
11318c2ecf20Sopenharmony_ci	VIRTIO_IOMMU_F_INPUT_RANGE,
11328c2ecf20Sopenharmony_ci	VIRTIO_IOMMU_F_DOMAIN_RANGE,
11338c2ecf20Sopenharmony_ci	VIRTIO_IOMMU_F_PROBE,
11348c2ecf20Sopenharmony_ci	VIRTIO_IOMMU_F_MMIO,
11358c2ecf20Sopenharmony_ci};
11368c2ecf20Sopenharmony_ci
11378c2ecf20Sopenharmony_cistatic struct virtio_device_id id_table[] = {
11388c2ecf20Sopenharmony_ci	{ VIRTIO_ID_IOMMU, VIRTIO_DEV_ANY_ID },
11398c2ecf20Sopenharmony_ci	{ 0 },
11408c2ecf20Sopenharmony_ci};
11418c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(virtio, id_table);
11428c2ecf20Sopenharmony_ci
11438c2ecf20Sopenharmony_cistatic struct virtio_driver virtio_iommu_drv = {
11448c2ecf20Sopenharmony_ci	.driver.name		= KBUILD_MODNAME,
11458c2ecf20Sopenharmony_ci	.driver.owner		= THIS_MODULE,
11468c2ecf20Sopenharmony_ci	.id_table		= id_table,
11478c2ecf20Sopenharmony_ci	.feature_table		= features,
11488c2ecf20Sopenharmony_ci	.feature_table_size	= ARRAY_SIZE(features),
11498c2ecf20Sopenharmony_ci	.probe			= viommu_probe,
11508c2ecf20Sopenharmony_ci	.remove			= viommu_remove,
11518c2ecf20Sopenharmony_ci	.config_changed		= viommu_config_changed,
11528c2ecf20Sopenharmony_ci};
11538c2ecf20Sopenharmony_ci
11548c2ecf20Sopenharmony_cimodule_virtio_driver(virtio_iommu_drv);
11558c2ecf20Sopenharmony_ci
11568c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Virtio IOMMU driver");
11578c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jean-Philippe Brucker <jean-philippe.brucker@arm.com>");
11588c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
1159