/* 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 . */
#include "maps.h"
#include "map.h"
#include "page_frame.h"
#include "tlb.h"
#include
#include
#include
#include
#include
#include
#include
/* structure pour le parcours des pages du processus courant */
struct traverse_state_struct {
unsigned long *page_dir;
unsigned long start;
unsigned long length;
unsigned long *page_dir_entry;
struct page_table_entry_data_struct pde_dat;
unsigned long *page_table;
unsigned long *page_table_entry;
struct page_table_entry_data_struct pte_dat;
};
static void init(struct traverse_state_struct *state, unsigned long *page_dir, unsigned long start, unsigned long length)
{
struct linear_address_data_struct laddr_dat;
state->page_dir = page_dir;
state->start = start;
state->length = length;
get_linear_address(&start, &laddr_dat);
state->page_dir_entry = &state->page_dir[laddr_dat.dir];
get_page_table_entry(state->page_dir_entry, &state->pde_dat);
if (state->pde_dat.present) {
state->page_table = (unsigned long *)state->pde_dat.page_frame_addr;
state->page_table_entry = &state->page_table[laddr_dat.page];
get_page_table_entry(state->page_table_entry, &state->pte_dat);
}
}
/* Deplacements elementaires */
/* last page table entry / not-present page dir entry -> next page dir entry */
static void next_pde(struct traverse_state_struct *state)
{
state->page_dir_entry++;
get_page_table_entry(state->page_dir_entry, &state->pde_dat);
}
/* present page dir entry -> first page table entry */
static void enter_pt(struct traverse_state_struct *state)
{
state->page_table = (unsigned long *)state->pde_dat.page_frame_addr;
state->page_table_entry = &state->page_table[0];
get_page_table_entry(state->page_table_entry, &state->pte_dat);
}
/* not-last page table entry -> next page table entry */
static void next_pte(struct traverse_state_struct *state)
{
state->page_table_entry++;
get_page_table_entry(state->page_table_entry, &state->pte_dat);
}
/* Deplacement vers la prochaine entree */
/* last page table entry / not-present page dir entry -> next entry */
static void pde_next(struct traverse_state_struct *state)
{
assert(state->page_dir_entry != &state->page_dir[PAGE_SIZE / 4 - 1]);
next_pde(state);
if (state->pde_dat.present)
enter_pt(state);
}
/* page table entry -> next entry */
static int pte_next(struct traverse_state_struct *state)
{
if (state->length == PAGE_SIZE) {
state->length = 0;
return 0;
}
state->start += PAGE_SIZE;
state->length -= PAGE_SIZE;
if (state->page_table_entry == &state->page_table[PAGE_SIZE / 4 - 1])
pde_next(state);
else
next_pte(state);
return 1;
}
/* not-present page dir entry -> next entry */
static int npde_next(struct traverse_state_struct *state)
{
if (state->length <= (PAGE_SIZE / 4) * PAGE_SIZE) {
state->length = 0;
return 0;
}
state->start += (PAGE_SIZE / 4) * PAGE_SIZE;
state->length -= (PAGE_SIZE / 4) * PAGE_SIZE;
pde_next(state);
return 1;
}
/* Parcours */
struct callbacks_struct {
int (*begin_page_table)(const struct traverse_state_struct *, void *);
int (*end_page_table)(const struct traverse_state_struct *, void *);
int (*visit_present_page_table_entry)(const struct traverse_state_struct *, void *);
};
/* length doit etre non nul et multiple de PAGE_SIZE.
* [start, start + length[ doit etre inclus dans l'espace d'adressage lineaire. */
static unsigned long traverse(unsigned long *page_dir, unsigned long start, unsigned long length, const struct callbacks_struct *callbacks, void *arg)
{
struct traverse_state_struct state;
init(&state, page_dir, start, length);
while (1) {
if (state.pde_dat.present) {
if (state.page_table_entry == &state.page_table[0])
if (!callbacks->begin_page_table(&state, arg))
break;
if (state.pte_dat.present)
if (!callbacks->visit_present_page_table_entry(&state, arg))
break;
if (state.page_table_entry == &state.page_table[PAGE_SIZE / 4 - 1])
if (!callbacks->end_page_table(&state, arg))
break;
if (!pte_next(&state))
break;
}
else
if (!npde_next(&state))
break;
}
return length - state.length;
}
/* copy_pages */
struct copy_pages_data_struct {
unsigned long *dest_page_dir;
unsigned long *dest_page_table;
};
static int copy_pages_begin_page_table(const struct traverse_state_struct *state, struct copy_pages_data_struct *data)
{
data->dest_page_table = NULL;
return 1;
}
static int copy_pages_end_page_table(const struct traverse_state_struct *state, struct copy_pages_data_struct *data)
{
return 1;
}
static int copy_pages_visit_present_page_table_entry(const struct traverse_state_struct *state, struct copy_pages_data_struct *data)
{
unsigned long *dest_page_dir_entry;
struct page_table_entry_data_struct dest_pde_dat;
unsigned long *dest_page_table_entry;
struct page_table_entry_data_struct pte_dat;
if (!data->dest_page_table) {
if (!(data->dest_page_table = kmalloc(PAGE_SIZE)))
return 0;
clear_page_table(data->dest_page_table);
dest_page_dir_entry = data->dest_page_dir + (state->page_dir_entry - state->page_dir);
dest_pde_dat.present = 1;
dest_pde_dat.rw = 1;
dest_pde_dat.us = 1;
dest_pde_dat.accessed = 0;
dest_pde_dat.dirty = 0;
dest_pde_dat.avail = 0;
dest_pde_dat.page_frame_addr = (unsigned long)data->dest_page_table;
set_page_table_entry(dest_page_dir_entry, &dest_pde_dat);
}
dest_page_table_entry = data->dest_page_table + (state->page_table_entry - state->page_table);
hold_page_frame(state->pte_dat.page_frame_addr);
pte_dat = state->pte_dat;
pte_dat.rw = 0;
set_page_table_entry(dest_page_table_entry, &pte_dat);
set_page_table_entry(state->page_table_entry, &pte_dat);
return 1;
}
static const struct callbacks_struct copy_pages_callbacks = {
.begin_page_table = (int (*)(const struct traverse_state_struct *, void *))copy_pages_begin_page_table,
.end_page_table = (int (*)(const struct traverse_state_struct *, void *))copy_pages_end_page_table,
.visit_present_page_table_entry = (int (*)(const struct traverse_state_struct *, void *))copy_pages_visit_present_page_table_entry,
};
static unsigned long do_copy_pages(unsigned long *page_dir, unsigned long start, unsigned long length, unsigned long *dest_page_directory)
{
struct copy_pages_data_struct data;
struct linear_address_data_struct laddr_dat;
unsigned long *dest_page_dir_entry;
struct page_table_entry_data_struct pde_dat;
data.dest_page_dir = dest_page_directory;
get_linear_address(&start, &laddr_dat);
dest_page_dir_entry = &dest_page_directory[laddr_dat.dir];
get_page_table_entry(dest_page_dir_entry, &pde_dat);
data.dest_page_table = pde_dat.present ? (unsigned long *)pde_dat.page_frame_addr : NULL;
return traverse(page_dir, start, length, ©_pages_callbacks, &data);
}
/* free_pages */
struct free_pages_data_struct {
const struct maps_struct *maps;
int entire_page_table;
};
static int free_pages_begin_page_table(const struct traverse_state_struct *state, struct free_pages_data_struct *data)
{
data->entire_page_table = 1;
return 1;
}
static int free_pages_end_page_table(const struct traverse_state_struct *state, struct free_pages_data_struct *data)
{
struct page_table_entry_data_struct pde_dat;
if (data->entire_page_table) {
kfree(state->page_table, PAGE_SIZE);
pde_dat.present = 0;
set_page_table_entry(state->page_dir_entry, &pde_dat);
}
return 1;
}
static int free_pages_visit_present_page_table_entry(const struct traverse_state_struct *state, struct free_pages_data_struct *data)
{
struct map_struct *map;
unsigned long page_frame;
struct page_table_entry_data_struct pte_dat;
page_frame = state->pte_dat.page_frame_addr;
if (!is_page_frame_shared(page_frame)) {
map = find_map(data->maps, state->start);
assert(map);
map_dirty_page(map, state->start, page_frame);
}
release_page_frame(page_frame);
pte_dat = state->pte_dat;
pte_dat.present = 0;
set_page_table_entry(state->page_table_entry, &pte_dat);
return 1;
}
static const struct callbacks_struct free_pages_callbacks = {
.begin_page_table = (int (*)(const struct traverse_state_struct *, void *))free_pages_begin_page_table,
.end_page_table = (int (*)(const struct traverse_state_struct *, void *))free_pages_end_page_table,
.visit_present_page_table_entry = (int (*)(const struct traverse_state_struct *, void *))free_pages_visit_present_page_table_entry
};
static unsigned long do_free_pages(unsigned long *page_dir, unsigned long start, unsigned long length, const struct maps_struct *maps)
{
struct free_pages_data_struct data;
struct linear_address_data_struct laddr_dat;
data.maps = maps;
get_linear_address(&start, &laddr_dat);
data.entire_page_table = laddr_dat.page == 0;
return traverse(page_dir, start, length, &free_pages_callbacks, &data);
}
/* Interface */
/* Les pages de dest doivent etre non presentes dans l'intervalle concerne. */
int copy_pages(unsigned long start, unsigned long length, unsigned long *dest_page_directory)
{
unsigned long *page_dir;
unsigned long len;
get_pdbr(page_dir);
if ((len = do_copy_pages(page_dir, start, length, dest_page_directory)) < length)
goto error;
invalidate(page_dir);
return 0;
error:
do_free_pages(page_dir, start, len, NULL);
invalidate(page_dir);
return -ENOMEM;
}
void free_pages(unsigned long start, unsigned long length, const struct maps_struct *maps)
{
unsigned long *page_dir;
get_pdbr(page_dir);
do_free_pages(page_dir, start, length, maps);
invalidate(page_dir);
}