#include "device_struct.h"
#include <block_driver_struct.h>
#include <printk.h>
#include <condition.h>
#include <condition_struct.h>
#include <areas.h>
#include <consts.h>
#include <config.h>
#include <stddef.h>
#define HASH_TABLE_SIZE 307
#define hash(dev, blk) (((unsigned)(dev->id ^ blk)) % HASH_TABLE_SIZE)
struct block_info_struct {
const struct device_struct *device;
unsigned long block;
void *buffer;
unsigned locked : 1;
unsigned invalid : 1;
unsigned uptodate : 1;
unsigned dirty : 1;
struct block_info_struct *previous_free;
struct block_info_struct *next_free;
struct block_info_struct *previous_hash;
struct block_info_struct *next_hash;
struct block_info_struct *next_dirty;
};
#define get_block_info(buf) ((struct block_info_struct *)buffer_start + ((buffer_end - (buf)) / BLOCK_SIZE - 1))
static struct block_info_struct *first_free;
static struct block_info_struct *last_free;
static struct block_info_struct *hash_table[HASH_TABLE_SIZE];
static unsigned long size;
static struct condition_struct condition;
static struct block_info_struct *get_free()
{
return first_free;
}
static void push_free(struct block_info_struct *block_info)
{
block_info->previous_free = last_free;
block_info->next_free = NULL;
if (last_free)
last_free->next_free = block_info;
else
first_free = block_info;
last_free = block_info;
}
static void remove_free(struct block_info_struct *block_info)
{
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;
}
static void take(struct block_info_struct *block_info)
{
remove_free(block_info);
block_info->locked = 1;
}
static void release(struct block_info_struct *block_info)
{
block_info->locked = 0;
push_free(block_info);
}
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;
}
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];
block_info->previous_hash = NULL;
block_info->next_hash = fh;
if (fh)
fh->previous_hash = block_info;
hash_table[hash_code] = block_info;
}
static void remove_hash(struct block_info_struct *block_info)
{
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;
}
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;
}
}
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);
}
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);
}
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);
}
}
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;
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);
if (first_dirty) {
st = 1;
sort_dirty(&first_dirty);
bad_dev = 0;
for (bi = first_dirty; bi; bi = bi->next_dirty) {
if ((!device || bi->device == device) && !bi->locked && bi->dirty) {
if (bi->device == bad_dev)
continue;
take(bi);
if (!write(bi)) {
bad_dev = bi->device;
st = 0;
}
else {
bi->uptodate = 1;
bi->dirty = 0;
}
release(bi);
}
}
return st;
}
else
return 1;
}
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;
bi->locked = 0;
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;
}
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();
}
void *buffer_get(const struct device_struct *device, unsigned long block)
{
struct block_info_struct *bi;
if (device->type != FT_BLK)
return NULL;
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())) {
condition_wait(&condition);
goto start;
}
take(bi);
if (bi->device && get_hash(bi->device, bi->block)) {
if (bi->dirty)
write(bi);
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;
}
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);
}
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;
}
void buffer_dirty(const void *buffer)
{
struct block_info_struct *bi;
bi = get_block_info(buffer);
if (bi->invalid)
return;
bi->dirty = 1;
}
int buffer_sync(const struct device_struct *device)
{
if (device->type != FT_BLK)
return 0;
return sync(device);
}