/* -*- 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 #include #include #include #include #include #include #include #include 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 ", 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; }