1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Re-map IO memory to kernel address space so that we can access it.
4*4882a593Smuzhiyun * This is needed for high PCI addresses that aren't mapped in the
5*4882a593Smuzhiyun * 640k-1MB IO memory area on PC's
6*4882a593Smuzhiyun *
7*4882a593Smuzhiyun * (C) Copyright 1995 1996 Linus Torvalds
8*4882a593Smuzhiyun */
9*4882a593Smuzhiyun #include <linux/vmalloc.h>
10*4882a593Smuzhiyun #include <linux/mm.h>
11*4882a593Smuzhiyun #include <linux/sched.h>
12*4882a593Smuzhiyun #include <linux/io.h>
13*4882a593Smuzhiyun #include <linux/export.h>
14*4882a593Smuzhiyun #include <asm/cacheflush.h>
15*4882a593Smuzhiyun
16*4882a593Smuzhiyun #include "pgalloc-track.h"
17*4882a593Smuzhiyun
18*4882a593Smuzhiyun #ifdef CONFIG_HAVE_ARCH_HUGE_VMAP
19*4882a593Smuzhiyun static int __read_mostly ioremap_p4d_capable;
20*4882a593Smuzhiyun static int __read_mostly ioremap_pud_capable;
21*4882a593Smuzhiyun static int __read_mostly ioremap_pmd_capable;
22*4882a593Smuzhiyun static int __read_mostly ioremap_huge_disabled;
23*4882a593Smuzhiyun
set_nohugeiomap(char * str)24*4882a593Smuzhiyun static int __init set_nohugeiomap(char *str)
25*4882a593Smuzhiyun {
26*4882a593Smuzhiyun ioremap_huge_disabled = 1;
27*4882a593Smuzhiyun return 0;
28*4882a593Smuzhiyun }
29*4882a593Smuzhiyun early_param("nohugeiomap", set_nohugeiomap);
30*4882a593Smuzhiyun
ioremap_huge_init(void)31*4882a593Smuzhiyun void __init ioremap_huge_init(void)
32*4882a593Smuzhiyun {
33*4882a593Smuzhiyun if (!ioremap_huge_disabled) {
34*4882a593Smuzhiyun if (arch_ioremap_p4d_supported())
35*4882a593Smuzhiyun ioremap_p4d_capable = 1;
36*4882a593Smuzhiyun if (arch_ioremap_pud_supported())
37*4882a593Smuzhiyun ioremap_pud_capable = 1;
38*4882a593Smuzhiyun if (arch_ioremap_pmd_supported())
39*4882a593Smuzhiyun ioremap_pmd_capable = 1;
40*4882a593Smuzhiyun }
41*4882a593Smuzhiyun }
42*4882a593Smuzhiyun
ioremap_p4d_enabled(void)43*4882a593Smuzhiyun static inline int ioremap_p4d_enabled(void)
44*4882a593Smuzhiyun {
45*4882a593Smuzhiyun return ioremap_p4d_capable;
46*4882a593Smuzhiyun }
47*4882a593Smuzhiyun
ioremap_pud_enabled(void)48*4882a593Smuzhiyun static inline int ioremap_pud_enabled(void)
49*4882a593Smuzhiyun {
50*4882a593Smuzhiyun return ioremap_pud_capable;
51*4882a593Smuzhiyun }
52*4882a593Smuzhiyun
ioremap_pmd_enabled(void)53*4882a593Smuzhiyun static inline int ioremap_pmd_enabled(void)
54*4882a593Smuzhiyun {
55*4882a593Smuzhiyun return ioremap_pmd_capable;
56*4882a593Smuzhiyun }
57*4882a593Smuzhiyun
58*4882a593Smuzhiyun #else /* !CONFIG_HAVE_ARCH_HUGE_VMAP */
ioremap_p4d_enabled(void)59*4882a593Smuzhiyun static inline int ioremap_p4d_enabled(void) { return 0; }
ioremap_pud_enabled(void)60*4882a593Smuzhiyun static inline int ioremap_pud_enabled(void) { return 0; }
ioremap_pmd_enabled(void)61*4882a593Smuzhiyun static inline int ioremap_pmd_enabled(void) { return 0; }
62*4882a593Smuzhiyun #endif /* CONFIG_HAVE_ARCH_HUGE_VMAP */
63*4882a593Smuzhiyun
ioremap_pte_range(pmd_t * pmd,unsigned long addr,unsigned long end,phys_addr_t phys_addr,pgprot_t prot,pgtbl_mod_mask * mask)64*4882a593Smuzhiyun static int ioremap_pte_range(pmd_t *pmd, unsigned long addr,
65*4882a593Smuzhiyun unsigned long end, phys_addr_t phys_addr, pgprot_t prot,
66*4882a593Smuzhiyun pgtbl_mod_mask *mask)
67*4882a593Smuzhiyun {
68*4882a593Smuzhiyun pte_t *pte;
69*4882a593Smuzhiyun u64 pfn;
70*4882a593Smuzhiyun
71*4882a593Smuzhiyun pfn = phys_addr >> PAGE_SHIFT;
72*4882a593Smuzhiyun pte = pte_alloc_kernel_track(pmd, addr, mask);
73*4882a593Smuzhiyun if (!pte)
74*4882a593Smuzhiyun return -ENOMEM;
75*4882a593Smuzhiyun do {
76*4882a593Smuzhiyun BUG_ON(!pte_none(*pte));
77*4882a593Smuzhiyun set_pte_at(&init_mm, addr, pte, pfn_pte(pfn, prot));
78*4882a593Smuzhiyun pfn++;
79*4882a593Smuzhiyun } while (pte++, addr += PAGE_SIZE, addr != end);
80*4882a593Smuzhiyun *mask |= PGTBL_PTE_MODIFIED;
81*4882a593Smuzhiyun return 0;
82*4882a593Smuzhiyun }
83*4882a593Smuzhiyun
ioremap_try_huge_pmd(pmd_t * pmd,unsigned long addr,unsigned long end,phys_addr_t phys_addr,pgprot_t prot)84*4882a593Smuzhiyun static int ioremap_try_huge_pmd(pmd_t *pmd, unsigned long addr,
85*4882a593Smuzhiyun unsigned long end, phys_addr_t phys_addr,
86*4882a593Smuzhiyun pgprot_t prot)
87*4882a593Smuzhiyun {
88*4882a593Smuzhiyun if (!ioremap_pmd_enabled())
89*4882a593Smuzhiyun return 0;
90*4882a593Smuzhiyun
91*4882a593Smuzhiyun if ((end - addr) != PMD_SIZE)
92*4882a593Smuzhiyun return 0;
93*4882a593Smuzhiyun
94*4882a593Smuzhiyun if (!IS_ALIGNED(addr, PMD_SIZE))
95*4882a593Smuzhiyun return 0;
96*4882a593Smuzhiyun
97*4882a593Smuzhiyun if (!IS_ALIGNED(phys_addr, PMD_SIZE))
98*4882a593Smuzhiyun return 0;
99*4882a593Smuzhiyun
100*4882a593Smuzhiyun if (pmd_present(*pmd) && !pmd_free_pte_page(pmd, addr))
101*4882a593Smuzhiyun return 0;
102*4882a593Smuzhiyun
103*4882a593Smuzhiyun return pmd_set_huge(pmd, phys_addr, prot);
104*4882a593Smuzhiyun }
105*4882a593Smuzhiyun
ioremap_pmd_range(pud_t * pud,unsigned long addr,unsigned long end,phys_addr_t phys_addr,pgprot_t prot,pgtbl_mod_mask * mask)106*4882a593Smuzhiyun static inline int ioremap_pmd_range(pud_t *pud, unsigned long addr,
107*4882a593Smuzhiyun unsigned long end, phys_addr_t phys_addr, pgprot_t prot,
108*4882a593Smuzhiyun pgtbl_mod_mask *mask)
109*4882a593Smuzhiyun {
110*4882a593Smuzhiyun pmd_t *pmd;
111*4882a593Smuzhiyun unsigned long next;
112*4882a593Smuzhiyun
113*4882a593Smuzhiyun pmd = pmd_alloc_track(&init_mm, pud, addr, mask);
114*4882a593Smuzhiyun if (!pmd)
115*4882a593Smuzhiyun return -ENOMEM;
116*4882a593Smuzhiyun do {
117*4882a593Smuzhiyun next = pmd_addr_end(addr, end);
118*4882a593Smuzhiyun
119*4882a593Smuzhiyun if (ioremap_try_huge_pmd(pmd, addr, next, phys_addr, prot)) {
120*4882a593Smuzhiyun *mask |= PGTBL_PMD_MODIFIED;
121*4882a593Smuzhiyun continue;
122*4882a593Smuzhiyun }
123*4882a593Smuzhiyun
124*4882a593Smuzhiyun if (ioremap_pte_range(pmd, addr, next, phys_addr, prot, mask))
125*4882a593Smuzhiyun return -ENOMEM;
126*4882a593Smuzhiyun } while (pmd++, phys_addr += (next - addr), addr = next, addr != end);
127*4882a593Smuzhiyun return 0;
128*4882a593Smuzhiyun }
129*4882a593Smuzhiyun
ioremap_try_huge_pud(pud_t * pud,unsigned long addr,unsigned long end,phys_addr_t phys_addr,pgprot_t prot)130*4882a593Smuzhiyun static int ioremap_try_huge_pud(pud_t *pud, unsigned long addr,
131*4882a593Smuzhiyun unsigned long end, phys_addr_t phys_addr,
132*4882a593Smuzhiyun pgprot_t prot)
133*4882a593Smuzhiyun {
134*4882a593Smuzhiyun if (!ioremap_pud_enabled())
135*4882a593Smuzhiyun return 0;
136*4882a593Smuzhiyun
137*4882a593Smuzhiyun if ((end - addr) != PUD_SIZE)
138*4882a593Smuzhiyun return 0;
139*4882a593Smuzhiyun
140*4882a593Smuzhiyun if (!IS_ALIGNED(addr, PUD_SIZE))
141*4882a593Smuzhiyun return 0;
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun if (!IS_ALIGNED(phys_addr, PUD_SIZE))
144*4882a593Smuzhiyun return 0;
145*4882a593Smuzhiyun
146*4882a593Smuzhiyun if (pud_present(*pud) && !pud_free_pmd_page(pud, addr))
147*4882a593Smuzhiyun return 0;
148*4882a593Smuzhiyun
149*4882a593Smuzhiyun return pud_set_huge(pud, phys_addr, prot);
150*4882a593Smuzhiyun }
151*4882a593Smuzhiyun
ioremap_pud_range(p4d_t * p4d,unsigned long addr,unsigned long end,phys_addr_t phys_addr,pgprot_t prot,pgtbl_mod_mask * mask)152*4882a593Smuzhiyun static inline int ioremap_pud_range(p4d_t *p4d, unsigned long addr,
153*4882a593Smuzhiyun unsigned long end, phys_addr_t phys_addr, pgprot_t prot,
154*4882a593Smuzhiyun pgtbl_mod_mask *mask)
155*4882a593Smuzhiyun {
156*4882a593Smuzhiyun pud_t *pud;
157*4882a593Smuzhiyun unsigned long next;
158*4882a593Smuzhiyun
159*4882a593Smuzhiyun pud = pud_alloc_track(&init_mm, p4d, addr, mask);
160*4882a593Smuzhiyun if (!pud)
161*4882a593Smuzhiyun return -ENOMEM;
162*4882a593Smuzhiyun do {
163*4882a593Smuzhiyun next = pud_addr_end(addr, end);
164*4882a593Smuzhiyun
165*4882a593Smuzhiyun if (ioremap_try_huge_pud(pud, addr, next, phys_addr, prot)) {
166*4882a593Smuzhiyun *mask |= PGTBL_PUD_MODIFIED;
167*4882a593Smuzhiyun continue;
168*4882a593Smuzhiyun }
169*4882a593Smuzhiyun
170*4882a593Smuzhiyun if (ioremap_pmd_range(pud, addr, next, phys_addr, prot, mask))
171*4882a593Smuzhiyun return -ENOMEM;
172*4882a593Smuzhiyun } while (pud++, phys_addr += (next - addr), addr = next, addr != end);
173*4882a593Smuzhiyun return 0;
174*4882a593Smuzhiyun }
175*4882a593Smuzhiyun
ioremap_try_huge_p4d(p4d_t * p4d,unsigned long addr,unsigned long end,phys_addr_t phys_addr,pgprot_t prot)176*4882a593Smuzhiyun static int ioremap_try_huge_p4d(p4d_t *p4d, unsigned long addr,
177*4882a593Smuzhiyun unsigned long end, phys_addr_t phys_addr,
178*4882a593Smuzhiyun pgprot_t prot)
179*4882a593Smuzhiyun {
180*4882a593Smuzhiyun if (!ioremap_p4d_enabled())
181*4882a593Smuzhiyun return 0;
182*4882a593Smuzhiyun
183*4882a593Smuzhiyun if ((end - addr) != P4D_SIZE)
184*4882a593Smuzhiyun return 0;
185*4882a593Smuzhiyun
186*4882a593Smuzhiyun if (!IS_ALIGNED(addr, P4D_SIZE))
187*4882a593Smuzhiyun return 0;
188*4882a593Smuzhiyun
189*4882a593Smuzhiyun if (!IS_ALIGNED(phys_addr, P4D_SIZE))
190*4882a593Smuzhiyun return 0;
191*4882a593Smuzhiyun
192*4882a593Smuzhiyun if (p4d_present(*p4d) && !p4d_free_pud_page(p4d, addr))
193*4882a593Smuzhiyun return 0;
194*4882a593Smuzhiyun
195*4882a593Smuzhiyun return p4d_set_huge(p4d, phys_addr, prot);
196*4882a593Smuzhiyun }
197*4882a593Smuzhiyun
ioremap_p4d_range(pgd_t * pgd,unsigned long addr,unsigned long end,phys_addr_t phys_addr,pgprot_t prot,pgtbl_mod_mask * mask)198*4882a593Smuzhiyun static inline int ioremap_p4d_range(pgd_t *pgd, unsigned long addr,
199*4882a593Smuzhiyun unsigned long end, phys_addr_t phys_addr, pgprot_t prot,
200*4882a593Smuzhiyun pgtbl_mod_mask *mask)
201*4882a593Smuzhiyun {
202*4882a593Smuzhiyun p4d_t *p4d;
203*4882a593Smuzhiyun unsigned long next;
204*4882a593Smuzhiyun
205*4882a593Smuzhiyun p4d = p4d_alloc_track(&init_mm, pgd, addr, mask);
206*4882a593Smuzhiyun if (!p4d)
207*4882a593Smuzhiyun return -ENOMEM;
208*4882a593Smuzhiyun do {
209*4882a593Smuzhiyun next = p4d_addr_end(addr, end);
210*4882a593Smuzhiyun
211*4882a593Smuzhiyun if (ioremap_try_huge_p4d(p4d, addr, next, phys_addr, prot)) {
212*4882a593Smuzhiyun *mask |= PGTBL_P4D_MODIFIED;
213*4882a593Smuzhiyun continue;
214*4882a593Smuzhiyun }
215*4882a593Smuzhiyun
216*4882a593Smuzhiyun if (ioremap_pud_range(p4d, addr, next, phys_addr, prot, mask))
217*4882a593Smuzhiyun return -ENOMEM;
218*4882a593Smuzhiyun } while (p4d++, phys_addr += (next - addr), addr = next, addr != end);
219*4882a593Smuzhiyun return 0;
220*4882a593Smuzhiyun }
221*4882a593Smuzhiyun
ioremap_page_range(unsigned long addr,unsigned long end,phys_addr_t phys_addr,pgprot_t prot)222*4882a593Smuzhiyun int ioremap_page_range(unsigned long addr,
223*4882a593Smuzhiyun unsigned long end, phys_addr_t phys_addr, pgprot_t prot)
224*4882a593Smuzhiyun {
225*4882a593Smuzhiyun pgd_t *pgd;
226*4882a593Smuzhiyun unsigned long start;
227*4882a593Smuzhiyun unsigned long next;
228*4882a593Smuzhiyun int err;
229*4882a593Smuzhiyun pgtbl_mod_mask mask = 0;
230*4882a593Smuzhiyun
231*4882a593Smuzhiyun might_sleep();
232*4882a593Smuzhiyun BUG_ON(addr >= end);
233*4882a593Smuzhiyun
234*4882a593Smuzhiyun start = addr;
235*4882a593Smuzhiyun pgd = pgd_offset_k(addr);
236*4882a593Smuzhiyun do {
237*4882a593Smuzhiyun next = pgd_addr_end(addr, end);
238*4882a593Smuzhiyun err = ioremap_p4d_range(pgd, addr, next, phys_addr, prot,
239*4882a593Smuzhiyun &mask);
240*4882a593Smuzhiyun if (err)
241*4882a593Smuzhiyun break;
242*4882a593Smuzhiyun } while (pgd++, phys_addr += (next - addr), addr = next, addr != end);
243*4882a593Smuzhiyun
244*4882a593Smuzhiyun flush_cache_vmap(start, end);
245*4882a593Smuzhiyun
246*4882a593Smuzhiyun if (mask & ARCH_PAGE_TABLE_SYNC_MASK)
247*4882a593Smuzhiyun arch_sync_kernel_mappings(start, end);
248*4882a593Smuzhiyun
249*4882a593Smuzhiyun return err;
250*4882a593Smuzhiyun }
251*4882a593Smuzhiyun
252*4882a593Smuzhiyun #ifdef CONFIG_GENERIC_IOREMAP
ioremap_prot(phys_addr_t addr,size_t size,unsigned long prot)253*4882a593Smuzhiyun void __iomem *ioremap_prot(phys_addr_t addr, size_t size, unsigned long prot)
254*4882a593Smuzhiyun {
255*4882a593Smuzhiyun unsigned long offset, vaddr;
256*4882a593Smuzhiyun phys_addr_t last_addr;
257*4882a593Smuzhiyun struct vm_struct *area;
258*4882a593Smuzhiyun
259*4882a593Smuzhiyun /* Disallow wrap-around or zero size */
260*4882a593Smuzhiyun last_addr = addr + size - 1;
261*4882a593Smuzhiyun if (!size || last_addr < addr)
262*4882a593Smuzhiyun return NULL;
263*4882a593Smuzhiyun
264*4882a593Smuzhiyun /* Page-align mappings */
265*4882a593Smuzhiyun offset = addr & (~PAGE_MASK);
266*4882a593Smuzhiyun addr -= offset;
267*4882a593Smuzhiyun size = PAGE_ALIGN(size + offset);
268*4882a593Smuzhiyun
269*4882a593Smuzhiyun area = get_vm_area_caller(size, VM_IOREMAP,
270*4882a593Smuzhiyun __builtin_return_address(0));
271*4882a593Smuzhiyun if (!area)
272*4882a593Smuzhiyun return NULL;
273*4882a593Smuzhiyun vaddr = (unsigned long)area->addr;
274*4882a593Smuzhiyun
275*4882a593Smuzhiyun if (ioremap_page_range(vaddr, vaddr + size, addr, __pgprot(prot))) {
276*4882a593Smuzhiyun free_vm_area(area);
277*4882a593Smuzhiyun return NULL;
278*4882a593Smuzhiyun }
279*4882a593Smuzhiyun
280*4882a593Smuzhiyun return (void __iomem *)(vaddr + offset);
281*4882a593Smuzhiyun }
282*4882a593Smuzhiyun EXPORT_SYMBOL(ioremap_prot);
283*4882a593Smuzhiyun
iounmap(volatile void __iomem * addr)284*4882a593Smuzhiyun void iounmap(volatile void __iomem *addr)
285*4882a593Smuzhiyun {
286*4882a593Smuzhiyun vunmap((void *)((unsigned long)addr & PAGE_MASK));
287*4882a593Smuzhiyun }
288*4882a593Smuzhiyun EXPORT_SYMBOL(iounmap);
289*4882a593Smuzhiyun #endif /* CONFIG_GENERIC_IOREMAP */
290