/* * \brief ATA protocol driver * \author Sebastian Sumpf * \date 2015-04-29 */ /* * Copyright (C) 2015-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. */ #ifndef _AHCI__ATA_PROTOCOL_H_ #define _AHCI__ATA_PROTOCOL_H_ #include #include "ahci.h" #include "util.h" namespace Ata { struct Identity; template struct String; class Protocol; using namespace Genode; using namespace Ahci; } /** * Return data of 'identify_device' ATA command */ struct Ata::Identity : Mmio { Identity(addr_t base) : Mmio(base) { } struct Serial_number : Register_array<0x14, 8, 20, 8> { }; struct Model_number : Register_array<0x36, 8, 40, 8> { }; struct Queue_depth : Register<0x96, 16> { struct Max_depth : Bitfield<0, 5> { }; }; struct Sata_caps : Register<0x98, 16> { struct Ncq_support : Bitfield<8, 1> { }; }; struct Sector_count : Register<0xc8, 64> { }; struct Logical_block : Register<0xd4, 16> { struct Per_physical : Bitfield<0, 3> { }; /* 2^X logical per physical */ struct Longer_512 : Bitfield<12, 1> { }; struct Multiple : Bitfield<13, 1> { }; /* multiple logical blocks per physical */ }; struct Logical_words : Register<0xea, 32> { }; /* words (16 bit) per logical block */ struct Alignment : Register<0x1a2, 16> { struct Logical_offset : Bitfield<0, 14> { }; /* offset first logical block in physical */ }; void info() { log(" queue depth: ", read() + 1, " " "ncq: ", read()); log(" numer of sectors: ", read()); log(" multiple logical blocks per physical: ", read() ? "yes" : "no"); log(" logical blocks per physical: ", 1U << read()); log(" logical block size is above 512 byte: ", read() ? "yes" : "no"); log(" words (16bit) per logical block: ", read()); log(" offset of first logical block within physical: ", read()); } }; /** * 16-bit word big endian device ASCII characters */ template struct Ata::String { char buf[DEVICE_STRING::ITEMS + 1]; String(Identity & info) { long j = 0; for (unsigned long i = 0; i < DEVICE_STRING::ITEMS; i++) { /* read and swap even and uneven characters */ char c = (char)info.read(i ^ 1); if (is_whitespace(c) && j == 0) continue; buf[j++] = c; } buf[j] = 0; /* remove trailing white spaces */ while ((j > 0) && (buf[--j] == ' ')) buf[j] = 0; } bool operator == (char const *other) const { return strcmp(buf, other) == 0; } void print(Output &out) const { print(out, (char const *)buf); } char const *cstring() { return buf; } }; /** * Protocol driver using ncq- and non-ncq commands */ class Ata::Protocol : public Ahci::Protocol, Noncopyable { private: bool _syncing { false }; struct Request : Block::Request { bool valid() const { return operation.valid(); } void invalidate() { operation.type = Block::Operation::Type::INVALID; } Request & operator=(const Block::Request &request) { operation = request.operation; success = request.success; offset = request.offset; tag = request.tag; return *this; } }; Util::Slots _slots { }; unsigned _slot_states = 0; typedef String Serial_string; typedef String Model_string; Constructible _identity { }; bool _writeable { false }; public: Constructible serial { }; Constructible model { }; private: bool _overlap_check(Block::Request const &request) { block_number_t block_number = request.operation.block_number; block_number_t end = block_number + request.operation.count - 1; auto overlap_check = [&] (Request const &req) { if (req.operation.type == Block::Operation::Type::SYNC) return false; block_number_t pending_start = req.operation.block_number; block_number_t pending_end = pending_start + req.operation.count - 1; /* check if a pending packet overlaps */ if ((block_number >= pending_start && block_number <= pending_end) || (end >= pending_start && end <= pending_end) || (pending_start >= block_number && pending_start <= end) || (pending_end >= block_number && pending_end <= end)) { warning("overlap: ", "pending ", pending_start, " + ", req.operation.count, " (", req.operation.type == Block::Operation::Type::WRITE ? "write" : "read", "), ", "request: ", block_number, " + ", request.operation.count, " (", request.operation.type == Block::Operation::Type::WRITE ? "write" : "read", ")"); return true; } return false; }; return _slots.for_each(overlap_check); } bool _ncq_support(Port &port) { return _identity->read() && port.hba.ncq(); } size_t _block_size() const { size_t size = 512; if (_identity->read()) size = _identity->read() / 2; return size; } Block::sector_t _block_count() const { return _identity->read(); } public: /****************************** ** Ahci::Protocol interface ** ******************************/ unsigned init(Port &port) override { /* identify device */ addr_t phys = Dataspace_client(port.device_info_ds).phys_addr(); Command_table table(port.command_table_addr(0), phys, 0x1000); table.fis.identify_device(); port.execute(0); port.wait_for_any(port.hba.delayer(), Port::Is::Dss::Equal(1), Port::Is::Pss::Equal(1), Port::Is::Dhrs::Equal(1)); _identity.construct(port.device_info); serial.construct(*_identity); model.construct(*_identity); if (verbose) { log(" model number: ", Cstring(model->buf)); log(" serial number: ", Cstring(serial->buf)); _identity->info(); } /* read number of command slots of ATA device */ unsigned cmd_slots = _identity->read() + 1; /* no native command queueing */ if (!_ncq_support(port)) cmd_slots = 1; _slots.limit((size_t)cmd_slots); port.ack_irq(); return cmd_slots; } void handle_irq(Port &port) override { unsigned is = port.read(); /* ncg */ if (_ncq_support(port) && Port::Is::Fpdma_irq::get(is)) do { port.ack_irq(); } while (Port::Is::Sdbs::get(port.read())); /* normal dma */ else if (Port::Is::Dma_ext_irq::get(port.read())) port.ack_irq(); _slot_states = port.read() | port.read(); port.stop(); _syncing = false; } Block::Session::Info info() const override { return { .block_size = _block_size(), .block_count = _block_count(), .align_log2 = log2(2ul), .writeable = _writeable }; } void writeable(bool rw) override { _writeable = rw; } Response submit(Port &port, Block::Request const request) override { Block::Operation const op = request.operation; bool const sync = (op.type == Block::Operation::Type::SYNC); bool const write = (op.type == Block::Operation::Type::WRITE); if ((sync && _slot_states) || _syncing) return Response::RETRY; if (_writeable == false && write) return Response::REJECTED; if (Block::Operation::has_payload(op.type)) { if (port.sanity_check(request) == false || port.dma_base == 0) return Response::REJECTED; if (_overlap_check(request)) return Response::RETRY; } Request *r = _slots.get(); if (r == nullptr) return Response::RETRY; *r = request; size_t slot = _slots.index(*r); _slot_states |= 1u << slot; /* setup fis */ Command_table table(port.command_table_addr(slot), port.dma_base + request.offset, /* physical address */ op.count * _block_size()); /* setup ATA command */ if (sync) { table.fis.flush_cache_ext(); _syncing = true; } else if (_ncq_support(port)) { table.fis.fpdma(write == false, op.block_number, op.count, slot); /* ensure that 'Cmd::St' is 1 before writing 'Sact' */ port.start(); /* set pending */ port.write(1U << slot); } else { table.fis.dma_ext(write == false, op.block_number, op.count); } /* set or clear write flag in command header */ Command_header header(port.command_header_addr(slot)); header.write(write ? 1 : 0); header.clear_byte_count(); port.execute(slot); return Response::ACCEPTED; } Block::Request completed(Port & /* port */) override { Block::Request r { }; _slots.for_each([&](Request &request) { size_t index = _slots.index(request); /* request still pending */ if (_slot_states & (1u << index)) return false; r = request; request.invalidate(); return true; }); return r; } }; #endif /* _AHCI__ATA_PROTOCOL_H_ */