diff --git a/arch/arm/mach-msm/smd_qmi_wince.c b/arch/arm/mach-msm/smd_qmi_wince.c new file mode 100644 index 00000000..ae918e00 --- /dev/null +++ b/arch/arm/mach-msm/smd_qmi_wince.c @@ -0,0 +1,957 @@ +/* arch/arm/mach-msm/smd_qmi_wm.c +* +* QMI Control Driver for CE AMSS -- Manages network data connections. +* +* Copyright (C) 2010 Cotulla +* Copyright (C) 2007 Google, Inc. +* Author: Brian Swetland +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* 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. +* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +//#define KERN_INFO "" + +#if 0 +#define DBG(x...) pr_info("QMI: "x) +#else +#define DBG(x...) do{}while(0) +#endif + +#define QMI_CTL 0x00 +#define QMI_WDS 0x01 +#define QMI_DMS 0x02 +#define QMI_NAS 0x03 + +#define QMI_RESULT_SUCCESS 0x0000 +#define QMI_RESULT_FAILURE 0x0001 + +struct qmi_msg { + unsigned char service; + unsigned char client_id; + unsigned short txn_id; + unsigned short type; + unsigned short size; + unsigned char *tlv; +}; + +#define qmi_ctl_client_id 0 + +#define STATE_OFFLINE 0 +#define STATE_QUERYING 1 +#define STATE_ONLINE 2 + + +static smd_channel_t *ctrl_ch; +static struct work_struct open_work; +static struct work_struct read_work; +static struct wake_lock wakelock; + +static struct qmi_ctxt qmi_device0; +static struct qmi_ctxt qmi_device1; +static struct qmi_ctxt qmi_device2; + +struct qmi_ctxt +{ + struct miscdevice misc; + + struct mutex lock; + + unsigned char ctl_txn_id; + unsigned char wds_client_id; + unsigned short wds_txn_id; + + unsigned wds_busy; + unsigned wds_handle; + unsigned state_dirty; + unsigned state; + + unsigned char addr[4]; + unsigned char mask[4]; + unsigned char gateway[4]; + unsigned char dns1[4]; + unsigned char dns2[4]; + + const char *ch_name; + int ch_num; + +}; + +static struct qmi_ctxt *qmi_minor_to_ctxt(unsigned n); + +static void qmi_read_work(struct work_struct *ws); +static void qmi_open_work(struct work_struct *work); + +void qmi_ctxt_init(struct qmi_ctxt *ctxt, unsigned n) +{ + mutex_init(&ctxt->lock); + ctxt->ctl_txn_id = 1; + ctxt->wds_txn_id = 1; + ctxt->wds_busy = 1; + ctxt->state = STATE_OFFLINE; + +} + +static struct workqueue_struct *qmi_wq; + +static int verbose = 0; + +/* anyone waiting for a state change waits here */ +static DECLARE_WAIT_QUEUE_HEAD(qmi_wait_queue); + + +static void qmi_dump_msg(struct qmi_msg *msg, const char *prefix) +{ + unsigned sz, n; + unsigned char *x; + + //if (!verbose) + // return; + + printk(KERN_INFO + "qmi: %s: svc=%02x cid=%02x tid=%04x type=%04x size=%04x\n", + prefix, msg->service, msg->client_id, + msg->txn_id, msg->type, msg->size); + + x = msg->tlv; + sz = msg->size; + + while (sz >= 3) { + sz -= 3; + + n = x[1] | (x[2] << 8); + if (n > sz) + break; + + printk(KERN_INFO "qmi: %s: tlv: %02x %04x { ", + prefix, x[0], n); + x += 3; + sz -= n; + while (n-- > 0) + printk("%02x ", *x++); + printk("}\n"); + } +} + +int qmi_add_tlv(struct qmi_msg *msg, + unsigned type, unsigned size, const void *data) +{ + unsigned char *x = msg->tlv + msg->size; + + x[0] = type; + x[1] = size; + x[2] = size >> 8; + + memcpy(x + 3, data, size); + + msg->size += (size + 3); + + return 0; +} + +/* Extract a tagged item from a qmi message buffer, +** taking care not to overrun the buffer. +*/ +static int qmi_get_tlv(struct qmi_msg *msg, + unsigned type, unsigned size, void *data) +{ + unsigned char *x = msg->tlv; + unsigned len = msg->size; + unsigned n; + + while (len >= 3) { + len -= 3; + + /* size of this item */ + n = x[1] | (x[2] << 8); + if (n > len) + break; + + if (x[0] == type) { + if (n != size) + return -1; + memcpy(data, x + 3, size); + return 0; + } + + x += (n + 3); + len -= n; + } + + return -1; +} + +static unsigned qmi_get_status(struct qmi_msg *msg, unsigned *error) +{ + unsigned short status[2]; + if (qmi_get_tlv(msg, 0x02, sizeof(status), status)) { + *error = 0; + return QMI_RESULT_FAILURE; + } else { + *error = status[1]; + return status[0]; + } +} + +/* 0x01 */ +#define QMUX_HEADER 13 + +/* should be >= HEADER + FOOTER */ +#define QMUX_OVERHEAD 16 + +static int qmi_send(struct qmi_ctxt *ctxt, struct qmi_msg *msg) +{ + unsigned char *data; + unsigned hlen; + unsigned len; + int r; + + qmi_dump_msg(msg, "send"); + + if (msg->service == QMI_CTL) { + hlen = QMUX_HEADER - 1; + } else { + hlen = QMUX_HEADER; + } + + /* QMUX length is total header + total payload - IFC selector */ + len = hlen + msg->size - 1; + if (len > 0xffff) + return -1; + + data = msg->tlv - hlen; + + /* prepend encap and qmux header */ + *data++ = 0x01; /* ifc selector */ + + /* qmux header */ + *data++ = len; + *data++ = len >> 8; + *data++ = 0x00; /* flags: client */ + *data++ = msg->service; + *data++ = msg->client_id; + + /* qmi header */ + *data++ = 0x00; /* flags: send */ + *data++ = msg->txn_id; + if (msg->service != QMI_CTL) + *data++ = msg->txn_id >> 8; + + *data++ = msg->type; + *data++ = msg->type >> 8; + *data++ = msg->size; + *data++ = msg->size >> 8; + + // add channel number here + *(uint32_t*)(msg->tlv + msg->size) = ctxt->ch_num; + DBG("send %d %d\n", len + 1 + 4, ctxt->ch_num); + // print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, msg->tlv - hlen, len + 1 + 4); + + /* len + 1 takes the interface selector into account */ + // and add ch_num_size + r = smd_write(ctrl_ch, msg->tlv - hlen, len + 1 + 4); + + if (r != len) { + return -1; + } else { + return 0; + } +} + +static void qmi_process_ctl_msg(struct qmi_ctxt *ctxt, struct qmi_msg *msg) +{ + unsigned err; + if (msg->type == 0x0022) { + unsigned char n[2]; + if (qmi_get_status(msg, &err)) + return; + if (qmi_get_tlv(msg, 0x01, sizeof(n), n)) + return; + if (n[0] == QMI_WDS) { + printk(KERN_INFO + "qmi: ctl: wds use client_id 0x%02x\n", n[1]); + ctxt->wds_client_id = n[1]; + ctxt->wds_busy = 0; + } + } +} + +static int qmi_network_get_profile(struct qmi_ctxt *ctxt); + +static void swapaddr(unsigned char *src, unsigned char *dst) +{ + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; +} + +static unsigned char zero[4]; +static void qmi_read_runtime_profile(struct qmi_ctxt *ctxt, struct qmi_msg *msg) +{ + unsigned char tmp[4]; + unsigned r; + + r = qmi_get_tlv(msg, 0x1e, 4, tmp); + swapaddr(r ? zero : tmp, ctxt->addr); + r = qmi_get_tlv(msg, 0x21, 4, tmp); + swapaddr(r ? zero : tmp, ctxt->mask); + r = qmi_get_tlv(msg, 0x20, 4, tmp); + swapaddr(r ? zero : tmp, ctxt->gateway); + r = qmi_get_tlv(msg, 0x15, 4, tmp); + swapaddr(r ? zero : tmp, ctxt->dns1); + r = qmi_get_tlv(msg, 0x16, 4, tmp); + swapaddr(r ? zero : tmp, ctxt->dns2); +} + +static void qmi_process_unicast_wds_msg(struct qmi_ctxt *ctxt, +struct qmi_msg *msg) +{ + unsigned err; + switch (msg->type) { +case 0x0021: + if (qmi_get_status(msg, &err)) { + printk(KERN_ERR + "qmi: wds: network stop failed (%04x)\n", err); + } else { + printk(KERN_INFO + "qmi: wds: network stopped\n"); + ctxt->state = STATE_OFFLINE; + ctxt->state_dirty = 1; + } + break; +case 0x0020: + if (qmi_get_status(msg, &err)) { + printk(KERN_ERR + "qmi: wds: network start failed (%04x)\n", err); + } else if (qmi_get_tlv(msg, 0x01, sizeof(ctxt->wds_handle), &ctxt->wds_handle)) { + printk(KERN_INFO + "qmi: wds no handle?\n"); + } else { + printk(KERN_INFO + "qmi: wds: got handle 0x%08x\n", + ctxt->wds_handle); + } + break; +case 0x002D: + printk("qmi: got network profile\n"); + if (ctxt->state == STATE_QUERYING) { + qmi_read_runtime_profile(ctxt, msg); + ctxt->state = STATE_ONLINE; + ctxt->state_dirty = 1; + } + break; +default: + printk(KERN_ERR "qmi: unknown msg type 0x%04x\n", msg->type); + } + ctxt->wds_busy = 0; +} + +static void qmi_process_broadcast_wds_msg(struct qmi_ctxt *ctxt, +struct qmi_msg *msg) +{ + if (msg->type == 0x0022) { + unsigned char n[2]; + if (qmi_get_tlv(msg, 0x01, sizeof(n), n)) + return; + switch (n[0]) { +case 1: + printk(KERN_INFO "qmi: wds: DISCONNECTED\n"); + ctxt->state = STATE_OFFLINE; + ctxt->state_dirty = 1; + break; +case 2: + printk(KERN_INFO "qmi: wds: CONNECTED\n"); + ctxt->state = STATE_QUERYING; + ctxt->state_dirty = 1; + qmi_network_get_profile(ctxt); + break; +case 3: + printk(KERN_INFO "qmi: wds: SUSPENDED\n"); + ctxt->state = STATE_OFFLINE; + ctxt->state_dirty = 1; + } + } else { + printk(KERN_ERR "qmi: unknown bcast msg type 0x%04x\n", msg->type); + } +} + +static void qmi_process_wds_msg(struct qmi_ctxt *ctxt, +struct qmi_msg *msg) +{ + printk("wds: %04x @ %02x\n", msg->type, msg->client_id); + if (msg->client_id == ctxt->wds_client_id) { + qmi_process_unicast_wds_msg(ctxt, msg); + } else if (msg->client_id == 0xff) { + qmi_process_broadcast_wds_msg(ctxt, msg); + } else { + printk(KERN_ERR + "qmi_process_wds_msg client id 0x%02x unknown\n", + msg->client_id); + } +} + +static void qmi_process_qmux(struct qmi_ctxt *ctxt, + unsigned char *buf, unsigned sz) +{ + struct qmi_msg msg; + + /* require a full header */ + if (sz < 5) + return; + + /* require a size that matches the buffer size */ + if (sz != (buf[0] | (buf[1] << 8))) + return; + + /* only messages from a service (bit7=1) are allowed */ + if (buf[2] != 0x80) + return; + + msg.service = buf[3]; + msg.client_id = buf[4]; + + /* annoyingly, CTL messages have a shorter TID */ + if (buf[3] == 0) { + if (sz < 7) + return; + msg.txn_id = buf[6]; + buf += 7; + sz -= 7; + } else { + if (sz < 8) + return; + msg.txn_id = buf[6] | (buf[7] << 8); + buf += 8; + sz -= 8; + } + + /* no type and size!? */ + if (sz < 4) + return; + sz -= 4; + + msg.type = buf[0] | (buf[1] << 8); + msg.size = buf[2] | (buf[3] << 8); + msg.tlv = buf + 4; + + if (sz != msg.size) + return; + + qmi_dump_msg(&msg, "recv"); + + mutex_lock(&ctxt->lock); + switch (msg.service) { +case QMI_CTL: + qmi_process_ctl_msg(ctxt, &msg); + break; +case QMI_WDS: + qmi_process_wds_msg(ctxt, &msg); + break; +default: + printk(KERN_ERR "qmi: msg from unknown svc 0x%02x\n", + msg.service); + break; + } + mutex_unlock(&ctxt->lock); + + wake_up(&qmi_wait_queue); +} + +#define QMI_MAX_PACKET (256 + QMUX_OVERHEAD) + +static void qmi_read_work(struct work_struct *ws) +{ + //struct qmi_ctxt *ctxt = container_of(ws, struct qmi_ctxt, read_work); + //struct smd_channel *ch = ctxt->ch; + unsigned char buf[QMI_MAX_PACKET]; + struct qmi_ctxt *ctxt; + int sz; + uint32_t chnum; + + for (;;) + { + sz = smd_cur_packet_size(ctrl_ch); + if (sz == 0) + break; + if (sz < smd_read_avail(ctrl_ch)) + break; + if (sz > QMI_MAX_PACKET) + { + smd_read(ctrl_ch, 0, sz); + continue; + } + if (smd_read(ctrl_ch, buf, sz) != sz) + { + printk(KERN_ERR "qmi: not enough data?!\n"); + continue; + } + + DBG("packet: %d\n", sz); + // print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buf, sz); + + if (sz <= 4) + { + DBG("packet size less 4\n"); + continue; + } + chnum = *(uint32_t*)&buf[sz - 4]; + DBG("chnum = %d\n", chnum); + + /* interface selector must be 1 */ + if (buf[0] != 0x01) + continue; + + if (qmi_device0.ch_num == chnum) + ctxt = &qmi_device0; + else if (qmi_device1.ch_num == chnum) + ctxt = &qmi_device1; + else if (qmi_device2.ch_num == chnum) + ctxt = &qmi_device2; + else + { + DBG("bad chnum %d\n", chnum); + continue; + } + + qmi_process_qmux(ctxt, buf + 1, sz - 1 - 4); + } +} + +static int qmi_request_wds_cid(struct qmi_ctxt *ctxt); + +static void qmi_open_work(struct work_struct *ws) +{ + struct qmi_ctxt *ctxt; //= container_of(ws, struct qmi_ctxt, open_work); + + ctxt = &qmi_device0; + mutex_lock(&ctxt->lock); + qmi_request_wds_cid(ctxt); + mutex_unlock(&ctxt->lock); + + ctxt = &qmi_device1; + mutex_lock(&ctxt->lock); + qmi_request_wds_cid(ctxt); + mutex_unlock(&ctxt->lock); + + ctxt = &qmi_device2; + mutex_lock(&ctxt->lock); + qmi_request_wds_cid(ctxt); + mutex_unlock(&ctxt->lock); + +} + +static void qmi_notify(void *priv, unsigned event) +{ + //struct qmi_ctxt *ctxt = priv; + + switch (event) + { + case SMD_EVENT_DATA: + { + int sz; + sz = smd_cur_packet_size(ctrl_ch); + if ((sz > 0) && (sz <= smd_read_avail(ctrl_ch))) + { + wake_lock_timeout(&wakelock, HZ / 2); + queue_work(qmi_wq, &read_work); + } + break; + } + case SMD_EVENT_OPEN: + printk(KERN_INFO "qmi: smd opened\n"); + queue_work(qmi_wq, &open_work); + break; + case SMD_EVENT_CLOSE: + printk(KERN_INFO "qmi: smd closed\n"); + break; + } +} + +static int qmi_request_wds_cid(struct qmi_ctxt *ctxt) +{ + unsigned char data[64 + QMUX_OVERHEAD]; + struct qmi_msg msg; + unsigned char n; + + msg.service = QMI_CTL; + msg.client_id = qmi_ctl_client_id; + msg.txn_id = ctxt->ctl_txn_id; + msg.type = 0x0022; + msg.size = 0; + msg.tlv = data + QMUX_HEADER; + + ctxt->ctl_txn_id += 2; + + n = QMI_WDS; + qmi_add_tlv(&msg, 0x01, 0x01, &n); + + return qmi_send(ctxt, &msg); +} + +static int qmi_network_get_profile(struct qmi_ctxt *ctxt) +{ + unsigned char data[96 + QMUX_OVERHEAD]; + struct qmi_msg msg; + + msg.service = QMI_WDS; + msg.client_id = ctxt->wds_client_id; + msg.txn_id = ctxt->wds_txn_id; + msg.type = 0x002D; + msg.size = 0; + msg.tlv = data + QMUX_HEADER; + + ctxt->wds_txn_id += 2; + + return qmi_send(ctxt, &msg); +} + +static int qmi_network_up(struct qmi_ctxt *ctxt, char *apn) +{ + unsigned char data[96 + QMUX_OVERHEAD]; + struct qmi_msg msg; + char *auth_type; + char *user; + char *pass; + + for (user = apn; *user; user++) { + if (*user == ' ') { + *user++ = 0; + break; + } + } + for (pass = user; *pass; pass++) { + if (*pass == ' ') { + *pass++ = 0; + break; + } + } + + for (auth_type = pass; *auth_type; auth_type++) { + if (*auth_type == ' ') { + *auth_type++ = 0; + break; + } + } + + msg.service = QMI_WDS; + msg.client_id = ctxt->wds_client_id; + msg.txn_id = ctxt->wds_txn_id; + msg.type = 0x0020; + msg.size = 0; + msg.tlv = data + QMUX_HEADER; + + ctxt->wds_txn_id += 2; + + qmi_add_tlv(&msg, 0x14, strlen(apn), apn); + if (*auth_type) + qmi_add_tlv(&msg, 0x16, strlen(auth_type), auth_type); + if (*user) { + if (!*auth_type) { + unsigned char x; + x = 3; + qmi_add_tlv(&msg, 0x16, 1, &x); + } + qmi_add_tlv(&msg, 0x17, strlen(user), user); + if (*pass) + qmi_add_tlv(&msg, 0x18, strlen(pass), pass); + } + return qmi_send(ctxt, &msg); +} + +static int qmi_network_down(struct qmi_ctxt *ctxt) +{ + unsigned char data[16 + QMUX_OVERHEAD]; + struct qmi_msg msg; + + msg.service = QMI_WDS; + msg.client_id = ctxt->wds_client_id; + msg.txn_id = ctxt->wds_txn_id; + msg.type = 0x0021; + msg.size = 0; + msg.tlv = data + QMUX_HEADER; + + ctxt->wds_txn_id += 2; + + qmi_add_tlv(&msg, 0x01, sizeof(ctxt->wds_handle), &ctxt->wds_handle); + + return qmi_send(ctxt, &msg); +} + +static int qmi_print_state(struct qmi_ctxt *ctxt, char *buf, int max) +{ + int i; + char *statename; + + if (ctxt->state == STATE_ONLINE) { + statename = "up"; + } else if (ctxt->state == STATE_OFFLINE) { + statename = "down"; + } else { + statename = "busy"; + } + + i = scnprintf(buf, max, "STATE=%s\n", statename); + i += scnprintf(buf + i, max - i, "CID=%d\n",ctxt->wds_client_id); + + if (ctxt->state != STATE_ONLINE){ + return i; + } + + i += scnprintf(buf + i, max - i, "ADDR=%d.%d.%d.%d\n", + ctxt->addr[0], ctxt->addr[1], ctxt->addr[2], ctxt->addr[3]); + i += scnprintf(buf + i, max - i, "MASK=%d.%d.%d.%d\n", + ctxt->mask[0], ctxt->mask[1], ctxt->mask[2], ctxt->mask[3]); + i += scnprintf(buf + i, max - i, "GATEWAY=%d.%d.%d.%d\n", + ctxt->gateway[0], ctxt->gateway[1], ctxt->gateway[2], + ctxt->gateway[3]); + i += scnprintf(buf + i, max - i, "DNS1=%d.%d.%d.%d\n", + ctxt->dns1[0], ctxt->dns1[1], ctxt->dns1[2], ctxt->dns1[3]); + i += scnprintf(buf + i, max - i, "DNS2=%d.%d.%d.%d\n", + ctxt->dns2[0], ctxt->dns2[1], ctxt->dns2[2], ctxt->dns2[3]); + + return i; +} + +static ssize_t qmi_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct qmi_ctxt *ctxt = fp->private_data; + char msg[256]; + int len; + int r; + + printk("+qmi_read %d\n", count); + mutex_lock(&ctxt->lock); + for (;;) { + if (ctxt->state_dirty) { + ctxt->state_dirty = 0; + len = qmi_print_state(ctxt, msg, 256); + break; + } + mutex_unlock(&ctxt->lock); + r = wait_event_interruptible(qmi_wait_queue, ctxt->state_dirty); + if (r < 0) + return r; + mutex_lock(&ctxt->lock); + } + mutex_unlock(&ctxt->lock); + + if (len > count) + len = count; + + if (copy_to_user(buf, msg, len)) + return -EFAULT; + + printk("-qmi_read %d\n", len); + return len; +} + + +static ssize_t qmi_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct qmi_ctxt *ctxt = fp->private_data; + unsigned char cmd[64]; + int len; + int r; + + if (count < 1) + return 0; + + len = count > 63 ? 63 : count; + + if (copy_from_user(cmd, buf, len)) + return -EFAULT; + + DBG("+write %s %d\n", cmd, count); + + cmd[len] = 0; + + /* lazy */ + if (cmd[len-1] == '\n') + { + cmd[len-1] = 0; + len--; + } + + if (!strncmp(cmd, "verbose", 7)) { + verbose = 1; + } else if (!strncmp(cmd, "terse", 5)) { + verbose = 0; + } else if (!strncmp(cmd, "poll", 4)) { + ctxt->state_dirty = 1; + wake_up(&qmi_wait_queue); + } else if (!strncmp(cmd, "down", 4)) { +retry_down: + mutex_lock(&ctxt->lock); + if (ctxt->wds_busy) { + mutex_unlock(&ctxt->lock); + r = wait_event_interruptible(qmi_wait_queue, !ctxt->wds_busy); + if (r < 0) + return r; + goto retry_down; + } + ctxt->wds_busy = 1; + qmi_network_down(ctxt); + mutex_unlock(&ctxt->lock); + } else if (!strncmp(cmd, "up:", 3)) { +retry_up: + mutex_lock(&ctxt->lock); + if (ctxt->wds_busy) { + mutex_unlock(&ctxt->lock); + r = wait_event_interruptible(qmi_wait_queue, !ctxt->wds_busy); + if (r < 0) + return r; + goto retry_up; + } + ctxt->wds_busy = 1; + qmi_network_up(ctxt, cmd+3); + mutex_unlock(&ctxt->lock); + } else { + DBG(" bad command\n"); + return -EINVAL; + } + + DBG("-write %d\n", count); + return count; +} + +static int qmi_open(struct inode *ip, struct file *fp) +{ + struct qmi_ctxt *ctxt = qmi_minor_to_ctxt(MINOR(ip->i_rdev)); + int r = 0; + +// TEST - disable GPRS +// return -1; + if (!ctxt) + { + printk(KERN_ERR "unknown qmi misc %d\n", MINOR(ip->i_rdev)); + return -ENODEV; + } + DBG("open %s %d\n", ctxt->ch_name, ctxt->ch_num); + + fp->private_data = ctxt; + + mutex_lock(&ctxt->lock); + + if (ctrl_ch == 0) + { + DBG("first open\n"); + r = smd_open("SMD_CONTROL", &ctrl_ch, ctxt, qmi_notify); + } + else + { + DBG("already opened\n"); + r = 0; + } + + + if (r == 0) + wake_up(&qmi_wait_queue); + mutex_unlock(&ctxt->lock); + + printk("-qmi_open %d\n", r); + return r; +} + +static int qmi_release(struct inode *ip, struct file *fp) +{ + return 0; +} + +static struct file_operations qmi_fops = { + .owner = THIS_MODULE, + .read = qmi_read, + .write = qmi_write, + .open = qmi_open, + .release = qmi_release, +}; + + +static struct qmi_ctxt qmi_device0 = { + .ch_name = "SMD_DATA5", + .ch_num = 11, + .misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "qmi0", + .fops = &qmi_fops, + } +}; +static struct qmi_ctxt qmi_device1 = { + .ch_name = "SMD_DATA6", + .ch_num = 12, + .misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "qmi1", + .fops = &qmi_fops, + } +}; +static struct qmi_ctxt qmi_device2 = { + .ch_name = "SMD_DATA7", + .ch_num = 13, + .misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "qmi2", + .fops = &qmi_fops, + } +}; + +static struct qmi_ctxt *qmi_minor_to_ctxt(unsigned n) +{ + if (n == qmi_device0.misc.minor) + return &qmi_device0; + if (n == qmi_device1.misc.minor) + return &qmi_device1; + if (n == qmi_device2.misc.minor) + return &qmi_device2; + return 0; +} + +static int __init qmi_init(void) +{ + int ret; + + qmi_wq = create_singlethread_workqueue("qmi"); + if (qmi_wq == 0) + return -ENOMEM; + + wake_lock_init(&wakelock, WAKE_LOCK_SUSPEND, "qmi"); + INIT_WORK(&read_work, qmi_read_work); + INIT_WORK(&open_work, qmi_open_work); + + qmi_ctxt_init(&qmi_device0, 0); + qmi_ctxt_init(&qmi_device1, 1); + qmi_ctxt_init(&qmi_device2, 2); + + ret = misc_register(&qmi_device0.misc); + if (ret == 0) + ret = misc_register(&qmi_device1.misc); + if (ret == 0) + ret = misc_register(&qmi_device2.misc); + return ret; +} + +module_init(qmi_init); + + diff --git a/drivers/net/msm_rmnet_wince.c b/drivers/net/msm_rmnet_wince.c new file mode 100644 index 00000000..ad1d1c93 --- /dev/null +++ b/drivers/net/msm_rmnet_wince.c @@ -0,0 +1,542 @@ +/* linux/drivers/net/msm_rmnet_wm.c + * + * Virtual Ethernet Interface for Networking + * + * Copyright (C) 2010 Cotulla + * Copyright (C) 2007 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + +//#define ENABLE_LOGGING 1 + +// enabled for test +//#define CONFIG_MSM_RMNET_DEBUG 1 + +#define READ_BUF_SIZE 32768 + +#if 0 +#define DBG(x...) pr_info("RMNET: "x) +#else +#define DBG(x...) do{}while(0) +#endif + +#include + +/* XXX should come from smd headers */ +#define SMD_PORT_ETHER0 11 +#define POLL_DELAY 1000000 /* 1 second delay interval */ + +struct rmnet_private +{ + smd_channel_t *ch; + struct net_device_stats stats; + const char *chname; + struct wake_lock wake_lock; + uint8_t *buf; +#ifdef CONFIG_MSM_RMNET_DEBUG + ktime_t last_packet; + short active_countdown; /* Number of times left to check */ + short restart_count; /* Number of polls seems so far */ + unsigned long wakeups_xmit; + unsigned long wakeups_rcv; + unsigned long timeout_us; + unsigned long awake_time_ms; + struct delayed_work work; +#endif +}; + +static int count_this_packet(void *_hdr, int len) +{ + struct ethhdr *hdr = _hdr; + + if (len >= ETH_HLEN && hdr->h_proto == htons(ETH_P_ARP)) + return 0; + + return 1; +} + +#ifdef CONFIG_MSM_RMNET_DEBUG +static int in_suspend; +static unsigned long timeout_us; +static struct workqueue_struct *rmnet_wq; + +static void do_check_active(struct work_struct *work) +{ + struct rmnet_private *p = + container_of(work, struct rmnet_private, work.work); + + /* + * Soft timers do not wake the cpu from suspend. + * If we are in suspend, do_check_active is only called once at the + * timeout time instead of polling at POLL_DELAY interval. Otherwise the + * cpu will sleeps and the timer can fire much much later than POLL_DELAY + * casuing a skew in time calculations. + */ + if (in_suspend) { + /* + * Assume for N packets sent durring this session, they are + * uniformly distributed durring the timeout window. + */ + int tmp = p->timeout_us * 2 - + (p->timeout_us / (p->active_countdown + 1)); + tmp /= 1000; + p->awake_time_ms += tmp; + + p->active_countdown = p->restart_count = 0; + return; + } + + /* + * Poll if not in suspend, since this gives more accurate tracking of + * rmnet sessions. + */ + p->restart_count++; + if (--p->active_countdown == 0) { + p->awake_time_ms += p->restart_count * POLL_DELAY / 1000; + p->restart_count = 0; + } else { + queue_delayed_work(rmnet_wq, &p->work, + usecs_to_jiffies(POLL_DELAY)); + } +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +/* + * If early suspend is enabled then we specify two timeout values, + * screen on (default), and screen is off. + */ +static unsigned long timeout_suspend_us; +static struct device *rmnet0; + +/* Set timeout in us when the screen is off. */ +static ssize_t timeout_suspend_store(struct device *d, struct device_attribute *attr, const char *buf, size_t n) +{ + timeout_suspend_us = simple_strtoul(buf, NULL, 10); + return n; +} + +static ssize_t timeout_suspend_show(struct device *d, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%lu\n", (unsigned long) timeout_suspend_us); +} + +static DEVICE_ATTR(timeout_suspend, 0664, timeout_suspend_show, timeout_suspend_store); + +static void rmnet_early_suspend(struct early_suspend *handler) { + if (rmnet0) { + struct rmnet_private *p = netdev_priv(to_net_dev(rmnet0)); + p->timeout_us = timeout_suspend_us; + } + in_suspend = 1; +} + +static void rmnet_late_resume(struct early_suspend *handler) { + if (rmnet0) { + struct rmnet_private *p = netdev_priv(to_net_dev(rmnet0)); + p->timeout_us = timeout_us; + } + in_suspend = 0; +} + +static struct early_suspend rmnet_power_suspend = { + .suspend = rmnet_early_suspend, + .resume = rmnet_late_resume, +}; + +static int __init rmnet_late_init(void) +{ + register_early_suspend(&rmnet_power_suspend); + return 0; +} + +late_initcall(rmnet_late_init); +#endif + +/* Returns 1 if packet caused rmnet to wakeup, 0 otherwise. */ +static int rmnet_cause_wakeup(struct rmnet_private *p) { + int ret = 0; + ktime_t now; + if (p->timeout_us == 0) /* Check if disabled */ + return 0; + + /* Start timer on a wakeup packet */ + if (p->active_countdown == 0) { + ret = 1; + now = ktime_get_real(); + p->last_packet = now; + if (in_suspend) + queue_delayed_work(rmnet_wq, &p->work, + usecs_to_jiffies(p->timeout_us)); + else + queue_delayed_work(rmnet_wq, &p->work, + usecs_to_jiffies(POLL_DELAY)); + } + + if (in_suspend) + p->active_countdown++; + else + p->active_countdown = p->timeout_us / POLL_DELAY; + + return ret; +} + +static ssize_t wakeups_xmit_show(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + return sprintf(buf, "%lu\n", p->wakeups_xmit); +} + +DEVICE_ATTR(wakeups_xmit, 0444, wakeups_xmit_show, NULL); + +static ssize_t wakeups_rcv_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + return sprintf(buf, "%lu\n", p->wakeups_rcv); +} + +DEVICE_ATTR(wakeups_rcv, 0444, wakeups_rcv_show, NULL); + +/* Set timeout in us. */ +static ssize_t timeout_store(struct device *d, struct device_attribute *attr, + const char *buf, size_t n) +{ +#ifndef CONFIG_HAS_EARLYSUSPEND + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + p->timeout_us = timeout_us = simple_strtoul(buf, NULL, 10); +#else +/* If using early suspend/resume hooks do not write the value on store. */ + timeout_us = simple_strtoul(buf, NULL, 10); +#endif + return n; +} + +static ssize_t timeout_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + p = netdev_priv(to_net_dev(d)); + return sprintf(buf, "%lu\n", timeout_us); +} + +DEVICE_ATTR(timeout, 0664, timeout_show, timeout_store); + +/* Show total radio awake time in ms */ +static ssize_t awake_time_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + return sprintf(buf, "%lu\n", p->awake_time_ms); +} +DEVICE_ATTR(awake_time_ms, 0444, awake_time_show, NULL); + +#endif + +/* Called in soft-irq context */ +static void smd_net_data_handler(unsigned long arg) +{ + struct net_device *dev = (struct net_device *) arg; + struct rmnet_private *p = netdev_priv(dev); + struct sk_buff *skb; + void *ptr = 0; + uint8_t *curbuf = 0; + uint32_t offset; + uint32_t l, pksz; + int sz; + struct ethhdr *hdr; + + // DBG("+rx data\n"); + for (;;) + { + sz = smd_read_avail(p->ch); + DBG("RX: size %d\n", sz); + if (sz == 0) + break; + + if (sz > READ_BUF_SIZE) + { + pr_err("rmnet_recv() size too big %d?!\n", sz); + BUG(); + } + + wake_lock_timeout(&p->wake_lock, HZ / 2); + + if (smd_read(p->ch, p->buf, sz) != sz) + { + pr_err("rmnet_recv() smd lied about avail?!\n"); + continue; + } + offset = 0; + + while (offset < sz) + { + curbuf = p->buf + offset; + hdr = (struct ethhdr *)curbuf; + + if (curbuf[12] == 8 && curbuf[13] == 6) + { + struct arphdr *ah = (struct arphdr*)(curbuf + 14); + l = sizeof(struct arphdr) + (ah->ar_hln + ah->ar_pln) * 2; + pksz = l + 14; + } + else if (curbuf[12] == 8 && curbuf[13] == 0) + { + struct iphdr *ih = (struct iphdr *)(curbuf + 14); + l = ntohs(ih->tot_len); + pksz = l + 14; + } + else + { + pr_err("rmnet_recv() wrong header!\n"); + l = 0; + pksz = 0; + break; + } + + skb = dev_alloc_skb(pksz + NET_IP_ALIGN); + if (skb == NULL) + { + pr_err("rmnet_recv() cannot allocate skb\n"); + } + else + { + skb->dev = dev; + skb_reserve(skb, NET_IP_ALIGN); + ptr = skb_put(skb, pksz); + memcpy(ptr, curbuf, pksz); + + skb->protocol = eth_type_trans(skb, dev); +#ifdef ENABLE_LOGGING + printk("RX: packetdata: %d %x recv: %d\n", pksz, offset, skb->protocol); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, ptr, pksz); +#endif + + if (count_this_packet(ptr, skb->len)) + { +#ifdef CONFIG_MSM_RMNET_DEBUG + p->wakeups_rcv += rmnet_cause_wakeup(p); +#endif + p->stats.rx_packets++; + p->stats.rx_bytes += skb->len; + } + netif_rx(skb); + } + offset += pksz; + } + } + // DBG("-rx data\n"); +} + + +static DECLARE_TASKLET(smd_net_data_tasklet, smd_net_data_handler, 0); + +static void smd_net_notify(void *_dev, unsigned event) +{ + if (event != SMD_EVENT_DATA) + return; + + smd_net_data_tasklet.data = (unsigned long) _dev; + + tasklet_schedule(&smd_net_data_tasklet); +} + + +static int rmnet_open(struct net_device *dev) +{ + int r; + struct rmnet_private *p = netdev_priv(dev); + + pr_info("rmnet_open()\n"); + + if (!p->ch) + { + r = smd_open(p->chname, &p->ch, dev, smd_net_notify); + + if (r < 0) + return -ENODEV; + } + + p->buf = kmalloc(READ_BUF_SIZE, GFP_KERNEL); + netif_start_queue(dev); + return 0; +} + +static int rmnet_stop(struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + pr_info("rmnet_stop()\n"); + netif_stop_queue(dev); + kfree(p->buf); + return 0; +} + +static int rmnet_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + smd_channel_t *ch = p->ch; + +#ifdef ENABLE_LOGGING + printk("TX: packetdata: %d\n", skb->len); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, skb->data, skb->len); +#endif + + if (smd_write_atomic(ch, skb->data, skb->len) != skb->len) { + pr_err("rmnet fifo full, dropping packet\n"); + } else { + if (count_this_packet(skb->data, skb->len)) { + p->stats.tx_packets++; + p->stats.tx_bytes += skb->len; +#ifdef CONFIG_MSM_RMNET_DEBUG + p->wakeups_xmit += rmnet_cause_wakeup(p); +#endif + } + } + + dev_kfree_skb_irq(skb); + return 0; +} + +static struct net_device_stats *rmnet_get_stats(struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + return &p->stats; +} + +static void rmnet_set_multicast_list(struct net_device *dev) +{ +} + +static void rmnet_tx_timeout(struct net_device *dev) +{ + pr_info("rmnet_tx_timeout()\n"); +} + +static struct net_device_ops rmnet_ops = { + .ndo_open = rmnet_open, + .ndo_stop = rmnet_stop, + .ndo_start_xmit = rmnet_xmit, + .ndo_get_stats = rmnet_get_stats, + .ndo_set_multicast_list = rmnet_set_multicast_list, + .ndo_tx_timeout = rmnet_tx_timeout, +}; + +static void __init rmnet_setup(struct net_device *dev) +{ + dev->netdev_ops = &rmnet_ops; + + DBG("setup\n"); + dev->watchdog_timeo = 20; /* ??? */ + + ether_setup(dev); + + //dev->change_mtu = 0; /* ??? */ + + random_ether_addr(dev->dev_addr); +} + + +static const char *ch_name[3] = { + "SMD_DATA5", + "SMD_DATA6", + "SMD_DATA7", +}; + +static int __init rmnet_init(void) +{ + int ret; + struct device *d; + struct net_device *dev; + struct rmnet_private *p; + unsigned n; + +#ifdef CONFIG_MSM_RMNET_DEBUG + timeout_us = 0; +#ifdef CONFIG_HAS_EARLYSUSPEND + timeout_suspend_us = 0; +#endif +#endif + +#ifdef CONFIG_MSM_RMNET_DEBUG + rmnet_wq = create_workqueue("rmnet"); +#endif + + for (n = 0; n < 3; n++) + { + dev = alloc_netdev(sizeof(struct rmnet_private), + "rmnet%d", rmnet_setup); + + if (!dev) + return -ENOMEM; + + d = &(dev->dev); + p = netdev_priv(dev); + p->chname = ch_name[n]; + wake_lock_init(&p->wake_lock, WAKE_LOCK_SUSPEND, ch_name[n]); +#ifdef CONFIG_MSM_RMNET_DEBUG + p->timeout_us = timeout_us; + p->awake_time_ms = p->wakeups_xmit = p->wakeups_rcv = 0; + p->active_countdown = p->restart_count = 0; + INIT_DELAYED_WORK_DEFERRABLE(&p->work, do_check_active); +#endif + + ret = register_netdev(dev); + if (ret) { + free_netdev(dev); + return ret; + } + +#ifdef CONFIG_MSM_RMNET_DEBUG + if (device_create_file(d, &dev_attr_timeout)) + continue; + if (device_create_file(d, &dev_attr_wakeups_xmit)) + continue; + if (device_create_file(d, &dev_attr_wakeups_rcv)) + continue; + if (device_create_file(d, &dev_attr_awake_time_ms)) + continue; +#ifdef CONFIG_HAS_EARLYSUSPEND + if (device_create_file(d, &dev_attr_timeout_suspend)) + continue; + + /* Only care about rmnet0 for suspend/resume tiemout hooks. */ + if (n == 0) + rmnet0 = d; +#endif +#endif + } + return 0; +} + +module_init(rmnet_init);