diff --git a/os/run/blk_cache.run b/os/run/blk_cache.run new file mode 100644 index 000000000..47bfd4a88 --- /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 000000000..251e55d20 --- /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 000000000..f7198f104 --- /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 000000000..a84e08bdd --- /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 000000000..6a8bd8e6c --- /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 000000000..3b2bb2aec --- /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 000000000..56285279f --- /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 85674087b..c8e2af7c1 100644 --- a/tool/autopilot.list +++ b/tool/autopilot.list @@ -37,3 +37,4 @@ resource_yield gdb_monitor part_blk xml_generator +blk_cache