2 * LibUSB helpers for LibUCW
4 * (c) 2022--2023 Martin Mares <mj@ucw.cz>
6 * Based on USB handling in my USB-to-RS485 switch daemon.
11 #include <ucw/clists.h>
12 #include <ucw/mainloop.h>
13 #include <ucw/stkstring.h>
19 #include "usb-mainloop.h"
20 #include "usb-helper.h"
24 clist usb_device_list;
26 struct hotplug_request {
28 libusb_device *device; // referenced
30 char port[USB_PORT_NAME_LEN];
34 static clist hotplug_request_list;
36 #define HR_MSG(hp_req, level, fmt, ...) msg(level, "%s: " fmt, hp_req->port, ##__VA_ARGS__)
37 #define HR_DBG(hp_req, fmt, ...) msg(L_DEBUG | log_type_usb, "%s: " fmt, hp_req->port, ##__VA_ARGS__)
39 void FORMAT_CHECK(printf,2,3) usb_error(struct usb_dev *u, const char *fmt, ...)
43 const char *formatted_msg = stk_vprintf(fmt, args);
46 USB_MSG(u, L_ERROR, "%s", formatted_msg);
47 u->state = USTATE_BROKEN;
49 // Cancel all pending transfers
53 static void check_if_broken(struct usb_dev *u)
55 // USB devices cannot be closed from libusb callbacks, so we handle it here
56 if (u->state != USTATE_BROKEN)
59 // If there are still in-flight transfers, wait for them to complete
60 if (usb_dev_in_flight(u))
63 USB_DBG(u, "Leaving broken device");
64 u->state = USTATE_INIT;
67 // Device already unplugged, so dismantle the USB context
68 USB_MSG(u, L_INFO, "Disconnected");
70 timer_del(&u->connect_timer);
73 libusb_close(u->devh);
82 timer_add_rel(&u->connect_timer, 5000);
86 static void connect_handler(struct main_timer *timer)
88 struct usb_dev *u = timer->data;
93 libusb_reset_device(u->devh);
95 if (err = libusb_claim_interface(u->devh, 0)) {
96 usb_error(u, "Cannot claim interface: error %d", err);
100 u->state = USTATE_RUNNING;
104 static void hotplug_connect(struct hotplug_request *hr)
106 HR_DBG(hr, "Connected");
108 // We might get duplicate events, so ignore the event if the device is already known
109 CLIST_FOR_EACH(struct usb_dev *, u, usb_device_list) {
110 if (u->bus == hr->bus && u->dev == hr->dev)
114 struct usb_dev *u = xmalloc_zero(sizeof(*u));
117 strcpy(u->port, hr->port);
118 u->state = USTATE_INIT;
122 if (err = libusb_get_device_descriptor(hr->device, &u->desc)) {
123 USB_MSG(u, L_ERROR, "Cannot read descriptors: error %d", err);
127 if (err = libusb_open(hr->device, &u->devh)) {
128 USB_MSG(u, L_ERROR, "Cannot open device: error %d", err);
132 if (!usb_dev_init(u))
135 u->connect_timer.handler = connect_handler;
136 u->connect_timer.data = u;
137 timer_add_rel(&u->connect_timer, 0);
141 libusb_close(u->devh);
146 static void hotplug_disconnect(struct hotplug_request *hr)
148 HR_DBG(hr, "Disconnected");
150 CLIST_FOR_EACH(struct usb_dev *, u, usb_device_list) {
151 if (u->bus == hr->bus && u->dev == hr->dev) {
152 u->state = USTATE_BROKEN;
153 u->bus = u->dev = -1;
160 static void handle_hotplug(void)
162 struct hotplug_request *hr;
163 while (hr = clist_remove_head(&hotplug_request_list)) {
167 hotplug_disconnect(hr);
168 libusb_unref_device(hr->device);
173 static struct main_hook usb_hook;
175 static int usb_hook_handler(struct main_hook *hook UNUSED)
179 CLIST_FOR_EACH(struct usb_dev *, u, usb_device_list)
184 static int hotplug_callback(libusb_context *ctx UNUSED, libusb_device *device, libusb_hotplug_event event, void *user_data UNUSED)
186 // Called asynchronously, need to defer to the main loop
188 if (event != LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED && event != LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
191 struct hotplug_request *hr = xmalloc_zero(sizeof(*hr));
192 hr->device = libusb_ref_device(device);
193 hr->bus = libusb_get_bus_number(device);
194 hr->dev = libusb_get_device_address(device);
195 snprintf(hr->port, sizeof(hr->port), "usb%d.%d", hr->bus, hr->dev);
196 hr->is_connect = (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED);
197 clist_add_tail(&hotplug_request_list, &hr->n);
199 HR_DBG(hr, "Scheduled hotplug event");
203 void usb_helper_init(uint vendor_id, uint device_id)
207 log_type_usb = log_register_type("usb");
209 clist_init(&usb_device_list);
211 clist_init(&hotplug_request_list);
212 usb_hook.handler = usb_hook_handler;
216 if (err = libusb_hotplug_register_callback(usb_ctx,
217 LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
218 LIBUSB_HOTPLUG_ENUMERATE,
221 LIBUSB_HOTPLUG_MATCH_ANY,
223 NULL, // no user data
224 NULL // no need to store callback handle
226 die("Cannot register libusb hotplug callback");