sos-code-article10/sos/physmem.c

388 lines
11 KiB
C

/* Copyright (C) 2004 David Decotigny
Copyright (C) 2000 The KOS Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
*/
#include <sos/list.h>
#include <sos/macros.h>
#include <sos/assert.h>
#include <sos/klibc.h>
#include "physmem.h"
/** A descriptor for a physical page in SOS */
struct physical_page_descr
{
/** The physical base address for the page */
sos_paddr_t paddr;
/** The reference count for this physical page. > 0 means that the
page is in the nonfree list. */
sos_count_t ref_cnt;
/**
* An additional counter for user-defined use. The management of this
* counter is up to the user, however a simple set of rules applies:
* - it can only be incremented/decremented if the page is referenced
* - when it reaches 0, no automatic action is taken
* The first rule means in particular that a page whose reference
* count reaches 0 (=> will be freed) cannot have a custom_count
* value > 0 ! The system will be HALTED if this ever happens
*/
sos_count_t occupation_cnt;
/** Some data associated with the page when it is mapped in kernel space */
struct sos_kmem_range *kernel_range;
/** The other pages on the list (nonfree, free) */
struct physical_page_descr *prev, *next;
};
/** These are some markers present in the executable file (see sos.lds) */
extern char __b_kernel, __e_kernel;
/** The array of ppage descriptors will be located at this address */
#define PAGE_DESCR_ARRAY_ADDR \
SOS_PAGE_ALIGN_SUP((sos_paddr_t) (& __e_kernel))
static struct physical_page_descr * physical_page_descr_array;
/** The list of physical pages currently available */
static struct physical_page_descr *free_ppage;
/** The list of physical pages currently allocated */
static struct physical_page_descr *nonfree_ppage;
/** We will store here the interval of valid physical addresses */
static sos_paddr_t physmem_base, physmem_top;
/** We store the number of pages nonfree/free */
static sos_count_t physmem_total_pages, physmem_nonfree_pages;
sos_ret_t sos_physmem_subsystem_setup(sos_size_t ram_size,
/* out */sos_paddr_t *kernel_core_base,
/* out */sos_paddr_t *kernel_core_top)
{
/* The iterator over the page descriptors */
struct physical_page_descr *ppage_descr;
/* The iterator over the physical addresses */
sos_paddr_t ppage_addr;
/* Make sure ram size is aligned on a page boundary */
ram_size = SOS_PAGE_ALIGN_INF(ram_size);/* Yes, we may lose at most a page */
/* Reset the nonfree/free page lists before building them */
free_ppage = nonfree_ppage = NULL;
physmem_total_pages = physmem_nonfree_pages = 0;
/* Make sure that there is enough memory to store the array of page
descriptors */
*kernel_core_base = SOS_PAGE_ALIGN_INF((sos_paddr_t)(& __b_kernel));
*kernel_core_top
= PAGE_DESCR_ARRAY_ADDR
+ SOS_PAGE_ALIGN_SUP( (ram_size >> SOS_PAGE_SHIFT)
* sizeof(struct physical_page_descr));
if (*kernel_core_top > ram_size)
return -SOS_ENOMEM;
/* Page 0-4kB is not available in order to return address 0 as a
means to signal "no page available" */
physmem_base = SOS_PAGE_SIZE;
physmem_top = ram_size;
/* Setup the page descriptor arrray */
physical_page_descr_array
= (struct physical_page_descr*)PAGE_DESCR_ARRAY_ADDR;
/* Scan the list of physical pages */
for (ppage_addr = 0,
ppage_descr = physical_page_descr_array ;
ppage_addr < physmem_top ;
ppage_addr += SOS_PAGE_SIZE,
ppage_descr ++)
{
enum { PPAGE_MARK_RESERVED, PPAGE_MARK_FREE,
PPAGE_MARK_KERNEL, PPAGE_MARK_HWMAP } todo;
memset(ppage_descr, 0x0, sizeof(struct physical_page_descr));
/* Init the page descriptor for this page */
ppage_descr->paddr = ppage_addr;
/* Reserved : 0 ... base */
if (ppage_addr < physmem_base)
todo = PPAGE_MARK_RESERVED;
/* Free : base ... BIOS */
else if ((ppage_addr >= physmem_base)
&& (ppage_addr < BIOS_N_VIDEO_START))
todo = PPAGE_MARK_FREE;
/* Used : BIOS */
else if ((ppage_addr >= BIOS_N_VIDEO_START)
&& (ppage_addr < BIOS_N_VIDEO_END))
todo = PPAGE_MARK_HWMAP;
/* Free : BIOS ... kernel */
else if ((ppage_addr >= BIOS_N_VIDEO_END)
&& (ppage_addr < (sos_paddr_t) (& __b_kernel)))
todo = PPAGE_MARK_FREE;
/* Used : Kernel code/data/bss + physcal page descr array */
else if ((ppage_addr >= *kernel_core_base)
&& (ppage_addr < *kernel_core_top))
todo = PPAGE_MARK_KERNEL;
/* Free : first page of descr ... end of RAM */
else
todo = PPAGE_MARK_FREE;
/* Actually does the insertion in the nonfree/free page lists */
physmem_total_pages ++;
switch (todo)
{
case PPAGE_MARK_FREE:
ppage_descr->ref_cnt = 0;
list_add_head(free_ppage, ppage_descr);
break;
case PPAGE_MARK_KERNEL:
case PPAGE_MARK_HWMAP:
ppage_descr->ref_cnt = 1;
list_add_head(nonfree_ppage, ppage_descr);
physmem_nonfree_pages ++;
break;
default:
/* Reserved page: nop */
break;
}
}
return SOS_OK;
}
sos_paddr_t sos_physmem_ref_physpage_new(sos_bool_t can_block)
{
struct physical_page_descr *ppage_descr;
if (! free_ppage)
return (sos_paddr_t)NULL;
/* Retrieve a page in the free list */
ppage_descr = list_pop_head(free_ppage);
/* The page is assumed not to be already in the nonfree list */
SOS_ASSERT_FATAL(ppage_descr->ref_cnt == 0);
/* Mark the page as nonfree (this of course sets the ref count to 1) */
ppage_descr->ref_cnt ++;
/* The page descriptor should be unmodified since previous
deallocation. Otherwise this means that something unauthorized
overwrote the page descriptor table contents ! */
SOS_ASSERT_FATAL(ppage_descr->occupation_cnt == 0);
SOS_ASSERT_FATAL(ppage_descr->kernel_range == NULL);
/* Put the page in the nonfree list */
list_add_tail(nonfree_ppage, ppage_descr);
physmem_nonfree_pages ++;
return ppage_descr->paddr;
}
/**
* Helper function to get the physical page descriptor for the given
* physical page address.
*
* @return NULL when out-of-bounds or non-page-aligned
*/
inline static struct physical_page_descr *
get_page_descr_at_paddr(sos_paddr_t ppage_paddr)
{
/* Don't handle non-page-aligned addresses */
if (ppage_paddr & SOS_PAGE_MASK)
return NULL;
/* Don't support out-of-bounds requests */
if ((ppage_paddr < physmem_base) || (ppage_paddr >= physmem_top))
return NULL;
return physical_page_descr_array + (ppage_paddr >> SOS_PAGE_SHIFT);
}
sos_ret_t sos_physmem_ref_physpage_at(sos_paddr_t ppage_paddr)
{
struct physical_page_descr *ppage_descr
= get_page_descr_at_paddr(ppage_paddr);
if (! ppage_descr)
return -SOS_EINVAL;
/* Increment the reference count for the page */
ppage_descr->ref_cnt ++;
/* If the page is newly referenced (ie we are the only owners of the
page => ref cnt == 1), transfer it in the nonfree pages list */
if (ppage_descr->ref_cnt == 1)
{
/* The page descriptor should be unmodified since previous
deallocation. Otherwise this means that something unauthorized
overwrote the page descriptor table contents ! */
SOS_ASSERT_FATAL(ppage_descr->occupation_cnt == 0);
SOS_ASSERT_FATAL(ppage_descr->kernel_range == NULL);
list_delete(free_ppage, ppage_descr);
list_add_tail(nonfree_ppage, ppage_descr);
physmem_nonfree_pages ++;
/* The page is newly referenced */
return FALSE;
}
/* The page was already referenced by someone */
return TRUE;
}
sos_ret_t
sos_physmem_unref_physpage(sos_paddr_t ppage_paddr)
{
/* By default the return value indicates that the page is still
used */
sos_ret_t retval = FALSE;
struct physical_page_descr *ppage_descr
= get_page_descr_at_paddr(ppage_paddr);
if (! ppage_descr)
return -SOS_EINVAL;
/* Don't do anything if the page is not in the nonfree list */
if (ppage_descr->ref_cnt <= 0)
return -SOS_EINVAL;
/* Unreference the page, and, when no mapping is active anymore, put
the page in the free list */
ppage_descr->ref_cnt--;
if (ppage_descr->ref_cnt == 0)
{
/* Make sure that the occupation counter is set to 0 */
SOS_ASSERT_FATAL(ppage_descr->occupation_cnt == 0);
/* Reset associated kernel range */
ppage_descr->kernel_range = NULL;
/* Transfer the page, considered NON-FREE, to the free list */
list_delete(nonfree_ppage, ppage_descr);
physmem_nonfree_pages --;
list_add_head(free_ppage, ppage_descr);
/* Indicate that the page is now unreferenced */
retval = TRUE;
}
/* The page was already referenced by someone */
return retval;
}
sos_ret_t sos_physmem_get_physpage_refcount(sos_paddr_t ppage_paddr)
{
struct physical_page_descr *ppage_descr
= get_page_descr_at_paddr(ppage_paddr);
if (! ppage_descr)
return -SOS_EINVAL;
return ppage_descr->ref_cnt;
}
sos_ret_t sos_physmem_inc_physpage_occupation(sos_paddr_t ppage_paddr)
{
struct physical_page_descr *ppage_descr
= get_page_descr_at_paddr(ppage_paddr);
if (! ppage_descr)
return -SOS_EINVAL;
/* Don't do anything if the page is not in the nonfree list */
SOS_ASSERT_FATAL(ppage_descr->ref_cnt > 0);
ppage_descr->occupation_cnt ++;
return (ppage_descr->occupation_cnt > 1);
}
sos_ret_t sos_physmem_dec_physpage_occupation(sos_paddr_t ppage_paddr)
{
struct physical_page_descr *ppage_descr
= get_page_descr_at_paddr(ppage_paddr);
if (! ppage_descr)
return -SOS_EINVAL;
/* Don't do anything if the page is not in the nonfree list */
SOS_ASSERT_FATAL(ppage_descr->ref_cnt > 0);
SOS_ASSERT_FATAL(ppage_descr->occupation_cnt > 0);
ppage_descr->occupation_cnt --;
return (ppage_descr->occupation_cnt == 0);
}
struct sos_kmem_range* sos_physmem_get_kmem_range(sos_paddr_t ppage_paddr)
{
struct physical_page_descr *ppage_descr
= get_page_descr_at_paddr(ppage_paddr);
if (! ppage_descr)
return NULL;
return ppage_descr->kernel_range;
}
sos_ret_t sos_physmem_set_kmem_range(sos_paddr_t ppage_paddr,
struct sos_kmem_range *range)
{
struct physical_page_descr *ppage_descr
= get_page_descr_at_paddr(ppage_paddr);
if (! ppage_descr)
return -SOS_EINVAL;
ppage_descr->kernel_range = range;
return SOS_OK;
}
sos_ret_t sos_physmem_get_state(/* out */sos_count_t *total_ppages,
/* out */sos_count_t *nonfree_ppages)
{
if (total_ppages)
*total_ppages = physmem_total_pages;
if (nonfree_ppages)
*nonfree_ppages = physmem_nonfree_pages;
return SOS_OK;
}