/* 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); }