ahci: switch to request stream API + structural changes

- Remove dated 'Block::Driver' front end and implement
  'Block::Request_stream' front end
- Remove all dynamic memory allocations but DMA memory allocations
- Remove 'Platform_hba' and implement platform specific functions in
  'spec/<platform>/*'
- Ata and Atapi don't inherit from 'Port' any more, but are a member of
  'Port' as a protocol implementation
- Use platform driver for MMIO mappings (x86)
- Exchange stateful initialization of Ata/Atapi with a sequential
  initialization using 'wait_for_any' and 'retry' patterns
- Fix Atapi initialization by setting the byte count limit
- Set FIS receive base only when Cmd::FRE is disabled and Cmd::FR is 0
- Put everything in namespaces ('Ahci', 'Ata', or 'Atapi')
- Ata decides during read/write operations to use native-command queuing
  or normal DMA requests
- Remove port claiming logic (is now done via 'Constructibles')

fixes #3636
This commit is contained in:
Sebastian Sumpf 2020-01-08 12:58:32 +01:00 committed by Christian Helmuth
parent 73f2c7043c
commit 8a7deae238
12 changed files with 1471 additions and 1510 deletions

View File

@ -62,9 +62,10 @@ append config {
<start name="ahci_drv">
<resource name="RAM" quantum="10M" />
<provides><service name="Block" /></provides>
<config>
<config atapi="yes">
<report ports="yes"/>
<policy label_prefix="test-ahci" device="0" writeable="yes" />
<policy label="test-ahci -> " device="0" writeable="yes" />
<policy label="test-ahci-atapi -> " device="1" writeable="no" />
</config>
<route>
<service name="Report"> <child name="ahci_report_rom"/> </service>
@ -80,6 +81,15 @@ append config {
<any-service> <parent/> <any-child /> </any-service>
</route>
</start>
<start name="test-ahci-atapi">
<binary name="test-block-client" />
<resource name="RAM" quantum="50M" />
<config test_size="100M"></config>
<route>
<service name="Block"><child name="ahci_drv"/></service>
<any-service> <parent/> <any-child /> </any-service>
</route>
</start>
</config> }
install_config $config
@ -96,8 +106,8 @@ build_boot_image $boot_modules
append qemu_args " -nographic -device ahci,id=ahci -boot d "
append qemu_args " -drive id=disk,file=bin/ext2.raw,format=raw,if=none -device ide-hd,drive=disk,bus=ahci.0 "
append qemu_args " -drive id=cd,file=[run_dir]/../ahci_block.iso,if=none,media=cdrom -device ide-cd,drive=cd,bus=ahci.1 "
append qemu_args " -drive id=disk2,file=bin/ext2.raw,format=raw,if=none -device ide-hd,drive=disk2,bus=ahci.2 "
run_genode_until "Tests finished successfully!" 100
run_genode_until "Tests finished successfully!" 100 [output_spawn_id]
exec rm -f bin/ext2.raw

View File

@ -1,278 +0,0 @@
/**
* \brief AHCI port controller handling
* \author Sebastian Sumpf
* \date 2015-04-29
*/
/*
* Copyright (C) 2015-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.
*/
/* Genode includes */
#include <timer_session/connection.h>
/* local includes */
#include <ata_driver.h>
#include <atapi_driver.h>
struct Ahci
{
Genode::Env &env;
Genode::Allocator &alloc;
/* read device signature */
enum Signature {
ATA_SIG = 0x101,
ATAPI_SIG = 0xeb140101,
ATAPI_SIG_QEMU = 0xeb140000, /* will be fixed in Qemu */
};
struct Timer_delayer : Mmio::Delayer, Timer::Connection
{
Timer_delayer(Genode::Env &env)
: Timer::Connection(env) { }
void usleep(uint64_t us) override { Timer::Connection::usleep(us); }
} _delayer { env };
Ahci_root &root;
Platform::Hba &platform_hba = Platform::init(env, _delayer);
Hba hba { env, platform_hba, _delayer };
enum { MAX_PORTS = 32 };
Port_driver *ports[MAX_PORTS];
bool port_claimed[MAX_PORTS];
Signal_handler<Ahci> irq;
unsigned ready_count = 0;
bool enable_atapi;
Signal_context_capability device_identified;
Ahci(Genode::Env &env, Genode::Allocator &alloc,
Ahci_root &root, bool support_atapi,
Genode::Signal_context_capability device_identified)
:
env(env), alloc(alloc),
root(root), irq(root.entrypoint(), *this, &Ahci::handle_irq),
enable_atapi(support_atapi),
device_identified(device_identified)
{
info();
/* register irq handler */
platform_hba.sigh_irq(irq);
/* initialize HBA (IRQs, memory) */
hba.init();
/* search for devices */
scan_ports(env.rm(), env.ram());
}
/**
* Forward IRQs to ports
*/
void handle_irq()
{
unsigned port_list = hba.read<Hba::Is>();
while (port_list) {
unsigned port = log2(port_list);
port_list &= ~(1U << port);
ports[port]->handle_irq();
}
/* clear status register */
hba.ack_irq();
/* ack at interrupt controller */
platform_hba.ack_irq();
}
/*
* Least significant bit
*/
unsigned lsb(unsigned bits) const
{
for (unsigned i = 0; i < 32; i++)
if (bits & (1u << i)) {
return i;
}
return 0;
}
void info()
{
using Genode::log;
log("version: "
"major=", Genode::Hex(hba.read<Hba::Version::Major>()), " "
"minor=", Genode::Hex(hba.read<Hba::Version::Minor>()));
log("command slots: ", hba.command_slots());
log("native command queuing: ", hba.ncq() ? "yes" : "no");
log("64-bit support: ", hba.supports_64bit() ? "yes" : "no");
}
void scan_ports(Genode::Region_map &rm, Genode::Ram_allocator &ram)
{
log("number of ports: ", hba.port_count(), " pi: ",
Hex(hba.read<Hba::Pi>()));
unsigned available = hba.read<Hba::Pi>();
for (unsigned i = 0; i < hba.port_count(); i++) {
/* check if port is implemented */
if (!available) break;
unsigned index = lsb(available);
available ^= (1u << index);
bool enabled = false;
switch (Port_base(index, hba).read<Port_base::Sig>()) {
case ATA_SIG:
try {
ports[index] = new (&alloc)
Ata_driver(alloc, ram, root, ready_count, rm, hba,
platform_hba, index, device_identified);
enabled = true;
} catch (...) { }
log("\t\t#", index, ":", enabled ? " ATA" : " off (ATA)");
break;
case ATAPI_SIG:
case ATAPI_SIG_QEMU:
if (enable_atapi)
try {
ports[index] = new (&alloc)
Atapi_driver(ram, root, ready_count, rm, hba,
platform_hba, index);
enabled = true;
} catch (...) { }
log("\t\t#", index, ":", enabled ? " ATAPI" : " off (ATAPI)");
break;
default:
log("\t\t#", index, ": off (unknown device signature)");
}
}
};
Block::Driver *claim_port(unsigned port_num)
{
if (!avail(port_num))
throw -1;
port_claimed[port_num] = true;
return ports[port_num];
}
void free_port(unsigned port_num)
{
port_claimed[port_num] = false;
}
bool avail(unsigned port_num)
{
return port_num < MAX_PORTS && ports[port_num] && !port_claimed[port_num] &&
ports[port_num]->ready();
}
Port_driver * port(unsigned num)
{
return num < MAX_PORTS ? ports[num] : nullptr;
}
long device_number(const char *model_num, const char *serial_num)
{
for (long port_num = 0; port_num < MAX_PORTS; port_num++) {
Ata_driver* drv = dynamic_cast<Ata_driver *>(ports[port_num]);
if (!drv)
continue;
if (*drv->model == model_num && *drv->serial == serial_num)
return port_num;
}
return -1;
}
private:
/*
* Noncopyable
*/
Ahci(Ahci const &);
Ahci &operator = (Ahci const &);
};
static Ahci *sata_ahci(Ahci *ahci = 0)
{
static Ahci *a = ahci;
return a;
}
void Ahci_driver::init(Genode::Env &env, Genode::Allocator &alloc,
Ahci_root &root, bool support_atapi,
Genode::Signal_context_capability device_identified)
{
static Ahci ahci(env, alloc, root, support_atapi, device_identified);
sata_ahci(&ahci);
}
Block::Driver *Ahci_driver::claim_port(long device_num)
{
return sata_ahci()->claim_port(device_num);
}
void Ahci_driver::free_port(long device_num)
{
sata_ahci()->free_port(device_num);
}
bool Ahci_driver::avail(long device_num)
{
return sata_ahci()->avail(device_num);
}
long Ahci_driver::device_number(char const *model_num, char const *serial_num)
{
return sata_ahci()->device_number(model_num, serial_num);
}
void Ahci_driver::report_ports(Genode::Reporter &reporter)
{
Genode::Reporter::Xml_generator xml(reporter, [&] () {
for (unsigned i = 0; i < Ahci::MAX_PORTS; ++i) {
Port_driver *port = sata_ahci()->port(i);
if (!port || !port->ready()) continue;
Ata_driver *ata = dynamic_cast<Ata_driver *>(port);
xml.node("port", [&] () {
xml.attribute("num", i);
xml.attribute("type", ata ? "ATA" : "ATAPI");
if (ata) {
xml.attribute("block_count", ata->block_count());
xml.attribute("block_size", ata->block_size());
xml.attribute("model", ata->model->cstring());
xml.attribute("serial", ata->serial->cstring());
}
});
}
});
}

File diff suppressed because it is too large Load Diff

View File

@ -1,427 +0,0 @@
/*
* \brief AHCI-port driver for ATA devices
* \author Sebastian Sumpf
* \date 2015-04-29
*/
/*
* Copyright (C) 2015-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.
*/
#ifndef _ATA_DRIVER_H_
#define _ATA_DRIVER_H_
#include <base/log.h>
#include "ahci.h"
using namespace Genode;
/**
* Return data of 'identify_device' ATA command
*/
struct Identity : Genode::Mmio
{
Identity(Genode::addr_t base) : Mmio(base) { }
struct Serial_number : Register_array<0x14, 8, 20, 8> { };
struct Model_number : Register_array<0x36, 8, 40, 8> { };
struct Queue_depth : Register<0x96, 16>
{
struct Max_depth : Bitfield<0, 5> { };
};
struct Sata_caps : Register<0x98, 16>
{
struct Ncq_support : Bitfield<8, 1> { };
};
struct Sector_count : Register<0xc8, 64> { };
struct Logical_block : Register<0xd4, 16>
{
struct Per_physical : Bitfield<0, 3> { }; /* 2^X logical per physical */
struct Longer_512 : Bitfield<12, 1> { };
struct Multiple : Bitfield<13, 1> { }; /* multiple logical blocks per physical */
};
struct Logical_words : Register<0xea, 32> { }; /* words (16 bit) per logical block */
struct Alignment : Register<0x1a2, 16>
{
struct Logical_offset : Bitfield<0, 14> { }; /* offset first logical block in physical */
};
void info()
{
using Genode::log;
log(" queue depth: ", read<Queue_depth::Max_depth>() + 1, " "
"ncq: ", read<Sata_caps::Ncq_support>());
log(" numer of sectors: ", read<Sector_count>());
log(" multiple logical blocks per physical: ",
read<Logical_block::Multiple>() ? "yes" : "no");
log(" logical blocks per physical: ",
1U << read<Logical_block::Per_physical>());
log(" logical block size is above 512 byte: ",
read<Logical_block::Longer_512>() ? "yes" : "no");
log(" words (16bit) per logical block: ",
read<Logical_words>());
log(" offset of first logical block within physical: ",
read<Alignment::Logical_offset>());
}
};
/**
* 16-bit word big endian device ASCII characters
*/
template <typename DEVICE_STRING>
struct String
{
char buf[DEVICE_STRING::ITEMS + 1];
String(Identity & info)
{
long j = 0;
for (unsigned long i = 0; i < DEVICE_STRING::ITEMS; i++) {
/* read and swap even and uneven characters */
char c = (char)info.read<DEVICE_STRING>(i ^ 1);
if (Genode::is_whitespace(c) && j == 0)
continue;
buf[j++] = c;
}
buf[j] = 0;
/* remove trailing white spaces */
while ((j > 0) && (buf[--j] == ' '))
buf[j] = 0;
}
bool operator == (char const *other) const
{
return strcmp(buf, other) == 0;
}
void print(Genode::Output &out) const { Genode::print(out, (char const *)buf); }
char const *cstring() { return buf; }
};
/**
* Commands to distinguish between ncq and non-ncq operation
*/
struct Io_command : Interface
{
virtual void command(Port &por,
Command_table &table,
bool read,
Block::sector_t block_number,
size_t count,
unsigned slot) = 0;
virtual void handle_irq(Port &port, Port::Is::access_t status) = 0;
};
struct Ncq_command : Io_command
{
void command(Port &port,
Command_table &table,
bool read,
Block::sector_t block_number,
size_t count,
unsigned slot) override
{
table.fis.fpdma(read, block_number, count, slot);
/* ensure that 'Cmd::St' is 1 before writing 'Sact' */
port.start();
/* set pending */
port.write<Port::Sact>(1U << slot);
}
void handle_irq(Port &port, Port::Is::access_t status) override
{
/*
* Check for completions of other requests immediately
*/
while (Port::Is::Sdbs::get(status = port.read<Port::Is>()))
port.ack_irq();
}
};
struct Dma_ext_command : Io_command
{
void command(Port &,
Command_table &table,
bool read,
Block::sector_t block_number,
size_t count,
unsigned /* slot */) override
{
table.fis.dma_ext(read, block_number, count);
}
void handle_irq(Port &port, Port::Is::access_t status) override
{
if (Port::Is::Dma_ext_irq::get(status))
port.ack_irq();
}
};
/**
* Drivers using ncq- and non-ncq commands
*/
struct Ata_driver : Port_driver
{
Genode::Allocator &alloc;
typedef ::String<Identity::Serial_number> Serial_string;
typedef ::String<Identity::Model_number> Model_string;
Genode::Constructible<Identity> identity { };
Genode::Constructible<Serial_string> serial { };
Genode::Constructible<Model_string> model { };
Io_command *io_cmd = nullptr;
Block::Packet_descriptor pending[32];
Signal_context_capability device_identified;
Ata_driver(Genode::Allocator &alloc,
Genode::Ram_allocator &ram,
Ahci_root &root,
unsigned &sem,
Genode::Region_map &rm,
Hba &hba,
Platform::Hba &platform_hba,
unsigned number,
Genode::Signal_context_capability device_identified)
: Port_driver(ram, root, sem, rm, hba, platform_hba, number),
alloc(alloc), device_identified(device_identified)
{
Port::init();
identify_device();
}
~Ata_driver()
{
if (io_cmd)
destroy(&alloc, io_cmd);
}
unsigned find_free_cmd_slot()
{
for (unsigned slot = 0; slot < cmd_slots; slot++)
if (!pending[slot].size())
return slot;
throw Block::Driver::Request_congestion();
}
void ack_packets()
{
unsigned slots = Port::read<Ci>() | Port::read<Sact>();
for (unsigned slot = 0; slot < cmd_slots; slot++) {
if ((slots & (1U << slot)) || !pending[slot].size())
continue;
Block::Packet_descriptor p = pending[slot];
pending[slot] = Block::Packet_descriptor();
ack_packet(p, true);
}
}
void overlap_check(Block::sector_t block_number,
size_t count)
{
Block::sector_t end = block_number + count - 1;
for (unsigned slot = 0; slot < cmd_slots; slot++) {
if (!pending[slot].size())
continue;
Block::sector_t pending_start = pending[slot].block_number();
Block::sector_t pending_end = pending_start + pending[slot].block_count() - 1;
/* check if a pending packet overlaps */
if ((block_number >= pending_start && block_number <= pending_end) ||
(end >= pending_start && end <= pending_end) ||
(pending_start >= block_number && pending_start <= end) ||
(pending_end >= block_number && pending_end <= end)) {
Genode::warning("overlap: "
"pending ", pending[slot].block_number(),
" + ", pending[slot].block_count(), ", "
"request: ", block_number, " + ", count);
throw Block::Driver::Request_congestion();
}
}
}
void io(bool read,
Block::sector_t block_number,
size_t count,
addr_t phys,
Block::Packet_descriptor &packet)
{
sanity_check(block_number, count);
overlap_check(block_number, count);
unsigned slot = find_free_cmd_slot();
pending[slot] = packet;
/* setup fis */
Command_table table(command_table_addr(slot), phys, count * block_size());
/* set ATA command */
io_cmd->command(*this, table, read, block_number, count, slot);
/* set or clear write flag in command header */
Command_header header(command_header_addr(slot));
header.write<Command_header::Bits::W>(read ? 0 : 1);
header.clear_byte_count();
execute(slot);
}
/*****************
** Port_driver **
*****************/
void handle_irq() override
{
Is::access_t status = Port::read<Is>();
switch (state) {
case IDENTIFY:
if (Port::Is::Dss::get(status)
|| Port::Is::Pss::get(status)
|| Port::Is::Dhrs::get(status)) {
identity.construct(device_info);
serial.construct(*identity);
model.construct(*identity);
if (verbose) {
Genode::log(" model number: ", Genode::Cstring(model->buf));
Genode::log(" serial number: ", Genode::Cstring(serial->buf));
identity->info();
}
check_device();
if (ncq_support())
io_cmd = new (&alloc) Ncq_command();
else
io_cmd = new (&alloc) Dma_ext_command();
ack_irq();
Genode::Signal_transmitter(device_identified).submit();
}
break;
case READY:
io_cmd->handle_irq(*this, status);
ack_packets();
default:
break;
}
stop();
}
bool ncq_support()
{
return identity->read<Identity::Sata_caps::Ncq_support>() && hba.ncq();
}
void check_device()
{
cmd_slots = min((int)cmd_slots,
identity->read<Identity::Queue_depth::Max_depth >() + 1);
/* no native command queueing */
if (!ncq_support())
cmd_slots = 1;
state = READY;
state_change();
}
void identify_device()
{
state = IDENTIFY;
addr_t phys = (addr_t)Dataspace_client(device_info_ds).phys_addr();
Command_table table(command_table_addr(0), phys, 0x1000);
table.fis.identify_device();
execute(0);
}
/*****************************
** Block::Driver interface **
*****************************/
bool dma_enabled() override { return true; };
Block::Session::Info info() const override
{
return { .block_size = block_size(),
.block_count = block_count(),
.align_log2 = log2(block_size()),
.writeable = true };
}
void read_dma(Block::sector_t block_number,
size_t block_count,
addr_t phys,
Block::Packet_descriptor &packet) override
{
io(true, block_number, block_count, phys, packet);
}
void write_dma(Block::sector_t block_number,
size_t block_count,
addr_t phys,
Block::Packet_descriptor &packet) override
{
io(false, block_number, block_count, phys, packet);
}
Genode::size_t block_size() const override
{
Genode::size_t size = 512;
if (identity->read<Identity::Logical_block::Longer_512>())
size = identity->read<Identity::Logical_words>() / 2;
return size;
}
Block::sector_t block_count() const override
{
return identity->read<Identity::Sector_count>();
}
private:
/*
* Noncopyable
*/
Ata_driver(Ata_driver const &);
Ata_driver &operator = (Ata_driver const &);
};
#endif /* _ATA_DRIVER_H_ */

View File

@ -0,0 +1,344 @@
/*
* \brief ATA protocol driver
* \author Sebastian Sumpf
* \date 2015-04-29
*/
/*
* Copyright (C) 2015-2020 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 _AHCI__ATA_PROTOCOL_H_
#define _AHCI__ATA_PROTOCOL_H_
#include <base/log.h>
#include "ahci.h"
#include "util.h"
namespace Ata {
struct Identity;
template <typename DEVICE_STRING> struct String;
class Protocol;
using namespace Genode;
using namespace Ahci;
}
/**
* Return data of 'identify_device' ATA command
*/
struct Ata::Identity : Mmio
{
Identity(addr_t base) : Mmio(base) { }
struct Serial_number : Register_array<0x14, 8, 20, 8> { };
struct Model_number : Register_array<0x36, 8, 40, 8> { };
struct Queue_depth : Register<0x96, 16>
{
struct Max_depth : Bitfield<0, 5> { };
};
struct Sata_caps : Register<0x98, 16>
{
struct Ncq_support : Bitfield<8, 1> { };
};
struct Sector_count : Register<0xc8, 64> { };
struct Logical_block : Register<0xd4, 16>
{
struct Per_physical : Bitfield<0, 3> { }; /* 2^X logical per physical */
struct Longer_512 : Bitfield<12, 1> { };
struct Multiple : Bitfield<13, 1> { }; /* multiple logical blocks per physical */
};
struct Logical_words : Register<0xea, 32> { }; /* words (16 bit) per logical block */
struct Alignment : Register<0x1a2, 16>
{
struct Logical_offset : Bitfield<0, 14> { }; /* offset first logical block in physical */
};
void info()
{
log(" queue depth: ", read<Queue_depth::Max_depth>() + 1, " "
"ncq: ", read<Sata_caps::Ncq_support>());
log(" numer of sectors: ", read<Sector_count>());
log(" multiple logical blocks per physical: ",
read<Logical_block::Multiple>() ? "yes" : "no");
log(" logical blocks per physical: ",
1U << read<Logical_block::Per_physical>());
log(" logical block size is above 512 byte: ",
read<Logical_block::Longer_512>() ? "yes" : "no");
log(" words (16bit) per logical block: ",
read<Logical_words>());
log(" offset of first logical block within physical: ",
read<Alignment::Logical_offset>());
}
};
/**
* 16-bit word big endian device ASCII characters
*/
template <typename DEVICE_STRING>
struct Ata::String
{
char buf[DEVICE_STRING::ITEMS + 1];
String(Identity & info)
{
long j = 0;
for (unsigned long i = 0; i < DEVICE_STRING::ITEMS; i++) {
/* read and swap even and uneven characters */
char c = (char)info.read<DEVICE_STRING>(i ^ 1);
if (is_whitespace(c) && j == 0)
continue;
buf[j++] = c;
}
buf[j] = 0;
/* remove trailing white spaces */
while ((j > 0) && (buf[--j] == ' '))
buf[j] = 0;
}
bool operator == (char const *other) const
{
return strcmp(buf, other) == 0;
}
void print(Output &out) const { print(out, (char const *)buf); }
char const *cstring() { return buf; }
};
/**
* Protocol driver using ncq- and non-ncq commands
*/
class Ata::Protocol : public Ahci::Protocol, Noncopyable
{
private:
struct Request : Block::Request
{
bool valid() const { return operation.valid(); }
void invalidate() { operation.type = Block::Operation::Type::INVALID; }
Request & operator=(const Block::Request &request)
{
operation = request.operation;
success = request.success;
offset = request.offset;
tag = request.tag;
return *this;
}
};
Util::Slots<Request, 32> _slots { };
unsigned _slot_states = 0;
typedef String<Identity::Serial_number> Serial_string;
typedef String<Identity::Model_number> Model_string;
Constructible<Identity> _identity { };
bool _writeable { false };
public:
Constructible<Serial_string> serial { };
Constructible<Model_string> model { };
private:
bool _overlap_check(Block::Request const &request)
{
block_number_t block_number = request.operation.block_number;
block_number_t end = block_number + request.operation.count - 1;
auto overlap_check = [&] (Request const &req) {
block_number_t pending_start = req.operation.block_number;
block_number_t pending_end = pending_start + req.operation.count - 1;
/* check if a pending packet overlaps */
if ((block_number >= pending_start && block_number <= pending_end) ||
(end >= pending_start && end <= pending_end) ||
(pending_start >= block_number && pending_start <= end) ||
(pending_end >= block_number && pending_end <= end)) {
warning("overlap: "
"pending ", pending_start,
" + ", req.operation.count, ", "
"request: ", block_number, " + ", request.operation.count);
return true;
}
return false;
};
return _slots.for_each(overlap_check);
}
bool _ncq_support(Port &port)
{
return _identity->read<Identity::Sata_caps::Ncq_support>() && port.hba.ncq();
}
size_t _block_size() const
{
size_t size = 512;
if (_identity->read<Identity::Logical_block::Longer_512>())
size = _identity->read<Identity::Logical_words>() / 2;
return size;
}
Block::sector_t _block_count() const
{
return _identity->read<Identity::Sector_count>();
}
public:
/******************************
** Ahci::Protocol interface **
******************************/
unsigned init(Port &port) override
{
/* identify device */
addr_t phys = Dataspace_client(port.device_info_ds).phys_addr();
Command_table table(port.command_table_addr(0), phys, 0x1000);
table.fis.identify_device();
port.execute(0);
port.wait_for_any(port.hba.delayer(), Port::Is::Dss::Equal(1),
Port::Is::Pss::Equal(1),
Port::Is::Dhrs::Equal(1));
_identity.construct(port.device_info);
serial.construct(*_identity);
model.construct(*_identity);
if (verbose) {
log(" model number: ", Cstring(model->buf));
log(" serial number: ", Cstring(serial->buf));
_identity->info();
}
/* read number of command slots of ATA device */
unsigned cmd_slots = _identity->read<Identity::Queue_depth::Max_depth >() + 1;
/* no native command queueing */
if (!_ncq_support(port))
cmd_slots = 1;
_slots.limit((size_t)cmd_slots);
port.ack_irq();
return cmd_slots;
}
void handle_irq(Port &port) override
{
/* ncg */
if (_ncq_support(port))
while (Port::Is::Sdbs::get(port.read<Port::Is>()))
port.ack_irq();
/* normal dma */
else if (Port::Is::Dma_ext_irq::get(port.read<Port::Is>()))
port.ack_irq();
_slot_states = port.read<Port::Ci>() | port.read<Port::Sact>();
port.stop();
}
Block::Session::Info info() const override
{
return { .block_size = _block_size(),
.block_count = _block_count(),
.align_log2 = log2(2ul),
.writeable = _writeable };
}
void writeable(bool rw) override { _writeable = rw; }
Response submit(Port &port, Block::Request const request) override
{
if (port.sanity_check(request) == false || port.dma_base == 0)
return Response::REJECTED;
if (_writeable == false && request.operation.type == Block::Operation::Type::WRITE)
return Response::REJECTED;
if (_overlap_check(request))
return Response::RETRY;
Request *r = _slots.get();
if (r == nullptr)
return Response::RETRY;
*r = request;
Block::Operation op = request.operation;
size_t slot = _slots.index(*r);
_slot_states |= 1u << slot;
/* setup fis */
Command_table table(port.command_table_addr(slot),
port.dma_base + request.offset, /* physical address */
op.count * _block_size());
/* setup ATA command */
bool read = op.type == Block::Operation::Type::READ;
if (_ncq_support(port)) {
table.fis.fpdma(read, op.block_number, op.count, slot);
/* ensure that 'Cmd::St' is 1 before writing 'Sact' */
port.start();
/* set pending */
port.write<Port::Sact>(1U << slot);
} else {
table.fis.dma_ext(read, op.block_number, op.count);
}
/* set or clear write flag in command header */
Command_header header(port.command_header_addr(slot));
header.write<Command_header::Bits::W>(read ? 0 : 1);
header.clear_byte_count();
port.execute(slot);
return Response::ACCEPTED;
}
Block::Request completed(Port & /* port */) override
{
Block::Request r { };
_slots.for_each([&](Request &request)
{
size_t index = _slots.index(request);
/* request still pending */
if (_slot_states & (1u << index))
return false;
r = request;
request.invalidate();
return true;
});
return r;
}
};
#endif /* _AHCI__ATA_PROTOCOL_H_ */

View File

@ -1,218 +1,195 @@
/*
* \brief AHCI-port driver for ATAPI devices
* \brief ATAPI protocol driver
* \author Sebastian Sumpf
* \date 2015-04-29
*/
/*
* Copyright (C) 2015-2017 Genode Labs GmbH
* Copyright (C) 2015-2020 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 _ATAPI_DRIVER_H_
#define _ATAPI_DRIVER_H_
#ifndef _AHCI__ATAPI_PROTOCOL_H_
#define _AHCI__ATAPI_PROTOCOL_H_
#include "ahci.h"
#include <util/endian.h>
namespace Atapi {
class Protocol;
using namespace Ahci;
using namespace Genode;
}
struct Atapi_driver : Port_driver
class Atapi::Protocol : public Ahci::Protocol, Noncopyable
{
unsigned sense_tries = 0;
Block::Packet_descriptor pending { };
private:
Atapi_driver(Genode::Ram_allocator &ram,
Ahci_root &root,
unsigned &sem,
Genode::Region_map &rm,
Hba &hba,
Platform::Hba &platform_hba,
unsigned number)
: Port_driver(ram, root, sem, rm, hba, platform_hba, number)
{
Port::init();
Port::write<Cmd::Atapi>(1);
read_sense();
}
Block::Request _pending { };
block_number_t _block_count { 0 };
size_t _block_size { 2048 };
void atapi_command()
{
Command_header header(command_header_addr(0));
header.atapi_command();
header.clear_byte_count();
execute(0);
}
void test_unit_ready()
{
state = TEST_READY;
Command_table table(command_table_addr(0), 0, 0);
table.fis.atapi();
table.atapi_cmd.test_unit_ready();
atapi_command();
}
void read_sense()
{
state = STATUS;
if (sense_tries++ >= 3) {
Genode::error("could not power up device");
state_change();
return;
void _atapi_command(Port &port)
{
Command_header header(port.command_header_addr(0));
header.atapi_command();
header.clear_byte_count();
port.execute(0);
}
addr_t phys = (addr_t)Dataspace_client(device_info_ds).phys_addr();
void _read_sense(Port &port)
{
addr_t phys = Dataspace_client(port.device_info_ds).phys_addr();
Command_table table(command_table_addr(0), phys, 0x1000);
table.fis.atapi();
table.atapi_cmd.read_sense();
Command_table table(port.command_table_addr(0), phys, 0x1000);
table.fis.atapi();
table.atapi_cmd.read_sense();
atapi_command();
}
void read_capacity()
{
state = IDENTIFY;
addr_t phys = (addr_t)Dataspace_client(device_info_ds).phys_addr();
Command_table table(command_table_addr(0), phys, 0x1000);
table.fis.atapi();
table.atapi_cmd.read_capacity();
atapi_command();
}
void ack_packets()
{
unsigned slots = Port::read<Ci>();
if (slots & 1 || !pending.size())
return;
Block::Packet_descriptor p = pending;
pending = Block::Packet_descriptor();
ack_packet(p, true);
}
/*****************
** Port_driver **
*****************/
void handle_irq() override
{
Is::access_t status = Port::read<Is>();
if (verbose) {
Genode::log("irq: "
"is: ", Genode::Hex(status), " "
"ci: ", Genode::Hex(Port::read<Ci>()), " "
"state: ", (int)state);
Device_fis f(fis_base);
Genode::log("d2h: "
"status: ", f.read<Device_fis::Status>(), " "
"error: ", Genode::Hex(f.read<Device_fis::Error>()));
_atapi_command(port);
}
ack_irq();
void _test_unit_ready(Port &port)
{
Command_table table(port.command_table_addr(0), 0, 0);
table.fis.atapi();
table.atapi_cmd.test_unit_ready();
if (state == TEST_READY && Port::Is::Dhrs::get(status)) {
Device_fis f(fis_base);
/* check if devic is ready */
if (f.read<Device_fis::Status::Device_ready>() && !f.read<Device_fis::Error>())
read_capacity();
else
read_sense();
_atapi_command(port);
}
if (state == READY && Port::Is::Dhrs::get(status)) {
ack_packets();
void _read_capacity(Port &port)
{
addr_t phys = Dataspace_client(port.device_info_ds).phys_addr();
Command_table table(port.command_table_addr(0), phys, 0x1000);
table.fis.atapi();
table.fis.byte_count(~0);
table.atapi_cmd.read_capacity();
_atapi_command(port);
}
if (Port::Is::Dss::get(status) || Port::Is::Pss::get(status)) {
switch (state) {
void _start_unit(Port &port)
{
Command_table table(port.command_table_addr(0), 0, 0);
table.fis.atapi();
table.atapi_cmd.start_unit();
case STATUS:
test_unit_ready();
break;
case IDENTIFY:
state = READY;
state_change();
break;
case READY:
ack_packets();
return;
default:
break;
}
_atapi_command(port);
}
}
Block::Session::Info info() const override
{
return { .block_size = block_size(),
.block_count = block_count(),
.align_log2 = 11,
.writeable = false };
}
public:
/******************************
** Ahci::Protocol interface **
******************************/
/*****************************
** Block::Driver interface **
*****************************/
unsigned init(Port &port) override
{
port.write<Port::Cmd::Atapi>(1);
bool dma_enabled() override { return true; };
retry<Port::Polling_timeout>(
[&] {
Genode::size_t block_size() const override
{
return host_to_big_endian(((unsigned *)device_info)[1]);
}
_start_unit(port);
port.wait_for_any(port.hba.delayer(),
Port::Is::Dss::Equal(1), Port::Is::Pss::Equal(1),
Port::Is::Dhrs::Equal(1));
port.ack_irq();
Block::sector_t block_count() const override
{
return host_to_big_endian(((unsigned *)device_info)[0]) + 1;
}
/* read sense */
_read_sense(port);
port.wait_for_any(port.hba.delayer(),
Port::Is::Dss::Equal(1), Port::Is::Pss::Equal(1),
Port::Is::Dhrs::Equal(1));
port.ack_irq();
void read_dma(Block::sector_t block_number,
size_t count,
addr_t phys,
Block::Packet_descriptor &packet) override
{
if (pending.size())
throw Block::Driver::Request_congestion();
/* test unit ready */
_test_unit_ready(port);
port.wait_for(port.hba.delayer(), Port::Is::Dhrs::Equal(1));
port.ack_irq();
sanity_check(block_number, count);
Device_fis f(port.fis_base);
/* check if devic is ready */
if (!f.read<Device_fis::Status::Device_ready>() || f.read<Device_fis::Error>())
throw Port::Polling_timeout();
pending = packet;
_read_capacity(port);
port.wait_for_any(port.hba.delayer(),
Port::Is::Dss::Equal(1), Port::Is::Pss::Equal(1),
Port::Is::Dhrs::Equal(1));
port.ack_irq();
if (verbose)
Genode::log("add packet read ", block_number, " count ", count, " -> 0");
_block_count = host_to_big_endian(((unsigned *)port.device_info)[0]) + 1;
_block_size = host_to_big_endian(((unsigned *)port.device_info)[1]);
},
[&] {}, 3);
/* setup fis */
Command_table table(command_table_addr(0), phys, count * block_size());
table.fis.atapi();
return 1;
}
/* setup atapi command */
table.atapi_cmd.read10(block_number, count);
Block::Session::Info info() const override
{
return { .block_size = _block_size,
.block_count = _block_count,
.align_log2 = 1,
.writeable = false };
}
/* set and clear write flag in command header */
Command_header header(command_header_addr(0));
header.write<Command_header::Bits::W>(0);
header.clear_byte_count();
void handle_irq(Port &port) override
{
port.ack_irq();
}
/* set pending */
execute(0);
}
void writeable(bool) override { }
Response submit(Port &port, Block::Request const request) override
{
if (port.sanity_check(request) == false || port.dma_base == 0 ||
request.operation.type != Block::Operation::Type::READ)
return Response::REJECTED;
if (_pending.operation.valid())
return Response::RETRY;
Block::Operation op = request.operation;
_pending = request;
_pending.success = false;
/* setup fis */
Command_table table(port.command_table_addr(0),
port.dma_base + request.offset,
op.count * _block_size);
table.fis.atapi();
/* setup atapi command */
table.atapi_cmd.read10(op.block_number, op.count);
table.fis.byte_count(~0);
/* set and clear write flag in command header */
Command_header header(port.command_header_addr(0));
header.write<Command_header::Bits::W>(0);
header.clear_byte_count();
/* set pending */
port.execute(0);
return Response::ACCEPTED;
}
Block::Request completed(Port &port) override
{
if (!_pending.operation.valid() || port.read<Port::Ci>())
return Block::Request();
Block::Request request = _pending;
_pending.operation.type = Block::Operation::Type::INVALID;
return request;
}
};
#endif /* _ATAPI_DRIVER_H_ */
#endif /* _AHCI__ATAPI_PROTOCOL_H_ */

View File

@ -5,7 +5,7 @@
*/
/*
* Copyright (C) 2016-2017 Genode Labs GmbH
* Copyright (C) 2016-2020 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.
@ -14,195 +14,394 @@
/* Genode includes */
#include <base/attached_rom_dataspace.h>
#include <base/component.h>
#include <base/heap.h>
#include <base/log.h>
#include <block/component.h>
#include <block/request_stream.h>
#include <os/session_policy.h>
#include <timer_session/connection.h>
#include <util/xml_node.h>
#include <os/reporter.h>
/* local includes */
#include <ahci.h>
#include <ata_protocol.h>
#include <atapi_protocol.h>
namespace Block {
class Factory;
class Root_multiple_clients;
class Main;
namespace Ahci {
struct Dispatch;
class Driver;
struct Main;
struct Block_session_handler;
struct Block_session_component;
}
struct Block::Factory : Driver_factory
struct Ahci::Dispatch : Interface
{
long device_num;
Block::Driver *create() override
{
return Ahci_driver::claim_port(device_num);
}
void destroy(Block::Driver *) override
{
Ahci_driver::free_port(device_num);
}
Factory(long device_num) : device_num(device_num) { }
virtual void session(unsigned index) = 0;
};
class Session_component : public Block::Session_component
class Ahci::Driver : Noncopyable
{
public:
Session_component(Block::Driver_factory &driver_factory,
Genode::Entrypoint &ep,
Genode::Region_map &rm,
Genode::size_t buf_size,
bool writeable)
: Block::Session_component(driver_factory, ep, rm, buf_size, writeable) { }
enum { MAX_PORTS = 32 };
Block::Driver_factory &factory() { return _driver_factory; }
};
class Block::Root_multiple_clients : public Root_component< ::Session_component>,
public Ahci_root
{
private:
Genode::Env &_env;
Genode::Allocator &_alloc;
Genode::Xml_node _config;
Env &_env;
Dispatch &_dispatch;
protected:
::Session_component *_create_session(const char *args) override
struct Timer_delayer : Mmio::Delayer, Timer::Connection
{
Session_label const label = label_from_args(args);
Session_policy const policy(label, _config);
Timer_delayer(Env &env)
: Timer::Connection(env) { }
size_t ram_quota =
Arg_string::find_arg(args, "ram_quota").ulong_value(0);
size_t tx_buf_size =
Arg_string::find_arg(args, "tx_buf_size").ulong_value(0);
void usleep(uint64_t us) override { Timer::Connection::usleep(us); }
} _delayer { _env };
if (!tx_buf_size)
throw Service_denied();
Hba _hba { _env, _delayer };
size_t session_size = sizeof(::Session_component)
+ sizeof(Factory) + tx_buf_size;
Constructible<Ata::Protocol> _ata[MAX_PORTS];
Constructible<Atapi::Protocol> _atapi[MAX_PORTS];
Constructible<Port> _ports[MAX_PORTS];
if (max((size_t)4096, session_size) > ram_quota) {
error("insufficient 'ram_quota' from '", label, "',"
" got ", ram_quota, ", need ", session_size);
throw Insufficient_ram_quota();
}
Signal_handler<Driver> _irq { _env.ep(), *this, &Driver::handle_irq };
bool _enable_atapi;
/* try read device port number attribute */
long num = policy.attribute_value("device", -1L);
/* try read device model and serial number attributes */
auto const model = policy.attribute_value("model", String<64>());
auto const serial = policy.attribute_value("serial", String<64>());
/* sessions are not writeable by default */
bool writeable = policy.attribute_value("writeable", false);
/* prefer model/serial routing */
if ((model != "") && (serial != ""))
num = Ahci_driver::device_number(model.string(), serial.string());
if (num < 0) {
error("rejecting session request, no matching policy for '", label, "'",
model == "" ? ""
: " (model=", model, " serial=", serial, ")");
throw Service_denied();
}
if (!Ahci_driver::avail(num)) {
error("Device ", num, " not available");
throw Service_denied();
}
if (writeable)
writeable = Arg_string::find_arg(args, "writeable").bool_value(true);
Block::Factory *factory = new (&_alloc) Block::Factory(num);
::Session_component *session = new (&_alloc)
::Session_component(*factory, _env.ep(), _env.rm(), tx_buf_size, writeable);
log(
writeable ? "writeable " : "read-only ",
"session opened at device ", num, " for '", label, "'");
return session;
void _info()
{
log("version: "
"major=", Hex(_hba.read<Hba::Version::Major>()), " "
"minor=", Hex(_hba.read<Hba::Version::Minor>()));
log("command slots: ", _hba.command_slots());
log("native command queuing: ", _hba.ncq() ? "yes" : "no");
log("64-bit support: ", _hba.supports_64bit() ? "yes" : "no");
}
void _destroy_session(::Session_component *session) override
void _scan_ports(Region_map &rm)
{
Driver_factory &factory = session->factory();
Genode::destroy(&_alloc, session);
Genode::destroy(&_alloc, &factory);
log("number of ports: ", _hba.port_count(), " pi: ",
Hex(_hba.read<Hba::Pi>()));
for (unsigned index = 0; index < MAX_PORTS; index++) {
Port_base port(index, _hba);
if (port.implemented() == false)
continue;
bool enabled = false;
if (port.ata()) {
try {
_ata[index].construct();
_ports[index].construct(*_ata[index], rm, _hba, index);
enabled = true;
} catch (...) { }
log("\t\t#", index, ":", enabled ? " ATA" : " off (ATA)");
} else if (port.atapi() && _enable_atapi) {
try {
_atapi[index].construct();
_ports[index].construct(*_atapi[index], rm, _hba, index);
enabled = true;
} catch (...) { }
log("\t\t#", index, ":", enabled ? " ATAPI" : " off (ATAPI)");
} else {
log("\t\t#", index, ":", port.atapi() ? " off (ATAPI)"
: " off (unknown device signature)");
}
}
}
public:
Root_multiple_clients(Genode::Env &env, Genode::Allocator &alloc,
Genode::Xml_node config)
:
Root_component(&env.ep().rpc_ep(), &alloc),
_env(env), _alloc(alloc), _config(config)
{ }
Genode::Entrypoint &entrypoint() override { return _env.ep(); }
void announce() override
Driver(Env &env, Dispatch &dispatch, bool support_atapi)
: _env(env), _dispatch(dispatch), _enable_atapi(support_atapi)
{
_env.parent().announce(_env.ep().manage(*this));
_info();
/* register irq handler */
_hba.sigh_irq(_irq);
/* initialize HBA (IRQs, memory) */
_hba.init();
/* search for devices */
_scan_ports(env.rm());
}
/**
* Forward IRQs to ports/block sessions
*/
void handle_irq()
{
unsigned port_list = _hba.read<Hba::Is>();
while (port_list) {
unsigned port = log2(port_list);
port_list &= ~(1U << port);
/* ack irq */
if (_ports[port].constructed())
_ports[port]->handle_irq();
/* handle (pending) requests */
_dispatch.session(port);
}
/* clear status register */
_hba.ack_irq();
}
Port &port(long device, char const *model_num, char const *serial_num)
{
/* check for model/device */
if (model_num && serial_num) {
for (long index = 0; index < MAX_PORTS; index++) {
if (!_ata[index].constructed()) continue;
Ata::Protocol &protocol = *_ata[index];
if (*protocol.model == model_num && *protocol.serial == serial_num)
return *_ports[index];
}
}
/* check for device number */
if (device >= 0 && device < MAX_PORTS && _ports[device].constructed())
return *_ports[device];
throw -1;
}
template <typename FN> void for_each_port(FN const &fn)
{
for (unsigned index = 0; index < MAX_PORTS; index++) {
if (!_ports[index].constructed()) continue;
fn(*_ports[index], index, !_ata[index].constructed());
}
}
void report_ports(Reporter &reporter)
{
auto report = [&](Port const &port, unsigned index, bool atapi) {
Block::Session::Info info = port.info();
Reporter::Xml_generator xml(reporter, [&] () {
xml.node("port", [&] () {
xml.attribute("num", index);
xml.attribute("type", atapi ? "ATAPI" : "ATA");
xml.attribute("block_count", info.block_count);
xml.attribute("block_size", info.block_size);
if (!atapi) {
xml.attribute("model", _ata[index]->model->cstring());
xml.attribute("serial", _ata[index]->serial->cstring());
}
});
});
};
for_each_port(report);
}
};
struct Block::Main
struct Ahci::Block_session_handler : Interface
{
Genode::Env &env;
Genode::Heap heap { env.ram(), env.rm() };
Env &env;
Port &port;
Ram_dataspace_capability ds;
Genode::Attached_rom_dataspace config { env, "config" };
Signal_handler<Block_session_handler> request_handler
{ env.ep(), *this, &Block_session_handler::handle};
Genode::Constructible<Genode::Reporter> reporter { };
Block_session_handler(Env &env, Port &port, size_t buffer_size)
: env(env), port(port), ds(port.alloc_buffer(buffer_size))
{ }
Block::Root_multiple_clients root;
Signal_handler<Main> device_identified {
env.ep(), *this, &Main::handle_device_identified };
Main(Genode::Env &env)
: env(env), root(env, heap, config.xml())
~Block_session_handler()
{
Genode::log("--- Starting AHCI driver ---");
port.free_buffer(ds);
}
virtual void handle_requests()= 0;
void handle()
{
handle_requests();
}
};
struct Ahci::Block_session_component : Rpc_object<Block::Session>,
Block_session_handler,
Block::Request_stream
{
Block_session_component(Env &env, Port &port, size_t buffer_size)
:
Block_session_handler(env, port, buffer_size),
Request_stream(env.rm(), ds, env.ep(), request_handler, port.info())
{
env.ep().manage(*this);
}
~Block_session_component()
{
env.ep().dissolve(*this);
}
Info info() const override { return Request_stream::info(); }
Capability<Tx> tx_cap() override { return Request_stream::tx_cap(); }
void handle_requests() override
{
while (true) {
bool progress = false;
/*
* Acknowledge any pending packets before sending new request to the
* controller.
*/
try_acknowledge([&](Ack &ack) {
port.for_one_completed_request([&] (Block::Request request) {
progress = true;
ack.submit(request);
});
});
with_requests([&] (Block::Request request) {
Response response = Response::RETRY;
/* only READ/WRITE requests, others are noops for now */
if (Block::Operation::has_payload(request.operation.type) == false) {
request.success = true;
progress = true;
return Response::REJECTED;
}
if ((response = port.submit(request)) != Response::RETRY)
progress = true;
return response;
});
if (progress == false) break;
}
/* poke */
wakeup_client_if_needed();
}
};
struct Ahci::Main : Rpc_object<Typed_root<Block::Session>>,
Dispatch
{
Env &env;
Attached_rom_dataspace config { env, "config" };
Constructible<Ahci::Driver> driver { };
Constructible<Reporter> reporter { };
Constructible<Block_session_component> block_session[Driver::MAX_PORTS];
Main(Env &env)
: env(env)
{
log("--- Starting AHCI driver ---");
bool support_atapi = config.xml().attribute_value("atapi", false);
try {
Ahci_driver::init(env, heap, root, support_atapi, device_identified);
} catch (Ahci_driver::Missing_controller) {
Genode::error("no AHCI controller found");
driver.construct(env, *this, support_atapi);
report_ports();
} catch (Ahci::Missing_controller) {
error("no AHCI controller found");
env.parent().exit(~0);
} catch (Genode::Service_denied) {
Genode::error("hardware access denied");
} catch (Service_denied) {
error("hardware access denied");
env.parent().exit(~0);
}
env.parent().announce(env.ep().manage(*this));
}
void session(unsigned index) override
{
if (index > Driver::MAX_PORTS || !block_session[index].constructed()) return;
block_session[index]->handle_requests();
}
Session_capability session(Root::Session_args const &args,
Affinity const &) override
{
Session_label const label = label_from_args(args.string());
Session_policy const policy(label, config.xml());
Ram_quota const ram_quota = ram_quota_from_args(args.string());
size_t const tx_buf_size =
Arg_string::find_arg(args.string(), "tx_buf_size").ulong_value(0);
if (!tx_buf_size)
throw Service_denied();
if (tx_buf_size > ram_quota.value) {
error("insufficient 'ram_quota' from '", label, "',"
" got ", ram_quota, ", need ", tx_buf_size);
throw Insufficient_ram_quota();
}
/* try read device port number attribute */
long device = policy.attribute_value("device", -1L);
/* try read device model and serial number attributes */
auto const model = policy.attribute_value("model", String<64>());
auto const serial = policy.attribute_value("serial", String<64>());
bool const writeable = policy.attribute_value("writeable", false);
try {
Port &port = driver->port(device, model.string(), serial.string());
if (block_session[port.index].constructed()) {
error("Device with number=", port.index, " is already in use");
throw Service_denied();
}
port.writeable(writeable);
block_session[port.index].construct(env, port, tx_buf_size);
return block_session[port.index]->cap();
} catch (...) {
error("rejecting session request, no matching policy for '", label, "'",
" (model=", model, " serial=", serial, " device index=", device, ")");
}
throw Service_denied();
}
void upgrade(Session_capability, Root::Upgrade_args const&) override { }
void close(Session_capability cap) override
{
for (int index = 0; index < Driver::MAX_PORTS; index++) {
if (!block_session[index].constructed() || !(cap == block_session[index]->cap()))
continue;
block_session[index].destruct();
}
}
void handle_device_identified()
void report_ports()
{
try {
Xml_node report = config.xml().sub_node("report");
if (report.attribute_value("ports", false)) {
reporter.construct(env, "ports");
reporter->enabled(true);
Ahci_driver::report_ports(*reporter);
driver->report_ports(*reporter);
}
} catch (Genode::Xml_node::Nonexistent_sub_node) { }
} catch (Xml_node::Nonexistent_sub_node) { }
}
};
void Component::construct(Genode::Env &env) { static Block::Main server(env); }
void Component::construct(Genode::Env &env) { static Ahci::Main server(env); }

View File

@ -1,156 +1,87 @@
/*
* \brief Driver for PCI-bus platforms
* \author Sebastian Sumpf
* \date 2015-04-29
* \date 2020-01-20
*/
/*
* Copyright (C) 2015-2017 Genode Labs GmbH
* Copyright (C) 2020 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 <irq_session/connection.h>
#include <platform_session/connection.h>
#include <platform_device/client.h>
#include <util/reconstructible.h>
#include <ahci.h>
using namespace Genode;
struct X86_hba : Platform::Hba
Ahci::Data::Data(Env &env)
: env(env)
{
enum Pci_config {
CLASS_MASS_STORAGE = 0x10000u,
SUBCLASS_AHCI = 0x600u,
CLASS_MASK = 0xffff00u,
AHCI_DEVICE = CLASS_MASS_STORAGE | SUBCLASS_AHCI,
AHCI_BASE_ID = 0x5, /* resource id of ahci base addr <bar 5> */
PCI_CMD = 0x4,
};
pci_device_cap = pci.with_upgrade(
[&] () { return pci.next_device(pci_device_cap, AHCI_DEVICE,
CLASS_MASK); });
Genode::Env &env;
Platform::Connection pci { env };
Platform::Device_capability pci_device_cap { };
Constructible<Platform::Device_client> pci_device { };
Constructible<Irq_session_client> irq { };
addr_t res_base { 0 };
size_t res_size { 0 };
X86_hba(Genode::Env &env) : env(env)
{
pci_device_cap = pci.with_upgrade(
[&] () { return pci.next_device(pci_device_cap, AHCI_DEVICE,
CLASS_MASK); });
if (!pci_device_cap.valid()) {
throw Ahci_driver::Missing_controller();
}
/* construct pci client */
pci_device.construct(pci_device_cap);
Genode::log("AHCI found ("
"vendor: ", Genode::Hex(pci_device->vendor_id()), " "
"device: ", Genode::Hex(pci_device->device_id()), " "
"class: ", Genode::Hex(pci_device->class_code()), ")");
/* read base address of controller */
Platform::Device::Resource resource = pci_device->resource(AHCI_BASE_ID);
res_base = resource.base();
res_size = resource.size();
uint16_t cmd = pci_device->config_read(PCI_CMD, Platform::Device::ACCESS_16BIT);
cmd |= 0x2; /* respond to memory space accesses */
cmd |= 0x4; /* enable bus master */
_config_write(PCI_CMD, cmd, Platform::Device::ACCESS_16BIT);
irq.construct(pci_device->irq(0));
if (!pci_device_cap.valid()) {
throw Missing_controller();
}
void disable_msi()
{
enum { PM_CAP_OFF = 0x34, MSI_CAP = 0x5, MSI_ENABLED = 0x1 };
uint8_t cap = pci_device->config_read(PM_CAP_OFF, Platform::Device::ACCESS_8BIT);
/* construct pci client */
pci_device.construct(pci_device_cap);
log("AHCI found ("
"vendor: ", Hex(pci_device->vendor_id()), " "
"device: ", Hex(pci_device->device_id()), " "
"class: ", Hex(pci_device->class_code()), ")");
/* iterate through cap pointers */
for (uint16_t val = 0; cap; cap = val >> 8) {
val = pci_device->config_read(cap, Platform::Device::ACCESS_16BIT);
/* map base address of controller */
Io_mem_session_capability iomem_cap = pci_device->io_mem(pci_device->phys_bar_to_virt(AHCI_BASE_ID));
iomem.construct(env.rm(), Io_mem_session_client(iomem_cap).dataspace());
if ((val & 0xff) != MSI_CAP)
continue;
uint16_t cmd = pci_device->config_read(PCI_CMD, ::Platform::Device::ACCESS_16BIT);
cmd |= 0x2; /* respond to memory space accesses */
cmd |= 0x4; /* enable bus master */
_config_write(PCI_CMD, cmd, ::Platform::Device::ACCESS_16BIT);
uint16_t msi = pci_device->config_read(cap + 2, Platform::Device::ACCESS_16BIT);
if (msi & MSI_ENABLED) {
_config_write(cap + 2, msi ^ MSI_CAP,
Platform::Device::ACCESS_8BIT);
Genode::log("disabled MSI ", msi);
}
}
}
void _config_write(uint8_t op, uint16_t cmd,
Platform::Device::Access_size width)
{
Genode::size_t donate = 4096;
Genode::retry<Platform::Out_of_ram>(
[&] () {
Genode::retry<Platform::Out_of_caps>(
[&] () { pci_device->config_write(op, cmd, width); },
[&] () { pci.upgrade_caps(2); });
},
[&] () {
pci.upgrade_ram(donate);
donate *= 2;
});
}
/*******************
** Hba interface **
*******************/
Genode::addr_t base() const override { return res_base; }
Genode::size_t size() const override { return res_size; }
void sigh_irq(Signal_context_capability sigh) override
{
irq->sigh(sigh);
ack_irq();
}
void ack_irq() override { irq->ack_irq(); }
Ram_dataspace_capability
alloc_dma_buffer(Genode::size_t size) override
{
size_t donate = size;
return retry<Genode::Out_of_ram>(
[&] () {
return retry<Genode::Out_of_caps>(
[&] () { return pci.alloc_dma_buffer(size); },
[&] () { pci.upgrade_caps(2); });
},
[&] () {
pci.upgrade_ram(donate);
donate = donate * 2 > size ? 4096 : donate * 2;
});
}
void free_dma_buffer(Genode::Ram_dataspace_capability ds) override
{
pci.free_dma_buffer(ds);
}
};
Platform::Hba &Platform::init(Genode::Env &env, Mmio::Delayer &)
{
static X86_hba h(env);
return h;
irq.construct(pci_device->irq(0));
}
/************************
** Platform interface **
************************/
Genode::addr_t Ahci::Platform::_mmio_base() const
{
return addr_t(_data.iomem->local_addr<addr_t>());
}
void Ahci::Platform::sigh_irq(Signal_context_capability sigh)
{
_data.irq->sigh(sigh);
ack_irq();
}
void Ahci::Platform::ack_irq() { _data.irq->ack_irq(); }
Genode::Ram_dataspace_capability Ahci::Platform::alloc_dma_buffer(size_t size)
{
size_t donate = size;
return retry<Genode::Out_of_ram>(
[&] () {
return retry<Genode::Out_of_caps>(
[&] () { return _data.pci.alloc_dma_buffer(size); },
[&] () { _data.pci.upgrade_caps(2); });
},
[&] () {
_data.pci.upgrade_ram(donate);
donate = donate * 2 > size ? 4096 : donate * 2;
});
}
void Ahci::Platform::free_dma_buffer(Genode::Ram_dataspace_capability ds)
{
_data.pci.free_dma_buffer(ds);
}

View File

@ -0,0 +1,65 @@
#ifndef _AHCI__SPEC__X86__PLATFORM_H_
#define _AHCI__SPEC__X86__PLATFORM_H_
/*
* \brief Driver for PCI-bus platforms
* \author Sebastian Sumpf
* \date 2020-01-20
*/
/*
* Copyright (C) 2020 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 <irq_session/connection.h>
#include <platform_session/connection.h>
#include <platform_device/client.h>
#include <util/reconstructible.h>
namespace Ahci {
struct Data;
using namespace Genode;
}
struct Ahci::Data
{
enum Pci_config {
CLASS_MASS_STORAGE = 0x10000u,
SUBCLASS_AHCI = 0x600u,
CLASS_MASK = 0xffff00u,
AHCI_DEVICE = CLASS_MASS_STORAGE | SUBCLASS_AHCI,
AHCI_BASE_ID = 0x5, /* resource id of ahci base addr <bar 5> */
PCI_CMD = 0x4,
};
Genode::Env &env;
Platform::Connection pci { env };
Platform::Device_capability pci_device_cap { };
Constructible<Platform::Device_client> pci_device { };
Constructible<Irq_session_client> irq { };
Constructible<Attached_dataspace> iomem { };
Data(Env &env);
void _config_write(uint8_t op, uint16_t cmd,
Platform::Device::Access_size width)
{
size_t donate = 4096;
retry<Platform::Out_of_ram>(
[&] () {
retry<Platform::Out_of_caps>(
[&] () { pci_device->config_write(op, cmd, width); },
[&] () { pci.upgrade_caps(2); });
},
[&] () {
pci.upgrade_ram(donate);
donate *= 2;
});
}
};
#endif /* _AHCI__SPEC__X86__PLATFORM_H_ */

View File

@ -1,5 +1,7 @@
TARGET = ahci_drv
REQUIRES = x86
INC_DIR += $(REP_DIR)/src/drivers/ahci/spec/x86
include $(REP_DIR)/src/drivers/ahci/target.inc

View File

@ -1,4 +1,4 @@
SRC_CC += main.cc ahci.cc platform.cc
SRC_CC += main.cc platform.cc
INC_DIR += $(REP_DIR)/src/drivers/ahci
LIBS += base

View File

@ -0,0 +1,71 @@
/*
* \brief Utilitize used by the AHCI driver
* \author Josef Soentgen
* \date 2018-03-05
*/
/*
* Copyright (C) 2018 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 _AHCI_UTIL_H_
#define _AHCI_UTIL_H_
/* Genode includes */
#include <base/fixed_stdint.h>
namespace Util {
using namespace Genode;
/*
* Wrap array into convinient interface
*
* The used datatype T must implement the following methods:
*
* bool valid() const returns true if the object is valid
* void invalidate() adjusts the object so that valid() returns false
*/
template <typename T, size_t CAP>
struct Slots
{
T _entries[CAP] { };
size_t _limit { CAP };
/**
* Get free slot
*/
T *get()
{
for (size_t i = 0; i < _limit; i++) {
if (!_entries[i].valid()) { return &_entries[i]; }
}
return nullptr;
}
/**
* Iterate over all slots until FUNC returns true
*/
template <typename FUNC>
bool for_each(FUNC const &func)
{
for (size_t i = 0; i < _limit; i++) {
if (!_entries[i].valid()) { continue; }
if ( func(_entries[i])) { return true; }
}
return false;
}
size_t index(T const &entry) const
{
size_t index = &entry - _entries;
return index;
}
void limit(size_t limit) { _limit = limit; }
};
}
#endif /* _AHCI_UTIL_H_ */