blob: 842ef3e20f57d0c769ab1e3bdf0d1f482758ddfd [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/gtt.h"
#include <lib/ddk/debug.h>
#include <lib/mmio/mmio.h>
#include <lib/zircon-internal/align.h>
#include <climits>
#include <cstdlib>
#include <limits>
#include <memory>
#include <utility>
#include <fbl/algorithm.h>
#include "src/graphics/display/drivers/intel-i915/registers.h"
#include "src/graphics/display/drivers/intel-i915/tiling.h"
#define PAGE_PRESENT (1 << 0)
namespace {
constexpr size_t kEntriesPerPinTxn = PAGE_SIZE / sizeof(zx_paddr_t);
inline uint64_t gen_pte_encode(uint64_t bus_addr) {
// Make every page present so we don't have to deal with padding for framebuffers
return bus_addr | PAGE_PRESENT;
}
inline uint32_t get_pte_offset(uint32_t idx) {
return static_cast<uint32_t>(idx * sizeof(uint64_t));
}
} // namespace
namespace i915 {
Gtt::Gtt()
: region_allocator_(RegionAllocator::RegionPool::Create(std::numeric_limits<size_t>::max())) {}
Gtt::~Gtt() {
if (scratch_buffer_paddr_) {
scratch_buffer_pmt_.unpin();
}
}
zx_status_t Gtt::Init(const ddk::Pci& pci, fdf::MmioBuffer buffer, uint32_t fb_offset) {
ZX_DEBUG_ASSERT(pci.is_valid());
buffer_ = std::move(buffer);
zx_status_t status = pci.GetBti(0, &bti_);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to get bti (%d)", status);
return status;
}
zx_info_bti_t info;
status = bti_.get_info(ZX_INFO_BTI, &info, sizeof(zx_info_bti_t), nullptr, nullptr);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to fetch bti info (%d)", status);
return status;
}
min_contiguity_ = info.minimum_contiguity;
// Calculate the size of the gtt.
auto gmch_gfx_ctrl = registers::GmchGfxControl::Get().FromValue(0);
status = pci.ReadConfig16(gmch_gfx_ctrl.kAddr, gmch_gfx_ctrl.reg_value_ptr());
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to read GfxControl");
return status;
}
uint32_t gtt_size = gmch_gfx_ctrl.gtt_mappable_mem_size();
zxlogf(TRACE, "Gtt::Init gtt_size (for page tables) 0x%x", gtt_size);
if (gtt_size == 0) {
// IHD-OS-KBL-Vol 5-1.17 (intel-gfx-prm-osrc-kbl-vol05-memory_views.pdf p.35) lists that the GPU
// supports a global GTT and the size can be either 128KB, 256KB, or 512KB, which further map to
// aperture sizes of 128MB, 256MB, and 512MB). Here we are treating a 0-size aperture as
// illegal.
//
// TODO(armansito): The "GMCH Graphics Control" (GGC_0_0_0_PCI) register documentation says that
// the |gtt_size| value here actually corresponds to "the amount of main memory that is
// pre-allocated to supported the Internal GTT", which comes in sizes of 2MB, 4MB, and 8MB. Is
// it an error if the BIOS does not pre-allocate this memory?
zxlogf(ERROR, "The BIOS pre-allocated memory size for the internal GTT is 0! Aborting.");
return ZX_ERR_INTERNAL;
}
status = zx::vmo::create(PAGE_SIZE, 0, &scratch_buffer_);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to alloc scratch buffer (%d)", status);
return status;
}
status = bti_.pin(ZX_BTI_PERM_READ, scratch_buffer_, 0, PAGE_SIZE, &scratch_buffer_paddr_, 1,
&scratch_buffer_pmt_);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to look up scratch buffer (%d)", status);
return status;
}
scratch_buffer_.op_range(ZX_VMO_OP_CACHE_CLEAN, 0, PAGE_SIZE, nullptr, 0);
// Populate the gtt with the scratch buffer. If we've been given an offset for the bootloader
// framebuffer, then leave the range up to |fb_offset| unchanged as the bootloader framebuffer
// gets allocated out of stolen memory.
uint32_t offset = ZX_ROUNDUP(fb_offset, PAGE_SIZE);
uint64_t pte = gen_pte_encode(scratch_buffer_paddr_);
unsigned i;
for (i = offset / PAGE_SIZE; i < gtt_size / sizeof(uint64_t); i++) {
buffer_->Write<uint64_t>(pte, get_pte_offset(i));
}
buffer_->Read<uint32_t>(get_pte_offset(i - 1)); // Posting read
gfx_mem_size_ = gtt_size / sizeof(uint64_t) * PAGE_SIZE;
return region_allocator_.AddRegion({.base = offset, .size = gfx_mem_size_ - offset});
}
zx_status_t Gtt::AllocRegion(uint32_t length, uint32_t align_pow2,
std::unique_ptr<GttRegionImpl>* region_out) {
uint32_t region_length = ZX_ROUNDUP(length, PAGE_SIZE);
RegionAllocator::Region::UPtr region;
if (region_allocator_.GetRegion(region_length, align_pow2, region) != ZX_OK) {
return ZX_ERR_NO_RESOURCES;
}
fbl::AllocChecker ac;
auto r = fbl::make_unique_checked<GttRegionImpl>(&ac, this, std::move(region));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
*region_out = std::move(r);
return ZX_OK;
}
void Gtt::SetupForMexec(uintptr_t stolen_fb, uint32_t length) {
// Just clobber everything to get the bootloader framebuffer to work.
unsigned pte_idx = 0;
for (unsigned i = 0; i < ZX_ROUNDUP(length, PAGE_SIZE) / PAGE_SIZE; i++, stolen_fb += PAGE_SIZE) {
uint64_t pte = gen_pte_encode(stolen_fb);
buffer_->Write<uint64_t>(pte, get_pte_offset(pte_idx++));
}
buffer_->Read<uint32_t>(get_pte_offset(pte_idx - 1)); // Posting read
}
GttRegionImpl::GttRegionImpl(Gtt* gtt, RegionAllocator::Region::UPtr region)
: region_(std::move(region)), gtt_(gtt) {}
GttRegionImpl::~GttRegionImpl() { ClearRegion(); }
zx_status_t GttRegionImpl::PopulateRegion(zx_handle_t vmo, uint64_t page_offset, uint64_t length,
bool writable) {
if (length > region_->size) {
return ZX_ERR_INVALID_ARGS;
}
if (mapped_end_ != 0) {
return ZX_ERR_ALREADY_BOUND;
}
vmo_ = vmo;
zx_paddr_t paddrs[kEntriesPerPinTxn];
zx_status_t status;
uint32_t num_pages = static_cast<uint32_t>(ZX_ROUNDUP(length, PAGE_SIZE) / PAGE_SIZE);
uint64_t vmo_offset = page_offset * PAGE_SIZE;
uint32_t pte_idx = static_cast<uint32_t>(region_->base / PAGE_SIZE);
uint32_t pte_idx_end = pte_idx + num_pages;
size_t num_pins = ZX_ROUNDUP(length, gtt_->min_contiguity_) / gtt_->min_contiguity_;
fbl::AllocChecker ac;
pmts_.reserve(num_pins, &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
int32_t flags = ZX_BTI_COMPRESS | ZX_BTI_PERM_READ | (writable ? ZX_BTI_PERM_WRITE : 0);
while (pte_idx < pte_idx_end) {
uint64_t cur_len = (pte_idx_end - pte_idx) * PAGE_SIZE;
if (cur_len > kEntriesPerPinTxn * gtt_->min_contiguity_) {
cur_len = kEntriesPerPinTxn * gtt_->min_contiguity_;
}
uint64_t actual_entries = ZX_ROUNDUP(cur_len, gtt_->min_contiguity_) / gtt_->min_contiguity_;
zx::pmt pmt;
status = gtt_->bti_.pin(flags, *zx::unowned_vmo(vmo_), vmo_offset, cur_len, paddrs,
actual_entries, &pmt);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to get paddrs (%d)", status);
return status;
}
vmo_offset += cur_len;
mapped_end_ = static_cast<uint32_t>(vmo_offset);
pmts_.push_back(std::move(pmt), &ac);
ZX_DEBUG_ASSERT(ac.check()); // Shouldn't fail because of the reserve above.
for (unsigned i = 0; i < actual_entries; i++) {
for (unsigned j = 0; j < gtt_->min_contiguity_ / PAGE_SIZE && pte_idx < pte_idx_end; j++) {
uint64_t pte = gen_pte_encode(paddrs[i] + j * PAGE_SIZE);
gtt_->buffer_->Write<uint64_t>(pte, get_pte_offset(pte_idx++));
}
}
}
gtt_->buffer_->Read<uint32_t>(get_pte_offset(pte_idx - 1)); // Posting read
return ZX_OK;
}
void GttRegionImpl::ClearRegion() {
if (!region_) {
return;
}
uint32_t pte_idx = static_cast<uint32_t>(region_->base / PAGE_SIZE);
uint64_t pte = gen_pte_encode(gtt_->scratch_buffer_paddr_);
auto mmio_space = &gtt_->buffer_.value();
for (unsigned i = 0; i < mapped_end_ / PAGE_SIZE; i++) {
uint32_t pte_offset = get_pte_offset(pte_idx++);
mmio_space->Write<uint64_t>(pte, pte_offset);
}
if (mapped_end_) {
mmio_space->Read<uint32_t>(get_pte_offset(pte_idx - 1)); // Posting read
}
for (zx::pmt& pmt : pmts_) {
if (pmt.unpin() != ZX_OK) {
zxlogf(INFO, "Error unpinning gtt region");
}
}
pmts_.reset();
mapped_end_ = 0;
if (vmo_ != ZX_HANDLE_INVALID) {
zx_handle_close(vmo_);
}
vmo_ = ZX_HANDLE_INVALID;
}
void GttRegionImpl::SetRotation(uint32_t rotation, const image_metadata_t& image_metadata) {
bool rotated = (rotation == FRAME_TRANSFORM_ROT_90 || rotation == FRAME_TRANSFORM_ROT_270);
if (rotated == is_rotated_) {
return;
}
is_rotated_ = rotated;
// Displaying an image with 90/270 degree rotation requires rearranging the image's
// GTT mapping. Since permutations are composed of disjoint cycles and because we can
// calculate each page's location in the new mapping, we can remap the image by shifting
// the GTT entries around each cycle. We use one of the ignored bits in the global GTT
// PTEs to keep track of whether or not entries have been rotated.
constexpr uint32_t kRotatedFlag = (1 << 1);
uint64_t mask = is_rotated_ ? kRotatedFlag : 0;
uint32_t width = [&]() {
uint64_t width = bytes_per_row() / get_tile_byte_width(image_metadata.tiling_type);
ZX_DEBUG_ASSERT_MSG(width <= std::numeric_limits<uint32_t>::max(), "%lu overflows uint32_t",
width);
return static_cast<uint32_t>(width);
}();
uint32_t height = height_in_tiles(image_metadata.tiling_type, image_metadata.height);
auto mmio_space = &gtt_->buffer_.value();
uint32_t pte_offset = static_cast<uint32_t>(base() / PAGE_SIZE);
for (uint32_t i = 0; i < size() / PAGE_SIZE; i++) {
uint64_t entry = mmio_space->Read<uint64_t>(get_pte_offset(i + pte_offset));
uint32_t position = i;
// If the entry has already been cycled into the correct place, the
// loop check will immediately fail.
while ((entry & kRotatedFlag) != mask) {
if (mask) {
uint32_t x = position % width;
uint32_t y = position / width;
position = ((x + 1) * height) - y - 1;
} else {
uint32_t x = position % height;
uint32_t y = position / height;
position = ((height - x - 1) * width) + y;
}
uint32_t dest_offset = get_pte_offset(position + pte_offset);
uint64_t next_entry = mmio_space->Read<uint64_t>(dest_offset);
mmio_space->Write<uint64_t>(entry ^ kRotatedFlag, dest_offset);
entry = next_entry;
}
}
}
} // namespace i915