mirror of
https://github.com/genodelabs/genode.git
synced 2025-01-29 07:33:57 +00:00
b37f411c3f
Issue #2417
876 lines
20 KiB
C++
876 lines
20 KiB
C++
/**
|
|
* \brief Generic AHCI controller definitions
|
|
* \author Sebasitan Sumpf
|
|
* \date 2015-04-29
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2015-2017 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU Affero General Public License version 3.
|
|
*/
|
|
|
|
#ifndef _INCLUDE__AHCI_H_
|
|
#define _INCLUDE__AHCI_H_
|
|
|
|
#include <block/component.h>
|
|
#include <os/attached_mmio.h>
|
|
#include <os/reporter.h>
|
|
#include <util/retry.h>
|
|
#include <util/reconstructible.h>
|
|
|
|
static bool constexpr verbose = false;
|
|
|
|
namespace Platform {
|
|
struct Hba;
|
|
Hba &init(Genode::Env &env, Genode::Mmio::Delayer &delayer);
|
|
};
|
|
|
|
|
|
struct Ahci_root
|
|
{
|
|
virtual Genode::Entrypoint &entrypoint() = 0;
|
|
virtual void announce() = 0;
|
|
};
|
|
|
|
|
|
namespace Ahci_driver {
|
|
|
|
void init(Genode::Env &env, Genode::Allocator &alloc, Ahci_root &ep,
|
|
bool support_atapi, Genode::Signal_context_capability device_identified);
|
|
|
|
bool avail(long device_num);
|
|
long device_number(char const *model_num, char const *serial_num);
|
|
|
|
Block::Driver *claim_port(long device_num);
|
|
void free_port(long device_num);
|
|
void report_ports(Genode::Reporter &reporter);
|
|
|
|
struct Missing_controller { };
|
|
}
|
|
|
|
|
|
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
|
|
{
|
|
Mmio::Delayer &_delayer;
|
|
|
|
Hba(Genode::Env &env, Platform::Hba &hba, Mmio::Delayer &delayer)
|
|
: Attached_mmio(env, hba.base(), hba.size()), _delayer(delayer) { }
|
|
|
|
/**
|
|
* 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 Sncq : Bitfield<30, 1> { }; /* supports native command queuing */
|
|
struct Sa64 : Bitfield<31, 1> { }; /* supports 64 bit addressing */
|
|
};
|
|
|
|
unsigned port_count() { return read<Cap::Np>() + 1; }
|
|
unsigned command_slots() { return read<Cap::Ncs>() + 1; }
|
|
bool ncq() { return !!read<Cap::Sncq>(); }
|
|
bool supports_64bit(){ return !!read<Cap::Sa64>(); }
|
|
|
|
/**
|
|
* Generic host control
|
|
*/
|
|
struct Ghc : Register<0x4, 32>
|
|
{
|
|
struct Hr : Bitfield<0, 1> { }; /* hard reset */
|
|
struct Ie : Bitfield<1, 1> { }; /* interrupt enable */
|
|
struct Ae : Bitfield<31, 1> { }; /* AHCI enable */
|
|
};
|
|
|
|
/**
|
|
* Interrupt status
|
|
*/
|
|
struct Is : Register<0x8, 32> { };
|
|
|
|
void ack_irq() { write<Is>(read<Is>()); }
|
|
|
|
/**
|
|
* Ports implemented
|
|
*/
|
|
struct Pi : Register<0xc, 32> { };
|
|
|
|
/**
|
|
* AHCI version
|
|
*/
|
|
struct Version : Register<0x10, 32>
|
|
{
|
|
struct Minor : Bitfield<0, 16> { };
|
|
struct Major : Bitfield<16, 16> { };
|
|
};
|
|
|
|
struct Cap2 : Register<0x24, 32> { };
|
|
|
|
void init()
|
|
{
|
|
/* enable AHCI */
|
|
write<Ghc::Ae>(1);
|
|
|
|
/* enable interrupts */
|
|
write<Ghc::Ie>(1);
|
|
}
|
|
|
|
Mmio::Delayer &delayer() { return _delayer; }
|
|
};
|
|
|
|
|
|
struct Device_fis : Genode::Mmio
|
|
{
|
|
struct Status : Register<0x2, 8>
|
|
{
|
|
/* ATAPI bits */
|
|
struct Device_ready : Bitfield<6, 1> { };
|
|
};
|
|
struct Error : Register<0x3, 8> { };
|
|
|
|
Device_fis(Genode::addr_t recv_base)
|
|
: Mmio(recv_base + 0x40) { }
|
|
};
|
|
|
|
|
|
struct Command_fis : Genode::Mmio
|
|
{
|
|
struct Type : Register<0x0, 8> { }; /* FIS type */
|
|
struct Bits : Register<0x1, 8, 1>
|
|
{
|
|
struct C : Bitfield<7, 1> { }; /* update of command register */
|
|
};
|
|
struct Command : Register<0x2, 8, 1> { }; /* ATA command */
|
|
struct Features0_7 : Register<0x3, 8> { };
|
|
|
|
/* big-endian use byte access */
|
|
struct Lba0_7 : Register<0x4, 8> { };
|
|
struct Lba8_15 : Register<0x5, 8> { };
|
|
struct Lba16_23 : Register<0x6, 8> { };
|
|
struct Lba24 : Genode::Bitset_3<Lba0_7, Lba8_15, Lba16_23> { };
|
|
struct Device : Register<0x7, 8>
|
|
{
|
|
struct Lba : Bitfield<6, 1> { }; /* enable LBA mode */
|
|
};
|
|
|
|
/* big endian */
|
|
struct Lba24_31 : Register<0x8, 8> { };
|
|
struct Lba32_39 : Register<0x9, 8> { };
|
|
struct Lba40_47 : Register<0xa, 8> { };
|
|
struct Features8_15 : Register<0xb, 8> { };
|
|
struct Features : Genode::Bitset_2<Features0_7, Features8_15> { };
|
|
struct Lba48 : Genode::Bitset_3<Lba24_31, Lba32_39, Lba40_47> { };
|
|
struct Lba : Genode::Bitset_2<Lba24, Lba48> { }; /* LBA 0 - 47 */
|
|
|
|
/* big endian */
|
|
struct Sector0_7 : Register<0xc, 8, 1>
|
|
{
|
|
struct Tag : Bitfield<3, 5> { };
|
|
};
|
|
struct Sector8_15 : Register<0xd, 8> { };
|
|
struct Sector : Genode::Bitset_2<Sector0_7, Sector8_15> { }; /* sector count */
|
|
|
|
Command_fis(Genode::addr_t base)
|
|
: Mmio(base)
|
|
{
|
|
clear();
|
|
|
|
enum { HOST_TO_DEVICE = 0x27 };
|
|
write<Type>(HOST_TO_DEVICE);
|
|
}
|
|
|
|
static constexpr Genode::size_t size() { return 0x14; }
|
|
void clear() { Genode::memset((void *)base(), 0, size()); }
|
|
|
|
|
|
/************************
|
|
** ATA spec commands **
|
|
************************/
|
|
|
|
void identify_device()
|
|
{
|
|
write<Bits::C>(1);
|
|
write<Device::Lba>(0);
|
|
write<Command>(0xec);
|
|
}
|
|
|
|
void dma_ext(bool read, Block::sector_t block_number, Genode::size_t block_count)
|
|
{
|
|
write<Bits::C>(1);
|
|
write<Device::Lba>(1);
|
|
/* read_dma_ext : write_dma_ext */
|
|
write<Command>(read ? 0x25 : 0x35);
|
|
write<Lba>(block_number);
|
|
write<Sector>(block_count);
|
|
}
|
|
|
|
void fpdma(bool read, Block::sector_t block_number, Genode::size_t block_count,
|
|
unsigned slot)
|
|
{
|
|
write<Bits::C>(1);
|
|
write<Device::Lba>(1);
|
|
/* read_fpdma : write_fpdma */
|
|
write<Command>(read ? 0x60 : 0x61);
|
|
write<Lba>(block_number);
|
|
write<Features>(block_count);
|
|
write<Sector0_7::Tag>(slot);
|
|
}
|
|
|
|
void atapi()
|
|
{
|
|
write<Bits::C>(1);
|
|
/* packet command */
|
|
write<Command>(0xa0);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* AHCI command list structure header
|
|
*/
|
|
struct Command_header : Genode::Mmio
|
|
{
|
|
struct Bits : Register<0x0, 16>
|
|
{
|
|
struct Cfl : Bitfield<0, 5> { }; /* command FIS length */
|
|
struct A : Bitfield<5, 1> { }; /* ATAPI command flag */
|
|
struct W : Bitfield<6, 1> { }; /* write flag */
|
|
struct P : Bitfield<7, 1> { }; /* prefetchable flag */
|
|
struct C : Bitfield<10, 1> { }; /* clear busy upon R_OK */
|
|
};
|
|
|
|
struct Prdtl : Register<0x2, 16> { }; /* physical region descr. length */
|
|
struct Prdbc : Register<0x4, 32> { }; /* PRD byte count */
|
|
struct Ctba0 : Register<0x8, 32> { }; /* command table base addr (low) */
|
|
struct Ctba0_u0 : Register<0xc, 32> { }; /* command table base addr (upper) */
|
|
|
|
Command_header(Genode::addr_t base) : Mmio(base) { }
|
|
|
|
void cmd_table_base(Genode::addr_t base_phys)
|
|
{
|
|
Genode::uint64_t addr = base_phys;
|
|
write<Ctba0>(addr);
|
|
write<Ctba0_u0>(addr >> 32);
|
|
write<Prdtl>(1);
|
|
write<Bits::Cfl>(Command_fis::size() / sizeof(unsigned));
|
|
}
|
|
|
|
void clear_byte_count()
|
|
{
|
|
write<Prdbc>(0);
|
|
}
|
|
|
|
void atapi_command()
|
|
{
|
|
write<Bits::A>(1);
|
|
}
|
|
|
|
static constexpr Genode::size_t size() { return 0x20; }
|
|
};
|
|
|
|
|
|
/**
|
|
* ATAPI packet 12 or 16 bytes
|
|
*/
|
|
struct Atapi_command : Genode::Mmio
|
|
{
|
|
struct Command : Register<0, 8> { };
|
|
|
|
/* LBA32 big endian */
|
|
struct Lba24_31 : Register<0x2, 8> { };
|
|
struct Lba16_23 : Register<0x3, 8> { };
|
|
struct Lba8_15 : Register<0x4, 8> { };
|
|
struct Lba0_7 : Register<0x5, 8> { };
|
|
struct Lba24 : Genode::Bitset_3<Lba0_7, Lba8_15, Lba16_23> { };
|
|
struct Lba : Genode::Bitset_2<Lba24, Lba24_31> { };
|
|
|
|
/* sector count big endian */
|
|
struct Sector8_15 : Register<0x8, 8> { };
|
|
struct Sector0_7 : Register<0x9, 8> { };
|
|
struct Sector : Genode::Bitset_2<Sector0_7, Sector8_15> { };
|
|
|
|
|
|
Atapi_command(Genode::addr_t base) : Mmio(base)
|
|
{
|
|
Genode::memset((void *)base, 0, 16);
|
|
}
|
|
|
|
void read_capacity()
|
|
{
|
|
write<Command>(0x25);
|
|
}
|
|
|
|
void test_unit_ready()
|
|
{
|
|
write<Command>(0x0);
|
|
}
|
|
|
|
void read_sense()
|
|
{
|
|
write<Command>(0x3);
|
|
write<Lba8_15>(18);
|
|
}
|
|
|
|
void read10(Block::sector_t block_number, Genode::size_t block_count)
|
|
{
|
|
write<Command>(0x28);
|
|
write<Lba>(block_number);
|
|
write<Sector>(block_count);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Physical region descritpor table
|
|
*/
|
|
struct Prdt : Genode::Mmio
|
|
{
|
|
struct Dba : Register<0x0, 32> { }; /* data base address */
|
|
struct Dbau : Register<0x4, 32> { }; /* data base address upper 32 bits */
|
|
|
|
struct Bits : Register<0xc, 32>
|
|
{
|
|
struct Dbc : Bitfield<0, 22> { }; /* data byte count */
|
|
struct Irq : Bitfield<31,1> { }; /* interrupt completion */
|
|
};
|
|
|
|
Prdt(Genode::addr_t base, Genode::addr_t phys, Genode::size_t bytes)
|
|
: Mmio(base)
|
|
{
|
|
Genode::uint64_t addr = phys;
|
|
write<Dba>(addr);
|
|
write<Dbau>(addr >> 32);
|
|
write<Bits::Dbc>(bytes > 0 ? bytes - 1 : 0);
|
|
}
|
|
|
|
static constexpr Genode::size_t size() { return 0x10; }
|
|
};
|
|
|
|
|
|
struct Command_table
|
|
{
|
|
Command_fis fis;
|
|
Atapi_command atapi_cmd;
|
|
|
|
/* in Genode we only need one PRD (for one packet) */
|
|
Prdt prdt;
|
|
|
|
Command_table(Genode::addr_t base,
|
|
Genode::addr_t phys,
|
|
Genode::size_t bytes = 0)
|
|
: fis(base), atapi_cmd(base + 0x40),
|
|
prdt(base + 0x80, phys, bytes)
|
|
{ }
|
|
|
|
static constexpr Genode::size_t size() { return 0x100; }
|
|
};
|
|
|
|
|
|
/**
|
|
* Minimalistic AHCI port structure to merely detect device signature
|
|
*/
|
|
struct Port_base : Genode::Mmio
|
|
{
|
|
/**
|
|
* Port signature
|
|
*/
|
|
struct Sig : Register<0x24, 32> { };
|
|
|
|
static constexpr Genode::addr_t offset() { return 0x100; }
|
|
static constexpr Genode::size_t size() { return 0x80; }
|
|
|
|
Port_base(unsigned number, Hba &hba)
|
|
: Mmio(hba.base() + offset() + (number * size())) { }
|
|
};
|
|
|
|
|
|
/**
|
|
* AHCI port
|
|
*/
|
|
struct Port : Port_base
|
|
{
|
|
struct Not_ready : Genode::Exception { };
|
|
|
|
Genode::Region_map &rm;
|
|
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(Genode::Region_map &rm, Hba &hba, Platform::Hba &platform_hba,
|
|
unsigned number)
|
|
:
|
|
Port_base(number, hba), rm(rm), hba(hba),
|
|
platform_hba(platform_hba)
|
|
{
|
|
reset();
|
|
if (!enable())
|
|
throw 1;
|
|
stop();
|
|
wait_for(hba.delayer(), Cmd::Cr::Equal(0));
|
|
}
|
|
|
|
virtual ~Port()
|
|
{
|
|
if (device_ds.valid()) {
|
|
rm.detach((void *)cmd_list);
|
|
platform_hba.free_dma_buffer(device_ds);
|
|
}
|
|
|
|
if (cmd_ds.valid()) {
|
|
rm.detach((void *)cmd_table);
|
|
platform_hba.free_dma_buffer(cmd_ds);
|
|
}
|
|
|
|
if (device_info_ds.valid()) {
|
|
rm.detach((void*)device_info);
|
|
platform_hba.free_dma_buffer(device_info_ds);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Command list base (1K length naturally aligned)
|
|
*/
|
|
struct Clb : Register<0x0, 32> { };
|
|
|
|
/**
|
|
* Command list base upper 32 bit
|
|
*/
|
|
struct Clbu : Register<0x4, 32> { };
|
|
|
|
void command_list_base(Genode::addr_t phys)
|
|
{
|
|
Genode::uint64_t addr = phys;
|
|
write<Clb>(addr);
|
|
write<Clbu>(addr >> 32);
|
|
}
|
|
|
|
/**
|
|
* FIS base address (256 bytes naturally aligned)
|
|
*/
|
|
struct Fb : Register<0x8, 32> { };
|
|
|
|
/**
|
|
* FIS base address upper 32 bit
|
|
*/
|
|
struct Fbu : Register<0xc, 32> { };
|
|
|
|
void fis_rcv_base(Genode::addr_t phys)
|
|
{
|
|
Genode::uint64_t addr = phys;
|
|
write<Fb>(addr);
|
|
write<Fbu>(addr >> 32);
|
|
}
|
|
|
|
/**
|
|
* Port interrupt status
|
|
*/
|
|
struct Is : Register<0x10, 32, 1>
|
|
{
|
|
struct Dhrs : Bitfield<0, 1> { }; /* device to host register FIS */
|
|
struct Pss : Bitfield<1, 1> { }; /* PIO setup FIS */
|
|
struct Dss : Bitfield<2, 1> { }; /* DMA setup FIS */
|
|
struct Sdbs : Bitfield<3, 1> { }; /* Set device bit */
|
|
struct Pcs : Bitfield<6, 1> { }; /* port connect change status */
|
|
struct Prcs : Bitfield<22, 1> { }; /* PhyRdy change status */
|
|
struct Infs : Bitfield<26, 1> { }; /* interface non-fatal error */
|
|
struct Ifs : Bitfield<27, 1> { }; /* interface fatal error */
|
|
|
|
/* ncq irq */
|
|
struct Fpdma_irq : Sdbs { };
|
|
|
|
/* non-ncq irq */
|
|
struct Dma_ext_irq : Bitfield<0, 3> { };
|
|
};
|
|
|
|
void ack_irq()
|
|
{
|
|
Is::access_t status = read <Is>();
|
|
|
|
/* clear Serr.Diag.x */
|
|
if (Is::Pcs::get(status))
|
|
write<Serr::Diag::X>(0);
|
|
|
|
/* clear Serr.Diag.n */
|
|
if (Is::Prcs::get(status))
|
|
write<Serr::Diag::N>(0);
|
|
|
|
write<Is>(read<Is>());
|
|
};
|
|
|
|
/**
|
|
* Port interrupt enable
|
|
*/
|
|
struct Ie : Register<0x14, 32, 1>
|
|
{
|
|
struct Dhre : Bitfield<0, 1> { }; /* device to host register FIS interrupt */
|
|
struct Pse : Bitfield<1, 1> { }; /* PIO setup FIS interrupt */
|
|
struct Dse : Bitfield<2, 1> { }; /* DMA setup FIS interrupt */
|
|
struct Sdbe : Bitfield<3, 1> { }; /* set device bits FIS interrupt (ncq) */
|
|
struct Ufe : Bitfield<4, 1> { }; /* unknown FIS */
|
|
struct Dpe : Bitfield<5, 1> { }; /* descriptor processed */
|
|
struct Ifne : Bitfield<26, 1> { }; /* interface non-fatal error */
|
|
struct Ife : Bitfield<27, 1> { }; /* interface fatal error */
|
|
struct Hbde : Bitfield<28, 1> { }; /* host bus data error */
|
|
struct Hbfe : Bitfield<29, 1> { }; /* host bus fatal error */
|
|
struct Tfee : Bitfield<30, 1> { }; /* task file error */
|
|
};
|
|
|
|
void interrupt_enable()
|
|
{
|
|
Ie::access_t ie = 0;
|
|
Ie::Dhre::set(ie, 1);
|
|
Ie::Pse::set(ie, 1);
|
|
Ie::Dse::set(ie, 1);
|
|
Ie::Sdbe::set(ie, 1);
|
|
Ie::Ufe::set(ie, 1);
|
|
Ie::Dpe::set(ie, 1);
|
|
Ie::Ifne::set(ie, 1);
|
|
Ie::Hbde::set(ie, 1);
|
|
Ie::Hbfe::set(ie, 1);
|
|
Ie::Tfee::set(ie, 1);
|
|
write<Ie>(~0U);
|
|
}
|
|
|
|
/**
|
|
* Port command
|
|
*/
|
|
struct Cmd : Register<0x18, 32>
|
|
{
|
|
struct St : Bitfield<0, 1> { }; /* start */
|
|
struct Sud : Bitfield<1, 1> { }; /* spin-up device */
|
|
struct Pod : Bitfield<2, 1> { }; /* power-up device */
|
|
struct Fre : Bitfield<4, 1> { }; /* FIS receive enable */
|
|
struct Cr : Bitfield<15, 1> { }; /* command list running */
|
|
struct Atapi : Bitfield<24, 1> { };
|
|
struct Icc : Bitfield<28, 4> { }; /* interface communication control */
|
|
};
|
|
|
|
void start()
|
|
{
|
|
if (read<Cmd::St>())
|
|
return;
|
|
|
|
try {
|
|
wait_for(hba.delayer(), Tfd::Sts_bsy::Equal(0));
|
|
} catch (Polling_timeout) {
|
|
Genode::error("HBA busy unable to start command processing.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
wait_for(hba.delayer(), Tfd::Sts_drq::Equal(0));
|
|
} catch (Polling_timeout) {
|
|
Genode::error("HBA in DRQ unable to start command processing.");
|
|
return;
|
|
}
|
|
|
|
write<Cmd::St>(1);
|
|
}
|
|
|
|
void stop()
|
|
{
|
|
if (!(read<Ci>() | read<Sact>()))
|
|
write<Cmd::St>(0);
|
|
}
|
|
|
|
void power_up()
|
|
{
|
|
Cmd::access_t cmd = read<Cmd>();
|
|
Cmd::Sud::set(cmd, 1);
|
|
Cmd::Pod::set(cmd, 1);
|
|
Cmd::Fre::set(cmd, 1);
|
|
write<Cmd>(cmd);
|
|
}
|
|
|
|
/**
|
|
* Task file data
|
|
*/
|
|
struct Tfd : Register<0x20, 32>
|
|
{
|
|
struct Sts_drq : Bitfield<3, 1> { }; /* indicates data transfer request */
|
|
struct Sts_bsy : Bitfield<7, 1> { }; /* interface is busy */
|
|
};
|
|
|
|
/**
|
|
* Serial ATA status
|
|
*/
|
|
struct Ssts : Register<0x28, 32>
|
|
{
|
|
struct Dec : Bitfield<0, 4> /* device detection */
|
|
{
|
|
enum {
|
|
NONE = 0x0,
|
|
ESTABLISHED = 0x3
|
|
};
|
|
};
|
|
struct Ipm : Bitfield<8, 4> /* interface power mgmt */
|
|
{
|
|
enum {
|
|
ACTIVE = 0x1,
|
|
SUSPEND = 0x2,
|
|
};
|
|
};
|
|
};
|
|
|
|
bool enable()
|
|
{
|
|
Ssts::access_t status = read<Ssts>();
|
|
if (Ssts::Dec::get(status) == Ssts::Dec::NONE)
|
|
return false;
|
|
|
|
/* if in power-mgmt state (partial or slumber) */
|
|
if (Ssts::Ipm::get(status) & Ssts::Ipm::SUSPEND) {
|
|
/* try to wake up device */
|
|
write<Cmd::Icc>(Ssts::Ipm::ACTIVE);
|
|
|
|
Genode::retry<Not_ready>(
|
|
[&] {
|
|
if ((Ssts::Dec::get(status) != Ssts::Dec::ESTABLISHED) ||
|
|
!(Ssts::Ipm::get(status) & Ssts::Ipm::ACTIVE))
|
|
throw Not_ready();
|
|
},
|
|
[&] {
|
|
hba.delayer().usleep(1000);
|
|
status = read<Ssts>();
|
|
}, 10);
|
|
}
|
|
|
|
return ((Ssts::Dec::get(status) == Ssts::Dec::ESTABLISHED) &&
|
|
(Ssts::Ipm::get(status) & Ssts::Ipm::ACTIVE));
|
|
}
|
|
|
|
/**
|
|
* Serial ATA control
|
|
*/
|
|
struct Sctl : Register<0x2c, 32>
|
|
{
|
|
struct Det : Bitfield<0, 4> { }; /* device dectection initialization */
|
|
struct Ipmt : Bitfield<8, 4> { }; /* allowed power management transitions */
|
|
};
|
|
|
|
void reset()
|
|
{
|
|
if (read<Cmd::St>())
|
|
Genode::warning("CMD.ST bit set during device reset --> unknown behavior");
|
|
|
|
write<Sctl::Det>(1);
|
|
hba.delayer().usleep(1000);
|
|
write<Sctl::Det>(0);
|
|
|
|
try {
|
|
wait_for(hba.delayer(), Ssts::Dec::Equal(Ssts::Dec::ESTABLISHED));
|
|
} catch (Polling_timeout) {
|
|
Genode::warning("Port reset failed");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Serial ATA error
|
|
*/
|
|
struct Serr : Register<0x30, 32>
|
|
{
|
|
struct Diag : Register<0x2, 16>
|
|
{
|
|
struct N : Bitfield<0, 1> { }; /* PhyRdy change */
|
|
struct X : Bitfield<10, 1> { }; /* exchanged */
|
|
};
|
|
};
|
|
|
|
void clear_serr() { write<Serr>(read<Serr>()); }
|
|
|
|
/**
|
|
* Serial ATA active
|
|
*/
|
|
struct Sact : Register<0x34, 32> { };
|
|
|
|
/**
|
|
* Command issue
|
|
*/
|
|
struct Ci : Register<0x38, 32> { };
|
|
|
|
void init()
|
|
{
|
|
/* stop command list processing */
|
|
stop();
|
|
|
|
/* setup command list/table */
|
|
setup_memory();
|
|
|
|
/* disallow all power-management transitions */
|
|
write<Sctl::Ipmt>(0x3);
|
|
|
|
/* power-up device */
|
|
power_up();
|
|
|
|
/* reset port */
|
|
reset();
|
|
|
|
/* clean error register */
|
|
clear_serr();
|
|
|
|
/* enable required interrupts */
|
|
interrupt_enable();
|
|
|
|
/* acknowledge all pending interrupts */
|
|
ack_irq();
|
|
hba.ack_irq();
|
|
}
|
|
|
|
void setup_memory()
|
|
{
|
|
using Genode::addr_t;
|
|
using Genode::size_t;
|
|
|
|
device_ds = platform_hba.alloc_dma_buffer(0x1000);
|
|
|
|
/* command list 1K */
|
|
addr_t phys = Genode::Dataspace_client(device_ds).phys_addr();
|
|
cmd_list = (addr_t)rm.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)rm.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 = rm.attach(device_info_ds);
|
|
}
|
|
|
|
Genode::addr_t command_table_addr(unsigned slot)
|
|
{
|
|
return cmd_table + (slot * Command_table::size());
|
|
};
|
|
|
|
Genode::addr_t command_header_addr(unsigned slot)
|
|
{
|
|
return cmd_list + (slot * Command_header::size());
|
|
}
|
|
|
|
void execute(unsigned slot)
|
|
{
|
|
start();
|
|
write<Ci>(1U << slot);
|
|
}
|
|
|
|
bool ready() { return state == READY; }
|
|
};
|
|
|
|
|
|
struct Port_driver : Port, Block::Driver
|
|
{
|
|
Ahci_root &root;
|
|
unsigned &sem;
|
|
|
|
Port_driver(Genode::Ram_session &ram,
|
|
Ahci_root &root,
|
|
unsigned &sem,
|
|
Genode::Region_map &rm,
|
|
Hba &hba,
|
|
Platform::Hba &platform_hba,
|
|
unsigned number)
|
|
: Port(rm, hba, platform_hba, number), Block::Driver(ram), root(root),
|
|
sem(sem) { sem++; }
|
|
|
|
virtual void handle_irq() = 0;
|
|
|
|
void state_change()
|
|
{
|
|
if (--sem) return;
|
|
|
|
/* announce service */
|
|
root.announce();
|
|
}
|
|
|
|
void sanity_check(Block::sector_t block_number, Genode::size_t count)
|
|
{
|
|
/* max. PRDT size is 4MB */
|
|
if (count * block_size() > 4 * 1024 * 1024) {
|
|
Genode::error("error: maximum supported packet size is 4MB");
|
|
throw Io_error();
|
|
}
|
|
|
|
/* sanity check */
|
|
if (block_number + count > block_count()) {
|
|
Genode::error("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_ */
|