~ubuntu-branches/debian/wheezy/linux-2.6/wheezy

« back to all changes in this revision

Viewing changes to drivers/staging/dream/smd/smd_qmi.c

  • Committer: Bazaar Package Importer
  • Author(s): Ben Hutchings, Ben Hutchings, Aurelien Jarno, Martin Michlmayr
  • Date: 2011-04-06 13:53:30 UTC
  • mfrom: (43.1.5 sid)
  • Revision ID: james.westby@ubuntu.com-20110406135330-wjufxhd0tvn3zx4z
Tags: 2.6.38-3
[ Ben Hutchings ]
* [ppc64] Add to linux-tools package architectures (Closes: #620124)
* [amd64] Save cr4 to mmu_cr4_features at boot time (Closes: #620284)
* appletalk: Fix bugs introduced when removing use of BKL
* ALSA: Fix yet another race in disconnection
* cciss: Fix lost command issue
* ath9k: Fix kernel panic in AR2427
* ses: Avoid kernel panic when lun 0 is not mapped
* PCI/ACPI: Report ASPM support to BIOS if not disabled from command line

[ Aurelien Jarno ]
* rtlwifi: fix build when PCI is not enabled.

[ Martin Michlmayr ]
* rtlwifi: Eliminate udelay calls with too large values (Closes: #620204)

Show diffs side-by-side

added added

removed removed

Lines of Context:
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);