From 7910b5146f4fb26968a026c44cbb96b9ac19e72d Mon Sep 17 00:00:00 2001 From: Sebastian Sumpf Date: Wed, 25 Mar 2015 11:12:52 +0100 Subject: [PATCH] ahci: new AHCI driver implementation Supports native command queueing and multiple ports. --- repos/libports/run/libc_ffat.run | 2 +- repos/libports/run/libc_ffat_fs.run | 22 +- repos/libports/run/libc_filesystem_test.inc | 15 +- repos/libports/run/qt5_drivers.inc | 30 + repos/libports/run/qt5_textedit.run | 9 +- repos/os/lib/mk/ahci.inc | 17 - repos/os/lib/mk/exynos5/ahci.mk | 17 - repos/os/lib/mk/exynos5/ahci_platform.mk | 6 + repos/os/lib/mk/x86/ahci.mk | 11 - repos/os/lib/mk/x86/ahci_platform.mk | 6 + repos/os/src/drivers/ahci/README | 32 +- repos/os/src/drivers/ahci/ahci.cc | 208 ++ repos/os/src/drivers/ahci/ahci.h | 866 ++++++++ repos/os/src/drivers/ahci/ata_driver.h | 212 ++ repos/os/src/drivers/ahci/atapi_driver.h | 206 ++ repos/os/src/drivers/ahci/empty.cc | 3 - .../src/drivers/ahci/exynos5/ahci_driver.cc | 1960 ----------------- .../os/src/drivers/ahci/exynos5/ahci_driver.h | 83 - .../os/src/drivers/ahci/exynos5/bench/main.cc | 198 -- .../src/drivers/ahci/exynos5/bench/target.mk | 6 - repos/os/src/drivers/ahci/exynos5/platform.cc | 385 ++++ .../drivers/ahci/include/ahci_device_base.h | 578 ----- .../drivers/ahci/include/ahci_driver_base.h | 90 - repos/os/src/drivers/ahci/main.cc | 171 +- repos/os/src/drivers/ahci/target.mk | 6 +- repos/os/src/drivers/ahci/x86/ahci_device.h | 209 -- repos/os/src/drivers/ahci/x86/ahci_driver.h | 58 - repos/os/src/drivers/ahci/x86/platform.cc | 137 ++ repos/ports-foc/run/l4linux_ahci_bench.run | 4 +- repos/ports/run/seoul.inc | 6 +- 30 files changed, 2248 insertions(+), 3305 deletions(-) delete mode 100644 repos/os/lib/mk/ahci.inc delete mode 100644 repos/os/lib/mk/exynos5/ahci.mk create mode 100644 repos/os/lib/mk/exynos5/ahci_platform.mk delete mode 100644 repos/os/lib/mk/x86/ahci.mk create mode 100644 repos/os/lib/mk/x86/ahci_platform.mk create mode 100644 repos/os/src/drivers/ahci/ahci.cc create mode 100644 repos/os/src/drivers/ahci/ahci.h create mode 100644 repos/os/src/drivers/ahci/ata_driver.h create mode 100644 repos/os/src/drivers/ahci/atapi_driver.h delete mode 100644 repos/os/src/drivers/ahci/empty.cc delete mode 100644 repos/os/src/drivers/ahci/exynos5/ahci_driver.cc delete mode 100644 repos/os/src/drivers/ahci/exynos5/ahci_driver.h delete mode 100644 repos/os/src/drivers/ahci/exynos5/bench/main.cc delete mode 100644 repos/os/src/drivers/ahci/exynos5/bench/target.mk create mode 100644 repos/os/src/drivers/ahci/exynos5/platform.cc delete mode 100644 repos/os/src/drivers/ahci/include/ahci_device_base.h delete mode 100644 repos/os/src/drivers/ahci/include/ahci_driver_base.h delete mode 100644 repos/os/src/drivers/ahci/x86/ahci_device.h delete mode 100644 repos/os/src/drivers/ahci/x86/ahci_driver.h create mode 100644 repos/os/src/drivers/ahci/x86/platform.cc diff --git a/repos/libports/run/libc_ffat.run b/repos/libports/run/libc_ffat.run index 2cf795f00..ec69eeef3 100644 --- a/repos/libports/run/libc_ffat.run +++ b/repos/libports/run/libc_ffat.run @@ -1,4 +1,4 @@ -set mkfs_cmd mkfs.vfat +set mkfs_cmd [check_installed mkfs.vfat] set mkfs_opts "-F32" set filesystem ffat diff --git a/repos/libports/run/libc_ffat_fs.run b/repos/libports/run/libc_ffat_fs.run index ad05cd9eb..c6f24eece 100644 --- a/repos/libports/run/libc_ffat_fs.run +++ b/repos/libports/run/libc_ffat_fs.run @@ -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 { - + + + + } append_if [expr ![have_spec acpi] && [have_spec pci]] config { + + + } append_if $use_ahci_drv config { - + - + + + } @@ -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 } diff --git a/repos/libports/run/libc_filesystem_test.inc b/repos/libports/run/libc_filesystem_test.inc index 1804b43df..7ac793171 100644 --- a/repos/libports/run/libc_filesystem_test.inc +++ b/repos/libports/run/libc_filesystem_test.inc @@ -126,16 +126,17 @@ append_if [expr ![have_spec acpi] && [have_spec pci]] config { append_if $use_ahci config { - + - } - -append_if [expr $use_ahci && [have_spec acpi]] config { - } -append_if $use_ahci config { + + } +append_if $use_ahci config " + " +append_if $use_ahci config { + } 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 diff --git a/repos/libports/run/qt5_drivers.inc b/repos/libports/run/qt5_drivers.inc index 6acce9033..d8bbd81f6 100644 --- a/repos/libports/run/qt5_drivers.inc +++ b/repos/libports/run/qt5_drivers.inc @@ -129,6 +129,25 @@ proc drivers_start_nodes { feature_arg } { + + + + + + + + + + + + + + + + + + + @@ -179,6 +198,17 @@ proc drivers_start_nodes { feature_arg } { + + + + + + + + + + + } diff --git a/repos/libports/run/qt5_textedit.run b/repos/libports/run/qt5_textedit.run index f3249918c..ca55f7f96 100644 --- a/repos/libports/run/qt5_textedit.run +++ b/repos/libports/run/qt5_textedit.run @@ -51,9 +51,12 @@ append config [qt5_start_nodes feature] append_if $use_ahci_driver config { - + + - + + + } 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 diff --git a/repos/os/lib/mk/ahci.inc b/repos/os/lib/mk/ahci.inc deleted file mode 100644 index a3ceab080..000000000 --- a/repos/os/lib/mk/ahci.inc +++ /dev/null @@ -1,17 +0,0 @@ -# -# \brief Generic toolchain configurations for AHCI -# \author Martin Stein -# \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 diff --git a/repos/os/lib/mk/exynos5/ahci.mk b/repos/os/lib/mk/exynos5/ahci.mk deleted file mode 100644 index dd45bd76e..000000000 --- a/repos/os/lib/mk/exynos5/ahci.mk +++ /dev/null @@ -1,17 +0,0 @@ -# -# \brief Toolchain configurations for AHCI on Exynos -# \author Martin Stein -# \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 diff --git a/repos/os/lib/mk/exynos5/ahci_platform.mk b/repos/os/lib/mk/exynos5/ahci_platform.mk new file mode 100644 index 000000000..fa416556a --- /dev/null +++ b/repos/os/lib/mk/exynos5/ahci_platform.mk @@ -0,0 +1,6 @@ +SRC_CC = platform.cc + +INC_DIR += $(REP_DIR)/src/drivers/ahci + +vpath platform.cc $(REP_DIR)/src/drivers/ahci/exynos5 + diff --git a/repos/os/lib/mk/x86/ahci.mk b/repos/os/lib/mk/x86/ahci.mk deleted file mode 100644 index 85524346a..000000000 --- a/repos/os/lib/mk/x86/ahci.mk +++ /dev/null @@ -1,11 +0,0 @@ -# -# \brief Toolchain configurations for AHCI on X86 -# \author Martin Stein -# \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 diff --git a/repos/os/lib/mk/x86/ahci_platform.mk b/repos/os/lib/mk/x86/ahci_platform.mk new file mode 100644 index 000000000..e92501687 --- /dev/null +++ b/repos/os/lib/mk/x86/ahci_platform.mk @@ -0,0 +1,6 @@ +SRC_CC = platform.cc + +INC_DIR += $(REP_DIR)/src/drivers/ahci + +vpath platform.cc $(REP_DIR)/src/drivers/ahci/x86 + diff --git a/repos/os/src/drivers/ahci/README b/repos/os/src/drivers/ahci/README index 793460260..3ab8e528f 100644 --- a/repos/os/src/drivers/ahci/README +++ b/repos/os/src/drivers/ahci/README @@ -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. +! +! +! +! } +! +! +! +! +! +! +! +! -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. diff --git a/repos/os/src/drivers/ahci/ahci.cc b/repos/os/src/drivers/ahci/ahci.cc new file mode 100644 index 000000000..b6262bb36 --- /dev/null +++ b/repos/os/src/drivers/ahci/ahci.cc @@ -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 +#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 irq; + Signal_rpc_member 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(); + 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.read()); + 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()); + unsigned avaiable = hba.read(); + 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(); + + 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); +} + + + diff --git a/repos/os/src/drivers/ahci/ahci.h b/repos/os/src/drivers/ahci/ahci.h new file mode 100644 index 000000000..357d2519a --- /dev/null +++ b/repos/os/src/drivers/ahci/ahci.h @@ -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 +#include +#include + +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() + 1; } + unsigned command_slots() { return read() + 1; } + bool ncg() { return !!read(); } + bool supports_64bit(){ return !!read(); } + + /** + * 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(read()); } + + /** + * 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(1); + + /* enable interrupts */ + write(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 { }; + 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 { }; + struct Lba48 : Genode::Bitset_3 { }; + struct Lba : Genode::Bitset_2 { }; /* 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 { }; /* sector count */ + + Command_fis(Genode::addr_t base) + : Mmio(base) + { + clear(); + + enum { HOST_TO_DEVICE = 0x27 }; + write(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(1); + write(0); + write(0xec); + } + + void dma_ext(bool read, Block::sector_t block_number, Genode::size_t block_count) + { + write(1); + write(1); + /* read_dma_ext : write_dma_ext */ + write(read ? 0x25 : 0x35); + write(block_number); + write(block_count); + } + + void fpdma(bool read, Block::sector_t block_number, Genode::size_t block_count, + unsigned slot) + { + write(1); + write(1); + /* read_fpdma : write_fpdma */ + write(read ? 0x60 : 0x61); + write(block_number); + write(block_count); + write(slot); + } + + void atapi() + { + write(1); + /* packet command */ + write(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(addr); + write(addr >> 32); + write(1); + write(Command_fis::size() / sizeof(unsigned)); + } + + void clear_byte_count() + { + write(0); + } + + void atapi_command() + { + write(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 { }; + struct Lba : Genode::Bitset_2 { }; + + /* sector count big endian */ + struct Sector8_15 : Register<0x8, 8> { }; + struct Sector0_7 : Register<0x9, 8> { }; + struct Sector : Genode::Bitset_2 { }; + + + Atapi_command(Genode::addr_t base) : Mmio(base) + { + Genode::memset((void *)base, 0, 16); + } + + void read_capacity() + { + write(0x25); + } + + void test_unit_ready() + { + write(0x0); + } + + void read_sense() + { + write(0x3); + write(18); + } + + void read10(Block::sector_t block_number, Genode::size_t block_count) + { + write(0x28); + write(block_number); + write(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(addr); + write(addr >> 32); + write(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() + 1, + read()); + PLOG("\t\tnumer of sectors: %llu", read()); + PLOG("\t\tmultiple logical blocks per physical: %s", + read() ? "yes" : "no"); + PLOG("\t\tlogical blocks per physical: %u", + 1U << read()); + PLOG("\t\tlogical block size is above 512 byte: %s", + read() ? "yes" : "no"); + PLOG("\t\twords (16bit) per logical block: %u", + read()); + PLOG("\t\toffset of first logical block within physical: %u", + read()); + } +}; + + +/** + * 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(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(addr); + write(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(addr); + write(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 (); + + /* clear Serr.Diag.x */ + if (Is::Pcs::get(status)) + write(0); + + /* clear Serr.Diag.n */ + if (Is::Prcs::get(status)) + write(0); + + write(read()); + }; + + /** + * 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(~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()) + return; + + if (!wait_for(0, hba.delayer(), 500, 1000)) { + PERR("HBA busy unable to start command processing."); + return; + } + + if (!wait_for(0, hba.delayer(), 500, 1000)) { + PERR("HBA in DRQ unable to start command processing."); + return; + } + + write(1); + } + + void stop() + { + if (!(read() | read())) + write(0); + } + + void power_up() + { + Cmd::access_t cmd = read(); + Cmd::Sud::set(cmd, 1); + Cmd::Pod::set(cmd, 1); + Cmd::Fre::set(cmd, 1); + write(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(); + 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(Ssts::Ipm::ACTIVE); + + while ((Ssts::Dec::get(status) != Ssts::Dec::ESTABLISHED) || + !(Ssts::Ipm::get(status) & Ssts::Ipm::ACTIVE)) + status = read(); + } + + 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()) + PWRN("CMD.ST bit set during device reset --> unknown behavior"); + + write(1); + hba.delayer().usleep(1000); + write(0); + + if (!wait_for(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(read()); } + + /** + * 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(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(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_ */ diff --git a/repos/os/src/drivers/ahci/ata_driver.h b/repos/os/src/drivers/ahci/ata_driver.h new file mode 100644 index 000000000..44ed911d9 --- /dev/null +++ b/repos/os/src/drivers/ahci/ata_driver.h @@ -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 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() | Port::read(); + + 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(read ? 0 : 1); + header.clear_byte_count(); + + /* set pending */ + Port::write(1U << slot); + execute(slot); + } + + + /***************** + ** Port_driver ** + *****************/ + + void handle_irq() override + { + Is::access_t status = Port::read(); + + 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())) { + ack_irq(); + ack_packets(); + } + + stop(); + } + + void check_device() + { + if (!info->read() || + !hba.ncg()) { + PERR("Device does not support native command queuing: abort"); + state_change(); + return; + } + + cmd_slots = min((int)cmd_slots, + info->read() + 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()) + size = info->read() / 2; + + return size; + } + + Block::sector_t block_count() override + { + return info->read(); + } +}; + + +#endif /* _ATA_DRIVER_H_ */ diff --git a/repos/os/src/drivers/ahci/atapi_driver.h b/repos/os/src/drivers/ahci/atapi_driver.h new file mode 100644 index 000000000..45aa67b2f --- /dev/null +++ b/repos/os/src/drivers/ahci/atapi_driver.h @@ -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 + +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(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(); + + 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(); + + if (verbose) { + PDBG("irq: is: %x ci: %x state: %u", status, Port::read(), state); + Device_fis f(fis_base); + PDBG("d2h: status: %x error: %x", f.read(), f.read()); + } + + ack_irq(); + + if (state == TEST_READY && Port::Is::Dhrs::get(status)) { + Device_fis f(fis_base); + + /* check if devic is ready */ + if (f.read() && !f.read()) + 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(0); + header.clear_byte_count(); + + /* set pending */ + execute(0); + } +}; + +#endif /* _ATAPI_DRIVER_H_ */ diff --git a/repos/os/src/drivers/ahci/empty.cc b/repos/os/src/drivers/ahci/empty.cc deleted file mode 100644 index 066c1bf82..000000000 --- a/repos/os/src/drivers/ahci/empty.cc +++ /dev/null @@ -1,3 +0,0 @@ -/* - * Dummy compilation unit needed to link a valid target. - */ diff --git a/repos/os/src/drivers/ahci/exynos5/ahci_driver.cc b/repos/os/src/drivers/ahci/exynos5/ahci_driver.cc deleted file mode 100644 index 15ad874b6..000000000 --- a/repos/os/src/drivers/ahci/exynos5/ahci_driver.cc +++ /dev/null @@ -1,1960 +0,0 @@ -/* - * \brief AHCI driver implementation - * \author Martin Stein - * \date 2013-05-17 - */ - -/* - * 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. - */ - -/* local includes */ -#include - -/* Genode includes */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace Genode; - -/** - * Delayer for MMIO polling - */ -struct Timer_delayer : Mmio::Delayer, Timer::Connection -{ - void usleep(unsigned us) { Timer::Connection::usleep(us); } -}; - -static Mmio::Delayer * delayer() { - static Timer_delayer s; - return &s; -} - -/** - * Compose a physical region descriptor - * - * \param prd_addr destination - * \param phys physical region base - * \param size physical region size - */ -void write_prd(addr_t prd_addr, uint64_t phys, unsigned size) -{ - struct Bits : Register<32> - { - struct Dbc : Bitfield<0, 22> { }; /* data byte count */ - struct I : Bitfield<31, 1> { }; /* IRQ on completion */ - }; - struct Prd - { - uint64_t dba; /* data base address */ - uint32_t zero; - uint32_t bits; - }; - Bits::access_t bits = 0; - Bits::Dbc::set(bits, size - 1); - - Prd volatile * prd = (Prd volatile *)prd_addr; - prd->dba = phys; - prd->zero = 0; - prd->bits = bits; -} - -/** - * Compose a command slot - * - * \param slot_addr destination - * \param ct_phys physical command table base address - * \param w writes 1: host-to-device 0: device-to-host - * \param reset wether it is a soft reset command - * \param pmp port multiplier port - * \param prdtl physical region descriptor table length in entries - */ -void write_cmd_slot(addr_t slot_addr, uint64_t ct_phys, bool w, - bool reset, uint8_t pmp, uint16_t prdtl) -{ - struct Bits : Register<32> - { - struct Cfl : Bitfield<0, 5> { }; /* command FIS length */ - struct A : Bitfield<5, 1> { }; /* ATAPI command */ - struct W : Bitfield<6, 1> { }; /* write (1: H2D, 0: D2H) */ - struct P : Bitfield<7, 1> { }; /* prefetchable */ - struct R : Bitfield<8, 1> { }; /* reset */ - struct B : Bitfield<9, 1> { }; /* BIST */ - struct C : Bitfield<10, 1> { }; /* clear busy upon R_OK */ - struct Pmp : Bitfield<12, 4> { }; /* port multiplier port */ - struct Prdtl : Bitfield<16, 16> { }; /* PRD-table length in entries */ - }; - struct Slot - { - uint32_t bits; - uint32_t prdbc; /* PRD byte count */ - uint64_t ctba; /* command table descriptor base address */ - uint32_t zero; - }; - Bits::access_t bits = 0; - Bits::Cfl::set(bits, 5); - Bits::W::set(bits, w); - Bits::R::set(bits, reset); - Bits::C::set(bits, reset); - Bits::Pmp::set(bits, pmp); - Bits::Prdtl::set(bits, prdtl); - - Slot volatile * slot = (Slot volatile *)slot_addr; - slot->bits = bits; - slot->prdbc = 0; - slot->ctba = ct_phys; - slot->zero = 0; -} - -/** - * Frame information structure - */ -struct Fis -{ - /* FIS payload */ - uint8_t volatile byte[20]; - - void _init() - { - for (unsigned i = 0; i < sizeof(byte)/sizeof(byte[0]); i++) - byte[i] = 0; - } - - void _reg_h2d() - { - byte[0] = 0x27; /* type */ - byte[15] = 0x08; /* control */ - } - - void _cmd_h2d() - { - _reg_h2d(); - - struct Flags : Register<8> - { - struct Cmd : Bitfield<7, 1> { }; /* 1: command 0: control */ - }; - Flags::access_t flags = 0; - Flags::Cmd::set(flags, 1); - - byte[1] = flags; - } - - void _obsolete_device() - { - byte[7] = 0xa0; - } - - void _lba(uint64_t lba) - { - byte[4] = lba & 0xff; - byte[5] = (lba >> 8) & 0xff; - byte[6] = (lba >> 16) & 0xff; - byte[8] = (lba >> 24) & 0xff; - byte[9] = (lba >> 32) & 0xff; - byte[10] = (lba >> 40) & 0xff; - } - - void _feature(uint16_t ft) - { - byte[3] = ft & 0xff; - byte[11] = (ft >> 8) & 0xff; - } - - void _count(uint16_t cnt) - { - byte[12] = cnt & 0xff; - byte[13] = (cnt >> 8) & 0xff; - } - - public: - - /** - * Read PIO-setup transfer count - */ - uint16_t transfer_cnt() - { - uint16_t ret = 0; - ret |= (uint16_t)byte[16]; - ret |= (uint16_t)byte[17] << 8; - return ret; - } - - /** - * Read count - */ - uint16_t count() - { - uint16_t ret = 0; - ret |= (uint16_t)byte[12]; - ret |= (uint16_t)byte[13] << 8; - return ret; - } - - /** - * Read logical block address - */ - uint64_t lba() - { - uint64_t ret = 0; - ret |= (uint64_t)byte[4]; - ret |= (uint64_t)byte[5] << 8; - ret |= (uint64_t)byte[6] << 16; - ret |= (uint64_t)byte[8] << 24; - ret |= (uint64_t)byte[9] << 32; - ret |= (uint64_t)byte[10] << 40; - return ret; - } - - /** - * FIS to clear device-to-host receive area - */ - void clear_d2h_rx() - { - _init(); - _reg_h2d(); - _obsolete_device(); - - byte[2] = 0x80; /* command */ - } - - /** - * Command FIS for ATA command 'identify device' - */ - void identify_device() - { - _init(); - _cmd_h2d(); - _obsolete_device(); - - byte[2] = 0xec; /* command */ - } - - /** - * Command FIS for ATA command 'read native max addr' - */ - void read_native_max_addr() - { - _init(); - _cmd_h2d(); - _obsolete_device(); - - byte[2] = 0x27; /* command */ - byte[7] |= 0x40; /* device */ - } - - /** - * Command FIS for ATA command 'set features' with feature 'set transfer mode' - * - * \param transfer_mode ID of targeted mode - */ - void set_transfer_mode(uint8_t transfer_mode) - { - _init(); - _cmd_h2d(); - _obsolete_device(); - _feature(3); - _count(transfer_mode); - - byte[2] = 0xef; /* command */ - } - - /** - * Command FIS for ATA command 'read / write FPDMA queued' - * - * \param w 1: do write FPDMA queued 0: do read FPDMA queued - * \param block_nr logical block address (LBA) - * \param block_cnt blocks to be read / write - * \param tag command slot ID - */ - void fpdma_queued(bool w, uint64_t block_nr, - uint16_t block_cnt, unsigned tag) - { - _init(); - _cmd_h2d(); - _feature(block_cnt); - _lba(block_nr); - - struct Count : Register<16> - { - struct Tag : Bitfield<3, 5> { }; - }; - Count::access_t cnt = 0; - Count::Tag::set(cnt, tag); - _count(cnt); - - byte[2] = w ? 0x61 : 0x60; /* command */ - - struct Device : Register<8> - { - struct Lba_mode : Bitfield<6, 1> { }; - }; - Device::access_t dev = byte[7]; - Device::Lba_mode::set(dev, 1); - byte[7] = dev; - } - - /** - * First and second soft reset FIS - * - * \param second if this is the second soft reset FIS or the first - * \param pmp port multiplier port - */ - void soft_reset(bool second, uint8_t pmp) - { - _init(); - _reg_h2d(); - _obsolete_device(); - - struct Flags : Register<8> - { - struct Pmp : Bitfield<0, 4> { }; /* port multiplier port */ - }; - Flags::access_t flags = byte[1]; - Flags::Pmp::set(flags, pmp); - byte[1] = flags; - - struct Control : Register<8> - { - struct Softreset : Bitfield<2, 1> { }; - }; - Control::access_t ctl = byte[15]; - Control::Softreset::set(ctl, !second); - byte[15] = ctl; - } - - /** - * Wether a PIO setup FIS was sucessfully received - * - * \param transfer_size size of transfered data - * \param block_nr LBA of transfered data (0 if it has no LBA) - */ - bool is_pio_setup(uint16_t transfer_size, uint64_t block_nr) - { - struct Flags : Register<8> - { - struct Pmp : Bitfield<0,4> { }; // port multiplier port - struct D : Bitfield<5,1> { }; // data transfer direction, 1: D2H - struct I : Bitfield<6,1> { }; // interrupt bit - }; - Flags::access_t flags = 0; - Flags::D::set(flags, 1); - Flags::I::set(flags, 1); - - /** - * ATA device register - */ - struct Device : Register<8> - { - struct Lba28_27_24 : Bitfield<0, 4> { }; // LBA[27..24] if LBA28 is used - struct Slave : Bitfield<4, 1> { }; // 0: master 1: slave - struct Obsolete_0 : Bitfield<5, 1> { }; // = 1 - struct Lba : Bitfield<6, 1> { }; // FIXME: LBA flag does what? - struct Obsolete_1 : Bitfield<7, 1> { }; // = 1 - }; - - /* - * FIXME - * The count register is set differently for different - * drives and i've no idea what it means in this context - * but as long as all works fine i ignore it simply. - * (WD2500BEVS: 0xff, SAMSUNG840PRO128GB: 0x1) - * - * FIXME - * LBA flag in device register is 1 for at least - * OCZ Agility 3 120 GB but normally it is 0. Hopefully - * ignoring it becomes not a problem in future. - */ - return byte[0] == 0x5f && /* type */ - byte[1] == flags && - byte[2] == 0x58 && /* old status */ - byte[3] == 0 && /* error */ - lba() == block_nr && - Device::Lba28_27_24::get(byte[7]) == 0 && - Device::Slave::get(byte[7]) == 0 && - Device::Obsolete_0::get(byte[7]) == 1 && - Device::Obsolete_1::get(byte[7]) == 1 && - byte[15] == 0x50 && /* new status */ - transfer_cnt() == transfer_size; - } - - /** - * Print out FIS content in three lines with two spaces indent - */ - void print() - { - printf(" 0: 0x%02x", byte[ 0]); - printf(" 1: 0x%02x", byte[ 1]); - printf(" 2: 0x%02x", byte[ 2]); - printf(" 3: 0x%02x", byte[ 3]); - printf(" 4: 0x%02x", byte[ 4]); - printf(" 5: 0x%02x", byte[ 5]); - printf(" 6: 0x%02x", byte[ 6]); - printf(" 7: 0x%02x\n", byte[ 7]); - printf(" 8: 0x%02x", byte[ 8]); - printf(" 9: 0x%02x", byte[ 9]); - printf(" 10: 0x%02x", byte[10]); - printf(" 11: 0x%02x", byte[11]); - printf(" 12: 0x%02x", byte[12]); - printf(" 13: 0x%02x", byte[13]); - printf(" 14: 0x%02x", byte[14]); - printf(" 15: 0x%02x\n", byte[15]); - printf(" lba: %llu", lba()); - printf(" cnt: %u\n", count()); - } - - /** - * Wether reply for 'set transfer mode' was successfully received - * - * \param transfer_mode ID of transfer mode that should be set - */ - bool is_set_transfer_mode_reply(uint8_t transfer_mode) - { - /* - * FIXME - * I've no idea what most of these values stand for and - * interpreting Linux seems to be the only way to change this. - */ - bool result = 0; - result = byte[0] == 0x34 && /* type */ - byte[1] == 0x40 && - byte[2] == 0x50 && - byte[3] == 0 && - lba() == 0 && - byte[7] == 0xa0 && /* device */ - byte[11] == 0 && - byte[14] == 0 && - byte[15] == 0; - - /* - * FIXME - * Sometimes count is 0 and sometimes it equals the transfer - * mode that was set but both seems to work. - */ - if (count() == 0) - printf("cleared transfer mode in reconfiguration reply\n"); - else if (count() != transfer_mode) - result = 0; - - return result; - } -}; - -/** - * I2C master interface - */ -struct I2c_interface : Attached_mmio -{ - enum { VERBOSE = 0 }; - - 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> { }; - }; - - /* 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) - : Attached_mmio(base, 0x10000), - 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() && !read()) 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()) { - 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(0, *delayer())) { - PERR("I2C busy"); - return -1; - } - Stat::access_t stat = read(); - Stat::Txrx_en::set(stat, 1); - Stat::Mode::set(stat, 3); - write(stat); - write(start_msg); - delayer()->usleep(1000); - write(11); - write(1); - - /* transmit message payload */ - for (unsigned i = 0; i < msg_size; i++) { - if (!ack_received()) return -1; - write(msg[i]); - delayer()->usleep(TX_DELAY_US); - write(0); - if (arbitration_error()) return -1; - } - /* end message transfer */ - if (!ack_received()) return -1; - write(0); - write(0); - write(0); /* FIXME fixup */ - if (arbitration_error()) return -1; - if (!wait_for(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() : I2c_interface(0x121d0000, SLAVE_ADDR) { } - - /** - * 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(SLAVE_ADDR); - - Con::access_t con = read(); - 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); - - Lc::access_t lc = 0; - Lc::Sda_out_delay::set(lc, 3); - Lc::Filter_en::set(lc, 1); - write(lc); - } -}; - -static I2c_sataphy * i2c_sataphy() { - static I2c_sataphy i2c_sataphy; - return &i2c_sataphy; -} - -/** - * Classical control interface of SATA PHY-layer controller - */ -struct Sata_phy_ctrl : Attached_mmio -{ - enum { VERBOSE = 0 }; - - /******************************** - ** 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() : Attached_mmio(0x12170000, 0x10000) { } - - /** - * Initialize parts of SATA PHY that are controlled classically - * - * \retval 0 call was successful - * \retval <0 call failed, error code - */ - int init() - { - /* reset */ - write(0); - write(~0); - write(~0); - write(~0); - - /* set up SATA phy generation 3 (6 Gb/s) */ - Phctrlm::access_t phctrlm = read(); - Phctrlm::Ref_rate::set(phctrlm, 0); - Phctrlm::High_speed::set(phctrlm, 1); - write(phctrlm); - Ctrl0::access_t ctrl0 = read(); - Ctrl0::P0_phy_calibrated::set(ctrl0, 1); - Ctrl0::P0_phy_calibrated_sel::set(ctrl0, 1); - write(ctrl0); - write(2); - if (i2c_sataphy()->enable_40_pins()) return -1; - - /* Release reset */ - write(0); - write(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(1, *delayer())) { - PERR("PLL lock failed"); - return -1; - } - if (VERBOSE) printf("SATA PHY initialized\n"); - return 0; - } -}; - -static Sata_phy_ctrl * sata_phy_ctrl() { - static Sata_phy_ctrl sata_phy_ctrl; - return &sata_phy_ctrl; -} - -/** - * SATA AHCI interface - */ -struct Sata_ahci : Attached_mmio -{ - enum { VERBOSE = 0 }; - - /* general config */ - enum { - /* FIXME only with port multiplier support (sata_srst_pmp in Linux) */ - SOFT_RESET_PMP = 15, - BLOCK_SIZE = 512, - BLOCKS_PER_LOG = 1, - BYTES_PER_PRD = 1 << 22, - }; - - /* DMA structure */ - enum { - CMD_LIST_SIZE = 0x400, - CMD_SLOT_SIZE = 0x20, - FIS_AREA_SIZE = 0x100, - CMD_TABLE_SIZE = 0xb00, - CMD_TABLE_HEAD_SIZE = 0x80, - PRD_SIZE = 0x10, - }; - - /* FIS RX area structure */ - enum { - REG_D2H_FIS_OFFSET = 0x40, - PIO_SETUP_FIS_OFFSET = 0x20, - }; - - /* debouncing settings */ - enum { - FAST_DBC_TRIAL_US = 5000, - SLOW_DBC_TRIAL_US = 25000, - }; - - /* modes when doing 'set features' with feature 'set transfer mode' */ - enum { UDMA_133 = 0x46, }; - - /******************************** - ** MMIO structure description ** - ********************************/ - - struct Cap : Register<0x0, 32> - { - struct Np : Bitfield<0, 4> { }; - struct Ems : Bitfield<6, 1> { }; - struct Ncs : Bitfield<8, 5> { }; - struct Iss : Bitfield<20, 4> { }; - }; - struct Ghc : Register<0x4, 32> - { - struct Hr : Bitfield<0, 1> { }; - struct Ie : Bitfield<1, 1> { }; - struct Ae : Bitfield<31, 1> { }; - }; - struct Is : Register<0x8, 32, 1> - { - struct Ips : Bitfield<0, 1> { }; - }; - struct Pi : Register<0xc, 32> { }; - struct Vs : Register<0x10, 32> - { - struct Mnr : Bitfield<0, 16> { }; - struct Mjr : Bitfield<16, 16> { }; - }; - struct Cap2 : Register<0x24, 32> { }; - struct P0clb : Register<0x100, 32> - { - struct Clb : Bitfield<10, 22> { }; - }; - struct P0fb : Register<0x108, 32> - { - struct Fb : Bitfield<8, 24> { }; - }; - struct P0is : Register<0x110, 32, 1> - { - struct Dhrs : Bitfield<0, 1> { }; - struct Pss : Bitfield<1, 1> { }; - struct Sdbs : Bitfield<3, 1> { }; - struct Infs : Bitfield<26, 1> { }; - struct Ifs : Bitfield<27, 1> { }; - }; - struct P0ie : Register<0x114, 32> - { - struct Dhre : Bitfield<0, 1> { }; - struct Pse : Bitfield<1, 1> { }; - struct Dse : Bitfield<2, 1> { }; - struct Sdbe : Bitfield<3, 1> { }; - struct Ufe : Bitfield<4, 1> { }; - struct Dpe : Bitfield<5, 1> { }; - struct Pce : Bitfield<6, 1> { }; - struct Prce : Bitfield<22, 1> { }; - struct Ife : Bitfield<27, 1> { }; - struct Hbde : Bitfield<28, 1> { }; - struct Hbfe : Bitfield<29, 1> { }; - struct Tfee : Bitfield<30, 1> { }; - }; - struct P0cmd : Register<0x118, 32> - { - struct St : Bitfield<0, 1> { }; - struct Sud : Bitfield<1, 1> { }; - struct Pod : Bitfield<2, 1> { }; - struct Fre : Bitfield<4, 1> { }; - struct Fr : Bitfield<14, 1> { }; - struct Cr : Bitfield<15, 1> { }; - struct Pma : Bitfield<17, 1> { }; - struct Atapi : Bitfield<24, 4> { }; - struct Icc : Bitfield<28, 4> { }; - }; - struct P0tfd : Register<0x120, 32> - { - struct Sts_bsy : Bitfield<7, 1> { }; - }; - struct P0sig : Register<0x124, 32> - { - struct Lba_8_15 : Bitfield<16, 8> { }; - struct Lba_16_31 : Bitfield<24, 8> { }; - }; - struct P0ssts : Register<0x128, 32> - { - struct Det : Bitfield<0, 4> { }; - struct Spd : Bitfield<4, 4> { }; - struct Ipm : Bitfield<8, 4> { }; - }; - struct P0sctl : Register<0x12c, 32> - { - struct Det : Bitfield<0, 4> { }; - struct Spd : Bitfield<4, 4> { }; - struct Ipm : Bitfield<8, 4> { }; - }; - struct P0serr : Register<0x130, 32> - { - struct Err_c : Bitfield<9, 1> { }; - struct Err_p : Bitfield<10, 1> { }; - struct Diag_n : Bitfield<16, 1> { }; - struct Diag_b : Bitfield<19, 1> { }; - struct Diag_c : Bitfield<21, 1> { }; - struct Diag_h : Bitfield<22, 1> { }; - struct Diag_x : Bitfield<26, 1> { }; - }; - struct P0sact : Register<0x134, 32, 1> { }; - struct P0ci : Register<0x138, 32, 1> { }; - struct P0sntf : Register<0x13c, 32, 1> - { - struct Pmn : Bitfield<0, 16> { }; - }; - - /* device settings */ - uint64_t block_cnt; - - /* working-DMA structure */ - Dataspace_capability ds; - addr_t cl_phys; /* command list */ - addr_t cl_virt; - addr_t fb_phys; /* FIS receive area */ - addr_t fb_virt; - addr_t ct_phys; /* command table */ - addr_t ct_virt; - - /* debouncing settings */ - unsigned dbc_trial_us; - unsigned dbc_trials; - unsigned dbc_stable_trials; - - /* port 0 settings */ - unsigned p0_speed; - Irq_connection p0_irq; - Genode::Signal_receiver p0_irq_rec; - Genode::Signal_context p0_irq_ctx; - - enum { SATA_3_MAX_SPEED = 3 }; - - /** - * Constructor - */ - Sata_ahci() - : Attached_mmio(0x122f0000, 0x10000), - ds(env()->ram_session()->alloc(0x20000, Genode::UNCACHED)), - cl_phys(Dataspace_client(ds).phys_addr()), - cl_virt(env()->rm_session()->attach(ds)), - fb_phys(cl_phys + CMD_LIST_SIZE), - fb_virt(cl_virt + CMD_LIST_SIZE), - ct_phys(fb_phys + FIS_AREA_SIZE), - ct_virt(fb_virt + FIS_AREA_SIZE), - dbc_trial_us(FAST_DBC_TRIAL_US), - dbc_trials(50), - dbc_stable_trials(5), - p0_speed(SATA_3_MAX_SPEED), - p0_irq(Genode::Board_base::SATA_IRQ) - { - p0_irq.sigh(p0_irq_rec.manage(&p0_irq_ctx)); - p0_irq.ack_irq(); - } - - /** - * Clear all interrupts at port 0 - * - * \return value of P0IS before it was cleared - */ - P0is::access_t p0_clear_irqs() - { - P0is::access_t p0is = read(); - write(p0is); - return p0is; - } - - /** - * Get port back ready after port IRQs were raised - * - * \param lba holds current drive LBA if call returns 1 - * - * \retval 0 no errors were detected during IRQ handling - * \retval 1 port has been recovered from errors, lba denotes error point - * \retval -1 errors occured and port couln't be recovered - */ - int p0_handle_irqs(uint64_t & lba) - { - /* ack interrupts and errors */ - P0is::access_t p0is = p0_clear_irqs(); - P0serr::access_t p0serr = p0_clear_errors(); - - /* leave if interrupts are just as expected */ - if (p0is == P0is::Sdbs::bits(1)) return 0; - if (p0is == P0is::Dhrs::bits(1)) return 0; - - /* interpret unexpected interrupts */ - bool interface_err = 0; - bool fatal = 0; - if (P0is::Ifs::get(p0is)) { - interface_err = 1; - if (VERBOSE) fatal = 1; - } else if (P0is::Infs::get(p0is)) - interface_err = 1; - - /* print and handle known errors */ - if (interface_err) - { - /* print errors */ - if(VERBOSE) { - printf("handle"); - if (fatal) printf(" fatal"); - else printf(" non-fatal"); - printf(" interface errors:\n"); - if (P0serr::Diag_b::get(p0serr)) - printf(" 10 B to 8 B decode error\n"); - if (P0serr::Err_p::get(p0serr)) - printf(" protocol error\n"); - if (P0serr::Diag_c::get(p0serr)) - printf(" CRC error\n"); - if (P0serr::Err_c::get(p0serr)) - printf(" non-recovered persistent communication error\n"); - if (P0serr::Diag_h::get(p0serr)) - printf(" handshake error\n"); - } - /* get error LBA */ - Fis * fis = (Fis *)(fb_virt + REG_D2H_FIS_OFFSET); - lba = fis->lba(); - - /* print reply FIS */ - if (VERBOSE) { - printf("error report that was sent by the drive:\n"); - fis->print(); - } - /* handle errors */ - return p0_error_recovery() ? -1 : 1; - } - /* complain about unkown errors */ - PERR("unknown error (P0IS 0x%x P0SERR 0x%x)\n", p0is, p0serr); - return -1; - } - - /** - * Get the AHCI controller ready for port initializations - * - * \retval 0 call was successful - * \retval <0 call failed, error code - */ - int init() - { - /* enable AHCI */ - write(1); - if (!read()) { - PERR("SATA AHCI failed to enable AHCI"); - return -1; - } - /* save HBA config */ - Cap::access_t cap = read(); - Pi::access_t pi = read(); - Vs::access_t vs = read(); - Cap2::access_t cap2 = read(); - - /* check port number and mask */ - unsigned ports = 0; - for (unsigned i = 0; i < Pi::ACCESS_WIDTH; i++) if (pi & (1 << i)) ports++; - if (ports != Cap::Np::get(cap) + 1) { - ports = Cap::Np::get(cap) + 1; - pi = (1 << ports) - 1; - } - if (ports != 1 || pi != 1) { - PERR("SATA AHCI driver proved with port 0 only"); - return -1; - } - /* check enclosure management support */ - if (Cap::Ems::get(cap)) { - PERR("SATA AHCI driver proved without EMS only"); - return -1; - } - /* check AHCI revision */ - unsigned rev_mjr = Vs::Mjr::get(vs); - unsigned rev_mnr = Vs::Mnr::get(vs); - if (rev_mjr != 0x1 || rev_mnr != 0x300) { - PERR("SATA AHCI driver proved with AHCI rev 1.3 only"); - return -1; - } - /* check interface speed */ - char const * speed; - switch(Cap::Iss::get(cap)) { - case 1: - speed = "1.5"; - break; - case 2: - speed = "3"; - break; - case 3: - speed = "6"; - break; - default: - PERR("SATA AHCI failed to get controller speed"); - return -1; - } - /* check number of command slots */ - unsigned slots = Cap::Ncs::get(cap) + 1; - if (slots != 32) { - PERR("SATA AHCI driver proved with 32 slots only"); - return -1; - } - /* reset */ - write(1); - if (!wait_for(0, *delayer(), 1000, 1000)) { - PERR("SATA AHCI reset failed"); - return -1; - } - /* enable AHCI */ - write(1); - if (!read()) { - PERR("SATA AHCI failed to enable AHCI"); - return -1; - } - /* restore HBA config */ - write(cap); - write(cap2); - write(pi); - if (VERBOSE) - printf("SATA AHCI initialized, AHCI rev %x.%x, " - "%s Gbps, %u slots, %u port%c\n", - rev_mjr, rev_mnr, speed, slots, - ports, ports > 1 ? 's' : ' '); - return 0; - } - - /** - * Stop processing commands at port 0 - * - * \retval 0 call was successful - * \retval <0 call failed, error code - */ - int p0_disable_cmd_processing() - { - P0cmd::access_t p0cmd = read(); - if (P0cmd::St::get(p0cmd) || P0cmd::Cr::get(p0cmd)) { - write(0); - if (!wait_for(0, *delayer(), 500, 1000)) { - PERR("PORT0 failed to stop HBA processing"); - return -1; - } - } - return 0; - } - - /** - * Start processing commands at port 0 - */ - void p0_enable_cmd_processing() - { - write(1); - read(); /* flush */ - } - - /** - * Stop and restart processing commands at port 0 - * - * \retval 0 call was successful - * \retval <0 call failed, error code - */ - int p0_restart_cmd_processing() - { - if (p0_disable_cmd_processing()) return -1; - p0_enable_cmd_processing(); - return 0; - } - - /** - * Execute prepared command, wait for completion and acknowledge at port - * - * \param P0IS_BIT state bit of the interrupt that's expected to be raised - * \param tag command slot ID - * - * \retval 0 call was successful - * \retval -1 call failed - */ - template - int p0_issue_cmd(unsigned tag) - { - typedef typename P0IS_BIT::Bitfield_base P0is_bit; - write(1 << tag); - p0_irq_rec.wait_for_signal(); - if (!read()) { - PERR("ATA0 no IRQ raised"); - return -1; - } - if (read() != P0is_bit::bits(1)) { - PERR("ATA0 expected P0IS to be %x (is %x)", - P0is_bit::bits(1), read()); - return -1; - } - write(1); - if (read()) { - PERR("ATA0 unfinished IRQ after command"); - return -1; - } - return 0; - } - - /** - * Request and read out the identification data of the port 0 device - * - * \retval 0 call was successful - * \retval <0 call failed, error code - */ - int p0_identify_device() - { - /** - * Device identification data - */ - struct Device_id - { - enum { - /* FIXME use register framework to do shifts */ - UDMA_133_SUPPORTED = 1 << 6, - UDMA_133_ACTIVE = 1 << 14, - SIZE = 0x200, - }; - - uint16_t na_0[23]; /* word 0.. 22 */ - char revision[8]; /* word 23.. 26 */ - char model_nr[40]; /* word 27.. 46 */ - uint16_t na_1[28]; /* word 47.. 74 */ - uint16_t queue_depth; /* word 75 */ - uint16_t sata_caps; /* word 76 */ - uint16_t na_2[11]; /* word 77.. 87 */ - uint16_t udma; /* word 88 */ - uint16_t na_3[11]; /* word 89.. 99 */ - uint64_t total_lba_sectors; /* word 100 */ - - /** - * Helper to print interchanged char arrays - */ - void print(char const * src, size_t size) { - for(unsigned i = 0; i < size; i+=2) - { - if (!src[i+1] || !src[i]) return; - if (src[i+1] == 0x20 && src[i] == 0x20) return; - printf("%c%c", src[i+1], src[i]); - } - } - - /** - * Print model name and firmware revision of the device - */ - void print_label() - { - print(model_nr, sizeof(model_nr)/sizeof(model_nr[0])); - printf(" rev "); - print(revision, sizeof(revision)/sizeof(revision[0])); - } - - /** - * Wether device supports native command queueing (NCQ) - */ - bool supports_ncq() { return sata_caps & (1 << 8); } - }; - - /* create receive buffer DMA */ - Ram_dataspace_capability dev_id_ds = env()->ram_session()->alloc(0x1000, Genode::UNCACHED);; - addr_t dev_id_virt = (addr_t)env()->rm_session()->attach(dev_id_ds); - addr_t dev_id_phys = Dataspace_client(dev_id_ds).phys_addr(); - - /* do command 'identify device' */ - unsigned tag = 31; - addr_t cmd_table = ct_virt + tag * CMD_TABLE_SIZE; - Fis * fis = (Fis *)cmd_table; - fis->identify_device(); - unsigned prd_id = 0; - addr_t prd = cmd_table + CMD_TABLE_HEAD_SIZE + prd_id * PRD_SIZE; - write_prd(prd, dev_id_phys, Device_id::SIZE); - addr_t cmd_slot = cl_virt + tag * CMD_SLOT_SIZE; - write_cmd_slot(cmd_slot, ct_phys + tag * CMD_TABLE_SIZE, 0, 0, 0, 1); - if (p0_issue_cmd(tag)) return -1; - - /* check if we received the requested data */ - fis = (Fis *)(fb_virt + PIO_SETUP_FIS_OFFSET); - if (!fis->is_pio_setup(Device_id::SIZE, 0)) { - PERR("Invalid PIO setup FIS"); - fis->print(); - return -1; - } - /* interpret device ID */ - Device_id * dev_id = (Device_id *)dev_id_virt; - block_cnt = dev_id->total_lba_sectors; - if (VERBOSE) { - printf("ATA0 "); - dev_id->print_label(); - printf(", %llu blocks, %llu GB\n", block_cnt, - ((uint64_t)block_cnt * BLOCK_SIZE) / 1000000000); - } - /* get command mode */ - if (!dev_id->supports_ncq()) { - PERR("ATA0 driver not proved with modes other than NCQ"); - return -1; - } - /* get transfer mode */ - if (!(dev_id->udma & Device_id::UDMA_133_SUPPORTED)) { - PERR("ATA0 driver not proved with other modes than UDMA133"); - return -1; - } - if (VERBOSE) - printf("ATA0 supports UDMA-133 and NCQ with queue depth %u\n", - dev_id->queue_depth + 1); - write(1); - p0_irq.ack_irq(); - - /* destroy receive buffer DMA */ - env()->rm_session()->detach(dev_id_virt); - env()->ram_session()->free(dev_id_ds);; - return 0; - } - - /** - * Wether the port 0 device hides blocks via the HPA feature - * - * \retval 1 hides blocks - * \retval 0 doesn't hide blocks - * \retval -1 failed to determine - */ - int p0_hides_blocks() - { - /* do command 'read native max addr' */ - unsigned tag = 31; - addr_t cmd_table = ct_virt + tag * CMD_TABLE_SIZE; - Fis * fis = (Fis *)cmd_table; - fis->read_native_max_addr(); - addr_t cmd_slot = cl_virt + tag * CMD_SLOT_SIZE; - write_cmd_slot(cmd_slot, ct_phys + tag * CMD_TABLE_SIZE, 0, 0, 0, 0); - if (p0_issue_cmd(tag)) return -1; - - /* read received address */ - fis = (Fis *)(fb_virt + REG_D2H_FIS_OFFSET); - uint64_t max_native_addr = fis->lba(); - - /* end command */ - write(1); - p0_irq.ack_irq(); - - /* check for hidden blocks */ - return max_native_addr + 1 != block_cnt; - } - - /** - * Clear all port errors at port 0 - * - * \return value of P0SERR before it was cleared - */ - P0serr::access_t p0_clear_errors() - { - P0serr::access_t const p0serr = read(); - write(p0serr); - return p0serr; - } - - /** - * Tell port 0 device wich transfer mode to use - * - * \param mode ID of targeted transfer mode - * - * \retval 0 call was successful - * \retval <0 call failed, error code - */ - int p0_transfer_mode(uint8_t mode) - { - /* do command 'set features' with feature 'set transfer mode' */ - unsigned tag = 31; - addr_t cmd_table = ct_virt + tag * CMD_TABLE_SIZE; - addr_t cmd_slot = cl_virt + tag * CMD_SLOT_SIZE; - Fis * fis = (Fis *)cmd_table; - fis->set_transfer_mode(mode); - write_cmd_slot(cmd_slot, ct_phys + tag * CMD_TABLE_SIZE, 0, 0, 0, 0); - if (p0_issue_cmd(tag)) return -1; - - /* check answer */ - fis = (Fis *)(fb_virt + REG_D2H_FIS_OFFSET); - if (!fis->is_set_transfer_mode_reply(mode)) { - PERR("Invalid reply after set up transfer mode"); - return -1; - } - /* end command */ - write(1); - p0_irq.ack_irq(); - return 0; - } - - /** - * Enable interrupt reception for port 0 - */ - void p0_enable_irqs() - { - enum { PORT = 0 }; - - /* clear IRQs */ - p0_clear_irqs(); - write(1 << PORT); - - /* enable all IRQs we need */ - P0ie::access_t p0ie = 0; - P0ie::Dhre::set(p0ie, 1); - P0ie::Pse::set(p0ie, 1); - P0ie::Dse::set(p0ie, 1); - P0ie::Sdbe::set(p0ie, 1); - P0ie::Ufe::set(p0ie, 1); - P0ie::Dpe::set(p0ie, 1); - P0ie::Pce::set(p0ie, 1); - P0ie::Prce::set(p0ie, 1); - P0ie::Ife::set(p0ie, 1); - P0ie::Hbde::set(p0ie, 1); - P0ie::Hbfe::set(p0ie, 1); - P0ie::Tfee::set(p0ie, 1); - write(p0ie); - } - - /** - * Soft reset link at port 0 - * - * \retval 0 call was successful - * \retval <0 call failed, error code - */ - int p0_soft_reset() - { - /* first soft reset FIS */ - if (p0_restart_cmd_processing()) return -1; - Fis * fis = (Fis *)ct_virt; - fis->soft_reset(0, SOFT_RESET_PMP); - write_cmd_slot(cl_virt, ct_phys, 0, 1, SOFT_RESET_PMP, 0); - - /* we can't do p0_issue_cmd here - IRQ gets not triggered */ - write(1); - if (!wait_for(0, *delayer(), 500, 1000)) { - PERR("ATA0 failed to issue first soft-reset command"); - return -1; - } - delayer()->usleep(5); /* according to spec wait at least 5 us */ - - /* second soft reset FIS */ - fis->soft_reset(1, SOFT_RESET_PMP); - write_cmd_slot(cl_virt, ct_phys, 0, 0, SOFT_RESET_PMP, 0); - write(1); - read(); /* this time simply flush because dynamic waiting not needed */ - - /* old devices might need 150 ms but newer specs say 2 ms */ - if (!wait_for(0, *delayer(), 150, 1000)) { - PERR("ATA0 drive hangs in soft reset"); - return -1; - } - return 0; - } - - /** - * Hard reset link at port 0 - * - * \retval 0 call was successful - * \retval <0 call failed, error code - */ - int p0_hard_reset(bool const set_speed = 0, - P0sctl::access_t const speed = 0) - { - enum { IPM = 3 }; - if (set_speed) { - /* - * SATA spec doesn't provide much information about speed - * reconfig. So turn off PHY meanwhile to be on the safe side. - */ - P0sctl::access_t p0sctl = read(); - P0sctl::Ipm::set(p0sctl, IPM); - P0sctl::Det::set(p0sctl, 4); - write(p0sctl); - - /* reconfigure speed */ - p0sctl = read(); - P0sctl::Spd::set(p0sctl, speed); - write(p0sctl); - } - /* request for reset via P0SCTL */ - P0sctl::access_t p0sctl = read(); - P0sctl::Ipm::set(p0sctl, IPM); - P0sctl::Det::set(p0sctl, 1); - write(p0sctl); - read(); /* flush */ - - /* wait until reset is done and end operation */ - delayer()->usleep(1000); - unsigned trials = 100; - for (; trials; trials--) - { - write(0); - - /* - * FIXME - * Some PHY controllers need much time at this point. - * Thus normally we should wait at least 200 ms to avoid - * bad behaviour but as long as exynos5 does fine - * we do it faster. - */ - delayer()->usleep(1000); - p0sctl = read(); - - if (P0sctl::Det::get(p0sctl) == 0 && - P0sctl::Ipm::get(p0sctl) == 3) break; - } - if (!trials) { - PERR("PORT0 resume after hard reset failed"); - return -1; - } - return 0; - } - - /** - * Debounce link at port 0 - * - * \param trials total amount of debouncing trials - * \param trial_us time to wait between two trials - * \param stable targeted amount of consecutive stable trials - * - * \retval 0 call was successful - * \retval <0 call failed, error code - * - * We give the port some time in order that the P0SSTS becomes stable - * over multiple reads. The call is successful if the register gets - * stable in time and with P0SSTS.DET saying "connection established". - */ - int p0_debounce(unsigned const trials, unsigned const trial_us, - unsigned const stable) - { - unsigned t = 0; /* current trial */ - unsigned s = 0; /* current amount of stable trials */ - P0ssts::access_t old_det = read(); - for (; t < trials; t++) { - delayer()->usleep(trial_us); - P0ssts::access_t new_det = read(); - if (new_det == 3 && new_det == old_det) { - s++; - if (s >= stable) break; - } else s = 0; - old_det = new_det; - } - if (t >= trials) { - if (VERBOSE) printf("PORT0 failed debouncing\n"); - return -1; - } - return 0; - } - - /** - * Disable interrupt reception for port 0 - */ - void p0_disable_irqs() { write(0); } - - /** - * Get port 0 and its device ready for NCQ commands - * - * \retval 0 call was successful - * \retval <0 call failed, error code - */ - int p0_init() - { - /* disable command processing and FIS reception */ - p0_disable_cmd_processing(); - write(0); - if (!wait_for(0, *delayer(), 500, 1000)) { - PERR("PORT0 failed to stop FIS reception"); - return -1; - } - /* clear all S-errors and interrupts */ - p0_clear_errors(); - write(read()); - write(1); - - /* activate */ - write(1); - read(); - P0cmd::access_t p0cmd = read(); - P0cmd::Sud::set(p0cmd, 1); - P0cmd::Pod::set(p0cmd, 1); - P0cmd::Icc::set(p0cmd, 1); - write(p0cmd); - - /* set up command-list- and FIS-DMA */ - write(P0clb::Clb::masked(cl_phys)); - write(P0fb::Fb::masked(fb_phys)); - - /* enable FIS reception and command processing */ - write(1); - read(); - p0_enable_cmd_processing(); - - /* disable port multiplier */ - write(0); - - /* freeze AHCI */ - p0_disable_irqs(); - p0_disable_cmd_processing(); - - /* clear D2H receive area */ - Fis * fis = (Fis *)(fb_virt + REG_D2H_FIS_OFFSET); - fis->clear_d2h_rx(); - - if (p0_hard_reset(1, p0_speed)) return -1; - if (p0_dynamic_debounce()) return -1; - - /* check if device is ready */ - if (!wait_for(0, *delayer())) { - PERR("PORT0 device not ready"); - return -1; - } - p0_enable_cmd_processing(); - - if (p0_soft_reset()) return -1; - p0_enable_irqs(); - p0_clear_errors(); - - /* set ATAPI bit appropriatly */ - write(0); - read(); /* flush */ - - /* check device type (LBA[31:8] = 0 means ATA device) */ - P0sig::access_t p0sig = read(); - if (P0sig::Lba_8_15::get(p0sig) || P0sig::Lba_16_31::get(p0sig)) { - PERR("PORT0 driver not proved with non-ATA devices"); - return -1; - } - /* check device speed */ - char const * speed; - P0ssts::access_t p0ssts = read(); - switch(P0ssts::Spd::get(p0ssts)) { - case 1: - speed = "1.5"; - break; - case 2: - speed = "3"; - break; - case 3: - speed = "6"; - break; - default: - PERR("PORT0 failed to get port speed"); - return -1; - } - /* check PM state of device */ - if (P0ssts::Ipm::get(p0ssts) != 1) { - PERR("PORT0 device not in active PM state"); - return -1; - } - if (VERBOSE) printf("PORT0 connected, ATA device, %s Gbps\n", speed); - - if (p0_identify_device()) return -1; - if (p0_hides_blocks()) { - PERR("ATA0 drive hides blocks via HPA"); - return -1; - } - - /* - * FIXME - * At this point Linux normally reads out the parameters of the - * SATA DevSlp feature but the values are used only when it comes - * to LPM wich wasn't needed at all in our use cases. Look for - * 'ata_dev_configure' and 'ATA_LOG_DEVSLP_*' in Linux if you want - * to add this feature. - */ - - if (p0_transfer_mode(UDMA_133)) return -1; - - if (p0_clear_errors()) { - PERR("ATA0 errors after initialization"); - return -1; - } - delayer()->usleep(10000); - return 0; - } - - /** - * Do a NCQ command, wait until it is finished, and end it - * - * \param lba Logical block address of first block. - * Holds current error LBA if call returns 1. - * \param cnt blocks to transfer - * \param phys physical adress of receive/send buffer DMA - * \param w 1: write 0: read - * - * \retval 0 command finished without errors - * \retval 1 port has been recovered from errors, lba denotes error point - * \retval -1 errors occured and port couln't be recovered - */ - int ncq_command(uint64_t & lba, size_t cnt, addr_t phys, bool w) - { - /* set up command table entry */ - unsigned tag = 0; - Fis * fis = (Fis *)(ct_virt + tag * CMD_TABLE_SIZE); - fis->fpdma_queued(w, lba, cnt, tag); - - /* set up scatter/gather list */ - addr_t prd_list = ct_virt + tag * CMD_TABLE_SIZE + CMD_TABLE_HEAD_SIZE; - uint8_t prdtl = 0; - if (write_prd_list(prd_list, phys, cnt, prdtl)) { - PERR("failed to set up scatter/gather list"); - return -1; - } - /* set up command list entry */ - addr_t cmd_slot = cl_virt + tag * CMD_SLOT_SIZE; - addr_t cmd_table = ct_phys + tag * CMD_TABLE_SIZE; - write_cmd_slot(cmd_slot, cmd_table, w, 0, 0, prdtl); - - /* issue command and wait for completion */ - write(1 << tag); - write(1 << tag); - p0_irq_rec.wait_for_signal(); - - /* get port back ready and deteremine command state */ - int ret = p0_handle_irqs(lba); - if (ret >= 0) { - P0sntf::access_t pmn = read(); - if (pmn) { - write(pmn); - PERR("ATA0 PM notification after NCQ command"); - return -1; - } - if (read()) { - PERR("ATA0 outstanding commands after NCQ command"); - return -1; - } - write(1); - p0_irq.ack_irq(); - } - return ret; - } - - /** - * Try debouncing, if it fails lower settings one by one till it succeeds - * - * \retval 0 debouncing succeeded with settings stored in member vars - * \retval -1 failed to do successful debouncing - */ - int p0_dynamic_debounce() - { - unsigned const initial_p0_speed = p0_speed; - - /* try debouncing with presettings first */ - while (p0_debounce(dbc_trials, dbc_trial_us, dbc_stable_trials)) - { - /* recover from debouncing error */ - p0_clear_errors(); - delayer()->usleep(10000); - if (read()) { - p0_clear_irqs(); - write(read()); - } - p0_clear_errors(); - if (read()) { - PERR("PORT0 failed to recover from debouncing error %x", read()); - return -1; - } - - /* - * FIXME - * Linux cleared D2H FIS again at this point but it seemed not - * to be necessary as all works fine without. - */ - - /* try to lower settings and retry debouncing */ - if (dbc_trial_us == SLOW_DBC_TRIAL_US && p0_speed == 1) { - PERR("PORT0 debouncing failed with lowest settings"); - return -1; - } else if (p0_speed != 1) { - /* - * If no speed limit is set, go to the most generous limit, - * otherwise choose the next harder limit. - */ - if (VERBOSE) printf("PORT0 lower port speed\n"); - if (p0_speed == 0) p0_speed = 3; - else p0_speed--; - if (p0_hard_reset(1, p0_speed)) return -1; - } else { - /* - * Reset port speed and redo dynamic debouncing - * but do it more gently this time. - */ - if (VERBOSE) printf("PORT0 retry debouncing more gently\n"); - dbc_trial_us = SLOW_DBC_TRIAL_US; - p0_speed = initial_p0_speed; - if (p0_hard_reset(1, p0_speed)) return -1; - } - } - p0_clear_errors(); - return 0; - } - - /** - * Rescue port 0 from an error that occured after port initialization - * - * \retval 0 call was successful - * \retval <0 call failed, error code - * - * FIXME - * This function is merely a trimmed version of 'p0_init' to keep - * implementation costs low. Implement specialized methods to speed up - * recovery. - */ - int p0_error_recovery() - { - /* disable command processing and FIS reception */ - p0_disable_cmd_processing(); - write(0); - if (!wait_for(0, *delayer(), 500, 1000)) { - PERR("PORT0 failed to stop FIS reception"); - return -1; - } - /* clear all S-errors and interrupts */ - p0_clear_errors(); - write(read()); - write(1); - - /* activate */ - write(1); - read(); - P0cmd::access_t p0cmd = read(); - P0cmd::Sud::set(p0cmd, 1); - P0cmd::Pod::set(p0cmd, 1); - P0cmd::Icc::set(p0cmd, 1); - write(p0cmd); - - /* set up command-list- and FIS-DMA */ - write(P0clb::Clb::masked(cl_phys)); - write(P0fb::Fb::masked(fb_phys)); - - /* enable FIS reception and command processing */ - write(1); - read(); - p0_enable_cmd_processing(); - - /* disable port multiplier */ - write(0); - - /* freeze AHCI */ - p0_disable_irqs(); - p0_disable_cmd_processing(); - - /* clear D2H receive area */ - Fis * fis = (Fis *)(fb_virt + REG_D2H_FIS_OFFSET); - fis->clear_d2h_rx(); - - if (p0_hard_reset()) return -1; - if (p0_dynamic_debounce()) return -1; - - /* check if device is ready */ - if (!wait_for(0, *delayer())) { - PERR("PORT0 device not ready"); - return -1; - } - p0_enable_cmd_processing(); - - if (p0_soft_reset()) return -1; - p0_enable_irqs(); - p0_clear_errors(); - - /* set ATAPI bit appropriatly */ - write(0); - read(); /* flush */ - - /* - * In contrast to 'p0_init' don't check static port parameters - * like speed and device type at this point. - */ - - /* check PM state of device */ - P0ssts::access_t p0ssts = read(); - if (P0ssts::Ipm::get(p0ssts) != 1) { - PERR("PORT0 device not in active PM state"); - return -1; - } - - /* - * In contrast to 'p0_init' don't check static device parameters - * like device ID and native max address at this point. - */ - - /* - * FIXME - * At this point Linux normally reads out the parameters of the - * SATA DevSlp feature but the values are used only when it comes - * to LPM wich wasn't needed at all in our use cases. Look for - * 'ata_dev_configure' and 'ATA_LOG_DEVSLP_*' in Linux if you want - * to add this feature. - */ - - /* In contrast to 'p0_init' don't set transfer mode at this point */ - - if (p0_clear_errors()) { - PERR("ATA0 errors after initialization"); - return -1; - } - - /* - * FIXME - * Linux waits 10 ms at this point in driver initialization - * (this is not initialization but mimics it for error recovery) - * but as long as all works fine we do it faster. - */ - delayer()->usleep(1000); - return 0; - } - - /** - * Set up scatter/gather list for contiguous DMA - * - * \param list virtual base of scatter/gather list - * \param phys physical base of DMA - * \param cnt DMA size in blocks - * \param prdtl gets overridden with list size in PRDs - * - * \return size of DMA tail not written to the list due to size limit - */ - size_t write_prd_list(addr_t list, addr_t phys, - unsigned cnt, uint8_t & prdtl) - { - unsigned bytes = cnt * BLOCK_SIZE; - addr_t prd = list; - addr_t seek = phys; - while (1) { - if (bytes > BYTES_PER_PRD) { - write_prd(prd, seek, BYTES_PER_PRD); - seek += BYTES_PER_PRD; - bytes -= BYTES_PER_PRD; - prd += PRD_SIZE; - prdtl++; - if (prdtl == 0xff) return bytes; - } else { - if (bytes) { - write_prd(prd, seek, bytes); - prdtl++; - } - return 0; - } - } - } -}; - -static Sata_ahci * sata_ahci() { - static Sata_ahci sata_ahci; - return &sata_ahci; -} - - -/***************** - ** Ahci_driver ** - *****************/ - -Ahci_driver::Ahci_driver() -{ - static Regulator::Connection clock_src(Regulator::CLK_SATA); - static Regulator::Connection power_src(Regulator::PWR_SATA); - clock_src.state(true); - power_src.state(true); - i2c_sataphy()->init(); - if (sata_phy_ctrl()->init()) throw Root::Unavailable(); - if (sata_ahci()->init()) throw Root::Unavailable(); - if (sata_ahci()->p0_init()) throw Root::Unavailable(); -} - -int Ahci_driver::_ncq_command(uint64_t lba, unsigned cnt, addr_t phys, bool w) -{ - /* sanity check */ - if (!cnt || (lba + cnt) > block_count()) { - PERR("Sanity check failed on block driver command"); - return -1; - } - /* if error occurs during command continue from error LBA */ - int ret = 1; - while (1) - { - /* try to execute command */ - uint64_t last_lba = lba; - ret = sata_ahci()->ncq_command(lba, cnt, phys, w); - if (ret != 1) break; - - /* calculate remaining area */ - unsigned done_cnt = lba - last_lba; - cnt -= done_cnt; - phys += done_cnt * block_size(); - if (VERBOSE) - printf("continue with blocks %llu..%llu after error\n", - lba, lba + cnt - 1); - } - return ret; -} - -Block::sector_t Ahci_driver::block_count() { return sata_ahci()->block_cnt; } -size_t Ahci_driver::block_size() { return Sata_ahci::BLOCK_SIZE; } diff --git a/repos/os/src/drivers/ahci/exynos5/ahci_driver.h b/repos/os/src/drivers/ahci/exynos5/ahci_driver.h deleted file mode 100644 index dbafb260b..000000000 --- a/repos/os/src/drivers/ahci/exynos5/ahci_driver.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * \brief AHCI driver declaration - * \author Martin Stein - * \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 -#include - -/** - * 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_ */ - diff --git a/repos/os/src/drivers/ahci/exynos5/bench/main.cc b/repos/os/src/drivers/ahci/exynos5/bench/main.cc deleted file mode 100644 index 0c300ff09..000000000 --- a/repos/os/src/drivers/ahci/exynos5/bench/main.cc +++ /dev/null @@ -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 -#include -#include -#include -#include - -/* local includes */ -#include - - -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(); - 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; -} diff --git a/repos/os/src/drivers/ahci/exynos5/bench/target.mk b/repos/os/src/drivers/ahci/exynos5/bench/target.mk deleted file mode 100644 index 19745512c..000000000 --- a/repos/os/src/drivers/ahci/exynos5/bench/target.mk +++ /dev/null @@ -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)/.. diff --git a/repos/os/src/drivers/ahci/exynos5/platform.cc b/repos/os/src/drivers/ahci/exynos5/platform.cc new file mode 100644 index 000000000..309ccc0db --- /dev/null +++ b/repos/os/src/drivers/ahci/exynos5/platform.cc @@ -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 +#include +#include +#include + +#include + +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() && !read()) 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()) { + 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(0, delayer)) { + PERR("I2C busy"); + return -1; + } + Stat::access_t stat = read(); + Stat::Txrx_en::set(stat, 1); + Stat::Mode::set(stat, 3); + write(stat); + write(start_msg); + delayer.usleep(1000); + write(11); + write(1); + + /* transmit message payload */ + for (unsigned i = 0; i < msg_size; i++) { + if (!ack_received()) return -1; + write(msg[i]); + delayer.usleep(TX_DELAY_US); + write(0); + if (arbitration_error()) return -1; + } + /* end message transfer */ + if (!ack_received()) return -1; + write(0); + write(0); + write(0); /* FIXME fixup */ + if (arbitration_error()) return -1; + if (!wait_for(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(SLAVE_ADDR); + + Con::access_t con = read(); + 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); + + Lc::access_t lc = 0; + Lc::Sda_out_delay::set(lc, 3); + Lc::Filter_en::set(lc, 1); + write(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(0); + write(~0); + write(~0); + write(~0); + + /* set up SATA phy generation 3 (6 Gb/s) */ + Phctrlm::access_t phctrlm = read(); + Phctrlm::Ref_rate::set(phctrlm, 0); + Phctrlm::High_speed::set(phctrlm, 1); + write(phctrlm); + Ctrl0::access_t ctrl0 = read(); + Ctrl0::P0_phy_calibrated::set(ctrl0, 1); + Ctrl0::P0_phy_calibrated_sel::set(ctrl0, 1); + write(ctrl0); + write(2); + if (i2c_sataphy.enable_40_pins()) return -1; + + /* Release reset */ + write(0); + write(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(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; +} diff --git a/repos/os/src/drivers/ahci/include/ahci_device_base.h b/repos/os/src/drivers/ahci/include/ahci_device_base.h deleted file mode 100644 index 11abd3ad2..000000000 --- a/repos/os/src/drivers/ahci/include/ahci_device_base.h +++ /dev/null @@ -1,578 +0,0 @@ -/* - * \brief Generic base of AHCI device - * \author Sebastian Sumpf - * \author Martin Stein - * \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 -#include -#include -#include -#include -#include -#include -#include -#include - -/** - * 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_ */ - diff --git a/repos/os/src/drivers/ahci/include/ahci_driver_base.h b/repos/os/src/drivers/ahci/include/ahci_driver_base.h deleted file mode 100644 index 8f0df87d9..000000000 --- a/repos/os/src/drivers/ahci/include/ahci_driver_base.h +++ /dev/null @@ -1,90 +0,0 @@ - -/* - * \brief Generic base of AHCI driver - * \author Sebastian Sumpf - * \author Martin Stein - * \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 - -/** - * 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_ */ - diff --git a/repos/os/src/drivers/ahci/main.cc b/repos/os/src/drivers/ahci/main.cc index 05e43c7ec..296ec09a8 100644 --- a/repos/os/src/drivers/ahci/main.cc +++ b/repos/os/src/drivers/ahci/main.cc @@ -1,63 +1,158 @@ -/* - * \brief Minimal AHCI-ATA driver - * \author Sebastian Sumpf - * \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 +#include #include +#include -/* local includes */ -#include +#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), + ""); + 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); } } - diff --git a/repos/os/src/drivers/ahci/target.mk b/repos/os/src/drivers/ahci/target.mk index 6e16923eb..0fa53e173 100644 --- a/repos/os/src/drivers/ahci/target.mk +++ b/repos/os/src/drivers/ahci/target.mk @@ -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 diff --git a/repos/os/src/drivers/ahci/x86/ahci_device.h b/repos/os/src/drivers/ahci/x86/ahci_device.h deleted file mode 100644 index 32023de0d..000000000 --- a/repos/os/src/drivers/ahci/x86/ahci_device.h +++ /dev/null @@ -1,209 +0,0 @@ -/* - * \brief AHCI device - * \author Sebastian Sumpf - * \author Martin Stein - * \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 -#include - -/* local includes */ -#include - -/** - * 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 */ - 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_ */ - diff --git a/repos/os/src/drivers/ahci/x86/ahci_driver.h b/repos/os/src/drivers/ahci/x86/ahci_driver.h deleted file mode 100644 index 289e6d876..000000000 --- a/repos/os/src/drivers/ahci/x86/ahci_driver.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * \brief AHCI driver - * \author Sebastian Sumpf - * \author Martin Stein - * \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 - -/* local includes */ -#include - -/** - * 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_ */ - diff --git a/repos/os/src/drivers/ahci/x86/platform.cc b/repos/os/src/drivers/ahci/x86/platform.cc new file mode 100644 index 000000000..722840526 --- /dev/null +++ b/repos/os/src/drivers/ahci/x86/platform.cc @@ -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 +#include +#include +#include + +#include + + +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 */ + PCI_CMD = 0x4, + }; + + Pci::Connection pci; + Pci::Device_capability pci_device_cap; + Lazy_volatile_object pci_device; + Lazy_volatile_object 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; +} diff --git a/repos/ports-foc/run/l4linux_ahci_bench.run b/repos/ports-foc/run/l4linux_ahci_bench.run index b9785929a..036314632 100644 --- a/repos/ports-foc/run/l4linux_ahci_bench.run +++ b/repos/ports-foc/run/l4linux_ahci_bench.run @@ -58,7 +58,7 @@ set config { - + @@ -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 diff --git a/repos/ports/run/seoul.inc b/repos/ports/run/seoul.inc index 79557c2fb..303cef554 100644 --- a/repos/ports/run/seoul.inc +++ b/repos/ports/run/seoul.inc @@ -168,8 +168,12 @@ append_if [expr !$use_usb] config { append_if $use_block_sata config { + + + + } 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