co_usb
Loading...
Searching...
No Matches
device_acceptor.hpp
Go to the documentation of this file.
1
6#pragma once
7
10#include "co_usb/usb_error.hpp"
13#include <algorithm>
14#include <boost/capy/concept/io_awaitable.hpp>
15#include <boost/capy/continuation.hpp>
16#include <boost/capy/ex/async_mutex.hpp>
17#include <boost/capy/ex/executor_ref.hpp>
18#include <boost/capy/ex/io_env.hpp>
19#include <boost/capy/ex/run.hpp>
20#include <boost/capy/ex/run_async.hpp>
21#include <boost/capy/ex/this_coro.hpp>
22#include <boost/capy/io_result.hpp>
23#include <boost/capy/io_task.hpp>
24#include <boost/capy/task.hpp>
25#include <coroutine>
26#include <libusb.h>
27#include <memory_resource>
28#include <mutex>
29#include <stop_token>
30#include <system_error>
31#include <utility>
32
33namespace co_usb::hotplug
34{
35
55{
56 explicit device_acceptor (boost::capy::executor_ref exec,
57 std::pmr::memory_resource *memres = std::pmr::get_default_resource())
58 : m_ev_handler_ref(::co_usb::ev::detail::get_handler_service(exec).handler()),
59 m_usb_ctx(::co_usb::ev::detail::get_handler_service(exec).usb_context()),
60 m_memres(memres), m_resumptions(memres), m_arrived_devices(memres), m_handle(0)
61 {
62 }
63
65 {
66 shutdown();
67 }
68
69 auto shutdown () -> void
70 {
71 libusb_hotplug_callback_handle handle{0};
72 {
73 std::unique_lock<std::mutex> lock{m_mutex};
74 handle = std::exchange(m_handle, 0);
75 if (handle == 0)
76 return;
77 }
78 libusb_hotplug_deregister_callback(m_usb_ctx, handle);
79
80 std::unique_lock lock{m_mutex};
81 for (resumption_t *res : m_resumptions)
82 {
83 lock.unlock();
84 res->op_res->ec = std::make_error_code(std::errc::operation_canceled);
85 res->env->executor.post(res->cont);
86 lock.lock();
87 }
88 m_resumptions.clear();
89 m_arrived_devices.clear();
90 }
91
92 auto bind (device_triplet triplet, std::error_code &ec) -> void
93 {
94 m_filter = triplet;
95 ec.clear();
96 }
97
98 [[nodiscard]] auto bind (device_triplet triplet) -> std::error_code
99 {
100 if (m_handle != 0)
101 {
102 return std::make_error_code(std::errc::operation_in_progress);
103 }
104 m_filter = triplet;
105 return {};
106 }
107
108 auto listen () -> std::error_code
109 {
110 if (m_handle != 0)
111 {
112 return std::make_error_code(std::errc::operation_in_progress);
113 }
114 libusb_hotplug_callback_handle handle;
115 auto r = libusb_hotplug_register_callback(
116 m_usb_ctx, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
117 LIBUSB_HOTPLUG_ENUMERATE, m_filter.vid, m_filter.pid, m_filter.dev_class,
118 [] ([[maybe_unused]] libusb_context *ctx, libusb_device *dev, libusb_hotplug_event ev,
119 void *user_data) -> int
120 {
121 libusb_device_descriptor dev_desc;
122 libusb_get_device_descriptor(dev, &dev_desc);
123 device_triplet const triplet = triplet_from_descriptor(dev_desc);
124 device_ref dev_ref{dev};
125 device_acceptor &self = *static_cast<device_acceptor *>(user_data);
126 if (ev == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
127 {
128 std::unique_lock lock{self.m_mutex};
129 auto it = std::ranges::find_if(
130 self.m_arrived_devices, [&] (dev_info_t const &info)
131 { return wildcard_triplet_comparator()(info.triplet, triplet); });
132 if (it != self.m_arrived_devices.end())
133 {
134 self.m_arrived_devices.erase(it);
135 }
136 }
137 else
138 {
139 std::unique_lock lock{self.m_mutex};
140 if (self.m_resumptions.empty())
141 {
142 self.m_arrived_devices.emplace_back(triplet, device_ref{dev});
143 return 0;
144 }
145 resumption_t *r = self.m_resumptions.front();
146 r->op_res->dev_ref = device_ref{dev};
147 r->env->executor.post(r->cont);
148 self.m_resumptions.erase(self.m_resumptions.begin());
149 }
150 return 0;
151 },
152 this, &handle);
153 if (r != LIBUSB_SUCCESS)
154 {
155 return make_usb_error_code(static_cast<usb_error>(r));
156 }
157 m_handle = handle;
158 return {};
159 }
160
161 auto close () -> std::error_code
162 {
163 if (m_handle == 0)
164 {
165 return std::make_error_code(std::errc::invalid_argument);
166 }
167 libusb_hotplug_deregister_callback(m_usb_ctx, m_handle);
168 return {};
169 }
170
171 auto accept () -> boost::capy::io_task<device_ref>
172 {
173 auto exec = co_await boost::capy::this_coro::executor;
174 auto stop = co_await boost::capy::this_coro::stop_token;
175 auto alloc = co_await boost::capy::this_coro::frame_allocator;
176 op_result op_res{};
177 resumption_t res{};
178
179 auto stop_fn = [this, op_res_ptr = &op_res, res = &res] () mutable
180 {
181 std::unique_lock lock{m_mutex};
182 if (res->env && res->cont.h)
183 {
184 res->op_res->ec = std::make_error_code(std::errc::operation_canceled);
185 res->env->executor.post(res->cont);
186 auto it =
187 std::find_if(m_resumptions.begin(), m_resumptions.end(),
188 [res_ptr = res] (resumption_t *res) { return res == res_ptr; });
189 if (it != m_resumptions.end())
190 {
191 m_resumptions.erase(it);
192 }
193 }
194 return;
195 };
196
197 std::stop_callback stop_cb{stop, std::move(stop_fn)};
198 co_return co_await awaitable(this, &op_res, &res);
199 }
200
205
206 private:
207 struct op_result
208 {
209 device_ref dev_ref{};
210 std::error_code ec{};
211 };
212
213 struct resumption_t
214 {
215 boost::capy::io_env const *env{nullptr};
216 boost::capy::continuation cont{.h = nullptr};
217 op_result *op_res{nullptr};
218 };
219
220 struct dev_info_t
221 {
222 device_triplet triplet;
223 device_ref dev_ref;
224 };
225
226 struct awaitable
227 {
228 explicit awaitable (device_acceptor *acceptor, op_result *state, resumption_t *res)
229 : acceptor_ptr(acceptor), op_res(state), res(res)
230 {
231 }
232
233 inline bool await_ready ()
234 {
235 std::unique_lock lock{acceptor_ptr->m_mutex};
236 if (!acceptor_ptr->m_arrived_devices.empty())
237 {
238 op_res->dev_ref = acceptor_ptr->m_arrived_devices.back().dev_ref;
239 acceptor_ptr->m_arrived_devices.pop_back();
240 return true;
241 }
242 return false;
243 }
244
245 inline std::coroutine_handle<> await_suspend (std::coroutine_handle<> h,
246 boost::capy::io_env const *env)
247 {
248 if (env->stop_token.stop_requested())
249 {
250 std::unique_lock lock{acceptor_ptr->m_mutex};
251 op_res->ec = std::make_error_code(std::errc::operation_canceled);
252 return h;
253 }
254
255 std::unique_lock lock{acceptor_ptr->m_mutex};
256 if (!acceptor_ptr->m_arrived_devices.empty())
257 {
258 op_res->dev_ref = acceptor_ptr->m_arrived_devices.back().dev_ref;
259 acceptor_ptr->m_arrived_devices.pop_back();
260 return h;
261 }
262
263 *res = resumption_t{env, boost::capy::continuation{h}, op_res};
264 acceptor_ptr->m_resumptions.emplace_back(res);
265 return std::noop_coroutine();
266 }
267
268 inline boost::capy::io_result<device_ref> await_resume ()
269 {
270 std::unique_lock lock{acceptor_ptr->m_mutex};
271 if (op_res->ec)
272 {
273 return {op_res->ec, device_ref{}};
274 }
275 return {std::error_code{}, op_res->dev_ref};
276 }
277
278 device_acceptor *acceptor_ptr;
279 op_result *op_res;
280 resumption_t *res;
281 };
282
283 ev::event_handler_ref m_ev_handler_ref;
284 std::mutex m_mutex;
285 std::pmr::memory_resource *m_memres;
286
287 std::pmr::vector<resumption_t *> m_resumptions{};
288 std::pmr::vector<dev_info_t> m_arrived_devices{};
289
290 device_triplet m_filter{};
291 libusb_context *m_usb_ctx;
292 libusb_hotplug_callback_handle m_handle{0};
293};
294
295} // namespace co_usb::hotplug
Wrapper for nullable libusb_device that increments ref count on ctor and decrements on dtor.
An aggregate struct to pass to functions requiring device information.
Type-erased reference to any event handler.
Capy execution_context service wrapping a libusb event handler.
Definition hotplug_awaitable.hpp:18
Definition flag_type.hpp:6
usb_error
USB error enumeration.
Definition usb_error.hpp:20
std::error_code make_usb_error_code(usb_error e) noexcept
Definition usb_error.hpp:85
Wrapper for nullable libusb_device that increments ref count on ctor and decrements on dtor.
Definition device_ref.hpp:25
An aggregate struct to pass to functions requiring device information.
Definition device_triplet.hpp:21
int pid
Definition device_triplet.hpp:23
int dev_class
Definition device_triplet.hpp:24
int vid
Definition device_triplet.hpp:22
Asio/Corosio-like acceptor for devices via hotplug API.
Definition device_acceptor.hpp:55
auto listen() -> std::error_code
Definition device_acceptor.hpp:108
device_acceptor & operator=(device_acceptor &&)=delete
auto shutdown() -> void
Definition device_acceptor.hpp:69
device_acceptor(device_acceptor &&)=delete
auto accept() -> boost::capy::io_task< device_ref >
Definition device_acceptor.hpp:171
~device_acceptor()
Definition device_acceptor.hpp:64
auto bind(device_triplet triplet, std::error_code &ec) -> void
Definition device_acceptor.hpp:92
device_acceptor(const device_acceptor &)=delete
device_acceptor & operator=(const device_acceptor &)=delete
auto close() -> std::error_code
Definition device_acceptor.hpp:161
device_acceptor(boost::capy::executor_ref exec, std::pmr::memory_resource *memres=std::pmr::get_default_resource())
Definition device_acceptor.hpp:56
auto bind(device_triplet triplet) -> std::error_code
Definition device_acceptor.hpp:98
USB error enumeration.