18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Re-map IO memory to kernel address space so that we can access it.
48c2ecf20Sopenharmony_ci * This is needed for high PCI addresses that aren't mapped in the
58c2ecf20Sopenharmony_ci * 640k-1MB IO memory area on PC's
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * (C) Copyright 1995 1996 Linus Torvalds
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci#include <linux/vmalloc.h>
108c2ecf20Sopenharmony_ci#include <linux/mm.h>
118c2ecf20Sopenharmony_ci#include <linux/sched.h>
128c2ecf20Sopenharmony_ci#include <linux/io.h>
138c2ecf20Sopenharmony_ci#include <linux/export.h>
148c2ecf20Sopenharmony_ci#include <asm/cacheflush.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include "pgalloc-track.h"
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#ifdef CONFIG_HAVE_ARCH_HUGE_VMAP
198c2ecf20Sopenharmony_cistatic int __read_mostly ioremap_p4d_capable;
208c2ecf20Sopenharmony_cistatic int __read_mostly ioremap_pud_capable;
218c2ecf20Sopenharmony_cistatic int __read_mostly ioremap_pmd_capable;
228c2ecf20Sopenharmony_cistatic int __read_mostly ioremap_huge_disabled;
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_cistatic int __init set_nohugeiomap(char *str)
258c2ecf20Sopenharmony_ci{
268c2ecf20Sopenharmony_ci	ioremap_huge_disabled = 1;
278c2ecf20Sopenharmony_ci	return 0;
288c2ecf20Sopenharmony_ci}
298c2ecf20Sopenharmony_ciearly_param("nohugeiomap", set_nohugeiomap);
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_civoid __init ioremap_huge_init(void)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	if (!ioremap_huge_disabled) {
348c2ecf20Sopenharmony_ci		if (arch_ioremap_p4d_supported())
358c2ecf20Sopenharmony_ci			ioremap_p4d_capable = 1;
368c2ecf20Sopenharmony_ci		if (arch_ioremap_pud_supported())
378c2ecf20Sopenharmony_ci			ioremap_pud_capable = 1;
388c2ecf20Sopenharmony_ci		if (arch_ioremap_pmd_supported())
398c2ecf20Sopenharmony_ci			ioremap_pmd_capable = 1;
408c2ecf20Sopenharmony_ci	}
418c2ecf20Sopenharmony_ci}
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic inline int ioremap_p4d_enabled(void)
448c2ecf20Sopenharmony_ci{
458c2ecf20Sopenharmony_ci	return ioremap_p4d_capable;
468c2ecf20Sopenharmony_ci}
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic inline int ioremap_pud_enabled(void)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	return ioremap_pud_capable;
518c2ecf20Sopenharmony_ci}
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistatic inline int ioremap_pmd_enabled(void)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	return ioremap_pmd_capable;
568c2ecf20Sopenharmony_ci}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci#else	/* !CONFIG_HAVE_ARCH_HUGE_VMAP */
598c2ecf20Sopenharmony_cistatic inline int ioremap_p4d_enabled(void) { return 0; }
608c2ecf20Sopenharmony_cistatic inline int ioremap_pud_enabled(void) { return 0; }
618c2ecf20Sopenharmony_cistatic inline int ioremap_pmd_enabled(void) { return 0; }
628c2ecf20Sopenharmony_ci#endif	/* CONFIG_HAVE_ARCH_HUGE_VMAP */
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic int ioremap_pte_range(pmd_t *pmd, unsigned long addr,
658c2ecf20Sopenharmony_ci		unsigned long end, phys_addr_t phys_addr, pgprot_t prot,
668c2ecf20Sopenharmony_ci		pgtbl_mod_mask *mask)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	pte_t *pte;
698c2ecf20Sopenharmony_ci	u64 pfn;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	pfn = phys_addr >> PAGE_SHIFT;
728c2ecf20Sopenharmony_ci	pte = pte_alloc_kernel_track(pmd, addr, mask);
738c2ecf20Sopenharmony_ci	if (!pte)
748c2ecf20Sopenharmony_ci		return -ENOMEM;
758c2ecf20Sopenharmony_ci	do {
768c2ecf20Sopenharmony_ci		BUG_ON(!pte_none(*pte));
778c2ecf20Sopenharmony_ci		set_pte_at(&init_mm, addr, pte, pfn_pte(pfn, prot));
788c2ecf20Sopenharmony_ci		pfn++;
798c2ecf20Sopenharmony_ci	} while (pte++, addr += PAGE_SIZE, addr != end);
808c2ecf20Sopenharmony_ci	*mask |= PGTBL_PTE_MODIFIED;
818c2ecf20Sopenharmony_ci	return 0;
828c2ecf20Sopenharmony_ci}
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_cistatic int ioremap_try_huge_pmd(pmd_t *pmd, unsigned long addr,
858c2ecf20Sopenharmony_ci				unsigned long end, phys_addr_t phys_addr,
868c2ecf20Sopenharmony_ci				pgprot_t prot)
878c2ecf20Sopenharmony_ci{
888c2ecf20Sopenharmony_ci	if (!ioremap_pmd_enabled())
898c2ecf20Sopenharmony_ci		return 0;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	if ((end - addr) != PMD_SIZE)
928c2ecf20Sopenharmony_ci		return 0;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	if (!IS_ALIGNED(addr, PMD_SIZE))
958c2ecf20Sopenharmony_ci		return 0;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	if (!IS_ALIGNED(phys_addr, PMD_SIZE))
988c2ecf20Sopenharmony_ci		return 0;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	if (pmd_present(*pmd) && !pmd_free_pte_page(pmd, addr))
1018c2ecf20Sopenharmony_ci		return 0;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	return pmd_set_huge(pmd, phys_addr, prot);
1048c2ecf20Sopenharmony_ci}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_cistatic inline int ioremap_pmd_range(pud_t *pud, unsigned long addr,
1078c2ecf20Sopenharmony_ci		unsigned long end, phys_addr_t phys_addr, pgprot_t prot,
1088c2ecf20Sopenharmony_ci		pgtbl_mod_mask *mask)
1098c2ecf20Sopenharmony_ci{
1108c2ecf20Sopenharmony_ci	pmd_t *pmd;
1118c2ecf20Sopenharmony_ci	unsigned long next;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	pmd = pmd_alloc_track(&init_mm, pud, addr, mask);
1148c2ecf20Sopenharmony_ci	if (!pmd)
1158c2ecf20Sopenharmony_ci		return -ENOMEM;
1168c2ecf20Sopenharmony_ci	do {
1178c2ecf20Sopenharmony_ci		next = pmd_addr_end(addr, end);
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci		if (ioremap_try_huge_pmd(pmd, addr, next, phys_addr, prot)) {
1208c2ecf20Sopenharmony_ci			*mask |= PGTBL_PMD_MODIFIED;
1218c2ecf20Sopenharmony_ci			continue;
1228c2ecf20Sopenharmony_ci		}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci		if (ioremap_pte_range(pmd, addr, next, phys_addr, prot, mask))
1258c2ecf20Sopenharmony_ci			return -ENOMEM;
1268c2ecf20Sopenharmony_ci	} while (pmd++, phys_addr += (next - addr), addr = next, addr != end);
1278c2ecf20Sopenharmony_ci	return 0;
1288c2ecf20Sopenharmony_ci}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_cistatic int ioremap_try_huge_pud(pud_t *pud, unsigned long addr,
1318c2ecf20Sopenharmony_ci				unsigned long end, phys_addr_t phys_addr,
1328c2ecf20Sopenharmony_ci				pgprot_t prot)
1338c2ecf20Sopenharmony_ci{
1348c2ecf20Sopenharmony_ci	if (!ioremap_pud_enabled())
1358c2ecf20Sopenharmony_ci		return 0;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	if ((end - addr) != PUD_SIZE)
1388c2ecf20Sopenharmony_ci		return 0;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	if (!IS_ALIGNED(addr, PUD_SIZE))
1418c2ecf20Sopenharmony_ci		return 0;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	if (!IS_ALIGNED(phys_addr, PUD_SIZE))
1448c2ecf20Sopenharmony_ci		return 0;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	if (pud_present(*pud) && !pud_free_pmd_page(pud, addr))
1478c2ecf20Sopenharmony_ci		return 0;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	return pud_set_huge(pud, phys_addr, prot);
1508c2ecf20Sopenharmony_ci}
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_cistatic inline int ioremap_pud_range(p4d_t *p4d, unsigned long addr,
1538c2ecf20Sopenharmony_ci		unsigned long end, phys_addr_t phys_addr, pgprot_t prot,
1548c2ecf20Sopenharmony_ci		pgtbl_mod_mask *mask)
1558c2ecf20Sopenharmony_ci{
1568c2ecf20Sopenharmony_ci	pud_t *pud;
1578c2ecf20Sopenharmony_ci	unsigned long next;
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	pud = pud_alloc_track(&init_mm, p4d, addr, mask);
1608c2ecf20Sopenharmony_ci	if (!pud)
1618c2ecf20Sopenharmony_ci		return -ENOMEM;
1628c2ecf20Sopenharmony_ci	do {
1638c2ecf20Sopenharmony_ci		next = pud_addr_end(addr, end);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci		if (ioremap_try_huge_pud(pud, addr, next, phys_addr, prot)) {
1668c2ecf20Sopenharmony_ci			*mask |= PGTBL_PUD_MODIFIED;
1678c2ecf20Sopenharmony_ci			continue;
1688c2ecf20Sopenharmony_ci		}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci		if (ioremap_pmd_range(pud, addr, next, phys_addr, prot, mask))
1718c2ecf20Sopenharmony_ci			return -ENOMEM;
1728c2ecf20Sopenharmony_ci	} while (pud++, phys_addr += (next - addr), addr = next, addr != end);
1738c2ecf20Sopenharmony_ci	return 0;
1748c2ecf20Sopenharmony_ci}
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_cistatic int ioremap_try_huge_p4d(p4d_t *p4d, unsigned long addr,
1778c2ecf20Sopenharmony_ci				unsigned long end, phys_addr_t phys_addr,
1788c2ecf20Sopenharmony_ci				pgprot_t prot)
1798c2ecf20Sopenharmony_ci{
1808c2ecf20Sopenharmony_ci	if (!ioremap_p4d_enabled())
1818c2ecf20Sopenharmony_ci		return 0;
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	if ((end - addr) != P4D_SIZE)
1848c2ecf20Sopenharmony_ci		return 0;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	if (!IS_ALIGNED(addr, P4D_SIZE))
1878c2ecf20Sopenharmony_ci		return 0;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	if (!IS_ALIGNED(phys_addr, P4D_SIZE))
1908c2ecf20Sopenharmony_ci		return 0;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	if (p4d_present(*p4d) && !p4d_free_pud_page(p4d, addr))
1938c2ecf20Sopenharmony_ci		return 0;
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	return p4d_set_huge(p4d, phys_addr, prot);
1968c2ecf20Sopenharmony_ci}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_cistatic inline int ioremap_p4d_range(pgd_t *pgd, unsigned long addr,
1998c2ecf20Sopenharmony_ci		unsigned long end, phys_addr_t phys_addr, pgprot_t prot,
2008c2ecf20Sopenharmony_ci		pgtbl_mod_mask *mask)
2018c2ecf20Sopenharmony_ci{
2028c2ecf20Sopenharmony_ci	p4d_t *p4d;
2038c2ecf20Sopenharmony_ci	unsigned long next;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	p4d = p4d_alloc_track(&init_mm, pgd, addr, mask);
2068c2ecf20Sopenharmony_ci	if (!p4d)
2078c2ecf20Sopenharmony_ci		return -ENOMEM;
2088c2ecf20Sopenharmony_ci	do {
2098c2ecf20Sopenharmony_ci		next = p4d_addr_end(addr, end);
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci		if (ioremap_try_huge_p4d(p4d, addr, next, phys_addr, prot)) {
2128c2ecf20Sopenharmony_ci			*mask |= PGTBL_P4D_MODIFIED;
2138c2ecf20Sopenharmony_ci			continue;
2148c2ecf20Sopenharmony_ci		}
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci		if (ioremap_pud_range(p4d, addr, next, phys_addr, prot, mask))
2178c2ecf20Sopenharmony_ci			return -ENOMEM;
2188c2ecf20Sopenharmony_ci	} while (p4d++, phys_addr += (next - addr), addr = next, addr != end);
2198c2ecf20Sopenharmony_ci	return 0;
2208c2ecf20Sopenharmony_ci}
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ciint ioremap_page_range(unsigned long addr,
2238c2ecf20Sopenharmony_ci		       unsigned long end, phys_addr_t phys_addr, pgprot_t prot)
2248c2ecf20Sopenharmony_ci{
2258c2ecf20Sopenharmony_ci	pgd_t *pgd;
2268c2ecf20Sopenharmony_ci	unsigned long start;
2278c2ecf20Sopenharmony_ci	unsigned long next;
2288c2ecf20Sopenharmony_ci	int err;
2298c2ecf20Sopenharmony_ci	pgtbl_mod_mask mask = 0;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	might_sleep();
2328c2ecf20Sopenharmony_ci	BUG_ON(addr >= end);
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	start = addr;
2358c2ecf20Sopenharmony_ci	pgd = pgd_offset_k(addr);
2368c2ecf20Sopenharmony_ci	do {
2378c2ecf20Sopenharmony_ci		next = pgd_addr_end(addr, end);
2388c2ecf20Sopenharmony_ci		err = ioremap_p4d_range(pgd, addr, next, phys_addr, prot,
2398c2ecf20Sopenharmony_ci					&mask);
2408c2ecf20Sopenharmony_ci		if (err)
2418c2ecf20Sopenharmony_ci			break;
2428c2ecf20Sopenharmony_ci	} while (pgd++, phys_addr += (next - addr), addr = next, addr != end);
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	flush_cache_vmap(start, end);
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	if (mask & ARCH_PAGE_TABLE_SYNC_MASK)
2478c2ecf20Sopenharmony_ci		arch_sync_kernel_mappings(start, end);
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	return err;
2508c2ecf20Sopenharmony_ci}
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci#ifdef CONFIG_GENERIC_IOREMAP
2538c2ecf20Sopenharmony_civoid __iomem *ioremap_prot(phys_addr_t addr, size_t size, unsigned long prot)
2548c2ecf20Sopenharmony_ci{
2558c2ecf20Sopenharmony_ci	unsigned long offset, vaddr;
2568c2ecf20Sopenharmony_ci	phys_addr_t last_addr;
2578c2ecf20Sopenharmony_ci	struct vm_struct *area;
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	/* Disallow wrap-around or zero size */
2608c2ecf20Sopenharmony_ci	last_addr = addr + size - 1;
2618c2ecf20Sopenharmony_ci	if (!size || last_addr < addr)
2628c2ecf20Sopenharmony_ci		return NULL;
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	/* Page-align mappings */
2658c2ecf20Sopenharmony_ci	offset = addr & (~PAGE_MASK);
2668c2ecf20Sopenharmony_ci	addr -= offset;
2678c2ecf20Sopenharmony_ci	size = PAGE_ALIGN(size + offset);
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	area = get_vm_area_caller(size, VM_IOREMAP,
2708c2ecf20Sopenharmony_ci			__builtin_return_address(0));
2718c2ecf20Sopenharmony_ci	if (!area)
2728c2ecf20Sopenharmony_ci		return NULL;
2738c2ecf20Sopenharmony_ci	vaddr = (unsigned long)area->addr;
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	if (ioremap_page_range(vaddr, vaddr + size, addr, __pgprot(prot))) {
2768c2ecf20Sopenharmony_ci		free_vm_area(area);
2778c2ecf20Sopenharmony_ci		return NULL;
2788c2ecf20Sopenharmony_ci	}
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci	return (void __iomem *)(vaddr + offset);
2818c2ecf20Sopenharmony_ci}
2828c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ioremap_prot);
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_civoid iounmap(volatile void __iomem *addr)
2858c2ecf20Sopenharmony_ci{
2868c2ecf20Sopenharmony_ci	vunmap((void *)((unsigned long)addr & PAGE_MASK));
2878c2ecf20Sopenharmony_ci}
2888c2ecf20Sopenharmony_ciEXPORT_SYMBOL(iounmap);
2898c2ecf20Sopenharmony_ci#endif /* CONFIG_GENERIC_IOREMAP */
290