linux/drivers/staging/dream/smd/smd_qmi.c
<<
>>
Prefs
   1/* arch/arm/mach-msm/smd_qmi.c
   2 *
   3 * QMI Control Driver -- Manages network data connections.
   4 *
   5 * Copyright (C) 2007 Google, Inc.
   6 * Author: Brian Swetland <swetland@google.com>
   7 *
   8 * This software is licensed under the terms of the GNU General Public
   9 * License version 2, as published by the Free Software Foundation, and
  10 * may be copied, distributed, and modified under those terms.
  11 *
  12 * This program is distributed in the hope that it will be useful,
  13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15 * GNU General Public License for more details.
  16 *
  17 */
  18
  19#include <linux/module.h>
  20#include <linux/fs.h>
  21#include <linux/cdev.h>
  22#include <linux/device.h>
  23#include <linux/sched.h>
  24#include <linux/wait.h>
  25#include <linux/miscdevice.h>
  26#include <linux/workqueue.h>
  27#include <linux/wakelock.h>
  28
  29#include <asm/uaccess.h>
  30#include <mach/msm_smd.h>
  31
  32#define QMI_CTL 0x00
  33#define QMI_WDS 0x01
  34#define QMI_DMS 0x02
  35#define QMI_NAS 0x03
  36
  37#define QMI_RESULT_SUCCESS 0x0000
  38#define QMI_RESULT_FAILURE 0x0001
  39
  40struct qmi_msg {
  41        unsigned char service;
  42        unsigned char client_id;
  43        unsigned short txn_id;
  44        unsigned short type;
  45        unsigned short size;
  46        unsigned char *tlv;
  47};
  48
  49#define qmi_ctl_client_id 0
  50
  51#define STATE_OFFLINE    0
  52#define STATE_QUERYING   1
  53#define STATE_ONLINE     2
  54
  55struct qmi_ctxt {
  56        struct miscdevice misc;
  57
  58        struct mutex lock;
  59
  60        unsigned char ctl_txn_id;
  61        unsigned char wds_client_id;
  62        unsigned short wds_txn_id;
  63
  64        unsigned wds_busy;
  65        unsigned wds_handle;
  66        unsigned state_dirty;
  67        unsigned state;
  68
  69        unsigned char addr[4];
  70        unsigned char mask[4];
  71        unsigned char gateway[4];
  72        unsigned char dns1[4];
  73        unsigned char dns2[4];
  74
  75        smd_channel_t *ch;
  76        const char *ch_name;
  77        struct wake_lock wake_lock;
  78
  79        struct work_struct open_work;
  80        struct work_struct read_work;
  81};
  82
  83static struct qmi_ctxt *qmi_minor_to_ctxt(unsigned n);
  84
  85static void qmi_read_work(struct work_struct *ws);
  86static void qmi_open_work(struct work_struct *work);
  87
  88void qmi_ctxt_init(struct qmi_ctxt *ctxt, unsigned n)
  89{
  90        mutex_init(&ctxt->lock);
  91        INIT_WORK(&ctxt->read_work, qmi_read_work);
  92        INIT_WORK(&ctxt->open_work, qmi_open_work);
  93        wake_lock_init(&ctxt->wake_lock, WAKE_LOCK_SUSPEND, ctxt->misc.name);
  94        ctxt->ctl_txn_id = 1;
  95        ctxt->wds_txn_id = 1;
  96        ctxt->wds_busy = 1;
  97        ctxt->state = STATE_OFFLINE;
  98
  99}
 100
 101static struct workqueue_struct *qmi_wq;
 102
 103static int verbose = 0;
 104
 105/* anyone waiting for a state change waits here */
 106static DECLARE_WAIT_QUEUE_HEAD(qmi_wait_queue);
 107
 108
 109static void qmi_dump_msg(struct qmi_msg *msg, const char *prefix)
 110{
 111        unsigned sz, n;
 112        unsigned char *x;
 113
 114        if (!verbose)
 115                return;
 116
 117        printk(KERN_INFO
 118               "qmi: %s: svc=%02x cid=%02x tid=%04x type=%04x size=%04x\n",
 119               prefix, msg->service, msg->client_id,
 120               msg->txn_id, msg->type, msg->size);
 121
 122        x = msg->tlv;
 123        sz = msg->size;
 124
 125        while (sz >= 3) {
 126                sz -= 3;
 127
 128                n = x[1] | (x[2] << 8);
 129                if (n > sz)
 130                        break;
 131
 132                printk(KERN_INFO "qmi: %s: tlv: %02x %04x { ",
 133                       prefix, x[0], n);
 134                x += 3;
 135                sz -= n;
 136                while (n-- > 0)
 137                        printk("%02x ", *x++);
 138                printk("}\n");
 139        }
 140}
 141
 142int qmi_add_tlv(struct qmi_msg *msg,
 143                unsigned type, unsigned size, const void *data)
 144{
 145        unsigned char *x = msg->tlv + msg->size;
 146
 147        x[0] = type;
 148        x[1] = size;
 149        x[2] = size >> 8;
 150
 151        memcpy(x + 3, data, size);
 152
 153        msg->size += (size + 3);
 154
 155        return 0;
 156}
 157
 158/* Extract a tagged item from a qmi message buffer,
 159** taking care not to overrun the buffer.
 160*/
 161static int qmi_get_tlv(struct qmi_msg *msg,
 162                       unsigned type, unsigned size, void *data)
 163{
 164        unsigned char *x = msg->tlv;
 165        unsigned len = msg->size;
 166        unsigned n;
 167
 168        while (len >= 3) {
 169                len -= 3;
 170
 171                /* size of this item */
 172                n = x[1] | (x[2] << 8);
 173                if (n > len)
 174                        break;
 175
 176                if (x[0] == type) {
 177                        if (n != size)
 178                                return -1;
 179                        memcpy(data, x + 3, size);
 180                        return 0;
 181                }
 182
 183                x += (n + 3);
 184                len -= n;
 185        }
 186
 187        return -1;
 188}
 189
 190static unsigned qmi_get_status(struct qmi_msg *msg, unsigned *error)
 191{
 192        unsigned short status[2];
 193        if (qmi_get_tlv(msg, 0x02, sizeof(status), status)) {
 194                *error = 0;
 195                return QMI_RESULT_FAILURE;
 196        } else {
 197                *error = status[1];
 198                return status[0];
 199        }
 200}
 201
 202/* 0x01 <qmux-header> <payload> */
 203#define QMUX_HEADER    13
 204
 205/* should be >= HEADER + FOOTER */
 206#define QMUX_OVERHEAD  16
 207
 208static int qmi_send(struct qmi_ctxt *ctxt, struct qmi_msg *msg)
 209{
 210        unsigned char *data;
 211        unsigned hlen;
 212        unsigned len;
 213        int r;
 214
 215        qmi_dump_msg(msg, "send");
 216
 217        if (msg->service == QMI_CTL) {
 218                hlen = QMUX_HEADER - 1;
 219        } else {
 220                hlen = QMUX_HEADER;
 221        }
 222
 223        /* QMUX length is total header + total payload - IFC selector */
 224        len = hlen + msg->size - 1;
 225        if (len > 0xffff)
 226                return -1;
 227
 228        data = msg->tlv - hlen;
 229
 230        /* prepend encap and qmux header */
 231        *data++ = 0x01; /* ifc selector */
 232
 233        /* qmux header */
 234        *data++ = len;
 235        *data++ = len >> 8;
 236        *data++ = 0x00; /* flags: client */
 237        *data++ = msg->service;
 238        *data++ = msg->client_id;
 239
 240        /* qmi header */
 241        *data++ = 0x00; /* flags: send */
 242        *data++ = msg->txn_id;
 243        if (msg->service != QMI_CTL)
 244                *data++ = msg->txn_id >> 8;
 245
 246        *data++ = msg->type;
 247        *data++ = msg->type >> 8;
 248        *data++ = msg->size;
 249        *data++ = msg->size >> 8;
 250
 251        /* len + 1 takes the interface selector into account */
 252        r = smd_write(ctxt->ch, msg->tlv - hlen, len + 1);
 253
 254        if (r != len) {
 255                return -1;
 256        } else {
 257                return 0;
 258        }
 259}
 260
 261static void qmi_process_ctl_msg(struct qmi_ctxt *ctxt, struct qmi_msg *msg)
 262{
 263        unsigned err;
 264        if (msg->type == 0x0022) {
 265                unsigned char n[2];
 266                if (qmi_get_status(msg, &err))
 267                        return;
 268                if (qmi_get_tlv(msg, 0x01, sizeof(n), n))
 269                        return;
 270                if (n[0] == QMI_WDS) {
 271                        printk(KERN_INFO
 272                               "qmi: ctl: wds use client_id 0x%02x\n", n[1]);
 273                        ctxt->wds_client_id = n[1];
 274                        ctxt->wds_busy = 0;
 275                }
 276        }
 277}
 278
 279static int qmi_network_get_profile(struct qmi_ctxt *ctxt);
 280
 281static void swapaddr(unsigned char *src, unsigned char *dst)
 282{
 283        dst[0] = src[3];
 284        dst[1] = src[2];
 285        dst[2] = src[1];
 286        dst[3] = src[0];
 287}
 288
 289static unsigned char zero[4];
 290static void qmi_read_runtime_profile(struct qmi_ctxt *ctxt, struct qmi_msg *msg)
 291{
 292        unsigned char tmp[4];
 293        unsigned r;
 294
 295        r = qmi_get_tlv(msg, 0x1e, 4, tmp);
 296        swapaddr(r ? zero : tmp, ctxt->addr);
 297        r = qmi_get_tlv(msg, 0x21, 4, tmp);
 298        swapaddr(r ? zero : tmp, ctxt->mask);
 299        r = qmi_get_tlv(msg, 0x20, 4, tmp);
 300        swapaddr(r ? zero : tmp, ctxt->gateway);
 301        r = qmi_get_tlv(msg, 0x15, 4, tmp);
 302        swapaddr(r ? zero : tmp, ctxt->dns1);
 303        r = qmi_get_tlv(msg, 0x16, 4, tmp);
 304        swapaddr(r ? zero : tmp, ctxt->dns2);
 305}
 306
 307static void qmi_process_unicast_wds_msg(struct qmi_ctxt *ctxt,
 308                                        struct qmi_msg *msg)
 309{
 310        unsigned err;
 311        switch (msg->type) {
 312        case 0x0021:
 313                if (qmi_get_status(msg, &err)) {
 314                        printk(KERN_ERR
 315                               "qmi: wds: network stop failed (%04x)\n", err);
 316                } else {
 317                        printk(KERN_INFO
 318                               "qmi: wds: network stopped\n");
 319                        ctxt->state = STATE_OFFLINE;
 320                        ctxt->state_dirty = 1;
 321                }
 322                break;
 323        case 0x0020:
 324                if (qmi_get_status(msg, &err)) {
 325                        printk(KERN_ERR
 326                               "qmi: wds: network start failed (%04x)\n", err);
 327                } else if (qmi_get_tlv(msg, 0x01, sizeof(ctxt->wds_handle), &ctxt->wds_handle)) {
 328                        printk(KERN_INFO
 329                               "qmi: wds no handle?\n");
 330                } else {
 331                        printk(KERN_INFO
 332                               "qmi: wds: got handle 0x%08x\n",
 333                               ctxt->wds_handle);
 334                }
 335                break;
 336        case 0x002D:
 337                printk("qmi: got network profile\n");
 338                if (ctxt->state == STATE_QUERYING) {
 339                        qmi_read_runtime_profile(ctxt, msg);
 340                        ctxt->state = STATE_ONLINE;
 341                        ctxt->state_dirty = 1;
 342                }
 343                break;
 344        default:
 345                printk(KERN_ERR "qmi: unknown msg type 0x%04x\n", msg->type);
 346        }
 347        ctxt->wds_busy = 0;
 348}
 349
 350static void qmi_process_broadcast_wds_msg(struct qmi_ctxt *ctxt,
 351                                          struct qmi_msg *msg)
 352{
 353        if (msg->type == 0x0022) {
 354                unsigned char n[2];
 355                if (qmi_get_tlv(msg, 0x01, sizeof(n), n))
 356                        return;
 357                switch (n[0]) {
 358                case 1:
 359                        printk(KERN_INFO "qmi: wds: DISCONNECTED\n");
 360                        ctxt->state = STATE_OFFLINE;
 361                        ctxt->state_dirty = 1;
 362                        break;
 363                case 2:
 364                        printk(KERN_INFO "qmi: wds: CONNECTED\n");
 365                        ctxt->state = STATE_QUERYING;
 366                        ctxt->state_dirty = 1;
 367                        qmi_network_get_profile(ctxt);
 368                        break;
 369                case 3:
 370                        printk(KERN_INFO "qmi: wds: SUSPENDED\n");
 371                        ctxt->state = STATE_OFFLINE;
 372                        ctxt->state_dirty = 1;
 373                }
 374        } else {
 375                printk(KERN_ERR "qmi: unknown bcast msg type 0x%04x\n", msg->type);
 376        }
 377}
 378
 379static void qmi_process_wds_msg(struct qmi_ctxt *ctxt,
 380                                struct qmi_msg *msg)
 381{
 382        printk("wds: %04x @ %02x\n", msg->type, msg->client_id);
 383        if (msg->client_id == ctxt->wds_client_id) {
 384                qmi_process_unicast_wds_msg(ctxt, msg);
 385        } else if (msg->client_id == 0xff) {
 386                qmi_process_broadcast_wds_msg(ctxt, msg);
 387        } else {
 388                printk(KERN_ERR
 389                       "qmi_process_wds_msg client id 0x%02x unknown\n",
 390                       msg->client_id);
 391        }
 392}
 393
 394static void qmi_process_qmux(struct qmi_ctxt *ctxt,
 395                             unsigned char *buf, unsigned sz)
 396{
 397        struct qmi_msg msg;
 398
 399        /* require a full header */
 400        if (sz < 5)
 401                return;
 402
 403        /* require a size that matches the buffer size */
 404        if (sz != (buf[0] | (buf[1] << 8)))
 405                return;
 406
 407        /* only messages from a service (bit7=1) are allowed */
 408        if (buf[2] != 0x80)
 409                return;
 410
 411        msg.service = buf[3];
 412        msg.client_id = buf[4];
 413
 414        /* annoyingly, CTL messages have a shorter TID */
 415        if (buf[3] == 0) {
 416                if (sz < 7)
 417                        return;
 418                msg.txn_id = buf[6];
 419                buf += 7;
 420                sz -= 7;
 421        } else {
 422                if (sz < 8)
 423                        return;
 424                msg.txn_id = buf[6] | (buf[7] << 8);
 425                buf += 8;
 426                sz -= 8;
 427        }
 428
 429        /* no type and size!? */
 430        if (sz < 4)
 431                return;
 432        sz -= 4;
 433
 434        msg.type = buf[0] | (buf[1] << 8);
 435        msg.size = buf[2] | (buf[3] << 8);
 436        msg.tlv = buf + 4;
 437
 438        if (sz != msg.size)
 439                return;
 440
 441        qmi_dump_msg(&msg, "recv");
 442
 443        mutex_lock(&ctxt->lock);
 444        switch (msg.service) {
 445        case QMI_CTL:
 446                qmi_process_ctl_msg(ctxt, &msg);
 447                break;
 448        case QMI_WDS:
 449                qmi_process_wds_msg(ctxt, &msg);
 450                break;
 451        default:
 452                printk(KERN_ERR "qmi: msg from unknown svc 0x%02x\n",
 453                       msg.service);
 454                break;
 455        }
 456        mutex_unlock(&ctxt->lock);
 457
 458        wake_up(&qmi_wait_queue);
 459}
 460
 461#define QMI_MAX_PACKET (256 + QMUX_OVERHEAD)
 462
 463static void qmi_read_work(struct work_struct *ws)
 464{
 465        struct qmi_ctxt *ctxt = container_of(ws, struct qmi_ctxt, read_work);
 466        struct smd_channel *ch = ctxt->ch;
 467        unsigned char buf[QMI_MAX_PACKET];
 468        int sz;
 469
 470        for (;;) {
 471                sz = smd_cur_packet_size(ch);
 472                if (sz == 0)
 473                        break;
 474                if (sz < smd_read_avail(ch))
 475                        break;
 476                if (sz > QMI_MAX_PACKET) {
 477                        smd_read(ch, 0, sz);
 478                        continue;
 479                }
 480                if (smd_read(ch, buf, sz) != sz) {
 481                        printk(KERN_ERR "qmi: not enough data?!\n");
 482                        continue;
 483                }
 484
 485                /* interface selector must be 1 */
 486                if (buf[0] != 0x01)
 487                        continue;
 488
 489                qmi_process_qmux(ctxt, buf + 1, sz - 1);
 490        }
 491}
 492
 493static int qmi_request_wds_cid(struct qmi_ctxt *ctxt);
 494
 495static void qmi_open_work(struct work_struct *ws)
 496{
 497        struct qmi_ctxt *ctxt = container_of(ws, struct qmi_ctxt, open_work);
 498        mutex_lock(&ctxt->lock);
 499        qmi_request_wds_cid(ctxt);
 500        mutex_unlock(&ctxt->lock);
 501}
 502
 503static void qmi_notify(void *priv, unsigned event)
 504{
 505        struct qmi_ctxt *ctxt = priv;
 506
 507        switch (event) {
 508        case SMD_EVENT_DATA: {
 509                int sz;
 510                sz = smd_cur_packet_size(ctxt->ch);
 511                if ((sz > 0) && (sz <= smd_read_avail(ctxt->ch))) {
 512                        wake_lock_timeout(&ctxt->wake_lock, HZ / 2);
 513                        queue_work(qmi_wq, &ctxt->read_work);
 514                }
 515                break;
 516        }
 517        case SMD_EVENT_OPEN:
 518                printk(KERN_INFO "qmi: smd opened\n");
 519                queue_work(qmi_wq, &ctxt->open_work);
 520                break;
 521        case SMD_EVENT_CLOSE:
 522                printk(KERN_INFO "qmi: smd closed\n");
 523                break;
 524        }
 525}
 526
 527static int qmi_request_wds_cid(struct qmi_ctxt *ctxt)
 528{
 529        unsigned char data[64 + QMUX_OVERHEAD];
 530        struct qmi_msg msg;
 531        unsigned char n;
 532
 533        msg.service = QMI_CTL;
 534        msg.client_id = qmi_ctl_client_id;
 535        msg.txn_id = ctxt->ctl_txn_id;
 536        msg.type = 0x0022;
 537        msg.size = 0;
 538        msg.tlv = data + QMUX_HEADER;
 539
 540        ctxt->ctl_txn_id += 2;
 541
 542        n = QMI_WDS;
 543        qmi_add_tlv(&msg, 0x01, 0x01, &n);
 544
 545        return qmi_send(ctxt, &msg);
 546}
 547
 548static int qmi_network_get_profile(struct qmi_ctxt *ctxt)
 549{
 550        unsigned char data[96 + QMUX_OVERHEAD];
 551        struct qmi_msg msg;
 552
 553        msg.service = QMI_WDS;
 554        msg.client_id = ctxt->wds_client_id;
 555        msg.txn_id = ctxt->wds_txn_id;
 556        msg.type = 0x002D;
 557        msg.size = 0;
 558        msg.tlv = data + QMUX_HEADER;
 559
 560        ctxt->wds_txn_id += 2;
 561
 562        return qmi_send(ctxt, &msg);
 563}
 564
 565static int qmi_network_up(struct qmi_ctxt *ctxt, char *apn)
 566{
 567        unsigned char data[96 + QMUX_OVERHEAD];
 568        struct qmi_msg msg;
 569        char *auth_type;
 570        char *user;
 571        char *pass;
 572
 573        for (user = apn; *user; user++) {
 574                if (*user == ' ') {
 575                        *user++ = 0;
 576                        break;
 577                }
 578        }
 579        for (pass = user; *pass; pass++) {
 580                if (*pass == ' ') {
 581                        *pass++ = 0;
 582                        break;
 583                }
 584        }
 585
 586        for (auth_type = pass; *auth_type; auth_type++) {
 587                if (*auth_type == ' ') {
 588                        *auth_type++ = 0;
 589                        break;
 590                }
 591        }
 592
 593        msg.service = QMI_WDS;
 594        msg.client_id = ctxt->wds_client_id;
 595        msg.txn_id = ctxt->wds_txn_id;
 596        msg.type = 0x0020;
 597        msg.size = 0;
 598        msg.tlv = data + QMUX_HEADER;
 599
 600        ctxt->wds_txn_id += 2;
 601
 602        qmi_add_tlv(&msg, 0x14, strlen(apn), apn);
 603        if (*auth_type)
 604                qmi_add_tlv(&msg, 0x16, strlen(auth_type), auth_type);
 605        if (*user) {
 606                if (!*auth_type) {
 607                        unsigned char x;
 608                        x = 3;
 609                        qmi_add_tlv(&msg, 0x16, 1, &x);
 610                }
 611                qmi_add_tlv(&msg, 0x17, strlen(user), user);
 612                if (*pass)
 613                        qmi_add_tlv(&msg, 0x18, strlen(pass), pass);
 614        }
 615        return qmi_send(ctxt, &msg);
 616}
 617
 618static int qmi_network_down(struct qmi_ctxt *ctxt)
 619{
 620        unsigned char data[16 + QMUX_OVERHEAD];
 621        struct qmi_msg msg;
 622
 623        msg.service = QMI_WDS;
 624        msg.client_id = ctxt->wds_client_id;
 625        msg.txn_id = ctxt->wds_txn_id;
 626        msg.type = 0x0021;
 627        msg.size = 0;
 628        msg.tlv = data + QMUX_HEADER;
 629
 630        ctxt->wds_txn_id += 2;
 631
 632        qmi_add_tlv(&msg, 0x01, sizeof(ctxt->wds_handle), &ctxt->wds_handle);
 633
 634        return qmi_send(ctxt, &msg);
 635}
 636
 637static int qmi_print_state(struct qmi_ctxt *ctxt, char *buf, int max)
 638{
 639        int i;
 640        char *statename;
 641
 642        if (ctxt->state == STATE_ONLINE) {
 643                statename = "up";
 644        } else if (ctxt->state == STATE_OFFLINE) {
 645                statename = "down";
 646        } else {
 647                statename = "busy";
 648        }
 649
 650        i = scnprintf(buf, max, "STATE=%s\n", statename);
 651        i += scnprintf(buf + i, max - i, "CID=%d\n",ctxt->wds_client_id);
 652
 653        if (ctxt->state != STATE_ONLINE){
 654                return i;
 655        }
 656
 657        i += scnprintf(buf + i, max - i, "ADDR=%d.%d.%d.%d\n",
 658                ctxt->addr[0], ctxt->addr[1], ctxt->addr[2], ctxt->addr[3]);
 659        i += scnprintf(buf + i, max - i, "MASK=%d.%d.%d.%d\n",
 660                ctxt->mask[0], ctxt->mask[1], ctxt->mask[2], ctxt->mask[3]);
 661        i += scnprintf(buf + i, max - i, "GATEWAY=%d.%d.%d.%d\n",
 662                ctxt->gateway[0], ctxt->gateway[1], ctxt->gateway[2],
 663                ctxt->gateway[3]);
 664        i += scnprintf(buf + i, max - i, "DNS1=%d.%d.%d.%d\n",
 665                ctxt->dns1[0], ctxt->dns1[1], ctxt->dns1[2], ctxt->dns1[3]);
 666        i += scnprintf(buf + i, max - i, "DNS2=%d.%d.%d.%d\n",
 667                ctxt->dns2[0], ctxt->dns2[1], ctxt->dns2[2], ctxt->dns2[3]);
 668
 669        return i;
 670}
 671
 672static ssize_t qmi_read(struct file *fp, char __user *buf,
 673                        size_t count, loff_t *pos)
 674{
 675        struct qmi_ctxt *ctxt = fp->private_data;
 676        char msg[256];
 677        int len;
 678        int r;
 679
 680        mutex_lock(&ctxt->lock);
 681        for (;;) {
 682                if (ctxt->state_dirty) {
 683                        ctxt->state_dirty = 0;
 684                        len = qmi_print_state(ctxt, msg, 256);
 685                        break;
 686                }
 687                mutex_unlock(&ctxt->lock);
 688                r = wait_event_interruptible(qmi_wait_queue, ctxt->state_dirty);
 689                if (r < 0)
 690                        return r;
 691                mutex_lock(&ctxt->lock);
 692        }
 693        mutex_unlock(&ctxt->lock);
 694
 695        if (len > count)
 696                len = count;
 697
 698        if (copy_to_user(buf, msg, len))
 699                return -EFAULT;
 700
 701        return len;
 702}
 703
 704
 705static ssize_t qmi_write(struct file *fp, const char __user *buf,
 706                         size_t count, loff_t *pos)
 707{
 708        struct qmi_ctxt *ctxt = fp->private_data;
 709        unsigned char cmd[64];
 710        int len;
 711        int r;
 712
 713        if (count < 1)
 714                return 0;
 715
 716        len = count > 63 ? 63 : count;
 717
 718        if (copy_from_user(cmd, buf, len))
 719                return -EFAULT;
 720
 721        cmd[len] = 0;
 722
 723        /* lazy */
 724        if (cmd[len-1] == '\n') {
 725                cmd[len-1] = 0;
 726                len--;
 727        }
 728
 729        if (!strncmp(cmd, "verbose", 7)) {
 730                verbose = 1;
 731        } else if (!strncmp(cmd, "terse", 5)) {
 732                verbose = 0;
 733        } else if (!strncmp(cmd, "poll", 4)) {
 734                ctxt->state_dirty = 1;
 735                wake_up(&qmi_wait_queue);
 736        } else if (!strncmp(cmd, "down", 4)) {
 737retry_down:
 738                mutex_lock(&ctxt->lock);
 739                if (ctxt->wds_busy) {
 740                        mutex_unlock(&ctxt->lock);
 741                        r = wait_event_interruptible(qmi_wait_queue, !ctxt->wds_busy);
 742                        if (r < 0)
 743                                return r;
 744                        goto retry_down;
 745                }
 746                ctxt->wds_busy = 1;
 747                qmi_network_down(ctxt);
 748                mutex_unlock(&ctxt->lock);
 749        } else if (!strncmp(cmd, "up:", 3)) {
 750retry_up:
 751                mutex_lock(&ctxt->lock);
 752                if (ctxt->wds_busy) {
 753                        mutex_unlock(&ctxt->lock);
 754                        r = wait_event_interruptible(qmi_wait_queue, !ctxt->wds_busy);
 755                        if (r < 0)
 756                                return r;
 757                        goto retry_up;
 758                }
 759                ctxt->wds_busy = 1;
 760                qmi_network_up(ctxt, cmd+3);
 761                mutex_unlock(&ctxt->lock);
 762        } else {
 763                return -EINVAL;
 764        }
 765
 766        return count;
 767}
 768
 769static int qmi_open(struct inode *ip, struct file *fp)
 770{
 771        struct qmi_ctxt *ctxt = qmi_minor_to_ctxt(MINOR(ip->i_rdev));
 772        int r = 0;
 773
 774        if (!ctxt) {
 775                printk(KERN_ERR "unknown qmi misc %d\n", MINOR(ip->i_rdev));
 776                return -ENODEV;
 777        }
 778
 779        fp->private_data = ctxt;
 780
 781        mutex_lock(&ctxt->lock);
 782        if (ctxt->ch == 0)
 783                r = smd_open(ctxt->ch_name, &ctxt->ch, ctxt, qmi_notify);
 784        if (r == 0)
 785                wake_up(&qmi_wait_queue);
 786        mutex_unlock(&ctxt->lock);
 787
 788        return r;
 789}
 790
 791static int qmi_release(struct inode *ip, struct file *fp)
 792{
 793        return 0;
 794}
 795
 796static struct file_operations qmi_fops = {
 797        .owner = THIS_MODULE,
 798        .read = qmi_read,
 799        .write = qmi_write,
 800        .open = qmi_open,
 801        .release = qmi_release,
 802};
 803
 804static struct qmi_ctxt qmi_device0 = {
 805        .ch_name = "SMD_DATA5_CNTL",
 806        .misc = {
 807                .minor = MISC_DYNAMIC_MINOR,
 808                .name = "qmi0",
 809                .fops = &qmi_fops,
 810        }
 811};
 812static struct qmi_ctxt qmi_device1 = {
 813        .ch_name = "SMD_DATA6_CNTL",
 814        .misc = {
 815                .minor = MISC_DYNAMIC_MINOR,
 816                .name = "qmi1",
 817                .fops = &qmi_fops,
 818        }
 819};
 820static struct qmi_ctxt qmi_device2 = {
 821        .ch_name = "SMD_DATA7_CNTL",
 822        .misc = {
 823                .minor = MISC_DYNAMIC_MINOR,
 824                .name = "qmi2",
 825                .fops = &qmi_fops,
 826        }
 827};
 828
 829static struct qmi_ctxt *qmi_minor_to_ctxt(unsigned n)
 830{
 831        if (n == qmi_device0.misc.minor)
 832                return &qmi_device0;
 833        if (n == qmi_device1.misc.minor)
 834                return &qmi_device1;
 835        if (n == qmi_device2.misc.minor)
 836                return &qmi_device2;
 837        return 0;
 838}
 839
 840static int __init qmi_init(void)
 841{
 842        int ret;
 843
 844        qmi_wq = create_singlethread_workqueue("qmi");
 845        if (qmi_wq == 0)
 846                return -ENOMEM;
 847
 848        qmi_ctxt_init(&qmi_device0, 0);
 849        qmi_ctxt_init(&qmi_device1, 1);
 850        qmi_ctxt_init(&qmi_device2, 2);
 851
 852        ret = misc_register(&qmi_device0.misc);
 853        if (ret == 0)
 854                ret = misc_register(&qmi_device1.misc);
 855        if (ret == 0)
 856                ret = misc_register(&qmi_device2.misc);
 857        return ret;
 858}
 859
 860module_init(qmi_init);
 861