2023-04-11 21:15:01 +08:00

1198 lines
38 KiB
C

/******************************************************************************
@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 <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <termios.h>
#include <dirent.h>
#include <pthread.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/utsname.h>
#include <netinet/in.h>
#include <linux/un.h>
#include <linux/usbdevice_fs.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 20)
#include <linux/usb/ch9.h>
#else
#include <linux/usb_ch9.h>
#endif
#include <sys/time.h>
#include <stdarg.h>
#include <sys/sysmacros.h>
#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 <asm/ioctl.h> 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);
}