| Index: drivers/net/usb/gobi/qmi.c
|
| diff --git a/drivers/net/usb/gobi/qmi.c b/drivers/net/usb/gobi/qmi.c
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cdbdbaf7acc2bc022c54c68d6177fccb86414636
|
| --- /dev/null
|
| +++ b/drivers/net/usb/gobi/qmi.c
|
| @@ -0,0 +1,358 @@
|
| +/* qmi.c - QMI protocol implementation
|
| + * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
|
| +
|
| + * 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.
|
| + */
|
| +
|
| +#include "qmi.h"
|
| +
|
| +#include <linux/slab.h>
|
| +
|
| +struct qmux {
|
| + u8 tf; /* always 1 */
|
| + u16 len;
|
| + u8 ctrl;
|
| + u8 service;
|
| + u8 qmicid;
|
| +} __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 ready_req {
|
| + struct qmux header;
|
| + u8 req;
|
| + u8 tid;
|
| + u16 msgid;
|
| + u16 tlvsize;
|
| +} __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__));
|
| +
|
| +const size_t qmux_size = sizeof(struct qmux);
|
| +
|
| +void *qmictl_new_getcid(u8 tid, u8 svctype, size_t *size)
|
| +{
|
| + struct getcid_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
|
| + if (!req)
|
| + return NULL;
|
| + req->req = 0x00;
|
| + req->tid = tid;
|
| + req->msgid = 0x0022;
|
| + req->tlvsize = 0x0004;
|
| + req->service = 0x01;
|
| + req->size = 0x0001;
|
| + req->qmisvc = svctype;
|
| + *size = sizeof(*req);
|
| + return req;
|
| +}
|
| +
|
| +void *qmictl_new_releasecid(u8 tid, u16 cid, size_t *size)
|
| +{
|
| + struct releasecid_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
|
| + if (!req)
|
| + return NULL;
|
| + req->req = 0x00;
|
| + req->tid = tid;
|
| + req->msgid = 0x0023;
|
| + req->tlvsize = 0x05;
|
| + req->rlscid = 0x01;
|
| + req->size = 0x0002;
|
| + req->cid = cid;
|
| + *size = sizeof(*req);
|
| + return req;
|
| +}
|
| +
|
| +void *qmictl_new_ready(u8 tid, size_t *size)
|
| +{
|
| + struct ready_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
|
| + if (!req)
|
| + return NULL;
|
| + req->req = 0x00;
|
| + req->tid = tid;
|
| + req->msgid = 0x21;
|
| + req->tlvsize = 0;
|
| + *size = sizeof(*req);
|
| + return req;
|
| +}
|
| +
|
| +void *qmiwds_new_seteventreport(u8 tid, size_t *size)
|
| +{
|
| + struct seteventreport_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
|
| + 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;
|
| +}
|
| +
|
| +void *qmiwds_new_getpkgsrvcstatus(u8 tid, size_t *size)
|
| +{
|
| + struct getpkgsrvcstatus_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
|
| + if (!req)
|
| + return NULL;
|
| + req->req = 0x00;
|
| + req->tid = tid;
|
| + req->msgid = 0x22;
|
| + req->tlvsize = 0x0000;
|
| + *size = sizeof(*req);
|
| + return req;
|
| +}
|
| +
|
| +void *qmidms_new_getmeid(u8 tid, size_t *size)
|
| +{
|
| + struct getmeid_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
|
| + if (!req)
|
| + return NULL;
|
| + req->req = 0x00;
|
| + req->tid = tid;
|
| + req->msgid = 0x25;
|
| + req->tlvsize = 0x0000;
|
| + *size = sizeof(*req);
|
| + return req;
|
| +}
|
| +
|
| +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);
|
| +}
|
| +
|
| +int qmux_fill(u16 cid, void *buf, size_t size)
|
| +{
|
| + struct qmux *qmux = buf;
|
| +
|
| + if (!buf || size < sizeof(*qmux))
|
| + return -ENOMEM;
|
| +
|
| + qmux->tf = 1;
|
| + qmux->len = size - 1;
|
| + qmux->ctrl = 0;
|
| + qmux->service = cid & 0xff;
|
| + qmux->qmicid = cid >> 8;
|
| + return 0;
|
| +}
|
| +
|
| +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;
|
| +}
|
| +
|
| +int qmi_msgisvalid(void *msg, u16 size)
|
| +{
|
| + char tlv[4];
|
| +
|
| + if (tlv_get(msg, size, 2, &tlv[0], 4) == 4) {
|
| + if (*(u16 *)&tlv[0] != 0)
|
| + return *(u16 *)&tlv[2];
|
| + else
|
| + return 0;
|
| + }
|
| + return -ENOMSG;
|
| +}
|
| +
|
| +int qmi_msgid(void *msg, u16 size)
|
| +{
|
| + return size < 2 ? -ENODATA : *(u16 *)msg;
|
| +}
|
| +
|
| +int qmictl_alloccid_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;
|
| +}
|
| +
|
| +int qmictl_freecid_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;
|
| +}
|
| +
|
| +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;
|
| +}
|
| +
|
| +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;
|
| +}
|
|
|