diff --git a/repos/os/src/drivers/platform/device.cc b/repos/os/src/drivers/platform/device.cc index f26a9fe93c..fa584fc357 100644 --- a/repos/os/src/drivers/platform/device.cc +++ b/repos/os/src/drivers/platform/device.cc @@ -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); + }); } diff --git a/repos/os/src/drivers/platform/pci.cc b/repos/os/src/drivers/platform/pci.cc index bba8e33647..b211c7e056 100644 --- a/repos/os/src/drivers/platform/pci.cc +++ b/repos/os/src/drivers/platform/pci.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -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() }; 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(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(cmd); } + + void apply_quirks() + { + Config::Command::access_t cmd = + _config.read(); + 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(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(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) diff --git a/repos/os/src/drivers/platform/pci.h b/repos/os/src/drivers/platform/pci.h index ce36bb2927..08126f5f33 100644 --- a/repos/os/src/drivers/platform/pci.h +++ b/repos/os/src/drivers/platform/pci.h @@ -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, diff --git a/repos/os/src/drivers/platform/pci_ehci.h b/repos/os/src/drivers/platform/pci_ehci.h new file mode 100644 index 0000000000..49b14314cb --- /dev/null +++ b/repos/os/src/drivers/platform/pci_ehci.h @@ -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 +#include + +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()); + addr_t offset = + ehci.read(); + + /* iterate over EHCI extended capabilities */ + while (offset) { + Cap cap(base + offset); + if (cap.read() != Cap::Pointer::Id::SYNC) + break; + + bool bios_owned = cap.read(); + + if (bios_owned) cap.write(1); + + enum { MAX_ROUNDS = 1000000 }; + unsigned rounds = MAX_ROUNDS; + while (cap.read() && --rounds) ; + + if (!rounds) cap.write(0); + + cap.write(0); + + if (bios_owned) ehci.write(0); + + offset = cap.read(); + } + }); +} diff --git a/repos/os/src/drivers/platform/pci_uhci.h b/repos/os/src/drivers/platform/pci_uhci.h index 9d52a9bdd5..713575d52f 100644 --- a/repos/os/src/drivers/platform/pci_uhci.h +++ b/repos/os/src/drivers/platform/pci_uhci.h @@ -11,15 +11,20 @@ * under the terms of the GNU Affero General Public License version 3. */ +#include #include #include 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) + 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); + 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(0); }