The ColdFire SoC internal peripherals are mapped into virtual address space using the ACR registers of the cache control unit. This means we are using a 1:1 physical:virtual mapping for them that does not rely on page table mappings. We can quickly determine if we are accessing an internal peripheral device given the physical or vitrual address using the same range check. The implications of this mapping is that an ioremap should return the physical address as the virtual mapping __iomem cookie as well. So fix ioremap() to deal with this on ColdFire. Of course you need to take care of this in the iounmap() path as well. Reported-by: Angelo Dureghello <angelo@sysam.it> Signed-off-by: Greg Ungerer <gerg@linux-m68k.org> Reviewed-by: Angelo Dureghello <angelo@sysam.it> Tested-by: Angelo Dureghello <angelo@sysam.it>
377 lines
8.0 KiB
C
377 lines
8.0 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* linux/arch/m68k/mm/kmap.c
|
|
*
|
|
* Copyright (C) 1997 Roman Hodek
|
|
*
|
|
* 10/01/99 cleaned up the code and changing to the same interface
|
|
* used by other architectures /Roman Zippel
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/string.h>
|
|
#include <linux/types.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include <asm/setup.h>
|
|
#include <asm/segment.h>
|
|
#include <asm/page.h>
|
|
#include <asm/pgalloc.h>
|
|
#include <asm/io.h>
|
|
|
|
#undef DEBUG
|
|
|
|
#define PTRTREESIZE (256*1024)
|
|
|
|
/*
|
|
* For 040/060 we can use the virtual memory area like other architectures,
|
|
* but for 020/030 we want to use early termination page descriptors and we
|
|
* can't mix this with normal page descriptors, so we have to copy that code
|
|
* (mm/vmalloc.c) and return appropriately aligned addresses.
|
|
*/
|
|
|
|
#ifdef CPU_M68040_OR_M68060_ONLY
|
|
|
|
#define IO_SIZE PAGE_SIZE
|
|
|
|
static inline struct vm_struct *get_io_area(unsigned long size)
|
|
{
|
|
return get_vm_area(size, VM_IOREMAP);
|
|
}
|
|
|
|
|
|
static inline void free_io_area(void *addr)
|
|
{
|
|
vfree((void *)(PAGE_MASK & (unsigned long)addr));
|
|
}
|
|
|
|
#else
|
|
|
|
#define IO_SIZE (256*1024)
|
|
|
|
static struct vm_struct *iolist;
|
|
|
|
static struct vm_struct *get_io_area(unsigned long size)
|
|
{
|
|
unsigned long addr;
|
|
struct vm_struct **p, *tmp, *area;
|
|
|
|
area = kmalloc(sizeof(*area), GFP_KERNEL);
|
|
if (!area)
|
|
return NULL;
|
|
addr = KMAP_START;
|
|
for (p = &iolist; (tmp = *p) ; p = &tmp->next) {
|
|
if (size + addr < (unsigned long)tmp->addr)
|
|
break;
|
|
if (addr > KMAP_END-size) {
|
|
kfree(area);
|
|
return NULL;
|
|
}
|
|
addr = tmp->size + (unsigned long)tmp->addr;
|
|
}
|
|
area->addr = (void *)addr;
|
|
area->size = size + IO_SIZE;
|
|
area->next = *p;
|
|
*p = area;
|
|
return area;
|
|
}
|
|
|
|
static inline void free_io_area(void *addr)
|
|
{
|
|
struct vm_struct **p, *tmp;
|
|
|
|
if (!addr)
|
|
return;
|
|
addr = (void *)((unsigned long)addr & -IO_SIZE);
|
|
for (p = &iolist ; (tmp = *p) ; p = &tmp->next) {
|
|
if (tmp->addr == addr) {
|
|
*p = tmp->next;
|
|
__iounmap(tmp->addr, tmp->size);
|
|
kfree(tmp);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Map some physical address range into the kernel address space.
|
|
*/
|
|
/* Rewritten by Andreas Schwab to remove all races. */
|
|
|
|
void __iomem *__ioremap(unsigned long physaddr, unsigned long size, int cacheflag)
|
|
{
|
|
struct vm_struct *area;
|
|
unsigned long virtaddr, retaddr;
|
|
long offset;
|
|
pgd_t *pgd_dir;
|
|
pmd_t *pmd_dir;
|
|
pte_t *pte_dir;
|
|
|
|
/*
|
|
* Don't allow mappings that wrap..
|
|
*/
|
|
if (!size || physaddr > (unsigned long)(-size))
|
|
return NULL;
|
|
|
|
#ifdef CONFIG_AMIGA
|
|
if (MACH_IS_AMIGA) {
|
|
if ((physaddr >= 0x40000000) && (physaddr + size < 0x60000000)
|
|
&& (cacheflag == IOMAP_NOCACHE_SER))
|
|
return (void __iomem *)physaddr;
|
|
}
|
|
#endif
|
|
#ifdef CONFIG_COLDFIRE
|
|
if (__cf_internalio(physaddr))
|
|
return (void __iomem *) physaddr;
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
printk("ioremap: 0x%lx,0x%lx(%d) - ", physaddr, size, cacheflag);
|
|
#endif
|
|
/*
|
|
* Mappings have to be aligned
|
|
*/
|
|
offset = physaddr & (IO_SIZE - 1);
|
|
physaddr &= -IO_SIZE;
|
|
size = (size + offset + IO_SIZE - 1) & -IO_SIZE;
|
|
|
|
/*
|
|
* Ok, go for it..
|
|
*/
|
|
area = get_io_area(size);
|
|
if (!area)
|
|
return NULL;
|
|
|
|
virtaddr = (unsigned long)area->addr;
|
|
retaddr = virtaddr + offset;
|
|
#ifdef DEBUG
|
|
printk("0x%lx,0x%lx,0x%lx", physaddr, virtaddr, retaddr);
|
|
#endif
|
|
|
|
/*
|
|
* add cache and table flags to physical address
|
|
*/
|
|
if (CPU_IS_040_OR_060) {
|
|
physaddr |= (_PAGE_PRESENT | _PAGE_GLOBAL040 |
|
|
_PAGE_ACCESSED | _PAGE_DIRTY);
|
|
switch (cacheflag) {
|
|
case IOMAP_FULL_CACHING:
|
|
physaddr |= _PAGE_CACHE040;
|
|
break;
|
|
case IOMAP_NOCACHE_SER:
|
|
default:
|
|
physaddr |= _PAGE_NOCACHE_S;
|
|
break;
|
|
case IOMAP_NOCACHE_NONSER:
|
|
physaddr |= _PAGE_NOCACHE;
|
|
break;
|
|
case IOMAP_WRITETHROUGH:
|
|
physaddr |= _PAGE_CACHE040W;
|
|
break;
|
|
}
|
|
} else {
|
|
physaddr |= (_PAGE_PRESENT | _PAGE_ACCESSED |
|
|
_PAGE_DIRTY | _PAGE_READWRITE);
|
|
switch (cacheflag) {
|
|
case IOMAP_NOCACHE_SER:
|
|
case IOMAP_NOCACHE_NONSER:
|
|
default:
|
|
physaddr |= _PAGE_NOCACHE030;
|
|
break;
|
|
case IOMAP_FULL_CACHING:
|
|
case IOMAP_WRITETHROUGH:
|
|
break;
|
|
}
|
|
}
|
|
|
|
while ((long)size > 0) {
|
|
#ifdef DEBUG
|
|
if (!(virtaddr & (PTRTREESIZE-1)))
|
|
printk ("\npa=%#lx va=%#lx ", physaddr, virtaddr);
|
|
#endif
|
|
pgd_dir = pgd_offset_k(virtaddr);
|
|
pmd_dir = pmd_alloc(&init_mm, pgd_dir, virtaddr);
|
|
if (!pmd_dir) {
|
|
printk("ioremap: no mem for pmd_dir\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (CPU_IS_020_OR_030) {
|
|
pmd_dir->pmd[(virtaddr/PTRTREESIZE) & 15] = physaddr;
|
|
physaddr += PTRTREESIZE;
|
|
virtaddr += PTRTREESIZE;
|
|
size -= PTRTREESIZE;
|
|
} else {
|
|
pte_dir = pte_alloc_kernel(pmd_dir, virtaddr);
|
|
if (!pte_dir) {
|
|
printk("ioremap: no mem for pte_dir\n");
|
|
return NULL;
|
|
}
|
|
|
|
pte_val(*pte_dir) = physaddr;
|
|
virtaddr += PAGE_SIZE;
|
|
physaddr += PAGE_SIZE;
|
|
size -= PAGE_SIZE;
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
printk("\n");
|
|
#endif
|
|
flush_tlb_all();
|
|
|
|
return (void __iomem *)retaddr;
|
|
}
|
|
EXPORT_SYMBOL(__ioremap);
|
|
|
|
/*
|
|
* Unmap an ioremap()ed region again
|
|
*/
|
|
void iounmap(void __iomem *addr)
|
|
{
|
|
#ifdef CONFIG_AMIGA
|
|
if ((!MACH_IS_AMIGA) ||
|
|
(((unsigned long)addr < 0x40000000) ||
|
|
((unsigned long)addr > 0x60000000)))
|
|
free_io_area((__force void *)addr);
|
|
#else
|
|
#ifdef CONFIG_COLDFIRE
|
|
if (cf_internalio(addr))
|
|
return;
|
|
#endif
|
|
free_io_area((__force void *)addr);
|
|
#endif
|
|
}
|
|
EXPORT_SYMBOL(iounmap);
|
|
|
|
/*
|
|
* __iounmap unmaps nearly everything, so be careful
|
|
* Currently it doesn't free pointer/page tables anymore but this
|
|
* wasn't used anyway and might be added later.
|
|
*/
|
|
void __iounmap(void *addr, unsigned long size)
|
|
{
|
|
unsigned long virtaddr = (unsigned long)addr;
|
|
pgd_t *pgd_dir;
|
|
pmd_t *pmd_dir;
|
|
pte_t *pte_dir;
|
|
|
|
while ((long)size > 0) {
|
|
pgd_dir = pgd_offset_k(virtaddr);
|
|
if (pgd_bad(*pgd_dir)) {
|
|
printk("iounmap: bad pgd(%08lx)\n", pgd_val(*pgd_dir));
|
|
pgd_clear(pgd_dir);
|
|
return;
|
|
}
|
|
pmd_dir = pmd_offset(pgd_dir, virtaddr);
|
|
|
|
if (CPU_IS_020_OR_030) {
|
|
int pmd_off = (virtaddr/PTRTREESIZE) & 15;
|
|
int pmd_type = pmd_dir->pmd[pmd_off] & _DESCTYPE_MASK;
|
|
|
|
if (pmd_type == _PAGE_PRESENT) {
|
|
pmd_dir->pmd[pmd_off] = 0;
|
|
virtaddr += PTRTREESIZE;
|
|
size -= PTRTREESIZE;
|
|
continue;
|
|
} else if (pmd_type == 0)
|
|
continue;
|
|
}
|
|
|
|
if (pmd_bad(*pmd_dir)) {
|
|
printk("iounmap: bad pmd (%08lx)\n", pmd_val(*pmd_dir));
|
|
pmd_clear(pmd_dir);
|
|
return;
|
|
}
|
|
pte_dir = pte_offset_kernel(pmd_dir, virtaddr);
|
|
|
|
pte_val(*pte_dir) = 0;
|
|
virtaddr += PAGE_SIZE;
|
|
size -= PAGE_SIZE;
|
|
}
|
|
|
|
flush_tlb_all();
|
|
}
|
|
|
|
/*
|
|
* Set new cache mode for some kernel address space.
|
|
* The caller must push data for that range itself, if such data may already
|
|
* be in the cache.
|
|
*/
|
|
void kernel_set_cachemode(void *addr, unsigned long size, int cmode)
|
|
{
|
|
unsigned long virtaddr = (unsigned long)addr;
|
|
pgd_t *pgd_dir;
|
|
pmd_t *pmd_dir;
|
|
pte_t *pte_dir;
|
|
|
|
if (CPU_IS_040_OR_060) {
|
|
switch (cmode) {
|
|
case IOMAP_FULL_CACHING:
|
|
cmode = _PAGE_CACHE040;
|
|
break;
|
|
case IOMAP_NOCACHE_SER:
|
|
default:
|
|
cmode = _PAGE_NOCACHE_S;
|
|
break;
|
|
case IOMAP_NOCACHE_NONSER:
|
|
cmode = _PAGE_NOCACHE;
|
|
break;
|
|
case IOMAP_WRITETHROUGH:
|
|
cmode = _PAGE_CACHE040W;
|
|
break;
|
|
}
|
|
} else {
|
|
switch (cmode) {
|
|
case IOMAP_NOCACHE_SER:
|
|
case IOMAP_NOCACHE_NONSER:
|
|
default:
|
|
cmode = _PAGE_NOCACHE030;
|
|
break;
|
|
case IOMAP_FULL_CACHING:
|
|
case IOMAP_WRITETHROUGH:
|
|
cmode = 0;
|
|
}
|
|
}
|
|
|
|
while ((long)size > 0) {
|
|
pgd_dir = pgd_offset_k(virtaddr);
|
|
if (pgd_bad(*pgd_dir)) {
|
|
printk("iocachemode: bad pgd(%08lx)\n", pgd_val(*pgd_dir));
|
|
pgd_clear(pgd_dir);
|
|
return;
|
|
}
|
|
pmd_dir = pmd_offset(pgd_dir, virtaddr);
|
|
|
|
if (CPU_IS_020_OR_030) {
|
|
int pmd_off = (virtaddr/PTRTREESIZE) & 15;
|
|
|
|
if ((pmd_dir->pmd[pmd_off] & _DESCTYPE_MASK) == _PAGE_PRESENT) {
|
|
pmd_dir->pmd[pmd_off] = (pmd_dir->pmd[pmd_off] &
|
|
_CACHEMASK040) | cmode;
|
|
virtaddr += PTRTREESIZE;
|
|
size -= PTRTREESIZE;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (pmd_bad(*pmd_dir)) {
|
|
printk("iocachemode: bad pmd (%08lx)\n", pmd_val(*pmd_dir));
|
|
pmd_clear(pmd_dir);
|
|
return;
|
|
}
|
|
pte_dir = pte_offset_kernel(pmd_dir, virtaddr);
|
|
|
|
pte_val(*pte_dir) = (pte_val(*pte_dir) & _CACHEMASK040) | cmode;
|
|
virtaddr += PAGE_SIZE;
|
|
size -= PAGE_SIZE;
|
|
}
|
|
|
|
flush_tlb_all();
|
|
}
|
|
EXPORT_SYMBOL(kernel_set_cachemode);
|