Logo Search packages:      
Sourcecode: linux-2.6 version File versions

small_page.c

/*
 *  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 16K and 32K page sizes
 *                containing 8K 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
            BUG_ON(USED_MAP(page) & ~order->all_used);
#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
            BUG_ON(USED_MAP(page) & ~order->all_used);
#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);
}

Generated by  Doxygen 1.6.0   Back to index