From patchwork Fri Feb 6 14:55:13 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Robin Murphy X-Patchwork-Id: 5792481 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 495069F336 for ; Fri, 6 Feb 2015 14:58:07 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 86E9B201F2 for ; Fri, 6 Feb 2015 14:58:05 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id B564B20172 for ; Fri, 6 Feb 2015 14:58:03 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1YJkK4-0000Ws-OW; Fri, 06 Feb 2015 14:55:56 +0000 Received: from service87.mimecast.com ([91.220.42.44]) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1YJkJv-0000Ln-Rw for linux-arm-kernel@lists.infradead.org; Fri, 06 Feb 2015 14:55:50 +0000 Received: from cam-owa2.Emea.Arm.com (fw-tnat.cambridge.arm.com [217.140.96.140]) by service87.mimecast.com; Fri, 06 Feb 2015 14:55:21 +0000 Received: from e104324-lin.cambridge.arm.com ([10.1.255.212]) by cam-owa2.Emea.Arm.com with Microsoft SMTPSVC(6.0.3790.3959); Fri, 6 Feb 2015 14:55:20 +0000 From: Robin Murphy To: iommu@lists.linux-foundation.org, linux-arm-kernel@lists.infradead.org Subject: [RFC PATCH v2 1/3] iommu: implement common IOMMU ops for DMA mapping Date: Fri, 6 Feb 2015 14:55:13 +0000 Message-Id: X-Mailer: git-send-email 1.9.1 In-Reply-To: References: X-OriginalArrivalTime: 06 Feb 2015 14:55:20.0428 (UTC) FILETIME=[ED774AC0:01D0421C] X-MC-Unique: 115020614552101201 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20150206_065548_229834_1393EED8 X-CRM114-Status: GOOD ( 16.80 ) X-Spam-Score: -2.3 (--) Cc: lauraa@codeaurora.org, arnd@arndb.de, stefano.stabellini@eu.citrix.com, catalin.marinas@arm.com, joro@8bytes.org, thunder.leizhen@huawei.com, will.deacon@arm.com, linux@arm.linux.org.uk, ritesh.harjani@gmail.com, suravee.suthikulpanit@amd.com, josephl@nvidia.com, yingjoe.chen@mediatek.com, yong.wu@mediatek.com X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.2 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Taking inspiration from the existing arch/arm code, break out some generic functions to interface the DMA-API to the IOMMU-API. This will do the bulk of the heavy lifting for IOMMU-backed dma-mapping. Whilst this RFC series is aimed at enabling arm64, once any remaining obvious issues in the common code are addressed we can complete the refactoring by porting arch/arm over for a merge-worthy series. Signed-off-by: Robin Murphy --- drivers/iommu/Kconfig | 7 + drivers/iommu/Makefile | 1 + drivers/iommu/dma-iommu.c | 552 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/dma-iommu.h | 94 ++++++++ 4 files changed, 654 insertions(+) create mode 100644 drivers/iommu/dma-iommu.c create mode 100644 include/linux/dma-iommu.h diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index a839ca9..19027bb 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -20,6 +20,13 @@ config OF_IOMMU def_bool y depends on OF && IOMMU_API +# IOMMU-agnostic DMA-mapping layer +config IOMMU_DMA + def_bool n + depends on NEED_SG_DMA_LENGTH + select IOMMU_API + select IOMMU_IOVA + config FSL_PAMU bool "Freescale IOMMU support" depends on PPC_E500MC diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index 0b1b94e..37bfc4e 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -1,6 +1,7 @@ obj-$(CONFIG_IOMMU_API) += iommu.o obj-$(CONFIG_IOMMU_API) += iommu-traces.o obj-$(CONFIG_IOMMU_API) += iommu-sysfs.o +obj-$(CONFIG_IOMMU_DMA) += dma-iommu.o obj-$(CONFIG_IOMMU_IOVA) += iova.o obj-$(CONFIG_OF_IOMMU) += of_iommu.o obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c new file mode 100644 index 0000000..b97cc0b9 --- /dev/null +++ b/drivers/iommu/dma-iommu.c @@ -0,0 +1,552 @@ +/* + * A fairly generic DMA-API to IOMMU-API glue layer. + * + * Copyright (C) 2014 ARM Ltd. + * + * based in part on arch/arm/mm/dma-mapping.c: + * Copyright (C) 2000-2004 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include + +int iommu_dma_init(void) +{ + return iommu_iova_cache_init(); +} + +struct iommu_dma_domain { + struct iommu_domain *domain; + struct iova_domain *iovad; + struct kref kref; +}; + +static inline dma_addr_t dev_dma_addr(struct device *dev, dma_addr_t addr) +{ + BUG_ON(addr < dev->dma_pfn_offset); + return addr - ((dma_addr_t)dev->dma_pfn_offset << PAGE_SHIFT); +} + +static int __dma_direction_to_prot(enum dma_data_direction dir, bool coherent) +{ + int prot = coherent ? IOMMU_CACHE : 0; + + switch (dir) { + case DMA_BIDIRECTIONAL: + return prot | IOMMU_READ | IOMMU_WRITE; + case DMA_TO_DEVICE: + return prot | IOMMU_READ; + case DMA_FROM_DEVICE: + return prot | IOMMU_WRITE; + default: + return 0; + } +} + +static struct iova *__alloc_iova(struct device *dev, size_t size, bool coherent) +{ + struct iommu_dma_domain *dom = get_dma_domain(dev); + struct iova_domain *iovad = dom->iovad; + unsigned long shift = iova_shift(iovad); + unsigned long length = iova_align(iovad, size) >> shift; + unsigned long limit_pfn = iovad->dma_32bit_pfn; + u64 dma_limit = coherent ? dev->coherent_dma_mask : *dev->dma_mask; + + limit_pfn = min(limit_pfn, (unsigned long)(dma_limit >> shift)); + /* Alignment should probably come from a domain/device attribute... */ + return alloc_iova(iovad, length, limit_pfn, false); +} + +/* + * Create a mapping in device IO address space for specified pages + */ +dma_addr_t iommu_dma_create_iova_mapping(struct device *dev, + struct page **pages, size_t size, bool coherent) +{ + struct iommu_dma_domain *dom = get_dma_domain(dev); + struct iova_domain *iovad = dom->iovad; + struct iommu_domain *domain = dom->domain; + struct iova *iova; + unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT; + dma_addr_t addr_lo, addr_hi; + int i, prot = __dma_direction_to_prot(DMA_BIDIRECTIONAL, coherent); + + iova = __alloc_iova(dev, size, coherent); + if (!iova) + return DMA_ERROR_CODE; + + addr_hi = addr_lo = iova_dma_addr(iovad, iova); + for (i = 0; i < count; ) { + unsigned int next_pfn = page_to_pfn(pages[i]) + 1; + phys_addr_t phys = page_to_phys(pages[i]); + unsigned int len, j; + + for (j = i+1; j < count; j++, next_pfn++) + if (page_to_pfn(pages[j]) != next_pfn) + break; + + len = (j - i) << PAGE_SHIFT; + if (iommu_map(domain, addr_hi, phys, len, prot)) + goto fail; + addr_hi += len; + i = j; + } + return dev_dma_addr(dev, addr_lo); +fail: + iommu_unmap(domain, addr_lo, addr_hi - addr_lo); + __free_iova(iovad, iova); + return DMA_ERROR_CODE; +} + +int iommu_dma_release_iova_mapping(struct device *dev, dma_addr_t iova, + size_t size) +{ + struct iommu_dma_domain *dom = get_dma_domain(dev); + struct iova_domain *iovad = dom->iovad; + size_t offset = iova_offset(iovad, iova); + size_t len = iova_align(iovad, size + offset); + + iommu_unmap(dom->domain, iova - offset, len); + free_iova(iovad, iova_pfn(iovad, iova)); + return 0; +} + +struct page **iommu_dma_alloc_buffer(struct device *dev, size_t size, + gfp_t gfp, struct dma_attrs *attrs, + void (clear_buffer)(struct page *page, size_t size)) +{ + struct page **pages; + int count = size >> PAGE_SHIFT; + int array_size = count * sizeof(struct page *); + int i = 0; + + if (array_size <= PAGE_SIZE) + pages = kzalloc(array_size, GFP_KERNEL); + else + pages = vzalloc(array_size); + if (!pages) + return NULL; + + if (dma_get_attr(DMA_ATTR_FORCE_CONTIGUOUS, attrs)) { + unsigned long order = get_order(size); + struct page *page; + + page = dma_alloc_from_contiguous(dev, count, order); + if (!page) + goto error; + + if (clear_buffer) + clear_buffer(page, size); + + for (i = 0; i < count; i++) + pages[i] = page + i; + + return pages; + } + + /* + * IOMMU can map any pages, so himem can also be used here + */ + gfp |= __GFP_NOWARN | __GFP_HIGHMEM; + + while (count) { + int j, order = __fls(count); + + pages[i] = alloc_pages(gfp, order); + while (!pages[i] && order) + pages[i] = alloc_pages(gfp, --order); + if (!pages[i]) + goto error; + + if (order) { + split_page(pages[i], order); + j = 1 << order; + while (--j) + pages[i + j] = pages[i] + j; + } + + if (clear_buffer) + clear_buffer(pages[i], PAGE_SIZE << order); + i += 1 << order; + count -= 1 << order; + } + + return pages; +error: + while (i--) + if (pages[i]) + __free_pages(pages[i], 0); + if (array_size <= PAGE_SIZE) + kfree(pages); + else + vfree(pages); + return NULL; +} + +int iommu_dma_free_buffer(struct device *dev, struct page **pages, size_t size, + struct dma_attrs *attrs) +{ + int count = size >> PAGE_SHIFT; + int array_size = count * sizeof(struct page *); + int i; + + if (dma_get_attr(DMA_ATTR_FORCE_CONTIGUOUS, attrs)) { + dma_release_from_contiguous(dev, pages[0], count); + } else { + for (i = 0; i < count; i++) + if (pages[i]) + __free_pages(pages[i], 0); + } + + if (array_size <= PAGE_SIZE) + kfree(pages); + else + vfree(pages); + return 0; +} + +static dma_addr_t __iommu_dma_map_page(struct device *dev, struct page *page, + unsigned long offset, size_t size, enum dma_data_direction dir, + bool coherent) +{ + dma_addr_t dma_addr; + struct iommu_dma_domain *dom = get_dma_domain(dev); + struct iova_domain *iovad = dom->iovad; + phys_addr_t phys = page_to_phys(page) + offset; + size_t iova_off = iova_offset(iovad, phys); + size_t len = iova_align(iovad, size + iova_off); + int prot = __dma_direction_to_prot(dir, coherent); + struct iova *iova = __alloc_iova(dev, len, coherent); + + if (!iova) + return DMA_ERROR_CODE; + + dma_addr = iova_dma_addr(iovad, iova); + if (iommu_map(dom->domain, dma_addr, phys - iova_off, len, prot)) { + __free_iova(iovad, iova); + return DMA_ERROR_CODE; + } + + return dev_dma_addr(dev, dma_addr + iova_off); +} + +dma_addr_t iommu_dma_map_page(struct device *dev, struct page *page, + unsigned long offset, size_t size, enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + return __iommu_dma_map_page(dev, page, offset, size, dir, false); +} + +dma_addr_t iommu_dma_coherent_map_page(struct device *dev, struct page *page, + unsigned long offset, size_t size, enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + return __iommu_dma_map_page(dev, page, offset, size, dir, true); +} + +void iommu_dma_unmap_page(struct device *dev, dma_addr_t handle, size_t size, + enum dma_data_direction dir, struct dma_attrs *attrs) +{ + struct iommu_dma_domain *dom = get_dma_domain(dev); + struct iova_domain *iovad = dom->iovad; + size_t offset = iova_offset(iovad, handle); + size_t len = iova_align(iovad, size + offset); + dma_addr_t iova = handle - offset; + + if (!iova) + return; + + iommu_unmap(dom->domain, iova, len); + free_iova(iovad, iova_pfn(iovad, iova)); +} + +static int finalise_sg(struct device *dev, struct scatterlist *sg, int nents, + dma_addr_t dma_addr) +{ + struct scatterlist *s, *seg = sg; + unsigned long seg_mask = dma_get_seg_boundary(dev); + unsigned int max_len = dma_get_max_seg_size(dev); + unsigned int seg_len = 0, seg_dma = 0; + int i, count = 1; + + for_each_sg(sg, s, nents, i) { + /* Un-swizzling the fields here, hence the naming mismatch */ + unsigned int s_offset = sg_dma_address(s); + unsigned int s_length = sg_dma_len(s); + unsigned int s_dma_len = s->length; + + s->offset = s_offset; + s->length = s_length; + sg_dma_address(s) = DMA_ERROR_CODE; + sg_dma_len(s) = 0; + + if (seg_len && (seg_dma + seg_len == dma_addr + s_offset) && + (seg_len + s_dma_len <= max_len) && + ((seg_dma & seg_mask) <= seg_mask - (seg_len + s_length)) + ) { + sg_dma_len(seg) += s_dma_len; + } else { + if (seg_len) { + seg = sg_next(seg); + count++; + } + sg_dma_len(seg) = s_dma_len; + sg_dma_address(seg) = dma_addr + s_offset; + + seg_len = s_offset; + seg_dma = dma_addr + s_offset; + } + seg_len += s_length; + dma_addr += s_dma_len; + } + return count; +} + +static void invalidate_sg(struct scatterlist *sg, int nents) +{ + struct scatterlist *s; + int i; + + for_each_sg(sg, s, nents, i) { + if (sg_dma_address(s) != DMA_ERROR_CODE) + s->offset = sg_dma_address(s); + if (sg_dma_len(s)) + s->length = sg_dma_len(s); + sg_dma_address(s) = DMA_ERROR_CODE; + sg_dma_len(s) = 0; + } +} + +static int __iommu_dma_map_sg(struct device *dev, struct scatterlist *sg, + int nents, enum dma_data_direction dir, struct dma_attrs *attrs, + bool coherent) +{ + struct iommu_dma_domain *dom = get_dma_domain(dev); + struct iova_domain *iovad = dom->iovad; + struct iova *iova; + struct scatterlist *s; + dma_addr_t dma_addr; + size_t iova_len = 0; + int i, prot = __dma_direction_to_prot(dir, coherent); + + /* + * Work out how much IOVA space we need, and align the segments to + * IOVA granules for the IOMMU driver to handle. With some clever + * trickery we can modify the list in a reversible manner. + */ + for_each_sg(sg, s, nents, i) { + size_t s_offset = iova_offset(iovad, s->offset); + size_t s_length = s->length; + + sg_dma_address(s) = s->offset; + sg_dma_len(s) = s_length; + s->offset -= s_offset; + s_length = iova_align(iovad, s_length + s_offset); + s->length = s_length; + + iova_len += s_length; + } + + iova = __alloc_iova(dev, iova_len, coherent); + if (!iova) + goto out_restore_sg; + + /* + * We'll leave any physical concatenation to the IOMMU driver's + * implementation - it knows better than we do. + */ + dma_addr = iova_dma_addr(iovad, iova); + if (iommu_map_sg(dom->domain, dma_addr, sg, nents, prot) < iova_len) + goto out_free_iova; + + return finalise_sg(dev, sg, nents, dev_dma_addr(dev, dma_addr)); + +out_free_iova: + __free_iova(iovad, iova); +out_restore_sg: + invalidate_sg(sg, nents); + return 0; +} + +int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, + enum dma_data_direction dir, struct dma_attrs *attrs) +{ + return __iommu_dma_map_sg(dev, sg, nents, dir, attrs, false); +} + +int iommu_dma_coherent_map_sg(struct device *dev, struct scatterlist *sg, + int nents, enum dma_data_direction dir, struct dma_attrs *attrs) +{ + return __iommu_dma_map_sg(dev, sg, nents, dir, attrs, true); +} + +void iommu_dma_unmap_sg(struct device *dev, struct scatterlist *sg, int nents, + enum dma_data_direction dir, struct dma_attrs *attrs) +{ + struct iommu_dma_domain *dom = get_dma_domain(dev); + struct iova_domain *iovad = dom->iovad; + struct scatterlist *s; + int i; + dma_addr_t iova = sg_dma_address(sg) & ~iova_mask(iovad); + size_t len = 0; + + /* + * The scatterlist segments are mapped into contiguous IOVA space, + * so just add up the total length and unmap it in one go. + */ + for_each_sg(sg, s, nents, i) + len += sg_dma_len(s); + + iommu_unmap(dom->domain, iova, len); + free_iova(iovad, iova_pfn(iovad, iova)); +} + +struct iommu_dma_domain *iommu_dma_create_domain(const struct iommu_ops *ops, + dma_addr_t base, size_t size) +{ + struct iommu_dma_domain *dom; + struct iommu_domain *domain; + struct iova_domain *iovad; + struct iommu_domain_geometry *dg; + unsigned long order, base_pfn, end_pfn; + + pr_debug("base=%pad\tsize=0x%zx\n", &base, size); + dom = kzalloc(sizeof(*dom), GFP_KERNEL); + if (!dom) + return NULL; + + /* + * HACK: We'd like to ask the relevant IOMMU in ops for a suitable + * domain, but until that happens, bypass the bus nonsense and create + * one directly for this specific device/IOMMU combination... + */ + domain = kzalloc(sizeof(*domain), GFP_KERNEL); + + if (!domain) + goto out_free_dma_domain; + domain->ops = ops; + + if (ops->domain_init(domain)) + goto out_free_iommu_domain; + /* + * ...and do the bare minimum to sanity-check that the domain allows + * at least some access to the device... + */ + dg = &domain->geometry; + if (!(base <= dg->aperture_end && base + size > dg->aperture_start)) { + pr_warn("DMA range outside IOMMU capability; is DT correct?\n"); + goto out_free_iommu_domain; + } + /* ...then finally give it a kicking to make sure it fits */ + dg->aperture_start = max(base, dg->aperture_start); + dg->aperture_end = min(base + size - 1, dg->aperture_end); + /* + * Note that this almost certainly breaks the case where multiple + * devices with different DMA capabilities need to share a domain, + * but we don't have the necessary information to handle that here + * anyway - "proper" group and domain allocation needs to involve + * the IOMMU driver and a complete view of the bus. + */ + + iovad = kzalloc(sizeof(*iovad), GFP_KERNEL); + if (!iovad) + goto out_free_iommu_domain; + + /* Use the smallest supported page size for IOVA granularity */ + order = __ffs(ops->pgsize_bitmap); + base_pfn = max(dg->aperture_start >> order, (dma_addr_t)1); + end_pfn = dg->aperture_end >> order; + init_iova_domain(iovad, 1UL << order, base_pfn, end_pfn); + + dom->domain = domain; + dom->iovad = iovad; + kref_init(&dom->kref); + pr_debug("domain %p created\n", dom); + return dom; + +out_free_iommu_domain: + kfree(domain); +out_free_dma_domain: + kfree(dom); + return NULL; +} + +static void iommu_dma_free_domain(struct kref *kref) +{ + struct iommu_dma_domain *dom; + + dom = container_of(kref, struct iommu_dma_domain, kref); + put_iova_domain(dom->iovad); + iommu_domain_free(dom->domain); + kfree(dom); + pr_debug("domain %p freed\n", dom); +} + +void iommu_dma_release_domain(struct iommu_dma_domain *dom) +{ + kref_put(&dom->kref, iommu_dma_free_domain); +} + +struct iommu_domain *iommu_dma_raw_domain(struct iommu_dma_domain *dom) +{ + return dom ? dom->domain : NULL; +} + +int iommu_dma_attach_device(struct device *dev, struct iommu_dma_domain *dom) +{ + int ret; + + kref_get(&dom->kref); + ret = iommu_attach_device(dom->domain, dev); + if (ret) + iommu_dma_release_domain(dom); + else + set_dma_domain(dev, dom); + pr_debug("%s%s attached to domain %p\n", dev_name(dev), + ret?" *not*":"", dom); + return ret; +} + +void iommu_dma_detach_device(struct device *dev) +{ + struct iommu_dma_domain *dom = get_dma_domain(dev); + + if (!dom) { + dev_warn(dev, "Not attached\n"); + return; + } + set_dma_domain(dev, NULL); + iommu_detach_device(dom->domain, dev); + iommu_dma_release_domain(dom); + pr_debug("%s detached from domain %p\n", dev_name(dev), dom); +} + +int iommu_dma_supported(struct device *dev, u64 mask) +{ + /* + * This looks awful, but really it's reasonable to assume that if an + * IOMMU can't address everything that the CPU can, it probably isn't + * generic enough to be using this implementation in the first place. + */ + return 1; +} + +int iommu_dma_mapping_error(struct device *dev, dma_addr_t dma_addr) +{ + return dma_addr == DMA_ERROR_CODE; +} diff --git a/include/linux/dma-iommu.h b/include/linux/dma-iommu.h new file mode 100644 index 0000000..4bba85a --- /dev/null +++ b/include/linux/dma-iommu.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2014 ARM Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef __DMA_IOMMU_H +#define __DMA_IOMMU_H + +#ifdef __KERNEL__ + +#include +#include + +#ifdef CONFIG_IOMMU_DMA + +int iommu_dma_init(void); + + +struct iommu_dma_domain *iommu_dma_create_domain(const struct iommu_ops *ops, + dma_addr_t base, size_t size); +void iommu_dma_release_domain(struct iommu_dma_domain *dma_domain); + +struct iommu_domain *iommu_dma_raw_domain(struct iommu_dma_domain *dma_domain); + +int iommu_dma_attach_device(struct device *dev, struct iommu_dma_domain *domain); +void iommu_dma_detach_device(struct device *dev); + +/* + * Implementation of these is left to arch code - it can associate domains + * with devices however it likes, provided the lookup is efficient. + */ +struct iommu_dma_domain *get_dma_domain(struct device *dev); +void set_dma_domain(struct device *dev, struct iommu_dma_domain *dma_domain); + + +dma_addr_t iommu_dma_create_iova_mapping(struct device *dev, + struct page **pages, size_t size, bool coherent); +int iommu_dma_release_iova_mapping(struct device *dev, dma_addr_t iova, + size_t size); + +struct page **iommu_dma_alloc_buffer(struct device *dev, size_t size, + gfp_t gfp, struct dma_attrs *attrs, + void (clear_buffer)(struct page *page, size_t size)); +int iommu_dma_free_buffer(struct device *dev, struct page **pages, size_t size, + struct dma_attrs *attrs); + +dma_addr_t iommu_dma_map_page(struct device *dev, struct page *page, + unsigned long offset, size_t size, enum dma_data_direction dir, + struct dma_attrs *attrs); +dma_addr_t iommu_dma_coherent_map_page(struct device *dev, struct page *page, + unsigned long offset, size_t size, enum dma_data_direction dir, + struct dma_attrs *attrs); +void iommu_dma_unmap_page(struct device *dev, dma_addr_t handle, size_t size, + enum dma_data_direction dir, struct dma_attrs *attrs); + +int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, + enum dma_data_direction dir, struct dma_attrs *attrs); +int iommu_dma_coherent_map_sg(struct device *dev, struct scatterlist *sg, + int nents, enum dma_data_direction dir, + struct dma_attrs *attrs); +void iommu_dma_unmap_sg(struct device *dev, struct scatterlist *sgl, int nents, + enum dma_data_direction dir, struct dma_attrs *attrs); + +int iommu_dma_supported(struct device *dev, u64 mask); +int iommu_dma_mapping_error(struct device *dev, dma_addr_t dma_addr); + +#else + +static inline int iommu_dma_init(void) +{ + return 0; +} + +static inline struct iommu_dma_domain *get_dma_domain(struct device *dev) +{ + return NULL; +} + +void set_dma_domain(struct device *dev, struct iommu_dma_domain *dma_domain) { } + +#endif /* CONFIG_IOMMU_DMA */ + +#endif /* __KERNEL__ */ +#endif /* __DMA_IOMMU_H */