| // Copyright 2017 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 <ddk/protocol/pci.h> |
| #include <ddk/protocol/pci-lib.h> |
| |
| #include <climits> |
| #include <fbl/algorithm.h> |
| #include <limits> |
| #include <stdlib.h> |
| #include <utility> |
| |
| #include "intel-i915.h" |
| #include "gtt.h" |
| #include "macros.h" |
| #include "tiling.h" |
| #include "registers.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) { |
| constexpr uint32_t GTT_BASE_OFFSET = 0x800000; |
| return static_cast<uint32_t>(GTT_BASE_OFFSET + (idx * sizeof(uint64_t))); |
| } |
| |
| } |
| |
| 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(Controller* controller) { |
| controller_ = controller; |
| |
| zx_status_t status = pci_get_bti(controller->pci(), 0, bti_.reset_and_get_address()); |
| if (status != ZX_OK) { |
| LOG_ERROR("Failed to get bti (%d)\n", 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) { |
| LOG_ERROR("Failed to fetch bti info (%d)\n", 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_config_read16(controller_->pci(), gmch_gfx_ctrl.kAddr, |
| gmch_gfx_ctrl.reg_value_ptr()); |
| if (status != ZX_OK) { |
| LOG_ERROR("Failed to read GfxControl\n"); |
| return status; |
| } |
| uint32_t gtt_size = gmch_gfx_ctrl.gtt_mappable_mem_size(); |
| LOG_TRACE("Gtt::Init gtt_size (for page tables) 0x%x\n", gtt_size); |
| |
| status = zx::vmo::create(PAGE_SIZE, 0, &scratch_buffer_); |
| if (status != ZX_OK) { |
| LOG_ERROR("Failed to alloc scratch buffer (%d)\n", 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) { |
| LOG_ERROR("Failed to look up scratch buffer (%d)\n", status); |
| return status; |
| } |
| |
| scratch_buffer_.op_range(ZX_VMO_OP_CACHE_CLEAN, 0, PAGE_SIZE, nullptr, 0); |
| |
| // Populate the gtt with the scratch buffer. |
| uint64_t pte = gen_pte_encode(scratch_buffer_paddr_); |
| unsigned i; |
| for (i = 0; i < gtt_size / sizeof(uint64_t); i++) { |
| controller_->mmio_space()->Write<uint64_t>(pte, get_pte_offset(i)); |
| } |
| controller_->mmio_space()->Read<uint32_t>(get_pte_offset(i - i)); // Posting read |
| |
| gfx_mem_size_ = gtt_size / sizeof(uint64_t) * PAGE_SIZE; |
| return region_allocator_.AddRegion({ .base = 0, .size = gfx_mem_size_ }); |
| } |
| |
| zx_status_t Gtt::AllocRegion(uint32_t length, uint32_t align_pow2, |
| fbl::unique_ptr<GttRegion>* region_out) { |
| uint32_t region_length = ROUNDUP(length, PAGE_SIZE); |
| fbl::AllocChecker ac; |
| auto r = fbl::make_unique_checked<GttRegion>(&ac, this); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| if (region_allocator_.GetRegion(region_length, align_pow2, r->region_) != ZX_OK) { |
| return ZX_ERR_NO_RESOURCES; |
| } |
| *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 < ROUNDUP(length, PAGE_SIZE) / PAGE_SIZE; i++, stolen_fb += PAGE_SIZE) { |
| uint64_t pte = gen_pte_encode(stolen_fb); |
| controller_->mmio_space()->Write<uint64_t>(pte, get_pte_offset(pte_idx++)); |
| } |
| controller_->mmio_space()->Read<uint32_t>(get_pte_offset(pte_idx - 1)); // Posting read |
| } |
| |
| GttRegion::GttRegion(Gtt* gtt) : gtt_(gtt), is_rotated_(false) {} |
| |
| GttRegion::~GttRegion() { |
| ClearRegion(false); |
| } |
| |
| zx_status_t GttRegion::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>(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 = 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 = 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) { |
| LOG_ERROR("Failed to get paddrs (%d)\n", 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_->controller_->mmio_space()->Write<uint64_t>(pte, get_pte_offset(pte_idx++)); |
| } |
| } |
| } |
| |
| gtt_->controller_->mmio_space()->Read<uint32_t>(get_pte_offset(pte_idx - 1)); // Posting read |
| return ZX_OK; |
| } |
| |
| void GttRegion::ClearRegion(bool close_vmo) { |
| 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_->controller_->mmio_space(); |
| |
| 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); |
| } |
| |
| mmio_space->Read<uint32_t>(get_pte_offset(pte_idx - 1)); // Posting read |
| |
| for (zx::pmt& pmt : pmts_) { |
| if (pmt.unpin() != ZX_OK) { |
| LOG_INFO("Error unpinning gtt region\n"); |
| } |
| } |
| pmts_.reset(); |
| mapped_end_ = 0; |
| |
| if (close_vmo && vmo_ != ZX_HANDLE_INVALID) { |
| zx_handle_close(vmo_); |
| } |
| vmo_ = ZX_HANDLE_INVALID; |
| } |
| |
| void GttRegion::SetRotation(uint32_t rotation, const image_t& image) { |
| 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 = width_in_tiles(image.type, image.width, image.pixel_format); |
| uint32_t height = height_in_tiles(image.type, image.height, image.pixel_format); |
| |
| auto mmio_space = gtt_->controller_->mmio_space(); |
| 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 |