]> mj.ucw.cz Git - home-hw.git/commitdiff
ucw-libusb: Added helper modules
authorMartin Mares <mj@ucw.cz>
Sun, 14 May 2023 12:34:53 +0000 (14:34 +0200)
committerMartin Mares <mj@ucw.cz>
Sun, 14 May 2023 12:34:53 +0000 (14:34 +0200)
ucw-libusb/usb-helper.c [new file with mode: 0644]
ucw-libusb/usb-helper.h [new file with mode: 0644]
ucw-libusb/usb-mainloop.c [new file with mode: 0644]
ucw-libusb/usb-mainloop.h [new file with mode: 0644]

diff --git a/ucw-libusb/usb-helper.c b/ucw-libusb/usb-helper.c
new file mode 100644 (file)
index 0000000..c6a99e2
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ *     LibUSB helpers for LibUCW
+ *
+ *     (c) 2022--2023 Martin Mares <mj@ucw.cz>
+ *
+ *     Based on USB handling in my USB-to-RS485 switch daemon.
+ */
+
+#include <ucw/lib.h>
+#include <ucw/log.h>
+#include <ucw/clists.h>
+#include <ucw/mainloop.h>
+#include <ucw/stkstring.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "usb-mainloop.h"
+#include "usb-helper.h"
+
+uint log_type_usb;
+
+clist usb_device_list;
+
+struct hotplug_request {
+       cnode n;
+       libusb_device *device;                  // referenced
+       int bus, dev;
+       char port[USB_PORT_NAME_LEN];
+       bool is_connect;
+};
+
+static clist hotplug_request_list;
+
+#define HR_MSG(hp_req, level, fmt, ...) msg(level, "%s: " fmt, hp_req->port, ##__VA_ARGS__)
+#define HR_DBG(hp_req, fmt, ...) msg(L_DEBUG | log_type_usb, "%s: " fmt, hp_req->port, ##__VA_ARGS__)
+
+void FORMAT_CHECK(printf,2,3) usb_error(struct usb_dev *u, const char *fmt, ...)
+{
+       va_list args;
+       va_start(args, fmt);
+       const char *formatted_msg = stk_vprintf(fmt, args);
+       va_end(args);
+
+       USB_MSG(u, L_ERROR, "%s", formatted_msg);
+       u->state = USTATE_BROKEN;
+
+       // Cancel all pending transfers
+       usb_dev_cancel(u);
+}
+
+static void check_if_broken(struct usb_dev *u)
+{
+       // USB devices cannot be closed from libusb callbacks, so we handle it here
+       if (u->state != USTATE_BROKEN)
+               return;
+
+       // If there are still in-flight transfers, wait for them to complete
+       if (usb_dev_in_flight(u))
+               return;
+
+       USB_DBG(u, "Leaving broken device");
+       u->state = USTATE_INIT;
+
+       if (u->bus < 0) {
+               // Device already unplugged, so dismantle the USB context
+               USB_MSG(u, L_INFO, "Disconnected");
+
+               timer_del(&u->connect_timer);
+
+               if (u->devh) {
+                       libusb_close(u->devh);
+                       u->devh = NULL;
+               }
+
+               usb_dev_cleanup(u);
+               clist_remove(&u->n);
+               xfree(u);
+       } else {
+               // Re-connect later
+               timer_add_rel(&u->connect_timer, 5000);
+       }
+}
+
+static void connect_handler(struct main_timer *timer)
+{
+       struct usb_dev *u = timer->data;
+       int err;
+
+       timer_del(timer);
+
+       libusb_reset_device(u->devh);
+
+       if (err = libusb_claim_interface(u->devh, 0)) {
+               usb_error(u, "Cannot claim interface: error %d", err);
+               return;
+       }
+
+       u->state = USTATE_RUNNING;
+       usb_dev_connect(u);
+}
+
+static void hotplug_connect(struct hotplug_request *hr)
+{
+       HR_DBG(hr, "Connected");
+
+       // We might get duplicate events, so ignore the event if the device is already known
+       CLIST_FOR_EACH(struct usb_dev *, u, usb_device_list) {
+               if (u->bus == hr->bus && u->dev == hr->dev)
+                       return;
+       }
+
+       struct usb_dev *u = xmalloc_zero(sizeof(*u));
+       u->bus = hr->bus;
+       u->dev = hr->dev;
+       strcpy(u->port, hr->port);
+       u->state = USTATE_INIT;
+
+       int err;
+
+       if (err = libusb_get_device_descriptor(hr->device, &u->desc)) {
+               USB_MSG(u, L_ERROR, "Cannot read descriptors: error %d", err);
+               goto out_early;
+       }
+
+       if (err = libusb_open(hr->device, &u->devh)) {
+               USB_MSG(u, L_ERROR, "Cannot open device: error %d", err);
+               goto out_early;
+       }
+
+       if (!usb_dev_init(u))
+               goto out;
+
+       u->connect_timer.handler = connect_handler;
+       u->connect_timer.data = u;
+       timer_add_rel(&u->connect_timer, 0);
+       return;
+
+out:
+       libusb_close(u->devh);
+out_early:
+       xfree(u);
+}
+
+static void hotplug_disconnect(struct hotplug_request *hr)
+{
+       HR_DBG(hr, "Disconnected");
+
+       CLIST_FOR_EACH(struct usb_dev *, u, usb_device_list) {
+               if (u->bus == hr->bus && u->dev == hr->dev) {
+                       u->state = USTATE_BROKEN;
+                       u->bus = u->dev = -1;
+                       usb_dev_cancel(u);
+                       return;
+               }
+       }
+}
+
+static void handle_hotplug(void)
+{
+       struct hotplug_request *hr;
+       while (hr = clist_remove_head(&hotplug_request_list)) {
+               if (hr->is_connect)
+                       hotplug_connect(hr);
+               else
+                       hotplug_disconnect(hr);
+               libusb_unref_device(hr->device);
+               xfree(hr);
+       }
+}
+
+static struct main_hook usb_hook;
+
+static int usb_hook_handler(struct main_hook *hook UNUSED)
+{
+       handle_hotplug();
+
+       CLIST_FOR_EACH(struct usb_dev *, u, usb_device_list)
+               check_if_broken(u);
+       return HOOK_IDLE;
+}
+
+static int hotplug_callback(libusb_context *ctx UNUSED, libusb_device *device, libusb_hotplug_event event, void *user_data UNUSED)
+{
+       // Called asynchronously, need to defer to the main loop
+
+       if (event != LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED && event != LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
+               return 0;
+
+       struct hotplug_request *hr = xmalloc_zero(sizeof(*hr));
+       hr->device = libusb_ref_device(device);
+       hr->bus = libusb_get_bus_number(device);
+       hr->dev = libusb_get_device_address(device);
+       snprintf(hr->port, sizeof(hr->port), "usb%d.%d", hr->bus, hr->dev);
+       hr->is_connect = (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED);
+       clist_add_tail(&hotplug_request_list, &hr->n);
+
+       HR_DBG(hr, "Scheduled hotplug event");
+       return 0;
+}
+
+void usb_helper_init(uint vendor_id, uint device_id)
+{
+       usb_init_mainloop();
+
+       log_type_usb = log_register_type("usb");
+
+       clist_init(&usb_device_list);
+
+       clist_init(&hotplug_request_list);
+       usb_hook.handler = usb_hook_handler;
+       hook_add(&usb_hook);
+
+       int err;
+       if (err = libusb_hotplug_register_callback(usb_ctx,
+               LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
+               LIBUSB_HOTPLUG_ENUMERATE,
+               vendor_id,
+               device_id,
+               LIBUSB_HOTPLUG_MATCH_ANY,
+               hotplug_callback,
+               NULL,                   // no user data
+               NULL                    // no need to store callback handle
+               ))
+               die("Cannot register libusb hotplug callback");
+}
diff --git a/ucw-libusb/usb-helper.h b/ucw-libusb/usb-helper.h
new file mode 100644 (file)
index 0000000..8a6a1c8
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ *     LibUSB helpers for LibUCW
+ *
+ *     (c) 2022--2023 Martin Mares <mj@ucw.cz>
+ */
+
+#include <libusb-1.0/libusb.h>
+
+#include <ucw/clists.h>
+#include <ucw/mainloop.h>
+
+#define USB_PORT_NAME_LEN 16
+
+struct device;         // Provided by the user
+
+enum usb_dev_state {
+       USTATE_INIT,
+       USTATE_RUNNING,
+       USTATE_BROKEN,
+};
+
+struct usb_dev {
+       cnode n;
+
+       int bus, dev;                           // -1 if device already unplugged
+       char port[USB_PORT_NAME_LEN];
+       enum usb_dev_state state;
+
+       struct main_timer connect_timer;
+
+       struct libusb_device_handle *devh;
+       struct libusb_device_descriptor desc;
+
+       struct device *device;
+};
+
+extern clist usb_dev_list;
+
+extern uint log_type_usb;
+
+#define USB_MSG(usb_dev, level, fmt, ...) msg(level, "%s: " fmt, usb_dev->port, ##__VA_ARGS__)
+#define USB_DBG(usb_dev, fmt, ...) msg(L_DEBUG | log_type_usb, "%s: " fmt, usb_dev->port, ##__VA_ARGS__)
+
+void usb_helper_init(uint vendor_id, uint device_id);
+void FORMAT_CHECK(printf,2,3) usb_error(struct usb_dev *u, const char *fmt, ...);
+
+/*** Callbacks provided by the user ***/
+
+// A new device was detected. Read its configuration and check if it can
+// be handled. Allocate transfers. But do not start them until usb_dev_connect().
+bool usb_dev_init(struct usb_dev *u);
+
+// Device was connected, start transfers.
+void usb_dev_connect(struct usb_dev *u);
+
+// An error has been detected, cancel all pending transfers.
+void usb_dev_cancel(struct usb_dev *u);
+
+// Are there any in-flight transfers?
+bool usb_dev_in_flight(struct usb_dev *u);
+
+// Clean up application state, free all usb transfers.
+void usb_dev_cleanup(struct usb_dev *u);
diff --git a/ucw-libusb/usb-mainloop.c b/ucw-libusb/usb-mainloop.c
new file mode 100644 (file)
index 0000000..0f02064
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ *     LibUSB over LibUCW Mainloop
+ *
+ *     (c) 2014--2022 Martin Mares <mj@ucw.cz>
+ *
+ *     Originally developed as a part of the Ursary project.
+ */
+
+#undef LOCAL_DEBUG
+
+#include <ucw/lib.h>
+#include <ucw/clists.h>
+#include <ucw/gary.h>
+#include <ucw/mainloop.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/poll.h>
+
+#include "usb-mainloop.h"
+
+libusb_context *usb_ctx;
+
+static struct main_file **usb_fds;
+
+static int usb_fd_ready(struct main_file *f UNUSED)
+{
+       DBG("USB: Handling events (ready on fd %d)", f->fd);
+       struct timeval tv = { 0, 0 };
+       int comp = 0;
+       int err = libusb_handle_events_timeout_completed(usb_ctx, &tv, &comp);
+       if (err < 0)
+               msg(L_ERROR, "libusb_handle_events: error %d", err);
+       return HOOK_IDLE;
+}
+
+static void usb_added_fd(int fd, short events, void *user_data UNUSED)
+{
+       if (fd >= (int) GARY_SIZE(usb_fds))
+               GARY_RESIZE(usb_fds, fd + 1);
+
+       struct main_file *f = usb_fds[fd];
+       if (!f) {
+               f = xmalloc_zero(sizeof(*f));
+               usb_fds[fd] = f;
+       } else if (file_is_active(f)) {
+               DBG("USB: Releasing fd %d", fd);
+               file_del(f);
+       }
+
+       DBG("USB: Adding fd %d with event mask %u", fd, events);
+       f->fd = fd;
+       f->read_handler = (events & POLLIN) ? usb_fd_ready : NULL;
+       f->write_handler = (events & POLLOUT) ? usb_fd_ready : NULL;
+       file_add(f);
+}
+
+static void usb_removed_fd(int fd, void *user_data UNUSED)
+{
+       DBG("USB: Releasing fd %d", fd);
+       ASSERT(fd < (int) GARY_SIZE(usb_fds));
+       struct main_file *f = usb_fds[fd];
+       ASSERT(f);
+       ASSERT(file_is_active(f));
+       file_del(f);
+}
+
+void usb_init_mainloop(void)
+{
+       int err;
+
+       // Initialize libusb
+       if ((err = libusb_init(&usb_ctx)) < 0)
+               die("libusb_init failed: error %d", err);
+
+       // Connect libusb to UCW mainloop
+
+       if (!libusb_pollfds_handle_timeouts(usb_ctx))
+               die("Unsupported version of libusb, please fix me");
+
+       GARY_INIT_ZERO(usb_fds, 0);
+       libusb_set_pollfd_notifiers(usb_ctx, usb_added_fd, usb_removed_fd, NULL);
+
+       const struct libusb_pollfd **fds = libusb_get_pollfds(usb_ctx);
+       ASSERT(fds);
+       for (int i=0; fds[i]; i++)
+               usb_added_fd(fds[i]->fd, fds[i]->events, NULL);
+       free(fds);
+}
diff --git a/ucw-libusb/usb-mainloop.h b/ucw-libusb/usb-mainloop.h
new file mode 100644 (file)
index 0000000..c874f76
--- /dev/null
@@ -0,0 +1,11 @@
+/*
+ *     LibUSB over LibUCW Mainloop
+ *
+ *     (c) 2014--2022 Martin Mares <mj@ucw.cz>
+ */
+
+#include <libusb.h>
+
+extern libusb_context *usb_ctx;
+
+void usb_init_mainloop(void);