// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "edl.h" #include "watch.h" #define MAX_USBFS_BULK_SIZE (16*1024) enum { EDL_STATE_START, EDL_STATE_OPENED, EDL_STATE_CLOSED, }; struct edl { const char *serial; const char *dev_path; void *data; struct edl_ops *ops; void (*present)(bool present); int state; struct udev_monitor *mon; }; static int parse_usb_desc(int usbfd) { const struct usb_interface_descriptor *ifc; const struct usb_endpoint_descriptor *ept; const struct usb_device_descriptor *dev; const struct usb_config_descriptor *cfg; const struct usb_descriptor_header *hdr; unsigned type; unsigned k; unsigned l; ssize_t n; char *ptr; char *end; char desc[1024]; n = read(usbfd, desc, sizeof(desc)); if (n < 0) return n; ptr = desc; end = ptr + n; dev = (void *)ptr; ptr += dev->bLength; if (ptr >= end || dev->bDescriptorType != USB_DT_DEVICE) return -EINVAL; if (dev->idVendor != 0x05c6) return -ENOENT; if (dev->idProduct != 0x9008) return -ENOENT; cfg = (void *)ptr; ptr += cfg->bLength; if (ptr >= end || cfg->bDescriptorType != USB_DT_CONFIG) return -EINVAL; for (k = 0; k < cfg->bNumInterfaces; k++) { if (ptr >= end) return -EINVAL; do { ifc = (void *)ptr; if (ifc->bLength < USB_DT_INTERFACE_SIZE) return -EINVAL; ptr += ifc->bLength; } while (ptr < end && ifc->bDescriptorType != USB_DT_INTERFACE); for (l = 0; l < ifc->bNumEndpoints; l++) { if (ptr >= end) return -EINVAL; do { ept = (void *)ptr; if (ept->bLength < USB_DT_ENDPOINT_SIZE) return -EINVAL; ptr += ept->bLength; } while (ptr < end && ept->bDescriptorType != USB_DT_ENDPOINT); type = ept->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; if (type != USB_ENDPOINT_XFER_BULK) continue; if (ptr >= end) break; hdr = (void *)ptr; if (hdr->bDescriptorType == USB_DT_SS_ENDPOINT_COMP) ptr += USB_DT_SS_EP_COMP_SIZE; } if (ifc->bInterfaceClass != 0xff) continue; if (ifc->bInterfaceSubClass != 0xff) continue; if (ifc->bInterfaceProtocol != 0xff && ifc->bInterfaceProtocol != 16 && ifc->bInterfaceProtocol != 17) continue; /* TODO: check serial number */ return 0; } return -ENOENT; } static int handle_edl_add(struct edl *edl, struct udev_device *dev) { const char *dev_path; const char *dev_node; int usbfd; int ret; dev_path = udev_device_get_devpath(dev); dev_node = udev_device_get_devnode(dev); usbfd = open(dev_node, O_RDWR); if (usbfd < 0) return usbfd; ret = parse_usb_desc(usbfd); if (ret < 0) { close(usbfd); return ret; } edl->dev_path = strdup(dev_path); if (edl->present) edl->present(true); return 0; } static int handle_udev_event(int fd, void *data) { struct edl *edl = data; struct udev_device* dev; const char *dev_path; const char *action; dev = udev_monitor_receive_device(edl->mon); action = udev_device_get_action(dev); dev_path = udev_device_get_devpath(dev); if (!action || !dev_path) goto unref_dev; if (!strcmp(action, "add")) { handle_edl_add(edl, dev); } else if (!strcmp(action, "remove")) { if (!edl->dev_path || strcmp(dev_path, edl->dev_path)) goto unref_dev; edl->dev_path = NULL; if (edl->present) edl->present(false); } unref_dev: udev_device_unref(dev); return 0; } struct edl *edl_open(const char *serial, void (*present)(bool present)) { struct edl *edl; struct udev* udev; int fd; struct udev_enumerate* udev_enum; struct udev_list_entry* first, *item; udev = udev_new(); if (!udev) err(1, "udev_new() failed"); edl = calloc(1, sizeof(struct edl)); if (!edl) err(1, "failed to allocate edl structure"); edl->serial = serial; edl->present = present; edl->mon = udev_monitor_new_from_netlink(udev, "udev"); udev_monitor_filter_add_match_subsystem_devtype(edl->mon, "usb", NULL); udev_monitor_enable_receiving(edl->mon); fd = udev_monitor_get_fd(edl->mon); watch_add_readfd(fd, handle_udev_event, edl); udev_enum = udev_enumerate_new(udev); udev_enumerate_add_match_subsystem(udev_enum, "usb"); udev_enumerate_scan_devices(udev_enum); first = udev_enumerate_get_list_entry(udev_enum); udev_list_entry_foreach(item, first) { const char *path; struct udev_device *dev; path = udev_list_entry_get_name(item); dev = udev_device_new_from_syspath(udev, path); handle_edl_add(edl, dev); } udev_enumerate_unref(udev_enum); return edl; }