/** * \brief AHCI port controller handling * \author Sebastian 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. */ /* Genode includes */ #include /* local includes */ #include #include struct Ahci { Genode::Env &env; Genode::Allocator &alloc; /* read device signature */ enum Signature { ATA_SIG = 0x101, ATAPI_SIG = 0xeb140101, ATAPI_SIG_QEMU = 0xeb140000, /* will be fixed in Qemu */ }; struct Timer_delayer : Mmio::Delayer, Timer::Connection { Timer_delayer(Genode::Env &env) : Timer::Connection(env) { } void usleep(uint64_t us) override { Timer::Connection::usleep(us); } } _delayer { env }; Ahci_root &root; Platform::Hba &platform_hba = Platform::init(env, _delayer); Hba hba { env, platform_hba, _delayer }; enum { MAX_PORTS = 32 }; Port_driver *ports[MAX_PORTS]; bool port_claimed[MAX_PORTS]; Signal_handler irq; unsigned ready_count = 0; bool enable_atapi; Signal_context_capability device_identified; Ahci(Genode::Env &env, Genode::Allocator &alloc, Ahci_root &root, bool support_atapi, Genode::Signal_context_capability device_identified) : env(env), alloc(alloc), root(root), irq(root.entrypoint(), *this, &Ahci::handle_irq), enable_atapi(support_atapi), device_identified(device_identified) { info(); /* register irq handler */ platform_hba.sigh_irq(irq); /* initialize HBA (IRQs, memory) */ hba.init(); /* search for devices */ scan_ports(env.rm(), env.ram()); } /** * Forward IRQs to ports */ void handle_irq() { 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(); } /* * Least significant bit */ unsigned lsb(unsigned bits) const { for (unsigned i = 0; i < 32; i++) if (bits & (1u << i)) { return i; } return 0; } void info() { using Genode::log; log("version: " "major=", Genode::Hex(hba.read()), " " "minor=", Genode::Hex(hba.read())); log("command slots: ", hba.command_slots()); log("native command queuing: ", hba.ncq() ? "yes" : "no"); log("64-bit support: ", hba.supports_64bit() ? "yes" : "no"); } void scan_ports(Genode::Region_map &rm, Genode::Ram_allocator &ram) { log("number of ports: ", hba.port_count(), " pi: ", Hex(hba.read())); unsigned available = hba.read(); for (unsigned i = 0; i < hba.port_count(); i++) { /* check if port is implemented */ if (!available) break; unsigned index = lsb(available); available ^= (1u << index); bool enabled = false; switch (Port_base(index, hba).read()) { case ATA_SIG: try { ports[index] = new (&alloc) Ata_driver(alloc, ram, root, ready_count, rm, hba, platform_hba, index, device_identified); enabled = true; } catch (...) { } log("\t\t#", index, ":", enabled ? " ATA" : " off (ATA)"); break; case ATAPI_SIG: case ATAPI_SIG_QEMU: if (enable_atapi) try { ports[index] = new (&alloc) Atapi_driver(ram, root, ready_count, rm, hba, platform_hba, index); enabled = true; } catch (...) { } log("\t\t#", index, ":", enabled ? " ATAPI" : " off (ATAPI)"); break; default: log("\t\t#", index, ": off (unknown device signature)"); } } }; Block::Driver *claim_port(unsigned port_num) { if (!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 avail(unsigned port_num) { return port_num < MAX_PORTS && ports[port_num] && !port_claimed[port_num] && ports[port_num]->ready(); } Port_driver * port(unsigned num) { return num < MAX_PORTS ? ports[num] : nullptr; } long device_number(const char *model_num, const char *serial_num) { for (long port_num = 0; port_num < MAX_PORTS; port_num++) { Ata_driver* drv = dynamic_cast(ports[port_num]); if (!drv) continue; if (*drv->model == model_num && *drv->serial == serial_num) return port_num; } return -1; } private: /* * Noncopyable */ Ahci(Ahci const &); Ahci &operator = (Ahci const &); }; static Ahci *sata_ahci(Ahci *ahci = 0) { static Ahci *a = ahci; return a; } void Ahci_driver::init(Genode::Env &env, Genode::Allocator &alloc, Ahci_root &root, bool support_atapi, Genode::Signal_context_capability device_identified) { static Ahci ahci(env, alloc, root, support_atapi, device_identified); 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::avail(long device_num) { return sata_ahci()->avail(device_num); } long Ahci_driver::device_number(char const *model_num, char const *serial_num) { return sata_ahci()->device_number(model_num, serial_num); } void Ahci_driver::report_ports(Genode::Reporter &reporter) { Genode::Reporter::Xml_generator xml(reporter, [&] () { for (unsigned i = 0; i < Ahci::MAX_PORTS; ++i) { Port_driver *port = sata_ahci()->port(i); if (!port || !port->ready()) continue; Ata_driver *ata = dynamic_cast(port); xml.node("port", [&] () { xml.attribute("num", i); xml.attribute("type", ata ? "ATA" : "ATAPI"); if (ata) { xml.attribute("block_count", ata->block_count()); xml.attribute("block_size", ata->block_size()); xml.attribute("model", ata->model->cstring()); xml.attribute("serial", ata->serial->cstring()); } }); } }); }