blob: ef550cc131c2ae815360a8796dfb4604cdefa56a [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 <ddk/debug.h>
#include <fbl/auto_lock.h>
#include <lib/async/cpp/task.h>
#include <audio-proto-utils/format-utils.h>
#include <zircon/device/display-controller.h>
#include "controller.h"
#include "client.h"
#include "fuchsia/display/c/fidl.h"
namespace {
typedef struct i2c_bus {
i2c_impl_protocol_t* i2c;
uint32_t bus_id;
} i2c_bus_t;
edid::ddc_i2c_transact ddc_tx = [](void* ctx, edid::ddc_i2c_msg_t* msgs, uint32_t count) -> bool {
auto i2c = static_cast<i2c_bus_t*>(ctx);
// TODO(ZX-2487): Remove the special casing when the i2c_impl API gets updated
if (count == 3) {
ZX_ASSERT(!msgs[0].is_read);
if (i2c_impl_write_read(i2c->i2c, i2c->bus_id,
msgs->addr, msgs->buf, msgs->length, nullptr, 0)) {
return false;
}
msgs++;
}
ZX_ASSERT(!msgs[0].is_read);
ZX_ASSERT(msgs[1].is_read);
ZX_ASSERT(msgs[0].addr == msgs[1].addr);
return i2c_impl_write_read(i2c->i2c, i2c->bus_id,
msgs[0].addr, msgs[0].buf, msgs[0].length,
msgs[1].buf, msgs[1].length) == ZX_OK;
};
} // namespace
namespace display {
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(WARN, "Edid skip allocation failed\n");
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(ZX-2607): 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_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.\n");
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_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 < fbl::count_of(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 < fbl::count_of(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.\n");
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);
fbl::unique_ptr<fbl::RefPtr<DisplayInfo>[]> added_success;
fbl::unique_ptr<uint64_t[]> removed;
fbl::unique_ptr<async::Task> task;
uint32_t added_success_count = 0;
fbl::AllocChecker ac;
if (added_count) {
added_success = fbl::unique_ptr<fbl::RefPtr<DisplayInfo>[]>(
new (&ac) fbl::RefPtr<DisplayInfo>[added_count]);
if (!ac.check()) {
zxlogf(ERROR, "No memory when processing hotplug\n");
return;
}
}
if (removed_count) {
removed = fbl::unique_ptr<uint64_t[]>(new (&ac) uint64_t[removed_count]);
if (!ac.check()) {
zxlogf(ERROR, "No memory when processing hotplug\n");
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\n");
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))) {
n->self->StartRetire();
n->self->OnRetire();
n->self.reset();
}
} else {
zxlogf(TRACE, "Unknown display %ld removed\n", 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\n");
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\n");
break;
}
memcpy(info->pixel_formats_.get(), display_params.pixel_format_list,
display_params.pixel_format_count * sizeof(zx_pixel_format_t));
memcpy(info->cursor_infos_.get(), 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 (!has_i2c_ops_) {
zxlogf(ERROR, "Presented edid display with no i2c bus\n");
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(TRACE, "Error %d/%d initializing edid: \"%s\"\n",
edid_attempt, kEdidRetries, edid_err);
zx_nanosleep(zx_deadline_after(ZX_MSEC(5)));
}
edid_attempt++;
struct i2c_bus i2c = { &i2c_ops_, 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\"\n", edid_err);
continue;
}
PopulateDisplayAudio(info);
if (zxlog_level_enabled_etc(DDK_LOG_TRACE) && info->edid_audio_.size()) {
zxlogf(TRACE, "Supported audio formats:\n");
for (auto range : info->edid_audio_) {
for (auto rate : audio::utils::FrameRateEnumerator(range)) {
zxlogf(TRACE, " rate=%d, channels=[%d, %d], sample=%x\n",
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->monitor_name, info->edid.monitor_name());
strcpy(display_info->monitor_serial, info->edid.monitor_serial());
display_info->manufacturer_name = info->edid.manufacturer_name();
}
if (zxlog_level_enabled_etc(DDK_LOG_TRACE)) {
zxlogf(TRACE, "Manufacturer \"%s\", product %d, name \"%s\", serial \"%s\"\n",
info->edid.manufacturer_name(), info->edid.product_code(),
info->edid.monitor_name(), info->edid.monitor_serial());
info->edid.Print([](const char* str) {zxlogf(TRACE, "%s", str);});
}
} else {
info->params = display_params.panel.params;
}
if (displays_.insert_or_find(info)) {
added_success[added_success_count++] = fbl::move(info);
} else {
zxlogf(INFO, "Ignoring duplicate display\n");
}
}
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_);
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;
} else {
zxlogf(WARN, "Ignoring display with no compatible edid timings\n");
}
}
if (vc_client_ && vc_ready_) {
vc_client_->OnDisplaysChanged(
added_ids, final_added_success_count, removed_ptr, removed_count);
}
if (primary_client_ && primary_ready_) {
primary_client_->OnDisplaysChanged(
added_ids, final_added_success_count, removed_ptr, removed_count);
}
} else {
zxlogf(ERROR, "Failed to dispatch display change task %d\n", status);
}
delete[] added_ptr;
delete[] removed_ptr;
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) {
fbl::AutoLock lock(&mtx_);
DisplayInfo* info = nullptr;
for (auto& display_config : displays_) {
if (display_config.id == display_id) {
info = &display_config;
break;
}
}
if (!info) {
return;
}
// See ::ApplyConfig for more explaination 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(info->vsync_layer_count == 0);
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();
}
}
}
// Drop the vsync event if we're in the middle of switching clients, since we don't want to
// send garbage image ids. Switching clients is rare enough that any minor timing issues that
// this could cause aren't worth worrying about.
if (!info->switching_client) {
uint64_t images[handle_count];
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) {
images[i] = cur->self->id;
break;
}
}
}
if (vc_applied_ && vc_client_) {
vc_client_->OnDisplayVsync(display_id, timestamp, images, handle_count);
} else if (!vc_applied_ && primary_client_) {
primary_client_->OnDisplayVsync(display_id, timestamp, images, handle_count);
}
} else {
zxlogf(TRACE, "Dropping vsync\n");
}
if (info->pending_layer_change) {
return;
}
// 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.
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);
cur->self->OnRetire();
cur->self.reset();
}
}
}
zx_status_t Controller::DisplayControllerInterfaceGetAudioFormat(
uint64_t display_id, uint32_t fmt_idx, 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) {
const display_config_t* display_configs[count];
uint32_t display_count = 0;
{
fbl::AutoLock lock(&mtx_);
// 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 the 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 (vc_applied_ != is_vc || 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 = is_vc != vc_applied_;
display->pending_layer_change =
config->apply_layer_change() || display->switching_client;
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
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);
}
}
vc_applied_ = is_vc;
applied_stamp_ = client_stamp;
}
dc_.ApplyConfiguration(display_configs, display_count);
}
void Controller::ReleaseImage(Image* image) {
dc_.ReleaseImage(&image->info());
}
void Controller::SetVcMode(uint8_t vc_mode) {
fbl::AutoLock lock(&mtx_);
vc_mode_ = vc_mode;
HandleClientOwnershipChanges();
}
void Controller::HandleClientOwnershipChanges() {
ClientProxy* new_active;
if (vc_mode_ == fuchsia_display_VirtconMode_FORCED
|| (vc_mode_ == fuchsia_display_VirtconMode_FALLBACK && 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) {
fbl::AutoLock lock(&mtx_);
if (client == vc_client_) {
vc_client_ = nullptr;
vc_mode_ = fuchsia_display_VirtconMode_INACTIVE;
} else if (client == primary_client_) {
primary_client_ = nullptr;
}
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);
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->get(), display.FIELD.get(), 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);
zx_status_t Controller::DdkOpen(zx_device_t** dev_out, uint32_t flags) {
return DdkOpenAt(dev_out, "", flags);
}
zx_status_t Controller::DdkOpenAt(zx_device_t** dev_out, const char* path, uint32_t flags) {
fbl::AllocChecker ac;
fbl::unique_ptr<async::Task> task = fbl::make_unique_checked<async::Task>(&ac);
if (!ac.check()) {
zxlogf(TRACE, "Failed to alloc client task\n");
return ZX_ERR_NO_MEMORY;
}
fbl::AutoLock lock(&mtx_);
bool is_vc = strcmp("virtcon", path) == 0;
if ((is_vc && vc_client_) || (!is_vc && primary_client_)) {
zxlogf(TRACE, "Already bound\n");
return ZX_ERR_ALREADY_BOUND;
}
auto client = fbl::make_unique_checked<ClientProxy>(&ac, this, is_vc);
if (!ac.check()) {
zxlogf(TRACE, "Failed to alloc client\n");
return ZX_ERR_NO_MEMORY;
}
zx_status_t status = client->Init();
if (status != ZX_OK) {
zxlogf(TRACE, "Failed to init client %d\n", status);
return status;
}
if ((status = client->DdkAdd(is_vc ? "dc-vc" : "dc", DEVICE_ADD_INSTANCE)) != ZX_OK) {
zxlogf(TRACE, "Failed to add client %d\n", status);
return status;
}
ClientProxy* client_ptr = client.release();
*dev_out = client_ptr->zxdev();
zxlogf(TRACE, "New client connected at \"%s\"\n", path);
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 (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());
}
zx_status_t Controller::Bind(fbl::unique_ptr<display::Controller>* device_ptr) {
zx_status_t status;
display_controller_protocol_t dc_proto;
if (device_get_protocol(parent_, ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL, &dc_proto)) {
ZX_DEBUG_ASSERT_MSG(false, "Display controller bind mismatch");
return ZX_ERR_NOT_SUPPORTED;
}
dc_ = ddk::DisplayControllerProtocolProxy(&dc_proto);
if (device_get_protocol(parent_, ZX_PROTOCOL_I2C_IMPL, &i2c_ops_) == ZX_OK) {
has_i2c_ops_ = true;
} else {
has_i2c_ops_ = false;
}
status = loop_.StartThread("display-client-loop", &loop_thread_);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to start loop %d\n", status);
return status;
}
if ((status = DdkAdd("display-controller")) != ZX_OK) {
zxlogf(ERROR, "Failed to add display core device %d\n", status);
return status;
}
__UNUSED auto ptr = device_ptr->release();
display_controller_interface_t intf = {&display_controller_interface_ops_, this};
dc_.SetDisplayControllerInterface(&intf);
return ZX_OK;
}
void Controller::DdkUnbind() {
{
fbl::AutoLock lock(&mtx_);
if (vc_client_) {
vc_client_->Close();
}
if (primary_client_) {
primary_client_->Close();
}
}
DdkRemove();
}
void Controller::DdkRelease() {
delete this;
}
Controller::Controller(zx_device_t* parent)
: ControllerParent(parent), loop_(&kAsyncLoopConfigNoAttachToThread) {
mtx_init(&mtx_, mtx_plain);
}
// ControllerInstance methods
} // namespace display
zx_status_t display_controller_bind(void* ctx, zx_device_t* parent) {
fbl::AllocChecker ac;
fbl::unique_ptr<display::Controller> core(new (&ac) display::Controller(parent));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
return core->Bind(&core);
}