mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-07 11:27:29 +00:00
ahci: new AHCI driver implementation
Supports native command queueing and multiple ports.
This commit is contained in:
parent
33bc63e7c3
commit
7910b5146f
@ -1,4 +1,4 @@
|
||||
set mkfs_cmd mkfs.vfat
|
||||
set mkfs_cmd [check_installed mkfs.vfat]
|
||||
set mkfs_opts "-F32"
|
||||
set filesystem ffat
|
||||
|
||||
|
@ -6,9 +6,7 @@
|
||||
|
||||
set use_sd_card_drv [expr [have_spec omap4] || [have_spec platform_arndale] || [have_spec pl180]]
|
||||
set use_ahci_drv [have_spec x86]
|
||||
|
||||
if {[catch { exec which mkfs.vfat } ]} {
|
||||
puts stderr "Error: mkfs.vfat not installed, aborting test"; exit }
|
||||
set mkfs [check_installed mkfs.vfat]
|
||||
|
||||
if {[expr [have_spec linux] || [have_spec hw_odroid_xu]]} {
|
||||
puts "Run script does not support this platform"; exit }
|
||||
@ -85,22 +83,30 @@ append_if [have_spec acpi] config {
|
||||
<service name="IRQ" />
|
||||
</provides>
|
||||
<route>
|
||||
<service name="PCI"> <any-child /> </service>
|
||||
<any-service> <parent/> <any-child /> </any-service>
|
||||
</route>
|
||||
<config>
|
||||
<policy label="acpi_drv"><pci class="ALL" /></policy>
|
||||
<policy label="ahci_drv"><pci class="AHCI" /></policy>
|
||||
</config>
|
||||
</start>}
|
||||
|
||||
append_if [expr ![have_spec acpi] && [have_spec pci]] config {
|
||||
<start name="pci_drv">
|
||||
<resource name="RAM" quantum="5M" constrain_phys="yes"/>
|
||||
<provides><service name="PCI"/></provides>
|
||||
<config>
|
||||
<policy label="ahci_drv"/><pci class="AHCI" /></policy>
|
||||
</config>
|
||||
</start>}
|
||||
|
||||
append_if $use_ahci_drv config {
|
||||
<start name="ahci">
|
||||
<start name="ahci_drv">
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<provides> <service name="Block"/> </provides>
|
||||
<config ata="yes" />
|
||||
<config>
|
||||
<policy label="ffat_fs" device="0" />
|
||||
</config>
|
||||
</start>
|
||||
}
|
||||
|
||||
@ -130,7 +136,7 @@ set boot_modules {
|
||||
|
||||
lappend_if [have_spec pci] boot_modules pci_drv
|
||||
lappend_if [have_spec acpi] boot_modules acpi_drv
|
||||
lappend_if $use_ahci_drv boot_modules ahci
|
||||
lappend_if $use_ahci_drv boot_modules ahci_drv
|
||||
lappend_if $use_sd_card_drv boot_modules sd_card_drv
|
||||
|
||||
build_boot_image $boot_modules
|
||||
@ -144,7 +150,7 @@ set cmd "dd if=/dev/zero of=$disk_image bs=1024 count=65536"
|
||||
puts "creating disk image: $cmd"
|
||||
catch { exec sh -c $cmd }
|
||||
|
||||
set cmd "mkfs.vfat -F32 $disk_image"
|
||||
set cmd "$mkfs -F32 $disk_image"
|
||||
puts "formating disk image with vfat file system: $cmd"
|
||||
catch { exec sh -c $cmd }
|
||||
|
||||
|
@ -126,16 +126,17 @@ append_if [expr ![have_spec acpi] && [have_spec pci]] config {
|
||||
|
||||
append_if $use_ahci config {
|
||||
<start name="ahci">
|
||||
<binary name="ahci" />
|
||||
<binary name="ahci_drv" />
|
||||
<resource name="RAM" quantum="10M" />
|
||||
<provides><service name="Block" /></provides>
|
||||
<route>}
|
||||
|
||||
append_if [expr $use_ahci && [have_spec acpi]] config {
|
||||
<service name="IRQ"><child name="acpi" /></service>}
|
||||
append_if $use_ahci config {
|
||||
<route>
|
||||
<any-service> <parent /> <any-child /></any-service>
|
||||
</route>
|
||||
<config>}
|
||||
append_if $use_ahci config "
|
||||
<policy label=\"test-libc_$filesystem\" device=\"0\" />"
|
||||
append_if $use_ahci config {
|
||||
</config>
|
||||
</start>}
|
||||
|
||||
append_if $use_sd_card_drv config {
|
||||
@ -167,7 +168,7 @@ append boot_modules libc_$filesystem.lib.so
|
||||
|
||||
lappend_if [have_spec pci] boot_modules pci_drv
|
||||
lappend_if [have_spec acpi] boot_modules acpi_drv
|
||||
lappend_if $use_ahci boot_modules ahci
|
||||
lappend_if $use_ahci boot_modules ahci_drv
|
||||
lappend_if $use_sd_card_drv boot_modules sd_card_drv
|
||||
|
||||
build_boot_image $boot_modules
|
||||
|
@ -129,6 +129,25 @@ proc drivers_start_nodes { feature_arg } {
|
||||
<service name="PCI"/>
|
||||
<service name="IRQ" />
|
||||
</provides>
|
||||
<binary name="acpi_drv"/>
|
||||
<provides>
|
||||
<service name="PCI"/>
|
||||
<service name="IRQ" />
|
||||
</provides>
|
||||
<config>
|
||||
<policy label="acpi_drv">
|
||||
<pci class="ALL"/>
|
||||
</policy>
|
||||
<policy label="ahci">
|
||||
<pci class="AHCI"/>
|
||||
</policy>
|
||||
<policy label="ps2_drv">
|
||||
<device name="PS2" />
|
||||
</policy>
|
||||
<policy label="fb_drv">
|
||||
<pci class="VGA" />
|
||||
</policy>
|
||||
</config>
|
||||
<route>
|
||||
<service name="PCI"> <any-child /> </service>
|
||||
<any-service> <parent/> <any-child /> </any-service>
|
||||
@ -179,6 +198,17 @@ proc drivers_start_nodes { feature_arg } {
|
||||
<start name="pci_drv">
|
||||
<resource name="RAM" quantum="2M" constrain_phys="yes"/>
|
||||
<provides><service name="PCI"/></provides>
|
||||
<config>
|
||||
<policy label="ahci">
|
||||
<pci class="AHCI"/>
|
||||
</policy>
|
||||
<policy label="ps2_drv">
|
||||
<device name="PS2" />
|
||||
</policy>
|
||||
<policy label="fb_drv">
|
||||
<pci class="VGA" />
|
||||
</policy>
|
||||
</config>
|
||||
</start>
|
||||
}
|
||||
|
||||
|
@ -51,9 +51,12 @@ append config [qt5_start_nodes feature]
|
||||
|
||||
append_if $use_ahci_driver config {
|
||||
<start name="ahci">
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<binary name="ahci_drv" />
|
||||
<resource name="RAM" quantum="5M"/>
|
||||
<provides> <service name="Block"/> </provides>
|
||||
<config ata="yes" />
|
||||
<config>
|
||||
<policy label="ffat_fs" device="0" />
|
||||
</config>
|
||||
</start>}
|
||||
|
||||
append_if $use_sd_card_driver config {
|
||||
@ -145,7 +148,7 @@ append boot_modules {
|
||||
lappend_if [have_spec linux] boot_modules ram_fs
|
||||
lappend_if [expr ![have_spec linux]] boot_modules ffat_fs
|
||||
lappend_if $use_sd_card_driver boot_modules sd_card_drv
|
||||
lappend_if $use_ahci_driver boot_modules ahci
|
||||
lappend_if $use_ahci_driver boot_modules ahci_drv
|
||||
|
||||
build_boot_image $boot_modules
|
||||
|
||||
|
@ -1,17 +0,0 @@
|
||||
#
|
||||
# \brief Generic toolchain configurations for AHCI
|
||||
# \author Martin Stein <martin.stein@genode-labs.com>
|
||||
# \date 2013-05-17
|
||||
#
|
||||
|
||||
# add C++ sources
|
||||
SRC_CC += main.cc
|
||||
|
||||
# add library dependencies
|
||||
LIBS += base
|
||||
|
||||
# add include directories
|
||||
INC_DIR += $(REP_DIR)/src/drivers/ahci/include
|
||||
|
||||
# declare source paths
|
||||
vpath main.cc $(REP_DIR)/src/drivers/ahci
|
@ -1,17 +0,0 @@
|
||||
#
|
||||
# \brief Toolchain configurations for AHCI on Exynos
|
||||
# \author Martin Stein <martin.stein@genode-labs.com>
|
||||
# \date 2013-05-17
|
||||
#
|
||||
|
||||
# add C++ sources
|
||||
SRC_CC += ahci_driver.cc
|
||||
|
||||
# add include directories
|
||||
INC_DIR += $(REP_DIR)/src/drivers/ahci/exynos5
|
||||
|
||||
# declare source paths
|
||||
vpath ahci_driver.cc $(REP_DIR)/src/drivers/ahci/exynos5
|
||||
|
||||
# insert configurations that are less specific
|
||||
include $(REP_DIR)/lib/mk/ahci.inc
|
6
repos/os/lib/mk/exynos5/ahci_platform.mk
Normal file
6
repos/os/lib/mk/exynos5/ahci_platform.mk
Normal file
@ -0,0 +1,6 @@
|
||||
SRC_CC = platform.cc
|
||||
|
||||
INC_DIR += $(REP_DIR)/src/drivers/ahci
|
||||
|
||||
vpath platform.cc $(REP_DIR)/src/drivers/ahci/exynos5
|
||||
|
@ -1,11 +0,0 @@
|
||||
#
|
||||
# \brief Toolchain configurations for AHCI on X86
|
||||
# \author Martin Stein <martin.stein@genode-labs.com>
|
||||
# \date 2013-05-17
|
||||
#
|
||||
|
||||
# add include directories
|
||||
INC_DIR += $(REP_DIR)/src/drivers/ahci/x86
|
||||
|
||||
# include less specific config
|
||||
include $(REP_DIR)/lib/mk/ahci.inc
|
6
repos/os/lib/mk/x86/ahci_platform.mk
Normal file
6
repos/os/lib/mk/x86/ahci_platform.mk
Normal file
@ -0,0 +1,6 @@
|
||||
SRC_CC = platform.cc
|
||||
|
||||
INC_DIR += $(REP_DIR)/src/drivers/ahci
|
||||
|
||||
vpath platform.cc $(REP_DIR)/src/drivers/ahci/x86
|
||||
|
@ -1,19 +1,27 @@
|
||||
This directory contains an implementation of a simple AHCI driver.
|
||||
This directory contains the implementation of Genode's AHCI driver
|
||||
|
||||
Behavior
|
||||
--------
|
||||
|
||||
The server implements Genode's new block-driver API ('os/include/block'), thus
|
||||
exposing the block-session interface as front-end. AHCI depends on Genode's PCI
|
||||
driver as well as the timer server. For a usage example see: 'os/run/ahci.run'.
|
||||
The driver supports x86 32/64 bit platforms and the Exynos5 SOC. If
|
||||
more than one AHCI controller is present, the first one will be used.
|
||||
Each active device on each AHCI port will be represented by a Genode
|
||||
block session. The server must be configured via a policy, that states
|
||||
which client can access a certain device:
|
||||
|
||||
Limitations and known issues
|
||||
----------------------------
|
||||
|
||||
Currently, the server scans the PCI bus at startup and retrieves the first available
|
||||
AHCI controller, scans the controller ports and uses the first non-ATAPI port
|
||||
where a device is present.
|
||||
!<start name="ahci">
|
||||
! <binary name="ahci_drv" />
|
||||
! <resource name="RAM" quantum="10M" />
|
||||
! <provides><service name="Block" /></provides> }
|
||||
! <route>
|
||||
! <any-service> <parent /> <any-child /> </any-service>
|
||||
! </route>
|
||||
! <config>
|
||||
! <policy label="test-ahci" device="0" />
|
||||
! <policy label="bench" device="1" />
|
||||
! </config>
|
||||
!</start>
|
||||
|
||||
On real hardware and on kernels taking advantage of I/O APICs (namely NOVA and
|
||||
Fiasco.OC) we still lack support for ACPI parsing and thus for interrupts,
|
||||
leading to a non-working driver.
|
||||
In the example above, a session request labeled with "test-ahci"
|
||||
gains access to device 0, while "bench" gains access to device 1.
|
||||
|
208
repos/os/src/drivers/ahci/ahci.cc
Normal file
208
repos/os/src/drivers/ahci/ahci.cc
Normal file
@ -0,0 +1,208 @@
|
||||
/**
|
||||
* \brief AHCI port controller handling
|
||||
* \author Sebastian Sumpf
|
||||
* \date 2015-04-29
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#include <timer_session/connection.h>
|
||||
#include "ata_driver.h"
|
||||
#include "atapi_driver.h"
|
||||
|
||||
|
||||
struct Timer_delayer : Mmio::Delayer, Timer::Connection
|
||||
{
|
||||
void usleep(unsigned us) { Timer::Connection::usleep(us); }
|
||||
};
|
||||
|
||||
|
||||
Mmio::Delayer &Hba::delayer()
|
||||
{
|
||||
static Timer_delayer d;
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
struct Ahci
|
||||
{
|
||||
/* read device signature */
|
||||
enum Signature {
|
||||
ATA_SIG = 0x101,
|
||||
ATAPI_SIG = 0xeb140101,
|
||||
ATAPI_SIG_QEMU = 0xeb140000, /* will be fixed in Qemu */
|
||||
};
|
||||
|
||||
Ahci_root &root;
|
||||
Platform::Hba &platform_hba = Platform::init(Hba::delayer());
|
||||
Hba hba = { platform_hba };
|
||||
|
||||
enum { MAX_PORTS = 32 };
|
||||
Port_driver *ports[MAX_PORTS];
|
||||
bool port_claimed[MAX_PORTS];
|
||||
|
||||
Signal_rpc_member<Ahci> irq;
|
||||
Signal_rpc_member<Ahci> device_ready;
|
||||
unsigned ready_count = 0;
|
||||
|
||||
Ahci(Ahci_root &root)
|
||||
: root(root), irq(root.entrypoint(), *this, &Ahci::handle_irq),
|
||||
device_ready(root.entrypoint(), *this, &Ahci::ready)
|
||||
{
|
||||
info();
|
||||
|
||||
/* register irq handler */
|
||||
platform_hba.sigh_irq(irq);
|
||||
|
||||
/* initialize HBA (IRQs, memory) */
|
||||
hba.init();
|
||||
|
||||
/* search for devices */
|
||||
scan_ports();
|
||||
}
|
||||
|
||||
bool is_atapi(unsigned sig)
|
||||
{
|
||||
return sig == ATAPI_SIG_QEMU || sig == ATAPI_SIG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward IRQs to ports
|
||||
*/
|
||||
void handle_irq(unsigned)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
void ready(unsigned)
|
||||
{
|
||||
if (--ready_count)
|
||||
return;
|
||||
|
||||
/* announce service */
|
||||
root.announce();
|
||||
}
|
||||
|
||||
|
||||
void info()
|
||||
{
|
||||
PINF("\tversion: %x.%04x", hba.read<Hba::Version::Major>(),
|
||||
hba.read<Hba::Version::Minor>());
|
||||
PINF("\tcommand slots: %u", hba.command_slots());
|
||||
PINF("\tnative command queuing: %s", hba.ncg() ? "yes" : "no");
|
||||
PINF("\t64 bit support: %s", hba.supports_64bit() ? "yes" : "no");
|
||||
}
|
||||
|
||||
void scan_ports()
|
||||
{
|
||||
PINF("\tnumber of ports: %u pi: %x", hba.port_count(), hba.read<Hba::Pi>());
|
||||
unsigned avaiable = hba.read<Hba::Pi>();
|
||||
for (unsigned i = 0; i < hba.port_count(); i++) {
|
||||
|
||||
/* check if port is implemented */
|
||||
if (!(avaiable & (1U << i)))
|
||||
continue;
|
||||
|
||||
Port port(hba, platform_hba, i);
|
||||
bool enabled = port.enable();
|
||||
unsigned sig = port.read<Port::Sig>();
|
||||
|
||||
PINF("\t\t#%u: %s", i, enabled ?
|
||||
(is_atapi(sig) ? "ATAPI" : "ATA") :
|
||||
"off");
|
||||
|
||||
if (!enabled)
|
||||
continue;
|
||||
|
||||
|
||||
switch (sig) {
|
||||
|
||||
case ATA_SIG:
|
||||
ports[i] = new (Genode::env()->heap()) Ata_driver(port, device_ready);
|
||||
ready_count++;
|
||||
break;
|
||||
|
||||
case ATAPI_SIG:
|
||||
case ATAPI_SIG_QEMU:
|
||||
ports[i] = new (Genode::env()->heap()) Atapi_driver(port, device_ready);
|
||||
ready_count++;
|
||||
break;
|
||||
|
||||
default:
|
||||
PWRN("Device signature %x unsupported", sig);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Block::Driver *claim_port(unsigned port_num)
|
||||
{
|
||||
if (!is_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 is_avail(unsigned port_num)
|
||||
{
|
||||
return port_num < MAX_PORTS && ports[port_num] && !port_claimed[port_num] &&
|
||||
ports[port_num]->ready();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static Ahci *sata_ahci(Ahci *ahci = 0)
|
||||
{
|
||||
static Ahci *a = ahci;
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
void Ahci_driver::init(Ahci_root &root)
|
||||
{
|
||||
static Ahci ahci(root);
|
||||
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::is_avail(long device_num)
|
||||
{
|
||||
return sata_ahci()->is_avail(device_num);
|
||||
}
|
||||
|
||||
|
||||
|
866
repos/os/src/drivers/ahci/ahci.h
Normal file
866
repos/os/src/drivers/ahci/ahci.h
Normal file
@ -0,0 +1,866 @@
|
||||
/**
|
||||
* \brief Generic AHCI controller definitions
|
||||
* \author Sebasitan Sumpf
|
||||
* \date 2015-04-29
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _INCLUDE__AHCI_H_
|
||||
#define _INCLUDE__AHCI_H_
|
||||
|
||||
#include <block/component.h>
|
||||
#include <os/attached_mmio.h>
|
||||
#include <util/volatile_object.h>
|
||||
|
||||
static bool constexpr verbose = false;
|
||||
|
||||
namespace Platform {
|
||||
struct Hba;
|
||||
Hba &init(Genode::Mmio::Delayer &delayer);
|
||||
};
|
||||
|
||||
|
||||
struct Ahci_root
|
||||
{
|
||||
virtual Server::Entrypoint &entrypoint() = 0;
|
||||
virtual void announce() = 0;
|
||||
};
|
||||
|
||||
|
||||
namespace Ahci_driver {
|
||||
|
||||
void init(Ahci_root &ep);
|
||||
|
||||
bool is_avail(long device_num);
|
||||
|
||||
Block::Driver *claim_port(long device_num);
|
||||
void free_port(long device_num);
|
||||
}
|
||||
|
||||
|
||||
struct Platform::Hba
|
||||
{
|
||||
/**
|
||||
* Return base address and size of HBA device registers
|
||||
*/
|
||||
virtual Genode::addr_t base() const = 0;
|
||||
virtual Genode::size_t size() const = 0;
|
||||
|
||||
/**
|
||||
* Register interrupt signal context
|
||||
*/
|
||||
virtual void sigh_irq(Genode::Signal_context_capability sigh) = 0;
|
||||
virtual void ack_irq() = 0;
|
||||
|
||||
/**
|
||||
* DMA
|
||||
*/
|
||||
virtual Genode::Ram_dataspace_capability alloc_dma_buffer(Genode::size_t size) = 0;
|
||||
virtual void free_dma_buffer(Genode::Ram_dataspace_capability ds) = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* HBA definitions
|
||||
*/
|
||||
struct Hba : Genode::Attached_mmio
|
||||
{
|
||||
Hba(Platform::Hba &hba)
|
||||
: Attached_mmio(hba.base(), hba.size()) { }
|
||||
|
||||
/**
|
||||
* Host capabilites
|
||||
*/
|
||||
struct Cap : Register<0x0, 32>
|
||||
{
|
||||
struct Np : Bitfield<0, 4> { }; /* number of ports */
|
||||
struct Ncs : Bitfield<8, 5> { }; /* number of command slots */
|
||||
struct Iss : Bitfield<20, 4> { }; /* interface speed support */
|
||||
struct Sncg : Bitfield<30, 1> { }; /* supports native command queuing */
|
||||
struct Sa64 : Bitfield<31, 1> { }; /* supports 64 bit addressing */
|
||||
};
|
||||
|
||||
unsigned port_count() { return read<Cap::Np>() + 1; }
|
||||
unsigned command_slots() { return read<Cap::Ncs>() + 1; }
|
||||
bool ncg() { return !!read<Cap::Sncg>(); }
|
||||
bool supports_64bit(){ return !!read<Cap::Sa64>(); }
|
||||
|
||||
/**
|
||||
* Generic host control
|
||||
*/
|
||||
struct Ghc : Register<0x4, 32>
|
||||
{
|
||||
struct Hr : Bitfield<0, 1> { }; /* hard reset */
|
||||
struct Ie : Bitfield<1, 1> { }; /* interrupt enable */
|
||||
struct Ae : Bitfield<31, 1> { }; /* AHCI enable */
|
||||
};
|
||||
|
||||
/**
|
||||
* Interrupt status
|
||||
*/
|
||||
struct Is : Register<0x8, 32> { };
|
||||
|
||||
void ack_irq() { write<Is>(read<Is>()); }
|
||||
|
||||
/**
|
||||
* Ports implemented
|
||||
*/
|
||||
struct Pi : Register<0xc, 32> { };
|
||||
|
||||
/**
|
||||
* AHCI version
|
||||
*/
|
||||
struct Version : Register<0x10, 32>
|
||||
{
|
||||
struct Minor : Bitfield<0, 16> { };
|
||||
struct Major : Bitfield<16, 16> { };
|
||||
};
|
||||
|
||||
struct Cap2 : Register<0x24, 32> { };
|
||||
|
||||
void init()
|
||||
{
|
||||
/* enable AHCI */
|
||||
write<Ghc::Ae>(1);
|
||||
|
||||
/* enable interrupts */
|
||||
write<Ghc::Ie>(1);
|
||||
}
|
||||
|
||||
static Mmio::Delayer &delayer();
|
||||
};
|
||||
|
||||
|
||||
struct Device_fis : Genode::Mmio
|
||||
{
|
||||
struct Status : Register<0x2, 8>
|
||||
{
|
||||
/* ATAPI bits */
|
||||
struct Device_ready : Bitfield<6, 1> { };
|
||||
};
|
||||
struct Error : Register<0x3, 8> { };
|
||||
|
||||
Device_fis(Genode::addr_t recv_base)
|
||||
: Mmio(recv_base + 0x40) { }
|
||||
};
|
||||
|
||||
|
||||
struct Command_fis : Genode::Mmio
|
||||
{
|
||||
struct Type : Register<0x0, 8> { }; /* FIS type */
|
||||
struct Bits : Register<0x1, 8, 1>
|
||||
{
|
||||
struct C : Bitfield<7, 1> { }; /* update of command register */
|
||||
};
|
||||
struct Command : Register<0x2, 8, 1> { }; /* ATA command */
|
||||
struct Features0_7 : Register<0x3, 8> { };
|
||||
|
||||
/* big-endian use byte access */
|
||||
struct Lba0_7 : Register<0x4, 8> { };
|
||||
struct Lba8_15 : Register<0x5, 8> { };
|
||||
struct Lba16_23 : Register<0x6, 8> { };
|
||||
struct Lba24 : Genode::Bitset_3<Lba0_7, Lba8_15, Lba16_23> { };
|
||||
struct Device : Register<0x7, 8>
|
||||
{
|
||||
struct Lba : Bitfield<6, 1> { }; /* enable LBA mode */
|
||||
};
|
||||
|
||||
/* big endian */
|
||||
struct Lba24_31 : Register<0x8, 8> { };
|
||||
struct Lba32_39 : Register<0x9, 8> { };
|
||||
struct Lba40_47 : Register<0xa, 8> { };
|
||||
struct Features8_15 : Register<0xb, 8> { };
|
||||
struct Features : Genode::Bitset_2<Features0_7, Features8_15> { };
|
||||
struct Lba48 : Genode::Bitset_3<Lba24_31, Lba32_39, Lba40_47> { };
|
||||
struct Lba : Genode::Bitset_2<Lba24, Lba48> { }; /* LBA 0 - 47 */
|
||||
|
||||
/* big endian */
|
||||
struct Sector0_7 : Register<0xc, 8, 1>
|
||||
{
|
||||
struct Tag : Bitfield<3, 5> { };
|
||||
};
|
||||
struct Sector8_15 : Register<0xd, 8> { };
|
||||
struct Sector : Genode::Bitset_2<Sector0_7, Sector8_15> { }; /* sector count */
|
||||
|
||||
Command_fis(Genode::addr_t base)
|
||||
: Mmio(base)
|
||||
{
|
||||
clear();
|
||||
|
||||
enum { HOST_TO_DEVICE = 0x27 };
|
||||
write<Type>(HOST_TO_DEVICE);
|
||||
}
|
||||
|
||||
static constexpr Genode::size_t size() { return 0x14; }
|
||||
void clear() { Genode::memset((void *)base, 0, size()); }
|
||||
|
||||
|
||||
/************************
|
||||
** ATA spec commands **
|
||||
************************/
|
||||
|
||||
void identify_device()
|
||||
{
|
||||
write<Bits::C>(1);
|
||||
write<Device::Lba>(0);
|
||||
write<Command>(0xec);
|
||||
}
|
||||
|
||||
void dma_ext(bool read, Block::sector_t block_number, Genode::size_t block_count)
|
||||
{
|
||||
write<Bits::C>(1);
|
||||
write<Device::Lba>(1);
|
||||
/* read_dma_ext : write_dma_ext */
|
||||
write<Command>(read ? 0x25 : 0x35);
|
||||
write<Lba>(block_number);
|
||||
write<Sector>(block_count);
|
||||
}
|
||||
|
||||
void fpdma(bool read, Block::sector_t block_number, Genode::size_t block_count,
|
||||
unsigned slot)
|
||||
{
|
||||
write<Bits::C>(1);
|
||||
write<Device::Lba>(1);
|
||||
/* read_fpdma : write_fpdma */
|
||||
write<Command>(read ? 0x60 : 0x61);
|
||||
write<Lba>(block_number);
|
||||
write<Features>(block_count);
|
||||
write<Sector0_7::Tag>(slot);
|
||||
}
|
||||
|
||||
void atapi()
|
||||
{
|
||||
write<Bits::C>(1);
|
||||
/* packet command */
|
||||
write<Command>(0xa0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* AHCI command list structure header
|
||||
*/
|
||||
struct Command_header : Genode::Mmio
|
||||
{
|
||||
struct Bits : Register<0x0, 16>
|
||||
{
|
||||
struct Cfl : Bitfield<0, 5> { }; /* command FIS length */
|
||||
struct A : Bitfield<5, 1> { }; /* ATAPI command flag */
|
||||
struct W : Bitfield<6, 1> { }; /* write flag */
|
||||
struct P : Bitfield<7, 1> { }; /* prefetchable flag */
|
||||
struct C : Bitfield<10, 1> { }; /* clear busy upon R_OK */
|
||||
};
|
||||
|
||||
struct Prdtl : Register<0x2, 16> { }; /* physical region descr. length */
|
||||
struct Prdbc : Register<0x4, 32> { }; /* PRD byte count */
|
||||
struct Ctba0 : Register<0x8, 32> { }; /* command table base addr (low) */
|
||||
struct Ctba0_u0 : Register<0xc, 32> { }; /* command table base addr (upper) */
|
||||
|
||||
Command_header(Genode::addr_t base) : Mmio(base) { }
|
||||
|
||||
void cmd_table_base(Genode::addr_t base_phys)
|
||||
{
|
||||
Genode::uint64_t addr = base_phys;
|
||||
write<Ctba0>(addr);
|
||||
write<Ctba0_u0>(addr >> 32);
|
||||
write<Prdtl>(1);
|
||||
write<Bits::Cfl>(Command_fis::size() / sizeof(unsigned));
|
||||
}
|
||||
|
||||
void clear_byte_count()
|
||||
{
|
||||
write<Prdbc>(0);
|
||||
}
|
||||
|
||||
void atapi_command()
|
||||
{
|
||||
write<Bits::A>(1);
|
||||
}
|
||||
|
||||
static constexpr Genode::size_t size() { return 0x20; }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* ATAPI packet 12 or 16 bytes
|
||||
*/
|
||||
struct Atapi_command : Genode::Mmio
|
||||
{
|
||||
struct Command : Register<0, 8> { };
|
||||
|
||||
/* LBA32 big endian */
|
||||
struct Lba24_31 : Register<0x2, 8> { };
|
||||
struct Lba16_23 : Register<0x3, 8> { };
|
||||
struct Lba8_15 : Register<0x4, 8> { };
|
||||
struct Lba0_7 : Register<0x5, 8> { };
|
||||
struct Lba24 : Genode::Bitset_3<Lba0_7, Lba8_15, Lba16_23> { };
|
||||
struct Lba : Genode::Bitset_2<Lba24, Lba24_31> { };
|
||||
|
||||
/* sector count big endian */
|
||||
struct Sector8_15 : Register<0x8, 8> { };
|
||||
struct Sector0_7 : Register<0x9, 8> { };
|
||||
struct Sector : Genode::Bitset_2<Sector0_7, Sector8_15> { };
|
||||
|
||||
|
||||
Atapi_command(Genode::addr_t base) : Mmio(base)
|
||||
{
|
||||
Genode::memset((void *)base, 0, 16);
|
||||
}
|
||||
|
||||
void read_capacity()
|
||||
{
|
||||
write<Command>(0x25);
|
||||
}
|
||||
|
||||
void test_unit_ready()
|
||||
{
|
||||
write<Command>(0x0);
|
||||
}
|
||||
|
||||
void read_sense()
|
||||
{
|
||||
write<Command>(0x3);
|
||||
write<Lba8_15>(18);
|
||||
}
|
||||
|
||||
void read10(Block::sector_t block_number, Genode::size_t block_count)
|
||||
{
|
||||
write<Command>(0x28);
|
||||
write<Lba>(block_number);
|
||||
write<Sector>(block_count);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Physical region descritpor table
|
||||
*/
|
||||
struct Prdt : Genode::Mmio
|
||||
{
|
||||
struct Dba : Register<0x0, 32> { }; /* data base address */
|
||||
struct Dbau : Register<0x4, 32> { }; /* data base address upper 32 bits */
|
||||
|
||||
struct Bits : Register<0xc, 32>
|
||||
{
|
||||
struct Dbc : Bitfield<0, 22> { }; /* data byte count */
|
||||
struct Irq : Bitfield<31,1> { }; /* interrupt completion */
|
||||
};
|
||||
|
||||
Prdt(Genode::addr_t base, Genode::addr_t phys, Genode::size_t bytes)
|
||||
: Mmio(base)
|
||||
{
|
||||
Genode::uint64_t addr = phys;
|
||||
write<Dba>(addr);
|
||||
write<Dbau>(addr >> 32);
|
||||
write<Bits::Dbc>(bytes > 0 ? bytes - 1 : 0);
|
||||
}
|
||||
|
||||
static constexpr Genode::size_t size() { return 0x10; }
|
||||
};
|
||||
|
||||
|
||||
struct Command_table
|
||||
{
|
||||
Command_fis fis;
|
||||
Atapi_command atapi_cmd;
|
||||
|
||||
/* in Genode we only need one PRD (for one packet) */
|
||||
Prdt prdt;
|
||||
|
||||
Command_table(Genode::addr_t base,
|
||||
Genode::addr_t phys,
|
||||
Genode::size_t bytes = 0)
|
||||
: fis(base), atapi_cmd(base + 0x40),
|
||||
prdt(base + 0x80, phys, bytes)
|
||||
{ }
|
||||
|
||||
static constexpr Genode::size_t size() { return 0x100; }
|
||||
};
|
||||
|
||||
|
||||
struct Identity : Genode::Mmio
|
||||
{
|
||||
Identity(Genode::addr_t base) : Mmio(base) { }
|
||||
|
||||
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()
|
||||
{
|
||||
PLOG("\t\tqueue depth: %u ncg: %u",
|
||||
read<Queue_depth::Max_depth>() + 1,
|
||||
read<Sata_caps::Ncq_support>());
|
||||
PLOG("\t\tnumer of sectors: %llu", read<Sector_count>());
|
||||
PLOG("\t\tmultiple logical blocks per physical: %s",
|
||||
read<Logical_block::Multiple>() ? "yes" : "no");
|
||||
PLOG("\t\tlogical blocks per physical: %u",
|
||||
1U << read<Logical_block::Per_physical>());
|
||||
PLOG("\t\tlogical block size is above 512 byte: %s",
|
||||
read<Logical_block::Longer_512>() ? "yes" : "no");
|
||||
PLOG("\t\twords (16bit) per logical block: %u",
|
||||
read<Logical_words>());
|
||||
PLOG("\t\toffset of first logical block within physical: %u",
|
||||
read<Alignment::Logical_offset>());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* AHCI port
|
||||
*/
|
||||
struct Port : Genode::Mmio
|
||||
{
|
||||
Hba &hba;
|
||||
Platform::Hba &platform_hba;
|
||||
unsigned cmd_slots = hba.command_slots();
|
||||
|
||||
Genode::Ram_dataspace_capability device_ds;
|
||||
Genode::Ram_dataspace_capability cmd_ds;
|
||||
Genode::Ram_dataspace_capability device_info_ds;
|
||||
|
||||
Genode::addr_t cmd_list = 0;
|
||||
Genode::addr_t fis_base = 0;
|
||||
Genode::addr_t cmd_table = 0;
|
||||
Genode::addr_t device_info = 0;
|
||||
|
||||
enum State {
|
||||
NONE,
|
||||
STATUS,
|
||||
TEST_READY,
|
||||
IDENTIFY,
|
||||
READY,
|
||||
};
|
||||
|
||||
State state = NONE;
|
||||
|
||||
Port(Hba &hba, Platform::Hba &platform_hba, unsigned number)
|
||||
: Mmio(hba.base + offset() + (number * size())), hba(hba),
|
||||
platform_hba(platform_hba)
|
||||
{
|
||||
stop();
|
||||
if (!wait_for<Cmd::Cr>(0, Hba::delayer(), 500, 1000))
|
||||
PERR("failed to stop command list processing");
|
||||
}
|
||||
|
||||
virtual ~Port()
|
||||
{
|
||||
if (device_ds.valid()) {
|
||||
Genode::env()->rm_session()->detach((void *)cmd_list);
|
||||
platform_hba.free_dma_buffer(device_ds);
|
||||
}
|
||||
|
||||
if (cmd_ds.valid()) {
|
||||
Genode::env()->rm_session()->detach((void *)cmd_table);
|
||||
platform_hba.free_dma_buffer(cmd_ds);
|
||||
}
|
||||
|
||||
if (device_info_ds.valid()) {
|
||||
Genode::env()->rm_session()->detach((void*)device_info);
|
||||
platform_hba.free_dma_buffer(device_info_ds);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr Genode::addr_t offset() { return 0x100; }
|
||||
static constexpr Genode::size_t size() { return 0x80; }
|
||||
|
||||
/**
|
||||
* Command list base (1K length naturally aligned)
|
||||
*/
|
||||
struct Clb : Register<0x0, 32> { };
|
||||
|
||||
/**
|
||||
* Command list base upper 32 bit
|
||||
*/
|
||||
struct Clbu : Register<0x4, 32> { };
|
||||
|
||||
void command_list_base(Genode::addr_t phys)
|
||||
{
|
||||
Genode::uint64_t addr = phys;
|
||||
write<Clb>(addr);
|
||||
write<Clbu>(addr >> 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* FIS base address (256 bytes naturally aligned)
|
||||
*/
|
||||
struct Fb : Register<0x8, 32> { };
|
||||
|
||||
/**
|
||||
* FIS base address upper 32 bit
|
||||
*/
|
||||
struct Fbu : Register<0xc, 32> { };
|
||||
|
||||
void fis_rcv_base(Genode::addr_t phys)
|
||||
{
|
||||
Genode::uint64_t addr = phys;
|
||||
write<Fb>(addr);
|
||||
write<Fbu>(addr >> 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Port interrupt status
|
||||
*/
|
||||
struct Is : Register<0x10, 32, 1>
|
||||
{
|
||||
struct Dhrs : Bitfield<0, 1> { }; /* device to host register FIS */
|
||||
struct Pss : Bitfield<1, 1> { }; /* PIO setup FIS */
|
||||
struct Dss : Bitfield<2, 1> { }; /* DMA setup FIS */
|
||||
struct Sdbs : Bitfield<3, 1> { }; /* Set device bit */
|
||||
struct Pcs : Bitfield<6, 1> { }; /* port connect change status */
|
||||
struct Prcs : Bitfield<22, 1> { }; /* PhyRdy change status */
|
||||
struct Infs : Bitfield<26, 1> { }; /* interface non-fatal error */
|
||||
struct Ifs : Bitfield<27, 1> { }; /* interface fatal error */
|
||||
};
|
||||
|
||||
void ack_irq()
|
||||
{
|
||||
Is::access_t status = read <Is>();
|
||||
|
||||
/* clear Serr.Diag.x */
|
||||
if (Is::Pcs::get(status))
|
||||
write<Serr::Diag::X>(0);
|
||||
|
||||
/* clear Serr.Diag.n */
|
||||
if (Is::Prcs::get(status))
|
||||
write<Serr::Diag::N>(0);
|
||||
|
||||
write<Is>(read<Is>());
|
||||
};
|
||||
|
||||
/**
|
||||
* Port interrupt enable
|
||||
*/
|
||||
struct Ie : Register<0x14, 32, 1>
|
||||
{
|
||||
struct Dhre : Bitfield<0, 1> { }; /* device to host register FIS interrupt */
|
||||
struct Pse : Bitfield<1, 2> { }; /* PIO setup FIS interrupt */
|
||||
struct Dse : Bitfield<2, 1> { }; /* DMA setup FIS interrupt */
|
||||
struct Sdbe : Bitfield<3, 1> { }; /* set device bits FIS interrupt (ncg) */
|
||||
struct Ufe : Bitfield<4, 1> { }; /* unknown FIS */
|
||||
struct Dpe : Bitfield<5, 1> { }; /* descriptor processed */
|
||||
struct Ifne : Bitfield<26, 1> { }; /* interface non-fatal error */
|
||||
struct Ife : Bitfield<27, 1> { }; /* interface fatal error */
|
||||
struct Hbde : Bitfield<28, 1> { }; /* host bus data error */
|
||||
struct Hbfe : Bitfield<29, 1> { }; /* host bus fatal error */
|
||||
struct Tfee : Bitfield<30, 1> { }; /* task file error */
|
||||
};
|
||||
|
||||
void interrupt_enable()
|
||||
{
|
||||
Ie::access_t ie = 0;
|
||||
Ie::Dhre::set(ie, 1);
|
||||
Ie::Pse::set(ie, 1);
|
||||
Ie::Dse::set(ie, 1);
|
||||
Ie::Sdbe::set(ie, 1);
|
||||
Ie::Ufe::set(ie, 1);
|
||||
Ie::Dpe::set(ie, 1);
|
||||
Ie::Ifne::set(ie, 1);
|
||||
Ie::Hbde::set(ie, 1);
|
||||
Ie::Hbfe::set(ie, 1);
|
||||
Ie::Tfee::set(ie, 1);
|
||||
write<Ie>(~0U);
|
||||
}
|
||||
|
||||
/**
|
||||
* Port command
|
||||
*/
|
||||
struct Cmd : Register<0x18, 32>
|
||||
{
|
||||
struct St : Bitfield<0, 1> { }; /* start */
|
||||
struct Sud : Bitfield<1, 1> { }; /* spin-up device */
|
||||
struct Pod : Bitfield<2, 1> { }; /* power-up device */
|
||||
struct Fre : Bitfield<4, 1> { }; /* FIS receive enable */
|
||||
struct Cr : Bitfield<15, 1> { }; /* command list running */
|
||||
struct Atapi : Bitfield<24, 1> { };
|
||||
struct Icc : Bitfield<28, 4> { }; /* interface communication control */
|
||||
};
|
||||
|
||||
void start()
|
||||
{
|
||||
if (read<Cmd::St>())
|
||||
return;
|
||||
|
||||
if (!wait_for<Tfd::Sts_bsy>(0, hba.delayer(), 500, 1000)) {
|
||||
PERR("HBA busy unable to start command processing.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wait_for<Tfd::Sts_drq>(0, hba.delayer(), 500, 1000)) {
|
||||
PERR("HBA in DRQ unable to start command processing.");
|
||||
return;
|
||||
}
|
||||
|
||||
write<Cmd::St>(1);
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
if (!(read<Ci>() | read<Sact>()))
|
||||
write<Cmd::St>(0);
|
||||
}
|
||||
|
||||
void power_up()
|
||||
{
|
||||
Cmd::access_t cmd = read<Cmd>();
|
||||
Cmd::Sud::set(cmd, 1);
|
||||
Cmd::Pod::set(cmd, 1);
|
||||
Cmd::Fre::set(cmd, 1);
|
||||
write<Cmd>(cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Task file data
|
||||
*/
|
||||
struct Tfd : Register<0x20, 32>
|
||||
{
|
||||
struct Sts_drq : Bitfield<3, 1> { }; /* indicates data transfer request */
|
||||
struct Sts_bsy : Bitfield<7, 1> { }; /* interface is busy */
|
||||
};
|
||||
|
||||
/**
|
||||
* Port signature
|
||||
*/
|
||||
struct Sig : Register<0x24, 32> { };
|
||||
|
||||
/**
|
||||
* Serial ATA status
|
||||
*/
|
||||
struct Ssts : Register<0x28, 32>
|
||||
{
|
||||
struct Dec : Bitfield<0, 4> /* device detection */
|
||||
{
|
||||
enum {
|
||||
NONE = 0x0,
|
||||
ESTABLISHED = 0x3
|
||||
};
|
||||
};
|
||||
struct Ipm : Bitfield<8, 4> /* interface power mgmt */
|
||||
{
|
||||
enum {
|
||||
ACTIVE = 0x1,
|
||||
SUSPEND = 0x2,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
bool enable()
|
||||
{
|
||||
Ssts::access_t status = read<Ssts>();
|
||||
if (Ssts::Dec::get(status) == Ssts::Dec::NONE)
|
||||
return false;
|
||||
|
||||
/* if in power-mgmt state (partial or slumber) */
|
||||
if (Ssts::Ipm::get(status) & Ssts::Ipm::SUSPEND) {
|
||||
/* try to wake up device */
|
||||
write<Cmd::Icc>(Ssts::Ipm::ACTIVE);
|
||||
|
||||
while ((Ssts::Dec::get(status) != Ssts::Dec::ESTABLISHED) ||
|
||||
!(Ssts::Ipm::get(status) & Ssts::Ipm::ACTIVE))
|
||||
status = read<Ssts>();
|
||||
}
|
||||
|
||||
return ((Ssts::Dec::get(status) == Ssts::Dec::ESTABLISHED) &&
|
||||
(Ssts::Ipm::get(status) & Ssts::Ipm::ACTIVE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Serial ATA control
|
||||
*/
|
||||
struct Sctl : Register<0x2c, 32>
|
||||
{
|
||||
struct Det : Bitfield<0, 4> { }; /* device dectection initialization */
|
||||
struct Ipmt : Bitfield<8, 4> { }; /* allowed power management transitions */
|
||||
};
|
||||
|
||||
void reset()
|
||||
{
|
||||
if (read<Cmd::St>())
|
||||
PWRN("CMD.ST bit set during device reset --> unknown behavior");
|
||||
|
||||
write<Sctl::Det>(1);
|
||||
hba.delayer().usleep(1000);
|
||||
write<Sctl::Det>(0);
|
||||
|
||||
if (!wait_for<Ssts::Dec>(Ssts::Dec::ESTABLISHED, hba.delayer()))
|
||||
PWRN("Port reset failed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Serial ATA error
|
||||
*/
|
||||
struct Serr : Register<0x30, 32>
|
||||
{
|
||||
struct Diag : Register<0x2, 16>
|
||||
{
|
||||
struct N : Bitfield<0, 1> { }; /* PhyRdy change */
|
||||
struct X : Bitfield<10, 1> { }; /* exchanged */
|
||||
};
|
||||
};
|
||||
|
||||
void clear_serr() { write<Serr>(read<Serr>()); }
|
||||
|
||||
/**
|
||||
* Serial ATA active
|
||||
*/
|
||||
struct Sact : Register<0x34, 32> { };
|
||||
|
||||
/**
|
||||
* Command issue
|
||||
*/
|
||||
struct Ci : Register<0x38, 32> { };
|
||||
|
||||
void init()
|
||||
{
|
||||
/* stop command list processing */
|
||||
stop();
|
||||
|
||||
/* setup command list/table */
|
||||
setup_memory();
|
||||
|
||||
/* disallow all power-management transitions */
|
||||
write<Sctl::Ipmt>(0x3);
|
||||
|
||||
/* power-up device */
|
||||
power_up();
|
||||
|
||||
/* reset port */
|
||||
reset();
|
||||
|
||||
/* clean error register */
|
||||
clear_serr();
|
||||
|
||||
/* enable required interrupts */
|
||||
interrupt_enable();
|
||||
|
||||
/* acknowledge all pending interrupts */
|
||||
ack_irq();
|
||||
hba.ack_irq();
|
||||
}
|
||||
|
||||
void setup_memory()
|
||||
{
|
||||
using Genode::addr_t;
|
||||
using Genode::size_t;
|
||||
|
||||
device_ds = platform_hba.alloc_dma_buffer(0x1000);
|
||||
|
||||
/* command list 1K */
|
||||
addr_t phys = Genode::Dataspace_client(device_ds).phys_addr();
|
||||
cmd_list = (addr_t)Genode::env()->rm_session()->attach(device_ds);
|
||||
command_list_base(phys);
|
||||
|
||||
/* receive FIS base 256 byte */
|
||||
fis_base = cmd_list + 1024;
|
||||
fis_rcv_base(phys + 1024);
|
||||
|
||||
/* command table */
|
||||
size_t cmd_size = Genode::align_addr(cmd_slots * Command_table::size(), 12);
|
||||
cmd_ds = platform_hba.alloc_dma_buffer(cmd_size);
|
||||
cmd_table = (addr_t)Genode::env()->rm_session()->attach(cmd_ds);
|
||||
phys = (addr_t)Genode::Dataspace_client(cmd_ds).phys_addr();
|
||||
|
||||
/* set command table addresses in command list */
|
||||
for (unsigned i = 0; i < cmd_slots; i++) {
|
||||
Command_header h(cmd_list + (i * Command_header::size()));
|
||||
h.cmd_table_base(phys + (i * Command_table::size()));
|
||||
}
|
||||
|
||||
/* dataspace for device info */
|
||||
device_info_ds = platform_hba.alloc_dma_buffer(0x1000);
|
||||
device_info = Genode::env()->rm_session()->attach(device_info_ds);
|
||||
}
|
||||
|
||||
Genode::addr_t command_table_addr(unsigned slot)
|
||||
{
|
||||
return cmd_table + (slot * Command_table::size());
|
||||
};
|
||||
|
||||
Genode::addr_t command_header_addr(unsigned slot)
|
||||
{
|
||||
return cmd_list + (slot * Command_header::size());
|
||||
}
|
||||
|
||||
void execute(unsigned slot)
|
||||
{
|
||||
start();
|
||||
write<Ci>(1U << slot);
|
||||
}
|
||||
|
||||
bool ready() { return state == READY; }
|
||||
};
|
||||
|
||||
|
||||
struct Port_driver : Port, Block::Driver
|
||||
{
|
||||
Genode::Signal_context_capability state_change_cap;
|
||||
|
||||
Port_driver(Port &port, Genode::Signal_context_capability state_change_cap)
|
||||
: Port(port), state_change_cap(state_change_cap)
|
||||
{ }
|
||||
|
||||
virtual void handle_irq() = 0;
|
||||
|
||||
void state_change() { Genode::Signal_transmitter(state_change_cap).submit(); }
|
||||
|
||||
void sanity_check(Block::sector_t block_number, Genode::size_t count)
|
||||
{
|
||||
/* max. PRDT size is 4MB */
|
||||
if (count * block_size() > 4 * 1024 * 1024) {
|
||||
PERR("error: maximum supported packet size is 4MB");
|
||||
throw Io_error();
|
||||
}
|
||||
|
||||
/* sanity check */
|
||||
if (block_number + count > block_count()) {
|
||||
PERR("error: requested blocks are outside of device");
|
||||
throw Io_error();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************
|
||||
** Block::Driver **
|
||||
*******************/
|
||||
|
||||
Genode::Ram_dataspace_capability
|
||||
alloc_dma_buffer(Genode::size_t size) override
|
||||
{
|
||||
return platform_hba.alloc_dma_buffer(size);
|
||||
}
|
||||
|
||||
void free_dma_buffer(Genode::Ram_dataspace_capability c) override
|
||||
{
|
||||
platform_hba.free_dma_buffer(c);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _INCLUDE__AHCI_H_ */
|
212
repos/os/src/drivers/ahci/ata_driver.h
Normal file
212
repos/os/src/drivers/ahci/ata_driver.h
Normal file
@ -0,0 +1,212 @@
|
||||
/**
|
||||
* \brief AHCI-port driver for ATA devices
|
||||
* \author Sebastian Sumpf
|
||||
* \date 2015-04-29
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _ATA_DRIVER_H_
|
||||
#define _ATA_DRIVER_H_
|
||||
|
||||
#include "ahci.h"
|
||||
|
||||
using namespace Genode;
|
||||
|
||||
struct Ata_driver : Port_driver
|
||||
{
|
||||
Genode::Lazy_volatile_object<Identity> info;
|
||||
Block::Packet_descriptor pending[32];
|
||||
|
||||
Ata_driver(Port &port, Signal_context_capability state_change)
|
||||
: Port_driver(port, state_change)
|
||||
{
|
||||
Port::init();
|
||||
identify_device();
|
||||
}
|
||||
|
||||
unsigned find_free_cmd_slot()
|
||||
{
|
||||
for (unsigned slot = 0; slot < cmd_slots; slot++)
|
||||
if (!pending[slot].valid())
|
||||
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].valid())
|
||||
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].valid())
|
||||
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)) {
|
||||
PWRN("Overlap: pending %llu + %zu, request: %llu + %zu", pending[slot].block_number(),
|
||||
pending[slot].block_count(), 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());
|
||||
table.fis.fpdma(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();
|
||||
|
||||
/* set pending */
|
||||
Port::write<Sact>(1U << slot);
|
||||
execute(slot);
|
||||
}
|
||||
|
||||
|
||||
/*****************
|
||||
** Port_driver **
|
||||
*****************/
|
||||
|
||||
void handle_irq() override
|
||||
{
|
||||
Is::access_t status = Port::read<Is>();
|
||||
|
||||
if (verbose)
|
||||
PDBG("irq: %x state: %u", status, state);
|
||||
|
||||
if ((Port::Is::Dss::get(status) || Port::Is::Pss::get(status)) &&
|
||||
state == IDENTIFY) {
|
||||
info.construct(device_info);
|
||||
if (verbose)
|
||||
info->info();
|
||||
|
||||
check_device();
|
||||
}
|
||||
|
||||
/*
|
||||
* When packets get acked, new ones may be inserted by the block driver
|
||||
* layer, these new packet requests might be finished during 'ack_packets',
|
||||
* so clear the IRQ and re-read after 'ack_packets'
|
||||
*/
|
||||
while (Is::Sdbs::get(status = Port::read<Is>())) {
|
||||
ack_irq();
|
||||
ack_packets();
|
||||
}
|
||||
|
||||
stop();
|
||||
}
|
||||
|
||||
void check_device()
|
||||
{
|
||||
if (!info->read<Identity::Sata_caps::Ncq_support>() ||
|
||||
!hba.ncg()) {
|
||||
PERR("Device does not support native command queuing: abort");
|
||||
state_change();
|
||||
return;
|
||||
}
|
||||
|
||||
cmd_slots = min((int)cmd_slots,
|
||||
info->read<Identity::Queue_depth::Max_depth >() + 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() { return true; };
|
||||
|
||||
Block::Session::Operations ops() override
|
||||
{
|
||||
Block::Session::Operations o;
|
||||
o.set_operation(Block::Packet_descriptor::READ);
|
||||
o.set_operation(Block::Packet_descriptor::WRITE);
|
||||
return o;
|
||||
}
|
||||
|
||||
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() override
|
||||
{
|
||||
Genode::size_t size = 512;
|
||||
|
||||
if (info->read<Identity::Logical_block::Longer_512>())
|
||||
size = info->read<Identity::Logical_words>() / 2;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
Block::sector_t block_count() override
|
||||
{
|
||||
return info->read<Identity::Sector_count>();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif /* _ATA_DRIVER_H_ */
|
206
repos/os/src/drivers/ahci/atapi_driver.h
Normal file
206
repos/os/src/drivers/ahci/atapi_driver.h
Normal file
@ -0,0 +1,206 @@
|
||||
/**
|
||||
* \brief AHCI-port driver for ATAPI devices
|
||||
* \author Sebastian Sumpf
|
||||
* \date 2015-04-29
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _ATAPI_DRIVER_H_
|
||||
#define _ATAPI_DRIVER_H_
|
||||
|
||||
#include "ahci.h"
|
||||
#include <util/endian.h>
|
||||
|
||||
using namespace Genode;
|
||||
|
||||
struct Atapi_driver : Port_driver
|
||||
{
|
||||
unsigned sense_tries = 0;
|
||||
Block::Packet_descriptor pending;
|
||||
|
||||
Atapi_driver(Port &port, Signal_context_capability state_change)
|
||||
: Port_driver(port, state_change)
|
||||
{
|
||||
Port::init();
|
||||
Port::write<Cmd::Atapi>(1);
|
||||
read_sense();
|
||||
}
|
||||
|
||||
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) {
|
||||
PERR("Could not power up device");
|
||||
state_change();
|
||||
return;
|
||||
}
|
||||
|
||||
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_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.valid())
|
||||
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) {
|
||||
PDBG("irq: is: %x ci: %x state: %u", status, Port::read<Ci>(), state);
|
||||
Device_fis f(fis_base);
|
||||
PDBG("d2h: status: %x error: %x", f.read<Device_fis::Status>(), f.read<Device_fis::Error>());
|
||||
}
|
||||
|
||||
ack_irq();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (state == READY && Port::Is::Dhrs::get(status)) {
|
||||
ack_packets();
|
||||
}
|
||||
|
||||
if (Port::Is::Dss::get(status) || Port::Is::Pss::get(status)) {
|
||||
switch (state) {
|
||||
|
||||
case STATUS:
|
||||
test_unit_ready();
|
||||
break;
|
||||
|
||||
case IDENTIFY:
|
||||
state = READY;
|
||||
state_change();
|
||||
break;
|
||||
|
||||
case READY:
|
||||
ack_packets();
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*****************************
|
||||
** Block::Driver interface **
|
||||
*****************************/
|
||||
|
||||
bool dma_enabled() { return true; };
|
||||
|
||||
Block::Session::Operations ops() override
|
||||
{
|
||||
Block::Session::Operations o;
|
||||
o.set_operation(Block::Packet_descriptor::READ);
|
||||
return o;
|
||||
}
|
||||
|
||||
Genode::size_t block_size() override
|
||||
{
|
||||
return host_to_big_endian(((unsigned *)device_info)[1]);
|
||||
}
|
||||
|
||||
Block::sector_t block_count() override
|
||||
{
|
||||
return host_to_big_endian(((unsigned *)device_info)[0]) + 1;
|
||||
}
|
||||
|
||||
void read_dma(Block::sector_t block_number,
|
||||
size_t count,
|
||||
addr_t phys,
|
||||
Block::Packet_descriptor &packet) override
|
||||
{
|
||||
if (pending.valid())
|
||||
throw Block::Driver::Request_congestion();
|
||||
|
||||
sanity_check(block_number, count);
|
||||
|
||||
pending = packet;
|
||||
|
||||
if (verbose)
|
||||
PDBG("Add packet read %llu count %zu -> %u", block_number, count, 0);
|
||||
|
||||
/* setup fis */
|
||||
Command_table table(command_table_addr(0), phys, count * block_size());
|
||||
table.fis.atapi();
|
||||
|
||||
/* setup atapi command */
|
||||
table.atapi_cmd.read10(block_number, count);
|
||||
|
||||
/* 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();
|
||||
|
||||
/* set pending */
|
||||
execute(0);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _ATAPI_DRIVER_H_ */
|
@ -1,3 +0,0 @@
|
||||
/*
|
||||
* Dummy compilation unit needed to link a valid target.
|
||||
*/
|
File diff suppressed because it is too large
Load Diff
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* \brief AHCI driver declaration
|
||||
* \author Martin Stein <martin.stein@genode-labs.com>
|
||||
* \date 2013-04-10
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2013 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _AHCI_DRIVER_H_
|
||||
#define _AHCI_DRIVER_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <block/component.h>
|
||||
#include <ram_session/ram_session.h>
|
||||
|
||||
/**
|
||||
* AHCI driver
|
||||
*/
|
||||
class Ahci_driver : public Block::Driver
|
||||
{
|
||||
enum { VERBOSE = 0 };
|
||||
|
||||
/* import Genode symbols */
|
||||
typedef Genode::size_t size_t;
|
||||
typedef Genode::uint64_t uint64_t;
|
||||
typedef Genode::addr_t addr_t;
|
||||
|
||||
int _ncq_command(uint64_t lba, unsigned cnt, addr_t phys, bool w);
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
Ahci_driver();
|
||||
|
||||
|
||||
/*****************************
|
||||
** Block::Driver interface **
|
||||
*****************************/
|
||||
|
||||
Block::Session::Operations ops()
|
||||
{
|
||||
Block::Session::Operations o;
|
||||
o.set_operation(Block::Packet_descriptor::READ);
|
||||
o.set_operation(Block::Packet_descriptor::WRITE);
|
||||
return o;
|
||||
}
|
||||
|
||||
size_t block_size();
|
||||
Block::sector_t block_count();
|
||||
bool dma_enabled() { return true; }
|
||||
|
||||
Genode::Ram_dataspace_capability alloc_dma_buffer(Genode::size_t size) {
|
||||
return Genode::env()->ram_session()->alloc(size, Genode::UNCACHED); }
|
||||
|
||||
void free_dma_buffer(Genode::Ram_dataspace_capability c) {
|
||||
return Genode::env()->ram_session()->free(c); }
|
||||
|
||||
void read_dma(Block::sector_t block_nr, size_t block_cnt, addr_t phys,
|
||||
Block::Packet_descriptor &packet)
|
||||
{
|
||||
if (_ncq_command(block_nr, block_cnt, phys, 0))
|
||||
throw Io_error();
|
||||
ack_packet(packet);
|
||||
}
|
||||
|
||||
void write_dma(Block::sector_t block_nr, size_t block_cnt, addr_t phys,
|
||||
Block::Packet_descriptor &packet)
|
||||
{
|
||||
if (_ncq_command(block_nr, block_cnt, phys, 1))
|
||||
throw Io_error();
|
||||
ack_packet(packet);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _AHCI_DRIVER_H_ */
|
||||
|
@ -1,198 +0,0 @@
|
||||
/*
|
||||
* \brief SATA benchmark for Exynos5 platform
|
||||
* \author Martin Stein
|
||||
* \date 2012-06-05
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/sleep.h>
|
||||
#include <base/printf.h>
|
||||
#include <timer_session/connection.h>
|
||||
#include <os/attached_ram_dataspace.h>
|
||||
#include <dataspace/client.h>
|
||||
|
||||
/* local includes */
|
||||
#include <ahci_driver.h>
|
||||
|
||||
|
||||
struct Operation
|
||||
{
|
||||
virtual void operator () (Block::Driver &driver,
|
||||
Genode::addr_t block_number,
|
||||
Genode::size_t block_count,
|
||||
Genode::addr_t buffer_phys,
|
||||
char *buffer_virt) = 0;
|
||||
};
|
||||
|
||||
void print_bench_head()
|
||||
{
|
||||
Genode::printf("\n");
|
||||
Genode::printf("bytes/block bytes sec MB/sec\n");
|
||||
Genode::printf("----------------------------------------------\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* \param total_size total number of bytes to read
|
||||
* \param request_size number of bytes per request
|
||||
*/
|
||||
static void run_benchmark(Block::Driver &driver,
|
||||
Timer::Session &timer,
|
||||
char *buffer_virt,
|
||||
Genode::addr_t buffer_phys,
|
||||
Genode::size_t buffer_size,
|
||||
Genode::size_t request_size,
|
||||
Operation &operation)
|
||||
{
|
||||
using namespace Genode;
|
||||
if (request_size > buffer_size) {
|
||||
PERR("undersized buffer %u, need %u", buffer_size, buffer_size);
|
||||
while (1) ;
|
||||
}
|
||||
size_t const block_count = request_size / driver.block_size();
|
||||
/*
|
||||
* The goal is to get 5 test repetitions that took 2 s < time < 2.3 s,
|
||||
* each. Thus we start with count = 32 and then adjust the count
|
||||
* for a retry if the test time is not in range.
|
||||
*/
|
||||
unsigned tmp_bytes = 64 * request_size;;
|
||||
unsigned bytes = 0;
|
||||
unsigned ms = 0;
|
||||
unsigned reps = 0;
|
||||
float sec = 0;
|
||||
float mb_per_sec = 0;
|
||||
while (1)
|
||||
{
|
||||
size_t num_requests = tmp_bytes / request_size;
|
||||
|
||||
/* do measurement */
|
||||
unsigned const time_before_ms = timer.elapsed_ms();
|
||||
for (unsigned i = 0; i < num_requests; i++)
|
||||
{
|
||||
addr_t const block_number = i * block_count;
|
||||
operation(driver, block_number, block_count,
|
||||
buffer_phys, buffer_virt);
|
||||
}
|
||||
/* read results */
|
||||
unsigned const time_after_ms = timer.elapsed_ms();
|
||||
ms = time_after_ms - time_before_ms;
|
||||
|
||||
/* check if test time in range */
|
||||
if (ms < 2000 || ms >= 2300) {
|
||||
/*
|
||||
* adjust transfer amount according to measured time
|
||||
*
|
||||
* FIXME implement static inertia
|
||||
*/
|
||||
tmp_bytes = (((float) 2150 / ms) * tmp_bytes);
|
||||
tmp_bytes &= 0xfffffe00; /* align to 512 byte blocks */
|
||||
} else {
|
||||
/* if new result is better than the last one do update */
|
||||
float const tmp_mb = ((float)tmp_bytes / 1000) / 1000;
|
||||
float const tmp_sec = (float)ms / 1000;
|
||||
float const tmp_mb_per_sec = (float)tmp_mb / tmp_sec;
|
||||
if (tmp_mb_per_sec > mb_per_sec) {
|
||||
sec = tmp_sec;
|
||||
mb_per_sec = tmp_mb_per_sec;
|
||||
bytes = tmp_bytes;
|
||||
}
|
||||
/* check if we need more repetitions */
|
||||
reps++;
|
||||
if (reps == 5) break;
|
||||
}
|
||||
}
|
||||
unsigned const sec_left = sec;
|
||||
unsigned const sec_right = 1000 * ((float)sec - sec_left);
|
||||
unsigned const mb_per_sec_left = mb_per_sec;
|
||||
unsigned const mb_per_sec_right = 1000 * ((float)mb_per_sec - mb_per_sec_left);
|
||||
PLOG(" %10u %10u %u.%03u %10u.%03u", request_size, bytes, sec_left, sec_right, mb_per_sec_left, mb_per_sec_right);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
using namespace Genode;
|
||||
|
||||
printf("AHCI bench\n");
|
||||
printf("==========\n");
|
||||
printf("\n");
|
||||
|
||||
static Ahci_driver driver;
|
||||
|
||||
static Timer::Connection timer;
|
||||
|
||||
long const request_sizes[] = {
|
||||
1048576, 262144, 16384, 8192, 4096, 2048, 1024, 512, 0 };
|
||||
|
||||
/* total size of communication buffer */
|
||||
size_t const buffer_size = 1024*1024;
|
||||
|
||||
/* allocate read/write buffer */
|
||||
static Attached_ram_dataspace buffer(env()->ram_session(), buffer_size, Genode::UNCACHED);
|
||||
char * const buffer_virt = buffer.local_addr<char>();
|
||||
addr_t const buffer_phys = Dataspace_client(buffer.cap()).phys_addr();
|
||||
|
||||
/*
|
||||
* Benchmark reading from SATA device
|
||||
*/
|
||||
|
||||
printf("read\n");
|
||||
printf("~~~~\n");
|
||||
print_bench_head();
|
||||
|
||||
struct Read : Operation
|
||||
{
|
||||
void operator () (Block::Driver &driver,
|
||||
addr_t number, size_t count, addr_t phys, char *virt)
|
||||
{
|
||||
Block::Packet_descriptor packet;
|
||||
if (driver.dma_enabled())
|
||||
driver.read_dma(number, count, phys, packet);
|
||||
else
|
||||
driver.read(number, count, virt, packet);
|
||||
}
|
||||
} read_operation;
|
||||
|
||||
for (unsigned i = 0; request_sizes[i]; i++)
|
||||
run_benchmark(driver, timer, buffer_virt, buffer_phys, buffer_size,
|
||||
request_sizes[i], read_operation);
|
||||
|
||||
/*
|
||||
* Benchmark writing to SATA device
|
||||
*
|
||||
* Attention: Original data will be overridden on target drive
|
||||
*/
|
||||
|
||||
printf("\n");
|
||||
printf("write\n");
|
||||
printf("~~~~~\n");
|
||||
print_bench_head();
|
||||
|
||||
struct Write : Operation
|
||||
{
|
||||
void operator () (Block::Driver &driver,
|
||||
addr_t number, size_t count, addr_t phys, char *virt)
|
||||
{
|
||||
Block::Packet_descriptor packet;
|
||||
if (driver.dma_enabled())
|
||||
driver.write_dma(number, count, phys, packet);
|
||||
else
|
||||
driver.write(number, count, virt, packet);
|
||||
}
|
||||
} write_operation;
|
||||
|
||||
for (unsigned i = 0; request_sizes[i]; i++)
|
||||
run_benchmark(driver, timer, buffer_virt, buffer_phys, buffer_size,
|
||||
request_sizes[i], write_operation);
|
||||
|
||||
printf("\n");
|
||||
printf("benchmark finished\n");
|
||||
sleep_forever();
|
||||
return 0;
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
TARGET = ahci_bench
|
||||
REQUIRES = exynos5
|
||||
SRC_CC = main.cc ahci_driver.cc
|
||||
LIBS = base
|
||||
INC_DIR += $(PRG_DIR)/.. $(PRG_DIR)/../..
|
||||
vpath ahci_driver.cc $(PRG_DIR)/..
|
385
repos/os/src/drivers/ahci/exynos5/platform.cc
Normal file
385
repos/os/src/drivers/ahci/exynos5/platform.cc
Normal file
@ -0,0 +1,385 @@
|
||||
/**
|
||||
* \brief Driver for Exynos-5250 soc
|
||||
* \author Martin Stein
|
||||
* \author Sebastian Sumpf
|
||||
* \date 2015-05-04
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#include <drivers/board_base.h>
|
||||
#include <irq_session/connection.h>
|
||||
#include <regulator/consts.h>
|
||||
#include <regulator_session/connection.h>
|
||||
|
||||
#include <ahci.h>
|
||||
|
||||
using namespace Genode;
|
||||
|
||||
/**
|
||||
* I2C master interface
|
||||
*/
|
||||
struct I2c_interface : Attached_mmio
|
||||
{
|
||||
enum { TX_DELAY_US = 1 };
|
||||
|
||||
/********************************
|
||||
** MMIO structure description **
|
||||
********************************/
|
||||
|
||||
struct Start_msg : Genode::Register<8>
|
||||
{
|
||||
struct Addr : Bitfield<1, 7> { };
|
||||
};
|
||||
|
||||
struct Con : Register<0x0, 8>
|
||||
{
|
||||
struct Tx_prescaler : Bitfield<0, 4> { };
|
||||
struct Irq_pending : Bitfield<4, 1> { };
|
||||
struct Irq_en : Bitfield<5, 1> { };
|
||||
struct Clk_sel : Bitfield<6, 1> { };
|
||||
struct Ack_en : Bitfield<7, 1> { };
|
||||
};
|
||||
|
||||
struct Stat : Register<0x4, 8>
|
||||
{
|
||||
struct Last_bit : Bitfield<0, 1> { };
|
||||
struct Arbitr : Bitfield<3, 1> { };
|
||||
struct Txrx_en : Bitfield<4, 1> { };
|
||||
struct Busy : Bitfield<5, 1> { };
|
||||
struct Mode : Bitfield<6, 2> { };
|
||||
};
|
||||
|
||||
struct Add : Register<0x8, 8>
|
||||
{
|
||||
struct Slave_addr : Bitfield<0, 8> { };
|
||||
};
|
||||
|
||||
struct Ds : Register<0xc, 8> { };
|
||||
|
||||
struct Lc : Register<0x10, 8>
|
||||
{
|
||||
struct Sda_out_delay : Bitfield<0, 2> { };
|
||||
struct Filter_en : Bitfield<2, 1> { };
|
||||
};
|
||||
|
||||
Mmio::Delayer &delayer;
|
||||
/* single-word message that starts a multi-word message transfer */
|
||||
Start_msg::access_t const start_msg;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* \param base physical MMIO base
|
||||
* \param slave_addr ID of the targeted slave
|
||||
*/
|
||||
I2c_interface(addr_t base, unsigned slave_addr, Mmio::Delayer &delayer)
|
||||
: Attached_mmio(base, 0x10000), delayer(delayer),
|
||||
start_msg(Start_msg::Addr::bits(slave_addr))
|
||||
{ }
|
||||
|
||||
/**
|
||||
* Wether acknowledgment for last transaction can be received
|
||||
*/
|
||||
bool ack_received()
|
||||
{
|
||||
for (unsigned i = 0; i < 3; i++) {
|
||||
if (read<Con::Irq_pending>() && !read<Stat::Last_bit>()) return 1;
|
||||
delayer.usleep(TX_DELAY_US);
|
||||
}
|
||||
PERR("I2C ack not received");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wether arbitration errors occured during the last transaction
|
||||
*/
|
||||
bool arbitration_error()
|
||||
{
|
||||
if (read<Stat::Arbitr>()) {
|
||||
PERR("I2C arbitration failed");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Let I2C master send a message to I2C slave
|
||||
*
|
||||
* \param msg message base
|
||||
* \param msg_size message size
|
||||
*
|
||||
* \retval 0 call was successful
|
||||
* \retval <0 call failed, error code
|
||||
*/
|
||||
int send(uint8_t * msg, size_t msg_size)
|
||||
{
|
||||
/* initiate message transfer */
|
||||
if (!wait_for<Stat::Busy>(0, delayer)) {
|
||||
PERR("I2C busy");
|
||||
return -1;
|
||||
}
|
||||
Stat::access_t stat = read<Stat>();
|
||||
Stat::Txrx_en::set(stat, 1);
|
||||
Stat::Mode::set(stat, 3);
|
||||
write<Stat>(stat);
|
||||
write<Ds>(start_msg);
|
||||
delayer.usleep(1000);
|
||||
write<Con::Tx_prescaler>(11);
|
||||
write<Stat::Busy>(1);
|
||||
|
||||
/* transmit message payload */
|
||||
for (unsigned i = 0; i < msg_size; i++) {
|
||||
if (!ack_received()) return -1;
|
||||
write<Ds>(msg[i]);
|
||||
delayer.usleep(TX_DELAY_US);
|
||||
write<Con::Irq_pending>(0);
|
||||
if (arbitration_error()) return -1;
|
||||
}
|
||||
/* end message transfer */
|
||||
if (!ack_received()) return -1;
|
||||
write<Stat::Busy>(0);
|
||||
write<Con::Irq_en>(0);
|
||||
write<Con::Irq_pending>(0); /* FIXME fixup */
|
||||
if (arbitration_error()) return -1;
|
||||
if (!wait_for<Stat::Busy>(0, delayer)) {
|
||||
PERR("I2C end transfer failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* I2C control interface of SATA PHY-layer controller
|
||||
*/
|
||||
struct I2c_sataphy : I2c_interface
|
||||
{
|
||||
enum { SLAVE_ADDR = 0x38 };
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
I2c_sataphy(Mmio::Delayer &delayer)
|
||||
: I2c_interface(0x121d0000, SLAVE_ADDR, delayer)
|
||||
{ }
|
||||
|
||||
/**
|
||||
* Enable the 40-pin interface of the SATA PHY controller
|
||||
*
|
||||
* \retval 0 call was successful
|
||||
* \retval <0 call failed, error code
|
||||
*/
|
||||
int enable_40_pins()
|
||||
{
|
||||
/*
|
||||
* I2C message
|
||||
*
|
||||
* first byte: set address
|
||||
* second byte: set data
|
||||
*/
|
||||
static uint8_t msg[] = { 0x3a, 0x0b };
|
||||
enum { MSG_SIZE = sizeof(msg)/sizeof(msg[0]) };
|
||||
|
||||
/* send messaage */
|
||||
if (send(msg, MSG_SIZE)) return -1;
|
||||
if (verbose) printf("SATA PHY 40-pin interface enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get I2C interface ready for transmissions
|
||||
*/
|
||||
void init()
|
||||
{
|
||||
write<Add::Slave_addr>(SLAVE_ADDR);
|
||||
|
||||
Con::access_t con = read<Con>();
|
||||
Con::Irq_en::set(con, 1);
|
||||
Con::Ack_en::set(con, 1);
|
||||
Con::Clk_sel::set(con, 1);
|
||||
Con::Tx_prescaler::set(con, 9);
|
||||
write<Con>(con);
|
||||
|
||||
Lc::access_t lc = 0;
|
||||
Lc::Sda_out_delay::set(lc, 3);
|
||||
Lc::Filter_en::set(lc, 1);
|
||||
write<Lc>(lc);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Classical control interface of SATA PHY-layer controller
|
||||
*/
|
||||
struct Sata_phy_ctrl : Attached_mmio
|
||||
{
|
||||
Mmio::Delayer &delayer;
|
||||
I2c_sataphy i2c_sataphy { delayer };
|
||||
|
||||
/********************************
|
||||
** MMIO structure description **
|
||||
********************************/
|
||||
|
||||
struct Reset : Register<0x4, 32>
|
||||
{
|
||||
struct Global : Bitfield<1, 1> { };
|
||||
struct Non_link : Bitfield<0, 8> { };
|
||||
struct Link : Bitfield<16, 4> { };
|
||||
};
|
||||
|
||||
struct Mode0 : Register<0x10, 32>
|
||||
{
|
||||
struct P0_phy_spdmode : Bitfield<0, 2> { };
|
||||
};
|
||||
|
||||
struct Ctrl0 : Register<0x14, 32>
|
||||
{
|
||||
struct P0_phy_calibrated : Bitfield<8, 1> { };
|
||||
struct P0_phy_calibrated_sel : Bitfield<9, 1> { };
|
||||
};
|
||||
|
||||
struct Phctrlm : Register<0xe0, 32>
|
||||
{
|
||||
struct High_speed : Bitfield<0, 1> { };
|
||||
struct Ref_rate : Bitfield<1, 1> { };
|
||||
};
|
||||
|
||||
struct Phstatm : Register<0xf0, 32>
|
||||
{
|
||||
struct Pll_locked : Bitfield<0, 1> { };
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
Sata_phy_ctrl(Mmio::Delayer &delayer)
|
||||
: Attached_mmio(0x12170000, 0x10000), delayer(delayer)
|
||||
{
|
||||
i2c_sataphy.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize parts of SATA PHY that are controlled classically
|
||||
*
|
||||
* \retval 0 call was successful
|
||||
* \retval <0 call failed, error code
|
||||
*/
|
||||
int init()
|
||||
{
|
||||
/* reset */
|
||||
write<Reset>(0);
|
||||
write<Reset::Non_link>(~0);
|
||||
write<Reset::Link>(~0);
|
||||
write<Reset::Global>(~0);
|
||||
|
||||
/* set up SATA phy generation 3 (6 Gb/s) */
|
||||
Phctrlm::access_t phctrlm = read<Phctrlm>();
|
||||
Phctrlm::Ref_rate::set(phctrlm, 0);
|
||||
Phctrlm::High_speed::set(phctrlm, 1);
|
||||
write<Phctrlm>(phctrlm);
|
||||
Ctrl0::access_t ctrl0 = read<Ctrl0>();
|
||||
Ctrl0::P0_phy_calibrated::set(ctrl0, 1);
|
||||
Ctrl0::P0_phy_calibrated_sel::set(ctrl0, 1);
|
||||
write<Ctrl0>(ctrl0);
|
||||
write<Mode0::P0_phy_spdmode>(2);
|
||||
if (i2c_sataphy.enable_40_pins()) return -1;
|
||||
|
||||
/* Release reset */
|
||||
write<Reset::Global>(0);
|
||||
write<Reset::Global>(1);
|
||||
|
||||
/*
|
||||
* FIXME Linux reads this bit once only and continues
|
||||
* directly, also with zero. So if we get an error
|
||||
* at this point we should study the Linux behavior
|
||||
* in more depth.
|
||||
*/
|
||||
if (!wait_for<Phstatm::Pll_locked>(1, delayer)) {
|
||||
PERR("PLL lock failed");
|
||||
return -1;
|
||||
}
|
||||
if (verbose)
|
||||
printf("SATA PHY initialized\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct Exynos5_hba : Platform::Hba
|
||||
{
|
||||
Irq_connection irq { Board_base::SATA_IRQ };
|
||||
Regulator::Connection clock_src { Regulator::CLK_SATA };
|
||||
Regulator::Connection power_src { Regulator::PWR_SATA };
|
||||
|
||||
Exynos5_hba(Mmio::Delayer &delayer)
|
||||
{
|
||||
clock_src.state(true);
|
||||
power_src.state(true);
|
||||
|
||||
Sata_phy_ctrl phy(delayer);
|
||||
|
||||
if (phy.init())
|
||||
throw Root::Unavailable();
|
||||
|
||||
/* additionally perform some generic initializations */
|
||||
::Hba hba(*this);
|
||||
|
||||
::Hba::Cap::access_t cap = hba.read< ::Hba::Cap>();
|
||||
::Hba::Cap2::access_t cap2 = hba.read< ::Hba::Cap2>();
|
||||
|
||||
/* reset */
|
||||
hba.write< ::Hba::Ghc::Hr>(1);
|
||||
if (!hba.wait_for< ::Hba::Ghc::Hr>(0, ::Hba::delayer(), 1000, 1000)) {
|
||||
PERR("HBA reset failed");
|
||||
throw Root::Unavailable();
|
||||
}
|
||||
|
||||
hba.write< ::Hba::Cap>(cap);
|
||||
hba.write< ::Hba::Cap2>(cap2);
|
||||
|
||||
/* for exynos set port 0 as implemented, usally set by BIOS */
|
||||
hba.write< ::Hba::Pi>(0x1);
|
||||
}
|
||||
|
||||
|
||||
/*******************
|
||||
** Hba interface **
|
||||
*******************/
|
||||
|
||||
Genode::addr_t base() const override { return 0x122f0000; }
|
||||
Genode::size_t size() const override { return 0x10000; }
|
||||
|
||||
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(size_t size) override
|
||||
{
|
||||
return env()->ram_session()->alloc(size, UNCACHED);
|
||||
}
|
||||
|
||||
void free_dma_buffer(Ram_dataspace_capability ds)
|
||||
{
|
||||
env()->ram_session()->free(ds);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Platform::Hba &Platform::init(Mmio::Delayer &delayer)
|
||||
{
|
||||
static Exynos5_hba h(delayer);
|
||||
return h;
|
||||
}
|
@ -1,578 +0,0 @@
|
||||
/*
|
||||
* \brief Generic base of AHCI device
|
||||
* \author Sebastian Sumpf <Sebastian.Sumpf@genode-labs.com>
|
||||
* \author Martin Stein <Martin.Stein@genode-labs.com>
|
||||
* \date 2011-08-10
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2011-2013 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _AHCI_DEVICE_BASE_H_
|
||||
#define _AHCI_DEVICE_BASE_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/printf.h>
|
||||
#include <base/sleep.h>
|
||||
#include <block/driver.h>
|
||||
#include <block/component.h>
|
||||
#include <cap_session/connection.h>
|
||||
#include <dataspace/client.h>
|
||||
#include <irq_session/connection.h>
|
||||
#include <io_mem_session/connection.h>
|
||||
#include <timer_session/connection.h>
|
||||
|
||||
/**
|
||||
* Enable for debugging output
|
||||
*/
|
||||
static const bool verbose = false;
|
||||
|
||||
using namespace Genode;
|
||||
|
||||
|
||||
/**
|
||||
* Base class for register access
|
||||
*/
|
||||
class Reg_base
|
||||
{
|
||||
protected:
|
||||
|
||||
uint32_t _value(uint32_t offset) { return *(volatile uint32_t *)(this + offset); }
|
||||
void _set(uint32_t offset, uint32_t val) { *(volatile uint32_t *)(this + offset) = val; }
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* HBA: Generic Host Control
|
||||
*/
|
||||
class Generic_ctrl : public Reg_base
|
||||
{
|
||||
public:
|
||||
|
||||
/* host capabilities */
|
||||
uint32_t hba_cap() { return _value(0x0); }
|
||||
|
||||
/* return port count from hba_cap */
|
||||
uint32_t port_count() { return (hba_cap() & 0x1f) + 1; }
|
||||
|
||||
/* return command slot count from hba_cap */
|
||||
uint32_t cmd_slots() { return ((hba_cap() >> 8) & 0x1f) + 1; }
|
||||
|
||||
/* global host control */
|
||||
uint32_t hba_ctrl() { return _value(0x4); }
|
||||
void hba_ctrl(uint32_t val) { _set(0x4, val); }
|
||||
|
||||
/* set Interrupt Enable (IE) in hba_ctrl */
|
||||
void global_interrupt_enable()
|
||||
{
|
||||
hba_ctrl(hba_ctrl() | 2);
|
||||
|
||||
if (verbose)
|
||||
PDBG("HBA %x", hba_ctrl());
|
||||
}
|
||||
|
||||
/* set AHCI enable (AE) in hba_ctrl */
|
||||
void global_enable_ahci()
|
||||
{
|
||||
if (!(hba_ctrl() & (1 << 31)))
|
||||
hba_ctrl(hba_ctrl() | (1 << 31));
|
||||
|
||||
if (verbose)
|
||||
PDBG("AHCI ENABLED: %x", hba_ctrl());
|
||||
}
|
||||
|
||||
/* global interrupt status (contains port interrupts) */
|
||||
uint32_t hba_intr_status() { return _value(0x8); }
|
||||
void hba_intr_status(uint32_t val) { return _set(0x8, val); }
|
||||
|
||||
/* acknowledge global interrupt */
|
||||
void hba_interrupt_ack() { hba_intr_status(hba_intr_status()); }
|
||||
|
||||
/* AHCI version */
|
||||
uint32_t version() { return _value(0x10); }
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* AHCI port registers (one set per port)
|
||||
*/
|
||||
class Ahci_port : public Reg_base
|
||||
{
|
||||
public:
|
||||
|
||||
/* command list base */
|
||||
void cmd_list_base(addr_t cmd_base)
|
||||
{
|
||||
uint64_t addr = cmd_base;
|
||||
|
||||
_set(0x0, addr);
|
||||
_set(0x4, addr >> 32);
|
||||
}
|
||||
|
||||
/* receive FIS base address */
|
||||
void fis_base(addr_t fis_base)
|
||||
{
|
||||
uint64_t addr = fis_base;
|
||||
|
||||
_set(0x8, addr);
|
||||
_set(0xc, addr >> 32);
|
||||
}
|
||||
|
||||
/* interrupt status */
|
||||
uint32_t intr_status() { return _value(0x10); }
|
||||
void intr_status(uint32_t val) { _set(0x10, val); }
|
||||
|
||||
/* interrupt enable */
|
||||
void intr_enable(uint32_t val) { _set(0x14, val); }
|
||||
|
||||
/* command */
|
||||
uint32_t cmd() { return _value(0x18); }
|
||||
void cmd(uint32_t val) { _set(0x18, val); }
|
||||
|
||||
/* task file data */
|
||||
uint32_t tfd() { return _value(0x20); }
|
||||
|
||||
/* Serial ATA status */
|
||||
uint32_t status() { return _value(0x28); }
|
||||
|
||||
/* Serial ATA control */
|
||||
void sctl(uint32_t val) { _set(0x2c, val); }
|
||||
uint32_t sctl() { return _value(0x2c); }
|
||||
|
||||
/* Serial ATA error */
|
||||
void err(uint32_t val) { _set(0x30, val); }
|
||||
uint32_t err() { return _value(0x30); }
|
||||
|
||||
/* command issue (1 bit per command slot) */
|
||||
void cmd_issue(uint32_t val) { _set(0x38, val); }
|
||||
uint32_t cmd_issue() { return _value(0x38); }
|
||||
|
||||
/**
|
||||
* Check if device is active
|
||||
*/
|
||||
bool status_active()
|
||||
{
|
||||
enum {
|
||||
PRESENT_ESTABLISHED = 0x3, /* device is present and connection is established */
|
||||
PM_ACTIVE = 0x100, /* interface is in active power-mngmt. state */
|
||||
PM_PARTIRL = 0x200, /* interface is in partial power-mngmt. state */
|
||||
PM_SLUMBER = 0x600, /* interface is in slumber power-mngmt. state */
|
||||
};
|
||||
|
||||
uint32_t stat = status();
|
||||
uint32_t pm_stat = stat & 0xf00;
|
||||
|
||||
/* if controller is in sleep state, try to wake up */
|
||||
if (pm_stat == PM_PARTIRL || pm_stat == PM_SLUMBER) {
|
||||
|
||||
if (verbose)
|
||||
PDBG("Controller is in sleep state, trying to wake up ...");
|
||||
|
||||
cmd(cmd() | (1 << 28));
|
||||
|
||||
while (!(stat & PM_ACTIVE) || (stat & 0xf) != PRESENT_ESTABLISHED) { stat = status(); }
|
||||
}
|
||||
return (((stat & 0xf) == PRESENT_ESTABLISHED) && (stat & PM_ACTIVE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable CMD.ST to start command list processing
|
||||
*/
|
||||
void hba_enable()
|
||||
{
|
||||
enum {
|
||||
STS_BSY = 0x80, /* device is busy */
|
||||
STS_DRQ = 0x08, /* data transfer requested */
|
||||
};
|
||||
|
||||
while (tfd() & (STS_BSY | STS_DRQ))
|
||||
if (verbose)
|
||||
PDBG("TFD %x", tfd());
|
||||
|
||||
cmd(cmd() | 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable CMD.ST
|
||||
*/
|
||||
void hba_disable()
|
||||
{
|
||||
if ((cmd() & 1) && !(cmd_issue() & 1))
|
||||
cmd(cmd() & ~1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable port interrupts
|
||||
*/
|
||||
void interrupt_enable() { intr_enable(~0U); }
|
||||
|
||||
/**
|
||||
* Acknowledge port interrupts
|
||||
*/
|
||||
uint32_t interrupt_ack()
|
||||
{
|
||||
interrupt_pm_ack();
|
||||
|
||||
uint32_t status = intr_status();
|
||||
intr_status(status);
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and handle interrupts due to power mgmt. state transitions
|
||||
*/
|
||||
void interrupt_pm_ack()
|
||||
{
|
||||
enum {
|
||||
INT_PORT_CON_STATUS = 0x40,
|
||||
INT_PHY_RDY_STATUS = 0x400000,
|
||||
};
|
||||
uint32_t status = intr_status();
|
||||
|
||||
if (status & INT_PORT_CON_STATUS)
|
||||
|
||||
/* clear DIAG.x */
|
||||
err(err() & ~(1 << 26));
|
||||
|
||||
if (status & INT_PORT_CON_STATUS)
|
||||
|
||||
/* clear DIAG.n */
|
||||
err(err() & ~(1 << 16));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable power mgmt. set SCTL.IPM to 3
|
||||
*/
|
||||
void disable_pm() { sctl(sctl() | (3 << 8)); }
|
||||
|
||||
/**
|
||||
* Power up device
|
||||
*/
|
||||
void get_ready()
|
||||
{
|
||||
enum {
|
||||
SPIN_UP_DEVICE = 0x2,
|
||||
POWER_ON_DEVICE = 0x4,
|
||||
FIS_RECV_ENABLE = 0x10,
|
||||
ENABLE = SPIN_UP_DEVICE | POWER_ON_DEVICE | FIS_RECV_ENABLE
|
||||
};
|
||||
cmd(cmd() | ENABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset this port
|
||||
*/
|
||||
void reset()
|
||||
{
|
||||
/* check for ST bit in command register */
|
||||
if (cmd() & 1)
|
||||
PWRN("CMD.ST bit set during device reset --> unknown behavior");
|
||||
|
||||
/* set device initialization bit for at least 1ms */
|
||||
sctl((sctl() & ~0xf) | 1);
|
||||
|
||||
Timer::Connection timer;
|
||||
timer.msleep(1);
|
||||
|
||||
sctl(sctl() & ~0xf);
|
||||
|
||||
while ((status() & 0xf) != 0x3) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of this structure
|
||||
*/
|
||||
static uint32_t size() { return 0x80; }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* AHCI command list structure
|
||||
*/
|
||||
struct Command_list
|
||||
{
|
||||
uint8_t cfl:5; /* Command FIS length */
|
||||
uint8_t a:1; /* ATAPI command flag */
|
||||
uint8_t w:1; /* Write flag */
|
||||
uint8_t p:1; /* Prefetchable flag */
|
||||
uint8_t unsused; /* we don't use byte 2 yet */
|
||||
uint16_t prdtl; /* Physical region descr. length */
|
||||
uint32_t prdbc; /* PRD byte count */
|
||||
uint32_t cmd_table_base_l; /* Command table base addr (low) */
|
||||
uint32_t cmd_table_base_u;
|
||||
uint32_t reserved[0];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* AHCI command table structure
|
||||
*/
|
||||
struct Command_table
|
||||
{
|
||||
/**
|
||||
* Setup FIS and PRD
|
||||
*/
|
||||
void setup_command(uint8_t cmd, uint64_t lba48, uint16_t blk_cnt, addr_t phys_addr)
|
||||
{
|
||||
enum { MAX_BYTES = 1 << 22 }; /* 4MB = one PRD */
|
||||
uint8_t *fis = (uint8_t *)this;
|
||||
|
||||
uint64_t addr = phys_addr;
|
||||
|
||||
/* setup FIS */
|
||||
fis[0] = 0x27; /* type = host to device */
|
||||
fis[1] = 0x80; /* set update command flag */
|
||||
fis[2] = cmd; /* actual command */
|
||||
fis[4] = lba48 & 0xff; /* LBA 0 - 7 */
|
||||
fis[5] = (lba48 >> 8) & 0xff; /* LBA 8 - 15 */
|
||||
fis[6] = (lba48 >> 16) & 0xff; /* LBA 16 - 23 */
|
||||
fis[7] = 0x40; /* LBA mode flag */
|
||||
fis[8] = (lba48 >> 24) & 0xff; /* LBA 24 - 31 */
|
||||
fis[9] = (lba48 >> 32) & 0xff; /* LBA 32 - 39 */
|
||||
fis[10] = (lba48 >> 40) & 0xff; /* LBA 40 - 47 */
|
||||
fis[12] = blk_cnt & 0xff; /* sector count 0 - 7 */
|
||||
fis[13] = (blk_cnt >> 8) & 0xff; /* sector count 8 - 15 */
|
||||
|
||||
/* setup PRD for DMA */
|
||||
uint32_t addr_l = addr;
|
||||
uint32_t addr_u = addr >> 32;
|
||||
memcpy(&fis[0x80], &addr_l, 4); /* DBA: data base address */
|
||||
memcpy(&fis[0x84], &addr_u, 4); /* DBA: data base address upper */
|
||||
uint32_t bytes = (blk_cnt * 512) - 1;
|
||||
|
||||
if (bytes + 1 > MAX_BYTES) {
|
||||
PERR("Unsupported request size %u > %u", bytes, MAX_BYTES);
|
||||
throw Block::Driver::Io_error();
|
||||
}
|
||||
|
||||
/* set byte count for PRD 22 bit */
|
||||
fis[0x8c] = bytes & 0xff;
|
||||
fis[0x8d] = (bytes >> 8) & 0xff;
|
||||
fis[0x8e] = (bytes >> 16) & 0x3f;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic base of AHCI device
|
||||
*/
|
||||
class Ahci_device_base
|
||||
{
|
||||
protected:
|
||||
|
||||
enum {
|
||||
AHCI_PORT_BASE = 0x100,
|
||||
};
|
||||
|
||||
Generic_ctrl *_ctrl; /* generic host control */
|
||||
Ahci_port *_port; /* port base of device */
|
||||
Irq_session_client *_irq; /* device IRQ */
|
||||
Genode::Signal_receiver _irq_rec; /* IRQ signal receiver */
|
||||
Genode::Signal_context _irq_ctx; /* IRQ signal context */
|
||||
size_t _block_cnt; /* number of blocks on device */
|
||||
Command_list *_cmd_list; /* pointer to command list */
|
||||
Command_table *_cmd_table; /* pointer to command table */
|
||||
Ram_dataspace_capability _ds; /* backing-store of internal data structures */
|
||||
Io_mem_session_capability _io_cap; /* I/O mem cap */
|
||||
|
||||
/**
|
||||
* Find first non-ATAPI device that is ready
|
||||
*/
|
||||
bool _scan_ports()
|
||||
{
|
||||
uint32_t port_cnt = _ctrl->port_count();
|
||||
|
||||
Ahci_port *port = (Ahci_port *)((char *)_ctrl + AHCI_PORT_BASE);
|
||||
for (uint32_t i = 0;
|
||||
i <= port_cnt;
|
||||
i++, port = (Ahci_port *)((char *)port + Ahci_port::size())) {
|
||||
|
||||
bool is_atapi = port->cmd() & (1 << 24); /* check bit 24 */
|
||||
PINF("Port %u: ATAPI %s", i, is_atapi ? "yes" : "no");
|
||||
|
||||
if (is_atapi)
|
||||
continue;
|
||||
|
||||
/* port status */
|
||||
if (port->status_active()) {
|
||||
PINF("Port %u: Detected interface is active", i);
|
||||
_port = port;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void _setup_memory()
|
||||
{
|
||||
_ds = alloc_dma_buffer(0x1000);
|
||||
addr_t phys = Dataspace_client(_ds).phys_addr();
|
||||
uint8_t *virt = (uint8_t *)env()->rm_session()->attach(_ds);
|
||||
|
||||
/* setup command list (size 1k naturally aligned) */
|
||||
_port->cmd_list_base(phys);
|
||||
_cmd_list = (struct Command_list *)(virt);
|
||||
|
||||
/* for now we transfer one PRD with a FIS size of 5 byte */
|
||||
_cmd_list->prdtl = 1;
|
||||
_cmd_list->cfl = 5;
|
||||
virt += 1024; phys += 1024;
|
||||
|
||||
/* setup received FIS base (256 byte naturally aligned) */
|
||||
_port->fis_base(phys);
|
||||
virt += 256; phys += 256;
|
||||
|
||||
uint64_t addr = phys;
|
||||
/* setup command table (128 byte aligned (cache line size)) */
|
||||
_cmd_list->cmd_table_base_l = addr;
|
||||
_cmd_list->cmd_table_base_u = addr >> 32;
|
||||
_cmd_table = (struct Command_table *)(virt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a prepared command
|
||||
*/
|
||||
void _execute_command()
|
||||
{
|
||||
/* reset byte count */
|
||||
_cmd_list->prdbc = 0;
|
||||
|
||||
/* start HBA command processing */
|
||||
_port->hba_enable();
|
||||
|
||||
if (verbose)
|
||||
PDBG("Int status: global: %x port: %x error: %x",
|
||||
_ctrl->hba_intr_status(), _port->intr_status(), _port->err());
|
||||
|
||||
/* write CI (command issue) slot 0 */
|
||||
_port->cmd_issue(1);
|
||||
|
||||
uint32_t status = 0;
|
||||
while (!status) {
|
||||
|
||||
/* wait for interrupt */
|
||||
_irq_rec.wait_for_signal();
|
||||
|
||||
if (verbose)
|
||||
PDBG("Int status (IRQ): global: %x port: %x error: %x",
|
||||
_ctrl->hba_intr_status(), _port->intr_status(), _port->err());
|
||||
|
||||
/* acknowledge interrupt */
|
||||
status = _port->interrupt_ack();
|
||||
}
|
||||
|
||||
/* check for error */
|
||||
enum {
|
||||
INT_SETUP_FIS_DMA = 0x4,
|
||||
INT_SETUP_FIS_PIO = 0x2,
|
||||
INT_HOST_REGISTER_FIS = 0x1,
|
||||
INT_OK = INT_SETUP_FIS_DMA | INT_SETUP_FIS_PIO | INT_HOST_REGISTER_FIS
|
||||
};
|
||||
|
||||
if (!(status & INT_OK)) {
|
||||
PERR("Error during SATA request (irq state %x)", status);
|
||||
throw Block::Driver::Io_error();
|
||||
}
|
||||
|
||||
/* acknowledge global port interrupt */
|
||||
_ctrl->hba_interrupt_ack();
|
||||
|
||||
_irq->ack_irq();
|
||||
|
||||
/* disable hba */
|
||||
_port->hba_disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute ATA 'IDENTIFY DEVICE' command
|
||||
*/
|
||||
void _identify_device()
|
||||
{
|
||||
Ram_dataspace_capability ds = alloc_dma_buffer(0x1000);
|
||||
uint16_t *dev_info = (uint16_t *)env()->rm_session()->attach(ds);
|
||||
|
||||
enum { IDENTIFY_DEVICE = 0xec };
|
||||
try {
|
||||
addr_t phys = Dataspace_client(ds).phys_addr();
|
||||
_cmd_table->setup_command(IDENTIFY_DEVICE, 0, 0, phys);
|
||||
_execute_command();
|
||||
|
||||
/* XXX: just read 32 bit for now */
|
||||
_block_cnt = *((size_t *)&dev_info[100]);
|
||||
|
||||
} catch (Block::Driver::Io_error) {
|
||||
PERR("I/O Error: Could not identify device");
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
PDBG("Max LBA48 block: %zu", _block_cnt);
|
||||
|
||||
env()->rm_session()->detach(dev_info);
|
||||
env()->ram_session()->free(ds);
|
||||
}
|
||||
|
||||
Ahci_device_base(addr_t base, Io_mem_session_capability io_cap)
|
||||
: _ctrl((Generic_ctrl *)base), _port(0), _irq(0), _cmd_list(0),
|
||||
_cmd_table(0), _io_cap(io_cap) { }
|
||||
|
||||
public:
|
||||
|
||||
virtual ~Ahci_device_base()
|
||||
{
|
||||
/* delete internal data structures */
|
||||
if (_ds.valid()) {
|
||||
env()->rm_session()->detach((void*)_cmd_list);
|
||||
env()->ram_session()->free(_ds);
|
||||
}
|
||||
|
||||
/* close I/O mem session */
|
||||
env()->rm_session()->detach((void *)_ctrl);
|
||||
env()->parent()->close(_io_cap);
|
||||
|
||||
/* XXX release _pci_device */
|
||||
|
||||
/* close IRQ session */
|
||||
destroy(env()->heap(), _irq);
|
||||
}
|
||||
|
||||
static size_t block_size() { return 512; }
|
||||
size_t block_count() { return _block_cnt; }
|
||||
|
||||
/**
|
||||
* Issue ATA 'READ_DMA_EXT' command
|
||||
*/
|
||||
void read(Block::sector_t block_number, size_t block_count,
|
||||
addr_t phys)
|
||||
{
|
||||
_cmd_list->w = 0;
|
||||
|
||||
enum { READ_DMA_EXT = 0x25 };
|
||||
_cmd_table->setup_command(READ_DMA_EXT, block_number,
|
||||
block_count, phys);
|
||||
_execute_command();
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue ATA 'WRITE_DMA_EXT' command
|
||||
*/
|
||||
void write(Block::sector_t block_number, size_t block_count,
|
||||
addr_t phys)
|
||||
{
|
||||
_cmd_list->w = 1;
|
||||
|
||||
enum { WRITE_DMA_EXT = 0x35 };
|
||||
_cmd_table->setup_command(WRITE_DMA_EXT, block_number,
|
||||
block_count, phys);
|
||||
_execute_command();
|
||||
}
|
||||
|
||||
virtual Ram_dataspace_capability alloc_dma_buffer(size_t size) = 0;
|
||||
virtual void free_dma_buffer(Ram_dataspace_capability cap) = 0;
|
||||
};
|
||||
|
||||
#endif /* _AHCI_DEVICE_BASE_H_ */
|
||||
|
@ -1,90 +0,0 @@
|
||||
|
||||
/*
|
||||
* \brief Generic base of AHCI driver
|
||||
* \author Sebastian Sumpf <Sebastian.Sumpf@genode-labs.com>
|
||||
* \author Martin Stein <Martin.Stein@genode-labs.com>
|
||||
* \date 2011-08-10
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2011-2013 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _AHCI_DRIVER_BASE_H_
|
||||
#define _AHCI_DRIVER_BASE_H_
|
||||
|
||||
/* local includes */
|
||||
#include <ahci_device.h>
|
||||
|
||||
/**
|
||||
* Implementation of block driver interface
|
||||
*/
|
||||
class Ahci_driver_base : public Block::Driver
|
||||
{
|
||||
protected:
|
||||
|
||||
Ahci_device *_device;
|
||||
|
||||
void _sanity_check(size_t block_number, size_t count)
|
||||
{
|
||||
if (!_device || (block_number + count > block_count()))
|
||||
throw Io_error();
|
||||
}
|
||||
|
||||
Ahci_driver_base(Ahci_device * const device)
|
||||
: _device(device) { }
|
||||
|
||||
public:
|
||||
|
||||
~Ahci_driver_base()
|
||||
{
|
||||
if (_device)
|
||||
destroy(env()->heap(), _device);
|
||||
}
|
||||
|
||||
size_t block_size() { return Ahci_device::block_size(); }
|
||||
Block::sector_t block_count() {
|
||||
return _device ? _device->block_count() : 0; }
|
||||
|
||||
Block::Session::Operations ops()
|
||||
{
|
||||
Block::Session::Operations o;
|
||||
o.set_operation(Block::Packet_descriptor::READ);
|
||||
o.set_operation(Block::Packet_descriptor::WRITE);
|
||||
return o;
|
||||
}
|
||||
|
||||
bool dma_enabled() { return true; }
|
||||
|
||||
void read_dma(Block::sector_t block_number,
|
||||
size_t block_count,
|
||||
addr_t phys,
|
||||
Block::Packet_descriptor &packet)
|
||||
{
|
||||
_sanity_check(block_number, block_count);
|
||||
_device->read(block_number, block_count, phys);
|
||||
ack_packet(packet);
|
||||
}
|
||||
|
||||
void write_dma(Block::sector_t block_number,
|
||||
size_t block_count,
|
||||
addr_t phys,
|
||||
Block::Packet_descriptor &packet)
|
||||
{
|
||||
_sanity_check(block_number, block_count);
|
||||
_device->write(block_number, block_count, phys);
|
||||
ack_packet(packet);
|
||||
}
|
||||
|
||||
Ram_dataspace_capability alloc_dma_buffer(size_t size) {
|
||||
return _device->alloc_dma_buffer(size); }
|
||||
|
||||
void free_dma_buffer(Ram_dataspace_capability cap) {
|
||||
return _device->free_dma_buffer(cap); }
|
||||
};
|
||||
|
||||
#endif /* _AHCI_DRIVER_BASE_H_ */
|
||||
|
@ -1,63 +1,158 @@
|
||||
/*
|
||||
* \brief Minimal AHCI-ATA driver
|
||||
* \author Sebastian Sumpf <Sebastian.Sumpf@genode-labs.com>
|
||||
* \date 2011-08-10
|
||||
*
|
||||
* This driver currently supports only one command slot, one FIS, and one PRD
|
||||
* per FIS, thus limiting the request size to 4MB per request. Since the packet
|
||||
* interface currently only supports a synchronous mode of operation the above
|
||||
* limitations seems reasonable.
|
||||
/**
|
||||
* \brief Block driver session creation
|
||||
* \author Sebastian Sumpf
|
||||
* \date 2015-09-29
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2011-2013 Genode Labs GmbH
|
||||
* Copyright (C) 2015 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
/* Genode includes */
|
||||
#include <block/component.h>
|
||||
#include <os/config.h>
|
||||
#include <os/server.h>
|
||||
#include <util/xml_node.h>
|
||||
|
||||
/* local includes */
|
||||
#include <ahci_driver.h>
|
||||
#include "ahci.h"
|
||||
|
||||
using namespace Genode;
|
||||
namespace Block {
|
||||
class Factory;
|
||||
class Root_multiple_clients;
|
||||
}
|
||||
|
||||
|
||||
struct Block::Factory : Driver_factory
|
||||
{
|
||||
long device_num;
|
||||
|
||||
Block::Driver *create()
|
||||
{
|
||||
return Ahci_driver::claim_port(device_num);
|
||||
}
|
||||
|
||||
void destroy(Block::Driver *driver)
|
||||
{
|
||||
Ahci_driver::free_port(device_num);
|
||||
}
|
||||
|
||||
Factory(long device_num) : device_num(device_num) { }
|
||||
};
|
||||
|
||||
|
||||
class Session_component : public Block::Session_component
|
||||
{
|
||||
public:
|
||||
|
||||
Session_component(Block::Driver_factory &driver_factory,
|
||||
Server::Entrypoint &ep, Genode::size_t buf_size)
|
||||
: Block::Session_component(driver_factory, ep, buf_size) { }
|
||||
|
||||
Block::Driver_factory &factory() { return _driver_factory; }
|
||||
};
|
||||
|
||||
|
||||
class Block::Root_multiple_clients : public Root_component< ::Session_component>,
|
||||
public Ahci_root
|
||||
{
|
||||
private:
|
||||
|
||||
Server::Entrypoint &_ep;
|
||||
|
||||
long _device_num(const char *session_label)
|
||||
{
|
||||
long num = -1;
|
||||
|
||||
try {
|
||||
using namespace Genode;
|
||||
|
||||
Xml_node policy = Genode::config()->xml_node().sub_node("policy");
|
||||
|
||||
for (;; policy = policy.next("policy")) {
|
||||
char label_buf[64];
|
||||
policy.attribute("label").value(label_buf, sizeof(label_buf));
|
||||
|
||||
if (Genode::strcmp(session_label, label_buf))
|
||||
continue;
|
||||
|
||||
/* read device attribute */
|
||||
policy.attribute("device").value(&num);
|
||||
break;
|
||||
}
|
||||
} catch (...) {}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
::Session_component *_create_session(const char *args)
|
||||
{
|
||||
size_t tx_buf_size =
|
||||
Arg_string::find_arg(args, "tx_buf_size").ulong_value(0);
|
||||
|
||||
/* TODO: build quota check */
|
||||
|
||||
/* Search for configured device */
|
||||
char label_buf[64];
|
||||
Genode::Arg_string::find_arg(args,
|
||||
"label").string(label_buf,
|
||||
sizeof(label_buf),
|
||||
"<unlabeled>");
|
||||
long num = _device_num(label_buf);
|
||||
if (num < 0) {
|
||||
PERR("No confguration found for client: %s", label_buf);
|
||||
throw Root::Invalid_args();
|
||||
}
|
||||
|
||||
if (!Ahci_driver::is_avail(num)) {
|
||||
PERR("Device %ld not avaiable", num);
|
||||
throw Root::Unavailable();
|
||||
}
|
||||
|
||||
Factory *factory = new (Genode::env()->heap()) Factory(num);
|
||||
return new (md_alloc()) ::Session_component(*factory,
|
||||
_ep, tx_buf_size);
|
||||
}
|
||||
|
||||
void _destroy_session(::Session_component *session)
|
||||
{
|
||||
Driver_factory &factory = session->factory();
|
||||
destroy(env()->heap(), session);
|
||||
destroy(env()->heap(), &factory);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Root_multiple_clients(Server::Entrypoint &ep, Allocator *md_alloc)
|
||||
: Root_component(&ep.rpc_ep(), md_alloc), _ep(ep) { }
|
||||
|
||||
Server::Entrypoint &entrypoint() override { return _ep; }
|
||||
|
||||
void announce() override
|
||||
{
|
||||
Genode::env()->parent()->announce(_ep.manage(*this));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct Main
|
||||
{
|
||||
Server::Entrypoint &ep;
|
||||
|
||||
struct Factory : Block::Driver_factory
|
||||
{
|
||||
Block::Driver *create() {
|
||||
static Ahci_driver driver;
|
||||
return &driver; }
|
||||
|
||||
void destroy(Block::Driver *driver) { }
|
||||
} factory;
|
||||
|
||||
Block::Root root;
|
||||
Block::Root_multiple_clients root;
|
||||
|
||||
Main(Server::Entrypoint &ep)
|
||||
: ep(ep), root(ep, Genode::env()->heap(), factory)
|
||||
: root(ep, Genode::env()->heap())
|
||||
{
|
||||
printf("--- AHCI driver started ---\n");
|
||||
|
||||
Genode::env()->parent()->announce(ep.manage(root));
|
||||
PINF("--- Starting AHCI driver -> done right .-) --\n");
|
||||
Ahci_driver::init(root);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/************
|
||||
** Server **
|
||||
************/
|
||||
|
||||
namespace Server {
|
||||
char const *name() { return "ahci_ep"; }
|
||||
size_t stack_size() { return 2*1024*sizeof(long); }
|
||||
void construct(Entrypoint &ep) { static Main server(ep); }
|
||||
char const *name() { return "ahci_ep"; }
|
||||
size_t stack_size() { return 2 * 1024 * sizeof(long); }
|
||||
void construct(Entrypoint &ep) { static Main server(ep); }
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
TARGET = ahci
|
||||
LIBS += ahci server
|
||||
SRC_CC += empty.cc
|
||||
TARGET = ahci_drv
|
||||
SRC_CC = main.cc ahci.cc
|
||||
LIBS += server base config ahci_platform
|
||||
|
@ -1,209 +0,0 @@
|
||||
/*
|
||||
* \brief AHCI device
|
||||
* \author Sebastian Sumpf <Sebastian.Sumpf@genode-labs.com>
|
||||
* \author Martin Stein <Martin.Stein@genode-labs.com>
|
||||
* \date 2011-08-10
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2011-2013 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _AHCI_DEVICE_H_
|
||||
#define _AHCI_DEVICE_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <pci_session/connection.h>
|
||||
#include <pci_device/client.h>
|
||||
|
||||
/* local includes */
|
||||
#include <ahci_device_base.h>
|
||||
|
||||
/**
|
||||
* AHCI device
|
||||
*/
|
||||
class Ahci_device : public Ahci_device_base
|
||||
{
|
||||
private:
|
||||
|
||||
enum Pci_config {
|
||||
PCI_CFG_BMIBA_OFF = 0x24, /* offset in PCI config space */
|
||||
CLASS_MASS_STORAGE = 0x10000U,
|
||||
SUBCLASS_AHCI = 0x0600U,
|
||||
CLASS_MASK = 0xffff00U,
|
||||
AHCI_BASE_ID = 0x5, /* resource id of AHCI base addr <BAR 5> */
|
||||
AHCI_INTR_OFF = 0x3c, /* offset of interrupt information in config space */
|
||||
PCI_CFG_CMD_REG = 0x4,
|
||||
};
|
||||
|
||||
::Pci::Connection &_pci;
|
||||
::Pci::Device_client *_pci_device;
|
||||
|
||||
/**
|
||||
* Return next PCI device
|
||||
*/
|
||||
static Pci::Device_capability _scan_pci(Pci::Connection &pci, Pci::Device_capability prev_device_cap)
|
||||
{
|
||||
Pci::Device_capability device_cap;
|
||||
|
||||
for (unsigned i = 0; i < 2; i++) {
|
||||
try {
|
||||
device_cap = pci.next_device(prev_device_cap,
|
||||
CLASS_MASS_STORAGE | SUBCLASS_AHCI,
|
||||
CLASS_MASK);
|
||||
break;
|
||||
} catch (Pci::Device::Quota_exceeded) {
|
||||
Genode::env()->parent()->upgrade(pci.cap(), "ram_quota=4096");
|
||||
}
|
||||
}
|
||||
|
||||
if (prev_device_cap.valid())
|
||||
pci.release_device(prev_device_cap);
|
||||
|
||||
return device_cap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize port
|
||||
*/
|
||||
void _init(::Pci::Device_client *pci_device)
|
||||
{
|
||||
uint32_t version = _ctrl->version();
|
||||
PINF("AHCI Version: %x.%04x", (version >> 16), version & 0xffff);
|
||||
|
||||
/* HBA capabilities at offset 0 */
|
||||
uint32_t caps = _ctrl->hba_cap();
|
||||
PINF("CAPs:");
|
||||
PINF("\tPort count: %u", _ctrl->port_count());
|
||||
PINF("\tCommand slots: %u", _ctrl->cmd_slots());
|
||||
PINF("\tAHCI only: %s", (caps & 0x20000) ? "yes" : "no");
|
||||
PINF("\tNative command queuing: %s", (caps & 0x40000000) ? "yes" : "no");
|
||||
PINF("\t64 Bit: %s", (caps & 0x80000000) ? "yes" : "no");
|
||||
|
||||
/* setup up AHCI data structures */
|
||||
_setup_memory();
|
||||
|
||||
/* check and possibly enable AHCI mode */
|
||||
_ctrl->global_enable_ahci();
|
||||
|
||||
/* enable global interrupts */
|
||||
_ctrl->global_interrupt_enable();
|
||||
|
||||
/* disable power mgmt. */
|
||||
_port->disable_pm();
|
||||
|
||||
/* startup device */
|
||||
_port->get_ready();
|
||||
|
||||
/* reset port */
|
||||
_port->reset();
|
||||
|
||||
/* clear error register */
|
||||
_port->err(_port->err());
|
||||
|
||||
/* port interrupt enable */
|
||||
_port->interrupt_enable();
|
||||
|
||||
/* ack all possibly pending interrupts */
|
||||
_port->interrupt_ack();
|
||||
_ctrl->hba_interrupt_ack();
|
||||
|
||||
/* retrieve block count */
|
||||
_identify_device();
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Ahci_device(addr_t base, Io_mem_session_capability io_cap,
|
||||
Pci::Connection &pci)
|
||||
: Ahci_device_base(base, io_cap), _pci(pci), _pci_device(0) { }
|
||||
|
||||
/**
|
||||
* Probe PCI-bus for AHCI and ATA-devices
|
||||
*/
|
||||
static Ahci_device *probe(Pci::Connection &pci)
|
||||
{
|
||||
Pci::Device_capability device_cap;
|
||||
Ahci_device *device;
|
||||
|
||||
while ((device_cap = _scan_pci(pci, device_cap)).valid()) {
|
||||
|
||||
::Pci::Device_client * pci_device =
|
||||
new(env()->heap()) ::Pci::Device_client(device_cap);
|
||||
|
||||
PINF("Found AHCI HBA (Vendor ID: %04x Device ID: %04x Class:"
|
||||
" %08x)\n", pci_device->vendor_id(),
|
||||
pci_device->device_id(), pci_device->class_code());
|
||||
|
||||
/* enable Bus Master, Memory Space, I/O Space access by dev */
|
||||
pci_device->config_write(PCI_CFG_CMD_REG, 0x7,
|
||||
::Pci::Device::ACCESS_16BIT);
|
||||
|
||||
/* read and map base address of AHCI controller */
|
||||
Pci::Device::Resource resource = pci_device->resource(AHCI_BASE_ID);
|
||||
|
||||
Io_mem_connection io(resource.base(), resource.size());
|
||||
addr_t addr = (addr_t)env()->rm_session()->attach(io.dataspace());
|
||||
|
||||
/* add possible resource offset */
|
||||
addr += resource.base() & 0xfff;
|
||||
|
||||
if (verbose)
|
||||
PDBG("resource base: %x virt: %lx", resource.base(), addr);
|
||||
|
||||
/* create and test device */
|
||||
device = new(env()->heap()) Ahci_device(addr, io.cap(), pci);
|
||||
if (device->_scan_ports()) {
|
||||
|
||||
io.on_destruction(Io_mem_connection::KEEP_OPEN);
|
||||
|
||||
/* read IRQ information */
|
||||
unsigned long intr = pci_device->config_read(AHCI_INTR_OFF,
|
||||
::Pci::Device::ACCESS_32BIT);
|
||||
|
||||
if (verbose) {
|
||||
PDBG("Interrupt pin: %lu line: %lu", (intr >> 8) & 0xff, intr & 0xff);
|
||||
|
||||
unsigned char bus, dev, func;
|
||||
pci_device->bus_address(&bus, &dev, &func);
|
||||
PDBG("Bus address: %x:%02x.%u (0x%x)", bus, dev, func, (bus << 8) | ((dev & 0x1f) << 3) | (func & 0x7));
|
||||
}
|
||||
|
||||
device->_irq = new(env()->heap()) Irq_session_client(pci_device->irq(0));
|
||||
Genode::Signal_context_capability cap = device->_irq_rec.manage(&device->_irq_ctx);
|
||||
device->_irq->sigh(cap);
|
||||
device->_irq->ack_irq();
|
||||
|
||||
device->_pci_device = pci_device;
|
||||
/* get device ready */
|
||||
device->_init(pci_device);
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
destroy(env()->heap(), pci_device);
|
||||
env()->rm_session()->detach(addr);
|
||||
destroy(env()->heap(), device);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Ram_dataspace_capability alloc_dma_buffer(size_t size)
|
||||
{
|
||||
/* transfer quota to pci driver, otherwise we get a exception */
|
||||
char quota[32];
|
||||
Genode::snprintf(quota, sizeof(quota), "ram_quota=%zd", size);
|
||||
Genode::env()->parent()->upgrade(_pci.cap(), quota);
|
||||
|
||||
return _pci.alloc_dma_buffer(size);
|
||||
}
|
||||
|
||||
void free_dma_buffer(Ram_dataspace_capability cap) {
|
||||
return _pci.free_dma_buffer(cap); }
|
||||
};
|
||||
|
||||
#endif /* _AHCI_DEVICE_H_ */
|
||||
|
@ -1,58 +0,0 @@
|
||||
/*
|
||||
* \brief AHCI driver
|
||||
* \author Sebastian Sumpf <Sebastian.Sumpf@genode-labs.com>
|
||||
* \author Martin Stein <Martin.Stein@genode-labs.com>
|
||||
* \date 2006-08-15
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2006-2013 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _AHCI_DRIVER_H_
|
||||
#define _AHCI_DRIVER_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <pci_session/connection.h>
|
||||
|
||||
/* local includes */
|
||||
#include <ahci_driver_base.h>
|
||||
|
||||
/**
|
||||
* Helper class for AHCI driver to ensure specific member-initialization order
|
||||
*/
|
||||
class Ahci_pci_connection {
|
||||
|
||||
protected:
|
||||
|
||||
Pci::Connection _pci;
|
||||
};
|
||||
|
||||
/**
|
||||
* AHCI driver
|
||||
*/
|
||||
class Ahci_driver : public Ahci_pci_connection,
|
||||
public Ahci_driver_base
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
Ahci_driver() : Ahci_driver_base(nullptr) {
|
||||
Ahci_device * device = Ahci_device::probe(_pci);
|
||||
|
||||
if (!device) {
|
||||
PWRN("Could not find ahci device");
|
||||
throw Root::Unavailable();
|
||||
}
|
||||
|
||||
_device = device;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _AHCI_DRIVER_H_ */
|
||||
|
137
repos/os/src/drivers/ahci/x86/platform.cc
Normal file
137
repos/os/src/drivers/ahci/x86/platform.cc
Normal file
@ -0,0 +1,137 @@
|
||||
/**
|
||||
* \brief Driver for PCI-bus platforms
|
||||
* \author Sebastian Sumpf
|
||||
* \date 2015-04-29
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#include <irq_session/connection.h>
|
||||
#include <pci_session/connection.h>
|
||||
#include <pci_device/client.h>
|
||||
#include <util/volatile_object.h>
|
||||
|
||||
#include <ahci.h>
|
||||
|
||||
|
||||
using namespace Genode;
|
||||
|
||||
struct X86_hba : Platform::Hba
|
||||
{
|
||||
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::Connection pci;
|
||||
Pci::Device_capability pci_device_cap;
|
||||
Lazy_volatile_object<Pci::Device_client> pci_device;
|
||||
Lazy_volatile_object<Irq_session_client> irq;
|
||||
addr_t res_base;
|
||||
size_t res_size;
|
||||
|
||||
X86_hba()
|
||||
{
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
try {
|
||||
if (!(pci_device_cap =
|
||||
pci.next_device(pci_device_cap, AHCI_DEVICE, CLASS_MASK)).valid()) {
|
||||
PERR("No AHCI controller found");
|
||||
throw -1;
|
||||
}
|
||||
break;
|
||||
} catch (Pci::Device::Quota_exceeded) {
|
||||
Genode::env()->parent()->upgrade(pci.cap(), "ram_quota=4096");
|
||||
}
|
||||
|
||||
/* construct pci client */
|
||||
pci_device.construct(pci_device_cap);
|
||||
PINF("AHCI found (vendor: %04x device: %04x class:"
|
||||
" %08x)\n", pci_device->vendor_id(),
|
||||
pci_device->device_id(), pci_device->class_code());
|
||||
|
||||
/* read base address of controller */
|
||||
Pci::Device::Resource resource = pci_device->resource(AHCI_BASE_ID);
|
||||
res_base = resource.base();
|
||||
res_size = resource.size();
|
||||
|
||||
if (verbose)
|
||||
PDBG("base: %lx size: %zx", res_base, res_size);
|
||||
|
||||
/* enable bus master */
|
||||
uint16_t cmd = pci_device->config_read(PCI_CMD, Pci::Device::ACCESS_16BIT);
|
||||
cmd |= 0x4;
|
||||
pci_device->config_write(PCI_CMD, cmd, Pci::Device::ACCESS_16BIT);
|
||||
|
||||
irq.construct(pci_device->irq(0));
|
||||
}
|
||||
|
||||
void disable_msi()
|
||||
{
|
||||
enum { PM_CAP_OFF = 0x34, MSI_CAP = 0x5, MSI_ENABLED = 0x1 };
|
||||
uint8_t cap = pci_device->config_read(PM_CAP_OFF, ::Pci::Device::ACCESS_8BIT);
|
||||
|
||||
/* iterate through cap pointers */
|
||||
for (uint16_t val = 0; cap; cap = val >> 8) {
|
||||
val = pci_device->config_read(cap, ::Pci::Device::ACCESS_16BIT);
|
||||
|
||||
if ((val & 0xff) != MSI_CAP)
|
||||
continue;
|
||||
|
||||
uint16_t msi = pci_device->config_read(cap + 2, ::Pci::Device::ACCESS_16BIT);
|
||||
|
||||
if (msi & MSI_ENABLED) {
|
||||
pci_device->config_write(cap + 2, msi ^ MSI_CAP, ::Pci::Device::ACCESS_8BIT);
|
||||
PINF("Disabled MSIs %x", msi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************
|
||||
** 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
|
||||
{
|
||||
/* transfer quota to pci driver, otherwise we get a exception */
|
||||
char quota[32];
|
||||
snprintf(quota, sizeof(quota), "ram_quota=%zd", size);
|
||||
env()->parent()->upgrade(pci.cap(), quota);
|
||||
|
||||
return pci.alloc_dma_buffer(size);
|
||||
}
|
||||
|
||||
void free_dma_buffer(Genode::Ram_dataspace_capability ds)
|
||||
{
|
||||
pci.free_dma_buffer(ds);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Platform::Hba &Platform::init(Mmio::Delayer &)
|
||||
{
|
||||
static X86_hba h;
|
||||
return h;
|
||||
}
|
@ -58,7 +58,7 @@ set config {
|
||||
</provides>
|
||||
<config><policy label="l4linux" uart="0" detect_size="yes"/></config>
|
||||
</start>
|
||||
<start name="ahci">
|
||||
<start name="ahci_drv">
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<provides><service name="Block"/></provides>
|
||||
</start>
|
||||
@ -109,7 +109,7 @@ set boot_modules {
|
||||
kdb_uart_drv
|
||||
l4linux
|
||||
initrd.gz
|
||||
ahci
|
||||
ahci_drv
|
||||
platform_drv }
|
||||
|
||||
lappend_if $interpose_part_blk boot_modules part_blk
|
||||
|
@ -168,8 +168,12 @@ append_if [expr !$use_usb] config {
|
||||
|
||||
append_if $use_block_sata config {
|
||||
<start name="ahci" priority="-1">
|
||||
<binary name="ahci_drv" />
|
||||
<resource name="RAM" quantum="1M" />
|
||||
<provides><service name="Block"/></provides>
|
||||
<config>
|
||||
<policy label="seoul" device="0" />
|
||||
</config>
|
||||
</start>}
|
||||
|
||||
append_if $use_genode_iso config {
|
||||
@ -422,7 +426,7 @@ lappend_if [have_spec acpi] boot_modules acpi_drv
|
||||
lappend_if [have_spec nova] boot_modules pci_device_pd
|
||||
lappend_if [expr !$use_usb] boot_modules ps2_drv
|
||||
lappend_if $use_usb boot_modules usb_drv
|
||||
lappend_if $use_block_sata boot_modules ahci
|
||||
lappend_if $use_block_sata boot_modules ahci_drv
|
||||
lappend_if $use_nic_session boot_modules nic_drv
|
||||
lappend_if $use_nic_bridge boot_modules nic_bridge
|
||||
lappend_if $use_framebuffer boot_modules fb_drv
|
||||
|
Loading…
x
Reference in New Issue
Block a user