Fix broken pipes and improve receiver handling

Retry before failing on broken pipe error (fixes Nano receiver),
and test for virtual devices without I/O.
This commit is contained in:
pixl 2023-05-16 15:16:11 -04:00
parent be5ee9f793
commit 5e32120b2c
No known key found for this signature in database
GPG Key ID: 1866C148CD593B6E
8 changed files with 129 additions and 54 deletions

View File

@ -114,35 +114,8 @@ void Device::_setupReportsAndInit() {
/* hid_logitech_dj creates virtual /dev/hidraw nodes for receiver
* devices. We should ignore these devices.
*/
if (_index == hidpp::DefaultDevice) {
_raw_handler = _raw_device->addEventHandler(
{[](const std::vector<uint8_t>& report) -> bool {
return (report[Offset::Type] == Report::Type::Short ||
report[Offset::Type] == Report::Type::Long);
},
[self_weak = _self](const std::vector<uint8_t>& report) -> void {
Report _report(report);
if(auto self = self_weak.lock())
self->handleEvent(_report);
}});
try {
auto rsp = sendReport({ReportType::Short, _index,
hidpp20::FeatureID::ROOT, hidpp20::Root::Ping,
hidpp::softwareID});
if (rsp.deviceIndex() != _index)
throw InvalidDevice(InvalidDevice::VirtualNode);
} catch (hidpp10::Error& e) {
if (e.deviceIndex() != _index)
throw InvalidDevice(InvalidDevice::VirtualNode);
} catch (hidpp20::Error& e) {
/* This shouldn't happen, the device must not be ready */
if (e.deviceIndex() != _index)
throw InvalidDevice(InvalidDevice::VirtualNode);
else
throw DeviceNotReady();
}
}
if (_raw_device->isSubDevice())
throw InvalidDevice(InvalidDevice::VirtualNode);
_raw_handler = _raw_device->addEventHandler(
{[index = _index](
@ -228,6 +201,7 @@ Report Device::sendReport(const Report& report) {
/* Must complete transaction before next send */
std::lock_guard send_lock(_send_mutex);
_sent_sub_id = report.subId();
_sent_address = report.address();
std::unique_lock lock(_response_mutex);
_sendReport(report);
@ -244,6 +218,7 @@ Report Device::sendReport(const Report& report) {
Response response = _response.value();
_response.reset();
_sent_sub_id.reset();
_sent_address.reset();
if (std::holds_alternative<Report>(response)) {
return std::get<Report>(response);
@ -263,20 +238,23 @@ bool Device::responseReport(const Report& report) {
std::lock_guard lock(_response_mutex);
Response response = report;
uint8_t sub_id;
uint8_t address;
Report::Hidpp10Error hidpp10_error{};
Report::Hidpp20Error hidpp20_error{};
if (report.isError10(hidpp10_error)) {
sub_id = hidpp10_error.sub_id;
address = hidpp10_error.address;
response = hidpp10_error;
} else if (report.isError20(hidpp20_error)) {
sub_id = hidpp20_error.feature_index;
response = hidpp20_error;
address = (hidpp20_error.function << 4) | (hidpp20_error.software_id & 0x0f);
} else {
sub_id = report.subId();
address = report.address();
}
if (sub_id == _sent_sub_id) {
if (sub_id == _sent_sub_id && address == _sent_address) {
_response = response;
_response_cv.notify_all();
return true;

View File

@ -158,6 +158,7 @@ namespace logid::backend::hidpp {
std::optional<Response> _response;
std::optional<uint8_t> _sent_sub_id{};
std::optional<uint8_t> _sent_address{};
std::shared_ptr<EventHandlerList<Device>> _event_handlers;

View File

@ -42,7 +42,7 @@ namespace logid::backend::hidpp {
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 uint8_t noAckSoftwareID = 3;
static constexpr std::size_t ShortParamLength = 3;
static constexpr std::size_t LongParamLength = 16;

View File

@ -24,6 +24,23 @@
using namespace logid::backend;
using namespace logid::backend::hidpp10;
hidpp::Report setupRegReport(hidpp::DeviceIndex index,
uint8_t sub_id, uint8_t address,
const std::vector<uint8_t>& params) {
hidpp::Report::Type type = params.size() <= hidpp::ShortParamLength ?
hidpp::Report::Type::Short : hidpp::Report::Type::Long;
if (sub_id == SetRegisterLong) {
// When setting a long register, the report must be long.
type = hidpp::Report::Type::Long;
}
hidpp::Report request(type, index, sub_id, address);
std::copy(params.begin(), params.end(), request.paramBegin());
return request;
}
Device::Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
hidpp::Device(path, index, monitor, timeout) {
@ -124,22 +141,24 @@ std::vector<uint8_t> Device::setRegister(uint8_t address,
return accessRegister(sub_id, address, params);
}
void Device::setRegisterNoResponse(uint8_t address,
const std::vector<uint8_t>& params,
hidpp::Report::Type type) {
assert(params.size() <= hidpp::LongParamLength);
uint8_t sub_id = type == hidpp::Report::Type::Short ?
SetRegisterShort : SetRegisterLong;
return accessRegisterNoResponse(sub_id, address, params);
}
std::vector<uint8_t> Device::accessRegister(uint8_t sub_id, uint8_t address,
const std::vector<uint8_t>& params) {
hidpp::Report::Type type = params.size() <= hidpp::ShortParamLength ?
hidpp::Report::Type::Short : hidpp::Report::Type::Long;
if (sub_id == SetRegisterLong) {
// When setting a long register, the report must be long.
type = hidpp::Report::Type::Long;
}
hidpp::Report request(type, deviceIndex(), sub_id, address);
std::copy(params.begin(), params.end(), request.paramBegin());
auto response = sendReport(request);
auto response = sendReport(setupRegReport(deviceIndex(), sub_id, address, params));
return {response.paramBegin(), response.paramEnd()};
}
void Device::accessRegisterNoResponse(uint8_t sub_id, uint8_t address,
const std::vector<uint8_t>& params) {
sendReportNoACK(setupRegReport(deviceIndex(), sub_id, address, params));
}

View File

@ -39,6 +39,9 @@ namespace logid::backend::hidpp10 {
const std::vector<uint8_t>& params,
hidpp::Report::Type type);
void setRegisterNoResponse(uint8_t address, const std::vector<uint8_t>& params,
hidpp::Report::Type type);
protected:
Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
@ -53,18 +56,24 @@ namespace logid::backend::hidpp10 {
private:
typedef std::variant<hidpp::Report, hidpp::Report::Hidpp10Error> Response;
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);
void accessRegisterNoResponse(
uint8_t sub_id, uint8_t address, const std::vector<uint8_t>& params);
protected:
template <typename T, typename... Args>
template<typename T, typename... Args>
static std::shared_ptr<T> makeDerived(Args... args) {
auto device = hidpp::Device::makeDerived<T>(std::forward<Args>(args)...);
@ -73,8 +82,9 @@ namespace logid::backend::hidpp10 {
return device;
}
public:
template <typename... Args>
template<typename... Args>
static std::shared_ptr<Device> make(Args... args) {
return makeDerived<Device>(std::forward<Args>(args)...);
}

View File

@ -68,7 +68,7 @@ void Receiver::setNotifications(NotificationFlags flags) {
}
void Receiver::enumerate() {
setRegister(ConnectionState, {2}, hidpp::ReportType::Short);
setRegisterNoResponse(ConnectionState, {2}, hidpp::ReportType::Short);
}
///TODO: Investigate usage

View File

@ -24,6 +24,7 @@
#include <string>
#include <system_error>
#include <utility>
#include <regex>
extern "C"
{
@ -32,12 +33,17 @@ extern "C"
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/hidraw.h>
#include <linux/input.h>
}
using namespace logid::backend::raw;
using namespace logid::backend;
using namespace std::chrono;
static constexpr int max_write_tries = 8;
static const std::regex virtual_path_regex(R"~((.*\/)(.*:)([0-9]+))~");
int get_fd(const std::string& path) {
int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK);
if (fd == -1)
@ -56,7 +62,37 @@ RawDevice::dev_info get_dev_info(int fd) {
"RawDevice HIDIOCGRAWINFO failed");
}
return {dev_info.vendor, dev_info.product};
RawDevice::BusType type = RawDevice::OtherBus;
switch (dev_info.bustype) {
case BUS_USB:
type = RawDevice::USB;
break;
case BUS_BLUETOOTH:
type = RawDevice::Bluetooth;
break;
default:;
}
return {
.vid = dev_info.vendor,
.pid = dev_info.product,
.bus_type = type
};
}
std::string get_phys(int fd) {
ssize_t len;
char buf[256];
if (-1 == (len = ::ioctl(fd, HIDIOCGRAWPHYS(sizeof(buf)), buf))) {
int err = errno;
::close(fd);
throw std::system_error(err, std::system_category(),
"RawDevice HIDIOCGRAWPHYS failed");
}
return {buf, static_cast<size_t>(len) - 1};
}
std::string get_name(int fd) {
@ -68,7 +104,7 @@ std::string get_name(int fd) {
throw std::system_error(err, std::system_category(),
"RawDevice HIDIOCGRAWNAME failed");
}
return {name_buf, static_cast<size_t>(len)};
return {name_buf, static_cast<size_t>(len) - 1};
}
RawDevice::RawDevice(std::string path, const std::shared_ptr<DeviceMonitor>& monitor) :
@ -76,6 +112,12 @@ RawDevice::RawDevice(std::string path, const std::shared_ptr<DeviceMonitor>& mon
_dev_info(get_dev_info(_fd)), _name(get_name(_fd)),
_report_desc(getReportDescriptor(_fd)), _io_monitor(monitor->ioMonitor()),
_event_handlers(std::make_shared<EventHandlerList<RawDevice>>()) {
if (busType() == USB) {
auto phys = get_phys(_fd);
_sub_device = std::regex_match(phys, virtual_path_regex);
}
_io_monitor->add(_fd, {
[this]() { _readReports(); },
[this]() { _valid = false; },
@ -105,6 +147,14 @@ int16_t RawDevice::productId() const {
return _dev_info.pid;
}
RawDevice::BusType RawDevice::busType() const {
return _dev_info.bus_type;
}
bool RawDevice::isSubDevice() const {
return _sub_device;
}
std::vector<uint8_t> RawDevice::getReportDescriptor(const std::string& path) {
int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK);
if (fd == -1)
@ -150,9 +200,13 @@ void RawDevice::sendReport(const std::vector<uint8_t>& report) {
printf("\n");
}
if (write(_fd, report.data(), report.size()) == -1)
throw std::system_error(errno, std::system_category(),
"sendReport write failed");
for (int i = 0; i < max_write_tries && write(_fd, report.data(), report.size()) == -1; ++i) {
auto err = errno;
if (err != EPIPE)
throw std::system_error(err, std::system_category(),
"sendReport write failed");
}
}
EventHandlerLock<RawDevice> RawDevice::addEventHandler(RawEventHandler handler) {

View File

@ -39,9 +39,16 @@ namespace logid::backend::raw {
static constexpr int max_data_length = 32;
typedef RawEventHandler EventHandler;
enum BusType {
USB,
Bluetooth,
OtherBus
};
struct dev_info {
int16_t vid;
int16_t pid;
BusType bus_type;
};
RawDevice(std::string path, const std::shared_ptr<DeviceMonitor>& monitor);
@ -57,6 +64,10 @@ namespace logid::backend::raw {
[[nodiscard]] int16_t productId() const;
[[nodiscard]] BusType busType() const;
[[nodiscard]] bool isSubDevice() const;
static std::vector<uint8_t> getReportDescriptor(const std::string& path);
static std::vector<uint8_t> getReportDescriptor(int fd);
@ -80,6 +91,8 @@ namespace logid::backend::raw {
std::shared_ptr<IOMonitor> _io_monitor;
bool _sub_device = false;
std::shared_ptr<EventHandlerList<RawDevice>> _event_handlers;
void _handleEvent(const std::vector<uint8_t>& report);