/* Segments de memoire lineaire */
/* 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 . */
/* Toutes les maps sont privees et bloquent les ecritures sur le fichier
* sous-jacent (drapeaux POSIX MAP_PRIVATE et MAP_DENYWRITE). */
#include "map_struct.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static char zero_page[PAGE_SIZE] __attribute__ ((aligned (PAGE_SIZE)));
/* Segments */
/* Les segments ne devraient etre accedes qu'en lecture seule en dehors de
* cette section.
* start doit etre aligne sur PAGE_SIZE.
* end doit etre superieur a start et congru a -1 modulo PAGE_SIZE. */
static void init_segment(struct segment_struct *segment, unsigned long start, unsigned long end)
{
segment->start = start;
segment->end = end;
}
/* laddr doit etre inferieur a segment->start. */
static void grow_down_segment(struct segment_struct *segment, unsigned long laddr)
{
segment->start = PAGE(laddr);
}
static void resize_segment(struct segment_struct *segment, unsigned long end)
{
segment->end = end;
}
static int compare_segment_laddr(const struct segment_struct *segment, unsigned long laddr)
{
if (segment->end < laddr)
return -1;
if (segment->start > laddr)
return 1;
return 0;
}
static int compare_segments(const struct segment_struct *segment1, const struct segment_struct *segment2)
{
if (segment1->end < segment2->start)
return -1;
if (segment1->start > segment2->end)
return 1;
return 0;
}
static int intersect_segments(const struct segment_struct *segment1, const struct segment_struct *segment2, struct segment_struct *inter_segment)
{
if (segment1->end < segment2->start)
return -1;
if (segment1->start > segment2->end)
return 1;
inter_segment->start = max(segment1->start, segment2->start);
inter_segment->end = min(segment1->end, segment2->end);
return 0;
}
static int merge_segments(const struct segment_struct *segment1, const struct segment_struct *segment2, struct segment_struct *merge_segment)
{
if (segment2->start == 0 || segment1->end != segment2->start - 1)
return 0;
merge_segment->start = segment1->start;
merge_segment->end = segment2->end;
return 1;
}
/* Projections */
/* [start, end[ ne doit pas couvrir tout l'espace d'adressage, de maniere a ce
* que length = end - start + 1 soit toujours calculable. */
static void init_map(struct map_struct *map, unsigned long start, unsigned long end, int prot, int grows_down, const struct map_operations_struct *operations)
{
init_segment(&map->segment, start, end);
map->prot = prot;
map->grows_down = grows_down;
map->operations = operations;
}
/* mem_map */
static inline unsigned long mem_map_get_mem_page_frame(const struct map_struct *mem_map, unsigned long page)
{
return (unsigned long)(mem_map->mem_start + (page - mem_map->segment.start));
}
static void mem_map_do_destroy(struct map_struct *mem_map) {}
static void mem_map_do_clone(const struct map_struct *mem_map, struct map_struct *dest_map)
{
dest_map->mem_start = mem_map->mem_start;
}
static int mem_map_do_is_mergeable(const struct map_struct *mem_map, const struct map_struct *mem_map1)
{
return mem_map1->mem_start == mem_map->mem_start;
}
static void mem_map_do_merge(struct map_struct *mem_map, struct map_struct *mem_map1) {}
static void mem_map_do_grow_down(struct map_struct *mem_map) {}
static int mem_map_do_fill_page(const struct map_struct *mem_map, unsigned long page)
{
memcpy((void *)page, (void *)mem_map_get_mem_page_frame(mem_map, page), PAGE_SIZE);
return 1;
}
static unsigned long mem_map_do_get_clean_page_frame(const struct map_struct *mem_map, unsigned long laddr)
{
return mem_map_get_mem_page_frame(mem_map, PAGE(laddr));
}
static void mem_map_do_set_clean_page_frame(const struct map_struct *mem_map, unsigned long laddr, unsigned long page_frame) {}
static void mem_map_do_dirty_page(const struct map_struct *mem_map, unsigned long laddr, unsigned long page_frame) {}
static int mem_map_get_info(const struct map_struct *mem_map, struct map_info_struct *map_info)
{
map_info->offset = 0;
map_info->dev = 0;
map_info->id = 0;
map_info->pathname = NULL;
return 0;
}
static const struct map_operations_struct mem_map_operations = {
.do_destroy = mem_map_do_destroy,
.do_clone = mem_map_do_clone,
.do_is_mergeable = mem_map_do_is_mergeable,
.do_merge = mem_map_do_merge,
.do_grow_down = mem_map_do_grow_down,
.do_fill_page = mem_map_do_fill_page,
.do_get_clean_page_frame = mem_map_do_get_clean_page_frame,
.do_set_clean_page_frame = mem_map_do_set_clean_page_frame,
.do_dirty_page = mem_map_do_dirty_page,
.do_get_info = mem_map_get_info
};
static void init_mem_map(struct map_struct *mem_map, unsigned long start, unsigned long length, int prot, void *mem_start)
{
init_map(mem_map, start, start + (length - 1), prot, 0, &mem_map_operations);
mem_map->mem_start = mem_start;
}
/* anon_map */
static void anon_map_do_destroy(struct map_struct *anon_map) {}
static void anon_map_do_clone(const struct map_struct *anon_map, struct map_struct *dest_map)
{
dest_map->name = anon_map->name;
}
static int anon_map_do_is_mergeable(const struct map_struct *anon_map, const struct map_struct *anon_map1)
{
if (!anon_map1->name || !anon_map->name)
return !anon_map1->name && !anon_map->name;
return !strcmp(anon_map1->name, anon_map->name);
}
static void anon_map_do_merge(struct map_struct *anon_map, struct map_struct *anon_map1) {}
static void anon_map_do_grow_down(struct map_struct *anon_map) {}
static int anon_map_do_fill_page(const struct map_struct *anon_map, unsigned long page)
{
memset((void *)page, 0, PAGE_SIZE);
return 1;
}
static unsigned long anon_map_do_get_clean_page_frame(const struct map_struct *anon_map, unsigned long laddr)
{
return (unsigned long)zero_page;
}
static void anon_map_do_set_clean_page_frame(const struct map_struct *anon_map, unsigned long laddr, unsigned long page_frame) {}
static void anon_map_do_dirty_page(const struct map_struct *anon_map, unsigned long laddr, unsigned long page_frame) {}
static int anon_map_get_info(const struct map_struct *anon_map, struct map_info_struct *map_info)
{
unsigned int size;
map_info->offset = 0;
map_info->dev = 0;
map_info->id = 0;
if (anon_map->name) {
size = strlen(anon_map->name) + 3;
if (!(map_info->pathname = kmalloc(size)))
return -ENOMEM;
snprintf(map_info->pathname, size, "[%s]", anon_map->name);
}
else
map_info->pathname = NULL;
return 0;
}
static const struct map_operations_struct anon_map_operations = {
.do_destroy = anon_map_do_destroy,
.do_clone = anon_map_do_clone,
.do_is_mergeable = anon_map_do_is_mergeable,
.do_merge = anon_map_do_merge,
.do_grow_down = anon_map_do_grow_down,
.do_fill_page = anon_map_do_fill_page,
.do_get_clean_page_frame = anon_map_do_get_clean_page_frame,
.do_set_clean_page_frame = anon_map_do_set_clean_page_frame,
.do_dirty_page = anon_map_do_dirty_page,
.do_get_info = anon_map_get_info
};
static void init_anon_map(struct map_struct *anon_map, unsigned long start, unsigned long length, int prot, int grows_down, const char *name)
{
init_map(anon_map, start, start + (length - 1), prot, grows_down, &anon_map_operations);
anon_map->name = name;
}
/* file_map */
static inline unsigned long file_map_get_file_offset(const struct map_struct *file_map, unsigned long page)
{
return file_map->offset + (page - file_map->segment.start);
}
static void file_map_do_destroy(struct map_struct *file_map)
{
node_release_write_access(file_map->file);
node_release(file_map->file);
}
static void file_map_do_clone(const struct map_struct *file_map, struct map_struct *dest_map)
{
node_hold(file_map->file);
node_deny_write_access(file_map->file); /* ne peut pas echouer car file_map->file est deja bloque en ecriture */
dest_map->file = file_map->file;
dest_map->offset = file_map_get_file_offset(file_map, dest_map->segment.start);
}
static int file_map_do_is_mergeable(const struct map_struct *file_map, const struct map_struct *file_map1)
{
/* file_map->segment.end + 1 est valide car egal a file_map1->segment.start. */
return (file_map1->file == file_map->file
&& file_map_get_file_offset(file_map, file_map->segment.end + 1) == file_map1->offset);
}
static void file_map_do_merge(struct map_struct *file_map, struct map_struct *file_map1)
{
node_release_write_access(file_map1->file);
node_release(file_map1->file);
}
static void file_map_do_grow_down(struct map_struct *file_map) {}
static int file_map_do_fill_page(const struct map_struct *file_map, unsigned long page)
{
unsigned long foff;
unsigned int count;
int rv;
foff = file_map_get_file_offset(file_map, page);
if ((rv = read_node(file_map->file, foff, (void *)page, PAGE_SIZE)) < 0)
return 0;
count = rv;
if (count < PAGE_SIZE)
memset((void *)(page + count), 0, PAGE_SIZE - count);
return 1;
}
static unsigned long file_map_do_get_clean_page_frame(const struct map_struct *file_map, unsigned long laddr)
{
return mapping_get_page_frame(node_get_mapping(file_map->file), file_map_get_file_offset(file_map, laddr));
}
static void file_map_do_set_clean_page_frame(const struct map_struct *file_map, unsigned long laddr, unsigned long page_frame)
{
mapping_set_page_frame(node_get_mapping(file_map->file), file_map_get_file_offset(file_map, laddr), page_frame); /* on ignore le code de retour */
}
static void file_map_do_dirty_page(const struct map_struct *file_map, unsigned long laddr, unsigned long page_frame)
{
mapping_delete_page_frame(node_get_mapping(file_map->file), file_map_get_file_offset(file_map, laddr), page_frame);
}
static int file_map_get_info(const struct map_struct *file_map, struct map_info_struct *map_info)
{
struct stat_struct statbuf;
int rv;
if ((rv = stat_node(file_map->file, &statbuf)) < 0)
return rv;
map_info->offset = file_map->offset;
map_info->dev = statbuf.dev;
map_info->id = statbuf.id;
if ((rv = get_path(file_map->file, &map_info->pathname)) < 0)
return rv;
return 0;
}
static const struct map_operations_struct file_map_operations = {
.do_destroy = file_map_do_destroy,
.do_clone = file_map_do_clone,
.do_is_mergeable = file_map_do_is_mergeable,
.do_merge = file_map_do_merge,
.do_grow_down = file_map_do_grow_down,
.do_fill_page = file_map_do_fill_page,
.do_get_clean_page_frame = file_map_do_get_clean_page_frame,
.do_set_clean_page_frame = file_map_do_set_clean_page_frame,
.do_dirty_page = file_map_do_dirty_page,
.do_get_info = file_map_get_info
};
static int init_file_map(struct map_struct *file_map, unsigned long start, unsigned long length, int prot, struct file_struct *file, unsigned long offset)
{
int retval;
init_map(file_map, start, start + (length - 1), prot, 0, &file_map_operations);
node_hold(file);
if ((retval = node_deny_write_access(file)) < 0)
goto error_release;
file_map->file = file;
file_map->offset = offset;
return 0;
error_release:
node_release(file);
return retval;
}
/* Interface - initialisation */
void init_zero_page()
{
memset(zero_page, 0, PAGE_SIZE);
}
/* Interface - creation/destruction */
/* Pour toutes les fonctions de l'interface :
* start, base doivent etre aligne sur PAGE_SIZE.
* length, size doivent etre non nuls et multiples de PAGE_SIZE.
* [start, start + length[, [base, base + size[ doivent etre valides. */
int alloc_mem_map(unsigned long start, unsigned long length, int prot, void *mem_start, struct map_struct **mem_map)
{
if (!(*mem_map = kmalloc(sizeof (struct map_struct))))
return -ENOMEM;
init_mem_map(*mem_map, start, length, prot, mem_start);
return 0;
}
int alloc_anon_map(unsigned long start, unsigned long length, int prot, int grows_down, const char *name, struct map_struct **anon_map)
{
if (!(*anon_map = kmalloc(sizeof (struct map_struct))))
return -ENOMEM;
init_anon_map(*anon_map, start, length, prot, grows_down, name);
return 0;
}
/* file doit etre un fichier regulier ou un fichier special. */
int alloc_file_map(unsigned long start, unsigned long length, int prot, struct file_struct *file, unsigned long offset, struct map_struct **file_map)
{
int retval;
if (!(*file_map = kmalloc(sizeof (struct map_struct)))) {
retval = -ENOMEM;
goto error;
}
if ((retval = init_file_map(*file_map, start, length, prot, file, offset)) < 0)
goto error_kfree;
return 0;
error_kfree:
kfree(*file_map, sizeof (struct map_struct));
error:
return retval;
}
void free_map(struct map_struct *map)
{
map->operations->do_destroy(map);
kfree(map, sizeof (struct map_struct));
}
/* Interface - creation/destruction a partir d'une projection */
/* Clone une projection.
* Si l'intersection entre la projection et le segment [start, start + length[
* est vide, status est mis a -1 si la projection est inferieure au segment et
* a 1 sinon.
* Si la projection chevauche le segment, status est mis a 0 et new_map est
* construit comme clone de l'intersection de la projection et du segment. */
int map_clone(const struct map_struct *map, unsigned long start, unsigned long length, int *status, struct map_struct **new_map)
{
struct segment_struct seg, inter_seg;
int st;
init_segment(&seg, start, start + (length - 1));
st = intersect_segments(&map->segment, &seg, &inter_seg);
if (st) {
*status = st;
return 0;
}
if (!(*new_map = kmalloc(sizeof (struct map_struct))))
return -ENOMEM;
init_map(*new_map, inter_seg.start, inter_seg.end, map->prot, map->grows_down, map->operations);
map->operations->do_clone(map, *new_map);
*status = 0;
return 0;
}
/* Divise une projection.
* start doit se trouver dans la projection.
* map est modifiee pour contenir la projection inferieure. La projection
* superieure est mise dans new_map, sauf si start est l'adresse de debut de
* map, auquel cas map n'est pas modifiee et new_map est mise a NULL. */
int map_split(struct map_struct *map, unsigned long start, struct map_struct **new_map)
{
if (start == map->segment.start) {
*new_map = NULL;
return 0;
}
if (!(*new_map = kmalloc(sizeof (struct map_struct))))
return -ENOMEM;
init_map(*new_map, start, map->segment.end, map->prot, map->grows_down, map->operations);
map->operations->do_clone(map, *new_map);
resize_segment(&map->segment, start - 1);
return 0;
}
/* Fusionne les projections si possible.
* En cas de reussite, le resultat est mis dans map et map1 est liberee. */
int map_merge(struct map_struct *map, struct map_struct *map1)
{
struct segment_struct merge_seg;
if (!merge_segments(&map->segment, &map1->segment, &merge_seg))
return 0;
if (map1->prot != map->prot)
return 0;
if (map1->operations != map->operations)
return 0;
if (!map->operations->do_is_mergeable(map, map1))
return 0;
map->segment = merge_seg;
map->operations->do_merge(map, map1);
kfree(map1, sizeof (struct map_struct));
return 1;
}
/* Etend la projection vers le bas de maniere a ce qu'elle contienne laddr. */
void map_grow_down(struct map_struct *map, unsigned long laddr)
{
grow_down_segment(&map->segment, laddr);
map->operations->do_grow_down(map);
}
/* Change la protection d'une projection. */
void map_protect(struct map_struct *map, int prot)
{
map->prot = prot;
}
/* Interface - fonctions sur les projections */
int map_verify_access(const struct map_struct *map, int access)
{
switch (access) {
case PF_READ:
return 1;
case PF_WRITE:
default:
return map->prot == PROT_RDWR;
}
}
int map_grows_down(const struct map_struct *map)
{
return map->grows_down;
}
int map_fill_page(const struct map_struct *map, unsigned long page)
{
return map->operations->do_fill_page(map, page);
}
unsigned long map_get_clean_page_frame(const struct map_struct *map, unsigned long laddr)
{
return map->operations->do_get_clean_page_frame(map, laddr);
}
void map_set_clean_page_frame(const struct map_struct *map, unsigned long laddr, unsigned long page_frame)
{
map->operations->do_set_clean_page_frame(map, laddr, page_frame);
}
void map_dirty_page(const struct map_struct *map, unsigned long laddr, unsigned long page_frame)
{
map->operations->do_dirty_page(map, laddr, page_frame);
}
int map_get_info(const struct map_struct *map, struct map_info_struct *map_info)
{
map_info->start_addr = map->segment.start;
map_info->end_addr = map->segment.end + 1;
switch (map->prot) {
case PROT_RDONLY:
map_info->perms.read = 1;
map_info->perms.write = 0;
break;
case PROT_RDWR:
map_info->perms.read = 1;
map_info->perms.write = 1;
break;
}
return map->operations->do_get_info(map, map_info);
}
void map_info_destroy(struct map_info_struct *map_info)
{
if (map_info->pathname)
kfree(map_info->pathname, strlen(map_info->pathname) + 1);
}
/* Interface - utilitaires sur les projections */
/* Retourne :
* - -1 si map est avant laddr ;
* - 0 si map contient laddr ;
* - 1 si map est apres laddr. */
int compare_map_laddr(const struct map_struct *map, unsigned long laddr)
{
return compare_segment_laddr(&map->segment, laddr);
}
int compare_map_prot(const struct map_struct *map, int prot)
{
return map->prot == prot;
}
/* Retourne :
* - -1 si map1 est avant map2 ;
* - 0 si map1 et map2 se chevauchent ;
* - 1 si map1 est apres map2. */
int compare_maps(const struct map_struct *map1, const struct map_struct *map2)
{
return compare_segments(&map1->segment, &map2->segment);
}
/* map1 doit etre strictement inferieure a map2.
* Si map1 est nulle, 0 est pris comme debut.
* Si map2 est nulle, ULONG_MAX est pris comme fin.
* Le trou est l'intervalle compris entre map1 et map2.
* Retourne :
* - -1 si le trou est avant [base, base + size[ ;
* - 0 si le trou chevauche [base, base + size[ ;
* - 1 si le trou est apres [base, base + size[. */
int hole_intersect(const struct map_struct *map1, const struct map_struct *map2, unsigned long base, unsigned long size, unsigned long *start, unsigned long *length)
{
struct segment_struct hole, seg, inter;
int st;
if (map1 && map1->segment.end == ULONG_MAX)
return 1;
if (map2 && map2->segment.start == 0)
return -1;
init_segment(&hole, map1 ? map1->segment.end + 1 : 0, map2 ? map2->segment.start - 1 : ULONG_MAX);
init_segment(&seg, base, base + (size - 1));
st = intersect_segments(&hole, &seg, &inter);
if (st)
return st;
*start = inter.start;
*length = inter.end - inter.start + 1; /* valide car inferieur ou egal a size */
return 0;
}