genode/repos/libports/src/drivers/framebuffer/vesa/hw_emul.cc

344 lines
7.8 KiB
C++

/*
* \brief Hardware emulation implementation
* \author Christian Helmuth
* \author Stefan Kalkowski
* \date 2010-06-01
*
* The emulation comprises:
*
* - Simple programmable interval timer (PIT)
* - Virtual PCI bus with VGA card attached to its physical slot
*/
/*
* Copyright (C) 2010-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.
*/
/* Genode includes */
#include <base/log.h>
#include <util/retry.h>
/* local includes */
#include "framebuffer.h"
#include "hw_emul.h"
using namespace Genode;
static const bool verbose = false;
/************************
** PCI virtualization **
************************/
#include <platform_session/connection.h>
#include <platform_device/client.h>
enum {
PCI_ADDR_REG = 0xcf8,
PCI_DATA_REG = 0xcfc
};
struct Devfn
{
unsigned char b, d, f;
Devfn(Platform::Device &device) { device.bus_address(&b, &d, &f); }
Devfn(unsigned short devfn)
: b((devfn >> 8) & 0xff), d((devfn >> 3) & 0x1f), f(devfn & 7) { }
unsigned short devfn() const { return b << 8 | d << 3 | f; }
void print(Genode::Output &out) const
{
Genode::print(out, Hex(b, Hex::OMIT_PREFIX, Hex::PAD), ":",
Hex(d, Hex::OMIT_PREFIX, Hex::PAD), ".",
Hex(f, Hex::OMIT_PREFIX));
}
};
class Pci_card
{
private:
Platform::Connection _pci_drv;
Platform::Device_client _device;
Devfn _devfn;
Platform::Device_capability _first_device()
{
return _pci_drv.with_upgrade([&] () {
return _pci_drv.first_device(); });
}
Platform::Device_capability _next_device(Platform::Device_capability prev)
{
return _pci_drv.with_upgrade([&] () {
return _pci_drv.next_device(prev); });
}
Platform::Device_capability _find_vga_card()
{
/*
* Iterate through all accessible devices.
*/
Platform::Device_capability prev_device_cap, device_cap;
for (device_cap = _first_device();
device_cap.valid();
device_cap = _next_device(prev_device_cap)) {
Platform::Device_client device(device_cap);
if (prev_device_cap.valid())
_pci_drv.release_device(prev_device_cap);
/*
* If the device is an VGA compatible controller with base
* class 0x03 and sub class 0x00 stop iteration. (We shift out
* the interface bits.)
*/
if ((device.class_code() >> 8) == 0x0300)
break;
prev_device_cap = device_cap;
}
if (!device_cap.valid()) {
Genode::error("PCI VGA card not found.");
throw Framebuffer::Fatal();
}
return device_cap;
}
public:
Pci_card(Genode::Env &env)
: _pci_drv(env), _device(_find_vga_card()), _devfn(_device)
{
Genode::log("Found PCI VGA at ", _devfn);
}
Platform::Device &device() { return _device; }
unsigned short devfn() const { return _devfn.devfn(); }
};
static Constructible<Pci_card> pci_card;
/**
* Last address written to address port.
*/
static unsigned pci_cfg_addr;
static bool pci_cfg_addr_valid = false;
template <typename T>
static bool handle_pci_port_write(unsigned short port, T val)
{
switch (port) {
case PCI_ADDR_REG:
/*
* The virtual bus has only 1 device - the VGA card - at its physical
* bus address.
*/
{
if (sizeof(T) != 4) {
warning("writing with size ", sizeof(T), " not supported", sizeof(T));
return true;
}
unsigned const devfn = (val >> 8) & 0xffff;
if (devfn != pci_card->devfn()) {
if (verbose)
warning("accessing unknown PCI device ", Devfn(devfn));
pci_cfg_addr_valid = false;
return true;
}
/* remember the configuration address */
pci_cfg_addr = val & 0xfc;
pci_cfg_addr_valid = true;
return true;
}
case PCI_DATA_REG:
warning("writing data register not supported (value=", Hex(val), ")");
return true;
default:
return false;
}
}
template <typename T>
static bool handle_pci_port_read(unsigned short port, T *val)
{
unsigned raw_val;
*val = 0;
switch (port & ~3) {
case PCI_ADDR_REG:
*val = pci_cfg_addr;
return true;
case PCI_DATA_REG:
{
unsigned off_bits = (port & 3) * 8;
if (!pci_cfg_addr_valid) {
/*
* XXX returning -1 here for "invalid PCI device" breaks Qemu's
* Cirrus VGA BIOS.
*/
*val = 0;
return true;
}
switch (pci_cfg_addr) {
case 0: /* vendor / device ID */
raw_val = pci_card->device().vendor_id() |
(pci_card->device().device_id() << 16);
break;
case 4: /* status and command */
case 8: /* class code / revision ID */
raw_val = pci_card->device().config_read(pci_cfg_addr,
Platform::Device::ACCESS_32BIT);
break;
case 0x10: /* base address register 0 */
case 0x14: /* base address register 1 */
case 0x18: /* base address register 2 */
case 0x1c: /* base address register 3 */
case 0x20: /* base address register 4 */
case 0x24: /* base address register 5 */
{
unsigned bar = (pci_cfg_addr - 0x10) / 4;
Platform::Device::Resource res = pci_card->device().resource(bar);
if (res.type() == Platform::Device::Resource::INVALID) {
warning("requested PCI resource ", bar, " invalid");
*val = 0;
return true;
}
raw_val = res.bar();
break;
}
default:
warning("unexpected configuration address ", Hex(pci_cfg_addr));
return true;
}
*val = raw_val >> off_bits;
return true;
}
default:
return false;
}
}
/************************
** PIT virtualization **
************************/
/*
* Some VESA BIOS implementations use the PIT as time source. However, usually
* only the PIT counter is queried by first writing a latch command (0) to the
* command register and subsequently reading the data port two times (low word
* and high word). Returning non-zero bogus values seems to make (at least
* some) VESA BIOS implementations happy.
*/
enum {
PIT_DATA_PORT_0 = 0x40,
PIT_CMD_PORT = 0x43,
};
/**
* Handle port-write access to the PIT
*
* \return true if the port access referred to the PIT
*/
template <typename T>
static bool handle_pit_port_write(unsigned short port, T val)
{
if (port >= PIT_DATA_PORT_0 && port <= PIT_CMD_PORT)
return true;
return false;
}
/**
* Handle port-read access from the PIT registers
*
* \return true if the port access referred to the PIT
*/
template <typename T>
static bool handle_pit_port_read(unsigned short port, T *val)
{
if (port >= PIT_DATA_PORT_0 && port <= PIT_CMD_PORT) {
*val = 0x15; /* bogus value */
return true;
}
return false;
}
/************************
** API implementation **
************************/
/* instantiate externally used template funciton */
template bool hw_emul_handle_port_read(unsigned short port, unsigned char *val);
template bool hw_emul_handle_port_read(unsigned short port, unsigned short *val);
template bool hw_emul_handle_port_read(unsigned short port, unsigned long *val);
template bool hw_emul_handle_port_read(unsigned short port, unsigned int *val);
template bool hw_emul_handle_port_write(unsigned short port, unsigned char val);
template bool hw_emul_handle_port_write(unsigned short port, unsigned short val);
template bool hw_emul_handle_port_write(unsigned short port, unsigned long val);
template bool hw_emul_handle_port_write(unsigned short port, unsigned int val);
template <typename T>
bool hw_emul_handle_port_read(unsigned short port, T *val)
{
T ret;
if (handle_pci_port_read(port, &ret)
|| handle_pit_port_read(port, &ret)) {
*val = ret;
return true;
}
return false;
}
template <typename T>
bool hw_emul_handle_port_write(unsigned short port, T val)
{
if (handle_pci_port_write(port, val)
|| handle_pit_port_write(port, val))
return true;
return false;
}
void hw_emul_init(Genode::Env &env) { pci_card.construct(env); }