]> mj.ucw.cz Git - home-hw.git/blob - ucw-libusb/usb-helper.c
Telegram: The same aiomqtt adjustments
[home-hw.git] / ucw-libusb / usb-helper.c
1 /*
2  *      LibUSB helpers for LibUCW
3  *
4  *      (c) 2022--2023 Martin Mares <mj@ucw.cz>
5  *
6  *      Based on USB handling in my USB-to-RS485 switch daemon.
7  */
8
9 #include <ucw/lib.h>
10 #include <ucw/log.h>
11 #include <ucw/clists.h>
12 #include <ucw/mainloop.h>
13 #include <ucw/stkstring.h>
14
15 #include <stdio.h>
16 #include <string.h>
17 #include <stdlib.h>
18
19 #include "usb-mainloop.h"
20 #include "usb-helper.h"
21
22 uint log_type_usb;
23
24 clist usb_device_list;
25
26 struct hotplug_request {
27         cnode n;
28         libusb_device *device;                  // referenced
29         int bus, dev;
30         char port[USB_PORT_NAME_LEN];
31         bool is_connect;
32 };
33
34 static clist hotplug_request_list;
35
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__)
38
39 void FORMAT_CHECK(printf,2,3) usb_error(struct usb_dev *u, const char *fmt, ...)
40 {
41         va_list args;
42         va_start(args, fmt);
43         const char *formatted_msg = stk_vprintf(fmt, args);
44         va_end(args);
45
46         USB_MSG(u, L_ERROR, "%s", formatted_msg);
47         u->state = USTATE_BROKEN;
48
49         // Cancel all pending transfers
50         usb_dev_cancel(u);
51 }
52
53 static void check_if_broken(struct usb_dev *u)
54 {
55         // USB devices cannot be closed from libusb callbacks, so we handle it here
56         if (u->state != USTATE_BROKEN)
57                 return;
58
59         // If there are still in-flight transfers, wait for them to complete
60         if (usb_dev_in_flight(u))
61                 return;
62
63         USB_DBG(u, "Leaving broken device");
64         u->state = USTATE_INIT;
65
66         if (u->bus < 0) {
67                 // Device already unplugged, so dismantle the USB context
68                 USB_MSG(u, L_INFO, "Disconnected");
69
70                 timer_del(&u->connect_timer);
71
72                 if (u->devh) {
73                         libusb_close(u->devh);
74                         u->devh = NULL;
75                 }
76
77                 usb_dev_cleanup(u);
78                 clist_remove(&u->n);
79                 xfree(u);
80         } else {
81                 // Re-connect later
82                 timer_add_rel(&u->connect_timer, 5000);
83         }
84 }
85
86 static void connect_handler(struct main_timer *timer)
87 {
88         struct usb_dev *u = timer->data;
89         int err;
90
91         timer_del(timer);
92
93         libusb_reset_device(u->devh);
94
95         if (err = libusb_claim_interface(u->devh, 0)) {
96                 usb_error(u, "Cannot claim interface: error %d", err);
97                 return;
98         }
99
100         u->state = USTATE_RUNNING;
101         usb_dev_connect(u);
102 }
103
104 static void hotplug_connect(struct hotplug_request *hr)
105 {
106         HR_DBG(hr, "Connected");
107
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)
111                         return;
112         }
113
114         struct usb_dev *u = xmalloc_zero(sizeof(*u));
115         u->bus = hr->bus;
116         u->dev = hr->dev;
117         strcpy(u->port, hr->port);
118         u->state = USTATE_INIT;
119
120         int err;
121
122         if (err = libusb_get_device_descriptor(hr->device, &u->desc)) {
123                 USB_MSG(u, L_ERROR, "Cannot read descriptors: error %d", err);
124                 goto out_early;
125         }
126
127         if (err = libusb_open(hr->device, &u->devh)) {
128                 USB_MSG(u, L_ERROR, "Cannot open device: error %d", err);
129                 goto out_early;
130         }
131
132         if (!usb_dev_init(u))
133                 goto out;
134
135         u->connect_timer.handler = connect_handler;
136         u->connect_timer.data = u;
137         timer_add_rel(&u->connect_timer, 0);
138         return;
139
140 out:
141         libusb_close(u->devh);
142 out_early:
143         xfree(u);
144 }
145
146 static void hotplug_disconnect(struct hotplug_request *hr)
147 {
148         HR_DBG(hr, "Disconnected");
149
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;
154                         usb_dev_cancel(u);
155                         return;
156                 }
157         }
158 }
159
160 static void handle_hotplug(void)
161 {
162         struct hotplug_request *hr;
163         while (hr = clist_remove_head(&hotplug_request_list)) {
164                 if (hr->is_connect)
165                         hotplug_connect(hr);
166                 else
167                         hotplug_disconnect(hr);
168                 libusb_unref_device(hr->device);
169                 xfree(hr);
170         }
171 }
172
173 static struct main_hook usb_hook;
174
175 static int usb_hook_handler(struct main_hook *hook UNUSED)
176 {
177         handle_hotplug();
178
179         CLIST_FOR_EACH(struct usb_dev *, u, usb_device_list)
180                 check_if_broken(u);
181         return HOOK_IDLE;
182 }
183
184 static int hotplug_callback(libusb_context *ctx UNUSED, libusb_device *device, libusb_hotplug_event event, void *user_data UNUSED)
185 {
186         // Called asynchronously, need to defer to the main loop
187
188         if (event != LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED && event != LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
189                 return 0;
190
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);
198
199         HR_DBG(hr, "Scheduled hotplug event");
200         return 0;
201 }
202
203 void usb_helper_init(uint vendor_id, uint device_id)
204 {
205         usb_init_mainloop();
206
207         log_type_usb = log_register_type("usb");
208
209         clist_init(&usb_device_list);
210
211         clist_init(&hotplug_request_list);
212         usb_hook.handler = usb_hook_handler;
213         hook_add(&usb_hook);
214
215         int err;
216         if (err = libusb_hotplug_register_callback(usb_ctx,
217                 LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
218                 LIBUSB_HOTPLUG_ENUMERATE,
219                 vendor_id,
220                 device_id,
221                 LIBUSB_HOTPLUG_MATCH_ANY,
222                 hotplug_callback,
223                 NULL,                   // no user data
224                 NULL                    // no need to store callback handle
225                 ))
226                 die("Cannot register libusb hotplug callback");
227 }