genode/repos/os/src/drivers/platform/spec/x86/pci_device.cc
Reto Buerki 47724c68c2 platform_drv/x86: Switch to ECAM/MMCONF
Switch port I/O based PCI config space access to memory-mapped IO.  The
base address of the PCI configuration space is acquired by mapping the
ACPI ROM and reading the first <bdf> node. An exception is thrown if the
first <bdf> node is not for PCI domain zero or if multiple <bdf> nodes
exist. This is to reduce complexity and also because multiple PCI
domains are rare.

The PCI configuration space is accessed via I/O mem dataspace which is
created in the platform_drv root and then passed on to the PCI session,
device components and finally to the actual PCI config access instances.

The memory access code is implemented in a way to make it work with Muen
subject monitor (SM) device emulation and also general x86 targets. On
Muen, the simplified device emulation code (which works also for Linux)
always returns 0xffff in EAX to indicate a non-existing device.
Therefore, EAX is enforced in the assembly templates.

Fixes #2547
2018-03-29 14:59:04 +02:00

230 lines
7.5 KiB
C++

/*
* \brief PCI device component implementation
* \author Alexander Boettcher
*/
/*
* Copyright (C) 2015-2017 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.
*/
#include "pci_session_component.h"
#include "pci_device_component.h"
Genode::Io_port_session_capability Platform::Device_component::io_port(Genode::uint8_t v_id)
{
Genode::uint8_t max = sizeof(_io_port_conn) / sizeof(_io_port_conn[0]);
Genode::uint8_t i = 0, r_id = 0;
for (Resource res = resource(0); i < max; i++, res = resource(i))
{
if (res.type() != Resource::IO)
continue;
if (v_id != r_id) {
r_id ++;
continue;
}
if (_io_port_conn[v_id] != nullptr)
return _io_port_conn[v_id]->cap();
try {
_io_port_conn[v_id] = new (_slab_ioport) Genode::Io_port_connection(_env, res.base(), res.size());
return _io_port_conn[v_id]->cap();
} catch (...) {
return Genode::Io_port_session_capability();
}
}
return Genode::Io_port_session_capability();
}
Genode::Io_mem_session_capability Platform::Device_component::io_mem(Genode::uint8_t const v_id,
Genode::Cache_attribute const caching,
Genode::addr_t const offset,
Genode::size_t const size)
{
Genode::uint8_t max = sizeof(_io_mem) / sizeof(_io_mem[0]);
Genode::uint8_t i = 0, r_id = 0;
for (Resource res = resource(0); i < max; i++, res = resource(i))
{
if (res.type() != Resource::MEMORY)
continue;
if (v_id != r_id) {
r_id ++;
continue;
}
/* limit IO_MEM session size to resource size */
Genode::size_t const res_size = Genode::min(size, res.size());
if (offset >= res.size() || offset > res.size() - res_size)
return Genode::Io_mem_session_capability();
try {
bool const wc = caching == Genode::Cache_attribute::WRITE_COMBINED;
Io_mem * io_mem = new (_slab_iomem) Io_mem(_env,
res.base() + offset,
res_size, wc);
_io_mem[i].insert(io_mem);
return io_mem->cap();
}
catch (Genode::Out_of_caps) {
Genode::warning("Out_of_caps in Device_component::io_mem");
throw;
}
catch (Genode::Out_of_ram) {
Genode::warning("Out_of_ram in Device_component::io_mem");
throw;
}
catch (...) {
Genode::warning("unhandled exception in 'Device_component::io_mem'");
return Genode::Io_mem_session_capability();
}
}
return Genode::Io_mem_session_capability();
}
void Platform::Device_component::config_write(unsigned char address,
unsigned value,
Access_size size)
{
/* white list of ports which we permit to write */
switch (address) {
case 0x40 ... 0xff:
/* allow access to device-specific registers if not used by us */
if (!_device_config.reg_in_use(_config_access, address, size))
break;
Genode::error(_device_config, " write access to "
"address=", Genode::Hex(address), " "
"value=", Genode::Hex(value), " "
"size=", Genode::Hex(size), " "
"denied - it is used by the platform driver.");
return;
case PCI_CMD_REG: /* COMMAND register - first byte */
if (size == Access_size::ACCESS_16BIT)
break;
case PCI_CMD_REG + 1: /* COMMAND register - second byte */
case 0xd: /* Latency timer */
if (size == Access_size::ACCESS_8BIT)
break;
default:
Genode::warning(_device_config, " write access to "
"address=", Genode::Hex(address), " "
"value=", Genode::Hex(value), " "
"size=", Genode::Hex(size), " "
"got dropped");
return;
}
/* assign device to device_pd */
if (address == PCI_CMD_REG && value & PCI_CMD_DMA) {
try { _session.assign_device(this); }
catch (Out_of_ram) { throw; }
catch (Out_of_caps) { throw; }
catch (...) {
Genode::error("assignment to device failed");
}
}
_device_config.write(_config_access, address, value, size,
_device_config.DONT_TRACK_ACCESS);
}
Genode::Irq_session_capability Platform::Device_component::irq(Genode::uint8_t id)
{
if (id != 0)
return Genode::Irq_session_capability();
if (_irq_session)
return _irq_session->cap();
using Genode::construct_at;
if (!_device_config.valid()) {
/* Non PCI devices */
_irq_session = construct_at<Irq_session_component>(_mem_irq_component,
_irq_line, ~0UL,
_env,
_global_heap);
_env.ep().rpc_ep().manage(_irq_session);
return _irq_session->cap();
}
_irq_session = construct_at<Irq_session_component>(_mem_irq_component,
_configure_irq(_irq_line),
(!_session.msi_usage() || !_msi_cap()) ? ~0UL : _config_space,
_env, _global_heap);
_env.ep().rpc_ep().manage(_irq_session);
Genode::uint16_t msi_cap = _msi_cap();
if (_irq_session->msi()) {
Genode::addr_t msi_address = _irq_session->msi_address();
Genode::uint32_t msi_value = _irq_session->msi_data();
Genode::uint16_t msi = _device_config.read(_config_access,
msi_cap + 2,
Platform::Device::ACCESS_16BIT);
_device_config.write(_config_access, msi_cap + 0x4, msi_address,
Platform::Device::ACCESS_32BIT);
if (msi & CAP_MSI_64) {
Genode::uint32_t upper_address = sizeof(msi_address) > 4
? (Genode::uint64_t)msi_address >> 32
: 0UL;
_device_config.write(_config_access, msi_cap + 0x8,
upper_address,
Platform::Device::ACCESS_32BIT);
_device_config.write(_config_access, msi_cap + 0xc,
msi_value,
Platform::Device::ACCESS_16BIT);
}
else
_device_config.write(_config_access, msi_cap + 0x8, msi_value,
Platform::Device::ACCESS_16BIT);
/* enable MSI */
_device_config.write(_config_access, msi_cap + 2,
msi ^ MSI_ENABLED,
Platform::Device::ACCESS_8BIT);
}
bool msi_64 = false;
bool msi_mask = false;
if (msi_cap) {
Genode::uint16_t msi = _device_config.read(_config_access,
msi_cap + 2,
Platform::Device::ACCESS_16BIT);
msi_64 = msi & CAP_MSI_64;
msi_mask = msi & CAP_MASK;
}
if (_irq_session->msi())
Genode::log(_device_config, " uses ",
"MSI ", (msi_64 ? "64bit" : "32bit"), ", "
"vector ", Genode::Hex(_irq_session->msi_data()), ", "
"address ", Genode::Hex(_irq_session->msi_address()), ", ",
(msi_mask ? "maskable" : "non-maskable"));
else
Genode::log(_device_config, " uses IRQ, vector ",
Genode::Hex(_irq_line),
(msi_cap ? (msi_64 ? ", MSI 64bit capable"
: ", MSI 32bit capable")
: ""),
(msi_mask ? ", maskable" : ", non-maskable"));
return _irq_session->cap();
}