blob: fb7bc595448cd303d49a8c2204b25a6a0bcb1ef0 [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 "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/compat/cpp/device_server.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <fbl/alloc_checker.h>
namespace buttons {
namespace {
struct MetadataConfig {
fbl::Array<buttons_button_config_t> buttons;
fbl::Array<buttons_gpio_config_t> gpios;
};
zx::result<MetadataConfig> ParseMetadata(
const fidl::VectorView<::fuchsia_driver_compat::wire::Metadata>& metadata) {
MetadataConfig metadata_config;
fbl::AllocChecker ac;
for (const auto& m : metadata) {
if (m.type == DEVICE_METADATA_BUTTONS_BUTTONS) {
size_t size;
auto status = m.data.get_prop_content_size(&size);
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to get_prop_content_size: %s", zx_status_get_string(status));
return zx::error(status);
}
if (size % sizeof(buttons_button_config_t)) {
FDF_LOG(ERROR, "Bad button metadata size: %zu", size);
return zx::error(ZX_ERR_BAD_STATE);
}
size_t n_buttons = size / sizeof(buttons_button_config_t);
auto buttons = fbl::MakeArray<buttons_button_config_t>(&ac, n_buttons);
if (!ac.check()) {
return zx::error(ZX_ERR_NO_MEMORY);
}
status = m.data.read(&buttons[0], 0, size);
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to read button metadata: %s", zx_status_get_string(status));
return zx::error(status);
}
metadata_config.buttons = std::move(buttons);
} else if (m.type == DEVICE_METADATA_BUTTONS_GPIOS) {
size_t size;
auto status = m.data.get_prop_content_size(&size);
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to get_prop_content_size: %s", zx_status_get_string(status));
return zx::error(status);
}
if (size % sizeof(buttons_gpio_config_t)) {
FDF_LOG(ERROR, "Bad GPIO metadata size: %zu", size);
return zx::error(ZX_ERR_BAD_STATE);
}
size_t n_gpios = size / sizeof(buttons_gpio_config_t);
auto gpios = fbl::MakeArray<buttons_gpio_config_t>(&ac, n_gpios);
if (!ac.check()) {
return zx::error(ZX_ERR_NO_MEMORY);
}
status = m.data.read(&gpios[0], 0, size);
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to read GPIO metadata: %s", zx_status_get_string(status));
return zx::error(status);
}
metadata_config.gpios = std::move(gpios);
}
}
if (metadata_config.buttons.empty() || metadata_config.gpios.empty()) {
FDF_LOG(ERROR, "Failed to get metadata, %zu buttons and %zu GPIO found",
metadata_config.buttons.size(), metadata_config.gpios.size());
return zx::error(ZX_ERR_INVALID_ARGS);
}
return zx::ok(std::move(metadata_config));
}
} // namespace
zx::result<> Buttons::Start() {
// Get metadata.
zx::result compat_result = incoming()->Connect<fuchsia_driver_compat::Service::Device>();
if (compat_result.is_error()) {
FDF_LOG(ERROR, "Failed to open compat service: %s", compat_result.status_string());
return compat_result.take_error();
}
auto compat = fidl::WireSyncClient(std::move(compat_result.value()));
if (!compat.is_valid()) {
FDF_LOG(ERROR, "Failed to get compat acccess");
return zx::error(ZX_ERR_NO_RESOURCES);
}
auto metadata = compat->GetMetadata();
if (!metadata.ok()) {
FDF_LOG(ERROR, "Failed to send GetMetadata request: %s", metadata.FormatDescription().data());
return zx::error(metadata.status());
}
if (metadata->is_error()) {
FDF_LOG(ERROR, "Failed to GetMetadata: %s", zx_status_get_string(metadata->error_value()));
return metadata->take_error();
}
auto metadata_config = ParseMetadata(metadata.value()->metadata);
if (metadata_config.is_error()) {
FDF_LOG(ERROR, "Failed to ParseMetadata: %s",
zx_status_get_string(metadata_config.error_value()));
return metadata_config.take_error();
}
// Prepare gpios array.
fbl::AllocChecker ac;
auto gpios = fbl::Array(new (&ac) ButtonsDevice::Gpio[metadata_config->gpios.size()],
metadata_config->gpios.size());
if (!ac.check()) {
return zx::error(ZX_ERR_NO_MEMORY);
}
for (uint32_t i = 0; i < metadata_config->gpios.size(); ++i) {
const char* name;
switch (metadata_config->buttons[i].id) {
case BUTTONS_ID_VOLUME_UP:
name = "volume-up";
break;
case BUTTONS_ID_VOLUME_DOWN:
name = "volume-down";
break;
case BUTTONS_ID_FDR:
name = "volume-both";
break;
case BUTTONS_ID_MIC_MUTE:
case BUTTONS_ID_MIC_AND_CAM_MUTE:
name = "mic-privacy";
break;
case BUTTONS_ID_CAM_MUTE:
name = "cam-mute";
break;
case BUTTONS_ID_POWER:
name = "power";
break;
case BUTTONS_ID_PLAY_PAUSE:
name = "play-pause";
break;
case BUTTONS_ID_KEY_A:
name = "key-a";
break;
case BUTTONS_ID_KEY_M:
name = "key-m";
break;
case BUTTONS_ID_FUNCTION:
name = "function";
break;
default:
FDF_LOG(ERROR, "Unknown Button id: %d", metadata_config->buttons[i].id);
return zx::error(ZX_ERR_NOT_SUPPORTED);
};
zx::result gpio_client = incoming()->Connect<fuchsia_hardware_gpio::Service::Device>(name);
if (gpio_client.is_error() || !gpio_client->is_valid()) {
FDF_LOG(ERROR, "Connect to GPIO %s failed: %s", name, gpio_client.status_string());
return gpio_client.take_error();
}
gpios[i].client.Bind(std::move(gpio_client.value()));
gpios[i].config = metadata_config->gpios[i];
}
device_ = std::make_unique<buttons::ButtonsDevice>(
dispatcher(), std::move(metadata_config->buttons), std::move(gpios));
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 input report 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 Buttons::PrepareStop(fdf::PrepareStopCompleter completer) {
device_->ShutDown();
completer(zx::ok());
}
zx::result<> Buttons::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 node controller 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 node 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 buttons
FUCHSIA_DRIVER_EXPORT(buttons::Buttons);