blob: 75ac02fc0b6956904c33a9a303b2d336c5511569 [file] [log] [blame]
// Copyright 2020 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.
#ifndef SRC_UI_INPUT_LIB_INPUT_REPORT_READER_READER_H_
#define SRC_UI_INPUT_LIB_INPUT_REPORT_READER_READER_H_
#include <fuchsia/input/report/llcpp/fidl.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <list>
#include <ddk/trace/event.h>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#include <fbl/ring_buffer.h>
namespace input {
namespace fuchsia_input_report = ::llcpp::fuchsia::input::report;
template <class Report>
class InputReportReader;
// This class creates and manages the InputReportReaders. It is able to send reports
// to all existing InputReportReaders.
// When this class is destructed, all of the InputReportReaders will be free.
// This class is thread-safe.
// Typical Usage:
// An InputReport Driver should have one InputReportReaderManager member object.
// The Driver should also have some form of InputReport object that can be converted to Fidl.
//
// Eg:
//
// class MyTouchScreenDriver {
// ...
// private:
// struct TouchScreenReport {
// int64_t x;
// int64_t y;
// void ToFidlInputReport(fuchsia_input_report::InputReport::Builder& builder,
// fidl::Allocator& allocator);
// };
//
// InputReportReaderManager<TouchScreenReport> input_report_readers_;
// };
template <class Report>
class InputReportReaderManager {
public:
// Assert that our template type `Report` has the following function:
// void ToFidlInputReport(fuchsia_input_report::InputReport::Builder& builder,
// fidl::Allocator& allocator);
DECLARE_HAS_MEMBER_FN_WITH_SIGNATURE(
has_to_fidl_input_report, ToFidlInputReport,
void (C::*)(fuchsia_input_report::InputReport::Builder& builder, fidl::Allocator& allocator));
static_assert(
has_to_fidl_input_report<Report>::value,
"Report must implement void ToFidlInputReport(fuchsia_input_report::InputReportBuilder& "
"builder, fidl::Allocator&);");
// This class can't be moved because the InputReportReaders are pointing to the main class.
DISALLOW_COPY_ASSIGN_AND_MOVE(InputReportReaderManager);
InputReportReaderManager() = default;
// Create a new InputReportReader that is managed by this InputReportReaderManager.
zx_status_t CreateReader(async_dispatcher_t* dispatcher, zx::channel server) {
fbl::AutoLock lock(&readers_lock_);
auto reader =
InputReportReader<Report>::Create(this, next_reader_id_, dispatcher, std::move(server));
if (!reader) {
return ZX_ERR_INTERNAL;
}
next_reader_id_++;
readers_list_.push_back(std::move(reader));
return ZX_OK;
}
// Send a report to all InputReportReaders.
void SendReportToAllReaders(const Report& report) {
fbl::AutoLock lock(&readers_lock_);
for (auto& reader : readers_list_) {
reader->ReceiveReport(report);
}
}
// Remove a given reader from the list. This is called by the InputReportReader itself
// when it wishes to be removed.
void RemoveReaderFromList(InputReportReader<Report>* reader) {
fbl::AutoLock lock(&readers_lock_);
for (auto iter = readers_list_.begin(); iter != readers_list_.end(); ++iter) {
if (iter->get() == reader) {
readers_list_.erase(iter);
break;
}
}
}
private:
fbl::Mutex readers_lock_;
size_t next_reader_id_ TA_GUARDED(readers_lock_) = 1;
std::list<std::unique_ptr<InputReportReader<Report>>> readers_list_ TA_GUARDED(readers_lock_);
};
// This class represents an InputReportReader that sends InputReports out to a specific client.
// This class is thread safe.
// Typical usage:
// This class shouldn't be touched directly. An InputReport driver should only manipulate
// the InputReportReaderManager.
template <class Report>
class InputReportReader : public fuchsia_input_report::InputReportsReader::Interface {
public:
// Create the InputReportReader. `manager` and `dispatcher` must outlive this InputReportReader.
static std::unique_ptr<InputReportReader<Report>> Create(
InputReportReaderManager<Report>* manager, size_t reader_id, async_dispatcher_t* dispatcher,
zx::channel server);
// This is only public to make std::unique_ptr work.
explicit InputReportReader(InputReportReaderManager<Report>* manager, size_t reader_id)
: reader_id_(reader_id), manager_(manager) {}
void ReceiveReport(const Report& report) TA_EXCL(&report_lock_);
void ReadInputReports(ReadInputReportsCompleter::Sync& completer) TA_EXCL(&report_lock_) override;
private:
static constexpr size_t kInputReportBufferSize = 4096 * 4;
void ReplyWithReports(ReadInputReportsCompleterBase& completer) TA_REQ(&report_lock_);
fbl::Mutex report_lock_;
std::optional<ReadInputReportsCompleter::Async> completer_ TA_GUARDED(&report_lock_);
fidl::BufferThenHeapAllocator<kInputReportBufferSize> report_allocator_
__TA_GUARDED(report_lock_);
fbl::RingBuffer<Report, fuchsia_input_report::MAX_DEVICE_REPORT_COUNT> reports_data_
__TA_GUARDED(report_lock_);
const size_t reader_id_;
InputReportReaderManager<Report>* manager_;
};
// Template Implementation.
template <class Report>
std::unique_ptr<InputReportReader<Report>> InputReportReader<Report>::Create(
InputReportReaderManager<Report>* manager, size_t reader_id, async_dispatcher_t* dispatcher,
zx::channel server) {
fidl::OnUnboundFn<fuchsia_input_report::InputReportsReader::Interface> unbound_fn(
[](fuchsia_input_report::InputReportsReader::Interface* interface, fidl::UnbindInfo info,
zx::channel channel) {
auto* reader = static_cast<InputReportReader<Report>*>(interface);
reader->manager_->RemoveReaderFromList(reader);
});
auto reader = std::make_unique<InputReportReader<Report>>(manager, reader_id);
auto binding = fidl::BindServer(
dispatcher, std::move(server),
static_cast<fuchsia_input_report::InputReportsReader::Interface*>(reader.get()),
std::move(unbound_fn));
if (binding.is_error()) {
return nullptr;
}
return reader;
}
template <class Report>
void InputReportReader<Report>::ReceiveReport(const Report& report) {
fbl::AutoLock lock(&report_lock_);
if (reports_data_.full()) {
reports_data_.pop();
}
reports_data_.push(report);
if (completer_) {
ReplyWithReports(*completer_);
completer_.reset();
}
}
template <class Report>
void InputReportReader<Report>::ReadInputReports(ReadInputReportsCompleter::Sync& completer) {
fbl::AutoLock lock(&report_lock_);
if (completer_) {
completer.ReplyError(ZX_ERR_ALREADY_BOUND);
return;
}
if (reports_data_.empty()) {
completer_.emplace(completer.ToAsync());
} else {
ReplyWithReports(completer);
}
}
template <class Report>
void InputReportReader<Report>::ReplyWithReports(ReadInputReportsCompleterBase& completer) {
std::array<fuchsia_input_report::InputReport, fuchsia_input_report::MAX_DEVICE_REPORT_COUNT>
reports;
TRACE_DURATION("input", "InputReportInstance GetReports", "instance_id", reader_id_);
size_t num_reports = 0;
for (; !reports_data_.empty() && num_reports < reports.size(); num_reports++) {
fidl::Allocator& allocator = report_allocator_;
// Build the report.
auto builder = fuchsia_input_report::InputReport::Builder(
allocator.make<fuchsia_input_report::InputReport::Frame>());
reports_data_.front().ToFidlInputReport(builder, report_allocator_);
// Add some common fields if they weren't already set.
if (!builder.has_trace_id()) {
builder.set_trace_id(allocator.make<uint64_t>(TRACE_NONCE()));
}
if (!builder.has_event_time()) {
builder.set_event_time(allocator.make<zx_time_t>(zx_clock_get_monotonic()));
}
reports[num_reports] = builder.build();
TRACE_FLOW_BEGIN("input", "input_report", reports[num_reports].trace_id());
reports_data_.pop();
}
completer.ReplySuccess(fidl::VectorView(fidl::unowned_ptr(reports.data()), num_reports));
if (reports_data_.empty()) {
report_allocator_.inner_allocator().reset();
}
}
} // namespace input
#endif // SRC_UI_INPUT_LIB_INPUT_REPORT_READER_READER_H_