1 2 #include <errno.h> 3 #include <pthread.h> 4 #include "qemu.h" 5 #include <fcntl.h> 6 #include <sys/epoll.h> 7 #include <math.h> 8 #include <time.h> 9 10 #define LOG_TAG "gps_qemu" 11 #include <cutils/log.h> 12 #include <cutils/sockets.h> 13 #include <hardware_legacy/gps.h> 14 15 /* the name of the qemud-controlled socket */ 16 #define QEMU_CHANNEL_NAME "gps" 17 18 #define GPS_DEBUG 0 19 20 #if GPS_DEBUG 21 # define D(...) LOGD(__VA_ARGS__) 22 #else 23 # define D(...) ((void)0) 24 #endif 25 26 27 /*****************************************************************/ 28 /*****************************************************************/ 29 /***** *****/ 30 /***** N M E A T O K E N I Z E R *****/ 31 /***** *****/ 32 /*****************************************************************/ 33 /*****************************************************************/ 34 35 typedef struct { 36 const char* p; 37 const char* end; 38 } Token; 39 40 #define MAX_NMEA_TOKENS 16 41 42 typedef struct { 43 int count; 44 Token tokens[ MAX_NMEA_TOKENS ]; 45 } NmeaTokenizer; 46 47 static int 48 nmea_tokenizer_init( NmeaTokenizer* t, const char* p, const char* end ) 49 { 50 int count = 0; 51 char* q; 52 53 // the initial '$' is optional 54 if (p < end && p[0] == '$') 55 p += 1; 56 57 // remove trailing newline 58 if (end > p && end[-1] == '\n') { 59 end -= 1; 60 if (end > p && end[-1] == '\r') 61 end -= 1; 62 } 63 64 // get rid of checksum at the end of the sentecne 65 if (end >= p+3 && end[-3] == '*') { 66 end -= 3; 67 } 68 69 while (p < end) { 70 const char* q = p; 71 72 q = memchr(p, ',', end-p); 73 if (q == NULL) 74 q = end; 75 76 if (q > p) { 77 if (count < MAX_NMEA_TOKENS) { 78 t->tokens[count].p = p; 79 t->tokens[count].end = q; 80 count += 1; 81 } 82 } 83 if (q < end) 84 q += 1; 85 86 p = q; 87 } 88 89 t->count = count; 90 return count; 91 } 92 93 static Token 94 nmea_tokenizer_get( NmeaTokenizer* t, int index ) 95 { 96 Token tok; 97 static const char* dummy = ""; 98 99 if (index < 0 || index >= t->count) { 100 tok.p = tok.end = dummy; 101 } else 102 tok = t->tokens[index]; 103 104 return tok; 105 } 106 107 108 static int 109 str2int( const char* p, const char* end ) 110 { 111 int result = 0; 112 int len = end - p; 113 114 for ( ; len > 0; len--, p++ ) 115 { 116 int c; 117 118 if (p >= end) 119 goto Fail; 120 121 c = *p - '0'; 122 if ((unsigned)c >= 10) 123 goto Fail; 124 125 result = result*10 + c; 126 } 127 return result; 128 129 Fail: 130 return -1; 131 } 132 133 static double 134 str2float( const char* p, const char* end ) 135 { 136 int result = 0; 137 int len = end - p; 138 char temp[16]; 139 140 if (len >= (int)sizeof(temp)) 141 return 0.; 142 143 memcpy( temp, p, len ); 144 temp[len] = 0; 145 return strtod( temp, NULL ); 146 } 147 148 /*****************************************************************/ 149 /*****************************************************************/ 150 /***** *****/ 151 /***** N M E A P A R S E R *****/ 152 /***** *****/ 153 /*****************************************************************/ 154 /*****************************************************************/ 155 156 #define NMEA_MAX_SIZE 83 157 158 typedef struct { 159 int pos; 160 int overflow; 161 int utc_year; 162 int utc_mon; 163 int utc_day; 164 int utc_diff; 165 GpsLocation fix; 166 gps_location_callback callback; 167 char in[ NMEA_MAX_SIZE+1 ]; 168 } NmeaReader; 169 170 171 static void 172 nmea_reader_update_utc_diff( NmeaReader* r ) 173 { 174 time_t now = time(NULL); 175 struct tm tm_local; 176 struct tm tm_utc; 177 long time_local, time_utc; 178 179 gmtime_r( &now, &tm_utc ); 180 localtime_r( &now, &tm_local ); 181 182 time_local = tm_local.tm_sec + 183 60*(tm_local.tm_min + 184 60*(tm_local.tm_hour + 185 24*(tm_local.tm_yday + 186 365*tm_local.tm_year))); 187 188 time_utc = tm_utc.tm_sec + 189 60*(tm_utc.tm_min + 190 60*(tm_utc.tm_hour + 191 24*(tm_utc.tm_yday + 192 365*tm_utc.tm_year))); 193 194 r->utc_diff = time_utc - time_local; 195 } 196 197 198 static void 199 nmea_reader_init( NmeaReader* r ) 200 { 201 memset( r, 0, sizeof(*r) ); 202 203 r->pos = 0; 204 r->overflow = 0; 205 r->utc_year = -1; 206 r->utc_mon = -1; 207 r->utc_day = -1; 208 r->callback = NULL; 209 210 nmea_reader_update_utc_diff( r ); 211 } 212 213 214 static void 215 nmea_reader_set_callback( NmeaReader* r, gps_location_callback cb ) 216 { 217 r->callback = cb; 218 if (cb != NULL && r->fix.flags != 0) { 219 D("%s: sending latest fix to new callback", __FUNCTION__); 220 r->callback( &r->fix ); 221 r->fix.flags = 0; 222 } 223 } 224 225 226 static int 227 nmea_reader_update_time( NmeaReader* r, Token tok ) 228 { 229 int hour, minute; 230 double seconds; 231 struct tm tm; 232 time_t fix_time; 233 234 if (tok.p + 6 > tok.end) 235 return -1; 236 237 if (r->utc_year < 0) { 238 // no date yet, get current one 239 time_t now = time(NULL); 240 gmtime_r( &now, &tm ); 241 r->utc_year = tm.tm_year + 1900; 242 r->utc_mon = tm.tm_mon + 1; 243 r->utc_day = tm.tm_mday; 244 } 245 246 hour = str2int(tok.p, tok.p+2); 247 minute = str2int(tok.p+2, tok.p+4); 248 seconds = str2float(tok.p+4, tok.end); 249 250 tm.tm_hour = hour; 251 tm.tm_min = minute; 252 tm.tm_sec = (int) seconds; 253 tm.tm_year = r->utc_year - 1900; 254 tm.tm_mon = r->utc_mon - 1; 255 tm.tm_mday = r->utc_day; 256 tm.tm_isdst = -1; 257 258 fix_time = mktime( &tm ) + r->utc_diff; 259 r->fix.timestamp = (long long)fix_time * 1000; 260 return 0; 261 } 262 263 static int 264 nmea_reader_update_date( NmeaReader* r, Token date, Token time ) 265 { 266 Token tok = date; 267 int day, mon, year; 268 269 if (tok.p + 6 != tok.end) { 270 D("date not properly formatted: '%.*s'", tok.end-tok.p, tok.p); 271 return -1; 272 } 273 day = str2int(tok.p, tok.p+2); 274 mon = str2int(tok.p+2, tok.p+4); 275 year = str2int(tok.p+4, tok.p+6) + 2000; 276 277 if ((day|mon|year) < 0) { 278 D("date not properly formatted: '%.*s'", tok.end-tok.p, tok.p); 279 return -1; 280 } 281 282 r->utc_year = year; 283 r->utc_mon = mon; 284 r->utc_day = day; 285 286 return nmea_reader_update_time( r, time ); 287 } 288 289 290 static double 291 convert_from_hhmm( Token tok ) 292 { 293 double val = str2float(tok.p, tok.end); 294 int degrees = (int)(floor(val) / 100); 295 double minutes = val - degrees*100.; 296 double dcoord = degrees + minutes / 60.0; 297 return dcoord; 298 } 299 300 301 static int 302 nmea_reader_update_latlong( NmeaReader* r, 303 Token latitude, 304 char latitudeHemi, 305 Token longitude, 306 char longitudeHemi ) 307 { 308 double lat, lon; 309 Token tok; 310 311 tok = latitude; 312 if (tok.p + 6 > tok.end) { 313 D("latitude is too short: '%.*s'", tok.end-tok.p, tok.p); 314 return -1; 315 } 316 lat = convert_from_hhmm(tok); 317 if (latitudeHemi == 'S') 318 lat = -lat; 319 320 tok = longitude; 321 if (tok.p + 6 > tok.end) { 322 D("longitude is too short: '%.*s'", tok.end-tok.p, tok.p); 323 return -1; 324 } 325 lon = convert_from_hhmm(tok); 326 if (longitudeHemi == 'W') 327 lon = -lon; 328 329 r->fix.flags |= GPS_LOCATION_HAS_LAT_LONG; 330 r->fix.latitude = lat; 331 r->fix.longitude = lon; 332 return 0; 333 } 334 335 336 static int 337 nmea_reader_update_altitude( NmeaReader* r, 338 Token altitude, 339 Token units ) 340 { 341 double alt; 342 Token tok = altitude; 343 344 if (tok.p >= tok.end) 345 return -1; 346 347 r->fix.flags |= GPS_LOCATION_HAS_ALTITUDE; 348 r->fix.altitude = str2float(tok.p, tok.end); 349 return 0; 350 } 351 352 353 static int 354 nmea_reader_update_bearing( NmeaReader* r, 355 Token bearing ) 356 { 357 double alt; 358 Token tok = bearing; 359 360 if (tok.p >= tok.end) 361 return -1; 362 363 r->fix.flags |= GPS_LOCATION_HAS_BEARING; 364 r->fix.bearing = str2float(tok.p, tok.end); 365 return 0; 366 } 367 368 369 static int 370 nmea_reader_update_speed( NmeaReader* r, 371 Token speed ) 372 { 373 double alt; 374 Token tok = speed; 375 376 if (tok.p >= tok.end) 377 return -1; 378 379 r->fix.flags |= GPS_LOCATION_HAS_SPEED; 380 r->fix.speed = str2float(tok.p, tok.end); 381 return 0; 382 } 383 384 385 static void 386 nmea_reader_parse( NmeaReader* r ) 387 { 388 /* we received a complete sentence, now parse it to generate 389 * a new GPS fix... 390 */ 391 NmeaTokenizer tzer[1]; 392 Token tok; 393 394 D("Received: '%.*s'", r->pos, r->in); 395 if (r->pos < 9) { 396 D("Too short. discarded."); 397 return; 398 } 399 400 nmea_tokenizer_init(tzer, r->in, r->in + r->pos); 401 #if GPS_DEBUG 402 { 403 int n; 404 D("Found %d tokens", tzer->count); 405 for (n = 0; n < tzer->count; n++) { 406 Token tok = nmea_tokenizer_get(tzer,n); 407 D("%2d: '%.*s'", n, tok.end-tok.p, tok.p); 408 } 409 } 410 #endif 411 412 tok = nmea_tokenizer_get(tzer, 0); 413 if (tok.p + 5 > tok.end) { 414 D("sentence id '%.*s' too short, ignored.", tok.end-tok.p, tok.p); 415 return; 416 } 417 418 // ignore first two characters. 419 tok.p += 2; 420 if ( !memcmp(tok.p, "GGA", 3) ) { 421 // GPS fix 422 Token tok_time = nmea_tokenizer_get(tzer,1); 423 Token tok_latitude = nmea_tokenizer_get(tzer,2); 424 Token tok_latitudeHemi = nmea_tokenizer_get(tzer,3); 425 Token tok_longitude = nmea_tokenizer_get(tzer,4); 426 Token tok_longitudeHemi = nmea_tokenizer_get(tzer,5); 427 Token tok_altitude = nmea_tokenizer_get(tzer,9); 428 Token tok_altitudeUnits = nmea_tokenizer_get(tzer,10); 429 430 nmea_reader_update_time(r, tok_time); 431 nmea_reader_update_latlong(r, tok_latitude, 432 tok_latitudeHemi.p[0], 433 tok_longitude, 434 tok_longitudeHemi.p[0]); 435 nmea_reader_update_altitude(r, tok_altitude, tok_altitudeUnits); 436 437 } else if ( !memcmp(tok.p, "GSA", 3) ) { 438 // do something ? 439 } else if ( !memcmp(tok.p, "RMC", 3) ) { 440 Token tok_time = nmea_tokenizer_get(tzer,1); 441 Token tok_fixStatus = nmea_tokenizer_get(tzer,2); 442 Token tok_latitude = nmea_tokenizer_get(tzer,3); 443 Token tok_latitudeHemi = nmea_tokenizer_get(tzer,4); 444 Token tok_longitude = nmea_tokenizer_get(tzer,5); 445 Token tok_longitudeHemi = nmea_tokenizer_get(tzer,6); 446 Token tok_speed = nmea_tokenizer_get(tzer,7); 447 Token tok_bearing = nmea_tokenizer_get(tzer,8); 448 Token tok_date = nmea_tokenizer_get(tzer,9); 449 450 D("in RMC, fixStatus=%c", tok_fixStatus.p[0]); 451 if (tok_fixStatus.p[0] == 'A') 452 { 453 nmea_reader_update_date( r, tok_date, tok_time ); 454 455 nmea_reader_update_latlong( r, tok_latitude, 456 tok_latitudeHemi.p[0], 457 tok_longitude, 458 tok_longitudeHemi.p[0] ); 459 460 nmea_reader_update_bearing( r, tok_bearing ); 461 nmea_reader_update_speed ( r, tok_speed ); 462 } 463 } else { 464 tok.p -= 2; 465 D("unknown sentence '%.*s", tok.end-tok.p, tok.p); 466 } 467 if (r->fix.flags != 0) { 468 #if GPS_DEBUG 469 char temp[256]; 470 char* p = temp; 471 char* end = p + sizeof(temp); 472 struct tm utc; 473 474 p += snprintf( p, end-p, "sending fix" ); 475 if (r->fix.flags & GPS_LOCATION_HAS_LAT_LONG) { 476 p += snprintf(p, end-p, " lat=%g lon=%g", r->fix.latitude, r->fix.longitude); 477 } 478 if (r->fix.flags & GPS_LOCATION_HAS_ALTITUDE) { 479 p += snprintf(p, end-p, " altitude=%g", r->fix.altitude); 480 } 481 if (r->fix.flags & GPS_LOCATION_HAS_SPEED) { 482 p += snprintf(p, end-p, " speed=%g", r->fix.speed); 483 } 484 if (r->fix.flags & GPS_LOCATION_HAS_BEARING) { 485 p += snprintf(p, end-p, " bearing=%g", r->fix.bearing); 486 } 487 if (r->fix.flags & GPS_LOCATION_HAS_ACCURACY) { 488 p += snprintf(p,end-p, " accuracy=%g", r->fix.accuracy); 489 } 490 gmtime_r( (time_t*) &r->fix.timestamp, &utc ); 491 p += snprintf(p, end-p, " time=%s", asctime( &utc ) ); 492 D(temp); 493 #endif 494 if (r->callback) { 495 r->callback( &r->fix ); 496 r->fix.flags = 0; 497 } 498 else { 499 D("no callback, keeping data until needed !"); 500 } 501 } 502 } 503 504 505 static void 506 nmea_reader_addc( NmeaReader* r, int c ) 507 { 508 if (r->overflow) { 509 r->overflow = (c != '\n'); 510 return; 511 } 512 513 if (r->pos >= (int) sizeof(r->in)-1 ) { 514 r->overflow = 1; 515 r->pos = 0; 516 return; 517 } 518 519 r->in[r->pos] = (char)c; 520 r->pos += 1; 521 522 if (c == '\n') { 523 nmea_reader_parse( r ); 524 r->pos = 0; 525 } 526 } 527 528 529 /*****************************************************************/ 530 /*****************************************************************/ 531 /***** *****/ 532 /***** C O N N E C T I O N S T A T E *****/ 533 /***** *****/ 534 /*****************************************************************/ 535 /*****************************************************************/ 536 537 /* commands sent to the gps thread */ 538 enum { 539 CMD_QUIT = 0, 540 CMD_START = 1, 541 CMD_STOP = 2 542 }; 543 544 545 /* this is the state of our connection to the qemu_gpsd daemon */ 546 typedef struct { 547 int init; 548 int fd; 549 GpsCallbacks callbacks; 550 pthread_t thread; 551 int control[2]; 552 QemuChannel channel; 553 } GpsState; 554 555 static GpsState _gps_state[1]; 556 557 558 static void 559 gps_state_done( GpsState* s ) 560 { 561 // tell the thread to quit, and wait for it 562 char cmd = CMD_QUIT; 563 void* dummy; 564 write( s->control[0], &cmd, 1 ); 565 pthread_join(s->thread, &dummy); 566 567 // close the control socket pair 568 close( s->control[0] ); s->control[0] = -1; 569 close( s->control[1] ); s->control[1] = -1; 570 571 // close connection to the QEMU GPS daemon 572 close( s->fd ); s->fd = -1; 573 s->init = 0; 574 } 575 576 577 static void 578 gps_state_start( GpsState* s ) 579 { 580 char cmd = CMD_START; 581 int ret; 582 583 do { ret=write( s->control[0], &cmd, 1 ); } 584 while (ret < 0 && errno == EINTR); 585 586 if (ret != 1) 587 D("%s: could not send CMD_START command: ret=%d: %s", 588 __FUNCTION__, ret, strerror(errno)); 589 } 590 591 592 static void 593 gps_state_stop( GpsState* s ) 594 { 595 char cmd = CMD_STOP; 596 int ret; 597 598 do { ret=write( s->control[0], &cmd, 1 ); } 599 while (ret < 0 && errno == EINTR); 600 601 if (ret != 1) 602 D("%s: could not send CMD_STOP command: ret=%d: %s", 603 __FUNCTION__, ret, strerror(errno)); 604 } 605 606 607 static int 608 epoll_register( int epoll_fd, int fd ) 609 { 610 struct epoll_event ev; 611 int ret, flags; 612 613 /* important: make the fd non-blocking */ 614 flags = fcntl(fd, F_GETFL); 615 fcntl(fd, F_SETFL, flags | O_NONBLOCK); 616 617 ev.events = EPOLLIN; 618 ev.data.fd = fd; 619 do { 620 ret = epoll_ctl( epoll_fd, EPOLL_CTL_ADD, fd, &ev ); 621 } while (ret < 0 && errno == EINTR); 622 return ret; 623 } 624 625 626 static int 627 epoll_deregister( int epoll_fd, int fd ) 628 { 629 int ret; 630 do { 631 ret = epoll_ctl( epoll_fd, EPOLL_CTL_DEL, fd, NULL ); 632 } while (ret < 0 && errno == EINTR); 633 return ret; 634 } 635 636 /* this is the main thread, it waits for commands from gps_state_start/stop and, 637 * when started, messages from the QEMU GPS daemon. these are simple NMEA sentences 638 * that must be parsed to be converted into GPS fixes sent to the framework 639 */ 640 static void* 641 gps_state_thread( void* arg ) 642 { 643 GpsState* state = (GpsState*) arg; 644 NmeaReader reader[1]; 645 int epoll_fd = epoll_create(2); 646 int started = 0; 647 int gps_fd = state->fd; 648 int control_fd = state->control[1]; 649 650 nmea_reader_init( reader ); 651 652 // register control file descriptors for polling 653 epoll_register( epoll_fd, control_fd ); 654 epoll_register( epoll_fd, gps_fd ); 655 656 D("gps thread running"); 657 658 // now loop 659 for (;;) { 660 struct epoll_event events[2]; 661 int ne, nevents; 662 663 nevents = epoll_wait( epoll_fd, events, 2, -1 ); 664 if (nevents < 0) { 665 if (errno != EINTR) 666 LOGE("epoll_wait() unexpected error: %s", strerror(errno)); 667 continue; 668 } 669 D("gps thread received %d events", nevents); 670 for (ne = 0; ne < nevents; ne++) { 671 if ((events[ne].events & (EPOLLERR|EPOLLHUP)) != 0) { 672 LOGE("EPOLLERR or EPOLLHUP after epoll_wait() !?"); 673 goto Exit; 674 } 675 if ((events[ne].events & EPOLLIN) != 0) { 676 int fd = events[ne].data.fd; 677 678 if (fd == control_fd) 679 { 680 char cmd = 255; 681 int ret; 682 D("gps control fd event"); 683 do { 684 ret = read( fd, &cmd, 1 ); 685 } while (ret < 0 && errno == EINTR); 686 687 if (cmd == CMD_QUIT) { 688 D("gps thread quitting on demand"); 689 goto Exit; 690 } 691 else if (cmd == CMD_START) { 692 if (!started) { 693 D("gps thread starting location_cb=%p", state->callbacks.location_cb); 694 started = 1; 695 nmea_reader_set_callback( reader, state->callbacks.location_cb ); 696 } 697 } 698 else if (cmd == CMD_STOP) { 699 if (started) { 700 D("gps thread stopping"); 701 started = 0; 702 nmea_reader_set_callback( reader, NULL ); 703 } 704 } 705 } 706 else if (fd == gps_fd) 707 { 708 char buff[32]; 709 D("gps fd event"); 710 for (;;) { 711 int nn, ret; 712 713 ret = read( fd, buff, sizeof(buff) ); 714 if (ret < 0) { 715 if (errno == EINTR) 716 continue; 717 if (errno != EWOULDBLOCK) 718 LOGE("error while reading from gps daemon socket: %s:", strerror(errno)); 719 break; 720 } 721 D("received %d bytes: %.*s", ret, ret, buff); 722 for (nn = 0; nn < ret; nn++) 723 nmea_reader_addc( reader, buff[nn] ); 724 } 725 D("gps fd event end"); 726 } 727 else 728 { 729 LOGE("epoll_wait() returned unkown fd %d ?", fd); 730 } 731 } 732 } 733 } 734 Exit: 735 return NULL; 736 } 737 738 739 static void 740 gps_state_init( GpsState* state ) 741 { 742 state->init = 1; 743 state->control[0] = -1; 744 state->control[1] = -1; 745 state->fd = -1; 746 747 state->fd = qemu_channel_open( &state->channel, 748 QEMU_CHANNEL_NAME, 749 O_RDONLY ); 750 751 if (state->fd < 0) { 752 D("no gps emulation detected"); 753 return; 754 } 755 756 D("gps emulation will read from '%s' qemud channel", QEMU_CHANNEL_NAME ); 757 758 if ( socketpair( AF_LOCAL, SOCK_STREAM, 0, state->control ) < 0 ) { 759 LOGE("could not create thread control socket pair: %s", strerror(errno)); 760 goto Fail; 761 } 762 763 if ( pthread_create( &state->thread, NULL, gps_state_thread, state ) != 0 ) { 764 LOGE("could not create gps thread: %s", strerror(errno)); 765 goto Fail; 766 } 767 768 D("gps state initialized"); 769 return; 770 771 Fail: 772 gps_state_done( state ); 773 } 774 775 776 /*****************************************************************/ 777 /*****************************************************************/ 778 /***** *****/ 779 /***** I N T E R F A C E *****/ 780 /***** *****/ 781 /*****************************************************************/ 782 /*****************************************************************/ 783 784 785 static int 786 qemu_gps_init(GpsCallbacks* callbacks) 787 { 788 GpsState* s = _gps_state; 789 790 if (!s->init) 791 gps_state_init(s); 792 793 if (s->fd < 0) 794 return -1; 795 796 s->callbacks = *callbacks; 797 798 return 0; 799 } 800 801 static void 802 qemu_gps_cleanup(void) 803 { 804 GpsState* s = _gps_state; 805 806 if (s->init) 807 gps_state_done(s); 808 } 809 810 811 static int 812 qemu_gps_start() 813 { 814 GpsState* s = _gps_state; 815 816 if (!s->init) { 817 D("%s: called with uninitialized state !!", __FUNCTION__); 818 return -1; 819 } 820 821 D("%s: called", __FUNCTION__); 822 gps_state_start(s); 823 return 0; 824 } 825 826 827 static int 828 qemu_gps_stop() 829 { 830 GpsState* s = _gps_state; 831 832 if (!s->init) { 833 D("%s: called with uninitialized state !!", __FUNCTION__); 834 return -1; 835 } 836 837 D("%s: called", __FUNCTION__); 838 gps_state_stop(s); 839 return 0; 840 } 841 842 843 static int 844 qemu_gps_inject_time(GpsUtcTime time, int64_t timeReference, int uncertainty) 845 { 846 return 0; 847 } 848 849 static int 850 qemu_gps_inject_location(double latitude, double longitude, float accuracy) 851 { 852 return 0; 853 } 854 855 static void 856 qemu_gps_delete_aiding_data(GpsAidingData flags) 857 { 858 } 859 860 static int qemu_gps_set_position_mode(GpsPositionMode mode, int fix_frequency) 861 { 862 // FIXME - support fix_frequency 863 return 0; 864 } 865 866 static const void* 867 qemu_gps_get_extension(const char* name) 868 { 869 return NULL; 870 } 871 872 static const GpsInterface qemuGpsInterface = { 873 qemu_gps_init, 874 qemu_gps_start, 875 qemu_gps_stop, 876 qemu_gps_cleanup, 877 qemu_gps_inject_time, 878 qemu_gps_inject_location, 879 qemu_gps_delete_aiding_data, 880 qemu_gps_set_position_mode, 881 qemu_gps_get_extension, 882 }; 883 884 const GpsInterface* gps_get_qemu_interface() 885 { 886 return &qemuGpsInterface; 887 } 888 889