From: Martin Mares Date: Sun, 14 May 2023 12:34:53 +0000 (+0200) Subject: ucw-libusb: Added helper modules X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=d8b6db4a81191f35d3f2865c8fc4c7771f9a7538;p=home-hw.git ucw-libusb: Added helper modules --- diff --git a/ucw-libusb/usb-helper.c b/ucw-libusb/usb-helper.c new file mode 100644 index 0000000..c6a99e2 --- /dev/null +++ b/ucw-libusb/usb-helper.c @@ -0,0 +1,227 @@ +/* + * LibUSB helpers for LibUCW + * + * (c) 2022--2023 Martin Mares + * + * Based on USB handling in my USB-to-RS485 switch daemon. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 index 0000000..8a6a1c8 --- /dev/null +++ b/ucw-libusb/usb-helper.h @@ -0,0 +1,63 @@ +/* + * LibUSB helpers for LibUCW + * + * (c) 2022--2023 Martin Mares + */ + +#include + +#include +#include + +#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 index 0000000..0f02064 --- /dev/null +++ b/ucw-libusb/usb-mainloop.c @@ -0,0 +1,90 @@ +/* + * LibUSB over LibUCW Mainloop + * + * (c) 2014--2022 Martin Mares + * + * Originally developed as a part of the Ursary project. + */ + +#undef LOCAL_DEBUG + +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 index 0000000..c874f76 --- /dev/null +++ b/ucw-libusb/usb-mainloop.h @@ -0,0 +1,11 @@ +/* + * LibUSB over LibUCW Mainloop + * + * (c) 2014--2022 Martin Mares + */ + +#include + +extern libusb_context *usb_ctx; + +void usb_init_mainloop(void);