xref: /OK3568_Linux_fs/app/forlinx/quectelCM/device.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1 /******************************************************************************
2   @file    device.c
3   @brief   QMI device dirver.
4 
5   DESCRIPTION
6   Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules.
7 
8   INITIALIZATION AND SEQUENCING REQUIREMENTS
9   None.
10 
11   ---------------------------------------------------------------------------
12   Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd.  All Rights Reserved.
13   Quectel Wireless Solution Proprietary and Confidential.
14   ---------------------------------------------------------------------------
15 ******************************************************************************/
16 #include <unistd.h>
17 #include <sys/types.h>
18 #include <fcntl.h>
19 #include <dirent.h>
20 #include <errno.h>
21 #include <strings.h>
22 #include <stdlib.h>
23 #include <limits.h>
24 #include <linux/usbdevice_fs.h>
25 #include <linux/types.h>
26 #include <sys/ioctl.h>
27 #include <sys/socket.h>
28 #include <net/if.h>
29 #include <time.h>
30 #include <pthread.h>
31 
32 #include "QMIThread.h"
33 #include "ethtool-copy.h"
34 
35 #define USB_CLASS_VENDOR_SPEC		0xff
36 #define USB_CLASS_COMM			2
37 #define USB_CDC_SUBCLASS_ACM			0x02
38 #define USB_CDC_SUBCLASS_ETHERNET       0x06
39 #define USB_CDC_SUBCLASS_NCM			0x0d
40 #define USB_CDC_SUBCLASS_MBIM			0x0e
41 #define USB_CLASS_WIRELESS_CONTROLLER	0xe0
42 
43 #define CM_MAX_PATHLEN 256
44 
45 #define CM_INVALID_VAL (~((int)0))
46 /* get first line from file 'fname'
47  * And convert the content into a hex number, then return this number */
file_get_value(const char * fname,int base)48 static int file_get_value(const char *fname, int base)
49 {
50     FILE *fp = NULL;
51     long num;
52     char buff[32 + 1] = {'\0'};
53     char *endptr = NULL;
54 
55     fp = fopen(fname, "r");
56     if (!fp) goto error;
57     if (fgets(buff, sizeof(buff), fp) == NULL)
58         goto error;
59     fclose(fp);
60 
61     num = (int)strtol(buff, &endptr, base);
62     if (errno == ERANGE && (num == LONG_MAX || num == LONG_MIN))
63         goto error;
64     /* if there is no digit in buff */
65     if (endptr == buff)
66         goto error;
67 
68     if (debug_qmi)
69         dbg_time("(%s) = %lx", fname, num);
70     return (int)num;
71 
72 error:
73     if (fp) fclose(fp);
74     return CM_INVALID_VAL;
75 }
76 
77 /*
78  * This function will search the directory 'dirname' and return the first child.
79  * '.' and '..' is ignored by default
80  */
dir_get_child(const char * dirname,char * buff,unsigned bufsize,const char * prefix)81 static int dir_get_child(const char *dirname, char *buff, unsigned bufsize, const char *prefix)
82 {
83     struct dirent *entptr = NULL;
84     DIR *dirptr;
85 
86     buff[0] = 0;
87 
88     dirptr = opendir(dirname);
89     if (!dirptr)
90         return -1;
91 
92     while ((entptr = readdir(dirptr))) {
93         if (entptr->d_name[0] == '.')
94             continue;
95         if (prefix && strlen(prefix) && strncmp(entptr->d_name, prefix, strlen(prefix)))
96             continue;
97         snprintf(buff, bufsize, "%s", entptr->d_name);
98         break;
99     }
100     closedir(dirptr);
101 
102     return 0;
103 }
104 
conf_get_val(const char * fname,const char * key)105 static int conf_get_val(const char *fname, const char *key)
106 {
107     char buff[128] = {'\0'};
108     FILE *fp = fopen(fname, "r");
109     if (!fp)
110         return CM_INVALID_VAL;
111 
112     while (fgets(buff, sizeof(buff)-1, fp)) {
113         char prefix[128] = {'\0'};
114         char tail[128] = {'\0'};
115         /* To eliminate cppcheck warnning: Assume string length is no more than 15 */
116         sscanf(buff, "%15[^=]=%15s", prefix, tail);
117         if (!strncasecmp(prefix, key, strlen(key))) {
118             fclose(fp);
119             return atoi(tail);
120         }
121     }
122 
123     fclose(fp);
124     return CM_INVALID_VAL;
125 }
126 
query_usb_device_info(char * path,struct usb_device_info * p)127 static void query_usb_device_info(char *path, struct usb_device_info *p) {
128     size_t offset = strlen(path);
129 
130     memset(p, 0, sizeof(*p));
131 
132     path[offset] = '\0';
133     strcat(path, "/idVendor");
134     p->idVendor = file_get_value(path, 16);
135 
136     if (p->idVendor == CM_INVALID_VAL)
137         return;
138 
139     path[offset] = '\0';
140     strcat(path, "/idProduct");
141     p->idProduct = file_get_value(path, 16);
142 
143     path[offset] = '\0';
144     strcat(path, "/busnum");
145     p->busnum = file_get_value(path, 10);
146 
147     path[offset] = '\0';
148     strcat(path, "/devnum");
149     p->devnum = file_get_value(path, 10);
150 
151     path[offset] = '\0';
152     strcat(path, "/bNumInterfaces");
153     p->bNumInterfaces = file_get_value(path, 10);
154 
155     path[offset] = '\0';
156 }
157 
query_usb_interface_info(char * path,struct usb_interface_info * p)158 static void query_usb_interface_info(char *path, struct usb_interface_info *p) {
159     char driver[128];
160     size_t offset = strlen(path);
161     int n;
162 
163     memset(p, 0, sizeof(*p));
164 
165     path[offset] = '\0';
166     strcat(path, "/bNumEndpoints");
167     p->bInterfaceClass = file_get_value(path, 16);
168 
169     path[offset] = '\0';
170     strcat(path, "/bInterfaceClass");
171     p->bInterfaceClass = file_get_value(path, 16);
172 
173     path[offset] = '\0';
174     strcat(path, "/bInterfaceSubClass");
175     p->bInterfaceSubClass = file_get_value(path, 16);
176 
177     path[offset] = '\0';
178     strcat(path, "/bInterfaceProtocol");
179     p->bInterfaceProtocol = file_get_value(path, 16);
180 
181     path[offset] = '\0';
182     strcat(path, "/driver");
183     n = readlink(path, driver, sizeof(driver));
184     if (n > 0) {
185         driver[n] = 0;
186         if (debug_qmi) dbg_time("driver -> %s", driver);
187         n = strlen(driver);
188         while (n > 0) {
189             if (driver[n] == '/')
190                 break;
191             n--;
192         }
193         strncpy(p->driver, &driver[n+1], sizeof(p->driver));
194     }
195 
196     path[offset] = '\0';
197 }
198 
detect_path_cdc_wdm_or_qcqmi(char * path,char * devname,size_t bufsize)199 static int detect_path_cdc_wdm_or_qcqmi(char *path, char *devname, size_t bufsize)
200 {
201     size_t offset = strlen(path);
202     char tmp[32];
203 
204     devname[0] = 0;
205 
206     if (access(path, R_OK))
207         return -1;
208 
209     path[offset] = '\0';
210     strcat(path, "/GobiQMI");
211     if (!access(path, R_OK))
212         goto step_1;
213 
214     path[offset] = '\0';
215     strcat(path, "/usbmisc");
216     if (!access(path, R_OK))
217         goto step_1;
218 
219     path[offset] = '\0';
220     strcat(path, "/usb");
221     if (!access(path, R_OK))
222         goto step_1;
223 
224     return -1;
225 
226 step_1:
227     /* get device(qcqmiX|cdc-wdmX) */
228     if (debug_qmi) dbg_time("%s", path);
229     dir_get_child(path, tmp, sizeof(tmp), NULL);
230     if (tmp[0] == '\0')
231         return -1;
232 
233     /* There is a chance that, no device(qcqmiX|cdc-wdmX) is generated. We should warn user about that! */
234     snprintf(devname, bufsize, "/dev/%s", tmp);
235     if (access(devname, R_OK | F_OK) && errno == ENOENT)
236     {
237         int major, minor;
238 
239         dbg_time("access %s  failed, errno: %d (%s)", devname, errno, strerror(errno));
240         strcat(path, "/");
241         strcat(path, tmp);
242         strcat(path, "/uevent");
243         major = conf_get_val(path, "MAJOR");
244         minor = conf_get_val(path, "MINOR");
245 
246         if(major == CM_INVALID_VAL || minor == CM_INVALID_VAL)
247             dbg_time("get major and minor failed");
248          else if (mknod(devname, S_IFCHR|0666, (((major & 0xfff) << 8) | (minor & 0xff) | ((minor & 0xfff00) << 12))))
249             dbg_time("please mknod %s c %d %d", devname, major, minor);
250     }
251 
252     return 0;
253 }
254 
255 /* To detect the device info of the modem.
256  * return:
257  *  FALSE -> fail
258  *  TRUE -> ok
259  */
qmidevice_detect(char * qmichannel,char * usbnet_adapter,unsigned bufsize,PROFILE_T * profile)260 BOOL qmidevice_detect(char *qmichannel, char *usbnet_adapter, unsigned bufsize, PROFILE_T *profile) {
261     struct dirent* ent = NULL;
262     DIR *pDir;
263     const char *rootdir = "/sys/bus/usb/devices";
264     struct {
265         char path[255*2];
266     } *pl;
267     pl = (typeof(pl)) malloc(sizeof(*pl));
268     memset(pl, 0x00, sizeof(*pl));
269 
270     pDir = opendir(rootdir);
271     if (!pDir) {
272         dbg_time("opendir %s failed: %s", rootdir, strerror(errno));
273         goto error;
274     }
275 
276     while ((ent = readdir(pDir)) != NULL)  {
277         char netcard[32+1] = {'\0'};
278         char devname[32+5] = {'\0'}; //+strlen("/dev/")
279         int netIntf;
280         int driver_type;
281 
282         if (ent->d_name[0] == 'u')
283             continue;
284 
285         snprintf(pl->path, sizeof(pl->path), "%s/%s", rootdir, ent->d_name);
286         query_usb_device_info(pl->path, &profile->usb_dev);
287         if (profile->usb_dev.idVendor == CM_INVALID_VAL)
288             continue;
289 
290         if (profile->usb_dev.idVendor == 0x2c7c || profile->usb_dev.idVendor == 0x05c6) {
291             dbg_time("Find %s/%s idVendor=0x%x idProduct=0x%x, bus=0x%03x, dev=0x%03x",
292                 rootdir, ent->d_name, profile->usb_dev.idVendor, profile->usb_dev.idProduct,
293                 profile->usb_dev.busnum, profile->usb_dev.devnum);
294         }
295 
296         /* get network interface */
297         /* NOTICE: there is a case that, bNumberInterface=6, but the net interface is 8 */
298         /* toolchain-mips_24kc_gcc-5.4.0_musl donot support GLOB_BRACE */
299         /* RG500U's MBIM is at inteface 0 */
300         for (netIntf = 0;  netIntf < (profile->usb_dev.bNumInterfaces + 8); netIntf++) {
301             snprintf(pl->path, sizeof(pl->path), "%s/%s:1.%d/net", rootdir, ent->d_name, netIntf);
302             dir_get_child(pl->path, netcard, sizeof(netcard), NULL);
303             if (netcard[0])
304                 break;
305         }
306 
307         if (netcard[0] == '\0') { //for centos 2.6.x
308             const char *n= "usb0";
309             const char *c = "qcqmi0";
310 
311             snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4/net:%s", rootdir, ent->d_name, n);
312             if (!access(pl->path, F_OK)) {
313                 snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4/GobiQMI:%s", rootdir, ent->d_name, c);
314                 if (!access(pl->path, F_OK)) {
315                     snprintf(qmichannel, bufsize, "/dev/%s", c);
316                     snprintf(usbnet_adapter, bufsize, "%s", n);
317                     snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4", rootdir, ent->d_name);
318                     query_usb_interface_info(pl->path, &profile->usb_intf);
319                     break;
320                 }
321             }
322         }
323 
324         if (netcard[0] == '\0')
325             continue;
326 
327         /* not '-i iface' */
328         if (usbnet_adapter[0] && strcmp(usbnet_adapter, netcard))
329             continue;
330 
331         snprintf(pl->path, sizeof(pl->path), "%s/%s:1.%d", rootdir, ent->d_name, netIntf);
332         query_usb_interface_info(pl->path, &profile->usb_intf);
333         driver_type = get_driver_type(profile);
334 
335         if (driver_type == SOFTWARE_QMI || driver_type == SOFTWARE_MBIM) {
336             detect_path_cdc_wdm_or_qcqmi(pl->path, devname, sizeof(devname));
337         }
338         else if (driver_type == SOFTWARE_ECM_RNDIS_NCM)
339         {
340             int atIntf = -1;
341 
342             if (profile->usb_dev.idVendor == 0x2c7c) { //Quectel
343                 if ((profile->usb_dev.idProduct&0xFF00) == 0x0900) //unisoc
344                     atIntf = 4;
345                 else if ((profile->usb_dev.idProduct&0xF000) == 0x8000) //hisi
346                     atIntf = 4;
347                 else if ((profile->usb_dev.idProduct&0xFF00) == 0x6000) //asr
348                     atIntf = 3;
349                 //else if ((profile->usb_dev.idProduct&0xF000) == 0x0000) //mdm
350                 //    atIntf = 2;
351             }
352 
353             if (atIntf != -1) {
354                 snprintf(pl->path, sizeof(pl->path), "%s/%s:1.%d", rootdir, ent->d_name, atIntf);
355                 dir_get_child(pl->path, devname, sizeof(devname), "tty");
356                 if (devname[0] && !strcmp(devname, "tty")) {
357                     snprintf(pl->path, sizeof(pl->path), "%s/%s:1.%d/tty", rootdir, ent->d_name, atIntf);
358                     dir_get_child(pl->path, devname, sizeof(devname), "tty");
359                 }
360             }
361         }
362 
363         if (netcard[0] && devname[0]) {
364             if (devname[0] == '/')
365                 snprintf(qmichannel, bufsize, "%s", devname);
366             else
367                 snprintf(qmichannel, bufsize, "/dev/%s", devname);
368             snprintf(usbnet_adapter, bufsize, "%s", netcard);
369             dbg_time("Auto find qmichannel = %s", qmichannel);
370             dbg_time("Auto find usbnet_adapter = %s", usbnet_adapter);
371             break;
372         }
373     }
374     closedir(pDir);
375 
376     if (qmichannel[0] == '\0' || usbnet_adapter[0] == '\0') {
377         dbg_time("network interface '%s' or qmidev '%s' is not exist", usbnet_adapter, qmichannel);
378         goto error;
379     }
380     free(pl);
381     return TRUE;
382 error:
383     free(pl);
384     return FALSE;
385 }
386 
mhidevice_detect(char * qmichannel,char * usbnet_adapter,PROFILE_T * profile)387 int mhidevice_detect(char *qmichannel, char *usbnet_adapter, PROFILE_T *profile) {
388     if (!access("/sys/class/net/pcie_mhi0", F_OK))
389         strcpy(usbnet_adapter, "pcie_mhi0");
390     else if (!access("/sys/class/net/rmnet_mhi0", F_OK))
391         strcpy(usbnet_adapter, "rmnet_mhi0");
392     else {
393         dbg_time("qmidevice_detect failed");
394         goto error;
395     }
396 
397     if (!access("/dev/mhi_MBIM", F_OK)) {
398         strcpy(qmichannel, "/dev/mhi_MBIM");
399         profile->software_interface = SOFTWARE_MBIM;
400     }
401     else if (!access("/dev/mhi_QMI0", F_OK)) {
402         strcpy(qmichannel, "/dev/mhi_QMI0");
403         profile->software_interface = SOFTWARE_QMI;
404     }
405     else {
406         goto error;
407     }
408 
409     return 1;
410 error:
411     return 0;
412 }
413 
get_driver_type(PROFILE_T * profile)414 int get_driver_type(PROFILE_T *profile)
415 {
416     /* QMI_WWAN */
417     if (profile->usb_intf.bInterfaceClass == USB_CLASS_VENDOR_SPEC) {
418         return SOFTWARE_QMI;
419     }
420     else if (profile->usb_intf.bInterfaceClass == USB_CLASS_COMM) {
421         switch (profile->usb_intf.bInterfaceSubClass) {
422             case USB_CDC_SUBCLASS_MBIM:
423                 return SOFTWARE_MBIM;
424             break;
425             case USB_CDC_SUBCLASS_ETHERNET:
426             case USB_CDC_SUBCLASS_NCM:
427                 return SOFTWARE_ECM_RNDIS_NCM;
428             break;
429             default:
430             break;
431         }
432     }
433     else if (profile->usb_intf.bInterfaceClass == USB_CLASS_WIRELESS_CONTROLLER) {
434         if (profile->usb_intf.bInterfaceSubClass == 1 && profile->usb_intf.bInterfaceProtocol == 3)
435             return SOFTWARE_ECM_RNDIS_NCM;
436     }
437 
438     dbg_time("%s unknow bInterfaceClass=%d, bInterfaceSubClass=%d", __func__,
439         profile->usb_intf.bInterfaceClass, profile->usb_intf.bInterfaceSubClass);
440     return DRV_INVALID;
441 }
442 
443 struct usbfs_getdriver
444 {
445     unsigned int interface;
446     char driver[255 + 1];
447 };
448 
449 struct usbfs_ioctl
450 {
451     int ifno;       /* interface 0..N ; negative numbers reserved */
452     int ioctl_code; /* MUST encode size + direction of data so the
453 			 * macros in <asm/ioctl.h> give correct values */
454     void *data;     /* param buffer (in, or out) */
455 };
456 
457 #define IOCTL_USBFS_DISCONNECT	_IO('U', 22)
458 #define IOCTL_USBFS_CONNECT	_IO('U', 23)
459 
usbfs_is_kernel_driver_alive(int fd,int ifnum)460 int usbfs_is_kernel_driver_alive(int fd, int ifnum)
461 {
462     struct usbfs_getdriver getdrv;
463     getdrv.interface = ifnum;
464     if (ioctl(fd, USBDEVFS_GETDRIVER, &getdrv) < 0) {
465         dbg_time("%s ioctl USBDEVFS_GETDRIVER failed, kernel driver may be inactive", __func__);
466         return 0;
467     }
468     dbg_time("%s find interface %d has match the driver %s", __func__, ifnum, getdrv.driver);
469     return 1;
470 }
471 
usbfs_detach_kernel_driver(int fd,int ifnum)472 void usbfs_detach_kernel_driver(int fd, int ifnum)
473 {
474     struct usbfs_ioctl operate;
475     operate.data = NULL;
476     operate.ifno = ifnum;
477     operate.ioctl_code = IOCTL_USBFS_DISCONNECT;
478     if (ioctl(fd, USBDEVFS_IOCTL, &operate) < 0) {
479         dbg_time("%s detach kernel driver failed", __func__);
480     } else {
481         dbg_time("%s detach kernel driver success", __func__);
482     }
483 }
484 
usbfs_attach_kernel_driver(int fd,int ifnum)485 void usbfs_attach_kernel_driver(int fd, int ifnum)
486 {
487     struct usbfs_ioctl operate;
488     operate.data = NULL;
489     operate.ifno = ifnum;
490     operate.ioctl_code = IOCTL_USBFS_CONNECT;
491     if (ioctl(fd, USBDEVFS_IOCTL, &operate) < 0) {
492         dbg_time("%s detach kernel driver failed", __func__);
493     } else {
494         dbg_time("%s detach kernel driver success", __func__);
495     }
496 }
497 
reattach_driver(PROFILE_T * profile)498 int reattach_driver(PROFILE_T *profile)
499 {
500     int ifnum = 4;
501     int fd;
502     char devpath[128] = {'\0'};
503     snprintf(devpath, sizeof(devpath), "/dev/bus/usb/%03d/%03d", profile->usb_dev.busnum, profile->usb_dev.devnum);
504     fd = open(devpath, O_RDWR | O_NOCTTY);
505     if (fd < 0)
506     {
507         dbg_time("%s fail to open %s", __func__, devpath);
508         return -1;
509     }
510     usbfs_detach_kernel_driver(fd, ifnum);
511     usbfs_attach_kernel_driver(fd, ifnum);
512 	close(fd);
513     return 0;
514 }
515 
516 #define SIOCETHTOOL     0x8946
ql_get_netcard_driver_info(const char * devname)517 int ql_get_netcard_driver_info(const char *devname)
518 {
519     int fd = -1;
520     struct ethtool_drvinfo drvinfo;
521     struct ifreq ifr;	/* ifreq suitable for ethtool ioctl */
522 
523     memset(&ifr, 0, sizeof(ifr));
524     strcpy(ifr.ifr_name, devname);
525 
526     fd = socket(AF_INET, SOCK_DGRAM, 0);
527     if (fd < 0) {
528         dbg_time("Cannot get control socket: errno(%d)(%s)", errno, strerror(errno));
529         return -1;
530     }
531 
532     drvinfo.cmd = ETHTOOL_GDRVINFO;
533     ifr.ifr_data = (void *)&drvinfo;
534 
535     if (ioctl(fd, SIOCETHTOOL, &ifr) < 0) {
536         dbg_time("ioctl() error: errno(%d)(%s)", errno, strerror(errno));
537         return -1;
538     }
539 
540     dbg_time("netcard driver = %s, driver version = %s", drvinfo.driver, drvinfo.version);
541 
542 	close(fd);
543 
544     return 0;
545 }
546 
catch_log(void * arg)547 static void *catch_log(void *arg)
548 {
549     PROFILE_T *profile = (PROFILE_T *)arg;
550     int nreads = 0;
551     char tbuff[256+32];
552     char filter[32];
553     size_t tsize = strlen(get_time()) + 1;
554 
555     snprintf(filter, sizeof(filter), ":%d:%03d:", profile->usb_dev.busnum, profile->usb_dev.devnum);
556 
557     while(1) {
558         nreads = read(profile->usbmon_fd, tbuff + tsize, sizeof(tbuff) - tsize - 1);
559         if (nreads <= 0) {
560             if (nreads == -1 && errno == EINTR)
561                 continue;
562             break;
563         }
564 
565         tbuff[tsize+nreads] = '\0';   // printf("%s", buff);
566 
567         if (!strstr(tbuff+tsize, filter))
568             continue;
569 
570         snprintf(tbuff, sizeof(tbuff), "%s", get_time());
571         tbuff[tsize-1] = ' ';
572 
573         fwrite(tbuff, strlen(tbuff), 1, profile->usbmon_logfile_fp);
574     }
575 
576     return NULL;
577 }
578 
ql_capture_usbmon_log(PROFILE_T * profile,const char * log_path)579 int ql_capture_usbmon_log(PROFILE_T *profile, const char *log_path)
580 {
581     char usbmon_path[256];
582     pthread_t pt;
583     pthread_attr_t attr;
584 
585     if (access("/sys/module/usbmon", F_OK)) {
586         dbg_time("usbmon is not load, please execute \"modprobe usbmon\" or \"insmod usbmon.ko\"");
587         return -1;
588     }
589 
590     if (access("/sys/kernel/debug/usb", F_OK)) {
591         dbg_time("debugfs is not mount, please execute \"mount -t debugfs none_debugs /sys/kernel/debug\"");
592         return -1;
593     }
594 
595     snprintf(usbmon_path, sizeof(usbmon_path), "/sys/kernel/debug/usb/usbmon/%du", profile->usb_dev.busnum);
596     profile->usbmon_fd = open(usbmon_path, O_RDONLY);
597     if (profile->usbmon_fd < 0) {
598         dbg_time("open %s error(%d) (%s)", usbmon_path, errno, strerror(errno));
599         return -1;
600     }
601 
602     snprintf(usbmon_path, sizeof(usbmon_path), "cat /sys/kernel/debug/usb/devices >> %s", log_path);
603     if (system(usbmon_path) == -1) {};
604 
605     profile->usbmon_logfile_fp = fopen(log_path, "wb");
606     if (!profile->usbmon_logfile_fp) {
607       dbg_time("open %s error(%d) (%s)", log_path, errno, strerror(errno));
608       close(profile->usbmon_fd);
609       profile->usbmon_fd = -1;
610       return -1;
611     }
612 
613     pthread_attr_init(&attr);
614     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
615 
616     pthread_create(&pt, &attr, catch_log, (void *)profile);
617 
618     return 0;
619 }
620 
ql_stop_usbmon_log(PROFILE_T * profile)621 void ql_stop_usbmon_log(PROFILE_T *profile) {
622     if (profile->usbmon_fd > 0)
623         close(profile->usbmon_fd);
624     if (profile->usbmon_logfile_fp)
625         fclose(profile->usbmon_logfile_fp);
626 }
627