#include "qlog.h" #include "getopt.h" #define LOGFILE_SIZE_MIN (2*1024*1024) #define LOGFILE_SIZE_MAX (512*1024*1024) #define LOGFILE_SIZE_DEFAULT (128*1024*1024) #define LOGFILE_NUM 512 static char s_logfile_List[LOGFILE_NUM][32]; static unsigned s_logfile_num = 0; uint32_t qlog_le32 (uint32_t v32) { uint32_t tmp = v32; const int is_bigendian = 1; if ( (*(char*)&is_bigendian) == 0 ) { unsigned char *s = (unsigned char *)(&v32); unsigned char *d = (unsigned char *)(&tmp); d[0] = s[3]; d[1] = s[2]; d[2] = s[1]; d[3] = s[0]; } return tmp; } uint64_t qlog_le64(uint64_t v64) { const uint64_t is_bigendian = 1; uint64_t tmp = v64; if ((*(char*)&is_bigendian) == 0) { unsigned char *s = (unsigned char *)(&v64); unsigned char *d = (unsigned char *)(&tmp); d[0] = s[7]; d[1] = s[6]; d[2] = s[5]; d[3] = s[4]; d[4] = s[3]; d[5] = s[2]; d[6] = s[1]; d[7] = s[0]; } return tmp; } unsigned qlog_msecs(void) { static unsigned start = 0; unsigned now; struct timeval tv; gettimeofday(&tv, NULL); now = (unsigned)tv.tv_sec*1000 + (unsigned)tv.tv_usec / 1000; if (start == 0) start = now; return now - start; } void qlog_get_vidpid_by_ttyport(const char *ttyport, char idVendor[5], char idProduct[5], char bNumInterfaces[5]) { char syspath[255]; char sysport[64]; int count; char *pchar = NULL; int fd = 0; memset(idVendor, 0x00, 5); memset(idProduct, 0x00, 5); memset(bNumInterfaces, 0x00, 5); snprintf(sysport, sizeof(sysport), "/sys/class/tty/%s", &ttyport[strlen("/dev/")]); count = readlink(sysport, syspath, sizeof(syspath) - 1); if (count < strlen(":1.0/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, ":1.0/tty"); //MDM if (pchar == NULL) { pchar = strstr(syspath, ":1.2/tty"); //ASR g_is_asr_chip = (pchar != NULL); } if (pchar == NULL) { qlog_dbg("%s is not a usb-to-serial device?\n", ttyport); return; } *pchar = '\0'; while (*pchar != '/') pchar--; strcpy(sysport, pchar + 1); snprintf(syspath, sizeof(syspath), "/sys/bus/usb/devices/%s/idVendor", sysport); fd = open(syspath, O_RDONLY); if (fd <= 0) { qlog_dbg("Fail to open %s, errno: %d (%s)\n", syspath, errno, strerror(errno)); return; } read(fd, idVendor, 4); close(fd); snprintf(syspath, sizeof(syspath), "/sys/bus/usb/devices/%s/idProduct", sysport); fd = open(syspath, O_RDONLY); if (fd <= 0) { qlog_dbg("Fail to open %s, errno: %d (%s)\n", syspath, errno, strerror(errno)); return; } read(fd, idProduct, 4); close(fd); snprintf(syspath, sizeof(syspath), "/sys/bus/usb/devices/%s/bNumInterfaces", sysport); fd = open(syspath, O_RDONLY); if (fd <= 0) { qlog_dbg("Fail to open %s, errno: %d (%s)\n", syspath, errno, strerror(errno)); return; } read(fd, bNumInterfaces, 4); close(fd); qlog_dbg("%s idVendor=%s, idProduct=%s, bNumInterfaces=%d\n", __func__, idVendor, idProduct, atoi(bNumInterfaces)); } static unsigned long get_now_msec() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec*1000 + tv.tv_usec / 1000; } static size_t ql_tty_read(int fd, void *buf, size_t size) { size_t rc; rc = read(fd,buf,size); if (rc > 0) { static size_t total_read = 0; static unsigned long now_msec = 0; unsigned long n = get_now_msec(); if (now_msec == 0) now_msec = get_now_msec(); total_read += rc; if ((total_read >= (4*1024*1024)) || (n >= (now_msec + 5000))) { qlog_dbg("recv: %zdM %zdK %zdB in %ld msec\n", total_read/(1024*1024), total_read/1024%1024,total_read%1024, n-now_msec); now_msec = n; total_read = 0; } } return rc; } ssize_t qlog_poll_write(int fd, const void *buf, size_t size, unsigned timeout_msec) { ssize_t wc = 0; ssize_t nbytes; nbytes = write(fd, buf+wc, size-wc); if (nbytes <= 0) { if (errno != EAGAIN) { qlog_dbg("Fail to write fd = %d, errno : %d (%s)\n", fd, errno, strerror(errno)); goto out; } else { nbytes = 0; } } wc += nbytes; while (wc < size) { int ret; struct pollfd pollfds[] = {{fd, POLLOUT, 0}}; ret = poll(pollfds, 1, timeout_msec); if (ret <= 0) { qlog_dbg("Fail to poll fd = %d, errno : %d (%s)\n", fd, errno, strerror(errno)); break; } if (pollfds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) { qlog_dbg("Fail to poll fd = %d, revents = %04x\n", fd, pollfds[0].revents); break; } if (pollfds[0].revents & (POLLOUT)) { nbytes = write(fd, buf+wc, size-wc); if (nbytes <= 0) { qlog_dbg("Fail to write fd = %d, errno : %d (%s)\n", fd, errno, strerror(errno)); break; } wc += nbytes; } } out: if (wc != size) { qlog_dbg("%s fd=%d, size=%zd, timeout=%d, wc=%zd\n", __func__, fd, size, timeout_msec, wc); } return (wc); } static int qlog_logfile_create(const char *logfile_dir, const char *logfile_suffix, unsigned logfile_seq) { int logfd; time_t ltime; char shortname[32]; char filename[255+1]; struct tm *currtime; //delete old logfile if (s_logfile_num && s_logfile_List[logfile_seq%s_logfile_num][0]) { sprintf(filename, "%s/%s.%s", logfile_dir, s_logfile_List[logfile_seq%s_logfile_num], logfile_suffix); if (access(filename, R_OK) == 0) { remove(filename); } } time(<ime); currtime = localtime(<ime); snprintf(shortname, sizeof(shortname), "%04d%02d%02d_%02d%02d%02d_%04d", (currtime->tm_year+1900), (currtime->tm_mon+1), currtime->tm_mday, currtime->tm_hour, currtime->tm_min, currtime->tm_sec, logfile_seq); sprintf(filename, "%s/%s.%s", logfile_dir, shortname, logfile_suffix); logfd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0444); if (logfd <= 0) { qlog_dbg("Fail to create new logfile! errno : %d (%s)\n", errno, strerror(errno)); } qlog_dbg("%s %s logfd=%d\n", __func__, filename, logfd); if (s_logfile_num) { strcpy(s_logfile_List[logfile_seq%s_logfile_num], shortname); } return logfd; } static size_t qlog_logfile_save(int logfd, const void *buf, size_t size) { return qlog_poll_write(logfd, buf, size, 1000); } static int qlog_logfile_close(int logfd) { return close(logfd); } static void* qlog_logfile_init_filter_thread(void* arg) { void **thread_args = (void **)arg; qlog_ops_t *qlog_ops = (qlog_ops_t *)thread_args[0]; int *ttyfd = (int *)thread_args[1]; const char *filter_cfg = ( const char *)thread_args[2]; if (qlog_ops->init_filter) qlog_ops->init_filter(*ttyfd, filter_cfg); return NULL; } static int qlog_handle(int ttyfd, const char *logfile_dir, size_t logfile_size, unsigned logfile_num, const char *filter_cfg) { ssize_t savelog_size = 0; void *rbuf; const size_t rbuf_size = (16*1024); static int logfd = -1; unsigned logfile_seq = 0; const char *logfile_suffix = g_is_asr_chip ? "sdl" : "qmdl"; static qlog_ops_t qlog_ops; pthread_t thread_id; pthread_attr_t thread_attr; const void *thread_args[3]; if (logfile_dir[0] == '9' && atoi(logfile_dir) >= 9000) { filter_cfg = logfile_dir; qlog_ops = tty2tcp_qlog_ops; } else { qlog_ops = g_is_asr_chip ? asr_qlog_ops : mdm_qlog_ops; if (access(logfile_dir, F_OK) && errno == ENOENT) mkdir(logfile_dir, 0755); } if (!qlog_ops.logfile_create) qlog_ops.logfile_create = qlog_logfile_create; if (!qlog_ops.logfile_save) qlog_ops.logfile_save = qlog_logfile_save; if (!qlog_ops.logfile_close) qlog_ops.logfile_close = qlog_logfile_close; rbuf = malloc(rbuf_size); if (rbuf == NULL) { qlog_dbg("Fail to malloc rbuf_size=%zd, errno: %d (%s)\n", rbuf_size, errno, strerror(errno)); return -1; } thread_args[0] = &qlog_ops; thread_args[1] = &ttyfd; thread_args[2] = filter_cfg; pthread_attr_init(&thread_attr); pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); pthread_create(&thread_id, &thread_attr, qlog_logfile_init_filter_thread, (void*)thread_args); while(1) { ssize_t rc, wc; int ret; struct pollfd pollfds[] = {{ttyfd, POLLIN, 0}}; ret = poll(pollfds, 1, -1); if (ret <= 0) { qlog_dbg("poll(ttyfd) =%d, errno: %d (%s)\n", ret, errno, strerror(errno)); break; } if (pollfds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) { qlog_dbg("ttyfd revents = %04x\n", pollfds[0].revents); break; } if (pollfds[0].revents & (POLLIN)) { rc = ql_tty_read(ttyfd, rbuf, rbuf_size); if(rc > 0) { if (logfd == -1) { logfd = qlog_ops.logfile_create(logfile_dir, logfile_suffix, logfile_seq); if (logfd <= 0) { break; } if (qlog_ops.logfile_init) qlog_ops.logfile_init(logfd, logfile_seq); logfile_seq++; } wc = qlog_ops.logfile_save(logfd, rbuf, rc); if (wc != rc) { qlog_dbg("savelog fail %zd/%zd, break\n", wc, rc); break; } savelog_size += wc; if (savelog_size >= logfile_size) { savelog_size = 0; qlog_ops.logfile_close(logfd); logfd = -1; } } else { qlog_dbg("ttyfd recv %zd Bytes. break\n", rc); break; } } } if (logfd > 0) qlog_ops.logfile_close(logfd); free(rbuf); return 0; } static void ql_sigaction(int signal_num) { qlog_dbg("recv signal %d\n", signal_num); } static void qlog_usage(const char *self, const char *dev) { qlog_dbg("Usage: %s -p -s -f filter_cfg -n -b \n", self); qlog_dbg("Default: %s -p %s -s %s -n %d -b %d to save log to local disk\n", self, dev, ".", LOGFILE_NUM, LOGFILE_SIZE_DEFAULT/1024/1024); qlog_dbg(" -p ttyport to catch log, default is '/dev/ttyUSB0'\n"); qlog_dbg(" -s where to save log, default is '.' \n"); qlog_dbg(" if set as '9000', QLog will run as TCP Server Mode, and let 'QPST/QWinLog/CATStudio' to connect!\n"); qlog_dbg(" -f filter cfg for catch log, can be found in directory 'conf'. if not set this arg, will use default filter conf\n"); qlog_dbg(" and UC200T&EC200T do not need filter cfg.\n"); qlog_dbg(" -n max num of log file to save, range is '0~512'. default is 0. 0 means no limit.\n"); qlog_dbg(" or QLog will auto delete oldtest log file if exceed max num\n"); qlog_dbg(" -m max size of single log file, unit is MBytes, range is '2~512', default is 128\n"); qlog_dbg("\nFor example: %s -p /dev/ttyUSB0 -w .\n", self); } int main(int argc, char **argv) { int opt; char ttyname[32] = "/dev/ttyUSB0"; int ttyfd = -1; const char *logfile_dir = "./"; const char *filter_cfg = NULL; size_t logfile_size = LOGFILE_SIZE_DEFAULT; unsigned logfile_num = LOGFILE_NUM; char idVendor[5] = "0000"; char idProduct[5] = "0000"; char bNumInterfaces[5] = "0"; qlog_dbg("QLog Version: Quectel_QLog_Linux&Android_V1.3\n"); //when release, rename to V1.X optind = 1; //call by popen(), optind mayby is not 1 while ( -1 != (opt = getopt(argc, argv, "d:p:s:w:n:m:b:f:c:h"))) { switch (opt) { case 'p': if (optarg[0] == 't') //ttyUSB0 snprintf(ttyname, sizeof(ttyname), "/dev/%s", optarg); else if (optarg[0] == 'U') //USB0 snprintf(ttyname, sizeof(ttyname), "/dev/tty%s", optarg); else if (optarg[0] == '/') snprintf(ttyname, sizeof(ttyname), "%s", optarg); else { qlog_dbg("unknow dev %s\n", optarg); } break; case 's': logfile_dir = optarg; break; case 'n': logfile_num = atoi(optarg); if (logfile_num < 0) logfile_num = 0; else if (logfile_num > LOGFILE_NUM) logfile_num = LOGFILE_NUM; s_logfile_num = logfile_num; break; case 'm': logfile_size = atoi(optarg)*1024*1024; if (logfile_size < LOGFILE_SIZE_MIN) logfile_size = LOGFILE_SIZE_MIN; else if (logfile_size > LOGFILE_SIZE_MAX) logfile_size = LOGFILE_SIZE_MAX; break; case 'f': filter_cfg = optarg; break; case 'b': case 'c': //unused break; default: qlog_usage(argv[0], ttyname); return 0; break; } } signal(SIGTERM, ql_sigaction); signal(SIGHUP, ql_sigaction); signal(SIGINT, ql_sigaction); ttyfd = open (ttyname, O_RDWR | O_NDELAY | O_NOCTTY); if (ttyfd <= 0) { qlog_dbg("Fail to open %s, errno : %d (%s)\n", ttyname, errno, strerror(errno)); return -1; } qlog_dbg("open %s ttyfd = %d\n", ttyname, ttyfd); if ( ttyfd > 0 ) { struct termios ios; memset(&ios, 0, sizeof(ios)); tcgetattr( ttyfd, &ios ); cfmakeraw(&ios); cfsetispeed(&ios, B115200); cfsetospeed(&ios, B115200); tcsetattr( ttyfd, TCSANOW, &ios ); } if (strncmp(ttyname, "/dev/mhi", strlen("/dev/mhi"))) { qlog_get_vidpid_by_ttyport(ttyname, idVendor, idProduct, bNumInterfaces); } else { if (!strcmp(ttyname, "/dev/mhi_DIAG")) { strcpy(idVendor, "2c7c"); strcpy(bNumInterfaces, "5"); //log mode } else if (!strcmp(ttyname, "/dev/mhi_SAHARA")) { strcpy(idVendor, "2c7c"); strcpy(bNumInterfaces, "1"); //dump mode } } qlog_dbg("Press CTRL+C to stop catch log.\n"); if (g_is_asr_chip == 0 && atoi(bNumInterfaces) == 1) { if (access(logfile_dir, F_OK) && errno == ENOENT) mkdir(logfile_dir, 0755); sahara_catch_dump(ttyfd, logfile_dir, 1); } else if (atoi(bNumInterfaces) > 1) { qlog_handle(ttyfd, logfile_dir, logfile_size, logfile_num, filter_cfg); } else { qlog_dbg("unknow state! quit!\n"); } close(ttyfd); return 0; }