/** * \brief AHCI port controller handling * \author Sebastian Sumpf * \date 2015-04-29 */ /* * Copyright (C) 2015 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU General Public License version 2. */ #include #include "ata_driver.h" #include "atapi_driver.h" struct Timer_delayer : Mmio::Delayer, Timer::Connection { void usleep(unsigned us) { Timer::Connection::usleep(us); } }; Mmio::Delayer &Hba::delayer() { static Timer_delayer d; return d; } struct Ahci { /* read device signature */ enum Signature { ATA_SIG = 0x101, ATAPI_SIG = 0xeb140101, ATAPI_SIG_QEMU = 0xeb140000, /* will be fixed in Qemu */ }; Ahci_root &root; Platform::Hba &platform_hba = Platform::init(Hba::delayer()); Hba hba = { platform_hba }; enum { MAX_PORTS = 32 }; Port_driver *ports[MAX_PORTS]; bool port_claimed[MAX_PORTS]; Signal_rpc_member irq; Signal_rpc_member device_ready; unsigned ready_count = 0; Ahci(Ahci_root &root) : root(root), irq(root.entrypoint(), *this, &Ahci::handle_irq), device_ready(root.entrypoint(), *this, &Ahci::ready) { info(); /* register irq handler */ platform_hba.sigh_irq(irq); /* initialize HBA (IRQs, memory) */ hba.init(); /* search for devices */ scan_ports(); } bool is_atapi(unsigned sig) { return sig == ATAPI_SIG_QEMU || sig == ATAPI_SIG; } bool is_ata(unsigned sig) { return sig == ATA_SIG; } /** * Forward IRQs to ports */ void handle_irq(unsigned) { unsigned port_list = hba.read(); while (port_list) { unsigned port = log2(port_list); port_list &= ~(1U << port); ports[port]->handle_irq(); } /* clear status register */ hba.ack_irq(); /* ack at interrupt controller */ platform_hba.ack_irq(); } void ready(unsigned) { if (--ready_count) return; /* announce service */ root.announce(); } void info() { PINF("\tversion: %x.%04x", hba.read(), hba.read()); PINF("\tcommand slots: %u", hba.command_slots()); PINF("\tnative command queuing: %s", hba.ncq() ? "yes" : "no"); PINF("\t64 bit support: %s", hba.supports_64bit() ? "yes" : "no"); } void scan_ports() { PINF("\tnumber of ports: %u pi: %x", hba.port_count(), hba.read()); unsigned available = hba.read(); for (unsigned i = 0; i < hba.port_count(); i++) { /* check if port is implemented */ if (!(available & (1U << i))) continue; Port port(hba, platform_hba, i); /* check for ATA/ATAPI devices */ unsigned sig = port.read(); if (!is_atapi(sig) && !is_ata(sig)) { PINF("\t\t#%u: off", i); continue; } port.reset(); bool enabled = false; try { enabled = port.enable(); } catch (Port::Not_ready) { PERR("Could not enable port %u", i); } PINF("\t\t#%u: %s", i, is_atapi(sig) ? "ATAPI" : "ATA"); if (!enabled) continue; switch (sig) { case ATA_SIG: ports[i] = new (Genode::env()->heap()) Ata_driver(port, device_ready); ready_count++; break; case ATAPI_SIG: case ATAPI_SIG_QEMU: ports[i] = new (Genode::env()->heap()) Atapi_driver(port, device_ready); ready_count++; break; default: PWRN("Device signature %x unsupported", sig); } } }; Block::Driver *claim_port(unsigned port_num) { if (!is_avail(port_num)) throw -1; port_claimed[port_num] = true; return ports[port_num]; } void free_port(unsigned port_num) { port_claimed[port_num] = false; } bool is_avail(unsigned port_num) { return port_num < MAX_PORTS && ports[port_num] && !port_claimed[port_num] && ports[port_num]->ready(); } }; static Ahci *sata_ahci(Ahci *ahci = 0) { static Ahci *a = ahci; return a; } void Ahci_driver::init(Ahci_root &root) { static Ahci ahci(root); sata_ahci(&ahci); } Block::Driver *Ahci_driver::claim_port(long device_num) { return sata_ahci()->claim_port(device_num); } void Ahci_driver::free_port(long device_num) { sata_ahci()->free_port(device_num); } bool Ahci_driver::is_avail(long device_num) { return sata_ahci()->is_avail(device_num); }