2005-04-16 15:20:36 -07:00
/*
* linux / arch / arm26 / mm / small_page . c
*
* Copyright ( C ) 1996 Russell King
* Copyright ( C ) 2003 Ian Molton
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* Changelog :
* 26 / 01 / 1996 RMK Cleaned up various areas to make little more generic
* 07 / 02 / 1999 RMK Support added for 16 K and 32 K page sizes
* containing 8 K blocks
* 23 / 05 / 2004 IM Fixed to use struct page - > lru ( thanks wli )
*
*/
# include <linux/signal.h>
# include <linux/sched.h>
# include <linux/kernel.h>
# include <linux/errno.h>
# include <linux/string.h>
# include <linux/types.h>
# include <linux/ptrace.h>
# include <linux/mman.h>
# include <linux/mm.h>
# include <linux/swap.h>
# include <linux/smp.h>
# include <linux/bitops.h>
# include <asm/pgtable.h>
# define PEDANTIC
/*
* Requirement :
* We need to be able to allocate naturally aligned memory of finer
* granularity than the page size . This is typically used for the
* second level page tables on 32 - bit ARMs .
*
* FIXME - this comment is * out of date *
* Theory :
* We " misuse " the Linux memory management system . We use alloc_page
* to allocate a page and then mark it as reserved . The Linux memory
* management system will then ignore the " offset " , " next_hash " and
* " pprev_hash " entries in the mem_map for this page .
*
* We then use a bitstring in the " offset " field to mark which segments
* of the page are in use , and manipulate this as required during the
* allocation and freeing of these small pages .
*
* We also maintain a queue of pages being used for this purpose using
* the " next_hash " and " pprev_hash " entries of mem_map ;
*/
struct order {
struct list_head queue ;
unsigned int mask ; /* (1 << shift) - 1 */
unsigned int shift ; /* (1 << shift) size of page */
unsigned int block_mask ; /* nr_blocks - 1 */
unsigned int all_used ; /* (1 << nr_blocks) - 1 */
} ;
static struct order orders [ ] = {
# if PAGE_SIZE == 32768
{ LIST_HEAD_INIT ( orders [ 0 ] . queue ) , 2047 , 11 , 15 , 0x0000ffff } ,
{ LIST_HEAD_INIT ( orders [ 1 ] . queue ) , 8191 , 13 , 3 , 0x0000000f }
# else
# error unsupported page size (ARGH!)
# endif
} ;
# define USED_MAP(pg) ((pg)->index)
# define TEST_AND_CLEAR_USED(pg,off) (test_and_clear_bit(off, &USED_MAP(pg)))
# define SET_USED(pg,off) (set_bit(off, &USED_MAP(pg)))
static DEFINE_SPINLOCK ( small_page_lock ) ;
static unsigned long __get_small_page ( int priority , struct order * order )
{
unsigned long flags ;
struct page * page ;
int offset ;
do {
spin_lock_irqsave ( & small_page_lock , flags ) ;
if ( list_empty ( & order - > queue ) )
goto need_new_page ;
page = list_entry ( order - > queue . next , struct page , lru ) ;
again :
# ifdef PEDANTIC
2005-05-01 08:59:01 -07:00
BUG_ON ( USED_MAP ( page ) & ~ order - > all_used ) ;
2005-04-16 15:20:36 -07:00
# endif
offset = ffz ( USED_MAP ( page ) ) ;
SET_USED ( page , offset ) ;
if ( USED_MAP ( page ) = = order - > all_used )
list_del_init ( & page - > lru ) ;
spin_unlock_irqrestore ( & small_page_lock , flags ) ;
return ( unsigned long ) page_address ( page ) + ( offset < < order - > shift ) ;
need_new_page :
spin_unlock_irqrestore ( & small_page_lock , flags ) ;
page = alloc_page ( priority ) ;
spin_lock_irqsave ( & small_page_lock , flags ) ;
if ( list_empty ( & order - > queue ) ) {
if ( ! page )
goto no_page ;
SetPageReserved ( page ) ;
USED_MAP ( page ) = 0 ;
list_add ( & page - > lru , & order - > queue ) ;
goto again ;
}
spin_unlock_irqrestore ( & small_page_lock , flags ) ;
__free_page ( page ) ;
} while ( 1 ) ;
no_page :
spin_unlock_irqrestore ( & small_page_lock , flags ) ;
return 0 ;
}
static void __free_small_page ( unsigned long spage , struct order * order )
{
unsigned long flags ;
struct page * page ;
if ( virt_addr_valid ( spage ) ) {
page = virt_to_page ( spage ) ;
/*
* The container - page must be marked Reserved
*/
if ( ! PageReserved ( page ) | | spage & order - > mask )
goto non_small ;
# ifdef PEDANTIC
2005-05-01 08:59:01 -07:00
BUG_ON ( USED_MAP ( page ) & ~ order - > all_used ) ;
2005-04-16 15:20:36 -07:00
# endif
spage = spage > > order - > shift ;
spage & = order - > block_mask ;
/*
* the following must be atomic wrt get_page
*/
spin_lock_irqsave ( & small_page_lock , flags ) ;
if ( USED_MAP ( page ) = = order - > all_used )
list_add ( & page - > lru , & order - > queue ) ;
if ( ! TEST_AND_CLEAR_USED ( page , spage ) )
goto already_free ;
if ( USED_MAP ( page ) = = 0 )
goto free_page ;
spin_unlock_irqrestore ( & small_page_lock , flags ) ;
}
return ;
free_page :
/*
* unlink the page from the small page queue and free it
*/
list_del_init ( & page - > lru ) ;
spin_unlock_irqrestore ( & small_page_lock , flags ) ;
ClearPageReserved ( page ) ;
__free_page ( page ) ;
return ;
non_small :
printk ( " Trying to free non-small page from %p \n " , __builtin_return_address ( 0 ) ) ;
return ;
already_free :
printk ( " Trying to free free small page from %p \n " , __builtin_return_address ( 0 ) ) ;
}
unsigned long get_page_8k ( int priority )
{
return __get_small_page ( priority , orders + 1 ) ;
}
void free_page_8k ( unsigned long spage )
{
__free_small_page ( spage , orders + 1 ) ;
}