blob: a49f25cd3492f6a0f4193c3a4c4d31ff0f952c3f [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 "clock.h"
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/ddk/metadata.h>
#include <lib/fdf/dispatcher.h>
#include <zircon/types.h>
#include <memory>
#include <bind/fuchsia/clock/cpp/bind.h>
#include <ddk/metadata/clock.h>
#include <fbl/alloc_checker.h>
void ClockDevice::Enable(EnableCompleter::Sync& completer) {
completer.Reply(zx::make_result(clock_.Enable(id_)));
}
void ClockDevice::Disable(DisableCompleter::Sync& completer) {
completer.Reply(zx::make_result(clock_.Disable(id_)));
}
void ClockDevice::IsEnabled(IsEnabledCompleter::Sync& completer) {
bool enabled;
zx_status_t status = clock_.IsEnabled(id_, &enabled);
if (status == ZX_OK) {
completer.ReplySuccess(enabled);
} else {
completer.ReplyError(status);
}
}
void ClockDevice::SetRate(SetRateRequestView request, SetRateCompleter::Sync& completer) {
completer.Reply(zx::make_result(clock_.SetRate(id_, request->hz)));
}
void ClockDevice::ClockDevice::QuerySupportedRate(QuerySupportedRateRequestView request,
QuerySupportedRateCompleter::Sync& completer) {
uint64_t hz_out;
zx_status_t status = clock_.QuerySupportedRate(id_, request->hz_in, &hz_out);
if (status == ZX_OK) {
completer.ReplySuccess(hz_out);
} else {
completer.ReplyError(status);
}
}
void ClockDevice::GetRate(GetRateCompleter::Sync& completer) {
uint64_t current_rate;
zx_status_t status = clock_.GetRate(id_, &current_rate);
if (status == ZX_OK) {
completer.ReplySuccess(current_rate);
} else {
completer.ReplyError(status);
}
}
void ClockDevice::SetInput(SetInputRequestView request, SetInputCompleter::Sync& completer) {
completer.Reply(zx::make_result(clock_.SetInput(id_, request->idx)));
}
void ClockDevice::GetNumInputs(GetNumInputsCompleter::Sync& completer) {
uint32_t num_inputs;
zx_status_t status = clock_.GetNumInputs(id_, &num_inputs);
if (status == ZX_OK) {
completer.ReplySuccess(num_inputs);
} else {
completer.ReplyError(status);
}
}
void ClockDevice::GetInput(GetInputCompleter::Sync& completer) {
uint32_t input;
zx_status_t status = clock_.GetInput(id_, &input);
if (status == ZX_OK) {
completer.ReplySuccess(input);
} else {
completer.ReplyError(status);
}
}
zx_status_t ClockDevice::ServeOutgoing(fidl::ServerEnd<fuchsia_io::Directory> server_end) {
fuchsia_hardware_clock::Service::InstanceHandler handler({
.clock = bindings_.CreateHandler(this, dispatcher_, fidl::kIgnoreBindingClosure),
});
zx::result result = outgoing_.AddService<fuchsia_hardware_clock::Service>(std::move(handler));
if (result.is_error()) {
zxlogf(ERROR, "Failed to add service to the outgoing directory: %s", result.status_string());
return result.status_value();
}
result = outgoing_.Serve(std::move(server_end));
if (result.is_error()) {
zxlogf(ERROR, "Failed to serve the outgoing directory: %s", result.status_string());
return result.status_value();
}
return ZX_OK;
}
void ClockDevice::DdkRelease() { delete this; }
zx_status_t ClockDevice::Create(void* ctx, zx_device_t* parent) {
const ddk::ClockImplProtocolClient clock_banjo(parent);
if (clock_banjo.is_valid()) {
zxlogf(INFO, "Using Banjo clockimpl protocol");
}
{
fdf::WireSyncClient<fuchsia_hardware_clockimpl::ClockImpl> clock_fidl;
if (!clock_banjo.is_valid()) {
zx::result clock_fidl_client =
DdkConnectRuntimeProtocol<fuchsia_hardware_clockimpl::Service::Device>(parent);
if (clock_fidl_client.is_ok()) {
zxlogf(INFO, "Failed to get Banjo clockimpl protocol, falling back to FIDL");
clock_fidl = fdf::WireSyncClient(std::move(*clock_fidl_client));
} else {
zxlogf(ERROR, "Failed to get Banjo or FIDL clockimpl protocol");
return ZX_ERR_NO_RESOURCES;
}
}
// Process init metadata while we are still the exclusive owner of the clock client.
ClockInitDevice::Create(parent, {clock_banjo, std::move(clock_fidl)});
}
auto clock_ids = ddk::GetMetadataArray<clock_id_t>(parent, DEVICE_METADATA_CLOCK_IDS);
if (!clock_ids.is_ok()) {
zxlogf(ERROR, "GetMetadataArray failed %d.", clock_ids.error_value());
return clock_ids.error_value();
}
for (auto clock : *clock_ids) {
fdf::WireSyncClient<fuchsia_hardware_clockimpl::ClockImpl> clock_fidl;
if (!clock_banjo.is_valid()) {
zx::result clock_fidl_client =
DdkConnectRuntimeProtocol<fuchsia_hardware_clockimpl::Service::Device>(parent);
ZX_ASSERT_MSG(clock_fidl_client.is_ok(), "Failed to get additional FIDL client: %s",
clock_fidl_client.status_string());
clock_fidl = fdf::WireSyncClient(std::move(*clock_fidl_client));
}
ClockImplProxy clock_client(clock_banjo, std::move(clock_fidl));
auto clock_id = clock.clock_id;
fbl::AllocChecker ac;
std::unique_ptr<ClockDevice> dev(new (&ac)
ClockDevice(parent, std::move(clock_client), clock_id));
if (!ac.check()) {
zxlogf(ERROR, "Failed to allocate clock device.");
return ZX_ERR_NO_MEMORY;
}
char name[20];
snprintf(name, sizeof(name), "clock-%u", clock_id);
zx_device_prop_t props[] = {
{BIND_CLOCK_ID, 0, clock_id},
};
zx::result endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
zxlogf(ERROR, "Failed to create IO directory endpoints - %s.", endpoints.status_string());
return endpoints.status_value();
}
zx_status_t status = dev->ServeOutgoing(std::move(endpoints->server));
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to serve outgoing directory: %s", zx_status_get_string(status));
return status;
}
std::array offers = {
fuchsia_hardware_clock::Service::Name,
};
status = dev->DdkAdd(ddk::DeviceAddArgs(name)
.set_flags(DEVICE_ADD_ALLOW_MULTI_COMPOSITE)
.set_props(props)
.set_fidl_service_offers(offers)
.set_outgoing_dir(endpoints->client.TakeChannel()));
if (status != ZX_OK) {
zxlogf(ERROR, "DdkAdd failed - %s", zx_status_get_string(status));
return status;
}
// dev is now owned by devmgr.
[[maybe_unused]] auto ptr = dev.release();
}
return ZX_OK;
}
void ClockInitDevice::Create(zx_device_t* parent, const ClockImplProxy& clock) {
auto decoded = ddk::GetEncodedMetadata<fuchsia_hardware_clockimpl::wire::InitMetadata>(
parent, DEVICE_METADATA_CLOCK_INIT);
if (!decoded.is_ok()) {
if (decoded.status_value() == ZX_ERR_NOT_FOUND) {
zxlogf(INFO, "No init metadata provided");
} else {
zxlogf(ERROR, "Failed to decode metadata: %s", decoded.status_string());
}
return;
}
auto device = std::make_unique<ClockInitDevice>(parent);
if (device->ConfigureClocks(*decoded.value(), clock) != ZX_OK) {
// Return without adding the init device if some clocks could not be configured. This will
// prevent all drivers that depend on the initial state from binding, which should make it more
// obvious that something has gone wrong.
return;
}
zx_device_prop_t props[] = {
{BIND_INIT_STEP, 0, bind_fuchsia_clock::BIND_INIT_STEP_CLOCK},
};
zx_status_t status = device->DdkAdd(ddk::DeviceAddArgs("clock-init")
.set_flags(DEVICE_ADD_ALLOW_MULTI_COMPOSITE)
.set_props(props));
if (status == ZX_OK) {
[[maybe_unused]] auto _ = device.release();
} else {
zxlogf(ERROR, "Failed to add clock-init: %s", zx_status_get_string(status));
}
}
zx_status_t ClockInitDevice::ConfigureClocks(
const fuchsia_hardware_clockimpl::wire::InitMetadata& metadata, const ClockImplProxy& clock) {
// Stop processing the list if any call returns an error so that clocks are not accidentally
// enabled in an unknown state.
for (const auto& step : metadata.steps) {
if (step.call.is_enable()) {
if (zx_status_t status = clock.Enable(step.id); status != ZX_OK) {
zxlogf(ERROR, "Enable() failed for %u: %s", step.id, zx_status_get_string(status));
return status;
}
} else if (step.call.is_disable()) {
if (zx_status_t status = clock.Disable(step.id); status != ZX_OK) {
zxlogf(ERROR, "Disable() failed for %u: %s", step.id, zx_status_get_string(status));
return status;
}
} else if (step.call.is_rate_hz()) {
if (zx_status_t status = clock.SetRate(step.id, step.call.rate_hz()); status != ZX_OK) {
zxlogf(ERROR, "SetRate(%lu) failed for %u: %s", step.call.rate_hz(), step.id,
zx_status_get_string(status));
return status;
}
} else if (step.call.is_input_idx()) {
if (zx_status_t status = clock.SetInput(step.id, step.call.input_idx()); status != ZX_OK) {
zxlogf(ERROR, "SetInput(%u) failed for %u: %s", step.call.input_idx(), step.id,
zx_status_get_string(status));
return status;
}
} else if (step.call.is_delay()) {
zx::nanosleep(zx::deadline_after(zx::duration(step.call.delay())));
}
}
return ZX_OK;
}
zx_status_t ClockImplProxy::Enable(uint32_t id) const {
if (clock_banjo_.is_valid()) {
return clock_banjo_.Enable(id);
}
fdf::Arena arena('CLK_');
const auto result = clock_fidl_.buffer(arena)->Enable(id);
if (!result.ok()) {
return result.status();
}
return result->is_error() ? result->error_value() : ZX_OK;
}
zx_status_t ClockImplProxy::Disable(uint32_t id) const {
if (clock_banjo_.is_valid()) {
return clock_banjo_.Disable(id);
}
fdf::Arena arena('CLK_');
const auto result = clock_fidl_.buffer(arena)->Disable(id);
if (!result.ok()) {
return result.status();
}
return result->is_error() ? result->error_value() : ZX_OK;
}
zx_status_t ClockImplProxy::IsEnabled(uint32_t id, bool* out_enabled) const {
if (clock_banjo_.is_valid()) {
return clock_banjo_.IsEnabled(id, out_enabled);
}
fdf::Arena arena('CLK_');
const auto result = clock_fidl_.buffer(arena)->IsEnabled(id);
if (!result.ok()) {
return result.status();
}
if (result->is_error()) {
return result->error_value();
}
*out_enabled = result->value()->enabled;
return ZX_OK;
}
zx_status_t ClockImplProxy::SetRate(uint32_t id, uint64_t hz) const {
if (clock_banjo_.is_valid()) {
return clock_banjo_.SetRate(id, hz);
}
fdf::Arena arena('CLK_');
const auto result = clock_fidl_.buffer(arena)->SetRate(id, hz);
if (!result.ok()) {
return result.status();
}
return result->is_error() ? result->error_value() : ZX_OK;
}
zx_status_t ClockImplProxy::QuerySupportedRate(uint32_t id, uint64_t hz, uint64_t* out_hz) const {
if (clock_banjo_.is_valid()) {
return clock_banjo_.QuerySupportedRate(id, hz, out_hz);
}
fdf::Arena arena('CLK_');
const auto result = clock_fidl_.buffer(arena)->QuerySupportedRate(id, hz);
if (!result.ok()) {
return result.status();
}
if (result->is_error()) {
return result->error_value();
}
*out_hz = result->value()->hz;
return ZX_OK;
}
zx_status_t ClockImplProxy::GetRate(uint32_t id, uint64_t* out_hz) const {
if (clock_banjo_.is_valid()) {
return clock_banjo_.GetRate(id, out_hz);
}
fdf::Arena arena('CLK_');
const auto result = clock_fidl_.buffer(arena)->GetRate(id);
if (!result.ok()) {
return result.status();
}
if (result->is_error()) {
return result->error_value();
}
*out_hz = result->value()->hz;
return ZX_OK;
}
zx_status_t ClockImplProxy::SetInput(uint32_t id, uint32_t idx) const {
if (clock_banjo_.is_valid()) {
return clock_banjo_.SetInput(id, idx);
}
fdf::Arena arena('CLK_');
const auto result = clock_fidl_.buffer(arena)->SetInput(id, idx);
if (!result.ok()) {
return result.status();
}
return result->is_error() ? result->error_value() : ZX_OK;
}
zx_status_t ClockImplProxy::GetNumInputs(uint32_t id, uint32_t* out_n) const {
if (clock_banjo_.is_valid()) {
return clock_banjo_.GetNumInputs(id, out_n);
}
fdf::Arena arena('CLK_');
const auto result = clock_fidl_.buffer(arena)->GetNumInputs(id);
if (!result.ok()) {
return result.status();
}
if (result->is_error()) {
return result->error_value();
}
*out_n = result->value()->n;
return ZX_OK;
}
zx_status_t ClockImplProxy::GetInput(uint32_t id, uint32_t* out_index) const {
if (clock_banjo_.is_valid()) {
return clock_banjo_.GetInput(id, out_index);
}
fdf::Arena arena('CLK_');
const auto result = clock_fidl_.buffer(arena)->GetInput(id);
if (!result.ok()) {
return result.status();
}
if (result->is_error()) {
return result->error_value();
}
*out_index = result->value()->index;
return ZX_OK;
}
namespace {
constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = ClockDevice::Create;
return ops;
}();
} // namespace
ZIRCON_DRIVER(clock, driver_ops, "zircon", "0.1");