blob: dc0303babe7de0caf4a66e0136583a35ebb9a9ca [file] [log] [blame]
// Copyright 2024 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-device.h"
#include <lib/driver/logging/cpp/structured_logger.h>
#include <lib/zx/clock.h>
#include <fbl/alloc_checker.h>
namespace buttons {
void ButtonsDevice::ButtonsInputReport::ToFidlInputReport(
fidl::WireTableBuilder<::fuchsia_input_report::wire::InputReport>& input_report,
fidl::AnyArena& allocator) {
fidl::VectorView<fuchsia_input_report::wire::ConsumerControlButton> buttons_rpt(
allocator, fuchsia_input_report::wire::kConsumerControlMaxNumButtons);
size_t count = 0;
bool mic_mute = false;
bool cam_mute = false;
for (uint32_t id = 0; id < buttons.size(); id++) {
if (!buttons[id]) {
continue;
}
switch (id) {
case BUTTONS_ID_POWER:
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kPower;
count++;
break;
case BUTTONS_ID_VOLUME_UP:
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kVolumeUp;
count++;
break;
case BUTTONS_ID_VOLUME_DOWN:
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kVolumeDown;
count++;
break;
case BUTTONS_ID_FDR:
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kFactoryReset;
count++;
break;
case BUTTONS_ID_MIC_MUTE:
if (!mic_mute) {
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kMicMute;
count++;
mic_mute = true;
}
break;
case BUTTONS_ID_CAM_MUTE:
if (!cam_mute) {
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kCameraDisable;
count++;
cam_mute = true;
}
break;
case BUTTONS_ID_MIC_AND_CAM_MUTE:
if (!mic_mute) {
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kMicMute;
count++;
mic_mute = true;
}
if (!cam_mute) {
buttons_rpt[count] = fuchsia_input_report::ConsumerControlButton::kCameraDisable;
count++;
cam_mute = true;
}
break;
default:
FDF_LOG(ERROR, "Invalid Button ID encountered %d", id);
}
}
buttons_rpt.set_count(count);
auto consumer_control =
fuchsia_input_report::wire::ConsumerControlInputReport::Builder(allocator).pressed_buttons(
buttons_rpt);
input_report.event_time(event_time.get()).consumer_control(consumer_control.Build());
}
void ButtonsDevice::Notify(size_t button_index) {
auto result = GetInputReportInternal();
if (result.is_error()) {
FDF_LOG(ERROR, "GetInputReport failed %s", zx_status_get_string(result.error_value()));
} else if (!last_report_.has_value() || *last_report_ != result.value()) {
last_report_ = result.value();
readers_.SendReportToAllReaders(*last_report_);
if (debounce_states_[button_index].timestamp != zx::time::infinite_past()) {
const zx::duration latency =
zx::clock::get_monotonic() - debounce_states_[button_index].timestamp;
total_latency_ += latency;
report_count_++;
average_latency_usecs_.Set(total_latency_.to_usecs() / report_count_);
if (latency > max_latency_) {
max_latency_ = latency;
max_latency_usecs_.Set(max_latency_.to_usecs());
}
}
if (!last_report_->empty()) {
total_report_count_.Add(1);
last_event_timestamp_.Set(last_report_->event_time.get());
}
}
if (buttons_[button_index].id == BUTTONS_ID_FDR) {
FDF_LOG(INFO, "FDR (up and down buttons) pressed");
}
debounce_states_[button_index].enqueued = false;
debounce_states_[button_index].timestamp = zx::time::infinite_past();
}
int ButtonsDevice::Thread() {
thread_started_.Signal();
if (poll_period_ != zx::duration::infinite()) {
poll_timer_.set(zx::deadline_after(poll_period_), zx::duration(0));
poll_timer_.wait_async(port_, kPortKeyPollTimer, ZX_TIMER_SIGNALED, 0);
}
while (1) {
zx_port_packet_t packet;
zx_status_t status = port_.wait(zx::time::infinite(), &packet);
FDF_LOG(DEBUG, "msg received on port key %lu", packet.key);
if (status != ZX_OK) {
FDF_LOG(ERROR, "port wait failed %d", status);
return thrd_error;
}
if (packet.key == kPortKeyShutDown) {
FDF_LOG(INFO, "shutting down");
return thrd_success;
}
if (packet.key >= kPortKeyInterruptStart &&
packet.key < (kPortKeyInterruptStart + buttons_.size())) {
uint32_t type = static_cast<uint32_t>(packet.key - kPortKeyInterruptStart);
if (gpios_[type].config.type == BUTTONS_GPIO_TYPE_INTERRUPT) {
// We need to reconfigure the GPIO to catch the opposite polarity.
auto reconfig_result = ReconfigurePolarity(type, packet.key);
if (!reconfig_result.is_ok()) {
return reconfig_result.error_value();
}
debounce_states_[type].value = *reconfig_result;
// Notify
debounce_states_[type].timer.set(zx::deadline_after(zx::duration(kDebounceThresholdNs)),
zx::duration(0));
if (!debounce_states_[type].enqueued) {
debounce_states_[type].timer.wait_async(port_, kPortKeyTimerStart + type,
ZX_TIMER_SIGNALED, 0);
debounce_states_[type].timestamp = zx::time(packet.interrupt.timestamp);
}
debounce_states_[type].enqueued = true;
}
gpios_[type].irq.ack();
}
if (packet.key >= kPortKeyTimerStart && packet.key < (kPortKeyTimerStart + buttons_.size())) {
Notify(packet.key - kPortKeyTimerStart);
}
if (packet.key == kPortKeyPollTimer) {
for (size_t i = 0; i < gpios_.size(); i++) {
if (gpios_[i].config.type != BUTTONS_GPIO_TYPE_POLL) {
continue;
}
fidl::WireResult read_result = gpios_[i].client->Read();
if (!read_result.ok()) {
FDF_LOG(ERROR, "Failed to send Read request to gpio %lu: %s", i,
read_result.status_string());
return read_result.status();
}
if (read_result->is_error()) {
FDF_LOG(ERROR, "Failed to read gpio %lu: %s", i,
zx_status_get_string(read_result->error_value()));
return read_result->error_value();
}
if (!!read_result.value()->value != debounce_states_[i].value) {
Notify(i);
}
debounce_states_[i].value = read_result.value()->value;
}
poll_timer_.set(zx::deadline_after(poll_period_), zx::duration(0));
poll_timer_.wait_async(port_, kPortKeyPollTimer, ZX_TIMER_SIGNALED, 0);
}
}
return thrd_success;
}
void ButtonsDevice::GetInputReportsReader(GetInputReportsReaderRequestView request,
GetInputReportsReaderCompleter::Sync& completer) {
auto initial_report = GetInputReportInternal();
if (initial_report.is_error()) {
FDF_LOG(ERROR, "Failed to get initial report %d", initial_report.error_value());
}
auto status = readers_.CreateReader(
dispatcher_, std::move(request->reader),
initial_report.is_ok() ? std::make_optional(initial_report.value()) : std::nullopt);
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to create a reader %d", status);
}
}
void ButtonsDevice::GetDescriptor(GetDescriptorCompleter::Sync& completer) {
fidl::Arena<kFeatureAndDescriptorBufferSize> arena;
auto device_info = fuchsia_input_report::wire::DeviceInformation::Builder(arena);
device_info.vendor_id(static_cast<uint32_t>(fuchsia_input_report::VendorId::kGoogle));
// Product id is "HID" buttons only for backward compatibility with users of this driver.
// There is no HID support in this driver anymore.
device_info.product_id(
static_cast<uint32_t>(fuchsia_input_report::VendorGoogleProductId::kHidButtons));
const std::vector<fuchsia_input_report::ConsumerControlButton> buttons = {
fuchsia_input_report::ConsumerControlButton::kVolumeUp,
fuchsia_input_report::ConsumerControlButton::kVolumeDown,
fuchsia_input_report::ConsumerControlButton::kFactoryReset,
fuchsia_input_report::ConsumerControlButton::kCameraDisable,
fuchsia_input_report::ConsumerControlButton::kMicMute,
fuchsia_input_report::ConsumerControlButton::kPower};
const auto input = fuchsia_input_report::wire::ConsumerControlInputDescriptor::Builder(arena)
.buttons(buttons)
.Build();
const auto consumer_control =
fuchsia_input_report::wire::ConsumerControlDescriptor::Builder(arena).input(input).Build();
completer.Reply(fuchsia_input_report::wire::DeviceDescriptor::Builder(arena)
.device_information(device_info.Build())
.consumer_control(consumer_control)
.Build());
}
// Requires interrupts to be disabled for all rows/cols.
zx::result<bool> ButtonsDevice::MatrixScan(uint32_t row, uint32_t col, zx_duration_t delay) {
auto& gpio_col = gpios_[col];
{
fidl::WireResult result = gpio_col.client->ConfigIn(
fuchsia_hardware_gpio::GpioFlags::kNoPull); // Float column to find row in use.
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send ConfigIn request to gpio %u: %s", col, result.status_string());
return zx::error(result.status());
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to configuire gpio %u to input: %s", col,
zx_status_get_string(result->error_value()));
return zx::error(result->error_value());
}
}
zx::nanosleep(zx::deadline_after(zx::duration(delay)));
fidl::WireResult read_result = gpios_[row].client->Read();
if (!read_result.ok()) {
FDF_LOG(ERROR, "Failed to send Read request to gpio %u: %s", row, read_result.status_string());
return zx::error(read_result.status());
}
if (read_result->is_error()) {
FDF_LOG(ERROR, "Failed to read gpio %u: %s", row,
zx_status_get_string(read_result->error_value()));
return zx::error(read_result->error_value());
}
{
fidl::WireResult result = gpio_col.client->ConfigOut(gpio_col.config.matrix.output_value);
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send ConfigOut request to gpio %u: %s", col,
result.status_string());
return zx::error(read_result.status());
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to configuire gpio %u to output: %s", col,
zx_status_get_string(result->error_value()));
return zx::error(result->error_value());
}
}
FDF_LOG(DEBUG, "row %u col %u val %u", row, col, read_result.value()->value);
return zx::ok(static_cast<bool>(read_result.value()->value));
}
zx::result<ButtonsDevice::ButtonsInputReport> ButtonsDevice::GetInputReportInternal() {
ButtonsInputReport input_rpt;
for (size_t i = 0; i < buttons_.size(); ++i) {
bool new_value = false; // A value true means a button is pressed.
if (buttons_[i].type == BUTTONS_TYPE_MATRIX) {
auto scan_result =
MatrixScan(buttons_[i].gpioA_idx, buttons_[i].gpioB_idx, buttons_[i].gpio_delay);
if (!scan_result.is_ok()) {
return zx::error(scan_result.error_value());
}
new_value = *scan_result;
} else if (buttons_[i].type == BUTTONS_TYPE_DIRECT) {
auto gpio_index = buttons_[i].gpioA_idx;
fidl::WireResult read_result = gpios_[gpio_index].client->Read();
if (!read_result.ok()) {
FDF_LOG(ERROR, "Failed to send Read request to gpio %u: %s", gpio_index,
read_result.status_string());
return zx::error(read_result.status());
}
if (read_result->is_error()) {
FDF_LOG(ERROR, "Failed to read gpio %u: %s", gpio_index,
zx_status_get_string(read_result->error_value()));
return zx::error(read_result->error_value());
}
new_value = read_result.value()->value;
FDF_LOG(DEBUG, "GPIO direct read %u for button %lu", new_value, i);
} else {
FDF_LOG(ERROR, "unknown button type %u", buttons_[i].type);
return zx::error(ZX_ERR_INTERNAL);
}
if (gpios_[i].config.flags & BUTTONS_GPIO_FLAG_INVERTED) {
new_value = !new_value;
}
FDF_LOG(DEBUG, "GPIO new value %u for button %lu", new_value, i);
input_rpt.set(buttons_[i].id, new_value);
}
input_rpt.event_time = zx::clock::get_monotonic();
return zx::ok(input_rpt);
}
void ButtonsDevice::GetInputReport(GetInputReportRequestView request,
GetInputReportCompleter::Sync& completer) {
if (request->device_type != fuchsia_input_report::DeviceType::kConsumerControl) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
return;
}
auto result = GetInputReportInternal();
if (!result.is_ok()) {
completer.ReplyError(result.error_value());
return;
}
fidl::Arena<> arena;
auto input_report = fuchsia_input_report::wire::InputReport::Builder(arena);
result->ToFidlInputReport(input_report, arena);
completer.ReplySuccess(input_report.Build());
}
zx::result<uint8_t> ButtonsDevice::ReconfigurePolarity(uint32_t idx, uint64_t int_port) {
FDF_LOG(DEBUG, "gpio %u port %lu", idx, int_port);
uint8_t current = 0, old;
auto& gpio = gpios_[idx];
fidl::WireResult read_result1 = gpio.client->Read();
if (!read_result1.ok()) {
FDF_LOG(ERROR, "Failed to send Read request to gpio %u: %s", idx, read_result1.status_string());
return zx::error(read_result1.status());
}
if (read_result1->is_error()) {
FDF_LOG(ERROR, "Failed to read gpio %u: %s", idx,
zx_status_get_string(read_result1->error_value()));
return zx::error(read_result1->error_value());
}
current = read_result1.value()->value;
do {
{
fidl::WireResult result =
gpio.client->SetPolarity(current ? fuchsia_hardware_gpio::GpioPolarity::kLow
: fuchsia_hardware_gpio::GpioPolarity::kHigh);
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send SetPolarity request to gpio %u: %s", idx,
result.status_string());
return zx::error(result.status());
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to set polarity of gpio %u: %s", idx,
zx_status_get_string(result->error_value()));
return zx::error(result->error_value());
}
}
old = current;
fidl::WireResult read_result2 = gpio.client->Read();
if (!read_result2.ok()) {
FDF_LOG(ERROR, "Failed to send Read request to gpio %u: %s", idx,
read_result2.status_string());
return zx::error(read_result2.status());
}
if (read_result2->is_error()) {
FDF_LOG(ERROR, "Failed to read gpio %u: %s", idx,
zx_status_get_string(read_result2->error_value()));
return zx::error(read_result2->error_value());
}
current = read_result2.value()->value;
FDF_LOG(TRACE, "%u old gpio %u new gpio %u", idx, old, current);
// If current switches after setup, we setup a new trigger for it (opposite edge).
} while (current != old);
return zx::ok(current);
}
zx_status_t ButtonsDevice::ConfigureInterrupt(uint32_t idx, uint64_t int_port) {
FDF_LOG(DEBUG, "gpio %u port %lu", idx, int_port);
zx_status_t status;
uint8_t current = 0;
auto& gpio = gpios_[idx];
fidl::WireResult read_result = gpio.client->Read();
if (!read_result.ok()) {
FDF_LOG(ERROR, "Failed to send Read request to gpio %u: %s", idx, read_result.status_string());
return read_result.status();
}
if (read_result->is_error()) {
FDF_LOG(ERROR, "Failed to read gpio %u: %s", idx,
zx_status_get_string(read_result->error_value()));
return read_result->error_value();
}
current = read_result.value()->value;
{
fidl::WireResult result = gpio.client->ReleaseInterrupt();
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send ReleaseInterrupt request to gpio %u: %s", idx,
result.status_string());
return result.status();
}
if (result->is_error() && result->error_value() != ZX_ERR_NOT_FOUND) {
FDF_LOG(ERROR, "Failed to release interrupt for gpio %u: %s", idx,
zx_status_get_string(result->error_value()));
return result->error_value();
}
}
// We setup a trigger for the opposite of the current GPIO value.
uint32_t flags =
(current ? ZX_INTERRUPT_MODE_EDGE_LOW : ZX_INTERRUPT_MODE_EDGE_HIGH) |
((gpio.config.flags & BUTTONS_GPIO_FLAG_WAKE_VECTOR) ? ZX_INTERRUPT_WAKE_VECTOR : 0);
fidl::WireResult interrupt_result = gpio.client->GetInterrupt(flags);
if (!interrupt_result.ok()) {
FDF_LOG(ERROR, "Failed to send GetInterrupt request to gpio %u: %s", idx,
interrupt_result.status_string());
return interrupt_result.status();
}
if (interrupt_result->is_error()) {
FDF_LOG(ERROR, "Failed to get interrupt for gpio %u: %s", idx,
zx_status_get_string(interrupt_result->error_value()));
return interrupt_result->error_value();
}
gpio.irq = std::move(interrupt_result.value()->irq);
status = gpios_[idx].irq.bind(port_, int_port, 0);
if (status != ZX_OK) {
FDF_LOG(ERROR, "zx_interrupt_bind failed %d", status);
return status;
}
// To make sure polarity is correct in case it changed during configuration.
auto reconfig_result = ReconfigurePolarity(idx, int_port);
if (!reconfig_result.is_ok()) {
return reconfig_result.error_value();
}
return ZX_OK;
}
ButtonsDevice::ButtonsDevice(async_dispatcher_t* dispatcher,
fbl::Array<buttons_button_config_t> buttons, fbl::Array<Gpio> gpios)
: dispatcher_(dispatcher), buttons_(std::move(buttons)), gpios_(std::move(gpios)) {
ZX_ASSERT(Init() == ZX_OK);
}
zx_status_t ButtonsDevice::Init() {
zx_status_t status;
fbl::AllocChecker ac;
metrics_root_ = inspector_.GetRoot().CreateChild("hid-input-report-touch");
average_latency_usecs_ = metrics_root_.CreateUint("average_latency_usecs", 0);
max_latency_usecs_ = metrics_root_.CreateUint("max_latency_usecs", 0);
total_report_count_ = metrics_root_.CreateUint("total_report_count", 0);
last_event_timestamp_ = metrics_root_.CreateUint("last_event_timestamp", 0);
status = zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port_);
if (status != ZX_OK) {
FDF_LOG(ERROR, "port_create failed %d", status);
return status;
}
debounce_states_ = fbl::Array(new (&ac) debounce_state[buttons_.size()], buttons_.size());
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
for (auto& i : debounce_states_) {
i.enqueued = false;
zx::timer::create(0, ZX_CLOCK_MONOTONIC, &(i.timer));
i.value = false;
}
zx::timer::create(0, ZX_CLOCK_MONOTONIC, &poll_timer_);
// Check the metadata.
for (auto& button : buttons_) {
if (button.gpioA_idx >= gpios_.size()) {
FDF_LOG(ERROR, "invalid gpioA_idx %u", button.gpioA_idx);
return ZX_ERR_INTERNAL;
}
if (button.gpioB_idx >= gpios_.size()) {
FDF_LOG(ERROR, "invalid gpioB_idx %u", button.gpioB_idx);
return ZX_ERR_INTERNAL;
}
if (gpios_[button.gpioA_idx].config.type != BUTTONS_GPIO_TYPE_INTERRUPT &&
gpios_[button.gpioA_idx].config.type != BUTTONS_GPIO_TYPE_POLL) {
FDF_LOG(ERROR, "invalid gpioA type %u", gpios_[button.gpioA_idx].config.type);
return ZX_ERR_INTERNAL;
}
if (button.type == BUTTONS_TYPE_MATRIX &&
gpios_[button.gpioB_idx].config.type != BUTTONS_GPIO_TYPE_MATRIX_OUTPUT) {
FDF_LOG(ERROR, "invalid matrix gpioB type %u", gpios_[button.gpioB_idx].config.type);
return ZX_ERR_INTERNAL;
}
if (button.id == BUTTONS_ID_FDR) {
FDF_LOG(INFO, "FDR (up and down buttons) setup to GPIO %u", button.gpioA_idx);
}
if (gpios_[button.gpioA_idx].config.type == BUTTONS_GPIO_TYPE_POLL) {
const auto button_poll_period = zx::duration(gpios_[button.gpioA_idx].config.poll.period);
if (poll_period_ == zx::duration::infinite()) {
poll_period_ = button_poll_period;
}
if (button_poll_period != poll_period_) {
FDF_LOG(ERROR, "GPIOs must have the same poll period");
return ZX_ERR_INTERNAL;
}
}
}
// Setup.
for (uint32_t i = 0; i < gpios_.size(); ++i) {
auto& gpio = gpios_[i];
{
fidl::WireResult result = gpio.client->SetAltFunction(0); // 0 means function GPIO.
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send SetAltFunction request to gpio %u: %s", i,
result.status_string());
return result.status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to set alt function for gpio %u: %s", i,
zx_status_get_string(result->error_value()));
return ZX_ERR_NOT_SUPPORTED;
}
}
if (gpio.config.type == BUTTONS_GPIO_TYPE_MATRIX_OUTPUT) {
fidl::WireResult result = gpio.client->ConfigOut(gpio.config.matrix.output_value);
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send ConfigOut request to gpio %u: %s", i,
result.status_string());
return result.status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to configure gpio %u to output: %s", i,
zx_status_get_string(result->error_value()));
return ZX_ERR_NOT_SUPPORTED;
}
} else if (gpio.config.type == BUTTONS_GPIO_TYPE_INTERRUPT) {
fidl::WireResult result = gpio.client->ConfigIn(
static_cast<fuchsia_hardware_gpio::GpioFlags>(gpio.config.interrupt.internal_pull));
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send ConfigIn request to gpio %u: %s", i, result.status_string());
return result.status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to configure gpio %u to input: %s", i,
zx_status_get_string(result->error_value()));
return ZX_ERR_NOT_SUPPORTED;
}
status = ConfigureInterrupt(i, kPortKeyInterruptStart + i);
if (status != ZX_OK) {
return status;
}
} else if (gpio.config.type == BUTTONS_GPIO_TYPE_POLL) {
fidl::WireResult result = gpio.client->ConfigIn(
static_cast<fuchsia_hardware_gpio::GpioFlags>(gpio.config.interrupt.internal_pull));
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send ConfigIn request to gpio %u: %s", i, result.status_string());
return result.status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "Failed to configure gpio %u to input: %s", i,
zx_status_get_string(result->error_value()));
return ZX_ERR_NOT_SUPPORTED;
}
}
}
auto f = [](void* arg) -> int { return reinterpret_cast<ButtonsDevice*>(arg)->Thread(); };
int rc = thrd_create_with_name(&thread_, f, this, "buttons-thread");
if (rc != thrd_success) {
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
void ButtonsDevice::ShutDown() {
zx_port_packet packet = {kPortKeyShutDown, ZX_PKT_TYPE_USER, ZX_OK, {}};
zx_status_t status = port_.queue(&packet);
ZX_ASSERT(status == ZX_OK);
thread_started_.Wait();
thrd_join(thread_, NULL);
for (auto& gpio : gpios_) {
gpio.irq.destroy();
}
}
} // namespace buttons