/******************************************************************************
|
@file quectel-qrtr-proxy.c
|
@brief The qrtr proxy.
|
|
DESCRIPTION
|
Connectivity Management Tool for USB/PCIE network adapter of Quectel wireless cellular modules.
|
|
INITIALIZATION AND SEQUENCING REQUIREMENTS
|
None.
|
|
---------------------------------------------------------------------------
|
Copyright (c) 2016 - 2023 Quectel Wireless Solution, Co., Ltd. All Rights Reserved.
|
Quectel Wireless Solution Proprietary and Confidential.
|
---------------------------------------------------------------------------
|
******************************************************************************/
|
#include <unistd.h>
|
#include <stdlib.h>
|
#include <stdio.h>
|
#include <string.h>
|
#include <errno.h>
|
#include <stdarg.h>
|
#include <stddef.h>
|
#include <fcntl.h>
|
#include <pthread.h>
|
#include <poll.h>
|
#include <sys/socket.h>
|
#include <sys/time.h>
|
#include <sys/ioctl.h>
|
#include <linux/un.h>
|
#include <linux/if.h>
|
#include <dirent.h>
|
#include <signal.h>
|
#include <inttypes.h>
|
#include <linux/socket.h>
|
#include "qrtr.h"
|
|
#include "qendian.h"
|
#include "qlist.h"
|
#include "QCQMI.h"
|
#include "QCQCTL.h"
|
#include "QCQMUX.h"
|
|
static const char * get_time(void) {
|
static char time_buf[128];
|
struct timeval tv;
|
time_t time;
|
suseconds_t millitm;
|
struct tm *ti;
|
|
gettimeofday (&tv, NULL);
|
|
time= tv.tv_sec;
|
millitm = (tv.tv_usec + 500) / 1000;
|
|
if (millitm == 1000) {
|
++time;
|
millitm = 0;
|
}
|
|
ti = localtime(&time);
|
sprintf(time_buf, "[%02d-%02d_%02d:%02d:%02d:%03d]", ti->tm_mon+1, ti->tm_mday, ti->tm_hour, ti->tm_min, ti->tm_sec, (int)millitm);
|
return time_buf;
|
}
|
|
#define dprintf(fmt, args...) do { fprintf(stdout, "%s " fmt, get_time(), ##args); } while(0);
|
#define SYSCHECK(c) do{if((c)<0) {dprintf("%s %d error: '%s' (code: %d)\n", __func__, __LINE__, strerror(errno), errno); return -1;}}while(0)
|
#define cfmakenoblock(fd) do{fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) | O_NONBLOCK);}while(0)
|
#define align_4(_len) (((_len) + 3) & ~3)
|
|
typedef struct {
|
struct qlistnode qnode;
|
int ClientFd;
|
QCQMIMSG qrtr[0];
|
} QRTR_PROXY_MSG;
|
|
typedef struct {
|
struct qlistnode qnode;
|
uint8_t QMIType;
|
uint8_t ClientId;
|
uint32_t node_id;
|
uint32_t port_id;
|
unsigned AccessTime;
|
} QRTR_PROXY_CLINET;
|
|
typedef struct {
|
struct qlistnode qnode;
|
struct qlistnode client_qnode;
|
int ClientFd;
|
unsigned AccessTime;
|
} QRTR_PROXY_CONNECTION;
|
|
typedef struct {
|
struct qlistnode qnode;
|
uint32_t service;
|
uint32_t version;
|
uint32_t instance;
|
uint32_t node;
|
uint32_t port;
|
|
__le32 src_node_id;
|
__le32 src_port_id;
|
} QRTR_SERVICE;
|
|
static int qrtr_proxy_quit = 0;
|
static pthread_t thread_id = 0;
|
static int cdc_wdm_fd = -1;
|
static int qrtr_proxy_server_fd = -1;
|
static struct qlistnode qrtr_proxy_connection;
|
static struct qlistnode qrtr_server_list;
|
static int verbose_debug = 0;
|
static uint32_t node_modem = 3; //IPQ ~ 3, QCM ~ 0
|
static uint32_t node_myself = 1;
|
|
static QRTR_SERVICE *find_qrtr_service(uint8_t QMIType)
|
{
|
struct qlistnode *node;
|
|
qlist_for_each (node, &qrtr_server_list) {
|
QRTR_SERVICE *srv = qnode_to_item(node, QRTR_SERVICE, qnode);
|
if (srv->service == QMIType)
|
return srv;
|
}
|
|
return NULL;
|
}
|
|
static uint8_t client_bitmap[0xf0];
|
static uint8_t port_bitmap[0xff0];
|
static int alloc_client_id(void) {
|
int id = 1;
|
|
for (id = 1; id < (int)sizeof(client_bitmap); id++) {
|
if (client_bitmap[id] == 0) {
|
client_bitmap[id] = id;
|
return id;
|
}
|
}
|
|
dprintf("NOT find %s()\n", __func__);
|
return 0;
|
}
|
|
static void free_client_id(int id) {
|
if (id < (int)sizeof(client_bitmap) && client_bitmap[id] == id) {
|
client_bitmap[id] = 0;
|
return;
|
}
|
dprintf("NOT find %s(id=%d)\n", __func__, id);
|
}
|
|
static int alloc_port_id(void) {
|
int id = 1;
|
|
for (id = 1; id < (int)sizeof(port_bitmap); id++) {
|
if (port_bitmap[id] == 0) {
|
port_bitmap[id] = id;
|
return id;
|
}
|
}
|
|
dprintf("NOT find %s()\n", __func__);
|
return 0;
|
}
|
|
static void free_port_id(int id) {
|
if (id < (int)sizeof(port_bitmap) && port_bitmap[id] == id) {
|
port_bitmap[id] = 0;
|
return;
|
}
|
dprintf("NOT find %s(id=%d)\n", __func__, id);
|
}
|
|
static void dump_qrtr(void *buf, size_t len, char flag)
|
{
|
size_t i;
|
static char printf_buf[1024];
|
int cnt = 0, limit=1024;
|
unsigned char *d = (unsigned char *)buf;
|
struct qrtr_hdr_v1 *hdr = (struct qrtr_hdr_v1 *)buf;
|
const char *ctrl_pkt_strings[] = {
|
[QRTR_TYPE_DATA] = "data",
|
[QRTR_TYPE_HELLO] = "hello",
|
[QRTR_TYPE_BYE] = "bye",
|
[QRTR_TYPE_NEW_SERVER] = "new-server",
|
[QRTR_TYPE_DEL_SERVER] = "del-server",
|
[QRTR_TYPE_DEL_CLIENT] = "del-client",
|
[QRTR_TYPE_RESUME_TX] = "resume-tx",
|
[QRTR_TYPE_EXIT] = "exit",
|
[QRTR_TYPE_PING] = "ping",
|
[QRTR_TYPE_NEW_LOOKUP] = "new-lookup",
|
[QRTR_TYPE_DEL_LOOKUP] = "del-lookup",
|
};
|
|
for (i = 0; i < len && i < 64; i++) {
|
if (i%4 == 0)
|
cnt += snprintf(printf_buf+cnt, limit-cnt, " ");
|
cnt += snprintf(printf_buf+cnt, limit-cnt, "%02x", d[i]);
|
}
|
dprintf("%s\n", printf_buf);
|
|
dprintf("%c ver=%d, type=%d(%s), %x,%x -> %x,%x, confirm_rx=%d, size=%u\n",
|
flag,
|
le32toh(hdr->version), le32toh(hdr->type), ctrl_pkt_strings[le32toh(hdr->type)],
|
le32toh(hdr->src_node_id), le32toh(hdr->src_port_id), le32toh(hdr->dst_node_id), le32toh(hdr->dst_port_id),
|
le32toh(hdr->confirm_rx), le32toh(hdr->size));
|
}
|
|
static int send_qmi_to_client(PQCQMIMSG pQMI, int fd) {
|
struct pollfd pollfds[]= {{fd, POLLOUT, 0}};
|
ssize_t ret = 0;
|
ssize_t size = le16toh(pQMI->QMIHdr.Length) + 1;
|
|
do {
|
ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000);
|
} while (ret == -1 && errno == EINTR && qrtr_proxy_quit == 0);
|
|
if (pollfds[0].revents & POLLOUT) {
|
ret = write(fd, pQMI, size);
|
}
|
|
return ret == size ? 0 : -1;
|
}
|
|
static int send_qrtr_to_dev(struct qrtr_hdr_v1 *hdr, int fd) {
|
struct pollfd pollfds[]= {{fd, POLLOUT, 0}};
|
ssize_t ret = 0;
|
ssize_t size = align_4(le32toh(hdr->size) + sizeof(*hdr));
|
|
do {
|
ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000);
|
} while (ret == -1 && errno == EINTR && qrtr_proxy_quit == 0);
|
|
if (pollfds[0].revents & POLLOUT) {
|
ret = write(fd, hdr, size);
|
}
|
|
return ret == size ? 0 : -1;
|
}
|
|
static int qrtr_node_enqueue(const void *data, size_t len,
|
int type, struct sockaddr_qrtr *from,
|
struct sockaddr_qrtr *to, unsigned int confirm_rx)
|
{
|
int rc = -1;
|
size_t size = sizeof(struct qrtr_hdr_v1) + len;
|
struct qrtr_hdr_v1 *hdr = (struct qrtr_hdr_v1 *)malloc(align_4(size));
|
|
if (hdr) {
|
hdr->version = htole32(QRTR_PROTO_VER_1);
|
hdr->type = htole32(type);
|
hdr->src_node_id = htole32(from->sq_node);
|
hdr->src_port_id = htole32(from->sq_port);
|
hdr->dst_node_id = htole32(to->sq_node);
|
hdr->dst_port_id = htole32(to->sq_port);
|
hdr->size = htole32(len);
|
hdr->confirm_rx = htole32(!!confirm_rx);
|
|
memcpy(hdr + 1, data, len);
|
dump_qrtr(hdr, size, '>');
|
send_qrtr_to_dev(hdr, cdc_wdm_fd);
|
free(hdr);
|
}
|
|
return rc;
|
}
|
|
static int send_ctrl_hello(__u32 sq_node, __u32 sq_port)
|
{
|
struct qrtr_ctrl_pkt pkt;
|
int rc;
|
struct sockaddr_qrtr to = {AF_QIPCRTR, sq_node, sq_port};
|
struct sockaddr_qrtr from = {AF_QIPCRTR, node_myself, QRTR_PORT_CTRL};
|
|
memset(&pkt, 0, sizeof(pkt));
|
pkt.cmd = htole32(QRTR_TYPE_HELLO);
|
|
rc = qrtr_node_enqueue(&pkt, sizeof(pkt), QRTR_TYPE_HELLO, &from, &to, 0);
|
if (rc < 0)
|
return rc;
|
|
return 0;
|
}
|
|
static int ctrl_cmd_del_client(__u32 sq_node, __u32 sq_port, uint8_t QMIType)
|
{
|
struct qrtr_ctrl_pkt pkt;
|
int rc;
|
struct sockaddr_qrtr to = {AF_QIPCRTR, QRTR_NODE_BCAST, QRTR_PORT_CTRL};
|
struct sockaddr_qrtr from = {AF_QIPCRTR, sq_node, sq_port};
|
QRTR_SERVICE *srv = find_qrtr_service(QMIType);
|
|
if (srv) {
|
to.sq_node = srv->src_node_id;
|
}
|
|
memset(&pkt, 0, sizeof(pkt));
|
pkt.cmd = htole32(QRTR_TYPE_DEL_CLIENT);
|
pkt.client.node = htole32(sq_node);
|
pkt.client.port = htole32(sq_port);
|
|
rc = qrtr_node_enqueue(&pkt, sizeof(pkt), QRTR_TYPE_DATA, &from, &to, 0);
|
if (rc < 0)
|
return rc;
|
|
return 0;
|
}
|
|
static void handle_server_change(struct qrtr_hdr_v1 *hdr) {
|
struct qrtr_ctrl_pkt *pkt = (struct qrtr_ctrl_pkt *)(hdr + 1);
|
QRTR_SERVICE *s;
|
|
dprintf ("[qrtr] %s server on %u:%u(%u:%u) -> service %u, instance %x\n",
|
QRTR_TYPE_NEW_SERVER == hdr->type ? "add" : "remove",
|
le32toh(pkt->server.node), le32toh(pkt->server.port),
|
le32toh(hdr->src_node_id), le32toh(hdr->src_port_id),
|
le32toh(pkt->server.service), le32toh(pkt->server.instance));
|
|
if (le32toh(pkt->server.node) != node_modem) {
|
return; //we only care modem
|
}
|
|
s = (QRTR_SERVICE *)malloc(sizeof(QRTR_SERVICE));
|
if (!s)
|
return;
|
|
qlist_init(&s->qnode);
|
s->service = le32toh(pkt->server.service);
|
s->version = le32toh(pkt->server.instance) & 0xff;
|
s->instance = le32toh(pkt->server.instance) >> 8;
|
s->node = le32toh(pkt->server.node);
|
s->port = le32toh(pkt->server.port);
|
|
s->src_node_id = le32toh(hdr->src_node_id);
|
s->src_port_id = le32toh(hdr->src_port_id);
|
|
if (QRTR_TYPE_NEW_SERVER == hdr->type) {
|
qlist_add_tail(&qrtr_server_list, &s->qnode);
|
}
|
else if (QRTR_TYPE_DEL_SERVER == hdr->type) {
|
qlist_remove(&s->qnode);
|
}
|
}
|
|
static int create_local_server(const char *name) {
|
int sockfd = -1;
|
int reuse_addr = 1;
|
struct sockaddr_un sockaddr;
|
socklen_t alen;
|
|
/*Create server socket*/
|
SYSCHECK(sockfd = socket(AF_LOCAL, SOCK_STREAM, 0));
|
|
memset(&sockaddr, 0, sizeof(sockaddr));
|
sockaddr.sun_family = AF_LOCAL;
|
sockaddr.sun_path[0] = 0;
|
memcpy(sockaddr.sun_path + 1, name, strlen(name) );
|
|
alen = strlen(name) + offsetof(struct sockaddr_un, sun_path) + 1;
|
SYSCHECK(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr,sizeof(reuse_addr)));
|
if(bind(sockfd, (struct sockaddr *)&sockaddr, alen) < 0) {
|
close(sockfd);
|
dprintf("bind %s errno: %d (%s)\n", name, errno, strerror(errno));
|
return -1;
|
}
|
|
dprintf("local server: %s sockfd = %d\n", name, sockfd);
|
cfmakenoblock(sockfd);
|
listen(sockfd, 1);
|
|
return sockfd;
|
}
|
|
static uint8_t alloc_qrtr_client_id(QRTR_PROXY_CONNECTION *qrtr_con, uint8_t QMIType) {
|
QRTR_PROXY_CLINET *qrtr_client = (QRTR_PROXY_CLINET *)malloc(sizeof(QRTR_PROXY_CLINET));
|
|
qlist_init(&qrtr_client->qnode);
|
qrtr_client->QMIType = QMIType;
|
qrtr_client->ClientId = alloc_client_id();
|
qrtr_client->node_id = 1;
|
qrtr_client->port_id = alloc_port_id();
|
qrtr_client->AccessTime = 0;
|
|
dprintf("+++ ClientFd=%d QMIType=%d ClientId=%d, node_id=%d, port_id=%d\n",
|
qrtr_con->ClientFd, qrtr_client->QMIType, qrtr_client->ClientId,
|
qrtr_client->node_id, qrtr_client->port_id);
|
qlist_add_tail(&qrtr_con->client_qnode, &qrtr_client->qnode);
|
|
return qrtr_client->ClientId;
|
}
|
|
static void release_qrtr_client_id(QRTR_PROXY_CONNECTION *qrtr_con, uint8_t QMIType, uint8_t ClientId) {
|
struct qlistnode *client_node;
|
int find = 0;
|
|
qlist_for_each (client_node, &qrtr_con->client_qnode) {
|
QRTR_PROXY_CLINET *qrtr_client = qnode_to_item(client_node, QRTR_PROXY_CLINET, qnode);
|
|
if (QMIType == qrtr_client->QMIType && ClientId == qrtr_client->ClientId) {
|
dprintf("--- ClientFd=%d QMIType=%d ClientId=%d, node_id=%d, port_id=%d\n",
|
qrtr_con->ClientFd, qrtr_client->QMIType, qrtr_client->ClientId,
|
qrtr_client->node_id, qrtr_client->port_id);
|
ctrl_cmd_del_client(qrtr_client->node_id, qrtr_client->port_id, qrtr_client->QMIType);
|
free_client_id(qrtr_client->ClientId);
|
free_port_id(qrtr_client->port_id);
|
qlist_remove(&qrtr_client->qnode);
|
free(qrtr_client);
|
find++;
|
break;
|
}
|
}
|
|
if (!find) {
|
dprintf("NOT find on %s(ClientFd=%d, QMIType=%d, ClientId=%d)\n",
|
__func__, qrtr_con->ClientFd, QMIType, ClientId);
|
}
|
}
|
|
static void accept_qrtr_connection(int serverfd) {
|
int clientfd = -1;
|
unsigned char addr[128];
|
socklen_t alen = sizeof(addr);
|
QRTR_PROXY_CONNECTION *qrtr_con;
|
|
clientfd = accept(serverfd, (struct sockaddr *)addr, &alen);
|
|
qrtr_con = (QRTR_PROXY_CONNECTION *)malloc(sizeof(QRTR_PROXY_CONNECTION));
|
if (qrtr_con) {
|
qlist_init(&qrtr_con->qnode);
|
qlist_init(&qrtr_con->client_qnode);
|
qrtr_con->ClientFd= clientfd;
|
qrtr_con->AccessTime = 0;
|
dprintf("+++ ClientFd=%d\n", qrtr_con->ClientFd);
|
qlist_add_tail(&qrtr_proxy_connection, &qrtr_con->qnode);
|
}
|
|
cfmakenoblock(clientfd);
|
}
|
|
static void cleanup_qrtr_connection(int clientfd) {
|
struct qlistnode *con_node;
|
int find = 0;
|
|
qlist_for_each(con_node, &qrtr_proxy_connection) {
|
QRTR_PROXY_CONNECTION *qrtr_con = qnode_to_item(con_node, QRTR_PROXY_CONNECTION, qnode);
|
|
if (qrtr_con->ClientFd == clientfd) {
|
while (!qlist_empty(&qrtr_con->client_qnode)) {
|
QRTR_PROXY_CLINET *qrtr_client = qnode_to_item(qlist_head(&qrtr_con->client_qnode), QRTR_PROXY_CLINET, qnode);
|
|
release_qrtr_client_id(qrtr_con, qrtr_client->QMIType, qrtr_client->ClientId);
|
}
|
|
dprintf("--- ClientFd=%d\n", qrtr_con->ClientFd);
|
close(qrtr_con->ClientFd);
|
qlist_remove(&qrtr_con->qnode);
|
free(qrtr_con);
|
find = 1;
|
break;
|
}
|
}
|
|
if (!find) {
|
dprintf("NOT find on %s(ClientFd=%d)\n", __func__, clientfd);
|
}
|
}
|
|
static void recv_qrtr_from_dev(struct qrtr_hdr_v1 *hdr) {
|
int find = 0;
|
uint32_t type = le32toh(hdr->type);
|
|
if (type == QRTR_TYPE_HELLO) {
|
send_ctrl_hello(le32toh(hdr->src_node_id), le32toh(hdr->src_port_id));
|
find++;
|
}
|
else if (type == QRTR_TYPE_NEW_SERVER || type == QRTR_TYPE_DEL_SERVER) {
|
handle_server_change(hdr);
|
find++;
|
}
|
else if (type == QRTR_TYPE_DATA) {
|
struct qlistnode *con_node, *client_node;
|
|
qlist_for_each(con_node, &qrtr_proxy_connection) {
|
QRTR_PROXY_CONNECTION *qrtr_con = qnode_to_item(con_node, QRTR_PROXY_CONNECTION, qnode);
|
|
qlist_for_each(client_node, &qrtr_con->client_qnode) {
|
QRTR_PROXY_CLINET *qrtr_client = qnode_to_item(client_node, QRTR_PROXY_CLINET, qnode);
|
|
if (qrtr_client->node_id == le32toh(hdr->dst_node_id) && qrtr_client->port_id == le32toh(hdr->dst_port_id)) {
|
PQCQMIMSG pQMI = (PQCQMIMSG)malloc(hdr->size + sizeof(QCQMI_HDR));
|
|
if (pQMI) {
|
pQMI->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
|
pQMI->QMIHdr.Length = htole16(hdr->size + sizeof(QCQMI_HDR) - 1);
|
pQMI->QMIHdr.CtlFlags = 0x00;
|
pQMI->QMIHdr.QMIType = qrtr_client->QMIType;
|
pQMI->QMIHdr.ClientId = qrtr_client->ClientId;
|
memcpy(&pQMI->MUXMsg, hdr + 1, hdr->size);
|
send_qmi_to_client(pQMI, qrtr_con->ClientFd);
|
free(pQMI);
|
find++;
|
}
|
}
|
}
|
}
|
|
if (hdr->confirm_rx) {
|
struct qrtr_ctrl_pkt pkt;
|
struct sockaddr_qrtr from = {AF_QIPCRTR, le32toh(hdr->dst_node_id), le32toh(hdr->dst_port_id)};
|
struct sockaddr_qrtr to = {AF_QIPCRTR, le32toh(hdr->src_node_id), le32toh(hdr->src_port_id)};
|
|
memset(&pkt, 0, sizeof(pkt));
|
pkt.cmd = htole32(QRTR_TYPE_RESUME_TX);
|
pkt.client.node = hdr->dst_node_id;
|
pkt.client.port = hdr->dst_port_id;
|
|
qrtr_node_enqueue(&pkt, sizeof(pkt), QRTR_TYPE_RESUME_TX, &from, &to, 0);
|
}
|
}
|
else if (type == QRTR_TYPE_RESUME_TX) {
|
}
|
|
if (!find) {
|
dprintf("NOT find on %s()\n", __func__);
|
}
|
}
|
|
static int recv_qmi_from_client(PQCQMIMSG pQMI, int clientfd) {
|
QRTR_PROXY_CONNECTION *qrtr_con;
|
struct qlistnode *con_node, *client_node;
|
int find = 0;
|
|
qlist_for_each(con_node, &qrtr_proxy_connection) {
|
qrtr_con = qnode_to_item(con_node, QRTR_PROXY_CONNECTION, qnode);
|
if (qrtr_con->ClientFd == clientfd)
|
break;
|
qrtr_con = NULL;
|
}
|
|
if (!qrtr_con) {
|
return -1;
|
}
|
|
if (le16toh(pQMI->QMIHdr.QMIType) == QMUX_TYPE_CTL) {
|
if (pQMI->CTLMsg.QMICTLMsgHdr.QMICTLType == QMICTL_SYNC_REQ) {
|
dprintf("do not allow client send QMICTL_SYNC_REQ\n");
|
return 0;
|
}
|
else if (le16toh(pQMI->CTLMsg.QMICTLMsgHdr.QMICTLType) == QMICTL_GET_CLIENT_ID_REQ) {
|
uint8_t QMIType = pQMI->CTLMsg.GetClientIdReq.QMIType;
|
PQCQMIMSG pRsp = (PQCQMIMSG)malloc(256);
|
|
if (pRsp) {
|
uint8_t ClientId = 0;
|
|
if (find_qrtr_service(QMIType)) {
|
ClientId = alloc_qrtr_client_id(qrtr_con, QMIType);
|
}
|
|
pRsp->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
|
pRsp->QMIHdr.Length = htole16(sizeof(pRsp->CTLMsg.GetClientIdRsp) + sizeof(pRsp->QMIHdr) - 1);
|
pRsp->QMIHdr.CtlFlags = 0x00;
|
pRsp->QMIHdr.QMIType = QMUX_TYPE_CTL;
|
pRsp->QMIHdr.ClientId = 0;
|
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.CtlFlags = QMICTL_FLAG_RESPONSE;
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.TransactionId = pQMI->CTLMsg.QMICTLMsgHdr.TransactionId;
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMICTLType = pQMI->CTLMsg.QMICTLMsgHdr.QMICTLType;
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.Length = htole16(sizeof(pRsp->CTLMsg.GetClientIdRsp) - sizeof(pRsp->CTLMsg.QMICTLMsgHdr));
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.TLVType = QCTLV_TYPE_RESULT_CODE;
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.TLVLength = htole16(4);
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXResult = htole16(ClientId ? 0 : QMI_RESULT_FAILURE);
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXError = htole16(ClientId ? 0 : QMI_ERR_INTERNAL);
|
pRsp->CTLMsg.GetClientIdRsp.TLV2Type = QCTLV_TYPE_REQUIRED_PARAMETER;
|
pRsp->CTLMsg.GetClientIdRsp.TLV2Length = htole16(2);
|
pRsp->CTLMsg.GetClientIdRsp.QMIType = QMIType;
|
pRsp->CTLMsg.GetClientIdRsp.ClientId = ClientId;
|
|
send_qmi_to_client(pRsp, clientfd);
|
free(pRsp);
|
find++;
|
}
|
}
|
else if (le16toh(pQMI->CTLMsg.QMICTLMsgHdr.QMICTLType) == QMICTL_RELEASE_CLIENT_ID_REQ) {
|
PQCQMIMSG pRsp = (PQCQMIMSG)malloc(256);
|
release_qrtr_client_id(qrtr_con, pQMI->CTLMsg.ReleaseClientIdReq.QMIType, pQMI->CTLMsg.ReleaseClientIdReq.ClientId);
|
|
if (pRsp) {
|
pRsp->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
|
pRsp->QMIHdr.Length = htole16(sizeof(pRsp->CTLMsg.ReleaseClientIdRsp) + sizeof(pRsp->QMIHdr) - 1);
|
pRsp->QMIHdr.CtlFlags = 0x00;
|
pRsp->QMIHdr.QMIType = QMUX_TYPE_CTL;
|
pRsp->QMIHdr.ClientId = 0;
|
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.CtlFlags = QMICTL_FLAG_RESPONSE;
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.TransactionId = pQMI->CTLMsg.QMICTLMsgHdr.TransactionId;
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMICTLType = pQMI->CTLMsg.QMICTLMsgHdr.QMICTLType;
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.Length = htole16(sizeof(pRsp->CTLMsg.ReleaseClientIdRsp) - sizeof(pRsp->CTLMsg.QMICTLMsgHdr));
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.TLVType = QCTLV_TYPE_RESULT_CODE;
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.TLVLength = htole16(4);
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXResult = htole16(0);
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXError = htole16(0);
|
pRsp->CTLMsg.ReleaseClientIdRsp.TLV2Type = QCTLV_TYPE_REQUIRED_PARAMETER;
|
pRsp->CTLMsg.ReleaseClientIdRsp.TLV2Length = htole16(2);
|
pRsp->CTLMsg.ReleaseClientIdRsp.QMIType = pQMI->CTLMsg.ReleaseClientIdReq.QMIType;
|
pRsp->CTLMsg.ReleaseClientIdRsp.ClientId = pQMI->CTLMsg.ReleaseClientIdReq.ClientId;
|
|
send_qmi_to_client(pRsp, clientfd);
|
free(pRsp);
|
find++;
|
}
|
}
|
else if (le16toh(pQMI->CTLMsg.QMICTLMsgHdr.QMICTLType) == QMICTL_GET_VERSION_REQ) {
|
PQCQMIMSG pRsp = (PQCQMIMSG)malloc(256);
|
|
if (pRsp) {
|
pRsp->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
|
pRsp->QMIHdr.Length = htole16(sizeof(pRsp->CTLMsg.GetVersionRsp) + sizeof(pRsp->QMIHdr) - 1);
|
pRsp->QMIHdr.CtlFlags = 0x00;
|
pRsp->QMIHdr.QMIType = QMUX_TYPE_CTL;
|
pRsp->QMIHdr.ClientId = 0;
|
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.CtlFlags = QMICTL_FLAG_RESPONSE;
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.TransactionId = pQMI->CTLMsg.QMICTLMsgHdr.TransactionId;
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMICTLType = pQMI->CTLMsg.QMICTLMsgHdr.QMICTLType;
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.Length = htole16(sizeof(pRsp->CTLMsg.GetVersionRsp) - sizeof(pRsp->CTLMsg.QMICTLMsgHdr));
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.TLVType = QCTLV_TYPE_RESULT_CODE;
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.TLVLength = htole16(4);
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXResult = htole16(0);
|
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXError = htole16(0);
|
pRsp->CTLMsg.GetVersionRsp.TLV2Type = QCTLV_TYPE_REQUIRED_PARAMETER;
|
pRsp->CTLMsg.GetVersionRsp.TLV2Length = htole16(1);
|
pRsp->CTLMsg.GetVersionRsp.NumElements = 0;
|
|
send_qmi_to_client(pRsp, clientfd);
|
free(pRsp);
|
find++;
|
}
|
}
|
}
|
else {
|
qlist_for_each (client_node, &qrtr_con->client_qnode) {
|
QRTR_PROXY_CLINET *qrtr_client = qnode_to_item(client_node, QRTR_PROXY_CLINET, qnode);
|
|
if (pQMI->QMIHdr.QMIType == qrtr_client->QMIType && pQMI->QMIHdr.ClientId == qrtr_client->ClientId) {
|
QRTR_SERVICE *srv = find_qrtr_service(pQMI->QMIHdr.QMIType);
|
|
if (srv && srv->service) {
|
struct sockaddr_qrtr from = {AF_QIPCRTR, qrtr_client->node_id, qrtr_client->port_id};
|
struct sockaddr_qrtr to = {AF_QIPCRTR, srv->node, srv->port};
|
|
qrtr_node_enqueue(&pQMI->MUXMsg, le16toh(pQMI->QMIHdr.Length) + 1 - sizeof(QCQMI_HDR),
|
QRTR_TYPE_DATA, &from, &to, 0);
|
find++;
|
}
|
break;
|
}
|
}
|
}
|
|
if (!find) {
|
dprintf("NOT find on %s()\n", __func__);
|
}
|
|
return 0;
|
}
|
|
static int qrtr_proxy_init(void) {
|
unsigned i;
|
int qrtr_sync_done = 0;
|
|
dprintf("%s enter\n", __func__);
|
send_ctrl_hello(QRTR_NODE_BCAST, QRTR_PORT_CTRL);
|
|
for (i = 0; i < 10; i++) {
|
sleep(1);
|
qrtr_sync_done = !qlist_empty(&qrtr_server_list);
|
if (qrtr_sync_done)
|
break;
|
}
|
|
dprintf("%s %s\n", __func__, qrtr_sync_done ? "succful" : "fail");
|
return qrtr_sync_done ? 0 : -1;
|
}
|
|
static void qrtr_start_server(const char* servername) {
|
qrtr_proxy_server_fd = create_local_server(servername);
|
dprintf("qrtr_proxy_server_fd = %d\n", qrtr_proxy_server_fd);
|
if (qrtr_proxy_server_fd == -1) {
|
dprintf("Failed to create %s, errno: %d (%s)\n", servername, errno, strerror(errno));
|
}
|
}
|
|
static void qrtr_close_server(const char* servername) {
|
if (qrtr_proxy_server_fd != -1) {
|
dprintf("%s %s\n", __func__, servername);
|
close(qrtr_proxy_server_fd);
|
qrtr_proxy_server_fd = -1;
|
}
|
}
|
|
static void *qrtr_proxy_loop(void *param)
|
{
|
void *rx_buf;
|
struct qlistnode *con_node;
|
QRTR_PROXY_CONNECTION *qrtr_con;
|
|
(void)param;
|
dprintf("%s enter thread_id %p\n", __func__, (void *)pthread_self());
|
|
rx_buf = malloc(8192);
|
if (!rx_buf)
|
return NULL;
|
|
while (cdc_wdm_fd > 0 && qrtr_proxy_quit == 0) {
|
struct pollfd pollfds[32];
|
int ne, ret, nevents = 0;
|
ssize_t nreads;
|
|
pollfds[nevents].fd = cdc_wdm_fd;
|
pollfds[nevents].events = POLLIN;
|
pollfds[nevents].revents= 0;
|
nevents++;
|
|
if (qrtr_proxy_server_fd > 0) {
|
pollfds[nevents].fd = qrtr_proxy_server_fd;
|
pollfds[nevents].events = POLLIN;
|
pollfds[nevents].revents= 0;
|
nevents++;
|
}
|
|
qlist_for_each(con_node, &qrtr_proxy_connection) {
|
qrtr_con = qnode_to_item(con_node, QRTR_PROXY_CONNECTION, qnode);
|
|
pollfds[nevents].fd = qrtr_con->ClientFd;
|
pollfds[nevents].events = POLLIN;
|
pollfds[nevents].revents= 0;
|
nevents++;
|
|
if (nevents == (sizeof(pollfds)/sizeof(pollfds[0])))
|
break;
|
}
|
|
do {
|
//ret = poll(pollfds, nevents, -1);
|
ret = poll(pollfds, nevents, (qrtr_proxy_server_fd > 0) ? -1 : 200);
|
} while (ret == -1 && errno == EINTR && qrtr_proxy_quit == 0);
|
|
if (ret < 0) {
|
dprintf("%s poll=%d, errno: %d (%s)\n", __func__, ret, errno, strerror(errno));
|
goto qrtr_proxy_loop_exit;
|
}
|
|
for (ne = 0; ne < nevents; ne++) {
|
int fd = pollfds[ne].fd;
|
short revents = pollfds[ne].revents;
|
|
if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
dprintf("%s poll fd = %d, revents = %04x\n", __func__, fd, revents);
|
if (fd == cdc_wdm_fd) {
|
goto qrtr_proxy_loop_exit;
|
}
|
else if (fd == qrtr_proxy_server_fd) {
|
|
}
|
else {
|
cleanup_qrtr_connection(fd);
|
}
|
|
continue;
|
}
|
|
if (!(pollfds[ne].revents & POLLIN)) {
|
continue;
|
}
|
|
if (fd == qrtr_proxy_server_fd) {
|
accept_qrtr_connection(fd);
|
}
|
else if (fd == cdc_wdm_fd) {
|
struct qrtr_hdr_v1 *hdr = (struct qrtr_hdr_v1 *)rx_buf;
|
|
nreads = read(fd, rx_buf, 8192);
|
if (nreads <= 0) {
|
dprintf("%s read=%d errno: %d (%s)\n", __func__, (int)nreads, errno, strerror(errno));
|
goto qrtr_proxy_loop_exit;
|
}
|
else if (nreads != (int)align_4(le32toh(hdr->size) + sizeof(*hdr))) {
|
dprintf("%s nreads=%d, hdr->size = %d\n", __func__, (int)nreads, le32toh(hdr->size));
|
continue;
|
}
|
|
dump_qrtr(hdr, nreads, '<');
|
recv_qrtr_from_dev(hdr);
|
}
|
else {
|
PQCQMIMSG pQMI = (PQCQMIMSG)rx_buf;
|
|
nreads = read(fd, rx_buf, 8192);
|
if (nreads <= 0) {
|
dprintf("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno));
|
cleanup_qrtr_connection(fd);
|
break;
|
}
|
else if (nreads != (le16toh(pQMI->QMIHdr.Length) + 1)) {
|
dprintf("%s nreads=%d, pQCQMI->QMIHdr.Length = %d\n", __func__, (int)nreads, le16toh(pQMI->QMIHdr.Length));
|
continue;
|
}
|
|
recv_qmi_from_client(pQMI, fd);
|
}
|
}
|
}
|
|
qrtr_proxy_loop_exit:
|
while (!qlist_empty(&qrtr_proxy_connection)) {
|
QRTR_PROXY_CONNECTION *qrtr_con = qnode_to_item(qlist_head(&qrtr_proxy_connection), QRTR_PROXY_CONNECTION, qnode);
|
|
cleanup_qrtr_connection(qrtr_con->ClientFd);
|
}
|
|
dprintf("%s exit, thread_id %p\n", __func__, (void *)pthread_self());
|
free(rx_buf);
|
|
return NULL;
|
}
|
|
static void usage(void) {
|
dprintf(" -d <device_name> A valid qrtr device\n"
|
" default /dev/mhi_IPCR, but mhi_IPCR may be invalid\n"
|
" -i <netcard_name> netcard name\n"
|
" -v Will show all details\n");
|
}
|
|
static void sig_action(int sig) {
|
if (qrtr_proxy_quit == 0) {
|
qrtr_proxy_quit = 1;
|
if (thread_id)
|
pthread_kill(thread_id, sig);
|
}
|
}
|
|
int main(int argc, char *argv[]) {
|
int opt;
|
char cdc_wdm[32+1] = "/dev/mhi_IPCR";
|
char servername[64] = {0};
|
|
signal(SIGINT, sig_action);
|
signal(SIGTERM, sig_action);
|
|
optind = 1;
|
while ( -1 != (opt = getopt(argc, argv, "d:i:vh"))) {
|
switch (opt) {
|
case 'd':
|
strcpy(cdc_wdm, optarg);
|
break;
|
case 'v':
|
verbose_debug = 1;
|
break;
|
default:
|
usage();
|
return 0;
|
}
|
}
|
|
sprintf(servername, "quectel-qrtr-proxy%c", cdc_wdm[strlen(cdc_wdm)-1]);
|
dprintf("Will use cdc-wdm='%s', proxy='%s'\n", cdc_wdm, servername);
|
|
while (qrtr_proxy_quit == 0) {
|
cdc_wdm_fd = open(cdc_wdm, O_RDWR | O_NONBLOCK | O_NOCTTY);
|
if (cdc_wdm_fd == -1) {
|
dprintf("Failed to open %s, errno: %d (%s)\n", cdc_wdm, errno, strerror(errno));
|
sleep(5);
|
continue;
|
}
|
cfmakenoblock(cdc_wdm_fd);
|
qlist_init(&qrtr_proxy_connection);
|
qlist_init(&qrtr_server_list);
|
pthread_create(&thread_id, NULL, qrtr_proxy_loop, NULL);
|
|
if (qrtr_proxy_init() == 0) {
|
qrtr_start_server(servername);
|
pthread_join(thread_id, NULL);
|
qrtr_close_server(servername);
|
}
|
else {
|
pthread_cancel(thread_id);
|
pthread_join(thread_id, NULL);
|
}
|
|
close(cdc_wdm_fd);
|
}
|
|
return 0;
|
}
|