| // Copyright 2016 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 <string.h> |
| |
| #include <ddk/binding.h> |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/protocol/intel-gpu-core.h> |
| #include <ddk/protocol/pci.h> |
| #include <hw/inout.h> |
| #include <hw/pci.h> |
| |
| #include <assert.h> |
| #include <fbl/unique_ptr.h> |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| #include <lib/zx/vmar.h> |
| #include <lib/zx/vmo.h> |
| |
| #include "dp-display.h" |
| #include "hdmi-display.h" |
| #include "intel-i915.h" |
| #include "macros.h" |
| #include "pci-ids.h" |
| #include "registers.h" |
| #include "registers-ddi.h" |
| #include "registers-dpll.h" |
| #include "registers-pipe.h" |
| #include "registers-transcoder.h" |
| #include "registers.h" |
| #include "tiling.h" |
| |
| #define INTEL_I915_BROADWELL_DID (0x1616) |
| |
| #define INTEL_I915_REG_WINDOW_SIZE (0x1000000u) |
| #define INTEL_I915_FB_WINDOW_SIZE (0x10000000u) |
| |
| #define BACKLIGHT_CTRL_OFFSET (0xc8250) |
| #define BACKLIGHT_CTRL_BIT ((uint32_t)(1u << 31)) |
| |
| #define FLAGS_BACKLIGHT 1 |
| |
| namespace { |
| static const zx_pixel_format_t supported_formats[1] = { ZX_PIXEL_FORMAT_ARGB_8888 }; |
| |
| bool pipe_in_use(const fbl::Vector<fbl::unique_ptr<i915::DisplayDevice>>& displays, |
| registers::Pipe pipe) { |
| for (size_t i = 0; i < displays.size(); i++) { |
| if (displays[i]->pipe() == pipe) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static zx_status_t read_pci_config_16(void* ctx, uint16_t addr, uint16_t* value_out) { |
| return static_cast<i915::Controller*>(ctx)->ReadPciConfig16(addr, value_out); |
| } |
| |
| static zx_status_t map_pci_mmio(void* ctx, uint32_t pci_bar, void** addr_out, uint64_t* size_out) { |
| return static_cast<i915::Controller*>(ctx)->MapPciMmio(pci_bar, addr_out, size_out); |
| } |
| |
| static zx_status_t unmap_pci_mmio(void* ctx, uint32_t pci_bar) { |
| return static_cast<i915::Controller*>(ctx)->UnmapPciMmio(pci_bar); |
| } |
| |
| static zx_status_t get_pci_bti(void* ctx, uint32_t index, zx_handle_t* bti_out) { |
| return static_cast<i915::Controller*>(ctx)->GetPciBti(index, bti_out); |
| } |
| |
| static zx_status_t register_interrupt_callback(void* ctx, |
| zx_intel_gpu_core_interrupt_callback_t callback, |
| void* data, uint32_t interrupt_mask) { |
| return static_cast<i915::Controller*>(ctx) |
| ->RegisterInterruptCallback(callback, data, interrupt_mask); |
| } |
| |
| static zx_status_t unregister_interrupt_callback(void* ctx) { |
| return static_cast<i915::Controller*>(ctx)->UnregisterInterruptCallback(); |
| } |
| |
| static uint64_t gtt_get_size(void* ctx) { |
| return static_cast<i915::Controller*>(ctx)->GttGetSize(); |
| } |
| |
| static zx_status_t gtt_alloc(void* ctx, uint64_t page_count, uint64_t* addr_out) { |
| return static_cast<i915::Controller*>(ctx)->GttAlloc(page_count, addr_out); |
| } |
| |
| static zx_status_t gtt_free(void* ctx, uint64_t addr) { |
| return static_cast<i915::Controller*>(ctx)->GttFree(addr); |
| } |
| |
| static zx_status_t gtt_clear(void* ctx, uint64_t addr) { |
| return static_cast<i915::Controller*>(ctx)->GttClear(addr); |
| } |
| |
| static zx_status_t gtt_insert(void* ctx, uint64_t addr, zx_handle_t buffer, uint64_t page_offset, |
| uint64_t page_count) { |
| return static_cast<i915::Controller*>(ctx)->GttInsert(addr, buffer, page_offset, page_count); |
| } |
| |
| static zx_intel_gpu_core_protocol_ops_t i915_gpu_core_protocol_ops = { |
| .read_pci_config_16 = read_pci_config_16, |
| .map_pci_mmio = map_pci_mmio, |
| .unmap_pci_mmio = unmap_pci_mmio, |
| .get_pci_bti = get_pci_bti, |
| .register_interrupt_callback = register_interrupt_callback, |
| .unregister_interrupt_callback = unregister_interrupt_callback, |
| .gtt_get_size = gtt_get_size, |
| .gtt_alloc = gtt_alloc, |
| .gtt_free = gtt_free, |
| .gtt_clear = gtt_clear, |
| .gtt_insert = gtt_insert |
| }; |
| |
| static void gpu_release(void* ctx) { |
| static_cast<i915::Controller*>(ctx)->GpuRelease(); |
| } |
| |
| static zx_protocol_device_t i915_gpu_core_device_proto = {}; |
| |
| static int finish_init(void* arg) { |
| static_cast<i915::Controller*>(arg)->FinishInit(); |
| return 0; |
| } |
| |
| } // namespace |
| |
| namespace i915 { |
| |
| void Controller::EnableBacklight(bool enable) { |
| if (flags_ & FLAGS_BACKLIGHT) { |
| uint32_t tmp = mmio_space_->Read<uint32_t>(BACKLIGHT_CTRL_OFFSET); |
| |
| if (enable) { |
| tmp |= BACKLIGHT_CTRL_BIT; |
| } else { |
| tmp &= ~BACKLIGHT_CTRL_BIT; |
| } |
| |
| mmio_space_->Write<uint32_t>(BACKLIGHT_CTRL_OFFSET, tmp); |
| } |
| } |
| |
| void Controller::HandleHotplug(registers::Ddi ddi, bool long_pulse) { |
| LOG_TRACE("Hotplug detected on ddi %d (long_pulse=%d)\n", ddi, long_pulse); |
| fbl::unique_ptr<DisplayDevice> device = nullptr; |
| uint64_t display_added = INVALID_DISPLAY_ID; |
| uint64_t display_removed = INVALID_DISPLAY_ID; |
| |
| acquire_dc_cb_lock(); |
| { |
| fbl::AutoLock lock(&display_lock_); |
| |
| for (size_t i = 0; i < display_devices_.size(); i++) { |
| if (display_devices_[i]->ddi() == ddi) { |
| if (display_devices_[i]->HandleHotplug(long_pulse)) { |
| LOG_SPEW("hotplug handled by device\n"); |
| release_dc_cb_lock(); |
| return; |
| } |
| device = display_devices_.erase(i); |
| break; |
| } |
| } |
| if (device) { // Existing device was unplugged |
| LOG_INFO("Display %ld unplugged\n", device->id()); |
| display_removed = device->id(); |
| // Make sure the display's resources get freed before reallocating the pipe buffers |
| device.reset(); |
| } else { // New device was plugged in |
| fbl::unique_ptr<DisplayDevice> device = InitDisplay(ddi); |
| if (!device) { |
| LOG_INFO("failed to init hotplug display\n"); |
| } else { |
| uint64_t id = device->id(); |
| if (AddDisplay(fbl::move(device)) == ZX_OK) { |
| display_added = id; |
| } |
| } |
| } |
| |
| ReallocatePipeBuffers(true); |
| } |
| if (dc_cb() && (display_added != INVALID_DISPLAY_ID || display_removed != INVALID_DISPLAY_ID)) { |
| dc_cb()->on_displays_changed(dc_cb_ctx_, |
| &display_added, display_added != INVALID_DISPLAY_ID, |
| &display_removed, display_removed != INVALID_DISPLAY_ID); |
| } |
| release_dc_cb_lock(); |
| } |
| |
| void Controller::HandlePipeVsync(registers::Pipe pipe) { |
| acquire_dc_cb_lock(); |
| |
| if (!dc_cb()) { |
| release_dc_cb_lock(); |
| return; |
| } |
| |
| uint64_t id = INVALID_DISPLAY_ID; |
| void* handles[3]; |
| int32_t handle_count = 0; |
| { |
| fbl::AutoLock lock(&display_lock_); |
| for (auto& display : display_devices_) { |
| if (display->pipe() == pipe) { |
| id = display->id(); |
| |
| registers::PipeRegs regs(pipe); |
| for (int i = 0; i < 3; i++) { |
| auto live_surface = regs.PlaneSurfaceLive(i).ReadFrom(mmio_space()); |
| void* handle = reinterpret_cast<void*>( |
| live_surface.surface_base_addr() << live_surface.kPageShift); |
| if (handle) { |
| handles[handle_count++] = handle; |
| } |
| } |
| break; |
| } |
| } |
| } |
| |
| if (id != INVALID_DISPLAY_ID && handle_count) { |
| dc_cb()->on_display_vsync(dc_cb_ctx_, id, handles, handle_count); |
| } |
| release_dc_cb_lock(); |
| } |
| |
| DisplayDevice* Controller::FindDevice(uint64_t display_id) { |
| for (auto& d : display_devices_) { |
| if (d->id() == display_id) { |
| return d.get(); |
| } |
| } |
| return nullptr; |
| } |
| |
| bool Controller::BringUpDisplayEngine(bool resume) { |
| // Enable PCH Reset Handshake |
| auto nde_rstwrn_opt = registers::NorthDERestetWarning::Get().ReadFrom(mmio_space_.get()); |
| nde_rstwrn_opt.set_rst_pch_handshake_enable(1); |
| nde_rstwrn_opt.WriteTo(mmio_space_.get()); |
| |
| // Wait for Power Well 0 distribution |
| if (!WAIT_ON_US(registers::FuseStatus::Get().ReadFrom(mmio_space_.get()).pg0_dist_status(), 5)) { |
| LOG_ERROR("Power Well 0 distribution failed\n"); |
| return false; |
| } |
| |
| if (resume) { |
| power_.Resume(); |
| } else { |
| cd_clk_power_well_ = power_.GetCdClockPowerWellRef(); |
| } |
| |
| // Enable CDCLK PLL to 337.5mhz if the BIOS didn't already enable it. If it needs to be |
| // something special (i.e. for eDP), assume that the BIOS already enabled it. |
| auto dpll_enable = registers::DpllEnable::Get(registers::DPLL_0).ReadFrom(mmio_space_.get()); |
| if (!dpll_enable.enable_dpll()) { |
| // Set the cd_clk frequency to the minimum |
| auto cd_clk = registers::CdClockCtl::Get().ReadFrom(mmio_space_.get()); |
| cd_clk.set_cd_freq_select(cd_clk.kFreqSelect3XX); |
| cd_clk.set_cd_freq_decimal(cd_clk.kFreqDecimal3375); |
| cd_clk.WriteTo(mmio_space_.get()); |
| |
| // Configure DPLL0 |
| auto dpll_ctl1 = registers::DpllControl1::Get().ReadFrom(mmio_space_.get()); |
| dpll_ctl1.dpll_link_rate(registers::DPLL_0).set(dpll_ctl1.kLinkRate810Mhz); |
| dpll_ctl1.dpll_override(registers::DPLL_0).set(1); |
| dpll_ctl1.dpll_hdmi_mode(registers::DPLL_0).set(0); |
| dpll_ctl1.dpll_ssc_enable(registers::DPLL_0).set(0); |
| dpll_ctl1.WriteTo(mmio_space_.get()); |
| |
| // Enable DPLL0 and wait for it |
| dpll_enable.set_enable_dpll(1); |
| dpll_enable.WriteTo(mmio_space_.get()); |
| if (!WAIT_ON_MS(registers::Lcpll1Control::Get().ReadFrom(mmio_space_.get()).pll_lock(), 5)) { |
| LOG_ERROR("Failed to configure dpll0\n"); |
| return false; |
| } |
| |
| // Do the magic sequence for Changing CD Clock Frequency specified on |
| // intel-gfx-prm-osrc-skl-vol12-display.pdf p.135 |
| constexpr uint32_t kGtDriverMailboxInterface = 0x138124; |
| constexpr uint32_t kGtDriverMailboxData0 = 0x138128; |
| constexpr uint32_t kGtDriverMailboxData1 = 0x13812c; |
| mmio_space_.get()->Write<uint32_t>(kGtDriverMailboxData0, 0x3); |
| mmio_space_.get()->Write<uint32_t>(kGtDriverMailboxData1, 0x0); |
| mmio_space_.get()->Write<uint32_t>(kGtDriverMailboxInterface, 0x80000007); |
| |
| int count = 0; |
| for (;;) { |
| if (!WAIT_ON_US(mmio_space_.get() |
| ->Read<uint32_t>(kGtDriverMailboxInterface) & |
| 0x80000000, |
| 150)) { |
| LOG_ERROR("GT Driver Mailbox driver busy\n"); |
| return false; |
| } |
| if (mmio_space_.get()->Read<uint32_t>(kGtDriverMailboxData0) & 0x1) { |
| break; |
| } |
| if (count++ == 3) { |
| LOG_ERROR("Failed to set cd_clk\n"); |
| return false; |
| } |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(1))); |
| } |
| |
| cd_clk.WriteTo(mmio_space_.get()); |
| |
| mmio_space_.get()->Write<uint32_t>(kGtDriverMailboxData0, 0x3); |
| mmio_space_.get()->Write<uint32_t>(kGtDriverMailboxData1, 0x0); |
| mmio_space_.get()->Write<uint32_t>(kGtDriverMailboxInterface, 0x80000007); |
| } |
| |
| // Enable and wait for DBUF |
| auto dbuf_ctl = registers::DbufCtl::Get().ReadFrom(mmio_space_.get()); |
| dbuf_ctl.set_power_request(1); |
| dbuf_ctl.WriteTo(mmio_space_.get()); |
| |
| if (!WAIT_ON_US(registers::DbufCtl::Get().ReadFrom(mmio_space_.get()).power_state(), 10)) { |
| LOG_ERROR("Failed to enable DBUF\n"); |
| return false; |
| } |
| |
| // We never use VGA, so just disable it at startup |
| constexpr uint16_t kSequencerIdx = 0x3c4; |
| constexpr uint16_t kSequencerData = 0x3c5; |
| constexpr uint8_t kClockingModeIdx = 1; |
| constexpr uint8_t kClockingModeScreenOff = (1 << 5); |
| zx_status_t status = zx_mmap_device_io(get_root_resource(), kSequencerIdx, 2); |
| if (status != ZX_OK) { |
| LOG_ERROR("Failed to map vga ports\n"); |
| return false; |
| } |
| outp(kSequencerIdx, kClockingModeIdx); |
| uint8_t clocking_mode = inp(kSequencerData); |
| if (!(clocking_mode & kClockingModeScreenOff)) { |
| outp(kSequencerIdx, inp(kSequencerData) | kClockingModeScreenOff); |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(100))); |
| |
| auto vga_ctl = registers::VgaCtl::Get().ReadFrom(mmio_space()); |
| vga_ctl.set_vga_display_disable(1); |
| vga_ctl.WriteTo(mmio_space()); |
| } |
| |
| for (unsigned i = 0; i < registers::kPipeCount; i++) { |
| ResetPipe(registers::kPipes[i]); |
| |
| registers::PipeRegs pipe_regs(registers::kPipes[i]); |
| |
| // Disable the cursor watermark |
| for (int wm_num = 0; wm_num < 8; wm_num++) { |
| auto wm = pipe_regs.PlaneWatermark(0, wm_num).FromValue(0); |
| wm.WriteTo(mmio_space()); |
| } |
| |
| // Disable the primary plane watermarks and reset their buffer allocation |
| for (unsigned plane_num = 0; plane_num < registers::kPrimaryPlaneCount; plane_num++) { |
| for (int wm_num = 0; wm_num < 8; wm_num++) { |
| auto wm = pipe_regs.PlaneWatermark(plane_num + 1, wm_num).FromValue(0); |
| wm.WriteTo(mmio_space()); |
| } |
| } |
| } |
| |
| for (unsigned i = 0; i < registers::kTransCount; i++) { |
| ResetTrans(registers::kTrans[i]); |
| } |
| |
| for (unsigned i = 0; i < registers::kDdiCount; i++) { |
| ResetDdi(registers::kDdis[i]); |
| } |
| |
| for (unsigned i = 0; i < registers::kDpllCount; i++) { |
| dplls_[i].use_count = 0; |
| } |
| |
| return true; |
| } |
| |
| void Controller::ResetPipe(registers::Pipe pipe) { |
| registers::PipeRegs pipe_regs(pipe); |
| |
| // Disable planes |
| for (int i = 0; i < 3; i++ ) { |
| pipe_regs.PlaneControl(i).FromValue(0).WriteTo(mmio_space()); |
| pipe_regs.PlaneSurface(i).FromValue(0).WriteTo(mmio_space()); |
| } |
| |
| // Disable the scalers (double buffered on PipeScalerWinSize) |
| pipe_regs.PipeScalerCtrl(0).ReadFrom(mmio_space()).set_enable(0).WriteTo(mmio_space()); |
| pipe_regs.PipeScalerWinSize(0).ReadFrom(mmio_space()).WriteTo(mmio_space()); |
| if (pipe != registers::PIPE_C) { |
| pipe_regs.PipeScalerCtrl(1).ReadFrom(mmio_space()).set_enable(0).WriteTo(mmio_space()); |
| pipe_regs.PipeScalerWinSize(1).ReadFrom(mmio_space()).WriteTo(mmio_space()); |
| } |
| |
| ZX_DEBUG_ASSERT(mtx_trylock(&display_lock_) == thrd_busy); |
| for (unsigned plane_num = 0; plane_num < registers::kPrimaryPlaneCount; plane_num++) { |
| plane_buffers_[pipe][plane_num].start = registers::PlaneBufCfg::kBufferCount; |
| plane_buffers_[pipe][plane_num].minimum = 0; |
| } |
| } |
| |
| bool Controller::ResetTrans(registers::Trans trans) { |
| registers::TranscoderRegs trans_regs(trans); |
| |
| // Disable transcoder and wait it to stop |
| auto trans_conf = trans_regs.Conf().ReadFrom(mmio_space()); |
| trans_conf.set_transcoder_enable(0); |
| trans_conf.WriteTo(mmio_space()); |
| if (!WAIT_ON_MS(!trans_regs.Conf().ReadFrom(mmio_space()).transcoder_state(), 60)) { |
| LOG_ERROR("Failed to reset transcoder\n"); |
| return false; |
| } |
| |
| // Disable transcoder ddi select and clock select |
| auto trans_ddi_ctl = trans_regs.DdiFuncControl().ReadFrom(mmio_space()); |
| trans_ddi_ctl.set_trans_ddi_function_enable(0); |
| trans_ddi_ctl.set_ddi_select(0); |
| trans_ddi_ctl.WriteTo(mmio_space()); |
| |
| if (trans != registers::TRANS_EDP) { |
| auto trans_clk_sel = trans_regs.ClockSelect().ReadFrom(mmio_space()); |
| trans_clk_sel.set_trans_clock_select(0); |
| trans_clk_sel.WriteTo(mmio_space()); |
| } |
| |
| return true; |
| } |
| |
| bool Controller::ResetDdi(registers::Ddi ddi) { |
| registers::DdiRegs ddi_regs(ddi); |
| |
| // Disable the port |
| auto ddi_buf_ctl = ddi_regs.DdiBufControl().ReadFrom(mmio_space()); |
| bool was_enabled = ddi_buf_ctl.ddi_buffer_enable(); |
| ddi_buf_ctl.set_ddi_buffer_enable(0); |
| ddi_buf_ctl.WriteTo(mmio_space()); |
| |
| auto ddi_dp_tp_ctl = ddi_regs.DdiDpTransportControl().ReadFrom(mmio_space()); |
| ddi_dp_tp_ctl.set_transport_enable(0); |
| ddi_dp_tp_ctl.set_dp_link_training_pattern(ddi_dp_tp_ctl.kTrainingPattern1); |
| ddi_dp_tp_ctl.WriteTo(mmio_space()); |
| |
| if (was_enabled && !WAIT_ON_MS(ddi_regs.DdiBufControl().ReadFrom(mmio_space()).ddi_idle_status(), 8)) { |
| LOG_ERROR("Port failed to go idle\n"); |
| return false; |
| } |
| |
| // Disable IO power |
| auto pwc2 = registers::PowerWellControl2::Get().ReadFrom(mmio_space()); |
| pwc2.ddi_io_power_request(ddi).set(0); |
| pwc2.WriteTo(mmio_space()); |
| |
| // Remove the PLL mapping and disable the PLL (we don't share PLLs) |
| auto dpll_ctrl2 = registers::DpllControl2::Get().ReadFrom(mmio_space()); |
| if (!dpll_ctrl2.ddi_clock_off(ddi).get()) { |
| dpll_ctrl2.ddi_clock_off(ddi).set(1); |
| dpll_ctrl2.WriteTo(mmio_space()); |
| |
| registers::Dpll dpll = static_cast<registers::Dpll>(dpll_ctrl2.ddi_clock_select(ddi).get()); |
| // Don't underflow if we're resetting at initialization |
| dplls_[dpll].use_count = |
| static_cast<uint8_t>(dplls_[dpll].use_count > 0 ? dplls_[dpll].use_count - 1 : 0); |
| // We don't want to disable DPLL0, since that drives cdclk. |
| if (dplls_[dpll].use_count == 0 && dpll != registers::DPLL_0) { |
| auto dpll_enable = registers::DpllEnable::Get(dpll).ReadFrom(mmio_space()); |
| dpll_enable.set_enable_dpll(0); |
| dpll_enable.WriteTo(mmio_space()); |
| } |
| } |
| |
| return true; |
| } |
| |
| registers::Dpll Controller::SelectDpll(bool is_edp, bool is_hdmi, uint32_t rate) { |
| registers::Dpll res = registers::DPLL_INVALID; |
| if (is_edp) { |
| if (dplls_[0].use_count == 0 || dplls_[0].rate == rate) { |
| res = registers::DPLL_0; |
| } |
| } else { |
| for (unsigned i = registers::kDpllCount - 1; i > 0; i--) { |
| if (dplls_[i].use_count == 0) { |
| res = static_cast<registers::Dpll>(i); |
| } else if (dplls_[i].is_hdmi == is_hdmi && dplls_[i].rate == rate) { |
| res = static_cast<registers::Dpll>(i); |
| break; |
| } |
| } |
| } |
| |
| if (res != registers::DPLL_INVALID) { |
| dplls_[res].is_hdmi = is_hdmi; |
| dplls_[res].rate = rate; |
| dplls_[res].use_count++; |
| LOG_SPEW("Selected DPLL %d\n", res); |
| } else { |
| LOG_WARN("Failed to allocate DPLL\n"); |
| } |
| |
| return res; |
| } |
| |
| fbl::unique_ptr<DisplayDevice> Controller::InitDisplay(registers::Ddi ddi) { |
| registers::Pipe pipe; |
| if (!pipe_in_use(display_devices_, registers::PIPE_A)) { |
| pipe = registers::PIPE_A; |
| } else if (!pipe_in_use(display_devices_, registers::PIPE_B)) { |
| pipe = registers::PIPE_B; |
| } else if (!pipe_in_use(display_devices_, registers::PIPE_C)) { |
| pipe = registers::PIPE_C; |
| } else { |
| LOG_WARN("Could not allocate pipe for ddi %d\n", ddi); |
| return nullptr; |
| } |
| |
| fbl::AllocChecker ac; |
| if (igd_opregion_.SupportsDp(ddi)) { |
| LOG_SPEW("Checking for displayport monitor\n"); |
| auto dp_disp = fbl::make_unique_checked<DpDisplay>(&ac, this, next_id_, ddi, pipe); |
| if (ac.check() && reinterpret_cast<DisplayDevice*>(dp_disp.get())->Init()) { |
| return dp_disp; |
| } |
| } |
| if (igd_opregion_.SupportsHdmi(ddi) || igd_opregion_.SupportsDvi(ddi)) { |
| LOG_SPEW("Checking for hdmi monitor\n"); |
| auto hdmi_disp = fbl::make_unique_checked<HdmiDisplay>(&ac, this, next_id_, ddi, pipe); |
| if (ac.check() && reinterpret_cast<DisplayDevice*>(hdmi_disp.get())->Init()) { |
| return hdmi_disp; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| void Controller::InitDisplays() { |
| fbl::AutoLock lock(&display_lock_); |
| BringUpDisplayEngine(false); |
| |
| for (uint32_t i = 0; i < registers::kDdiCount; i++) { |
| auto disp_device = InitDisplay(registers::kDdis[i]); |
| if (disp_device) { |
| AddDisplay(fbl::move(disp_device)); |
| } |
| } |
| |
| if (display_devices_.size() == 0) { |
| LOG_INFO("No displays detected\n"); |
| } |
| |
| ReallocatePipeBuffers(false); |
| } |
| |
| zx_status_t Controller::AddDisplay(fbl::unique_ptr<DisplayDevice>&& display) { |
| fbl::AllocChecker ac; |
| display_devices_.reserve(display_devices_.size() + 1, &ac); |
| |
| if (ac.check()) { |
| display_devices_.push_back(fbl::move(display), &ac); |
| assert(ac.check()); |
| |
| fbl::unique_ptr<DisplayDevice>& new_device = display_devices_[display_devices_.size() - 1]; |
| LOG_INFO("Display %ld connected (%d x %d, fmt=%08x)\n", new_device->id(), |
| new_device->width(), new_device->height(), new_device->format()); |
| } else { |
| LOG_WARN("Failed to add display device\n"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| next_id_++; |
| return ZX_OK; |
| } |
| |
| // DisplayController methods |
| |
| void Controller::SetDisplayControllerCb(void* cb_ctx, display_controller_cb_t* cb) { |
| acquire_dc_cb_lock(); |
| dc_cb_ctx_ = cb_ctx; |
| _dc_cb_ = cb; |
| |
| if (ready_for_callback_) { |
| uint64_t displays[registers::kDdiCount]; |
| uint32_t size; |
| { |
| fbl::AutoLock lock(&display_lock_); |
| size = static_cast<uint32_t>(display_devices_.size()); |
| for (unsigned i = 0; i < size; i++) { |
| displays[i] = display_devices_[i]->id(); |
| } |
| } |
| |
| cb->on_displays_changed(cb_ctx, displays, size, NULL, 0); |
| } |
| |
| release_dc_cb_lock(); |
| } |
| |
| zx_status_t Controller::GetDisplayInfo(uint64_t display_id, display_info_t* info) { |
| DisplayDevice* device; |
| fbl::AutoLock lock(&display_lock_); |
| if ((device = FindDevice(display_id)) == nullptr) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| info->edid_present = true; |
| info->panel.edid.data = device->edid().edid_bytes(); |
| info->panel.edid.length = device->edid().edid_length(); |
| info->pixel_formats = supported_formats; |
| info->pixel_format_count = static_cast<uint32_t>(fbl::count_of(supported_formats)); |
| return ZX_OK; |
| } |
| |
| zx_status_t Controller::ImportVmoImage(image_t* image, const zx::vmo& vmo, size_t offset) { |
| if (!(image->type == IMAGE_TYPE_SIMPLE || image->type == IMAGE_TYPE_X_TILED |
| || image->type == IMAGE_TYPE_Y_LEGACY_TILED |
| || image->type == IMAGE_TYPE_YF_TILED)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (offset % PAGE_SIZE != 0) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::AutoLock lock(>t_lock_); |
| fbl::AllocChecker ac; |
| imported_images_.reserve(imported_images_.size() + 1, &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| uint32_t length = width_in_tiles(image->type, image->width, image->pixel_format) * |
| height_in_tiles(image->type, image->height, image->pixel_format) * |
| get_tile_byte_size(image->type); |
| |
| uint32_t align; |
| if (image->type == IMAGE_TYPE_SIMPLE) { |
| align = registers::PlaneSurface::kLinearAlignment; |
| } else if (image->type == IMAGE_TYPE_X_TILED) { |
| align = registers::PlaneSurface::kXTilingAlignment; |
| } else { |
| align = registers::PlaneSurface::kYTilingAlignment; |
| } |
| fbl::unique_ptr<GttRegion> gtt_region; |
| zx_status_t status = gtt_.AllocRegion(length, align, >t_region); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // The vsync logic requires that images not have base == 0 |
| if (gtt_region->base() == 0) { |
| fbl::unique_ptr<GttRegion> alt_gtt_region; |
| zx_status_t status = gtt_.AllocRegion(length, align, &alt_gtt_region); |
| if (status != ZX_OK) { |
| return status; |
| } |
| gtt_region = fbl::move(alt_gtt_region); |
| } |
| |
| status = gtt_region->PopulateRegion(vmo.get(), offset / PAGE_SIZE, length); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| image->handle = reinterpret_cast<void*>(gtt_region->base()); |
| imported_images_.push_back(fbl::move(gtt_region)); |
| return ZX_OK; |
| } |
| |
| void Controller::ReleaseImage(image_t* image) { |
| fbl::AutoLock lock(>t_lock_); |
| for (unsigned i = 0; i < imported_images_.size(); i++) { |
| if (imported_images_[i]->base() == reinterpret_cast<uint64_t>(image->handle)) { |
| imported_images_.erase(i); |
| return; |
| } |
| } |
| } |
| |
| const fbl::unique_ptr<GttRegion>& Controller::GetGttRegion(void* handle) { |
| fbl::AutoLock lock(>t_lock_); |
| for (auto& region : imported_images_) { |
| if (region->base() == reinterpret_cast<uint64_t>(handle)) { |
| return region; |
| } |
| } |
| ZX_ASSERT(false); |
| } |
| |
| bool Controller::GetLayer(registers::Pipe pipe, uint32_t plane, |
| const display_config_t** configs, uint32_t display_count, |
| const layer_t** layer_out) { |
| DisplayDevice* disp = nullptr; |
| for (auto& d : display_devices_) { |
| if (d->pipe() == pipe) { |
| disp = d.get(); |
| break; |
| } |
| } |
| if (disp == nullptr) { |
| return false; |
| } |
| |
| for (unsigned i = 0; i < display_count; i++) { |
| const display_config_t* config = configs[i]; |
| if (config->display_id != disp->id()) { |
| continue; |
| } |
| for (unsigned j = 0; j < config->layer_count; j++) { |
| if (config->layers[j]->z_index == plane) { |
| *layer_out = config->layers[j]; |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| bool Controller::CalculateMinimumAllocations(const display_config_t** display_configs, |
| uint32_t display_count, |
| uint16_t min_allocs[registers::kPipeCount] |
| [registers::kPrimaryPlaneCount]) { |
| ZX_ASSERT(display_count < registers::kPipeCount); |
| // This fn ignores layers after kPrimaryPlaneCount. Displays with too many layers already |
| // failed in ::CheckConfiguration, so it doesn't matter if we incorrectly say they pass here. |
| |
| bool success = true; |
| for (unsigned pipe_num = 0; pipe_num < registers::kPipeCount; pipe_num++) { |
| registers::Pipe pipe = registers::kPipes[pipe_num]; |
| uint32_t total = 0; |
| |
| for (unsigned plane_num = 0; plane_num < registers::kPrimaryPlaneCount; plane_num++) { |
| const layer_t* layer; |
| if (!GetLayer(pipe, plane_num, display_configs, display_count, &layer)) { |
| min_allocs[pipe_num][plane_num] = 0; |
| continue; |
| } |
| ZX_ASSERT(layer->type == LAYER_PRIMARY); |
| const primary_layer_t* primary = &layer->cfg.primary; |
| |
| if (primary->image.type == IMAGE_TYPE_SIMPLE |
| || primary->image.type == IMAGE_TYPE_X_TILED) { |
| min_allocs[pipe_num][plane_num] = 8; |
| } else { |
| uint32_t plane_source_width; |
| uint32_t min_scan_lines; |
| uint32_t bytes_per_pixel = ZX_PIXEL_FORMAT_BYTES(primary->image.pixel_format); |
| if (primary->transform_mode == FRAME_TRANSFORM_IDENTITY |
| || primary->transform_mode == FRAME_TRANSFORM_ROT_180) { |
| plane_source_width = primary->src_frame.width; |
| min_scan_lines = 8; |
| } else { |
| plane_source_width = primary->src_frame.height; |
| min_scan_lines = 32 / bytes_per_pixel; |
| } |
| min_allocs[pipe_num][plane_num] = static_cast<uint16_t>( |
| ((fbl::round_up(4u * plane_source_width * bytes_per_pixel, 512u) / 512u) * |
| (min_scan_lines / 4)) + 3); |
| if (min_allocs[pipe_num][plane_num] < 8) { |
| min_allocs[pipe_num][plane_num] = 8; |
| } |
| |
| } |
| total += min_allocs[pipe_num][plane_num]; |
| } |
| |
| ZX_ASSERT(pipe_buffers_[pipe_num].end >= pipe_buffers_[pipe_num].start); |
| if (total > static_cast<uint16_t>( |
| pipe_buffers_[pipe_num].end - pipe_buffers_[pipe_num].start)) { |
| min_allocs[pipe_num][0] = UINT16_MAX; |
| success = false; |
| } |
| } |
| |
| return success; |
| } |
| |
| void Controller::UpdateAllocations(const uint16_t min_allocs[registers::kPipeCount] |
| [registers::kPrimaryPlaneCount], |
| const uint64_t data_rate[registers::kPipeCount] |
| [registers::kPrimaryPlaneCount]) { |
| uint16_t allocs[registers::kPipeCount][registers::kPrimaryPlaneCount]; |
| |
| for (unsigned pipe_num = 0; pipe_num < registers::kPipeCount; pipe_num++) { |
| uint64_t total_data_rate = 0; |
| for (unsigned plane_num = 0; plane_num < registers::kPrimaryPlaneCount; plane_num++) { |
| total_data_rate += data_rate[pipe_num][plane_num]; |
| } |
| if (total_data_rate == 0) { |
| for (unsigned plane_num = 0; plane_num < registers::kPrimaryPlaneCount; plane_num++) { |
| allocs[pipe_num][plane_num] = 0; |
| } |
| continue; |
| } |
| |
| // Allocate buffers based on the percentage of the total pixel bandwidth they take. If |
| // that percentage isn't enough for a plane, give that plane its minimum allocation and |
| // then try again. |
| double buffers_per_pipe = pipe_buffers_[pipe_num].end - pipe_buffers_[pipe_num].start; |
| bool forced_alloc[registers::kPrimaryPlaneCount] = {}; |
| bool done = false; |
| while (!done) { |
| for (unsigned plane_num = 0; plane_num < registers::kPrimaryPlaneCount; plane_num++) { |
| if (forced_alloc[plane_num]) { |
| continue; |
| } |
| |
| double blocks = buffers_per_pipe * |
| static_cast<double>(data_rate[pipe_num][plane_num]) / |
| static_cast<double>(total_data_rate); |
| allocs[pipe_num][plane_num] = static_cast<uint16_t>(blocks); |
| } |
| |
| done = true; |
| |
| for (unsigned plane_num = 0; plane_num < registers::kPrimaryPlaneCount; plane_num++) { |
| if (allocs[pipe_num][plane_num] < min_allocs[pipe_num][plane_num]) { |
| done = false; |
| allocs[pipe_num][plane_num] = min_allocs[pipe_num][plane_num]; |
| forced_alloc[plane_num] = true; |
| total_data_rate -= data_rate[pipe_num][plane_num]; |
| buffers_per_pipe -= allocs[pipe_num][plane_num]; |
| } |
| } |
| } |
| } |
| |
| // Do the actual allocation, using the buffers that are asigned to each pipe. |
| for (unsigned pipe_num = 0; pipe_num < registers::kPipeCount; pipe_num++) { |
| uint16_t start = pipe_buffers_[pipe_num].start; |
| for (unsigned plane_num = 0; plane_num < registers::kPrimaryPlaneCount; plane_num++) { |
| auto cur = &plane_buffers_[pipe_num][plane_num]; |
| cur->minimum = min_allocs[pipe_num][plane_num]; |
| |
| if (allocs[pipe_num][plane_num] == 0) { |
| cur->start = registers::PlaneBufCfg::kBufferCount; |
| cur->end = static_cast<uint16_t>(cur->start + 1); |
| } else { |
| cur->start = start; |
| cur->end = static_cast<uint16_t>(start + allocs[pipe_num][plane_num]); |
| } |
| start = static_cast<uint16_t>(start + allocs[pipe_num][plane_num]); |
| |
| registers::Pipe pipe = registers::kPipes[pipe_num]; |
| registers::PipeRegs pipe_regs(pipe); |
| |
| // These are latched on the surface address register, so we don't yet need to |
| // worry about overlaps when updating planes during a pipe allocation. |
| auto buf_cfg = pipe_regs.PlaneBufCfg(plane_num + 1).FromValue(0); |
| buf_cfg.set_buffer_start(cur->start); |
| buf_cfg.set_buffer_end(cur->end - 1); |
| buf_cfg.WriteTo(mmio_space()); |
| |
| // TODO(stevensd): Real watermark programming |
| auto wm0 = pipe_regs.PlaneWatermark(plane_num + 1, 0).FromValue(0); |
| wm0.set_enable(cur->start != registers::PlaneBufCfg::kBufferCount); |
| wm0.set_blocks(cur->end - cur->start); |
| wm0.WriteTo(mmio_space()); |
| } |
| } |
| } |
| |
| bool Controller::ReallocatePlaneBuffers(const display_config_t** display_configs, |
| uint32_t display_count) { |
| uint16_t min_allocs[registers::kPipeCount][registers::kPrimaryPlaneCount]; |
| if (!CalculateMinimumAllocations(display_configs, display_count, min_allocs)) { |
| return false; |
| } |
| |
| // Calculate the data rates and store the minimum allocations |
| uint64_t data_rate[registers::kPipeCount][registers::kPrimaryPlaneCount]; |
| for (unsigned pipe_num = 0; pipe_num < registers::kPipeCount; pipe_num++) { |
| registers::Pipe pipe = registers::kPipes[pipe_num]; |
| for (unsigned plane_num = 0; plane_num < registers::kPrimaryPlaneCount; plane_num++) { |
| const layer_t* layer; |
| if (!GetLayer(pipe, plane_num, display_configs, display_count, &layer)) { |
| data_rate[pipe_num][plane_num] = 0; |
| } else { |
| ZX_ASSERT(layer->type == LAYER_PRIMARY); |
| const primary_layer_t* primary = &layer->cfg.primary; |
| |
| data_rate[pipe_num][plane_num] = primary->src_frame.width * |
| primary->src_frame.height * |
| ZX_PIXEL_FORMAT_BYTES(primary->image.pixel_format); |
| } |
| } |
| } |
| |
| // It's not necessary to flush the buffer changes since the pipe allocs didn't change |
| UpdateAllocations(min_allocs, data_rate); |
| return true; |
| } |
| |
| void Controller::ReallocatePipeBuffers(bool is_hotplug) { |
| if (display_devices_.size() == 0) { |
| // We'll reallocate things when there's actually a display |
| return; |
| } |
| |
| // TODO(stevensd): Separate pipe allocation for displays being connected |
| bool realloc_fail = false; |
| uint16_t buffers_per_pipe = static_cast<uint16_t>(registers::PlaneBufCfg::kBufferCount / |
| fbl::min(display_devices_.size(), static_cast<size_t>(registers::kPipeCount))); |
| |
| // Approximate the data rate based on how many buffers are allocated to each plane. This |
| // can be slightly off, but that'll be fixed on the next page flip. |
| uint16_t min_allocs[registers::kPipeCount][registers::kPrimaryPlaneCount]; |
| uint64_t data_rate[registers::kPipeCount][registers::kPrimaryPlaneCount]; |
| for (unsigned pipe_num = 0; pipe_num < registers::kPipeCount; pipe_num++) { |
| uint16_t pipe_total = 0; |
| for (unsigned plane_num = 0; plane_num < registers::kPrimaryPlaneCount; plane_num++) { |
| auto alloc = &plane_buffers_[pipe_num][plane_num]; |
| data_rate[pipe_num][plane_num] = alloc->start == registers::PlaneBufCfg::kBufferCount ? |
| 0 : alloc->end - alloc->start; |
| min_allocs[pipe_num][plane_num] = alloc->minimum; |
| pipe_total = static_cast<uint16_t>(pipe_total + alloc->minimum); |
| } |
| if (pipe_total > buffers_per_pipe) { |
| realloc_fail = true; |
| } |
| } |
| |
| // If we can't reallocate anything, disable all the planes and wait for a page flip due |
| // to the client handling the hotplug. This will cause the displays to flash, but bad |
| // hotplugs like this should be uncommon. This shouldn't happen with the virtcon, since |
| // its buffer requirements are really low, so waiting for a flip is okay. |
| if (realloc_fail) { |
| ZX_DEBUG_ASSERT(is_hotplug); |
| |
| LOG_INFO("Cannot reallocate buffers for hot plug\n"); |
| for (unsigned pipe_num = 0; pipe_num < registers::kPipeCount; pipe_num++) { |
| registers::Pipe pipe = registers::kPipes[pipe_num]; |
| registers::PipeRegs pipe_regs(pipe); |
| for (unsigned plane_num = 0; plane_num < registers::kPrimaryPlaneCount; plane_num++) { |
| pipe_regs.PlaneControl(plane_num).ReadFrom(mmio_space()) |
| .set_plane_enable(0).WriteTo(mmio_space()); |
| pipe_regs.PlaneSurface(plane_num).ReadFrom(mmio_space()).WriteTo(mmio_space()); |
| } |
| } |
| return; |
| } |
| |
| // Allocate buffers to each pipe, but save the old one for use later. |
| pipe_buffer_allocation_t active_allocation[registers::kPipeCount]; |
| memcpy(active_allocation, pipe_buffers_, sizeof(active_allocation)); |
| int active_pipes = 0; |
| for (unsigned pipe_num = 0; pipe_num < registers::kPipeCount; pipe_num++) { |
| bool found = false; |
| for (auto& display : display_devices_) { |
| if (display->pipe() == pipe_num) { |
| found = true; |
| break; |
| } |
| } |
| if (found) { |
| pipe_buffers_[pipe_num].start = static_cast<uint16_t>(buffers_per_pipe * active_pipes); |
| pipe_buffers_[pipe_num].end = static_cast<uint16_t>( |
| pipe_buffers_[pipe_num].start + buffers_per_pipe); |
| active_pipes++; |
| } else { |
| pipe_buffers_[pipe_num].start = pipe_buffers_[pipe_num].end = 0; |
| } |
| LOG_SPEW("Pipe %d buffers: [%d, %d)\n", pipe_num, |
| pipe_buffers_[pipe_num].start, pipe_buffers_[pipe_num].end); |
| } |
| |
| UpdateAllocations(min_allocs, data_rate); |
| |
| // If it's not a hotplug, we weren't using anything before so we don't need to |
| // worry about allocations overlapping. |
| if (!is_hotplug) { |
| return; |
| } |
| |
| // Given that the order of the allocations is fixed, an allocation X_i is contained completely |
| // within its old allocation if {new len of allocations preceding X_i} >= {start of old X_i} and |
| // {new len of allocations preceding X_i + new len of X_i} <= {end of old X_i}. For any i, |
| // if condition 1 holds, either condition 2 is true and we're done, or condition 2 doesn't |
| // and condition 1 holds for i + 1. Since condition 1 holds for i == 0 and because condition |
| // 2 holds for the last allocation (since the allocation is valid), it is guaranteed that |
| // at least one allocation is entirely within its old allocation. The remaining buffers |
| // are guaranteed to be re-allocatable recursively in the same manner. Therefore the loop will |
| // make progress every iteration. |
| bool done = false; |
| while (!done) { |
| done = true; |
| for (unsigned pipe_num = 0; pipe_num < registers::kPipeCount; pipe_num++) { |
| auto active_alloc = active_allocation + pipe_num; |
| auto goal_alloc = pipe_buffers_ + pipe_num; |
| |
| if (active_alloc->start == goal_alloc->start && |
| active_alloc->end == goal_alloc->end) { |
| continue; |
| } |
| |
| // Look through all the other active pipe allocations for overlap |
| bool overlap = false; |
| if (goal_alloc->start != goal_alloc->end) { |
| for (unsigned other_pipe = 0; other_pipe < registers::kPipeCount; other_pipe++) { |
| if (other_pipe == pipe_num) { |
| continue; |
| } |
| |
| auto other_active = active_allocation + other_pipe; |
| if (other_active->start == other_active->end) { |
| continue; |
| } |
| |
| if ((other_active->start <= goal_alloc->start |
| && goal_alloc->start < other_active->end) |
| || (other_active->start <= goal_alloc->end |
| && goal_alloc->end < other_active->end)) { |
| overlap = true; |
| break; |
| } |
| } |
| } |
| |
| if (!overlap) { |
| // Flush the pipe allocation, wait for it to be active, and update |
| // what is current active. |
| registers::PipeRegs pipe_regs(registers::kPipes[pipe_num]); |
| for (unsigned j = 0; j < registers::kPrimaryPlaneCount; j++) { |
| pipe_regs.PlaneSurface(j).ReadFrom(mmio_space()).WriteTo(mmio_space()); |
| } |
| |
| // TODO(stevensd): Wait for vsync instead of sleeping |
| // TODO(stevesnd): Parallelize/reduce the number of vsyncs we wait for |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(33))); |
| |
| *active_alloc = *goal_alloc; |
| } else { |
| done = false; |
| } |
| } |
| } |
| } |
| |
| void Controller::CheckConfiguration(const display_config_t** display_config, |
| uint32_t** layer_cfg_result, uint32_t display_count) { |
| if (display_count > registers::kPipeCount) { |
| // TODO(stevensd): Surface this to higher layers |
| ZX_ASSERT(false); |
| } |
| |
| fbl::AutoLock lock(&display_lock_); |
| |
| for (unsigned i = 0; i < display_count; i++) { |
| auto* config = display_config[i]; |
| if (config->layer_count > 3) { |
| layer_cfg_result[i][0] = CLIENT_MERGE_BASE; |
| for (unsigned j = 1; j < config->layer_count; j++) { |
| layer_cfg_result[i][j] = CLIENT_MERGE_SRC; |
| } |
| continue; |
| } |
| |
| for (unsigned j = 0; j < config->layer_count; j++) { |
| if (config->layers[j]->type != LAYER_PRIMARY) { |
| layer_cfg_result[i][j] = CLIENT_USE_PRIMARY; |
| continue; |
| } |
| primary_layer_t* primary = &config->layers[j]->cfg.primary; |
| if (primary->transform_mode == FRAME_TRANSFORM_ROT_90 |
| || primary->transform_mode == FRAME_TRANSFORM_ROT_270) { |
| // Linear and x tiled images don't support 90/270 rotation |
| if (primary->image.type == IMAGE_TYPE_SIMPLE |
| || primary->image.type == IMAGE_TYPE_X_TILED) { |
| layer_cfg_result[i][j] |= CLIENT_TRANSFORM; |
| } |
| } else if (primary->transform_mode != FRAME_TRANSFORM_IDENTITY |
| && primary->transform_mode != FRAME_TRANSFORM_ROT_180) { |
| // Cover unsupported rotations |
| layer_cfg_result[i][j] |= CLIENT_TRANSFORM; |
| } |
| |
| uint32_t src_width; |
| uint32_t src_height; |
| if (primary->transform_mode == FRAME_TRANSFORM_IDENTITY |
| || primary->transform_mode == FRAME_TRANSFORM_ROT_180 |
| || primary->transform_mode == FRAME_TRANSFORM_REFLECT_X |
| || primary->transform_mode == FRAME_TRANSFORM_REFLECT_Y) { |
| src_width = primary->src_frame.width; |
| src_height = primary->src_frame.height; |
| } else { |
| src_width = primary->src_frame.height; |
| src_height = primary->src_frame.width; |
| } |
| |
| if (primary->dest_frame.width != src_width |
| || primary->dest_frame.height != src_height) { |
| layer_cfg_result[i][j] |= CLIENT_FRAME_SCALE; |
| } |
| } |
| } |
| |
| // CalculateMinimumAllocations ignores layers after kPrimaryPlaneCount. That's fine, since |
| // that case already fails from an earlier check. |
| uint16_t arr[registers::kPipeCount][registers::kPrimaryPlaneCount]; |
| if (!CalculateMinimumAllocations(display_config, display_count, arr)) { |
| // Find any displays whose allocation fails and set the return code. Overwrite |
| // any previous errors, since they get solved by the merge. |
| for (unsigned pipe_num = 0; pipe_num < registers::kPipeCount; pipe_num++) { |
| if (arr[pipe_num][0] != UINT16_MAX) { |
| continue; |
| } |
| for (auto& display : display_devices_) { |
| if (display->pipe() != pipe_num) { |
| continue; |
| } |
| |
| for (unsigned i = 0; i < display_count; i++) { |
| if (display_config[i]->display_id != display->id()) { |
| continue; |
| } |
| layer_cfg_result[i][0] = CLIENT_MERGE_BASE; |
| for (unsigned j = 1; j < display_config[i]->layer_count; j++) { |
| layer_cfg_result[i][j] = CLIENT_MERGE_SRC; |
| } |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| void Controller::ApplyConfiguration(const display_config_t** display_config, |
| uint32_t display_count) { |
| uint64_t fake_vsyncs[registers::kDdiCount]; |
| uint32_t fake_vsync_count = 0; |
| |
| { |
| fbl::AutoLock lock(&display_lock_); |
| |
| // If we reallocated the pipe allocations since things were validated, then this |
| // can fail. In that case, just wait for the client to respond to the hotplug event. |
| if (!ReallocatePlaneBuffers(display_config, display_count)) { |
| return; |
| } |
| |
| for (auto& display : display_devices_) { |
| const display_config_t* config = nullptr; |
| for (unsigned i = 0; i < display_count; i++) { |
| if (display_config[i]->display_id == display->id()) { |
| config = display_config[i]; |
| break; |
| } |
| } |
| display->ApplyConfiguration(config); |
| |
| // The hardware only gives vsyncs if at least one plane is enabled, so |
| // fake one if we need to, to inform the client that we're done with the |
| // images. |
| if (!config || config->layer_count == 0) { |
| fake_vsyncs[fake_vsync_count++] = display->id(); |
| } |
| } |
| } |
| |
| acquire_dc_cb_lock(); |
| if (dc_cb()) { |
| for (unsigned i = 0; i < fake_vsync_count; i++) { |
| dc_cb()->on_display_vsync(dc_cb_ctx_, fake_vsyncs[i], nullptr, 0); |
| } |
| } |
| release_dc_cb_lock(); |
| } |
| |
| uint32_t Controller::ComputeLinearStride(uint32_t width, zx_pixel_format_t format) { |
| return fbl::round_up(width, |
| get_tile_byte_width(IMAGE_TYPE_SIMPLE, format) / ZX_PIXEL_FORMAT_BYTES(format)); |
| } |
| |
| zx_status_t Controller::AllocateVmo(uint64_t size, zx_handle_t* vmo_out) { |
| return zx_vmo_create(size, 0, vmo_out); |
| } |
| |
| // Intel GPU core methods |
| |
| zx_status_t Controller::ReadPciConfig16(uint16_t addr, uint16_t* value_out) { |
| return pci_config_read16(&pci_, addr, value_out); |
| } |
| |
| zx_status_t Controller::MapPciMmio(uint32_t pci_bar, void** addr_out, uint64_t* size_out) { |
| if (pci_bar > PCI_MAX_BAR_COUNT) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| fbl::AutoLock lock(&bar_lock_); |
| if (mapped_bars_[pci_bar].count == 0) { |
| zx_status_t status = pci_map_bar(&pci_, pci_bar, ZX_CACHE_POLICY_UNCACHED_DEVICE, |
| &mapped_bars_[pci_bar].base, |
| &mapped_bars_[pci_bar].size, |
| &mapped_bars_[pci_bar].vmo); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| *addr_out = mapped_bars_[pci_bar].base; |
| *size_out = mapped_bars_[pci_bar].size; |
| mapped_bars_[pci_bar].count++; |
| return ZX_OK; |
| } |
| |
| zx_status_t Controller::UnmapPciMmio(uint32_t pci_bar) { |
| if (pci_bar > PCI_MAX_BAR_COUNT) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| fbl::AutoLock lock(&bar_lock_); |
| if (mapped_bars_[pci_bar].count == 0) { |
| return ZX_OK; |
| } |
| if (--mapped_bars_[pci_bar].count == 0) { |
| zx_vmar_unmap(zx_vmar_root_self(), |
| reinterpret_cast<uintptr_t>(mapped_bars_[pci_bar].base), |
| mapped_bars_[pci_bar].size); |
| zx_handle_close(mapped_bars_[pci_bar].vmo); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Controller::GetPciBti(uint32_t index, zx_handle_t* bti_out) { |
| return pci_get_bti(&pci_, index, bti_out); |
| } |
| |
| zx_status_t Controller::RegisterInterruptCallback(zx_intel_gpu_core_interrupt_callback_t callback, |
| void* data, uint32_t interrupt_mask) { |
| return interrupts_.SetInterruptCallback(callback, data, interrupt_mask); |
| } |
| |
| zx_status_t Controller::UnregisterInterruptCallback() { |
| interrupts_.SetInterruptCallback(nullptr, nullptr, 0); |
| return ZX_OK; |
| } |
| |
| uint64_t Controller::GttGetSize() { |
| fbl::AutoLock lock(>t_lock_); |
| return gtt_.size(); |
| } |
| |
| zx_status_t Controller::GttAlloc(uint64_t page_count, uint64_t* addr_out) { |
| uint64_t length = page_count * PAGE_SIZE; |
| fbl::AutoLock lock(>t_lock_); |
| if (length > gtt_.size()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| fbl::unique_ptr<GttRegion> region; |
| zx_status_t status = gtt_.AllocRegion(static_cast<uint32_t>(page_count * PAGE_SIZE), |
| PAGE_SIZE, ®ion); |
| if (status != ZX_OK) { |
| return status; |
| } |
| *addr_out = region->base(); |
| |
| imported_gtt_regions_.push_back(fbl::move(region)); |
| return ZX_OK; |
| } |
| |
| zx_status_t Controller::GttFree(uint64_t addr) { |
| fbl::AutoLock lock(>t_lock_); |
| for (unsigned i = 0; i < imported_gtt_regions_.size(); i++) { |
| if (imported_gtt_regions_[i]->base() == addr) { |
| imported_gtt_regions_.erase(i)->ClearRegion(true); |
| return ZX_OK; |
| } |
| } |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| zx_status_t Controller::GttClear(uint64_t addr) { |
| fbl::AutoLock lock(>t_lock_); |
| for (unsigned i = 0; i < imported_gtt_regions_.size(); i++) { |
| if (imported_gtt_regions_[i]->base() == addr) { |
| imported_gtt_regions_[i]->ClearRegion(true); |
| return ZX_OK; |
| } |
| } |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| zx_status_t Controller::GttInsert(uint64_t addr, zx_handle_t buffer, |
| uint64_t page_offset, uint64_t page_count) { |
| fbl::AutoLock lock(>t_lock_); |
| for (unsigned i = 0; i < imported_gtt_regions_.size(); i++) { |
| if (imported_gtt_regions_[i]->base() == addr) { |
| return imported_gtt_regions_[i]->PopulateRegion(buffer, page_offset, |
| page_count * PAGE_SIZE, |
| true /* writable */); |
| } |
| } |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| void Controller::GpuRelease() { |
| gpu_released_ = true; |
| if (display_released_) { |
| delete this; |
| } |
| } |
| |
| // Ddk methods |
| |
| void Controller::DdkUnbind() { |
| device_remove(zxdev()); |
| device_remove(zx_gpu_dev_); |
| } |
| |
| void Controller::DdkRelease() { |
| display_released_ = true; |
| if (gpu_released_) { |
| delete this; |
| } |
| } |
| |
| zx_status_t Controller::DdkSuspend(uint32_t hint) { |
| if ((hint & DEVICE_SUSPEND_REASON_MASK) == DEVICE_SUSPEND_FLAG_MEXEC) { |
| uint32_t format, width, height, stride; |
| if (zx_framebuffer_get_info(get_root_resource(), &format, &width, |
| &height, &stride) != ZX_OK) { |
| return ZX_OK; |
| } |
| |
| // The bootloader framebuffer is most likely at the start of the display |
| // controller's bar 2. Try to get that buffer working again across the |
| // mexec by mapping gfx stolen memory to gaddr 0. |
| |
| auto bdsm_reg = registers::BaseDsm::Get().FromValue(0); |
| zx_status_t status = |
| pci_config_read32(&pci_, bdsm_reg.kAddr, bdsm_reg.reg_value_ptr()); |
| if (status != ZX_OK) { |
| LOG_TRACE("Failed to read dsm base\n"); |
| return ZX_OK; |
| } |
| |
| // The Intel docs say that the first page should be reserved for the gfx |
| // hardware, but a lot of BIOSes seem to ignore that. |
| uintptr_t fb = bdsm_reg.base_phys_addr() << bdsm_reg.base_phys_addr_shift; |
| uint32_t fb_size = stride * height * ZX_PIXEL_FORMAT_BYTES(format); |
| |
| { |
| fbl::AutoLock lock(>t_lock_); |
| gtt_.SetupForMexec(fb, fb_size); |
| } |
| |
| // Try to map the framebuffer and clear it. If not, oh well. |
| void* gmadr; |
| uint64_t gmadr_size; |
| zx_handle_t gmadr_handle; |
| if (pci_map_bar(&pci_, 2, ZX_CACHE_POLICY_WRITE_COMBINING, |
| &gmadr, &gmadr_size, &gmadr_handle) == ZX_OK) { |
| memset(reinterpret_cast<void*>(gmadr), 0, fb_size); |
| zx_handle_close(gmadr_handle); |
| } |
| |
| { |
| fbl::AutoLock lock(&display_lock_); |
| for (auto& display : display_devices_) { |
| // TODO(ZX-1413): Reset/scale the display to ensure the buffer displays properly |
| registers::PipeRegs pipe_regs(display->pipe()); |
| |
| auto plane_stride = pipe_regs.PlaneSurfaceStride(0).ReadFrom(mmio_space_.get()); |
| plane_stride.set_stride(width_in_tiles(IMAGE_TYPE_SIMPLE, width, format)); |
| plane_stride.WriteTo(mmio_space_.get()); |
| |
| auto plane_surface = pipe_regs.PlaneSurface(0).ReadFrom(mmio_space_.get()); |
| plane_surface.set_surface_base_addr(0); |
| plane_surface.WriteTo(mmio_space_.get()); |
| } |
| } |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Controller::DdkResume(uint32_t hint) { |
| fbl::AutoLock lock(&display_lock_); |
| BringUpDisplayEngine(true); |
| |
| registers::PanelPowerDivisor::Get().FromValue(pp_divisor_val_).WriteTo(mmio_space_.get()); |
| registers::PanelPowerOffDelay::Get().FromValue(pp_off_delay_val_).WriteTo(mmio_space_.get()); |
| registers::PanelPowerOnDelay::Get().FromValue(pp_on_delay_val_).WriteTo(mmio_space_.get()); |
| registers::SouthBacklightCtl1::Get().FromValue(0) |
| .set_polarity(sblc_polarity_).WriteTo(mmio_space_.get()); |
| registers::SouthBacklightCtl2::Get().FromValue(sblc_ctrl2_val_).WriteTo(mmio_space_.get()); |
| registers::SChicken1::Get().FromValue(schicken1_val_).WriteTo(mmio_space_.get()); |
| |
| registers::DdiRegs(registers::DDI_A).DdiBufControl().ReadFrom(mmio_space_.get()) |
| .set_ddi_a_lane_capability_control(ddi_a_lane_capability_control_) |
| .WriteTo(mmio_space_.get()); |
| |
| for (auto& disp : display_devices_) { |
| if (!disp->Resume()) { |
| LOG_ERROR("Failed to resume display\n"); |
| } |
| } |
| |
| interrupts_.Resume(); |
| |
| ReallocatePipeBuffers(false); |
| |
| return ZX_OK; |
| } |
| |
| // TODO(stevensd): Move this back into ::Bind once long-running binds don't |
| // break devmgr's suspend/mexec. |
| void Controller::FinishInit() { |
| LOG_TRACE("i915: initializing displays\n"); |
| InitDisplays(); |
| |
| acquire_dc_cb_lock(); |
| uint64_t displays[registers::kDdiCount]; |
| uint32_t size = 0; |
| { |
| fbl::AutoLock lock(&display_lock_); |
| if (display_devices_.size()) { |
| size = static_cast<uint32_t>(display_devices_.size()); |
| for (unsigned i = 0; i < size; i++) { |
| displays[i] = display_devices_[i]->id(); |
| } |
| } |
| } |
| |
| if (dc_cb() && size) { |
| dc_cb()->on_displays_changed(dc_cb_ctx_, displays, size, NULL, 0); |
| } |
| |
| ready_for_callback_ = true; |
| release_dc_cb_lock(); |
| |
| interrupts_.FinishInit(); |
| |
| // TODO remove when the gfxconsole moves to user space |
| EnableBacklight(true); |
| |
| LOG_TRACE("i915: initialization done\n"); |
| } |
| |
| zx_status_t Controller::Bind(fbl::unique_ptr<i915::Controller>* controller_ptr) { |
| LOG_TRACE("Binding to display controller\n"); |
| |
| if (device_get_protocol(parent_, ZX_PROTOCOL_PCI, &pci_)) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| pci_config_read16(&pci_, PCI_CONFIG_DEVICE_ID, &device_id_); |
| LOG_TRACE("Device id %x\n", device_id_); |
| if (device_id_ == INTEL_I915_BROADWELL_DID) { |
| // TODO: this should be based on the specific target |
| flags_ |= FLAGS_BACKLIGHT; |
| } |
| |
| zx_status_t status = igd_opregion_.Init(&pci_); |
| if (status != ZX_OK) { |
| LOG_ERROR("Failed to init VBT (%d)\n", status); |
| return status; |
| } |
| |
| LOG_TRACE("Mapping registers\n"); |
| // map register window |
| void* regs; |
| uint64_t size; |
| status = MapPciMmio(0u, ®s, &size); |
| if (status != ZX_OK) { |
| LOG_ERROR("Failed to map bar 0: %d\n", status); |
| return status; |
| } |
| |
| fbl::AllocChecker ac; |
| fbl::unique_ptr<hwreg::RegisterIo> mmio_space( |
| new (&ac) hwreg::RegisterIo(reinterpret_cast<volatile void*>(regs))); |
| if (!ac.check()) { |
| LOG_ERROR("Failed to alloc RegisterIo\n"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| mmio_space_ = fbl::move(mmio_space); |
| |
| pp_divisor_val_ = registers::PanelPowerDivisor::Get().ReadFrom(mmio_space_.get()).reg_value(); |
| pp_off_delay_val_ = |
| registers::PanelPowerOffDelay::Get().ReadFrom(mmio_space_.get()).reg_value(); |
| pp_on_delay_val_ = registers::PanelPowerOnDelay::Get().ReadFrom(mmio_space_.get()).reg_value(); |
| sblc_ctrl2_val_ = registers::SouthBacklightCtl2::Get().ReadFrom(mmio_space_.get()).reg_value(); |
| schicken1_val_ = registers::SChicken1::Get().ReadFrom(mmio_space_.get()).reg_value(); |
| |
| sblc_polarity_ = registers::SouthBacklightCtl1::Get().ReadFrom(mmio_space_.get()).polarity(); |
| ddi_a_lane_capability_control_ = registers::DdiRegs(registers::DDI_A).DdiBufControl() |
| .ReadFrom(mmio_space_.get()).ddi_a_lane_capability_control(); |
| |
| LOG_TRACE("Initialzing hotplug\n"); |
| status = interrupts_.Init(this); |
| if (status != ZX_OK) { |
| LOG_ERROR("Failed to init hotplugging\n"); |
| return status; |
| } |
| |
| LOG_TRACE("Mapping gtt\n"); |
| { |
| fbl::AutoLock lock(>t_lock_); |
| if ((status = gtt_.Init(this)) != ZX_OK) { |
| LOG_ERROR("Failed to init gtt (%d)\n", status); |
| return status; |
| } |
| } |
| |
| thrd_t init_thread; |
| status = thrd_create_with_name(&init_thread, finish_init, this, "i915-init-thread"); |
| if (status != ZX_OK) { |
| LOG_ERROR("Failed to create init thread\n"); |
| return status; |
| } |
| init_thrd_started_ = true; |
| |
| status = DdkAdd("intel_i915"); |
| if (status != ZX_OK) { |
| LOG_ERROR("Failed to add controller device\n"); |
| return status; |
| } |
| // DevMgr now owns this pointer, release it to avoid destroying the object |
| // when device goes out of scope. |
| __UNUSED auto ptr = controller_ptr->release(); |
| |
| i915_gpu_core_device_proto.version = DEVICE_OPS_VERSION; |
| i915_gpu_core_device_proto.release = gpu_release; |
| // zx_gpu_dev_ is removed when unbind is called for zxdev() (in ::DdkUnbind), |
| // so it's not necessary to give it its own unbind method. |
| |
| device_add_args_t args = {}; |
| args.version = DEVICE_ADD_ARGS_VERSION; |
| args.name = "intel-gpu-core"; |
| args.ctx = this; |
| args.ops = &i915_gpu_core_device_proto; |
| args.proto_id = ZX_PROTOCOL_INTEL_GPU_CORE; |
| args.proto_ops = &i915_gpu_core_protocol_ops; |
| status = device_add(zxdev(), &args, &zx_gpu_dev_); |
| if (status != ZX_OK) { |
| LOG_ERROR("Failed to publish gpu core device (%d)\n", status); |
| device_remove(zxdev()); |
| return status; |
| } |
| |
| LOG_TRACE("bind done\n"); |
| |
| return ZX_OK; |
| } |
| |
| Controller::Controller(zx_device_t* parent) |
| : DeviceType(parent), power_(this) { |
| mtx_init(&display_lock_, mtx_plain); |
| mtx_init(>t_lock_, mtx_plain); |
| mtx_init(&bar_lock_, mtx_plain); |
| mtx_init(&_dc_cb_lock_, mtx_plain); |
| } |
| |
| Controller::~Controller() { |
| if (init_thrd_started_) { |
| thrd_join(init_thread_, nullptr); |
| } |
| |
| interrupts_.Destroy(); |
| if (mmio_space_) { |
| EnableBacklight(false); |
| } |
| // Drop our own reference to bar 0. No-op if we failed before we mapped it. |
| UnmapPciMmio(0u); |
| // Release anything leaked by the gpu-core client. |
| fbl::AutoLock lock(&bar_lock_); |
| for (unsigned i = 0; i < PCI_MAX_BAR_COUNT; i++) { |
| if (mapped_bars_[i].count) { |
| LOG_INFO("Leaked bar %d\n", i); |
| mapped_bars_[i].count = 1; |
| UnmapPciMmio(i); |
| } |
| } |
| } |
| |
| } // namespace i915 |
| |
| zx_status_t intel_i915_bind(void* ctx, zx_device_t* parent) { |
| fbl::AllocChecker ac; |
| fbl::unique_ptr<i915::Controller> controller(new (&ac) i915::Controller(parent)); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| return controller->Bind(&controller); |
| } |