pci: add msi support to platform driver

Fixes #1216
This commit is contained in:
Alexander Boettcher 2015-04-27 11:59:58 +02:00 committed by Christian Helmuth
parent d998df3b7f
commit ecc9007e84
4 changed files with 324 additions and 101 deletions

View File

@ -82,14 +82,19 @@ namespace Irq {
if (!args.is_valid_string()) throw Root::Invalid_args();
long irq_number = Arg_string::find_arg(args.string(), "irq_number").long_value(-1);
long msi = Arg_string::find_arg(args.string(), "device_config_phys").long_value(0);
/* check for 'MADT' overrides */
unsigned mode;
irq_number = Acpi::override(irq_number, &mode);
long irq_legacy = Acpi::override(irq_number, &mode);
/* rewrite IRQ solely if this is not a MSI request */
if (!msi)
irq_number = irq_legacy;
/* allocate IRQ at parent*/
try {
Irq_connection irq(irq_number, _mode2trigger(mode), _mode2polarity(mode));
Irq_connection irq(irq_number, _mode2trigger(mode),
_mode2polarity(mode), msi);
irq.on_destruction(Irq_connection::KEEP_OPEN);
return irq.cap();
} catch (...) { throw Root::Unavailable(); }

View File

@ -29,37 +29,65 @@ namespace Pci {
}
using namespace Genode;
using Genode::size_t;
using Genode::addr_t;
/**
* A simple range allocator implementation used by the Irq_proxy
*/
class Pci::Irq_allocator : public Range_allocator, Bit_allocator<256>
class Pci::Irq_allocator : public Genode::Range_allocator
{
Alloc_return alloc_addr(size_t size, addr_t addr) override
{
try {
_array.set(addr, size);
return Alloc_return::OK;
} catch (...) {
return Alloc_return::RANGE_CONFLICT;
private:
/*
* We partition the IRQ space (128 GSIs) into 40 legacy IRQs and 64 MSIs (and
* hope the partitions will never overlap on any bizarre platform.)
*/
enum { LEGACY = 40, MSI = 64, LEGACY_ARRAY = 64 };
Genode::Bit_array<LEGACY_ARRAY> _legacy;
Genode::Bit_allocator<MSI> _msi;
public:
Irq_allocator()
{
/* reserve the last 24 legacy IRQs - 40 IRQs remaining */
_legacy.set(LEGACY, LEGACY_ARRAY - LEGACY);
}
}
/* unused methods */
int remove_range(addr_t, size_t) override { return 0; }
int add_range(addr_t, size_t) override { return 0; }
bool valid_addr(addr_t) const override { return false; }
size_t avail() const override { return 0; }
bool alloc(size_t, void **) override { return false; }
void free(void *) override { }
void free(void *, size_t) override { }
size_t overhead(size_t) const override { return 0; }
bool need_size_for_free() const override { return 0; }
unsigned alloc_msi()
{
try {
return _msi.alloc();
} catch (Genode::Bit_allocator<MSI>::Out_of_indices) { return ~0U; }
}
Alloc_return alloc_aligned(size_t, void **, int, addr_t, addr_t) override {
return Alloc_return::RANGE_CONFLICT; }
void free_msi(unsigned msi) { _msi.free(msi); }
Alloc_return alloc_addr(size_t size, addr_t addr) override
{
try {
_legacy.set(addr, size);
return Alloc_return::OK;
} catch (...) {
return Alloc_return::RANGE_CONFLICT;
}
}
/* unused methods */
int remove_range(addr_t, size_t) override { return 0; }
int add_range(addr_t, size_t) override { return 0; }
bool valid_addr(addr_t) const override { return false; }
size_t avail() const override { return 0; }
bool alloc(size_t, void **) override { return false; }
void free(void *) override { }
void free(void *, size_t) override { }
size_t overhead(size_t) const override { return 0; }
bool need_size_for_free() const override { return 0; }
Alloc_return alloc_aligned(size_t, void **, int, addr_t, addr_t) override {
return Alloc_return::RANGE_CONFLICT; }
};
@ -84,13 +112,13 @@ class Pci::Irq_thread : public Genode::Thread<4096>
{
private:
Signal_receiver _sig_rec;
Genode::Signal_receiver _sig_rec;
public:
Irq_thread() : Thread<4096>("irq_sig_recv") { start(); }
Signal_receiver & sig_rec() { return _sig_rec; }
Genode::Signal_receiver & sig_rec() { return _sig_rec; }
void entry() {
@ -154,7 +182,7 @@ class Pci::Irq_component : public Proxy
bool _associate() { return _associated; }
void _wait_for_irq() { }
virtual bool remove_sharer(Irq_sigh *s) override {
virtual bool remove_sharer(Genode::Irq_sigh *s) override {
if (!Proxy::remove_sharer(s))
return false;
@ -182,6 +210,13 @@ class Pci::Irq_component : public Proxy
void Pci::Irq_session_component::ack_irq()
{
if (msi()) {
Genode::Irq_session_client irq_msi(_irq_cap);
irq_msi.ack_irq();
return;
}
/* shared irq handling */
Irq_component *irq_obj = Proxy::get_irq_proxy<Irq_component>(_gsi);
if (!irq_obj) {
PERR("Expected to find IRQ proxy for IRQ %02x", _gsi);
@ -193,26 +228,63 @@ void Pci::Irq_session_component::ack_irq()
}
Pci::Irq_session_component::Irq_session_component(unsigned irq) : _gsi(irq)
Pci::Irq_session_component::Irq_session_component(unsigned irq,
addr_t pci_config_space)
:
_gsi(irq)
{
bool valid = false;
/* invalid irq number for pci devices */
if (irq == 0xFF)
/* invalid irq number for pci_devices */
if (irq >= INVALID_IRQ)
return;
if (pci_config_space != ~0UL) {
/* msi way */
unsigned msi = irq_alloc.alloc_msi();
if (msi != ~0U) {
try {
using namespace Genode;
Irq_connection conn(msi, Irq_session::TRIGGER_UNCHANGED,
Irq_session::POLARITY_UNCHANGED,
pci_config_space);
_msi_info = conn.info();
if (_msi_info.type == Irq_session::Info::Type::MSI) {
_gsi = msi;
_irq_cap = conn;
conn.on_destruction(Irq_connection::KEEP_OPEN);
return;
}
} catch (Genode::Parent::Service_denied) { }
irq_alloc.free_msi(msi);
}
}
try {
/* check if IRQ object was used before */
valid = Proxy::get_irq_proxy<Irq_component>(_gsi, &irq_alloc);
/* check if shared IRQ object was used before */
if (Proxy::get_irq_proxy<Irq_component>(_gsi, &irq_alloc))
return;
} catch (Genode::Parent::Service_denied) { }
if (!valid)
PERR("unavailable IRQ object 0x%x requested", _gsi);
PERR("unavailable IRQ 0x%x requested", _gsi);
}
Pci::Irq_session_component::~Irq_session_component()
{
if (msi()) {
Genode::Irq_session_client irq_msi(_irq_cap);
irq_msi.sigh(Genode::Signal_context_capability());
Genode::env()->parent()->close(_irq_cap);
irq_alloc.free_msi(_gsi);
return;
}
/* shared irq handling */
Irq_component *irq_obj = Proxy::get_irq_proxy<Irq_component>(_gsi);
if (!irq_obj) return;
@ -223,6 +295,14 @@ Pci::Irq_session_component::~Irq_session_component()
void Pci::Irq_session_component::sigh(Genode::Signal_context_capability sigh)
{
if (msi()) {
/* register signal handler for msi directly at parent */
Genode::Irq_session_client irq_msi(_irq_cap);
irq_msi.sigh(sigh);
return;
}
/* shared irq handling */
Irq_component *irq_obj = Proxy::get_irq_proxy<Irq_component>(_gsi);
if (!irq_obj) {
PERR("signal handler got not registered - irq object unavailable");

View File

@ -32,14 +32,29 @@ class Pci::Irq_session_component : public Genode::Rpc_object<Genode::Irq_session
{
private:
unsigned _gsi;
Genode::Irq_sigh _irq_sigh;
unsigned _gsi;
Genode::Irq_sigh _irq_sigh;
Genode::Irq_session_capability _irq_cap;
Genode::Irq_session::Info _msi_info;
public:
Irq_session_component(unsigned);
enum { INVALID_IRQ = 0xffU };
Irq_session_component(unsigned, Genode::addr_t);
~Irq_session_component();
bool msi()
{
return _irq_cap.valid() &&
_msi_info.type == Genode::Irq_session::Info::Type::MSI;
}
unsigned gsi() { return _gsi; }
unsigned long msi_address() const { return _msi_info.address; }
unsigned long msi_data() const { return _msi_info.value; }
/***************************
** Irq session interface **
***************************/

View File

@ -14,15 +14,17 @@
#ifndef _PCI_DEVICE_COMPONENT_H_
#define _PCI_DEVICE_COMPONENT_H_
#include <pci_device/pci_device.h>
#include <util/list.h>
/* base */
#include <base/rpc_server.h>
#include <base/printf.h>
#include <io_mem_session/connection.h>
#include <util/list.h>
#include <util/mmio.h>
/* os */
#include <pci_device/pci_device.h>
/* local */
#include "pci_device_config.h"
#include "irq.h"
namespace Pci { class Device_component; class Session_component; }
@ -32,19 +34,25 @@ class Pci::Device_component : public Genode::Rpc_object<Pci::Device>,
{
private:
Device_config _device_config;
Genode::addr_t _config_space;
Genode::Io_mem_connection *_io_mem;
Config_access _config_access;
Genode::Rpc_entrypoint *_ep;
Pci::Session_component *_session;
Irq_session_component _irq_session;
bool _rewrite_irq_line;
Device_config _device_config;
Genode::addr_t _config_space;
Genode::Io_mem_session_capability _io_mem_config_extended;
Config_access _config_access;
Genode::Rpc_entrypoint *_ep;
Pci::Session_component *_session;
unsigned short _irq_line;
Irq_session_component _irq_session;
bool _rewrite_irq_line;
enum {
IO_BLOCK_SIZE = sizeof(Genode::Io_port_connection) *
Device::NUM_RESOURCES + 32 + 8 * sizeof(void *)
Device::NUM_RESOURCES + 32 + 8 * sizeof(void *),
PCI_CMD_REG = 0x4,
PCI_CMD_DMA = 0x4,
PCI_IRQ_LINE = 0x3c,
PCI_IRQ_PIN = 0x3d
};
Genode::Tslab<Genode::Io_port_connection, IO_BLOCK_SIZE> _slab_ioport;
Genode::Slab_block _slab_ioport_block;
char _slab_ioport_block_data[IO_BLOCK_SIZE];
@ -56,7 +64,95 @@ class Pci::Device_component : public Genode::Rpc_object<Pci::Device>,
Genode::Io_port_connection *_io_port_conn [Device::NUM_RESOURCES];
Genode::Io_mem_connection *_io_mem_conn [Device::NUM_RESOURCES];
enum { PCI_IRQ_LINE = 0x3c };
struct Status : Genode::Register<8> {
struct Capabilities : Bitfield<4,1> { };
inline static access_t read(Genode::uint8_t t) { return t; };
};
/**
* Read out msi capabilities of the device.
*/
Genode::uint16_t _msi_cap()
{
enum { PCI_STATUS = 0x6, PCI_CAP_OFFSET = 0x34, CAP_MSI = 0x5 };
Status::access_t status = Status::read(_device_config.read(&_config_access,
PCI_STATUS,
Pci::Device::ACCESS_16BIT));
if (!Status::Capabilities::get(status))
return 0;
Genode::uint8_t cap = _device_config.read(&_config_access,
PCI_CAP_OFFSET,
Pci::Device::ACCESS_8BIT);
for (Genode::uint16_t val = 0; cap; cap = val >> 8) {
val = _device_config.read(&_config_access, cap,
Pci::Device::ACCESS_16BIT);
if ((val & 0xff) != CAP_MSI)
continue;
return cap;
}
return 0;
}
/**
* Disable MSI if already enabled.
*/
unsigned _disable_msi(unsigned irq)
{
using Genode::uint16_t;
using Genode::uint8_t;
uint8_t has_irq = _device_config.read(&_config_access, PCI_IRQ_PIN,
Pci::Device::ACCESS_8BIT);
if (!has_irq)
return Irq_session_component::INVALID_IRQ;
uint16_t cap = _msi_cap();
if (!cap)
return irq;
uint16_t msi = _device_config.read(&_config_access, cap + 2,
Pci::Device::ACCESS_16BIT);
enum { MSI_ENABLED = 0x1 };
if (msi & MSI_ENABLED)
/* disable MSI */
_device_config.write(&_config_access, cap + 2,
msi ^ MSI_ENABLED,
Pci::Device::ACCESS_8BIT);
return irq;
}
/**
* Disable bus master dma if already enabled.
*/
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.is_pci_bridge())
return;
unsigned cmd = _device_config.read(&_config_access, PCI_CMD_REG,
Pci::Device::ACCESS_16BIT);
if (cmd & PCI_CMD_DMA)
_device_config.write(&_config_access, PCI_CMD_REG,
cmd ^ PCI_CMD_DMA,
Pci::Device::ACCESS_16BIT);
}
public:
@ -69,13 +165,22 @@ class Pci::Device_component : public Genode::Rpc_object<Pci::Device>,
bool rewrite_irq_line)
:
_device_config(device_config), _config_space(addr),
_io_mem(0), _ep(ep), _session(session),
_irq_session(_device_config.read(&_config_access, PCI_IRQ_LINE,
Pci::Device::ACCESS_8BIT)),
_ep(ep), _session(session),
_irq_line(_device_config.read(&_config_access, PCI_IRQ_LINE,
Pci::Device::ACCESS_8BIT)),
_irq_session(_disable_msi(_irq_line), _msi_cap() ? _config_space : ~0UL),
_rewrite_irq_line(rewrite_irq_line),
_slab_ioport(0, &_slab_ioport_block),
_slab_iomem(0, &_slab_iomem_block)
{
if (_config_space != ~0UL) {
try {
Genode::Io_mem_connection conn(_config_space, 0x1000);
conn.on_destruction(Genode::Io_mem_connection::KEEP_OPEN);
_io_mem_config_extended = conn;
} catch (Genode::Parent::Service_denied) { }
}
_ep->manage(&_irq_session);
for (unsigned i = 0; i < Device::NUM_RESOURCES; i++) {
@ -87,6 +192,43 @@ class Pci::Device_component : public Genode::Rpc_object<Pci::Device>,
PERR("incorrect amount of space for io port resources");
if (_slab_iomem.num_elem() != Device::NUM_RESOURCES)
PERR("incorrect amount of space for io mem resources");
_disable_bus_master_dma();
if (!_irq_session.msi())
return;
Genode::addr_t msi_address = _irq_session.msi_address();
Genode::uint32_t msi_value = _irq_session.msi_data();
Genode::uint16_t msi_cap = _msi_cap();
enum { CAP_MSI_64 = 0x80, MSI_ENABLED = 0x1 };
Genode::uint16_t msi = _device_config.read(&_config_access,
msi_cap + 2,
Pci::Device::ACCESS_16BIT);
_device_config.write(&_config_access, msi_cap + 0x4, msi_address,
Pci::Device::ACCESS_32BIT);
if (msi & CAP_MSI_64) {
Genode::uint32_t upper_address = sizeof(msi_address) > 4 ?
msi_address >> 32 : 0UL;
_device_config.write(&_config_access, msi_cap + 0x8,
upper_address,
Pci::Device::ACCESS_32BIT);
_device_config.write(&_config_access, msi_cap + 0xc,
msi_value,
Pci::Device::ACCESS_16BIT);
}
else
_device_config.write(&_config_access, msi_cap + 0x8, msi_value,
Pci::Device::ACCESS_16BIT);
/* enable MSI */
_device_config.write(&_config_access, msi_cap + 2,
msi ^ MSI_ENABLED,
Pci::Device::ACCESS_8BIT);
}
/**
@ -95,8 +237,9 @@ class Pci::Device_component : public Genode::Rpc_object<Pci::Device>,
Device_component(Genode::Rpc_entrypoint * ep,
Pci::Session_component * session, unsigned irq)
:
_config_space(~0UL), _io_mem(0), _ep(ep), _session(session),
_irq_session(irq),
_config_space(~0UL), _ep(ep), _session(session),
_irq_line(irq),
_irq_session(_irq_line, _config_space),
_slab_ioport(0, &_slab_ioport_block),
_slab_iomem(0, &_slab_iomem_block)
{
@ -121,6 +264,12 @@ class Pci::Device_component : public Genode::Rpc_object<Pci::Device>,
if (_io_mem_conn[i])
Genode::destroy(_slab_iomem, _io_mem_conn[i]);
}
if (_io_mem_config_extended.valid())
Genode::env()->parent()->close(_io_mem_config_extended);
if (_device_config.valid())
_disable_bus_master_dma();
}
/****************************************
@ -129,32 +278,34 @@ class Pci::Device_component : public Genode::Rpc_object<Pci::Device>,
Device_config config() { return _device_config; }
Genode::addr_t config_space() { return _config_space; }
Genode::Io_mem_dataspace_capability get_config_space()
{
if (!_io_mem_config_extended.valid())
return Genode::Io_mem_dataspace_capability();
void set_config_space(Genode::Io_mem_connection * io_mem) {
_io_mem = io_mem; }
Genode::Io_mem_connection * get_config_space() { return _io_mem; }
Genode::Io_mem_session_client client(_io_mem_config_extended);
return client.dataspace();
}
/**************************
** PCI-device interface **
**************************/
void bus_address(unsigned char *bus, unsigned char *dev,
unsigned char *fn)
unsigned char *fn) override
{
*bus = _device_config.bus_number();
*dev = _device_config.device_number();
*fn = _device_config.function_number();
}
unsigned short vendor_id() { return _device_config.vendor_id(); }
unsigned short vendor_id() override { return _device_config.vendor_id(); }
unsigned short device_id() { return _device_config.device_id(); }
unsigned short device_id() override { return _device_config.device_id(); }
unsigned class_code() { return _device_config.class_code(); }
unsigned class_code() override { return _device_config.class_code(); }
Resource resource(int resource_id)
Resource resource(int resource_id) override
{
/* return invalid resource if device is invalid */
if (!_device_config.valid())
@ -163,41 +314,13 @@ class Pci::Device_component : public Genode::Rpc_object<Pci::Device>,
return _device_config.resource(resource_id);
}
unsigned config_read(unsigned char address, Access_size size)
unsigned config_read(unsigned char address, Access_size size) override
{
return _device_config.read(&_config_access, address, size);
}
void config_write(unsigned char address, unsigned value, Access_size size)
{
/* white list of ports which we permit to write */
switch(address) {
case 0x40 ... 0xFF:
/* all device specific registers are permitted */
break;
case 0x4: /* COMMAND register - first byte */
if (size == Access_size::ACCESS_16BIT)
break;
case 0x5: /* COMMAND register - second byte */
case 0xd: /* Latency timer */
if (size == Access_size::ACCESS_8BIT)
break;
case PCI_IRQ_LINE:
/* permitted up to now solely for acpi driver */
if (address == PCI_IRQ_LINE && _rewrite_irq_line &&
size == Access_size::ACCESS_8BIT)
break;
default:
PWRN("%x:%x:%x write access to address=%x value=0x%x "
" size=0x%x got dropped", _device_config.bus_number(),
_device_config.device_number(),
_device_config.function_number(),
address, value, size);
return;
}
_device_config.write(&_config_access, address, value, size);
}
void config_write(unsigned char address, unsigned value,
Access_size size) override;
Genode::Irq_session_capability irq(Genode::uint8_t id) override
{