| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/graphics/display/drivers/intel-i915/firmware-bridge.h" |
| |
| #include <lib/ddk/debug.h> |
| #include <lib/zx/result.h> |
| #include <zircon/assert.h> |
| #include <zircon/errors.h> |
| #include <zircon/status.h> |
| #include <zircon/types.h> |
| |
| #include <cstddef> |
| |
| #include <hwreg/bitfields.h> |
| |
| #include "src/graphics/display/drivers/intel-i915/poll-until.h" |
| |
| // Our implementation is based on the following versions of Intel's ACPI IGD |
| // (Integrated Graphics Device) OpRegion Specification: |
| // * First release: OpRegion versions 1.0-2.0, Revision 1.0, October 2008 - |
| // at https://01.org/sites/default/files/documentation/acpi_igd_opregion_spec_0.pdf |
| // announced at |
| // https://lists.freedesktop.org/archives/xorg/2008-October/039119.html |
| // * Document 728117: for Skylake Processors, Revision 0.5, September 2016 - |
| // at https://01.org/sites/default/files/documentation/skl_opregion_rev0p5.pdf |
| // * Document 621530: OpRegion versions 2.0-2.4, Revision 0.7.1, August 2020 |
| // |
| // We consider the specification's first release obsoleted, because there are |
| // areas where it is inconsistent with the newer versions, and the hardware that |
| // we support matches the description in the newer versions. The first release |
| // should only be used to supplement the information in the other documents, and |
| // it should never be the only reference for any aspect of our implementation. |
| |
| namespace i915 { |
| |
| PciConfigOpRegion::PciConfigOpRegion(ddk::Pci& pci) : pci_(pci) {} |
| |
| PciConfigOpRegion::~PciConfigOpRegion() = default; |
| |
| namespace { |
| |
| // The ASLS (ASL Storage) register is reserved for the system boot firmware to |
| // pass the physical address of the Memory OpRegion to us (the graphics driver). |
| // |
| // The Memory OpRegion was intended for passing data between the system firmware |
| // (BIOS), which was expected to be using ASL (ACPI Source Language), and the |
| // graphics driver. This explains both the ASLS (ASL Storage) register name, and |
| // the name "Mailboxes" for the areas of the Memory OpRegion. |
| // |
| // First release: Section 2.3 "OpRegion Initialization", page 27 |
| // 728117: Section 2.2 "GMCH PCI Config OpRegion", page 9 |
| // 621530: Section 2.3 "GMCH PCI Config OpRegion", page 10 |
| constexpr uint16_t kAslStoragePciOffset = 0xfc; |
| |
| // Delivery method for GMCH trigger events. |
| // |
| // This describes the behavior of GMCH (Graphics and Memory Controller Hub) |
| // hardware when software triggers an event via the SWSCI (driver Software / |
| // System Control Interrupt) register. |
| enum class GraphicsMediaHubEventDelivery { |
| // SWSCI delivered using an SMI (System Management Interrupt). |
| // |
| // Indicates that the boot firmware does not support the OpRegion protocol. |
| kSystemManagementInterrupt = 0, |
| |
| // SWSCI delivered using an ACPI SCI (System Control Interrupt). |
| // |
| // SCI are introduced in the ACPI (Advanced Configuration and Power Interface) |
| // specification. The operating system's ACPI driver is expected to execute |
| // ALS code in response to the events. |
| kSystemControlInterrupt = 1, |
| }; |
| |
| // The SWSCI (driver Software / System Control Interrupts) register allows the |
| // graphics driver to invoke system firmware (BIOS) code. |
| // |
| // This register is not covered in Document 621530. |
| // |
| // First release: Section 7.1.1 "Software SCI Register", pages 91-92 |
| // 728117: Section 4 "Software SCI Mechanism", page 76 and Section 5.1.1 "GMCH |
| // SWSCI Register", pages 112-113 |
| class SoftwareSystemControlInterrupt |
| : public hwreg::RegisterBase<SoftwareSystemControlInterrupt, uint16_t> { |
| public: |
| // If true, the GMCH hardware delivers trigger events using ACPI SCI. |
| // |
| // If false, the GMCH (Graphics and Media Controller Hub) hardware delivers |
| // events via SMI (System Maangement Interrupt). This setting indicates that |
| // the boot firmware does not support the OpRegion protocol. |
| DEF_ENUM_FIELD(GraphicsMediaHubEventDelivery, 15, 15, event_delivery); |
| |
| // "Software scratchpad" field that we treat as reserved. |
| DEF_FIELD(14, 1, scratchpad); |
| |
| // Transitioning this bit 0->1 triggers the GMCH hardware to deliver an event. |
| // |
| // The event delivered by the GMCH hardware is configured using the |
| // `event_delivery_select` field. |
| // |
| // The graphics driver should only transition this field from 0 to 1, to make |
| // invoke the system firmware. The system firmware transitions the field back |
| // from 1 to 0 when it finishes servicing the driver's request. |
| DEF_BIT(0, entry_trigger); |
| |
| // First release: Section 2.4.1 "SWSCI - Software SCI Register", page 29 |
| // 728117: Section 2.2 "GMCH PCI Config OpRegion", pages 8-9 |
| static constexpr uint16_t kPciOffset = 0xe8; |
| |
| // This register does not live in the MMIO space. It must be read from the PCI |
| // configuration space, at `kPciOffset`. |
| static auto GetFromValue(uint16_t value) { |
| return hwreg::RegisterAddr<SoftwareSystemControlInterrupt>(0).FromValue(value); |
| } |
| }; |
| |
| } // namespace |
| |
| zx::result<zx_paddr_t> PciConfigOpRegion::ReadMemoryOpRegionAddress() { |
| uint32_t asl_storage_value; |
| const zx_status_t status = pci_.ReadConfig32(kAslStoragePciOffset, &asl_storage_value); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "PCI OpRegion register read error: %s", zx_status_get_string(status)); |
| return zx::error_result(status); |
| } |
| |
| // The introduction to Section 3 "OpRegion Memory Layout" states that a zero |
| // value in the ASLS (ASL Storage) register indicates lack of BIOS support. |
| if (!asl_storage_value) { |
| return zx::error_result(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| return zx::ok(asl_storage_value); |
| } |
| |
| zx::result<bool> PciConfigOpRegion::IsSystemControlInterruptInUse() { |
| uint16_t swsci_value; |
| const zx_status_t status = |
| pci_.ReadConfig16(SoftwareSystemControlInterrupt::kPciOffset, &swsci_value); |
| if (status != ZX_OK) { |
| return zx::error_result(status); |
| } |
| |
| auto swsci = SoftwareSystemControlInterrupt::GetFromValue(swsci_value); |
| if (swsci.event_delivery() != GraphicsMediaHubEventDelivery::kSystemControlInterrupt) { |
| // The event delivery mechanism does not match the OpRegion specification. |
| return zx::error_result(ZX_ERR_NOT_SUPPORTED); |
| } |
| return zx::ok(swsci.entry_trigger() != 0); |
| } |
| |
| zx::result<> PciConfigOpRegion::TriggerSystemControlInterrupt() { |
| // This code below is very similar to IsSystemControlInterruptInUse(). The |
| // key difference is that we reuse the raw register value in a |
| // read-modify-write operation, in order to preserve the `scratchpad` field. |
| uint16_t swsci_value; |
| const zx_status_t read_status = |
| pci_.ReadConfig16(SoftwareSystemControlInterrupt::kPciOffset, &swsci_value); |
| if (read_status != ZX_OK) { |
| zxlogf(ERROR, "PCI OpRegion register read error: %s", zx_status_get_string(read_status)); |
| return zx::error_result(read_status); |
| } |
| auto swsci = SoftwareSystemControlInterrupt::GetFromValue(swsci_value); |
| if (swsci.event_delivery() != GraphicsMediaHubEventDelivery::kSystemControlInterrupt) { |
| return zx::error_result(ZX_ERR_NOT_SUPPORTED); |
| } |
| if (swsci.entry_trigger() != 0) { |
| zxlogf(ERROR, "PCI OpRegion interrupt trigger already armed. Bad boot firmware handoff?"); |
| return zx::error_result(ZX_ERR_BAD_STATE); |
| } |
| |
| const zx_status_t write_status = pci_.WriteConfig16(SoftwareSystemControlInterrupt::kPciOffset, |
| swsci.set_entry_trigger(1).reg_value()); |
| if (write_status != ZX_OK) { |
| zxlogf(ERROR, "PCI OpRegion register write error: %s", zx_status_get_string(write_status)); |
| } |
| return zx::make_result(write_status); |
| } |
| |
| } // namespace i915 |