platform_drv: implement PCI powering and reset

Ref genodelabs/genode#4578
This commit is contained in:
Stefan Kalkowski 2022-10-10 15:17:55 +02:00 committed by Christian Helmuth
parent 8f0a012345
commit bc1e231775
10 changed files with 161 additions and 27 deletions

View File

@ -97,6 +97,7 @@ append config {
</provides>
<route>
<service name="ROM" label="devices"> <child name="report_rom"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<service name="IRQ"> <parent/> </service>
<service name="IO_MEM"> <parent/> </service>
<service name="ROM"> <parent/> </service>

View File

@ -85,6 +85,7 @@ install_config {
</provides>
<route>
<service name="ROM" label="devices"> <child name="report_rom"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<service name="IRQ"> <parent/> </service>
<service name="IO_MEM"> <parent/> </service>
<service name="ROM"> <parent/> </service>

View File

@ -106,18 +106,23 @@ struct Pci::Config : Genode::Mmio
Bar_32bit::access_t _conf_value { 0 };
template <typename REG>
typename REG::access_t _get_and_set(typename REG::access_t value)
{
write<REG>(0xffffffff);
typename REG::access_t ret = read<REG>();
write<REG>(value);
return ret;
}
Bar_32bit::access_t _conf()
{
/*
* Initialize _conf_value on demand only to prevent read-write
* operations on BARs of invalid devices at construction time.
*/
if (!_conf_value) {
Bar_32bit::access_t v = read<Bar_32bit>();
write<Bar_32bit>(0xffffffff);
_conf_value = read<Bar_32bit>();
write<Bar_32bit>(v);
}
if (!_conf_value)
_conf_value = _get_and_set<Bar_32bit>(read<Bar_32bit>());
return _conf_value;
}
@ -152,6 +157,19 @@ struct Pci::Config : Genode::Mmio
else
return Bar_32bit::Io_base::masked(read<Bar_32bit>());
}
void set(Genode::uint64_t v)
{
if (!valid() || v == addr())
return;
if (memory()) {
if (bit64())
_get_and_set<Upper_bits>((Upper_bits::access_t)(v >> 32));
_get_and_set<Bar_32bit>(Bar_32bit::Memory_base::masked(v & ~0U));
} else
_get_and_set<Bar_32bit>(Bar_32bit::Io_base::masked(v & ~0U));
}
};
enum Base_addresses {
@ -204,14 +222,53 @@ struct Pci::Config : Genode::Mmio
struct Power_management_capability : Pci_capability
{
struct Capabilities : Register<0x2, 16> {};
struct Capabilities : Register<0x2, 16> {};
struct Control_status : Register<0x4, 16>
{
struct Pme_status : Bitfield<15,1> {};
struct Power_state : Bitfield<0, 2>
{
enum { D0, D1, D2, D3 };
};
struct No_soft_reset : Bitfield<3, 1> {};
struct Pme_status : Bitfield<15,1> {};
};
struct Data : Register<0x7, 8> {};
struct Data : Register<0x7, 8> {};
using Pci_capability::Pci_capability;
bool power_on(Delayer & delayer)
{
using Reg = Control_status::Power_state;
if (read<Reg>() == Reg::D0)
return false;
write<Reg>(Reg::D0);
/*
* 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);
return true;
}
void power_off()
{
using Reg = Control_status::Power_state;
if (read<Reg>() != Reg::D3) write<Reg>(Reg::D3);
}
bool soft_reset()
{
return !read<Control_status::No_soft_reset>();
}
};
@ -310,11 +367,19 @@ struct Pci::Config : Genode::Mmio
struct Pci_express_capability : Pci_capability
{
struct Capabilities : Register<0x2, 16> {};
struct Device_capabilities : Register<0x4, 32> {};
struct Device_control : Register<0x8, 16> {};
struct Capabilities : Register<0x2, 16> {};
struct Device_status : Register<0xa, 16>
struct Device_capabilities : Register<0x4, 32>
{
struct Function_level_reset : Bitfield<28,1> {};
};
struct Device_control : Register<0x8, 16>
{
struct Function_level_reset : Bitfield<15,1> {};
};
struct Device_status : Register<0xa, 16>
{
struct Correctable_error : Bitfield<0, 1> {};
struct Non_fatal_error : Bitfield<1, 1> {};
@ -392,6 +457,17 @@ struct Pci::Config : Genode::Mmio
write<Link_status::Lbm_status>(1);
write<Link_control::Lbm_irq_enable>(1);
}
void reset(Delayer & delayer)
{
if (!read<Device_capabilities::Function_level_reset>())
return;
write<Device_control::Function_level_reset>(1);
try {
wait_for(Attempts(100), Microseconds(10000), delayer,
Device_status::Transactions_pending::Equal(0));
} catch(Polling_timeout) { }
}
};
@ -545,6 +621,29 @@ struct Pci::Config : Genode::Mmio
io(reg0.addr(), reg0.size(), i);
}
};
void set_bar_address(unsigned idx, Genode::uint64_t addr)
{
if (idx > 5 || (idx > 1 && bridge()))
return;
Base_address bar { base() + BASE_ADDRESS_0 + idx*0x4 };
bar.set(addr);
}
void power_on(Delayer & delayer)
{
if (!power_cap.constructed() || !power_cap->power_on(delayer))
return;
if (power_cap->soft_reset() && pci_e_cap.constructed())
pci_e_cap->reset(delayer);
}
void power_off()
{
if (power_cap.constructed()) power_cap->power_off();
}
};

View File

@ -143,7 +143,7 @@ append config {
<service name="PD"> <parent/> </service>
<service name="CPU"> <parent/> </service>
<service name="LOG"> <parent/> </service>
<service name="Timer"> <parent/> </service>
<service name="Timer"> <child name="timer"/> </service>
</route>
<config>
<report devices="yes"/>

View File

@ -239,7 +239,7 @@ class Driver::Device : private List_model<Device>::Element
{
unsigned idx = 0;
_io_port_range_list.for_each([&] (Io_port_range const & ipr) {
fn(idx++, ipr.range); });
fn(idx++, ipr.range, ipr.bar); });
}
template <typename FN> void for_pci_config(FN const & fn) const

View File

@ -190,7 +190,8 @@ Device_component::Device_component(Registry<Device_component> & registry,
new (session.heap()) Io_mem(_io_mem_registry, bar, idx, range, pf);
});
device.for_each_io_port_range([&] (unsigned idx, Io_port_range::Range range)
device.for_each_io_port_range([&] (unsigned idx, Io_port_range::Range range,
Device::Pci_bar)
{
session.ram_quota_guard().withdraw(Ram_quota{Io_port_session::RAM_QUOTA});
_ram_quota += Io_port_session::RAM_QUOTA;

View File

@ -12,6 +12,7 @@
*/
#include <base/attached_io_mem_dataspace.h>
#include <timer_session/connection.h>
#include <pci/config.h>
#include <util/reconstructible.h>
@ -29,6 +30,20 @@ using namespace Genode;
using namespace Pci;
static Config::Delayer & delayer(Env & env)
{
struct Delayer : Config::Delayer, Timer::Connection
{
using Timer::Connection::Connection;
void usleep(uint64_t us) override {
return Timer::Connection::usleep(us); }
};
static Delayer delayer(env);
return delayer;
};
struct Config_helper
{
Env & _env;
@ -41,28 +56,39 @@ struct Config_helper
Config_helper(Env & env,
Driver::Device const & dev,
Driver::Device::Pci_config const & cfg)
: _env(env), _dev(dev), _cfg(cfg) { }
: _env(env), _dev(dev), _cfg(cfg) { _config.scan(); }
void enable(Driver::Device_pd & pd)
{
pd.assign_pci(_io_mem.cap(),
{ _cfg.bus_num, _cfg.dev_num, _cfg.func_num });
_config.power_on(delayer(_env));
Config::Command::access_t cmd =
_config.read<Config::Command>();
/* always allow DMA operations */
Config::Command::Bus_master_enable::set(cmd, 1);
/* enable memory space when I/O mem is defined */
_dev.for_each_io_mem([&] (unsigned, Driver::Device::Io_mem::Range,
Driver::Device::Pci_bar, bool) {
Config::Command::Memory_space_enable::set(cmd, 1); });
_dev.for_each_io_mem([&] (unsigned, Driver::Device::Io_mem::Range r,
Driver::Device::Pci_bar b, bool)
{
_config.set_bar_address(b.number, r.start);
/* enable i/o space when I/O ports are defined */
_dev.for_each_io_port_range(
[&] (unsigned, Driver::Device::Io_port_range::Range) {
Config::Command::Io_space_enable::set(cmd, 1); });
/* enable memory space when I/O mem is defined */
Config::Command::Memory_space_enable::set(cmd, 1);
});
_dev.for_each_io_port_range([&] (unsigned,
Driver::Device::Io_port_range::Range r,
Driver::Device::Pci_bar b)
{
_config.set_bar_address(b.number, r.addr);
/* enable i/o space when I/O ports are defined */
Config::Command::Io_space_enable::set(cmd, 1);
});
_config.write<Config::Command>(cmd);
}
@ -76,6 +102,8 @@ struct Config_helper
Config::Command::Bus_master_enable::set(cmd, 0);
Config::Command::Interrupt_enable::set(cmd, 0);
_config.write<Config::Command>(cmd);
_config.power_off();
}
void apply_quirks()
@ -91,7 +119,8 @@ struct Config_helper
/* enable i/o space when I/O ports are defined */
_dev.for_each_io_port_range(
[&] (unsigned, Driver::Device::Io_port_range::Range) {
[&] (unsigned, Driver::Device::Io_port_range::Range,
Driver::Device::Pci_bar) {
Config::Command::Io_space_enable::set(cmd, 1); });
_config.write<Config::Command>(cmd);

View File

@ -51,7 +51,8 @@ void Driver::pci_uhci_quirks(Env & env,
/* find uhci controller i/o ports */
Device::Io_port_range::Range range { 0, 0 };
dev.for_each_io_port_range([&] (unsigned, Device::Io_port_range::Range r) {
dev.for_each_io_port_range([&] (unsigned, Device::Io_port_range::Range r,
Device::Pci_bar) {
if (!range.size) range = r; });
Io_port_connection io_ports(env, range.addr, range.size);

View File

@ -120,6 +120,7 @@ append config {
<service name="PD"> <parent/> </service>
<service name="CPU"> <parent/> </service>
<service name="LOG"> <parent/> </service>
<service name="Timer"> <child name="timer"/> </service>
</route>
<config>
<policy label_prefix="intel_fb_drv" info="yes">

View File

@ -160,6 +160,7 @@ append config {
<provides> <service name="Platform"/> </provides>
<route>
<service name="ROM" label="devices"> <child name="report_rom"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<service name="IRQ"> <parent/> </service>
<service name="IO_MEM"> <parent/> </service>
<service name="IO_PORT"> <parent/> </service>