/* 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 . */
/* Memoire cache pour les acces disque.
* Reference : Tanenbaum, p. 447. */
#include "device_struct.h"
#include
#include
#include
#include
#include
#include
#include
#include
#define HASH_TABLE_SIZE 307 /* un nombre premier raisonnable */
#define hash(dev, blk) (((unsigned)(dev->id ^ blk)) % HASH_TABLE_SIZE)
struct block_info_struct {
/* cle */
const struct device_struct *device;
unsigned long block;
/* tampon */
void *buffer;
/* indicateurs */
unsigned locked : 1; /* verrouille (en cours d'utilisation) */
unsigned invalid : 1; /* a ete invalide (n'a de sens que si locked != 0) */
unsigned uptodate : 1; /* contient les donnees du bloc du disque */
unsigned dirty : 1; /* a ete modifie par rapport au bloc du disque */
/* chainage pour la liste des blocs libres */
struct block_info_struct *previous_free;
struct block_info_struct *next_free;
/* chainage pour la table de hachage */
struct block_info_struct *previous_hash;
struct block_info_struct *next_hash;
/* chainage pour la liste de blocs sales */
struct block_info_struct *next_dirty;
};
#define get_block_info(buf) ((struct block_info_struct *)buffer_start + ((buffer_end - (buf)) / BLOCK_SIZE - 1))
/* liste des blocs libres */
static struct block_info_struct *first_free; /* LRU */
static struct block_info_struct *last_free; /* MRU */
/* table de hachage */
static struct block_info_struct *hash_table[HASH_TABLE_SIZE];
/* divers */
static unsigned long size; /* nombre de blocs */
static struct condition_struct condition; /* condition pour l'obtention d'un block_info */
/* Le buffer cache lui-meme n'est pas synchronise. Il ne doit donc pas etre
* accede dans des gestionnaires d'interruptions. */
/* Liste des blocs libres */
/* Retourne le bloc libre le moins recemment utilise (LRU). */
static struct block_info_struct *get_free()
{
return first_free;
}
/* Le bloc ne doit pas etre dans la liste. */
static void push_free(struct block_info_struct *block_info)
{
/* preparation */
block_info->previous_free = last_free;
block_info->next_free = NULL;
/* connexion */
if (last_free) /* first_free et last_free sont toujours tous deux nuls ou tous deux non nuls */
last_free->next_free = block_info;
else
first_free = block_info;
last_free = block_info;
}
/* Le bloc doit etre dans la liste. */
static void remove_free(struct block_info_struct *block_info)
{
/* deconnexion */
if (block_info == first_free)
first_free = first_free->next_free;
else
block_info->previous_free->next_free = block_info->next_free;
if (block_info == last_free)
last_free = last_free->previous_free;
else
block_info->next_free->previous_free = block_info->previous_free;
}
/* Enleve le bloc de la liste des blocs vides et le verrouille (maintient la
* coherence de la structure). */
static void take(struct block_info_struct *block_info)
{
remove_free(block_info);
block_info->locked = 1;
}
/* Deverrouille le bloc et l'ajoute dans la liste des blocs vides (maintient la
* coherence de la structure). */
static void release(struct block_info_struct *block_info)
{
block_info->locked = 0;
push_free(block_info);
}
/* Table de hachage */
/* Retourne NULL si le bloc demande n'est pas dans la table. */
static struct block_info_struct *get_hash(const struct device_struct *device, unsigned long block)
{
struct block_info_struct *bi;
for (bi = hash_table[hash(device, block)]; bi; bi = bi->next_hash)
if (bi->device == device && bi->block == block)
return bi;
return NULL;
}
/* Le bloc ne doit pas etre dans la table. */
static void put_hash(struct block_info_struct *block_info)
{
unsigned long hash_code;
struct block_info_struct *fh;
hash_code = hash(block_info->device, block_info->block);
fh = hash_table[hash_code];
/* preparation */
block_info->previous_hash = NULL;
block_info->next_hash = fh;
/* connexion */
if (fh)
fh->previous_hash = block_info;
hash_table[hash_code] = block_info;
}
/* Le bloc doit etre dans la table. */
static void remove_hash(struct block_info_struct *block_info)
{
/* deconnexion */
if (block_info->previous_hash)
block_info->previous_hash->next_hash = block_info->next_hash;
else
hash_table[hash(block_info->device, block_info->block)] = block_info->next_hash;
if (block_info->next_hash)
block_info->next_hash->previous_hash = block_info->previous_hash;
}
/* Liste des blocs sales */
static void add_dirty(struct block_info_struct **first_dirty, struct block_info_struct *block_info)
{
block_info->next_dirty = *first_dirty;
*first_dirty = block_info;
}
static inline int compare_dirty(const struct block_info_struct *block_info1, const struct block_info_struct *block_info2)
{
if (block_info1->device != block_info2->device)
return block_info1->device < block_info2->device ? -1 : 1;
if (block_info1->block != block_info2->block)
return block_info1->block < block_info2->block ? -1 : 1;
return 0;
}
static void divide_dirty(struct block_info_struct *first, struct block_info_struct **first1, struct block_info_struct **first2)
{
struct block_info_struct *bi1, *bi2;
if (!(*first1 = first)) {
*first2 = NULL;
return;
}
if (!(*first2 = first->next_dirty))
return;
divide_dirty(first->next_dirty->next_dirty, &bi1, &bi2);
(*first1)->next_dirty = bi1;
(*first2)->next_dirty = bi2;
}
static void merge_dirty(struct block_info_struct *first1, struct block_info_struct *first2, struct block_info_struct **first)
{
if (!first1) {
*first = first2;
return;
}
if (!first2) {
*first = first1;
return;
}
if (compare_dirty(first1, first2) < 0) {
merge_dirty(first1->next_dirty, first2, first);
first1->next_dirty = *first;
*first = first1;
}
else {
merge_dirty(first1, first2->next_dirty, first);
first2->next_dirty = *first;
*first = first2;
}
}
/* Tri de la liste des blocs sales par tri-fusion. */
static void sort_dirty(struct block_info_struct **first)
{
struct block_info_struct *first1, *first2;
if (!*first || !(*first)->next_dirty)
return;
divide_dirty(*first, &first1, &first2);
sort_dirty(&first1);
sort_dirty(&first2);
merge_dirty(first1, first2, first);
}
/* Entrees / Sorties */
static int read(struct block_info_struct *block_info)
{
return block_info->device->block_driver->read(block_info->block, block_info->buffer);
}
static int write(struct block_info_struct *block_info)
{
return block_info->device->block_driver->write(block_info->block, block_info->buffer);
}
/* Cache */
/* non bloquante */
static void invalidate(const struct device_struct *device)
{
struct block_info_struct *bi;
int i;
for (i = 0; i < HASH_TABLE_SIZE; i++)
for (bi = hash_table[i]; bi; bi = bi->next_hash)
if (!device || bi->device == device) {
if (bi->locked)
bi->invalid = 1;
else
remove_hash(bi); /* n'invalide pas l'iteration :) */
}
}
/* A la difference de invalidate(), sync() est bloquante, car elle fait des
* appels a write(). Il est donc necessaire de verrouiller les blocs concernes.
* A defaut d'un veritable algorithme d'optimisation des deplacements du bras
* des lecteurs de disques, on ecrit les blocs sales dans l'ordre croissant, ce
* qui devrait reduire les degats. */
static int sync(const struct device_struct *device)
{
struct block_info_struct *bi;
struct block_info_struct *first_dirty;
const struct device_struct *bad_dev;
int i;
int st;
/* construction de la liste des blocs sales */
first_dirty = NULL;
for (i = 0; i < HASH_TABLE_SIZE; i++)
for (bi = hash_table[i]; bi; bi = bi->next_hash)
if ((!device || bi->device == device) && !bi->locked && bi->dirty)
add_dirty(&first_dirty, bi);
/* tri de la liste des blocs sales et ecriture */
if (first_dirty) { /* test non indispensable - petite optimisation */
st = 1;
sort_dirty(&first_dirty);
bad_dev = 0;
for (bi = first_dirty; bi; bi = bi->next_dirty) {
/* On est oblige de retester la condition car la fonction est bloquante. */
if ((!device || bi->device == device) && !bi->locked && bi->dirty) {
if (bi->device == bad_dev)
continue;
take(bi);
if (!write(bi)) {
bad_dev = bi->device; /* on retient que ce peripherique ne fonctionne pas */
st = 0;
}
else {
bi->uptodate = 1;
bi->dirty = 0;
}
release(bi);
}
}
return st;
}
else
return 1;
}
/* Initialisation */
static void init_cache()
{
struct block_info_struct *bi;
void *b;
bi = buffer_start;
b = buffer_end;
size = 0;
while ((b -= BLOCK_SIZE) >= (void *)(bi + 1)) {
bi->device = NULL;
bi->buffer = b; /* fixe */
bi->locked = 0; /* entree libre */
bi->previous_free = bi - 1;
bi->next_free = bi + 1;
bi++;
size++;
}
first_free = buffer_start;
first_free->previous_free = NULL;
last_free = bi - 1;
last_free->next_free = NULL;
}
static void init_hash_table()
{
int i;
for (i = 0; i < HASH_TABLE_SIZE; i++)
hash_table[i] = NULL;
}
/* Fonctions exportees */
void buffer_init()
{
init_cache();
init_hash_table();
condition_init(&condition);
}
void buffer_print_info()
{
printk("Buffer cache size: %luKB\n", (size * BLOCK_SIZE) >> 10);
}
int buffer_is_writable(const struct device_struct *device)
{
if (device->type != FT_BLK)
return 0;
return device->block_driver->is_writable();
}
/* Retourne un tampon sur un bloc. Cette fonction est bloquante si le cache est
* plein. La valeur de retour n'est jamais nulle, sauf si device n'est pas un
* peripherique de type bloc ou si le bloc n'existe pas pour ce peripherique.
*/
void *buffer_get(const struct device_struct *device, unsigned long block)
{
struct block_info_struct *bi;
if (device->type != FT_BLK)
return NULL;
/* Inutile de gaspiller de la place avec des blocs inexistants. */
if (block >= device->block_driver->get_block_count())
return NULL;
start:
if (device->block_driver->has_changed())
invalidate(device);
if (!(bi = get_hash(device, block))) {
if (!(bi = get_free())) { /* bloc libre le moins recemment utilise */
condition_wait(&condition);
goto start;
}
take(bi);
if (bi->device && get_hash(bi->device, bi->block)) { /* le bloc est utilise */
if (bi->dirty) /* il contient des donnees non synchronisees */
write(bi); /* on le synchronise */
remove_hash(bi);
}
bi->device = device;
bi->block = block;
bi->uptodate = 0;
bi->dirty = 0;
put_hash(bi);
}
else if (bi->locked) {
condition_wait(&condition);
goto start;
}
else
take(bi);
bi->invalid = 0;
return bi->buffer;
}
/* Indique que le tampon n'est plus en cours d'utilisation. */
void buffer_release(const void *buffer)
{
struct block_info_struct *bi;
bi = get_block_info(buffer);
if (bi->invalid)
bi->uptodate = 0;
release(bi);
condition_signal_all(&condition);
}
/* Transfert, si necessaire, le bloc du disque vers le tampon. Retourne une
* valeur non nulle si et seulement si le transfert a reussi. */
int buffer_update(void *buffer)
{
struct block_info_struct *bi;
bi = get_block_info(buffer);
if (bi->invalid)
return 0;
if (!bi->uptodate) {
if (!read(bi))
return 0;
bi->uptodate = 1;
bi->dirty = 0;
}
return 1;
}
/* Marque le bloc comme sale. Le transfert du bloc du tampon vers le disque
* aura lieu lors du prochain appel a buffer_sync(). */
void buffer_dirty(const void *buffer)
{
struct block_info_struct *bi;
bi = get_block_info(buffer);
if (bi->invalid)
return;
bi->dirty = 1;
}
/* Transfert tous les blocs sales du peripherique device de leur tampon vers le
* disque. Si device == 0, transfert tous les blocs sales. Retourne une valeur
* non nulle si et seulement si le transfert a reussi. */
int buffer_sync(const struct device_struct *device)
{
if (device->type != FT_BLK)
return 0;
return sync(device);
}