/****************************************************************************** @file usb_linux.c @brief read and write usb devices. DESCRIPTION QFirehoe Tool for USB and PCIE of Quectel wireless cellular modules. INITIALIZATION AND SEQUENCING REQUIREMENTS None. --------------------------------------------------------------------------- Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. Quectel Wireless Solution Proprietary and Confidential. --------------------------------------------------------------------------- ******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 20) #include #else #include #endif #include #include #include #include "usb_linux.h" int edl_pcie_mhifd = -1; int switch_to_edl_mode(void *usb_handle); extern uint32_t inet_addr(const char *); #define MAX_USBFS_BULK_IN_SIZE (4 * 1024) #define EC20_MAX_INF 4 #define MKDEV(__ma, __mi) (((__ma & 0xfff) << 8) | (__mi & 0xff) | ((__mi & 0xfff00) << 12)) struct quectel_usb_device { char devname[64]; int desc; int ttyfd; int idVendor; int idProduct; uint8_t bNumInterfaces; uint8_t intr_ep[EC20_MAX_INF]; uint8_t bulk_ep_in[EC20_MAX_INF]; uint8_t bulk_ep_out[EC20_MAX_INF]; int wMaxPacketSize[EC20_MAX_INF]; int control[EC20_MAX_INF][2]; }; static struct quectel_usb_device quectel_9x07; static int tcp_socket_fd = -1; static int usb_dm_interface = 0; static int strStartsWith(const char *line, const char *prefix) { if (!prefix || prefix[0] == '\0') return 1; for ( ; *line != '\0' && *prefix != '\0' ; line++, prefix++) { if (*line != *prefix) { return 0; } } return *prefix == '\0'; } static int strEndsWith(const char *line, const char *suffix) { size_t a, b; if (!suffix || suffix[0] == '\0') return 1; a = strlen(line); b = strlen(suffix); return (a >= b) && (strcmp(line + (a -b), suffix) == 0); } static int quectel_get_sysinfo_by_uevent(const char *uevent, MODULE_SYS_INFO *pSysInfo) { FILE *fp; char line[MAX_PATH]; memset(pSysInfo, 0x00, sizeof(MODULE_SYS_INFO)); fp = fopen(uevent, "r"); if (fp == NULL) { dbg_time("fail to fopen %s, errno: %d (%s)\n", uevent, errno, strerror(errno)); return 0; } //dbg_time("%s\n", uevent); while (fgets(line, sizeof(line), fp)) { if (line[strlen(line) - 1] == '\n' || line[strlen(line) - 1] == '\r') { line[strlen(line) - 1] = '\0'; } //dbg_time("%s\n", line); if (strStartsWith(line, "MAJOR=")) { pSysInfo->MAJOR = atoi(&line[strlen("MAJOR=")]); } else if (strStartsWith(line, "MINOR=")) { pSysInfo->MINOR = atoi(&line[strlen("MINOR=")]); } else if (strStartsWith(line, "DEVNAME=")) { if(pSysInfo) strncpy(pSysInfo->DEVNAME, &line[strlen("DEVNAME=")], sizeof(pSysInfo->DEVNAME)); } else if (strStartsWith(line, "DEVTYPE=")) { strncpy(pSysInfo->DEVTYPE, &line[strlen("DEVTYPE=")], sizeof(pSysInfo->DEVTYPE)); } else if (strStartsWith(line, "PRODUCT=")) { strncpy(pSysInfo->PRODUCT, &line[strlen("PRODUCT=")], sizeof(pSysInfo->PRODUCT)); } } fclose(fp); return 1; } // the return value is the number of quectel modules int auto_find_quectel_modules(char *module_sys_path, unsigned size) { const char *base = "/sys/bus/usb/devices"; DIR *busdir = NULL; struct dirent *de = NULL; int count = 0; busdir = opendir(base); if (busdir == NULL) return -1; while ((de = readdir(busdir))) { static char uevent[MAX_PATH]; static MODULE_SYS_INFO sysinfo; if (!isdigit(de->d_name[0])) continue; snprintf(uevent, sizeof(uevent), "%.24s/%.16s/uevent", base, de->d_name); if (!quectel_get_sysinfo_by_uevent(uevent, &sysinfo)) continue; if (sysinfo.MAJOR != 189) continue; //dbg_time("MAJOR=%d, MINOR=%d, DEVNAME=%s, DEVTYPE=%s, PRODUCT=%s\n", // sysinfo.MAJOR, sysinfo.MINOR, sysinfo.DEVNAME, sysinfo.DEVTYPE, sysinfo.PRODUCT); if (sysinfo.DEVTYPE[0] == '\0' || strStartsWith(sysinfo.DEVTYPE, "usb_device") == 0) continue; if (sysinfo.PRODUCT[0] == '\0') { continue; } if (!(strStartsWith(sysinfo.PRODUCT, "2c7c/") || strStartsWith(sysinfo.PRODUCT, "5c6/9008") || strStartsWith(sysinfo.PRODUCT, "5c6/901f") || strStartsWith(sysinfo.PRODUCT, "5c6/9091") || strStartsWith(sysinfo.PRODUCT, "3763/3c93/318"))) { continue; } if ((strStartsWith(sysinfo.PRODUCT, "2c7c/6") || strStartsWith(sysinfo.PRODUCT, "2c7c/8")) && (sysinfo.PRODUCT[strlen("2c7c/6000")] == '/')) //skip ASR&HISI modules continue; snprintf(module_sys_path, size, "%.24s/%s", base, de->d_name); count++; dbg_time("[%d] %s %s\n", count, module_sys_path, sysinfo.PRODUCT); } closedir(busdir); return count; } void quectel_get_ttyport_by_syspath(const char *module_sys_path, char *module_port_name, unsigned size) { char infname[256]; DIR *infdir = NULL; struct dirent *de = NULL; module_port_name[0] = '\0'; sprintf(infname, "%s:1.%d", module_sys_path, usb_dm_interface); infdir = opendir(infname); if (infdir == NULL) return; while ((de = readdir(infdir))) { if (strStartsWith(de->d_name, "ttyUSB")) { snprintf(module_port_name, size, "/dev/%s", de->d_name); break; } else if (!strncmp(de->d_name, "tty", strlen("tty"))) { sprintf(infname, "%s:1.%d/tty", module_sys_path, usb_dm_interface); closedir(infdir); infdir = opendir(infname); if (infdir == NULL) break; } } if (infdir) closedir(infdir); } static void quectel_fixup_sysport(const char *module_port_name, char *sysport, unsigned size) { char syspath[MAX_PATH+16]; const char *sys_base = "/sys/class/tty"; DIR *sys_dir = NULL; struct dirent *dev = NULL; sysport[0] = '\0'; sys_dir = opendir(sys_base); if (!sys_dir) { dbg_time("fail to opendir('%s'), errno: %d (%s)\n", sys_base, errno, strerror(errno)); return; } while (NULL != (dev = readdir(sys_dir))) { if (!strncasecmp("ttyUSB", dev->d_name, strlen("ttyUSB"))) { MODULE_SYS_INFO sysinfo; snprintf(syspath, sizeof(syspath), "%.24s/%.16s/uevent", sys_base, dev->d_name); if (quectel_get_sysinfo_by_uevent(syspath, &sysinfo)) { struct stat buf; dev_t devt; devt = makedev(sysinfo.MAJOR, sysinfo.MINOR); if(!stat(module_port_name, &buf) && buf.st_rdev == devt) { snprintf(sysport, size, "/sys/class/tty/%.16s", dev->d_name); break; } } } } closedir(sys_dir); } void quectel_get_syspath_name_by_ttyport(const char *module_port_name, char *module_sys_path, unsigned size) { char syspath[MAX_PATH]; char sysport[64]; int count; char *pchar = NULL; char dm_tty[24]; snprintf(dm_tty, sizeof(dm_tty), ":1.%d/tty", usb_dm_interface); module_sys_path[0] = '\0'; snprintf(sysport, sizeof(sysport), "/sys/class/tty/%.48s", &module_port_name[strlen("/dev/")]); if(access(sysport, F_OK) && errno == ENOENT) { quectel_fixup_sysport(module_port_name, sysport, sizeof(sysport));//query real name } if(access(sysport, F_OK) && errno == ENOENT) return; count = readlink(sysport, syspath, sizeof(syspath) - 1); if (count < (int)strlen(dm_tty)) return; //ttyUSB0 -> ../../devices/soc0/soc/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb1/1-1/1-1:1.0/ttyUSB0/tty/ttyUSB0 pchar = strstr(syspath, dm_tty); if (pchar == NULL) return; *pchar = '\0'; while (*pchar != '/') pchar--; snprintf(module_sys_path, size, "/sys/bus/usb/devices/%.232s", pchar + 1); } static void quectel_get_usb_device_info(const char *module_sys_path, struct quectel_usb_device *udev) { static unsigned char devdesc[1024]; size_t desclength, len; char devname[MAX_PATH]; int desc_fd; __u8 bInterfaceNumber = 0; int dev_mknod_and_delete_after_use = 0; MODULE_SYS_INFO sysinfo; snprintf(devname, sizeof(devname), "%.248s/%s", module_sys_path, "uevent"); if (!quectel_get_sysinfo_by_uevent(devname, &sysinfo)) return; snprintf(devname, sizeof(devname), "/dev/%s", sysinfo.DEVNAME); if (access(devname, R_OK) && errno_nodev()) { //maybe Linux have create /sys/ device, but not ready to create /dev/ device. usleep(100*1000); } if (access(devname, R_OK) && errno_nodev()) { char *p = strstr(devname+strlen("/dev/"), "/"); while (p) { p[0] = '_'; p = strstr(p, "/"); } if (mknod(devname, S_IFCHR|0666, MKDEV(sysinfo.MAJOR, sysinfo.MINOR))) { devname[1] = 't'; devname[2] = 'm'; devname[3] = 'p'; if (mknod(devname, S_IFCHR|0666, MKDEV(sysinfo.MAJOR, sysinfo.MINOR))) { dbg_time("Fail to mknod %s, errno : %d (%s)\n", devname, errno, strerror(errno)); return; } } dev_mknod_and_delete_after_use = 1; } desc_fd = open(devname, O_RDWR | O_NOCTTY); if (dev_mknod_and_delete_after_use) { remove(devname); } if (desc_fd <= 0) { dbg_time("fail to open %s, errno: %d (%s)\n", devname, errno, strerror(errno)); return; } desclength = read(desc_fd, devdesc, sizeof(devdesc)); len = 0; while (len < desclength) { struct usb_descriptor_header *h = (struct usb_descriptor_header *)(&devdesc[len]); if (h->bLength == sizeof(struct usb_device_descriptor) && h->bDescriptorType == USB_DT_DEVICE) { struct usb_device_descriptor *device = (struct usb_device_descriptor *)h; udev->idVendor = device->idVendor; udev->idProduct = device->idProduct; dbg_time("P: %s idVendor=%04x idProduct=%04x\n", devname, device->idVendor, device->idProduct); } else if (h->bLength == sizeof(struct usb_config_descriptor) && h->bDescriptorType == USB_DT_CONFIG) { struct usb_config_descriptor *config = (struct usb_config_descriptor *)h; dbg_time("C: %s bNumInterfaces: %d\n", devname, config->bNumInterfaces); udev->bNumInterfaces = config->bNumInterfaces; } else if (h->bLength == sizeof(struct usb_interface_descriptor) && h->bDescriptorType == USB_DT_INTERFACE) { struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)h; dbg_time("I: If#= %d Alt= %d #EPs= %d Cls=%02x Sub=%02x Prot=%02x\n", interface->bInterfaceNumber, interface->bAlternateSetting, interface->bNumEndpoints, interface->bInterfaceClass, interface->bInterfaceSubClass, interface->bInterfaceProtocol); bInterfaceNumber = interface->bInterfaceNumber; } else if (h->bLength == USB_DT_ENDPOINT_SIZE && h->bDescriptorType == USB_DT_ENDPOINT) { if (bInterfaceNumber < EC20_MAX_INF) { struct usb_endpoint_descriptor *endpoint = (struct usb_endpoint_descriptor *)h; dbg_time("E: Ad=%02x Atr=%02x MxPS= %d Ivl=%dms\n", endpoint->bEndpointAddress, endpoint->bmAttributes, endpoint->wMaxPacketSize, endpoint->bInterval); if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK) { if (endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) udev->bulk_ep_in[bInterfaceNumber] = endpoint->bEndpointAddress; else udev->bulk_ep_out[bInterfaceNumber] = endpoint->bEndpointAddress; udev->wMaxPacketSize[bInterfaceNumber] = endpoint->wMaxPacketSize; } else if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT){ udev->intr_ep[bInterfaceNumber] = endpoint->bEndpointAddress; } } } else { } len += h->bLength; } if (len == desclength) { strcpy(udev->devname, devname); udev->desc = desc_fd; } usb_dm_interface = 0; if ((udev->bulk_ep_in[usb_dm_interface] == 0 && udev->bulk_ep_in[usb_dm_interface] == 0) || (udev->idVendor == 0x2c7c && udev->idProduct == 0x0127) //EM05CEFC-LNV Laptop || (udev->idVendor == 0x2c7c && udev->idProduct == 0x0514) //EG060K-EA || (udev->idVendor == 0x2c7c && udev->idProduct == 0x0310) //EM05-CN Laptop || (udev->idVendor == 0x2c7c && udev->idProduct == 0x030a) //EM05-G Laptop || (udev->idVendor == 0x2c7c && udev->idProduct == 0x0309) //EM05E-EDU Laptop || (udev->idVendor == 0x2c7c && udev->idProduct == 0x030d)) //EM05G-FCCL Laptop { if ((udev->idVendor == 0x2c7c && udev->idProduct == 0x0127) //EM05CEFC-LNV Laptop || (udev->idVendor == 0x2c7c && udev->idProduct == 0x0310) //EM05-CN Laptop || (udev->idVendor == 0x2c7c && udev->idProduct == 0x030a) //EM05-G Laptop || (udev->idVendor == 0x2c7c && udev->idProduct == 0x0309) //EM05E-EDU Laptop || (udev->idVendor == 0x2c7c && udev->idProduct == 0x030d)) //EM05G-FCCL Laptop usb_dm_interface = 3; if (udev->idVendor == 0x2c7c && udev->idProduct == 0x0514) //EG060K-EA usb_dm_interface = 2; } } static int usbfs_bulk_write(struct quectel_usb_device *udev, const void *data, int len, int timeout_msec, int need_zlp) { struct usbdevfs_urb bulk; struct usbdevfs_urb *urb = &bulk; int n = -1; int bInterfaceNumber = usb_dm_interface; (void)timeout_msec; //if (urb->type == 0) { memset(urb, 0, sizeof(struct usbdevfs_urb)); urb->type = USBDEVFS_URB_TYPE_BULK; urb->endpoint = udev->bulk_ep_out[bInterfaceNumber]; } urb->status = -1; urb->buffer = (void *)data; urb->buffer_length = len; urb->usercontext = urb; if (need_zlp && (len%udev->wMaxPacketSize[bInterfaceNumber]) == 0) { //dbg_time("USBDEVFS_URB_ZERO_PACKET\n"); #ifndef USBDEVFS_URB_ZERO_PACKET #define USBDEVFS_URB_ZERO_PACKET 0x40 #endif urb->flags = USBDEVFS_URB_ZERO_PACKET; } else { urb->flags = 0; } do { n = ioctl(udev->desc, USBDEVFS_SUBMITURB, urb); } while((n < 0) && (errno == EINTR)); if (n != 0) { dbg_time("inf[%d] USBDEVFS_SUBMITURB %d/%d, errno = %d (%s)\n", bInterfaceNumber, n, urb->buffer_length, errno, strerror(errno)); return -1; } do { urb = NULL; n = ioctl(udev->desc, USBDEVFS_REAPURB, &urb); } while((n < 0) && (errno == EINTR)); if (n != 0) { dbg_time("inf[%d] ep_out %d/%d, errno = %d (%s)\n", bInterfaceNumber, n, urb->buffer_length, errno, strerror(errno)); } //dbg_time("[ urb @%p status = %d, actual = %d ]\n", urb, urb->status, urb->actual_length); if (urb && urb->status == 0 && urb->actual_length) return urb->actual_length; return -1; } static int poll_wait(int poll_fd, short events, int timeout_msec) { struct pollfd pollfds[] = {{poll_fd, events, 0}}; int ret; do { ret = poll(pollfds, 1, timeout_msec); } while(ret == -1 && errno == EINTR); if (ret == 1 && (pollfds[0].revents & (events))) return 0; else if (ret == 0) {//timeout dbg_time("poll_wait events=%s msec=%d timeout\n", (events & POLLIN) ? "POLLIN" : "POLLOUT", timeout_msec); return ETIMEDOUT; } return EIO; } static int usbfs_bulk_read(struct quectel_usb_device *udev, void *pbuf, int len, int timeout) { struct usbdevfs_bulktransfer bulk; int n = -1; int bInterfaceNumber = usb_dm_interface; if (len < 512) { dbg_time("%s len=%d is too short\n", __func__, len); return 0; } bulk.ep = udev->bulk_ep_in[bInterfaceNumber]; bulk.len = (len > MAX_USBFS_BULK_IN_SIZE) ? MAX_USBFS_BULK_IN_SIZE : len; bulk.data = (void *)pbuf; bulk.timeout = timeout; n = ioctl(udev->desc, USBDEVFS_BULK, &bulk); if( n <= 0 ) { if (errno == ETIMEDOUT) { dbg_time("inf[%d] ep_in %d/%d, errno = %d (%s), timeout=%d\n", bInterfaceNumber, n, bulk.len, errno, strerror(errno), timeout); n = 0; } else dbg_time("inf[%d] ep_in %d/%d, errno = %d (%s)\n", bInterfaceNumber, n, bulk.len, errno, strerror(errno)); } return n ; } static int qtcp_connect(const char *port_name, int *idVendor, int *idProduct, int *interfaceNum) { int fd = -1; char *tcp_host = strdup(port_name); char *tcp_port = strchr(tcp_host, ':'); struct sockaddr_in sockaddr; TLV_USB tlv_usb; dbg_time("%s port_name = %s\n", __func__, port_name); if (tcp_port == NULL) return -1; *tcp_port++ = '\0'; if (atoi(tcp_port) < 1 || atoi(tcp_port) > 0xFFFF) return -1; fd = socket(AF_INET, SOCK_STREAM, 0); if (fd <= 0) { dbg_time("Device could not be socket: Linux System Errno: %s\n", strerror (errno)); return -1; } memset(&sockaddr, 0, sizeof(sockaddr)); sockaddr.sin_family = AF_INET; sockaddr.sin_addr.s_addr = inet_addr(tcp_host); sockaddr.sin_port = htons(atoi(tcp_port)); free(tcp_host); if (connect(fd, (struct sockaddr *) &sockaddr, sizeof(sockaddr)) < 0) { close(fd); dbg_time("Device could not be connect: Linux System Errno: %s\n", strerror (errno)); return -1; } //block read, untill usb2tcp tell me the usb device information memset(&tlv_usb, 0x00, sizeof(tlv_usb)); if (read(fd, &tlv_usb, sizeof(tlv_usb)) == -1) { }; *idVendor = tlv_usb.idVendor; *idProduct = tlv_usb.idProduct; *interfaceNum = tlv_usb.interfaceNum; dbg_time("idVendor=%04x, idProduct=%04x, interfaceNum=%d\n", *idVendor, *idProduct, *interfaceNum); return fd; } static int qtcp_read(int fd, void *pbuf, int size, int timeout_msec) { static TLV tlv = {Q_USB2TCP_VERSION, 0}; int cur = 0; int len; if (tlv.length == 0) { len = read(fd, &tlv, sizeof(tlv)); if (len != sizeof(tlv)) { dbg_time("%s read=%d, errno: %d (%s)\n", __func__, len, errno, strerror(errno)); return 0; } if (tlv.tag != Q_USB2TCP_VERSION) { dbg_time("%s tlv->tag=0x%x\n", __func__, tlv.tag); return 0; } } if (size > tlv.length) size = tlv.length; tlv.length -= size; while (cur < size) { if (poll_wait(fd, POLLIN, timeout_msec)) break; len = read(fd, (uint8_t *)pbuf+cur, size-cur); if (len > 0) { cur += len; } else { dbg_time("%s read=%d, errno: %d (%s)\n", __func__, len, errno, strerror(errno)); break; } } if (cur != size) { dbg_time("%s cur=%d, size=%d\n", __func__, cur, size); } return cur; } static int qtcp_write(int fd, void*pbuf, int size, int timeout_msec) { TLV tlv = {Q_USB2TCP_VERSION, size}; int cur = 0; int len; len = write(fd, &tlv, sizeof(tlv)); if (len != sizeof(tlv)) { dbg_time("%s write=%d, errno: %d (%s)\n", __func__, len, errno, strerror(errno)); return 0; } while (cur < size) { if (poll_wait(fd, POLLOUT, timeout_msec)) break; len = write(fd, (uint8_t *)pbuf+cur, size-cur); if (len > 0) { cur += len; } else { dbg_time("%s write=%d, errno: %d (%s)\n", __func__, len, errno, strerror(errno)); break; } } if (cur != size) { dbg_time("%s cur=%d, size=%d\n", __func__, cur, size); } return cur; } struct usbfs_getdriver { unsigned int interface; char driver[255 + 1]; }; struct usbfs_ioctl { int ifno; /* interface 0..N ; negative numbers reserved */ int ioctl_code; /* MUST encode size + direction of data so the * macros in give correct values */ void *data; /* param buffer (in, or out) */ }; #define IOCTL_USBFS_DISCONNECT _IO('U', 22) #define IOCTL_USBFS_CONNECT _IO('U', 23) int usbfs_is_kernel_driver_alive(int fd, int ifnum) { struct usbfs_getdriver getdrv; getdrv.interface = ifnum; if (ioctl(fd, USBDEVFS_GETDRIVER, &getdrv) < 0) { if (errno != ENODATA) dbg_time("%s ioctl USBDEVFS_GETDRIVER failed, errno: %d (%s)\n", __func__, errno, strerror(errno)); return 0; } dbg_time("%s find interface %d has match the driver %s\n", __func__, ifnum, getdrv.driver); return 1; } void usbfs_detach_kernel_driver(int fd, int ifnum) { struct usbfs_ioctl operate; operate.data = NULL; operate.ifno = ifnum; operate.ioctl_code = IOCTL_USBFS_DISCONNECT; if (ioctl(fd, USBDEVFS_IOCTL, &operate) < 0) { dbg_time("%s detach kernel driver failed\n", __func__); } else { dbg_time("%s detach kernel driver success\n", __func__); } } #define KVERSION(j,n,p) ((j)*1000000 + (n)*1000 + (p)) static struct utsname utsname; /* for the kernel version */ static int ql_get_kernel_version(void) { int osmaj, osmin, ospatch; int kernel_version; uname(&utsname); osmaj = osmin = ospatch = 0; sscanf(utsname.release, "%d.%d.%d", &osmaj, &osmin, &ospatch); kernel_version = KVERSION(osmaj, osmin, ospatch); return kernel_version; } static int detect_xhci_usb_zero_packet_bug_not_fix(const char *module_sys_path) { char buf[256]; int tmp; char *driver; tmp = snprintf(buf, sizeof(buf), "/sys/bus/usb/devices/usb%c/../driver", module_sys_path[strlen("/sys/bus/usb/devices/")]); driver = buf + (++tmp); *driver = '\0'; tmp = readlink(buf, driver, sizeof(buf) - tmp); dbg_time("tmp=%s, driver=%s\n", buf, driver); if (tmp <= 0) return 0; if (!strstr(driver, "xhci")) return 0; tmp = ql_get_kernel_version(); if (tmp >= KVERSION(4,3,0)) return 0; dbg_time("WARNNING ON File:%s Function:%s Line:%d\n", __FILE__, __func__, __LINE__); dbg_time("The module attach to XHCI controller, but your kernel verison less than V4.3.0\n"); dbg_time("Please make sure your kernel had apply patch 'usb: xhci: Add support for URB_ZERO_PACKET to bulk/sg transfers'\n"); dbg_time("https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/drivers/usb/host/xhci-ring.c?id=4758dcd19a7d9ba9610b38fecb93f65f56f86346\n"); sleep(2); //sleep 2 seconds, make sure FAE/customers can notice this warnning. return 1; } void *qusb_noblock_open(const char *module_sys_path, int *idVendor, int *idProduct, int *interfaceNum) { struct termios ios; int retval; int fd = -1; struct quectel_usb_device *udev = &quectel_9x07; *idVendor = *idProduct = *interfaceNum = 0; tcp_socket_fd = -1; if (module_sys_path && module_sys_path[0] == '/') { char port_name[64]; memset(udev, 0, sizeof(struct quectel_usb_device)); quectel_get_usb_device_info(module_sys_path, udev); if (udev->desc <= 0) return NULL; quectel_get_ttyport_by_syspath(module_sys_path, port_name, sizeof(port_name)); detect_xhci_usb_zero_packet_bug_not_fix(module_sys_path); *idVendor = udev->idVendor; *idProduct = udev->idProduct; *interfaceNum = udev->bNumInterfaces; if (port_name[0] == '\0' || (port_name[0] != '\0' && access(port_name, R_OK)) || (udev->idVendor == 0x05c6 && udev->idProduct == 0x9008)) { int bInterfaceNumber = usb_dm_interface; if (usbfs_is_kernel_driver_alive(udev->desc, bInterfaceNumber)) { usbfs_detach_kernel_driver(udev->desc, bInterfaceNumber); } retval = ioctl(udev->desc, USBDEVFS_CLAIMINTERFACE, &bInterfaceNumber); if(retval != 0) { dbg_time("Fail to claim interface %d, errno: %d (%s)\n", bInterfaceNumber, errno, strerror(errno)); if (udev->idVendor == 0x05c6) { int n; struct { char infname[255 * 2]; char driver[255 * 2]; } *pl; const char *driver = NULL; pl = (typeof(pl)) malloc(sizeof(*pl)); snprintf(pl->infname, sizeof(pl->infname), "%.255s:1.%d/driver", module_sys_path, usb_dm_interface); n = readlink(pl->infname, pl->driver, sizeof(pl->driver)); if (n > 0) { pl->driver[n] = '\0'; while (pl->driver[n] != '/') n--; driver = (&pl->driver[n+1]); } dbg_time("Error: when module in 'Emergency download mode', should not register any usb driver\n"); if (driver) dbg_time("Error: it register to usb driver ' %s ' now, should delete 05c6&9008 from the source file of this driver\n", driver); if (driver && !strcmp(driver, "qcserial")) dbg_time("Delete 05c6&9008 from 'drivers/usb/serial/qcserial.c' or disable qcserial from kernel config\n"); qusb_noblock_close(udev); free(pl); } return NULL; } udev->ttyfd = -1; return udev; } else if (!access(port_name, R_OK)) { dbg_time("%s port_name = %s\n", __func__, port_name); fd = open (port_name, O_RDWR | O_SYNC); if (fd <= 0) { dbg_time("Device %s could not be open: Linux System Errno: %s", port_name, strerror (errno)); return NULL; } retval = tcgetattr (fd, &ios); if (-1 == retval) { dbg_time("ermio settings could not be fetched Linux System Error:%s", strerror (errno)); return NULL; } cfmakeraw (&ios); cfsetispeed(&ios, B115200); cfsetospeed(&ios, B115200); retval = tcsetattr (fd, TCSANOW, &ios); if (-1 == retval) { dbg_time("Device could not be configured: Linux System Errno: %s", strerror (errno)); } udev->ttyfd = fd; fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) | O_NONBLOCK); return udev; } else { dbg_time("fail to access %s errno: %d (%s)\n", port_name, errno, strerror(errno)); } } else { fd = qtcp_connect(module_sys_path, idVendor, idProduct, interfaceNum); if (fd > 0) { tcp_socket_fd = fd; fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) | O_NONBLOCK); return &tcp_socket_fd; } } return NULL; } int qusb_noblock_close(void *handle) { struct quectel_usb_device *udev = &quectel_9x07; if (handle == &tcp_socket_fd) { close(tcp_socket_fd); tcp_socket_fd = -1; } if (handle == udev && udev->ttyfd > 0) { close(udev->ttyfd); close(udev->desc); } else if (handle == udev && udev->desc > 0) { int bInterfaceNumber = usb_dm_interface; ioctl(udev->desc, USBDEVFS_RELEASEINTERFACE, &bInterfaceNumber); close(udev->desc); } else if (handle == &edl_pcie_mhifd && edl_pcie_mhifd > 0) { close(edl_pcie_mhifd); edl_pcie_mhifd = -1; } memset(udev, 0, sizeof(*udev)); return 0; } int qusb_use_usbfs_interface(const void *handle) { struct quectel_usb_device *udev = &quectel_9x07; return (handle == udev && udev->ttyfd <= 0 && udev->desc > 0); } int qusb_noblock_read(const void *handle, void *pbuf, int max_size, int min_size, int timeout_msec) { struct quectel_usb_device *udev = &quectel_9x07; int cur = 0; int poll_ret = 0; if (min_size == 0) min_size = 1; if (timeout_msec == 0) timeout_msec = 3000; #if 0 //depend on your worst net speed if (handle == &tcp_socket_fd) { if (timeout_msec > 1000) //before sahala&firebose, we allow read timeout occurs timeout_msec = 120*1000; } #endif while (cur < min_size) { int len = 0; if (handle == &tcp_socket_fd) { if ((poll_ret = poll_wait(tcp_socket_fd, POLLIN, timeout_msec))) break; len = qtcp_read(tcp_socket_fd, (uint8_t *)pbuf+cur, max_size-cur, timeout_msec); } else if (handle == udev && udev->ttyfd > 0) { if ((poll_ret = poll_wait(udev->ttyfd, POLLIN, timeout_msec))) break; len = read(udev->ttyfd, (uint8_t *)pbuf+cur, max_size-cur); } else if (handle == udev && udev->desc > 0) { len = usbfs_bulk_read(udev, (uint8_t *)pbuf+cur, max_size-cur, timeout_msec); } else if (handle == &edl_pcie_mhifd && edl_pcie_mhifd > 0) { if ((poll_ret = poll_wait(edl_pcie_mhifd, POLLIN, timeout_msec))) break; len = read(edl_pcie_mhifd, (uint8_t *)pbuf+cur, max_size-cur); } else { break; } if (len > 0) { cur += len; } else { dbg_time("%s read=%d, errno: %d (%s)\n", __func__, len, errno, strerror(errno)); break; } } if (poll_ret == EIO) return -1; else if (poll_ret == ETIMEDOUT) return cur; if (cur < min_size) { dbg_time("%s cur=%d, min_size=%d\n", __func__, cur, min_size); } return cur; } int qusb_noblock_write(const void *handle, void *pbuf, int max_size, int min_size, int timeout_msec, int need_zlp) { struct quectel_usb_device *udev = &quectel_9x07; int cur = 0; if (min_size == 0) min_size = 1; if (timeout_msec == 0) timeout_msec = 3000; #if 0 //depend on your worst net speed if (handle == &tcp_socket_fd) { timeout_msec = 120*1000; } #endif while (cur < min_size) { int len = 0; if (handle == &tcp_socket_fd) { if (poll_wait(tcp_socket_fd, POLLOUT, timeout_msec)) break; len = qtcp_write(tcp_socket_fd, (uint8_t *)pbuf+cur, max_size-cur, timeout_msec); } else if (handle == udev && udev->ttyfd > 0) { if (poll_wait(udev->ttyfd, POLLOUT, timeout_msec)) break; len = write(udev->ttyfd, (uint8_t *)pbuf+cur, max_size-cur); } else if (handle == udev && udev->desc > 0) { len = usbfs_bulk_write(udev, (uint8_t *)pbuf+cur, max_size-cur, timeout_msec, need_zlp); } else if (handle == &edl_pcie_mhifd && edl_pcie_mhifd > 0) { if (poll_wait(edl_pcie_mhifd, POLLOUT, timeout_msec)) break; len = write(edl_pcie_mhifd, (uint8_t *)pbuf+cur, max_size-cur); } else { break; } if (len > 0) { cur += len; } else { dbg_time("%s write=%d, errno: %d (%s)\n", __func__, len, errno, strerror(errno)); break; } } if (cur < min_size) { dbg_time("%s cur=%d, min_size=%d\n", __func__, cur, min_size); } return cur; } int qfile_find_file(const char *dir, const char *prefix, const char *suffix, char** filename) { DIR *pdir; struct dirent* ent = NULL; pdir = opendir(dir); *filename = NULL; if(pdir) { while((ent = readdir(pdir)) != NULL) { if (strStartsWith(ent->d_name, prefix) && strEndsWith(ent->d_name, suffix)) { dbg_time("find '%s'\n", ent->d_name); *filename = strdup(ent->d_name); break; } } } closedir(pdir); return *filename != NULL; } const char * firehose_get_time(void) { static char time_buf[50]; struct timeval tv; static int s_start_msec = -1; int now_msec, cost_msec; gettimeofday (&tv, NULL); now_msec = tv.tv_sec * 1000; now_msec += (tv.tv_usec + 500) / 1000; if (s_start_msec == -1) { s_start_msec = now_msec; } cost_msec = now_msec - s_start_msec; sprintf(time_buf, "[%03d.%03d]", cost_msec/1000, cost_msec%1000); return time_buf; } // void dbg_time (const char *fmt, ...) { // va_list args; // va_start(args, fmt); // static char line[2048]; // snprintf(line, sizeof(line), "%s ", firehose_get_time()); // vsnprintf(line + strlen(line), sizeof(line) - strlen(line), fmt, args); // fprintf(stdout, "%s", line); // fflush(stdout); // } int qpcie_open(const char *firehose_dir, const char *firehose_mbn) { int bhifd, edlfd, diagfd; long ret; FILE *fp; BHI_INFO_TYPE *bhi_info = malloc(sizeof(BHI_INFO_TYPE)); char prog_firehose_sdx24[256+32]; size_t filesize; void *filebuf; snprintf(prog_firehose_sdx24, sizeof(prog_firehose_sdx24), "%.255s/%s", firehose_dir, firehose_mbn); fp = fopen(prog_firehose_sdx24, "rb"); if (fp ==NULL) { dbg_time("fail to fopen %s, errno: %d (%s)\n", prog_firehose_sdx24, errno, strerror(errno)); error_return(); } fseek(fp, 0, SEEK_END); filesize = ftell(fp); fseek(fp, 0, SEEK_SET); filebuf = malloc(sizeof(filesize)+filesize); memcpy(filebuf, &filesize, sizeof(filesize)); if (fread((uint8_t *)filebuf+sizeof(filesize), 1, filesize, fp) == (size_t)0) { }; fclose(fp); diagfd = open("/dev/mhi_DIAG", O_RDWR | O_NOCTTY); if (diagfd > 0) { int edl_retry = 30; //SDX55 require long time by now 20190412 void *usb_handle = &edl_pcie_mhifd; edl_pcie_mhifd = diagfd; while (access("/dev/mhi_DIAG", R_OK) == 0 && edl_retry-- > 0) { dbg_time("switch_to_edl_mode\n"); switch_to_edl_mode(usb_handle); sleep(1); } close(diagfd); edl_pcie_mhifd = -1; } sleep(1); //see https://ticket.quectel.com/browse/FAE-39737 bhifd = open("/dev/mhi_BHI", O_RDWR | O_NOCTTY); if (bhifd <= 0) { dbg_time("fail to open %s, errno: %d (%s)\n", "/dev/mhi_BHI", errno, strerror(errno)); error_return(); } ret = ioctl(bhifd, IOCTL_BHI_GETDEVINFO, bhi_info); if (ret) { dbg_time("fail to ioctl IOCTL_BHI_GETDEVINFO, errno: %d (%s)\n", errno, strerror(errno)); error_return(); } dbg_time("bhi_ee = %d\n", bhi_info->bhi_ee); if (bhi_info->bhi_ee != MHI_EE_EDL) { dbg_time("bhi_ee is not MHI_EE_EDL\n"); close(bhifd); free(filebuf); error_return(); } free(bhi_info); ret = ioctl(bhifd, IOCTL_BHI_WRITEIMAGE, filebuf); if (ret) { dbg_time("fail to ioctl IOCTL_BHI_GETDEVINFO, errno: %d (%s)\n", errno, strerror(errno)); error_return(); } close(bhifd); free(filebuf); sleep(1); edlfd = open("/dev/mhi_EDL", O_RDWR | O_NOCTTY); if (edlfd <= 0) { dbg_time("fail to access %s, errno: %d (%s)\n", "/dev/mhi_EDL", errno, strerror(errno)); error_return(); } edl_pcie_mhifd = edlfd; return 0; } int usbmon_fd = -1; int usbmon_logfile_fd = -1; void *catch_log(void *arg) { int nreads = 0; char tbuff[256]; size_t off = strlen("[999.999] "); (void)arg; tbuff[off - 1] = ' '; while(1) { nreads = read(usbmon_fd, tbuff + off, sizeof(tbuff) - off); if (nreads == -1 && errno == EINTR) continue; if (nreads <= 0) break; tbuff[off + nreads] = '\0'; memcpy(tbuff, firehose_get_time(), off - 1); if (write(usbmon_logfile_fd, tbuff, strlen(tbuff)) == -1) { }; } return NULL; } int ql_capture_usbmon_log(const char* usbmon_logfile) { const char *usbmon_path = "/sys/kernel/debug/usb/usbmon/0u"; pthread_t pt; pthread_attr_t attr; if (access("/sys/kernel/debug/usb", F_OK)) { dbg_time("debugfs is not mount, please execute \"mount -t debugfs none_debugs /sys/kernel/debug\"\n"); return -1; } if (access("/sys/kernel/debug/usb/usbmon", F_OK)) { dbg_time("usbmon is not load, please execute \"modprobe usbmon\" or \"insmod usbmon.ko\"\n"); return -1; } usbmon_fd = open(usbmon_path, O_RDONLY); if (usbmon_fd < 0) { dbg_time("open %s error(%d) (%s)\n", usbmon_path, errno, strerror(errno)); return -1; } usbmon_logfile_fd = open(usbmon_logfile, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (usbmon_logfile_fd < 0) { dbg_time("open %s error(%d) (%s)\n", usbmon_logfile, errno, strerror(errno)); close(usbmon_fd); return -1; } pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&pt, &attr, catch_log, NULL); return 0; } void ql_stop_usbmon_log() { if (usbmon_logfile_fd > 0) close(usbmon_logfile_fd); if (usbmon_fd > 0) close(usbmon_fd); }