mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-13 22:23:45 +00:00
parent
2e7802b799
commit
37c2f101b1
17
os/lib/mk/ahci.inc
Normal file
17
os/lib/mk/ahci.inc
Normal file
@ -0,0 +1,17 @@
|
||||
#
|
||||
# \brief Generic toolchain configurations for AHCI
|
||||
# \author Martin Stein <martin.stein@genode-labs.com>
|
||||
# \date 2013-05-17
|
||||
#
|
||||
|
||||
# add C++ sources
|
||||
SRC_CC += main.cc
|
||||
|
||||
# add library dependencies
|
||||
LIBS += base
|
||||
|
||||
# add include directories
|
||||
INC_DIR += $(REP_DIR)/src/drivers/ahci/include
|
||||
|
||||
# declare source paths
|
||||
vpath main.cc $(REP_DIR)/src/drivers/ahci
|
11
os/lib/mk/x86_32/ahci.mk
Normal file
11
os/lib/mk/x86_32/ahci.mk
Normal file
@ -0,0 +1,11 @@
|
||||
#
|
||||
# \brief Toolchain configurations for AHCI on X86 32 bit
|
||||
# \author Martin Stein <martin.stein@genode-labs.com>
|
||||
# \date 2013-05-17
|
||||
#
|
||||
|
||||
# add include directories
|
||||
INC_DIR += $(REP_DIR)/src/drivers/ahci/x86_32
|
||||
|
||||
# include less specific config
|
||||
include $(REP_DIR)/lib/mk/ahci.inc
|
@ -1,17 +1,17 @@
|
||||
if {![have_spec x86_32]} {
|
||||
puts "\nThe AHCI driver supports the x86_32 architecture only\n"
|
||||
if {![have_spec x86_32] && ![have_spec exynos5]} {
|
||||
puts "\nThe AHCI driver supports x86_32 architecture and exynos5 only\n"
|
||||
exit 0
|
||||
}
|
||||
|
||||
#
|
||||
# Build
|
||||
#
|
||||
set build_components {
|
||||
core init drivers/timer drivers/pci drivers/ahci test/block
|
||||
}
|
||||
|
||||
lappend_if [have_spec acpi] build_components drivers/acpi
|
||||
lappend_if [have_spec pci] build_components drivers/pci/device_pd
|
||||
set build_components { core init drivers/timer drivers/ahci test/block }
|
||||
|
||||
lappend_if [have_spec x86_32] build_components drivers/pci
|
||||
lappend_if [have_spec acpi] build_components drivers/acpi
|
||||
lappend_if [have_spec pci] build_components drivers/pci/device_pd
|
||||
|
||||
build $build_components
|
||||
|
||||
@ -54,7 +54,7 @@ append_if [have_spec acpi] config {
|
||||
</route>
|
||||
</start>}
|
||||
|
||||
append_if [expr ![have_spec acpi]] config {
|
||||
append_if [expr ![have_spec acpi] && [have_spec x86_32]] config {
|
||||
<start name="pci_drv">
|
||||
<resource name="RAM" quantum="2M"/>
|
||||
<provides><service name="PCI"/></provides>
|
||||
@ -64,13 +64,17 @@ append config {
|
||||
<start name="timer">
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<provides><service name="Timer"/></provides>
|
||||
<route>
|
||||
<service name="IRQ"><child name="acpi" /></service>
|
||||
<route>}
|
||||
|
||||
append_if [have_spec acpi] config {
|
||||
<service name="IRQ"><child name="acpi" /></service>}
|
||||
|
||||
append config {
|
||||
<any-service> <parent /> <any-child /></any-service>
|
||||
</route>
|
||||
</start>
|
||||
<start name="ahci">
|
||||
<binary name="ahci_drv" />
|
||||
<binary name="ahci" />
|
||||
<resource name="RAM" quantum="10M" />
|
||||
<provides><service name="Block" /></provides>
|
||||
<route>}
|
||||
@ -97,13 +101,10 @@ install_config $config
|
||||
# Boot modules
|
||||
#
|
||||
|
||||
set boot_modules {
|
||||
core init timer pci_drv ahci_drv test-block acpi_drv
|
||||
}
|
||||
set boot_modules { core init timer ahci test-block }
|
||||
|
||||
if {[have_spec nova]} {
|
||||
append boot_modules {pci_device_pd}
|
||||
}
|
||||
append_if [have_spec x86_32] boot_modules { pci_drv acpi_drv }
|
||||
append_if [have_spec nova] boot_modules pci_device_pd
|
||||
|
||||
build_boot_image $boot_modules
|
||||
|
||||
|
3
os/src/drivers/ahci/empty.cc
Normal file
3
os/src/drivers/ahci/empty.cc
Normal file
@ -0,0 +1,3 @@
|
||||
/*
|
||||
* Dummy compilation unit needed to link a valid target.
|
||||
*/
|
547
os/src/drivers/ahci/include/ahci_device_base.h
Normal file
547
os/src/drivers/ahci/include/ahci_device_base.h
Normal file
@ -0,0 +1,547 @@
|
||||
/*
|
||||
* \brief Generic base of AHCI device
|
||||
* \author Sebastian Sumpf <Sebastian.Sumpf@genode-labs.com>
|
||||
* \author Martin Stein <Martin.Stein@genode-labs.com>
|
||||
* \date 2011-08-10
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2011-2013 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _AHCI_DEVICE_BASE_H_
|
||||
#define _AHCI_DEVICE_BASE_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/printf.h>
|
||||
#include <base/sleep.h>
|
||||
#include <block/driver.h>
|
||||
#include <block/component.h>
|
||||
#include <cap_session/connection.h>
|
||||
#include <dataspace/client.h>
|
||||
#include <irq_session/connection.h>
|
||||
#include <io_mem_session/connection.h>
|
||||
#include <timer_session/connection.h>
|
||||
|
||||
/**
|
||||
* Enable for debugging output
|
||||
*/
|
||||
static const bool verbose = false;
|
||||
|
||||
using namespace Genode;
|
||||
|
||||
|
||||
/**
|
||||
* Base class for register access
|
||||
*/
|
||||
class Reg_base
|
||||
{
|
||||
protected:
|
||||
|
||||
uint32_t _value(uint32_t offset) { return *(volatile uint32_t *)(this + offset); }
|
||||
void _set(uint32_t offset, uint32_t val) { *(volatile uint32_t *)(this + offset) = val; }
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* HBA: Generic Host Control
|
||||
*/
|
||||
class Generic_ctrl : public Reg_base
|
||||
{
|
||||
public:
|
||||
|
||||
/* host capabilities */
|
||||
uint32_t hba_cap() { return _value(0x0); }
|
||||
|
||||
/* return port count from hba_cap */
|
||||
uint32_t port_count() { return (hba_cap() & 0x1f) + 1; }
|
||||
|
||||
/* return command slot count from hba_cap */
|
||||
uint32_t cmd_slots() { return ((hba_cap() >> 8) & 0x1f) + 1; }
|
||||
|
||||
/* global host control */
|
||||
uint32_t hba_ctrl() { return _value(0x4); }
|
||||
void hba_ctrl(uint32_t val) { _set(0x4, val); }
|
||||
|
||||
/* set Interrupt Enable (IE) in hba_ctrl */
|
||||
void global_interrupt_enable()
|
||||
{
|
||||
hba_ctrl(hba_ctrl() | 2);
|
||||
|
||||
if (verbose)
|
||||
PDBG("HBA %x", hba_ctrl());
|
||||
}
|
||||
|
||||
/* set AHCI enable (AE) in hba_ctrl */
|
||||
void global_enable_ahci()
|
||||
{
|
||||
if (!(hba_ctrl() & (1 << 31)))
|
||||
hba_ctrl(hba_ctrl() | (1 << 31));
|
||||
|
||||
if (verbose)
|
||||
PDBG("AHCI ENABLED: %x", hba_ctrl());
|
||||
}
|
||||
|
||||
/* global interrupt status (contains port interrupts) */
|
||||
uint32_t hba_intr_status() { return _value(0x8); }
|
||||
void hba_intr_status(uint32_t val) { return _set(0x8, val); }
|
||||
|
||||
/* acknowledge global interrupt */
|
||||
void hba_interrupt_ack() { hba_intr_status(hba_intr_status()); }
|
||||
|
||||
/* AHCI version */
|
||||
uint32_t version() { return _value(0x10); }
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* AHCI port registers (one set per port)
|
||||
*/
|
||||
class Ahci_port : public Reg_base
|
||||
{
|
||||
public:
|
||||
|
||||
/* command list base (lower 32 bit) */
|
||||
void cmd_list_base(addr_t cmd_base) { _set(0x0, cmd_base); }
|
||||
|
||||
/* receive FIS base address (lower 32 bit) */
|
||||
void fis_base(addr_t fis_base) { _set(0x8, fis_base); }
|
||||
|
||||
/* 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, uint32_t lba48, uint16_t blk_cnt, addr_t phys_addr)
|
||||
{
|
||||
enum { MAX_BYTES = 1 << 22 }; /* 4MB = one PRD */
|
||||
uint8_t *fis = (uint8_t *)this;
|
||||
|
||||
/* 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] = 0x0; /* LBA 32 - 39 */
|
||||
fis[10] = 0x0; /* 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 */
|
||||
memcpy(&fis[0x80], &phys_addr, 4); /* DBA: data base address */
|
||||
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_connection *_irq; /* device IRQ */
|
||||
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;
|
||||
|
||||
/* setup command table (128 byte aligned (cache line size)) */
|
||||
_cmd_list->cmd_table_base_l = phys;
|
||||
_cmd_list->cmd_table_base_u = 0;
|
||||
_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);
|
||||
|
||||
/* wait for interrupt */
|
||||
_irq->wait_for_irq();
|
||||
|
||||
if (verbose)
|
||||
PDBG("Int status (IRQ): global: %x port: %x error: %x",
|
||||
_ctrl->hba_intr_status(), _port->intr_status(), _port->err());
|
||||
|
||||
/* acknowledge interrupt */
|
||||
uint32_t 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();
|
||||
|
||||
/* 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(size_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(size_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;
|
||||
};
|
||||
|
||||
#endif /* _AHCI_DEVICE_BASE_H_ */
|
||||
|
84
os/src/drivers/ahci/include/ahci_driver_base.h
Normal file
84
os/src/drivers/ahci/include/ahci_driver_base.h
Normal file
@ -0,0 +1,84 @@
|
||||
|
||||
/*
|
||||
* \brief Generic base of AHCI driver
|
||||
* \author Sebastian Sumpf <Sebastian.Sumpf@genode-labs.com>
|
||||
* \author Martin Stein <Martin.Stein@genode-labs.com>
|
||||
* \date 2011-08-10
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2011-2013 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _AHCI_DRIVER_BASE_H_
|
||||
#define _AHCI_DRIVER_BASE_H_
|
||||
|
||||
/* local includes */
|
||||
#include <ahci_device.h>
|
||||
|
||||
/**
|
||||
* Implementation of block driver interface
|
||||
*/
|
||||
class Ahci_driver_base : public Block::Driver
|
||||
{
|
||||
protected:
|
||||
|
||||
Ahci_device *_device;
|
||||
|
||||
void _sanity_check(size_t block_number, size_t count)
|
||||
{
|
||||
if (!_device || (block_number + count > block_count()))
|
||||
throw Io_error();
|
||||
}
|
||||
|
||||
Ahci_driver_base(Ahci_device * const device) : _device(device) { }
|
||||
|
||||
public:
|
||||
|
||||
~Ahci_driver_base()
|
||||
{
|
||||
if (_device)
|
||||
destroy(env()->heap(), _device);
|
||||
}
|
||||
|
||||
size_t block_size() { return Ahci_device::block_size(); }
|
||||
size_t block_count() { return _device ? _device->block_count() : 0; }
|
||||
bool dma_enabled() { return true; }
|
||||
|
||||
void read_dma(size_t block_number,
|
||||
size_t block_count,
|
||||
addr_t phys)
|
||||
{
|
||||
_sanity_check(block_number, block_count);
|
||||
_device->read(block_number, block_count, phys);
|
||||
}
|
||||
|
||||
void write_dma(size_t block_number,
|
||||
size_t block_count,
|
||||
addr_t phys)
|
||||
{
|
||||
_sanity_check(block_number, block_count);
|
||||
_device->write(block_number, block_count, phys);
|
||||
}
|
||||
|
||||
void read(size_t, size_t, char *)
|
||||
{
|
||||
PERR("%s should not be called", __PRETTY_FUNCTION__);
|
||||
throw Io_error();
|
||||
}
|
||||
|
||||
void write(size_t, size_t, char const *)
|
||||
{
|
||||
PERR("%s should not be called", __PRETTY_FUNCTION__);
|
||||
throw Io_error();
|
||||
}
|
||||
|
||||
Ram_dataspace_capability alloc_dma_buffer(size_t size) {
|
||||
return _device->alloc_dma_buffer(size); }
|
||||
};
|
||||
|
||||
#endif /* _AHCI_DRIVER_BASE_H_ */
|
||||
|
@ -16,769 +16,16 @@
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#include <base/printf.h>
|
||||
#include <base/sleep.h>
|
||||
#include <block/driver.h>
|
||||
#include <block/component.h>
|
||||
/* Genode includes */
|
||||
#include <cap_session/connection.h>
|
||||
#include <dataspace/client.h>
|
||||
#include <irq_session/connection.h>
|
||||
#include <pci_session/connection.h>
|
||||
#include <pci_device/client.h>
|
||||
#include <io_mem_session/connection.h>
|
||||
#include <timer_session/connection.h>
|
||||
#include <block/component.h>
|
||||
#include <base/sleep.h>
|
||||
|
||||
|
||||
/**
|
||||
* Enable for debugging output
|
||||
*/
|
||||
static const bool verbose = false;
|
||||
/* local includes */
|
||||
#include <ahci_driver.h>
|
||||
|
||||
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 (lower 32 bit) */
|
||||
void cmd_list_base(addr_t cmd_base) { _set(0x0, cmd_base); }
|
||||
|
||||
/* receive FIS base address (lower 32 bit) */
|
||||
void fis_base(addr_t fis_base) { _set(0x8, fis_base); }
|
||||
|
||||
/* 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, uint32_t lba48, uint16_t blk_cnt, addr_t phys_addr)
|
||||
{
|
||||
enum { MAX_BYTES = 1 << 22 }; /* 4MB = one PRD */
|
||||
uint8_t *fis = (uint8_t *)this;
|
||||
|
||||
/* 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] = 0x0; /* LBA 32 - 39 */
|
||||
fis[10] = 0x0; /* 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 */
|
||||
memcpy(&fis[0x80], &phys_addr, 4); /* DBA: data base address */
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* AHCI device
|
||||
*/
|
||||
class Ahci_device
|
||||
{
|
||||
private:
|
||||
|
||||
enum Pci_config {
|
||||
PCI_CFG_BMIBA_OFF = 0x24, /* offset in PCI config space */
|
||||
CLASS_MASS_STORAGE = 0x10000U,
|
||||
SUBCLASS_AHCI = 0x0600U,
|
||||
CLASS_MASK = 0xffff00U,
|
||||
AHCI_BASE_ID = 0x5, /* resource id of AHCI base addr <BAR 5> */
|
||||
AHCI_INTR_OFF = 0x3c, /* offset of interrupt information in config space */
|
||||
AHCI_PORT_BASE = 0x100,
|
||||
};
|
||||
|
||||
Generic_ctrl *_ctrl; /* generic host control */
|
||||
Ahci_port *_port; /* port base of device */
|
||||
Irq_connection *_irq; /* device IRQ */
|
||||
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 */
|
||||
|
||||
::Pci::Connection &_pci;
|
||||
::Pci::Device_client *_pci_device;
|
||||
::Pci::Device_capability _pci_device_cap;
|
||||
|
||||
/**
|
||||
* Return next PCI device
|
||||
*/
|
||||
static Pci::Device_capability _scan_pci(Pci::Connection &pci, Pci::Device_capability prev_device_cap)
|
||||
{
|
||||
Pci::Device_capability device_cap;
|
||||
device_cap = pci.next_device(prev_device_cap,
|
||||
CLASS_MASS_STORAGE | SUBCLASS_AHCI,
|
||||
CLASS_MASK);
|
||||
|
||||
if (prev_device_cap.valid())
|
||||
pci.release_device(prev_device_cap);
|
||||
|
||||
return device_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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(pci_device);
|
||||
|
||||
/* 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(pci_device);
|
||||
}
|
||||
|
||||
void _setup_memory(::Pci::Device_client *pci_device)
|
||||
{
|
||||
_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;
|
||||
|
||||
/* setup command table (128 byte aligned (cache line size)) */
|
||||
_cmd_list->cmd_table_base_l = phys;
|
||||
_cmd_list->cmd_table_base_u = 0;
|
||||
_cmd_table = (struct Command_table *)(virt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute ATA 'IDENTIFY DEVICE' command
|
||||
*/
|
||||
void _identify_device(Pci::Device_client *pci_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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/* wait for interrupt */
|
||||
_irq->wait_for_irq();
|
||||
|
||||
if (verbose)
|
||||
PDBG("Int status (IRQ): global: %x port: %x error: %x",
|
||||
_ctrl->hba_intr_status(), _port->intr_status(), _port->err());
|
||||
|
||||
/* acknowledge interrupt */
|
||||
uint32_t 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();
|
||||
|
||||
/* disable hba */
|
||||
_port->hba_disable();
|
||||
}
|
||||
|
||||
static void _disable_msi(::Pci::Device_client *pci)
|
||||
{
|
||||
enum { PM_CAP_OFF = 0x34, MSI_CAP = 0x5, MSI_ENABLED = 0x1 };
|
||||
uint8_t cap = pci->config_read(PM_CAP_OFF, ::Pci::Device::ACCESS_8BIT);
|
||||
|
||||
/* iterate through cap pointers */
|
||||
for (uint16_t val = 0; cap; cap = val >> 8) {
|
||||
val = pci->config_read(cap, ::Pci::Device::ACCESS_16BIT);
|
||||
|
||||
if ((val & 0xff) != MSI_CAP)
|
||||
continue;
|
||||
uint16_t msi = pci->config_read(cap + 2, ::Pci::Device::ACCESS_16BIT);
|
||||
|
||||
if (msi & MSI_ENABLED) {
|
||||
pci->config_write(cap + 2, msi ^ MSI_CAP, ::Pci::Device::ACCESS_8BIT);
|
||||
PINF("Disabled MSIs %x", msi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Ahci_device(addr_t base, Io_mem_session_capability io_cap,
|
||||
Pci::Connection &pci)
|
||||
:
|
||||
_ctrl((Generic_ctrl *)base), _port(0), _irq(0), _cmd_list(0),
|
||||
_cmd_table(0), _io_cap(io_cap), _pci(pci), _pci_device(0) {}
|
||||
|
||||
~Ahci_device()
|
||||
{
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
|
||||
/* 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));
|
||||
}
|
||||
|
||||
/* disable message signaled interrupts */
|
||||
_disable_msi(pci_device);
|
||||
|
||||
device->_irq = new(env()->heap()) Irq_connection(intr & 0xff);
|
||||
/* remember pci_device to be able to allocate ram memory which is dma able */
|
||||
device->_pci_device_cap = device_cap;
|
||||
device->_pci_device = pci_device;
|
||||
/* trigger assignment of pci device to the ahci driver */
|
||||
pci.config_extended(device_cap);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
static size_t block_size() { return 512; }
|
||||
size_t block_count() { return _block_cnt; }
|
||||
|
||||
/**
|
||||
* Issue ATA 'READ_DMA_EXT' command
|
||||
*/
|
||||
void read(size_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(size_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();
|
||||
}
|
||||
|
||||
Ram_dataspace_capability alloc_dma_buffer(size_t size) {
|
||||
return _pci.alloc_dma_buffer(_pci_device_cap, size); }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of block driver interface
|
||||
*/
|
||||
class Ahci_driver : public Block::Driver
|
||||
{
|
||||
private:
|
||||
|
||||
Pci::Connection _pci;
|
||||
Ahci_device *_device;
|
||||
|
||||
void _sanity_check(size_t block_number, size_t count)
|
||||
{
|
||||
if (!_device || (block_number + count > block_count()))
|
||||
throw Io_error();
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Ahci_driver() : _device(Ahci_device::probe(_pci)) { }
|
||||
|
||||
~Ahci_driver()
|
||||
{
|
||||
if (_device)
|
||||
destroy(env()->heap(), _device);
|
||||
}
|
||||
|
||||
size_t block_size() { return Ahci_device::block_size(); }
|
||||
size_t block_count() { return _device ? _device->block_count() : 0; }
|
||||
bool dma_enabled() { return true; }
|
||||
|
||||
void read_dma(size_t block_number,
|
||||
size_t block_count,
|
||||
addr_t phys)
|
||||
{
|
||||
_sanity_check(block_number, block_count);
|
||||
_device->read(block_number, block_count, phys);
|
||||
}
|
||||
|
||||
void write_dma(size_t block_number,
|
||||
size_t block_count,
|
||||
addr_t phys)
|
||||
{
|
||||
_sanity_check(block_number, block_count);
|
||||
_device->write(block_number, block_count, phys);
|
||||
}
|
||||
|
||||
void read(size_t, size_t, char *)
|
||||
{
|
||||
PERR("%s should not be called", __PRETTY_FUNCTION__);
|
||||
throw Io_error();
|
||||
}
|
||||
|
||||
void write(size_t, size_t, char const *)
|
||||
{
|
||||
PERR("%s should not be called", __PRETTY_FUNCTION__);
|
||||
throw Io_error();
|
||||
}
|
||||
|
||||
Ram_dataspace_capability alloc_dma_buffer(size_t size) {
|
||||
return _device->alloc_dma_buffer(size); }
|
||||
};
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("--- AHCI driver started ---\n");
|
||||
@ -792,7 +39,7 @@ int main()
|
||||
Genode::destroy(env()->heap(), static_cast<Ahci_driver *>(driver)); }
|
||||
} driver_factory;
|
||||
|
||||
enum { STACK_SIZE = 8129 };
|
||||
enum { STACK_SIZE = 8128 };
|
||||
static Cap_connection cap;
|
||||
static Rpc_entrypoint ep(&cap, STACK_SIZE, "block_ep");
|
||||
|
||||
@ -802,3 +49,4 @@ int main()
|
||||
sleep_forever();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
TARGET = ahci_drv
|
||||
REQUIRES = x86_32
|
||||
SRC_CC = main.cc
|
||||
LIBS = base
|
||||
|
||||
TARGET = ahci
|
||||
LIBS += ahci
|
||||
SRC_CC += empty.cc
|
||||
|
211
os/src/drivers/ahci/x86_32/ahci_device.h
Normal file
211
os/src/drivers/ahci/x86_32/ahci_device.h
Normal file
@ -0,0 +1,211 @@
|
||||
/*
|
||||
* \brief AHCI device
|
||||
* \author Sebastian Sumpf <Sebastian.Sumpf@genode-labs.com>
|
||||
* \author Martin Stein <Martin.Stein@genode-labs.com>
|
||||
* \date 2011-08-10
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2011-2013 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _AHCI_DEVICE_H_
|
||||
#define _AHCI_DEVICE_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <pci_session/connection.h>
|
||||
#include <pci_device/client.h>
|
||||
|
||||
/* local includes */
|
||||
#include <ahci_device_base.h>
|
||||
|
||||
/**
|
||||
* AHCI device
|
||||
*/
|
||||
class Ahci_device : public Ahci_device_base
|
||||
{
|
||||
private:
|
||||
|
||||
enum Pci_config {
|
||||
PCI_CFG_BMIBA_OFF = 0x24, /* offset in PCI config space */
|
||||
CLASS_MASS_STORAGE = 0x10000U,
|
||||
SUBCLASS_AHCI = 0x0600U,
|
||||
CLASS_MASK = 0xffff00U,
|
||||
AHCI_BASE_ID = 0x5, /* resource id of AHCI base addr <BAR 5> */
|
||||
AHCI_INTR_OFF = 0x3c, /* offset of interrupt information in config space */
|
||||
};
|
||||
|
||||
::Pci::Connection &_pci;
|
||||
::Pci::Device_client *_pci_device;
|
||||
::Pci::Device_capability _pci_device_cap;
|
||||
|
||||
/**
|
||||
* Return next PCI device
|
||||
*/
|
||||
static Pci::Device_capability _scan_pci(Pci::Connection &pci, Pci::Device_capability prev_device_cap)
|
||||
{
|
||||
Pci::Device_capability device_cap;
|
||||
device_cap = pci.next_device(prev_device_cap,
|
||||
CLASS_MASS_STORAGE | SUBCLASS_AHCI,
|
||||
CLASS_MASK);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
static void _disable_msi(::Pci::Device_client *pci)
|
||||
{
|
||||
enum { PM_CAP_OFF = 0x34, MSI_CAP = 0x5, MSI_ENABLED = 0x1 };
|
||||
uint8_t cap = pci->config_read(PM_CAP_OFF, ::Pci::Device::ACCESS_8BIT);
|
||||
|
||||
/* iterate through cap pointers */
|
||||
for (uint16_t val = 0; cap; cap = val >> 8) {
|
||||
val = pci->config_read(cap, ::Pci::Device::ACCESS_16BIT);
|
||||
|
||||
if ((val & 0xff) != MSI_CAP)
|
||||
continue;
|
||||
uint16_t msi = pci->config_read(cap + 2, ::Pci::Device::ACCESS_16BIT);
|
||||
|
||||
if (msi & MSI_ENABLED) {
|
||||
pci->config_write(cap + 2, msi ^ MSI_CAP, ::Pci::Device::ACCESS_8BIT);
|
||||
PINF("Disabled MSIs %x", msi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
/* 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));
|
||||
}
|
||||
|
||||
/* disable message signaled interrupts */
|
||||
_disable_msi(pci_device);
|
||||
|
||||
device->_irq = new(env()->heap()) Irq_connection(intr & 0xff);
|
||||
/* remember pci_device to be able to allocate ram memory which is dma able */
|
||||
device->_pci_device_cap = device_cap;
|
||||
device->_pci_device = pci_device;
|
||||
/* trigger assignment of pci device to the ahci driver */
|
||||
pci.config_extended(device_cap);
|
||||
|
||||
/* 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) {
|
||||
return _pci.alloc_dma_buffer(_pci_device_cap, size); }
|
||||
};
|
||||
|
||||
#endif /* _AHCI_DEVICE_H_ */
|
||||
|
49
os/src/drivers/ahci/x86_32/ahci_driver.h
Normal file
49
os/src/drivers/ahci/x86_32/ahci_driver.h
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* \brief AHCI driver
|
||||
* \author Sebastian Sumpf <Sebastian.Sumpf@genode-labs.com>
|
||||
* \author Martin Stein <Martin.Stein@genode-labs.com>
|
||||
* \date 2006-08-15
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2006-2013 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _AHCI_DRIVER_H_
|
||||
#define _AHCI_DRIVER_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <pci_session/connection.h>
|
||||
|
||||
/* local includes */
|
||||
#include <ahci_driver_base.h>
|
||||
|
||||
/**
|
||||
* Helper class for AHCI driver to ensure specific member-initialization order
|
||||
*/
|
||||
class Ahci_pci_connection {
|
||||
|
||||
protected:
|
||||
|
||||
Pci::Connection _pci;
|
||||
};
|
||||
|
||||
/**
|
||||
* AHCI driver
|
||||
*/
|
||||
class Ahci_driver : public Ahci_pci_connection,
|
||||
public Ahci_driver_base
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
Ahci_driver() : Ahci_driver_base(Ahci_device::probe(_pci)) { }
|
||||
};
|
||||
|
||||
#endif /* _AHCI_DRIVER_H_ */
|
||||
|
Loading…
x
Reference in New Issue
Block a user