Staging: vt665x: remove tbit.h
[safe/jmp/linux-2.6] / drivers / staging / dream / smd / smd_qmi.c
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
40 struct 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
55 struct 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
83 static struct qmi_ctxt *qmi_minor_to_ctxt(unsigned n);
84
85 static void qmi_read_work(struct work_struct *ws);
86 static void qmi_open_work(struct work_struct *work);
87
88 void 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
101 static struct workqueue_struct *qmi_wq;
102
103 static int verbose = 0;
104
105 /* anyone waiting for a state change waits here */
106 static DECLARE_WAIT_QUEUE_HEAD(qmi_wait_queue);
107
108
109 static 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
142 int 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 */
161 static 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
190 static 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
208 static 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
261 static 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
279 static int qmi_network_get_profile(struct qmi_ctxt *ctxt);
280
281 static 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
289 static unsigned char zero[4];
290 static 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
307 static 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
350 static 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
379 static 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
394 static 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
463 static 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
493 static int qmi_request_wds_cid(struct qmi_ctxt *ctxt);
494
495 static 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
503 static 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
527 static 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
548 static 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
565 static 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
618 static 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
637 static 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
672 static 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
705 static 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)) {
737 retry_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)) {
750 retry_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
769 static 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
791 static int qmi_release(struct inode *ip, struct file *fp)
792 {
793         return 0;
794 }
795
796 static 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
804 static 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 };
812 static 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 };
820 static 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
829 static 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
840 static 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
860 module_init(qmi_init);