blob: d9b2b773f56fbf61e865cb1bb3378cdf99c5e3d3 [file] [log] [blame]
// Copyright 2023 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 "adc-buttons.h"
#include <fidl/fuchsia.driver.compat/cpp/wire.h>
#include <fidl/fuchsia.hardware.platform.device/cpp/driver/fidl.h>
#include <lib/ddk/metadata.h>
#include <lib/device-protocol/pdev-fidl.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <lib/mmio/mmio.h>
namespace adc_buttons {
namespace {
constexpr uint32_t kDefaultPollingRateUsec = 1'000;
struct MetadataValues {
uint32_t polling_rate_usec = kDefaultPollingRateUsec;
std::map<uint32_t, std::vector<fuchsia_buttons::Button>> configs;
std::set<fuchsia_input_report::ConsumerControlButton> buttons;
};
zx::result<MetadataValues> ParseMetadata(
const fidl::VectorView<::fuchsia_driver_compat::wire::Metadata>& metadata) {
MetadataValues ret;
for (const auto& m : metadata) {
if (m.type == DEVICE_METADATA_BUTTONS) {
size_t size;
auto status = m.data.get_prop_content_size(&size);
if (status != ZX_OK) {
FDF_LOG(ERROR, "%s: failed to get_prop_content_size %s", __func__,
zx_status_get_string(status));
continue;
}
std::vector<uint8_t> data;
data.resize(size);
status = m.data.read(data.data(), 0, size);
if (status != ZX_OK) {
FDF_LOG(ERROR, "%s: failed to read %s", __func__, zx_status_get_string(status));
continue;
}
auto result = fidl::Unpersist<fuchsia_buttons::Metadata>(cpp20::span(data));
if (result.is_error()) {
FDF_LOG(ERROR, "%s: failed to unpersist metadata %s", __func__,
result.error_value().FormatDescription().data());
continue;
}
ret.polling_rate_usec = *result->polling_rate_usec();
for (auto& button : *result->buttons()) {
if (button.button_config()->Which() != fuchsia_buttons::ButtonConfig::Tag::kAdc) {
FDF_LOG(WARNING, "%s: received button config not of type adc, ignoring.", __func__);
continue;
}
ret.buttons.insert(button.types()->begin(), button.types()->end());
ret.configs[*button.button_config()->adc()->channel_idx()].emplace_back(std::move(button));
}
if (ret.buttons.size() >= fuchsia_input_report::kConsumerControlMaxNumButtons) {
FDF_LOG(
ERROR,
"%s: More buttons than expected (max = %d). Please increase kConsumerControlMaxNumButtons",
__func__, fuchsia_input_report::kConsumerControlMaxNumButtons);
return zx::error(ZX_ERR_INTERNAL);
}
break;
}
}
if (ret.configs.empty() || ret.buttons.empty()) {
FDF_LOG(ERROR, "%s: failed to get button configs in metadata.", __func__);
return zx::error(ZX_ERR_INTERNAL);
}
return zx::ok(std::move(ret));
}
} // namespace
zx::result<> AdcButtons::Start() {
// Get metadata.
MetadataValues values;
{
zx::result result = incoming()->Connect<fuchsia_driver_compat::Service::Device>();
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to open pdev service: %s", result.status_string());
return result.take_error();
}
auto compat = fidl::WireSyncClient(std::move(result.value()));
if (!compat.is_valid()) {
FDF_LOG(ERROR, "%s: failed to get compat", __func__);
return zx::error(ZX_ERR_NO_RESOURCES);
}
auto metadata = compat->GetMetadata();
if (!metadata.ok()) {
FDF_LOG(ERROR, "%s: failed to GetMetadata %s", __func__, metadata.FormatDescription().data());
return zx::error(metadata.status());
}
if (metadata->is_error()) {
FDF_LOG(ERROR, "%s: failed to GetMetadata %s", __func__,
zx_status_get_string(metadata->error_value()));
return metadata->take_error();
}
auto vals = ParseMetadata(metadata.value()->metadata);
if (vals.is_error()) {
FDF_LOG(ERROR, "%s: failed to ParseMetadata %s", __func__,
zx_status_get_string(vals.error_value()));
return vals.take_error();
}
values = std::move(vals.value());
}
// Get adc.
std::vector<adc_buttons_device::AdcButtonsDevice::AdcButtonClient> clients;
for (auto& [idx, buttons] : values.configs) {
char adc_name[32];
sprintf(adc_name, "adc-%u", idx);
zx::result result = incoming()->Connect<fuchsia_hardware_adc::Service::Device>(adc_name);
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to open adc service: %s", result.status_string());
return result.take_error();
}
clients.emplace_back(std::move(result.value()), std::move(buttons));
}
device_ = std::make_unique<adc_buttons_device::AdcButtonsDevice>(
dispatcher(), std::move(clients), values.polling_rate_usec, std::move(values.buttons));
auto result = outgoing()->component().AddUnmanagedProtocol<fuchsia_input_report::InputDevice>(
input_report_bindings_.CreateHandler(device_.get(), dispatcher(),
fidl::kIgnoreBindingClosure),
kDeviceName);
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to add Device service %s", result.status_string());
return result.take_error();
}
if (zx::result result = CreateDevfsNode(); result.is_error()) {
FDF_LOG(ERROR, "Failed to export to devfs %s", result.status_string());
return result.take_error();
}
return zx::ok();
}
void AdcButtons::Stop() { device_->Shutdown(); }
zx::result<> AdcButtons::CreateDevfsNode() {
fidl::Arena arena;
zx::result connector = devfs_connector_.Bind(dispatcher());
if (connector.is_error()) {
return connector.take_error();
}
auto devfs = fuchsia_driver_framework::wire::DevfsAddArgs::Builder(arena)
.connector(std::move(connector.value()))
.class_name("input-report");
auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
.name(arena, kDeviceName)
.devfs_args(devfs.Build())
.Build();
zx::result controller_endpoints =
fidl::CreateEndpoints<fuchsia_driver_framework::NodeController>();
ZX_ASSERT_MSG(controller_endpoints.is_ok(), "Failed to create endpoints: %s",
controller_endpoints.status_string());
zx::result node_endpoints = fidl::CreateEndpoints<fuchsia_driver_framework::Node>();
ZX_ASSERT_MSG(node_endpoints.is_ok(), "Failed to create endpoints: %s",
node_endpoints.status_string());
fidl::WireResult result = fidl::WireCall(node())->AddChild(
args, std::move(controller_endpoints->server), std::move(node_endpoints->server));
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to add child %s", result.status_string());
return zx::error(result.status());
}
controller_.Bind(std::move(controller_endpoints->client));
node_.Bind(std::move(node_endpoints->client));
return zx::ok();
}
} // namespace adc_buttons
FUCHSIA_DRIVER_EXPORT(adc_buttons::AdcButtons);