/* Memoire virtuelle utilisateur */
/* 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 "memory.h"
#include "page_fault.h"
#include "maps.h"
#include "map.h"
#include "paging.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define USER_LIMIT (0xfffff - USER_BASE / PAGE_SIZE) /* en pages */
#define STACK_MARGIN (65536 + 32 * sizeof (unsigned long))
static inline int check_for_addition(unsigned long i1, unsigned long i2)
{
return i1 <= ULONG_MAX - i2;
}
/* Verifie que la zone demarrant a l'adresse virtuelle start et de longueur
* length est incluse dans le segment utilisateur.
* Cela implique que toute adresse appartenant a cette zone peut etre convertie
* en une adresse lineaire en lui ajoutant USER_BASE. */
static inline int in_user_segment(unsigned long start, unsigned long length)
{
if (!check_for_addition(start, length - PAGE_SIZE))
return 0;
if (start + (length - PAGE_SIZE) > USER_LIMIT * PAGE_SIZE)
return 0;
return 1;
}
static inline int in_stack(unsigned long addr, unsigned long stack_top)
{
return stack_top < STACK_MARGIN || addr >= stack_top - STACK_MARGIN;
}
void mm_init()
{
init_zero_page();
gdt_set_user_code_seg_descr(USER_BASE, USER_LIMIT); /* segment de code utilisateur */
gdt_set_user_data_seg_descr(USER_BASE, USER_LIMIT); /* segment de donnees utilisateur */
}
/* Copie la memoire du processus courant. */
int vm_copy(const struct vm_struct *vm, struct vm_struct *dest_vm, unsigned long *dest_page_directory)
{
int rv;
init_page_directory(dest_page_directory);
init_maps(&dest_vm->maps);
if (!vm)
return 0;
else {
if ((rv = copy_memory(&vm->maps, &dest_vm->maps, dest_page_directory, USER_BASE, (USER_LIMIT + 1) * PAGE_SIZE)) < 0)
free_memory(&dest_vm->maps, USER_BASE, (USER_LIMIT + 1) * PAGE_SIZE);
return rv;
}
}
/* Libere la memoire du processus courant. */
void vm_free(struct vm_struct *vm)
{
/* Puisqu'on libere *toute* la memoire, free_memory() ne devrait jamais
echouer. On ignore sa valeur de retour. */
free_memory(&vm->maps, USER_BASE, (USER_LIMIT + 1) * PAGE_SIZE);
}
/* Cree une projection de la memoire du noyau dans l'espace d'adressage virtuel
* du processus courant.
* start et end doivent etre alignes sur une frontiere de page.
* end doit etre strictement superieur a start. */
int vm_map_mem(struct vm_struct *vm, unsigned long start, unsigned long end)
{
unsigned long length;
length = end - start;
if (!in_user_segment(start, length))
return -EINVAL;
return map_mem(&vm->maps, USER_BASE + start, length, PROT_RDWR, (void *)start);
}
/* Cree une projection anonyme dans l'espace d'adressage virtuel du
* processus courant.
* start doit etre aligne sur une frontiere de page.
* length doit etre non nul et multiple de PAGE_SIZE.
* name peut etre nul. */
int vm_map_anon(struct vm_struct *vm, unsigned long start, unsigned long length, int prot, int grows_down, const char *name)
{
if (!in_user_segment(start, length))
return -EINVAL;
return map_anon(&vm->maps, USER_BASE + start, length, prot, grows_down, name);
}
/* Cree une projection de fichier dans l'espace d'adressage virtuel du
* processus courant.
* start et offset doivent etre alignes sur une frontiere de page.
* length doit etre non nul et multiple de PAGE_SIZE.
* file doit etre un fichier regulier ou un fichier special. */
int vm_map_file(struct vm_struct *vm, unsigned long start, unsigned long length, int prot, struct file_struct *file, unsigned long offset)
{
if (!in_user_segment(start, length))
return -EINVAL;
return map_file(&vm->maps, USER_BASE + start, length, prot, file, offset);
}
/* Detruit une projection dans l'espace d'adressage virtuel du processus
* courant.
* start doit etre aligne sur une frontiere de page.
* length doit etre non nul et multiple de PAGE_SIZE. */
int vm_unmap(struct vm_struct *vm, unsigned long start, unsigned long length)
{
if (!in_user_segment(start, length))
return -EINVAL;
return free_memory(&vm->maps, USER_BASE + start, length);
}
/* Change la protection d'une projection dans l'espace d'adressage virtuel du
* processus courant.
* start doit etre aligne sur une frontiere de page.
* length doit etre non nul et multiple de PAGE_SIZE. */
int vm_protect(struct vm_struct *vm, unsigned long start, unsigned long length, int prot)
{
if (!in_user_segment(start, length))
return -EINVAL;
return protect_memory(&vm->maps, USER_BASE + start, length, prot);
}
/* Retourne l'adresse de demarrage d'une zone libre pour une projection de
* longueur length dans le segment [base, base + size[ de l'espace
* d'adressage virtuel du processus courant.
* base doit etre aligne sur une frontiere de page.
* size doit etre non nul et multiple de PAGE_SIZE.
* length doit etre non nul et multiple de PAGE_SIZE.
* Si side est negatif, la zone libre est calee vers les basses addresses,
* sinon la zone libre est calee vers les hautes addresses.
* Zero est retourne en cas d'echec. */
unsigned long vm_get_free_area(const struct vm_struct *vm, unsigned long base, unsigned long size, unsigned long length, int side)
{
unsigned long start;
if (!in_user_segment(base, size))
return 0;
if (!(start = get_free_area(&vm->maps, USER_BASE + base, size, length, side)))
return 0;
return start - USER_BASE;
}
/* Verifie que [start, start + length[ peut etre accede, et remplit les pages
* si necessaire.
* length doit etre non nul.
* start et length ne sont soumis a aucune autre contrainte.
* 0 est retourne si l'acces memoire est invalide. */
int vm_verify_area(const struct vm_struct *vm, unsigned long start, unsigned long length, int access)
{
unsigned long poff;
poff = PAGE_OFFSET(start);
start -= poff;
if (!check_for_addition(length, poff))
return 0;
length += poff;
if (!check_for_addition(length, PAGE_SIZE - 1))
return 0;
length = PAGE_ALIGN(length);
if (!in_user_segment(start, length))
return 0;
return verify_memory(&vm->maps, USER_BASE + start, length, access);
}
/* Recherche le caractere nul '\0' dans une page. */
static int scan_page(unsigned long start, unsigned int n, unsigned int *len)
{
const void *p;
n = min(n, PAGE_SIZE - PAGE_OFFSET(start));
if ((p = memchr((void *)(USER_BASE + start), 0, n))) {
if (len)
*len += p - (void *)(USER_BASE + start);
return 1;
}
else {
if (len)
*len += n;
return 0;
}
}
/* Verifie que la chaine de caractere demarrant a l'adresse virtuelle start
* peut etre accedee en lecture, et remplit les pages si necessaire.
* start n'est soumise a aucune contrainte.
* 0 est retourne si l'acces memoire est invalide. */
int vm_verify_str(const struct vm_struct *vm, unsigned long start, unsigned int n, unsigned int *len)
{
unsigned long poff;
poff = PAGE_OFFSET(start);
start -= poff;
if (!in_user_segment(start, PAGE_SIZE))
return 0;
if (!verify_memory(&vm->maps, USER_BASE + start, PAGE_SIZE, PF_READ))
return 0;
if (len)
*len = 0;
if (!scan_page(start + poff, n, len) && n >= PAGE_SIZE)
do {
n -= PAGE_SIZE;
if (!check_for_addition(start, PAGE_SIZE))
return 0;
start += PAGE_SIZE;
if (start > USER_LIMIT * PAGE_SIZE)
return 0;
if (!verify_memory(&vm->maps, USER_BASE + start, PAGE_SIZE, PF_READ))
return 0;
}
while (!scan_page(start, n, len) && n >= PAGE_SIZE);
return 1;
}
/* Retourne la liste des projections. */
int vm_get_maps(const struct vm_struct *vm, struct map_info_struct **map_info_array, unsigned int *count)
{
int rv;
unsigned int i;
if ((rv = get_maps(&vm->maps, map_info_array, count)) < 0)
return rv;
for (i = 0; i < *count; i++) {
(*map_info_array)[i].start_addr -= USER_BASE;
(*map_info_array)[i].end_addr -= USER_BASE;
}
return 0;
}
int vm_handle_page_fault(const struct vm_struct *vm, int cause, int access, unsigned long stack_top, unsigned long laddr)
{
return _handle_page_fault(laddr, cause, access, in_stack(laddr - USER_BASE, stack_top), &vm->maps);
}