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