579 lines
14 KiB
C++
579 lines
14 KiB
C++
/*
|
|
* \brief Generic base of AHCI device
|
|
* \author Sebastian Sumpf <Sebastian.Sumpf@genode-labs.com>
|
|
* \author Martin Stein <Martin.Stein@genode-labs.com>
|
|
* \date 2011-08-10
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2011-2013 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU General Public License version 2.
|
|
*/
|
|
|
|
#ifndef _AHCI_DEVICE_BASE_H_
|
|
#define _AHCI_DEVICE_BASE_H_
|
|
|
|
/* Genode includes */
|
|
#include <base/printf.h>
|
|
#include <base/sleep.h>
|
|
#include <block/driver.h>
|
|
#include <block/component.h>
|
|
#include <cap_session/connection.h>
|
|
#include <dataspace/client.h>
|
|
#include <irq_session/connection.h>
|
|
#include <io_mem_session/connection.h>
|
|
#include <timer_session/connection.h>
|
|
|
|
/**
|
|
* Enable for debugging output
|
|
*/
|
|
static const bool verbose = false;
|
|
|
|
using namespace Genode;
|
|
|
|
|
|
/**
|
|
* Base class for register access
|
|
*/
|
|
class Reg_base
|
|
{
|
|
protected:
|
|
|
|
uint32_t _value(uint32_t offset) { return *(volatile uint32_t *)(this + offset); }
|
|
void _set(uint32_t offset, uint32_t val) { *(volatile uint32_t *)(this + offset) = val; }
|
|
};
|
|
|
|
|
|
/*
|
|
* HBA: Generic Host Control
|
|
*/
|
|
class Generic_ctrl : public Reg_base
|
|
{
|
|
public:
|
|
|
|
/* host capabilities */
|
|
uint32_t hba_cap() { return _value(0x0); }
|
|
|
|
/* return port count from hba_cap */
|
|
uint32_t port_count() { return (hba_cap() & 0x1f) + 1; }
|
|
|
|
/* return command slot count from hba_cap */
|
|
uint32_t cmd_slots() { return ((hba_cap() >> 8) & 0x1f) + 1; }
|
|
|
|
/* global host control */
|
|
uint32_t hba_ctrl() { return _value(0x4); }
|
|
void hba_ctrl(uint32_t val) { _set(0x4, val); }
|
|
|
|
/* set Interrupt Enable (IE) in hba_ctrl */
|
|
void global_interrupt_enable()
|
|
{
|
|
hba_ctrl(hba_ctrl() | 2);
|
|
|
|
if (verbose)
|
|
PDBG("HBA %x", hba_ctrl());
|
|
}
|
|
|
|
/* set AHCI enable (AE) in hba_ctrl */
|
|
void global_enable_ahci()
|
|
{
|
|
if (!(hba_ctrl() & (1 << 31)))
|
|
hba_ctrl(hba_ctrl() | (1 << 31));
|
|
|
|
if (verbose)
|
|
PDBG("AHCI ENABLED: %x", hba_ctrl());
|
|
}
|
|
|
|
/* global interrupt status (contains port interrupts) */
|
|
uint32_t hba_intr_status() { return _value(0x8); }
|
|
void hba_intr_status(uint32_t val) { return _set(0x8, val); }
|
|
|
|
/* acknowledge global interrupt */
|
|
void hba_interrupt_ack() { hba_intr_status(hba_intr_status()); }
|
|
|
|
/* AHCI version */
|
|
uint32_t version() { return _value(0x10); }
|
|
};
|
|
|
|
|
|
/*
|
|
* AHCI port registers (one set per port)
|
|
*/
|
|
class Ahci_port : public Reg_base
|
|
{
|
|
public:
|
|
|
|
/* command list base */
|
|
void cmd_list_base(addr_t cmd_base)
|
|
{
|
|
uint64_t addr = cmd_base;
|
|
|
|
_set(0x0, addr);
|
|
_set(0x4, addr >> 32);
|
|
}
|
|
|
|
/* receive FIS base address */
|
|
void fis_base(addr_t fis_base)
|
|
{
|
|
uint64_t addr = fis_base;
|
|
|
|
_set(0x8, addr);
|
|
_set(0xc, addr >> 32);
|
|
}
|
|
|
|
/* interrupt status */
|
|
uint32_t intr_status() { return _value(0x10); }
|
|
void intr_status(uint32_t val) { _set(0x10, val); }
|
|
|
|
/* interrupt enable */
|
|
void intr_enable(uint32_t val) { _set(0x14, val); }
|
|
|
|
/* command */
|
|
uint32_t cmd() { return _value(0x18); }
|
|
void cmd(uint32_t val) { _set(0x18, val); }
|
|
|
|
/* task file data */
|
|
uint32_t tfd() { return _value(0x20); }
|
|
|
|
/* Serial ATA status */
|
|
uint32_t status() { return _value(0x28); }
|
|
|
|
/* Serial ATA control */
|
|
void sctl(uint32_t val) { _set(0x2c, val); }
|
|
uint32_t sctl() { return _value(0x2c); }
|
|
|
|
/* Serial ATA error */
|
|
void err(uint32_t val) { _set(0x30, val); }
|
|
uint32_t err() { return _value(0x30); }
|
|
|
|
/* command issue (1 bit per command slot) */
|
|
void cmd_issue(uint32_t val) { _set(0x38, val); }
|
|
uint32_t cmd_issue() { return _value(0x38); }
|
|
|
|
/**
|
|
* Check if device is active
|
|
*/
|
|
bool status_active()
|
|
{
|
|
enum {
|
|
PRESENT_ESTABLISHED = 0x3, /* device is present and connection is established */
|
|
PM_ACTIVE = 0x100, /* interface is in active power-mngmt. state */
|
|
PM_PARTIRL = 0x200, /* interface is in partial power-mngmt. state */
|
|
PM_SLUMBER = 0x600, /* interface is in slumber power-mngmt. state */
|
|
};
|
|
|
|
uint32_t stat = status();
|
|
uint32_t pm_stat = stat & 0xf00;
|
|
|
|
/* if controller is in sleep state, try to wake up */
|
|
if (pm_stat == PM_PARTIRL || pm_stat == PM_SLUMBER) {
|
|
|
|
if (verbose)
|
|
PDBG("Controller is in sleep state, trying to wake up ...");
|
|
|
|
cmd(cmd() | (1 << 28));
|
|
|
|
while (!(stat & PM_ACTIVE) || (stat & 0xf) != PRESENT_ESTABLISHED) { stat = status(); }
|
|
}
|
|
return (((stat & 0xf) == PRESENT_ESTABLISHED) && (stat & PM_ACTIVE));
|
|
}
|
|
|
|
/**
|
|
* Enable CMD.ST to start command list processing
|
|
*/
|
|
void hba_enable()
|
|
{
|
|
enum {
|
|
STS_BSY = 0x80, /* device is busy */
|
|
STS_DRQ = 0x08, /* data transfer requested */
|
|
};
|
|
|
|
while (tfd() & (STS_BSY | STS_DRQ))
|
|
if (verbose)
|
|
PDBG("TFD %x", tfd());
|
|
|
|
cmd(cmd() | 1);
|
|
}
|
|
|
|
/**
|
|
* Disable CMD.ST
|
|
*/
|
|
void hba_disable()
|
|
{
|
|
if ((cmd() & 1) && !(cmd_issue() & 1))
|
|
cmd(cmd() & ~1);
|
|
}
|
|
|
|
/**
|
|
* Enable port interrupts
|
|
*/
|
|
void interrupt_enable() { intr_enable(~0U); }
|
|
|
|
/**
|
|
* Acknowledge port interrupts
|
|
*/
|
|
uint32_t interrupt_ack()
|
|
{
|
|
interrupt_pm_ack();
|
|
|
|
uint32_t status = intr_status();
|
|
intr_status(status);
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Check and handle interrupts due to power mgmt. state transitions
|
|
*/
|
|
void interrupt_pm_ack()
|
|
{
|
|
enum {
|
|
INT_PORT_CON_STATUS = 0x40,
|
|
INT_PHY_RDY_STATUS = 0x400000,
|
|
};
|
|
uint32_t status = intr_status();
|
|
|
|
if (status & INT_PORT_CON_STATUS)
|
|
|
|
/* clear DIAG.x */
|
|
err(err() & ~(1 << 26));
|
|
|
|
if (status & INT_PORT_CON_STATUS)
|
|
|
|
/* clear DIAG.n */
|
|
err(err() & ~(1 << 16));
|
|
}
|
|
|
|
/**
|
|
* Disable power mgmt. set SCTL.IPM to 3
|
|
*/
|
|
void disable_pm() { sctl(sctl() | (3 << 8)); }
|
|
|
|
/**
|
|
* Power up device
|
|
*/
|
|
void get_ready()
|
|
{
|
|
enum {
|
|
SPIN_UP_DEVICE = 0x2,
|
|
POWER_ON_DEVICE = 0x4,
|
|
FIS_RECV_ENABLE = 0x10,
|
|
ENABLE = SPIN_UP_DEVICE | POWER_ON_DEVICE | FIS_RECV_ENABLE
|
|
};
|
|
cmd(cmd() | ENABLE);
|
|
}
|
|
|
|
/**
|
|
* Reset this port
|
|
*/
|
|
void reset()
|
|
{
|
|
/* check for ST bit in command register */
|
|
if (cmd() & 1)
|
|
PWRN("CMD.ST bit set during device reset --> unknown behavior");
|
|
|
|
/* set device initialization bit for at least 1ms */
|
|
sctl((sctl() & ~0xf) | 1);
|
|
|
|
Timer::Connection timer;
|
|
timer.msleep(1);
|
|
|
|
sctl(sctl() & ~0xf);
|
|
|
|
while ((status() & 0xf) != 0x3) {}
|
|
}
|
|
|
|
/**
|
|
* Return the size of this structure
|
|
*/
|
|
static uint32_t size() { return 0x80; }
|
|
};
|
|
|
|
|
|
/**
|
|
* AHCI command list structure
|
|
*/
|
|
struct Command_list
|
|
{
|
|
uint8_t cfl:5; /* Command FIS length */
|
|
uint8_t a:1; /* ATAPI command flag */
|
|
uint8_t w:1; /* Write flag */
|
|
uint8_t p:1; /* Prefetchable flag */
|
|
uint8_t unsused; /* we don't use byte 2 yet */
|
|
uint16_t prdtl; /* Physical region descr. length */
|
|
uint32_t prdbc; /* PRD byte count */
|
|
uint32_t cmd_table_base_l; /* Command table base addr (low) */
|
|
uint32_t cmd_table_base_u;
|
|
uint32_t reserved[0];
|
|
};
|
|
|
|
|
|
/**
|
|
* AHCI command table structure
|
|
*/
|
|
struct Command_table
|
|
{
|
|
/**
|
|
* Setup FIS and PRD
|
|
*/
|
|
void setup_command(uint8_t cmd, uint64_t lba48, uint16_t blk_cnt, addr_t phys_addr)
|
|
{
|
|
enum { MAX_BYTES = 1 << 22 }; /* 4MB = one PRD */
|
|
uint8_t *fis = (uint8_t *)this;
|
|
|
|
uint64_t addr = phys_addr;
|
|
|
|
/* setup FIS */
|
|
fis[0] = 0x27; /* type = host to device */
|
|
fis[1] = 0x80; /* set update command flag */
|
|
fis[2] = cmd; /* actual command */
|
|
fis[4] = lba48 & 0xff; /* LBA 0 - 7 */
|
|
fis[5] = (lba48 >> 8) & 0xff; /* LBA 8 - 15 */
|
|
fis[6] = (lba48 >> 16) & 0xff; /* LBA 16 - 23 */
|
|
fis[7] = 0x40; /* LBA mode flag */
|
|
fis[8] = (lba48 >> 24) & 0xff; /* LBA 24 - 31 */
|
|
fis[9] = (lba48 >> 32) & 0xff; /* LBA 32 - 39 */
|
|
fis[10] = (lba48 >> 40) & 0xff; /* LBA 40 - 47 */
|
|
fis[12] = blk_cnt & 0xff; /* sector count 0 - 7 */
|
|
fis[13] = (blk_cnt >> 8) & 0xff; /* sector count 8 - 15 */
|
|
|
|
/* setup PRD for DMA */
|
|
uint32_t addr_l = addr;
|
|
uint32_t addr_u = addr >> 32;
|
|
memcpy(&fis[0x80], &addr_l, 4); /* DBA: data base address */
|
|
memcpy(&fis[0x84], &addr_u, 4); /* DBA: data base address upper */
|
|
uint32_t bytes = (blk_cnt * 512) - 1;
|
|
|
|
if (bytes + 1 > MAX_BYTES) {
|
|
PERR("Unsupported request size %u > %u", bytes, MAX_BYTES);
|
|
throw Block::Driver::Io_error();
|
|
}
|
|
|
|
/* set byte count for PRD 22 bit */
|
|
fis[0x8c] = bytes & 0xff;
|
|
fis[0x8d] = (bytes >> 8) & 0xff;
|
|
fis[0x8e] = (bytes >> 16) & 0x3f;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Generic base of AHCI device
|
|
*/
|
|
class Ahci_device_base
|
|
{
|
|
protected:
|
|
|
|
enum {
|
|
AHCI_PORT_BASE = 0x100,
|
|
};
|
|
|
|
Generic_ctrl *_ctrl; /* generic host control */
|
|
Ahci_port *_port; /* port base of device */
|
|
Irq_connection *_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_ */
|
|
|