#include "maps.h"
#include "map.h"
#include "page.h"
#include "page_frame.h"
#include "highmem.h" /* is_highmem() */
#include <consts.h>
#include <enums.h>
#include <config.h>
#include <i386.h>
#include <assert.h>
#include <string.h>
#define copy_page(dest, src) memcpy((dest), (src), PAGE_SIZE)
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;
}
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)
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;
}
static int put_page(unsigned long laddr, unsigned long page_frame)
{
if (!set_page(laddr, page_frame, 0))
return 0;
return 1;
}
static void set_write_access(unsigned long laddr, const struct map_struct *map)
{
map_dirty_page(map, laddr, get_page_frame(laddr));
assert(set_page_rw(laddr, 1));
}
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) {
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 {
if (!(page_frame = create_page(laddr, PROT_RDONLY, map)))
return PF_ADRERR;
}
_1:
break;
case PF_WRITE:
default:
if (page_frame) {
if (!copy_page_frame(laddr, page_frame))
return PF_ADRERR;
}
else {
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)) {
if (!copy_page_frame(laddr, page_frame))
return PF_ADRERR;
release_page_frame(page_frame);
}
else
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;
if (!(map = find_next_map(maps, laddr)))
return PF_MAPERR;
if (compare_map_laddr(map, laddr) == 0) {
if (!map_verify_access(map, access))
return PF_ACCERR;
}
else {
if (!map_grows_down(map))
return PF_MAPERR;
if (!in_stack)
return PF_MAPERR;
if (!map_verify_access(map, access))
return PF_ACCERR;
map_grow_down(map, laddr);
}
switch (cause) {
case PF_NOT_PRESENT:
return handle_not_present(laddr, access, map);
case PF_PROTECTION_VIOLATION:
default:
return handle_protection_violation(laddr, map);
}
}