View of xos/mm/pages.c


XOS | Parent Directory | View | Download

/* 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 <http://www.gnu.org/licenses/>. */
 
#include "maps.h"
#include "map.h"
#include "page_frame.h"
#include "tlb.h"
 
#include <kmalloc.h>
#include <system.h>
#include <system_data_structs.h>
#include <errno.h>
#include <i386.h>
#include <assert.h>
#include <stddef.h>
 
/* 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, &copy_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);
}