/* * \brief Cache driver * \author Stefan Kalkowski * \date 2013-12-05 */ /* * Copyright (C) 2013-2017 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. */ #include #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; char * const buffer; Request(Block::Packet_descriptor &s, Block::Packet_descriptor &c, char * const b) : srv(s), cli(c), buffer(b) {} /* * \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: Genode::Env &_env; Genode::Tslab _r_slab; /* slab for requests */ Genode::List _r_list; /* list of requests */ Genode::Packet_allocator _alloc; /* packet allocator */ Block::Connection<> _blk; /* backend device */ Block::Session::Info const _info; /* block-device info */ Chunk_level_0 _cache; /* chunk hierarchy */ Genode::Io_signal_handler _source_ack; Genode::Io_signal_handler _source_submit; Genode::Io_signal_handler _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 / _info.block_size; } /* * 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(), r->buffer, r->cli); else write(r->cli.block_number(), r->cli.block_count(), r->buffer, r->cli); } catch(Block::Driver::Request_congestion) { Genode::warning("cli (", r->cli.block_number(), " ", r->cli.block_count(), ") " "srv (", r->srv.block_number(), " ", r->srv.block_count(), ")"); } } /* * Handle acknowledgements from the backend device */ void _ack_avail() { 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() * _info.block_size, p.block_number() * _info.block_size); /* 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() { } /* * 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, char * const buffer, 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, buffer)); return; } } /* it doesn't pay, we've to send a request to the device */ if (!_blk.tx()->ready_to_submit()) { Genode::warning("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 * _info.block_size, nr * _info.block_size); /* construct and send the packet */ p_to_dev = Block::Packet_descriptor(_blk.alloc_packet(_info.block_size*cnt), Block::Packet_descriptor::READ, nr, cnt); _r_list.insert(new (&_r_slab) Request(p_to_dev, packet, buffer)); _blk.tx()->submit_packet(p_to_dev); } catch(Block::Session::Tx::Source::Packet_alloc_failed) { throw Request_congestion(); } catch(Genode::Allocator::Out_of_memory) { /* clean up */ _blk.tx()->release_packet(p_to_dev); throw Request_congestion(); } } /* * Synchronize dirty chunks with backend device */ void _sync() { Cache::offset_t off = 0; Cache::size_t len = _info.block_size * _info.block_count; 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 = _info.block_size * _info.block_count - off; _env.ep().wait_and_dispatch_one_io_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, char * const buffer, Block::Packet_descriptor &p) { Cache::offset_t off = nr * _info.block_size; Cache::size_t size = cnt * _info.block_size; 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 / _info.block_size, size / _info.block_size, buffer, p); } return false; } /* * Signal handler for yield requests of the parent */ void _parent_yield() { 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(); } public: /* * Constructor * * \param ep server entrypoint */ Driver(Genode::Env &env, Genode::Heap &heap) : Block::Driver(env.ram()), _env(env), _r_slab(&heap), _alloc(&heap, CACHE_BLK_SIZE), _blk(_env, &_alloc, Block::Session::TX_QUEUE_SIZE*CACHE_BLK_SIZE), _info(_blk.info()), _cache(heap, 0), _source_ack(env.ep(), *this, &Driver::_ack_avail), _source_submit(env.ep(), *this, &Driver::_ready_to_submit), _yield(env.ep(), *this, &Driver::_parent_yield) { using namespace Genode; _blk.tx_channel()->sigh_ack_avail(_source_ack); _blk.tx_channel()->sigh_ready_to_submit(_source_submit); env.parent().yield_sigh(_yield); if (CACHE_BLK_SIZE % _info.block_size) { error("only devices that block size is divider of ", Hex(CACHE_BLK_SIZE, Hex::OMIT_PREFIX) ," supported"); throw Io_error(); } /* truncate chunk structure to real size of the device */ _cache.truncate(_info.block_size * _info.block_count); } ~Driver() { /* when session gets closed, synchronize and flush the cache */ _sync(); POLICY::flush(); } Block::Session_client* blk() { return &_blk; } Genode::size_t blk_sz() { return _info.block_size; } /**************************** ** Block-driver interface ** ****************************/ Block::Session::Info info() const override { return _info; } void read(Block::sector_t block_number, Genode::size_t block_count, char* buffer, Block::Packet_descriptor &packet) { if (!_stat(block_number, block_count, buffer, packet)) return; _cache.read(buffer, block_count *_info.block_size, block_number*_info.block_size); ack_packet(packet); } void write(Block::sector_t block_number, Genode::size_t block_count, const char * buffer, Block::Packet_descriptor &packet) { if (!_info.writeable) throw Io_error(); _cache.alloc(block_count * _info.block_size, block_number * _info.block_size); if ((block_number % _cache_blk_mod()) && !_stat(block_number, 1, const_cast(buffer), packet)) return; if (((block_number+block_count) % _cache_blk_mod()) && !_stat(block_number+block_count-1, 1, const_cast(buffer), packet)) return; _cache.write(buffer, block_count * _info.block_size, block_number * _info.block_size); ack_packet(packet); } void sync() { _sync(); } };