diff options
author | Aleksander Morgado <aleksandermj@chromium.org> | 2022-12-08 13:37:55 +0000 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2023-01-03 13:56:25 +0000 |
commit | e14b904cbd6816cb0227d519d308ae71ddaf6e07 (patch) | |
tree | 4997ab68cc606fdf4d72a571e821cec0c8df42ef /src/plugins/quectel | |
parent | 072d7ac9065f444e83b390a1e2af5471ac0d48f6 (diff) |
build: move plugins directory to src/plugins
We are going to allow including the plugin sources built within the
ModemManager daemon binary; moving the sources within the daemon
sources directory makes it easier.
Diffstat (limited to 'src/plugins/quectel')
-rw-r--r-- | src/plugins/quectel/77-mm-quectel-port-types.rules | 104 | ||||
-rw-r--r-- | src/plugins/quectel/mm-broadband-modem-mbim-quectel.c | 88 | ||||
-rw-r--r-- | src/plugins/quectel/mm-broadband-modem-mbim-quectel.h | 48 | ||||
-rw-r--r-- | src/plugins/quectel/mm-broadband-modem-qmi-quectel.c | 138 | ||||
-rw-r--r-- | src/plugins/quectel/mm-broadband-modem-qmi-quectel.h | 47 | ||||
-rw-r--r-- | src/plugins/quectel/mm-broadband-modem-quectel.c | 136 | ||||
-rw-r--r-- | src/plugins/quectel/mm-broadband-modem-quectel.h | 47 | ||||
-rw-r--r-- | src/plugins/quectel/mm-modem-helpers-quectel.c | 91 | ||||
-rw-r--r-- | src/plugins/quectel/mm-modem-helpers-quectel.h | 32 | ||||
-rw-r--r-- | src/plugins/quectel/mm-plugin-quectel.c | 117 | ||||
-rw-r--r-- | src/plugins/quectel/mm-plugin-quectel.h | 40 | ||||
-rw-r--r-- | src/plugins/quectel/mm-shared-quectel.c | 1039 | ||||
-rw-r--r-- | src/plugins/quectel/mm-shared-quectel.h | 93 | ||||
-rw-r--r-- | src/plugins/quectel/tests/test-modem-helpers-quectel.c | 93 |
14 files changed, 2113 insertions, 0 deletions
diff --git a/src/plugins/quectel/77-mm-quectel-port-types.rules b/src/plugins/quectel/77-mm-quectel-port-types.rules new file mode 100644 index 00000000..08564161 --- /dev/null +++ b/src/plugins/quectel/77-mm-quectel-port-types.rules @@ -0,0 +1,104 @@ +# do not edit this file, it will be overwritten on update +ACTION!="add|change|move|bind", GOTO="mm_quectel_end" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c7c", GOTO="mm_quectel_usb" +SUBSYSTEMS=="pci", ATTRS{vendor}=="0x1eac", GOTO="mm_quectel_pci" +GOTO="mm_quectel_end" + +LABEL="mm_quectel_usb" + +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +# Quectel EG06 +# ttyUSB0 (if #0): QCDM/DIAG port +# ttyUSB1 (if #1): GPS data port +# ttyUSB2 (if #2): AT primary port +# ttyUSB3 (if #3): AT secondary port +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0306", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0306", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0306", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0306", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Quectel EG91 +# ttyUSB0 (if #0): QCDM/DIAG port +# ttyUSB1 (if #1): GPS data port +# ttyUSB2 (if #2): AT primary port +# ttyUSB3 (if #3): AT secondary port +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0191", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0191", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0191", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0191", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Quectel EG95 +# ttyUSB0 (if #0): QCDM/DIAG port +# ttyUSB1 (if #1): GPS data port +# ttyUSB2 (if #2): AT primary port +# ttyUSB3 (if #3): AT secondary port +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0195", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0195", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0195", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0195", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Quectel BG96 +# ttyUSB0 (if #0): QCDM/DIAG port +# ttyUSB1 (if #1): GPS data port +# ttyUSB2 (if #2): AT primary port +# ttyUSB3 (if #3): AT secondary port +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0296", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0296", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0296", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0296", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Quectel EC25/EG25 +# ttyUSB0 (if #0): QCDM/DIAG port +# ttyUSB1 (if #1): GPS data port +# ttyUSB2 (if #2): AT primary port +# ttyUSB3 (if #3): AT secondary port +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Quectel RM500 +# ttyUSB0 (if #0): QCDM/DIAG port +# ttyUSB1 (if #1): GPS data port +# ttyUSB2 (if #2): AT primary port +# ttyUSB3 (if #3): AT secondary port +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0800", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0800", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0800", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0800", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" + +# Quectel EM05-G variants with Sahara-Firehose support: +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="030a", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="030a", ENV{ID_MM_QUECTEL_SAHARA}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="030c", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="030c", ENV{ID_MM_QUECTEL_SAHARA}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0311", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0311", ENV{ID_MM_QUECTEL_SAHARA}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0313", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0313", ENV{ID_MM_QUECTEL_SAHARA}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0314", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0314", ENV{ID_MM_QUECTEL_SAHARA}="1" + +# Quectel EM05-CN variants with Sahara-Firehose support +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0310", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0310", ENV{ID_MM_QUECTEL_SAHARA}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0312", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0312", ENV{ID_MM_QUECTEL_SAHARA}="1" + +# Quectel EM05-CE with Sahara-Firehose support +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0127", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0127", ENV{ID_MM_QUECTEL_SAHARA}="1" + +GOTO="mm_quectel_end" + +LABEL="mm_quectel_pci" + +# Quectel EM120 and EM160 with firehose support +ATTRS{vendor}=="0x1eac", ATTRS{device}=="0x1001", ENV{ID_MM_QUECTEL_FIREHOSE}="1" +ATTRS{vendor}=="0x1eac", ATTRS{device}=="0x1002", ENV{ID_MM_QUECTEL_FIREHOSE}="1" + +# Quectel RM520 with firehose support +ATTRS{vendor}=="0x1eac", ATTRS{device}=="0x1004", ENV{ID_MM_QUECTEL_FIREHOSE}="1" + +LABEL="mm_quectel_end" diff --git a/src/plugins/quectel/mm-broadband-modem-mbim-quectel.c b/src/plugins/quectel/mm-broadband-modem-mbim-quectel.c new file mode 100644 index 00000000..0ab40610 --- /dev/null +++ b/src/plugins/quectel/mm-broadband-modem-mbim-quectel.c @@ -0,0 +1,88 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2020 Aleksander Morgado <aleksander@aleksander.es> + * Copyright (C) 2021 Ivan Mikhanchuk <ivan.mikhanchuk@quectel.com> + */ + +#include <config.h> + +#include "mm-base-modem-at.h" +#include "mm-log-object.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-firmware.h" +#include "mm-iface-modem-time.h" +#include "mm-shared-quectel.h" +#include "mm-modem-helpers-quectel.h" +#include "mm-broadband-modem-mbim-quectel.h" + +static void iface_modem_firmware_init (MMIfaceModemFirmware *iface); +static void iface_modem_time_init (MMIfaceModemTime *iface); +static void shared_quectel_init (MMSharedQuectel *iface); + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimQuectel, mm_broadband_modem_mbim_quectel, MM_TYPE_BROADBAND_MODEM_MBIM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_FIRMWARE, iface_modem_firmware_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_QUECTEL, shared_quectel_init)) + +/*****************************************************************************/ + +MMBroadbandModemMbimQuectel * +mm_broadband_modem_mbim_quectel_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM_QUECTEL, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* include carrier information */ + MM_IFACE_MODEM_FIRMWARE_IGNORE_CARRIER, FALSE, + /* MBIM bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_mbim_quectel_init (MMBroadbandModemMbimQuectel *self) +{ +} + +static void +iface_modem_firmware_init (MMIfaceModemFirmware *iface) +{ + iface->load_update_settings = mm_shared_quectel_firmware_load_update_settings; + iface->load_update_settings_finish = mm_shared_quectel_firmware_load_update_settings_finish; +} + +static void +iface_modem_time_init (MMIfaceModemTime *iface) +{ + iface->check_support = mm_shared_quectel_time_check_support; + iface->check_support_finish = mm_shared_quectel_time_check_support_finish; +} + +static void +shared_quectel_init (MMSharedQuectel *iface) +{ +} + +static void +mm_broadband_modem_mbim_quectel_class_init (MMBroadbandModemMbimQuectelClass *klass) +{ +} diff --git a/src/plugins/quectel/mm-broadband-modem-mbim-quectel.h b/src/plugins/quectel/mm-broadband-modem-mbim-quectel.h new file mode 100644 index 00000000..0d0c2b95 --- /dev/null +++ b/src/plugins/quectel/mm-broadband-modem-mbim-quectel.h @@ -0,0 +1,48 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2020 Aleksander Morgado <aleksander@aleksander.es> + * Copyright (C) 2021 Ivan Mikhanchuk <ivan.mikhanchuk@quectel.com> + */ + +#ifndef MM_BROADBAND_MODEM_MBIM_QUECTEL_H +#define MM_BROADBAND_MODEM_MBIM_QUECTEL_H + +#include "mm-broadband-modem-mbim.h" + +#define MM_TYPE_BROADBAND_MODEM_MBIM_QUECTEL (mm_broadband_modem_mbim_quectel_get_type ()) +#define MM_BROADBAND_MODEM_MBIM_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_QUECTEL, MMBroadbandModemMbimQuectel)) +#define MM_BROADBAND_MODEM_MBIM_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_QUECTEL, MMBroadbandModemMbimQuectelClass)) +#define MM_IS_BROADBAND_MODEM_MBIM_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_QUECTEL)) +#define MM_IS_BROADBAND_MODEM_MBIM_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_QUECTEL)) +#define MM_BROADBAND_MODEM_MBIM_QUECTEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_QUECTEL, MMBroadbandModemMbimQuectelClass)) + +typedef struct _MMBroadbandModemMbimQuectel MMBroadbandModemMbimQuectel; +typedef struct _MMBroadbandModemMbimQuectelClass MMBroadbandModemMbimQuectelClass; + +struct _MMBroadbandModemMbimQuectel { + MMBroadbandModemMbim parent; +}; + +struct _MMBroadbandModemMbimQuectelClass{ + MMBroadbandModemMbimClass parent; +}; + +GType mm_broadband_modem_mbim_quectel_get_type (void); + +MMBroadbandModemMbimQuectel *mm_broadband_modem_mbim_quectel_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_MBIM_QUECTEL_H */ diff --git a/src/plugins/quectel/mm-broadband-modem-qmi-quectel.c b/src/plugins/quectel/mm-broadband-modem-qmi-quectel.c new file mode 100644 index 00000000..a4ccbfc9 --- /dev/null +++ b/src/plugins/quectel/mm-broadband-modem-qmi-quectel.c @@ -0,0 +1,138 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2018-2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include "mm-broadband-modem-qmi-quectel.h" +#include "mm-iface-modem-firmware.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-time.h" +#include "mm-shared-quectel.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_firmware_init (MMIfaceModemFirmware *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void iface_modem_time_init (MMIfaceModemTime *iface); +static void shared_quectel_init (MMSharedQuectel *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModemLocation *iface_modem_location_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQmiQuectel, mm_broadband_modem_qmi_quectel, MM_TYPE_BROADBAND_MODEM_QMI, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_FIRMWARE, iface_modem_firmware_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_QUECTEL, shared_quectel_init)) + +/*****************************************************************************/ + +MMBroadbandModemQmiQuectel * +mm_broadband_modem_qmi_quectel_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_QMI_QUECTEL, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* exclude carrier information */ + MM_IFACE_MODEM_FIRMWARE_IGNORE_CARRIER, TRUE, + /* QMI bearer supports NET only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_qmi_quectel_init (MMBroadbandModemQmiQuectel *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->setup_sim_hot_swap = mm_shared_quectel_setup_sim_hot_swap; + iface->setup_sim_hot_swap_finish = mm_shared_quectel_setup_sim_hot_swap_finish; + iface->cleanup_sim_hot_swap = mm_shared_quectel_cleanup_sim_hot_swap; +} + +static MMIfaceModem * +peek_parent_modem_interface (MMSharedQuectel *self) +{ + return iface_modem_parent; +} + +static MMBroadbandModemClass * +peek_parent_broadband_modem_class (MMSharedQuectel *self) +{ + return MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_qmi_quectel_parent_class); +} + +static void +iface_modem_firmware_init (MMIfaceModemFirmware *iface) +{ + iface->load_update_settings = mm_shared_quectel_firmware_load_update_settings; + iface->load_update_settings_finish = mm_shared_quectel_firmware_load_update_settings_finish; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = mm_shared_quectel_location_load_capabilities; + iface->load_capabilities_finish = mm_shared_quectel_location_load_capabilities_finish; + iface->enable_location_gathering = mm_shared_quectel_enable_location_gathering; + iface->enable_location_gathering_finish = mm_shared_quectel_enable_location_gathering_finish; + iface->disable_location_gathering = mm_shared_quectel_disable_location_gathering; + iface->disable_location_gathering_finish = mm_shared_quectel_disable_location_gathering_finish; +} + +static MMIfaceModemLocation * +peek_parent_modem_location_interface (MMSharedQuectel *self) +{ + return iface_modem_location_parent; +} + +static void +iface_modem_time_init (MMIfaceModemTime *iface) +{ + iface->check_support = mm_shared_quectel_time_check_support; + iface->check_support_finish = mm_shared_quectel_time_check_support_finish; +} + +static void +shared_quectel_init (MMSharedQuectel *iface) +{ + iface->peek_parent_modem_interface = peek_parent_modem_interface; + iface->peek_parent_modem_location_interface = peek_parent_modem_location_interface; + iface->peek_parent_broadband_modem_class = peek_parent_broadband_modem_class; +} + +static void +mm_broadband_modem_qmi_quectel_class_init (MMBroadbandModemQmiQuectelClass *klass) +{ + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + broadband_modem_class->setup_ports = mm_shared_quectel_setup_ports; +} diff --git a/src/plugins/quectel/mm-broadband-modem-qmi-quectel.h b/src/plugins/quectel/mm-broadband-modem-qmi-quectel.h new file mode 100644 index 00000000..f1580f0e --- /dev/null +++ b/src/plugins/quectel/mm-broadband-modem-qmi-quectel.h @@ -0,0 +1,47 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_BROADBAND_MODEM_QMI_QUECTEL_H +#define MM_BROADBAND_MODEM_QMI_QUECTEL_H + +#include "mm-broadband-modem-qmi.h" + +#define MM_TYPE_BROADBAND_MODEM_QMI_QUECTEL (mm_broadband_modem_qmi_quectel_get_type ()) +#define MM_BROADBAND_MODEM_QMI_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_QMI_QUECTEL, MMBroadbandModemQmiQuectel)) +#define MM_BROADBAND_MODEM_QMI_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_QMI_QUECTEL, MMBroadbandModemQmiQuectelClass)) +#define MM_IS_BROADBAND_MODEM_QMI_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_QMI_QUECTEL)) +#define MM_IS_BROADBAND_MODEM_QMI_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_QMI_QUECTEL)) +#define MM_BROADBAND_MODEM_QMI_QUECTEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_QMI_QUECTEL, MMBroadbandModemQmiQuectelClass)) + +typedef struct _MMBroadbandModemQmiQuectel MMBroadbandModemQmiQuectel; +typedef struct _MMBroadbandModemQmiQuectelClass MMBroadbandModemQmiQuectelClass; + +struct _MMBroadbandModemQmiQuectel { + MMBroadbandModemQmi parent; +}; + +struct _MMBroadbandModemQmiQuectelClass{ + MMBroadbandModemQmiClass parent; +}; + +GType mm_broadband_modem_qmi_quectel_get_type (void); + +MMBroadbandModemQmiQuectel *mm_broadband_modem_qmi_quectel_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_QMI_QUECTEL_H */ diff --git a/src/plugins/quectel/mm-broadband-modem-quectel.c b/src/plugins/quectel/mm-broadband-modem-quectel.c new file mode 100644 index 00000000..ad66b783 --- /dev/null +++ b/src/plugins/quectel/mm-broadband-modem-quectel.c @@ -0,0 +1,136 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2018-2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> + +#include "mm-broadband-modem-quectel.h" +#include "mm-iface-modem-firmware.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-time.h" +#include "mm-shared-quectel.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_firmware_init (MMIfaceModemFirmware *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void iface_modem_time_init (MMIfaceModemTime *iface); +static void shared_quectel_init (MMSharedQuectel *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModemLocation *iface_modem_location_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQuectel, mm_broadband_modem_quectel, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_FIRMWARE, iface_modem_firmware_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_QUECTEL, shared_quectel_init)) + +/*****************************************************************************/ + +MMBroadbandModemQuectel * +mm_broadband_modem_quectel_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_QUECTEL, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer supports TTY only */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_quectel_init (MMBroadbandModemQuectel *self) +{ +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->setup_sim_hot_swap = mm_shared_quectel_setup_sim_hot_swap; + iface->setup_sim_hot_swap_finish = mm_shared_quectel_setup_sim_hot_swap_finish; + iface->cleanup_sim_hot_swap = mm_shared_quectel_cleanup_sim_hot_swap; +} + +static MMIfaceModem * +peek_parent_modem_interface (MMSharedQuectel *self) +{ + return iface_modem_parent; +} + +static MMBroadbandModemClass * +peek_parent_broadband_modem_class (MMSharedQuectel *self) +{ + return MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_quectel_parent_class); +} + +static void +iface_modem_firmware_init (MMIfaceModemFirmware *iface) +{ + iface->load_update_settings = mm_shared_quectel_firmware_load_update_settings; + iface->load_update_settings_finish = mm_shared_quectel_firmware_load_update_settings_finish; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = mm_shared_quectel_location_load_capabilities; + iface->load_capabilities_finish = mm_shared_quectel_location_load_capabilities_finish; + iface->enable_location_gathering = mm_shared_quectel_enable_location_gathering; + iface->enable_location_gathering_finish = mm_shared_quectel_enable_location_gathering_finish; + iface->disable_location_gathering = mm_shared_quectel_disable_location_gathering; + iface->disable_location_gathering_finish = mm_shared_quectel_disable_location_gathering_finish; +} + +static MMIfaceModemLocation * +peek_parent_modem_location_interface (MMSharedQuectel *self) +{ + return iface_modem_location_parent; +} + +static void +iface_modem_time_init (MMIfaceModemTime *iface) +{ + iface->check_support = mm_shared_quectel_time_check_support; + iface->check_support_finish = mm_shared_quectel_time_check_support_finish; +} + +static void +shared_quectel_init (MMSharedQuectel *iface) +{ + iface->peek_parent_modem_interface = peek_parent_modem_interface; + iface->peek_parent_modem_location_interface = peek_parent_modem_location_interface; + iface->peek_parent_broadband_modem_class = peek_parent_broadband_modem_class; +} + +static void +mm_broadband_modem_quectel_class_init (MMBroadbandModemQuectelClass *klass) +{ + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + broadband_modem_class->setup_ports = mm_shared_quectel_setup_ports; +} diff --git a/src/plugins/quectel/mm-broadband-modem-quectel.h b/src/plugins/quectel/mm-broadband-modem-quectel.h new file mode 100644 index 00000000..bf4ef7a7 --- /dev/null +++ b/src/plugins/quectel/mm-broadband-modem-quectel.h @@ -0,0 +1,47 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_BROADBAND_MODEM_QUECTEL_H +#define MM_BROADBAND_MODEM_QUECTEL_H + +#include "mm-broadband-modem.h" + +#define MM_TYPE_BROADBAND_MODEM_QUECTEL (mm_broadband_modem_quectel_get_type ()) +#define MM_BROADBAND_MODEM_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_QUECTEL, MMBroadbandModemQuectel)) +#define MM_BROADBAND_MODEM_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_QUECTEL, MMBroadbandModemQuectelClass)) +#define MM_IS_BROADBAND_MODEM_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_QUECTEL)) +#define MM_IS_BROADBAND_MODEM_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_QUECTEL)) +#define MM_BROADBAND_MODEM_QUECTEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_QUECTEL, MMBroadbandModemQuectelClass)) + +typedef struct _MMBroadbandModemQuectel MMBroadbandModemQuectel; +typedef struct _MMBroadbandModemQuectelClass MMBroadbandModemQuectelClass; + +struct _MMBroadbandModemQuectel { + MMBroadbandModem parent; +}; + +struct _MMBroadbandModemQuectelClass{ + MMBroadbandModemClass parent; +}; + +GType mm_broadband_modem_quectel_get_type (void); + +MMBroadbandModemQuectel *mm_broadband_modem_quectel_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id); + +#endif /* MM_BROADBAND_MODEM_QUECTEL_H */ diff --git a/src/plugins/quectel/mm-modem-helpers-quectel.c b/src/plugins/quectel/mm-modem-helpers-quectel.c new file mode 100644 index 00000000..262d9794 --- /dev/null +++ b/src/plugins/quectel/mm-modem-helpers-quectel.c @@ -0,0 +1,91 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <glib.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-quectel.h" + +gboolean +mm_quectel_parse_ctzu_test_response (const gchar *response, + gpointer log_object, + gboolean *supports_disable, + gboolean *supports_enable, + gboolean *supports_enable_update_rtc, + GError **error) +{ + g_auto(GStrv) split = NULL; + g_autoptr(GArray) modes = NULL; + guint i; + + /* + * Response may be: + * - +CTZU: (0,1) + * - +CTZU: (0,1,3) + */ + +#define N_EXPECTED_GROUPS 1 + + split = mm_split_string_groups (mm_strip_tag (response, "+CTZU:")); + if (!split) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't split the +CTZU test response in groups"); + return FALSE; + } + + if (g_strv_length (split) != N_EXPECTED_GROUPS) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Cannot parse +CTZU test response: invalid number of groups (%u != %u)", + g_strv_length (split), N_EXPECTED_GROUPS); + return FALSE; + } + + modes = mm_parse_uint_list (split[0], error); + if (!modes) { + g_prefix_error (error, "Failed to parse integer list in +CTZU test response: "); + return FALSE; + } + + *supports_disable = FALSE; + *supports_enable = FALSE; + *supports_enable_update_rtc = FALSE; + + for (i = 0; i < modes->len; i++) { + guint mode; + + mode = g_array_index (modes, guint, i); + switch (mode) { + case 0: + *supports_disable = TRUE; + break; + case 1: + *supports_enable = TRUE; + break; + case 3: + *supports_enable_update_rtc = TRUE; + break; + default: + mm_obj_dbg (log_object, "unknown +CTZU mode: %u", mode); + break; + } + } + + return TRUE; +} diff --git a/src/plugins/quectel/mm-modem-helpers-quectel.h b/src/plugins/quectel/mm-modem-helpers-quectel.h new file mode 100644 index 00000000..d4ec0eae --- /dev/null +++ b/src/plugins/quectel/mm-modem-helpers-quectel.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_MODEM_HELPERS_QUECTEL_H +#define MM_MODEM_HELPERS_QUECTEL_H + +#include <glib.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +gboolean mm_quectel_parse_ctzu_test_response (const gchar *response, + gpointer log_object, + gboolean *supports_disable, + gboolean *supports_enable, + gboolean *supports_enable_update_rtc, + GError **error); + +#endif /* MM_MODEM_HELPERS_QUECTEL_H */ diff --git a/src/plugins/quectel/mm-plugin-quectel.c b/src/plugins/quectel/mm-plugin-quectel.c new file mode 100644 index 00000000..80e1b74d --- /dev/null +++ b/src/plugins/quectel/mm-plugin-quectel.c @@ -0,0 +1,117 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2017-2018 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <stdlib.h> +#include <gmodule.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-plugin-quectel.h" +#include "mm-broadband-modem-quectel.h" + +#if defined WITH_QMI +#include "mm-broadband-modem-qmi-quectel.h" +#endif + +#if defined WITH_MBIM +#include "mm-broadband-modem-mbim.h" +#include "mm-broadband-modem-mbim-quectel.h" +#endif + +G_DEFINE_TYPE (MMPluginQuectel, mm_plugin_quectel, MM_TYPE_PLUGIN) + +MM_PLUGIN_DEFINE_MAJOR_VERSION +MM_PLUGIN_DEFINE_MINOR_VERSION + +/*****************************************************************************/ + +static MMBaseModem * +create_modem (MMPlugin *self, + const gchar *uid, + const gchar **drivers, + guint16 vendor, + guint16 product, + guint16 subsystem_vendor, + GList *probes, + GError **error) +{ +#if defined WITH_QMI + if (mm_port_probe_list_has_qmi_port (probes)) { + mm_obj_dbg (self, "QMI-powered Quectel modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_qmi_quectel_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + +#if defined WITH_MBIM + if (mm_port_probe_list_has_mbim_port (probes)) { + mm_obj_dbg (self, "MBIM-powered Quectel modem found..."); + return MM_BASE_MODEM (mm_broadband_modem_mbim_quectel_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); + } +#endif + + return MM_BASE_MODEM (mm_broadband_modem_quectel_new (uid, + drivers, + mm_plugin_get_name (self), + vendor, + product)); +} + +/*****************************************************************************/ + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + static const gchar *subsystems[] = { "tty", "net", "usbmisc", "wwan", NULL }; + static const gchar *vendor_strings[] = { "quectel", NULL }; + static const guint16 vendor_ids[] = { + 0x2c7c, /* usb vid */ + 0x1eac, /* pci vid */ + 0 }; + + return MM_PLUGIN ( + g_object_new (MM_TYPE_PLUGIN_QUECTEL, + MM_PLUGIN_NAME, MM_MODULE_NAME, + MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems, + MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids, + MM_PLUGIN_ALLOWED_VENDOR_STRINGS, vendor_strings, + MM_PLUGIN_ALLOWED_AT, TRUE, + MM_PLUGIN_REQUIRED_QCDM, TRUE, + MM_PLUGIN_ALLOWED_QMI, TRUE, + MM_PLUGIN_ALLOWED_MBIM, TRUE, + NULL)); +} + +static void +mm_plugin_quectel_init (MMPluginQuectel *self) +{ +} + +static void +mm_plugin_quectel_class_init (MMPluginQuectelClass *klass) +{ + MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); + + plugin_class->create_modem = create_modem; +} diff --git a/src/plugins/quectel/mm-plugin-quectel.h b/src/plugins/quectel/mm-plugin-quectel.h new file mode 100644 index 00000000..ec888821 --- /dev/null +++ b/src/plugins/quectel/mm-plugin-quectel.h @@ -0,0 +1,40 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2017 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_PLUGIN_QUECTEL_H +#define MM_PLUGIN_QUECTEL_H + +#include "mm-plugin.h" + +#define MM_TYPE_PLUGIN_QUECTEL (mm_plugin_quectel_get_type ()) +#define MM_PLUGIN_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_QUECTEL, MMPluginQuectel)) +#define MM_PLUGIN_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_QUECTEL, MMPluginQuectelClass)) +#define MM_IS_PLUGIN_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_QUECTEL)) +#define MM_IS_PLUGIN_QUECTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_QUECTEL)) +#define MM_PLUGIN_QUECTEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_QUECTEL, MMPluginQuectelClass)) + +typedef struct { + MMPlugin parent; +} MMPluginQuectel; + +typedef struct { + MMPluginClass parent; +} MMPluginQuectelClass; + +GType mm_plugin_quectel_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_QUECTEL_H */ diff --git a/src/plugins/quectel/mm-shared-quectel.c b/src/plugins/quectel/mm-shared-quectel.c new file mode 100644 index 00000000..47d7cd33 --- /dev/null +++ b/src/plugins/quectel/mm-shared-quectel.c @@ -0,0 +1,1039 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2018-2020 Aleksander Morgado <aleksander@aleksander.es> + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. + */ + +#include <config.h> + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-log-object.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-firmware.h" +#include "mm-iface-modem-location.h" +#include "mm-base-modem.h" +#include "mm-base-modem-at.h" +#include "mm-shared-quectel.h" +#include "mm-modem-helpers-quectel.h" + +#if defined WITH_MBIM +#include "mm-broadband-modem-mbim.h" +#endif + +/*****************************************************************************/ +/* Private context */ + +#define PRIVATE_TAG "shared-quectel-private-tag" +static GQuark private_quark; + +typedef enum { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED, +} FeatureSupport; + +typedef struct { + MMBroadbandModemClass *broadband_modem_class_parent; + MMIfaceModem *iface_modem_parent; + MMIfaceModemLocation *iface_modem_location_parent; + MMModemLocationSource provided_sources; + MMModemLocationSource enabled_sources; + FeatureSupport qgps_supported; + GRegex *qgpsurc_regex; + GRegex *qlwurc_regex; + GRegex *rdy_regex; +} Private; + +static void +private_free (Private *priv) +{ + g_regex_unref (priv->qgpsurc_regex); + g_regex_unref (priv->qlwurc_regex); + g_regex_unref (priv->rdy_regex); + g_slice_free (Private, priv); +} + +static Private * +get_private (MMSharedQuectel *self) +{ + Private *priv; + + if (G_UNLIKELY (!private_quark)) + private_quark = g_quark_from_static_string (PRIVATE_TAG); + + priv = g_object_get_qdata (G_OBJECT (self), private_quark); + if (!priv) { + priv = g_slice_new0 (Private); + + priv->provided_sources = MM_MODEM_LOCATION_SOURCE_NONE; + priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE; + priv->qgps_supported = FEATURE_SUPPORT_UNKNOWN; + priv->qgpsurc_regex = g_regex_new ("\\r\\n\\+QGPSURC:.*", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + priv->qlwurc_regex = g_regex_new ("\\r\\n\\+QLWURC:.*", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + priv->rdy_regex = g_regex_new ("\\r\\nRDY", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + g_assert (priv->qgpsurc_regex); + g_assert (priv->qlwurc_regex); + g_assert (priv->rdy_regex); + + g_assert (MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_broadband_modem_class); + priv->broadband_modem_class_parent = MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_broadband_modem_class (self); + + g_assert (MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_location_interface); + priv->iface_modem_location_parent = MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_location_interface (self); + + g_assert (MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_interface); + priv->iface_modem_parent = MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_interface (self); + + g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free); + } + return priv; +} + +/*****************************************************************************/ +/* RDY unsolicited event handler */ + +static void +rdy_handler (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModem *self) +{ + /* The RDY URC indicates a modem reset that may or may not go hand-in-hand + * with USB re-enumeration. For the latter case, we must make sure to + * re-synchronize modem and ModemManager states by re-probing. + */ + mm_obj_warn (self, "modem reset detected, triggering reprobe"); + mm_base_modem_set_reprobe (MM_BASE_MODEM (self), TRUE); + mm_base_modem_set_valid (MM_BASE_MODEM (self), FALSE); +} + +/*****************************************************************************/ +/* Setup ports (Broadband modem class) */ + +void +mm_shared_quectel_setup_ports (MMBroadbandModem *self) +{ + Private *priv; + MMPortSerialAt *ports[2]; + guint i; + + priv = get_private (MM_SHARED_QUECTEL (self)); + g_assert (priv->broadband_modem_class_parent); + g_assert (priv->broadband_modem_class_parent->setup_ports); + + /* Parent setup always first */ + priv->broadband_modem_class_parent->setup_ports (self); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + /* Enable/disable unsolicited events in given port */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + /* Ignore +QGPSURC */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + priv->qgpsurc_regex, + NULL, NULL, NULL); + + /* Ignore +QLWURC */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + priv->qlwurc_regex, + NULL, NULL, NULL); + + /* Handle RDY */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + priv->rdy_regex, + (MMPortSerialAtUnsolicitedMsgFn)rdy_handler, + self, + NULL); + } +} + +/*****************************************************************************/ +/* Firmware update settings loading (Firmware interface) */ + +MMFirmwareUpdateSettings * +mm_shared_quectel_firmware_load_update_settings_finish (MMIfaceModemFirmware *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static gboolean +quectel_is_sahara_supported (MMBaseModem *modem, + MMPort *port) +{ + return mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (port), "ID_MM_QUECTEL_SAHARA"); +} + +static gboolean +quectel_is_firehose_supported (MMBaseModem *modem, + MMPort *port) +{ + return mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (port), "ID_MM_QUECTEL_FIREHOSE"); +} + +static MMModemFirmwareUpdateMethod +quectel_get_firmware_update_methods (MMBaseModem *modem, + MMPort *port) +{ + MMModemFirmwareUpdateMethod update_methods; + + update_methods = MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE; + + if (quectel_is_firehose_supported (modem, port)) + update_methods |= MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE; + if (quectel_is_sahara_supported (modem, port)) + update_methods |= MM_MODEM_FIRMWARE_UPDATE_METHOD_SAHARA; + + return update_methods; +} + +static void +quectel_at_port_get_firmware_version_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMFirmwareUpdateSettings *update_settings; + const gchar *version; + + update_settings = g_task_get_task_data (task); + + version = mm_base_modem_at_command_finish (modem, res, NULL); + if (version) + mm_firmware_update_settings_set_version (update_settings, version); + + g_task_return_pointer (task, g_object_ref (update_settings), g_object_unref); + g_object_unref (task); +} + +#if defined WITH_MBIM +static void +quectel_mbim_port_get_firmware_version_ready (MbimDevice *device, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(MbimMessage) response = NULL; + guint32 version_id; + g_autofree gchar *version_str = NULL; + MMFirmwareUpdateSettings *update_settings; + + update_settings = g_task_get_task_data (task); + + response = mbim_device_command_finish (device, res, NULL); + if (response && mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, NULL) && + mbim_message_qdu_quectel_read_version_response_parse (response, &version_id, &version_str, NULL)) { + mm_firmware_update_settings_set_version (update_settings, version_str); + } + + g_task_return_pointer (task, g_object_ref (update_settings), g_object_unref); + g_object_unref (task); +} +#endif + +static void +qfastboot_test_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + MMFirmwareUpdateSettings *update_settings; + + update_settings = g_task_get_task_data (task); + + /* Set update method */ + if (mm_base_modem_at_command_finish (self, res, NULL)) { + mm_firmware_update_settings_set_method (update_settings, MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT); + mm_firmware_update_settings_set_fastboot_at (update_settings, "AT+QFASTBOOT"); + } else + mm_firmware_update_settings_set_method (update_settings, MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE); + + /* Fetch full firmware info */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+QGMR?", + 3, + FALSE, + (GAsyncReadyCallback) quectel_at_port_get_firmware_version_ready, + task); +} + +static void +quectel_at_port_get_firmware_revision_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + MMFirmwareUpdateSettings *update_settings; + MMModemFirmwareUpdateMethod update_methods; + const gchar *revision; + const gchar *name; + const gchar *id; + g_autoptr(GPtrArray) ids = NULL; + GError *error = NULL; + + update_settings = g_task_get_task_data (task); + update_methods = mm_firmware_update_settings_get_method (update_settings); + + /* Set device ids */ + ids = mm_iface_firmware_build_generic_device_ids (MM_IFACE_MODEM_FIRMWARE (self), &error); + if (error) { + mm_obj_warn (self, "failed to build generic device ids: %s", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Add device id based on modem name */ + revision = mm_base_modem_at_command_finish (self, res, NULL); + if (revision && g_utf8_validate (revision, -1, NULL)) { + name = g_strndup (revision, 7); + mm_obj_dbg (self, "revision %s converted to modem name %s", revision, name); + id = (const gchar *) g_ptr_array_index (ids, 0); + g_ptr_array_insert (ids, 0, g_strdup_printf ("%s&NAME_%s", id, name)); + } + + mm_firmware_update_settings_set_device_ids (update_settings, (const gchar **)ids->pdata); + + /* Set update methods */ + if (update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE) { + /* Fetch full firmware info */ + mm_base_modem_at_command (self, + "+QGMR?", + 3, + TRUE, + (GAsyncReadyCallback) quectel_at_port_get_firmware_version_ready, + task); + } else { + /* Check fastboot support */ + mm_base_modem_at_command (self, + "AT+QFASTBOOT=?", + 3, + TRUE, + (GAsyncReadyCallback) qfastboot_test_ready, + task); + } +} + +void +mm_shared_quectel_firmware_load_update_settings (MMIfaceModemFirmware *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + MMPortSerialAt *at_port; + MMModemFirmwareUpdateMethod update_methods; + MMFirmwareUpdateSettings *update_settings; +#if defined WITH_MBIM + MMPortMbim *mbim; +#endif + + task = g_task_new (self, NULL, callback, user_data); + + at_port = mm_base_modem_peek_best_at_port (MM_BASE_MODEM (self), NULL); + if (at_port) { + update_methods = quectel_get_firmware_update_methods (MM_BASE_MODEM (self), MM_PORT (at_port)); + update_settings = mm_firmware_update_settings_new (update_methods); + g_task_set_task_data (task, update_settings, g_object_unref); + + /* Fetch modem name */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CGMR", + 3, + TRUE, + (GAsyncReadyCallback) quectel_at_port_get_firmware_revision_ready, + task); + + return; + } + +#if defined WITH_MBIM + mbim = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self)); + if (mbim) { + g_autoptr(MbimMessage) message = NULL; + + update_methods = quectel_get_firmware_update_methods (MM_BASE_MODEM (self), MM_PORT (mbim)); + update_settings = mm_firmware_update_settings_new (update_methods); + + /* Fetch firmware info */ + g_task_set_task_data (task, update_settings, g_object_unref); + message = mbim_message_qdu_quectel_read_version_set_new (MBIM_QDU_QUECTEL_VERSION_TYPE_FW_BUILD_ID, NULL); + mbim_device_command (mm_port_mbim_peek_device (mbim), + message, + 5, + NULL, + (GAsyncReadyCallback) quectel_mbim_port_get_firmware_version_ready, + task); + return; + } +#endif + + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't find a port to fetch firmware info"); + g_object_unref (task); +} + +/*****************************************************************************/ +/* "+QUSIM: 1" URC is emitted by Quectel modems after the USIM has been + * (re)initialized. We register a handler for this URC and perform a check + * for SIM swap when it is encountered. The motivation for this is to detect + * M2M eUICC profile switches. According to SGP.02 chapter 3.2.1, the eUICC + * shall trigger a REFRESH operation with eUICC reset when a new profile is + * enabled. The +QUSIM URC appears after the eUICC has restarted and can act + * as a trigger for profile switch check. This should basically be handled + * the same as a physical SIM swap, so the existing SIM hot swap mechanism + * is used. + */ + +static void +quectel_qusim_check_for_sim_swap_ready (MMIfaceModem *self, + GAsyncResult *res) +{ + g_autoptr(GError) error = NULL; + + if (!MM_IFACE_MODEM_GET_INTERFACE (self)->check_for_sim_swap_finish (self, res, &error)) + mm_obj_warn (self, "couldn't check SIM swap: %s", error->message); + else + mm_obj_dbg (self, "check SIM swap completed"); +} + +static void +quectel_qusim_unsolicited_handler (MMPortSerialAt *port, + GMatchInfo *match_info, + MMIfaceModem *self) +{ + if (!MM_IFACE_MODEM_GET_INTERFACE (self)->check_for_sim_swap || + !MM_IFACE_MODEM_GET_INTERFACE (self)->check_for_sim_swap_finish) + return; + + mm_obj_dbg (self, "checking SIM swap"); + MM_IFACE_MODEM_GET_INTERFACE (self)->check_for_sim_swap ( + self, + NULL, + NULL, + (GAsyncReadyCallback)quectel_qusim_check_for_sim_swap_ready, + NULL); +} + +/*****************************************************************************/ +/* Setup SIM hot swap context (Modem interface) */ + +gboolean +mm_shared_quectel_setup_sim_hot_swap_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_setup_sim_hot_swap_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + g_autoptr(GError) error = NULL; + + priv = get_private (MM_SHARED_QUECTEL (self)); + + if (!priv->iface_modem_parent->setup_sim_hot_swap_finish (self, res, &error)) + mm_obj_dbg (self, "additional SIM hot swap detection setup failed: %s", error->message); + + /* The +QUSIM based setup never fails, so we can safely return success here */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_quectel_setup_sim_hot_swap (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Private *priv; + MMPortSerialAt *ports[2]; + GTask *task; + guint i; + g_autoptr(GRegex) pattern = NULL; + g_autoptr(GError) error = NULL; + + priv = get_private (MM_SHARED_QUECTEL (self)); + + task = g_task_new (self, NULL, callback, user_data); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + pattern = g_regex_new ("\\+QUSIM:\\s*1\\r\\n", G_REGEX_RAW, 0, NULL); + g_assert (pattern); + + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (ports[i]) + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + pattern, + (MMPortSerialAtUnsolicitedMsgFn)quectel_qusim_unsolicited_handler, + self, + NULL); + } + + mm_obj_dbg (self, "+QUSIM detection set up"); + + if (!mm_broadband_modem_sim_hot_swap_ports_context_init (MM_BROADBAND_MODEM (self), &error)) + mm_obj_warn (self, "failed to initialize SIM hot swap ports context: %s", error->message); + + /* Now, if available, setup parent logic */ + if (priv->iface_modem_parent->setup_sim_hot_swap && + priv->iface_modem_parent->setup_sim_hot_swap_finish) { + priv->iface_modem_parent->setup_sim_hot_swap (self, + (GAsyncReadyCallback) parent_setup_sim_hot_swap_ready, + task); + return; + } + + /* Otherwise, we're done */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* SIM hot swap cleanup (Modem interface) */ + +void +mm_shared_quectel_cleanup_sim_hot_swap (MMIfaceModem *self) +{ + mm_broadband_modem_sim_hot_swap_ports_context_reset (MM_BROADBAND_MODEM (self)); +} + +/*****************************************************************************/ +/* GPS trace received */ + +static void +trace_received (MMPortSerialGps *port, + const gchar *trace, + MMIfaceModemLocation *self) +{ + mm_iface_modem_location_gps_update (self, trace); +} + +/*****************************************************************************/ +/* Location capabilities loading (Location interface) */ + +MMModemLocationSource +mm_shared_quectel_location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_LOCATION_SOURCE_NONE; + } + return (MMModemLocationSource)value; +} + +static void +probe_qgps_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMSharedQuectel *self; + Private *priv; + MMModemLocationSource sources; + + self = MM_SHARED_QUECTEL (g_task_get_source_object (task)); + priv = get_private (self); + + priv->qgps_supported = (!!mm_base_modem_at_command_finish (_self, res, NULL) ? + FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED); + + mm_obj_dbg (self, "GPS management with +QGPS is %ssupported", + priv->qgps_supported ? "" : "not "); + + /* Recover parent sources */ + sources = GPOINTER_TO_UINT (g_task_get_task_data (task)); + + /* Only flag as provided those sources not already provided by the parent */ + if (priv->qgps_supported == FEATURE_SUPPORTED) { + if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_NMEA)) + priv->provided_sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA; + if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_RAW)) + priv->provided_sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW; + if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) + priv->provided_sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED; + + sources |= priv->provided_sources; + + /* Add handler for the NMEA traces in the GPS data port */ + mm_port_serial_gps_add_trace_handler (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)), + (MMPortSerialGpsTraceFn)trace_received, + self, + NULL); + } + + /* So we're done, complete */ + g_task_return_int (task, sources); + g_object_unref (task); +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + Private *priv; + MMModemLocationSource sources; + GError *error = NULL; + + priv = get_private (MM_SHARED_QUECTEL (self)); + sources = priv->iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Now our own check. If we don't have any GPS port, we're done */ + if (!mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) { + mm_obj_dbg (self, "no GPS data port found: no GPS capabilities"); + g_task_return_int (task, sources); + g_object_unref (task); + return; + } + + /* Store parent supported sources in task data */ + g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL); + + /* Probe QGPS support */ + g_assert (priv->qgps_supported == FEATURE_SUPPORT_UNKNOWN); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+QGPS=?", + 3, + TRUE, /* cached */ + (GAsyncReadyCallback)probe_qgps_ready, + task); +} + +void +mm_shared_quectel_location_load_capabilities (MMIfaceModemLocation *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + + task = g_task_new (_self, NULL, callback, user_data); + priv = get_private (MM_SHARED_QUECTEL (_self)); + + /* Chain up parent's setup */ + priv->iface_modem_location_parent->load_capabilities (_self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + task); +} + +/*****************************************************************************/ +/* Enable location gathering (Location interface) */ + +/* NOTES: + * 1) "+QGPSCFG=\"nmeasrc\",1" will be necessary for getting location data + * without the nmea port. + * 2) may be necessary to set "+QGPSCFG=\"gpsnmeatype\". + * 3) QGPSXTRA=1 is necessary to support XTRA assistance data for + * faster GNSS location locks. + */ +static const MMBaseModemAtCommand gps_startup[] = { + { "+QGPSCFG=\"outport\",\"usbnmea\"", 3, FALSE, mm_base_modem_response_processor_no_result_continue }, + { "+QGPS=1", 3, FALSE, mm_base_modem_response_processor_no_result_continue }, + { "+QGPSXTRA=1", 3, FALSE, mm_base_modem_response_processor_no_result_continue }, + { NULL } +}; + +gboolean +mm_shared_quectel_enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +gps_startup_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource source; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_QUECTEL (self)); + + mm_base_modem_at_sequence_finish (self, res, NULL, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + source = GPOINTER_TO_UINT (g_task_get_task_data (task)); + + /* Check if the nmea/raw gps port exists and is available */ + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + MMPortSerialGps *gps_port; + + gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (!gps_port || !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) { + if (error) + g_task_return_error (task, error); + else + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't open raw GPS serial port"); + } else { + /* GPS port was successfully opened */ + priv->enabled_sources |= source; + g_task_return_boolean (task, TRUE); + } + } else { + /* No need to open GPS port */ + priv->enabled_sources |= source; + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_QUECTEL (self)); + if (!priv->iface_modem_location_parent->enable_location_gathering_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_quectel_enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + gboolean start_gps = FALSE; + + priv = get_private (MM_SHARED_QUECTEL (self)); + g_assert (priv->iface_modem_location_parent); + g_assert (priv->iface_modem_location_parent->enable_location_gathering); + g_assert (priv->iface_modem_location_parent->enable_location_gathering_finish); + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL); + + /* Check if the source is provided by the parent */ + if (!(priv->provided_sources & source)) { + priv->iface_modem_location_parent->enable_location_gathering ( + self, + source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + task); + return; + } + + /* Only start GPS engine if not done already */ + start_gps = ((source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) && + !(priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))); + + if (start_gps) { + mm_base_modem_at_sequence ( + MM_BASE_MODEM (self), + gps_startup, + NULL, /* response_processor_context */ + NULL, /* response_processor_context_free */ + (GAsyncReadyCallback)gps_startup_ready, + task); + return; + } + + /* If the GPS is already running just return */ + priv->enabled_sources |= source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Disable location gathering (Location interface) */ + +gboolean +mm_shared_quectel_disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +qgps_end_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +disable_location_gathering_parent_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_QUECTEL (self)); + if (!priv->iface_modem_location_parent->disable_location_gathering_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +mm_shared_quectel_disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + + priv = get_private (MM_SHARED_QUECTEL (self)); + g_assert (priv->iface_modem_location_parent); + + task = g_task_new (self, NULL, callback, user_data); + priv->enabled_sources &= ~source; + + /* Pass handling to parent if we don't handle it */ + if (!(source & priv->provided_sources)) { + /* The step to disable location gathering may not exist */ + if (priv->iface_modem_location_parent->disable_location_gathering && + priv->iface_modem_location_parent->disable_location_gathering_finish) { + priv->iface_modem_location_parent->disable_location_gathering (self, + source, + (GAsyncReadyCallback)disable_location_gathering_parent_ready, + task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Turn off gps on the modem if the source uses gps, + * and there are no other gps sources enabled */ + if ((source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) && + !(priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) { + /* Close the data port if we don't need it anymore */ + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_RAW | MM_MODEM_LOCATION_SOURCE_GPS_NMEA)) { + MMPortSerialGps *gps_port; + + gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (gps_port) + mm_port_serial_close (MM_PORT_SERIAL (gps_port)); + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+QGPSEND", + 3, + FALSE, + (GAsyncReadyCallback)qgps_end_ready, + task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +/*****************************************************************************/ +/* Check support (Time interface) */ + +gboolean +mm_shared_quectel_time_check_support_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +support_cclk_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + /* error never returned */ + g_task_return_boolean (task, !!mm_base_modem_at_command_finish (self, res, NULL)); + g_object_unref (task); +} + +static void +support_cclk_query (GTask *task) +{ + MMBaseModem *self; + + self = g_task_get_source_object (task); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CCLK?", + 3, + FALSE, + (GAsyncReadyCallback)support_cclk_query_ready, + task); +} + +static void +ctzu_set_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + mm_obj_warn (self, "couldn't enable automatic time zone update: %s", error->message); + + support_cclk_query (task); +} + +static void +ctzu_test_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + const gchar *response; + gboolean supports_disable; + gboolean supports_enable; + gboolean supports_enable_update_rtc; + const gchar *cmd = NULL; + + /* If CTZU isn't supported, run CCLK right away */ + response = mm_base_modem_at_command_finish (self, res, NULL); + if (!response) { + support_cclk_query (task); + return; + } + + if (!mm_quectel_parse_ctzu_test_response (response, + self, + &supports_disable, + &supports_enable, + &supports_enable_update_rtc, + &error)) { + mm_obj_warn (self, "couldn't parse +CTZU test response: %s", error->message); + support_cclk_query (task); + return; + } + + /* Custom time support check because some Quectel modems (e.g. EC25) require + * +CTZU=3 in order to have the CCLK? time reported in localtime, instead of + * UTC time. */ + if (supports_enable_update_rtc) + cmd = "+CTZU=3"; + else if (supports_enable) + cmd = "+CTZU=1"; + + if (!cmd) { + mm_obj_warn (self, "unknown +CTZU support"); + support_cclk_query (task); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + cmd, + 3, + FALSE, + (GAsyncReadyCallback)ctzu_set_ready, + task); +} + +void +mm_shared_quectel_time_check_support (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "+CTZU=?", + 3, + TRUE, /* cached! */ + (GAsyncReadyCallback)ctzu_test_ready, + task); +} + +/*****************************************************************************/ + +static void +shared_quectel_init (gpointer g_iface) +{ +} + +GType +mm_shared_quectel_get_type (void) +{ + static GType shared_quectel_type = 0; + + if (!G_UNLIKELY (shared_quectel_type)) { + static const GTypeInfo info = { + sizeof (MMSharedQuectel), /* class_size */ + shared_quectel_init, /* base_init */ + NULL, /* base_finalize */ + }; + + shared_quectel_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedQuectel", &info, 0); + g_type_interface_add_prerequisite (shared_quectel_type, MM_TYPE_IFACE_MODEM_FIRMWARE); + } + + return shared_quectel_type; +} diff --git a/src/plugins/quectel/mm-shared-quectel.h b/src/plugins/quectel/mm-shared-quectel.h new file mode 100644 index 00000000..0dfcbde4 --- /dev/null +++ b/src/plugins/quectel/mm-shared-quectel.h @@ -0,0 +1,93 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2018-2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef MM_SHARED_QUECTEL_H +#define MM_SHARED_QUECTEL_H + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-broadband-modem.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-firmware.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-time.h" + +#define MM_TYPE_SHARED_QUECTEL (mm_shared_quectel_get_type ()) +#define MM_SHARED_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_QUECTEL, MMSharedQuectel)) +#define MM_IS_SHARED_QUECTEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_QUECTEL)) +#define MM_SHARED_QUECTEL_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_QUECTEL, MMSharedQuectel)) + +typedef struct _MMSharedQuectel MMSharedQuectel; + +struct _MMSharedQuectel { + GTypeInterface g_iface; + MMBroadbandModemClass * (* peek_parent_broadband_modem_class) (MMSharedQuectel *self); + MMIfaceModem * (* peek_parent_modem_interface) (MMSharedQuectel *self); + MMIfaceModemLocation * (* peek_parent_modem_location_interface) (MMSharedQuectel *self); +}; + +GType mm_shared_quectel_get_type (void); + +void mm_shared_quectel_setup_ports (MMBroadbandModem *self); + +void mm_shared_quectel_firmware_load_update_settings (MMIfaceModemFirmware *self, + GAsyncReadyCallback callback, + gpointer user_data); + +MMFirmwareUpdateSettings *mm_shared_quectel_firmware_load_update_settings_finish (MMIfaceModemFirmware *self, + GAsyncResult *res, + GError **error); + +void mm_shared_quectel_setup_sim_hot_swap (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_quectel_setup_sim_hot_swap_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); +void mm_shared_quectel_cleanup_sim_hot_swap (MMIfaceModem *self); + +void mm_shared_quectel_location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data); +MMModemLocationSource mm_shared_quectel_location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); +void mm_shared_quectel_enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_quectel_enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); +void mm_shared_quectel_disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_quectel_disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); + +void mm_shared_quectel_time_check_support (MMIfaceModemTime *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_quectel_time_check_support_finish (MMIfaceModemTime *self, + GAsyncResult *res, + GError **error); + +#endif /* MM_SHARED_QUECTEL_H */ diff --git a/src/plugins/quectel/tests/test-modem-helpers-quectel.c b/src/plugins/quectel/tests/test-modem-helpers-quectel.c new file mode 100644 index 00000000..0e2c7420 --- /dev/null +++ b/src/plugins/quectel/tests/test-modem-helpers-quectel.c @@ -0,0 +1,93 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2020 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <glib.h> +#include <glib-object.h> +#include <locale.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> +#include <math.h> + +#include "mm-log-test.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-quectel.h" + +/*****************************************************************************/ +/* Test ^CTZU test responses */ + +typedef struct { + const gchar *response; + gboolean expect_supports_disable; + gboolean expect_supports_enable; + gboolean expect_supports_enable_update_rtc; +} TestCtzuResponse; + +static const TestCtzuResponse test_ctzu_response[] = { + { "+CTZU: (0,1)", TRUE, TRUE, FALSE }, + { "+CTZU: (0,1,3)", TRUE, TRUE, TRUE }, +}; + +static void +common_test_ctzu (const gchar *response, + gboolean expect_supports_disable, + gboolean expect_supports_enable, + gboolean expect_supports_enable_update_rtc) +{ + g_autoptr(GError) error = NULL; + gboolean res; + gboolean supports_disable = FALSE; + gboolean supports_enable = FALSE; + gboolean supports_enable_update_rtc = FALSE; + + res = mm_quectel_parse_ctzu_test_response (response, + NULL, + &supports_disable, + &supports_enable, + &supports_enable_update_rtc, + &error); + g_assert_no_error (error); + g_assert (res); + + g_assert_cmpuint (expect_supports_disable, ==, supports_disable); + g_assert_cmpuint (expect_supports_enable, ==, supports_enable); + g_assert_cmpuint (expect_supports_enable_update_rtc, ==, supports_enable_update_rtc); +} + +static void +test_ctzu (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (test_ctzu_response); i++) + common_test_ctzu (test_ctzu_response[i].response, + test_ctzu_response[i].expect_supports_disable, + test_ctzu_response[i].expect_supports_enable, + test_ctzu_response[i].expect_supports_enable_update_rtc); +} + +/*****************************************************************************/ + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/quectel/ctzu", test_ctzu); + + return g_test_run (); +} |