I/O fixes, ensure sync and responses are proper

This commit is contained in:
pixl 2023-04-28 00:39:04 -04:00
parent 9d15610596
commit adcb173e4c
No known key found for this signature in database
GPG Key ID: 1866C148CD593B6E
14 changed files with 191 additions and 192 deletions

View File

@ -30,7 +30,7 @@
namespace logid {
namespace defaults {
static constexpr double io_timeout = 500;
static constexpr double io_timeout = 1000;
static constexpr int gesture_threshold = 50;
}

View File

@ -51,10 +51,10 @@ Device::InvalidDevice::Reason Device::InvalidDevice::code() const noexcept {
}
Device::Device(const std::string& path, DeviceIndex index,
std::shared_ptr<raw::DeviceMonitor> monitor, double timeout) :
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
io_timeout(duration_cast<milliseconds>(
duration<double, std::milli>(timeout))),
_raw_device(std::make_shared<raw::RawDevice>(path, std::move(monitor))),
_raw_device(std::make_shared<raw::RawDevice>(path, monitor)),
_receiver(nullptr), _path(path), _index(index) {
_init();
}
@ -133,7 +133,7 @@ void Device::_init() {
auto rsp = sendReport(
{ReportType::Short, _index,
hidpp20::FeatureID::ROOT, hidpp20::Root::Ping,
LOGID_HIDPP_SOFTWARE_ID});
hidpp::softwareID});
if (rsp.deviceIndex() != _index) {
throw InvalidDevice(InvalidDevice::VirtualNode);
}
@ -152,12 +152,9 @@ void Device::_init() {
[index = this->_index](
const std::vector<uint8_t>& report) -> bool {
return (report[Offset::Type] ==
Report::Type::Short ||
report[Offset::Type] ==
Report::Type::Long) &&
(report[Offset::DeviceIndex] ==
index);
return (report[Offset::Type] == Report::Type::Short ||
report[Offset::Type] == Report::Type::Long) &&
(report[Offset::DeviceIndex] == index);
},
[this](const std::vector<uint8_t>& report) -> void {
Report _report(report);
@ -212,13 +209,13 @@ Device::~Device() {
}
Device::EvHandlerId Device::addEventHandler(EventHandler handler) {
std::lock_guard<std::mutex> lock(_event_handler_lock);
std::unique_lock lock(_event_handler_mutex);
_event_handlers.emplace_front(std::move(handler));
return _event_handlers.cbegin();
}
void Device::removeEventHandler(EvHandlerId id) {
std::lock_guard<std::mutex> lock(_event_handler_lock);
std::unique_lock lock(_event_handler_mutex);
_event_handlers.erase(id);
}
@ -226,66 +223,82 @@ void Device::handleEvent(Report& report) {
if (responseReport(report))
return;
std::lock_guard<std::mutex> lock(_event_handler_lock);
std::shared_lock lock(_event_handler_mutex);
for (auto& handler: _event_handlers)
if (handler.condition(report))
handler.callback(report);
}
Report Device::sendReport(const Report& report) {
std::lock_guard<std::mutex> lock(_send_lock);
{
std::lock_guard<std::mutex> sl(_slot_lock);
_report_slot = {};
}
sendReportNoResponse(report);
std::unique_lock<std::mutex> wait(_resp_wait_lock);
bool valid = _resp_cv.wait_for(
wait, io_timeout, [this]() {
std::lock_guard<std::mutex> sl(_slot_lock);
return _report_slot.has_value();
/* Must complete transaction before next send */
std::lock_guard send_lock(_send_mutex);
_sent_sub_id = report.subId();
std::unique_lock lock(_response_mutex);
_sendReport(report);
bool valid = _response_cv.wait_for(
lock, io_timeout, [this]() {
return _response.has_value();
});
if (!valid)
if (!valid) {
_sent_sub_id.reset();
throw TimeoutError();
std::lock_guard<std::mutex> sl(_slot_lock);
{
Report::Hidpp10Error error{};
if (_report_slot.value().isError10(&error))
throw hidpp10::Error(error.error_code);
}
{
Report::Hidpp20Error error{};
if (_report_slot.value().isError20(&error))
throw hidpp20::Error(error.error_code);
Response response = _response.value();
_response.reset();
_sent_sub_id.reset();
if (std::holds_alternative<Report>(response)) {
return std::get<Report>(response);
} else if(std::holds_alternative<Report::Hidpp10Error>(response)) {
throw hidpp20::Error(std::get<Report::Hidpp10Error>(response).error_code);
} else if(std::holds_alternative<Report::Hidpp20Error>(response)) {
throw hidpp20::Error(std::get<Report::Hidpp20Error>(response).error_code);
}
return _report_slot.value();
// Should not be reached
throw std::runtime_error("unhandled variant type");
}
bool Device::responseReport(const Report& report) {
if (_send_lock.try_lock()) {
_send_lock.unlock();
std::lock_guard lock(_response_mutex);
Response response = report;
uint8_t sub_id;
Report::Hidpp10Error hidpp10_error{};
Report::Hidpp20Error hidpp20_error{};
if (report.isError10(&hidpp10_error)) {
sub_id = hidpp10_error.sub_id;
response = hidpp10_error;
} else if (report.isError20(&hidpp20_error)) {
sub_id = hidpp20_error.feature_index;
response = hidpp20_error;
} else {
sub_id = report.subId();
}
if (sub_id == _sent_sub_id) {
_response = response;
_response_cv.notify_all();
return true;
} else {
return false;
}
// Basic check to see if the report is a response
if ((report.swId() != LOGID_HIDPP_SOFTWARE_ID)
&& report.subId() < 0x80)
return false;
std::lock_guard lock(_slot_lock);
_report_slot = report;
_resp_cv.notify_all();
return true;
}
void Device::sendReportNoResponse(Report report) {
void Device::_sendReport(Report report) {
reportFixup(report);
_raw_device->sendReport(report.rawReport());
}
void Device::sendReportNoACK(const Report& report) {
std::lock_guard lock(_send_mutex);
_sendReport(report);
}
void Device::reportFixup(Report& report) const {
switch (report.type()) {
case Report::Type::Short:

View File

@ -20,6 +20,7 @@
#define LOGID_BACKEND_HIDPP_DEVICE_H
#include <optional>
#include <variant>
#include <string>
#include <memory>
#include <functional>
@ -64,7 +65,7 @@ namespace logid::backend::hidpp {
};
Device(const std::string& path, DeviceIndex index,
std::shared_ptr<raw::DeviceMonitor> monitor, double timeout);
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
Device(std::shared_ptr<raw::RawDevice> raw_device, DeviceIndex index,
double timeout);
@ -93,7 +94,7 @@ namespace logid::backend::hidpp {
virtual Report sendReport(const Report& report);
void sendReportNoResponse(Report report);
virtual void sendReportNoACK(const Report& report);
void handleEvent(Report& report);
@ -101,10 +102,15 @@ namespace logid::backend::hidpp {
// Returns whether the report is a response
virtual bool responseReport(const Report& report);
void _sendReport(Report report);
void reportFixup(Report& report) const;
const std::chrono::milliseconds io_timeout;
uint8_t supported_reports;
uint8_t supported_reports{};
std::mutex _response_mutex;
std::condition_variable _response_cv;
private:
void _init();
@ -115,16 +121,17 @@ namespace logid::backend::hidpp {
DeviceIndex _index;
std::tuple<uint8_t, uint8_t> _version;
uint16_t _pid;
uint16_t _pid{};
std::string _name;
std::mutex _send_lock;
std::mutex _resp_wait_lock;
std::condition_variable _resp_cv;
std::mutex _slot_lock;
std::optional<Report> _report_slot;
std::mutex _send_mutex;
std::mutex _event_handler_lock;
typedef std::variant<Report, Report::Hidpp10Error, Report::Hidpp20Error> Response;
std::optional<Response> _response;
std::optional<uint8_t> _sent_sub_id{};
std::shared_mutex _event_handler_mutex;
std::list<EventHandler> _event_handlers;
};
}

View File

@ -19,8 +19,6 @@
#ifndef LOGID_BACKEND_HIDPP_DEFS_H
#define LOGID_BACKEND_HIDPP_DEFS_H
#define LOGID_HIDPP_SOFTWARE_ID 2
#include <cstdint>
namespace logid::backend::hidpp {
@ -42,6 +40,10 @@ namespace logid::backend::hidpp {
WirelessDevice6 = 6,
};
static constexpr uint8_t softwareID = 2;
/* For sending reports with no response, use a different SW ID */
static constexpr uint8_t noAckSoftwareID = 1;
static constexpr std::size_t ShortParamLength = 3;
static constexpr std::size_t LongParamLength = 16;
}

View File

@ -25,15 +25,13 @@
using namespace logid::backend;
using namespace logid::backend::hidpp10;
Device::Device(const std::string& path,
hidpp::DeviceIndex index,
std::shared_ptr<raw::DeviceMonitor> monitor, double timeout) :
hidpp::Device(path, index, std::move(monitor), timeout) {
Device::Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
hidpp::Device(path, index, monitor, timeout) {
assert(version() == std::make_tuple(1, 0));
}
Device::Device(std::shared_ptr<raw::RawDevice> raw_dev,
hidpp::DeviceIndex index,
Device::Device(std::shared_ptr<raw::RawDevice> raw_dev, hidpp::DeviceIndex index,
double timeout) : hidpp::Device(std::move(raw_dev), index, timeout) {
assert(version() == std::make_tuple(1, 0));
}
@ -45,44 +43,34 @@ Device::Device(const std::shared_ptr<dj::Receiver>& receiver,
assert(version() == std::make_tuple(1, 0));
}
hidpp::Report Device::sendReport(const hidpp::Report& report) {
decltype(_responses)::iterator response_slot;
while (true) {
{
std::lock_guard<std::mutex> lock(_response_lock);
response_slot = _responses.find(report.subId());
if (response_slot == _responses.end()) {
response_slot = _responses.emplace(
report.subId(), std::optional<Response>()).first;
break;
}
}
std::unique_lock<std::mutex> lock(_response_wait_lock);
_response_cv.wait(lock, [this, sub_id = report.subId()]() {
std::lock_guard<std::mutex> lock(_response_lock);
return _responses.find(sub_id) != _responses.end();
});
}
void Device::ResponseSlot::reset() {
response.reset();
sub_id.reset();
}
sendReportNoResponse(report);
std::unique_lock<std::mutex> wait(_response_wait_lock);
hidpp::Report Device::sendReport(const hidpp::Report& report) {
auto& response_slot = _responses[report.subId() % SubIDCount];
std::unique_lock<std::mutex> lock(_response_mutex);
_response_cv.wait(lock, [&response_slot]() {
return !response_slot.sub_id.has_value();
});
response_slot.sub_id = report.subId();
_sendReport(report);
bool valid = _response_cv.wait_for(
wait, io_timeout,
[this, &response_slot]() {
std::lock_guard<std::mutex> lock(_response_lock);
return response_slot->second.has_value();
lock, io_timeout,
[&response_slot]() {
return response_slot.response.has_value();
});
if (!valid) {
std::lock_guard<std::mutex> lock(_response_lock);
_responses.erase(response_slot);
response_slot.reset();
throw TimeoutError();
}
std::lock_guard<std::mutex> lock(_response_lock);
assert(response_slot->second.has_value());
auto response = response_slot->second.value();
_responses.erase(response_slot);
auto response = response_slot.response.value();
response_slot.reset();
if (std::holds_alternative<hidpp::Report>(response))
return std::get<hidpp::Report>(response);
else // if(std::holds_alternative<Error::ErrorCode>(response))
@ -90,7 +78,7 @@ hidpp::Report Device::sendReport(const hidpp::Report& report) {
}
bool Device::responseReport(const hidpp::Report& report) {
std::lock_guard<std::mutex> lock(_response_lock);
std::lock_guard<std::mutex> lock(_response_mutex);
uint8_t sub_id;
bool is_error = false;
@ -102,15 +90,15 @@ bool Device::responseReport(const hidpp::Report& report) {
sub_id = report.subId();
}
auto response_slot = _responses.find(sub_id);
if (response_slot == _responses.end())
auto& response_slot = _responses[sub_id % SubIDCount];
if (!response_slot.sub_id.has_value() || response_slot.sub_id.value() != sub_id)
return false;
if (is_error) {
response_slot->second = static_cast<Error::ErrorCode>(
hidpp10_error.error_code);
response_slot.response = static_cast<Error::ErrorCode>(hidpp10_error.error_code);
} else {
response_slot->second = report;
response_slot.response = report;
}
_response_cv.notify_all();

View File

@ -24,12 +24,13 @@
#include "../hidpp/Device.h"
#include "Error.h"
#include "defs.h"
namespace logid::backend::hidpp10 {
class Device : public hidpp::Device {
public:
Device(const std::string& path, hidpp::DeviceIndex index,
std::shared_ptr<raw::DeviceMonitor> monitor, double timeout);
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
Device(std::shared_ptr<raw::RawDevice> raw_dev,
hidpp::DeviceIndex index, double timeout);
@ -51,15 +52,16 @@ namespace logid::backend::hidpp10 {
bool responseReport(const hidpp::Report& report) final;
private:
std::mutex _response_lock;
std::mutex _response_wait_lock;
std::condition_variable _response_cv;
typedef std::variant<hidpp::Report, Error::ErrorCode> Response;
std::map<uint8_t, std::optional<Response>> _responses;
struct ResponseSlot {
std::optional<Response> response;
std::optional<uint8_t> sub_id;
void reset();
};
std::array<ResponseSlot, SubIDCount> _responses;
std::vector<uint8_t> accessRegister(uint8_t sub_id,
uint8_t address, const std::vector<uint8_t>& params);
std::vector<uint8_t> accessRegister(
uint8_t sub_id, uint8_t address, const std::vector<uint8_t>& params);
};
}

View File

@ -24,8 +24,10 @@ namespace logid::backend::hidpp10 {
SetRegisterShort = 0x80,
GetRegisterShort = 0x81,
SetRegisterLong = 0x82,
GetRegisterLong = 0x83
GetRegisterLong = 0x83,
};
static constexpr size_t SubIDCount = 4;
}
#endif //LOGID_BACKEND_HIDPP10_DEFS_H

View File

@ -26,9 +26,8 @@ using namespace logid::backend;
using namespace logid::backend::hidpp20;
Device::Device(const std::string& path, hidpp::DeviceIndex index,
std::shared_ptr<raw::DeviceMonitor> monitor, double timeout) :
hidpp::Device(path, index,
std::move(monitor), timeout) {
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
hidpp::Device(path, index, monitor, timeout) {
// TODO: Fix version check
if (std::get<0>(version()) < 2)
throw std::runtime_error("Invalid HID++ version");
@ -68,7 +67,7 @@ std::vector<uint8_t> Device::callFunction(uint8_t feature_index,
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, deviceIndex(), feature_index, function,
LOGID_HIDPP_SOFTWARE_ID);
hidpp::softwareID);
std::copy(params.begin(), params.end(), request.paramBegin());
auto response = this->sendReport(request);
@ -87,101 +86,85 @@ void Device::callFunctionNoResponse(uint8_t feature_index, uint8_t function,
else
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, deviceIndex(), feature_index, function,
LOGID_HIDPP_SOFTWARE_ID);
hidpp::Report request(type, deviceIndex(), feature_index, function, hidpp::softwareID);
std::copy(params.begin(), params.end(), request.paramBegin());
this->sendReportNoResponse(request);
this->sendReportNoACK(request);
}
hidpp::Report Device::sendReport(const hidpp::Report& report) {
decltype(_responses)::iterator response_slot;
auto& response_slot = _responses[report.feature() % _responses.size()];
while (true) {
{
std::lock_guard lock(_response_lock);
if (_responses.empty()) {
response_slot = _responses.emplace(
2, std::optional<Response>()).first;
break;
} else if (_responses.size() < response_slots) {
uint8_t i = 0;
for (auto& x: _responses) {
if (x.first != i + 1) {
++i;
break;
}
i = x.first;
}
assert(_responses.count(i) == 0);
std::unique_lock<std::mutex> response_lock(_response_mutex);
_response_cv.wait(response_lock, [&response_slot]() {
return !response_slot.feature.has_value();
});
response_slot = _responses.emplace(
i, std::optional<Response>()).first;
break;
}
}
response_slot.feature = report.feature();
std::unique_lock<std::mutex> lock(_response_wait_lock);
_response_cv.wait(lock, [this, sub_id = report.subId()]() {
std::lock_guard<std::mutex> lock(_response_lock);
return _responses.size() < response_slots;
});
}
_sendReport(report);
{
std::lock_guard<std::mutex> lock(_response_lock);
hidpp::Report mod_report{report};
mod_report.setSwId(response_slot->first);
sendReportNoResponse(std::move(mod_report));
}
std::unique_lock<std::mutex> wait(_response_wait_lock);
bool valid = _response_cv.wait_for(wait, io_timeout,
[this, &response_slot]() {
std::lock_guard<std::mutex> lock(_response_lock);
return response_slot->second.has_value();
});
bool valid = _response_cv.wait_for(
response_lock, io_timeout,
[&response_slot]() {
return response_slot.response.has_value();
});
if (!valid) {
std::lock_guard<std::mutex> lock(_response_lock);
_responses.erase(response_slot);
response_slot.reset();
throw TimeoutError();
}
std::lock_guard<std::mutex> lock(_response_lock);
assert(response_slot->second.has_value());
auto response = response_slot->second.value();
_responses.erase(response_slot);
assert(response_slot.response.has_value());
auto response = response_slot.response.value();
response_slot.reset();
if (std::holds_alternative<hidpp::Report>(response))
return std::get<hidpp::Report>(response);
else // if(std::holds_alternative<Error::ErrorCode>(response))
throw Error(std::get<Error::ErrorCode>(response));
}
void Device::sendReportNoACK(const hidpp::Report& report) {
hidpp::Report no_ack_report(report);
no_ack_report.setSwId(hidpp::noAckSoftwareID);
_sendReport(std::move(no_ack_report));
}
bool Device::responseReport(const hidpp::Report& report) {
std::lock_guard<std::mutex> lock(_response_lock);
uint8_t sw_id;
auto& response_slot = _responses[report.feature() % _responses.size()];
std::lock_guard<std::mutex> lock(_response_mutex);
uint8_t sw_id, feature;
bool is_error = false;
hidpp::Report::Hidpp20Error hidpp20_error{};
if (report.isError20(&hidpp20_error)) {
is_error = true;
sw_id = hidpp20_error.software_id;
feature = hidpp20_error.feature_index;
} else {
sw_id = report.swId();
feature = report.feature();
}
auto response_slot = _responses.find(sw_id);
if (response_slot == _responses.end())
if (sw_id != hidpp::softwareID)
return false;
if (!response_slot.feature || response_slot.feature.value() != feature) {
return false;
}
if (is_error) {
response_slot->second = static_cast<Error::ErrorCode>(
hidpp20_error.error_code);
response_slot.response = static_cast<Error::ErrorCode>(hidpp20_error.error_code);
} else {
response_slot->second = report;
response_slot.response = report;
}
_response_cv.notify_all();
return true;
}
void Device::ResponseSlot::reset() {
response.reset();
feature.reset();
}

View File

@ -30,7 +30,7 @@ namespace logid::backend::hidpp20 {
class Device : public hidpp::Device {
public:
Device(const std::string& path, hidpp::DeviceIndex index,
std::shared_ptr<raw::DeviceMonitor> monitor, double timeout);
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
Device(std::shared_ptr<raw::RawDevice> raw_device,
hidpp::DeviceIndex index, double timeout);
@ -51,17 +51,21 @@ namespace logid::backend::hidpp20 {
hidpp::Report sendReport(const hidpp::Report& report) final;
void sendReportNoACK(const hidpp::Report& report) final;
protected:
bool responseReport(const hidpp::Report& report) final;
private:
std::mutex _response_lock;
std::mutex _response_wait_lock;
std::condition_variable _response_cv;
static constexpr int response_slots = 14;
typedef std::variant<hidpp::Report, Error::ErrorCode> Response;
std::map<uint8_t, std::optional<Response>> _responses;
struct ResponseSlot {
std::optional<Response> response;
std::optional<uint8_t> feature;
void reset();
};
/* Multiplex responses on lower nibble of SubID, ignore upper nibble for space */
std::array<ResponseSlot, 16> _responses;
};
}

View File

@ -36,8 +36,7 @@ std::vector<uint8_t> EssentialFeature::callFunction(uint8_t function_id,
else
throw hidpp::Report::InvalidReportID();
hidpp::Report request(type, _device->deviceIndex(), _index, function_id,
LOGID_HIDPP_SOFTWARE_ID);
hidpp::Report request(type, _device->deviceIndex(), _index, function_id, hidpp::softwareID);
std::copy(params.begin(), params.end(), request.paramBegin());
auto response = _device->sendReport(request);

View File

@ -86,8 +86,8 @@ void IOMonitor::_listen() {
_is_running = true;
while (_is_running) {
{
std::unique_lock<std::mutex> lock(_interrupt_lock);
if (_interrupting) {
std::unique_lock<std::mutex> lock(_interrupt_mutex);
_interrupt_cv.wait(lock, [this]() {
return !(bool) _interrupting;
});

View File

@ -64,7 +64,7 @@ namespace logid::backend::raw {
std::atomic_bool _is_running;
std::atomic_bool _interrupting;
std::mutex _interrupt_lock;
std::mutex _interrupt_mutex;
std::condition_variable _interrupt_cv;
const int _epoll_fd;

View File

@ -155,13 +155,13 @@ void RawDevice::sendReport(const std::vector<uint8_t>& report) {
}
RawDevice::EvHandlerId RawDevice::addEventHandler(RawEventHandler handler) {
std::lock_guard<std::mutex> lock(_event_handler_lock);
std::unique_lock<std::shared_mutex> lock(_event_handler_mutex);
_event_handlers.emplace_front(std::move(handler));
return _event_handlers.cbegin();
}
void RawDevice::removeEventHandler(RawDevice::EvHandlerId id) {
std::lock_guard<std::mutex> lock(_event_handler_lock);
std::unique_lock<std::shared_mutex> lock(_event_handler_mutex);
_event_handlers.erase(id);
}
@ -185,7 +185,7 @@ void RawDevice::_readReports() {
}
void RawDevice::_handleEvent(const std::vector<uint8_t>& report) {
std::unique_lock<std::mutex> lock(_event_handler_lock);
std::shared_lock<std::shared_mutex> lock(_event_handler_mutex);
for (auto& handler: _event_handlers)
if (handler.condition(report))
handler.callback(report);

View File

@ -21,8 +21,7 @@
#include <string>
#include <vector>
#include <mutex>
#include <map>
#include <shared_mutex>
#include <atomic>
#include <future>
#include <set>
@ -85,7 +84,7 @@ namespace logid::backend::raw {
std::shared_ptr<IOMonitor> _io_monitor;
std::list<RawEventHandler> _event_handlers;
std::mutex _event_handler_lock;
std::shared_mutex _event_handler_mutex;
void _handleEvent(const std::vector<uint8_t>& report);
};