qmi-nmea.c (21286B)
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* 3 * qmi-nmea -- enable location services and print NMEA strings 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 * This program started life as qmicli, which is 19 * 20 * Copyright (C) 2012-2023 Aleksander Morgado <aleksander@aleksander.es> 21 * Copyright (c) 2022 Qualcomm Innovation Center, Inc. 22 * 23 * Changes Copyright (C) 2024 Daniel Barlow 24 */ 25 26 #include "config.h" 27 28 #include <stdio.h> 29 #include <stdlib.h> 30 #include <locale.h> 31 #include <string.h> 32 33 #include <glib.h> 34 #include <glib/gprintf.h> 35 #include <gio/gio.h> 36 #include <glib-unix.h> 37 38 #include <libqmi-glib.h> 39 40 #if QMI_MBIM_QMUX_SUPPORTED 41 #include <libmbim-glib.h> 42 #endif 43 44 #include "qmicli.h" 45 46 #define PROGRAM_NAME "qmi-nmea" 47 #define PROGRAM_VERSION PACKAGE_VERSION 48 49 /* Globals */ 50 static GMainLoop *loop; 51 static GCancellable *cancellable; 52 static QmiDevice *device; 53 static QmiClient *client; 54 static QmiService service; 55 static gboolean operation_status; 56 static gboolean expect_indications; 57 #if QMI_QRTR_SUPPORTED 58 static QrtrBus *qrtr_bus; 59 #endif 60 61 /* Main options */ 62 static gchar *device_str; 63 static gboolean get_service_version_info_flag; 64 static gchar *device_set_instance_id_str; 65 static gboolean device_open_version_info_flag; 66 static gboolean device_open_sync_flag; 67 static gchar *device_open_net_str; 68 #if QMI_MBIM_QMUX_SUPPORTED 69 static gboolean device_open_qmi_flag; 70 static gboolean device_open_mbim_flag; 71 static gboolean device_open_auto_flag; 72 #endif 73 static gchar *client_cid_str; 74 static gboolean client_no_release_cid_flag; 75 static gboolean verbose_flag; 76 static gboolean verbose_full_flag; 77 static gboolean silent_flag; 78 static gboolean version_flag; 79 80 static GOptionEntry main_entries[] = { 81 { "device", 'd', 0, G_OPTION_ARG_STRING, &device_str, 82 #if QMI_QRTR_SUPPORTED 83 "Specify device path or QRTR URI (e.g. qrtr://0)", 84 "[PATH|URI]" 85 #else 86 "Specify device path", 87 "[PATH]" 88 #endif 89 }, 90 #if QMI_MBIM_QMUX_SUPPORTED 91 { "device-open-qmi", 0, 0, G_OPTION_ARG_NONE, &device_open_qmi_flag, 92 "Open a cdc-wdm device explicitly in QMI mode", 93 NULL 94 }, 95 { "device-open-mbim", 0, 0, G_OPTION_ARG_NONE, &device_open_mbim_flag, 96 "Open a cdc-wdm device explicitly in MBIM mode", 97 NULL 98 }, 99 { "device-open-auto", 0, 0, G_OPTION_ARG_NONE, &device_open_auto_flag, 100 "Open a cdc-wdm device in either QMI or MBIM mode (default)", 101 NULL 102 }, 103 #endif 104 { "client-cid", 0, 0, G_OPTION_ARG_STRING, &client_cid_str, 105 "Use the given CID, don't allocate a new one", 106 "[CID]" 107 }, 108 { "client-no-release-cid", 0, 0, G_OPTION_ARG_NONE, &client_no_release_cid_flag, 109 "Do not release the CID when exiting", 110 NULL 111 }, 112 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose_flag, 113 "Run action with verbose logs, including the debug ones", 114 NULL 115 }, 116 { "verbose-full", 0, 0, G_OPTION_ARG_NONE, &verbose_full_flag, 117 "Run action with verbose logs, including the debug ones and personal info", 118 NULL 119 }, 120 { NULL, 0, 0, 0, NULL, NULL, NULL } 121 }; 122 123 static gboolean 124 signals_handler (void) 125 { 126 if (cancellable) { 127 /* Ignore consecutive requests of cancellation */ 128 if (!g_cancellable_is_cancelled (cancellable)) { 129 g_printerr ("cancelling the operation...\n"); 130 g_cancellable_cancel (cancellable); 131 /* Re-set the signal handler to allow main loop cancellation on 132 * second signal */ 133 return G_SOURCE_CONTINUE; 134 } 135 } 136 137 if (loop && g_main_loop_is_running (loop)) { 138 g_printerr ("cancelling the main loop...\n"); 139 g_idle_add ((GSourceFunc) g_main_loop_quit, loop); 140 } 141 return G_SOURCE_REMOVE; 142 } 143 144 static void 145 log_handler (const gchar *log_domain, 146 GLogLevelFlags log_level, 147 const gchar *message, 148 gpointer user_data) 149 { 150 const gchar *log_level_str; 151 time_t now; 152 gchar time_str[64]; 153 struct tm *local_time; 154 gboolean err; 155 156 /* Nothing to do if we're silent */ 157 if (silent_flag) 158 return; 159 160 now = time ((time_t *) NULL); 161 local_time = localtime (&now); 162 strftime (time_str, 64, "%d %b %Y, %H:%M:%S", local_time); 163 err = FALSE; 164 165 switch (log_level) { 166 case G_LOG_LEVEL_WARNING: 167 log_level_str = "-Warning **"; 168 err = TRUE; 169 break; 170 171 case G_LOG_LEVEL_CRITICAL: 172 case G_LOG_LEVEL_ERROR: 173 log_level_str = "-Error **"; 174 err = TRUE; 175 break; 176 177 case G_LOG_LEVEL_DEBUG: 178 log_level_str = "[Debug]"; 179 break; 180 181 case G_LOG_LEVEL_MESSAGE: 182 case G_LOG_LEVEL_INFO: 183 log_level_str = ""; 184 break; 185 186 case G_LOG_FLAG_FATAL: 187 case G_LOG_LEVEL_MASK: 188 case G_LOG_FLAG_RECURSION: 189 default: 190 g_assert_not_reached (); 191 } 192 193 if (!verbose_flag && !verbose_full_flag && !err) 194 return; 195 196 g_fprintf (err ? stderr : stdout, 197 "[%s] %s %s\n", 198 time_str, 199 log_level_str, 200 message); 201 } 202 203 G_GNUC_NORETURN 204 static void 205 print_version_and_exit (void) 206 { 207 g_print ("qmi-nmea\n\n" 208 "Copyright (C) 2012-2023 Aleksander Morgado, 2024 Daniel Barlow\n" 209 "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl-2.0.html>\n" 210 "This is free software: you are free to change and redistribute it.\n" 211 "There is NO WARRANTY, to the extent permitted by law.\n" 212 "\n"); 213 exit (EXIT_SUCCESS); 214 } 215 216 static gboolean 217 generic_options_enabled (void) 218 { 219 static guint n_actions = 0; 220 static gboolean checked = FALSE; 221 222 if (checked) 223 return !!n_actions; 224 225 n_actions = (!!device_set_instance_id_str + 226 get_service_version_info_flag); 227 228 if (n_actions > 1) { 229 g_printerr ("error: too many generic actions requested\n"); 230 exit (EXIT_FAILURE); 231 } 232 233 checked = TRUE; 234 return !!n_actions; 235 } 236 237 /*****************************************************************************/ 238 /* Report that indications are expected */ 239 240 void 241 qmicli_expect_indications (void) 242 { 243 expect_indications = TRUE; 244 } 245 246 /*****************************************************************************/ 247 /* Running asynchronously */ 248 249 static void 250 close_ready (QmiDevice *dev, 251 GAsyncResult *res) 252 { 253 GError *error = NULL; 254 255 if (!qmi_device_close_finish (dev, res, &error)) { 256 g_printerr ("error: couldn't close: %s\n", error->message); 257 g_error_free (error); 258 } else 259 g_debug ("Closed"); 260 261 g_main_loop_quit (loop); 262 } 263 264 static void 265 release_client_ready (QmiDevice *dev, 266 GAsyncResult *res) 267 { 268 GError *error = NULL; 269 270 if (!qmi_device_release_client_finish (dev, res, &error)) { 271 g_printerr ("error: couldn't release client: %s\n", error->message); 272 g_error_free (error); 273 } else 274 g_debug ("Client released"); 275 276 qmi_device_close_async (dev, 10, NULL, (GAsyncReadyCallback) close_ready, NULL); 277 } 278 279 static guint8 saved_cid = QMI_CID_NONE; 280 281 void 282 qmicli_async_operation_done (gboolean reported_operation_status, 283 gboolean skip_cid_release) 284 { 285 QmiDeviceReleaseClientFlags flags = QMI_DEVICE_RELEASE_CLIENT_FLAGS_NONE; 286 287 /* Keep the result of the operation */ 288 operation_status = reported_operation_status; 289 290 /* Cleanup cancellation */ 291 g_clear_object (&cancellable); 292 293 /* If no client was allocated (e.g. generic action), just quit */ 294 if (!client) { 295 g_main_loop_quit (loop); 296 return; 297 } 298 299 if (skip_cid_release) { 300 g_debug ("Skipped CID release"); 301 saved_cid = qmi_client_get_cid (client); 302 } 303 else if (!client_no_release_cid_flag) 304 flags |= QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID; 305 else 306 g_print ("[%s] Client ID not released:\n" 307 "\tService: '%s'\n" 308 "\t CID: '%u'\n", 309 qmi_device_get_path_display (device), 310 qmi_service_get_string (service), 311 qmi_client_get_cid (client)); 312 313 qmi_device_release_client (device, 314 client, 315 flags, 316 10, 317 NULL, 318 (GAsyncReadyCallback)release_client_ready, 319 NULL); 320 } 321 322 static void 323 allocate_client_ready (QmiDevice *dev, 324 GAsyncResult *res) 325 { 326 GError *error = NULL; 327 328 client = qmi_device_allocate_client_finish (dev, res, &error); 329 if (!client) { 330 g_printerr ("error: couldn't create client for the '%s' service: %s\n", 331 qmi_service_get_string (service), 332 error->message); 333 exit (EXIT_FAILURE); 334 } 335 336 /* Run the service-specific action */ 337 switch (service) { 338 case QMI_SERVICE_LOC: 339 #if defined HAVE_QMI_SERVICE_LOC 340 qmicli_loc_run (dev, QMI_CLIENT_LOC (client), cancellable); 341 return; 342 #else 343 break; 344 #endif 345 default: 346 break; 347 } 348 g_assert_not_reached (); 349 } 350 351 static void 352 device_allocate_client (QmiDevice *dev) 353 { 354 guint8 cid = QMI_CID_NONE; 355 356 if (client_cid_str) { 357 guint32 cid32; 358 359 cid32 = atoi (client_cid_str); 360 if (!cid32 || cid32 > G_MAXUINT8) { 361 g_printerr ("error: invalid CID given '%s'\n", 362 client_cid_str); 363 exit (EXIT_FAILURE); 364 } 365 366 cid = (guint8)cid32; 367 g_debug ("Reusing CID '%u'", cid); 368 } 369 if (saved_cid != QMI_CID_NONE) { 370 cid = saved_cid; 371 g_debug ("Using saved CID '%u'", cid); 372 } 373 374 /* As soon as we get the QmiDevice, create a client for the requested 375 * service */ 376 qmi_device_allocate_client (dev, 377 service, 378 cid, 379 10, 380 cancellable, 381 (GAsyncReadyCallback)allocate_client_ready, 382 NULL); 383 } 384 385 static void 386 set_instance_id_ready (QmiDevice *dev, 387 GAsyncResult *res) 388 { 389 GError *error = NULL; 390 guint16 link_id; 391 392 if (!qmi_device_set_instance_id_finish (dev, res, &link_id, &error)) { 393 g_printerr ("error: couldn't set instance ID: %s\n", 394 error->message); 395 exit (EXIT_FAILURE); 396 } 397 398 g_print ("[%s] Instance ID set:\n" 399 "\tLink ID: '%" G_GUINT16_FORMAT "'\n", 400 qmi_device_get_path_display (dev), 401 link_id); 402 403 /* We're done now */ 404 qmicli_async_operation_done (TRUE, FALSE); 405 } 406 407 static void 408 device_set_instance_id (QmiDevice *dev) 409 { 410 gint instance_id; 411 412 if (g_str_equal (device_set_instance_id_str, "0")) 413 instance_id = 0; 414 else { 415 instance_id = atoi (device_set_instance_id_str); 416 if (instance_id == 0) { 417 g_printerr ("error: invalid instance ID given: '%s'\n", device_set_instance_id_str); 418 exit (EXIT_FAILURE); 419 } else if (instance_id < 0 || instance_id > G_MAXUINT8) { 420 g_printerr ("error: given instance ID is out of range [0,%u]: '%s'\n", 421 G_MAXUINT8, 422 device_set_instance_id_str); 423 exit (EXIT_FAILURE); 424 } 425 } 426 427 g_debug ("Setting instance ID '%d'...", instance_id); 428 qmi_device_set_instance_id (dev, 429 (guint8)instance_id, 430 10, 431 cancellable, 432 (GAsyncReadyCallback)set_instance_id_ready, 433 NULL); 434 } 435 436 static void 437 get_service_version_info_ready (QmiDevice *dev, 438 GAsyncResult *res) 439 { 440 GError *error = NULL; 441 GArray *services; 442 guint i; 443 444 services = qmi_device_get_service_version_info_finish (dev, res, &error); 445 if (!services) { 446 g_printerr ("error: couldn't get service version info: %s\n", 447 error->message); 448 exit (EXIT_FAILURE); 449 } 450 451 g_print ("[%s] Supported versions:\n", 452 qmi_device_get_path_display (dev)); 453 for (i = 0; i < services->len; i++) { 454 QmiDeviceServiceVersionInfo *info; 455 const gchar *service_str; 456 457 info = &g_array_index (services, QmiDeviceServiceVersionInfo, i); 458 service_str = qmi_service_get_string (info->service); 459 if (service_str) 460 g_print ("\t%s (%u.%u)\n", 461 service_str, 462 info->major_version, 463 info->minor_version); 464 else 465 g_print ("\tunknown [0x%02x] (%u.%u)\n", 466 info->service, 467 info->major_version, 468 info->minor_version); 469 } 470 g_array_unref (services); 471 472 /* We're done now */ 473 qmicli_async_operation_done (TRUE, FALSE); 474 } 475 476 static void 477 device_get_service_version_info (QmiDevice *dev) 478 { 479 g_debug ("Getting service version info..."); 480 qmi_device_get_service_version_info (dev, 481 10, 482 cancellable, 483 (GAsyncReadyCallback)get_service_version_info_ready, 484 NULL); 485 } 486 487 static void 488 device_open_ready (QmiDevice *dev, 489 GAsyncResult *res) 490 { 491 GError *error = NULL; 492 493 if (!qmi_device_open_finish (dev, res, &error)) { 494 g_printerr ("error: couldn't open the QmiDevice: %s\n", 495 error->message); 496 exit (EXIT_FAILURE); 497 } 498 499 g_debug ("QMI Device at '%s' ready", 500 qmi_device_get_path_display (dev)); 501 502 if (device_set_instance_id_str) 503 device_set_instance_id (dev); 504 else if (get_service_version_info_flag) 505 device_get_service_version_info (dev); 506 else 507 device_allocate_client (dev); 508 } 509 510 static void 511 device_new_ready (GObject *unused, 512 GAsyncResult *res) 513 { 514 QmiDeviceOpenFlags open_flags = QMI_DEVICE_OPEN_FLAGS_NONE; 515 GError *error = NULL; 516 517 device = qmi_device_new_finish (res, &error); 518 if (!device) { 519 g_printerr ("error: couldn't create QmiDevice: %s\n", 520 error->message); 521 exit (EXIT_FAILURE); 522 } 523 524 #if QMI_MBIM_QMUX_SUPPORTED 525 if (device_open_mbim_flag + device_open_qmi_flag + device_open_auto_flag > 1) { 526 g_printerr ("error: cannot specify multiple mode flags to open device\n"); 527 exit (EXIT_FAILURE); 528 } 529 #endif 530 531 /* Setup device open flags */ 532 if (device_open_version_info_flag) 533 open_flags |= QMI_DEVICE_OPEN_FLAGS_VERSION_INFO; 534 if (device_open_sync_flag) 535 open_flags |= QMI_DEVICE_OPEN_FLAGS_SYNC; 536 open_flags |= QMI_DEVICE_OPEN_FLAGS_PROXY; 537 #if QMI_MBIM_QMUX_SUPPORTED 538 if (device_open_mbim_flag) 539 open_flags |= QMI_DEVICE_OPEN_FLAGS_MBIM; 540 if (device_open_auto_flag || (!device_open_qmi_flag && !device_open_mbim_flag)) 541 open_flags |= QMI_DEVICE_OPEN_FLAGS_AUTO; 542 #endif 543 if (expect_indications) 544 open_flags |= QMI_DEVICE_OPEN_FLAGS_EXPECT_INDICATIONS; 545 if (device_open_net_str) { 546 if (!qmicli_read_device_open_flags_from_string (device_open_net_str, &open_flags) || 547 !qmicli_validate_device_open_flags (open_flags)) 548 exit (EXIT_FAILURE); 549 } 550 551 /* Open the device */ 552 qmi_device_open (device, 553 open_flags, 554 15, 555 cancellable, 556 (GAsyncReadyCallback)device_open_ready, 557 NULL); 558 } 559 560 #if QMI_QRTR_SUPPORTED 561 562 static void 563 bus_new_ready (GObject *source, 564 GAsyncResult *res, 565 gpointer user_data) 566 { 567 g_autoptr(GError) error = NULL; 568 guint node_id; 569 QrtrNode *node; 570 571 node_id = GPOINTER_TO_UINT (user_data); 572 573 qrtr_bus = qrtr_bus_new_finish (res, &error); 574 if (!qrtr_bus) { 575 g_printerr ("error: couldn't access QRTR bus: %s\n", error->message); 576 exit (EXIT_FAILURE); 577 } 578 579 node = qrtr_bus_peek_node (qrtr_bus, node_id); 580 if (!node) { 581 g_printerr ("error: node with id %u not found in QRTR bus\n", node_id); 582 exit (EXIT_FAILURE); 583 } 584 585 qmi_device_new_from_node (node, 586 cancellable, 587 (GAsyncReadyCallback)device_new_ready, 588 NULL); 589 } 590 591 #endif 592 593 static gboolean 594 make_device (GFile *file) 595 { 596 g_autofree gchar *id = NULL; 597 598 id = g_file_get_path (file); 599 if (id) { 600 /* Describes a local device file. */ 601 qmi_device_new (file, 602 cancellable, 603 (GAsyncReadyCallback)device_new_ready, 604 NULL); 605 return TRUE; 606 } 607 608 #if QMI_QRTR_SUPPORTED 609 { 610 guint32 node_id; 611 612 id = g_file_get_uri (file); 613 if (qrtr_get_node_for_uri (id, &node_id)) { 614 qrtr_bus_new (1000, /* ms */ 615 cancellable, 616 (GAsyncReadyCallback)bus_new_ready, 617 GUINT_TO_POINTER (node_id)); 618 return TRUE; 619 } 620 621 g_printerr ("error: URI is neither a local file path nor a QRTR node: %s\n", id); 622 return FALSE; 623 } 624 #else 625 g_printerr ("error: URI is not a local file path: %s\n", id); 626 return FALSE; 627 #endif 628 } 629 630 /*****************************************************************************/ 631 632 static void 633 parse_actions (void) 634 { 635 guint actions_enabled = 0; 636 637 qmicli_loc_options_enabled (); 638 service = QMI_SERVICE_LOC; 639 640 /* Go on! */ 641 } 642 643 int main (int argc, char **argv) 644 { 645 GError *error = NULL; 646 GFile *file; 647 GOptionContext *context; 648 649 setlocale (LC_ALL, ""); 650 651 /* Setup option context, process it and destroy it */ 652 context = g_option_context_new ("- Control QMI devices"); 653 #if defined HAVE_QMI_SERVICE_LOC 654 g_option_context_add_group (context, qmicli_loc_get_option_group ()); 655 #endif 656 g_option_context_add_main_entries (context, main_entries, NULL); 657 if (!g_option_context_parse (context, &argc, &argv, &error)) { 658 g_printerr ("error: %s\n", 659 error->message); 660 exit (EXIT_FAILURE); 661 } 662 g_option_context_free (context); 663 664 if (version_flag) 665 print_version_and_exit (); 666 667 g_log_set_handler (NULL, G_LOG_LEVEL_MASK, log_handler, NULL); 668 g_log_set_handler ("Qmi", G_LOG_LEVEL_MASK, log_handler, NULL); 669 670 if (verbose_flag && verbose_full_flag) { 671 g_printerr ("error: cannot specify --verbose and --verbose-full at the same time\n"); 672 exit (EXIT_FAILURE); 673 } else if (verbose_flag) { 674 qmi_utils_set_traces_enabled (TRUE); 675 qmi_utils_set_show_personal_info (FALSE); 676 } else if (verbose_full_flag) { 677 qmi_utils_set_traces_enabled (TRUE); 678 qmi_utils_set_show_personal_info (TRUE); 679 } 680 681 #if QMI_MBIM_QMUX_SUPPORTED 682 /* libmbim logging */ 683 g_log_set_handler ("Mbim", G_LOG_LEVEL_MASK, log_handler, NULL); 684 if (verbose_flag) { 685 mbim_utils_set_traces_enabled (TRUE); 686 #if MBIM_CHECK_VERSION(1,27,6) 687 mbim_utils_set_show_personal_info (FALSE); 688 #endif 689 } else if (verbose_full_flag) { 690 mbim_utils_set_traces_enabled (TRUE); 691 #if MBIM_CHECK_VERSION(1,27,6) 692 mbim_utils_set_show_personal_info (TRUE); 693 #endif 694 } 695 #endif 696 697 #if QMI_QRTR_SUPPORTED 698 /* libqrtr-glib logging */ 699 g_log_set_handler ("Qrtr", G_LOG_LEVEL_MASK, log_handler, NULL); 700 #endif 701 702 /* No device path given? */ 703 if (!device_str) { 704 g_printerr ("error: no device path specified\n"); 705 exit (EXIT_FAILURE); 706 } 707 708 /* Build new GFile from the commandline arg */ 709 file = g_file_new_for_commandline_arg (device_str); 710 711 parse_actions (); 712 713 for(int i=0; i<2; i++){ 714 /* Create requirements for async options */ 715 cancellable = g_cancellable_new (); 716 loop = g_main_loop_new (NULL, FALSE); 717 718 /* Setup signals */ 719 g_unix_signal_add (SIGINT, (GSourceFunc) signals_handler, NULL); 720 g_unix_signal_add (SIGHUP, (GSourceFunc) signals_handler, NULL); 721 g_unix_signal_add (SIGTERM, (GSourceFunc) signals_handler, NULL); 722 723 /* Launch QmiDevice creation */ 724 if (!make_device (file)) 725 return EXIT_FAILURE; 726 727 g_main_loop_run (loop); 728 if (cancellable) 729 g_object_unref (cancellable); 730 if (client) 731 g_object_unref (client); 732 if (device) 733 g_object_unref (device); 734 g_main_loop_unref (loop); 735 } 736 737 g_object_unref (file); 738 739 return (operation_status ? EXIT_SUCCESS : EXIT_FAILURE); 740 }