libports: libc_block plugin

This plugin provides support for using a Block_session connection by
opening "/dev/blkdev".

Fixes #965.
This commit is contained in:
Josef Söntgen 2013-11-04 18:35:20 +01:00 committed by Norman Feske
parent 634f4c0d8c
commit 88851ab699
5 changed files with 798 additions and 0 deletions

View File

@ -0,0 +1,9 @@
LIBS = libc
SRC_CC = plugin.cc
vpath %.cc $(REP_DIR)/src/lib/libc_block
include $(REP_DIR)/lib/mk/libc-common.inc
SHARED_LIB = yes

164
libports/run/libc_block.run Normal file
View File

@ -0,0 +1,164 @@
set use_sd_card_drv [expr [have_spec omap4] || [have_spec exynos5] || [have_spec pl180]]
set use_atapi_drv [have_spec x86]
if {[have_spec linux]} {
puts "Run script does not support this platform"; exit }
if {[expr [have_spec 64bit] && $use_atapi_drv]} {
puts "ATAPI driver does not support 64 bit."; exit 0 }
if {![is_qemu_available]} {
puts "\nPlease setup your native sd or hard drive. Remove this fail stop";
puts "check when you have prepared your native environment.\n";
exit 0
}
if {[have_spec x86]} {
set qemu "qemu-system-i386"
if {[have_spec 64bit]} { set qemu "qemu-system-x86_64" }
set version [exec $qemu --version]
set version [regexp -inline {[0-9]+\.[0-9]+\.[0-9]+} $version]
if {$version >= 1.5} {
puts "Atapi driver on Qemu $version known to be not working.";
puts "For more details see Genode issue 827 on github";
puts stderr "Test requires 'fix for issue 827'"
exit 0;
}
}
#
# Build
#
set build_components {
core init
drivers/timer
test/libc_block
}
lappend_if [have_spec pci] build_components drivers/pci
lappend_if [have_spec acpi] build_components drivers/acpi
lappend_if $use_atapi_drv build_components drivers/atapi
lappend_if $use_sd_card_drv build_components drivers/sd_card
build $build_components
create_boot_directory
#
# Generate config
#
set config {
<config verbose="yes">
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="CAP"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
<service name="SIGNAL"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Timer"/> </provides>
</start>
<start name="test-libc_block">
<resource name="RAM" quantum="2M"/>
<config>
</config>
</start>
}
append_if [have_spec acpi] config {
<start name="acpi">
<resource name="RAM" quantum="8M"/>
<binary name="acpi_drv"/>
<provides>
<service name="PCI"/>
<service name="IRQ" />
</provides>
<route>
<service name="PCI"> <any-child /> </service>
<any-service> <parent/> <any-child /> </any-service>
</route>
</start>}
append_if [expr ![have_spec acpi] && [have_spec pci]] config {
<start name="pci_drv">
<resource name="RAM" quantum="5M"/>
<provides><service name="PCI"/></provides>
</start>}
append_if $use_atapi_drv config {
<start name="atapi_drv">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Block"/> </provides>
<config ata="yes" />
</start>
}
append_if $use_sd_card_drv config {
<start name="sd_card_drv">
<resource name="RAM" quantum="1M" />
<provides><service name="Block"/></provides>
</start>
}
append config {
</config>
}
install_config $config
#
# Boot modules
#
# generic modules
set boot_modules {
core init timer
ld.lib.so libc.lib.so libc_log.lib.so libc_block.lib.so
test-libc_block
}
lappend_if [have_spec pci] boot_modules pci_drv
lappend_if [have_spec acpi] boot_modules acpi_drv
lappend_if $use_atapi_drv boot_modules atapi_drv
lappend_if $use_sd_card_drv boot_modules sd_card_drv
build_boot_image $boot_modules
#
# Execute test case
#
set disk_image "bin/test.hda"
set cmd "dd if=/dev/zero of=$disk_image bs=1024 count=65536"
puts "creating disk image: $cmd"
catch { exec sh -c $cmd }
#
# Qemu
#
append qemu_args " -m 128 -nographic "
append_if $use_atapi_drv qemu_args " -hda $disk_image -boot order=d "
append_if $use_sd_card_drv qemu_args " -drive file=$disk_image,if=sd,cache=writeback "
run_genode_until {.*child exited with exit value 0.*} 60
exec rm -f $disk_image
puts "\nTest succeeded\n"
# vi: set ft=tcl :

View File

@ -0,0 +1,544 @@
/*
* \brief Libc Block_session plugin
* \author Josef Soentgen
* \date 2013-11-04
*/
/*
* 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.
*/
/* Genode includes */
#include <base/allocator_avl.h>
#include <base/printf.h>
#include <block_session/connection.h>
/* libc plugin includes */
#include <libc-plugin/plugin.h>
#include <libc-plugin/fd_alloc.h>
/* libc includes */
#include <sys/disk.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
using namespace Genode;
static bool const verbose = false;
/**
* There is a off_t typedef clash between sys/socket.h
* and base/stdint.h. We define the macro here to circumvent
* this issue.
*/
#undef DIOCGMEDIASIZE
#define DIOCGMEDIASIZE _IOR('d', 129, Genode::off_t)
/************
** Plugin **
************/
namespace {
struct Plugin_context : Libc::Plugin_context
{
private:
Genode::Allocator *_md_alloc;
Genode::Allocator_avl _tx_block_alloc;
char *_block_buffer;
Block::Connection *_block;
size_t _block_size;
size_t _block_count;
Block::Session::Operations _block_ops;
Block::Session::Tx::Source *_tx_source;
::off_t _cur_offset;
bool _readable;
bool _writeable;
int _flags;
public:
enum { BUFFER_BLOCK_NUMBER = 16 };
Plugin_context(Genode::Allocator *md_alloc, int flags)
:
_md_alloc(md_alloc),
_tx_block_alloc(_md_alloc),
_block_buffer(0),
_block(new (_md_alloc) Block::Connection(&_tx_block_alloc)),
_tx_source(_block->tx()),
_cur_offset(0),
_readable(false),
_writeable(false),
_flags(flags)
{
_block->info(&_block_count, &_block_size, &_block_ops);
_readable = _block_ops.supported(Block::Packet_descriptor::READ);
_writeable = _block_ops.supported(Block::Packet_descriptor::WRITE);
_block_buffer = reinterpret_cast<char*>(malloc(BUFFER_BLOCK_NUMBER * _block_size));
if (verbose) {
PDBG("number of blocks: %zu with block size: %zu (bytes)"
" , readable: %d writeable: %d",
_block_count, _block_size, _readable, _writeable);
}
}
~Plugin_context()
{
destroy(_md_alloc, _block);
free(_block_buffer);
}
/**
* Return read/write state
*/
bool readable() const { return _readable; }
bool writeable() const { return _writeable; }
/**
* Return file descriptor flags
*/
int flags() const { return _flags; }
/**
* Return seek offset
*/
::off_t seek_offset() const { return _cur_offset; }
/**
* Set seek offset
*/
void seek_offset(size_t seek_offset) { _cur_offset = seek_offset; }
/**
* Advance current seek offset position by 'incr' number of bytes
*/
void advance_seek_offset(size_t incr) { _cur_offset += incr; }
/**
* Set seek offset to maximum value
*/
void infinite_seek_offset() { _cur_offset = ~0; }
/**
* Return pointer to memory used as block puffer
*/
char *block_buffer() const { return _block_buffer; }
/**
* Return number of blocks
*/
size_t block_count() const { return _block_count; }
/**
* Return size of a block
*/
size_t block_size() const { return _block_size; }
/**
* Issue a block operation
*
* This implementation operates synchronized, it will wait for the
* acknowlegdement for the submitted packet.
*
* \param nr number of start block
* \param buf buffer to read from or write to
* \param sz size of buffer
* \param write if true issue write operation, otherwise issue read opertion
* \param bulk if true operate on as many block as fit into the block buffer
*
* return number of bytes transfered
*/
ssize_t block_io(size_t nr, void *buf, size_t sz, bool write, bool bulk = false)
{
/* sync offset */
_cur_offset = nr * _block_size;
Block::Packet_descriptor::Opcode op;
op = write ? Block::Packet_descriptor::WRITE : Block::Packet_descriptor::READ;
size_t packet_size = bulk ? sz : _block_size;
size_t packet_count = bulk ? (sz / _block_size) : 1;
/* sanity check */
if (packet_count > BUFFER_BLOCK_NUMBER) {
packet_size = BUFFER_BLOCK_NUMBER * _block_size;
packet_count = BUFFER_BLOCK_NUMBER;
}
if (verbose)
PDBG("%5s: block:%zu size:%zu packets:%zu",
write ? "write" : "read", nr, sz, packet_count);
Block::Packet_descriptor p(_tx_source->alloc_packet(packet_size), op,
nr, packet_count);
if (write)
Genode::memcpy(_tx_source->packet_content(p), buf, packet_size);
_tx_source->submit_packet(p);
p = _tx_source->get_acked_packet();
if (!p.succeeded()) {
PERR("Could not read block(s)");
_tx_source->release_packet(p);
errno = EIO;
return -1;
}
if (!write)
Genode::memcpy(buf, _tx_source->packet_content(p), packet_size);
_cur_offset += packet_size;
_tx_source->release_packet(p);
return packet_size;
}
};
static inline Plugin_context *context(Libc::File_descriptor *fd)
{
return static_cast<Plugin_context *>(fd->context);
}
class Plugin : public Libc::Plugin
{
private:
/**
* File name this plugin feels responsible for
*/
static char const *_dev_name() { return "/dev/blkdev"; }
bool _supports_path(const char *path)
{
return (Genode::strcmp(path, "/dev") == 0) ||
(Genode::strcmp(path, _dev_name()) == 0);
}
public:
/**
* Constructor
*/
Plugin() { }
bool supports_open(const char *pathname, int flags) {
return _supports_path(pathname); }
bool supports_stat(const char *path) {
return _supports_path(path); }
int close(Libc::File_descriptor *fd)
{
Plugin_context *ctx = context(fd);
destroy(env()->heap(), ctx);
Libc::file_descriptor_allocator()->free(fd);
return 0;
}
int fcntl(Libc::File_descriptor *fd, int cmd, long arg)
{
switch (cmd) {
case F_GETFL:
return context(fd)->flags();
case F_SETLK:
/**
* We do not support locking a block device but we keep
* the caller happy.
*/
return 0;
default:
PDBG("cmd: %d not implemented, return error.", cmd);
return -1;
}
return -1; /* never reached */
}
int fstat(Libc::File_descriptor *fd, struct stat *buf)
{
/* we pretent to be a block device */
if (buf) {
Genode::memset(buf, 0, sizeof (struct stat));
buf->st_mode = S_IFBLK;
}
return 0;
}
int fsync(Libc::File_descriptor *fd)
{
/**
* Currently all block I/O is done synchronized. To
* keep the caller happy we pretent that the fsync()
* call was successful.
*/
return 0;
}
int ioctl(Libc::File_descriptor *fd, int req, char *argp)
{
Plugin_context *ctx = context(fd);
switch (req) {
case DIOCGMEDIASIZE:
return ctx->block_count();
case DIOCGSECTORSIZE:
return ctx->block_size();
default:
PDBG("request: %d not supported", req);
return -1;
}
}
::off_t lseek(Libc::File_descriptor *fd, ::off_t offset, int whence)
{
Plugin_context *ctx = context(fd);
switch (whence) {
case SEEK_SET:
ctx->seek_offset(offset);
return offset;
case SEEK_CUR:
ctx->advance_seek_offset(offset);
return ctx->seek_offset();
case SEEK_END:
if (offset != 0) {
errno = EINVAL;
return -1;
}
ctx->infinite_seek_offset();
return (ctx->block_count() * ctx->block_size());
default:
errno = EINVAL;
return -1;
}
}
Libc::File_descriptor *open(const char *pathname, int flags)
{
try {
if (verbose)
PDBG("open block device '%s'", pathname);
Plugin_context *context = new (Genode::env()->heap())
Plugin_context(env()->heap(), flags);
return Libc::file_descriptor_allocator()->alloc(this, context);
} catch (...) {
PERR("could not create plugin context");
}
errno = ENOENT;
return 0;
}
ssize_t read(Libc::File_descriptor *fd, void *buf, ::size_t count)
{
Plugin_context *ctx = context(fd);
if (!ctx->readable()) {
PERR("block device is not readable");
errno = EINVAL;
return -1;
}
size_t blk_size = ctx->block_size();
char *blk_buffer = ctx->block_buffer();
char *_buf = (char*) buf;
ssize_t read = 0;
while (count > 0) {
size_t displ = 0;
size_t length = 0;
ssize_t nbytes = 0;
size_t blk_nr = ctx->seek_offset() / blk_size;
displ = ctx->seek_offset() % blk_size;
if ((displ + count) > blk_size)
length = (blk_size - displ);
else
length = count;
/**
* We take a shortcut and read the blocks all at once if the
* offset is aligned on a block boundary and we the count is a
* multiple of the block size, e.g. 4K reads will be read at
* once.
*
* XXX this is quite hackish because we have to omit partial
* blocks at the end.
*/
if (displ == 0 && (count % blk_size) >= 0 && !(count < blk_size)) {
size_t bytes_left = count - (count % blk_size);
nbytes = ctx->block_io(blk_nr, _buf + read, bytes_left, false, true);
if (nbytes == -1) {
PERR("error while reading block:%zu from block device",
blk_nr);
return -1;
}
read += nbytes;
count -= nbytes;
continue;
}
if (displ > 0)
PWRN("offset:%lld is not aligned to block_size:%zu"
" displacement:%zu", ctx->seek_offset(), blk_size, displ);
nbytes = ctx->block_io(blk_nr, blk_buffer, blk_size, false);
if (nbytes != blk_size) {
PERR("error while reading block:%zu from block device",
blk_nr);
return -1;
}
Genode::memcpy(_buf + read, blk_buffer + displ, length);
read += length;
count -= length;
}
return read;
}
int stat(const char *path, struct stat *buf)
{
/* we pretent to be a block device */
if (buf) {
Genode::memset(buf, 0, sizeof (struct stat));
if (Genode::strcmp(path, "/dev") == 0)
buf->st_mode = S_IFDIR;
else if (Genode::strcmp(path, _dev_name()) == 0)
buf->st_mode = S_IFBLK;
else {
errno = ENOENT;
return -1;
}
}
return 0;
}
ssize_t write(Libc::File_descriptor *fd, const void *buf, ::size_t count)
{
Plugin_context *ctx = context(fd);
if (!ctx->writeable()) {
PERR("block device is not writeable");
errno = EINVAL;
return -1;
}
size_t blk_size = ctx->block_size();
char *blk_buffer = ctx->block_buffer();
const char *_buf = (const char*)buf;
ssize_t written = 0;
while (count > 0) {
size_t displ = 0;
size_t length = 0;
size_t nbytes = 0;
size_t blk_nr = ctx->seek_offset() / blk_size;
displ = ctx->seek_offset() % blk_size;
if ((displ + count) > blk_size)
length = (blk_size - displ);
else
length = count;
/**
* We take a shortcut and write as much as possible without
* using the block buffer if the offset is aligned on a block
* boundary and the count is a multiple of the block size,
* e.g. 4K writes will be written at once.
*
* XXX this is quite hackish because we have to omit partial
* blocks at the end.
*/
if (displ == 0 && (count % blk_size) >= 0 && !(count < blk_size)) {
size_t bytes_left = count - (count % blk_size);
nbytes = ctx->block_io(blk_nr, (void*)(_buf + written),
bytes_left, true, true);
if (nbytes == -1) {
PERR("error while reading block:%zu from block device",
blk_nr);
return -1;
}
written += nbytes;
count -= nbytes;
continue;
}
/**
* The offset is not aligned on a block boundary. Therefore
* we need to read the block to the block buffer first and
* put the buffer content at the right offset before we can
* write the whole block back. In addition if length is less
* than block size, we also have to read the block first.
*/
if (displ > 0 || length < blk_size) {
PWRN("offset:%lld block_size:%zd displacement:%zd length:%zu",
ctx->seek_offset(), blk_size, displ, length);
ctx->block_io(blk_nr, blk_buffer, blk_size, false);
/* rewind seek offset to account for the block read */
ctx->seek_offset(ctx->seek_offset()-blk_size);
}
Genode::memcpy(blk_buffer + displ, _buf + written, length);
nbytes = ctx->block_io(blk_nr, blk_buffer, blk_size, true);
if (nbytes != blk_size) {
PERR("error while reading block:%zu from Block_device",
blk_nr);
return -1;
}
written += length;
count -= length;
}
return written;
}
};
} /* unnamed namespace */
void __attribute__((constructor)) init_libc_block(void)
{
PDBG("using the libc_block plugin");
static Plugin plugin;
}

View File

@ -0,0 +1,78 @@
/*
* \brief libc_block test
* \author Josef Soentgen
* \date 2013-11-04
*/
/*
* 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.
*/
/* Genode includes */
#include <os/config.h>
/* libc includes */
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
static char buf[16384];
static char str[] = "deadbeef";
int main(int argc, char *argv[])
{
int fd;
ssize_t n;
off_t offset;
printf("--- start test ---\n");
fd = open("/dev/blkdev", O_RDWR);
if (fd == -1) {
perror("open");
return 1;
}
offset = lseek(fd, 8193, SEEK_SET);
printf("offset: %zd\n", offset);
n = write(fd, str, sizeof (str));
offset = lseek(fd, 8193, SEEK_SET);
printf("offset: %zd\n", offset);
n = read(fd, buf, sizeof (str));
printf("bytes: %zd\n", n);
for (size_t i = 0; i < sizeof (str); i++)
printf("%c ", buf[i]);
printf("\n");
offset = lseek(fd, 16384, SEEK_SET);
n = write(fd, buf, sizeof (buf));
if (n != sizeof (buf))
printf("error write mismatch: %zd != %zu\n", n, sizeof (buf));
offset = lseek(fd, 4060, SEEK_SET);
n = write(fd, buf, sizeof (buf) / 2);
if (n != sizeof (buf)/2)
printf("error write mismatch: %zd != %zu\n", n, sizeof (buf)/2);
offset = lseek(fd, 2342, SEEK_SET);
n = read(fd, buf, sizeof (buf));
if (n != sizeof (buf))
printf("error read mismatch: %zd != %zu\n", n, sizeof (buf));
close(fd);
printf("--- test finished ---\n");
return 0;
}

View File

@ -0,0 +1,3 @@
TARGET = test-libc_block
LIBS = libc libc_log libc_block
SRC_CC = main.cc