genode/repos/dde_linux/src/include/lx_emul/impl/internal/pci_dev.h

242 lines
6.3 KiB
C++

/*
* \brief Emulate 'pci_dev' structure
* \author Sebastian Sumpf
* \author Josef Soentgen
* \author Norman Feske
* \date 2014-10-10
*/
/*
* Copyright (C) 2014 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _LX_EMUL__IMPL__INTERNAL__PCI_DEV_H_
#define _LX_EMUL__IMPL__INTERNAL__PCI_DEV_H_
/* Genode includes */
#include <base/object_pool.h>
#include <platform_session/connection.h>
#include <platform_device/client.h>
#include <io_mem_session/connection.h>
#include <os/attached_dataspace.h>
#include <util/retry.h>
/* Linux emulation environment includes */
#include <lx_emul/impl/internal/debug.h>
#include <lx_emul/impl/internal/list.h>
#include <lx_emul/impl/internal/io_port.h>
namespace Lx {
class Pci_dev;
/**
* Return singleton 'Platform::Connection'
*
* Implementation must be privided by the driver.
*/
Platform::Connection *pci();
template <typename FUNC>
static inline void for_each_pci_device(FUNC const &func);
}
class Lx::Pci_dev : public pci_dev, public Lx::List<Pci_dev>::Element
{
private:
bool const verbose = true;
Platform::Device_client _client;
Io_port _io_port;
/* offset used in PCI config space */
enum Pci_config { IRQ = 0x3c, REV = 0x8, CMD = 0x4,
STATUS = 0x4, CAP = 0x34 };
enum Pci_cap { CAP_LIST = 0x10, CAP_EXP = 0x10,
CAP_EXP_FLAGS = 0x2, CAP_EXP_DEVCAP = 0x4 };
template <typename T>
Platform::Device::Access_size _access_size(T t)
{
switch (sizeof(T))
{
case 1:
return Platform::Device::ACCESS_8BIT;
case 2:
return Platform::Device::ACCESS_16BIT;
default:
return Platform::Device::ACCESS_32BIT;
}
}
public:
/**
* Constructor
*/
Pci_dev(Platform::Device_capability cap)
:
_client(cap)
{
using namespace Platform;
Genode::memset(static_cast<pci_dev *>(this), 0, sizeof(pci_dev));
this->vendor = _client.vendor_id();
this->device = _client.device_id();
this->class_ = _client.class_code();
this->revision = _client.config_read(REV, Device::ACCESS_8BIT);
/* dummy dma mask used to mark device as DMA capable */
this->dev._dma_mask_buf = ~(u64)0;
this->dev.dma_mask = &this->dev._dma_mask_buf;
this->dev.coherent_dma_mask = ~0;
/* read interrupt line */
this->irq = _client.config_read(IRQ, Device::ACCESS_8BIT);
/* hide ourselfs in bus structure */
this->bus = (struct pci_bus *)this;
/* setup resources */
bool io = false;
for (int i = 0; i < Device::NUM_RESOURCES; i++) {
Device::Resource res = _client.resource(i);
if (res.type() == Device::Resource::INVALID)
continue;
this->resource[i].start = res.base();
this->resource[i].end = res.base() + res.size() - 1;
unsigned flags = 0;
if (res.type() == Device::Resource::IO) flags |= IORESOURCE_IO;
if (res.type() == Device::Resource::MEMORY) flags |= IORESOURCE_MEM;
this->resource[i].flags = flags;
PDBGV("this=%p base: %x size: %x type: %u",
this, res.base(), res.size(), res.type());
/* request port I/O session */
if (res.type() == Device::Resource::IO) {
uint8_t const virt_bar = _client.phys_bar_to_virt(i);
_io_port.session(res.base(), res.size(), _client.io_port(virt_bar));
io = true;
PDBGV("I/O [%u-%u)",
res.base(), res.base() + res.size());
}
/* request I/O memory (write combined) */
if (res.type() == Device::Resource::MEMORY)
PDBGV("I/O memory [%x-%x)", res.base(),
res.base() + res.size());
}
/* enable bus master and io bits */
uint16_t cmd = _client.config_read(CMD, Device::ACCESS_16BIT);
cmd |= io ? 0x1 : 0;
/* enable bus master */
cmd |= 0x4;
_client.config_write(CMD, cmd, Device::ACCESS_16BIT);
/* get pci express capability */
this->pcie_cap = 0;
uint16_t status = _client.config_read(STATUS, Device::ACCESS_32BIT) >> 16;
if (status & CAP_LIST) {
uint8_t offset = _client.config_read(CAP, Device::ACCESS_8BIT);
while (offset != 0x00) {
uint8_t value = _client.config_read(offset, Device::ACCESS_8BIT);
if (value == CAP_EXP)
this->pcie_cap = offset;
offset = _client.config_read(offset + 1, Device::ACCESS_8BIT);
}
}
if (this->pcie_cap) {
uint16_t reg_val = _client.config_read(this->pcie_cap, Device::ACCESS_16BIT);
this->pcie_flags_reg = reg_val;
}
}
/**
* Read/write data from/to config space
*/
template <typename T>
void config_read(unsigned int devfn, T *val)
{
*val = _client.config_read(devfn, _access_size(*val));
}
template <typename T>
void config_write(unsigned int devfn, T val)
{
_client.config_write(devfn, val, _access_size(val));
}
Platform::Device &client() { return _client; }
Io_port &io_port() { return _io_port; }
};
/**
* Call functor for each available physical PCI device
*
* The functor is called with the device capability as argument. If
* returns true if we can stop iterating. In this case, the device
* is expected to be acquired by the driver. All other devices are
* released at the platform driver.
*/
template <typename FUNC>
void Lx::for_each_pci_device(FUNC const &func)
{
/*
* Functor that is called if the platform driver throws a
* 'Quota_exceeded' exception.
*/
auto handler = [&] () {
Genode::env()->parent()->upgrade(Lx::pci()->cap(),
"ram_quota=4096"); };
/*
* Obtain first device, the operation may exceed the session quota.
* So we use the 'retry' mechanism.
*/
Platform::Device_capability cap;
auto attempt = [&] () { cap = Lx::pci()->first_device(); };
Genode::retry<Platform::Device::Quota_exceeded>(attempt, handler);
/*
* Iterate over the devices of the platform session.
*/
while (cap.valid()) {
/*
* Call functor, stop iterating depending on its return value.
*/
if (func(cap))
break;
/*
* Release current device and try next one. Upgrade session
* quota on demand.
*/
auto attempt = [&] () {
Platform::Device_capability next_cap = pci()->next_device(cap);
Lx::pci()->release_device(cap);
cap = next_cap;
};
Genode::retry<Platform::Device::Quota_exceeded>(attempt, handler);
}
}
#endif /* _LX_EMUL__IMPL__INTERNAL__PCI_DEV_H_ */