2005-04-17 02:20:36 +04:00
/*
* arch / parisc / mm / ioremap . c
*
* ( C ) Copyright 1995 1996 Linus Torvalds
* ( C ) Copyright 2001 Helge Deller < deller @ gmx . de >
2006-01-11 04:47:52 +03:00
* ( C ) Copyright 2005 Kyle McMartin < kyle @ parisc - linux . org >
2005-04-17 02:20:36 +04:00
*/
# include <linux/vmalloc.h>
# include <linux/errno.h>
# include <linux/module.h>
# include <asm/io.h>
# include <asm/pgalloc.h>
2006-01-11 04:47:52 +03:00
# include <asm/tlbflush.h>
# include <asm/cacheflush.h>
2005-04-17 02:20:36 +04:00
2006-01-11 04:47:52 +03:00
static inline void
remap_area_pte ( pte_t * pte , unsigned long address , unsigned long size ,
unsigned long phys_addr , unsigned long flags )
2005-04-17 02:20:36 +04:00
{
2006-01-11 04:47:52 +03:00
unsigned long end , pfn ;
pgprot_t pgprot = __pgprot ( _PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY |
_PAGE_ACCESSED | flags ) ;
2005-04-17 02:20:36 +04:00
address & = ~ PMD_MASK ;
2006-01-11 04:47:52 +03:00
2005-04-17 02:20:36 +04:00
end = address + size ;
if ( end > PMD_SIZE )
end = PMD_SIZE ;
2006-01-11 04:47:52 +03:00
BUG_ON ( address > = end ) ;
pfn = phys_addr > > PAGE_SHIFT ;
2005-04-17 02:20:36 +04:00
do {
2006-01-11 04:47:52 +03:00
BUG_ON ( ! pte_none ( * pte ) ) ;
set_pte ( pte , pfn_pte ( pfn , pgprot ) ) ;
2005-04-17 02:20:36 +04:00
address + = PAGE_SIZE ;
2006-01-11 04:47:52 +03:00
pfn + + ;
2005-04-17 02:20:36 +04:00
pte + + ;
} while ( address & & ( address < end ) ) ;
}
2006-01-11 04:47:52 +03:00
static inline int
remap_area_pmd ( pmd_t * pmd , unsigned long address , unsigned long size ,
unsigned long phys_addr , unsigned long flags )
2005-04-17 02:20:36 +04:00
{
unsigned long end ;
address & = ~ PGDIR_MASK ;
2006-01-11 04:47:52 +03:00
2005-04-17 02:20:36 +04:00
end = address + size ;
if ( end > PGDIR_SIZE )
end = PGDIR_SIZE ;
2006-01-11 04:47:52 +03:00
BUG_ON ( address > = end ) ;
2005-04-17 02:20:36 +04:00
phys_addr - = address ;
do {
2006-01-11 04:47:52 +03:00
pte_t * pte = pte_alloc_kernel ( pmd , address ) ;
2005-04-17 02:20:36 +04:00
if ( ! pte )
return - ENOMEM ;
2006-01-11 04:47:52 +03:00
remap_area_pte ( pte , address , end - address ,
address + phys_addr , flags ) ;
2005-04-17 02:20:36 +04:00
address = ( address + PMD_SIZE ) & PMD_MASK ;
pmd + + ;
} while ( address & & ( address < end ) ) ;
2006-01-11 04:47:52 +03:00
2005-04-17 02:20:36 +04:00
return 0 ;
}
2006-03-08 00:12:13 +03:00
# ifdef CONFIG_HPPA_IOREMAP
2006-01-11 04:47:52 +03:00
static int
remap_area_pages ( unsigned long address , unsigned long phys_addr ,
unsigned long size , unsigned long flags )
2005-04-17 02:20:36 +04:00
{
2006-01-11 04:47:52 +03:00
pgd_t * dir ;
int error = 0 ;
2005-04-17 02:20:36 +04:00
unsigned long end = address + size ;
2006-01-11 04:47:52 +03:00
BUG_ON ( address > = end ) ;
2005-04-17 02:20:36 +04:00
phys_addr - = address ;
2006-01-11 04:47:52 +03:00
dir = pgd_offset_k ( address ) ;
2005-04-17 02:20:36 +04:00
flush_cache_all ( ) ;
2006-01-11 04:47:52 +03:00
2005-04-17 02:20:36 +04:00
do {
2006-01-11 04:47:52 +03:00
pud_t * pud ;
2005-04-17 02:20:36 +04:00
pmd_t * pmd ;
2006-01-11 04:47:52 +03:00
2005-04-17 02:20:36 +04:00
error = - ENOMEM ;
2006-01-11 04:47:52 +03:00
pud = pud_alloc ( & init_mm , dir , address ) ;
if ( ! pud )
break ;
pmd = pmd_alloc ( & init_mm , pud , address ) ;
2005-04-17 02:20:36 +04:00
if ( ! pmd )
break ;
2006-01-11 04:47:52 +03:00
2005-04-17 02:20:36 +04:00
if ( remap_area_pmd ( pmd , address , end - address ,
2006-01-11 04:47:52 +03:00
phys_addr + address , flags ) )
2005-04-17 02:20:36 +04:00
break ;
2006-01-11 04:47:52 +03:00
2005-04-17 02:20:36 +04:00
error = 0 ;
address = ( address + PGDIR_SIZE ) & PGDIR_MASK ;
dir + + ;
} while ( address & & ( address < end ) ) ;
2006-01-11 04:47:52 +03:00
2005-04-17 02:20:36 +04:00
flush_tlb_all ( ) ;
2006-01-11 04:47:52 +03:00
2005-04-17 02:20:36 +04:00
return error ;
}
2006-03-08 00:12:13 +03:00
# endif /* CONFIG_HPPA_IOREMAP */
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_DEBUG_IOREMAP
static unsigned long last = 0 ;
void gsc_bad_addr ( unsigned long addr )
{
if ( time_after ( jiffies , last + HZ * 10 ) ) {
printk ( " gsc_foo() called with bad address 0x%lx \n " , addr ) ;
dump_stack ( ) ;
last = jiffies ;
}
}
EXPORT_SYMBOL ( gsc_bad_addr ) ;
void __raw_bad_addr ( const volatile void __iomem * addr )
{
if ( time_after ( jiffies , last + HZ * 10 ) ) {
printk ( " __raw_foo() called with bad address 0x%p \n " , addr ) ;
dump_stack ( ) ;
last = jiffies ;
}
}
EXPORT_SYMBOL ( __raw_bad_addr ) ;
# endif
/*
* Generic mapping function ( not visible outside ) :
*/
/*
* Remap an arbitrary physical address space into the kernel virtual
2006-01-11 04:47:52 +03:00
* address space .
2005-04-17 02:20:36 +04:00
*
* 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 __iomem * __ioremap ( unsigned long phys_addr , unsigned long size , unsigned long flags )
{
2006-03-08 00:12:13 +03:00
# if !defined(CONFIG_HPPA_IOREMAP)
2005-04-17 02:20:36 +04:00
unsigned long end = phys_addr + size - 1 ;
/* Support EISA addresses */
if ( ( phys_addr > = 0x00080000 & & end < 0x000fffff )
| | ( phys_addr > = 0x00500000 & & end < 0x03bfffff ) ) {
phys_addr | = 0xfc000000 ;
}
# ifdef CONFIG_DEBUG_IOREMAP
return ( void __iomem * ) ( phys_addr - ( 0x1UL < < NYBBLE_SHIFT ) ) ;
# else
return ( void __iomem * ) phys_addr ;
# endif
# else
2006-01-11 04:47:52 +03:00
void * addr ;
struct vm_struct * area ;
2005-04-17 02:20:36 +04:00
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 ;
/*
* Don ' t allow anybody to remap normal RAM that we ' re using . .
*/
if ( phys_addr < virt_to_phys ( high_memory ) ) {
char * t_addr , * t_end ;
struct page * page ;
t_addr = __va ( phys_addr ) ;
t_end = t_addr + ( size - 1 ) ;
2006-01-11 04:47:52 +03:00
for ( page = virt_to_page ( t_addr ) ;
page < = virt_to_page ( t_end ) ; page + + ) {
2005-04-17 02:20:36 +04:00
if ( ! PageReserved ( page ) )
return NULL ;
2006-01-11 04:47:52 +03:00
}
2005-04-17 02:20:36 +04:00
}
/*
* Mappings have to be page - aligned
*/
offset = phys_addr & ~ PAGE_MASK ;
phys_addr & = PAGE_MASK ;
size = PAGE_ALIGN ( last_addr ) - phys_addr ;
/*
* Ok , go for it . .
*/
area = get_vm_area ( size , VM_IOREMAP ) ;
if ( ! area )
return NULL ;
2006-01-11 04:47:52 +03:00
2005-04-17 02:20:36 +04:00
addr = area - > addr ;
if ( remap_area_pages ( ( unsigned long ) addr , phys_addr , size , flags ) ) {
vfree ( addr ) ;
return NULL ;
}
2006-01-11 04:47:52 +03:00
2005-04-17 02:20:36 +04:00
return ( void __iomem * ) ( offset + ( char * ) addr ) ;
# endif
}
void iounmap ( void __iomem * addr )
{
2006-03-08 00:12:13 +03:00
# ifdef CONFIG_HPPA_IOREMAP
2005-04-17 02:20:36 +04:00
if ( addr > high_memory )
return vfree ( ( void * ) ( PAGE_MASK & ( unsigned long __force ) addr ) ) ;
2006-03-08 00:12:13 +03:00
# else
return ;
2005-04-17 02:20:36 +04:00
# endif
}