Merge pull request #380 from PixlOne/fix-wakeup-memleak

This commit is contained in:
pixl 2023-05-18 21:15:53 -04:00 committed by GitHub
commit 5767aac362
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 317 additions and 171 deletions

View File

@ -184,7 +184,6 @@ void Device::sleep() {
void Device::wakeup() { void Device::wakeup() {
std::lock_guard<std::mutex> lock(_state_lock); std::lock_guard<std::mutex> lock(_state_lock);
logPrintf(INFO, "%s:%d woke up.", _path.c_str(), _index);
reconfigure(); reconfigure();
@ -192,6 +191,8 @@ void Device::wakeup() {
_awake = true; _awake = true;
_ipc_interface->notifyStatus(); _ipc_interface->notifyStatus();
} }
logPrintf(INFO, "%s:%d woke up.", _path.c_str(), _index);
} }
void Device::reconfigure() { void Device::reconfigure() {

View File

@ -24,21 +24,64 @@
#include <mutex> #include <mutex>
#include <shared_mutex> #include <shared_mutex>
#include <list> #include <list>
#include <atomic>
template <class T> template <class T>
class EventHandlerLock; class EventHandlerLock;
template <class T> template <class T>
struct EventHandlerList { class EventHandlerList {
typedef std::list<typename T::EventHandler> list_t; public:
typedef typename list_t::const_iterator iterator_t; typedef std::list<std::pair<typename T::EventHandler, std::atomic_bool>> list_t;
typedef typename list_t::iterator iterator_t;
private:
list_t list;
std::shared_mutex mutex;
std::shared_mutex add_mutex;
std::list<typename T::EventHandler> list; void cleanup() {
mutable std::shared_mutex mutex; std::unique_lock lock(mutex, std::try_to_lock);
if (lock.owns_lock()) {
std::list<iterator_t> to_remove;
for (auto it = list.begin(); it != list.end(); ++it) {
if (!it->second)
to_remove.push_back(it);
}
for(auto& it : to_remove)
list.erase(it);
}
}
public:
iterator_t add(typename T::EventHandler handler) {
std::unique_lock add_lock(add_mutex);
list.emplace_front(std::move(handler), true);
return list.begin();
}
void remove(iterator_t iterator) { void remove(iterator_t iterator) {
std::unique_lock lock(mutex); std::unique_lock lock(mutex, std::try_to_lock);
list.erase(iterator); if (lock.owns_lock()) {
std::unique_lock add_lock(add_mutex);
list.erase(iterator);
} else {
iterator->second = false;
}
}
template <typename Arg>
void run_all(Arg arg) {
cleanup();
std::shared_lock lock(mutex);
std::shared_lock add_lock(add_mutex);
for (auto& handler : list) {
add_lock.unlock();
if (handler.second) {
if (handler.first.condition(arg))
handler.first.callback(arg);
}
add_lock.lock();
}
} }
}; };

View File

@ -114,35 +114,8 @@ void Device::_setupReportsAndInit() {
/* hid_logitech_dj creates virtual /dev/hidraw nodes for receiver /* hid_logitech_dj creates virtual /dev/hidraw nodes for receiver
* devices. We should ignore these devices. * devices. We should ignore these devices.
*/ */
if (_index == hidpp::DefaultDevice) { if (_raw_device->isSubDevice())
_raw_handler = _raw_device->addEventHandler( throw InvalidDevice(InvalidDevice::VirtualNode);
{[](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();
}
}
_raw_handler = _raw_device->addEventHandler( _raw_handler = _raw_device->addEventHandler(
{[index = _index]( {[index = _index](
@ -214,25 +187,21 @@ void Device::_init() {
} }
EventHandlerLock<Device> Device::addEventHandler(EventHandler handler) { EventHandlerLock<Device> Device::addEventHandler(EventHandler handler) {
std::unique_lock lock(_event_handlers->mutex); return {_event_handlers, _event_handlers->add(std::move(handler))};
_event_handlers->list.emplace_front(std::move(handler));
return {_event_handlers, _event_handlers->list.cbegin()};
} }
void Device::handleEvent(Report& report) { void Device::handleEvent(Report& report) {
if (responseReport(report)) if (responseReport(report))
return; return;
std::shared_lock lock(_event_handlers->mutex); _event_handlers->run_all(report);
for (auto& handler: _event_handlers->list)
if (handler.condition(report))
handler.callback(report);
} }
Report Device::sendReport(const Report& report) { Report Device::sendReport(const Report& report) {
/* Must complete transaction before next send */ /* Must complete transaction before next send */
std::lock_guard send_lock(_send_mutex); std::lock_guard send_lock(_send_mutex);
_sent_sub_id = report.subId(); _sent_sub_id = report.subId();
_sent_address = report.address();
std::unique_lock lock(_response_mutex); std::unique_lock lock(_response_mutex);
_sendReport(report); _sendReport(report);
@ -249,6 +218,7 @@ Report Device::sendReport(const Report& report) {
Response response = _response.value(); Response response = _response.value();
_response.reset(); _response.reset();
_sent_sub_id.reset(); _sent_sub_id.reset();
_sent_address.reset();
if (std::holds_alternative<Report>(response)) { if (std::holds_alternative<Report>(response)) {
return std::get<Report>(response); return std::get<Report>(response);
@ -268,20 +238,23 @@ bool Device::responseReport(const Report& report) {
std::lock_guard lock(_response_mutex); std::lock_guard lock(_response_mutex);
Response response = report; Response response = report;
uint8_t sub_id; uint8_t sub_id;
uint8_t address;
Report::Hidpp10Error hidpp10_error{}; Report::Hidpp10Error hidpp10_error{};
Report::Hidpp20Error hidpp20_error{}; Report::Hidpp20Error hidpp20_error{};
if (report.isError10(hidpp10_error)) { if (report.isError10(hidpp10_error)) {
sub_id = hidpp10_error.sub_id; sub_id = hidpp10_error.sub_id;
address = hidpp10_error.address;
response = hidpp10_error; response = hidpp10_error;
} else if (report.isError20(hidpp20_error)) { } else if (report.isError20(hidpp20_error)) {
sub_id = hidpp20_error.feature_index; sub_id = hidpp20_error.feature_index;
response = hidpp20_error; address = (hidpp20_error.function << 4) | (hidpp20_error.software_id & 0x0f);
} else { } else {
sub_id = report.subId(); 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 = response;
_response_cv.notify_all(); _response_cv.notify_all();
return true; return true;

View File

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

View File

@ -42,7 +42,7 @@ namespace logid::backend::hidpp {
static constexpr uint8_t softwareID = 2; static constexpr uint8_t softwareID = 2;
/* For sending reports with no response, use a different SW ID */ /* 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 ShortParamLength = 3;
static constexpr std::size_t LongParamLength = 16; static constexpr std::size_t LongParamLength = 16;

View File

@ -24,6 +24,23 @@
using namespace logid::backend; using namespace logid::backend;
using namespace logid::backend::hidpp10; 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, Device::Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) : const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout) :
hidpp::Device(path, index, monitor, 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); 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, std::vector<uint8_t> Device::accessRegister(uint8_t sub_id, uint8_t address,
const std::vector<uint8_t>& params) { const std::vector<uint8_t>& params) {
hidpp::Report::Type type = params.size() <= hidpp::ShortParamLength ? auto response = sendReport(setupRegReport(deviceIndex(), sub_id, address, params));
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);
return {response.paramBegin(), response.paramEnd()}; 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, const std::vector<uint8_t>& params,
hidpp::Report::Type type); hidpp::Report::Type type);
void setRegisterNoResponse(uint8_t address, const std::vector<uint8_t>& params,
hidpp::Report::Type type);
protected: protected:
Device(const std::string& path, hidpp::DeviceIndex index, Device(const std::string& path, hidpp::DeviceIndex index,
const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout); const std::shared_ptr<raw::DeviceMonitor>& monitor, double timeout);
@ -53,18 +56,24 @@ namespace logid::backend::hidpp10 {
private: private:
typedef std::variant<hidpp::Report, hidpp::Report::Hidpp10Error> Response; typedef std::variant<hidpp::Report, hidpp::Report::Hidpp10Error> Response;
struct ResponseSlot { struct ResponseSlot {
std::optional<Response> response; std::optional<Response> response;
std::optional<uint8_t> sub_id; std::optional<uint8_t> sub_id;
void reset(); void reset();
}; };
std::array<ResponseSlot, SubIDCount> _responses; std::array<ResponseSlot, SubIDCount> _responses;
std::vector<uint8_t> accessRegister( std::vector<uint8_t> accessRegister(
uint8_t sub_id, uint8_t address, const std::vector<uint8_t>& params); 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: protected:
template <typename T, typename... Args> template<typename T, typename... Args>
static std::shared_ptr<T> makeDerived(Args... args) { static std::shared_ptr<T> makeDerived(Args... args) {
auto device = hidpp::Device::makeDerived<T>(std::forward<Args>(args)...); auto device = hidpp::Device::makeDerived<T>(std::forward<Args>(args)...);
@ -73,8 +82,9 @@ namespace logid::backend::hidpp10 {
return device; return device;
} }
public: public:
template <typename... Args> template<typename... Args>
static std::shared_ptr<Device> make(Args... args) { static std::shared_ptr<Device> make(Args... args) {
return makeDerived<Device>(std::forward<Args>(args)...); return makeDerived<Device>(std::forward<Args>(args)...);
} }

View File

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

View File

@ -158,28 +158,31 @@ void ReceiverMonitor::enumerate() {
} }
void ReceiverMonitor::waitForDevice(hidpp::DeviceIndex index) { void ReceiverMonitor::waitForDevice(hidpp::DeviceIndex index) {
auto handler_id = std::make_shared<EventHandlerLock<raw::RawDevice>>(); const std::lock_guard lock(_wait_mutex);
if (!_waiters.count(index)) {
_waiters.emplace(index, _receiver->rawDevice()->addEventHandler(
{[index](const std::vector<uint8_t>& report) -> bool {
/* Connection events should be handled by connect_ev_handler */
auto sub_id = report[Offset::SubID];
return report[Offset::DeviceIndex] == index &&
sub_id != Receiver::DeviceConnection &&
sub_id != Receiver::DeviceDisconnection;
},
[self_weak = _self, index](
[[maybe_unused]] const std::vector<uint8_t>& report) {
hidpp::DeviceConnectionEvent event{};
event.withPayload = false;
event.linkEstablished = true;
event.index = index;
event.fromTimeoutCheck = true;
*handler_id = _receiver->rawDevice()->addEventHandler( run_task([self_weak, event]() {
{[index](const std::vector<uint8_t>& report) -> bool { if (auto self = self_weak.lock())
return report[Offset::DeviceIndex] == index; self->_addHandler(event);
}, });
[self_weak = _self, index, handler_id]( }
[[maybe_unused]] const std::vector<uint8_t>& report) { }));
hidpp::DeviceConnectionEvent event{}; }
event.withPayload = false;
event.linkEstablished = true;
event.index = index;
event.fromTimeoutCheck = true;
run_task([self_weak, event, handler_id]() {
*handler_id = {};
if (auto self = self_weak.lock()) {
self->_addHandler(event);
}
});
}
});
} }
std::shared_ptr<Receiver> ReceiverMonitor::receiver() const { std::shared_ptr<Receiver> ReceiverMonitor::receiver() const {
@ -217,6 +220,8 @@ void ReceiverMonitor::_addHandler(const hidpp::DeviceConnectionEvent& event, int
auto device_path = _receiver->devicePath(); auto device_path = _receiver->devicePath();
try { try {
addDevice(event); addDevice(event);
const std::lock_guard lock(_wait_mutex);
_waiters.erase(event.index);
} catch (DeviceNotReady& e) { } catch (DeviceNotReady& e) {
if (tries == max_tries) { if (tries == max_tries) {
logPrintf(WARN, "Failed to add device %s:%d after %d tries." logPrintf(WARN, "Failed to add device %s:%d after %d tries."

View File

@ -101,6 +101,9 @@ namespace logid::backend::hidpp10 {
std::weak_ptr<ReceiverMonitor> _self; std::weak_ptr<ReceiverMonitor> _self;
std::mutex _wait_mutex;
std::map<hidpp::DeviceIndex, EventHandlerLock<raw::RawDevice>> _waiters;
public: public:
template<typename T, typename... Args> template<typename T, typename... Args>
static std::shared_ptr<T> make(Args... args) { static std::shared_ptr<T> make(Args... args) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2022 PixlOne * Copyright 2019-2023 PixlOne
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -17,6 +17,7 @@
*/ */
#include <backend/raw/IOMonitor.h> #include <backend/raw/IOMonitor.h>
#include <cassert> #include <cassert>
#include <optional>
extern "C" extern "C"
{ {
@ -35,6 +36,55 @@ IOHandler::IOHandler(std::function<void()> r,
error(std::move(err)) { error(std::move(err)) {
} }
class IOMonitor::io_lock {
std::optional<std::unique_lock<std::mutex>> _lock;
IOMonitor* _io_monitor;
const uint64_t counter = 1;
public:
explicit io_lock(IOMonitor* io_monitor) : _io_monitor(io_monitor) {
_io_monitor->_interrupting = true;
[[maybe_unused]] ssize_t ret = ::write(_io_monitor->_event_fd, &counter, sizeof(counter));
assert(ret == sizeof(counter));
_lock.emplace(_io_monitor->_run_mutex);
}
io_lock(const io_lock&) = delete;
io_lock& operator=(const io_lock&) = delete;
io_lock(io_lock&& o) noexcept: _lock(std::move(o._lock)), _io_monitor(o._io_monitor) {
o._lock.reset();
o._io_monitor = nullptr;
}
io_lock& operator=(io_lock&& o) noexcept {
if (this != &o) {
_lock = std::move(o._lock);
_io_monitor = o._io_monitor;
o._lock.reset();
o._io_monitor = nullptr;
}
return *this;
}
~io_lock() noexcept {
if (_lock && _io_monitor) {
uint64_t buf{};
[[maybe_unused]] const ssize_t ret = ::read(
_io_monitor->_event_fd, &buf, sizeof(counter));
assert(ret != -1);
if (buf == counter) {
_io_monitor->_interrupting = false;
_io_monitor->_interrupt_cv.notify_one();
}
}
}
};
IOMonitor::IOMonitor() : _epoll_fd(epoll_create1(0)), IOMonitor::IOMonitor() : _epoll_fd(epoll_create1(0)),
_event_fd(eventfd(0, EFD_NONBLOCK)) { _event_fd(eventfd(0, EFD_NONBLOCK)) {
if (_epoll_fd < 0) { if (_epoll_fd < 0) {
@ -69,7 +119,6 @@ IOMonitor::IOMonitor() : _epoll_fd(epoll_create1(0)),
} }
IOMonitor::~IOMonitor() noexcept { IOMonitor::~IOMonitor() noexcept {
std::lock_guard<std::mutex> ctl_lock(_ctl_lock);
_stop(); _stop();
if (_event_fd >= 0) if (_event_fd >= 0)
@ -80,23 +129,21 @@ IOMonitor::~IOMonitor() noexcept {
} }
void IOMonitor::_listen() { void IOMonitor::_listen() {
std::lock_guard<std::mutex> run_lock(_run_lock); std::unique_lock lock(_run_mutex);
std::vector<struct epoll_event> events; std::vector<struct epoll_event> events;
_is_running = true; _is_running = true;
while (_is_running) { while (_is_running) {
if (_interrupting) { if (_interrupting) {
std::unique_lock<std::mutex> lock(_interrupt_mutex);
_interrupt_cv.wait(lock, [this]() { _interrupt_cv.wait(lock, [this]() {
return !(bool) _interrupting; return !_interrupting;
}); });
if (!_is_running) if (!_is_running)
break; break;
} }
std::lock_guard<std::mutex> io_lock(_io_lock);
if (events.size() != _fds.size()) if (events.size() != _fds.size())
events.resize(_fds.size()); events.resize(_fds.size());
int ev_count = ::epoll_wait(_epoll_fd, events.data(), (int) events.size(), -1); int ev_count = ::epoll_wait(_epoll_fd, events.data(), (int) events.size(), -1);
@ -113,76 +160,32 @@ void IOMonitor::_listen() {
} }
void IOMonitor::_stop() noexcept { void IOMonitor::_stop() noexcept {
_interrupt(); {
_is_running = false; [[maybe_unused]] const io_lock lock(this);
_continue(); _is_running = false;
}
_io_thread->join(); _io_thread->join();
} }
[[maybe_unused]]
bool IOMonitor::_running() const {
std::unique_lock<std::mutex> run_lock(_run_lock, std::try_to_lock);
return !run_lock.owns_lock() || _is_running;
}
void IOMonitor::add(int fd, IOHandler handler) { void IOMonitor::add(int fd, IOHandler handler) {
std::lock_guard<std::mutex> lock(_ctl_lock); [[maybe_unused]] const io_lock lock(this);
_interrupt();
struct epoll_event event{}; struct epoll_event event{};
event.events = EPOLLIN | EPOLLHUP | EPOLLERR; event.events = EPOLLIN | EPOLLHUP | EPOLLERR;
event.data.fd = fd; event.data.fd = fd;
// TODO: EPOLL_CTL_MOD // TODO: EPOLL_CTL_MOD
if (_fds.contains(fd)) { if (_fds.contains(fd))
_continue();
throw std::runtime_error("duplicate io fd"); throw std::runtime_error("duplicate io fd");
}
if (::epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &event)) { if (::epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &event))
_continue();
throw std::system_error(errno, std::generic_category()); throw std::system_error(errno, std::generic_category());
}
_fds.emplace(fd, std::move(handler)); _fds.emplace(fd, std::move(handler));
_continue();
} }
void IOMonitor::remove(int fd) noexcept { void IOMonitor::remove(int fd) noexcept {
std::lock_guard<std::mutex> lock(_ctl_lock); [[maybe_unused]] const io_lock lock(this);
_interrupt();
std::lock_guard<std::mutex> io_lock(_io_lock);
::epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, nullptr); ::epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, nullptr);
_fds.erase(fd); _fds.erase(fd);
}
_continue();
}
void IOMonitor::_interrupt() noexcept {
std::unique_lock<std::mutex> run_lock(_run_lock, std::try_to_lock);
_interrupting = true;
uint64_t counter = 1;
[[maybe_unused]] ssize_t ret = ::write(_event_fd, &counter, sizeof(counter));
assert(ret == sizeof(counter));
// Wait for the IO monitor to _stop
std::lock_guard<std::mutex> io_lock(_io_lock);
}
void IOMonitor::_continue() noexcept {
std::unique_lock<std::mutex> run_lock(_run_lock, std::try_to_lock);
uint64_t counter;
[[maybe_unused]] ssize_t ret = ::read(_event_fd, &counter, sizeof(counter));
assert(ret != -1);
if (counter == 1) {
_interrupting = false;
_interrupt_cv.notify_all();
}
}

View File

@ -40,6 +40,14 @@ namespace logid::backend::raw {
public: public:
IOMonitor(); IOMonitor();
IOMonitor(IOMonitor&&) = delete;
IOMonitor(const IOMonitor&) = delete;
IOMonitor& operator=(IOMonitor&&) = delete;
IOMonitor& operator=(const IOMonitor&) = delete;
~IOMonitor() noexcept; ~IOMonitor() noexcept;
void add(int fd, IOHandler handler); void add(int fd, IOHandler handler);
@ -50,26 +58,19 @@ namespace logid::backend::raw {
void _listen(); // This is a blocking call void _listen(); // This is a blocking call
void _stop() noexcept; void _stop() noexcept;
[[maybe_unused]]
[[nodiscard]] bool _running() const;
void _interrupt() noexcept;
void _continue() noexcept;
std::unique_ptr<std::thread> _io_thread; std::unique_ptr<std::thread> _io_thread;
std::map<int, IOHandler> _fds; std::map<int, IOHandler> _fds;
std::mutex _io_lock, _ctl_lock; mutable std::mutex _run_mutex;
mutable std::mutex _run_lock;
std::atomic_bool _is_running; std::atomic_bool _is_running;
std::atomic_bool _interrupting; std::atomic_bool _interrupting;
std::mutex _interrupt_mutex;
std::condition_variable _interrupt_cv; std::condition_variable _interrupt_cv;
const int _epoll_fd; const int _epoll_fd;
const int _event_fd; const int _event_fd;
class io_lock;
}; };
} }

View File

@ -24,6 +24,7 @@
#include <string> #include <string>
#include <system_error> #include <system_error>
#include <utility> #include <utility>
#include <regex>
extern "C" extern "C"
{ {
@ -32,12 +33,17 @@ extern "C"
#include <fcntl.h> #include <fcntl.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <linux/hidraw.h> #include <linux/hidraw.h>
#include <linux/input.h>
} }
using namespace logid::backend::raw; using namespace logid::backend::raw;
using namespace logid::backend; using namespace logid::backend;
using namespace std::chrono; 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 get_fd(const std::string& path) {
int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK); int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK);
if (fd == -1) if (fd == -1)
@ -56,7 +62,37 @@ RawDevice::dev_info get_dev_info(int fd) {
"RawDevice HIDIOCGRAWINFO failed"); "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) { std::string get_name(int fd) {
@ -68,7 +104,7 @@ std::string get_name(int fd) {
throw std::system_error(err, std::system_category(), throw std::system_error(err, std::system_category(),
"RawDevice HIDIOCGRAWNAME failed"); "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) : 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)), _dev_info(get_dev_info(_fd)), _name(get_name(_fd)),
_report_desc(getReportDescriptor(_fd)), _io_monitor(monitor->ioMonitor()), _report_desc(getReportDescriptor(_fd)), _io_monitor(monitor->ioMonitor()),
_event_handlers(std::make_shared<EventHandlerList<RawDevice>>()) { _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, { _io_monitor->add(_fd, {
[this]() { _readReports(); }, [this]() { _readReports(); },
[this]() { _valid = false; }, [this]() { _valid = false; },
@ -105,6 +147,14 @@ int16_t RawDevice::productId() const {
return _dev_info.pid; 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) { std::vector<uint8_t> RawDevice::getReportDescriptor(const std::string& path) {
int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK); int fd = ::open(path.c_str(), O_RDWR | O_NONBLOCK);
if (fd == -1) if (fd == -1)
@ -150,15 +200,17 @@ void RawDevice::sendReport(const std::vector<uint8_t>& report) {
printf("\n"); printf("\n");
} }
if (write(_fd, report.data(), report.size()) == -1)
throw std::system_error(errno, std::system_category(), for (int i = 0; i < max_write_tries && write(_fd, report.data(), report.size()) == -1; ++i) {
"sendReport write failed"); auto err = errno;
if (err != EPIPE)
throw std::system_error(err, std::system_category(),
"sendReport write failed");
}
} }
EventHandlerLock<RawDevice> RawDevice::addEventHandler(RawEventHandler handler) { EventHandlerLock<RawDevice> RawDevice::addEventHandler(RawEventHandler handler) {
std::unique_lock<std::shared_mutex> lock(_event_handlers->mutex); return {_event_handlers, _event_handlers->add(std::forward<RawEventHandler>(handler))};
_event_handlers->list.emplace_front(std::move(handler));
return {_event_handlers, _event_handlers->list.cbegin()};
} }
void RawDevice::_readReports() { void RawDevice::_readReports() {
@ -181,8 +233,5 @@ void RawDevice::_readReports() {
} }
void RawDevice::_handleEvent(const std::vector<uint8_t>& report) { void RawDevice::_handleEvent(const std::vector<uint8_t>& report) {
std::shared_lock<std::shared_mutex> lock(_event_handlers->mutex); _event_handlers->run_all(report);
for (auto& handler : _event_handlers->list)
if (handler.condition(report))
handler.callback(report);
} }

View File

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

View File

@ -63,7 +63,7 @@ RemapButton::RemapButton(Device* dev) : DeviceFeature(dev),
if ((action->reprogFlags() & hidpp20::ReprogControls::RawXYDiverted) && if ((action->reprogFlags() & hidpp20::ReprogControls::RawXYDiverted) &&
(!_reprog_controls->supportsRawXY() || (!_reprog_controls->supportsRawXY() ||
!(info.additionalFlags & hidpp20::ReprogControls::RawXY))) !(info.additionalFlags & hidpp20::ReprogControls::RawXY)))
logPrintf(WARN, "%s: Cannot divert raw XY movements for CID 0x%02x", logPrintf(WARN, "%s: 'Cannot divert raw XY movements for CID 0x%02x",
_device->name().c_str(), info.controlID); _device->name().c_str(), info.controlID);
report.flags |= action->reprogFlags(); report.flags |= action->reprogFlags();

View File

@ -18,6 +18,7 @@
#include <util/task.h> #include <util/task.h>
#include <queue> #include <queue>
#include <optional> #include <optional>
#include <cassert>
using namespace logid; using namespace logid;
using namespace std::chrono; using namespace std::chrono;
@ -35,18 +36,38 @@ static std::priority_queue<task, std::vector<task>, task_less> tasks {};
static std::mutex task_mutex {}; static std::mutex task_mutex {};
static std::condition_variable task_cv {}; static std::condition_variable task_cv {};
static std::atomic_bool workers_init = false; static std::atomic_bool workers_init = false;
static std::atomic_bool workers_run = false;
[[noreturn]] static void worker() { void stop_workers() {
std::unique_lock lock(task_mutex); std::unique_lock lock(task_mutex);
while (true) { if (workers_init) {
task_cv.wait(lock, []() { return !tasks.empty(); }); workers_run = false;
lock.unlock();
task_cv.notify_all();
/* Wait for all workers to end */
lock.lock();
}
}
void worker() {
std::unique_lock lock(task_mutex);
while (workers_run) {
task_cv.wait(lock, []() { return !tasks.empty() || !workers_run; });
if (!workers_run)
break;
/* top task is in the future, wait */ /* top task is in the future, wait */
if (tasks.top().time >= system_clock::now()) { if (tasks.top().time >= system_clock::now()) {
auto wait = tasks.top().time - system_clock::now(); auto wait = tasks.top().time - system_clock::now();
task_cv.wait_for(lock, wait, []() { task_cv.wait_for(lock, wait, []() {
return !tasks.empty() && (tasks.top().time < system_clock::now()); return (!tasks.empty() && (tasks.top().time < system_clock::now())) ||
!workers_run;
}); });
if (!workers_run)
break;
} }
if (!tasks.empty()) { if (!tasks.empty()) {
@ -67,11 +88,15 @@ static std::atomic_bool workers_init = false;
void logid::init_workers(int worker_count) { void logid::init_workers(int worker_count) {
std::lock_guard lock(task_mutex); std::lock_guard lock(task_mutex);
assert(!workers_init);
for (int i = 0; i < worker_count; ++i) for (int i = 0; i < worker_count; ++i)
std::thread(&worker).detach(); std::thread(&worker).detach();
workers_init = true; workers_init = true;
workers_run = true;
atexit(&stop_workers);
} }
void logid::run_task(std::function<void()> function) { void logid::run_task(std::function<void()> function) {