/* 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.h"
#include "page_frame.h"
#include "highmem.h" /* is_highmem() */
#include
#include
#include
#include
#include
#include
#define copy_page(dest, src) memcpy((dest), (src), PAGE_SIZE)
/* Toutes les fonctions operent sur la traduction d'adresse du processus
* courant. Le parametre page_frame est une adresse physique de cadre de page,
* le parametre laddr est une adresse lineaire, non necessairement alignee sur
* une frontiere de page, mais seule sa page est prise en compte. */
/* Cette fonction compte sur le fait que la page utilisateur 0 est non presente
* pour y realiser une projection temporaire. */
static inline void copy_from_highmem(void *dest_page, unsigned long src_page_frame)
{
set_page(USER_BASE, src_page_frame, 0);
copy_page(dest_page, (void *)USER_BASE);
del_page(USER_BASE);
}
static unsigned long get_page_frame(unsigned long laddr)
{
unsigned long page_frame;
assert(get_page_frame_addr(laddr, &page_frame));
return page_frame;
}
/* Cree une nouvelle page et retourne son cadre de page. */
static unsigned long create_page(unsigned long laddr, int prot, const struct map_struct *map)
{
unsigned long page_frame;
if (!(page_frame = alloc_page_frame()))
goto error;
if (!set_page(laddr, page_frame, prot == PROT_RDWR))
goto error_release_page_frame;
if (!map_fill_page(map, PAGE(laddr)))
goto error_del_page;
if (prot == PROT_RDONLY)
/* la page est propre, on l'enregistre */
map_set_clean_page_frame(map, laddr, page_frame);
return page_frame;
error_del_page:
del_page(laddr);
error_release_page_frame:
release_page_frame(page_frame);
error:
return 0;
}
/* Cree une page en lecture seule avec le cadre de page. */
static int put_page(unsigned long laddr, unsigned long page_frame)
{
if (!set_page(laddr, page_frame, 0))
return 0;
return 1;
}
/* Donne la permission d'ecriture a une page. */
static void set_write_access(unsigned long laddr, const struct map_struct *map)
{
map_dirty_page(map, laddr, get_page_frame(laddr)); /* la page devient sale */
assert(set_page_rw(laddr, 1));
}
/* Cree une nouvelle page par copie, et lui donne la permission d'ecriture. */
static int copy_page_frame(unsigned long laddr, unsigned long page_frame)
{
unsigned long new_page_frame;
if (!(new_page_frame = alloc_page_frame()))
goto error;
if (!is_highmem(new_page_frame)) {
if (is_highmem(page_frame))
copy_from_highmem((void *)new_page_frame, page_frame);
else
copy_page((void *)new_page_frame, (void *)page_frame);
}
if (!set_page(laddr, new_page_frame, 1))
goto error_release_new_page_frame;
if (is_highmem(new_page_frame)) {
if (is_highmem(page_frame))
copy_from_highmem((void *)PAGE(laddr), page_frame);
else
copy_page((void *)PAGE(laddr), (void *)page_frame);
}
return 1;
error_release_new_page_frame:
release_page_frame(new_page_frame);
error:
return 0;
}
static int handle_not_present(unsigned long laddr, int access, const struct map_struct *map)
{
unsigned long page_frame;
page_frame = map_get_clean_page_frame(map, laddr);
switch (access) {
case PF_READ:
if (page_frame) {
/* on partage le cadre de page */
hold_page_frame(page_frame);
if (!put_page(laddr, page_frame))
goto error_release_page_frame;
goto _1;
error_release_page_frame:
release_page_frame(page_frame);
return PF_ADRERR;
}
else {
/* on cree la page */
if (!(page_frame = create_page(laddr, PROT_RDONLY, map))) /* la page est copy-on-write */
return PF_ADRERR;
}
_1:
break;
case PF_WRITE:
default:
if (page_frame) {
/* on fait une copie privee de la page */
if (!copy_page_frame(laddr, page_frame))
return PF_ADRERR;
}
else {
/* on cree la page */
if (!create_page(laddr, PROT_RDWR, map))
return PF_ADRERR;
}
}
return 0;
}
static int handle_protection_violation(unsigned long laddr, const struct map_struct *map)
{
unsigned long page_frame;
page_frame = get_page_frame(laddr);
if (is_page_frame_shared(page_frame)) {
/* on fait une copie privee de la page */
if (!copy_page_frame(laddr, page_frame))
return PF_ADRERR;
release_page_frame(page_frame);
}
else
/* on donne la permission d'ecriture a la page */
set_write_access(laddr, map);
return 0;
}
int _handle_page_fault(unsigned long laddr, int cause, int access, int in_stack, const struct maps_struct *maps)
{
struct map_struct *map;
/* on cherche le segment */
if (!(map = find_next_map(maps, laddr)))
return PF_MAPERR;
if (compare_map_laddr(map, laddr) == 0) {
/* on verifie les permissions */
if (!map_verify_access(map, access))
return PF_ACCERR;
}
else {
/* on verifie que la projection peut s'etendre vers le bas (pile) */
if (!map_grows_down(map))
return PF_MAPERR;
/* on verifie qu'on est bien dans la pile */
if (!in_stack)
return PF_MAPERR;
/* on verifie les permissions */
if (!map_verify_access(map, access))
return PF_ACCERR;
/* on etend la projection */
map_grow_down(map, laddr);
}
switch (cause) {
case PF_NOT_PRESENT: /* not-present page */
return handle_not_present(laddr, access, map);
case PF_PROTECTION_VIOLATION: /* page-level protection violation */
default:
return handle_protection_violation(laddr, map);
}
}