ahci: release device before suspend

- monitor system ROM changes
- stop processing of new Jobs before suspend
- destruct platform device before suspend, but keep platform DMA buffers
- re-construct platform device and reinit resources (mmio, irq) on resume
- re-start block job scheduling on resume

Issue #5101
This commit is contained in:
Alexander Boettcher 2024-01-17 14:08:30 +01:00 committed by Christian Helmuth
parent cde4d4aee0
commit 9d15735bc6
4 changed files with 125 additions and 3 deletions

View File

@ -113,6 +113,11 @@ struct Ahci::Hba
void with_mmio (auto const &, auto const &) const;
Hba(Resources &resource) : _resource(resource)
{
reinit();
}
void reinit()
{
with_mmio([&](Hba_mmio &mmio) {
@ -290,6 +295,27 @@ struct Ahci::Resources
reinit();
}
void release_device()
{
if (_device.constructed())
log("release AHCI device for suspend");
_mmio .destruct();
_irq .destruct();
_device.destruct();
}
void acquire_device()
{
_device.construct(_platform);
_irq .construct(*_device);
_mmio .construct(*_device, _mmio_index(_platform));
_hba.reinit();
reinit();
}
void with_mmio_irq(auto const &fn, auto const &fn_error)
{
if (_mmio.constructed() && _irq.constructed())
@ -679,6 +705,7 @@ struct Ahci::Protocol : Interface
virtual Block::Request completed(Port_mmio &) = 0;
virtual void handle_irq(Port &, Port_mmio &) = 0;
virtual void writeable(bool rw) = 0;
virtual bool pending_requests() const = 0;
};
@ -838,6 +865,8 @@ struct Ahci::Port : private Port_base
Region_map &rm;
unsigned cmd_slots = hba.command_slots();
bool stop_processing { };
Platform::Dma_buffer device_dma { plat, 0x1000, CACHED };
Platform::Dma_buffer cmd_dma { plat,
align_addr(cmd_slots * Command_table::size(), 12), CACHED };
@ -868,6 +897,11 @@ struct Ahci::Port : private Port_base
:
Port_base(index, plat, hba, delayer),
protocol(protocol), rm(rm)
{
reinit();
}
void reinit()
{
_with_port_mmio([&](Port_mmio &mmio) {
reset(mmio);
@ -1190,7 +1224,10 @@ struct Ahci::Port : private Port_base
request.success = true;
fn(request);
}, [&](){ error("for_one_completed_request failed"); });
}, [&](){
if (protocol.pending_requests())
error("for_one_completed_request failed with pending requests");
});
}
void writeable(bool rw) { protocol.writeable(rw); }

View File

@ -363,6 +363,8 @@ class Ata::Protocol : public Ahci::Protocol, Noncopyable
return r;
}
bool pending_requests() const override { return !!_slot_states; }
};
#endif /* _AHCI__ATA_PROTOCOL_H_ */

View File

@ -188,6 +188,11 @@ class Atapi::Protocol : public Ahci::Protocol, Noncopyable
return request;
}
bool pending_requests() const override
{
return _pending.operation.valid();
}
};
#endif /* _AHCI__ATAPI_PROTOCOL_H_ */

View File

@ -62,11 +62,16 @@ class Ahci::Driver : Noncopyable
Signal_handler<Driver> _handler { _env.ep(), *this, &Driver::handle_irq };
Resources _resources { _env, _handler };
Constructible<Attached_rom_dataspace> _system_rom { };
Signal_handler<Driver> _system_rom_sigh {
_env.ep(), *this, &Driver::_system_update };
Constructible<Ata::Protocol> _ata [MAX_PORTS];
Constructible<Atapi::Protocol> _atapi[MAX_PORTS];
Constructible<Port> _ports[MAX_PORTS];
bool _enable_atapi;
bool _schedule_stop { };
unsigned _scan_ports(Region_map &rm, Platform::Connection &plat, Hba &hba)
{
@ -111,9 +116,50 @@ class Ahci::Driver : Noncopyable
return port_count;
}
void _system_update()
{
if (!_system_rom.constructed())
return;
_system_rom->update();
if (!_system_rom->valid())
return;
auto state = _system_rom->xml().attribute_value("state",
String<32>(""));
if (state == "driver_stop") {
_schedule_stop = true;
for_each_port([&](auto &port, auto, auto) {
port.stop_processing = true;
});
device_release_if_stopped_and_idle();
return;
}
if (state == "driver_reinit") {
_resources.acquire_device();
_schedule_stop = false;
/* re-start request handling of client sessions */
for_each_port([&](auto &port, auto const index, auto) {
port.stop_processing = false;
port.reinit();
_dispatch.session(index);
});
return;
}
}
public:
Driver(Env &env, Dispatch &dispatch, bool support_atapi)
Driver(Env &env, Dispatch &dispatch, bool support_atapi, bool use_system_rom)
: _env(env), _dispatch(dispatch), _enable_atapi(support_atapi)
{
/* search for devices */
@ -126,6 +172,11 @@ class Ahci::Driver : Noncopyable
Hex(hba.cap_np_value()), ",PI=", Hex(hba.pi_value()), ")");
});
});
if (use_system_rom) {
_system_rom.construct(_env, "system");
_system_rom->sigh(_system_rom_sigh);
}
}
/**
@ -142,6 +193,28 @@ class Ahci::Driver : Noncopyable
_dispatch.session(port);
}, [&]() { error("hba handle_irq failed"); });
});
device_release_if_stopped_and_idle();
}
void device_release_if_stopped_and_idle()
{
if (!_schedule_stop)
return;
/* check for outstanding requests */
bool pending = false;
for_each_port([&](auto const &port, auto, auto) {
if (port.protocol.pending_requests())
pending = true;
});
/* avoid disabling device if we have outstanding requests */
if (pending)
return;
_resources.release_device();
}
Port &port(Session_label const &label, Session_policy const &policy)
@ -270,6 +343,10 @@ struct Ahci::Block_session_component : Rpc_object<Block::Session>,
});
});
/* all completed packets are handled, but no further processing */
if (port.stop_processing)
break;
with_requests([&] (Block::Request request) {
Response response = Response::RETRY;
@ -309,8 +386,9 @@ struct Ahci::Main : Rpc_object<Typed_root<Block::Session>>, Dispatch
{
log("--- Starting AHCI driver ---");
bool support_atapi = config.xml().attribute_value("atapi", false);
bool use_system_rom = config.xml().attribute_value("system", false);
try {
driver.construct(env, *this, support_atapi);
driver.construct(env, *this, support_atapi, use_system_rom);
report_ports();
} catch (Ahci::Missing_controller) {
error("no AHCI controller found");