blob: 5d20a548776a5cc79a43ebfd0cdda54d9cd0e144 [file] [log] [blame]
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* Copyright (C) 2011 - 2012 Red Hat, Inc.
* Copyright (C) 2010 - 2012 Google, Inc.
*/
#include <glib.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
typedef u_int16_t u16;
typedef u_int8_t u8;
typedef u_int32_t u32;
typedef u_int64_t u64;
typedef unsigned int qbool;
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
/***********************************************************/
static void
print_buf (const char *detail, const char *buf, size_t len)
{
int i = 0, z;
qbool newline = FALSE, indent = FALSE;
char *f;
guint flen;
f = g_strdup_printf ("%s (%zu) ", detail, len);
flen = strlen (f);
g_print ("%s", f);
for (i = 0; i < len; i++) {
if (indent) {
z = flen;
while (z--)
g_print (" ");
indent = FALSE;
}
g_print ("%02x ", buf[i] & 0xFF);
if (((i + 1) % 16) == 0) {
g_print ("\n");
newline = TRUE;
indent = TRUE;
} else
newline = FALSE;
}
if (!newline)
g_print ("\n");
}
/**************************************************************/
typedef enum {
QMI_SVC_CTL = 0x00,
QMI_SVC_WDS = 0x01,
QMI_SVC_DMS = 0x02,
QMI_SVC_NAS = 0x03,
QMI_SVC_QOS = 0x04,
QMI_SVC_WMS = 0x05,
QMI_SVC_PDS = 0x06,
QMI_SVC_AUTH = 0x07,
QMI_SVC_AT = 0x08,
QMI_SVC_VOICE = 0x09,
QMI_SVC_CAT2 = 0x0A,
QMI_SVC_UIM = 0x0B,
QMI_SVC_PBM = 0x0C,
QMI_SVC_LOC = 0x10,
QMI_SVC_SAR = 0x11,
QMI_SVC_RMTFS = 0x14,
QMI_SVC_CAT = 0xE0,
QMI_SVC_RMS = 0xE1,
QMI_SVC_OMA = 0xE2
} qmi_service_type;
struct qmux {
u8 tf; /* always 1 */
u16 len;
u8 ctrl;
u8 service;
u8 qmicid;
} __attribute__((__packed__));
struct qmi_tlv_status {
u16 status;
u16 error;
} __attribute__((__packed__));
struct getcid_req {
struct qmux header;
u8 req;
u8 tid;
u16 msgid;
u16 tlvsize;
u8 service;
u16 size;
u8 qmisvc;
} __attribute__((__packed__));
struct releasecid_req {
struct qmux header;
u8 req;
u8 tid;
u16 msgid;
u16 tlvsize;
u8 rlscid;
u16 size;
u16 cid;
} __attribute__((__packed__));
struct version_info_req {
struct qmux header;
u8 req;
u8 tid;
u16 msgid;
u16 tlvsize;
} __attribute__((__packed__));
struct qmi_ctl_version_info_list_service {
u8 service_type; /* QMI_SVC_xxx */
u16 major_version;
u16 minor_version;
} __attribute__((__packed__));
struct qmi_tlv_ctl_version_info_list {
u8 count;
struct qmi_ctl_version_info_list_service services[0];
}__attribute__((__packed__));
struct seteventreport_req {
struct qmux header;
u8 req;
u16 tid;
u16 msgid;
u16 tlvsize;
u8 reportchanrate;
u16 size;
u8 period;
u32 mask;
} __attribute__((__packed__));
struct getpkgsrvcstatus_req {
struct qmux header;
u8 req;
u16 tid;
u16 msgid;
u16 tlvsize;
} __attribute__((__packed__));
struct getmeid_req {
struct qmux header;
u8 req;
u16 tid;
u16 msgid;
u16 tlvsize;
} __attribute__((__packed__));
struct qmiwds_stats {
u32 txok;
u32 rxok;
u32 txerr;
u32 rxerr;
u32 txofl;
u32 rxofl;
u64 txbytesok;
u64 rxbytesok;
qbool linkstate;
qbool reconfigure;
};
struct nas_signal_req {
struct qmux header;
u8 req;
u16 tid;
u16 msgid;
u16 tlvsize;
} __attribute__((__packed__));
const size_t qmux_size = sizeof(struct qmux);
static void
qmux_fill(struct qmux *qmux, u8 service, u16 cid, u16 size)
{
qmux->tf = 1;
qmux->len = size - 1;
qmux->ctrl = 0;
qmux->service = cid & 0xff;
qmux->qmicid = cid >> 8;
}
static void *
qmictl_new_getcid(u8 tid, u8 svctype, size_t *size)
{
struct getcid_req *req;
req = g_malloc0 (sizeof (*req));
req->req = 0x00;
req->tid = tid;
req->msgid = 0x0022;
req->tlvsize = 0x0004;
req->service = 0x01;
req->size = 0x0001;
req->qmisvc = svctype;
*size = sizeof(*req);
qmux_fill (&req->header, QMI_SVC_CTL, 0, *size);
return req;
}
static void *
qmictl_new_releasecid(u8 tid, u16 cid, size_t *size)
{
struct releasecid_req *req;
req = g_malloc0 (sizeof (*req));
req->req = 0x00;
req->tid = tid;
req->msgid = 0x0023;
req->tlvsize = 0x05;
req->rlscid = 0x01;
req->size = 0x0002;
req->cid = cid;
*size = sizeof(*req);
qmux_fill (&req->header, QMI_SVC_CTL, 0, *size);
return req;
}
static void *
qmictl_new_version_info(u8 tid, size_t *size)
{
struct version_info_req *req;
req = g_malloc0 (sizeof (*req));
req->req = 0x00;
req->tid = tid;
req->msgid = 0x21;
req->tlvsize = 0;
*size = sizeof(*req);
qmux_fill (&req->header, QMI_SVC_CTL, 0, *size);
return req;
}
static void *
qmiwds_new_seteventreport(u8 tid, size_t *size)
{
struct seteventreport_req *req;
req = g_malloc0 (sizeof (*req));
req->req = 0x00;
req->tid = tid;
req->msgid = 0x0001;
req->tlvsize = 0x0008;
req->reportchanrate = 0x11;
req->size = 0x0005;
req->period = 0x01;
req->mask = 0x000000ff;
*size = sizeof(*req);
return req;
}
static void *
qmiwds_new_getpkgsrvcstatus(u8 tid, size_t *size)
{
struct getpkgsrvcstatus_req *req;
req = g_malloc0 (sizeof (*req));
req->req = 0x00;
req->tid = tid;
req->msgid = 0x22;
req->tlvsize = 0x0000;
*size = sizeof(*req);
return req;
}
static void *
qmidms_new_getmeid(u16 cid, u8 tid, size_t *size)
{
struct getmeid_req *req;
req = g_malloc0 (sizeof (*req));
req->req = 0x00;
req->tid = tid;
req->msgid = 0x25;
req->tlvsize = 0x0000;
*size = sizeof(*req);
qmux_fill (&req->header, QMI_SVC_WDS, cid, *size);
return req;
}
static int
qmux_parse(u16 *cid, void *buf, size_t size)
{
struct qmux *qmux = buf;
if (!buf || size < 12)
return -ENOMEM;
if (qmux->tf != 1 || qmux->len != size - 1 || qmux->ctrl != 0x80)
return -EINVAL;
*cid = (qmux->qmicid << 8) + qmux->service;
return sizeof(*qmux);
}
static u16
tlv_get(void *msg, u16 msgsize, u8 type, void *buf, u16 bufsize)
{
u16 pos;
u16 msize = 0;
if (!msg || !buf)
return -ENOMEM;
for (pos = 4; pos + 3 < msgsize; pos += msize + 3) {
msize = *(u16 *)(msg + pos + 1);
if (*(u8 *)(msg + pos) == type) {
if (bufsize < msize)
return -ENOMEM;
memcpy(buf, msg + pos + 3, msize);
return msize;
}
}
return -ENOMSG;
}
static int
qmi_msgisvalid(void *msg, u16 size)
{
struct qmi_tlv_status status;
if (tlv_get(msg, size, 2, &status, sizeof (status)) == sizeof (status)) {
if (le16toh (status.status != 0))
return le16toh (status.error);
else
return 0;
}
return -ENOMSG;
}
static int
qmi_msgid(void *msg, u16 size)
{
return size < 2 ? -ENODATA : *(u16 *)msg;
}
static int
qmictl_version_info_resp(void *buf, u16 size)
{
int result, i;
u8 offset = sizeof(struct qmux) + 2;
u8 svcbuf[100];
struct qmi_tlv_ctl_version_info_list *service_list;
struct qmi_ctl_version_info_list_service *svc;
if (!buf || size < offset)
return -ENOMEM;
buf = buf + offset;
size -= offset;
result = qmi_msgid(buf, size);
if (result != 0x21)
return -EFAULT;
result = qmi_msgisvalid(buf, size);
if (result != 0)
return -EFAULT;
/* Get the services TLV */
result = tlv_get(buf, size, 0x01, svcbuf, sizeof (svcbuf));
if (result < 0)
return -EFAULT;
service_list = (struct qmi_tlv_ctl_version_info_list *) svcbuf;
if (result < (service_list->count * sizeof (struct qmi_ctl_version_info_list_service)))
return -EFAULT;
svc = &(service_list->services[0]);
for (i = 0; i < service_list->count; i++, svc++) {
g_message ("SVC: %d v%d.%d",
svc->service_type,
le16toh (svc->major_version),
le16toh (svc->minor_version));
}
return 0;
}
static int
qmictl_getcid_resp(void *buf, u16 size, u16 *cid)
{
int result;
u8 offset = sizeof(struct qmux) + 2;
if (!buf || size < offset)
return -ENOMEM;
buf = buf + offset;
size -= offset;
result = qmi_msgid(buf, size);
if (result != 0x22)
return -EFAULT;
result = qmi_msgisvalid(buf, size);
if (result != 0)
return -EFAULT;
result = tlv_get(buf, size, 0x01, cid, 2);
if (result != 2)
return -EFAULT;
return 0;
}
static int
qmictl_releasecid_resp(void *buf, u16 size)
{
int result;
u8 offset = sizeof(struct qmux) + 2;
if (!buf || size < offset)
return -ENOMEM;
buf = buf + offset;
size -= offset;
result = qmi_msgid(buf, size);
if (result != 0x23)
return -EFAULT;
result = qmi_msgisvalid(buf, size);
if (result != 0)
return -EFAULT;
return 0;
}
static int
qmiwds_event_resp(void *buf, u16 size, struct qmiwds_stats *stats)
{
int result;
u8 status[2];
u8 offset = sizeof(struct qmux) + 3;
if (!buf || size < offset || !stats)
return -ENOMEM;
buf = buf + offset;
size -= offset;
result = qmi_msgid(buf, size);
if (result == 0x01) {
tlv_get(buf, size, 0x10, &stats->txok, 4);
tlv_get(buf, size, 0x11, &stats->rxok, 4);
tlv_get(buf, size, 0x12, &stats->txerr, 4);
tlv_get(buf, size, 0x13, &stats->rxerr, 4);
tlv_get(buf, size, 0x14, &stats->txofl, 4);
tlv_get(buf, size, 0x15, &stats->rxofl, 4);
tlv_get(buf, size, 0x19, &stats->txbytesok, 8);
tlv_get(buf, size, 0x1A, &stats->rxbytesok, 8);
} else if (result == 0x22) {
result = tlv_get(buf, size, 0x01, &status[0], 2);
if (result >= 1)
stats->linkstate = status[0] == 0x02;
if (result == 2)
stats->reconfigure = status[1] == 0x01;
if (result < 0)
return result;
} else {
return -EFAULT;
}
return 0;
}
static int
qmidms_meid_resp(void *buf, u16 size, char *meid, int meidsize)
{
int result;
u8 offset = sizeof(struct qmux) + 3;
if (!buf || size < offset || meidsize < 14)
return -ENOMEM;
buf = buf + offset;
size -= offset;
result = qmi_msgid(buf, size);
if (result != 0x25)
return -EFAULT;
result = qmi_msgisvalid(buf, size);
if (result)
return -EFAULT;
result = tlv_get(buf, size, 0x12, meid, 14);
if (result != 14)
return -EFAULT;
return 0;
}
static void *
qminas_new_signal(u16 cid, u8 tid, size_t *size)
{
struct nas_signal_req *req;
req = g_malloc0 (sizeof (*req));
req->req = 0x00;
req->tid = tid;
req->msgid = 0x20;
req->tlvsize = 0x0000;
*size = sizeof(*req);
qmux_fill (&req->header, QMI_SVC_NAS, cid, *size);
return req;
}
/* NAS/Get Signal Strength TLV 0x01 */
struct qminas_resp_signalstrength {
u8 dbm;
u8 act;
} __attribute__((__packed__));
static int
qminas_signal_resp(void *buf, u16 size, u8 *dbm, u8 *act)
{
int result;
struct qminas_resp_signalstrength signal;
u8 offset = sizeof(struct qmux) + 3;
if (!buf || size < offset)
return -ENOMEM;
buf = buf + offset;
size -= offset;
result = qmi_msgid(buf, size);
if (result != 0x20)
return -EFAULT;
result = qmi_msgisvalid(buf, size);
if (result)
return -EFAULT;
result = tlv_get(buf, size, 0x01, &signal, sizeof (signal));
if (result != sizeof (signal))
return -EFAULT;
*dbm = signal.dbm;
*act = signal.act;
return 0;
}
/*****************************************************/
static size_t
send_and_wait_reply (int fd, void *b, size_t blen, char *reply, size_t rlen)
{
ssize_t num;
fd_set in;
int result;
struct timeval timeout = { 1, 0 };
print_buf (">>>", b, blen);
num = write (fd, b, blen);
if (num != blen) {
g_warning ("Failed to write: wrote %zd err %d", num, errno);
return 0;
}
FD_ZERO (&in);
FD_SET (fd, &in);
result = select (fd + 1, &in, NULL, NULL, &timeout);
if (result != 1 || !FD_ISSET (fd, &in)) {
g_warning ("No data pending");
return 0;
}
errno = 0;
num = read (fd, reply, rlen);
if (num < 0) {
g_warning ("read error %d", errno);
return 0;
}
print_buf ("<<<", reply, num);
return num;
}
/* CTL service transaction ID */
static u8 ctl_tid = 1;
static int
get_meid (int fd)
{
void *b;
size_t blen;
u8 dms_tid = 1;
u16 dms_cid = 0;
char reply[2048];
size_t rlen;
char meid[16];
int err;
/* Allocate a DMS client ID */
b = qmictl_new_getcid (ctl_tid++, QMI_SVC_DMS, &blen);
g_assert (b);
rlen = send_and_wait_reply (fd, b, blen, reply, sizeof (reply));
g_free (b);
if (rlen <= 0)
return 1;
err = qmictl_getcid_resp (reply, rlen, &dms_cid);
if (err < 0) {
g_warning ("Failed to get DMS client ID: %d", err);
return 1;
}
g_message ("DMS CID %d 0x%X", dms_cid, dms_cid);
/* Get the MEID */
b = qmidms_new_getmeid(dms_cid, dms_tid++, &blen);
g_assert (b);
rlen = send_and_wait_reply (fd, b, blen, reply, sizeof (reply));
g_free (b);
if (rlen <= 0)
return 1;
memset (meid, 0, sizeof (meid));
err = qmidms_meid_resp (reply, rlen, meid, sizeof (meid) - 1);
if (err < 0)
g_warning ("Failed to get MEID: %d", err);
else
g_message ("MEID: %s", meid);
/* Relese the DMS client ID */
b = qmictl_new_releasecid (ctl_tid++, dms_cid, &blen);
g_assert (b);
rlen = send_and_wait_reply (fd, b, blen, reply, sizeof (reply));
g_free (b);
if (rlen <= 0)
return 1;
err = qmictl_releasecid_resp (reply, rlen);
if (err < 0) {
g_warning ("Failed to release DMS client ID: %d", err);
return 1;
}
return 0;
}
static const char *
act_to_string (u8 act)
{
switch (act) {
case 0:
return "no service";
case 1:
return "CDMA2000 1x";
case 2:
return "CDMA2000 HRPD/EVDO";
case 3:
return "AMPS";
case 4:
return "GSM";
case 5:
return "UMTS";
case 8:
return "LTE";
default:
break;
}
return "unknown";
}
static int
get_signal (int fd)
{
void *b;
size_t blen;
u8 nas_tid = 1;
u16 nas_cid = 0;
char reply[2048];
size_t rlen;
int err;
u8 dbm = 0, act = 0;
/* Allocate a NAS client ID */
b = qmictl_new_getcid (ctl_tid++, QMI_SVC_NAS, &blen);
g_assert (b);
rlen = send_and_wait_reply (fd, b, blen, reply, sizeof (reply));
g_free (b);
if (rlen <= 0)
return 1;
err = qmictl_getcid_resp (reply, rlen, &nas_cid);
if (err < 0) {
g_warning ("Failed to get NAS client ID: %d", err);
return 1;
}
g_message ("NAS CID %d 0x%X", nas_cid, nas_cid);
/* Get the signal strength */
b = qminas_new_signal(nas_cid, nas_tid++, &blen);
g_assert (b);
rlen = send_and_wait_reply (fd, b, blen, reply, sizeof (reply));
g_free (b);
if (rlen <= 0)
return 1;
err = qminas_signal_resp (reply, rlen, &dbm, &act);
if (err < 0)
g_warning ("Failed to get signal: %d", err);
else {
g_message ("dBm: -%d", 0x100 - (dbm & 0xFF));
g_message ("AcT: %s", act_to_string (act));
}
/* Relese the NAS client ID */
b = qmictl_new_releasecid (ctl_tid++, nas_cid, &blen);
g_assert (b);
rlen = send_and_wait_reply (fd, b, blen, reply, sizeof (reply));
g_free (b);
if (rlen <= 0)
return 1;
err = qmictl_releasecid_resp (reply, rlen);
if (err < 0) {
g_warning ("Failed to release NAS client ID: %d", err);
return 1;
}
return 0;
}
int main(int argc, char *argv[])
{
int fd;
void *b;
size_t blen;
u8 ctl_tid = 1;
char reply[2048];
size_t rlen;
int err;
if (argc != 2) {
g_warning ("usage: %s <port>", argv[0]);
return 1;
}
errno = 0;
fd = open (argv[1], O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY);
if (fd < 0) {
g_warning ("%s: open failed: %d", argv[1], errno);
return 1;
}
/* Send the ready request */
b = qmictl_new_version_info (ctl_tid++, &blen);
g_assert (b);
rlen = send_and_wait_reply (fd, b, blen, reply, sizeof (reply));
g_free (b);
if (rlen <= 0)
goto out;
err = qmictl_version_info_resp (reply, rlen);
if (err < 0) {
g_warning ("Failed to get version info: %d", err);
goto out;
}
get_meid (fd);
get_signal (fd);
out:
close (fd);
return 0;
}