blob: 2b42eb3854f3865074cf1a44b1a9ef8650c7b796 [file] [log] [blame]
// Copyright 2018 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 "controller.h"
#include <fuchsia/hardware/display/capture/c/banjo.h>
#include <fuchsia/hardware/display/capture/cpp/banjo.h>
#include <fuchsia/hardware/display/clamprgb/cpp/banjo.h>
#include <lib/async/cpp/task.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/driver.h>
#include <lib/ddk/trace/event.h>
#include <threads.h>
#include <zircon/threads.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include <iterator>
#include <memory>
#include <utility>
#include <vector>
#include <audio-proto-utils/format-utils.h>
#include <ddktl/device.h>
#include <ddktl/fidl.h>
#include <fbl/array.h>
#include <fbl/auto_lock.h>
#include <fbl/string_printf.h>
#include "client.h"
#include "eld.h"
#include "src/devices/lib/audio/audio.h"
#include "src/graphics/display/drivers/display/display-bind.h"
namespace fidl_display = fuchsia_hardware_display;
namespace {
struct I2cBus {
ddk::I2cImplProtocolClient i2c;
uint32_t bus_id;
};
edid::ddc_i2c_transact ddc_tx = [](void* ctx, edid::ddc_i2c_msg_t* msgs, uint32_t count) -> bool {
auto i2c = static_cast<I2cBus*>(ctx);
i2c_impl_op_t ops[count];
for (unsigned i = 0; i < count; i++) {
ops[i].address = msgs[i].addr;
ops[i].data_buffer = msgs[i].buf;
ops[i].data_size = msgs[i].length;
ops[i].is_read = msgs[i].is_read;
ops[i].stop = i == (count - 1);
}
return i2c->i2c.Transact(i2c->bus_id, ops, count) == ZX_OK;
};
bool IsKernelFramebufferDisabled() {
const char* value = getenv("driver.display.disable-kernel-framebuffer");
if (!value) {
return false;
}
if ((strcmp(value, "0") == 0) || (strcmp(value, "false") == 0) || (strcmp(value, "off") == 0)) {
return false;
}
return true;
}
} // namespace
namespace display {
void DisplayInfo::InitializeInspect(inspect::Node* parent_node) {
ZX_DEBUG_ASSERT(init_done);
node = parent_node->CreateChild(fbl::StringPrintf("display-%lu", id).c_str());
if (has_edid) {
size_t i = 0;
for (const auto& t : edid_timings) {
auto child = node.CreateChild(fbl::StringPrintf("timing-parameters-%lu", ++i).c_str());
child.CreateDouble("vsync-hz", static_cast<double>(t.vertical_refresh_e2) / 100.0,
&properties);
child.CreateUint("pixel-clock-khz", t.pixel_freq_10khz * 10, &properties);
child.CreateUint("horizontal-pixels", t.horizontal_addressable, &properties);
child.CreateUint("horizontal-blanking", t.horizontal_blanking, &properties);
child.CreateUint("horizontal-sync-offset", t.horizontal_front_porch, &properties);
child.CreateUint("horizontal-sync-pulse", t.horizontal_sync_pulse, &properties);
child.CreateUint("vertical-pixels", t.vertical_addressable, &properties);
child.CreateUint("vertical-blanking", t.vertical_blanking, &properties);
child.CreateUint("vertical-sync-offset", t.vertical_front_porch, &properties);
child.CreateUint("vertical-sync-pulse", t.vertical_sync_pulse, &properties);
properties.emplace(std::move(child));
}
} else {
node.CreateUint("width", params.width, &properties);
node.CreateUint("height", params.height, &properties);
}
}
void Controller::PopulateDisplayMode(const edid::timing_params_t& params, display_mode_t* mode) {
mode->pixel_clock_10khz = params.pixel_freq_10khz;
mode->h_addressable = params.horizontal_addressable;
mode->h_front_porch = params.horizontal_front_porch;
mode->h_sync_pulse = params.horizontal_sync_pulse;
mode->h_blanking = params.horizontal_blanking;
mode->v_addressable = params.vertical_addressable;
mode->v_front_porch = params.vertical_front_porch;
mode->v_sync_pulse = params.vertical_sync_pulse;
mode->v_blanking = params.vertical_blanking;
mode->flags = params.flags;
static_assert(MODE_FLAG_VSYNC_POSITIVE == edid::timing_params::kPositiveVsync, "");
static_assert(MODE_FLAG_HSYNC_POSITIVE == edid::timing_params::kPositiveHsync, "");
static_assert(MODE_FLAG_INTERLACED == edid::timing_params::kInterlaced, "");
static_assert(MODE_FLAG_ALTERNATING_VBLANK == edid::timing_params::kAlternatingVblank, "");
static_assert(MODE_FLAG_DOUBLE_CLOCKED == edid::timing_params::kDoubleClocked, "");
}
void Controller::PopulateDisplayTimings(const fbl::RefPtr<DisplayInfo>& info) {
// Go through all the display mode timings and record whether or not
// a basic layer configuration is acceptable.
layer_t test_layer = {};
layer_t* test_layers[] = {&test_layer};
test_layer.cfg.primary.image.pixel_format = info->pixel_formats_[0];
display_config_t test_config;
const display_config_t* test_configs[] = {&test_config};
test_config.display_id = info->id;
test_config.layer_count = 1;
test_config.layer_list = test_layers;
for (auto timing = edid::timing_iterator(&info->edid); timing.is_valid(); ++timing) {
uint32_t width = timing->horizontal_addressable;
uint32_t height = timing->vertical_addressable;
bool duplicate = false;
for (auto& existing_timing : info->edid_timings) {
if (existing_timing.vertical_refresh_e2 == timing->vertical_refresh_e2 &&
existing_timing.horizontal_addressable == width &&
existing_timing.vertical_addressable == height) {
duplicate = true;
break;
}
}
if (!duplicate) {
test_layer.cfg.primary.image.width = width;
test_layer.cfg.primary.image.height = height;
test_layer.cfg.primary.src_frame.width = width;
test_layer.cfg.primary.src_frame.height = height;
test_layer.cfg.primary.dest_frame.width = width;
test_layer.cfg.primary.dest_frame.height = height;
PopulateDisplayMode(*timing, &test_config.mode);
uint32_t display_cfg_result;
uint32_t layer_result = 0;
size_t display_layer_results_count;
uint32_t* display_layer_results[] = {&layer_result};
display_cfg_result = dc_.CheckConfiguration(test_configs, 1, display_layer_results,
&display_layer_results_count);
if (display_cfg_result == CONFIG_DISPLAY_OK) {
fbl::AllocChecker ac;
info->edid_timings.push_back(*timing, &ac);
if (!ac.check()) {
zxlogf(WARNING, "Edid skip allocation failed");
break;
}
}
}
}
}
void Controller::PopulateDisplayAudio(const fbl::RefPtr<DisplayInfo>& info) {
fbl::AllocChecker ac;
// Displays which support any audio are required to support basic
// audio, so just bail if that bit isn't set.
if (!info->edid.supports_basic_audio()) {
return;
}
// TODO(fxbug.dev/32457): Revisit dedupe/merge logic once the audio API takes a stance. First,
// this code always adds the basic audio formats before processing the SADs, which is likely
// redundant on some hardware (the spec isn't clear about whether or not the basic audio formats
// should also be included in the SADs). Second, this code assumes that the SADs are compact
// and not redundant, which is not guaranteed.
// Add the range for basic audio support.
audio_types_audio_stream_format_range_t range;
range.min_channels = 2;
range.max_channels = 2;
range.sample_formats = AUDIO_SAMPLE_FORMAT_16BIT;
range.min_frames_per_second = 32000;
range.max_frames_per_second = 48000;
range.flags = ASF_RANGE_FLAG_FPS_48000_FAMILY | ASF_RANGE_FLAG_FPS_44100_FAMILY;
info->edid_audio_.push_back(range, &ac);
if (!ac.check()) {
zxlogf(ERROR, "Out of memory attempting to construct supported format list.");
return;
}
for (auto it = edid::audio_data_block_iterator(&info->edid); it.is_valid(); ++it) {
if (it->format() != edid::ShortAudioDescriptor::kLPcm) {
// TODO(stevensd): Add compressed formats when audio format supports it
continue;
}
audio_types_audio_stream_format_range_t range;
constexpr audio_sample_format_t zero_format = static_cast<audio_sample_format_t>(0);
range.sample_formats = static_cast<audio_sample_format_t>(
(it->lpcm_24() ? AUDIO_SAMPLE_FORMAT_24BIT_PACKED | AUDIO_SAMPLE_FORMAT_24BIT_IN32
: zero_format) |
(it->lpcm_20() ? AUDIO_SAMPLE_FORMAT_20BIT_PACKED | AUDIO_SAMPLE_FORMAT_20BIT_IN32
: zero_format) |
(it->lpcm_16() ? AUDIO_SAMPLE_FORMAT_16BIT : zero_format));
range.min_channels = 1;
range.max_channels = static_cast<uint8_t>(it->num_channels_minus_1() + 1);
// Now build continuous ranges of sample rates in the each family
static constexpr struct {
const uint32_t flag, val;
} kRateLut[7] = {
{edid::ShortAudioDescriptor::kHz32, 32000}, {edid::ShortAudioDescriptor::kHz44, 44100},
{edid::ShortAudioDescriptor::kHz48, 48000}, {edid::ShortAudioDescriptor::kHz88, 88200},
{edid::ShortAudioDescriptor::kHz96, 96000}, {edid::ShortAudioDescriptor::kHz176, 176400},
{edid::ShortAudioDescriptor::kHz192, 192000},
};
for (uint32_t i = 0; i < std::size(kRateLut); ++i) {
if (!(it->sampling_frequencies & kRateLut[i].flag)) {
continue;
}
range.min_frames_per_second = kRateLut[i].val;
if (audio::utils::FrameRateIn48kFamily(kRateLut[i].val)) {
range.flags = ASF_RANGE_FLAG_FPS_48000_FAMILY;
} else {
range.flags = ASF_RANGE_FLAG_FPS_44100_FAMILY;
}
// We found the start of a range. At this point, we are guaranteed
// to add at least one new entry into the set of format ranges.
// Find the end of this range.
uint32_t j;
for (j = i + 1; j < std::size(kRateLut); ++j) {
if (!(it->bitrate & kRateLut[j].flag)) {
break;
}
if (audio::utils::FrameRateIn48kFamily(kRateLut[j].val)) {
range.flags |= ASF_RANGE_FLAG_FPS_48000_FAMILY;
} else {
range.flags |= ASF_RANGE_FLAG_FPS_44100_FAMILY;
}
}
i = j - 1;
range.max_frames_per_second = kRateLut[i].val;
info->edid_audio_.push_back(range, &ac);
if (!ac.check()) {
zxlogf(ERROR, "Out of memory attempting to construct supported format list.");
return;
}
}
}
}
void Controller::DisplayControllerInterfaceOnDisplaysChanged(
const added_display_args_t* displays_added, size_t added_count,
const uint64_t* displays_removed, size_t removed_count,
added_display_info_t* out_display_info_list, size_t display_info_count,
size_t* display_info_actual) {
ZX_DEBUG_ASSERT(!out_display_info_list || added_count == display_info_count);
std::unique_ptr<fbl::RefPtr<DisplayInfo>[]> added_success;
std::unique_ptr<uint64_t[]> removed;
std::unique_ptr<async::Task> task;
uint32_t added_success_count = 0;
fbl::AllocChecker ac;
if (added_count) {
added_success = std::unique_ptr<fbl::RefPtr<DisplayInfo>[]>(
new (&ac) fbl::RefPtr<DisplayInfo>[added_count]);
if (!ac.check()) {
zxlogf(ERROR, "No memory when processing hotplug");
return;
}
}
if (removed_count) {
removed = std::unique_ptr<uint64_t[]>(new (&ac) uint64_t[removed_count]);
if (!ac.check()) {
zxlogf(ERROR, "No memory when processing hotplug");
return;
}
memcpy(removed.get(), displays_removed, removed_count * sizeof(uint64_t));
}
task = fbl::make_unique_checked<async::Task>(&ac);
if (!ac.check()) {
zxlogf(ERROR, "No memory when processing hotplug");
return;
}
fbl::AutoLock lock(mtx());
for (unsigned i = 0; i < removed_count; i++) {
auto target = displays_.erase(displays_removed[i]);
if (target) {
image_node_t* n;
while ((n = list_remove_head_type(&target->images, image_node_t, link))) {
AssertMtxAliasHeld(n->self->mtx());
n->self->StartRetire();
n->self->OnRetire();
n->self.reset();
}
} else {
zxlogf(DEBUG, "Unknown display %ld removed", displays_removed[i]);
}
}
for (unsigned i = 0; i < added_count; i++) {
fbl::AllocChecker ac, ac2;
fbl::RefPtr<DisplayInfo> info = fbl::AdoptRef(new (&ac) DisplayInfo);
if (!ac.check()) {
zxlogf(INFO, "Out of memory when processing display hotplug");
break;
}
info->pending_layer_change = false;
info->vsync_layer_count = 0;
auto& display_params = displays_added[i];
auto* display_info = out_display_info_list ? &out_display_info_list[i] : nullptr;
info->id = display_params.display_id;
info->pixel_formats_ = fbl::Array<zx_pixel_format_t>(
new (&ac) zx_pixel_format_t[display_params.pixel_format_count],
display_params.pixel_format_count);
info->cursor_infos_ =
fbl::Array<cursor_info_t>(new (&ac2) cursor_info_t[display_params.cursor_info_count],
display_params.cursor_info_count);
if (!ac.check() || !ac2.check()) {
zxlogf(INFO, "Out of memory when processing display hotplug");
break;
}
memcpy(info->pixel_formats_.data(), display_params.pixel_format_list,
display_params.pixel_format_count * sizeof(zx_pixel_format_t));
memcpy(info->cursor_infos_.data(), display_params.cursor_info_list,
display_params.cursor_info_count * sizeof(cursor_info_t));
info->has_edid = display_params.edid_present;
if (info->has_edid) {
if (!i2c_.is_valid()) {
zxlogf(ERROR, "Presented edid display with no i2c bus");
continue;
}
bool success = false;
const char* edid_err = "unknown error";
uint32_t edid_attempt = 0;
static constexpr uint32_t kEdidRetries = 3;
do {
if (edid_attempt != 0) {
zxlogf(DEBUG, "Error %d/%d initializing edid: \"%s\"", edid_attempt, kEdidRetries,
edid_err);
zx_nanosleep(zx_deadline_after(ZX_MSEC(5)));
}
edid_attempt++;
I2cBus i2c = {i2c_, display_params.panel.i2c_bus_id};
success = info->edid.Init(&i2c, ddc_tx, &edid_err);
} while (!success && edid_attempt < kEdidRetries);
if (!success) {
zxlogf(INFO, "Failed to parse edid \"%s\"", edid_err);
continue;
}
PopulateDisplayAudio(info);
{
fbl::Array<uint8_t> eld;
ComputeEld(info->edid, eld);
dc_.SetEld(info->id, eld.get(), eld.size());
}
if (zxlog_level_enabled(DEBUG) && info->edid_audio_.size()) {
zxlogf(DEBUG, "Supported audio formats:");
for (auto range : info->edid_audio_) {
audio_stream_format_range temp_range;
audio::audio_stream_format_fidl_from_banjo(range, &temp_range);
for (auto rate : audio::utils::FrameRateEnumerator(temp_range)) {
zxlogf(DEBUG, " rate=%d, channels=[%d, %d], sample=%x", rate, range.min_channels,
range.max_channels, range.sample_formats);
}
}
}
if (display_info) {
display_info->is_hdmi_out = info->edid.is_hdmi();
display_info->is_standard_srgb_out = info->edid.is_standard_rgb();
display_info->audio_format_count = static_cast<uint32_t>(info->edid_audio_.size());
static_assert(
sizeof(display_info->monitor_name) == sizeof(edid::Descriptor::Monitor::data) + 1,
"Possible overflow");
static_assert(
sizeof(display_info->monitor_name) == sizeof(edid::Descriptor::Monitor::data) + 1,
"Possible overflow");
strcpy(display_info->manufacturer_id, info->edid.manufacturer_id());
strcpy(display_info->monitor_name, info->edid.monitor_name());
strcpy(display_info->monitor_serial, info->edid.monitor_serial());
display_info->manufacturer_name = info->edid.manufacturer_name();
display_info->horizontal_size_mm = info->edid.horizontal_size_mm();
display_info->vertical_size_mm = info->edid.vertical_size_mm();
}
if (zxlog_level_enabled(DEBUG)) {
const char* manufacturer = strlen(info->edid.manufacturer_name())
? info->edid.manufacturer_name()
: info->edid.manufacturer_id();
zxlogf(DEBUG, "Manufacturer \"%s\", product %d, name \"%s\", serial \"%s\"", manufacturer,
info->edid.product_code(), info->edid.monitor_name(), info->edid.monitor_serial());
info->edid.Print([](const char* str) { zxlogf(DEBUG, "%s", str); });
}
} else {
info->params = display_params.panel.params;
}
if (displays_.insert_or_find(info)) {
added_success[added_success_count++] = std::move(info);
} else {
zxlogf(INFO, "Ignoring duplicate display");
}
}
if (display_info_actual)
*display_info_actual = added_success_count;
task->set_handler([this, added_ptr = added_success.release(), removed_ptr = removed.release(),
added_success_count, removed_count](async_dispatcher_t* dispatcher,
async::Task* task, zx_status_t status) {
if (status == ZX_OK) {
for (unsigned i = 0; i < added_success_count; i++) {
if (added_ptr[i]->has_edid) {
PopulateDisplayTimings(added_ptr[i]);
}
}
fbl::AutoLock lock(mtx());
std::vector<uint64_t> added_ids(added_success_count);
uint32_t final_added_success_count = 0;
for (unsigned i = 0; i < added_success_count; i++) {
// Dropping some add events can result in spurious removes, but
// those are filtered out in the clients.
if (!added_ptr[i]->has_edid || !added_ptr[i]->edid_timings.is_empty()) {
added_ids[final_added_success_count++] = added_ptr[i]->id;
added_ptr[i]->init_done = true;
added_ptr[i]->InitializeInspect(&root_);
} else {
zxlogf(WARNING, "Ignoring display with no compatible edid timings");
}
}
if (vc_client_ && vc_ready_) {
vc_client_->OnDisplaysChanged(added_ids.data(), final_added_success_count, removed_ptr,
removed_count);
}
if (primary_client_ && primary_ready_) {
primary_client_->OnDisplaysChanged(added_ids.data(), final_added_success_count, removed_ptr,
removed_count);
}
} else {
zxlogf(ERROR, "Failed to dispatch display change task %d", status);
}
delete[] added_ptr;
delete[] removed_ptr;
delete task;
});
task.release()->Post(loop_.dispatcher());
}
void Controller::DisplayCaptureInterfaceOnCaptureComplete() {
std::unique_ptr<async::Task> task = std::make_unique<async::Task>();
fbl::AutoLock lock(mtx());
task->set_handler([this](async_dispatcher_t* dispatcher, async::Task* task, zx_status_t status) {
if (status == ZX_OK) {
// Free an image that was previously used by the hardware.
if (pending_capture_image_release_ != 0) {
ReleaseCaptureImage(pending_capture_image_release_);
pending_capture_image_release_ = 0;
}
fbl::AutoLock lock(mtx());
if (vc_client_ && vc_ready_) {
vc_client_->OnCaptureComplete();
}
if (primary_client_ && primary_ready_) {
primary_client_->OnCaptureComplete();
}
} else {
zxlogf(ERROR, "Failed to dispatch capture complete task %d", status);
}
delete task;
});
task.release()->Post(loop_.dispatcher());
}
void Controller::DisplayControllerInterfaceOnDisplayVsync(uint64_t display_id, zx_time_t timestamp,
const uint64_t* handles,
size_t handle_count) {
// Emit an event called "VSYNC", which is by convention the event
// that Trace Viewer looks for in its "Highlight VSync" feature.
TRACE_INSTANT("gfx", "VSYNC", TRACE_SCOPE_THREAD, "display_id", display_id);
TRACE_DURATION("gfx", "Display::Controller::OnDisplayVsync", "display_id", display_id);
last_vsync_ns_property_.Set(timestamp);
last_vsync_interval_ns_property_.Set(timestamp - last_vsync_timestamp_);
last_vsync_timestamp_ = timestamp;
fbl::AutoLock lock(mtx());
size_t found_handles = 0;
DisplayInfo* info = nullptr;
for (auto& display_config : displays_) {
if (display_config.id == display_id) {
info = &display_config;
break;
}
}
if (!info) {
zxlogf(ERROR, "No such display %lu", display_id);
return;
}
// See ::ApplyConfig for more explanation of how vsync image tracking works.
//
// If there's a pending layer change, don't process any present/retire actions
// until the change is complete.
if (info->pending_layer_change) {
bool done;
if (handle_count != info->vsync_layer_count) {
// There's an unexpected number of layers, so wait until the next vsync.
done = false;
} else if (list_is_empty(&info->images)) {
// If the images list is empty, then we can't have any pending layers and
// the change is done when there are no handles being displayed.
ZX_ASSERT_MSG(info->vsync_layer_count == 0, "vsync_layer_count = %d",
info->vsync_layer_count);
done = handle_count == 0;
} else {
// Otherwise the change is done when the last handle_count==info->layer_count
// images match the handles in the correct order.
auto node = list_peek_tail_type(&info->images, image_node_t, link);
ssize_t handle_idx = handle_count - 1;
while (handle_idx >= 0 && node != nullptr) {
if (handles[handle_idx] != node->self->info().handle) {
break;
}
node = list_prev_type(&info->images, &node->link, image_node_t, link);
handle_idx--;
}
done = handle_idx == -1;
}
if (done) {
info->pending_layer_change = false;
info->switching_client = false;
if (active_client_ && info->delayed_apply) {
active_client_->ReapplyConfig();
}
}
}
if (!info->pending_layer_change) {
// Since we know there are no pending layer changes, we know that every layer (i.e z_index)
// has an image. So every image either matches a handle (in which case it's being
// displayed), is older than its layer's image (i.e. in front of in the queue) and can be
// retired, or is newer than its layer's image (i.e. behind in the queue) and has yet to be
// presented.
std::vector<uint32_t> z_indices(handle_count);
for (unsigned i = 0; i < handle_count; i++) {
z_indices[i] = UINT32_MAX;
}
image_node_t* cur;
image_node_t* tmp;
list_for_every_entry_safe (&info->images, cur, tmp, image_node_t, link) {
bool z_already_matched = false;
for (unsigned i = 0; i < handle_count; i++) {
if (handles[i] == cur->self->info().handle) {
z_indices[i] = cur->self->z_index();
z_already_matched = true;
break;
} else if (z_indices[i] == cur->self->z_index()) {
z_already_matched = true;
break;
}
}
// Retire any images for which we don't already have a z-match, since
// those are older than whatever is currently in their layer.
if (!z_already_matched) {
list_delete(&cur->link);
AssertMtxAliasHeld(cur->self->mtx());
cur->self->OnRetire();
// Older images may not be presented. Ending their flows here
// ensures the sanity of traces.
//
// NOTE: If changing this flow name or ID, please also do so in the
// corresponding FLOW_BEGIN in display_swapchain.cc.
TRACE_FLOW_END("gfx", "present_image", cur->self->id);
cur->self.reset();
}
}
}
std::vector<uint64_t> primary_images, virtcon_images;
image_node_t* cur;
list_for_every_entry (&info->images, cur, image_node_t, link) {
for (unsigned i = 0; i < handle_count; i++) {
if (handles[i] == cur->self->info().handle) {
// End of the flow for the image going to be presented.
//
// NOTE: If changing this flow name or ID, please also do so in the
// corresponding FLOW_BEGIN in display_swapchain.cc.
TRACE_FLOW_END("gfx", "present_image", cur->self->id);
bool is_virtcon_image = vc_client_ && cur->self->client_id() == vc_client_->id();
bool is_primary_image = primary_client_ && cur->self->client_id() == primary_client_->id();
if (is_virtcon_image) {
virtcon_images.push_back(cur->self->id);
}
if (is_primary_image) {
primary_images.push_back(cur->self->id);
}
if (is_virtcon_image || is_primary_image) {
found_handles++;
break;
}
}
}
}
if (found_handles != handle_count) {
zxlogf(TRACE,
"OnDisplayVsync with %lu unmatched images (found_handles = %lu, handle_count = %lu)\n",
handle_count - found_handles, found_handles, handle_count);
return;
}
if (vc_applied_ && vc_client_) {
vc_client_->OnDisplayVsync(display_id, timestamp, virtcon_images.data(), virtcon_images.size());
} else if (!vc_applied_ && primary_client_) {
// A previous client applied a config and then disconnected before the vsync. Don't send garbage
// image IDs to the new primary client.
if (primary_client_->id() != applied_client_id_) {
zxlogf(DEBUG,
"Dropping vsync. This was meant for client[%d], "
"but client[%d] is currently active.\n",
applied_client_id_, primary_client_->id());
} else {
primary_client_->OnDisplayVsync(display_id, timestamp, primary_images.data(),
primary_images.size());
}
}
}
zx_status_t Controller::DisplayControllerInterfaceGetAudioFormat(
uint64_t display_id, uint32_t fmt_idx, audio_types_audio_stream_format_range_t* fmt_out) {
fbl::AutoLock lock(mtx());
auto display = displays_.find(display_id);
if (!display.IsValid()) {
return ZX_ERR_NOT_FOUND;
}
if (!display->has_edid) {
return ZX_ERR_NOT_SUPPORTED;
}
if (fmt_idx > display->edid_audio_.size()) {
return ZX_ERR_OUT_OF_RANGE;
}
*fmt_out = display->edid_audio_[fmt_idx];
return ZX_OK;
}
void Controller::ApplyConfig(DisplayConfig* configs[], int32_t count, bool is_vc,
uint32_t client_stamp, uint32_t client_id) {
fbl::Array<const display_config_t*> display_configs(new const display_config_t*[count], count);
uint32_t display_count = 0;
{
fbl::AutoLock lock(mtx());
bool switching_client = (is_vc != vc_applied_ || client_id != applied_client_id_);
// The fact that there could already be a vsync waiting to be handled when a config
// is applied means that a vsync with no handle for a layer could be interpreted as either
// nothing in the layer has been presented or everything in the layer can be retired. To
// prevent that ambiguity, we don't allow a layer to be disabled until an image from
// it has been displayed.
//
// Since layers can be moved between displays but the implementation only supports
// tracking the image in one display's queue, we need to ensure that the old display is
// done with a migrated image before the new display is done with it. This means
// that the new display can't flip until the configuration change is done. However, we
// don't want to completely prohibit flips, as that would add latency if the layer's new
// image is being waited for when the configuration is applied.
//
// To handle both of these cases, we force all layer changes to complete before the client
// can apply a new configuration. We allow the client to apply a more complete version of
// the configuration, although Client::HandleApplyConfig won't migrate a layer's current
// image if there is also a pending image.
if (switching_client || applied_stamp_ != client_stamp) {
for (int i = 0; i < count; i++) {
auto* config = configs[i];
auto display = displays_.find(config->id);
if (!display.IsValid()) {
continue;
}
if (display->pending_layer_change) {
display->delayed_apply = true;
return;
}
}
}
for (int i = 0; i < count; i++) {
auto* config = configs[i];
auto display = displays_.find(config->id);
if (!display.IsValid()) {
continue;
}
display->switching_client = switching_client;
display->pending_layer_change = config->apply_layer_change();
display->vsync_layer_count = config->vsync_layer_count();
display->delayed_apply = false;
if (display->vsync_layer_count == 0) {
continue;
}
display_configs[display_count++] = config->current_config();
for (auto& layer_node : config->get_current_layers()) {
Layer* layer = layer_node.layer;
fbl::RefPtr<Image> image = layer->current_image();
if (layer->is_skipped() || !image) {
continue;
}
// Set the image z index so vsync knows what layer the image is in
AssertMtxAliasHeld(image->mtx());
image->set_z_index(layer->z_order());
image->StartPresent();
// It's possible that the image's layer was moved between displays. The logic around
// pending_layer_change guarantees that the old display will be done with the image
// before the new display is, so deleting it from the old list is fine.
//
// Even if we're on the same display, the entry needs to be moved to the end of the
// list to ensure that the last config->current.layer_count elements in the queue
// are the current images.
if (list_in_list(&image->node.link)) {
list_delete(&image->node.link);
} else {
image->node.self = image;
}
list_add_tail(&display->images, &image->node.link);
}
ZX_ASSERT(display->vsync_layer_count == 0 || !list_is_empty(&display->images));
}
vc_applied_ = is_vc;
applied_stamp_ = client_stamp;
applied_client_id_ = client_id;
if (switching_client) {
active_client_->ReapplySpecialConfigs();
}
}
dc_.ApplyConfiguration(display_configs.get(), display_count);
}
void Controller::ReleaseImage(Image* image) { dc_.ReleaseImage(&image->info()); }
void Controller::ReleaseCaptureImage(uint64_t handle) {
if (dc_capture_.is_valid() && handle != 0) {
if (dc_capture_.ReleaseCapture(handle) == ZX_ERR_SHOULD_WAIT) {
ZX_DEBUG_ASSERT_MSG(pending_capture_image_release_ == 0,
"multiple pending releases for capture images");
// Delay the image release until the hardware is done.
pending_capture_image_release_ = handle;
}
}
}
void Controller::SetVcMode(uint8_t vc_mode) {
fbl::AutoLock lock(mtx());
vc_mode_ = static_cast<fidl_display::wire::VirtconMode>(vc_mode);
HandleClientOwnershipChanges();
}
void Controller::HandleClientOwnershipChanges() {
ClientProxy* new_active;
if (vc_mode_ == fidl_display::wire::VirtconMode::kForced ||
(vc_mode_ == fidl_display::wire::VirtconMode::kFallback && primary_client_ == nullptr)) {
new_active = vc_client_;
} else {
new_active = primary_client_;
}
if (new_active != active_client_) {
if (active_client_) {
active_client_->SetOwnership(false);
}
if (new_active) {
new_active->SetOwnership(true);
}
active_client_ = new_active;
}
}
void Controller::OnClientDead(ClientProxy* client) {
zxlogf(DEBUG, "Client %d dead", client->id());
fbl::AutoLock lock(mtx());
if (unbinding_) {
return;
}
if (client == vc_client_) {
vc_client_ = nullptr;
vc_mode_ = fidl_display::wire::VirtconMode::kInactive;
} else if (client == primary_client_) {
primary_client_ = nullptr;
} else {
ZX_DEBUG_ASSERT_MSG(false, "Dead client is neither vc nor primary\n");
}
HandleClientOwnershipChanges();
}
bool Controller::GetPanelConfig(uint64_t display_id,
const fbl::Vector<edid::timing_params_t>** timings,
const display_params_t** params) {
ZX_DEBUG_ASSERT(mtx_trylock(&mtx_) == thrd_busy);
if (unbinding_) {
return false;
}
for (auto& display : displays_) {
if (display.id == display_id) {
if (display.has_edid) {
*timings = &display.edid_timings;
*params = nullptr;
} else {
*params = &display.params;
*timings = nullptr;
}
return true;
}
}
return false;
}
#define GET_DISPLAY_INFO(FN_NAME, FIELD, TYPE) \
bool Controller::FN_NAME(uint64_t display_id, fbl::Array<TYPE>* data_out) { \
ZX_DEBUG_ASSERT(mtx_trylock(&mtx_) == thrd_busy); \
for (auto& display : displays_) { \
if (display.id == display_id) { \
fbl::AllocChecker ac; \
size_t size = display.FIELD.size(); \
*data_out = fbl::Array<TYPE>(new (&ac) TYPE[size], size); \
if (!ac.check()) { \
return false; \
} \
memcpy(data_out->data(), display.FIELD.data(), sizeof(TYPE) * size); \
return true; \
} \
} \
return false; \
}
GET_DISPLAY_INFO(GetCursorInfo, cursor_infos_, cursor_info_t)
GET_DISPLAY_INFO(GetSupportedPixelFormats, pixel_formats_, zx_pixel_format_t)
bool Controller::GetDisplayIdentifiers(uint64_t display_id, const char** manufacturer_name,
const char** monitor_name, const char** monitor_serial) {
ZX_DEBUG_ASSERT(mtx_trylock(&mtx_) == thrd_busy);
for (auto& display : displays_) {
if (display.id == display_id) {
if (display.has_edid) {
*manufacturer_name = display.edid.manufacturer_name();
if (!strcmp("", *manufacturer_name)) {
*manufacturer_name = display.edid.manufacturer_id();
}
*monitor_name = display.edid.monitor_name();
*monitor_serial = display.edid.monitor_serial();
} else {
*manufacturer_name = *monitor_name = *monitor_serial = "";
}
return true;
}
}
return false;
}
bool Controller::GetDisplayPhysicalDimensions(uint64_t display_id, uint32_t* horizontal_size_mm,
uint32_t* vertical_size_mm) {
ZX_DEBUG_ASSERT(mtx_trylock(&mtx_) == thrd_busy);
for (auto& display : displays_) {
if (display.id == display_id) {
if (display.has_edid) {
*horizontal_size_mm = display.edid.horizontal_size_mm();
*vertical_size_mm = display.edid.vertical_size_mm();
} else {
*horizontal_size_mm = *vertical_size_mm = 0;
}
return true;
}
}
return false;
}
zx_status_t Controller::DdkOpen(zx_device_t** dev_out, uint32_t flags) { return ZX_OK; }
static void PrintChannelKoids(bool is_vc, const zx::channel& channel) {
zx_info_handle_basic_t info{};
size_t actual, avail;
zx_status_t status = channel.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), &actual, &avail);
if (status != ZX_OK || info.type != ZX_OBJ_TYPE_CHANNEL) {
zxlogf(DEBUG, "Could not get koids for handle(type=%d): %d", info.type, status);
return;
}
ZX_DEBUG_ASSERT(actual == avail);
zxlogf(INFO, "%s client connecting on channel (c=0x%lx, s=0x%lx)", is_vc ? "vc" : "dc",
info.related_koid, info.koid);
}
zx_status_t Controller::CreateClient(bool is_vc, zx::channel device_channel,
zx::channel client_channel,
fit::function<void()> on_client_dead) {
PrintChannelKoids(is_vc, client_channel);
fbl::AllocChecker ac;
std::unique_ptr<async::Task> task = fbl::make_unique_checked<async::Task>(&ac);
if (!ac.check()) {
zxlogf(DEBUG, "Failed to alloc client task");
return ZX_ERR_NO_MEMORY;
}
fbl::AutoLock lock(mtx());
if (unbinding_) {
zxlogf(DEBUG, "Client connected during unbind");
return ZX_ERR_UNAVAILABLE;
}
if ((is_vc && vc_client_) || (!is_vc && primary_client_)) {
zxlogf(DEBUG, "Already bound");
return ZX_ERR_ALREADY_BOUND;
}
// Kernel framebuffer currently prevent non-linear formats and result
// in a significant performance cost each time a new config is applied.
// We limit usage to virtcon mode until these problems have been
// resolved.
bool use_kernel_framebuffer = is_vc && !kernel_framebuffer_disabled_;
auto client = fbl::make_unique_checked<ClientProxy>(&ac, this, is_vc, use_kernel_framebuffer,
next_client_id_++, std::move(on_client_dead));
if (!ac.check()) {
zxlogf(DEBUG, "Failed to alloc client");
return ZX_ERR_NO_MEMORY;
}
zx_status_t status = client->Init(&root_, std::move(client_channel));
if (status != ZX_OK) {
zxlogf(DEBUG, "Failed to init client %d", status);
return status;
}
status = client->DdkAdd(ddk::DeviceAddArgs(is_vc ? "dc-vc" : "dc")
.set_flags(DEVICE_ADD_INSTANCE)
.set_client_remote(std::move(device_channel)));
if (status != ZX_OK) {
zxlogf(DEBUG, "Failed to add client %d", status);
return status;
}
ClientProxy* client_ptr = client.release();
zxlogf(DEBUG, "New %s client [%d] connected.", is_vc ? "dc-vc" : "dc", client_ptr->id());
if (is_vc) {
vc_client_ = client_ptr;
vc_ready_ = false;
} else {
primary_client_ = client_ptr;
primary_ready_ = false;
}
HandleClientOwnershipChanges();
task->set_handler(
[this, client_ptr](async_dispatcher_t* dispatcher, async::Task* task, zx_status_t status) {
if (status == ZX_OK) {
fbl::AutoLock lock(mtx());
if (unbinding_) {
return;
}
if (client_ptr == vc_client_ || client_ptr == primary_client_) {
// Add all existing displays to the client
if (displays_.size() > 0) {
uint64_t current_displays[displays_.size()];
int idx = 0;
for (const DisplayInfo& display : displays_) {
if (display.init_done) {
current_displays[idx++] = display.id;
}
}
client_ptr->OnDisplaysChanged(current_displays, idx, nullptr, 0);
}
if (vc_client_ == client_ptr) {
vc_ready_ = true;
} else {
primary_ready_ = true;
}
}
}
delete task;
});
return task.release()->Post(loop_.dispatcher());
}
void Controller::OpenVirtconController(OpenVirtconControllerRequestView request,
OpenVirtconControllerCompleter::Sync& _completer) {
_completer.Reply(
CreateClient(/*is_vc=*/true, std::move(request->device), request->controller.TakeChannel()));
}
void Controller::OpenController(OpenControllerRequestView request,
OpenControllerCompleter::Sync& _completer) {
_completer.Reply(
CreateClient(/*is_vc=*/false, std::move(request->device), request->controller.TakeChannel()));
}
zx_status_t Controller::Bind(std::unique_ptr<display::Controller>* device_ptr) {
ZX_DEBUG_ASSERT_MSG(device_ptr && device_ptr->get() == this, "Wrong controller passed to Bind()");
zx_status_t status;
dc_ = ddk::DisplayControllerImplProtocolClient(parent_);
if (!dc_.is_valid()) {
ZX_DEBUG_ASSERT_MSG(false, "Display controller bind mismatch");
return ZX_ERR_NOT_SUPPORTED;
}
// optional display controller capture protocol client
dc_capture_ = ddk::DisplayCaptureImplProtocolClient(parent_);
if (!dc_capture_.is_valid()) {
zxlogf(WARNING, "Display Capture not supported by this platform");
}
// optional display controller clamp rgb protocol client
dc_clamp_rgb_ = ddk::DisplayClampRgbImplProtocolClient(parent_);
i2c_ = ddk::I2cImplProtocolClient(parent_);
status = loop_.StartThread("display-client-loop", &loop_thread_);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to start loop %d", status);
return status;
}
if ((status = DdkAdd(
ddk::DeviceAddArgs("display-controller").set_inspect_vmo(inspector_.DuplicateVmo()))) !=
ZX_OK) {
zxlogf(ERROR, "Failed to add display core device %d", status);
return status;
}
// Set the display controller looper thread to use a deadline profile.
// TODO(fxbug.dev/40858): Migrate to the role-based API when available, instead of hard
// coding parameters.
{
const zx_duration_t capacity = ZX_USEC(500);
const zx_duration_t deadline = ZX_MSEC(8);
const zx_duration_t period = deadline;
zx_handle_t profile = ZX_HANDLE_INVALID;
if ((status = device_get_deadline_profile(this->zxdev(), capacity, deadline, period,
"dev/display/controller", &profile)) != ZX_OK) {
zxlogf(ERROR, "Failed to get deadline profile %d", status);
} else {
zx_handle_t thread_handle = thrd_get_zx_handle(loop_thread_);
status = zx_object_set_profile(thread_handle, profile, 0);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to set deadline profile %d", status);
}
zx_handle_close(profile);
}
}
__UNUSED auto ptr = device_ptr->release();
dc_.SetDisplayControllerInterface(this, &display_controller_interface_protocol_ops_);
if (dc_capture_.is_valid()) {
dc_capture_.SetDisplayCaptureInterface(this, &display_capture_interface_protocol_ops_);
}
return ZX_OK;
}
void Controller::DdkUnbind(ddk::UnbindTxn txn) {
zxlogf(INFO, "Controller::DdkUnbind");
fbl::AutoLock lock(mtx());
unbinding_ = true;
txn.Reply();
}
void Controller::DdkRelease() {
// Clients may have active work holding mtx_ in loop_.dispatcher(), so shut it down without mtx_
loop_.Shutdown();
// Set an empty config so that the display driver releases resources.
const display_config_t* configs;
dc_.ApplyConfiguration(&configs, 0);
delete this;
}
// Use the same default watchdog timeout as scenic, which may help ensure watchdog logs/errors
// happen close together and can be correlated.
static constexpr uint64_t kWatchdogWarningIntervalMs = 15000;
static constexpr uint64_t kWatchdogTimeoutMs = 45000;
Controller::Controller(zx_device_t* parent)
: ControllerParent(parent),
kernel_framebuffer_disabled_(IsKernelFramebufferDisabled()),
loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
watchdog_("display-client-loop", kWatchdogWarningIntervalMs, kWatchdogTimeoutMs,
loop_.dispatcher()) {
mtx_init(&mtx_, mtx_plain);
root_ = inspector_.GetRoot().CreateChild("display");
last_vsync_ns_property_ = root_.CreateUint("last_vsync_timestamp_ns", 0);
last_vsync_interval_ns_property_ = root_.CreateUint("last_vsync_interval_ns", 0);
}
Controller::~Controller() { zxlogf(INFO, "Controller::~Controller"); }
size_t Controller::TEST_imported_images_count() const {
fbl::AutoLock lock(mtx());
size_t vc_images = vc_client_ ? vc_client_->TEST_imported_images_count() : 0;
size_t primary_images = primary_client_ ? primary_client_->TEST_imported_images_count() : 0;
size_t display_images = 0;
for (const auto& display : displays_) {
image_node_t* cur;
image_node_t* tmp;
list_for_every_entry_safe (&display.images, cur, tmp, image_node_t, link) { ++display_images; }
}
return vc_images + primary_images + display_images;
}
// ControllerInstance methods
} // namespace display
static zx_status_t display_controller_bind(void* ctx, zx_device_t* parent) {
fbl::AllocChecker ac;
std::unique_ptr<display::Controller> core(new (&ac) display::Controller(parent));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
return core->Bind(&core);
}
static constexpr zx_driver_ops_t display_controller_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = display_controller_bind;
return ops;
}();
// clang-format off
ZIRCON_DRIVER(display_controller, display_controller_ops, "zircon", "0.1");