2005-04-16 15:20:36 -07:00
/*
* 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 <asm/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 ) ;
static inline void remap_area_pte ( pte_t * pte , unsigned long address , unsigned long size ,
unsigned long phys_addr , unsigned long flags )
{
unsigned long end ;
unsigned long pfn ;
pgprot_t pgprot = __pgprot ( _PAGE_PRESENT | _PAGE_READ |
_PAGE_WRITE | _PAGE_DIRTY |
_PAGE_ACCESSED | _PAGE_SHARED | flags ) ;
address & = ~ PMD_MASK ;
end = address + size ;
if ( end > PMD_SIZE )
end = PMD_SIZE ;
if ( address > = end )
BUG ( ) ;
pfn = phys_addr > > PAGE_SHIFT ;
pr_debug ( " %s: pte %p address %lx size %lx phys_addr %lx \n " ,
__FUNCTION__ , pte , address , size , phys_addr ) ;
do {
if ( ! pte_none ( * pte ) ) {
printk ( " remap_area_pte: page already exists \n " ) ;
BUG ( ) ;
}
set_pte ( pte , pfn_pte ( pfn , pgprot ) ) ;
address + = PAGE_SIZE ;
pfn + + ;
pte + + ;
} while ( address & & ( address < end ) ) ;
}
static inline int remap_area_pmd ( pmd_t * pmd , unsigned long address , unsigned long size ,
unsigned long phys_addr , unsigned long flags )
{
unsigned long end ;
address & = ~ PGDIR_MASK ;
end = address + size ;
if ( end > PGDIR_SIZE )
end = PGDIR_SIZE ;
phys_addr - = address ;
if ( address > = end )
BUG ( ) ;
do {
[PATCH] mm: init_mm without ptlock
First step in pushing down the page_table_lock. init_mm.page_table_lock has
been used throughout the architectures (usually for ioremap): not to serialize
kernel address space allocation (that's usually vmlist_lock), but because
pud_alloc,pmd_alloc,pte_alloc_kernel expect caller holds it.
Reverse that: don't lock or unlock init_mm.page_table_lock in any of the
architectures; instead rely on pud_alloc,pmd_alloc,pte_alloc_kernel to take
and drop it when allocating a new one, to check lest a racing task already
did. Similarly no page_table_lock in vmalloc's map_vm_area.
Some temporary ugliness in __pud_alloc and __pmd_alloc: since they also handle
user mms, which are converted only by a later patch, for now they have to lock
differently according to whether or not it's init_mm.
If sources get muddled, there's a danger that an arch source taking
init_mm.page_table_lock will be mixed with common source also taking it (or
neither take it). So break the rules and make another change, which should
break the build for such a mismatch: remove the redundant mm arg from
pte_alloc_kernel (ppc64 scrapped its distinct ioremap_mm in 2.6.13).
Exceptions: arm26 used pte_alloc_kernel on user mm, now pte_alloc_map; ia64
used pte_alloc_map on init_mm, now pte_alloc_kernel; parisc had bad args to
pmd_alloc and pte_alloc_kernel in unused USE_HPPA_IOREMAP code; ppc64
map_io_page forgot to unlock on failure; ppc mmu_mapin_ram and ppc64 im_free
took page_table_lock for no good reason.
Signed-off-by: Hugh Dickins <hugh@veritas.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-10-29 18:16:21 -07:00
pte_t * pte = pte_alloc_kernel ( pmd , address ) ;
2005-04-16 15:20:36 -07:00
if ( ! pte )
return - ENOMEM ;
remap_area_pte ( pte , address , end - address , address + phys_addr , flags ) ;
address = ( address + PMD_SIZE ) & PMD_MASK ;
pmd + + ;
} while ( address & & ( address < end ) ) ;
return 0 ;
}
static int remap_area_pages ( unsigned long address , unsigned long phys_addr ,
unsigned long size , unsigned long flags )
{
int error ;
pgd_t * dir ;
unsigned long end = address + size ;
phys_addr - = address ;
dir = pgd_offset_k ( address ) ;
flush_cache_all ( ) ;
if ( address > = end )
BUG ( ) ;
do {
pmd_t * pmd = pmd_alloc ( & init_mm , dir , address ) ;
error = - ENOMEM ;
if ( ! pmd )
break ;
if ( remap_area_pmd ( pmd , address , end - address ,
phys_addr + address , flags ) ) {
break ;
}
error = 0 ;
address = ( address + PGDIR_SIZE ) & PGDIR_MASK ;
dir + + ;
} while ( address & & ( address < end ) ) ;
flush_tlb_all ( ) ;
return 0 ;
}
/*
* 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 ;
/* Don't allow wraparound or zero size */
last_addr = phys_addr + size - 1 ;
if ( ! size | | last_addr < phys_addr )
return NULL ;
/*
* 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 ( remap_area_pages ( ( unsigned long ) addr , phys_addr , size , flags ) ) {
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 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 " , r - > start , 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 ) ;