| // 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 = >t_->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 = >t_->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 |