platform_drv: add support to power PCI devices

- depending on available PCI power cap power off and on
- save and restore PCI bars if required
- reset PCI devices after power on if supported

Fixes #3963
This commit is contained in:
Alexander Boettcher 2020-12-03 15:26:41 +01:00 committed by Norman Feske
parent 5f7fe7498f
commit b185f3fac1
7 changed files with 299 additions and 34 deletions

View File

@ -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();

View File

@ -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,

View File

@ -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<Registered<Device_config::Device_bars> > Device_bars_pool;
}
class Platform::Device_component : public Genode::Rpc_object<Platform::Device>,
private Genode::List<Device_component>::Element
@ -45,13 +50,14 @@ class Platform::Device_component : public Genode::Rpc_object<Platform::Device>,
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<Platform::Device>,
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<Genode::Io_port_connection, IO_BLOCK_SIZE> _slab_ioport;
char _slab_ioport_block_data[IO_BLOCK_SIZE];
@ -105,7 +145,7 @@ class Platform::Device_component : public Genode::Rpc_object<Platform::Device>,
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<Platform::Device>,
/**
* 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<Platform::Device>,
/**
* 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<Platform::Device>,
addr_t const msix_table = reinterpret_cast<addr_t>(mem_io.local_addr<void>()) + 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<Pci_express::Capabilities::Reset>())
return;
log(_device_config, " reset function");
pci_cap.write<Pci_express::Control::Reset>(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>();
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<Device_config::Pci_header::Command>(command);
/* power off */
Pci_power pci_cap(*this, cap);
pci_cap.write<Pci_power::Control::D0_3>(3);
}
void _power_on()
{
uint16_t const cap = _power_cap();
if (!cap)
return;
Pci_power pci_cap(*this, cap);
if (pci_cap.read<Pci_power::Control::D0_3>() == 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<Pci_power::Control::No_soft_reset>() ? ", no_soft_reset" : "",
pci_cap.read<Pci_power::Capabilities::Specific_init>() ? ", specific_init_required" : "");
/* power on */
pci_cap.write<Pci_power::Control::D0_3>(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<Pci_power::Control::No_soft_reset>())
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::Device>,
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<Platform::Device>,
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<Platform::Device>,
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<Platform::Device>,
}
}
if (_device_config.valid() && _enabled_bus_master)
_disable_bus_master_dma();
if (!_device_config.valid())
return;
_power_off();
}
/****************************************

View File

@ -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<Pci_header::Command::Dma>())
header.write<Pci_header::Command::Dma>(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);
}
}
};

View File

@ -136,7 +136,7 @@ class Platform::Pci_buses
Genode::Bit_array<Device_config::MAX_BUSES> _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<Session>
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<Session>
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<Session>
_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<Session>
*/
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<Session_component>
Genode::Heap _heap { _env.ram(), _env.rm() };
Device_bars_pool _devices_bars { };
Genode::Constructible<Platform::Pci_buses> _buses { };
bool _iommu { false };
@ -988,7 +995,7 @@ class Platform::Root : public Genode::Root_component<Session_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<Session_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 ",

View File

@ -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<Device_config::Device_bars>(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);
}
}
}

View File

@ -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));
}