blob: 951554f75cfb63f8ef78ddbd3ce41f55cdcdc6dd [file] [log] [blame]
// 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, &region_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