diff --git a/repos/os/src/drivers/platform/spec/x86/nonpci_devices.cc b/repos/os/src/drivers/platform/spec/x86/nonpci_devices.cc index f71a19049f..765c1a9f61 100644 --- a/repos/os/src/drivers/platform/spec/x86/nonpci_devices.cc +++ b/repos/os/src/drivers/platform/spec/x86/nonpci_devices.cc @@ -40,10 +40,11 @@ class Nonpci::Ps2 : public Platform::Device_component Genode::Attached_io_mem_dataspace &pciconf, Platform::Session_component &session, Genode::Allocator &heap_for_irq, - Platform::Pci::Config::Delayer &delayer) + Platform::Pci::Config::Delayer &delayer, + Platform::Device_bars_pool &devices_bars) : Platform::Device_component(env, pciconf, session, IRQ_KEYBOARD, - heap_for_irq, delayer), + heap_for_irq, delayer, devices_bars), _ep(env.ep().rpc_ep()), _irq_mouse(IRQ_MOUSE, ~0UL, env, heap_for_irq), _data(env, REG_DATA, ACCESS_WIDTH), @@ -107,10 +108,11 @@ class Nonpci::Pit : public Platform::Device_component Genode::Attached_io_mem_dataspace &pciconf, Platform::Session_component &session, Genode::Allocator &heap_for_irq, - Platform::Pci::Config::Delayer &delayer) + Platform::Pci::Config::Delayer &delayer, + Platform::Device_bars_pool &devices_bars) : Platform::Device_component(env, pciconf, session, IRQ_PIT, - heap_for_irq, delayer), + heap_for_irq, delayer, devices_bars), _ports(env, PIT_PORT, PORTS_WIDTH) { } @@ -159,11 +161,13 @@ Platform::Device_capability Platform::Session_component::device(String const &na switch(devices_i) { case 0: dev = new (_md_alloc) Nonpci::Ps2(_env, _pciconf, *this, - _global_heap, _delayer); + _global_heap, _delayer, + _devices_bars); break; case 1: dev = new (_md_alloc) Nonpci::Pit(_env, _pciconf, *this, - _global_heap, _delayer); + _global_heap, _delayer, + _devices_bars); break; default: return Device_capability(); diff --git a/repos/os/src/drivers/platform/spec/x86/pci_device.cc b/repos/os/src/drivers/platform/spec/x86/pci_device.cc index 5e203fb3df..9ebb0d39ae 100644 --- a/repos/os/src/drivers/platform/spec/x86/pci_device.cc +++ b/repos/os/src/drivers/platform/spec/x86/pci_device.cc @@ -146,7 +146,7 @@ void Platform::Device_component::config_write(unsigned char address, catch (...) { Genode::error("assignment to device failed"); } - _enabled_bus_master = true; + _device_used = true; } _device_config.write(_config_access, address, value, size, diff --git a/repos/os/src/drivers/platform/spec/x86/pci_device_component.h b/repos/os/src/drivers/platform/spec/x86/pci_device_component.h index e524ff7056..76aa9cbb08 100644 --- a/repos/os/src/drivers/platform/spec/x86/pci_device_component.h +++ b/repos/os/src/drivers/platform/spec/x86/pci_device_component.h @@ -28,7 +28,12 @@ #include "pci_device_config.h" #include "irq.h" -namespace Platform { class Device_component; class Session_component; } +namespace Platform { + class Device_component; + class Session_component; + + typedef Registry > Device_bars_pool; +} class Platform::Device_component : public Genode::Rpc_object, private Genode::List::Element @@ -45,13 +50,14 @@ class Platform::Device_component : public Genode::Rpc_object, Genode::Env &_env; Pci::Config::Delayer &_delayer; + Device_bars_pool &_devices_bars; Device_config _device_config { }; Genode::addr_t _config_space; Config_access _config_access; Platform::Session_component &_session; Irq_session_component *_irq_session = nullptr; unsigned short _irq_line; - bool _enabled_bus_master { false }; + bool _device_used { false }; Genode::Allocator &_global_heap; @@ -89,6 +95,40 @@ class Platform::Device_component : public Genode::Rpc_object, struct Enable : Bitfield<15, 1> { }; }; + struct Pci_express : Pci::Config + { + Pci_express(Device_component &dev, uint16_t cap) + : Pci::Config(dev._config_access, dev._device_config.bdf(), cap) { } + + struct Capabilities : Register<0x04, 32> { + struct Reset : Bitfield< 28, 1> { }; }; + struct Control: Register<0x08, 16> { + struct Reset : Bitfield< 15, 1> { }; }; + struct Status: Register<0x0a, 16> { + struct Pending : Bitfield< 5, 1> { }; }; + struct Capabilities2 : Register<0x24, 32> { + struct Readiness : Bitfield<31, 1> { }; }; + struct Status2 : Register<0x32, 16> { + struct Readiness_status : Bitfield<15, 1> { }; }; + }; + + struct Pci_power: Pci::Config + { + Pci_power(Device_component &dev, uint16_t cap) + : Pci::Config(dev._config_access, dev._device_config.bdf(), cap) { } + + struct Capabilities : Register<0x02, 16> + { + struct Specific_init : Bitfield< 5, 1> { }; + }; + struct Control : Register<0x04, 16> + { + struct D0_3 : Bitfield< 0, 2> { }; + struct No_soft_reset : Bitfield< 3, 1> { }; + }; + }; + + Genode::Tslab _slab_ioport; char _slab_ioport_block_data[IO_BLOCK_SIZE]; @@ -105,7 +145,7 @@ class Platform::Device_component : public Genode::Rpc_object, struct Status : Genode::Register<8> { struct Capabilities : Bitfield<4,1> { }; - inline static access_t read(Genode::uint8_t t) { return t; }; + inline static access_t read(Genode::uint8_t t) { return t; } }; /** @@ -138,19 +178,32 @@ class Platform::Device_component : public Genode::Rpc_object, /** * Read out msi capabilities of the device. */ - Genode::uint16_t _msi_cap() + uint16_t _msi_cap() { enum { CAP_MSI = 0x5 }; return _lookup_cap(CAP_MSI); } - Genode::uint16_t _msix_cap() + uint16_t _msix_cap() { enum { CAP_MSI_X = 0x11 }; return _lookup_cap(CAP_MSI_X); } - Genode::uint16_t _lookup_cap(Genode::uint16_t const target_cap) + uint16_t _power_cap() + { + enum { CAP_POWER = 0x1 }; + return _lookup_cap(CAP_POWER); + } + + /* PCI express cap (not PCI express extended cap!) */ + uint16_t _pcie_cap() + { + enum { CAP_PCIE = 0x10 }; + return _lookup_cap(CAP_PCIE); + } + + uint16_t _lookup_cap(uint16_t const target_cap) { enum { PCI_STATUS = 0x6, PCI_CAP_OFFSET = 0x34 }; @@ -222,13 +275,14 @@ class Platform::Device_component : public Genode::Rpc_object, /** * Disable bus master dma if already enabled. */ - void _disable_bus_master_dma() { - + void _disable_bus_master_dma() + { /* * Disabling a bridge may make the devices behind non-functional, * as we have no driver which will switch it on again */ - if (_device_config.pci_bridge()) + if (_device_config.pci_bridge() || + _device_config.bdf() == Pci::Bdf(Platform::Bridge::root_bridge_bdf)) return; _device_config.disable_bus_master_dma(_config_access); @@ -278,7 +332,128 @@ class Platform::Device_component : public Genode::Rpc_object, addr_t const msix_table = reinterpret_cast(mem_io.local_addr()) + offset; fn(msix_table); - }; + } + + void _device_reset() + { + uint16_t const cap = _pcie_cap(); + if (!cap) + return; + + Pci_express pci_cap(*this, cap); + + if (!pci_cap.read()) + return; + + log(_device_config, " reset function"); + + pci_cap.write(1); + + try { + /* optional use FLR Time if available instead of heuristic */ + pci_cap.wait_for(Pci::Config::Attempts(100), + Pci::Config::Microseconds(10000), _delayer, + Pci_express::Status::Pending::Equal(0)); + } catch (Pci::Config::Polling_timeout) { + warning(_device_config, " reset timeout raised"); + } + } + + void _power_off() + { + /* don't touch unused device */ + if (!_device_used) + return; + + uint16_t const cap = _power_cap(); + if (!cap) { + _disable_bus_master_dma(); + return; + } + + /* + * PCI Power Management - 8.2.2 D3 State + * + * "If the device driver is not capable of fully reinitializing" + * "a function, the operating system should not put the function" + * "into D3" + * + * Actually, at this point we don't know about the capabilities of + * the actual driver. + */ + log(_device_config, " power off"); + + /* + * "When placing a function into D3, the operating system software" + * "is required to disable I/O and memory space as well as bus" + * "mastering via the PCI Command register. + */ + Device_config::Pci_header header (_config_access, _device_config.bdf()); + + auto command = header.read(); + Device_config::Pci_header::Command::Dma::set(command, 0); + Device_config::Pci_header::Command::Memory::set(command, 0); + Device_config::Pci_header::Command::Ioport::set(command, 0); + + header.write(command); + + /* power off */ + Pci_power pci_cap(*this, cap); + pci_cap.write(3); + } + + void _power_on() + { + uint16_t const cap = _power_cap(); + if (!cap) + return; + + Pci_power pci_cap(*this, cap); + + if (pci_cap.read() == 0) + return; + + /* since it was off before, it got used by powering it on */ + _device_used = true; + + log(_device_config, " power on", + pci_cap.read() ? ", no_soft_reset" : "", + pci_cap.read() ? ", specific_init_required" : ""); + + /* power on */ + pci_cap.write(0); + + /* + * PCI Express 4.3 - 5.3.1.4. D3 State + * + * "Unless Readiness Notifications mechanisms are used ..." + * "a minimum recovery time following a D3 hot → D0 transition of" + * "at least 10 ms ..." + */ + _delayer.usleep(10'000); + + /* + * PCI Power Management - 3.2.4 - PMCSR Power Management Control/Status + * + * "no additional operating system intervention is required ..." + * "beyond writing the PowerState" + */ + if (pci_cap.read()) + return; + + _device_reset(); + + _devices_bars.for_each([&](auto const &bars) { + if (!(bars.bdf == _device_config.bdf())) + return; + + _device_config.restore_bars(_config_access, bars); + }); + + /* re-read the resources which set to valid ones after power on */ + _device_config = Device_config(_device_config.bdf(), + &_config_access); + } public: @@ -291,10 +466,12 @@ class Platform::Device_component : public Genode::Rpc_object, Platform::Session_component &session, Genode::Allocator &md_alloc, Genode::Allocator &global_heap, - Pci::Config::Delayer &delayer) + Pci::Config::Delayer &delayer, + Device_bars_pool &devices_bars) : _env(env), _delayer(delayer), + _devices_bars(devices_bars), _device_config(device_config), _config_space(addr), _config_access(config_access), _session(session), @@ -307,6 +484,8 @@ class Platform::Device_component : public Genode::Rpc_object, for (unsigned i = 0; i < Device::NUM_RESOURCES; i++) { _io_port_conn[i] = nullptr; } + + _power_on(); } /** @@ -316,10 +495,12 @@ class Platform::Device_component : public Genode::Rpc_object, Genode::Attached_io_mem_dataspace &pciconf, Platform::Session_component &session, unsigned irq, Genode::Allocator &global_heap, - Pci::Config::Delayer &delayer) + Pci::Config::Delayer &delayer, + Device_bars_pool &devices_bars) : _env(env), _delayer(delayer), + _devices_bars(devices_bars), _config_space(~0UL), _config_access(pciconf), _session(session), @@ -353,8 +534,10 @@ class Platform::Device_component : public Genode::Rpc_object, } } - if (_device_config.valid() && _enabled_bus_master) - _disable_bus_master_dma(); + if (!_device_config.valid()) + return; + + _power_off(); } /**************************************** diff --git a/repos/os/src/drivers/platform/spec/x86/pci_device_config.h b/repos/os/src/drivers/platform/spec/x86/pci_device_config.h index 09e5bdb022..6d55de4f7a 100644 --- a/repos/os/src/drivers/platform/spec/x86/pci_device_config.h +++ b/repos/os/src/drivers/platform/spec/x86/pci_device_config.h @@ -142,6 +142,35 @@ namespace Platform { PCI_CMD_DMA = 0x4, }; + struct Pci_header : Pci::Config + { + Pci_header(Config_access &access, Pci::Bdf const bdf) + : Pci::Config(access, bdf, 0 /* from start */) { } + + struct Command : Register<0x04, 16> + { + struct Ioport : Bitfield< 0, 1> { }; + struct Memory : Bitfield< 1, 1> { }; + struct Dma : Bitfield< 2, 1> { }; + }; + }; + + struct Device_bars { + Pci::Bdf bdf; + uint32_t bar_addr[Device::NUM_RESOURCES] { }; + + bool all_invalid() const { + for (unsigned i = 0; i < Device::NUM_RESOURCES; i++) { + if (bar_addr[i] != 0 && bar_addr[i] != ~0U) + return false; + } + return true; + } + + Device_bars(Pci::Bdf bdf) : bdf(bdf) { } + virtual ~Device_bars() { }; + }; + /** * Constructor */ @@ -305,11 +334,41 @@ namespace Platform { void disable_bus_master_dma(Config_access &pci_config) { - unsigned const cmd = read(pci_config, PCI_CMD_REG, - Platform::Device::ACCESS_16BIT); - if (cmd & PCI_CMD_DMA) - write(pci_config, PCI_CMD_REG, cmd ^ PCI_CMD_DMA, - Platform::Device::ACCESS_16BIT); + Pci_header header (pci_config, _bdf); + + if (header.read()) + header.write(0); + } + + Device_bars save_bars() + { + Device_bars bars (_bdf); + + for (unsigned r = 0; r < Device::NUM_RESOURCES; r++) { + if (!_resource_id_is_valid(r)) + break; + + bars.bar_addr[r] = _resource[r].base(); + } + + return bars; + }; + + void restore_bars(Config_access &config, Device_bars const &bars) + { + for (unsigned r = 0; r < Device::NUM_RESOURCES; r++) { + if (!_resource_id_is_valid(r)) + break; + + /* index of base-address register in configuration space */ + unsigned const bar_idx = 0x10 + 4 * r; + + /* PCI protocol to write address after requesting size */ + config.write(_bdf, bar_idx, ~0U, Device::ACCESS_32BIT); + config.read (_bdf, bar_idx, Device::ACCESS_32BIT); + config.write(_bdf, bar_idx, bars.bar_addr[r], + Device::ACCESS_32BIT); + } } }; diff --git a/repos/os/src/drivers/platform/spec/x86/pci_session_component.h b/repos/os/src/drivers/platform/spec/x86/pci_session_component.h index ecb4e83b0c..657ae27990 100644 --- a/repos/os/src/drivers/platform/spec/x86/pci_session_component.h +++ b/repos/os/src/drivers/platform/spec/x86/pci_session_component.h @@ -136,7 +136,7 @@ class Platform::Pci_buses Genode::Bit_array _valid { }; - void scan_bus(Config_access &config_access, Genode::Allocator &heap, + void scan_bus(Config_access &, Genode::Allocator &, Device_bars_pool &, unsigned char bus = 0); bool _bus_valid(int bus) @@ -149,10 +149,12 @@ class Platform::Pci_buses public: - Pci_buses(Genode::Allocator &heap, Genode::Attached_io_mem_dataspace &pciconf) + Pci_buses(Genode::Allocator &heap, + Genode::Attached_io_mem_dataspace &pciconf, + Device_bars_pool &devices_bars) { Config_access c(pciconf); - scan_bus(c, heap); + scan_bus(c, heap, devices_bars); } /** @@ -217,6 +219,7 @@ class Platform::Session_component : public Genode::Rpc_object Platform::Pci_buses &_pci_bus; Genode::Heap &_global_heap; Pci::Config::Delayer &_delayer; + Device_bars_pool &_devices_bars; bool _iommu; /** @@ -480,6 +483,7 @@ class Platform::Session_component : public Genode::Rpc_object Platform::Pci_buses &buses, Genode::Heap &global_heap, Pci::Config::Delayer &delayer, + Device_bars_pool &devices_bars, char const *args, bool const iommu) : @@ -493,6 +497,7 @@ class Platform::Session_component : public Genode::Rpc_object _pci_bus(buses), _global_heap(global_heap), _delayer(delayer), + _devices_bars(devices_bars), _iommu(iommu) { /* subtract the RPC session and session dataspace capabilities */ @@ -701,7 +706,8 @@ class Platform::Session_component : public Genode::Rpc_object */ Device_component * dev = new (_md_alloc) Device_component(_env, config, config_space, config_access, - *this, _md_alloc, _global_heap, _delayer); + *this, _md_alloc, _global_heap, _delayer, + _devices_bars); _device_list.insert(dev); @@ -827,6 +833,7 @@ class Platform::Root : public Genode::Root_component Genode::Heap _heap { _env.ram(), _env.rm() }; + Device_bars_pool _devices_bars { }; Genode::Constructible _buses { }; bool _iommu { false }; @@ -988,7 +995,7 @@ class Platform::Root : public Genode::Root_component /* try surviving wrong ACPI ECAM/MMCONF table information */ while (true) { try { - _buses.construct(_heap, *_pci_confspace); + _buses.construct(_heap, *_pci_confspace, _devices_bars); /* construction and scan succeeded */ break; } catch (Platform::Config_access::Invalid_mmio_access) { @@ -1024,7 +1031,8 @@ class Platform::Root : public Genode::Root_component try { return new (md_alloc()) Session_component(_env, _config, *_pci_confspace, *_buses, - _heap, _delayer, args, _iommu); + _heap, _delayer, _devices_bars, args, + _iommu); } catch (Genode::Session_policy::No_policy_defined) { Genode::error("Invalid session request, no matching policy for ", diff --git a/repos/os/src/drivers/platform/spec/x86/session.cc b/repos/os/src/drivers/platform/spec/x86/session.cc index 6005f03aa0..a0298d8abe 100644 --- a/repos/os/src/drivers/platform/spec/x86/session.cc +++ b/repos/os/src/drivers/platform/spec/x86/session.cc @@ -39,7 +39,9 @@ unsigned short Platform::bridge_bdf(unsigned char bus) } void Platform::Pci_buses::scan_bus(Config_access &config_access, - Genode::Allocator &heap, unsigned char bus) + Genode::Allocator &heap, + Device_bars_pool &devices_bars, + unsigned char bus) { for (int dev = 0; dev < Device_config::MAX_DEVICES; ++dev) { for (int fun = 0; fun < Device_config::MAX_FUNCTIONS; ++fun) { @@ -47,6 +49,13 @@ void Platform::Pci_buses::scan_bus(Config_access &config_access, /* read config space */ Device_config config(Pci::Bdf(bus, dev, fun), &config_access); + /* remember Device BARs required after power off and/or reset */ + if (config.valid()) { + Device_config::Device_bars bars = config.save_bars(); + if (!bars.all_invalid()) + new (heap) Registered(devices_bars, bars); + } + /* * Switch off PCI bus master DMA for some classes of devices, * which caused trouble. @@ -112,7 +121,7 @@ void Platform::Pci_buses::scan_bus(Config_access &config_access, Hex(sec_bus, Hex::Prefix::OMIT_PREFIX, Hex::Pad::PAD), ":00.0", !enabled ? " enabled" : ""); - scan_bus(config_access, heap, sec_bus); + scan_bus(config_access, heap, devices_bars, sec_bus); } } } diff --git a/repos/os/src/test/pci/test.cc b/repos/os/src/test/pci/test.cc index 451aedc32a..69029a815c 100644 --- a/repos/os/src/test/pci/test.cc +++ b/repos/os/src/test/pci/test.cc @@ -44,6 +44,7 @@ enum { }; enum { + CAP_PWRM = 0x01, CAP_MSI = 0x05, CAP_HT = 0x08, CAP_SECDEV = 0x0f, @@ -189,6 +190,7 @@ static void print_device_info(Platform::Device_capability device_cap) case CAP_MSIX: cap_string = String<128>(cap_string, " MSI-X"); break; case CAP_PCI_E: cap_string = String<128>(cap_string, " PCI-E"); break; case CAP_SECDEV: cap_string = String<128>(cap_string, " SECURE-DEVICE"); break; + case CAP_PWRM: cap_string = String<128>(cap_string, " PWRM"); break; default: cap_string = String<128>(cap_string, " ", Hex(type)); }