mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-07 19:34:56 +00:00
parent
082d1780cf
commit
927d71ad59
1
repos/os/recipes/pkg/test-part_block_ahdi/README
Normal file
1
repos/os/recipes/pkg/test-part_block_ahdi/README
Normal file
@ -0,0 +1 @@
|
||||
Test part_block server with Atari AHDI disk.
|
7
repos/os/recipes/pkg/test-part_block_ahdi/archives
Normal file
7
repos/os/recipes/pkg/test-part_block_ahdi/archives
Normal file
@ -0,0 +1,7 @@
|
||||
_/src/init
|
||||
_/src/report_rom
|
||||
_/src/part_block
|
||||
_/src/vfs
|
||||
_/src/vfs_block
|
||||
_/src/block_tester
|
||||
_/raw/test-part_block_ahdi
|
1
repos/os/recipes/pkg/test-part_block_ahdi/hash
Normal file
1
repos/os/recipes/pkg/test-part_block_ahdi/hash
Normal file
@ -0,0 +1 @@
|
||||
2023-03-17-d 6690319be80b9b24b647c36303dde6ece6048cb8
|
81
repos/os/recipes/pkg/test-part_block_ahdi/runtime
Normal file
81
repos/os/recipes/pkg/test-part_block_ahdi/runtime
Normal file
@ -0,0 +1,81 @@
|
||||
<runtime ram="72M" caps="1000" binary="init">
|
||||
|
||||
<requires> <timer/> </requires>
|
||||
|
||||
<events>
|
||||
<timeout meaning="failed" sec="70" />
|
||||
<log meaning="succeeded">
|
||||
child "test-part*" exited with exit value 0
|
||||
</log>
|
||||
<log meaning="failed">Error: </log>
|
||||
</events>
|
||||
|
||||
<content>
|
||||
<rom label="ld.lib.so"/>
|
||||
<rom label="vfs_block"/>
|
||||
<rom label="vfs.lib.so"/>
|
||||
<rom label="part_block"/>
|
||||
<rom label="block_tester"/>
|
||||
<rom label="report_rom"/>
|
||||
<rom label="ata.ahdi.raw"/>
|
||||
</content>
|
||||
|
||||
<config prio_levels="1" verbose="yes">
|
||||
<parent-provides>
|
||||
<service name="ROM"/>
|
||||
<service name="IRQ"/>
|
||||
<service name="IO_MEM"/>
|
||||
<service name="IO_PORT"/>
|
||||
<service name="PD"/>
|
||||
<service name="RM"/>
|
||||
<service name="CPU"/>
|
||||
<service name="LOG"/>
|
||||
<service name="Timer"/>
|
||||
</parent-provides>
|
||||
<default-route>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</default-route>
|
||||
<default caps="100"/>
|
||||
<start name="vfs_block">
|
||||
<resource name="RAM" quantum="32M"/>
|
||||
<provides><service name="Block"/></provides>
|
||||
<config>
|
||||
<vfs>
|
||||
<rom name="ata.ahdi.raw"/>
|
||||
</vfs>
|
||||
<default-policy file="/ata.ahdi.raw" block_size="512"/>
|
||||
</config>
|
||||
</start>
|
||||
<start name="part_block">
|
||||
<resource name="RAM" quantum="10M" />
|
||||
<provides><service name="Block" /></provides>
|
||||
<route>
|
||||
<any-service><child name="vfs_block"/> <parent/><any-child/></any-service>
|
||||
</route>
|
||||
<config>
|
||||
<report partitions="yes"/>
|
||||
<policy label_prefix="test-part1" partition="1"/>
|
||||
</config>
|
||||
</start>
|
||||
<start name="report_rom">
|
||||
<provides>
|
||||
<service name="Report"/>
|
||||
<service name="ROM"/>
|
||||
</provides>
|
||||
<resource name="RAM" quantum="5M" />
|
||||
<config verbose="yes"/>
|
||||
</start>
|
||||
<start name="test-part1">
|
||||
<binary name="block_tester"/>
|
||||
<resource name="RAM" quantum="5M" />
|
||||
<config verbose="no" log="yes" stop_on_error="no">
|
||||
<tests>
|
||||
<sequential length="256K" size="1K" io_buffer="128K" batch="4"/>
|
||||
</tests>
|
||||
</config>
|
||||
<route>
|
||||
<any-service> <child name="part_block" /> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
</config>
|
||||
</runtime>
|
1
repos/os/recipes/pkg/test-part_block_disk/README
Normal file
1
repos/os/recipes/pkg/test-part_block_disk/README
Normal file
@ -0,0 +1 @@
|
||||
Test part_block server with GPT disk.
|
7
repos/os/recipes/pkg/test-part_block_disk/archives
Normal file
7
repos/os/recipes/pkg/test-part_block_disk/archives
Normal file
@ -0,0 +1,7 @@
|
||||
_/src/init
|
||||
_/src/report_rom
|
||||
_/src/part_block
|
||||
_/src/vfs
|
||||
_/src/vfs_block
|
||||
_/src/block_tester
|
||||
_/raw/test-part_block_gpt
|
1
repos/os/recipes/pkg/test-part_block_disk/hash
Normal file
1
repos/os/recipes/pkg/test-part_block_disk/hash
Normal file
@ -0,0 +1 @@
|
||||
2023-03-17 0d6f7581b18066825dd6e9db06faf2c6edb31331
|
81
repos/os/recipes/pkg/test-part_block_disk/runtime
Normal file
81
repos/os/recipes/pkg/test-part_block_disk/runtime
Normal file
@ -0,0 +1,81 @@
|
||||
<runtime ram="72M" caps="1000" binary="init">
|
||||
|
||||
<requires> <timer/> </requires>
|
||||
|
||||
<events>
|
||||
<timeout meaning="failed" sec="70" />
|
||||
<log meaning="succeeded">
|
||||
child "test-part*" exited with exit value 0
|
||||
</log>
|
||||
<log meaning="failed">Error: </log>
|
||||
</events>
|
||||
|
||||
<content>
|
||||
<rom label="ld.lib.so"/>
|
||||
<rom label="vfs_block"/>
|
||||
<rom label="vfs.lib.so"/>
|
||||
<rom label="part_block"/>
|
||||
<rom label="block_tester"/>
|
||||
<rom label="report_rom"/>
|
||||
<rom label="ata.gpt.raw"/>
|
||||
</content>
|
||||
|
||||
<config prio_levels="1" verbose="yes">
|
||||
<parent-provides>
|
||||
<service name="ROM"/>
|
||||
<service name="IRQ"/>
|
||||
<service name="IO_MEM"/>
|
||||
<service name="IO_PORT"/>
|
||||
<service name="PD"/>
|
||||
<service name="RM"/>
|
||||
<service name="CPU"/>
|
||||
<service name="LOG"/>
|
||||
<service name="Timer"/>
|
||||
</parent-provides>
|
||||
<default-route>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</default-route>
|
||||
<default caps="100"/>
|
||||
<start name="vfs_block">
|
||||
<resource name="RAM" quantum="32M"/>
|
||||
<provides><service name="Block"/></provides>
|
||||
<config>
|
||||
<vfs>
|
||||
<rom name="ata.gpt.raw"/>
|
||||
</vfs>
|
||||
<default-policy file="/ata.gpt.raw" block_size="512"/>
|
||||
</config>
|
||||
</start>
|
||||
<start name="part_block">
|
||||
<resource name="RAM" quantum="10M" />
|
||||
<provides><service name="Block" /></provides>
|
||||
<route>
|
||||
<any-service><child name="vfs_block"/> <parent/><any-child/></any-service>
|
||||
</route>
|
||||
<config ignore_gpt="true">
|
||||
<report partitions="yes"/>
|
||||
<policy label_prefix="test-part0" partition="0"/>
|
||||
</config>
|
||||
</start>
|
||||
<start name="report_rom">
|
||||
<provides>
|
||||
<service name="Report"/>
|
||||
<service name="ROM"/>
|
||||
</provides>
|
||||
<resource name="RAM" quantum="5M" />
|
||||
<config verbose="yes"/>
|
||||
</start>
|
||||
<start name="test-part0">
|
||||
<binary name="block_tester"/>
|
||||
<resource name="RAM" quantum="5M" />
|
||||
<config verbose="no" log="yes" stop_on_error="no">
|
||||
<tests>
|
||||
<sequential length="1M" size="1K" io_buffer="128K" batch="4"/>
|
||||
</tests>
|
||||
</config>
|
||||
<route>
|
||||
<any-service> <child name="part_block" /> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
</config>
|
||||
</runtime>
|
@ -53,7 +53,7 @@
|
||||
<route>
|
||||
<any-service><child name="vfs_block"/> <parent/><any-child/></any-service>
|
||||
</route>
|
||||
<config use_gpt="yes">
|
||||
<config>
|
||||
<report partitions="yes"/>
|
||||
<policy label_prefix="test-part1" partition="2"/>
|
||||
<policy label_prefix="test-part2" partition="1"/>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<requires> <timer/> </requires>
|
||||
|
||||
<events>
|
||||
<timeout meaning="failed" sec="60" />
|
||||
<timeout meaning="failed" sec="70" />
|
||||
<log meaning="succeeded">
|
||||
child "test-part*" exited with exit value 0*
|
||||
child "test-part*" exited with exit value 0*
|
||||
|
BIN
repos/os/recipes/raw/test-part_block_ahdi/ata.ahdi.raw.tar.gz
Normal file
BIN
repos/os/recipes/raw/test-part_block_ahdi/ata.ahdi.raw.tar.gz
Normal file
Binary file not shown.
4
repos/os/recipes/raw/test-part_block_ahdi/content.mk
Normal file
4
repos/os/recipes/raw/test-part_block_ahdi/content.mk
Normal file
@ -0,0 +1,4 @@
|
||||
content: ata.ahdi.raw
|
||||
|
||||
ata.ahdi.raw:
|
||||
tar -xzvf $(REP_DIR)/recipes/raw/test-part_block_ahdi/$@.tar.gz 1>/dev/null
|
1
repos/os/recipes/raw/test-part_block_ahdi/hash
Normal file
1
repos/os/recipes/raw/test-part_block_ahdi/hash
Normal file
@ -0,0 +1 @@
|
||||
2023-03-17 9c4b6f3d932b62b0c8d49ebd670aa2cada723988
|
Binary file not shown.
@ -50,6 +50,10 @@ looks like follows (for MBR resp. GPT).
|
||||
Clients have read-only access to partitions unless overriden by a 'writeable'
|
||||
policy attribute.
|
||||
|
||||
part_block requests a block session to access the storage device with a
|
||||
4 MiB I/O buffer per default. The buffer size can be tweaked with the
|
||||
'io_buffer' config attribute.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
@ -71,7 +75,7 @@ Configuration snippet with two clients and an (hypothetical) IDE driver:
|
||||
!
|
||||
! <!-- allow program 'test-part1' to access logical partition '6', while program
|
||||
! 'test-part2' receives access to primary partition 1, the buffer between
|
||||
! the 'ata_driver' and 'part_block' is 1 MeB ('io_buffer') -->
|
||||
! the 'ata_driver' and 'part_block' is 1 MiB ('io_buffer') -->
|
||||
! <config io_buffer="1M">
|
||||
! <report partitions="yes"/>
|
||||
! <policy label_prefix="test-part1" partition="6" writeable="yes"/>
|
||||
|
@ -1,6 +1,7 @@
|
||||
/*
|
||||
* \brief Atari ST partition scheme (AHDI)
|
||||
* \author Norman Feske
|
||||
* \author Christian Helmuth
|
||||
* \date 2019-08-09
|
||||
*/
|
||||
|
||||
@ -16,84 +17,184 @@
|
||||
|
||||
#include "partition_table.h"
|
||||
|
||||
namespace Block {
|
||||
struct Ahdi_partition;
|
||||
struct Ahdi;
|
||||
}
|
||||
|
||||
struct Ahdi
|
||||
|
||||
struct Block::Ahdi_partition : Partition
|
||||
{
|
||||
typedef Block::Partition_table::Sector Sector;
|
||||
using Type = String<4>;
|
||||
Type type;
|
||||
|
||||
typedef Genode::uint32_t uint32_t;
|
||||
typedef Genode::uint8_t uint8_t;
|
||||
Ahdi_partition(block_number_t lba,
|
||||
block_number_t sectors,
|
||||
Fs::Type fs_type,
|
||||
Type const &type)
|
||||
:
|
||||
Partition(lba, sectors, fs_type),
|
||||
type(type)
|
||||
{ }
|
||||
};
|
||||
|
||||
/**
|
||||
* 32-bit big-endian value
|
||||
*/
|
||||
struct Be32
|
||||
{
|
||||
uint8_t b0, b1, b2, b3;
|
||||
|
||||
uint32_t value() const {
|
||||
return ((uint32_t)b0 << 24) | ((uint32_t)b1 << 16)
|
||||
| ((uint32_t)b2 << 8) | ((uint32_t)b3 << 0); }
|
||||
class Block::Ahdi : public Partition_table
|
||||
{
|
||||
private:
|
||||
|
||||
} __attribute__((packed));
|
||||
|
||||
struct Partition_record
|
||||
{
|
||||
uint8_t _flags;
|
||||
uint8_t _id0, _id1, _id2;
|
||||
Be32 start; /* first block */
|
||||
Be32 length; /* in blocks */
|
||||
|
||||
typedef Genode::String<4> Id;
|
||||
|
||||
Id id() const
|
||||
/**
|
||||
* 32-bit big-endian value
|
||||
*/
|
||||
struct Be32
|
||||
{
|
||||
using Genode::Char;
|
||||
return Id(Char(_id0), Char(_id1), Char(_id2));
|
||||
uint8_t b0, b1, b2, b3;
|
||||
|
||||
uint32_t value() const {
|
||||
return ((uint32_t)b0 << 24) | ((uint32_t)b1 << 16)
|
||||
| ((uint32_t)b2 << 8) | ((uint32_t)b3 << 0); }
|
||||
|
||||
} __attribute__((packed));
|
||||
|
||||
struct Partition_record
|
||||
{
|
||||
uint8_t _flags;
|
||||
uint8_t _id0, _id1, _id2;
|
||||
Be32 start; /* first block */
|
||||
Be32 length; /* in blocks */
|
||||
|
||||
Ahdi_partition::Type id() const
|
||||
{
|
||||
return { Char(_id0), Char(_id1), Char(_id2) };
|
||||
}
|
||||
|
||||
bool bootable() const { return _flags & 1; }
|
||||
|
||||
bool valid() const
|
||||
{
|
||||
return start.value() > 0
|
||||
&& (id() == "BGM" || id() == "GEM" || id() == "LNX");
|
||||
}
|
||||
|
||||
} __attribute__((packed));
|
||||
|
||||
enum { MAX_PARTITIONS = 4 };
|
||||
|
||||
struct Root_sector
|
||||
{
|
||||
uint8_t boot_code[0x156];
|
||||
Partition_record icd_partitions[8];
|
||||
uint8_t unused[0xc];
|
||||
Be32 disk_blocks;
|
||||
Partition_record partitions[MAX_PARTITIONS];
|
||||
|
||||
} __attribute__((packed));
|
||||
|
||||
Constructible<Ahdi_partition> _part_list[MAX_PARTITIONS];
|
||||
|
||||
bool _valid(Sync_read const §or)
|
||||
{
|
||||
bool any_partition_valid = false;
|
||||
|
||||
Root_sector const root = *sector.addr<Root_sector const *>();
|
||||
for (unsigned i = 0; i < MAX_PARTITIONS; i++)
|
||||
if (root.partitions[i].valid())
|
||||
any_partition_valid = true;
|
||||
|
||||
return any_partition_valid;
|
||||
}
|
||||
|
||||
bool bootable() const { return _flags & 1; }
|
||||
template <typename FUNC>
|
||||
void _parse_ahdi(Sync_read const §or, FUNC const &fn)
|
||||
{
|
||||
Root_sector &root = *sector.addr<Root_sector *>();
|
||||
|
||||
bool valid() const { return id() == "BGM" && start.value() > 0; }
|
||||
for (unsigned i = 0; i < MAX_PARTITIONS; i++) {
|
||||
Partition_record const &part = root.partitions[i];
|
||||
if (!part.valid())
|
||||
continue;
|
||||
|
||||
} __attribute__((packed));
|
||||
|
||||
enum { MAX_PARTITIONS = 4 };
|
||||
|
||||
struct Root_sector
|
||||
{
|
||||
uint8_t boot_code[0x156];
|
||||
Partition_record icd_partitions[8];
|
||||
uint8_t unused[0xc];
|
||||
Be32 disk_blocks;
|
||||
Partition_record partitions[MAX_PARTITIONS];
|
||||
|
||||
} __attribute__((packed));
|
||||
|
||||
static bool valid(Sector const §or)
|
||||
{
|
||||
bool any_partition_valid = false;
|
||||
|
||||
Root_sector const root = *sector.addr<Root_sector const *>();
|
||||
for (unsigned i = 0; i < MAX_PARTITIONS; i++)
|
||||
if (root.partitions[i].valid())
|
||||
any_partition_valid = true;
|
||||
|
||||
return any_partition_valid;
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
static void for_each_partition(Sector const §or, FN const &fn)
|
||||
{
|
||||
Root_sector &root = *sector.addr<Root_sector *>();
|
||||
|
||||
for (unsigned i = 0; i < MAX_PARTITIONS; i++) {
|
||||
Partition_record const &part = root.partitions[i];
|
||||
if (part.valid())
|
||||
fn(i + 1, Block::Partition(part.start.value(), part.length.value(),
|
||||
Fs::Type(), 0));
|
||||
fn(i, part);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void _for_each_valid_partition(FN const &fn) const
|
||||
{
|
||||
for (unsigned i = 0; i < MAX_PARTITIONS; i++)
|
||||
if (_part_list[i].constructed())
|
||||
fn(i);
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
using Partition_table::Partition_table;
|
||||
|
||||
bool parse()
|
||||
{
|
||||
Sync_read s(_handler, _alloc, 0, 1);
|
||||
|
||||
if (!s.success() || !_valid(s))
|
||||
return false;
|
||||
|
||||
_parse_ahdi(s, [&] (unsigned i, Partition_record const &r) {
|
||||
block_number_t lba = r.start.value();
|
||||
block_number_t length = r.length.value();
|
||||
|
||||
Ahdi_partition::Type type = r.id();
|
||||
|
||||
_part_list[i].construct(lba, length, _fs_type(lba), type);
|
||||
|
||||
log("AHDI Partition ", i + 1, ": LBA ", lba, " (", length,
|
||||
" blocks) type: '", type, "'");
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool partition_valid(long num) const override
|
||||
{
|
||||
/* 1-based partition number to 0-based array index */
|
||||
num -= 1;
|
||||
|
||||
if (num < 0 || num >= MAX_PARTITIONS)
|
||||
return false;
|
||||
|
||||
return _part_list[num].constructed();
|
||||
}
|
||||
|
||||
block_number_t partition_lba(long num) const override
|
||||
{
|
||||
return partition_valid(num) ? _part_list[num - 1]->lba : 0;
|
||||
}
|
||||
|
||||
block_number_t partition_sectors(long num) const override
|
||||
{
|
||||
return partition_valid(num) ? _part_list[num - 1]->sectors : 0;
|
||||
}
|
||||
|
||||
void generate_report(Xml_generator &xml) const override
|
||||
{
|
||||
auto gen_partition_attr = [&] (Xml_generator &xml, unsigned i)
|
||||
{
|
||||
Ahdi_partition const &part = *_part_list[i];
|
||||
|
||||
xml.attribute("number", i + 1);
|
||||
xml.attribute("start", part.lba);
|
||||
xml.attribute("length", part.sectors);
|
||||
xml.attribute("block_size", _info.block_size);
|
||||
xml.attribute("type", part.type);
|
||||
|
||||
if (part.fs_type.valid())
|
||||
xml.attribute("file_system", part.fs_type);
|
||||
};
|
||||
|
||||
xml.attribute("type", "ahdi");
|
||||
|
||||
_for_each_valid_partition([&] (unsigned i) {
|
||||
xml.node("partition", [&] {
|
||||
gen_partition_attr(xml, i); }); });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _PART_BLOCK__AHDI_H_ */
|
||||
|
134
repos/os/src/server/part_block/block.h
Normal file
134
repos/os/src/server/part_block/block.h
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* \brief Block I/O utilities
|
||||
* \author Sebastian Sumpf
|
||||
* \author Stefan Kalkowski
|
||||
* \author Christian Helmuth
|
||||
* \date 2023-03-16
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 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.
|
||||
*/
|
||||
|
||||
#ifndef _PART_BLOCK__BLOCK_H_
|
||||
#define _PART_BLOCK__BLOCK_H_
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace Block {
|
||||
struct Job;
|
||||
struct Sync_read;
|
||||
}
|
||||
|
||||
|
||||
struct Block::Job : public Block_connection::Job
|
||||
{
|
||||
Registry<Job>::Element registry_element;
|
||||
|
||||
addr_t const index; /* job index */
|
||||
long const number; /* parition number */
|
||||
Request request;
|
||||
addr_t const addr; /* target payload address */
|
||||
|
||||
Job(Block_connection &connection,
|
||||
Operation operation,
|
||||
Registry<Job> ®istry,
|
||||
addr_t const index,
|
||||
addr_t const number,
|
||||
Request request,
|
||||
addr_t addr)
|
||||
:
|
||||
Block_connection::Job(connection, operation),
|
||||
registry_element(registry, *this),
|
||||
index(index), number(number), request(request), addr(addr)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Synchronous block I/O (for reading table info)
|
||||
*/
|
||||
class Block::Sync_read : Noncopyable
|
||||
{
|
||||
public:
|
||||
|
||||
struct Handler : Interface
|
||||
{
|
||||
virtual Block_connection & connection() = 0;
|
||||
virtual void block_for_io() = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
Handler &_handler;
|
||||
Allocator &_alloc;
|
||||
size_t _size { 0 };
|
||||
void *_buffer { nullptr };
|
||||
bool _success { false };
|
||||
|
||||
/*
|
||||
* Noncopyable
|
||||
*/
|
||||
Sync_read(Sync_read const &) = delete;
|
||||
Sync_read & operator = (Sync_read const &) = delete;
|
||||
|
||||
public:
|
||||
|
||||
Sync_read(Handler &handler,
|
||||
Allocator &alloc,
|
||||
block_number_t block_number,
|
||||
block_count_t count)
|
||||
: _handler(handler), _alloc(alloc)
|
||||
{
|
||||
Operation const operation {
|
||||
.type = Operation::Type::READ,
|
||||
.block_number = block_number,
|
||||
.count = count
|
||||
};
|
||||
|
||||
Block_connection::Job job { _handler.connection(), operation };
|
||||
|
||||
_handler.connection().update_jobs(*this);
|
||||
while (!job.completed()) {
|
||||
_handler.block_for_io();
|
||||
_handler.connection().update_jobs(*this);
|
||||
}
|
||||
}
|
||||
|
||||
~Sync_read()
|
||||
{
|
||||
_alloc.free(_buffer, _size);
|
||||
}
|
||||
|
||||
bool success() const { return _success; }
|
||||
|
||||
void consume_read_result(Block_connection::Job &, off_t offset,
|
||||
char const *src, size_t length)
|
||||
{
|
||||
_buffer = _alloc.alloc(length);
|
||||
memcpy((char *)_buffer + offset, src, length);
|
||||
_size += length;
|
||||
}
|
||||
|
||||
void produce_write_content(Block_connection::Job &, off_t,
|
||||
char *, size_t)
|
||||
{
|
||||
}
|
||||
|
||||
void completed(Block_connection::Job &, bool success)
|
||||
{
|
||||
if (!success)
|
||||
error("IO error during partition parsing");
|
||||
|
||||
_success = success;
|
||||
}
|
||||
|
||||
template <typename T> T addr() const {
|
||||
return reinterpret_cast<T>(_buffer); }
|
||||
};
|
||||
|
||||
|
||||
#endif /* _PART_BLOCK__BLOCK_H_ */
|
70
repos/os/src/server/part_block/disk.h
Normal file
70
repos/os/src/server/part_block/disk.h
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* \brief Entire disk a partition 0
|
||||
* \author Christian Helmuth
|
||||
* \date 2023-03-17
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 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.
|
||||
*/
|
||||
|
||||
#ifndef _PART_BLOCK__DISK_H_
|
||||
#define _PART_BLOCK__DISK_H_
|
||||
|
||||
#include "partition_table.h"
|
||||
|
||||
namespace Block { struct Disk; }
|
||||
|
||||
|
||||
class Block::Disk : public Partition_table
|
||||
{
|
||||
private:
|
||||
|
||||
Partition _part { 0, _info.block_count, _fs_type(0) };
|
||||
|
||||
public:
|
||||
|
||||
Disk(Sync_read::Handler &handler,
|
||||
Allocator &alloc,
|
||||
Session::Info info)
|
||||
:
|
||||
Partition_table(handler, alloc, info)
|
||||
{
|
||||
log("DISK Partition 0: LBA ", _part.lba, " (", _part.sectors, " blocks)");
|
||||
}
|
||||
|
||||
bool partition_valid(long num) const override
|
||||
{
|
||||
return num == 0;
|
||||
}
|
||||
|
||||
block_number_t partition_lba(long num) const override
|
||||
{
|
||||
return partition_valid(num) ? _part.lba : 0;
|
||||
}
|
||||
|
||||
block_number_t partition_sectors(long num) const override
|
||||
{
|
||||
return partition_valid(num) ? _part.sectors : 0;
|
||||
}
|
||||
|
||||
void generate_report(Xml_generator &xml) const override
|
||||
{
|
||||
xml.attribute("type", "disk");
|
||||
|
||||
xml.node("partition", [&] {
|
||||
xml.attribute("number", 0);
|
||||
xml.attribute("start", _part.lba);
|
||||
xml.attribute("length", _part.sectors);
|
||||
xml.attribute("block_size", _info.block_size);
|
||||
|
||||
if (_part.fs_type.valid())
|
||||
xml.attribute("file_system", _part.fs_type);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _PART_BLOCK__DISK_H_ */
|
@ -1,6 +1,7 @@
|
||||
/*
|
||||
* \brief Poor man's partition probe for known file system
|
||||
* \author Josef Soentgen
|
||||
* \author Christian Helmuth
|
||||
* \date 2018-05-03
|
||||
*/
|
||||
|
||||
@ -24,64 +25,68 @@ namespace Fs {
|
||||
|
||||
using Type = Genode::String<32>;
|
||||
|
||||
Type _probe_extfs(uint8_t *p, size_t len);
|
||||
Type _probe_fatfs(uint8_t *p, size_t len);
|
||||
Type probe(uint8_t *buffer, size_t len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Probe for Ext2/3/4
|
||||
*/
|
||||
Type _probe_extfs(uint8_t *p, size_t len)
|
||||
{
|
||||
if (len < 4096) { return Type(); }
|
||||
|
||||
/* super block starts a byte offset 1024 */
|
||||
p += 0x400;
|
||||
/**
|
||||
* Probe for Ext2/3/4
|
||||
*/
|
||||
Fs::Type Fs::_probe_extfs(uint8_t *p, size_t len)
|
||||
{
|
||||
if (len < 4096) { return Type(); }
|
||||
|
||||
bool const found_ext_sig = p[0x38] == 0x53 && p[0x39] == 0xEF;
|
||||
/* super block starts a byte offset 1024 */
|
||||
p += 0x400;
|
||||
|
||||
enum {
|
||||
COMPAT_HAS_JOURNAL = 0x004u,
|
||||
INCOMPAT_EXTENTS = 0x040u,
|
||||
RO_COMPAT_METADATA_CSUM = 0x400u,
|
||||
};
|
||||
bool const found_ext_sig = p[0x38] == 0x53 && p[0x39] == 0xEF;
|
||||
|
||||
uint32_t const compat = *(uint32_t*)(p + 0x5C);
|
||||
uint32_t const incompat = *(uint32_t*)(p + 0x60);
|
||||
uint32_t const ro_compat = *(uint32_t*)(p + 0x64);
|
||||
enum {
|
||||
COMPAT_HAS_JOURNAL = 0x004u,
|
||||
INCOMPAT_EXTENTS = 0x040u,
|
||||
RO_COMPAT_METADATA_CSUM = 0x400u,
|
||||
};
|
||||
|
||||
/* the feature flags should denote a given Ext version */
|
||||
uint32_t const compat = *(uint32_t*)(p + 0x5C);
|
||||
uint32_t const incompat = *(uint32_t*)(p + 0x60);
|
||||
uint32_t const ro_compat = *(uint32_t*)(p + 0x64);
|
||||
|
||||
bool const ext3 = compat & COMPAT_HAS_JOURNAL;
|
||||
bool const ext4 = ext3 && ((incompat & INCOMPAT_EXTENTS)
|
||||
&& (ro_compat & RO_COMPAT_METADATA_CSUM));
|
||||
/* the feature flags should denote a given Ext version */
|
||||
|
||||
if (found_ext_sig && ext4) { return Type("Ext4"); }
|
||||
else if (found_ext_sig && ext3) { return Type("Ext3"); }
|
||||
else if (found_ext_sig) { return Type("Ext2"); }
|
||||
bool const ext3 = compat & COMPAT_HAS_JOURNAL;
|
||||
bool const ext4 = ext3 && ((incompat & INCOMPAT_EXTENTS)
|
||||
&& (ro_compat & RO_COMPAT_METADATA_CSUM));
|
||||
|
||||
return Type();
|
||||
}
|
||||
if (found_ext_sig && ext4) { return Type("Ext4"); }
|
||||
else if (found_ext_sig && ext3) { return Type("Ext3"); }
|
||||
else if (found_ext_sig) { return Type("Ext2"); }
|
||||
|
||||
/**
|
||||
* Probe for FAT16/32
|
||||
*/
|
||||
Type _probe_fatfs(uint8_t *p, size_t len)
|
||||
{
|
||||
if (len < 512) { return Type(); }
|
||||
return Type();
|
||||
}
|
||||
|
||||
/* at least the checks ring true when mkfs.vfat is used... */
|
||||
bool const found_boot_sig = p[510] == 0x55 && p[511] == 0xAA;
|
||||
|
||||
bool const fat16 = p[38] == 0x28 || p[38] == 0x29;
|
||||
bool const fat32 = (p[66] == 0x28 || p[66] == 0x29)
|
||||
&& (p[82] == 'F' && p[83] == 'A');
|
||||
bool const gemdos = (p[0] == 0xe9);
|
||||
/**
|
||||
* Probe for FAT16/32
|
||||
*/
|
||||
Fs::Type Fs::_probe_fatfs(uint8_t *p, size_t len)
|
||||
{
|
||||
if (len < 512) { return Type(); }
|
||||
|
||||
if (found_boot_sig && fat32) { return Type("FAT32"); }
|
||||
if (found_boot_sig && fat16) { return Type("FAT16"); }
|
||||
if (gemdos) { return Type("GEMDOS"); }
|
||||
/* at least the checks ring true when mkfs.vfat is used... */
|
||||
bool const found_boot_sig = p[510] == 0x55 && p[511] == 0xAA;
|
||||
|
||||
return Type();
|
||||
}
|
||||
bool const fat16 = p[38] == 0x28 || p[38] == 0x29;
|
||||
bool const fat32 = (p[66] == 0x28 || p[66] == 0x29)
|
||||
&& (p[82] == 'F' && p[83] == 'A');
|
||||
bool const gemdos = (p[0] == 0xe9);
|
||||
|
||||
if (found_boot_sig && fat32) { return Type("FAT32"); }
|
||||
if (found_boot_sig && fat16) { return Type("FAT16"); }
|
||||
if (gemdos) { return Type("GEMDOS"); }
|
||||
|
||||
return Type();
|
||||
}
|
||||
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
* \brief GUID Partition table definitions
|
||||
* \author Josef Soentgen
|
||||
* \author Sebastian Sumpf
|
||||
* \author Christian Helmuth
|
||||
* \date 2014-09-19
|
||||
*/
|
||||
|
||||
@ -15,21 +16,38 @@
|
||||
#ifndef _PART_BLOCK__GPT_H_
|
||||
#define _PART_BLOCK__GPT_H_
|
||||
|
||||
#include <base/env.h>
|
||||
#include <base/log.h>
|
||||
#include <block_session/client.h>
|
||||
#include <util/misc_math.h>
|
||||
#include <util/mmio.h>
|
||||
#include <util/utf8.h>
|
||||
|
||||
#include "partition_table.h"
|
||||
|
||||
static bool constexpr verbose = false;
|
||||
|
||||
namespace Block {
|
||||
class Gpt_partition;
|
||||
class Gpt;
|
||||
};
|
||||
|
||||
|
||||
struct Block::Gpt_partition : Partition
|
||||
{
|
||||
using Uuid = String<40>;
|
||||
Uuid guid;
|
||||
Uuid type;
|
||||
|
||||
using Name = String<72>; /* use GPT name entry length */
|
||||
Name name;
|
||||
|
||||
Gpt_partition(block_number_t lba,
|
||||
block_number_t sectors,
|
||||
Fs::Type fs_type,
|
||||
Uuid const &guid,
|
||||
Uuid const &type,
|
||||
Name const &name)
|
||||
:
|
||||
Partition(lba, sectors, fs_type),
|
||||
guid(guid), type(type), name(name)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
class Block::Gpt : public Block::Partition_table
|
||||
{
|
||||
private:
|
||||
@ -37,9 +55,7 @@ class Block::Gpt : public Block::Partition_table
|
||||
enum { MAX_PARTITIONS = 128 };
|
||||
|
||||
/* contains valid partitions or not constructed */
|
||||
Constructible<Partition> _part_list[MAX_PARTITIONS];
|
||||
|
||||
typedef Block::Partition_table::Sector Sector;
|
||||
Constructible<Gpt_partition> _part_list[MAX_PARTITIONS];
|
||||
|
||||
/**
|
||||
* DCE uuid struct
|
||||
@ -83,7 +99,6 @@ class Block::Gpt : public Block::Partition_table
|
||||
* GUID parition table header
|
||||
*/
|
||||
struct Gpt_hdr : Mmio
|
||||
|
||||
{
|
||||
struct Sig : Register<0, 64> { }; /* identifies GUID Partition Table */
|
||||
struct Revision : Register<8, 32> { }; /* GPT specification revision */
|
||||
@ -147,7 +162,8 @@ class Block::Gpt : public Block::Partition_table
|
||||
log(" gpe crc: ", Hex(read<Gpe_crc>(), Hex::OMIT_PREFIX));
|
||||
}
|
||||
|
||||
bool valid(Partition_table::Sector_data &data, bool check_primary = true)
|
||||
bool valid(Sync_read::Handler &handler, Allocator &alloc,
|
||||
size_t block_size, bool check_primary = true)
|
||||
{
|
||||
dump_hdr(check_primary);
|
||||
|
||||
@ -172,17 +188,20 @@ class Block::Gpt : public Block::Partition_table
|
||||
|
||||
/* check GPT entry array */
|
||||
size_t length = entries() * entry_size();
|
||||
Sector gpe(data, gpe_lba(), length / data.block.info().block_size);
|
||||
if (crc32(gpe.addr<addr_t>(), length) != read<Gpe_crc>())
|
||||
Sync_read gpe(handler, alloc, gpe_lba(), length / block_size);
|
||||
if (!gpe.success()
|
||||
|| crc32(gpe.addr<addr_t>(), length) != read<Gpe_crc>())
|
||||
return false;
|
||||
|
||||
if (check_primary) {
|
||||
/* check backup gpt header */
|
||||
Sector backup_hdr(data, read<Backup_hdr_lba>(), 1);
|
||||
Sync_read backup_hdr(handler, alloc, read<Backup_hdr_lba>(), 1);
|
||||
if (!backup_hdr.success())
|
||||
return false;
|
||||
|
||||
Gpt_hdr backup(backup_hdr.addr<addr_t>());
|
||||
if (!backup.valid(data, false)) {
|
||||
if (!backup.valid(handler, alloc, block_size, false))
|
||||
warning("Backup GPT header is corrupted");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -336,13 +355,15 @@ class Block::Gpt : public Block::Partition_table
|
||||
/**
|
||||
* Parse the GPT header
|
||||
*/
|
||||
void _parse_gpt(Gpt_hdr &gpt)
|
||||
bool _parse_gpt(Gpt_hdr &gpt)
|
||||
{
|
||||
if (!(gpt.valid(data)))
|
||||
throw Exception();
|
||||
if (!gpt.valid(_handler, _alloc, _info.block_size))
|
||||
return false;
|
||||
|
||||
Sector entry_array(data, gpt.gpe_lba(),
|
||||
gpt.entries() * gpt.entry_size() / block.info().block_size);
|
||||
Sync_read entry_array(_handler, _alloc, gpt.gpe_lba(),
|
||||
gpt.entries() * gpt.entry_size() / _info.block_size);
|
||||
if (!entry_array.success())
|
||||
return false;
|
||||
Gpt_entry entries(entry_array.addr<addr_t>());
|
||||
|
||||
_gpt_part_lba_end = gpt.part_lba_end();
|
||||
@ -356,51 +377,37 @@ class Block::Gpt : public Block::Partition_table
|
||||
if (!e.valid())
|
||||
continue;
|
||||
|
||||
block_number_t const lba_start = e.lba_start();
|
||||
block_count_t const length = (block_count_t)(e.lba_end() - lba_start + 1); /* [...) */
|
||||
|
||||
enum { BYTES = 4096, };
|
||||
Sector fs(data, lba_start, BYTES / block.info().block_size);
|
||||
Fs::Type fs_type = Fs::probe(fs.addr<uint8_t*>(), BYTES);
|
||||
block_number_t const lba = e.lba_start();
|
||||
block_number_t const length = e.lba_end() - lba + 1;
|
||||
|
||||
String<40> guid { e.guid() };
|
||||
String<40> type { e.type() };
|
||||
String<Gpt_entry::NAME_LEN> name { e };
|
||||
|
||||
_part_list[i].construct(lba_start, length, fs_type,
|
||||
guid, type, name);
|
||||
_part_list[i].construct(lba, length, _fs_type(lba), guid, type, name);
|
||||
|
||||
log("GPT Partition ", i + 1, ": LBA ", lba_start, " (", length,
|
||||
log("GPT Partition ", i + 1, ": LBA ", lba, " (", length,
|
||||
" blocks) type: '", type,
|
||||
"' name: '", name, "'");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
using Partition_table::Partition_table;
|
||||
|
||||
Partition &partition(long num) override
|
||||
bool parse()
|
||||
{
|
||||
num -= 1;
|
||||
Sync_read s(_handler, _alloc, Gpt_hdr::Hdr_lba::LBA, 1);
|
||||
if (!s.success())
|
||||
return false;
|
||||
|
||||
if (num < 0 || num > MAX_PARTITIONS)
|
||||
throw -1;
|
||||
|
||||
if (!_part_list[num].constructed())
|
||||
throw -1;
|
||||
|
||||
return *_part_list[num];
|
||||
}
|
||||
|
||||
bool parse() override
|
||||
{
|
||||
block.sigh(io_sigh);
|
||||
|
||||
Sector s(data, Gpt_hdr::Hdr_lba::LBA, 1);
|
||||
Gpt_hdr gpt_hdr(s.addr<addr_t>());
|
||||
|
||||
_parse_gpt(gpt_hdr);
|
||||
if (!_parse_gpt(gpt_hdr))
|
||||
return false;
|
||||
|
||||
for (unsigned num = 0; num < MAX_PARTITIONS; num++)
|
||||
if (_part_list[num].constructed())
|
||||
@ -408,30 +415,49 @@ class Block::Gpt : public Block::Partition_table
|
||||
return false;
|
||||
}
|
||||
|
||||
bool partition_valid(long num) const override
|
||||
{
|
||||
/* 1-based partition number to 0-based array index */
|
||||
num -= 1;
|
||||
|
||||
if (num < 0 || num >= MAX_PARTITIONS)
|
||||
return false;
|
||||
|
||||
return _part_list[num].constructed();
|
||||
}
|
||||
|
||||
block_number_t partition_lba(long num) const override
|
||||
{
|
||||
return partition_valid(num) ? _part_list[num - 1]->lba : 0;
|
||||
}
|
||||
|
||||
block_number_t partition_sectors(long num) const override
|
||||
{
|
||||
return partition_valid(num) ? _part_list[num - 1]->sectors : 0;
|
||||
}
|
||||
|
||||
void generate_report(Xml_generator &xml) const override
|
||||
{
|
||||
xml.attribute("type", "gpt");
|
||||
|
||||
uint64_t const total_blocks = block.info().block_count;
|
||||
uint64_t const total_blocks = _info.block_count;
|
||||
xml.attribute("total_blocks", total_blocks);
|
||||
|
||||
xml.attribute("gpt_total", _gpt_total);
|
||||
xml.attribute("gpt_used", _gpt_used);
|
||||
|
||||
size_t const block_size = block.info().block_size;
|
||||
|
||||
_for_each_valid_partition([&] (unsigned i) {
|
||||
|
||||
Block::Partition const &part = *_part_list[i];
|
||||
Gpt_partition const &part = *_part_list[i];
|
||||
|
||||
xml.node("partition", [&] () {
|
||||
xml.attribute("number", i + 1);
|
||||
xml.attribute("name", part.name);
|
||||
xml.attribute("type", part.gpt_type);
|
||||
xml.attribute("type", part.type);
|
||||
xml.attribute("guid", part.guid);
|
||||
xml.attribute("start", part.lba);
|
||||
xml.attribute("length", part.sectors);
|
||||
xml.attribute("block_size", block_size);
|
||||
xml.attribute("block_size", _info.block_size);
|
||||
|
||||
uint64_t const gap =
|
||||
_calculate_gap(i, total_blocks);
|
||||
|
@ -3,16 +3,18 @@
|
||||
* \author Sebastian Sumpf
|
||||
* \author Stefan Kalkowski
|
||||
* \author Josef Soentgen
|
||||
* \author Christian Helmuth
|
||||
* \date 2011-05-30
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2011-2020 Genode Labs GmbH
|
||||
* Copyright (C) 2011-2023 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.
|
||||
*/
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/attached_rom_dataspace.h>
|
||||
#include <base/attached_ram_dataspace.h>
|
||||
#include <base/component.h>
|
||||
@ -20,10 +22,13 @@
|
||||
#include <block_session/rpc_object.h>
|
||||
#include <block/request_stream.h>
|
||||
#include <os/session_policy.h>
|
||||
#include <os/reporter.h>
|
||||
#include <util/bit_allocator.h>
|
||||
|
||||
#include "gpt.h"
|
||||
#include "mbr.h"
|
||||
#include "ahdi.h"
|
||||
#include "disk.h"
|
||||
|
||||
namespace Block {
|
||||
class Session_component;
|
||||
@ -202,7 +207,7 @@ class Block::Session_component : public Rpc_object<Block::Session>,
|
||||
|
||||
|
||||
class Block::Main : Rpc_object<Typed_root<Session>>,
|
||||
Dispatch
|
||||
Dispatch, public Sync_read::Handler
|
||||
{
|
||||
private:
|
||||
|
||||
@ -221,10 +226,15 @@ class Block::Main : Rpc_object<Typed_root<Session>>,
|
||||
|
||||
Allocator_avl _block_alloc { &_heap };
|
||||
Block_connection _block { _env, &_block_alloc, _io_buffer_size };
|
||||
Session::Info _info { _block.info() };
|
||||
Io_signal_handler<Main> _io_sigh { _env.ep(), *this, &Main::_handle_io };
|
||||
Mbr_partition_table _mbr { _env, _block, _heap };
|
||||
Gpt _gpt { _env, _block, _heap };
|
||||
Partition_table &_partition_table { _table() };
|
||||
Mbr _mbr { *this, _heap, _info };
|
||||
Gpt _gpt { *this, _heap, _info };
|
||||
Ahdi _ahdi { *this, _heap, _info };
|
||||
|
||||
Constructible<Disk> _disk { };
|
||||
|
||||
Partition_table & _partition_table { _table() };
|
||||
|
||||
enum { MAX_SESSIONS = 128 };
|
||||
Session_component *_sessions[MAX_SESSIONS] { };
|
||||
@ -289,6 +299,7 @@ class Block::Main : Rpc_object<Typed_root<Session>>,
|
||||
|
||||
Main(Env &env) : _env(env)
|
||||
{
|
||||
/* register final handler after initially synchronous block I/O */
|
||||
_block.sigh(_io_sigh);
|
||||
|
||||
/* announce at parent */
|
||||
@ -326,10 +337,7 @@ class Block::Main : Rpc_object<Typed_root<Session>>,
|
||||
throw Service_denied();
|
||||
}
|
||||
|
||||
try {
|
||||
_partition_table.partition(num);
|
||||
}
|
||||
catch (...) {
|
||||
if (!_partition_table.partition_valid(num)) {
|
||||
error("Partition ", num, " unavailable for '", label, "'");
|
||||
throw Service_denied();
|
||||
}
|
||||
@ -360,7 +368,7 @@ class Block::Main : Rpc_object<Typed_root<Session>>,
|
||||
|
||||
Session::Info info {
|
||||
.block_size = _block.info().block_size,
|
||||
.block_count = _partition_table.partition(num).sectors,
|
||||
.block_count = _partition_table.partition_sectors(num),
|
||||
.align_log2 = 0,
|
||||
.writeable = writeable,
|
||||
};
|
||||
@ -405,7 +413,6 @@ class Block::Main : Rpc_object<Typed_root<Session>>,
|
||||
void completed(Job &job, bool success)
|
||||
{
|
||||
job.request.success = success;
|
||||
job.completed = true;
|
||||
}
|
||||
|
||||
|
||||
@ -417,10 +424,9 @@ class Block::Main : Rpc_object<Typed_root<Session>>,
|
||||
|
||||
Response submit(long number, Request const &request, addr_t addr) override
|
||||
{
|
||||
Partition &partition = _partition_table.partition(number);
|
||||
block_number_t last = request.operation.block_number + request.operation.count;
|
||||
block_number_t last = request.operation.block_number + request.operation.count;
|
||||
|
||||
if (last > partition.sectors)
|
||||
if (last > _partition_table.partition_sectors(number))
|
||||
return Response::REJECTED;
|
||||
|
||||
addr_t index = 0;
|
||||
@ -431,7 +437,7 @@ class Block::Main : Rpc_object<Typed_root<Session>>,
|
||||
_job_queue.with_job(index, [&](Job_object &job) {
|
||||
|
||||
Operation op = request.operation;
|
||||
op.block_number += partition.lba;
|
||||
op.block_number += _partition_table.partition_lba(number);
|
||||
|
||||
job.construct(_block, op, _job_registry, index, number, request, addr);
|
||||
});
|
||||
@ -457,7 +463,7 @@ class Block::Main : Rpc_object<Typed_root<Session>>,
|
||||
void acknowledge_completed(bool all = true, long number = -1) override
|
||||
{
|
||||
_job_registry.for_each([&] (Job &job) {
|
||||
if (!job.completed) return;
|
||||
if (!job.completed()) return;
|
||||
|
||||
addr_t index = job.index;
|
||||
|
||||
@ -474,6 +480,17 @@ class Block::Main : Rpc_object<Typed_root<Session>>,
|
||||
_job_queue.free(index);
|
||||
});
|
||||
}
|
||||
|
||||
/************************
|
||||
** Sync_read::Handler **
|
||||
************************/
|
||||
|
||||
Block_connection & connection() override { return _block; }
|
||||
|
||||
void block_for_io() override
|
||||
{
|
||||
_env.ep().wait_and_dispatch_one_io_signal();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -487,6 +504,7 @@ Block::Partition_table & Block::Main::_table()
|
||||
bool valid_mbr = false;
|
||||
bool valid_gpt = false;
|
||||
bool pmbr_found = false;
|
||||
bool valid_ahdi = false;
|
||||
bool report = false;
|
||||
|
||||
if (ignore_gpt && ignore_mbr) {
|
||||
@ -505,22 +523,39 @@ Block::Partition_table & Block::Main::_table()
|
||||
throw;
|
||||
}
|
||||
|
||||
/*
|
||||
* The initial signal handler can be empty as it's only used to deblock
|
||||
* wait_and_dispatch_one_io_signal() in Sync_read.
|
||||
*/
|
||||
|
||||
struct Io_dummy { void fn() { }; } io_dummy;
|
||||
Io_signal_handler<Io_dummy> handler(_env.ep(), io_dummy, &Io_dummy::fn);
|
||||
_block.sigh(handler);
|
||||
|
||||
/*
|
||||
* Try to parse MBR as well as GPT first if not instructued
|
||||
* to ignore either one of them.
|
||||
*/
|
||||
|
||||
if (!ignore_mbr) {
|
||||
try { valid_mbr = _mbr.parse(); }
|
||||
catch (Mbr_partition_table::Protective_mbr_found) {
|
||||
using Parse_result = Mbr::Parse_result;
|
||||
|
||||
switch (_mbr.parse()) {
|
||||
case Parse_result::MBR:
|
||||
valid_mbr = true;
|
||||
break;
|
||||
case Parse_result::PROTECTIVE_MBR:
|
||||
pmbr_found = true;
|
||||
} catch (...) { };
|
||||
break;
|
||||
case Parse_result::NO_MBR:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ignore_gpt) {
|
||||
try { valid_gpt = _gpt.parse(); }
|
||||
catch (...) { }
|
||||
}
|
||||
if (!ignore_gpt)
|
||||
valid_gpt = _gpt.parse();
|
||||
|
||||
valid_ahdi = _ahdi.parse();
|
||||
|
||||
/*
|
||||
* Both tables are valid (although we would have expected a PMBR in
|
||||
@ -536,27 +571,31 @@ Block::Partition_table & Block::Main::_table()
|
||||
warning("will use GPT without proper protective MBR");
|
||||
}
|
||||
|
||||
/* PMBR missing, i.e, MBR part[0] contains whole disk and GPT valid */
|
||||
if (pmbr_found && ignore_gpt) {
|
||||
warning("found protective MBR but GPT is to be ignored");
|
||||
}
|
||||
|
||||
auto pick_final_table = [&] () -> Partition_table & {
|
||||
if (valid_gpt) return _gpt;
|
||||
if (valid_mbr) return _mbr;
|
||||
if (valid_ahdi) return _ahdi;
|
||||
|
||||
/* fall back to entire disk in partition 0 */
|
||||
_disk.construct(*this, _heap, _info);
|
||||
|
||||
return *_disk;
|
||||
};
|
||||
|
||||
Partition_table &table = pick_final_table();
|
||||
|
||||
/* generate appropriate report */
|
||||
if (_reporter.constructed())
|
||||
if (_reporter.constructed()) {
|
||||
_reporter->generate([&] (Xml_generator &xml) {
|
||||
if (valid_gpt) _gpt.generate_report(xml);
|
||||
if (valid_mbr) _mbr.generate_report(xml);
|
||||
table.generate_report(xml);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the appropriate table or abort if none is found.
|
||||
*/
|
||||
|
||||
if (valid_gpt) return _gpt;
|
||||
if (valid_mbr) return _mbr;
|
||||
|
||||
error("Aborting: no partition table found.");
|
||||
throw No_partition_table();
|
||||
return table;
|
||||
}
|
||||
|
||||
void Component::construct(Genode::Env &env) { static Block::Main main(env); }
|
||||
|
@ -4,6 +4,7 @@
|
||||
* \author Stefan Kalkowski
|
||||
* \author Josef Soentgen
|
||||
* \author Norman Feske
|
||||
* \author Christian Helmuth
|
||||
* \date 2013-12-04
|
||||
*/
|
||||
|
||||
@ -17,29 +18,37 @@
|
||||
#ifndef _PART_BLOCK__MBR_H_
|
||||
#define _PART_BLOCK__MBR_H_
|
||||
|
||||
#include <base/env.h>
|
||||
#include <base/log.h>
|
||||
#include <block_session/client.h>
|
||||
#include <util/mmio.h>
|
||||
|
||||
#include "partition_table.h"
|
||||
#include "fsprobe.h"
|
||||
#include "ahdi.h"
|
||||
|
||||
namespace Block {
|
||||
struct Mbr_partition_table;
|
||||
struct Mbr_partition;
|
||||
struct Mbr;
|
||||
};
|
||||
|
||||
struct Block::Mbr_partition_table : public Block::Partition_table
|
||||
|
||||
struct Block::Mbr_partition : Partition
|
||||
{
|
||||
uint8_t const type;
|
||||
|
||||
Mbr_partition(block_number_t lba,
|
||||
block_number_t sectors,
|
||||
Fs::Type fs_type,
|
||||
uint8_t type)
|
||||
:
|
||||
Partition(lba, sectors, fs_type),
|
||||
type(type)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
class Block::Mbr : public Partition_table
|
||||
{
|
||||
public:
|
||||
|
||||
class Protective_mbr_found { };
|
||||
enum class Parse_result { MBR, PROTECTIVE_MBR, NO_MBR };
|
||||
|
||||
private:
|
||||
|
||||
typedef Block::Partition_table::Sector Sector;
|
||||
|
||||
/**
|
||||
* Partition table entry format
|
||||
*/
|
||||
@ -74,16 +83,16 @@ struct Block::Mbr_partition_table : public Block::Partition_table
|
||||
/**
|
||||
* Master/Extented boot record format
|
||||
*/
|
||||
struct Mbr : Mmio
|
||||
struct Boot_record : Mmio
|
||||
{
|
||||
struct Magic : Register<510, 16>
|
||||
{
|
||||
enum { NUMBER = 0xaa55 };
|
||||
};
|
||||
|
||||
Mbr() = delete;
|
||||
Boot_record() = delete;
|
||||
|
||||
Mbr(addr_t base) : Mmio(base) { }
|
||||
Boot_record(addr_t base) : Mmio(base) { }
|
||||
|
||||
bool valid() const
|
||||
{
|
||||
@ -97,11 +106,9 @@ struct Block::Mbr_partition_table : public Block::Partition_table
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
enum { MAX_PARTITIONS = 32 };
|
||||
|
||||
/* contains pointers to valid partitions or 0 */
|
||||
Constructible<Partition> _part_list[MAX_PARTITIONS];
|
||||
Constructible<Mbr_partition> _part_list[MAX_PARTITIONS];
|
||||
|
||||
template <typename FUNC>
|
||||
void _parse_extended(Partition_record const &record, FUNC const &f) const
|
||||
@ -113,9 +120,11 @@ struct Block::Mbr_partition_table : public Block::Partition_table
|
||||
/* first logical partition number */
|
||||
int nr = 5;
|
||||
do {
|
||||
Sector s(const_cast<Sector_data&>(data), lba, 1);
|
||||
Mbr const ebr(s.addr<addr_t>());
|
||||
Sync_read s(_handler, _alloc, lba, 1);
|
||||
if (!s.success())
|
||||
return;
|
||||
|
||||
Boot_record const ebr(s.addr<addr_t>());
|
||||
if (!ebr.valid())
|
||||
return;
|
||||
|
||||
@ -140,7 +149,7 @@ struct Block::Mbr_partition_table : public Block::Partition_table
|
||||
}
|
||||
|
||||
template <typename FUNC>
|
||||
void _parse_mbr(Mbr const &mbr, FUNC const &f) const
|
||||
Parse_result _parse_mbr(Boot_record const &mbr, FUNC const &f) const
|
||||
{
|
||||
for (int i = 0; i < 4; i++) {
|
||||
Partition_record const r(mbr.record(i));
|
||||
@ -149,32 +158,15 @@ struct Block::Mbr_partition_table : public Block::Partition_table
|
||||
continue;
|
||||
|
||||
if (r.protective())
|
||||
throw Protective_mbr_found();
|
||||
return Parse_result::PROTECTIVE_MBR;
|
||||
|
||||
f(i + 1, r, 0);
|
||||
|
||||
if (r.extended())
|
||||
_parse_extended(r, f);
|
||||
}
|
||||
}
|
||||
|
||||
/* state for partitions report */
|
||||
bool _mbr_valid { false };
|
||||
bool _ahdi_valid { false };
|
||||
|
||||
public:
|
||||
|
||||
using Partition_table::Partition_table;
|
||||
|
||||
Partition &partition(long num) override
|
||||
{
|
||||
if (num < 0 || num > MAX_PARTITIONS)
|
||||
throw -1;
|
||||
|
||||
if (!_part_list[num].constructed())
|
||||
throw -1;
|
||||
|
||||
return *_part_list[num];
|
||||
return Parse_result::MBR;
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
@ -185,96 +177,78 @@ struct Block::Mbr_partition_table : public Block::Partition_table
|
||||
fn(i);
|
||||
};
|
||||
|
||||
bool parse() override
|
||||
{
|
||||
block.sigh(io_sigh);
|
||||
public:
|
||||
|
||||
Sector s(data, 0, 1);
|
||||
using Partition_table::Partition_table;
|
||||
|
||||
Parse_result parse()
|
||||
{
|
||||
Sync_read s(_handler, _alloc, 0, 1);
|
||||
if (!s.success())
|
||||
return Parse_result::NO_MBR;
|
||||
|
||||
/* check for MBR */
|
||||
Mbr const mbr(s.addr<addr_t>());
|
||||
_mbr_valid = mbr.valid();
|
||||
if (_mbr_valid) {
|
||||
_parse_mbr(mbr, [&] (int i, Partition_record const &r, unsigned offset) {
|
||||
log("MBR Partition ", i, ": LBA ",
|
||||
r.lba() + offset, " (",
|
||||
r.sectors(), " blocks) type: ",
|
||||
Hex(r.type(), Hex::OMIT_PREFIX));
|
||||
Boot_record const mbr(s.addr<addr_t>());
|
||||
if (!mbr.valid())
|
||||
return Parse_result::NO_MBR;
|
||||
|
||||
if (!r.extended()) {
|
||||
return _parse_mbr(mbr, [&] (int nr, Partition_record const &r, unsigned offset)
|
||||
{
|
||||
if (!r.extended()) {
|
||||
block_number_t const lba = r.lba() + offset;
|
||||
|
||||
block_number_t const lba = r.lba() + offset;
|
||||
_part_list[nr - 1].construct(lba, r.sectors(), _fs_type(lba), r.type());
|
||||
}
|
||||
|
||||
/* probe for known file-system types */
|
||||
enum { PROBE_BYTES = 4096, };
|
||||
Sector fs(data, lba , PROBE_BYTES / block.info().block_size);
|
||||
Fs::Type const fs_type =
|
||||
Fs::probe(fs.addr<uint8_t*>(), PROBE_BYTES);
|
||||
log("MBR Partition ", nr, ": LBA ",
|
||||
r.lba() + offset, " (",
|
||||
r.sectors(), " blocks) type: ",
|
||||
Hex(r.type(), Hex::OMIT_PREFIX));
|
||||
});
|
||||
}
|
||||
|
||||
_part_list[i].construct(
|
||||
Partition(lba, r.sectors(), fs_type, r.type()));
|
||||
}
|
||||
});
|
||||
}
|
||||
bool partition_valid(long num) const override
|
||||
{
|
||||
/* 1-based partition number to 0-based array index */
|
||||
num -= 1;
|
||||
|
||||
/* check for AHDI partition table */
|
||||
_ahdi_valid = !_mbr_valid && Ahdi::valid(s);
|
||||
if (_ahdi_valid)
|
||||
Ahdi::for_each_partition(s, [&] (unsigned i, Partition info) {
|
||||
if (i < MAX_PARTITIONS)
|
||||
_part_list[i].construct(
|
||||
Partition(info.lba, info.sectors, Fs::Type(), 0));
|
||||
});
|
||||
if (num < 0 || num >= MAX_PARTITIONS)
|
||||
return false;
|
||||
|
||||
/* no partition table, use whole disc as partition 0 */
|
||||
if (!_mbr_valid && !_ahdi_valid)
|
||||
_part_list[0].construct(
|
||||
Partition(0, (block_count_t)(block.info().block_count - 1),
|
||||
Fs::Type(), 0));
|
||||
return _part_list[num].constructed();
|
||||
}
|
||||
|
||||
bool any_partition_valid = false;
|
||||
_for_each_valid_partition([&] (unsigned) {
|
||||
any_partition_valid = true; });
|
||||
block_number_t partition_lba(long num) const override
|
||||
{
|
||||
return partition_valid(num) ? _part_list[num - 1]->lba : 0;
|
||||
}
|
||||
|
||||
return any_partition_valid;
|
||||
block_number_t partition_sectors(long num) const override
|
||||
{
|
||||
return partition_valid(num) ? _part_list[num - 1]->sectors : 0;
|
||||
}
|
||||
|
||||
void generate_report(Xml_generator &xml) const override
|
||||
{
|
||||
auto gen_partition_attr = [&] (Xml_generator &xml, unsigned i)
|
||||
{
|
||||
Partition const &part = *_part_list[i];
|
||||
Mbr_partition const &part = *_part_list[i];
|
||||
|
||||
size_t const block_size = block.info().block_size;
|
||||
|
||||
xml.attribute("number", i);
|
||||
xml.attribute("number", i + 1);
|
||||
xml.attribute("start", part.lba);
|
||||
xml.attribute("length", part.sectors);
|
||||
xml.attribute("block_size", block_size);
|
||||
|
||||
if (_mbr_valid)
|
||||
xml.attribute("type", part.mbr_type);
|
||||
else if (_ahdi_valid)
|
||||
xml.attribute("type", "bgm");
|
||||
xml.attribute("block_size", _info.block_size);
|
||||
xml.attribute("type", part.type);
|
||||
|
||||
if (part.fs_type.valid())
|
||||
xml.attribute("file_system", part.fs_type);
|
||||
};
|
||||
|
||||
xml.attribute("type", _mbr_valid ? "mbr" :
|
||||
_ahdi_valid ? "ahdi" :
|
||||
"disk");
|
||||
|
||||
if (_mbr_valid || _ahdi_valid) {
|
||||
_for_each_valid_partition([&] (unsigned i) {
|
||||
xml.node("partition", [&] {
|
||||
gen_partition_attr(xml, i); }); });
|
||||
|
||||
} else {
|
||||
xml.attribute("type", "mbr");
|
||||
|
||||
_for_each_valid_partition([&] (unsigned i) {
|
||||
xml.node("partition", [&] {
|
||||
gen_partition_attr(xml, 0); });
|
||||
}
|
||||
gen_partition_attr(xml, i); }); });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
* \brief Partition table definitions
|
||||
* \author Sebastian Sumpf
|
||||
* \author Stefan Kalkowski
|
||||
* \author Christian Helmuth
|
||||
* \date 2013-12-04
|
||||
*/
|
||||
|
||||
@ -15,193 +16,59 @@
|
||||
#ifndef _PART_BLOCK__PARTITION_TABLE_H_
|
||||
#define _PART_BLOCK__PARTITION_TABLE_H_
|
||||
|
||||
#include <base/env.h>
|
||||
#include <base/log.h>
|
||||
#include <base/registry.h>
|
||||
#include <block_session/connection.h>
|
||||
#include <os/reporter.h>
|
||||
|
||||
#include "block.h"
|
||||
#include "fsprobe.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace Block {
|
||||
struct Partition;
|
||||
class Partition_table;
|
||||
struct Job;
|
||||
using namespace Genode;
|
||||
typedef Block::Connection<Job> Block_connection;
|
||||
}
|
||||
|
||||
|
||||
struct Block::Partition
|
||||
struct Block::Partition : Noncopyable
|
||||
{
|
||||
block_number_t lba; /* logical block address on device */
|
||||
block_count_t sectors; /* number of sectors in patitions */
|
||||
block_number_t const lba; /* logical block address on device */
|
||||
block_number_t const sectors; /* number of sectors in partitions */
|
||||
|
||||
Fs::Type fs_type { };
|
||||
|
||||
uint8_t mbr_type { 0 };
|
||||
|
||||
using Uuid = String<40>;
|
||||
Uuid guid { };
|
||||
Uuid gpt_type { };
|
||||
|
||||
using Name = String<72>; /* use GPT name antry length */
|
||||
Name name { };
|
||||
|
||||
Partition(block_number_t lba,
|
||||
block_count_t sectors,
|
||||
Fs::Type fs_type,
|
||||
uint8_t mbr_type)
|
||||
:
|
||||
lba(lba), sectors(sectors), fs_type(fs_type),
|
||||
mbr_type(mbr_type)
|
||||
{ }
|
||||
|
||||
Partition(block_number_t lba,
|
||||
block_count_t sectors,
|
||||
Fs::Type fs_type,
|
||||
Uuid const &guid, Uuid const &gpt_type,
|
||||
Name const &name)
|
||||
:
|
||||
lba(lba), sectors(sectors), fs_type(fs_type),
|
||||
guid(guid), gpt_type(gpt_type), name(name)
|
||||
{ }
|
||||
block_number_t sectors,
|
||||
Fs::Type fs_type)
|
||||
: lba(lba), sectors(sectors), fs_type(fs_type) { }
|
||||
};
|
||||
|
||||
|
||||
struct Block::Job : public Block_connection::Job
|
||||
class Block::Partition_table : Interface, Noncopyable
|
||||
{
|
||||
Registry<Job>::Element registry_element;
|
||||
protected:
|
||||
|
||||
addr_t const index; /* job index */
|
||||
long const number; /* parition number */
|
||||
Request request;
|
||||
addr_t const addr; /* target payload address */
|
||||
bool completed { false };
|
||||
Sync_read::Handler &_handler;
|
||||
Allocator &_alloc;
|
||||
Session::Info const _info;
|
||||
|
||||
Job(Block_connection &connection,
|
||||
Operation operation,
|
||||
Registry<Job> ®istry,
|
||||
addr_t const index,
|
||||
addr_t const number,
|
||||
Request request,
|
||||
addr_t addr)
|
||||
: Block_connection::Job(connection, operation),
|
||||
registry_element(registry, *this),
|
||||
index(index), number(number), request(request), addr(addr) { }
|
||||
};
|
||||
|
||||
|
||||
struct Block::Partition_table : Interface
|
||||
{
|
||||
struct Sector;
|
||||
|
||||
struct Sector_data
|
||||
Fs::Type _fs_type(block_number_t lba)
|
||||
{
|
||||
Env &env;
|
||||
Block_connection █
|
||||
Allocator &alloc;
|
||||
Sector *current = nullptr;
|
||||
|
||||
Sector_data(Env &env, Block_connection &block, Allocator &alloc)
|
||||
: env(env), block(block), alloc(alloc) { }
|
||||
};
|
||||
|
||||
/**
|
||||
* Read sectors synchronously
|
||||
*/
|
||||
class Sector
|
||||
{
|
||||
private:
|
||||
|
||||
Sector_data &_data;
|
||||
bool _completed { false };
|
||||
size_t _size { 0 };
|
||||
void *_buffer { nullptr };
|
||||
|
||||
Sector(Sector const &);
|
||||
Sector &operator = (Sector const &);
|
||||
|
||||
public:
|
||||
|
||||
Sector(Sector_data &data,
|
||||
block_number_t block_number,
|
||||
block_count_t count)
|
||||
: _data(data)
|
||||
{
|
||||
Operation const operation {
|
||||
.type = Operation::Type::READ,
|
||||
.block_number = block_number,
|
||||
.count = count
|
||||
};
|
||||
|
||||
Block_connection::Job job { data.block, operation };
|
||||
_data.block.update_jobs(*this);
|
||||
|
||||
_data.current = this;
|
||||
|
||||
while (!_completed)
|
||||
data.env.ep().wait_and_dispatch_one_io_signal();
|
||||
|
||||
_data.current = nullptr;
|
||||
}
|
||||
|
||||
~Sector()
|
||||
{
|
||||
_data.alloc.free(_buffer, _size);
|
||||
}
|
||||
|
||||
void handle_io()
|
||||
{
|
||||
_data.block.update_jobs(*this);
|
||||
}
|
||||
|
||||
void consume_read_result(Block_connection::Job &, off_t offset,
|
||||
char const *src, size_t length)
|
||||
{
|
||||
_buffer = _data.alloc.alloc(length);
|
||||
memcpy((char *)_buffer + offset, src, length);
|
||||
_size += length;
|
||||
}
|
||||
|
||||
void produce_write_content(Block_connection::Job &, off_t,
|
||||
char *, size_t) { }
|
||||
|
||||
void completed(Block_connection::Job &, bool success)
|
||||
{
|
||||
_completed = true;
|
||||
|
||||
if (!success) {
|
||||
error("IO error during partition parsing");
|
||||
throw -1;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> T addr() const {
|
||||
return reinterpret_cast<T>(_buffer); }
|
||||
};
|
||||
|
||||
Env &env;
|
||||
Block_connection █
|
||||
Sector_data data;
|
||||
|
||||
Io_signal_handler<Partition_table> io_sigh {
|
||||
env.ep(), *this, &Partition_table::handle_io };
|
||||
|
||||
void handle_io()
|
||||
{
|
||||
if (data.current) { data.current->handle_io(); }
|
||||
/* probe for known file-system types */
|
||||
enum { BYTES = 4096 };
|
||||
Sync_read fs(_handler, _alloc, lba, BYTES / _info.block_size);
|
||||
if (fs.success())
|
||||
return Fs::probe(fs.addr<uint8_t*>(), BYTES);
|
||||
else
|
||||
return Fs::Type();
|
||||
}
|
||||
|
||||
Partition_table(Env &env,
|
||||
Block_connection &block,
|
||||
Allocator &alloc)
|
||||
: env(env), block(block), data(env, block, alloc)
|
||||
{ }
|
||||
public:
|
||||
|
||||
virtual Partition &partition(long num) = 0;
|
||||
Partition_table(Sync_read::Handler &handler,
|
||||
Allocator &alloc,
|
||||
Session::Info info)
|
||||
: _handler(handler), _alloc(alloc), _info(info) { }
|
||||
|
||||
virtual bool parse() = 0;
|
||||
virtual bool partition_valid(long num) const = 0;
|
||||
virtual block_number_t partition_lba(long num) const = 0;
|
||||
virtual block_number_t partition_sectors(long num) const = 0;
|
||||
|
||||
virtual void generate_report(Xml_generator &xml) const = 0;
|
||||
};
|
||||
|
33
repos/os/src/server/part_block/types.h
Normal file
33
repos/os/src/server/part_block/types.h
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* \brief Types used by part_block
|
||||
* \author Christian Helmuth
|
||||
* \date 2023-03-16
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 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.
|
||||
*/
|
||||
|
||||
#ifndef _PART_BLOCK__TYPES_H_
|
||||
#define _PART_BLOCK__TYPES_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/registry.h>
|
||||
#include <base/log.h>
|
||||
#include <util/xml_generator.h>
|
||||
#include <util/mmio.h>
|
||||
#include <util/misc_math.h>
|
||||
#include <util/utf8.h>
|
||||
#include <block_session/connection.h>
|
||||
|
||||
namespace Block {
|
||||
using namespace Genode;
|
||||
|
||||
struct Job;
|
||||
typedef Block::Connection<Job> Block_connection;
|
||||
}
|
||||
|
||||
#endif /* _PART_BLOCK__TYPES_H_ */
|
Loading…
x
Reference in New Issue
Block a user