225 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			225 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
| Copyright (C) 2015-2019 The University of Notre Dame
 | |
| This software is distributed under the GNU General Public License.
 | |
| See the file LICENSE for details.
 | |
| */
 | |
| 
 | |
| #include "bcache.h"
 | |
| #include "list.h"
 | |
| #include "page.h"
 | |
| #include "kmalloc.h"
 | |
| #include "string.h"
 | |
| #include "kernel/error.h"
 | |
| 
 | |
| struct bcache_entry {
 | |
| 	struct list_node node;
 | |
| 	struct device *device;
 | |
| 	int block;
 | |
| 	int dirty;
 | |
| 	char *data;
 | |
| };
 | |
| 
 | |
| static struct list cache = LIST_INIT;
 | |
| static struct bcache_stats stats = {0};
 | |
| static int max_cache_size = 100;
 | |
| 
 | |
| struct bcache_entry * bcache_entry_create( struct device *device, int block )
 | |
| {
 | |
| 	struct bcache_entry *e = kmalloc(sizeof(*e));
 | |
| 	if(!e) return 0;
 | |
| 
 | |
| 	e->device = device;
 | |
| 	e->block = block;
 | |
| 	e->data = page_alloc(1);
 | |
| 	if(!e->data) {
 | |
| 		kfree(e);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	return e;
 | |
| 
 | |
| }
 | |
| 
 | |
| void bcache_entry_delete( struct bcache_entry *e )
 | |
| {
 | |
| 	if(e) {
 | |
| 		if(e->data) page_free(e->data);
 | |
| 		kfree(e);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void bcache_entry_clean( struct bcache_entry *e )
 | |
| {
 | |
| 	if(e->dirty) {
 | |
| 		device_write(e->device,e->data,1,e->block);
 | |
| 		// XXX How to deal with failure here?
 | |
| 		e->dirty = 0;
 | |
| 		stats.writebacks++;
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| void bcache_trim()
 | |
| {
 | |
| 	struct bcache_entry *e;
 | |
| 
 | |
| 	while(list_size(&cache)>max_cache_size) {
 | |
| 		e = (struct bcache_entry *) list_pop_tail(&cache);
 | |
| 		bcache_entry_clean(e);
 | |
| 		bcache_entry_delete(e);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| struct bcache_entry * bcache_find( struct device *device, int block )
 | |
| {
 | |
| 	struct list_node *n;
 | |
| 	struct bcache_entry *e;
 | |
| 
 | |
| 	for(n=cache.head;n;n=n->next) {
 | |
| 		e = (struct bcache_entry *)n;
 | |
| 		if(e->device==device && e->block==block) {
 | |
| 			return e;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| struct bcache_entry * bcache_find_or_create( struct device *device, int block, int *was_a_hit )
 | |
| {
 | |
| 	struct bcache_entry *e = bcache_find(device,block);
 | |
| 	if(e) {
 | |
| 		*was_a_hit = 1;
 | |
| 	} else {
 | |
| 		*was_a_hit = 0;
 | |
| 		e = bcache_entry_create(device,block);
 | |
| 		if(!e) return 0;
 | |
| 		list_push_head(&cache,&e->node);
 | |
| 	}
 | |
| 
 | |
| 	bcache_trim();
 | |
| 
 | |
| 	return e;
 | |
| }
 | |
| 
 | |
| int bcache_read_block( struct device *device, char *data, int block )
 | |
| {
 | |
| 	int hit=0;
 | |
| 	int result;
 | |
| 
 | |
| 	struct bcache_entry *e = bcache_find_or_create(device,block,&hit);
 | |
| 	if(!e) return KERROR_OUT_OF_MEMORY;
 | |
| 
 | |
| 	if(hit) {
 | |
| 		stats.read_hits++;
 | |
| 		result = 1;
 | |
| 	} else {
 | |
| 		stats.read_misses++;
 | |
| 		result = device_read(device,e->data,1,block);
 | |
| 	}
 | |
| 
 | |
| 	if(result>0) {
 | |
| 		memcpy(data,e->data,device_block_size(device));
 | |
| 	} else {
 | |
| 		list_remove(&e->node);
 | |
| 		bcache_entry_delete(e);
 | |
| 	}
 | |
| 
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| int bcache_read( struct device *device, char *data, int blocks, int offset )
 | |
| {
 | |
| 	int i,r;
 | |
| 	int count = 0;
 | |
| 	int bs = device_block_size(device);
 | |
| 
 | |
| 	for(i=0;i<blocks;i++) {
 | |
| 		r = bcache_read_block(device,&data[i*bs],offset+i);
 | |
| 		if(r<1) break;
 | |
| 		count++;
 | |
| 	}
 | |
| 
 | |
| 	if(count>0) {
 | |
| 		return count;
 | |
| 	} else {
 | |
| 		return r;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| int bcache_write_block( struct device *device, const char *data, int block )
 | |
| {
 | |
| 	int hit;
 | |
| 
 | |
| 	struct bcache_entry *e = bcache_find_or_create(device,block,&hit);
 | |
| 	if(!e) return KERROR_OUT_OF_MEMORY;
 | |
| 
 | |
| 	if(hit) {
 | |
| 		stats.write_hits++;
 | |
| 	} else {
 | |
| 		stats.write_misses++;
 | |
| 	}
 | |
| 
 | |
| 	memcpy(e->data,data,device_block_size(device));
 | |
| 	e->dirty = 1;
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| int bcache_write( struct device *device, const char *data, int blocks, int offset )
 | |
| {
 | |
| 	int i,r;
 | |
| 	int count = 0;
 | |
| 	int bs = device_block_size(device);
 | |
| 
 | |
| 	for(i=0;i<blocks;i++) {
 | |
| 		r = bcache_write_block(device,&data[i*bs],offset+i);
 | |
| 		if(r<1) break;
 | |
| 		count++;
 | |
| 	}
 | |
| 
 | |
| 	if(count>0) {
 | |
| 		return count;
 | |
| 	} else {
 | |
| 		return r;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| void bcache_flush_block( struct device *device, int block )
 | |
| {
 | |
| 	struct bcache_entry *e;
 | |
| 	e = bcache_find(device,block);
 | |
| 	if(e) bcache_entry_clean(e);
 | |
| }
 | |
| 
 | |
| void bcache_flush_device( struct device *device )
 | |
| {
 | |
| 	struct list_node *n;
 | |
| 	struct bcache_entry *e;
 | |
| 
 | |
| 	for(n=cache.head;n;n=n->next) {
 | |
| 		e = (struct bcache_entry *) n;
 | |
| 		if(e->device==device) {
 | |
| 			bcache_entry_clean(e);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void bcache_flush_all()
 | |
| {
 | |
| 	struct list_node *n;
 | |
| 	struct bcache_entry *e;
 | |
| 
 | |
| 	for(n=cache.head;n;n=n->next) {
 | |
| 		e = (struct bcache_entry *) n;
 | |
| 		bcache_entry_clean(e);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void bcache_get_stats( struct bcache_stats *s )
 | |
| {
 | |
| 	memcpy(s,&stats,sizeof(*s));
 | |
| }
 |