diff --git a/os/run/blk_cache.run b/os/run/blk_cache.run
new file mode 100644
index 0000000000..47bfd4a883
--- /dev/null
+++ b/os/run/blk_cache.run
@@ -0,0 +1,73 @@
+#
+# \brief Test of Block session interface provided by server/blk_cache
+#
+
+#
+# Build
+#
+build {
+ core init
+ drivers/timer
+ server/blk_cache
+ test/blk
+}
+create_boot_directory
+
+#
+# Generate config
+#
+install_config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+
+#
+# Boot modules
+#
+build_boot_image { core init timer test-blk-srv blk_cache test-blk-cli }
+
+#
+# Qemu
+#
+append qemu_args " -nographic -m 64 "
+
+run_genode_until "Tests finished successfully.*\n" 60
+puts "Test succeeded"
diff --git a/os/src/server/blk_cache/chunk.h b/os/src/server/blk_cache/chunk.h
new file mode 100644
index 0000000000..251e55d20b
--- /dev/null
+++ b/os/src/server/blk_cache/chunk.h
@@ -0,0 +1,569 @@
+/*
+ * \brief Data structure for storing sparse blocks
+ * \author Norman Feske
+ * \author Stefan Kalkowski
+ * \date 2014-01-06
+ *
+ * Note: originally taken from ram_fs server, and adapted to cache needs
+ */
+
+/*
+ * Copyright (C) 2014 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU General Public License version 2.
+ */
+
+#ifndef _CHUNK_H_
+#define _CHUNK_H_
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+#include
+
+namespace Cache {
+
+ typedef Genode::uint64_t offset_t;
+ typedef Genode::uint64_t size_t;
+
+ /**
+ * Common base class of both 'Chunk' and 'Chunk_index'
+ */
+ class Chunk_base : Genode::Noncopyable
+ {
+ public:
+
+ struct Range_exception : Genode::Exception
+ {
+ offset_t off;
+ size_t size;
+
+ Range_exception(offset_t o, size_t s) : off(o), size(s) {}
+ };
+
+ typedef Range_exception Index_out_of_range;
+ typedef Range_exception Range_incomplete;
+
+ protected:
+
+ offset_t const _base_offset;
+ size_t _num_entries; /* corresponds to last used entry */
+ Chunk_base *_parent;
+
+ /**
+ * Test if specified range lies within the chunk
+ */
+ void assert_valid_range(offset_t start, size_t len,
+ size_t chunk_size) const
+ {
+ if (start < _base_offset)
+ throw Index_out_of_range(start, len);
+
+ if (start + len > _base_offset + chunk_size)
+ throw Index_out_of_range(start, len);
+ }
+
+ Chunk_base(offset_t base_offset, Chunk_base *p)
+ : _base_offset(base_offset), _num_entries(0), _parent(p) { }
+
+ /**
+ * Construct zero chunk
+ */
+ Chunk_base() : _base_offset(0), _num_entries(0), _parent(0) { }
+
+ public:
+
+ /**
+ * Return absolute base offset of chunk in bytes
+ */
+ offset_t base_offset() const { return _base_offset; }
+
+ /**
+ * Return true if chunk has no allocated sub chunks
+ */
+ bool empty() const { return _num_entries == 0; }
+
+ virtual void free(size_t, offset_t) = 0;
+ };
+
+
+ /**
+ * Chunk of bytes used as leaf in hierarchy of chunk indices
+ */
+ template
+ class Chunk : public Chunk_base,
+ public POLICY::Element
+ {
+ private:
+
+ char _data[CHUNK_SIZE];
+ unsigned _writes;
+
+ public:
+
+ typedef Range_exception Dirty_chunk;
+
+ static constexpr size_t SIZE = CHUNK_SIZE;
+
+ /**
+ * Construct byte chunk
+ *
+ * \param base_offset absolute offset of chunk in bytes
+ *
+ * The first argument is unused. Its mere purpose is to make the
+ * signature of the constructor compatible to the constructor
+ * of 'Chunk_index'.
+ */
+ Chunk(Genode::Allocator &, offset_t base_offset, Chunk_base *p)
+ : Chunk_base(base_offset, p), _writes(0) { }
+
+ /**
+ * Construct zero chunk
+ */
+ Chunk() : _writes(0) { }
+
+ /**
+ * Return number of used entries
+ *
+ * The returned value corresponds to the index of the last used
+ * entry + 1. It does not correlate to the number of actually
+ * allocated entries (there may be ranges of zero blocks).
+ */
+ size_t used_size() const { return _num_entries; }
+
+ void write(char const *src, size_t len, offset_t seek_offset)
+ {
+ assert_valid_range(seek_offset, len, SIZE);
+
+ POLICY::write(this);
+
+ /* offset relative to this chunk */
+ offset_t const local_offset = seek_offset - base_offset();
+
+ Genode::memcpy(&_data[local_offset], src, len);
+
+ _num_entries = Genode::max(_num_entries, local_offset + len);
+
+ _writes++;
+ }
+
+ void read(char *dst, size_t len, offset_t seek_offset) const
+ {
+ assert_valid_range(seek_offset, len, SIZE);
+
+ POLICY::read(this);
+
+ Genode::memcpy(dst, &_data[seek_offset - base_offset()], len);
+ }
+
+ void stat(size_t len, offset_t seek_offset) const
+ {
+ assert_valid_range(seek_offset, len, SIZE);
+
+ if (_writes == 0)
+ throw Range_incomplete(base_offset(), SIZE);
+ }
+
+ void sync(size_t len, offset_t seek_offset)
+ {
+ if (_writes > 1) {
+ POLICY::sync(this, (char*)_data);
+ _writes = 1;
+ }
+ }
+
+ void alloc(size_t len, offset_t seek_offset) { }
+
+ void truncate(size_t size)
+ {
+ assert_valid_range(size, 0, SIZE);
+
+ /*
+ * Offset of the first free position (relative to the beginning
+ * this chunk).
+ */
+ offset_t const local_offset = size - base_offset();
+
+ if (local_offset >= _num_entries)
+ return;
+
+ Genode::memset(&_data[local_offset], 0, _num_entries - local_offset);
+
+ _num_entries = local_offset;
+ }
+
+ void free(size_t, offset_t)
+ {
+ if (_writes > 1) throw Dirty_chunk(_base_offset, SIZE);
+
+ _num_entries = 0;
+ if (_parent) _parent->free(SIZE, _base_offset);
+ }
+ };
+
+
+ template
+ class Chunk_index : public Chunk_base
+ {
+ public:
+
+ typedef ENTRY_TYPE Entry;
+
+ static constexpr size_t ENTRY_SIZE = ENTRY_TYPE::SIZE;
+ static constexpr size_t SIZE = ENTRY_SIZE*NUM_ENTRIES;
+
+ private:
+
+ Genode::Allocator &_alloc;
+
+ Entry * _entries[NUM_ENTRIES];
+
+ /**
+ * Return instance of a zero sub chunk
+ */
+ static Entry &_zero_chunk()
+ {
+ static Entry zero_chunk;
+ return zero_chunk;
+ }
+
+ /**
+ * Return sub chunk at given index
+ *
+ * If there is no sub chunk at the specified index, this function
+ * transparently allocates one. Hence, the returned sub chunk
+ * is ready to be written to.
+ */
+ Entry &_alloc_entry(unsigned index)
+ {
+ if (index >= NUM_ENTRIES)
+ throw Index_out_of_range(base_offset() + index*ENTRY_SIZE,
+ ENTRY_SIZE);
+
+ if (_entries[index])
+ return *_entries[index];
+
+ offset_t entry_offset = base_offset() + index*ENTRY_SIZE;
+
+ for (;;) {
+ try {
+ _entries[index] = new (&_alloc)
+ Entry(_alloc, entry_offset, this);
+ break;
+ } catch(Genode::Allocator::Out_of_memory) {
+ POLICY::flush(sizeof(Entry));
+ }
+ }
+
+ _num_entries = Genode::max(_num_entries, index + 1);
+
+ return *_entries[index];
+ }
+
+ /**
+ * Return sub chunk at given index
+ */
+ Entry &_entry(unsigned index) const
+ {
+ if (index >= NUM_ENTRIES)
+ throw Index_out_of_range(base_offset() + index*ENTRY_SIZE,
+ ENTRY_SIZE);
+
+ if (_entries[index])
+ return *_entries[index];
+
+ throw Range_incomplete(base_offset() + index*ENTRY_SIZE,
+ ENTRY_SIZE);
+ }
+
+ /**
+ * Return sub chunk at given index (for syncing only)
+ *
+ * This function transparently provides a zero sub chunk for any
+ * index that is not populated by a real chunk.
+ */
+ Entry &_entry_for_syncing(unsigned index) const
+ {
+ if (index >= NUM_ENTRIES)
+ throw Index_out_of_range(base_offset() + index*ENTRY_SIZE,
+ ENTRY_SIZE);
+
+ if (_entries[index])
+ return *_entries[index];
+
+ return _zero_chunk();
+ }
+
+
+ /**
+ * Return index of entry located at specified byte offset
+ *
+ * The caller of this function must make sure that the offset
+ * parameter is within the bounds of the chunk.
+ */
+ unsigned _index_by_offset(offset_t offset) const
+ {
+ return (offset - base_offset()) / ENTRY_SIZE;
+ }
+
+ /**
+ * Apply operation 'func' to a range of entries
+ */
+ template
+ static void _range_op(THIS &obj, DATA *data, size_t len,
+ offset_t seek_offset, FUNC const &func)
+ {
+ /*
+ * Depending on whether this function is called for reading
+ * (const function) or writing (non-const function), the
+ * operand type is const or non-const Entry. The correct type
+ * is embedded as a trait in the 'FUNC' functor type.
+ */
+ typedef typename FUNC::Entry Const_qualified_entry;
+
+ obj.assert_valid_range(seek_offset, len, SIZE);
+
+ while (len > 0) {
+
+ unsigned const index = obj._index_by_offset(seek_offset);
+
+ Const_qualified_entry &entry = FUNC::lookup(obj, index);
+
+ /*
+ * Calculate byte offset relative to the chunk
+ *
+ * We cannot use 'entry.base_offset()' for this calculation
+ * because in the const case, the lookup might return a
+ * zero chunk, which has no defined base offset. Therefore,
+ * we calculate the base offset via index*ENTRY_SIZE.
+ */
+ offset_t const local_seek_offset =
+ seek_offset - obj.base_offset() - index*ENTRY_SIZE;
+
+ /* available capacity at 'entry' starting at seek offset */
+ offset_t const capacity = ENTRY_SIZE - local_seek_offset;
+ size_t const curr_len = Genode::min(len, capacity);
+
+ /* apply functor (read or write) to entry */
+ func(entry, data, curr_len, seek_offset);
+
+ /* advance to next entry */
+ len -= curr_len;
+ data += curr_len;
+ seek_offset += curr_len;
+ }
+ }
+
+ struct Alloc_func
+ {
+ typedef ENTRY_TYPE Entry;
+
+ static Entry &lookup(Chunk_index &chunk, unsigned i) {
+ return chunk._alloc_entry(i); }
+
+ void operator () (Entry &entry, char const *src, size_t len,
+ offset_t seek_offset) const
+ {
+ entry.alloc(len, seek_offset);
+ }
+ };
+
+ struct Write_func
+ {
+ typedef ENTRY_TYPE Entry;
+
+ static Entry &lookup(Chunk_index &chunk, unsigned i) {
+ return chunk._entry(i); }
+
+ void operator () (Entry &entry, char const *src, size_t len,
+ offset_t seek_offset) const
+ {
+ entry.write(src, len, seek_offset);
+ }
+ };
+
+ struct Read_func
+ {
+ typedef ENTRY_TYPE const Entry;
+
+ static Entry &lookup(Chunk_index const &chunk, unsigned i) {
+ return chunk._entry(i); }
+
+ void operator () (Entry &entry, char *dst, size_t len,
+ offset_t seek_offset) const {
+ entry.read(dst, len, seek_offset); }
+ };
+
+ struct Stat_func
+ {
+ typedef ENTRY_TYPE const Entry;
+
+ static Entry &lookup(Chunk_index const &chunk, unsigned i) {
+ return chunk._entry(i); }
+
+ void operator () (Entry &entry, char*, size_t len,
+ offset_t seek_offset) const {
+ entry.stat(len, seek_offset); }
+ };
+
+ struct Sync_func
+ {
+ typedef ENTRY_TYPE Entry;
+
+ static Entry &lookup(Chunk_index const &chunk, unsigned i) {
+ return chunk._entry_for_syncing(i); }
+
+ void operator () (Entry &entry, char*, size_t len,
+ offset_t seek_offset) const
+ {
+ entry.sync(len, seek_offset);
+ }
+ };
+
+ void _init_entries()
+ {
+ for (unsigned i = 0; i < NUM_ENTRIES; i++)
+ _entries[i] = 0;
+ }
+
+ void _destroy_entry(unsigned i)
+ {
+ if (_entries[i] && (i < _num_entries)) {
+ Genode::destroy(&_alloc, _entries[i]);
+ _entries[i] = 0;
+ }
+ }
+
+ public:
+
+ /**
+ * Constructor
+ *
+ * \param alloc allocator to use for allocating sub-chunk
+ * indices and chunks
+ * \param base_offset absolute offset of the chunk in bytes
+ */
+ Chunk_index(Genode::Allocator &alloc, offset_t base_offset,
+ Chunk_base *p = 0)
+ : Chunk_base(base_offset, p), _alloc(alloc) { _init_entries(); }
+
+ /**
+ * Construct zero chunk
+ */
+ Chunk_index() : _alloc(*(Genode::Allocator *)0) { }
+
+ /**
+ * Destructor
+ */
+ ~Chunk_index()
+ {
+ for (unsigned i = 0; i < NUM_ENTRIES; i++)
+ _destroy_entry(i);
+ }
+
+ /**
+ * Return size of chunk in bytes
+ *
+ * The returned value corresponds to the position after the highest
+ * offset that was written to.
+ */
+ size_t used_size() const
+ {
+ if (_num_entries == 0)
+ return 0;
+
+ /* size of entries that lie completely within the used range */
+ size_t const size_whole_entries = ENTRY_SIZE*(_num_entries - 1);
+
+ Entry *last_entry = _entries[_num_entries - 1];
+ if (!last_entry)
+ return size_whole_entries;
+
+ return size_whole_entries + last_entry->used_size();
+ }
+
+ /**
+ * Write data to chunk
+ */
+ void write(char const *src, size_t len, offset_t seek_offset) {
+ _range_op(*this, src, len, seek_offset, Write_func()); }
+
+ /**
+ * Allocate needed chunks
+ */
+ void alloc(size_t len, offset_t seek_offset) {
+ _range_op(*this, (char*)0, len, seek_offset, Alloc_func()); }
+
+ /**
+ * Read data from chunk
+ */
+ void read(char *dst, size_t len, offset_t seek_offset) const {
+ _range_op(*this, dst, len, seek_offset, Read_func()); }
+
+ /**
+ * Check for chunk availability
+ */
+ void stat(size_t len, offset_t seek_offset) const {
+ _range_op(*this, (char*)0, len, seek_offset, Stat_func()); }
+
+ /**
+ * Synchronize chunk when dirty
+ */
+ void sync(size_t len, offset_t seek_offset) const {
+ _range_op(*this, (char*)0, len, seek_offset, Sync_func()); }
+
+ /**
+ * Free chunks
+ */
+ void free(size_t len, offset_t seek_offset)
+ {
+ unsigned const index = _index_by_offset(seek_offset);
+ Genode::destroy(&_alloc, _entries[index]);
+ _entries[index] = 0;
+ for (unsigned i = 0; i < NUM_ENTRIES; i++)
+ if (_entries[i])
+ return;
+
+ if (_parent) _parent->free(SIZE, _base_offset);
+ }
+
+ /**
+ * Truncate chunk to specified size in bytes
+ *
+ * This function can be used to shrink a chunk only. Specifying a
+ * 'size' larger than 'used_size' has no effect. The value returned
+ * by 'used_size' refers always to the position of the last byte
+ * written to the chunk.
+ */
+ void truncate(size_t size)
+ {
+ unsigned const trunc_index = _index_by_offset(size);
+
+ if (trunc_index >= _num_entries)
+ return;
+
+ for (unsigned i = trunc_index + 1; i < _num_entries; i++)
+ _destroy_entry(i);
+
+ /* traverse into sub chunks */
+ if (_entries[trunc_index])
+ _entries[trunc_index]->truncate(size);
+
+ _num_entries = trunc_index + 1;
+
+ /*
+ * If the truncated at a chunk boundary, we can release the
+ * empty trailing chunk at 'trunc_index'.
+ */
+ if (_entries[trunc_index] && _entries[trunc_index]->empty()) {
+ _destroy_entry(trunc_index);
+ _num_entries--;
+ }
+ }
+ };
+};
+
+#endif /* _CHUNK_H_ */
diff --git a/os/src/server/blk_cache/driver.h b/os/src/server/blk_cache/driver.h
new file mode 100644
index 0000000000..f7198f1042
--- /dev/null
+++ b/os/src/server/blk_cache/driver.h
@@ -0,0 +1,429 @@
+/*
+ * \brief Cache driver
+ * \author Stefan Kalkowski
+ * \date 2013-12-05
+ */
+
+/*
+ * Copyright (C) 2013 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU General Public License version 2.
+ */
+
+#include
+#include
+#include
+
+#include "chunk.h"
+
+/**
+ * Cache driver used by the generic block driver framework
+ *
+ * \param POLICY the cache replacement policy (e.g. LRU)
+ */
+template
+class Driver : public Block::Driver
+{
+ private:
+
+ /**
+ * This class encapsulates requests to the backend device in progress,
+ * and the packets from the client side that triggered the request.
+ */
+ struct Request : public Genode::List::Element
+ {
+ Block::Packet_descriptor srv;
+ Block::Packet_descriptor cli;
+
+ Request(Block::Packet_descriptor &s,
+ Block::Packet_descriptor &c)
+ : srv(s), cli(c) {}
+
+ /*
+ * \return true when the given response packet matches
+ * the request send to the backend device
+ */
+ bool match(const Block::Packet_descriptor& reply) const
+ {
+ return reply.operation() == srv.operation() &&
+ reply.block_number() == srv.block_number() &&
+ reply.block_count() == srv.block_count();
+ }
+
+ /*
+ * \param write whether it's a write or read request
+ * \param nr block number requested
+ * \param cnt number of blocks requested
+ * \return true when the given parameters match
+ * the request send to the backend device
+ */
+ bool match(const bool write,
+ const Block::sector_t nr,
+ const Genode::size_t cnt) const
+ {
+ Block::Packet_descriptor::Opcode op = write
+ ? Block::Packet_descriptor::WRITE
+ : Block::Packet_descriptor::READ;
+ return op == srv.operation() &&
+ nr >= srv.block_number() &&
+ nr+cnt <= srv.block_number()+srv.block_count();
+ }
+ };
+
+
+ /**
+ * Write failed exception at a specific device offset,
+ * can be triggered whenever the backend device is not ready
+ * to proceed
+ */
+ struct Write_failed : Genode::Exception
+ {
+ Cache::offset_t off;
+
+ Write_failed(Cache::offset_t o) : off(o) {}
+ };
+
+
+ /*
+ * The given policy class is extended by a synchronization routine,
+ * used by the cache chunk structure
+ */
+ struct Policy : POLICY {
+ static void sync(const typename POLICY::Element *e, char *src); };
+
+ public:
+
+ enum {
+ SLAB_SZ = Block::Session::TX_QUEUE_SIZE*sizeof(Request),
+ CACHE_BLK_SIZE = 4096
+ };
+
+ /**
+ * We use five levels of page-table like chunk structure,
+ * thereby we've a maximum device size of 256^4*4096 (LBA48)
+ */
+ typedef Cache::Chunk Chunk_level_4;
+ typedef Cache::Chunk_index<256, Chunk_level_4, Policy> Chunk_level_3;
+ typedef Cache::Chunk_index<256, Chunk_level_3, Policy> Chunk_level_2;
+ typedef Cache::Chunk_index<256, Chunk_level_2, Policy> Chunk_level_1;
+ typedef Cache::Chunk_index<256, Chunk_level_1, Policy> Chunk_level_0;
+
+ private:
+
+ static Driver *_instance; /* singleton instance */
+
+ Genode::Tslab _r_slab; /* slab for requests */
+ Genode::List _r_list; /* list of requests */
+ Genode::Allocator_avl _alloc; /* packet allocator */
+ Block::Connection _blk; /* backend device */
+ Block::Session::Operations _ops; /* allowed operations */
+ Genode::size_t _blk_sz; /* block size */
+ Block::sector_t _blk_cnt; /* block count */
+ Chunk_level_0 _cache; /* chunk hierarchy */
+ Genode::Signal_rpc_member _source_ack;
+ Genode::Signal_rpc_member _source_submit;
+ Genode::Signal_rpc_member _yield;
+
+ Driver(Driver const&); /* singleton pattern */
+ Driver& operator=(Driver const&); /* singleton pattern */
+
+ /*
+ * Return modulus of cache's versus backend device's block size
+ */
+ inline int _cache_blk_mod() { return CACHE_BLK_SIZE / _blk_sz; }
+
+ /*
+ * Round off given block number to cache block size granularity
+ */
+ inline Block::sector_t _cache_blk_round_off(Block::sector_t nr) {
+ return nr - (nr % _cache_blk_mod()); }
+
+ /*
+ * Round up given block number to cache block size granularity
+ */
+ inline Block::sector_t _cache_blk_round_up(Block::sector_t nr) {
+ return (nr % _cache_blk_mod())
+ ? nr + _cache_blk_mod() - (nr % _cache_blk_mod())
+ : nr; }
+
+ /*
+ * Handle response to a single request
+ *
+ * \param srv packet received from the backend device
+ * \param r outstanding request
+ */
+ inline void _handle_reply(Block::Packet_descriptor &srv, Request *r)
+ {
+ try {
+ if (r->cli.operation() == Block::Packet_descriptor::READ)
+ read(r->cli.block_number(), r->cli.block_count(),
+ session->tx_sink()->packet_content(r->cli),
+ r->cli);
+ else
+ write(r->cli.block_number(), r->cli.block_count(),
+ session->tx_sink()->packet_content(r->cli),
+ r->cli);
+ } catch(Block::Driver::Request_congestion) {
+ PWRN("cli (%lld %zu) srv (%lld %zu)",
+ r->cli.block_number(), r->cli.block_count(),
+ r->srv.block_number(), r->srv.block_count());
+ }
+ }
+
+ /*
+ * Handle acknowledgements from the backend device
+ */
+ void _ack_avail(unsigned)
+ {
+ while (_blk.tx()->ack_avail()) {
+ Block::Packet_descriptor p = _blk.tx()->get_acked_packet();
+
+ /* when reading, write result into cache */
+ if (p.operation() == Block::Packet_descriptor::READ)
+ _cache.write(_blk.tx()->packet_content(p),
+ p.block_count() * _blk_sz,
+ p.block_number() * _blk_sz);
+
+ /* loop through the list of requests, and ack all related */
+ for (Request *r = _r_list.first(), *r_to_handle = r; r;
+ r_to_handle = r) {
+ r = r->next();
+ if (r_to_handle->match(p)) {
+ _handle_reply(p, r_to_handle);
+ _r_list.remove(r_to_handle);
+ Genode::destroy(&_r_slab, r_to_handle);
+ }
+ }
+
+ _blk.tx()->release_packet(p);
+ }
+ }
+
+ /*
+ * Handle that the backend device is ready to receive again
+ */
+ void _ready_to_submit(unsigned) { }
+
+ /*
+ * Setup a request to the backend device
+ *
+ * \param block_number block number offset
+ * \param block_count number of blocks
+ * \param packet original packet request received from the client
+ */
+ void _request(Block::sector_t block_number,
+ Genode::size_t block_count,
+ Block::Packet_descriptor &packet)
+ {
+ Block::Packet_descriptor p_to_dev;
+
+ try {
+ /* we've to look whether the request is already pending */
+ for (Request *r = _r_list.first(); r; r = r->next()) {
+ if (r->match(false, block_number, block_count)) {
+ _r_list.insert(new (&_r_slab) Request(r->srv, packet));
+ return;
+ }
+ }
+
+ /* it doesn't pay, we've to send a request to the device */
+ if (!_blk.tx()->ready_to_submit()) {
+ PWRN("not ready_to_submit");
+ throw Request_congestion();
+ }
+
+ /* read ahead CACHE_BLK_SIZE */
+ Block::sector_t nr = _cache_blk_round_off(block_number);
+ Genode::size_t cnt = _cache_blk_round_up(block_count +
+ (block_number - nr));
+
+ /* ensure all memory is available before sending the request */
+ _cache.alloc(cnt * _blk_sz, nr * _blk_sz);
+
+ /* construct and send the packet */
+ p_to_dev =
+ Block::Packet_descriptor(_blk.dma_alloc_packet(_blk_sz*cnt),
+ Block::Packet_descriptor::READ,
+ nr, cnt);
+ _r_list.insert(new (&_r_slab) Request(p_to_dev, packet));
+ _blk.tx()->submit_packet(p_to_dev);
+ } catch(Block::Session::Tx::Source::Packet_alloc_failed) {
+ throw Request_congestion();
+ } catch(Genode::Allocator::Out_of_memory) {
+ if (p_to_dev.valid()) /* clean up */
+ _blk.tx()->release_packet(p_to_dev);
+ //TODO
+ throw Request_congestion();
+ }
+ }
+
+ /*
+ * Synchronize dirty chunks with backend device
+ */
+ void _sync()
+ {
+ Cache::offset_t off = 0;
+ Cache::size_t len = _blk_sz * _blk_cnt;
+
+ while (len > 0) {
+ try {
+ _cache.sync(len, off);
+ len = 0;
+ } catch(Write_failed &e) {
+ /**
+ * Write to backend failed when backend device isn't ready
+ * to proceed, so handle signals, until it's ready again
+ */
+ off = e.off;
+ len = _blk_sz * _blk_cnt - off;
+ Server::wait_and_dispatch_one_signal();
+ }
+ }
+ }
+
+ /*
+ * Check for chunk availability
+ *
+ * \param nr block number offset
+ * \param cnt number of blocks
+ * \param p client side packet, which triggered this operation
+ */
+ bool _stat(Block::sector_t nr, Genode::size_t cnt,
+ Block::Packet_descriptor &p)
+ {
+ Cache::offset_t off = nr * _blk_sz;
+ Cache::size_t size = cnt * _blk_sz;
+ Cache::offset_t end = off + size;
+
+ try {
+ _cache.stat(size, off);
+ return true;
+ } catch(Cache::Chunk_base::Range_incomplete &e) {
+ off = Genode::max(off, e.off);
+ size = Genode::min(end - off, e.size);
+ _request(off / _blk_sz, size / _blk_sz, p);
+ }
+ return false;
+ }
+
+ /*
+ * Signal handler for yield requests of the parent
+ */
+ void _parent_yield(unsigned)
+ {
+ using namespace Genode;
+
+ Parent::Resource_args const args = env()->parent()->yield_request();
+ size_t const requested_ram_quota =
+ Arg_string::find_arg(args.string(), "ram_quota").ulong_value(0);
+
+ /* flush the requested amount of RAM from cache */
+ POLICY::flush(requested_ram_quota);
+ env()->parent()->yield_response();
+ }
+
+ /*
+ * Constructor
+ *
+ * \param ep server entrypoint
+ */
+ Driver(Server::Entrypoint &ep)
+ : _r_slab(Genode::env()->heap()),
+ _alloc(Genode::env()->heap()),
+ _blk(&_alloc, Block::Session::TX_QUEUE_SIZE*CACHE_BLK_SIZE),
+ _blk_sz(0),
+ _blk_cnt(0),
+ _cache(*Genode::env()->heap(), 0),
+ _source_ack(ep, *this, &Driver::_ack_avail),
+ _source_submit(ep, *this, &Driver::_ready_to_submit),
+ _yield(ep, *this, &Driver::_parent_yield)
+ {
+ _blk.info(&_blk_cnt, &_blk_sz, &_ops);
+ _blk.tx_channel()->sigh_ack_avail(_source_ack);
+ _blk.tx_channel()->sigh_ready_to_submit(_source_submit);
+ Genode::env()->parent()->yield_sigh(_yield);
+
+ if (CACHE_BLK_SIZE % _blk_sz) {
+ PERR("only devices that block size is divider of %x supported",
+ CACHE_BLK_SIZE);
+ throw Io_error();
+ }
+
+ /* truncate chunk structure to real size of the device */
+ _cache.truncate(_blk_sz*_blk_cnt);
+ }
+
+ public:
+
+ ~Driver()
+ {
+ /* when session gets closed, synchronize and flush the cache */
+ _sync();
+ POLICY::flush();
+ }
+
+ static Driver* instance(Server::Entrypoint &ep) {
+ _instance = new (Genode::env()->heap()) Driver(ep);
+ return _instance;
+ }
+
+ static Driver* instance() { return _instance; }
+
+ static void destroy()
+ {
+ Genode::destroy(Genode::env()->heap(), _instance);
+ _instance = 0;
+ }
+
+ Block::Session_client* blk() { return &_blk; }
+ Genode::size_t blk_sz() { return _blk_sz; }
+
+
+ /****************************
+ ** Block-driver interface **
+ ****************************/
+
+ Genode::size_t block_size() { return _blk_sz; }
+ Block::sector_t block_count() { return _blk_cnt; }
+ Block::Session::Operations ops() { return _ops; }
+
+ void read(Block::sector_t block_number,
+ Genode::size_t block_count,
+ char* buffer,
+ Block::Packet_descriptor &packet)
+ {
+ if (!_ops.supported(Block::Packet_descriptor::READ))
+ throw Io_error();
+
+ if (!_stat(block_number, block_count, packet))
+ return;
+
+ _cache.read(buffer, block_count*_blk_sz, block_number*_blk_sz);
+ session->ack_packet(packet);
+ }
+
+ void write(Block::sector_t block_number,
+ Genode::size_t block_count,
+ const char * buffer,
+ Block::Packet_descriptor &packet)
+ {
+ if (!_ops.supported(Block::Packet_descriptor::WRITE))
+ throw Io_error();
+
+ _cache.alloc(block_count * _blk_sz, block_number * _blk_sz);
+
+ if ((block_number % _cache_blk_mod()) && _stat(block_number, 1, packet))
+ return;
+
+ if (((block_number+block_count) % _cache_blk_mod())
+ && _stat(block_number+block_count-1, 1, packet))
+ return;
+
+ _cache.write(buffer, block_count * _blk_sz,
+ block_number * _blk_sz);
+ session->ack_packet(packet);
+ }
+
+ void sync() { _sync(); }
+};
diff --git a/os/src/server/blk_cache/lru.cc b/os/src/server/blk_cache/lru.cc
new file mode 100644
index 0000000000..a84e08bdd1
--- /dev/null
+++ b/os/src/server/blk_cache/lru.cc
@@ -0,0 +1,62 @@
+/*
+ * \brief Least-recently-used cache replacement strategy
+ * \author Stefan Kalkowski
+ * \date 2013-12-05
+ */
+
+/*
+ * Copyright (C) 2013 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU General Public License version 2.
+ */
+#include
+#include "lru.h"
+#include "driver.h"
+
+typedef Driver::Chunk_level_4 Chunk;
+
+static const Lru_policy::Element *lru = 0;
+static Genode::List lru_list;
+
+
+static void lru_access(const Lru_policy::Element *e)
+{
+ if (e == lru) return;
+
+ if (e->next()) lru_list.remove(e);
+
+ lru_list.insert(e, lru);
+ lru = e;
+}
+
+
+void Lru_policy::read(const Lru_policy::Element *e) {
+ lru_access(e); }
+
+
+void Lru_policy::write(const Lru_policy::Element *e) {
+ lru_access(e); }
+
+
+void Lru_policy::flush(Cache::size_t size)
+{
+ Cache::size_t s = 0;
+ for (Lru_policy::Element *e = lru_list.first();
+ e && ((size == 0) || (s < size));
+ e = lru_list.first(), s += sizeof(Chunk)) {
+ Chunk *cb = static_cast(e);
+ e = e->next();
+ try {
+ cb->free(Driver::CACHE_BLK_SIZE,
+ cb->base_offset());
+ lru_list.remove(cb);
+ } catch(Chunk::Dirty_chunk &e) {
+ cb->sync(e.size, e.off);
+ }
+ }
+
+ if (!lru_list.first()) lru = 0;
+
+ if (s < size) throw Block::Driver::Request_congestion();
+}
diff --git a/os/src/server/blk_cache/lru.h b/os/src/server/blk_cache/lru.h
new file mode 100644
index 0000000000..6a8bd8e6c3
--- /dev/null
+++ b/os/src/server/blk_cache/lru.h
@@ -0,0 +1,25 @@
+/*
+ * \brief Least-recently-used cache replacement strategy
+ * \author Stefan Kalkowski
+ * \date 2013-12-05
+ */
+
+/*
+ * Copyright (C) 2013 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU General Public License version 2.
+ */
+
+#include
+
+#include "chunk.h"
+
+struct Lru_policy
+{
+ class Element : public Genode::List::Element {};
+
+ static void read(const Element *e);
+ static void write(const Element *e);
+ static void flush(Cache::size_t size = 0);
+};
diff --git a/os/src/server/blk_cache/main.cc b/os/src/server/blk_cache/main.cc
new file mode 100644
index 0000000000..3b2bb2aecc
--- /dev/null
+++ b/os/src/server/blk_cache/main.cc
@@ -0,0 +1,84 @@
+/*
+ * \brief Cache a block device
+ * \author Stefan Kalkowski
+ * \date 2013-12-05
+ */
+
+/*
+ * Copyright (C) 2013 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU General Public License version 2.
+ */
+
+#include
+
+#include "lru.h"
+#include "driver.h"
+
+
+template Driver* Driver::_instance = 0;
+
+
+/**
+ * Synchronize a chunk with the backend device
+ */
+template
+void Driver::Policy::sync(const typename POLICY::Element *e, char *dst)
+{
+ Cache::offset_t off =
+ static_cast::Chunk_level_4*>(e)->base_offset();
+
+ if (!Driver::instance()->blk()->tx()->ready_to_submit())
+ throw Write_failed(off);
+ try {
+ Block::Packet_descriptor
+ p(Driver::instance()->blk()->dma_alloc_packet(Driver::CACHE_BLK_SIZE),
+ Block::Packet_descriptor::WRITE,
+ off / Driver::instance()->blk_sz(),
+ Driver::CACHE_BLK_SIZE / Driver::instance()->blk_sz());
+ Driver::instance()->blk()->tx()->submit_packet(p);
+ } catch(Block::Session::Tx::Source::Packet_alloc_failed) {
+ throw Write_failed(off);
+ }
+}
+
+
+struct Main
+{
+ Server::Entrypoint &ep;
+
+ struct Factory : Block::Driver_factory
+ {
+ Server::Entrypoint &ep;
+
+ Factory(Server::Entrypoint &ep) : ep(ep) {}
+
+ Block::Driver *create() { return Driver::instance(ep); }
+ void destroy(Block::Driver *driver) { Driver::destroy(); }
+ } factory;
+
+ void resource_handler(unsigned) { }
+
+ Block::Root root;
+ Server::Signal_rpc_member resource_dispatcher;
+
+ Main(Server::Entrypoint &ep)
+ : ep(ep), factory(ep), root(ep, Genode::env()->heap(), factory),
+ resource_dispatcher(ep, *this, &Main::resource_handler)
+ {
+ Genode::env()->parent()->announce(ep.manage(root));
+ Genode::env()->parent()->resource_avail_sigh(resource_dispatcher);
+ }
+};
+
+
+/************
+ ** Server **
+ ************/
+
+namespace Server {
+ char const *name() { return "blk_cache_ep"; }
+ size_t stack_size() { return 2*1024*sizeof(long); }
+ void construct(Entrypoint &ep) { static Main server(ep); }
+}
diff --git a/os/src/server/blk_cache/target.mk b/os/src/server/blk_cache/target.mk
new file mode 100644
index 0000000000..56285279f1
--- /dev/null
+++ b/os/src/server/blk_cache/target.mk
@@ -0,0 +1,3 @@
+TARGET = blk_cache
+LIBS = base server
+SRC_CC = main.cc lru.cc
diff --git a/tool/autopilot.list b/tool/autopilot.list
index 85674087b5..c8e2af7c13 100644
--- a/tool/autopilot.list
+++ b/tool/autopilot.list
@@ -37,3 +37,4 @@ resource_yield
gdb_monitor
part_blk
xml_generator
+blk_cache