382 lines
10 KiB
C
382 lines
10 KiB
C
/*
|
|
Copyright 2023 Quectel Wireless Solutions Co.,Ltd
|
|
|
|
Quectel hereby grants customers of Quectel a license to use, modify,
|
|
distribute and publish the Software in binary form provided that
|
|
customers shall have no right to reverse engineer, reverse assemble,
|
|
decompile or reduce to source code form any portion of the Software.
|
|
Under no circumstances may customers modify, demonstrate, use, deliver
|
|
or disclose any portion of the Software in source code form.
|
|
*/
|
|
|
|
#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 <sys/socket.h>
|
|
#include <sys/ioctl.h>
|
|
#include <poll.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 <asm/byteorder.h>
|
|
#include "usb_linux.h"
|
|
#include <endian.h> //for __BYTE_ORDER
|
|
char *inet_ntoa(struct in_addr in);
|
|
|
|
#define dprintf dbg_time
|
|
|
|
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
|
|
|
#define MAX_USBFS_BULK_IN_SIZE (4 * 1024)
|
|
#define MAX_USBFS_BULK_OUT_SIZE (16 * 1024)
|
|
|
|
static uint32_t cpu_to_le32(uint32_t v32)
|
|
{
|
|
uint32_t tmp = v32;
|
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
|
#else
|
|
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];
|
|
#endif
|
|
return tmp;
|
|
}
|
|
#define le32_to_cpu(_v32) cpu_to_le32(_v32)
|
|
|
|
static int qusb_control[2];
|
|
|
|
static int noblock_full_read(int fd, void *pbuf, ssize_t size)
|
|
{
|
|
ssize_t cur = 0;
|
|
|
|
while (cur < size)
|
|
{
|
|
ssize_t ret = read(fd, (char *)pbuf + cur, size - cur);
|
|
|
|
if (ret > 0)
|
|
cur += ret;
|
|
else if (ret < 0 && errno == EAGAIN)
|
|
{
|
|
struct pollfd pollfds[] = {{fd, POLLIN, 0}};
|
|
poll(pollfds, 1, -1);
|
|
if (pollfds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) break;
|
|
}
|
|
else
|
|
{
|
|
dprintf("fd=%d read=%zd, errno: %d (%s)\n", fd, ret, errno, strerror(errno));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cur != size)
|
|
{
|
|
dprintf("%s fd=%d cur=%zd, size=%zd\n", __func__, fd, cur, size);
|
|
}
|
|
|
|
return cur;
|
|
}
|
|
|
|
static ssize_t noblock_full_write(int fd, const void *pbuf, ssize_t size)
|
|
{
|
|
ssize_t cur = 0;
|
|
|
|
while (cur < size)
|
|
{
|
|
ssize_t ret = write(fd, (char *)pbuf + cur, size - cur);
|
|
if (ret > 0)
|
|
cur += ret;
|
|
else if (ret <= 0 && errno == EAGAIN)
|
|
{
|
|
struct pollfd pollfds[] = {{fd, POLLOUT, 0}};
|
|
poll(pollfds, 1, -1);
|
|
if (pollfds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) break;
|
|
}
|
|
else
|
|
{
|
|
dprintf("fd=%d write=%zd, errno: %d (%s)\n", fd, ret, errno, strerror(errno));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cur != size)
|
|
{
|
|
dprintf("%s fd=%d cur=%zd, size=%zd\n", __func__, fd, cur, size);
|
|
}
|
|
|
|
return cur;
|
|
}
|
|
|
|
static void *usb_bulk_read_thread(void *arg)
|
|
{
|
|
const void *usb_handle = arg;
|
|
void *buf = malloc(MAX_USBFS_BULK_IN_SIZE);
|
|
int fd = qusb_control[1];
|
|
|
|
if (buf == NULL) return NULL;
|
|
|
|
while (usb_handle)
|
|
{
|
|
int count = qusb_noblock_read(usb_handle, buf, MAX_USBFS_BULK_IN_SIZE, 1, 30000);
|
|
|
|
if (count > 0)
|
|
{
|
|
count = write(fd, buf, count);
|
|
count = read(fd, buf, 32); // wait usb2tcp_main read
|
|
if (count <= 0)
|
|
{
|
|
dprintf("read=%d\n", count);
|
|
break;
|
|
}
|
|
}
|
|
else if (count <= 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
close(fd);
|
|
free(buf);
|
|
return NULL;
|
|
}
|
|
|
|
static int qusb_open(const void *usb_handle)
|
|
{
|
|
int fd = -1;
|
|
pthread_t thread_id;
|
|
|
|
pthread_attr_t usb_thread_attr;
|
|
pthread_attr_init(&usb_thread_attr);
|
|
pthread_attr_setdetachstate(&usb_thread_attr, PTHREAD_CREATE_DETACHED);
|
|
|
|
socketpair(AF_LOCAL, SOCK_STREAM, 0, qusb_control);
|
|
pthread_create(&thread_id, &usb_thread_attr, usb_bulk_read_thread, (void *)usb_handle);
|
|
|
|
fd = qusb_control[0];
|
|
|
|
// pthread_attr_destroy(&usb_thread_attr); //aaron 2023.07.27
|
|
return fd;
|
|
}
|
|
|
|
static ssize_t qusb_read(int fd, void *pbuf, size_t size) { return read(fd, pbuf, size); }
|
|
|
|
static int create_tcp_server(int socket_port)
|
|
{
|
|
int sockfd = -1;
|
|
int reuse_addr = 1;
|
|
struct sockaddr_in sockaddr;
|
|
|
|
dprintf("%s tcp_port=%d\n", __func__, socket_port);
|
|
/*Create server socket*/
|
|
sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (sockfd <= 0) return sockfd;
|
|
|
|
memset(&sockaddr, 0, sizeof(sockaddr));
|
|
sockaddr.sin_family = AF_INET;
|
|
sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
sockaddr.sin_port = htons(socket_port);
|
|
|
|
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof(reuse_addr));
|
|
if (bind(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0)
|
|
{
|
|
close(sockfd);
|
|
dprintf("%s bind %d errno: %d (%s)\n", __func__, socket_port, errno, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
return sockfd;
|
|
}
|
|
|
|
static int wait_client_connect(int server_fd)
|
|
{
|
|
int client_fd = -1;
|
|
unsigned char addr[128];
|
|
socklen_t alen = sizeof(addr);
|
|
|
|
dprintf("%s\n", __func__);
|
|
listen(server_fd, 1);
|
|
client_fd = accept(server_fd, (struct sockaddr *)addr, &alen);
|
|
if (client_fd <= 0) return client_fd;
|
|
|
|
if (client_fd > 0)
|
|
{
|
|
struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
|
|
dprintf("clientfd = %d %s:%d connect\n", client_fd, inet_ntoa(addr_in->sin_addr),
|
|
addr_in->sin_port);
|
|
}
|
|
|
|
return client_fd;
|
|
}
|
|
|
|
int usb2tcp_main(const void *usb_handle, int tcp_port, unsigned qusb_zlp_mode)
|
|
{
|
|
void *pbuf = malloc(MAX_USBFS_BULK_OUT_SIZE);
|
|
int server_fd = -1, client_fd = -1, usb_fd = -1, size = -1;
|
|
TLV_USB tlv_usb;
|
|
|
|
if (pbuf == NULL) return -1;
|
|
|
|
server_fd = create_tcp_server(tcp_port);
|
|
dprintf("server_fd=%d\n", server_fd);
|
|
if (server_fd <= 0)
|
|
{
|
|
dprintf("Fail create_tcp_server\n");
|
|
goto _out;
|
|
}
|
|
|
|
if (client_fd <= 0)
|
|
{
|
|
client_fd = wait_client_connect(server_fd);
|
|
if (client_fd < 0)
|
|
{
|
|
dprintf("Fail wait_client_connect\n");
|
|
goto _out;
|
|
}
|
|
}
|
|
|
|
usb_fd = qusb_open(usb_handle);
|
|
dprintf("usb_fd = %d\n", usb_fd);
|
|
|
|
tlv_usb.tag = cpu_to_le32(Q_USB2TCP_VERSION);
|
|
tlv_usb.length = cpu_to_le32(12);
|
|
tlv_usb.idVendor = cpu_to_le32(0x05c6);
|
|
tlv_usb.idProduct = cpu_to_le32(0x9008);
|
|
tlv_usb.interfaceNum = cpu_to_le32(1);
|
|
if (write(client_fd, &tlv_usb, sizeof(tlv_usb)) == -1)
|
|
{
|
|
};
|
|
|
|
fcntl(usb_fd, F_SETFL, fcntl(usb_fd, F_GETFL) | O_NONBLOCK);
|
|
fcntl(client_fd, F_SETFL, fcntl(client_fd, F_GETFL) | O_NONBLOCK);
|
|
|
|
while (usb_fd > 0 && client_fd > 0)
|
|
{
|
|
struct pollfd pollfds[] = {{usb_fd, POLLIN, 0}, {client_fd, POLLIN, 0}};
|
|
int ne, ret, nevents = sizeof(pollfds) / sizeof(pollfds[0]);
|
|
|
|
do
|
|
{
|
|
ret = poll(pollfds, nevents, -1);
|
|
} while (ret < 0 && errno == EINTR);
|
|
|
|
if (ret <= 0)
|
|
{
|
|
dprintf("%s poll=%d, errno: %d (%s)\n", __func__, ret, errno, strerror(errno));
|
|
goto _hangup;
|
|
}
|
|
|
|
if (pollfds[0].revents & (POLLERR | POLLHUP | POLLNVAL))
|
|
{
|
|
dprintf("%s poll usb_fd = %d, revents = %04x\n", __func__, usb_fd, pollfds[0].revents);
|
|
goto _hangup;
|
|
}
|
|
|
|
if (pollfds[1].revents & (POLLERR | POLLHUP | POLLNVAL))
|
|
{
|
|
dprintf("%s poll client_fd = %d, revents = %04x\n", __func__, client_fd,
|
|
pollfds[1].revents);
|
|
goto _hangup;
|
|
}
|
|
|
|
for (ne = 0; ne < nevents; ne++)
|
|
{
|
|
int fd = pollfds[ne].fd;
|
|
TLV tlv = {Q_USB2TCP_VERSION, 0};
|
|
|
|
if ((pollfds[ne].revents & POLLIN) == 0) continue;
|
|
|
|
if (fd == usb_fd)
|
|
{
|
|
size = qusb_read(usb_fd, pbuf, MAX_USBFS_BULK_IN_SIZE);
|
|
if (size <= 0)
|
|
{
|
|
dprintf("usb_fd=%d read=%d, errno: %d (%s)\n", fd, size, errno,
|
|
strerror(errno));
|
|
goto _hangup;
|
|
;
|
|
}
|
|
if (write(usb_fd, pbuf, 1) == -1)
|
|
{
|
|
}; // wakeup usb_bulk_read_thread
|
|
|
|
tlv.tag = cpu_to_le32(Q_USB2TCP_VERSION);
|
|
tlv.length = cpu_to_le32(size);
|
|
if (sizeof(tlv) != noblock_full_write(client_fd, &tlv, sizeof(tlv)))
|
|
{
|
|
goto _hangup;
|
|
break;
|
|
}
|
|
|
|
if (size != noblock_full_write(client_fd, pbuf, size))
|
|
{
|
|
goto _hangup;
|
|
break;
|
|
}
|
|
}
|
|
else if (fd == client_fd)
|
|
{
|
|
size = noblock_full_read(client_fd, &tlv, sizeof(tlv));
|
|
if (size != sizeof(tlv))
|
|
{
|
|
dprintf("client_fd=%d read=%d, errno: %d (%s)\n", fd, size, errno,
|
|
strerror(errno));
|
|
goto _hangup;
|
|
}
|
|
|
|
if (le32_to_cpu(tlv.tag) != Q_USB2TCP_VERSION)
|
|
{
|
|
break;
|
|
}
|
|
|
|
size = le32_to_cpu(tlv.length);
|
|
if (size != noblock_full_read(client_fd, pbuf, size))
|
|
{
|
|
goto _hangup;
|
|
break;
|
|
}
|
|
qusb_noblock_write(usb_handle, pbuf, size, size, 3000, qusb_zlp_mode);
|
|
}
|
|
}
|
|
}
|
|
|
|
_hangup:
|
|
if (usb_fd > 0)
|
|
{
|
|
close(usb_fd);
|
|
usb_fd = -1;
|
|
}
|
|
if (client_fd > 0)
|
|
{
|
|
close(client_fd);
|
|
client_fd = -1;
|
|
}
|
|
|
|
_out:
|
|
if (server_fd > 0)
|
|
{
|
|
close(server_fd);
|
|
server_fd = -1;
|
|
}
|
|
|
|
free(pbuf);
|
|
return 0;
|
|
}
|