7f8f0f50ea
Ref #1987 Fix #2058
424 lines
12 KiB
C++
424 lines
12 KiB
C++
/*
|
|
* \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 <base/log.h>
|
|
#include <block_session/connection.h>
|
|
#include <block/component.h>
|
|
#include <os/packet_allocator.h>
|
|
|
|
#include "chunk.h"
|
|
|
|
/**
|
|
* Cache driver used by the generic block driver framework
|
|
*
|
|
* \param POLICY the cache replacement policy (e.g. LRU)
|
|
*/
|
|
template <typename POLICY>
|
|
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<Request>::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<CACHE_BLK_SIZE, Policy> 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<Request, SLAB_SZ> _r_slab; /* slab for requests */
|
|
Genode::List<Request> _r_list; /* list of requests */
|
|
Genode::Packet_allocator _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_handler<Driver> _source_ack;
|
|
Genode::Signal_handler<Driver> _source_submit;
|
|
Genode::Signal_handler<Driver> _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(),
|
|
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() * _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() { }
|
|
|
|
/*
|
|
* 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 * _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, 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 = _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;
|
|
_env.ep().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,
|
|
char * const buffer, 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, 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)
|
|
: _env(env),
|
|
_r_slab(&heap),
|
|
_alloc(&heap, CACHE_BLK_SIZE),
|
|
_blk(&_alloc, Block::Session::TX_QUEUE_SIZE*CACHE_BLK_SIZE),
|
|
_blk_sz(0),
|
|
_blk_cnt(0),
|
|
_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.info(&_blk_cnt, &_blk_sz, &_ops);
|
|
_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 % _blk_sz) {
|
|
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(_blk_sz*_blk_cnt);
|
|
}
|
|
|
|
~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 _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, buffer, packet))
|
|
return;
|
|
|
|
_cache.read(buffer, block_count*_blk_sz, block_number*_blk_sz);
|
|
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, const_cast<char* const>(buffer), packet))
|
|
return;
|
|
|
|
if (((block_number+block_count) % _cache_blk_mod())
|
|
&& !_stat(block_number+block_count-1, 1,
|
|
const_cast<char* const>(buffer), packet))
|
|
return;
|
|
|
|
_cache.write(buffer, block_count * _blk_sz,
|
|
block_number * _blk_sz);
|
|
ack_packet(packet);
|
|
}
|
|
|
|
void sync() { _sync(); }
|
|
};
|