388 lines
11 KiB
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;
|
|
}
|
|
|