blob: f9730a39d84bcd2825e03aae9eb094a3f3f66605 [file] [log] [blame]
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001/* Copyright (C) 2007-2008 The Android Open Source Project
2**
3** This software is licensed under the terms of the GNU General Public
4** License version 2, as published by the Free Software Foundation, and
5** may be copied, distributed, and modified under those terms.
6**
7** This program is distributed in the hope that it will be useful,
8** but WITHOUT ANY WARRANTY; without even the implied warranty of
9** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10** GNU General Public License for more details.
11*/
12#include "android/android.h"
13#include "android_modem.h"
David 'Digit' Turner73dd5fc2014-02-04 12:50:55 +010014#include "android/config-file.h"
Tim Baverstock622b8f42010-12-07 11:36:59 +000015#include "android/config/config.h"
16#include "android/snapshot.h"
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -080017#include "android/utils/debug.h"
18#include "android/utils/timezone.h"
19#include "android/utils/system.h"
Jaime Lopez1a000852010-07-21 18:03:58 -070020#include "android/utils/bufprint.h"
21#include "android/utils/path.h"
Tim Baverstocke50d7242010-12-21 19:20:04 +000022#include "hw/hw.h"
23#include "qemu-common.h"
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -080024#include "sim_card.h"
25#include "sysdeps.h"
26#include <memory.h>
27#include <stdarg.h>
28#include <time.h>
29#include <assert.h>
30#include <stdio.h>
31#include "sms.h"
32#include "remote_call.h"
33
34#define DEBUG 1
35
36#if 1
37# define D_ACTIVE VERBOSE_CHECK(modem)
38#else
39# define D_ACTIVE DEBUG
40#endif
41
42#if 1
43# define R_ACTIVE VERBOSE_CHECK(radio)
44#else
45# define R_ACTIVE DEBUG
46#endif
47
48#if DEBUG
49# define D(...) do { if (D_ACTIVE) fprintf( stderr, __VA_ARGS__ ); } while (0)
50# define R(...) do { if (R_ACTIVE) fprintf( stderr, __VA_ARGS__ ); } while (0)
51#else
52# define D(...) ((void)0)
53# define R(...) ((void)0)
54#endif
55
56#define CALL_DELAY_DIAL 1000
57#define CALL_DELAY_ALERT 1000
58
59/* the Android GSM stack checks that the operator's name has changed
60 * when roaming is on. If not, it will not update the Roaming status icon
61 *
62 * this means that we need to emulate two distinct operators:
63 * - the first one for the 'home' registration state, must also correspond
64 * to the emulated user's IMEI
65 *
66 * - the second one for the 'roaming' registration state, must have a
67 * different name and MCC/MNC
68 */
69
70#define OPERATOR_HOME_INDEX 0
71#define OPERATOR_HOME_MCC 310
72#define OPERATOR_HOME_MNC 260
73#define OPERATOR_HOME_NAME "Android"
74#define OPERATOR_HOME_MCCMNC STRINGIFY(OPERATOR_HOME_MCC) \
75 STRINGIFY(OPERATOR_HOME_MNC)
76
77#define OPERATOR_ROAMING_INDEX 1
78#define OPERATOR_ROAMING_MCC 310
79#define OPERATOR_ROAMING_MNC 295
80#define OPERATOR_ROAMING_NAME "TelKila"
81#define OPERATOR_ROAMING_MCCMNC STRINGIFY(OPERATOR_ROAMING_MCC) \
82 STRINGIFY(OPERATOR_ROAMING_MNC)
83
Jaime Lopez1a000852010-07-21 18:03:58 -070084static const char* _amodem_switch_technology(AModem modem, AModemTech newtech, int32_t newpreferred);
85static int _amodem_set_cdma_subscription_source( AModem modem, ACdmaSubscriptionSource ss);
86static int _amodem_set_cdma_prl_version( AModem modem, int prlVersion);
87
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -080088#if DEBUG
89static const char* quote( const char* line )
90{
91 static char temp[1024];
92 const char* hexdigits = "0123456789abcdef";
93 char* p = temp;
94 int c;
95
96 while ((c = *line++) != 0) {
97 c &= 255;
98 if (c >= 32 && c < 127) {
99 *p++ = c;
100 }
101 else if (c == '\r') {
102 memcpy( p, "<CR>", 4 );
103 p += 4;
104 }
105 else if (c == '\n') {
106 memcpy( p, "<LF>", 4 );strcat( p, "<LF>" );
107 p += 4;
108 }
109 else {
110 p[0] = '\\';
111 p[1] = 'x';
112 p[2] = hexdigits[ (c) >> 4 ];
113 p[3] = hexdigits[ (c) & 15 ];
114 p += 4;
115 }
116 }
117 *p = 0;
118 return temp;
119}
120#endif
121
Jaime Lopez1a000852010-07-21 18:03:58 -0700122extern AModemTech
123android_parse_modem_tech( const char * tech )
124{
125 const struct { const char* name; AModemTech tech; } techs[] = {
126 { "gsm", A_TECH_GSM },
127 { "wcdma", A_TECH_WCDMA },
128 { "cdma", A_TECH_CDMA },
129 { "evdo", A_TECH_EVDO },
130 { "lte", A_TECH_LTE },
131 { NULL, 0 }
132 };
133 int nn;
134
135 for (nn = 0; techs[nn].name; nn++) {
136 if (!strcmp(tech, techs[nn].name))
137 return techs[nn].tech;
138 }
139 /* not found */
140 return A_TECH_UNKNOWN;
141}
142
143extern ADataNetworkType
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800144android_parse_network_type( const char* speed )
145{
Jaime Lopez1a000852010-07-21 18:03:58 -0700146 const struct { const char* name; ADataNetworkType type; } types[] = {
147 { "gprs", A_DATA_NETWORK_GPRS },
148 { "edge", A_DATA_NETWORK_EDGE },
149 { "umts", A_DATA_NETWORK_UMTS },
150 { "hsdpa", A_DATA_NETWORK_UMTS }, /* not handled yet by Android GSM framework */
151 { "full", A_DATA_NETWORK_UMTS },
152 { "lte", A_DATA_NETWORK_LTE },
153 { "cdma", A_DATA_NETWORK_CDMA1X },
154 { "evdo", A_DATA_NETWORK_EVDO },
155 { NULL, 0 }
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800156 };
157 int nn;
158
159 for (nn = 0; types[nn].name; nn++) {
Jaime Lopez1a000852010-07-21 18:03:58 -0700160 if (!strcmp(speed, types[nn].name))
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800161 return types[nn].type;
162 }
163 /* not found, be conservative */
Jaime Lopez1a000852010-07-21 18:03:58 -0700164 return A_DATA_NETWORK_GPRS;
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800165}
166
167/* 'mode' for +CREG/+CGREG commands */
168typedef enum {
169 A_REGISTRATION_UNSOL_DISABLED = 0,
170 A_REGISTRATION_UNSOL_ENABLED = 1,
171 A_REGISTRATION_UNSOL_ENABLED_FULL = 2
172} ARegistrationUnsolMode;
173
174/* Operator selection mode, see +COPS commands */
175typedef enum {
176 A_SELECTION_AUTOMATIC,
177 A_SELECTION_MANUAL,
178 A_SELECTION_DEREGISTRATION,
179 A_SELECTION_SET_FORMAT,
180 A_SELECTION_MANUAL_AUTOMATIC
181} AOperatorSelection;
182
183/* Operator status, see +COPS commands */
184typedef enum {
185 A_STATUS_UNKNOWN = 0,
186 A_STATUS_AVAILABLE,
187 A_STATUS_CURRENT,
188 A_STATUS_DENIED
189} AOperatorStatus;
190
191typedef struct {
192 AOperatorStatus status;
193 char name[3][16];
194} AOperatorRec, *AOperator;
195
196typedef struct AVoiceCallRec {
197 ACallRec call;
198 SysTimer timer;
199 AModem modem;
200 char is_remote;
201} AVoiceCallRec, *AVoiceCall;
202
203#define MAX_OPERATORS 4
204
205typedef enum {
206 A_DATA_IP = 0,
207 A_DATA_PPP
208} ADataType;
209
210#define A_DATA_APN_SIZE 32
211
212typedef struct {
213 int id;
214 int active;
215 ADataType type;
216 char apn[ A_DATA_APN_SIZE ];
217
218} ADataContextRec, *ADataContext;
219
220/* the spec says that there can only be a max of 4 contexts */
221#define MAX_DATA_CONTEXTS 4
222#define MAX_CALLS 4
Jaime Lopez1a000852010-07-21 18:03:58 -0700223#define MAX_EMERGENCY_NUMBERS 16
224
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800225
226#define A_MODEM_SELF_SIZE 3
227
Jaime Lopez1a000852010-07-21 18:03:58 -0700228
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800229typedef struct AModemRec_
230{
David Turner6f290f22009-04-18 20:50:40 -0700231 /* Legacy support */
232 char supportsNetworkDataType;
233
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800234 /* Radio state */
235 ARadioState radio_state;
236 int area_code;
237 int cell_id;
238 int base_port;
239
Tim Baverstock4c6b10a2010-12-15 17:31:13 +0000240 int rssi;
241 int ber;
242
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800243 /* SMS */
244 int wait_sms;
245
246 /* SIM card */
247 ASimCard sim;
248
249 /* voice and data network registration */
250 ARegistrationUnsolMode voice_mode;
251 ARegistrationState voice_state;
252 ARegistrationUnsolMode data_mode;
253 ARegistrationState data_state;
Jaime Lopez1a000852010-07-21 18:03:58 -0700254 ADataNetworkType data_network;
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800255
256 /* operator names */
257 AOperatorSelection oper_selection_mode;
258 ANameIndex oper_name_index;
259 int oper_index;
260 int oper_count;
261 AOperatorRec operators[ MAX_OPERATORS ];
262
263 /* data connection contexts */
264 ADataContextRec data_contexts[ MAX_DATA_CONTEXTS ];
265
266 /* active calls */
267 AVoiceCallRec calls[ MAX_CALLS ];
268 int call_count;
269
270 /* unsolicited callback */ /* XXX: TODO: use this */
271 AModemUnsolFunc unsol_func;
272 void* unsol_opaque;
273
274 SmsReceiver sms_receiver;
275
276 int out_size;
277 char out_buff[1024];
278
Jaime Lopez1a000852010-07-21 18:03:58 -0700279 /*
280 * Hold non-volatile ram configuration for modem
281 */
282 AConfig *nvram_config;
283 char *nvram_config_filename;
284
285 AModemTech technology;
286 /*
287 * This is are really 4 byte-sized prioritized masks.
288 * Byte order gives the priority for the specific bitmask.
289 * Each bit position in each of the masks is indexed by the different
290 * A_TECH_XXXX values.
291 * e.g. 0x01 means only GSM is set (bit index 0), whereas 0x0f
292 * means that GSM,WCDMA,CDMA and EVDO are set
293 */
294 int32_t preferred_mask;
295 ACdmaSubscriptionSource subscription_source;
296 ACdmaRoamingPref roaming_pref;
297 int in_emergency_mode;
298 int prl_version;
299
300 const char *emergency_numbers[MAX_EMERGENCY_NUMBERS];
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800301} AModemRec;
302
303
304static void
305amodem_unsol( AModem modem, const char* format, ... )
306{
307 if (modem->unsol_func) {
308 va_list args;
309 va_start(args, format);
310 vsnprintf( modem->out_buff, sizeof(modem->out_buff), format, args );
311 va_end(args);
312
313 modem->unsol_func( modem->unsol_opaque, modem->out_buff );
314 }
315}
316
317void
318amodem_receive_sms( AModem modem, SmsPDU sms )
319{
320#define SMS_UNSOL_HEADER "+CMT: 0\r\n"
321
322 if (modem->unsol_func) {
323 int len, max;
324 char* p;
325
326 strcpy( modem->out_buff, SMS_UNSOL_HEADER );
327 p = modem->out_buff + (sizeof(SMS_UNSOL_HEADER)-1);
328 max = sizeof(modem->out_buff) - 3 - (sizeof(SMS_UNSOL_HEADER)-1);
329 len = smspdu_to_hex( sms, p, max );
330 if (len > max) /* too long */
331 return;
332 p[len] = '\r';
333 p[len+1] = '\n';
334 p[len+2] = 0;
335
336 R( "SMS>> %s\n", p );
337
338 modem->unsol_func( modem->unsol_opaque, modem->out_buff );
339 }
340}
341
342static const char*
343amodem_printf( AModem modem, const char* format, ... )
344{
345 va_list args;
346 va_start(args, format);
347 vsnprintf( modem->out_buff, sizeof(modem->out_buff), format, args );
348 va_end(args);
349
350 return modem->out_buff;
351}
352
353static void
354amodem_begin_line( AModem modem )
355{
356 modem->out_size = 0;
357}
358
359static void
360amodem_add_line( AModem modem, const char* format, ... )
361{
362 va_list args;
363 va_start(args, format);
364 modem->out_size += vsnprintf( modem->out_buff + modem->out_size,
365 sizeof(modem->out_buff) - modem->out_size,
366 format, args );
367 va_end(args);
368}
369
370static const char*
371amodem_end_line( AModem modem )
372{
373 modem->out_buff[ modem->out_size ] = 0;
374 return modem->out_buff;
375}
376
Jaime Lopez1a000852010-07-21 18:03:58 -0700377#define NV_OPER_NAME_INDEX "oper_name_index"
378#define NV_OPER_INDEX "oper_index"
379#define NV_SELECTION_MODE "selection_mode"
380#define NV_OPER_COUNT "oper_count"
381#define NV_MODEM_TECHNOLOGY "modem_technology"
382#define NV_PREFERRED_MODE "preferred_mode"
383#define NV_CDMA_SUBSCRIPTION_SOURCE "cdma_subscription_source"
384#define NV_CDMA_ROAMING_PREF "cdma_roaming_pref"
385#define NV_IN_ECBM "in_ecbm"
386#define NV_EMERGENCY_NUMBER_FMT "emergency_number_%d"
387#define NV_PRL_VERSION "prl_version"
388#define NV_SREGISTER "sregister"
389
390#define MAX_KEY_NAME 40
391
392static AConfig *
393amodem_load_nvram( AModem modem )
394{
395 AConfig* root = aconfig_node(NULL, NULL);
396 D("Using config file: %s\n", modem->nvram_config_filename);
397 if (aconfig_load_file(root, modem->nvram_config_filename)) {
398 D("Unable to load config\n");
399 aconfig_set(root, NV_MODEM_TECHNOLOGY, "gsm");
400 aconfig_save_file(root, modem->nvram_config_filename);
401 }
402 return root;
403}
404
405static int
406amodem_nvram_get_int( AModem modem, const char *nvname, int defval)
407{
408 int value;
409 char strval[MAX_KEY_NAME + 1];
410 char *newvalue;
411
412 value = aconfig_int(modem->nvram_config, nvname, defval);
413 snprintf(strval, MAX_KEY_NAME, "%d", value);
414 D("Setting value of %s to %d (%s)",nvname, value, strval);
415 newvalue = strdup(strval);
416 if (!newvalue) {
417 newvalue = "";
418 }
419 aconfig_set(modem->nvram_config, nvname, newvalue);
420
421 return value;
422}
423
424const char *
425amodem_nvram_get_str( AModem modem, const char *nvname, const char *defval)
426{
427 const char *value;
428
429 value = aconfig_str(modem->nvram_config, nvname, defval);
430
431 if (!value) {
432 if (!defval)
433 return NULL;
434 value = defval;
435 }
436
437 aconfig_set(modem->nvram_config, nvname, value);
438
439 return value;
440}
441
442static ACdmaSubscriptionSource _amodem_get_cdma_subscription_source( AModem modem )
443{
444 int iss = -1;
445 iss = amodem_nvram_get_int( modem, NV_CDMA_SUBSCRIPTION_SOURCE, A_SUBSCRIPTION_RUIM );
446 if (iss >= A_SUBSCRIPTION_UNKNOWN || iss < 0) {
447 iss = A_SUBSCRIPTION_RUIM;
448 }
449
450 return iss;
451}
452
453static ACdmaRoamingPref _amodem_get_cdma_roaming_preference( AModem modem )
454{
455 int rp = -1;
456 rp = amodem_nvram_get_int( modem, NV_CDMA_ROAMING_PREF, A_ROAMING_PREF_ANY );
457 if (rp >= A_ROAMING_PREF_UNKNOWN || rp < 0) {
458 rp = A_ROAMING_PREF_ANY;
459 }
460
461 return rp;
462}
463
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800464static void
465amodem_reset( AModem modem )
466{
Jaime Lopez1a000852010-07-21 18:03:58 -0700467 const char *tmp;
468 int i;
469 modem->nvram_config = amodem_load_nvram(modem);
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800470 modem->radio_state = A_RADIO_STATE_OFF;
471 modem->wait_sms = 0;
472
Tim Baverstock4c6b10a2010-12-15 17:31:13 +0000473 modem->rssi= 7; // Two signal strength bars
474 modem->ber = 99; // Means 'unknown'
475
Jaime Lopez1a000852010-07-21 18:03:58 -0700476 modem->oper_name_index = amodem_nvram_get_int(modem, NV_OPER_NAME_INDEX, 2);
477 modem->oper_selection_mode = amodem_nvram_get_int(modem, NV_SELECTION_MODE, A_SELECTION_AUTOMATIC);
478 modem->oper_index = amodem_nvram_get_int(modem, NV_OPER_INDEX, 0);
479 modem->oper_count = amodem_nvram_get_int(modem, NV_OPER_COUNT, 2);
480 modem->in_emergency_mode = amodem_nvram_get_int(modem, NV_IN_ECBM, 0);
481 modem->prl_version = amodem_nvram_get_int(modem, NV_PRL_VERSION, 0);
482
483 modem->emergency_numbers[0] = "911";
484 char key_name[MAX_KEY_NAME + 1];
485 for (i = 1; i < MAX_EMERGENCY_NUMBERS; i++) {
486 snprintf(key_name,MAX_KEY_NAME, NV_EMERGENCY_NUMBER_FMT, i);
487 modem->emergency_numbers[i] = amodem_nvram_get_str(modem,key_name, NULL);
488 }
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800489
490 modem->area_code = -1;
491 modem->cell_id = -1;
492
493 strcpy( modem->operators[0].name[0], OPERATOR_HOME_NAME );
494 strcpy( modem->operators[0].name[1], OPERATOR_HOME_NAME );
495 strcpy( modem->operators[0].name[2], OPERATOR_HOME_MCCMNC );
496
497 modem->operators[0].status = A_STATUS_AVAILABLE;
498
499 strcpy( modem->operators[1].name[0], OPERATOR_ROAMING_NAME );
500 strcpy( modem->operators[1].name[1], OPERATOR_ROAMING_NAME );
501 strcpy( modem->operators[1].name[2], OPERATOR_ROAMING_MCCMNC );
502
503 modem->operators[1].status = A_STATUS_AVAILABLE;
504
505 modem->voice_mode = A_REGISTRATION_UNSOL_ENABLED_FULL;
506 modem->voice_state = A_REGISTRATION_HOME;
507 modem->data_mode = A_REGISTRATION_UNSOL_ENABLED_FULL;
508 modem->data_state = A_REGISTRATION_HOME;
Jaime Lopez1a000852010-07-21 18:03:58 -0700509 modem->data_network = A_DATA_NETWORK_UMTS;
510
511 tmp = amodem_nvram_get_str( modem, NV_MODEM_TECHNOLOGY, "gsm" );
512 modem->technology = android_parse_modem_tech( tmp );
513 if (modem->technology == A_TECH_UNKNOWN) {
514 modem->technology = aconfig_int( modem->nvram_config, NV_MODEM_TECHNOLOGY, A_TECH_GSM );
515 }
516 // Support GSM, WCDMA, CDMA, EvDo
517 modem->preferred_mask = amodem_nvram_get_int( modem, NV_PREFERRED_MODE, 0x0f );
518
519 modem->subscription_source = _amodem_get_cdma_subscription_source( modem );
520 modem->roaming_pref = _amodem_get_cdma_roaming_preference( modem );
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800521}
522
Tim Baverstocke50d7242010-12-21 19:20:04 +0000523static AVoiceCall amodem_alloc_call( AModem modem );
524static void amodem_free_call( AModem modem, AVoiceCall call );
525
526#define MODEM_DEV_STATE_SAVE_VERSION 1
527
528static void android_modem_state_save(QEMUFile *f, void *opaque)
529{
530 AModem modem = opaque;
531
532 // TODO: save more than just calls and call_count - rssi, power, etc.
533
534 qemu_put_byte(f, modem->call_count);
535
536 int nn;
537 for (nn = modem->call_count - 1; nn >= 0; nn--) {
538 AVoiceCall vcall = modem->calls + nn;
539 // Note: not saving timers or remote calls.
540 ACall call = &vcall->call;
541 qemu_put_byte( f, call->dir );
542 qemu_put_byte( f, call->state );
543 qemu_put_byte( f, call->mode );
544 qemu_put_be32( f, call->multi );
545 qemu_put_buffer( f, (uint8_t *)call->number, A_CALL_NUMBER_MAX_SIZE+1 );
546 }
547}
548
549static int android_modem_state_load(QEMUFile *f, void *opaque, int version_id)
550{
551 if (version_id != MODEM_DEV_STATE_SAVE_VERSION)
552 return -1;
553
554 AModem modem = opaque;
555
556 // In case there are timers or remote calls.
557 int nn;
558 for (nn = modem->call_count - 1; nn >= 0; nn--) {
559 amodem_free_call( modem, modem->calls + nn);
560 }
561
562 int call_count = qemu_get_byte(f);
563 for (nn = call_count; nn > 0; nn--) {
564 AVoiceCall vcall = amodem_alloc_call( modem );
565 ACall call = &vcall->call;
566 call->dir = qemu_get_byte( f );
567 call->state = qemu_get_byte( f );
568 call->mode = qemu_get_byte( f );
569 call->multi = qemu_get_be32( f );
570 qemu_get_buffer( f, (uint8_t *)call->number, A_CALL_NUMBER_MAX_SIZE+1 );
571 }
572
573 return 0; // >=0 Happy
574}
575
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800576static AModemRec _android_modem[1];
577
578AModem
579amodem_create( int base_port, AModemUnsolFunc unsol_func, void* unsol_opaque )
580{
581 AModem modem = _android_modem;
Jaime Lopez1a000852010-07-21 18:03:58 -0700582 char nvfname[MAX_PATH];
583 char *start = nvfname;
584 char *end = start + sizeof(nvfname);
585
586 modem->base_port = base_port;
587 start = bufprint_config_file( start, end, "modem-nv-ram-" );
588 start = bufprint( start, end, "%d", modem->base_port );
589 modem->nvram_config_filename = strdup( nvfname );
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800590
591 amodem_reset( modem );
David Turner6f290f22009-04-18 20:50:40 -0700592 modem->supportsNetworkDataType = 1;
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800593 modem->unsol_func = unsol_func;
594 modem->unsol_opaque = unsol_opaque;
595
Marc Petit-Huguenina1b379c2010-07-14 12:33:15 -0700596 modem->sim = asimcard_create(base_port);
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800597
David 'Digit' Turnere92bc562010-09-07 06:21:25 -0700598 sys_main_init();
David 'Digit' Turner5cb5c0b2014-02-17 16:04:03 +0100599 register_savevm(NULL,
600 "android_modem",
601 0,
602 MODEM_DEV_STATE_SAVE_VERSION,
603 android_modem_state_save,
604 android_modem_state_load,
605 modem);
David 'Digit' Turnere92bc562010-09-07 06:21:25 -0700606
Jaime Lopez1a000852010-07-21 18:03:58 -0700607 aconfig_save_file( modem->nvram_config, modem->nvram_config_filename );
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800608 return modem;
609}
610
611void
David Turner6f290f22009-04-18 20:50:40 -0700612amodem_set_legacy( AModem modem )
613{
614 modem->supportsNetworkDataType = 0;
615}
616
617void
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800618amodem_destroy( AModem modem )
619{
620 asimcard_destroy( modem->sim );
621 modem->sim = NULL;
622}
623
624
625static int
626amodem_has_network( AModem modem )
627{
628 return !(modem->radio_state == A_RADIO_STATE_OFF ||
629 modem->oper_index < 0 ||
630 modem->oper_index >= modem->oper_count ||
631 modem->oper_selection_mode == A_SELECTION_DEREGISTRATION );
632}
633
634
635ARadioState
636amodem_get_radio_state( AModem modem )
637{
638 return modem->radio_state;
639}
640
641void
642amodem_set_radio_state( AModem modem, ARadioState state )
643{
644 modem->radio_state = state;
645}
646
647ASimCard
648amodem_get_sim( AModem modem )
649{
650 return modem->sim;
651}
652
653ARegistrationState
654amodem_get_voice_registration( AModem modem )
655{
656 return modem->voice_state;
657}
658
659void
660amodem_set_voice_registration( AModem modem, ARegistrationState state )
661{
662 modem->voice_state = state;
663
664 if (state == A_REGISTRATION_HOME)
665 modem->oper_index = OPERATOR_HOME_INDEX;
666 else if (state == A_REGISTRATION_ROAMING)
667 modem->oper_index = OPERATOR_ROAMING_INDEX;
668
669 switch (modem->voice_mode) {
670 case A_REGISTRATION_UNSOL_ENABLED:
671 amodem_unsol( modem, "+CREG: %d,%d\r",
672 modem->voice_mode, modem->voice_state );
673 break;
674
675 case A_REGISTRATION_UNSOL_ENABLED_FULL:
676 amodem_unsol( modem, "+CREG: %d,%d, \"%04x\", \"%04x\"\r",
677 modem->voice_mode, modem->voice_state,
David 'Digit' Turnerd7f019d2011-02-04 12:30:54 +0100678 modem->area_code & 0xffff, modem->cell_id & 0xffff);
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800679 break;
680 default:
681 ;
682 }
683}
684
685ARegistrationState
686amodem_get_data_registration( AModem modem )
687{
688 return modem->data_state;
689}
690
691void
692amodem_set_data_registration( AModem modem, ARegistrationState state )
693{
694 modem->data_state = state;
695
696 switch (modem->data_mode) {
697 case A_REGISTRATION_UNSOL_ENABLED:
698 amodem_unsol( modem, "+CGREG: %d,%d\r",
699 modem->data_mode, modem->data_state );
700 break;
701
702 case A_REGISTRATION_UNSOL_ENABLED_FULL:
David Turner6f290f22009-04-18 20:50:40 -0700703 if (modem->supportsNetworkDataType)
704 amodem_unsol( modem, "+CGREG: %d,%d,\"%04x\",\"%04x\",\"%04x\"\r",
705 modem->data_mode, modem->data_state,
David 'Digit' Turnerd7f019d2011-02-04 12:30:54 +0100706 modem->area_code & 0xffff, modem->cell_id & 0xffff,
David Turner6f290f22009-04-18 20:50:40 -0700707 modem->data_network );
708 else
709 amodem_unsol( modem, "+CGREG: %d,%d,\"%04x\",\"%04x\"\r",
710 modem->data_mode, modem->data_state,
David 'Digit' Turnerd7f019d2011-02-04 12:30:54 +0100711 modem->area_code & 0xffff, modem->cell_id & 0xffff );
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800712 break;
713
714 default:
715 ;
716 }
717}
718
Jaime Lopez1a000852010-07-21 18:03:58 -0700719static int
720amodem_nvram_set( AModem modem, const char *name, const char *value )
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800721{
Jaime Lopez1a000852010-07-21 18:03:58 -0700722 aconfig_set(modem->nvram_config, name, value);
723 return 0;
724}
725static AModemTech
726tech_from_network_type( ADataNetworkType type )
727{
728 switch (type) {
729 case A_DATA_NETWORK_GPRS:
730 case A_DATA_NETWORK_EDGE:
731 case A_DATA_NETWORK_UMTS:
732 // TODO: Add A_TECH_WCDMA
733 return A_TECH_GSM;
734 case A_DATA_NETWORK_LTE:
735 return A_TECH_LTE;
736 case A_DATA_NETWORK_CDMA1X:
737 case A_DATA_NETWORK_EVDO:
738 return A_TECH_CDMA;
739 case A_DATA_NETWORK_UNKNOWN:
740 return A_TECH_UNKNOWN;
741 }
742 return A_TECH_UNKNOWN;
743}
744
745void
746amodem_set_data_network_type( AModem modem, ADataNetworkType type )
747{
748 AModemTech modemTech;
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800749 modem->data_network = type;
750 amodem_set_data_registration( modem, modem->data_state );
Jaime Lopez1a000852010-07-21 18:03:58 -0700751 modemTech = tech_from_network_type(type);
752 if (modem->unsol_func && modemTech != A_TECH_UNKNOWN) {
753 if (_amodem_switch_technology( modem, modemTech, modem->preferred_mask )) {
754 modem->unsol_func( modem->unsol_opaque, modem->out_buff );
755 }
756 }
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800757}
758
759int
760amodem_get_operator_name ( AModem modem, ANameIndex index, char* buffer, int buffer_size )
761{
762 AOperator oper;
763 int len;
764
765 if ( (unsigned)modem->oper_index >= (unsigned)modem->oper_count ||
766 (unsigned)index > 2 )
767 return 0;
768
769 oper = modem->operators + modem->oper_index;
770 len = strlen(oper->name[index]) + 1;
771
772 if (buffer_size > len)
773 buffer_size = len;
774
775 if (buffer_size > 0) {
776 memcpy( buffer, oper->name[index], buffer_size-1 );
777 buffer[buffer_size] = 0;
778 }
779 return len;
780}
781
782/* reset one operator name from a user-provided buffer, set buffer_size to -1 for zero-terminated strings */
783void
784amodem_set_operator_name( AModem modem, ANameIndex index, const char* buffer, int buffer_size )
785{
786 AOperator oper;
787 int avail;
788
789 if ( (unsigned)modem->oper_index >= (unsigned)modem->oper_count ||
790 (unsigned)index > 2 )
791 return;
792
793 oper = modem->operators + modem->oper_index;
794
795 avail = sizeof(oper->name[0]);
796 if (buffer_size < 0)
797 buffer_size = strlen(buffer);
798 if (buffer_size > avail-1)
799 buffer_size = avail-1;
800 memcpy( oper->name[index], buffer, buffer_size );
801 oper->name[index][buffer_size] = 0;
802}
803
804/** CALLS
805 **/
806int
807amodem_get_call_count( AModem modem )
808{
809 return modem->call_count;
810}
811
812ACall
813amodem_get_call( AModem modem, int index )
814{
815 if ((unsigned)index >= (unsigned)modem->call_count)
816 return NULL;
817
818 return &modem->calls[index].call;
819}
820
821static AVoiceCall
822amodem_alloc_call( AModem modem )
823{
824 AVoiceCall call = NULL;
825 int count = modem->call_count;
826
827 if (count < MAX_CALLS) {
828 int id;
829
830 /* find a valid id for this call */
831 for (id = 0; id < modem->call_count; id++) {
832 int found = 0;
833 int nn;
834 for (nn = 0; nn < count; nn++) {
835 if ( modem->calls[nn].call.id == (id+1) ) {
836 found = 1;
837 break;
838 }
839 }
840 if (!found)
841 break;
842 }
843 call = modem->calls + count;
844 call->call.id = id + 1;
845 call->modem = modem;
846
847 modem->call_count += 1;
848 }
849 return call;
850}
851
852
853static void
854amodem_free_call( AModem modem, AVoiceCall call )
855{
856 int nn;
857
858 if (call->timer) {
859 sys_timer_destroy( call->timer );
860 call->timer = NULL;
861 }
862
863 if (call->is_remote) {
864 remote_call_cancel( call->call.number, modem->base_port );
865 call->is_remote = 0;
866 }
867
868 for (nn = 0; nn < modem->call_count; nn++) {
869 if ( modem->calls + nn == call )
870 break;
871 }
872 assert( nn < modem->call_count );
873
874 memmove( modem->calls + nn,
875 modem->calls + nn + 1,
876 (modem->call_count - 1 - nn)*sizeof(*call) );
877
878 modem->call_count -= 1;
879}
880
881
882static AVoiceCall
883amodem_find_call( AModem modem, int id )
884{
885 int nn;
886
887 for (nn = 0; nn < modem->call_count; nn++) {
888 AVoiceCall call = modem->calls + nn;
889 if (call->call.id == id)
890 return call;
891 }
892 return NULL;
893}
894
895static void
896amodem_send_calls_update( AModem modem )
897{
898 /* despite its name, this really tells the system that the call
899 * state has changed */
900 amodem_unsol( modem, "RING\r" );
901}
902
903
904int
905amodem_add_inbound_call( AModem modem, const char* number )
906{
907 AVoiceCall vcall = amodem_alloc_call( modem );
908 ACall call = &vcall->call;
909 int len;
910
911 if (call == NULL)
912 return -1;
913
914 call->dir = A_CALL_INBOUND;
915 call->state = A_CALL_INCOMING;
916 call->mode = A_CALL_VOICE;
917 call->multi = 0;
918
919 vcall->is_remote = (remote_number_string_to_port(number) > 0);
920
921 len = strlen(number);
922 if (len >= sizeof(call->number))
923 len = sizeof(call->number)-1;
924
925 memcpy( call->number, number, len );
926 call->number[len] = 0;
927
928 amodem_send_calls_update( modem );
929 return 0;
930}
931
932ACall
933amodem_find_call_by_number( AModem modem, const char* number )
934{
935 AVoiceCall vcall = modem->calls;
936 AVoiceCall vend = vcall + modem->call_count;
937
938 if (!number)
939 return NULL;
940
941 for ( ; vcall < vend; vcall++ )
942 if ( !strcmp(vcall->call.number, number) )
943 return &vcall->call;
944
945 return NULL;
946}
947
Tim Baverstock4c6b10a2010-12-15 17:31:13 +0000948void
949amodem_set_signal_strength( AModem modem, int rssi, int ber )
950{
951 modem->rssi = rssi;
952 modem->ber = ber;
953}
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800954
955static void
956acall_set_state( AVoiceCall call, ACallState state )
957{
958 if (state != call->call.state)
959 {
960 if (call->is_remote)
961 {
962 const char* number = call->call.number;
963 int port = call->modem->base_port;
964
965 switch (state) {
966 case A_CALL_HELD:
967 remote_call_other( number, port, REMOTE_CALL_HOLD );
968 break;
969
970 case A_CALL_ACTIVE:
971 remote_call_other( number, port, REMOTE_CALL_ACCEPT );
972 break;
973
974 default: ;
975 }
976 }
977 call->call.state = state;
978 }
979}
980
981
982int
983amodem_update_call( AModem modem, const char* fromNumber, ACallState state )
984{
985 AVoiceCall vcall = (AVoiceCall) amodem_find_call_by_number(modem, fromNumber);
986
987 if (vcall == NULL)
988 return -1;
989
990 acall_set_state( vcall, state );
991 amodem_send_calls_update(modem);
992 return 0;
993}
994
995
996int
997amodem_disconnect_call( AModem modem, const char* number )
998{
999 AVoiceCall vcall = (AVoiceCall) amodem_find_call_by_number(modem, number);
1000
1001 if (!vcall)
1002 return -1;
1003
1004 amodem_free_call( modem, vcall );
1005 amodem_send_calls_update(modem);
1006 return 0;
1007}
1008
1009/** COMMAND HANDLERS
1010 **/
1011
1012static const char*
1013unknownCommand( const char* cmd, AModem modem )
1014{
1015 modem=modem;
1016 fprintf(stderr, ">>> unknown command '%s'\n", cmd );
1017 return "ERROR: unknown command\r";
1018}
1019
Jaime Lopez1a000852010-07-21 18:03:58 -07001020/*
1021 * Tell whether the specified tech is valid for the preferred mask.
1022 * @pmask: The preferred mask
1023 * @tech: The AModemTech we try to validate
1024 * return: If the specified technology is not set in any of the 4
1025 * bitmasks, return 0.
1026 * Otherwise, return a non-zero value.
1027 */
1028static int matchPreferredMask( int32_t pmask, AModemTech tech )
1029{
1030 int ret = 0;
1031 int i;
1032 for ( i=3; i >= 0 ; i-- ) {
1033 if (pmask & (1 << (tech + i*8 ))) {
1034 ret = 1;
1035 break;
1036 }
1037 }
1038 return ret;
1039}
1040
1041static AModemTech
1042chooseTechFromMask( AModem modem, int32_t preferred )
1043{
1044 int i, j;
1045
1046 /* TODO: Current implementation will only return the highest priority,
1047 * lowest numbered technology that is set in the mask.
1048 * However the implementation could be changed to consider currently
1049 * available networks set from the console (or somewhere else)
1050 */
1051 for ( i=3 ; i >= 0; i-- ) {
1052 for ( j=0 ; j < A_TECH_UNKNOWN ; j++ ) {
1053 if (preferred & (1 << (j + 8 * i)))
1054 return (AModemTech) j;
1055 }
1056 }
1057 assert("This should never happen" == 0);
1058 // This should never happen. Just to please the compiler.
1059 return A_TECH_UNKNOWN;
1060}
1061
1062static const char*
1063_amodem_switch_technology( AModem modem, AModemTech newtech, int32_t newpreferred )
1064{
1065 D("_amodem_switch_technology: oldtech: %d, newtech %d, preferred: %d. newpreferred: %d\n",
1066 modem->technology, newtech, modem->preferred_mask,newpreferred);
1067 const char *ret = "+CTEC: DONE";
1068 assert( modem );
1069
1070 if (!newpreferred) {
1071 return "ERROR: At least one technology must be enabled";
1072 }
1073 if (modem->preferred_mask != newpreferred) {
1074 char value[MAX_KEY_NAME + 1];
1075 modem->preferred_mask = newpreferred;
1076 snprintf(value, MAX_KEY_NAME, "%d", newpreferred);
1077 amodem_nvram_set(modem, NV_PREFERRED_MODE, value);
1078 if (!matchPreferredMask(modem->preferred_mask, newtech)) {
1079 newtech = chooseTechFromMask(modem, newpreferred);
1080 }
1081 }
1082
1083 if (modem->technology != newtech) {
1084 modem->technology = newtech;
1085 ret = amodem_printf(modem, "+CTEC: %d", modem->technology);
1086 }
1087 return ret;
1088}
1089
1090static int
1091parsePreferred( const char *str, int *preferred )
1092{
1093 char *endptr = NULL;
1094 int result = 0;
1095 if (!str || !*str) { *preferred = 0; return 0; }
1096 if (*str == '"') str ++;
1097 if (!*str) return 0;
1098
1099 result = strtol(str, &endptr, 16);
1100 if (*endptr && *endptr != '"') {
1101 return 0;
1102 }
1103 if (preferred)
1104 *preferred = result;
1105 return 1;
1106}
1107
1108void
1109amodem_set_cdma_prl_version( AModem modem, int prlVersion)
1110{
1111 D("amodem_set_prl_version()\n");
1112 if (!_amodem_set_cdma_prl_version( modem, prlVersion)) {
1113 amodem_unsol(modem, "+WPRL: %d", prlVersion);
1114 }
1115}
1116
1117static int
1118_amodem_set_cdma_prl_version( AModem modem, int prlVersion)
1119{
1120 D("_amodem_set_cdma_prl_version");
1121 if (modem->prl_version != prlVersion) {
1122 modem->prl_version = prlVersion;
1123 return 0;
1124 }
1125 return -1;
1126}
1127
1128void
1129amodem_set_cdma_subscription_source( AModem modem, ACdmaSubscriptionSource ss)
1130{
1131 D("amodem_set_cdma_subscription_source()\n");
1132 if (!_amodem_set_cdma_subscription_source( modem, ss)) {
1133 amodem_unsol(modem, "+CCSS: %d", (int)ss);
1134 }
1135}
1136
1137#define MAX_INT_DIGITS 10
1138static int
1139_amodem_set_cdma_subscription_source( AModem modem, ACdmaSubscriptionSource ss)
1140{
1141 D("_amodem_set_cdma_subscription_source()\n");
1142 char value[MAX_INT_DIGITS + 1];
1143
1144 if (ss != modem->subscription_source) {
1145 snprintf( value, MAX_INT_DIGITS + 1, "%d", ss );
1146 amodem_nvram_set( modem, NV_CDMA_SUBSCRIPTION_SOURCE, value );
1147 modem->subscription_source = ss;
1148 return 0;
1149 }
1150 return -1;
1151}
1152
1153static const char*
1154handleSubscriptionSource( const char* cmd, AModem modem )
1155{
1156 int newsource;
1157 // TODO: Actually change subscription depending on source
1158 D("handleSubscriptionSource(%s)\n",cmd);
1159
1160 assert( !memcmp( "+CCSS", cmd, 5 ) );
1161 cmd += 5;
1162 if (cmd[0] == '?') {
1163 return amodem_printf( modem, "+CCSS: %d", modem->subscription_source );
1164 } else if (cmd[0] == '=') {
1165 switch (cmd[1]) {
1166 case '0':
1167 case '1':
1168 newsource = (ACdmaSubscriptionSource)cmd[1] - '0';
1169 _amodem_set_cdma_subscription_source( modem, newsource );
1170 return amodem_printf( modem, "+CCSS: %d", modem->subscription_source );
1171 break;
1172 }
1173 }
1174 return amodem_printf( modem, "ERROR: Invalid subscription source");
1175}
1176
1177static const char*
1178handleRoamPref( const char * cmd, AModem modem )
1179{
1180 int roaming_pref = -1;
1181 char *endptr = NULL;
1182 D("handleRoamPref(%s)\n", cmd);
1183 assert( !memcmp( "+WRMP", cmd, 5 ) );
1184 cmd += 5;
1185 if (cmd[0] == '?') {
1186 return amodem_printf( modem, "+WRMP: %d", modem->roaming_pref );
1187 }
1188
1189 if (!strcmp( cmd, "=?")) {
1190 return amodem_printf( modem, "+WRMP: 0,1,2" );
1191 } else if (cmd[0] == '=') {
1192 cmd ++;
1193 roaming_pref = strtol( cmd, &endptr, 10 );
1194 // Make sure the rest of the command is the number
1195 // (if *endptr is null, it means strtol processed the whole string as a number)
1196 if(endptr && !*endptr) {
1197 modem->roaming_pref = roaming_pref;
1198 aconfig_set( modem->nvram_config, NV_CDMA_ROAMING_PREF, cmd );
1199 aconfig_save_file( modem->nvram_config, modem->nvram_config_filename );
1200 return NULL;
1201 }
1202 }
1203 return amodem_printf( modem, "ERROR");
1204}
1205static const char*
1206handleTech( const char* cmd, AModem modem )
1207{
1208 AModemTech newtech = modem->technology;
1209 int pt = modem->preferred_mask;
1210 int havenewtech = 0;
1211 D("handleTech. cmd: %s\n", cmd);
1212 assert( !memcmp( "+CTEC", cmd, 5 ) );
1213 cmd += 5;
1214 if (cmd[0] == '?') {
1215 return amodem_printf( modem, "+CTEC: %d,%x",modem->technology, modem->preferred_mask );
1216 }
1217 amodem_begin_line( modem );
1218 if (!strcmp( cmd, "=?")) {
1219 return amodem_printf( modem, "+CTEC: 0,1,2,3" );
1220 }
1221 else if (cmd[0] == '=') {
1222 switch (cmd[1]) {
1223 case '0':
1224 case '1':
1225 case '2':
1226 case '3':
1227 havenewtech = 1;
1228 newtech = cmd[1] - '0';
1229 cmd += 1;
1230 break;
1231 }
1232 cmd += 1;
1233 }
1234 if (havenewtech) {
1235 D( "cmd: %s\n", cmd );
1236 if (cmd[0] == ',' && ! parsePreferred( ++cmd, &pt ))
1237 return amodem_printf( modem, "ERROR: invalid preferred mode" );
1238 return _amodem_switch_technology( modem, newtech, pt );
1239 }
1240 return amodem_printf( modem, "ERROR: %s: Unknown Technology", cmd + 1 );
1241}
1242
1243static const char*
1244handleEmergencyMode( const char* cmd, AModem modem )
1245{
1246 long arg;
1247 char *endptr = NULL;
1248 assert ( !memcmp( "+WSOS", cmd, 5 ) );
1249 cmd += 5;
1250 if (cmd[0] == '?') {
1251 return amodem_printf( modem, "+WSOS: %d", modem->in_emergency_mode);
1252 }
1253
1254 if (cmd[0] == '=') {
1255 if (cmd[1] == '?') {
1256 return amodem_printf(modem, "+WSOS: (0)");
1257 }
1258 if (cmd[1] == 0) {
1259 return amodem_printf(modem, "ERROR");
1260 }
1261 arg = strtol(cmd+1, &endptr, 10);
1262
1263 if (!endptr || endptr[0] != 0) {
1264 return amodem_printf(modem, "ERROR");
1265 }
1266
1267 arg = arg? 1 : 0;
1268
1269 if ((!arg) != (!modem->in_emergency_mode)) {
1270 modem->in_emergency_mode = arg;
1271 return amodem_printf(modem, "+WSOS: %d", arg);
1272 }
1273 }
1274 return amodem_printf(modem, "ERROR");
1275}
1276
1277static const char*
1278handlePrlVersion( const char* cmd, AModem modem )
1279{
Jaime Lopez1a000852010-07-21 18:03:58 -07001280 assert ( !memcmp( "+WPRL", cmd, 5 ) );
1281 cmd += 5;
1282 if (cmd[0] == '?') {
1283 return amodem_printf( modem, "+WPRL: %d", modem->prl_version);
1284 }
1285
1286 return amodem_printf(modem, "ERROR");
1287}
1288
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001289static const char*
1290handleRadioPower( const char* cmd, AModem modem )
1291{
1292 if ( !strcmp( cmd, "+CFUN=0" ) )
1293 {
1294 /* turn radio off */
1295 modem->radio_state = A_RADIO_STATE_OFF;
1296 }
1297 else if ( !strcmp( cmd, "+CFUN=1" ) )
1298 {
1299 /* turn radio on */
1300 modem->radio_state = A_RADIO_STATE_ON;
1301 }
1302 return NULL;
1303}
1304
1305static const char*
1306handleRadioPowerReq( const char* cmd, AModem modem )
1307{
1308 if (modem->radio_state != A_RADIO_STATE_OFF)
Jaime Lopez1a000852010-07-21 18:03:58 -07001309 return "+CFUN: 1";
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001310 else
Jaime Lopez1a000852010-07-21 18:03:58 -07001311 return "+CFUN: 0";
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001312}
1313
1314static const char*
1315handleSIMStatusReq( const char* cmd, AModem modem )
1316{
1317 const char* answer = NULL;
1318
1319 switch (asimcard_get_status(modem->sim)) {
1320 case A_SIM_STATUS_ABSENT: answer = "+CPIN: ABSENT"; break;
1321 case A_SIM_STATUS_READY: answer = "+CPIN: READY"; break;
1322 case A_SIM_STATUS_NOT_READY: answer = "+CMERROR: NOT READY"; break;
1323 case A_SIM_STATUS_PIN: answer = "+CPIN: SIM PIN"; break;
1324 case A_SIM_STATUS_PUK: answer = "+CPIN: SIM PUK"; break;
1325 case A_SIM_STATUS_NETWORK_PERSONALIZATION: answer = "+CPIN: PH-NET PIN"; break;
1326 default:
1327 answer = "ERROR: internal error";
1328 }
1329 return answer;
1330}
1331
Jaime Lopez1a000852010-07-21 18:03:58 -07001332/* TODO: Will we need this?
1333static const char*
1334handleSRegister( const char * cmd, AModem modem )
1335{
1336 char *end;
1337 assert( cmd[0] == 'S' || cmd[0] == 's' );
1338
1339 ++ cmd;
1340
1341 int l = strtol(cmd, &end, 10);
1342} */
1343
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001344static const char*
1345handleNetworkRegistration( const char* cmd, AModem modem )
1346{
1347 if ( !memcmp( cmd, "+CREG", 5 ) ) {
1348 cmd += 5;
1349 if (cmd[0] == '?') {
David 'Digit' Turnerd7f019d2011-02-04 12:30:54 +01001350 if (modem->voice_mode == A_REGISTRATION_UNSOL_ENABLED_FULL)
1351 return amodem_printf( modem, "+CREG: %d,%d, \"%04x\", \"%04x\"",
1352 modem->voice_mode, modem->voice_state,
1353 modem->area_code, modem->cell_id );
1354 else
1355 return amodem_printf( modem, "+CREG: %d,%d",
1356 modem->voice_mode, modem->voice_state );
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001357 } else if (cmd[0] == '=') {
1358 switch (cmd[1]) {
1359 case '0':
1360 modem->voice_mode = A_REGISTRATION_UNSOL_DISABLED;
1361 break;
1362
1363 case '1':
1364 modem->voice_mode = A_REGISTRATION_UNSOL_ENABLED;
1365 break;
1366
1367 case '2':
1368 modem->voice_mode = A_REGISTRATION_UNSOL_ENABLED_FULL;
1369 break;
1370
1371 case '?':
1372 return "+CREG: (0-2)";
1373
1374 default:
1375 return "ERROR: BAD COMMAND";
1376 }
1377 } else {
1378 assert( 0 && "unreachable" );
1379 }
1380 } else if ( !memcmp( cmd, "+CGREG", 6 ) ) {
1381 cmd += 6;
David Turner6f290f22009-04-18 20:50:40 -07001382 if (cmd[0] == '?') {
1383 if (modem->supportsNetworkDataType)
1384 return amodem_printf( modem, "+CGREG: %d,%d,\"%04x\",\"%04x\",\"%04x\"",
1385 modem->data_mode, modem->data_state,
1386 modem->area_code, modem->cell_id,
1387 modem->data_network );
1388 else
1389 return amodem_printf( modem, "+CGREG: %d,%d,\"%04x\",\"%04x\"",
1390 modem->data_mode, modem->data_state,
1391 modem->area_code, modem->cell_id );
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001392 } else if (cmd[0] == '=') {
1393 switch (cmd[1]) {
1394 case '0':
1395 modem->data_mode = A_REGISTRATION_UNSOL_DISABLED;
1396 break;
1397
1398 case '1':
1399 modem->data_mode = A_REGISTRATION_UNSOL_ENABLED;
1400 break;
1401
1402 case '2':
1403 modem->data_mode = A_REGISTRATION_UNSOL_ENABLED_FULL;
1404 break;
1405
1406 case '?':
1407 return "+CGREG: (0-2)";
1408
1409 default:
1410 return "ERROR: BAD COMMAND";
1411 }
1412 } else {
1413 assert( 0 && "unreachable" );
1414 }
1415 }
1416 return NULL;
1417}
1418
1419static const char*
1420handleSetDialTone( const char* cmd, AModem modem )
1421{
1422 /* XXX: TODO */
1423 return NULL;
1424}
1425
1426static const char*
1427handleDeleteSMSonSIM( const char* cmd, AModem modem )
1428{
1429 /* XXX: TODO */
1430 return NULL;
1431}
1432
1433static const char*
1434handleSIM_IO( const char* cmd, AModem modem )
1435{
1436 return asimcard_io( modem->sim, cmd );
1437}
1438
1439
1440static const char*
1441handleOperatorSelection( const char* cmd, AModem modem )
1442{
1443 assert( !memcmp( "+COPS", cmd, 5 ) );
1444 cmd += 5;
1445 if (cmd[0] == '?') { /* ask for current operator */
1446 AOperator oper = &modem->operators[ modem->oper_index ];
1447
1448 if ( !amodem_has_network( modem ) )
1449 {
1450 /* this error code means "no network" */
1451 return amodem_printf( modem, "+CME ERROR: 30" );
1452 }
1453
1454 oper = &modem->operators[ modem->oper_index ];
1455
1456 if ( modem->oper_name_index == 2 )
1457 return amodem_printf( modem, "+COPS: %d,2,%s",
1458 modem->oper_selection_mode,
1459 oper->name[2] );
1460
1461 return amodem_printf( modem, "+COPS: %d,%d,\"%s\"",
1462 modem->oper_selection_mode,
1463 modem->oper_name_index,
1464 oper->name[ modem->oper_name_index ] );
1465 }
1466 else if (cmd[0] == '=' && cmd[1] == '?') { /* ask for all available operators */
1467 const char* comma = "+COPS: ";
1468 int nn;
1469 amodem_begin_line( modem );
1470 for (nn = 0; nn < modem->oper_count; nn++) {
1471 AOperator oper = &modem->operators[nn];
1472 amodem_add_line( modem, "%s(%d,\"%s\",\"%s\",\"%s\")", comma,
1473 oper->status, oper->name[0], oper->name[1], oper->name[2] );
1474 comma = ", ";
1475 }
1476 return amodem_end_line( modem );
1477 }
1478 else if (cmd[0] == '=') {
1479 switch (cmd[1]) {
1480 case '0':
1481 modem->oper_selection_mode = A_SELECTION_AUTOMATIC;
1482 return NULL;
1483
1484 case '1':
1485 {
1486 int format, nn, len, found = -1;
1487
1488 if (cmd[2] != ',')
1489 goto BadCommand;
1490 format = cmd[3] - '0';
1491 if ( (unsigned)format > 2 )
1492 goto BadCommand;
1493 if (cmd[4] != ',')
1494 goto BadCommand;
1495 cmd += 5;
1496 len = strlen(cmd);
1497 if (*cmd == '"') {
1498 cmd++;
1499 len -= 2;
1500 }
1501 if (len <= 0)
1502 goto BadCommand;
1503
1504 for (nn = 0; nn < modem->oper_count; nn++) {
1505 AOperator oper = modem->operators + nn;
1506 char* name = oper->name[ format ];
1507
1508 if ( !memcpy( name, cmd, len ) && name[len] == 0 ) {
1509 found = nn;
1510 break;
1511 }
1512 }
1513
1514 if (found < 0) {
1515 /* Selection failed */
1516 return "+CME ERROR: 529";
1517 } else if (modem->operators[found].status == A_STATUS_DENIED) {
1518 /* network not allowed */
1519 return "+CME ERROR: 32";
1520 }
1521 modem->oper_index = found;
1522
1523 /* set the voice and data registration states to home or roaming
1524 * depending on the operator index
1525 */
1526 if (found == OPERATOR_HOME_INDEX) {
1527 modem->voice_state = A_REGISTRATION_HOME;
1528 modem->data_state = A_REGISTRATION_HOME;
1529 } else if (found == OPERATOR_ROAMING_INDEX) {
1530 modem->voice_state = A_REGISTRATION_ROAMING;
1531 modem->data_state = A_REGISTRATION_ROAMING;
1532 }
1533 return NULL;
1534 }
1535
1536 case '2':
1537 modem->oper_selection_mode = A_SELECTION_DEREGISTRATION;
1538 return NULL;
1539
1540 case '3':
1541 {
1542 int format;
1543
1544 if (cmd[2] != ',')
1545 goto BadCommand;
1546
1547 format = cmd[3] - '0';
1548 if ( (unsigned)format > 2 )
1549 goto BadCommand;
1550
1551 modem->oper_name_index = format;
1552 return NULL;
1553 }
1554 default:
1555 ;
1556 }
1557 }
1558BadCommand:
1559 return unknownCommand(cmd,modem);
1560}
1561
1562static const char*
1563handleRequestOperator( const char* cmd, AModem modem )
1564{
1565 AOperator oper;
1566 cmd=cmd;
1567
1568 if ( !amodem_has_network(modem) )
1569 return "+CME ERROR: 30";
1570
1571 oper = modem->operators + modem->oper_index;
1572 modem->oper_name_index = 2;
1573 return amodem_printf( modem, "+COPS: 0,0,\"%s\"\r"
1574 "+COPS: 0,1,\"%s\"\r"
1575 "+COPS: 0,2,\"%s\"",
1576 oper->name[0], oper->name[1], oper->name[2] );
1577}
1578
1579static const char*
1580handleSendSMStoSIM( const char* cmd, AModem modem )
1581{
1582 /* XXX: TODO */
1583 return "ERROR: unimplemented";
1584}
1585
1586static const char*
1587handleSendSMS( const char* cmd, AModem modem )
1588{
1589 modem->wait_sms = 1;
1590 return "> ";
1591}
1592
1593#if 0
1594static void
1595sms_address_dump( SmsAddress address, FILE* out )
1596{
1597 int nn, len = address->len;
1598
1599 if (address->toa == 0x91) {
1600 fprintf( out, "+" );
1601 }
1602 for (nn = 0; nn < len; nn += 2)
1603 {
1604 static const char dialdigits[16] = "0123456789*#,N%";
1605 int c = address->data[nn/2];
1606
1607 fprintf( out, "%c", dialdigits[c & 0xf] );
1608 if (nn+1 >= len)
1609 break;
1610
1611 fprintf( out, "%c", dialdigits[(c >> 4) & 0xf] );
1612 }
1613}
1614
1615static void
1616smspdu_dump( SmsPDU pdu, FILE* out )
1617{
1618 SmsAddressRec address;
1619 unsigned char temp[256];
1620 int len;
1621
1622 if (pdu == NULL) {
1623 fprintf( out, "SMS PDU is (null)\n" );
1624 return;
1625 }
1626
1627 fprintf( out, "SMS PDU type: " );
1628 switch (smspdu_get_type(pdu)) {
1629 case SMS_PDU_DELIVER: fprintf(out, "DELIVER"); break;
1630 case SMS_PDU_SUBMIT: fprintf(out, "SUBMIT"); break;
1631 case SMS_PDU_STATUS_REPORT: fprintf(out, "STATUS_REPORT"); break;
1632 default: fprintf(out, "UNKNOWN");
1633 }
1634 fprintf( out, "\n sender: " );
1635 if (smspdu_get_sender_address(pdu, &address) < 0)
1636 fprintf( out, "(N/A)" );
1637 else
1638 sms_address_dump(&address, out);
1639 fprintf( out, "\n receiver: " );
1640 if (smspdu_get_receiver_address(pdu, &address) < 0)
1641 fprintf(out, "(N/A)");
1642 else
1643 sms_address_dump(&address, out);
1644 fprintf( out, "\n text: " );
1645 len = smspdu_get_text_message( pdu, temp, sizeof(temp)-1 );
1646 if (len > sizeof(temp)-1 )
1647 len = sizeof(temp)-1;
1648 fprintf( out, "'%.*s'\n", len, temp );
1649}
1650#endif
1651
1652static const char*
1653handleSendSMSText( const char* cmd, AModem modem )
1654{
1655#if 1
1656 SmsAddressRec address;
Marc Petit-Huguenina1b379c2010-07-14 12:33:15 -07001657 char temp[16];
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001658 char number[16];
1659 int numlen;
1660 int len = strlen(cmd);
1661 SmsPDU pdu;
1662
1663 /* get rid of trailing escape */
1664 if (len > 0 && cmd[len-1] == 0x1a)
1665 len -= 1;
1666
1667 pdu = smspdu_create_from_hex( cmd, len );
1668 if (pdu == NULL) {
1669 D("%s: invalid SMS PDU ?: '%s'\n", __FUNCTION__, cmd);
1670 return "+CMS ERROR: INVALID SMS PDU";
1671 }
1672 if (smspdu_get_receiver_address(pdu, &address) < 0) {
1673 D("%s: could not get SMS receiver address from '%s'\n",
1674 __FUNCTION__, cmd);
1675 return "+CMS ERROR: BAD SMS RECEIVER ADDRESS";
1676 }
1677
1678 do {
1679 int index;
1680
Marc Petit-Huguenina1b379c2010-07-14 12:33:15 -07001681 numlen = sms_address_to_str( &address, temp, sizeof(temp) );
1682 if (numlen > sizeof(temp)-1)
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001683 break;
Marc Petit-Huguenina1b379c2010-07-14 12:33:15 -07001684 temp[numlen] = 0;
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001685
Marc Petit-Huguenina1b379c2010-07-14 12:33:15 -07001686 /* Converts 4, 7, and 10 digits number to 11 digits */
1687 if (numlen == 10 && !strncmp(temp, PHONE_PREFIX+1, 6)) {
1688 memcpy( number, PHONE_PREFIX, 1 );
1689 memcpy( number+1, temp, numlen );
1690 number[numlen+1] = 0;
1691 } else if (numlen == 7 && !strncmp(temp, PHONE_PREFIX+4, 3)) {
1692 memcpy( number, PHONE_PREFIX, 4 );
1693 memcpy( number+4, temp, numlen );
1694 number[numlen+4] = 0;
1695 } else if (numlen == 4) {
1696 memcpy( number, PHONE_PREFIX, 7 );
1697 memcpy( number+7, temp, numlen );
1698 number[numlen+7] = 0;
1699 } else {
1700 memcpy( number, temp, numlen );
1701 number[numlen] = 0;
1702 }
1703
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001704 if ( remote_number_string_to_port( number ) < 0 )
1705 break;
1706
1707 if (modem->sms_receiver == NULL) {
1708 modem->sms_receiver = sms_receiver_create();
1709 if (modem->sms_receiver == NULL) {
1710 D( "%s: could not create SMS receiver\n", __FUNCTION__ );
1711 break;
1712 }
1713 }
1714
1715 index = sms_receiver_add_submit_pdu( modem->sms_receiver, pdu );
1716 if (index < 0) {
1717 D( "%s: could not add submit PDU\n", __FUNCTION__ );
1718 break;
1719 }
1720 /* the PDU is now owned by the receiver */
1721 pdu = NULL;
1722
1723 if (index > 0) {
1724 SmsAddressRec from[1];
Marc Petit-Huguenina1b379c2010-07-14 12:33:15 -07001725 char temp[12];
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001726 SmsPDU* deliver;
1727 int nn;
1728
Marc Petit-Huguenina1b379c2010-07-14 12:33:15 -07001729 snprintf( temp, sizeof(temp), PHONE_PREFIX "%d", modem->base_port );
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001730 sms_address_from_str( from, temp, strlen(temp) );
1731
1732 deliver = sms_receiver_create_deliver( modem->sms_receiver, index, from );
1733 if (deliver == NULL) {
1734 D( "%s: could not create deliver PDUs for SMS index %d\n",
1735 __FUNCTION__, index );
1736 break;
1737 }
1738
1739 for (nn = 0; deliver[nn] != NULL; nn++) {
1740 if ( remote_call_sms( number, modem->base_port, deliver[nn] ) < 0 ) {
1741 D( "%s: could not send SMS PDU to remote emulator\n",
1742 __FUNCTION__ );
1743 break;
1744 }
1745 }
1746
1747 smspdu_free_list(deliver);
1748 }
1749
1750 } while (0);
1751
1752 if (pdu != NULL)
1753 smspdu_free(pdu);
1754
1755#elif 1
1756 SmsAddressRec address;
1757 char number[16];
1758 int numlen;
1759 int len = strlen(cmd);
1760 SmsPDU pdu;
1761
1762 /* get rid of trailing escape */
1763 if (len > 0 && cmd[len-1] == 0x1a)
1764 len -= 1;
1765
1766 pdu = smspdu_create_from_hex( cmd, len );
1767 if (pdu == NULL) {
1768 D("%s: invalid SMS PDU ?: '%s'\n", __FUNCTION__, cmd);
1769 return "+CMS ERROR: INVALID SMS PDU";
1770 }
1771 if (smspdu_get_receiver_address(pdu, &address) < 0) {
1772 D("%s: could not get SMS receiver address from '%s'\n",
1773 __FUNCTION__, cmd);
1774 return "+CMS ERROR: BAD SMS RECEIVER ADDRESS";
1775 }
1776 do {
1777 numlen = sms_address_to_str( &address, number, sizeof(number) );
1778 if (numlen > sizeof(number)-1)
1779 break;
1780
1781 number[numlen] = 0;
1782 if ( remote_number_string_to_port( number ) < 0 )
1783 break;
1784
1785 if ( remote_call_sms( number, modem->base_port, pdu ) < 0 )
1786 {
1787 D("%s: could not send SMS PDU to remote emulator\n",
1788 __FUNCTION__);
1789 return "+CMS ERROR: NO EMULATOR RECEIVER";
1790 }
1791 } while (0);
1792#else
1793 fprintf(stderr, "SMS<< %s\n", cmd);
1794 SmsPDU pdu = smspdu_create_from_hex( cmd, strlen(cmd) );
1795 if (pdu == NULL) {
1796 fprintf(stderr, "invalid SMS PDU ?: '%s'\n", cmd);
1797 } else {
1798 smspdu_dump(pdu, stderr);
1799 }
1800#endif
1801 return "+CMGS: 0\rOK\r";
1802}
1803
1804static const char*
1805handleChangeOrEnterPIN( const char* cmd, AModem modem )
1806{
1807 assert( !memcmp( cmd, "+CPIN=", 6 ) );
1808 cmd += 6;
1809
1810 switch (asimcard_get_status(modem->sim)) {
1811 case A_SIM_STATUS_ABSENT:
1812 return "+CME ERROR: SIM ABSENT";
1813
1814 case A_SIM_STATUS_NOT_READY:
1815 return "+CME ERROR: SIM NOT READY";
1816
1817 case A_SIM_STATUS_READY:
1818 /* this may be a request to change the PIN */
1819 {
1820 if (strlen(cmd) == 9 && cmd[4] == ',') {
1821 char pin[5];
1822 memcpy( pin, cmd, 4 ); pin[4] = 0;
1823
1824 if ( !asimcard_check_pin( modem->sim, pin ) )
1825 return "+CME ERROR: BAD PIN";
1826
1827 memcpy( pin, cmd+5, 4 );
1828 asimcard_set_pin( modem->sim, pin );
1829 return "+CPIN: READY";
1830 }
1831 }
1832 break;
1833
1834 case A_SIM_STATUS_PIN: /* waiting for PIN */
1835 if ( asimcard_check_pin( modem->sim, cmd ) )
1836 return "+CPIN: READY";
1837 else
1838 return "+CME ERROR: BAD PIN";
1839
1840 case A_SIM_STATUS_PUK:
1841 if (strlen(cmd) == 9 && cmd[4] == ',') {
1842 char puk[5];
1843 memcpy( puk, cmd, 4 );
1844 puk[4] = 0;
1845 if ( asimcard_check_puk( modem->sim, puk, cmd+5 ) )
1846 return "+CPIN: READY";
1847 else
1848 return "+CME ERROR: BAD PUK";
1849 }
1850 return "+CME ERROR: BAD PUK";
1851
1852 default:
1853 return "+CPIN: PH-NET PIN";
1854 }
1855
1856 return "+CME ERROR: BAD FORMAT";
1857}
1858
1859
1860static const char*
1861handleListCurrentCalls( const char* cmd, AModem modem )
1862{
1863 int nn;
1864 amodem_begin_line( modem );
1865 for (nn = 0; nn < modem->call_count; nn++) {
1866 AVoiceCall vcall = modem->calls + nn;
1867 ACall call = &vcall->call;
1868 if (call->mode == A_CALL_VOICE)
1869 amodem_add_line( modem, "+CLCC: %d,%d,%d,%d,%d,\"%s\",%d\r\n",
1870 call->id, call->dir, call->state, call->mode,
1871 call->multi, call->number, 129 );
1872 }
1873 return amodem_end_line( modem );
1874}
1875
Tim Baverstock622b8f42010-12-07 11:36:59 +00001876/* Add a(n unsolicited) time response.
1877 *
1878 * retrieve the current time and zone in a format suitable
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001879 * for %CTZV: unsolicited message
1880 * "yy/mm/dd,hh:mm:ss(+/-)tz"
1881 * mm is 0-based
1882 * tz is in number of quarter-hours
1883 *
1884 * it seems reference-ril doesn't parse the comma (,) as anything else than a token
1885 * separator, so use a column (:) instead, the Java parsing code won't see a difference
1886 *
1887 */
Tim Baverstock622b8f42010-12-07 11:36:59 +00001888static void
1889amodem_addTimeUpdate( AModem modem )
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001890{
1891 time_t now = time(NULL);
1892 struct tm utc, local;
1893 long e_local, e_utc;
1894 long tzdiff;
1895 char tzname[64];
1896
1897 tzset();
1898
1899 utc = *gmtime( &now );
1900 local = *localtime( &now );
1901
1902 e_local = local.tm_min + 60*(local.tm_hour + 24*local.tm_yday);
1903 e_utc = utc.tm_min + 60*(utc.tm_hour + 24*utc.tm_yday);
1904
1905 if ( utc.tm_year < local.tm_year )
1906 e_local += 24*60;
1907 else if ( utc.tm_year > local.tm_year )
1908 e_utc += 24*60;
1909
1910 tzdiff = e_local - e_utc; /* timezone offset in minutes */
1911
1912 /* retrieve a zoneinfo-compatible name for the host timezone
1913 */
1914 {
1915 char* end = tzname + sizeof(tzname);
1916 char* p = bufprint_zoneinfo_timezone( tzname, end );
1917 if (p >= end)
1918 strcpy(tzname, "Unknown/Unknown");
1919
1920 /* now replace every / in the timezone name by a "!"
1921 * that's because the code that reads the CTZV line is
1922 * dumb and treats a / as a field separator...
1923 */
1924 p = tzname;
1925 while (1) {
1926 p = strchr(p, '/');
1927 if (p == NULL)
1928 break;
1929 *p = '!';
1930 p += 1;
1931 }
1932 }
1933
1934 /* as a special extension, we append the name of the host's time zone to the
1935 * string returned with %CTZ. the system should contain special code to detect
1936 * and deal with this case (since it normally relied on the operator's country code
1937 * which is hard to simulate on a general-purpose computer
1938 */
Tim Baverstock622b8f42010-12-07 11:36:59 +00001939 amodem_add_line( modem, "%%CTZV: %02d/%02d/%02d:%02d:%02d:%02d%c%d:%d:%s\r\n",
1940 (utc.tm_year + 1900) % 100, utc.tm_mon + 1, utc.tm_mday,
1941 utc.tm_hour, utc.tm_min, utc.tm_sec,
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001942 (tzdiff >= 0) ? '+' : '-', (tzdiff >= 0 ? tzdiff : -tzdiff) / 15,
1943 (local.tm_isdst > 0),
1944 tzname );
1945}
1946
Tim Baverstock622b8f42010-12-07 11:36:59 +00001947static const char*
1948handleEndOfInit( const char* cmd, AModem modem )
1949{
1950 amodem_begin_line( modem );
1951 amodem_addTimeUpdate( modem );
1952 return amodem_end_line( modem );
1953}
1954
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001955
1956static const char*
1957handleListPDPContexts( const char* cmd, AModem modem )
1958{
1959 int nn;
1960 assert( !memcmp( cmd, "+CGACT?", 7 ) );
1961 amodem_begin_line( modem );
1962 for (nn = 0; nn < MAX_DATA_CONTEXTS; nn++) {
1963 ADataContext data = modem->data_contexts + nn;
1964 if (!data->active)
1965 continue;
David 'Digit' Turnerd7f019d2011-02-04 12:30:54 +01001966 amodem_add_line( modem, "+CGACT: %d,%d\r\n", data->id, data->active );
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001967 }
1968 return amodem_end_line( modem );
1969}
1970
1971static const char*
1972handleDefinePDPContext( const char* cmd, AModem modem )
1973{
1974 assert( !memcmp( cmd, "+CGDCONT=", 9 ) );
1975 cmd += 9;
1976 if (cmd[0] == '?') {
David 'Digit' Turnerd7f019d2011-02-04 12:30:54 +01001977 /* +CGDCONT=? is used to query the ranges of supported PDP Contexts.
1978 * We only really support IP ones in the emulator, so don't try to
1979 * fake PPP ones.
1980 */
1981 return "+CGDCONT: (1-1),\"IP\",,,(0-2),(0-4)\r\n";
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001982 } else {
1983 /* template is +CGDCONT=<id>,"<type>","<apn>",,0,0 */
1984 int id = cmd[0] - '1';
1985 ADataType type;
1986 char apn[32];
1987 ADataContext data;
1988
1989 if ((unsigned)id > 3)
1990 goto BadCommand;
1991
1992 if ( !memcmp( cmd+1, ",\"IP\",\"", 7 ) ) {
1993 type = A_DATA_IP;
1994 cmd += 8;
1995 } else if ( !memcmp( cmd+1, ",\"PPP\",\"", 8 ) ) {
1996 type = A_DATA_PPP;
1997 cmd += 9;
1998 } else
1999 goto BadCommand;
2000
2001 {
2002 const char* p = strchr( cmd, '"' );
2003 int len;
2004 if (p == NULL)
2005 goto BadCommand;
2006 len = (int)( p - cmd );
2007 if (len > sizeof(apn)-1 )
2008 len = sizeof(apn)-1;
2009 memcpy( apn, cmd, len );
2010 apn[len] = 0;
2011 }
2012
2013 data = modem->data_contexts + id;
2014
2015 data->id = id + 1;
2016 data->active = 1;
2017 data->type = type;
2018 memcpy( data->apn, apn, sizeof(data->apn) );
2019 }
2020 return NULL;
2021BadCommand:
2022 return "ERROR: BAD COMMAND";
2023}
2024
David 'Digit' Turnerd7f019d2011-02-04 12:30:54 +01002025static const char*
2026handleQueryPDPContext( const char* cmd, AModem modem )
2027{
2028 int nn;
2029 amodem_begin_line(modem);
2030 for (nn = 0; nn < MAX_DATA_CONTEXTS; nn++) {
2031 ADataContext data = modem->data_contexts + nn;
2032 if (!data->active)
2033 continue;
2034 amodem_add_line( modem, "+CGDCONT: %d,\"%s\",\"%s\",\"%s\",0,0\r\n",
2035 data->id,
2036 data->type == A_DATA_IP ? "IP" : "PPP",
2037 data->apn,
2038 /* Note: For now, hard-code the IP address of our
2039 * network interface
2040 */
2041 data->type == A_DATA_IP ? "10.0.2.15" : "");
2042 }
2043 return amodem_end_line(modem);
2044}
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08002045
2046static const char*
2047handleStartPDPContext( const char* cmd, AModem modem )
2048{
2049 /* XXX: TODO: handle PDP start appropriately */
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08002050 return NULL;
2051}
2052
2053
2054static void
2055remote_voice_call_event( void* _vcall, int success )
2056{
2057 AVoiceCall vcall = _vcall;
2058 AModem modem = vcall->modem;
2059
2060 /* NOTE: success only means we could send the "gsm in new" command
2061 * to the remote emulator, nothing more */
2062
2063 if (!success) {
2064 /* aargh, the remote emulator probably quitted at that point */
2065 amodem_free_call(modem, vcall);
2066 amodem_send_calls_update(modem);
2067 }
2068}
2069
2070
2071static void
2072voice_call_event( void* _vcall )
2073{
2074 AVoiceCall vcall = _vcall;
2075 ACall call = &vcall->call;
2076
2077 switch (call->state) {
2078 case A_CALL_DIALING:
2079 call->state = A_CALL_ALERTING;
2080
2081 if (vcall->is_remote) {
2082 if ( remote_call_dial( call->number,
2083 vcall->modem->base_port,
2084 remote_voice_call_event, vcall ) < 0 )
2085 {
2086 /* we could not connect, probably because the corresponding
2087 * emulator is not running, so simply destroy this call.
2088 * XXX: should we send some sort of message to indicate BAD NUMBER ? */
2089 /* it seems the Android code simply waits for changes in the list */
2090 amodem_free_call( vcall->modem, vcall );
2091 }
2092 } else {
2093 /* this is not a remote emulator number, so just simulate
2094 * a small ringing delay */
2095 sys_timer_set( vcall->timer, sys_time_ms() + CALL_DELAY_ALERT,
2096 voice_call_event, vcall );
2097 }
2098 break;
2099
2100 case A_CALL_ALERTING:
2101 call->state = A_CALL_ACTIVE;
2102 break;
2103
2104 default:
2105 assert( 0 && "unreachable event call state" );
2106 }
2107 amodem_send_calls_update(vcall->modem);
2108}
2109
Jaime Lopez1a000852010-07-21 18:03:58 -07002110static int amodem_is_emergency( AModem modem, const char *number )
2111{
2112 int i;
2113
2114 if (!number) return 0;
2115 for (i = 0; i < MAX_EMERGENCY_NUMBERS; i++) {
2116 if ( modem->emergency_numbers[i] && !strcmp( number, modem->emergency_numbers[i] )) break;
2117 }
2118
2119 if (i < MAX_EMERGENCY_NUMBERS) return 1;
2120
2121 return 0;
2122}
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08002123
2124static const char*
2125handleDial( const char* cmd, AModem modem )
2126{
2127 AVoiceCall vcall = amodem_alloc_call( modem );
2128 ACall call = &vcall->call;
2129 int len;
2130
2131 if (call == NULL)
2132 return "ERROR: TOO MANY CALLS";
2133
2134 assert( cmd[0] == 'D' );
2135 call->dir = A_CALL_OUTBOUND;
2136 call->state = A_CALL_DIALING;
2137 call->mode = A_CALL_VOICE;
2138 call->multi = 0;
2139
2140 cmd += 1;
2141 len = strlen(cmd);
2142 if (len > 0 && cmd[len-1] == ';')
2143 len--;
2144 if (len >= sizeof(call->number))
2145 len = sizeof(call->number)-1;
2146
Marc Petit-Huguenina1b379c2010-07-14 12:33:15 -07002147 /* Converts 4, 7, and 10 digits number to 11 digits */
2148 if (len == 10 && !strncmp(cmd, PHONE_PREFIX+1, 6)) {
2149 memcpy( call->number, PHONE_PREFIX, 1 );
2150 memcpy( call->number+1, cmd, len );
2151 call->number[len+1] = 0;
2152 } else if (len == 7 && !strncmp(cmd, PHONE_PREFIX+4, 3)) {
2153 memcpy( call->number, PHONE_PREFIX, 4 );
2154 memcpy( call->number+4, cmd, len );
2155 call->number[len+4] = 0;
2156 } else if (len == 4) {
2157 memcpy( call->number, PHONE_PREFIX, 7 );
2158 memcpy( call->number+7, cmd, len );
2159 call->number[len+7] = 0;
2160 } else {
2161 memcpy( call->number, cmd, len );
2162 call->number[len] = 0;
2163 }
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08002164
Jaime Lopez1a000852010-07-21 18:03:58 -07002165 amodem_begin_line( modem );
2166 if (amodem_is_emergency(modem, call->number)) {
2167 modem->in_emergency_mode = 1;
2168 amodem_add_line( modem, "+WSOS: 1" );
2169 }
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08002170 vcall->is_remote = (remote_number_string_to_port(call->number) > 0);
2171
2172 vcall->timer = sys_timer_create();
2173 sys_timer_set( vcall->timer, sys_time_ms() + CALL_DELAY_DIAL,
2174 voice_call_event, vcall );
2175
Jaime Lopez1a000852010-07-21 18:03:58 -07002176 return amodem_end_line( modem );
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08002177}
2178
2179
2180static const char*
2181handleAnswer( const char* cmd, AModem modem )
2182{
2183 int nn;
2184 for (nn = 0; nn < modem->call_count; nn++) {
2185 AVoiceCall vcall = modem->calls + nn;
2186 ACall call = &vcall->call;
2187
2188 if (cmd[0] == 'A') {
2189 if (call->state == A_CALL_INCOMING) {
2190 acall_set_state( vcall, A_CALL_ACTIVE );
2191 }
2192 else if (call->state == A_CALL_ACTIVE) {
2193 acall_set_state( vcall, A_CALL_HELD );
2194 }
2195 } else if (cmd[0] == 'H') {
2196 /* ATH: hangup, since user is busy */
2197 if (call->state == A_CALL_INCOMING) {
2198 amodem_free_call( modem, vcall );
2199 break;
2200 }
2201 }
2202 }
2203 return NULL;
2204}
2205
Tim Baverstock622b8f42010-12-07 11:36:59 +00002206int android_snapshot_update_time = 1;
2207int android_snapshot_update_time_request = 0;
Tim Baverstock622b8f42010-12-07 11:36:59 +00002208
2209static const char*
2210handleSignalStrength( const char* cmd, AModem modem )
2211{
Tim Baverstock622b8f42010-12-07 11:36:59 +00002212 amodem_begin_line( modem );
David 'Digit' Turnere5af8a22011-02-24 16:48:19 +01002213
Tim Baverstock622b8f42010-12-07 11:36:59 +00002214 /* Sneak time updates into the SignalStrength request, because it's periodic.
2215 * Ideally, we'd be able to prod the guest into asking immediately on restore
2216 * from snapshot, but that'd require a driver.
2217 */
2218 if ( android_snapshot_update_time && android_snapshot_update_time_request ) {
2219 amodem_addTimeUpdate( modem );
2220 android_snapshot_update_time_request = 0;
2221 }
David 'Digit' Turnere5af8a22011-02-24 16:48:19 +01002222
Tim Baverstock622b8f42010-12-07 11:36:59 +00002223 // rssi = 0 (<-113dBm) 1 (<-111) 2-30 (<-109--53) 31 (>=-51) 99 (?!)
2224 // ber (bit error rate) - always 99 (unknown), apparently.
Tim Baverstock4c6b10a2010-12-15 17:31:13 +00002225 // TODO: return 99 if modem->radio_state==A_RADIO_STATE_OFF, once radio_state is in snapshot.
2226 int rssi = modem->rssi;
2227 int ber = modem->ber;
2228 rssi = (0 > rssi && rssi > 31) ? 99 : rssi ;
2229 ber = (0 > ber && ber > 7 ) ? 99 : ber;
Uma Maheswari Ramalingame9bd6b62012-08-09 12:12:22 -07002230 amodem_add_line( modem, "+CSQ: %i,%i,85,130,90,6,4,25,9,50,68,12\r\n", rssi, ber );
Tim Baverstock622b8f42010-12-07 11:36:59 +00002231 return amodem_end_line( modem );
2232}
2233
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08002234static const char*
2235handleHangup( const char* cmd, AModem modem )
2236{
2237 if ( !memcmp(cmd, "+CHLD=", 6) ) {
2238 int nn;
2239 cmd += 6;
2240 switch (cmd[0]) {
2241 case '0': /* release all held, and set busy for waiting calls */
2242 for (nn = 0; nn < modem->call_count; nn++) {
2243 AVoiceCall vcall = modem->calls + nn;
2244 ACall call = &vcall->call;
2245 if (call->mode != A_CALL_VOICE)
2246 continue;
2247 if (call->state == A_CALL_HELD ||
2248 call->state == A_CALL_WAITING ||
2249 call->state == A_CALL_INCOMING) {
2250 amodem_free_call(modem, vcall);
2251 nn--;
2252 }
2253 }
2254 break;
2255
2256 case '1':
2257 if (cmd[1] == 0) { /* release all active, accept held one */
2258 for (nn = 0; nn < modem->call_count; nn++) {
2259 AVoiceCall vcall = modem->calls + nn;
2260 ACall call = &vcall->call;
2261 if (call->mode != A_CALL_VOICE)
2262 continue;
2263 if (call->state == A_CALL_ACTIVE) {
2264 amodem_free_call(modem, vcall);
2265 nn--;
2266 }
2267 else if (call->state == A_CALL_HELD ||
2268 call->state == A_CALL_WAITING) {
2269 acall_set_state( vcall, A_CALL_ACTIVE );
2270 }
2271 }
2272 } else { /* release specific call */
2273 int id = cmd[1] - '0';
2274 AVoiceCall vcall = amodem_find_call( modem, id );
2275 if (vcall != NULL)
2276 amodem_free_call( modem, vcall );
2277 }
2278 break;
2279
2280 case '2':
2281 if (cmd[1] == 0) { /* place all active on hold, accept held or waiting one */
2282 for (nn = 0; nn < modem->call_count; nn++) {
2283 AVoiceCall vcall = modem->calls + nn;
2284 ACall call = &vcall->call;
2285 if (call->mode != A_CALL_VOICE)
2286 continue;
2287 if (call->state == A_CALL_ACTIVE) {
2288 acall_set_state( vcall, A_CALL_HELD );
2289 }
2290 else if (call->state == A_CALL_HELD ||
2291 call->state == A_CALL_WAITING) {
2292 acall_set_state( vcall, A_CALL_ACTIVE );
2293 }
2294 }
2295 } else { /* place all active on hold, except a specific one */
2296 int id = cmd[1] - '0';
2297 for (nn = 0; nn < modem->call_count; nn++) {
2298 AVoiceCall vcall = modem->calls + nn;
2299 ACall call = &vcall->call;
2300 if (call->mode != A_CALL_VOICE)
2301 continue;
2302 if (call->state == A_CALL_ACTIVE && call->id != id) {
2303 acall_set_state( vcall, A_CALL_HELD );
2304 }
2305 }
2306 }
2307 break;
2308
2309 case '3': /* add a held call to the conversation */
2310 for (nn = 0; nn < modem->call_count; nn++) {
2311 AVoiceCall vcall = modem->calls + nn;
2312 ACall call = &vcall->call;
2313 if (call->mode != A_CALL_VOICE)
2314 continue;
2315 if (call->state == A_CALL_HELD) {
2316 acall_set_state( vcall, A_CALL_ACTIVE );
2317 break;
2318 }
2319 }
2320 break;
2321
2322 case '4': /* connect the two calls */
2323 for (nn = 0; nn < modem->call_count; nn++) {
2324 AVoiceCall vcall = modem->calls + nn;
2325 ACall call = &vcall->call;
2326 if (call->mode != A_CALL_VOICE)
2327 continue;
2328 if (call->state == A_CALL_HELD) {
2329 acall_set_state( vcall, A_CALL_ACTIVE );
2330 break;
2331 }
2332 }
2333 break;
2334 }
2335 }
2336 else
2337 return "ERROR: BAD COMMAND";
2338
2339 return NULL;
2340}
2341
2342
2343/* a function used to deal with a non-trivial request */
2344typedef const char* (*ResponseHandler)(const char* cmd, AModem modem);
2345
2346static const struct {
2347 const char* cmd; /* command coming from libreference-ril.so, if first
2348 character is '!', then the rest is a prefix only */
2349
2350 const char* answer; /* default answer, NULL if needs specific handling or
2351 if OK is good enough */
2352
2353 ResponseHandler handler; /* specific handler, ignored if 'answer' is not NULL,
2354 NULL if OK is good enough */
2355} sDefaultResponses[] =
2356{
2357 /* see onRadioPowerOn() */
2358 { "%CPHS=1", NULL, NULL },
2359 { "%CTZV=1", NULL, NULL },
2360
2361 /* see onSIMReady() */
2362 { "+CSMS=1", "+CSMS: 1, 1, 1", NULL },
2363 { "+CNMI=1,2,2,1,1", NULL, NULL },
2364
2365 /* see requestRadioPower() */
2366 { "+CFUN=0", NULL, handleRadioPower },
2367 { "+CFUN=1", NULL, handleRadioPower },
2368
Jaime Lopez1a000852010-07-21 18:03:58 -07002369 { "+CTEC=?", "+CTEC: 0,1,2,3", NULL }, /* Query available Techs */
2370 { "!+CTEC", NULL, handleTech }, /* Set/get current Technology and preferred mode */
2371
2372 { "+WRMP=?", "+WRMP: 0,1,2", NULL }, /* Query Roam Preference */
2373 { "!+WRMP", NULL, handleRoamPref }, /* Set/get Roam Preference */
2374
2375 { "+CCSS=?", "+CTEC: 0,1", NULL }, /* Query available subscription sources */
2376 { "!+CCSS", NULL, handleSubscriptionSource }, /* Set/Get current subscription source */
2377
2378 { "+WSOS=?", "+WSOS: 0", NULL}, /* Query supported +WSOS values */
2379 { "!+WSOS=", NULL, handleEmergencyMode },
2380
2381 { "+WPRL?", NULL, handlePrlVersion }, /* Query the current PRL version */
2382
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08002383 /* see requestOrSendPDPContextList() */
David 'Digit' Turnerd7f019d2011-02-04 12:30:54 +01002384 { "+CGACT?", NULL, handleListPDPContexts },
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08002385
2386 /* see requestOperator() */
2387 { "+COPS=3,0;+COPS?;+COPS=3,1;+COPS?;+COPS=3,2;+COPS?", NULL, handleRequestOperator },
2388
2389 /* see requestQueryNetworkSelectionMode() */
2390 { "!+COPS", NULL, handleOperatorSelection },
2391
2392 /* see requestGetCurrentCalls() */
2393 { "+CLCC", NULL, handleListCurrentCalls },
2394
2395 /* see requestWriteSmsToSim() */
2396 { "!+CMGW=", NULL, handleSendSMStoSIM },
2397
2398 /* see requestHangup() */
2399 { "!+CHLD=", NULL, handleHangup },
2400
2401 /* see requestSignalStrength() */
Tim Baverstock622b8f42010-12-07 11:36:59 +00002402 { "+CSQ", NULL, handleSignalStrength },
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08002403
2404 /* see requestRegistrationState() */
2405 { "!+CREG", NULL, handleNetworkRegistration },
2406 { "!+CGREG", NULL, handleNetworkRegistration },
2407
2408 /* see requestSendSMS() */
2409 { "!+CMGS=", NULL, handleSendSMS },
2410
2411 /* see requestSetupDefaultPDP() */
2412 { "%CPRIM=\"GMM\",\"CONFIG MULTISLOT_CLASS=<10>\"", NULL, NULL },
2413 { "%DATA=2,\"UART\",1,,\"SER\",\"UART\",0", NULL, NULL },
2414
2415 { "!+CGDCONT=", NULL, handleDefinePDPContext },
David 'Digit' Turnerd7f019d2011-02-04 12:30:54 +01002416 { "+CGDCONT?", NULL, handleQueryPDPContext },
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08002417
2418 { "+CGQREQ=1", NULL, NULL },
2419 { "+CGQMIN=1", NULL, NULL },
2420 { "+CGEREP=1,0", NULL, NULL },
2421 { "+CGACT=1,0", NULL, NULL },
2422 { "D*99***1#", NULL, handleStartPDPContext },
2423
2424 /* see requestDial() */
2425 { "!D", NULL, handleDial }, /* the code says that success/error is ignored, the call state will
2426 be polled through +CLCC instead */
2427
2428 /* see requestSMSAcknowledge() */
2429 { "+CNMA=1", NULL, NULL },
2430 { "+CNMA=2", NULL, NULL },
2431
2432 /* see requestSIM_IO() */
2433 { "!+CRSM=", NULL, handleSIM_IO },
2434
2435 /* see onRequest() */
2436 { "+CHLD=0", NULL, handleHangup },
2437 { "+CHLD=1", NULL, handleHangup },
2438 { "+CHLD=2", NULL, handleHangup },
2439 { "+CHLD=3", NULL, handleHangup },
2440 { "A", NULL, handleAnswer }, /* answer the call */
2441 { "H", NULL, handleAnswer }, /* user is busy */
2442 { "!+VTS=", NULL, handleSetDialTone },
2443 { "+CIMI", OPERATOR_HOME_MCCMNC "000000000", NULL }, /* request internation subscriber identification number */
2444 { "+CGSN", "000000000000000", NULL }, /* request model version */
2445 { "+CUSD=2",NULL, NULL }, /* Cancel USSD */
2446 { "+COPS=0", NULL, handleOperatorSelection }, /* set network selection to automatic */
2447 { "!+CMGD=", NULL, handleDeleteSMSonSIM }, /* delete SMS on SIM */
2448 { "!+CPIN=", NULL, handleChangeOrEnterPIN },
2449
2450 /* see getSIMStatus() */
2451 { "+CPIN?", NULL, handleSIMStatusReq },
2452 { "+CNMI?", "+CNMI: 1,2,2,1,1", NULL },
2453
2454 /* see isRadioOn() */
2455 { "+CFUN?", NULL, handleRadioPowerReq },
2456
2457 /* see initializeCallback() */
2458 { "E0Q0V1", NULL, NULL },
2459 { "S0=0", NULL, NULL },
2460 { "+CMEE=1", NULL, NULL },
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08002461 { "+CCWA=1", NULL, NULL },
2462 { "+CMOD=0", NULL, NULL },
2463 { "+CMUT=0", NULL, NULL },
2464 { "+CSSN=0,1", NULL, NULL },
2465 { "+COLP=0", NULL, NULL },
2466 { "+CSCS=\"HEX\"", NULL, NULL },
2467 { "+CUSD=1", NULL, NULL },
2468 { "+CGEREP=1,0", NULL, NULL },
2469 { "+CMGF=0", NULL, handleEndOfInit }, /* now is a goof time to send the current tme and timezone */
2470 { "%CPI=3", NULL, NULL },
2471 { "%CSTAT=1", NULL, NULL },
2472
2473 /* end of list */
2474 {NULL, NULL, NULL}
2475};
2476
2477
2478#define REPLY(str) do { const char* s = (str); R(">> %s\n", quote(s)); return s; } while (0)
2479
2480const char* amodem_send( AModem modem, const char* cmd )
2481{
2482 const char* answer;
2483
2484 if ( modem->wait_sms != 0 ) {
2485 modem->wait_sms = 0;
2486 R( "SMS<< %s\n", quote(cmd) );
2487 answer = handleSendSMSText( cmd, modem );
2488 REPLY(answer);
2489 }
2490
2491 /* everything that doesn't start with 'AT' is not a command, right ? */
2492 if ( cmd[0] != 'A' || cmd[1] != 'T' || cmd[2] == 0 ) {
2493 /* R( "-- %s\n", quote(cmd) ); */
2494 return NULL;
2495 }
2496 R( "<< %s\n", quote(cmd) );
2497
2498 cmd += 2;
2499
2500 /* TODO: implement command handling */
2501 {
2502 int nn, found = 0;
2503
2504 for (nn = 0; ; nn++) {
2505 const char* scmd = sDefaultResponses[nn].cmd;
2506
2507 if (!scmd) /* end of list */
2508 break;
2509
2510 if (scmd[0] == '!') { /* prefix match */
2511 int len = strlen(++scmd);
2512
2513 if ( !memcmp( scmd, cmd, len ) ) {
2514 found = 1;
2515 break;
2516 }
2517 } else { /* full match */
2518 if ( !strcmp( scmd, cmd ) ) {
2519 found = 1;
2520 break;
2521 }
2522 }
2523 }
2524
2525 if ( !found )
2526 {
2527 D( "** UNSUPPORTED COMMAND **\n" );
2528 REPLY( "ERROR: UNSUPPORTED" );
2529 }
2530 else
2531 {
2532 const char* answer = sDefaultResponses[nn].answer;
2533 ResponseHandler handler = sDefaultResponses[nn].handler;
2534
2535 if ( answer != NULL ) {
2536 REPLY( amodem_printf( modem, "%s\rOK", answer ) );
2537 }
2538
2539 if (handler == NULL) {
2540 REPLY( "OK" );
2541 }
2542
2543 answer = handler( cmd, modem );
2544 if (answer == NULL)
2545 REPLY( "OK" );
2546
2547 if ( !memcmp( answer, "> ", 2 ) ||
2548 !memcmp( answer, "ERROR", 5 ) ||
2549 !memcmp( answer, "+CME ERROR", 6 ) )
2550 {
2551 REPLY( answer );
2552 }
2553
2554 if (answer != modem->out_buff)
2555 REPLY( amodem_printf( modem, "%s\rOK", answer ) );
2556
2557 strcat( modem->out_buff, "\rOK" );
2558 REPLY( answer );
2559 }
2560 }
2561}