/** * * \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 _AHCI__AHCI_H_ #define _AHCI__AHCI_H_ #include #include #include #include #include #include static bool constexpr verbose = false; namespace Ahci { struct Missing_controller : Exception { }; class Platform; struct Protocol; struct Port; struct Port_base; struct Hba; using Response = Block::Request_stream::Response; using block_number_t = Block::block_number_t; using block_count_t = Block::block_count_t; } class Ahci::Platform { private : Data _data; protected: /** * Return base address and size of HBA device registers */ addr_t _mmio_base() const; public: Platform(Env &env) : _data(env) { }; /** * Register interrupt signal context */ void sigh_irq(Signal_context_capability sigh); void ack_irq(); /** * DMA */ Ram_dataspace_capability alloc_dma_buffer(size_t size); void free_dma_buffer(Ram_dataspace_capability ds); }; /** * HBA definitions */ struct Ahci::Hba : Ahci::Platform, Mmio { Mmio::Delayer &_delayer; Hba(Env &env, Mmio::Delayer &delayer) : Platform(env), Mmio(_mmio_base()), _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() + 1; } unsigned command_slots() { return read() + 1; } bool ncq() { 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()); Platform::ack_irq(); } /** * 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); } Mmio::Delayer &delayer() { return _delayer; } }; /*********************************** ** AHCI commands and descriptors ** ***********************************/ namespace Ahci { struct Device_fis : Mmio { struct Status : Register<0x2, 8> { /* ATAPI bits */ struct Device_ready : Bitfield<6, 1> { }; }; struct Error : Register<0x3, 8> { }; Device_fis(addr_t recv_base) : Mmio(recv_base + 0x40) { } }; struct Command_fis : 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 : 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 : Bitset_2 { }; struct Lba48 : Bitset_3 { }; struct Lba : 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 : Bitset_2 { }; /* sector count */ Command_fis(addr_t base) : Mmio(base) { clear(); enum { HOST_TO_DEVICE = 0x27 }; write(HOST_TO_DEVICE); } static constexpr size_t size() { return 0x14; } void clear() { memset((void *)base(), 0, size()); } /************************ ** ATA spec commands ** ************************/ void identify_device() { write(1); write(0); write(0xec); } void dma_ext(bool read, block_number_t block_number, block_count_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_number_t block_number, block_count_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); } /* * Sets byte count limit for PIO transfers */ void byte_count(uint16_t bytes) { write(bytes & 0xff); write(bytes >> 8); } }; /** * AHCI command list structure header */ struct Command_header : 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(addr_t base) : Mmio(base) { } void cmd_table_base(addr_t base_phys) { 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 size_t size() { return 0x20; } }; /** * ATAPI packet 12 or 16 bytes */ struct Atapi_command : 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 : Bitset_3 { }; struct Lba : Bitset_2 { }; /* sector count big endian */ struct Sector8_15 : Register<0x8, 8> { }; struct Sector0_7 : Register<0x9, 8> { }; struct Sector : Bitset_2 { }; Atapi_command(addr_t base) : Mmio(base) { 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_number_t block_number, block_count_t block_count) { write(0x28); write(block_number); write(block_count); } void start_unit() { write(0x1b); } }; /** * Physical region descritpor table */ struct Prdt : 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(addr_t base, addr_t phys, size_t bytes) : Mmio(base) { uint64_t addr = phys; write(addr); write(addr >> 32); write(bytes > 0 ? bytes - 1 : 0); } static constexpr 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(addr_t base, addr_t phys, size_t bytes = 0) : fis(base), atapi_cmd(base + 0x40), prdt(base + 0x80, phys, bytes) { } static constexpr size_t size() { return 0x100; } }; } /* namespace Ahci */ /** * Minimalistic AHCI port structure to merely detect device signature */ struct Ahci::Port_base : Mmio { /* device signature */ enum Signature { ATA_SIG = 0x101, ATAPI_SIG = 0xeb140101, ATAPI_SIG_QEMU = 0xeb140000, /* will be fixed in Qemu */ }; unsigned index { }; Hba &hba; /** * Port signature */ struct Sig : Register<0x24, 32> { }; static constexpr addr_t offset() { return 0x100; } static constexpr size_t size() { return 0x80; } Port_base(unsigned index, Hba &hba) : Mmio(hba.base() + offset() + (index * size())), index(index), hba(hba) { } bool implemented() const { return hba.read() & (1u << index); } bool ata() const { return read() == ATA_SIG; } bool atapi() const { unsigned sig = read(); return sig == ATAPI_SIG || sig == ATAPI_SIG_QEMU; } }; struct Ahci::Protocol : Interface { virtual unsigned init(Port &port) = 0; virtual Block::Session::Info info() const = 0; virtual void handle_irq(Port &port) = 0; virtual Response submit(Port &port, Block::Request const request) = 0; virtual Block::Request completed(Port &port) = 0; virtual void writeable(bool rw) = 0; }; /** * AHCI port */ struct Ahci::Port : private Port_base { using Port_base::write; using Port_base::read; using Port_base::wait_for_any; using Port_base::wait_for; using Port_base::Register_set::Polling_timeout; using Port_base::index; using Port_base::hba; struct Not_ready : Exception { }; Protocol &protocol; Region_map &rm; unsigned cmd_slots = hba.command_slots(); Ram_dataspace_capability device_ds { }; Ram_dataspace_capability cmd_ds { }; Ram_dataspace_capability device_info_ds { }; addr_t cmd_list = 0; addr_t fis_base = 0; addr_t cmd_table = 0; addr_t device_info = 0; addr_t dma_base = 0; /* physical address of DMA memory */ Port(Protocol &protocol, Region_map &rm, Hba &hba, unsigned index) : Port_base(index, hba), protocol(protocol), rm(rm) { reset(); if (!enable()) throw 1; stop(); wait_for(hba.delayer(), Cmd::Cr::Equal(0)); init(); /* * Init protocol and determine actual number of command slots of device */ try { unsigned device_slots = protocol.init(*this); cmd_slots = min(device_slots, cmd_slots); } catch (Polling_timeout) { /* ack any pending IRQ from failed device initialization */ ack_irq(); throw; } } virtual ~Port() { if (device_ds.valid()) { rm.detach((void *)cmd_list); hba.free_dma_buffer(device_ds); } if (cmd_ds.valid()) { rm.detach((void *)cmd_table); hba.free_dma_buffer(cmd_ds); } if (device_info_ds.valid()) { rm.detach((void*)device_info); 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(addr_t phys) { 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(addr_t phys) { 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 */ /* ncq irq */ struct Fpdma_irq : Sdbs { }; /* non-ncq irq */ struct Dma_ext_irq : Bitfield<0, 3> { }; }; 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, 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(~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 Fr : Bitfield<14, 1> { }; /* FIS receive running */ 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; try { wait_for(hba.delayer(), Tfd::Sts_bsy::Equal(0)); } catch (Polling_timeout) { error("HBA busy unable to start command processing."); return; } try { wait_for(hba.delayer(), Tfd::Sts_drq::Equal(0)); } catch (Polling_timeout) { error("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 */ }; /** * 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); retry( [&] { if ((Ssts::Dec::get(status) != Ssts::Dec::ESTABLISHED) || !(Ssts::Ipm::get(status) & Ssts::Ipm::ACTIVE)) throw Not_ready(); }, [&] { hba.delayer().usleep(1000); status = read(); }, 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()) warning("CMD.ST bit set during device reset --> unknown behavior"); write(1); hba.delayer().usleep(1000); write(0); try { wait_for(hba.delayer(), Ssts::Dec::Equal(Ssts::Dec::ESTABLISHED)); } catch (Polling_timeout) { 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(read()); } /** * Serial ATA active (strict write) */ struct Sact : Register<0x34, 32, true> { }; /** * Command issue (strict write) */ struct Ci : Register<0x38, 32, true> { }; 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() { device_ds = hba.alloc_dma_buffer(0x1000); /* command list 1K */ addr_t phys = 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; /* * Set fis receive base, clear Fre (FIS receive) before and wait for FR * (FIS receive running) to clear */ write(0); wait_for(hba.delayer(), Cmd::Fr::Equal(0)); fis_rcv_base(phys + 1024); /* command table */ size_t cmd_size = align_addr(cmd_slots * Command_table::size(), 12); cmd_ds = hba.alloc_dma_buffer(cmd_size); cmd_table = (addr_t)rm.attach(cmd_ds); phys = (addr_t)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 = hba.alloc_dma_buffer(0x1000); device_info = rm.attach(device_info_ds); } addr_t command_table_addr(unsigned slot) { return cmd_table + (slot * Command_table::size()); }; addr_t command_header_addr(unsigned slot) { return cmd_list + (slot * Command_header::size()); } void execute(unsigned slot) { start(); write(1U << slot); } bool sanity_check(Block::Request const &request) { Block::Session::Info const i = info(); /* max. PRDT size is 4MB */ if (request.operation.count * i.block_size > 4 * 1024 * 1024) { error("error: maximum supported packet size is 4MB"); return false; } /* sanity check */ if (request.operation.count + request.operation.block_number > i.block_count) { error("error: requested blocks are outside of device"); return false; } return true; } Ram_dataspace_capability alloc_buffer(size_t size) { if (dma_base) return Ram_dataspace_capability(); Ram_dataspace_capability dma = hba.alloc_dma_buffer(size); dma_base = Dataspace_client(dma).phys_addr(); return dma; } void free_buffer(Ram_dataspace_capability ds) { dma_base = 0; hba.free_dma_buffer(ds); } /********************** ** Protocol wrapper ** **********************/ Block::Session::Info info() const { return protocol.info(); } void handle_irq() { protocol.handle_irq(*this); } Response submit(Block::Request const request) { return protocol.submit(*this, request); } template void for_one_completed_request(FN const &fn) { Block::Request request = protocol.completed(*this); if (!request.operation.valid()) return; request.success = true; fn(request); } void writeable(bool rw) { protocol.writeable(rw); } }; #endif /* _AHCI__AHCI_H_ */