162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci#include <linux/cpu.h>
362306a36Sopenharmony_ci#include <linux/dma-direct.h>
462306a36Sopenharmony_ci#include <linux/dma-map-ops.h>
562306a36Sopenharmony_ci#include <linux/gfp.h>
662306a36Sopenharmony_ci#include <linux/highmem.h>
762306a36Sopenharmony_ci#include <linux/export.h>
862306a36Sopenharmony_ci#include <linux/memblock.h>
962306a36Sopenharmony_ci#include <linux/of_address.h>
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci#include <linux/types.h>
1262306a36Sopenharmony_ci#include <linux/vmalloc.h>
1362306a36Sopenharmony_ci#include <linux/swiotlb.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <xen/xen.h>
1662306a36Sopenharmony_ci#include <xen/interface/grant_table.h>
1762306a36Sopenharmony_ci#include <xen/interface/memory.h>
1862306a36Sopenharmony_ci#include <xen/page.h>
1962306a36Sopenharmony_ci#include <xen/xen-ops.h>
2062306a36Sopenharmony_ci#include <xen/swiotlb-xen.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include <asm/cacheflush.h>
2362306a36Sopenharmony_ci#include <asm/xen/hypercall.h>
2462306a36Sopenharmony_ci#include <asm/xen/interface.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic gfp_t xen_swiotlb_gfp(void)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	phys_addr_t base;
2962306a36Sopenharmony_ci	u64 i;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	for_each_mem_range(i, &base, NULL) {
3262306a36Sopenharmony_ci		if (base < (phys_addr_t)0xffffffff) {
3362306a36Sopenharmony_ci			if (IS_ENABLED(CONFIG_ZONE_DMA32))
3462306a36Sopenharmony_ci				return __GFP_DMA32;
3562306a36Sopenharmony_ci			return __GFP_DMA;
3662306a36Sopenharmony_ci		}
3762306a36Sopenharmony_ci	}
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	return GFP_KERNEL;
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic bool hypercall_cflush = false;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci/* buffers in highmem or foreign pages cannot cross page boundaries */
4562306a36Sopenharmony_cistatic void dma_cache_maint(struct device *dev, dma_addr_t handle,
4662306a36Sopenharmony_ci			    size_t size, u32 op)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	struct gnttab_cache_flush cflush;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	cflush.offset = xen_offset_in_page(handle);
5162306a36Sopenharmony_ci	cflush.op = op;
5262306a36Sopenharmony_ci	handle &= XEN_PAGE_MASK;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	do {
5562306a36Sopenharmony_ci		cflush.a.dev_bus_addr = dma_to_phys(dev, handle);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci		if (size + cflush.offset > XEN_PAGE_SIZE)
5862306a36Sopenharmony_ci			cflush.length = XEN_PAGE_SIZE - cflush.offset;
5962306a36Sopenharmony_ci		else
6062306a36Sopenharmony_ci			cflush.length = size;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci		HYPERVISOR_grant_table_op(GNTTABOP_cache_flush, &cflush, 1);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci		cflush.offset = 0;
6562306a36Sopenharmony_ci		handle += cflush.length;
6662306a36Sopenharmony_ci		size -= cflush.length;
6762306a36Sopenharmony_ci	} while (size);
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci/*
7162306a36Sopenharmony_ci * Dom0 is mapped 1:1, and while the Linux page can span across multiple Xen
7262306a36Sopenharmony_ci * pages, it is not possible for it to contain a mix of local and foreign Xen
7362306a36Sopenharmony_ci * pages.  Calling pfn_valid on a foreign mfn will always return false, so if
7462306a36Sopenharmony_ci * pfn_valid returns true the pages is local and we can use the native
7562306a36Sopenharmony_ci * dma-direct functions, otherwise we call the Xen specific version.
7662306a36Sopenharmony_ci */
7762306a36Sopenharmony_civoid xen_dma_sync_for_cpu(struct device *dev, dma_addr_t handle,
7862306a36Sopenharmony_ci			  size_t size, enum dma_data_direction dir)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	if (dir != DMA_TO_DEVICE)
8162306a36Sopenharmony_ci		dma_cache_maint(dev, handle, size, GNTTAB_CACHE_INVAL);
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_civoid xen_dma_sync_for_device(struct device *dev, dma_addr_t handle,
8562306a36Sopenharmony_ci			     size_t size, enum dma_data_direction dir)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	if (dir == DMA_FROM_DEVICE)
8862306a36Sopenharmony_ci		dma_cache_maint(dev, handle, size, GNTTAB_CACHE_INVAL);
8962306a36Sopenharmony_ci	else
9062306a36Sopenharmony_ci		dma_cache_maint(dev, handle, size, GNTTAB_CACHE_CLEAN);
9162306a36Sopenharmony_ci}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cibool xen_arch_need_swiotlb(struct device *dev,
9462306a36Sopenharmony_ci			   phys_addr_t phys,
9562306a36Sopenharmony_ci			   dma_addr_t dev_addr)
9662306a36Sopenharmony_ci{
9762306a36Sopenharmony_ci	unsigned int xen_pfn = XEN_PFN_DOWN(phys);
9862306a36Sopenharmony_ci	unsigned int bfn = XEN_PFN_DOWN(dma_to_phys(dev, dev_addr));
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	/*
10162306a36Sopenharmony_ci	 * The swiotlb buffer should be used if
10262306a36Sopenharmony_ci	 *	- Xen doesn't have the cache flush hypercall
10362306a36Sopenharmony_ci	 *	- The Linux page refers to foreign memory
10462306a36Sopenharmony_ci	 *	- The device doesn't support coherent DMA request
10562306a36Sopenharmony_ci	 *
10662306a36Sopenharmony_ci	 * The Linux page may be spanned acrros multiple Xen page, although
10762306a36Sopenharmony_ci	 * it's not possible to have a mix of local and foreign Xen page.
10862306a36Sopenharmony_ci	 * Furthermore, range_straddles_page_boundary is already checking
10962306a36Sopenharmony_ci	 * if buffer is physically contiguous in the host RAM.
11062306a36Sopenharmony_ci	 *
11162306a36Sopenharmony_ci	 * Therefore we only need to check the first Xen page to know if we
11262306a36Sopenharmony_ci	 * require a bounce buffer because the device doesn't support coherent
11362306a36Sopenharmony_ci	 * memory and we are not able to flush the cache.
11462306a36Sopenharmony_ci	 */
11562306a36Sopenharmony_ci	return (!hypercall_cflush && (xen_pfn != bfn) &&
11662306a36Sopenharmony_ci		!dev_is_dma_coherent(dev));
11762306a36Sopenharmony_ci}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic int __init xen_mm_init(void)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	struct gnttab_cache_flush cflush;
12262306a36Sopenharmony_ci	int rc;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	if (!xen_swiotlb_detect())
12562306a36Sopenharmony_ci		return 0;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	/* we can work with the default swiotlb */
12862306a36Sopenharmony_ci	rc = swiotlb_init_late(swiotlb_size_or_default(),
12962306a36Sopenharmony_ci			       xen_swiotlb_gfp(), NULL);
13062306a36Sopenharmony_ci	if (rc < 0)
13162306a36Sopenharmony_ci		return rc;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	cflush.op = 0;
13462306a36Sopenharmony_ci	cflush.a.dev_bus_addr = 0;
13562306a36Sopenharmony_ci	cflush.offset = 0;
13662306a36Sopenharmony_ci	cflush.length = 0;
13762306a36Sopenharmony_ci	if (HYPERVISOR_grant_table_op(GNTTABOP_cache_flush, &cflush, 1) != -ENOSYS)
13862306a36Sopenharmony_ci		hypercall_cflush = true;
13962306a36Sopenharmony_ci	return 0;
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ciarch_initcall(xen_mm_init);
142