platform_drv: add EHCI PCI quirk, apply in order

* Add EHCI PCI quirk
* Add UHCI reset to UHCI quirk
* Apply all PCI quirks in order of the PCI bus numbering
  otherwise the machine might stall

Ref genodelabs/genode#4578
This commit is contained in:
Stefan Kalkowski 2022-09-30 16:19:00 +02:00 committed by Christian Helmuth
parent a77ceb6871
commit 00c9ac363f
5 changed files with 209 additions and 10 deletions

View File

@ -205,4 +205,11 @@ void Driver::Device_model::generate(Xml_generator & xml) const
void Driver::Device_model::update(Xml_node const & node)
{
_model.update_from_xml(*this, node);
/*
* Iterate over all devices and apply PCI quirks if necessary
*/
for_each([&] (Device const & device) {
pci_apply_quirks(_env, device);
});
}

View File

@ -19,6 +19,7 @@
#include <device_pd.h>
#include <pci.h>
#include <pci_uhci.h>
#include <pci_ehci.h>
#include <pci_intel_graphics.h>
#include <pci_hd_audio.h>
#include <pci_virtio.h>
@ -29,16 +30,17 @@ using namespace Pci;
struct Config_helper
{
Env & _env;
Driver::Device const & _dev;
Driver::Device::Pci_config const & _cfg;
Attached_io_mem_dataspace _io_mem;
Attached_io_mem_dataspace _io_mem { _env, _cfg.addr, 0x1000 };
Config _config { (addr_t)_io_mem.local_addr<void>() };
Config_helper(Env & env,
Driver::Device const & dev,
Driver::Device::Pci_config const & cfg)
: _dev(dev), _cfg(cfg), _io_mem(env, cfg.addr, 0x1000) { }
: _env(env), _dev(dev), _cfg(cfg) { }
void enable(Driver::Device_pd & pd)
{
@ -62,10 +64,6 @@ struct Config_helper
Config::Command::Io_space_enable::set(cmd, 1); });
_config.write<Config::Command>(cmd);
/* apply different PCI quirks, bios handover etc. */
Driver::pci_uhci_quirks(_cfg, _config.base());
Driver::pci_hd_audio_quirks(_cfg, _config);
}
void disable()
@ -78,6 +76,32 @@ struct Config_helper
Config::Command::Interrupt_enable::set(cmd, 0);
_config.write<Config::Command>(cmd);
}
void apply_quirks()
{
Config::Command::access_t cmd =
_config.read<Config::Command>();
Config::Command::access_t cmd_old = cmd;
/* enable memory space when I/O mem is defined */
_dev.for_each_io_mem([&] (unsigned, Driver::Device::Io_mem::Range,
Driver::Device::Pci_bar, bool) {
Config::Command::Memory_space_enable::set(cmd, 1); });
/* enable i/o space when I/O ports are defined */
_dev.for_each_io_port_range(
[&] (unsigned, Driver::Device::Io_port_range::Range) {
Config::Command::Io_space_enable::set(cmd, 1); });
_config.write<Config::Command>(cmd);
/* apply different PCI quirks, bios handover etc. */
Driver::pci_uhci_quirks(_env, _dev, _cfg, _config.base());
Driver::pci_ehci_quirks(_env, _dev, _cfg, _config.base());
Driver::pci_hd_audio_quirks(_cfg, _config);
_config.write<Config::Command>(cmd_old);
}
};
@ -95,6 +119,13 @@ void Driver::pci_disable(Env & env, Device const & dev)
}
void Driver::pci_apply_quirks(Env & env, Device const & dev)
{
dev.for_pci_config([&] (Device::Pci_config const & pc) {
Config_helper(env, dev, pc).apply_quirks(); });
}
void Driver::pci_msi_enable(Env & env,
addr_t cfg_space,
Irq_session::Info const info)

View File

@ -24,6 +24,7 @@ namespace Driver {
void pci_enable(Genode::Env & env, Device_pd & pd, Device const & dev);
void pci_disable(Genode::Env & env, Device const & dev);
void pci_apply_quirks(Genode::Env & env, Device const & dev);
void pci_msi_enable(Genode::Env & env, addr_t cfg_space,
Genode::Irq_session::Info const info);
bool pci_device_matches(Genode::Session_policy const & policy,

View File

@ -0,0 +1,105 @@
/*
* \brief Platform driver - PCI EHCI utilities
* \author Stefan Kalkowski
* \date 2022-09-28
*/
/*
* Copyright (C) 2022 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/config.h>
#include <device.h>
namespace Driver {
static void pci_ehci_quirks(Env &, Device const &,
Device::Pci_config, addr_t);
}
void Driver::pci_ehci_quirks(Env & env,
Device const & dev,
Device::Pci_config cfg,
addr_t base)
{
enum { EHCI_CLASS_CODE = 0xc0320 };
if (cfg.class_code != EHCI_CLASS_CODE)
return;
/* EHCI host controller register definitions */
struct Ehci : Mmio
{
struct Capability_parameters : Register<0x8, 32>
{
struct Extended_cap_pointer : Bitfield<8,8> {};
};
struct Configure_flag : Register<0x40, 32> {};
using Mmio::Mmio;
};
struct Ehci_pci : Mmio
{
struct Port_wake : Register<0x62, 16> {};
using Mmio::Mmio;
};
/* PCI extended capability for EHCI */
struct Cap : Mmio
{
struct Pointer : Register<0x0, 16>
{
struct Id : Bitfield<0, 8> { enum { SYNC = 1 }; };
struct Next : Bitfield<8, 8> { };
};
struct Bios_semaphore : Register<0x2, 8> { };
struct Os_semaphore : Register<0x3, 8> { };
struct Usb_legacy_control_status : Register<0x4, 32> {};
using Mmio::Mmio;
};
/* find ehci controller registers behind PCI bar zero */
dev.for_each_io_mem([&] (unsigned, Device::Io_mem::Range range,
Device::Pci_bar bar, bool)
{
if (!bar.valid() || bar.number != 0)
return;
Ehci_pci pw(base);
Attached_io_mem_dataspace iomem(env, range.start, 0x1000);
Ehci ehci((addr_t)iomem.local_addr<void>());
addr_t offset =
ehci.read<Ehci::Capability_parameters::Extended_cap_pointer>();
/* iterate over EHCI extended capabilities */
while (offset) {
Cap cap(base + offset);
if (cap.read<Cap::Pointer::Id>() != Cap::Pointer::Id::SYNC)
break;
bool bios_owned = cap.read<Cap::Bios_semaphore>();
if (bios_owned) cap.write<Cap::Os_semaphore>(1);
enum { MAX_ROUNDS = 1000000 };
unsigned rounds = MAX_ROUNDS;
while (cap.read<Cap::Bios_semaphore>() && --rounds) ;
if (!rounds) cap.write<Cap::Bios_semaphore>(0);
cap.write<Cap::Usb_legacy_control_status>(0);
if (bios_owned) ehci.write<Ehci::Configure_flag>(0);
offset = cap.read<Cap::Pointer::Next>();
}
});
}

View File

@ -11,15 +11,20 @@
* under the terms of the GNU Affero General Public License version 3.
*/
#include <io_port_session/connection.h>
#include <pci/config.h>
#include <device.h>
namespace Driver {
static void pci_uhci_quirks(Device::Pci_config cfg, addr_t base);
static void pci_uhci_quirks(Env &, Device const &,
Device::Pci_config, addr_t);
}
void Driver::pci_uhci_quirks(Device::Pci_config cfg, addr_t base)
void Driver::pci_uhci_quirks(Env & env,
Device const & dev,
Device::Pci_config cfg,
addr_t base)
{
enum { UHCI_CLASS_CODE = 0xc0300 };
@ -44,20 +49,70 @@ void Driver::pci_uhci_quirks(Device::Pci_config cfg, addr_t base)
using Mmio::Mmio;
};
/* find uhci controller i/o ports */
Device::Io_port_range::Range range { 0, 0 };
dev.for_each_io_port_range([&] (unsigned, Device::Io_port_range::Range r) {
if (!range.size) range = r; });
Io_port_connection io_ports(env, range.addr, range.size);
Uhci config(base);
bool have_to_reset = false;
uint16_t UHCI_CMD = range.addr;
uint16_t UHCI_INTR = range.addr + 4;
struct Uhci_command : Register<16>
{
struct Enable : Bitfield<0,1> {};
struct Reset : Bitfield<1,1> {};
struct Global_suspend : Bitfield<3,1> {};
struct Config : Bitfield<6,1> {};
};
struct Uhci_irq_status : Register<16>
{
struct Resume : Bitfield<1,1> {};
};
using Reg = Uhci::Usb_legacy_support;
/* BIOS handover */
/***************************
** BIOS handover + reset **
***************************/
Reg::access_t reg = 0;
Reg::Trap_by_60h_read_status::set(reg,1);
Reg::Trap_by_60h_write_status::set(reg,1);
Reg::Trap_by_64h_read_status::set(reg,1);
Reg::Trap_by_64h_write_status::set(reg,1);
Reg::End_of_a20gate_pass_through_status::set(reg,1);
Reg::Usb_pirq_enable::set(reg,1);
if (config.read<Reg>() & ~reg)
have_to_reset = true;
if (!have_to_reset) {
Uhci_command::access_t cmd = io_ports.inw(UHCI_CMD);
if (Uhci_command::Enable::get(cmd) ||
!Uhci_command::Config::get(cmd) ||
!Uhci_command::Global_suspend::get(cmd))
have_to_reset = true;
}
if (!have_to_reset) {
Uhci_irq_status::access_t intr = io_ports.inw(UHCI_INTR);
if (intr & ~Uhci_irq_status::Resume::mask())
have_to_reset = true;
}
if (!have_to_reset)
return;
config.write<Reg>(reg);
io_ports.outw(UHCI_CMD, Uhci_command::Reset::bits(1));
io_ports.outw(UHCI_INTR, 0);
io_ports.outw(UHCI_CMD, 0);
if (cfg.vendor_id == 0x8086)
config.write<Uhci::Usb_resume_intel>(0);
}