block_cache.c 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /* Copyright (C) 2013 by John Cronin <jncronin@tysos.org>
  2. *
  3. * Permission is hereby granted, free of charge, to any person obtaining a copy
  4. * of this software and associated documentation files (the "Software"), to deal
  5. * in the Software without restriction, including without limitation the rights
  6. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. * copies of the Software, and to permit persons to whom the Software is
  8. * furnished to do so, subject to the following conditions:
  9. * The above copyright notice and this permission notice shall be included in
  10. * all copies or substantial portions of the Software.
  11. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  12. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  13. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  14. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  15. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  16. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  17. * THE SOFTWARE.
  18. */
  19. /* A driver to cache block device accesses
  20. *
  21. * Implemented as write through for now
  22. */
  23. #include <stdint.h>
  24. #include <string.h>
  25. #include <stdlib.h>
  26. #include <errno.h>
  27. #include "block.h"
  28. #include "util.h"
  29. // The name to append to the device name to distinguish this as a cached device
  30. //#define CACHE_DEV_NAME_APPEND "_cache"
  31. #define CACHE_DEV_NAME_APPEND ""
  32. static char dev_name[] = CACHE_DEV_NAME_APPEND;
  33. static char drv_name[] = "block_cache";
  34. struct cache_dev
  35. {
  36. struct block_device bd;
  37. struct block_device *parent;
  38. uintptr_t cache_start;
  39. int cache_entries;
  40. uint32_t cache_mask;
  41. uint32_t *cached_blocks;
  42. };
  43. int cache_read(struct block_device *, uint8_t *buf, size_t buf_size, uint32_t starting_block);
  44. int cache_write(struct block_device *, uint8_t *buf, size_t buf_size, uint32_t starting_block);
  45. inline static int cache_idx(struct cache_dev *dev, uint32_t block_no);
  46. int cache_init(struct block_device *parent, struct block_device **dev, uintptr_t cache_start, size_t cache_length)
  47. {
  48. if(parent == NULL)
  49. return -1;
  50. if(dev == NULL)
  51. return -1;
  52. // Build a block device structure
  53. struct cache_dev *cd = (struct cache_dev *)malloc(sizeof(struct cache_dev));
  54. if(cd == NULL)
  55. return -1;
  56. memset(cd, 0, sizeof(struct cache_dev));
  57. cd->bd.block_size = parent->block_size;
  58. cd->bd.device_name = (char *)malloc(strlen(parent->device_name) + strlen(dev_name) + 1);
  59. if(cd->bd.device_name == NULL)
  60. {
  61. free(cd);
  62. return -1;
  63. }
  64. strcpy(cd->bd.device_name, parent->device_name);
  65. strcat(cd->bd.device_name, dev_name);
  66. cd->bd.driver_name = drv_name;
  67. cd->bd.num_blocks = parent->num_blocks;
  68. if(parent->read)
  69. cd->bd.read = cache_read;
  70. if(parent->write)
  71. cd->bd.write = cache_write;
  72. cd->bd.supports_multiple_block_read = parent->supports_multiple_block_read;
  73. cd->bd.supports_multiple_block_write = parent->supports_multiple_block_write;
  74. // Calculate the number of cache entries
  75. int cache_entries = cache_length / cd->bd.block_size;
  76. if(cache_entries == 0)
  77. {
  78. free(cd->bd.device_name);
  79. free(cd);
  80. return -1;
  81. }
  82. // Calculate log2 of cache_entries
  83. int lg2 = 0;
  84. while(cache_entries >>= 1)
  85. lg2++;
  86. // Now restore to cache_entries (this has the effect of rounding down to a power
  87. // of two, so we can generate a simple mask function)
  88. cache_entries = (1 << lg2);
  89. cd->cache_entries = cache_entries;
  90. cd->cache_mask = (uint32_t)(cache_entries - 1);
  91. cd->cache_start = cache_start;
  92. cd->parent = parent;
  93. // Allocate an array to store the actual cached blocks in
  94. cd->cached_blocks = (uint32_t *)malloc(cd->cache_entries * sizeof(uint32_t));
  95. if(cd->cached_blocks == NULL)
  96. {
  97. free(cd->bd.device_name);
  98. free(cd);
  99. return -1;
  100. }
  101. // We use -1 to indicate an empty cache slot (as block 0 is a valid block)
  102. memset(cd->cached_blocks, 0xff, cd->cache_entries * sizeof(uint32_t));
  103. #ifdef DEBUG_CACHE
  104. printf("CACHE: initialised block cache for device %s, cache_entries %i, cache_mask %08x, cache_start %08x\n",
  105. parent->device_name, cd->cache_entries, cd->cache_mask, cd->cache_start);
  106. #endif
  107. *dev = (struct block_device *)cd;
  108. return 0;
  109. }
  110. inline static int cache_idx(struct cache_dev *dev, uint32_t block_no)
  111. {
  112. return (int)(block_no & dev->cache_mask);
  113. }
  114. int cache_read(struct block_device *dev, uint8_t *buf, size_t buf_size, uint32_t starting_block)
  115. {
  116. struct cache_dev *cd = (struct cache_dev *)dev;
  117. #ifdef DEBUG_CACHE
  118. printf("CACHE: request for %i bytes starting at block %i\n",
  119. buf_size, starting_block);
  120. #endif
  121. // Is this a multiblock request? Pass through to the parent
  122. if(buf_size > cd->bd.block_size)
  123. {
  124. #ifdef DEBUG_CACHE
  125. printf("CACHE: request for buf_size %i - passing through to parent\n",
  126. buf_size);
  127. #endif
  128. return cd->parent->read(cd->parent, buf, buf_size, starting_block);
  129. }
  130. // Else do we cache this block?
  131. int idx = cache_idx(cd, starting_block);
  132. void *cache_buf = (void *)(cd->cache_start + cd->bd.block_size * idx);
  133. // If not, load it up to the cache
  134. if(cd->cached_blocks[idx] != starting_block)
  135. {
  136. // If write-back, we need to evict a current cache entry and flush it
  137. // back to disk
  138. // TODO
  139. // Load up the new block
  140. #ifdef DEBUG_CACHE
  141. printf("CACHE: not in cache - requesting from parent\n");
  142. #endif
  143. int bytes_read = cd->parent->read(cd->parent, (uint8_t *)cache_buf,
  144. buf_size, starting_block);
  145. // Only cache if we loaded the entire block
  146. if(bytes_read == (int)cd->bd.block_size)
  147. {
  148. // Store this cache entry
  149. cd->cached_blocks[idx] = starting_block;
  150. #ifdef DEBUG_CACHE
  151. printf("CACHE: storing to slot %i\n", idx);
  152. #endif
  153. }
  154. else
  155. {
  156. // Invalidate the cache line
  157. cd->cached_blocks[idx] = 0xffffffff;
  158. }
  159. buf_size = bytes_read;
  160. }
  161. else
  162. {
  163. #ifdef DEBUG_CACHE
  164. printf("CACHE: fetching from cache slot %i\n", idx);
  165. #endif
  166. }
  167. // Copy the cached data to the calling process
  168. memcpy(buf, cache_buf, buf_size);
  169. return buf_size;
  170. }
  171. int cache_write(struct block_device *dev, uint8_t *buf, size_t buf_size, uint32_t starting_block)
  172. {
  173. struct cache_dev *cd = (struct cache_dev *)dev;
  174. // Only support whole block writes
  175. if(buf_size % cd->bd.block_size)
  176. {
  177. errno = EFAULT;
  178. return -1;
  179. }
  180. // Write through to the underlying device
  181. buf_size = cd->parent->write(cd->parent, buf, buf_size, starting_block);
  182. // How many blocks?
  183. int block_count = buf_size / cd->bd.block_size;
  184. // Update the cache if required
  185. for(int i = 0; i < block_count; i++)
  186. {
  187. int idx = cache_idx(cd, starting_block + i);
  188. if(cd->cached_blocks[idx] == starting_block + i)
  189. memcpy((void *)(cd->cache_start + idx * cd->bd.block_size), &buf[i * cd->bd.block_size], cd->bd.block_size);
  190. }
  191. return buf_size;
  192. }