/* Memoire basse */
/* Copyright (C) 2008 Emmanuel Varoquaux
This file is part of XOS.
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 3 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, see . */
/* Partie de la memoire physique accessible directement par les tables des
* pages du noyau.
* Le systeme d'allocation utilise est l'algorithme du compagnon ("buddy"). */
#include
#include
#include
#define FREE_TABLE_SIZE (32 - LOG_PAGE_SIZE)
struct lowmem_free_page_frame_data_struct {
struct lowmem_free_page_frame_data_struct *previous;
struct lowmem_free_page_frame_data_struct *next;
};
struct lowmem_allocated_page_frame_data_struct {
unsigned short count;
};
/* cadres de pages */
static void *page_frames_start;
static struct lowmem_allocated_page_frame_data_struct *page_frames_map;
static struct lowmem_allocated_page_frame_data_struct *page_frames_map_lazy_end;
static struct lowmem_allocated_page_frame_data_struct *page_frames_map_end;
static struct lowmem_free_page_frame_data_struct *first_free_page_frame_data[FREE_TABLE_SIZE];
#define _index(page_frame) (((page_frame) - page_frames_start) / PAGE_SIZE)
#define _page_frame(index) (page_frames_start + (index) * PAGE_SIZE)
static void init_page_frames(void *start, void *end)
{
unsigned long n;
void *page_frame;
unsigned int k;
struct lowmem_free_page_frame_data_struct *page_frame_data;
/* partition de la memoire */
n = (end - start) / (sizeof (struct lowmem_allocated_page_frame_data_struct) + PAGE_SIZE);
page_frames_start = end - n * PAGE_SIZE;
page_frames_map = start;
page_frames_map_lazy_end = start;
page_frames_map_end = start + n * sizeof (struct lowmem_allocated_page_frame_data_struct);
/* liste des cadres de pages libres */
page_frame = page_frames_start;
for (k = FREE_TABLE_SIZE; k--;) {
if (n & exp2(k)) {
page_frame_data = (struct lowmem_free_page_frame_data_struct *)page_frame;
page_frame_data->previous = NULL;
page_frame_data->next = NULL;
first_free_page_frame_data[k] = page_frame_data;
page_frame += PAGE_SIZE << k;
}
else
first_free_page_frame_data[k] = NULL;
}
}
static void push_free_page_frame_data(struct lowmem_free_page_frame_data_struct *page_frame_data, unsigned int k)
{
page_frame_data->previous = NULL;
page_frame_data->next = first_free_page_frame_data[k];
if (page_frame_data->next)
page_frame_data->next->previous = page_frame_data;
first_free_page_frame_data[k] = page_frame_data;
}
static struct lowmem_free_page_frame_data_struct *pop_free_page_frame_data(unsigned int k)
{
struct lowmem_free_page_frame_data_struct *page_frame_data;
if ((page_frame_data = first_free_page_frame_data[k])) {
if (page_frame_data->next)
page_frame_data->next->previous = NULL;
first_free_page_frame_data[k] = page_frame_data->next;
}
return page_frame_data;
}
static void remove_free_page_frame_data(struct lowmem_free_page_frame_data_struct *page_frame_data, unsigned int k)
{
if (page_frame_data->previous)
page_frame_data->previous->next = page_frame_data->next;
else
first_free_page_frame_data[k] = page_frame_data->next;
if (page_frame_data->next)
page_frame_data->next->previous = page_frame_data->previous;
}
static int is_mapped(const void *page_frame)
{
return page_frame >= page_frames_start;
}
static struct lowmem_allocated_page_frame_data_struct *get_page_frame_data(const void *page_frame)
{
struct lowmem_allocated_page_frame_data_struct *page_frame_data;
page_frame_data = &page_frames_map[_index(page_frame)];
while (page_frames_map_lazy_end <= page_frame_data) {
page_frames_map_lazy_end->count = 0;
page_frames_map_lazy_end++;
}
return page_frame_data;
}
static void allocate_page_frames(void *page_frames, unsigned long n)
{
while (n--) {
get_page_frame_data(page_frames)->count = 1;
page_frames += PAGE_SIZE;
}
}
static void deallocate_page_frames(void *page_frames, unsigned long n)
{
while (n--) {
get_page_frame_data(page_frames)->count = 0;
page_frames += PAGE_SIZE;
}
}
static int are_page_frames_free(void *page_frames, unsigned long n)
{
while (n--) {
if (get_page_frame_data(page_frames)->count)
return 0;
page_frames += PAGE_SIZE;
}
return 1;
}
static void hold_page_frame(void *page_frame)
{
get_page_frame_data(page_frame)->count++;
}
static int release_page_frame(void *page_frame)
{
struct lowmem_allocated_page_frame_data_struct *page_frame_data;
page_frame_data = get_page_frame_data(page_frame);
if (page_frame_data->count == 1)
return 0;
page_frame_data->count--;
return 1;
}
static int is_page_frame_shared(void *page_frame)
{
return get_page_frame_data(page_frame)->count > 1;
}
static void *get_buddy(void *page_frames, unsigned int k)
{
unsigned long i;
i = _index(page_frames) ^ exp2(k); /* compagnon */
if (&page_frames_map[i + exp2(k)] > page_frames_map_end)
return NULL; /* le compagnon n'existe pas */
return _page_frame(i);
}
/* Interface de l'allocateur de pages noyau contigues */
void lowmem_init(unsigned short size_m, void *start_data)
{
init_page_frames(start_data, (void *)((unsigned long)size_m << 20));
}
void lowmem_print_info() {}
/* Alloue 2^k pages noyaux contigues. */
void *alloc_page_frames(unsigned int k)
{
void *page_frames;
unsigned int i;
page_frames = NULL;
i = k;
while (i < FREE_TABLE_SIZE && !(page_frames = pop_free_page_frame_data(i)))
i++;
if (!page_frames)
return NULL;
/* division */
while (i > k) {
i--;
push_free_page_frame_data(page_frames + (PAGE_SIZE << i), i);
}
allocate_page_frames(page_frames, exp2(k));
return page_frames;
}
void free_page_frames(void *ptr, unsigned int k)
{
void *page_frames, *buddy;
page_frames = ptr;
deallocate_page_frames(page_frames, exp2(k));
/* fusion */
while (k < FREE_TABLE_SIZE && (buddy = get_buddy(page_frames, k)) && are_page_frames_free(buddy, exp2(k))) {
remove_free_page_frame_data(buddy, k);
if (buddy < page_frames)
page_frames = buddy;
k++;
}
push_free_page_frame_data(page_frames, k);
}
/* Allocateur de cadre de pages utilisateurs dans la memoire basse */
unsigned long alloc_page_frame_lowmem()
{
return (unsigned long)alloc_page_frames(0);
}
void hold_page_frame_lowmem(unsigned long page_frame)
{
if (is_mapped((void *)page_frame))
hold_page_frame((void *)page_frame);
}
void release_page_frame_lowmem(unsigned long page_frame)
{
if (is_mapped((void *)page_frame))
if (!release_page_frame((void *)page_frame))
free_page_frames((void *)page_frame, 0);
}
int is_page_frame_shared_lowmem(unsigned long page_frame)
{
if (is_mapped((void *)page_frame))
return is_page_frame_shared((void *)page_frame);
else
return 1;
}