lx_kit/lx_emul: add I/O port support

Ref genodelabs/genode#4438
This commit is contained in:
Josef Söntgen 2022-02-22 16:06:37 +01:00 committed by Christian Helmuth
parent 57aab46fc3
commit 2760b67902
7 changed files with 430 additions and 10 deletions

View File

@ -0,0 +1,33 @@
/*
* \brief Lx_emul support for I/O port access
* \author Josef Soentgen
* \date 2022-02-22
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is distributed under the terms of the GNU General Public License
* version 2.
*/
#ifndef _LX_EMUL__IO_PORT_H_
#define _LX_EMUL__IO_PORT_H_
#ifdef __cplusplus
extern "C" {
#endif
unsigned char lx_emul_io_port_inb(unsigned short addr);
unsigned short lx_emul_io_port_inw(unsigned short addr);
unsigned int lx_emul_io_port_inl(unsigned short addr);
void lx_emul_io_port_outb(unsigned char value, unsigned short addr);
void lx_emul_io_port_outw(unsigned short value, unsigned short addr);
void lx_emul_io_port_outl(unsigned int value, unsigned short addr);
#ifdef __cplusplus
}
#endif
#endif /* _LX_EMUL__IO_PORT_H_ */

View File

@ -74,6 +74,22 @@ class Lx_kit::Device : List<Device>::Element
void handle();
};
struct Io_port : List<Io_port>::Element
{
using Index = Platform::Device::Io_port_range::Index;
Index idx;
uint16_t addr;
uint16_t size;
Constructible<Platform::Device::Io_port_range> io_port {};
bool match(uint16_t addr);
Io_port(unsigned idx, uint16_t addr, uint16_t size)
: idx{idx}, addr(addr), size(size) {}
};
struct Clock : List<Clock>::Element
{
unsigned idx;
@ -93,6 +109,7 @@ class Lx_kit::Device : List<Device>::Element
Name const _name;
Type const _type;
List<Io_mem> _io_mems {};
List<Io_port> _io_ports {};
List<Irq> _irqs {};
List<Clock> _clocks {};
Constructible<Platform::Device> _pdev {};
@ -101,6 +118,10 @@ class Lx_kit::Device : List<Device>::Element
void _for_each_io_mem(FN const & fn) {
for (Io_mem * i = _io_mems.first(); i; i = i->next()) fn(*i); }
template <typename FN>
void _for_each_io_port(FN const & fn) {
for (Io_port * i = _io_ports.first(); i; i = i->next()) fn(*i); }
template <typename FN>
void _for_each_irq(FN const & fn) {
for (Irq * i = _irqs.first(); i; i = i->next()) fn(*i); }
@ -125,6 +146,14 @@ class Lx_kit::Device : List<Device>::Element
bool read_config(unsigned reg, unsigned len, unsigned *val);
bool write_config(unsigned reg, unsigned len, unsigned val);
bool io_port(uint16_t addr);
uint8_t io_port_inb(uint16_t addr);
uint16_t io_port_inw(uint16_t addr);
uint32_t io_port_inl(uint16_t addr);
void io_port_outb(uint16_t addr, uint8_t val);
void io_port_outw(uint16_t addr, uint16_t val);
void io_port_outl(uint16_t addr, uint32_t val);
};

View File

@ -0,0 +1,30 @@
/**
* \brief Shadow copy of asm/io.h
* \author Josef Soentgen
* \date 2022-02-21
*/
#ifndef _ASM_X86_IO_H
#define _ASM_X86_IO_H
#include <linux/string.h>
#include <linux/compiler.h>
#include <asm/page.h>
#include <asm/pgtable_types.h>
#include <lx_emul/io_port.h>
void __iomem *ioremap(resource_size_t offset, unsigned long size);
void iounmap(volatile void __iomem *addr);
#define inb lx_emul_io_port_inb
#define inw lx_emul_io_port_inw
#define inl lx_emul_io_port_inl
#define outb lx_emul_io_port_outb
#define outw lx_emul_io_port_outw
#define outl lx_emul_io_port_outl
#include <asm-generic/io.h>
#endif /* _ASM_X86_IO_H */

View File

@ -18,6 +18,7 @@
#include <base/attached_dataspace.h>
#include <base/exception.h>
#include <io_mem_session/client.h>
#include <io_port_session/client.h>
#include <irq_session/client.h>
#include <platform_session/connection.h>
#include <util/mmio.h>
@ -39,6 +40,7 @@ class Platform::Device : Interface, Noncopyable
struct Range { addr_t start; size_t size; };
struct Mmio;
struct Io_port_range;
struct Irq;
struct Config_space;
@ -104,6 +106,36 @@ class Platform::Device::Mmio : Range
};
class Platform::Device::Io_port_range : Noncopyable
{
public:
struct Index { unsigned value; };
private:
Device &_device;
Index _index;
Constructible<Io_port_session_client> _io_port { };
public:
Io_port_range(Device &device, Index index);
explicit Io_port_range(Device &device)
: _device { device }, _index { ~0u } { }
uint8_t inb(uint16_t addr);
uint16_t inw(uint16_t addr);
uint32_t inl(uint16_t addr);
void outb(uint16_t addr, uint8_t val);
void outw(uint16_t addr, uint16_t val);
void outl(uint16_t addr, uint32_t val);
};
class Platform::Device::Irq : Noncopyable
{
public:

View File

@ -0,0 +1,101 @@
/*
* \brief Lx_emul backend for I/O port access
* \author Josef Soentgen
* \date 2022-02-22
*/
/*
* Copyright (C) 2022 Genode Labs GmbH
*
* This file is distributed under the terms of the GNU General Public License
* version 2.
*/
#include <lx_kit/env.h>
#include <lx_emul/io_port.h>
template<typename T>
T _io_port_in(unsigned short addr)
{
using namespace Lx_kit;
using namespace Genode;
unsigned ret = 0;
bool valid_ret = false;
env().devices.for_each([&] (Device & d) {
if (d.io_port(addr)) {
valid_ret = true;
switch (sizeof (T)) {
case sizeof (unsigned char): ret = d.io_port_inb(addr); break;
case sizeof (unsigned short): ret = d.io_port_inw(addr); break;
case sizeof (unsigned int): ret = d.io_port_inl(addr); break;
default: valid_ret = false; break;
}
}
});
if (!valid_ret)
error("could not read I/O port resource ", Hex(addr));
return static_cast<T>(ret);
}
unsigned char lx_emul_io_port_inb(unsigned short addr)
{
return _io_port_in<unsigned char>(addr);
}
unsigned short lx_emul_io_port_inw(unsigned short addr)
{
return _io_port_in<unsigned short>(addr);
}
unsigned int lx_emul_io_port_inl(unsigned short addr)
{
return _io_port_in<unsigned int>(addr);
}
template<typename T>
void _io_port_out(unsigned short addr, T value)
{
using namespace Lx_kit;
using namespace Genode;
bool valid_ret = false;
env().devices.for_each([&] (Device & d) {
if (d.io_port(addr)) {
valid_ret = true;
switch (sizeof (T)) {
case sizeof (unsigned char): d.io_port_outb(addr, (unsigned char)value); break;
case sizeof (unsigned short): d.io_port_outw(addr, (unsigned short)value); break;
case sizeof (unsigned int): d.io_port_outl(addr, (unsigned int)value); break;
default: valid_ret = false; break;
}
}
});
if (!valid_ret)
error("could not write I/O port resource ", Hex(addr));
}
void lx_emul_io_port_outb(unsigned char value, unsigned short addr)
{
_io_port_out<unsigned char>(addr, value);
}
void lx_emul_io_port_outw(unsigned short value, unsigned short addr)
{
_io_port_out<unsigned short>(addr, value);
}
void lx_emul_io_port_outl(unsigned int value, unsigned short addr)
{
_io_port_out<unsigned int>(addr, value);
}

View File

@ -27,6 +27,17 @@ bool Device::Io_mem::match(addr_t addr, size_t size)
}
/**********************
** Device::Io_port **
**********************/
bool Device::Io_port::match(uint16_t addr)
{
return (this->addr <= addr) &&
((this->addr + this->size) >= addr);
}
/****************
** Device::Irq**
****************/
@ -168,6 +179,110 @@ void Device::irq_ack(unsigned number)
}
bool Device::io_port(uint16_t addr)
{
bool ret = false;
_for_each_io_port([&] (Io_port & io) {
if (io.match(addr))
ret = true;
});
return ret;
}
uint8_t Device::io_port_inb(uint16_t addr)
{
uint8_t ret = 0;
_for_each_io_port([&] (Device::Io_port & io) {
if (!io.match(addr))
return;
if (!io.io_port.constructed())
io.io_port.construct(*_pdev, io.idx);
ret = io.io_port->inb(addr);
});
return ret;
}
uint16_t Device::io_port_inw(uint16_t addr)
{
uint16_t ret = 0;
_for_each_io_port([&] (Device::Io_port & io) {
if (!io.match(addr))
return;
if (!io.io_port.constructed())
io.io_port.construct(*_pdev, io.idx);
ret = io.io_port->inw(addr);
});
return ret;
}
uint32_t Device::io_port_inl(uint16_t addr)
{
uint32_t ret = 0;
_for_each_io_port([&] (Device::Io_port & io) {
if (!io.match(addr))
return;
if (!io.io_port.constructed())
io.io_port.construct(*_pdev, io.idx);
ret = io.io_port->inl(addr);
});
return ret;
}
void Device::io_port_outb(uint16_t addr, uint8_t val)
{
_for_each_io_port([&] (Device::Io_port & io) {
if (!io.match(addr))
return;
if (!io.io_port.constructed())
io.io_port.construct(*_pdev, io.idx);
io.io_port->outb(addr, val);
});
}
void Device::io_port_outw(uint16_t addr, uint16_t val)
{
_for_each_io_port([&] (Device::Io_port & io) {
if (!io.match(addr))
return;
if (!io.io_port.constructed())
io.io_port.construct(*_pdev, io.idx);
io.io_port->outw(addr, val);
});
}
void Device::io_port_outl(uint16_t addr, uint32_t val)
{
_for_each_io_port([&] (Device::Io_port & io) {
if (!io.match(addr))
return;
if (!io.io_port.constructed())
io.io_port.construct(*_pdev, io.idx);
io.io_port->outl(addr, val);
});
}
void Device::enable()
{
if (_pdev.constructed())
@ -208,6 +323,13 @@ Device::Device(Entrypoint & ep,
_io_mems.insert(new (heap) Io_mem(i++, addr, size));
});
i = 0;
xml.for_each_sub_node("io_port", [&] (Xml_node node) {
uint16_t addr = node.attribute_value<uint16_t>("phys_addr", 0U);
uint16_t size = node.attribute_value<uint16_t>("size", 0U);
_io_ports.insert(new (heap) Io_port(i++, addr, size));
});
i = 0;
xml.for_each_sub_node("irq", [&] (Xml_node node) {
_irqs.insert(new (heap) Irq(ep, i++, node.attribute_value("number", 0U)));

View File

@ -229,6 +229,13 @@ static unsigned bar_size(Platform::Device const &dev,
val = node.attribute_value("size", 0u);
});
device.for_each_sub_node("io_port", [&] (Xml_node node) {
if (node.attribute_value("bar", 6u) != bar)
return;
val = node.attribute_value("size", 0u);
});
});
return val;
@ -271,7 +278,7 @@ Platform::Device::Device(Connection &platform, Name name)
unsigned Platform::Device::Config_space::read(unsigned char address,
Access_size size)
{
// 32bit BARs only for now
/* 32bit BARs only for now */
if (address >= 0x10 && address <= 0x24) {
unsigned const bar = (address - 0x10) / 4;
if (_device._bar_checked_for_size[bar]) {
@ -286,9 +293,6 @@ unsigned Platform::Device::Config_space::read(unsigned char address,
if (address == 0x34)
return 0u;
if (address > 0x3f)
return 0u;
Legacy_platform::Device::Access_size const as = convert(size);
Legacy_platform::Device_client device { _device._device_cap };
return device.config_read(address, as);
@ -299,7 +303,7 @@ void Platform::Device::Config_space::write(unsigned char address,
unsigned value,
Access_size size)
{
// 32bit BARs only for now
/* 32bit BARs only for now */
if (address >= 0x10 && address <= 0x24) {
unsigned const bar = (address - 0x10) / 4;
if (value == 0xffffffffu)
@ -307,8 +311,30 @@ void Platform::Device::Config_space::write(unsigned char address,
return;
}
if (address != 0x04)
/* reject writes to unknown addresses */
switch (address) {
case 0x04:
/*
* Doing this multiple times induces multiple "assignment of PCI
* device" diasgnostics currently.
*/
/* command register (value for enable io, memory, bus master) */
value = 7;
break;
/*
* Write in [0x40,0xff] should be okay if there are is no capability
* list (status register bit 4 + capability list pointer). Otherwise,
* writes into capability-list entries should be rejected.
*/
case 0xc0: /* UHCI BIOS handoff (UHCI_USBLEGSUP) */ break;
case 0xc4: /* UHCI INTEL resume-enable (USBRES_INTEL) */ break;
case 0x60 ... 0x6f: /* EHCI BIOS handoff (just empiric, address not fixed) */ break;
default:
return;
}
Legacy_platform::Device::Access_size const as = convert(size);
Legacy_platform::Device_client device { _device._device_cap };
@ -343,6 +369,53 @@ void *Platform::Device::Mmio::_local_addr()
}
Platform::Device::Io_port_range::Io_port_range(Device &device, Index index)
:
_device { device },
_index { index }
{
Legacy_platform::Device_client client { _device._device_cap };
_io_port.construct(client.io_port((uint8_t)index.value));
}
unsigned char Platform::Device::Io_port_range::inb(uint16_t addr)
{
return _io_port->inb(addr);
}
unsigned short Platform::Device::Io_port_range::inw(uint16_t addr)
{
return _io_port->inw(addr);
}
unsigned int Platform::Device::Io_port_range::inl(uint16_t addr)
{
return _io_port->inl(addr);
}
void Platform::Device::Io_port_range::outb(uint16_t addr, uint8_t val)
{
return _io_port->outb(addr, val);
}
void Platform::Device::Io_port_range::outw(uint16_t addr, uint16_t val)
{
return _io_port->outw(addr, val);
}
void Platform::Device::Io_port_range::outl(uint16_t addr, uint32_t val)
{
return _io_port->outl(addr, val);
}
Platform::Device::Irq::Irq(Platform::Device &device, Index index)
:
_device { device },