| // 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/acpi-memory-region.h" |
| |
| #include <lib/ddk/driver.h> |
| #include <lib/stdcompat/span.h> |
| #include <lib/zircon-internal/align.h> |
| #include <lib/zx/resource.h> |
| #include <lib/zx/result.h> |
| #include <lib/zx/vmar.h> |
| #include <lib/zx/vmo.h> |
| #include <zircon/assert.h> |
| #include <zircon/errors.h> |
| #include <zircon/types.h> |
| |
| #include <climits> |
| #include <cstddef> |
| #include <cstdint> |
| #include <limits> |
| |
| #include "src/graphics/display/drivers/intel-i915/acpi-memory-region-util.h" |
| |
| namespace i915 { |
| |
| // static |
| zx::result<AcpiMemoryRegion> AcpiMemoryRegion::Create(zx_device_t* parent, zx_paddr_t region_base, |
| size_t region_size) { |
| auto [first_page_physical_address, vmo_size] = RoundToPageBoundaries(region_base, region_size); |
| |
| // The static_cast below is lossless because of this. |
| static_assert(PAGE_SIZE < std::numeric_limits<uint32_t>::max()); |
| |
| // The offset of the region's start, within the region's first page. |
| const uint32_t page_offset = static_cast<uint32_t>(region_base ^ first_page_physical_address); |
| |
| // The IGD OpRegion specification asks the boot firmware to place the memory |
| // regions we're interested in (Memory OpRegion, extended Video BIOS Table) in |
| // one ACPI custom Operation Region of Type 4 (NVS = Non-Volatile Sleeping |
| // Memory). So, this entire method should be replaced by an ACPI driver call |
| // that returns a VMO representing the ACPI custom Operation Region that |
| // contains a given physical address. |
| zx::vmo region_vmo; |
| zx_status_t status = zx::vmo::create_physical(*zx::unowned_resource(get_mmio_resource(parent)), |
| first_page_physical_address, vmo_size, ®ion_vmo); |
| if (status != ZX_OK) { |
| return zx::error_result(status); |
| } |
| |
| zx_vaddr_t first_page_address; |
| status = zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, /*vmar_offset=*/0, |
| region_vmo, /*vmo_offset=*/0, vmo_size, &first_page_address); |
| if (status != ZX_OK) { |
| return zx::error_result(status); |
| } |
| |
| const zx_vaddr_t region_address = static_cast<zx_vaddr_t>(first_page_address + page_offset); |
| const cpp20::span<uint8_t> region_data(reinterpret_cast<uint8_t*>(region_address), region_size); |
| return zx::ok(AcpiMemoryRegion(std::move(region_vmo), region_data)); |
| } |
| |
| AcpiMemoryRegion::AcpiMemoryRegion(zx::vmo region_vmo, cpp20::span<uint8_t> region_data) |
| : region_data_(region_data), region_vmo_(std::move(region_vmo)) { |
| ZX_ASSERT(!region_data.empty()); |
| } |
| |
| AcpiMemoryRegion::AcpiMemoryRegion(AcpiMemoryRegion&& rhs) noexcept |
| : region_data_(rhs.region_data_), region_vmo_(std::move(rhs.region_vmo_)) { |
| rhs.region_data_ = cpp20::span<uint8_t>(); |
| } |
| |
| AcpiMemoryRegion& AcpiMemoryRegion::operator=(AcpiMemoryRegion&& rhs) noexcept { |
| // We use the swapping approach because the best alternative seems |
| // non-trivial. The alternative is to destroy the state in `lhs` and turn |
| // `rhs` into an empty region. Destroying the state in `lhs` comes down to a |
| // conditional VMAR unmapping, and closing the underlying the VMO. |
| |
| std::swap(region_vmo_, rhs.region_vmo_); |
| std::swap(region_data_, rhs.region_data_); |
| return *this; |
| } |
| |
| AcpiMemoryRegion::~AcpiMemoryRegion() { |
| if (region_vmo_.is_valid()) { |
| const zx_vaddr_t region_base = reinterpret_cast<zx_vaddr_t>(region_data_.data()); |
| auto [first_page_address, vmo_size] = RoundToPageBoundaries(region_base, region_data_.size()); |
| |
| zx_status_t unmap_status = zx::vmar::root_self()->unmap(region_base, vmo_size); |
| ZX_DEBUG_ASSERT(unmap_status == ZX_OK); |
| } |
| } |
| |
| } // namespace i915 |