/** * \brief Block driver session creation * \author Sebastian Sumpf * \date 2015-09-29 */ /* * Copyright (C) 2016-2020 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 #include #include #include #include #include #include #include /* local includes */ #include #include #include namespace Ahci { struct Dispatch; class Driver; struct Main; struct Block_session_handler; struct Block_session_component; } struct Ahci::Dispatch : Interface { virtual void session(unsigned index) = 0; }; class Ahci::Driver : Noncopyable { public: enum { MAX_PORTS = 32 }; private: Env &_env; Dispatch &_dispatch; struct Timer_delayer : Mmio::Delayer, Timer::Connection { Timer_delayer(Env &env) : Timer::Connection(env) { } void usleep(uint64_t us) override { Timer::Connection::usleep(us); } } _delayer { _env }; Hba _hba { _env, _delayer }; Constructible _ata[MAX_PORTS]; Constructible _atapi[MAX_PORTS]; Constructible _ports[MAX_PORTS]; Signal_handler _irq { _env.ep(), *this, &Driver::handle_irq }; bool _enable_atapi; void _info() { log("version: " "major=", Hex(_hba.read()), " " "minor=", 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(Region_map &rm) { log("number of ports: ", _hba.port_count(), " pi: ", Hex(_hba.read())); for (unsigned index = 0; index < MAX_PORTS; index++) { Port_base port(index, _hba); if (port.implemented() == false) continue; bool enabled = false; if (port.ata()) { try { _ata[index].construct(); _ports[index].construct(*_ata[index], rm, _hba, index); enabled = true; } catch (...) { } log("\t\t#", index, ":", enabled ? " ATA" : " off (ATA)"); } else if (port.atapi() && _enable_atapi) { try { _atapi[index].construct(); _ports[index].construct(*_atapi[index], rm, _hba, index); enabled = true; } catch (...) { } log("\t\t#", index, ":", enabled ? " ATAPI" : " off (ATAPI)"); } else { log("\t\t#", index, ":", port.atapi() ? " off (ATAPI)" : " off (unknown device signature)"); } } } public: Driver(Env &env, Dispatch &dispatch, bool support_atapi) : _env(env), _dispatch(dispatch), _enable_atapi(support_atapi) { _info(); /* register irq handler */ _hba.sigh_irq(_irq); /* initialize HBA (IRQs, memory) */ _hba.init(); /* search for devices */ _scan_ports(env.rm()); } /** * Forward IRQs to ports/block sessions */ void handle_irq() { unsigned port_list = _hba.read(); while (port_list) { unsigned port = log2(port_list); port_list &= ~(1U << port); /* ack irq */ if (_ports[port].constructed()) _ports[port]->handle_irq(); /* handle (pending) requests */ _dispatch.session(port); } /* clear status register */ _hba.ack_irq(); } Port &port(Session_label const &label, Session_policy const &policy) { /* try read device port number attribute */ long device = policy.attribute_value("device", -1L); /* try read device model and serial number attributes */ auto const model = policy.attribute_value("model", String<64>()); auto const serial = policy.attribute_value("serial", String<64>()); /* check for model/device */ if (model != "" && serial != "") { for (long index = 0; index < MAX_PORTS; index++) { if (!_ata[index].constructed()) continue; Ata::Protocol &protocol = *_ata[index]; if (*protocol.model == model.string() && *protocol.serial == serial.string()) return *_ports[index]; } warning("No device with model ", model, " and serial ", serial, " found for \"", label, "\""); } /* check for device number */ if (device >= 0 && device < MAX_PORTS && _ports[device].constructed()) return *_ports[device]; warning("No device found on port ", device, " for \"", label, "\""); throw Service_denied(); } template void for_each_port(FN const &fn) { for (unsigned index = 0; index < MAX_PORTS; index++) { if (!_ports[index].constructed()) continue; fn(*_ports[index], index, !_ata[index].constructed()); } } void report_ports(Reporter &reporter) { auto report = [&](Port const &port, unsigned index, bool atapi) { Block::Session::Info info = port.info(); Reporter::Xml_generator xml(reporter, [&] () { xml.node("port", [&] () { xml.attribute("num", index); xml.attribute("type", atapi ? "ATAPI" : "ATA"); xml.attribute("block_count", info.block_count); xml.attribute("block_size", info.block_size); if (!atapi) { xml.attribute("model", _ata[index]->model->cstring()); xml.attribute("serial", _ata[index]->serial->cstring()); } }); }); }; for_each_port(report); } }; struct Ahci::Block_session_handler : Interface { Env &env; Port &port; Ram_dataspace_capability ds; Signal_handler request_handler { env.ep(), *this, &Block_session_handler::handle}; Block_session_handler(Env &env, Port &port, size_t buffer_size) : env(env), port(port), ds(port.alloc_buffer(buffer_size)) { } ~Block_session_handler() { port.free_buffer(ds); } virtual void handle_requests()= 0; void handle() { handle_requests(); } }; struct Ahci::Block_session_component : Rpc_object, Block_session_handler, Block::Request_stream { Block_session_component(Env &env, Port &port, size_t buffer_size) : Block_session_handler(env, port, buffer_size), Request_stream(env.rm(), ds, env.ep(), request_handler, port.info()) { env.ep().manage(*this); } ~Block_session_component() { env.ep().dissolve(*this); } Info info() const override { return Request_stream::info(); } Capability tx_cap() override { return Request_stream::tx_cap(); } void handle_requests() override { while (true) { bool progress = false; /* * Acknowledge any pending packets before sending new request to the * controller. */ try_acknowledge([&](Ack &ack) { port.for_one_completed_request([&] (Block::Request request) { progress = true; ack.submit(request); }); }); with_requests([&] (Block::Request request) { Response response = Response::RETRY; /* only READ/WRITE requests, others are noops for now */ if (Block::Operation::has_payload(request.operation.type) == false) { request.success = true; progress = true; return Response::REJECTED; } if ((response = port.submit(request)) != Response::RETRY) progress = true; return response; }); if (progress == false) break; } /* poke */ wakeup_client_if_needed(); } }; struct Ahci::Main : Rpc_object>, Dispatch { Env &env; Attached_rom_dataspace config { env, "config" }; Constructible driver { }; Constructible reporter { }; Constructible block_session[Driver::MAX_PORTS]; Main(Env &env) : env(env) { log("--- Starting AHCI driver ---"); bool support_atapi = config.xml().attribute_value("atapi", false); try { driver.construct(env, *this, support_atapi); report_ports(); } catch (Ahci::Missing_controller) { error("no AHCI controller found"); env.parent().exit(~0); } catch (Service_denied) { error("hardware access denied"); env.parent().exit(~0); } env.parent().announce(env.ep().manage(*this)); } void session(unsigned index) override { if (index > Driver::MAX_PORTS || !block_session[index].constructed()) return; block_session[index]->handle_requests(); } Session_capability session(Root::Session_args const &args, Affinity const &) override { Session_label const label = label_from_args(args.string()); Session_policy const policy(label, config.xml()); Ram_quota const ram_quota = ram_quota_from_args(args.string()); size_t const tx_buf_size = Arg_string::find_arg(args.string(), "tx_buf_size").ulong_value(0); if (!tx_buf_size) throw Service_denied(); if (tx_buf_size > ram_quota.value) { error("insufficient 'ram_quota' from '", label, "'," " got ", ram_quota, ", need ", tx_buf_size); throw Insufficient_ram_quota(); } Port &port = driver->port(label, policy); if (block_session[port.index].constructed()) { error("Device with number=", port.index, " is already in use"); throw Service_denied(); } port.writeable(policy.attribute_value("writeable", false)); block_session[port.index].construct(env, port, tx_buf_size); return block_session[port.index]->cap(); } void upgrade(Session_capability, Root::Upgrade_args const&) override { } void close(Session_capability cap) override { for (int index = 0; index < Driver::MAX_PORTS; index++) { if (!block_session[index].constructed() || !(cap == block_session[index]->cap())) continue; block_session[index].destruct(); } } void report_ports() { try { Xml_node report = config.xml().sub_node("report"); if (report.attribute_value("ports", false)) { reporter.construct(env, "ports"); reporter->enabled(true); driver->report_ports(*reporter); } } catch (Xml_node::Nonexistent_sub_node) { } } }; void Component::construct(Genode::Env &env) { static Ahci::Main server(env); }