linux/arch/sh64/mm/ioremap.c
Paul Mundt fad9e7d957 sh64: Flag sh64_get_page() as __init_refok.
sh64_get_page() wraps in to regular allocators as well as the
bootmem allocator for fetching pages, it carefully checks to
see which one it can use depending on the system state, so
the access is safe.

Signed-off-by: Paul Mundt <lethal@linux-sh.org>
2007-07-20 17:46:42 +09:00

385 lines
9.5 KiB
C

/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* arch/sh64/mm/ioremap.c
*
* Copyright (C) 2000, 2001 Paolo Alberelli
* Copyright (C) 2003, 2004 Paul Mundt
*
* Mostly derived from arch/sh/mm/ioremap.c which, in turn is mostly
* derived from arch/i386/mm/ioremap.c .
*
* (C) Copyright 1995 1996 Linus Torvalds
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/io.h>
#include <asm/pgalloc.h>
#include <asm/tlbflush.h>
#include <linux/ioport.h>
#include <linux/bootmem.h>
#include <linux/proc_fs.h>
static void shmedia_mapioaddr(unsigned long, unsigned long);
static unsigned long shmedia_ioremap(struct resource *, u32, int);
/*
* Generic mapping function (not visible outside):
*/
/*
* Remap an arbitrary physical address space into the kernel virtual
* address space. Needed when the kernel wants to access high addresses
* directly.
*
* NOTE! We need to allow non-page-aligned mappings too: we will obviously
* have to convert them into an offset in a page-aligned mapping, but the
* caller shouldn't need to know that small detail.
*/
void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
{
void * addr;
struct vm_struct * area;
unsigned long offset, last_addr;
pgprot_t pgprot;
/* Don't allow wraparound or zero size */
last_addr = phys_addr + size - 1;
if (!size || last_addr < phys_addr)
return NULL;
pgprot = __pgprot(_PAGE_PRESENT | _PAGE_READ |
_PAGE_WRITE | _PAGE_DIRTY |
_PAGE_ACCESSED | _PAGE_SHARED | flags);
/*
* Mappings have to be page-aligned
*/
offset = phys_addr & ~PAGE_MASK;
phys_addr &= PAGE_MASK;
size = PAGE_ALIGN(last_addr + 1) - phys_addr;
/*
* Ok, go for it..
*/
area = get_vm_area(size, VM_IOREMAP);
pr_debug("Get vm_area returns %p addr %p\n",area,area->addr);
if (!area)
return NULL;
area->phys_addr = phys_addr;
addr = area->addr;
if (ioremap_page_range((unsigned long)addr, (unsigned long)addr + size,
phys_addr, pgprot)) {
vunmap(addr);
return NULL;
}
return (void *) (offset + (char *)addr);
}
void iounmap(void *addr)
{
struct vm_struct *area;
vfree((void *) (PAGE_MASK & (unsigned long) addr));
area = remove_vm_area((void *) (PAGE_MASK & (unsigned long) addr));
if (!area) {
printk(KERN_ERR "iounmap: bad address %p\n", addr);
return;
}
kfree(area);
}
static struct resource shmedia_iomap = {
.name = "shmedia_iomap",
.start = IOBASE_VADDR + PAGE_SIZE,
.end = IOBASE_END - 1,
};
static void shmedia_mapioaddr(unsigned long pa, unsigned long va);
static void shmedia_unmapioaddr(unsigned long vaddr);
static unsigned long shmedia_ioremap(struct resource *res, u32 pa, int sz);
/*
* We have the same problem as the SPARC, so lets have the same comment:
* Our mini-allocator...
* Boy this is gross! We need it because we must map I/O for
* timers and interrupt controller before the kmalloc is available.
*/
#define XNMLN 15
#define XNRES 10
struct xresource {
struct resource xres; /* Must be first */
int xflag; /* 1 == used */
char xname[XNMLN+1];
};
static struct xresource xresv[XNRES];
static struct xresource *xres_alloc(void)
{
struct xresource *xrp;
int n;
xrp = xresv;
for (n = 0; n < XNRES; n++) {
if (xrp->xflag == 0) {
xrp->xflag = 1;
return xrp;
}
xrp++;
}
return NULL;
}
static void xres_free(struct xresource *xrp)
{
xrp->xflag = 0;
}
static struct resource *shmedia_find_resource(struct resource *root,
unsigned long vaddr)
{
struct resource *res;
for (res = root->child; res; res = res->sibling)
if (res->start <= vaddr && res->end >= vaddr)
return res;
return NULL;
}
static unsigned long shmedia_alloc_io(unsigned long phys, unsigned long size,
const char *name)
{
static int printed_full = 0;
struct xresource *xres;
struct resource *res;
char *tack;
int tlen;
if (name == NULL) name = "???";
if ((xres = xres_alloc()) != 0) {
tack = xres->xname;
res = &xres->xres;
} else {
if (!printed_full) {
printk("%s: done with statics, switching to kmalloc\n",
__FUNCTION__);
printed_full = 1;
}
tlen = strlen(name);
tack = kmalloc(sizeof (struct resource) + tlen + 1, GFP_KERNEL);
if (!tack)
return -ENOMEM;
memset(tack, 0, sizeof(struct resource));
res = (struct resource *) tack;
tack += sizeof (struct resource);
}
strncpy(tack, name, XNMLN);
tack[XNMLN] = 0;
res->name = tack;
return shmedia_ioremap(res, phys, size);
}
static unsigned long shmedia_ioremap(struct resource *res, u32 pa, int sz)
{
unsigned long offset = ((unsigned long) pa) & (~PAGE_MASK);
unsigned long round_sz = (offset + sz + PAGE_SIZE-1) & PAGE_MASK;
unsigned long va;
unsigned int psz;
if (allocate_resource(&shmedia_iomap, res, round_sz,
shmedia_iomap.start, shmedia_iomap.end,
PAGE_SIZE, NULL, NULL) != 0) {
panic("alloc_io_res(%s): cannot occupy\n",
(res->name != NULL)? res->name: "???");
}
va = res->start;
pa &= PAGE_MASK;
psz = (res->end - res->start + (PAGE_SIZE - 1)) / PAGE_SIZE;
/* log at boot time ... */
printk("mapioaddr: %6s [%2d page%s] va 0x%08lx pa 0x%08x\n",
((res->name != NULL) ? res->name : "???"),
psz, psz == 1 ? " " : "s", va, pa);
for (psz = res->end - res->start + 1; psz != 0; psz -= PAGE_SIZE) {
shmedia_mapioaddr(pa, va);
va += PAGE_SIZE;
pa += PAGE_SIZE;
}
res->start += offset;
res->end = res->start + sz - 1; /* not strictly necessary.. */
return res->start;
}
static void shmedia_free_io(struct resource *res)
{
unsigned long len = res->end - res->start + 1;
BUG_ON((len & (PAGE_SIZE - 1)) != 0);
while (len) {
len -= PAGE_SIZE;
shmedia_unmapioaddr(res->start + len);
}
release_resource(res);
}
static __init_refok void *sh64_get_page(void)
{
extern int after_bootmem;
void *page;
if (after_bootmem) {
page = (void *)get_zeroed_page(GFP_ATOMIC);
} else {
page = alloc_bootmem_pages(PAGE_SIZE);
}
if (!page || ((unsigned long)page & ~PAGE_MASK))
panic("sh64_get_page: Out of memory already?\n");
return page;
}
static void shmedia_mapioaddr(unsigned long pa, unsigned long va)
{
pgd_t *pgdp;
pmd_t *pmdp;
pte_t *ptep, pte;
pgprot_t prot;
unsigned long flags = 1; /* 1 = CB0-1 device */
pr_debug("shmedia_mapiopage pa %08lx va %08lx\n", pa, va);
pgdp = pgd_offset_k(va);
if (pgd_none(*pgdp) || !pgd_present(*pgdp)) {
pmdp = (pmd_t *)sh64_get_page();
set_pgd(pgdp, __pgd((unsigned long)pmdp | _KERNPG_TABLE));
}
pmdp = pmd_offset(pgdp, va);
if (pmd_none(*pmdp) || !pmd_present(*pmdp) ) {
ptep = (pte_t *)sh64_get_page();
set_pmd(pmdp, __pmd((unsigned long)ptep + _PAGE_TABLE));
}
prot = __pgprot(_PAGE_PRESENT | _PAGE_READ | _PAGE_WRITE |
_PAGE_DIRTY | _PAGE_ACCESSED | _PAGE_SHARED | flags);
pte = pfn_pte(pa >> PAGE_SHIFT, prot);
ptep = pte_offset_kernel(pmdp, va);
if (!pte_none(*ptep) &&
pte_val(*ptep) != pte_val(pte))
pte_ERROR(*ptep);
set_pte(ptep, pte);
flush_tlb_kernel_range(va, PAGE_SIZE);
}
static void shmedia_unmapioaddr(unsigned long vaddr)
{
pgd_t *pgdp;
pmd_t *pmdp;
pte_t *ptep;
pgdp = pgd_offset_k(vaddr);
pmdp = pmd_offset(pgdp, vaddr);
if (pmd_none(*pmdp) || pmd_bad(*pmdp))
return;
ptep = pte_offset_kernel(pmdp, vaddr);
if (pte_none(*ptep) || !pte_present(*ptep))
return;
clear_page((void *)ptep);
pte_clear(&init_mm, vaddr, ptep);
}
unsigned long onchip_remap(unsigned long phys, unsigned long size, const char *name)
{
if (size < PAGE_SIZE)
size = PAGE_SIZE;
return shmedia_alloc_io(phys, size, name);
}
void onchip_unmap(unsigned long vaddr)
{
struct resource *res;
unsigned int psz;
res = shmedia_find_resource(&shmedia_iomap, vaddr);
if (!res) {
printk(KERN_ERR "%s: Failed to free 0x%08lx\n",
__FUNCTION__, vaddr);
return;
}
psz = (res->end - res->start + (PAGE_SIZE - 1)) / PAGE_SIZE;
printk(KERN_DEBUG "unmapioaddr: %6s [%2d page%s] freed\n",
res->name, psz, psz == 1 ? " " : "s");
shmedia_free_io(res);
if ((char *)res >= (char *)xresv &&
(char *)res < (char *)&xresv[XNRES]) {
xres_free((struct xresource *)res);
} else {
kfree(res);
}
}
#ifdef CONFIG_PROC_FS
static int
ioremap_proc_info(char *buf, char **start, off_t fpos, int length, int *eof,
void *data)
{
char *p = buf, *e = buf + length;
struct resource *r;
const char *nm;
for (r = ((struct resource *)data)->child; r != NULL; r = r->sibling) {
if (p + 32 >= e) /* Better than nothing */
break;
if ((nm = r->name) == 0) nm = "???";
p += sprintf(p, "%08lx-%08lx: %s\n",
(unsigned long)r->start,
(unsigned long)r->end, nm);
}
return p-buf;
}
#endif /* CONFIG_PROC_FS */
static int __init register_proc_onchip(void)
{
#ifdef CONFIG_PROC_FS
create_proc_read_entry("io_map",0,0, ioremap_proc_info, &shmedia_iomap);
#endif
return 0;
}
__initcall(register_proc_onchip);