173 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			173 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			C
		
	
	
	
| // SPDX-License-Identifier: GPL-2.0+
 | |
| /*
 | |
|  * Copyright (C) Nelson Integration, LLC 2016
 | |
|  * Author: Eric Nelson<eric@nelint.com>
 | |
|  *
 | |
|  */
 | |
| #include <config.h>
 | |
| #include <common.h>
 | |
| #include <malloc.h>
 | |
| #include <part.h>
 | |
| #include <linux/ctype.h>
 | |
| #include <linux/list.h>
 | |
| 
 | |
| struct block_cache_node {
 | |
| 	struct list_head lh;
 | |
| 	int iftype;
 | |
| 	int devnum;
 | |
| 	lbaint_t start;
 | |
| 	lbaint_t blkcnt;
 | |
| 	unsigned long blksz;
 | |
| 	char *cache;
 | |
| };
 | |
| 
 | |
| static LIST_HEAD(block_cache);
 | |
| 
 | |
| static struct block_cache_stats _stats = {
 | |
| 	.max_blocks_per_entry = 8,
 | |
| 	.max_entries = 32
 | |
| };
 | |
| 
 | |
| static struct block_cache_node *cache_find(int iftype, int devnum,
 | |
| 					   lbaint_t start, lbaint_t blkcnt,
 | |
| 					   unsigned long blksz)
 | |
| {
 | |
| 	struct block_cache_node *node;
 | |
| 
 | |
| 	list_for_each_entry(node, &block_cache, lh)
 | |
| 		if ((node->iftype == iftype) &&
 | |
| 		    (node->devnum == devnum) &&
 | |
| 		    (node->blksz == blksz) &&
 | |
| 		    (node->start <= start) &&
 | |
| 		    (node->start + node->blkcnt >= start + blkcnt)) {
 | |
| 			if (block_cache.next != &node->lh) {
 | |
| 				/* maintain MRU ordering */
 | |
| 				list_del(&node->lh);
 | |
| 				list_add(&node->lh, &block_cache);
 | |
| 			}
 | |
| 			return node;
 | |
| 		}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int blkcache_read(int iftype, int devnum,
 | |
| 		  lbaint_t start, lbaint_t blkcnt,
 | |
| 		  unsigned long blksz, void *buffer)
 | |
| {
 | |
| 	struct block_cache_node *node = cache_find(iftype, devnum, start,
 | |
| 						   blkcnt, blksz);
 | |
| 	if (node) {
 | |
| 		const char *src = node->cache + (start - node->start) * blksz;
 | |
| 		memcpy(buffer, src, blksz * blkcnt);
 | |
| 		debug("hit: start " LBAF ", count " LBAFU "\n",
 | |
| 		      start, blkcnt);
 | |
| 		++_stats.hits;
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	debug("miss: start " LBAF ", count " LBAFU "\n",
 | |
| 	      start, blkcnt);
 | |
| 	++_stats.misses;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void blkcache_fill(int iftype, int devnum,
 | |
| 		   lbaint_t start, lbaint_t blkcnt,
 | |
| 		   unsigned long blksz, void const *buffer)
 | |
| {
 | |
| 	lbaint_t bytes;
 | |
| 	struct block_cache_node *node;
 | |
| 
 | |
| 	/* don't cache big stuff */
 | |
| 	if (blkcnt > _stats.max_blocks_per_entry)
 | |
| 		return;
 | |
| 
 | |
| 	if (_stats.max_entries == 0)
 | |
| 		return;
 | |
| 
 | |
| 	bytes = blksz * blkcnt;
 | |
| 	if (_stats.max_entries <= _stats.entries) {
 | |
| 		/* pop LRU */
 | |
| 		node = (struct block_cache_node *)block_cache.prev;
 | |
| 		list_del(&node->lh);
 | |
| 		_stats.entries--;
 | |
| 		debug("drop: start " LBAF ", count " LBAFU "\n",
 | |
| 		      node->start, node->blkcnt);
 | |
| 		if (node->blkcnt * node->blksz < bytes) {
 | |
| 			free(node->cache);
 | |
| 			node->cache = 0;
 | |
| 		}
 | |
| 	} else {
 | |
| 		node = malloc(sizeof(*node));
 | |
| 		if (!node)
 | |
| 			return;
 | |
| 		node->cache = 0;
 | |
| 	}
 | |
| 
 | |
| 	if (!node->cache) {
 | |
| 		node->cache = malloc(bytes);
 | |
| 		if (!node->cache) {
 | |
| 			free(node);
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	debug("fill: start " LBAF ", count " LBAFU "\n",
 | |
| 	      start, blkcnt);
 | |
| 
 | |
| 	node->iftype = iftype;
 | |
| 	node->devnum = devnum;
 | |
| 	node->start = start;
 | |
| 	node->blkcnt = blkcnt;
 | |
| 	node->blksz = blksz;
 | |
| 	memcpy(node->cache, buffer, bytes);
 | |
| 	list_add(&node->lh, &block_cache);
 | |
| 	_stats.entries++;
 | |
| }
 | |
| 
 | |
| void blkcache_invalidate(int iftype, int devnum)
 | |
| {
 | |
| 	struct list_head *entry, *n;
 | |
| 	struct block_cache_node *node;
 | |
| 
 | |
| 	list_for_each_safe(entry, n, &block_cache) {
 | |
| 		node = (struct block_cache_node *)entry;
 | |
| 		if ((node->iftype == iftype) &&
 | |
| 		    (node->devnum == devnum)) {
 | |
| 			list_del(entry);
 | |
| 			free(node->cache);
 | |
| 			free(node);
 | |
| 			--_stats.entries;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void blkcache_configure(unsigned blocks, unsigned entries)
 | |
| {
 | |
| 	struct block_cache_node *node;
 | |
| 	if ((blocks != _stats.max_blocks_per_entry) ||
 | |
| 	    (entries != _stats.max_entries)) {
 | |
| 		/* invalidate cache */
 | |
| 		while (!list_empty(&block_cache)) {
 | |
| 			node = (struct block_cache_node *)block_cache.next;
 | |
| 			list_del(&node->lh);
 | |
| 			free(node->cache);
 | |
| 			free(node);
 | |
| 		}
 | |
| 		_stats.entries = 0;
 | |
| 	}
 | |
| 
 | |
| 	_stats.max_blocks_per_entry = blocks;
 | |
| 	_stats.max_entries = entries;
 | |
| 
 | |
| 	_stats.hits = 0;
 | |
| 	_stats.misses = 0;
 | |
| }
 | |
| 
 | |
| void blkcache_stats(struct block_cache_stats *stats)
 | |
| {
 | |
| 	memcpy(stats, &_stats, sizeof(*stats));
 | |
| 	_stats.hits = 0;
 | |
| 	_stats.misses = 0;
 | |
| }
 |