aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAleksander Morgado <aleksandermj@chromium.org>2022-12-08 13:37:55 +0000
committerAleksander Morgado <aleksander@aleksander.es>2023-01-03 13:56:25 +0000
commite14b904cbd6816cb0227d519d308ae71ddaf6e07 (patch)
tree4997ab68cc606fdf4d72a571e821cec0c8df42ef /src
parent072d7ac9065f444e83b390a1e2af5471ac0d48f6 (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')
-rw-r--r--src/meson.build3
-rw-r--r--src/plugins/README.txt160
-rw-r--r--src/plugins/altair/mm-broadband-bearer-altair-lte.c370
-rw-r--r--src/plugins/altair/mm-broadband-bearer-altair-lte.h59
-rw-r--r--src/plugins/altair/mm-broadband-modem-altair-lte.c1313
-rw-r--r--src/plugins/altair/mm-broadband-modem-altair-lte.h53
-rw-r--r--src/plugins/altair/mm-modem-helpers-altair-lte.c259
-rw-r--r--src/plugins/altair/mm-modem-helpers-altair-lte.h38
-rw-r--r--src/plugins/altair/mm-plugin-altair-lte.c101
-rw-r--r--src/plugins/altair/mm-plugin-altair-lte.h48
-rw-r--r--src/plugins/altair/tests/test-modem-helpers-altair-lte.c189
-rw-r--r--src/plugins/anydata/mm-broadband-modem-anydata.c355
-rw-r--r--src/plugins/anydata/mm-broadband-modem-anydata.h49
-rw-r--r--src/plugins/anydata/mm-plugin-anydata.c97
-rw-r--r--src/plugins/anydata/mm-plugin-anydata.h43
-rw-r--r--src/plugins/broadmobi/77-mm-broadmobi-port-types.rules16
-rw-r--r--src/plugins/broadmobi/mm-plugin-broadmobi.c95
-rw-r--r--src/plugins/broadmobi/mm-plugin-broadmobi.h40
-rw-r--r--src/plugins/cinterion/77-mm-cinterion-port-types.rules71
-rw-r--r--src/plugins/cinterion/mm-broadband-bearer-cinterion.c796
-rw-r--r--src/plugins/cinterion/mm-broadband-bearer-cinterion.h54
-rw-r--r--src/plugins/cinterion/mm-broadband-modem-cinterion.c3356
-rw-r--r--src/plugins/cinterion/mm-broadband-modem-cinterion.h54
-rw-r--r--src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.c167
-rw-r--r--src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.h47
-rw-r--r--src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c167
-rw-r--r--src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.h48
-rw-r--r--src/plugins/cinterion/mm-modem-helpers-cinterion.c1804
-rw-r--r--src/plugins/cinterion/mm-modem-helpers-cinterion.h211
-rw-r--r--src/plugins/cinterion/mm-plugin-cinterion.c218
-rw-r--r--src/plugins/cinterion/mm-plugin-cinterion.h47
-rw-r--r--src/plugins/cinterion/mm-shared-cinterion.c1601
-rw-r--r--src/plugins/cinterion/mm-shared-cinterion.h153
-rw-r--r--src/plugins/cinterion/tests/test-modem-helpers-cinterion.c1967
-rw-r--r--src/plugins/dell/77-mm-dell-port-types.rules32
-rw-r--r--src/plugins/dell/mm-plugin-dell.c528
-rw-r--r--src/plugins/dell/mm-plugin-dell.h46
-rw-r--r--src/plugins/dlink/77-mm-dlink-port-types.rules16
-rw-r--r--src/plugins/dlink/mm-plugin-dlink.c94
-rw-r--r--src/plugins/dlink/mm-plugin-dlink.h40
-rw-r--r--src/plugins/fibocom/77-mm-fibocom-port-types.rules109
-rw-r--r--src/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.c540
-rw-r--r--src/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.h50
-rw-r--r--src/plugins/fibocom/mm-broadband-modem-fibocom.c763
-rw-r--r--src/plugins/fibocom/mm-broadband-modem-fibocom.h49
-rw-r--r--src/plugins/fibocom/mm-broadband-modem-mbim-fibocom.c95
-rw-r--r--src/plugins/fibocom/mm-broadband-modem-mbim-fibocom.h47
-rw-r--r--src/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.c95
-rw-r--r--src/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.h47
-rw-r--r--src/plugins/fibocom/mm-plugin-fibocom.c136
-rw-r--r--src/plugins/fibocom/mm-plugin-fibocom.h40
-rw-r--r--src/plugins/fibocom/mm-shared-fibocom.c246
-rw-r--r--src/plugins/fibocom/mm-shared-fibocom.h53
-rw-r--r--src/plugins/fibocom/mm-shared.c20
-rw-r--r--src/plugins/foxconn/77-mm-foxconn-port-types.rules26
-rw-r--r--src/plugins/foxconn/mm-broadband-modem-mbim-foxconn.c612
-rw-r--r--src/plugins/foxconn/mm-broadband-modem-mbim-foxconn.h49
-rw-r--r--src/plugins/foxconn/mm-foxconn-t77w968-carrier-mapping.conf319
-rw-r--r--src/plugins/foxconn/mm-plugin-foxconn.c121
-rw-r--r--src/plugins/foxconn/mm-plugin-foxconn.h46
-rw-r--r--src/plugins/foxconn/mm-shared.c20
-rw-r--r--src/plugins/generic/mm-plugin-generic.c120
-rw-r--r--src/plugins/generic/mm-plugin-generic.h40
-rw-r--r--src/plugins/generic/tests/test-service-generic.c90
-rw-r--r--src/plugins/gosuncn/77-mm-gosuncn-port-types.rules17
-rw-r--r--src/plugins/gosuncn/mm-plugin-gosuncn.c114
-rw-r--r--src/plugins/gosuncn/mm-plugin-gosuncn.h40
-rw-r--r--src/plugins/haier/77-mm-haier-port-types.rules13
-rw-r--r--src/plugins/haier/mm-plugin-haier.c76
-rw-r--r--src/plugins/haier/mm-plugin-haier.h40
-rw-r--r--src/plugins/huawei/77-mm-huawei-net-port-types.rules37
-rw-r--r--src/plugins/huawei/mm-broadband-bearer-huawei.c879
-rw-r--r--src/plugins/huawei/mm-broadband-bearer-huawei.h59
-rw-r--r--src/plugins/huawei/mm-broadband-modem-huawei.c4732
-rw-r--r--src/plugins/huawei/mm-broadband-modem-huawei.h55
-rw-r--r--src/plugins/huawei/mm-modem-helpers-huawei.c1546
-rw-r--r--src/plugins/huawei/mm-modem-helpers-huawei.h193
-rw-r--r--src/plugins/huawei/mm-plugin-huawei.c735
-rw-r--r--src/plugins/huawei/mm-plugin-huawei.h42
-rw-r--r--src/plugins/huawei/mm-sim-huawei.c167
-rw-r--r--src/plugins/huawei/mm-sim-huawei.h53
-rw-r--r--src/plugins/huawei/tests/test-modem-helpers-huawei.c1422
-rw-r--r--src/plugins/icera/mm-broadband-bearer-icera.c882
-rw-r--r--src/plugins/icera/mm-broadband-bearer-icera.h66
-rw-r--r--src/plugins/icera/mm-broadband-modem-icera.c2374
-rw-r--r--src/plugins/icera/mm-broadband-modem-icera.h55
-rw-r--r--src/plugins/icera/mm-modem-helpers-icera.c389
-rw-r--r--src/plugins/icera/mm-modem-helpers-icera.h34
-rw-r--r--src/plugins/icera/mm-shared.c20
-rw-r--r--src/plugins/icera/tests/test-modem-helpers-icera.c262
-rw-r--r--src/plugins/intel/mm-broadband-modem-mbim-intel.c144
-rw-r--r--src/plugins/intel/mm-broadband-modem-mbim-intel.h47
-rw-r--r--src/plugins/intel/mm-plugin-intel.c91
-rw-r--r--src/plugins/intel/mm-plugin-intel.h40
-rw-r--r--src/plugins/iridium/mm-bearer-iridium.c266
-rw-r--r--src/plugins/iridium/mm-bearer-iridium.h55
-rw-r--r--src/plugins/iridium/mm-broadband-modem-iridium.c433
-rw-r--r--src/plugins/iridium/mm-broadband-modem-iridium.h49
-rw-r--r--src/plugins/iridium/mm-plugin-iridium.c89
-rw-r--r--src/plugins/iridium/mm-plugin-iridium.h47
-rw-r--r--src/plugins/iridium/mm-sim-iridium.c95
-rw-r--r--src/plugins/iridium/mm-sim-iridium.h52
-rw-r--r--src/plugins/linktop/77-mm-linktop-port-types.rules16
-rw-r--r--src/plugins/linktop/mm-broadband-modem-linktop.c269
-rw-r--r--src/plugins/linktop/mm-broadband-modem-linktop.h49
-rw-r--r--src/plugins/linktop/mm-modem-helpers-linktop.c54
-rw-r--r--src/plugins/linktop/mm-modem-helpers-linktop.h40
-rw-r--r--src/plugins/linktop/mm-plugin-linktop.c79
-rw-r--r--src/plugins/linktop/mm-plugin-linktop.h42
-rw-r--r--src/plugins/linktop/tests/test-modem-helpers-linktop.c71
-rw-r--r--src/plugins/longcheer/77-mm-longcheer-port-types.rules173
-rw-r--r--src/plugins/longcheer/mm-broadband-modem-longcheer.c416
-rw-r--r--src/plugins/longcheer/mm-broadband-modem-longcheer.h49
-rw-r--r--src/plugins/longcheer/mm-plugin-longcheer.c243
-rw-r--r--src/plugins/longcheer/mm-plugin-longcheer.h42
-rw-r--r--src/plugins/mbm/77-mm-ericsson-mbm.rules174
-rw-r--r--src/plugins/mbm/mm-broadband-bearer-mbm.c911
-rw-r--r--src/plugins/mbm/mm-broadband-bearer-mbm.h68
-rw-r--r--src/plugins/mbm/mm-broadband-modem-mbm.c1583
-rw-r--r--src/plugins/mbm/mm-broadband-modem-mbm.h58
-rw-r--r--src/plugins/mbm/mm-modem-helpers-mbm.c337
-rw-r--r--src/plugins/mbm/mm-modem-helpers-mbm.h51
-rw-r--r--src/plugins/mbm/mm-plugin-mbm.c101
-rw-r--r--src/plugins/mbm/mm-plugin-mbm.h43
-rw-r--r--src/plugins/mbm/mm-sim-mbm.c242
-rw-r--r--src/plugins/mbm/mm-sim-mbm.h51
-rw-r--r--src/plugins/mbm/tests/test-modem-helpers-mbm.c268
-rw-r--r--src/plugins/meson.build1027
-rw-r--r--src/plugins/motorola/mm-broadband-modem-motorola.c92
-rw-r--r--src/plugins/motorola/mm-broadband-modem-motorola.h49
-rw-r--r--src/plugins/motorola/mm-plugin-motorola.c84
-rw-r--r--src/plugins/motorola/mm-plugin-motorola.h42
-rw-r--r--src/plugins/mtk/77-mm-mtk-port-types.rules53
-rw-r--r--src/plugins/mtk/mm-broadband-modem-mtk.c934
-rw-r--r--src/plugins/mtk/mm-broadband-modem-mtk.h51
-rw-r--r--src/plugins/mtk/mm-plugin-mtk.c82
-rw-r--r--src/plugins/mtk/mm-plugin-mtk.h42
-rw-r--r--src/plugins/nokia/77-mm-nokia-port-types.rules38
-rw-r--r--src/plugins/nokia/mm-broadband-modem-nokia.c399
-rw-r--r--src/plugins/nokia/mm-broadband-modem-nokia.h49
-rw-r--r--src/plugins/nokia/mm-plugin-nokia-icera.c90
-rw-r--r--src/plugins/nokia/mm-plugin-nokia-icera.h41
-rw-r--r--src/plugins/nokia/mm-plugin-nokia.c93
-rw-r--r--src/plugins/nokia/mm-plugin-nokia.h41
-rw-r--r--src/plugins/nokia/mm-sim-nokia.c86
-rw-r--r--src/plugins/nokia/mm-sim-nokia.h51
-rw-r--r--src/plugins/novatel/mm-broadband-bearer-novatel-lte.c580
-rw-r--r--src/plugins/novatel/mm-broadband-bearer-novatel-lte.h60
-rw-r--r--src/plugins/novatel/mm-broadband-modem-novatel-lte.c701
-rw-r--r--src/plugins/novatel/mm-broadband-modem-novatel-lte.h50
-rw-r--r--src/plugins/novatel/mm-broadband-modem-novatel.c1609
-rw-r--r--src/plugins/novatel/mm-broadband-modem-novatel.h51
-rw-r--r--src/plugins/novatel/mm-common-novatel.c145
-rw-r--r--src/plugins/novatel/mm-common-novatel.h31
-rw-r--r--src/plugins/novatel/mm-plugin-novatel-lte.c81
-rw-r--r--src/plugins/novatel/mm-plugin-novatel-lte.h47
-rw-r--r--src/plugins/novatel/mm-plugin-novatel.c113
-rw-r--r--src/plugins/novatel/mm-plugin-novatel.h48
-rw-r--r--src/plugins/novatel/mm-shared.c20
-rw-r--r--src/plugins/novatel/mm-sim-novatel-lte.c234
-rw-r--r--src/plugins/novatel/mm-sim-novatel-lte.h51
-rw-r--r--src/plugins/option/mm-broadband-bearer-hso.c818
-rw-r--r--src/plugins/option/mm-broadband-bearer-hso.h61
-rw-r--r--src/plugins/option/mm-broadband-modem-hso.c788
-rw-r--r--src/plugins/option/mm-broadband-modem-hso.h51
-rw-r--r--src/plugins/option/mm-broadband-modem-option.c1228
-rw-r--r--src/plugins/option/mm-broadband-modem-option.h51
-rw-r--r--src/plugins/option/mm-plugin-hso.c202
-rw-r--r--src/plugins/option/mm-plugin-hso.h42
-rw-r--r--src/plugins/option/mm-plugin-option.c121
-rw-r--r--src/plugins/option/mm-plugin-option.h42
-rw-r--r--src/plugins/option/mm-shared-option.c77
-rw-r--r--src/plugins/option/mm-shared-option.h49
-rw-r--r--src/plugins/option/mm-shared.c20
-rw-r--r--src/plugins/option/mm-sim-option.c84
-rw-r--r--src/plugins/option/mm-sim-option.h51
-rw-r--r--src/plugins/pantech/mm-broadband-modem-pantech.c187
-rw-r--r--src/plugins/pantech/mm-broadband-modem-pantech.h47
-rw-r--r--src/plugins/pantech/mm-plugin-pantech.c161
-rw-r--r--src/plugins/pantech/mm-plugin-pantech.h40
-rw-r--r--src/plugins/pantech/mm-sim-pantech.c87
-rw-r--r--src/plugins/pantech/mm-sim-pantech.h51
-rw-r--r--src/plugins/qcom-soc/77-mm-qcom-soc.rules40
-rw-r--r--src/plugins/qcom-soc/mm-broadband-modem-qmi-qcom-soc.c176
-rw-r--r--src/plugins/qcom-soc/mm-broadband-modem-qmi-qcom-soc.h49
-rw-r--r--src/plugins/qcom-soc/mm-plugin-qcom-soc.c98
-rw-r--r--src/plugins/qcom-soc/mm-plugin-qcom-soc.h40
-rw-r--r--src/plugins/quectel/77-mm-quectel-port-types.rules104
-rw-r--r--src/plugins/quectel/mm-broadband-modem-mbim-quectel.c88
-rw-r--r--src/plugins/quectel/mm-broadband-modem-mbim-quectel.h48
-rw-r--r--src/plugins/quectel/mm-broadband-modem-qmi-quectel.c138
-rw-r--r--src/plugins/quectel/mm-broadband-modem-qmi-quectel.h47
-rw-r--r--src/plugins/quectel/mm-broadband-modem-quectel.c136
-rw-r--r--src/plugins/quectel/mm-broadband-modem-quectel.h47
-rw-r--r--src/plugins/quectel/mm-modem-helpers-quectel.c91
-rw-r--r--src/plugins/quectel/mm-modem-helpers-quectel.h32
-rw-r--r--src/plugins/quectel/mm-plugin-quectel.c117
-rw-r--r--src/plugins/quectel/mm-plugin-quectel.h40
-rw-r--r--src/plugins/quectel/mm-shared-quectel.c1039
-rw-r--r--src/plugins/quectel/mm-shared-quectel.h93
-rw-r--r--src/plugins/quectel/tests/test-modem-helpers-quectel.c93
-rw-r--r--src/plugins/samsung/mm-broadband-modem-samsung.c94
-rw-r--r--src/plugins/samsung/mm-broadband-modem-samsung.h49
-rw-r--r--src/plugins/samsung/mm-plugin-samsung.c83
-rw-r--r--src/plugins/samsung/mm-plugin-samsung.h48
-rw-r--r--src/plugins/sierra/77-mm-sierra.rules40
-rw-r--r--src/plugins/sierra/mm-broadband-bearer-sierra.c682
-rw-r--r--src/plugins/sierra/mm-broadband-bearer-sierra.h62
-rw-r--r--src/plugins/sierra/mm-broadband-modem-sierra-icera.c145
-rw-r--r--src/plugins/sierra/mm-broadband-modem-sierra-icera.h49
-rw-r--r--src/plugins/sierra/mm-broadband-modem-sierra.c1937
-rw-r--r--src/plugins/sierra/mm-broadband-modem-sierra.h51
-rw-r--r--src/plugins/sierra/mm-common-sierra.c516
-rw-r--r--src/plugins/sierra/mm-common-sierra.h67
-rw-r--r--src/plugins/sierra/mm-modem-helpers-sierra.c76
-rw-r--r--src/plugins/sierra/mm-modem-helpers-sierra.h26
-rw-r--r--src/plugins/sierra/mm-plugin-sierra-legacy.c99
-rw-r--r--src/plugins/sierra/mm-plugin-sierra-legacy.h40
-rw-r--r--src/plugins/sierra/mm-plugin-sierra.c137
-rw-r--r--src/plugins/sierra/mm-plugin-sierra.h42
-rw-r--r--src/plugins/sierra/mm-shared.c20
-rw-r--r--src/plugins/sierra/mm-sim-sierra.c158
-rw-r--r--src/plugins/sierra/mm-sim-sierra.h53
-rw-r--r--src/plugins/sierra/tests/test-modem-helpers-sierra.c130
-rw-r--r--src/plugins/simtech/77-mm-simtech-port-types.rules59
-rw-r--r--src/plugins/simtech/mm-broadband-modem-qmi-simtech.c127
-rw-r--r--src/plugins/simtech/mm-broadband-modem-qmi-simtech.h47
-rw-r--r--src/plugins/simtech/mm-broadband-modem-simtech.c1351
-rw-r--r--src/plugins/simtech/mm-broadband-modem-simtech.h51
-rw-r--r--src/plugins/simtech/mm-modem-helpers-simtech.c209
-rw-r--r--src/plugins/simtech/mm-modem-helpers-simtech.h67
-rw-r--r--src/plugins/simtech/mm-plugin-simtech.c98
-rw-r--r--src/plugins/simtech/mm-plugin-simtech.h42
-rw-r--r--src/plugins/simtech/mm-shared-simtech.c1261
-rw-r--r--src/plugins/simtech/mm-shared-simtech.h129
-rw-r--r--src/plugins/simtech/tests/test-modem-helpers-simtech.c324
-rw-r--r--src/plugins/symbol.map8
-rw-r--r--src/plugins/telit/77-mm-telit-port-types.rules146
-rw-r--r--src/plugins/telit/mm-broadband-modem-mbim-telit.c242
-rw-r--r--src/plugins/telit/mm-broadband-modem-mbim-telit.h48
-rw-r--r--src/plugins/telit/mm-broadband-modem-telit.c1562
-rw-r--r--src/plugins/telit/mm-broadband-modem-telit.h51
-rw-r--r--src/plugins/telit/mm-common-telit.c373
-rw-r--r--src/plugins/telit/mm-common-telit.h40
-rw-r--r--src/plugins/telit/mm-modem-helpers-telit.c967
-rw-r--r--src/plugins/telit/mm-modem-helpers-telit.h90
-rw-r--r--src/plugins/telit/mm-plugin-telit.c132
-rw-r--r--src/plugins/telit/mm-plugin-telit.h42
-rw-r--r--src/plugins/telit/mm-shared-telit.c795
-rw-r--r--src/plugins/telit/mm-shared-telit.h107
-rw-r--r--src/plugins/telit/mm-shared.c20
-rw-r--r--src/plugins/telit/tests/test-mm-modem-helpers-telit.c695
-rw-r--r--src/plugins/tests/gsm-port.conf46
-rw-r--r--src/plugins/tests/test-fixture.c163
-rw-r--r--src/plugins/tests/test-fixture.h54
-rw-r--r--src/plugins/tests/test-helpers.c52
-rw-r--r--src/plugins/tests/test-helpers.h26
-rw-r--r--src/plugins/tests/test-keyfiles.c79
-rw-r--r--src/plugins/tests/test-port-context.c422
-rw-r--r--src/plugins/tests/test-port-context.h35
-rw-r--r--src/plugins/tests/test-udev-rules.c257
-rw-r--r--src/plugins/thuraya/mm-broadband-modem-thuraya.c284
-rw-r--r--src/plugins/thuraya/mm-broadband-modem-thuraya.h50
-rw-r--r--src/plugins/thuraya/mm-modem-helpers-thuraya.c148
-rw-r--r--src/plugins/thuraya/mm-modem-helpers-thuraya.h28
-rw-r--r--src/plugins/thuraya/mm-plugin-thuraya.c85
-rw-r--r--src/plugins/thuraya/mm-plugin-thuraya.h48
-rw-r--r--src/plugins/thuraya/tests/test-mm-modem-helpers-thuraya.c106
-rw-r--r--src/plugins/tplink/77-mm-tplink-port-types.rules15
-rw-r--r--src/plugins/tplink/mm-plugin-tplink.c94
-rw-r--r--src/plugins/tplink/mm-plugin-tplink.h40
-rw-r--r--src/plugins/ublox/77-mm-ublox-port-types.rules78
-rw-r--r--src/plugins/ublox/README162
-rw-r--r--src/plugins/ublox/mm-broadband-bearer-ublox.c1035
-rw-r--r--src/plugins/ublox/mm-broadband-bearer-ublox.h63
-rw-r--r--src/plugins/ublox/mm-broadband-modem-ublox.c2093
-rw-r--r--src/plugins/ublox/mm-broadband-modem-ublox.h49
-rw-r--r--src/plugins/ublox/mm-modem-helpers-ublox.c2014
-rw-r--r--src/plugins/ublox/mm-modem-helpers-ublox.h213
-rw-r--r--src/plugins/ublox/mm-plugin-ublox.c265
-rw-r--r--src/plugins/ublox/mm-plugin-ublox.h40
-rw-r--r--src/plugins/ublox/mm-sim-ublox.c163
-rw-r--r--src/plugins/ublox/mm-sim-ublox.h51
-rw-r--r--src/plugins/ublox/tests/test-modem-helpers-ublox.c1026
-rw-r--r--src/plugins/via/mm-broadband-modem-via.c543
-rw-r--r--src/plugins/via/mm-broadband-modem-via.h49
-rw-r--r--src/plugins/via/mm-plugin-via.c84
-rw-r--r--src/plugins/via/mm-plugin-via.h46
-rw-r--r--src/plugins/wavecom/mm-broadband-modem-wavecom.c1320
-rw-r--r--src/plugins/wavecom/mm-broadband-modem-wavecom.h50
-rw-r--r--src/plugins/wavecom/mm-plugin-wavecom.c88
-rw-r--r--src/plugins/wavecom/mm-plugin-wavecom.h49
-rw-r--r--src/plugins/x22x/77-mm-x22x-port-types.rules68
-rw-r--r--src/plugins/x22x/mm-broadband-modem-x22x.c428
-rw-r--r--src/plugins/x22x/mm-broadband-modem-x22x.h51
-rw-r--r--src/plugins/x22x/mm-plugin-x22x.c255
-rw-r--r--src/plugins/x22x/mm-plugin-x22x.h42
-rw-r--r--src/plugins/xmm/mm-broadband-modem-mbim-xmm.c138
-rw-r--r--src/plugins/xmm/mm-broadband-modem-mbim-xmm.h47
-rw-r--r--src/plugins/xmm/mm-broadband-modem-xmm.c151
-rw-r--r--src/plugins/xmm/mm-broadband-modem-xmm.h47
-rw-r--r--src/plugins/xmm/mm-modem-helpers-xmm.c1003
-rw-r--r--src/plugins/xmm/mm-modem-helpers-xmm.h75
-rw-r--r--src/plugins/xmm/mm-shared-xmm.c1710
-rw-r--r--src/plugins/xmm/mm-shared-xmm.h184
-rw-r--r--src/plugins/xmm/mm-shared.c20
-rw-r--r--src/plugins/xmm/tests/test-modem-helpers-xmm.c780
-rw-r--r--src/plugins/zte/77-mm-zte-port-types.rules204
-rw-r--r--src/plugins/zte/mm-broadband-modem-zte-icera.c204
-rw-r--r--src/plugins/zte/mm-broadband-modem-zte-icera.h51
-rw-r--r--src/plugins/zte/mm-broadband-modem-zte.c763
-rw-r--r--src/plugins/zte/mm-broadband-modem-zte.h51
-rw-r--r--src/plugins/zte/mm-common-zte.c141
-rw-r--r--src/plugins/zte/mm-common-zte.h31
-rw-r--r--src/plugins/zte/mm-plugin-zte.c177
-rw-r--r--src/plugins/zte/mm-plugin-zte.h42
316 files changed, 85480 insertions, 0 deletions
diff --git a/src/meson.build b/src/meson.build
index 30aedbe9..db345e4c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -336,3 +336,6 @@ install_data(
if enable_tests
subdir('tests')
endif
+
+# Additional vendor plugins
+subdir('plugins') \ No newline at end of file
diff --git a/src/plugins/README.txt b/src/plugins/README.txt
new file mode 100644
index 00000000..ff36dc41
--- /dev/null
+++ b/src/plugins/README.txt
@@ -0,0 +1,160 @@
+
+The following list shows the relationship among the different plugins provided
+by default by ModemManager. For each of the plugin types, the list of modem
+objects created by the plugin is given.
+
+ * Altair:
+ ** MMBroadbandModemAltairLte
+
+ * Anydata:
+ ** MMBroadbandModemQmi (generic)
+ ** MMBroadbandModemAnydata
+
+ * Cinterion:
+ ** MMBroadbandModemQmiCinterion
+ ** MMBroadbandModemCinterion
+
+ * Dell:
+ ** MMBroadbandModemQmi (generic)
+ ** MMBroadbandModemFoxconnT77w968 (from the Foxconn utils)
+ ** MMBroadbandModemMbimXmm (from the XMM utils)
+ ** MMBroadbandModemMbim (generic)
+ ** MMBroadbandModemNovatel (from the Novatel utils)
+ ** MMBroadbandModemSierra (from the Sierra Legacy utils)
+ ** MMBroadbandModemTelit (from the Telit utils)
+ ** MMBroadbandModemXmm (from the XMM utils)
+ ** MMBroadbandModem (generic)
+
+ * D-Link:
+ ** MMBroadbandModemQmi (generic)
+ ** MMBroadbandModem (generic)
+
+ * Fibocom:
+ ** MMBroadbandModemMbimXmm (from the XMM utils)
+ ** MMBroadbandModemMbim (generic)
+ ** MMBroadbandModemXmm (from the XMM utils)
+ ** MMBroadbandModem (generic)
+
+ * Foxconn:
+ ** MMBroadbandModemQmi (generic)
+ ** MMBroadbandModemFoxconnT77w968
+ ** MMBroadbandModemMbim (generic)
+ ** MMBroadbandModem (generic)
+
+ * Generic:
+ ** MMBroadbandModemQmi (generic)
+ ** MMBroadbandModemMbim (generic)
+ ** MMBroadbandModem (generic)
+
+ * Gosuncn:
+ ** MMBroadbandModemQmi (generic)
+ ** MMBroadbandModemMbim (generic)
+ ** MMBroadbandModem (generic)
+
+ * Haier:
+ ** MMBroadbandModem (generic)
+
+ * Huawei:
+ ** MMBroadbandModemQmi (generic)
+ ** MMBroadbandModemMbim (generic)
+ ** MMBroadbandModemHuawei
+
+ * Icera (no explicit plugin):
+ ** MMBroadbandModemIcera
+
+ * Iridium:
+ ** MMBroadbandModemIridium
+
+ * Linktop:
+ ** MMBroadbandModemLinktop
+
+ * Longcheer:
+ ** MMBroadbandModemLongcheer
+
+ * MBM:
+ ** MMBroadbandModemMbim (generic)
+ ** MMBroadbandModemMbm
+
+ * Motorola:
+ ** MMBroadbandModemMotorola
+
+ * Mtk:
+ ** MMBroadbandModemMtk
+
+ * Nokia:
+ ** MMBroadbandModemNokia
+
+ * Nokia Icera:
+ ** MMBroadbandModemIcera (from the Icera utils)
+
+ * Novatel:
+ ** MMBroadbandModemQmi (generic)
+ ** MMBroadbandModemNovatel
+
+ * Novatel LTE:
+ ** MMBroadbandModemNovatelLte
+
+ * Option:
+ ** MMBroadbandModemOption
+
+ * Option HSO:
+ ** MMBroadbandModemHso
+
+ * Pantech:
+ ** MMBroadbandModemQmi (generic)
+ ** MMBroadbandModemPantech
+
+ * Quectel:
+ ** MMBroadbandModemQmiQuectel
+ ** MMBroadbandModemQuectel
+
+ * Samsung:
+ ** MMBroadbandModemSamsung (subclassed from the Icera utils)
+
+ * Sierra Legacy:
+ ** MMBroadbandModemSierraIcera (subclassed from the Icera utils)
+ ** MMBroadbandModemSierra
+
+ * Sierra:
+ ** MMBroadbandModemQmi (generic)
+ ** MMBroadbandModemMbim (generic)
+ ** MMBroadbandModem (generic)
+
+ * Simtech:
+ ** MMBroadbandModemQmiSimtech
+ ** MMBroadbandModemSimtech
+
+ * Telit:
+ ** MMBroadbandModemQmi (generic)
+ ** MMBroadbandModemMbimTelit
+ ** MMBroadbandModemTelit
+
+ * Thuraya
+ ** MMBroadbandModemThuraya
+
+ * TP-Link:
+ ** MMBroadbandModemQmi (generic)
+ ** MMBroadbandModem (generic)
+
+ * u-blox:
+ ** MMBroadbandModemUblox
+
+ * via:
+ ** MMBroadbandModemVia
+
+ * wavecom:
+ ** MMBroadbandModemWavecom
+
+ * x22x:
+ ** MMBroadbandModemQmi (generic)
+ ** MMBroadbandModemX22x
+
+ * XMM (no explicit plugin):
+ ** MMBroadbandModemMbimXmm
+ ** MMBroadbandModemXmm
+
+ * ZTE
+ ** MMBroadbandModemQmi (generic)
+ ** MMBroadbandModemMbim (generic)
+ ** MMBroadbandModemZteIcera (subclassed from the Icera utils)
+ ** MMBroadbandModemZte
diff --git a/src/plugins/altair/mm-broadband-bearer-altair-lte.c b/src/plugins/altair/mm-broadband-bearer-altair-lte.c
new file mode 100644
index 00000000..812b04d8
--- /dev/null
+++ b/src/plugins/altair/mm-broadband-bearer-altair-lte.c
@@ -0,0 +1,370 @@
+/* -*- 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) 2013 Altair Semiconductor
+ *
+ * Author: Ori Inbar <ori.inbar@altair-semi.com>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-base-modem-at.h"
+#include "mm-broadband-bearer-altair-lte.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-log.h"
+#include "mm-modem-helpers.h"
+
+#define CONNECTION_CHECK_TIMEOUT_SEC 5
+#define STATCM_TAG "%STATCM:"
+
+G_DEFINE_TYPE (MMBroadbandBearerAltairLte, mm_broadband_bearer_altair_lte, MM_TYPE_BROADBAND_BEARER);
+
+/*****************************************************************************/
+/* 3GPP Connect sequence */
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ MMPort *data;
+} DetailedConnectContext;
+
+static void
+detailed_connect_context_free (DetailedConnectContext *ctx)
+{
+ g_object_unref (ctx->data);
+ g_object_unref (ctx->primary);
+ g_object_unref (ctx->modem);
+ g_free (ctx);
+}
+
+static MMBearerConnectResult *
+connect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+connect_3gpp_connect_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ DetailedConnectContext *ctx;
+ const gchar *result;
+ GError *error = NULL;
+ MMBearerIpConfig *config;
+
+ result = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (!result) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ config = mm_bearer_ip_config_new ();
+
+ mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_DHCP);
+
+ /* Set operation result */
+ g_task_return_pointer (
+ task,
+ mm_bearer_connect_result_new (ctx->data, config, config),
+ (GDestroyNotify)mm_bearer_connect_result_unref);
+ g_object_unref (task);
+
+ g_object_unref (config);
+}
+
+static void
+connect_3gpp_apnsettings_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ DetailedConnectContext *ctx;
+ const gchar *result;
+ GError *error = NULL;
+
+ result = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (!result) {
+ g_prefix_error (&error, "setting APN failed: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ "%DPDNACT=1",
+ MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, /* timeout */
+ FALSE, /* allow_cached */
+ FALSE, /* is_raw */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)connect_3gpp_connect_ready,
+ task); /* user_data */
+}
+
+static void
+connect_3gpp (MMBroadbandBearer *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ DetailedConnectContext *ctx;
+ gchar *command, *apn;
+ MMBearerProperties *config;
+ MMModem3gppRegistrationState registration_state;
+ MMPort *data;
+ GTask *task;
+
+ /* There is a known firmware bug that can leave the modem unusable if a
+ * connect attempt is made when out of coverage. So, fail without trying.
+ */
+ g_object_get (modem,
+ MM_IFACE_MODEM_3GPP_REGISTRATION_STATE, &registration_state,
+ NULL);
+ if (registration_state == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN) {
+ g_task_report_new_error (self,
+ callback,
+ user_data,
+ connect_3gpp,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK,
+ "Out of coverage, can't connect.");
+ return;
+ }
+
+ /* Don't allow a connect while we detach from the network to process SIM
+ * refresh.
+ * */
+ if (mm_broadband_modem_altair_lte_is_sim_refresh_detach_in_progress (modem)) {
+ mm_obj_dbg (self, "detached from network to process SIM refresh, failing connect request");
+ g_task_report_new_error (self,
+ callback,
+ user_data,
+ connect_3gpp,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_RETRY,
+ "Detached from network to process SIM refresh, can't connect.");
+ return;
+ }
+
+ data = mm_base_modem_peek_best_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET);
+ if (!data) {
+ g_task_report_new_error (self,
+ callback,
+ user_data,
+ connect_3gpp,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_CONNECTED,
+ "Couldn't connect: no available net port available");
+ return;
+ }
+
+ ctx = g_new0 (DetailedConnectContext, 1);
+ ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
+ ctx->primary = g_object_ref (primary);
+ ctx->data = g_object_ref (data);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)detailed_connect_context_free);
+
+ config = mm_base_bearer_peek_config (MM_BASE_BEARER (self));
+ apn = mm_port_serial_at_quote_string (mm_bearer_properties_get_apn (config));
+ command = g_strdup_printf ("%%APNN=%s", apn);
+ g_free (apn);
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ 10, /* timeout */
+ FALSE, /* allow_cached */
+ FALSE, /* is_raw */
+ cancellable,
+ (GAsyncReadyCallback)connect_3gpp_apnsettings_ready,
+ task); /* user_data */
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* 3GPP Disconnect sequence */
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ MMPort *data;
+} DetailedDisconnectContext;
+
+static gboolean
+disconnect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+detailed_disconnect_context_free (DetailedDisconnectContext *ctx)
+{
+ g_object_unref (ctx->data);
+ g_object_unref (ctx->primary);
+ g_object_unref (ctx->modem);
+ g_free (ctx);
+}
+
+static void
+disconnect_3gpp_check_status (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+
+ const gchar *result;
+ GError *error = NULL;
+
+ result = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (!result)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+disconnect_3gpp (MMBroadbandBearer *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ DetailedDisconnectContext *ctx;
+ MMModem3gppRegistrationState registration_state;
+ GTask *task;
+
+ /* There is a known firmware bug that can leave the modem unusable if a
+ * disconnect attempt is made when out of coverage. So, fail without trying.
+ */
+ g_object_get (modem,
+ MM_IFACE_MODEM_3GPP_REGISTRATION_STATE, &registration_state,
+ NULL);
+ if (registration_state == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN) {
+ g_task_report_new_error (self,
+ callback,
+ user_data,
+ disconnect_3gpp,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK,
+ "Out of coverage, can't disconnect.");
+ return;
+ }
+
+ ctx = g_new0 (DetailedDisconnectContext, 1);
+ ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
+ ctx->primary = g_object_ref (primary);
+ ctx->data = g_object_ref (data);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)detailed_disconnect_context_free);
+
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ "%DPDNACT=0",
+ MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, /* timeout */
+ FALSE, /* allow_cached */
+ FALSE, /* is_raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)disconnect_3gpp_check_status,
+ task); /* user_data */
+}
+
+/*****************************************************************************/
+
+MMBaseBearer *
+mm_broadband_bearer_altair_lte_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *bearer;
+ GObject *source;
+
+ source = g_async_result_get_source_object (res);
+ bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!bearer)
+ return NULL;
+
+ /* Only export valid bearers */
+ mm_base_bearer_export (MM_BASE_BEARER (bearer));
+
+ return MM_BASE_BEARER (bearer);
+}
+
+void
+mm_broadband_bearer_altair_lte_new (MMBroadbandModemAltairLte *modem,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (
+ MM_TYPE_BROADBAND_BEARER_ALTAIR_LTE,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_BEARER_MODEM, modem,
+ MM_BASE_BEARER_CONFIG, config,
+ NULL);
+}
+
+static void
+mm_broadband_bearer_altair_lte_init (MMBroadbandBearerAltairLte *self)
+{
+
+}
+
+static void
+mm_broadband_bearer_altair_lte_class_init (MMBroadbandBearerAltairLteClass *klass)
+{
+ MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);
+ MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass);
+
+ base_bearer_class->load_connection_status = NULL;
+ base_bearer_class->load_connection_status_finish = NULL;
+#if defined WITH_SUSPEND_RESUME
+ base_bearer_class->reload_connection_status = NULL;
+ base_bearer_class->reload_connection_status_finish = NULL;
+#endif
+
+ broadband_bearer_class->connect_3gpp = connect_3gpp;
+ broadband_bearer_class->connect_3gpp_finish = connect_3gpp_finish;
+ broadband_bearer_class->disconnect_3gpp = disconnect_3gpp;
+ broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish;
+}
diff --git a/src/plugins/altair/mm-broadband-bearer-altair-lte.h b/src/plugins/altair/mm-broadband-bearer-altair-lte.h
new file mode 100644
index 00000000..907c7743
--- /dev/null
+++ b/src/plugins/altair/mm-broadband-bearer-altair-lte.h
@@ -0,0 +1,59 @@
+/* -*- 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) 2013 Altair Semiconductor
+ *
+ * Author: Ori Inbar <ori.inbar@altair-semi.com>
+ */
+
+#ifndef MM_BROADBAND_BEARER_ALTAIR_LTE_H
+#define MM_BROADBAND_BEARER_ALTAIR_LTE_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-broadband-bearer.h"
+#include "mm-broadband-modem-altair-lte.h"
+
+#define MM_TYPE_BROADBAND_BEARER_ALTAIR_LTE (mm_broadband_bearer_altair_lte_get_type ())
+#define MM_BROADBAND_BEARER_ALTAIR_LTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_ALTAIR_LTE, MMBroadbandBearerAltairLte))
+#define MM_BROADBAND_BEARER_ALTAIR_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_ALTAIR_LTE, MMBroadbandBearerAltairLteClass))
+#define MM_IS_BROADBAND_BEARER_ALTAIR_LTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_ALTAIR_LTE))
+#define MM_IS_BROADBAND_BEARER_ALTAIR_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_ALTAIR_LTE))
+#define MM_BROADBAND_BEARER_ALTAIR_LTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_ALTAIR_LTE, MMBroadbandBearerAltairLteClass))
+
+typedef struct _MMBroadbandBearerAltairLte MMBroadbandBearerAltairLte;
+typedef struct _MMBroadbandBearerAltairLteClass MMBroadbandBearerAltairLteClass;
+
+struct _MMBroadbandBearerAltairLte {
+ MMBroadbandBearer parent;
+};
+
+struct _MMBroadbandBearerAltairLteClass {
+ MMBroadbandBearerClass parent;
+};
+
+GType mm_broadband_bearer_altair_lte_get_type (void);
+
+/* Default 3GPP bearer creation implementation */
+void mm_broadband_bearer_altair_lte_new (MMBroadbandModemAltairLte *modem,
+ MMBearerProperties *properties,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseBearer *mm_broadband_bearer_altair_lte_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_BROADBAND_BEARER_ALTAIR_LTE_H */
diff --git a/src/plugins/altair/mm-broadband-modem-altair-lte.c b/src/plugins/altair/mm-broadband-modem-altair-lte.c
new file mode 100644
index 00000000..837d57cc
--- /dev/null
+++ b/src/plugins/altair/mm-broadband-modem-altair-lte.c
@@ -0,0 +1,1313 @@
+/* -*- 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) 2013 Altair Semiconductor
+ *
+ * Author: Ori Inbar <ori.inbar@altair-semi.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-base-modem-at.h"
+#include "mm-broadband-bearer-altair-lte.h"
+#include "mm-broadband-modem-altair-lte.h"
+#include "mm-errors-types.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-3gpp-ussd.h"
+#include "mm-iface-modem-messaging.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-altair-lte.h"
+#include "mm-serial-parsers.h"
+#include "mm-bearer-list.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface);
+static void iface_modem_messaging_init (MMIfaceModemMessaging *iface);
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemAltairLte, mm_broadband_modem_altair_lte, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_USSD, iface_modem_3gpp_ussd_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init))
+
+struct _MMBroadbandModemAltairLtePrivate {
+ /* Regex for SIM refresh notifications */
+ GRegex *sim_refresh_regex;
+ /* Timer that goes off 10s after the last SIM refresh notification.
+ * This indicates that there are no more SIM refreshes and we should
+ * reregister the device.*/
+ guint sim_refresh_timer_id;
+ /* Flag indicating that we are detaching from the network to process SIM
+ * refresh. This is used to prevent connect requests while we're in this
+ * state.*/
+ gboolean sim_refresh_detach_in_progress;
+ /* Regex for bearer related notifications */
+ GRegex *statcm_regex;
+ /* Regex for PCO notifications */
+ GRegex *pcoinfo_regex;
+
+ GList *pco_list;
+};
+
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+
+
+/*****************************************************************************/
+/* Modem power down (Modem interface) */
+
+static gboolean
+modem_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_power_down (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=4",
+ 20,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Create Bearer (Modem interface) */
+
+static MMBaseBearer *
+modem_create_bearer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+broadband_bearer_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer = NULL;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_altair_lte_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+ g_object_unref (task);
+}
+
+static void
+modem_create_bearer (MMIfaceModem *self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* We just create a MMBroadbandBearer */
+ mm_broadband_bearer_altair_lte_new (MM_BROADBAND_MODEM_ALTAIR_LTE (self),
+ properties,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_new_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load unlock retries (Modem interface) */
+
+static MMUnlockRetries *
+load_unlock_retries_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_unlock_retries_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ gint pin1, puk1, pin2, puk2;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ response = mm_strip_tag (response, "%CPININFO:");
+ if (sscanf (response, " %d, %d, %d, %d", &pin1, &puk1, &pin2, &puk2) == 4) {
+ MMUnlockRetries *retries;
+
+ retries = mm_unlock_retries_new ();
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN2, pin2);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK2, puk2);
+ g_task_return_pointer (task, retries, g_object_unref);
+ } else {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Invalid unlock retries response: '%s'",
+ response);
+ }
+ g_object_unref (task);
+}
+
+static void
+load_unlock_retries (MMIfaceModem *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),
+ "%CPININFO",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)load_unlock_retries_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load current capabilities (Modem interface) */
+
+static MMModemCapability
+load_current_capabilities_finish (MMIfaceModem *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_CAPABILITY_NONE;
+ }
+ return (MMModemCapability)value;
+}
+
+static void
+load_current_capabilities (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ /* This modem is LTE only.*/
+ g_task_return_int (task, MM_MODEM_CAPABILITY_LTE);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Load supported bands (Modem interface) */
+
+static GArray *
+load_supported_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+#define BANDCAP_TAG "%BANDCAP: "
+
+static void
+load_supported_bands_done (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GArray *bands;
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /*
+ * Response is "%BANDCAP: <band>,[<band>...]"
+ */
+ response = mm_strip_tag (response, BANDCAP_TAG);
+
+ bands = mm_altair_parse_bands_response (response);
+ if (!bands) {
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse supported bands response");
+ g_object_unref (task);
+ return;
+ }
+
+ g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_supported_bands (MMIfaceModem *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),
+ "%BANDCAP=",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)load_supported_bands_done,
+ task);
+}
+
+/*****************************************************************************/
+/* Load current bands (Modem interface) */
+
+static GArray *
+load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+#define CFGBANDS_TAG "Bands: "
+
+static void
+load_current_bands_done (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GArray *bands;
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /*
+ * Response is "Bands: <band>,[<band>...]"
+ */
+ response = mm_strip_tag (response, CFGBANDS_TAG);
+
+ bands = mm_altair_parse_bands_response (response);
+ if (!bands) {
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse current bands response");
+ g_object_unref (task);
+ return;
+ }
+
+ g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_current_bands (MMIfaceModem *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),
+ "%GETCFG=\"BAND\"",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)load_current_bands_done,
+ task);
+}
+
+/*****************************************************************************/
+/* Reset (Modem interface) */
+
+static gboolean
+reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "ATZ",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Run registration checks (3GPP interface) */
+
+static gboolean
+modem_3gpp_run_registration_checks_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+simulate_unprovisioned_subscription_pco_update (MMBroadbandModemAltairLte *self)
+{
+ MMPco *pco;
+
+ /* Simulate a %PCOINFO notification issued to the IMS PDN that indicates an
+ * unprovisioned Verizon SIM. See mm_altair_parse_vendor_pco_info() for the
+ * detailed format of a %PCOINFO response.
+ *
+ * 1,FF00,13018405 is constructed as follows:
+ *
+ * 1: CID for IMS PDN
+ * FF 00: Container ID for the Verizon-specific PCO content
+ * 13 01 84: Binary coded decimal representation of Verizon MCC/MNC 311/084
+ * 05: Value indicating an unprovisioned SIM
+ */
+ pco = mm_altair_parse_vendor_pco_info ("%PCOINFO: 1,FF00,13018405", NULL);
+ g_assert (pco != NULL);
+ self->priv->pco_list = mm_pco_list_add (self->priv->pco_list, pco);
+ mm_iface_modem_3gpp_update_pco_list (MM_IFACE_MODEM_3GPP (self), self->priv->pco_list);
+ g_object_unref (pco);
+}
+
+static void
+run_registration_checks_subscription_state_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *at_response;
+ gchar *ceer_response;
+
+ /* If the AT+CEER command fails, or we fail to obtain a valid result, we
+ * ignore the error. This allows the registration attempt to continue.
+ * So, the async response from this function is *always* True.
+ */
+
+ at_response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!at_response) {
+ g_assert (error);
+ mm_obj_warn (self, "AT+CEER failed: %s", error->message);
+ g_error_free (error);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ ceer_response = mm_altair_parse_ceer_response (at_response, &error);
+ if (!ceer_response) {
+ g_assert (error);
+ mm_obj_warn (self, "Failed to parse AT+CEER response: %s", error->message);
+ g_error_free (error);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ if (g_strcmp0 ("EPS_AND_NON_EPS_SERVICES_NOT_ALLOWED", ceer_response) == 0) {
+ mm_obj_dbg (self, "registration failed due to unprovisioned SIM");
+ simulate_unprovisioned_subscription_pco_update (MM_BROADBAND_MODEM_ALTAIR_LTE (self));
+ } else {
+ mm_obj_dbg (self, "failed to find a better reason for registration failure");
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ g_free (ceer_response);
+}
+
+static void
+run_registration_checks_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ gboolean success;
+
+ g_assert (iface_modem_3gpp_parent->run_registration_checks_finish);
+ success = iface_modem_3gpp_parent->run_registration_checks_finish (self, res, &error);
+ if (!success) {
+ g_assert (error);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "checking if SIM is unprovisioned (ignoring registration state)");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CEER",
+ 6,
+ FALSE,
+ (GAsyncReadyCallback) run_registration_checks_subscription_state_ready,
+ task);
+}
+
+static void
+modem_3gpp_run_registration_checks (MMIfaceModem3gpp *self,
+ gboolean is_cs_supported,
+ gboolean is_ps_supported,
+ gboolean is_eps_supported,
+ gboolean is_5gs_supported,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ g_assert (iface_modem_3gpp_parent->run_registration_checks);
+ iface_modem_3gpp_parent->run_registration_checks (self,
+ is_cs_supported,
+ is_ps_supported,
+ is_eps_supported,
+ is_5gs_supported,
+ (GAsyncReadyCallback) run_registration_checks_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Register in network (3GPP interface) */
+
+static gboolean
+modem_3gpp_register_in_network_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+cmatt_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
+modem_3gpp_register_in_network (MMIfaceModem3gpp *self,
+ const gchar *operator_id,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ if (operator_id) {
+ /* Currently only VZW is supported */
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Setting a specific operator ID is not supported");
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "%CMATT=1",
+ 3,
+ FALSE, /* allow cached */
+ (GAsyncReadyCallback)cmatt_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* SIMREFRESH unsolicited event handler */
+
+static void
+altair_reregister_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ if (!mm_base_modem_at_command_finish (self, res, NULL))
+ mm_obj_dbg (self, "failed to reregister modem");
+ else
+ mm_obj_dbg (self, "modem reregistered successfully");
+ MM_BROADBAND_MODEM_ALTAIR_LTE (self)->priv->sim_refresh_detach_in_progress = FALSE;
+}
+
+static void
+altair_deregister_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ if (!mm_base_modem_at_command_finish (self, res, NULL)) {
+ mm_obj_dbg (self, "deregister modem failed");
+ MM_BROADBAND_MODEM_ALTAIR_LTE (self)->priv->sim_refresh_detach_in_progress = FALSE;
+ return;
+ }
+
+ mm_obj_dbg (self, "deregistered modem, now reregistering");
+
+ /* Register */
+ mm_base_modem_at_command (
+ self,
+ "%CMATT=1",
+ 10,
+ FALSE, /* allow_cached */
+ (GAsyncReadyCallback)altair_reregister_ready,
+ NULL);
+}
+
+static void
+altair_load_own_numbers_ready (MMIfaceModem *iface_modem,
+ GAsyncResult *res,
+ MMBroadbandModemAltairLte *self)
+{
+ GError *error = NULL;
+ GStrv str_list;
+
+ str_list = MM_IFACE_MODEM_GET_INTERFACE (self)->load_own_numbers_finish (MM_IFACE_MODEM (self), res, &error);
+ if (error) {
+ mm_obj_warn (self, "Couldn't reload Own Numbers: '%s'", error->message);
+ g_error_free (error);
+ }
+ if (str_list) {
+ mm_iface_modem_update_own_numbers (iface_modem, str_list);
+ g_strfreev (str_list);
+ }
+
+ /* Set this flag to prevent connect requests from being processed while we
+ * detach from the network.*/
+ self->priv->sim_refresh_detach_in_progress = TRUE;
+
+ /* Deregister */
+ mm_obj_dbg (self, "reregistering modem");
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "%CMATT=0",
+ 10,
+ FALSE, /* allow_cached */
+ (GAsyncReadyCallback)altair_deregister_ready,
+ NULL);
+}
+
+static gboolean
+altair_sim_refresh_timer_expired (MMBroadbandModemAltairLte *self)
+{
+ mm_obj_dbg (self, "no more SIM refreshes, reloading own numbers and reregistering modem");
+
+ g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->load_own_numbers);
+ g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->load_own_numbers_finish);
+ MM_IFACE_MODEM_GET_INTERFACE (self)->load_own_numbers (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)altair_load_own_numbers_ready,
+ self);
+ self->priv->sim_refresh_timer_id = 0;
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+altair_sim_refresh_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemAltairLte *self)
+{
+ mm_obj_dbg (self, "received SIM refresh notification");
+ if (self->priv->sim_refresh_timer_id) {
+ g_source_remove (self->priv->sim_refresh_timer_id);
+ }
+ self->priv->sim_refresh_timer_id =
+ g_timeout_add_seconds(10,
+ (GSourceFunc)altair_sim_refresh_timer_expired,
+ self);
+}
+
+typedef enum {
+ MM_STATCM_ALTAIR_LTE_DEREGISTERED = 0,
+ MM_STATCM_ALTAIR_LTE_REGISTERED = 1,
+ MM_STATCM_ALTAIR_PDN_CONNECTED = 3,
+ MM_STATCM_ALTAIR_PDN_DISCONNECTED = 4,
+} MMStatcmAltair;
+
+static void
+bearer_list_report_disconnect_status_foreach (MMBaseBearer *bearer,
+ gpointer *user_data)
+{
+ mm_base_bearer_report_connection_status (bearer,
+ MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
+}
+
+/*****************************************************************************/
+/* STATCM unsolicited event handler */
+
+static void
+altair_statcm_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemAltairLte *self)
+{
+ gint pdn_event = 0;
+ MMBearerList *list = NULL;
+
+ mm_get_int_from_match_info (match_info, 1, &pdn_event);
+
+ mm_obj_dbg (self, "PDN event detected: %d", pdn_event);
+
+ /* Currently we only care about bearer disconnection */
+
+ if (pdn_event == MM_STATCM_ALTAIR_PDN_DISCONNECTED) {
+ /* If empty bearer list, nothing else to do */
+ g_object_get (self,
+ MM_IFACE_MODEM_BEARER_LIST, &list,
+ NULL);
+ if (!list)
+ return;
+
+ mm_bearer_list_foreach (list,
+ (MMBearerListForeachFunc)bearer_list_report_disconnect_status_foreach,
+ NULL);
+
+ g_object_unref (list);
+ }
+
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (3GPP interface) */
+
+static void
+altair_pco_info_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemAltairLte *self);
+
+static void
+set_3gpp_unsolicited_events_handlers (MMBroadbandModemAltairLte *self,
+ gboolean enable)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ 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;
+
+ /* SIM refresh handler */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->sim_refresh_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)altair_sim_refresh_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ /* bearer mode related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->statcm_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)altair_statcm_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ /* PCO info handler */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->pcoinfo_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)altair_pco_info_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+static gboolean
+modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_3gpp_setup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else {
+ /* Our own setup now */
+ set_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_ALTAIR_LTE (self), TRUE);
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's setup */
+ iface_modem_3gpp_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_3gpp_setup_unsolicited_events_ready,
+ task);
+}
+
+static void
+parent_3gpp_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Our own cleanup first */
+ set_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_ALTAIR_LTE (self), FALSE);
+
+ /* And now chain up parent's cleanup */
+ iface_modem_3gpp_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_3gpp_cleanup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Enabling unsolicited events (3GPP interface) */
+
+static const MMBaseModemAtCommand unsolicited_events_enable_sequence[] = {
+ { "%STATCM=1", 10, FALSE, mm_base_modem_response_processor_no_result_continue },
+ { "%NOTIFYEV=\"SIMREFRESH\",1", 10, FALSE, NULL },
+ { "%PCOINFO=1", 10, FALSE, NULL },
+ { NULL }
+};
+
+static gboolean
+modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+own_enable_unsolicited_events_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_finish (self, res, NULL, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Our own enable now */
+ mm_base_modem_at_sequence (
+ MM_BASE_MODEM (self),
+ unsolicited_events_enable_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ (GAsyncReadyCallback)own_enable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's enable */
+ iface_modem_3gpp_parent->enable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_enable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Disabling unsolicited events (3GPP interface) */
+
+static const MMBaseModemAtCommand unsolicited_events_disable_sequence[] = {
+ { "%STATCM=0", 10, FALSE, NULL },
+ { "%NOTIFYEV=\"SIMREFRESH\",0", 10, FALSE, NULL },
+ { "%PCOINFO=0", 10, FALSE, NULL },
+ { NULL }
+};
+
+static gboolean
+modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+own_disable_unsolicited_events_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_finish (self, res, NULL, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Next, chain up parent's disable */
+ iface_modem_3gpp_parent->disable_unsolicited_events (
+ MM_IFACE_MODEM_3GPP (self),
+ (GAsyncReadyCallback)parent_disable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Our own disable first */
+ mm_base_modem_at_sequence (
+ MM_BASE_MODEM (self),
+ unsolicited_events_disable_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ (GAsyncReadyCallback)own_disable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Operator Code loading (3GPP interface) */
+
+static gchar *
+modem_3gpp_load_operator_code_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *result;
+ gchar *operator_code = NULL;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!result)
+ return NULL;
+
+ if (!mm_3gpp_parse_cops_read_response (result,
+ NULL, /* mode */
+ NULL, /* format */
+ &operator_code,
+ NULL, /* act */
+ self,
+ error))
+ return NULL;
+
+ return operator_code;
+}
+
+static void
+modem_3gpp_load_operator_code (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+COPS=3,2",
+ 6,
+ FALSE,
+ NULL,
+ NULL);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+COPS?",
+ 6,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Operator Name loading (3GPP interface) */
+
+static gchar *
+modem_3gpp_load_operator_name_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *result;
+ gchar *operator_name = NULL;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!result)
+ return NULL;
+
+ if (!mm_3gpp_parse_cops_read_response (result,
+ NULL, /* mode */
+ NULL, /* format */
+ &operator_name,
+ NULL, /* act */
+ self,
+ error))
+ return NULL;
+
+ mm_3gpp_normalize_operator (&operator_name, MM_MODEM_CHARSET_UNKNOWN, self);
+ return operator_name;
+}
+
+static void
+modem_3gpp_load_operator_name (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+COPS=3,0",
+ 6,
+ FALSE,
+ NULL,
+ NULL);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+COPS?",
+ 6,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* PCOINFO unsolicited event handler */
+
+static void
+altair_pco_info_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemAltairLte *self)
+{
+ const gchar *pco_info;
+ MMPco *pco;
+ g_autoptr(GError) error = NULL;
+
+ pco_info = g_match_info_fetch (match_info, 0);
+
+ /* ignore if empty */
+ if (!pco_info || !pco_info[0])
+ return;
+
+ mm_obj_dbg (self, "parsing vendor PCO info: %s", pco_info);
+ pco = mm_altair_parse_vendor_pco_info (pco_info, &error);
+ if (!pco) {
+ mm_obj_warn (self, "error parsing vendor PCO info: %s", error->message);
+ return;
+ }
+
+ self->priv->pco_list = mm_pco_list_add (self->priv->pco_list, pco);
+ mm_iface_modem_3gpp_update_pco_list (MM_IFACE_MODEM_3GPP (self), self->priv->pco_list);
+ g_object_unref (pco);
+}
+
+/*****************************************************************************/
+/* Generic ports open/close context */
+
+static const gchar *primary_init_sequence[] = {
+ /* Extended numeric codes */
+ "+CMEE=1",
+ NULL
+};
+
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ MMPortSerialAt *primary;
+
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_altair_lte_parent_class)->setup_ports (self);
+
+ primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ if (!primary)
+ return;
+
+ g_object_set (primary,
+ MM_PORT_SERIAL_SEND_DELAY, (guint64) 0,
+ MM_PORT_SERIAL_AT_SEND_LF, TRUE,
+ MM_PORT_SERIAL_AT_INIT_SEQUENCE, primary_init_sequence,
+ NULL);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemAltairLte *
+mm_broadband_modem_altair_lte_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE,
+ 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,
+ /* Altair bearer supports NET only */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE,
+ /* Since this is an LTE-only modem - don't bother query
+ * anything else */
+ MM_IFACE_MODEM_3GPP_CS_NETWORK_SUPPORTED, FALSE,
+ MM_IFACE_MODEM_3GPP_PS_NETWORK_SUPPORTED, FALSE,
+ MM_IFACE_MODEM_3GPP_EPS_NETWORK_SUPPORTED, TRUE,
+ NULL);
+}
+
+gboolean
+mm_broadband_modem_altair_lte_is_sim_refresh_detach_in_progress (MMBroadbandModem *self)
+{
+ return MM_BROADBAND_MODEM_ALTAIR_LTE (self)->priv->sim_refresh_detach_in_progress;
+}
+
+static void
+mm_broadband_modem_altair_lte_init (MMBroadbandModemAltairLte *self)
+{
+
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
+ MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE,
+ MMBroadbandModemAltairLtePrivate);
+
+ self->priv->sim_refresh_regex = g_regex_new ("\\r\\n\\%NOTIFYEV:\\s*\"?SIMREFRESH\"?,?(\\d*)\\r+\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->sim_refresh_detach_in_progress = FALSE;
+ self->priv->sim_refresh_timer_id = 0;
+ self->priv->statcm_regex = g_regex_new ("\\r\\n\\%STATCM:\\s*(\\d*),?(\\d*)\\r+\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->pcoinfo_regex = g_regex_new ("\\r\\n\\%PCOINFO:\\s*(\\d*),([^,\\s]*),([^,\\s]*)\\r+\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemAltairLte *self = MM_BROADBAND_MODEM_ALTAIR_LTE (object);
+
+ if (self->priv->sim_refresh_timer_id)
+ g_source_remove (self->priv->sim_refresh_timer_id);
+ g_regex_unref (self->priv->sim_refresh_regex);
+ g_regex_unref (self->priv->statcm_regex);
+ g_regex_unref (self->priv->pcoinfo_regex);
+ G_OBJECT_CLASS (mm_broadband_modem_altair_lte_parent_class)->finalize (object);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface->modem_power_down = modem_power_down;
+ iface->modem_power_down_finish = modem_power_down_finish;
+ iface->create_bearer = modem_create_bearer;
+ iface->create_bearer_finish = modem_create_bearer_finish;
+ iface->load_unlock_retries = load_unlock_retries;
+ iface->load_unlock_retries_finish = load_unlock_retries_finish;
+ iface->load_current_capabilities = load_current_capabilities;
+ iface->load_current_capabilities_finish = load_current_capabilities_finish;
+ iface->load_supported_bands = load_supported_bands;
+ iface->load_supported_bands_finish = load_supported_bands_finish;
+ iface->load_current_bands = load_current_bands;
+ iface->load_current_bands_finish = load_current_bands_finish;
+
+ iface->load_access_technologies = NULL;
+ iface->load_access_technologies_finish = NULL;
+
+ iface->reset = reset;
+ iface->reset_finish = reset_finish;
+
+ iface->load_supported_charsets = NULL;
+ iface->load_supported_charsets_finish = NULL;
+ iface->setup_charset = NULL;
+ iface->setup_charset_finish = NULL;
+ iface->setup_flow_control = NULL;
+ iface->setup_flow_control_finish = NULL;
+}
+
+static void
+iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface)
+{
+ /* we don't have USSD support */
+ iface->check_support = NULL;
+ iface->check_support_finish = NULL;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish;
+
+ iface->register_in_network = modem_3gpp_register_in_network;
+ iface->register_in_network_finish = modem_3gpp_register_in_network_finish;
+ iface->run_registration_checks = modem_3gpp_run_registration_checks;
+ iface->run_registration_checks_finish = modem_3gpp_run_registration_checks_finish;
+
+ /* Scanning is not currently supported */
+ iface->scan_networks = NULL;
+ iface->scan_networks_finish = NULL;
+
+ /* Additional actions */
+ iface->load_operator_code = modem_3gpp_load_operator_code;
+ iface->load_operator_code_finish = modem_3gpp_load_operator_code_finish;
+ iface->load_operator_name = modem_3gpp_load_operator_name;
+ iface->load_operator_name_finish = modem_3gpp_load_operator_name_finish;
+}
+
+static void
+iface_modem_messaging_init (MMIfaceModemMessaging *iface)
+{
+ /* Currently no messaging is implemented - so skip checking*/
+ iface->check_support = NULL;
+ iface->check_support_finish = NULL;
+}
+
+static void
+mm_broadband_modem_altair_lte_class_init (MMBroadbandModemAltairLteClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemAltairLtePrivate));
+
+ object_class->finalize = finalize;
+ broadband_modem_class->setup_ports = setup_ports;
+
+ /* The Altair LTE modem reboots itself upon receiving an ATZ command. We
+ * need to skip the default implementation in MMBroadbandModem to prevent
+ * an ATZ command from being issued as part of the modem initialization
+ * sequence when enabling the modem. */
+ broadband_modem_class->enabling_modem_init = NULL;
+ broadband_modem_class->enabling_modem_init_finish = NULL;
+}
diff --git a/src/plugins/altair/mm-broadband-modem-altair-lte.h b/src/plugins/altair/mm-broadband-modem-altair-lte.h
new file mode 100644
index 00000000..fc8d362e
--- /dev/null
+++ b/src/plugins/altair/mm-broadband-modem-altair-lte.h
@@ -0,0 +1,53 @@
+/* -*- 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) 2013 Altair Semiconductor
+ *
+ * Author: Ori Inbar <ori.inbar@altair-semi.com>
+ */
+
+#ifndef MM_BROADBAND_MODEM_ALTAIR_LTE_H
+#define MM_BROADBAND_MODEM_ALTAIR_LTE_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE (mm_broadband_modem_altair_lte_get_type ())
+#define MM_BROADBAND_MODEM_ALTAIR_LTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE, MMBroadbandModemAltairLte))
+#define MM_BROADBAND_MODEM_ALTAIR_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE, MMBroadbandModemAltairLteClass))
+#define MM_IS_BROADBAND_MODEM_ALTAIR_LTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE))
+#define MM_IS_BROADBAND_MODEM_ALTAIR_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE))
+#define MM_BROADBAND_MODEM_ALTAIR_LTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_ALTAIR_LTE, MMBroadbandModemAltairLteClass))
+
+typedef struct _MMBroadbandModemAltairLte MMBroadbandModemAltairLte;
+typedef struct _MMBroadbandModemAltairLteClass MMBroadbandModemAltairLteClass;
+typedef struct _MMBroadbandModemAltairLtePrivate MMBroadbandModemAltairLtePrivate;
+
+struct _MMBroadbandModemAltairLte {
+ MMBroadbandModem parent;
+ MMBroadbandModemAltairLtePrivate *priv;
+};
+
+struct _MMBroadbandModemAltairLteClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_altair_lte_get_type (void);
+
+MMBroadbandModemAltairLte *mm_broadband_modem_altair_lte_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+gboolean mm_broadband_modem_altair_lte_is_sim_refresh_detach_in_progress (MMBroadbandModem *self);
+
+#endif /* MM_BROADBAND_MODEM_ALTAIR_LTE_H */
diff --git a/src/plugins/altair/mm-modem-helpers-altair-lte.c b/src/plugins/altair/mm-modem-helpers-altair-lte.c
new file mode 100644
index 00000000..d2fd9af7
--- /dev/null
+++ b/src/plugins/altair/mm-modem-helpers-altair-lte.c
@@ -0,0 +1,259 @@
+/* -*- 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) 2013 Google Inc.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-modem-helpers-altair-lte.h"
+
+#define MM_ALTAIR_IMS_PDN_CID 1
+#define MM_ALTAIR_INTERNET_PDN_CID 3
+
+/*****************************************************************************/
+/* Bands response parser */
+
+GArray *
+mm_altair_parse_bands_response (const gchar *response)
+{
+ gchar **split;
+ GArray *bands;
+ guint i;
+
+ /*
+ * Response is "<band>[,<band>...]"
+ */
+ split = g_strsplit_set (response, ",", -1);
+ if (!split)
+ return NULL;
+
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), g_strv_length (split));
+
+ for (i = 0; split[i]; i++) {
+ guint32 band_value;
+ MMModemBand band;
+
+ band_value = (guint32)strtoul (split[i], NULL, 10);
+ band = MM_MODEM_BAND_EUTRAN_1 - 1 + band_value;
+
+ /* Due to a firmware issue, the modem may incorrectly includes 0 in the
+ * bands response. We thus ignore any band value outside the range of
+ * E-UTRAN operating bands. */
+ if (band >= MM_MODEM_BAND_EUTRAN_1 && band <= MM_MODEM_BAND_EUTRAN_44)
+ g_array_append_val (bands, band);
+ }
+
+ g_strfreev (split);
+
+ return bands;
+}
+
+/*****************************************************************************/
+/* +CEER response parser */
+
+gchar *
+mm_altair_parse_ceer_response (const gchar *response,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ gchar *ceer_response = NULL;
+
+
+ /* First accept an empty response as the no error case. Sometimes, the only
+ * response to the AT+CEER query is an OK.
+ */
+ if (g_strcmp0 ("", response) == 0) {
+ return g_strdup ("");
+ }
+
+ /* The response we are interested in looks so:
+ * +CEER: EPS_AND_NON_EPS_SERVICES_NOT_ALLOWED
+ */
+ r = g_regex_new ("\\+CEER:\\s*(\\w*)?",
+ G_REGEX_RAW,
+ 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match (r, response, 0, &match_info)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not parse +CEER response");
+ return NULL;
+ }
+
+ if (g_match_info_matches (match_info)) {
+ ceer_response = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (!ceer_response)
+ ceer_response = g_strdup ("");
+ }
+
+ return ceer_response;
+}
+
+/*****************************************************************************/
+/* %CGINFO="cid",1 response parser */
+
+gint
+mm_altair_parse_cid (const gchar *response, GError **error)
+{
+ g_autoptr(GRegex) regex = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ guint cid = -1;
+
+ regex = g_regex_new ("\\%CGINFO:\\s*(\\d+)", G_REGEX_RAW, 0, NULL);
+ g_assert (regex);
+ if (!g_regex_match_full (regex, response, strlen (response), 0, 0, &match_info, error))
+ return -1;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &cid))
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse %%CGINFO=\"cid\",1 response");
+
+ return cid;
+}
+
+/*****************************************************************************/
+/* %PCOINFO response parser */
+
+MMPco *
+mm_altair_parse_vendor_pco_info (const gchar *pco_info, GError **error)
+{
+ g_autoptr(GRegex) regex = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ MMPco *pco = NULL;
+ gint num_matches;
+
+ if (!pco_info || !pco_info[0]) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "No PCO info given");
+ return NULL;
+ }
+
+ /* Expected %PCOINFO response:
+ *
+ * Solicited response: %PCOINFO:<mode>,<cid>[,<pcoid>[,<payload>]]
+ * Unsolicited response: %PCOINFO:<cid>,<pcoid>[,<payload>]
+ */
+ regex = g_regex_new ("\\%PCOINFO:(?:\\s*\\d+\\s*,)?(\\d+)\\s*(,([^,\\)]*),([0-9A-Fa-f]*))?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+ 0, NULL);
+ g_assert (regex);
+
+ if (!g_regex_match_full (regex, pco_info, strlen (pco_info), 0, 0, &match_info, error))
+ return NULL;
+
+ num_matches = g_match_info_get_match_count (match_info);
+ if (num_matches != 5) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse substrings, number of matches: %d",
+ num_matches);
+ return NULL;
+ }
+
+ while (g_match_info_matches (match_info)) {
+ guint pco_cid;
+ g_autofree gchar *pco_id = NULL;
+ g_autofree gchar *pco_payload = NULL;
+ g_autofree guint8 *pco_payload_bytes = NULL;
+ gsize pco_payload_bytes_len;
+ guint8 pco_prefix[6];
+ GByteArray *pco_raw;
+ gsize pco_raw_len;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &pco_cid)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse CID from PCO info: '%s'", pco_info);
+ break;
+ }
+
+ /* We are only interested in IMS and Internet PDN PCO. */
+ if (pco_cid != MM_ALTAIR_IMS_PDN_CID && pco_cid != MM_ALTAIR_INTERNET_PDN_CID) {
+ g_match_info_next (match_info, error);
+ continue;
+ }
+
+ pco_id = mm_get_string_unquoted_from_match_info (match_info, 3);
+ if (!pco_id) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse PCO ID from PCO info: '%s'", pco_info);
+ break;
+ }
+
+ if (g_strcmp0 (pco_id, "FF00")) {
+ g_match_info_next (match_info, error);
+ continue;
+ }
+
+ pco_payload = mm_get_string_unquoted_from_match_info (match_info, 4);
+ if (!pco_payload) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse PCO payload from PCO info: '%s'", pco_info);
+ break;
+ }
+
+ pco_payload_bytes = mm_utils_hexstr2bin (pco_payload, -1, &pco_payload_bytes_len, error);
+ if (!pco_payload_bytes) {
+ g_prefix_error (error, "Invalid PCO payload from PCO info '%s': ", pco_info);
+ break;
+ }
+
+ /* Protocol Configuration Options (PCO) is an information element with an
+ * identifier (IEI) 0x27 and contains between 3 and 253 octets. See 3GPP TS
+ * 24.008 for more details on PCO.
+ *
+ * NOTE: The standard uses one-based indexing, but to better correlate to the
+ * code, zero-based indexing is used in the description hereinafter.
+ *
+ * Octet | Value
+ * --------+--------------------------------------------
+ * 0 | PCO IEI (= 0x27)
+ * 1 | Length of PCO contents (= total length - 2)
+ * 2 | bit 7 : ext
+ * | bit 6 to 3 : spare (= 0b0000)
+ * | bit 2 to 0 : Configuration protocol
+ * 3 to 4 | Element 1 ID
+ * 5 | Length of element 1 contents
+ * 6 to m | Element 1 contents
+ * ... |
+ */
+ pco_raw_len = sizeof (pco_prefix) + pco_payload_bytes_len;
+ pco_prefix[0] = 0x27;
+ pco_prefix[1] = pco_raw_len - 2;
+ pco_prefix[2] = 0x80;
+ /* Verizon uses element ID 0xFF00 for carrier-specific PCO content. */
+ pco_prefix[3] = 0xFF;
+ pco_prefix[4] = 0x00;
+ pco_prefix[5] = pco_payload_bytes_len;
+
+ pco_raw = g_byte_array_sized_new (pco_raw_len);
+ g_byte_array_append (pco_raw, pco_prefix, sizeof (pco_prefix));
+ g_byte_array_append (pco_raw, pco_payload_bytes, pco_payload_bytes_len);
+
+ pco = mm_pco_new ();
+ mm_pco_set_session_id (pco, pco_cid);
+ mm_pco_set_complete (pco, TRUE);
+ mm_pco_set_data (pco, pco_raw->data, pco_raw->len);
+ break;
+ }
+
+ return pco;
+}
diff --git a/src/plugins/altair/mm-modem-helpers-altair-lte.h b/src/plugins/altair/mm-modem-helpers-altair-lte.h
new file mode 100644
index 00000000..ff7f64b0
--- /dev/null
+++ b/src/plugins/altair/mm-modem-helpers-altair-lte.h
@@ -0,0 +1,38 @@
+/* -*- 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) 2013 Google Inc.
+ *
+ */
+
+#ifndef MM_MODEM_HELPERS_ALTAIR_H
+#define MM_MODEM_HELPERS_ALTAIR_H
+
+#include <glib.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+/* Bands response parser */
+GArray *mm_altair_parse_bands_response (const gchar *response);
+
+/* +CEER response parser */
+gchar *mm_altair_parse_ceer_response (const gchar *response,
+ GError **error);
+
+/* %CGINFO="cid",1 response parser */
+gint mm_altair_parse_cid (const gchar *response, GError **error);
+
+/* %PCOINFO response parser */
+MMPco *mm_altair_parse_vendor_pco_info (const gchar *pco_info, GError **error);
+
+#endif /* MM_MODEM_HELPERS_ALTAIR_H */
diff --git a/src/plugins/altair/mm-plugin-altair-lte.c b/src/plugins/altair/mm-plugin-altair-lte.c
new file mode 100644
index 00000000..e58fe176
--- /dev/null
+++ b/src/plugins/altair/mm-plugin-altair-lte.c
@@ -0,0 +1,101 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2013 Altair Semiconductor
+ *
+ * Author: Ori Inbar <ori.inbar@altair-semi.com>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#include "mm-plugin-altair-lte.h"
+#include "mm-private-boxed-types.h"
+#include "mm-broadband-modem-altair-lte.h"
+#include "mm-log.h"
+
+G_DEFINE_TYPE (MMPluginAltairLte, mm_plugin_altair_lte, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+/* Custom commands for AT probing */
+
+/* Increase the response timeout for probe commands since some altair modems
+ take longer to respond after a reset.
+ */
+static const MMPortProbeAtCommand custom_at_probe[] = {
+ { "AT", 7, mm_port_probe_response_processor_is_at },
+ { "AT", 7, mm_port_probe_response_processor_is_at },
+ { "AT", 7, mm_port_probe_response_processor_is_at },
+ { NULL }
+};
+
+/*****************************************************************************/
+
+static MMBaseModem *
+create_modem (MMPlugin *self,
+ const gchar *uid,
+ const gchar **drivers,
+ guint16 vendor,
+ guint16 product,
+ guint16 subsystem_vendor,
+ GList *probes,
+ GError **error)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_altair_lte_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", NULL };
+ static const mm_uint16_pair products[] = {
+ { 0x216f, 0x0047 }, /* Altair NPe */
+ { 0, 0 }
+ };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_ALTAIR_LTE,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_PRODUCT_IDS, products,
+ MM_PLUGIN_CUSTOM_AT_PROBE, custom_at_probe,
+ MM_PLUGIN_ALLOWED_SINGLE_AT, TRUE,
+ MM_PLUGIN_SEND_LF, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_altair_lte_init (MMPluginAltairLte *self)
+{
+}
+
+static void
+mm_plugin_altair_lte_class_init (MMPluginAltairLteClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/altair/mm-plugin-altair-lte.h b/src/plugins/altair/mm-plugin-altair-lte.h
new file mode 100644
index 00000000..385a5cc2
--- /dev/null
+++ b/src/plugins/altair/mm-plugin-altair-lte.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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2013 Altair Semiconductor
+ *
+ * Author: Ori Inbar <ori.inbar@altair-semi.com>
+ */
+
+#ifndef MM_PLUGIN_ALTAIR_LTE_H
+#define MM_PLUGIN_ALTAIR_LTE_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_ALTAIR_LTE (mm_plugin_altair_lte_get_type ())
+#define MM_PLUGIN_ALTAIR_LTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_ALTAIR_LTE, MMPluginAltairLte))
+#define MM_PLUGIN_ALTAIR_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_ALTAIR_LTE, MMPluginAltairLteClass))
+#define MM_IS_PLUGIN_ALTAIR_LTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_ALTAIR_LTE))
+#define MM_IS_PLUGIN_ALTAIR_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_ALTAIR_LTE))
+#define MM_PLUGIN_ALTAIR_LTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_ALTAIR_LTE, MMPluginAltairLteClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginAltairLte;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginAltairLteClass;
+
+GType mm_plugin_altair_lte_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_ALTAIR_LTE_H */
diff --git a/src/plugins/altair/tests/test-modem-helpers-altair-lte.c b/src/plugins/altair/tests/test-modem-helpers-altair-lte.c
new file mode 100644
index 00000000..da9eaf32
--- /dev/null
+++ b/src/plugins/altair/tests/test-modem-helpers-altair-lte.c
@@ -0,0 +1,189 @@
+/* -*- 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) 2013 Google Inc.
+ *
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <locale.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-modem-helpers-altair-lte.h"
+
+/*****************************************************************************/
+/* Test bands response parsing */
+
+static void
+test_parse_bands (void)
+{
+ GArray *bands;
+
+ bands = mm_altair_parse_bands_response ("");
+ g_assert (bands != NULL);
+ g_assert_cmpuint (bands->len, ==, 0);
+ g_array_free (bands, TRUE);
+
+ /* 0 and 45 are outside the range of E-UTRAN operating bands and should be ignored. */
+ bands = mm_altair_parse_bands_response ("0, 0, 1, 4,13,44,45");
+ g_assert (bands != NULL);
+ g_assert_cmpuint (bands->len, ==, 4);
+ g_assert_cmpuint (g_array_index (bands, MMModemBand, 0), ==, MM_MODEM_BAND_EUTRAN_1);
+ g_assert_cmpuint (g_array_index (bands, MMModemBand, 1), ==, MM_MODEM_BAND_EUTRAN_4);
+ g_assert_cmpuint (g_array_index (bands, MMModemBand, 2), ==, MM_MODEM_BAND_EUTRAN_13);
+ g_assert_cmpuint (g_array_index (bands, MMModemBand, 3), ==, MM_MODEM_BAND_EUTRAN_44);
+ g_array_free (bands, TRUE);
+}
+
+/*****************************************************************************/
+/* Test +CEER responses */
+
+typedef struct {
+ const gchar *str;
+ const gchar *result;
+} CeerTest;
+
+static const CeerTest ceer_tests[] = {
+ { "", "" }, /* Special case, sometimes the response is empty, treat it as a good response. */
+ { "+CEER:", "" },
+ { "+CEER: EPS_AND_NON_EPS_SERVICES_NOT_ALLOWED", "EPS_AND_NON_EPS_SERVICES_NOT_ALLOWED" },
+ { "+CEER: NO_SUITABLE_CELLS_IN_TRACKING_AREA", "NO_SUITABLE_CELLS_IN_TRACKING_AREA" },
+ { "WRONG RESPONSE", NULL },
+ { NULL, NULL }
+};
+
+static void
+test_ceer (void)
+{
+ guint i;
+
+ for (i = 0; ceer_tests[i].str; ++i) {
+ GError *error = NULL;
+ gchar *result;
+
+ result = mm_altair_parse_ceer_response (ceer_tests[i].str, &error);
+ if (ceer_tests[i].result) {
+ g_assert_cmpstr (ceer_tests[i].result, ==, result);
+ g_assert_no_error (error);
+ g_free (result);
+ }
+ else {
+ g_assert (result == NULL);
+ g_assert (error != NULL);
+ g_error_free (error);
+ }
+ }
+}
+
+static void
+test_parse_cid (void)
+{
+ g_assert (mm_altair_parse_cid ("%CGINFO: 2", NULL) == 2);
+ g_assert (mm_altair_parse_cid ("%CGINFO:blah", NULL) == -1);
+}
+
+/*****************************************************************************/
+/* Test %PCOINFO responses */
+
+typedef struct {
+ const gchar *pco_info;
+ guint32 session_id;
+ gsize pco_data_size;
+ guint8 pco_data[50];
+} TestValidPcoInfo;
+
+static const TestValidPcoInfo good_pco_infos[] = {
+ /* Valid PCO values */
+ { "%PCOINFO: 1,1,FF00,13018400", 1, 10,
+ { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x00 } },
+ { "%PCOINFO: 1,1,FF00,13018403", 1, 10,
+ { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x03 } },
+ { "%PCOINFO: 1,1,FF00,13018405", 1, 10,
+ { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x05 } },
+ { "%PCOINFO: 1,3,FF00,13018400", 3, 10,
+ { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x00 } },
+ { "%PCOINFO: 1,3,FF00,13018403", 3, 10,
+ { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x03 } },
+ { "%PCOINFO: 1,3,FF00,13018405", 3, 10,
+ { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x05 } },
+ { "%PCOINFO:1,FF00,13018400", 1, 10,
+ { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x00 } },
+ { "%PCOINFO:1,FF00,13018403", 1, 10,
+ { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x03 } },
+ { "%PCOINFO:1,FF00,13018405", 1, 10,
+ { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x05 } },
+ /* Different payload */
+ { "%PCOINFO: 1,3,FF00,130185", 3, 9,
+ { 0x27, 0x07, 0x80, 0xFF, 0x00, 0x03, 0x13, 0x01, 0x85 } },
+ /* Multiline PCO info */
+ { "%PCOINFO: 1,2,FF00,13018400\r\n%PCOINFO: 1,3,FF00,13018403", 3, 10,
+ { 0x27, 0x08, 0x80, 0xFF, 0x00, 0x04, 0x13, 0x01, 0x84, 0x03 } },
+};
+
+static const gchar *bad_pco_infos[] = {
+ /* Different container */
+ "%PCOINFO: 1,3,F000,13018401",
+ /* Ingood CID */
+ "%PCOINFO: 1,2,FF00,13018401",
+ /* Bad PCO info */
+ "%PCOINFO: blah,blah,FF00,13018401",
+ /* Bad PCO payload */
+ "%PCOINFO: 1,1,FF00,130184011",
+};
+
+static void
+test_parse_vendor_pco_info (void)
+{
+ MMPco *pco;
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (good_pco_infos); ++i) {
+ const guint8 *pco_data;
+ gsize pco_data_size;
+
+ pco = mm_altair_parse_vendor_pco_info (good_pco_infos[i].pco_info, NULL);
+ g_assert (pco != NULL);
+ g_assert_cmpuint (mm_pco_get_session_id (pco), ==, good_pco_infos[i].session_id);
+ g_assert (mm_pco_is_complete (pco));
+ pco_data = mm_pco_get_data (pco, &pco_data_size);
+ g_assert (pco_data != NULL);
+ g_assert_cmpuint (pco_data_size, ==, good_pco_infos[i].pco_data_size);
+ g_assert_cmpint (memcmp (pco_data, good_pco_infos[i].pco_data, pco_data_size), ==, 0);
+ g_object_unref (pco);
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (bad_pco_infos); ++i) {
+ pco = mm_altair_parse_vendor_pco_info (bad_pco_infos[i], NULL);
+ g_assert (pco == NULL);
+ }
+}
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/altair/parse_bands", test_parse_bands);
+ g_test_add_func ("/MM/altair/ceer", test_ceer);
+ g_test_add_func ("/MM/altair/parse_cid", test_parse_cid);
+ g_test_add_func ("/MM/altair/parse_vendor_pco_info", test_parse_vendor_pco_info);
+
+ return g_test_run ();
+}
diff --git a/src/plugins/anydata/mm-broadband-modem-anydata.c b/src/plugins/anydata/mm-broadband-modem-anydata.c
new file mode 100644
index 00000000..36d72e56
--- /dev/null
+++ b/src/plugins/anydata/mm-broadband-modem-anydata.c
@@ -0,0 +1,355 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-serial-parsers.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-errors-types.h"
+#include "mm-base-modem-at.h"
+#include "mm-broadband-modem-anydata.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-cdma.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_cdma_init (MMIfaceModemCdma *iface);
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemAnydata, mm_broadband_modem_anydata, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_init))
+
+/*****************************************************************************/
+/* Detailed registration state (CDMA interface) */
+typedef struct {
+ MMModemCdmaRegistrationState detailed_cdma1x_state;
+ MMModemCdmaRegistrationState detailed_evdo_state;
+} DetailedRegistrationStateResults;
+
+static gboolean
+get_detailed_registration_state_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ MMModemCdmaRegistrationState *detailed_cdma1x_state,
+ MMModemCdmaRegistrationState *detailed_evdo_state,
+ GError **error)
+{
+ DetailedRegistrationStateResults *results;
+
+ results = g_task_propagate_pointer (G_TASK (res), error);
+ if (!results)
+ return FALSE;
+
+ *detailed_cdma1x_state = results->detailed_cdma1x_state;
+ *detailed_evdo_state = results->detailed_evdo_state;
+ g_free (results);
+ return TRUE;
+}
+
+static void
+hstate_ready (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ DetailedRegistrationStateResults *results;
+ GError *error = NULL;
+ const gchar *response;
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+
+ results = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ /* Leave superclass' reg state alone if AT*HSTATE isn't supported */
+ g_error_free (error);
+
+ g_task_return_pointer (task, g_memdup (results, sizeof (*results)), g_free);
+ g_object_unref (task);
+ return;
+ }
+
+ response = mm_strip_tag (response, "*HSTATE:");
+
+ /* Format is "<at state>,<session state>,<channel>,<pn>,<EcIo>,<rssi>,..." */
+ r = g_regex_new ("\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*([^,\\)]*)\\s*,\\s*([^,\\)]*)\\s*,.*",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match (r, response, 0, &match_info);
+ if (g_match_info_get_match_count (match_info) >= 6) {
+ guint val = 0;
+ gint dbm = 0;
+
+ /* dBm is between -106 (worst) and -20.7 (best) */
+ mm_get_int_from_match_info (match_info, 6, &dbm);
+
+ /* Parse the EVDO radio state */
+ if (mm_get_uint_from_match_info (match_info, 1, &val)) {
+ switch (val) {
+ case 3: /* IDLE */
+ /* If IDLE and the EVDO dBm is -105 or lower, assume no service.
+ * It may be that IDLE actually means NO SERVICE too; not sure.
+ */
+ if (dbm > -105)
+ results->detailed_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
+ break;
+ case 4: /* ACCESS */
+ case 5: /* CONNECT */
+ results->detailed_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
+ break;
+ default:
+ mm_obj_warn (self, "unknown *HSTATE (%d); assuming no service", val);
+ /* fall through */
+ case 0: /* NO SERVICE */
+ case 1: /* ACQUISITION */
+ case 2: /* SYNC */
+ break;
+ }
+ }
+ }
+
+ g_task_return_pointer (task, g_memdup (results, sizeof (*results)), g_free);
+ g_object_unref (task);
+}
+
+static void
+state_ready (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ DetailedRegistrationStateResults *results;
+ GError *error = NULL;
+ const gchar *response;
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ results = g_task_get_task_data (task);
+ response = mm_strip_tag (response, "*STATE:");
+
+ /* Format is "<channel>,<pn>,<sid>,<nid>,<state>,<rssi>,..." */
+ r = g_regex_new ("\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*([^,\\)]*)\\s*,.*",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match (r, response, 0, &match_info);
+ if (g_match_info_get_match_count (match_info) >= 6) {
+ guint val = 0;
+ gint dbm = 0;
+
+ /* dBm is between -106 (worst) and -20.7 (best) */
+ mm_get_int_from_match_info (match_info, 6, &dbm);
+
+ /* Parse the 1x radio state */
+ if (mm_get_uint_from_match_info (match_info, 5, &val)) {
+ switch (val) {
+ case 1: /* IDLE */
+ /* If IDLE and the 1X dBm is -105 or lower, assume no service.
+ * It may be that IDLE actually means NO SERVICE too; not sure.
+ */
+ if (dbm > -105)
+ results->detailed_cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
+ break;
+ case 2: /* ACCESS */
+ case 3: /* PAGING */
+ case 4: /* TRAFFIC */
+ results->detailed_cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
+ break;
+ default:
+ mm_obj_warn (self, "unknown *HSTATE (%d); assuming no service", val);
+ /* fall through */
+ case 0: /* NO SERVICE */
+ break;
+ }
+ }
+ }
+
+ /* Try for EVDO state too */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "*HSTATE?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)hstate_ready,
+ task);
+}
+
+static void
+get_detailed_registration_state (MMIfaceModemCdma *self,
+ MMModemCdmaRegistrationState cdma1x_state,
+ MMModemCdmaRegistrationState evdo_state,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ DetailedRegistrationStateResults *results;
+ GTask *task;
+
+ results = g_new (DetailedRegistrationStateResults, 1);
+ results->detailed_cdma1x_state = cdma1x_state;
+ results->detailed_evdo_state = evdo_state;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, results, g_free);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "*STATE?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)state_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Reset (Modem interface) */
+
+static gboolean
+reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "*RESET",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ MMPortSerialAt *ports[2];
+ g_autoptr(GRegex) active_regex = NULL;
+ g_autoptr(GRegex) inactive_regex = NULL;
+ g_autoptr(GRegex) dormant_regex = NULL;
+ g_autoptr(GRegex) offline_regex = NULL;
+ g_autoptr(GRegex) regreq_regex = NULL;
+ g_autoptr(GRegex) authreq_regex = NULL;
+ guint i;
+
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_anydata_parent_class)->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));
+
+ /* Data call has connected */
+ active_regex = g_regex_new ("\\r\\n\\*ACTIVE:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ /* Data call disconnected */
+ inactive_regex = g_regex_new ("\\r\\n\\*INACTIVE:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ /* Modem is now dormant */
+ dormant_regex = g_regex_new ("\\r\\n\\*DORMANT:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ /* Network acquisition fail */
+ offline_regex = g_regex_new ("\\r\\n\\*OFFLINE:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ /* Registration fail */
+ regreq_regex = g_regex_new ("\\r\\n\\*REGREQ:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ /* Authentication fail */
+ authreq_regex = g_regex_new ("\\r\\n\\*AUTHREQ:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ /* Now reset the unsolicited messages */
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ /* Data state notifications */
+ mm_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]), active_regex, NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]), inactive_regex, NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]), dormant_regex, NULL, NULL, NULL);
+
+ /* Abnormal state notifications
+ *
+ * FIXME: set 1X/EVDO registration state to UNKNOWN when these
+ * notifications are received?
+ */
+ mm_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]), offline_regex, NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]), regreq_regex, NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]), authreq_regex, NULL, NULL, NULL);
+ }
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemAnydata *
+mm_broadband_modem_anydata_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_ANYDATA,
+ 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,
+ NULL);
+}
+
+static void
+mm_broadband_modem_anydata_init (MMBroadbandModemAnydata *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface->reset = reset;
+ iface->reset_finish = reset_finish;
+}
+
+static void
+iface_modem_cdma_init (MMIfaceModemCdma *iface)
+{
+ iface->get_cdma1x_serving_system = NULL;
+ iface->get_cdma1x_serving_system_finish = NULL;
+ iface->get_detailed_registration_state = get_detailed_registration_state;
+ iface->get_detailed_registration_state_finish = get_detailed_registration_state_finish;
+}
+
+static void
+mm_broadband_modem_anydata_class_init (MMBroadbandModemAnydataClass *klass)
+{
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/anydata/mm-broadband-modem-anydata.h b/src/plugins/anydata/mm-broadband-modem-anydata.h
new file mode 100644
index 00000000..94145623
--- /dev/null
+++ b/src/plugins/anydata/mm-broadband-modem-anydata.h
@@ -0,0 +1,49 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 - Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_MODEM_ANYDATA_H
+#define MM_BROADBAND_MODEM_ANYDATA_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_ANYDATA (mm_broadband_modem_anydata_get_type ())
+#define MM_BROADBAND_MODEM_ANYDATA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_ANYDATA, MMBroadbandModemAnydata))
+#define MM_BROADBAND_MODEM_ANYDATA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_ANYDATA, MMBroadbandModemAnydataClass))
+#define MM_IS_BROADBAND_MODEM_ANYDATA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_ANYDATA))
+#define MM_IS_BROADBAND_MODEM_ANYDATA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_ANYDATA))
+#define MM_BROADBAND_MODEM_ANYDATA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_ANYDATA, MMBroadbandModemAnydataClass))
+
+typedef struct _MMBroadbandModemAnydata MMBroadbandModemAnydata;
+typedef struct _MMBroadbandModemAnydataClass MMBroadbandModemAnydataClass;
+
+struct _MMBroadbandModemAnydata {
+ MMBroadbandModem parent;
+};
+
+struct _MMBroadbandModemAnydataClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_anydata_get_type (void);
+
+MMBroadbandModemAnydata *mm_broadband_modem_anydata_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_ANYDATA_H */
diff --git a/src/plugins/anydata/mm-plugin-anydata.c b/src/plugins/anydata/mm-plugin-anydata.c
new file mode 100644
index 00000000..4b8f0a84
--- /dev/null
+++ b/src/plugins/anydata/mm-plugin-anydata.c
@@ -0,0 +1,97 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-plugin-anydata.h"
+#include "mm-broadband-modem-anydata.h"
+
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginAnydata, mm_plugin_anydata, 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 AnyDATA modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_anydata_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", NULL };
+ static const guint16 vendor_ids[] = { 0x16d5, 0 };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_ANYDATA,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_REQUIRED_QCDM, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_anydata_init (MMPluginAnydata *self)
+{
+}
+
+static void
+mm_plugin_anydata_class_init (MMPluginAnydataClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/anydata/mm-plugin-anydata.h b/src/plugins/anydata/mm-plugin-anydata.h
new file mode 100644
index 00000000..765c19a5
--- /dev/null
+++ b/src/plugins/anydata/mm-plugin-anydata.h
@@ -0,0 +1,43 @@
+
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_PLUGIN_ANYDATA_H
+#define MM_PLUGIN_ANYDATA_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_ANYDATA (mm_plugin_anydata_get_type ())
+#define MM_PLUGIN_ANYDATA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_ANYDATA, MMPluginAnydata))
+#define MM_PLUGIN_ANYDATA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_ANYDATA, MMPluginAnydataClass))
+#define MM_IS_PLUGIN_ANYDATA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_ANYDATA))
+#define MM_IS_PLUGIN_ANYDATA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_ANYDATA))
+#define MM_PLUGIN_ANYDATA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_ANYDATA, MMPluginAnydataClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginAnydata;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginAnydataClass;
+
+GType mm_plugin_anydata_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_ANYDATA_H */
diff --git a/src/plugins/broadmobi/77-mm-broadmobi-port-types.rules b/src/plugins/broadmobi/77-mm-broadmobi-port-types.rules
new file mode 100644
index 00000000..685e1467
--- /dev/null
+++ b/src/plugins/broadmobi/77-mm-broadmobi-port-types.rules
@@ -0,0 +1,16 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_broadmobi_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="2020", GOTO="mm_broadmobi_port_types"
+GOTO="mm_broadmobi_port_types_end"
+
+LABEL="mm_broadmobi_port_types"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# BroadMobi BM818
+ATTRS{idVendor}=="2020", ATTRS{idProduct}=="2060", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="2020", ATTRS{idProduct}=="2060", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="2020", ATTRS{idProduct}=="2060", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="2020", ATTRS{idProduct}=="2060", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+LABEL="mm_broadmobi_port_types_end" \ No newline at end of file
diff --git a/src/plugins/broadmobi/mm-plugin-broadmobi.c b/src/plugins/broadmobi/mm-plugin-broadmobi.c
new file mode 100644
index 00000000..805663fe
--- /dev/null
+++ b/src/plugins/broadmobi/mm-plugin-broadmobi.c
@@ -0,0 +1,95 @@
+/* -*- 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 <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-plugin-broadmobi.h"
+#include "mm-broadband-modem.h"
+
+#if defined WITH_QMI
+# include "mm-broadband-modem-qmi.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginBroadmobi, mm_plugin_broadmobi, 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 BroadMobi modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_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", NULL };
+ static const guint16 vendor_ids[] = { 0x2020, 0 };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_BROADMOBI,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_REQUIRED_QCDM, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_broadmobi_init (MMPluginBroadmobi *self)
+{
+}
+
+static void
+mm_plugin_broadmobi_class_init (MMPluginBroadmobiClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/broadmobi/mm-plugin-broadmobi.h b/src/plugins/broadmobi/mm-plugin-broadmobi.h
new file mode 100644
index 00000000..1f46cfce
--- /dev/null
+++ b/src/plugins/broadmobi/mm-plugin-broadmobi.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) 2020 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_PLUGIN_BROADMOBI_H
+#define MM_PLUGIN_BROADMOBI_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_BROADMOBI (mm_plugin_broadmobi_get_type ())
+#define MM_PLUGIN_BROADMOBI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_BROADMOBI, MMPluginBroadmobi))
+#define MM_PLUGIN_BROADMOBI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_BROADMOBI, MMPluginBroadmobiClass))
+#define MM_IS_PLUGIN_BROADMOBI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_BROADMOBI))
+#define MM_IS_PLUGIN_BROADMOBI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_BROADMOBI))
+#define MM_PLUGIN_BROADMOBI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_BROADMOBI, MMPluginBroadmobiClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginBroadmobi;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginBroadmobiClass;
+
+GType mm_plugin_broadmobi_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_BROADMOBI_H */
diff --git a/src/plugins/cinterion/77-mm-cinterion-port-types.rules b/src/plugins/cinterion/77-mm-cinterion-port-types.rules
new file mode 100644
index 00000000..c1a9bc4a
--- /dev/null
+++ b/src/plugins/cinterion/77-mm-cinterion-port-types.rules
@@ -0,0 +1,71 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_cinterion_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1e2d", GOTO="mm_cinterion_port_types"
+GOTO="mm_cinterion_port_types_end"
+
+LABEL="mm_cinterion_port_types"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# PHS8
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0053", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+
+# PLS8 port types
+# ttyACM0 (if #0): AT port
+# ttyACM1 (if #2): AT port
+# ttyACM2 (if #4): GPS data port
+# ttyACM3 (if #6): unknown
+# ttyACM4 (if #8): unknown
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="08", ENV{ID_MM_PORT_IGNORE}="1"
+
+# PLS62 family non-mbim enumeration uses alternate settings for 2G band management
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{ID_MM_CINTERION_MODEM_FAMILY}="imt"
+# PLS62 family non-mbim enumeration
+# ttyACM0 (if #0): AT port
+# ttyACM1 (if #2): AT port
+# ttyACM2 (if #4): can be AT or GNSS in some models
+# ttyACM3 (if #6): AT port (but just ignore)
+# ttyACM4 (if #8): DIAG/QCDM
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005b", ENV{.MM_USBIFNUM}=="08", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+
+# PLS62 family mbim enumeration
+# ttyACM0 (if #0): AT port
+# ttyACM1 (if #2): AT port
+# ttyACM2 (if #4): can be AT or GNSS in some models
+# ttyACM3 (if #6): AT port (but just ignore)
+# ttyACM4 (if #8): DIAG/QCDM
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005d", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005d", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005d", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005d", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="005d", ENV{.MM_USBIFNUM}=="08", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+
+# PLS63
+# ttyACM0 (if #0): AT port
+# ttyACM1 (if #2): AT port
+# ttyACM2 (if #4): GPS data port
+# ttyACM3 (if #6): DIAG/QCDM
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0069", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0069", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0069", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0069", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+
+# PLS83
+# ttyACM0 (if #0): AT port
+# ttyACM1 (if #2): AT port
+# ttyACM2 (if #4): GPS data port
+# ttyACM3 (if #6): DIAG/QCDM
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="006F", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="006F", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="006F", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="006F", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+
+LABEL="mm_cinterion_port_types_end"
diff --git a/src/plugins/cinterion/mm-broadband-bearer-cinterion.c b/src/plugins/cinterion/mm-broadband-bearer-cinterion.c
new file mode 100644
index 00000000..85fbf69c
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-bearer-cinterion.c
@@ -0,0 +1,796 @@
+/* -*- 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) 2016 Trimble Navigation Limited
+ * Author: Matthew Stanger <matthew_stanger@trimble.com>
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <arpa/inet.h>
+#include <ModemManager.h>
+#include "mm-base-modem-at.h"
+#include "mm-broadband-bearer-cinterion.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-cinterion.h"
+#include "mm-daemon-enums-types.h"
+
+G_DEFINE_TYPE (MMBroadbandBearerCinterion, mm_broadband_bearer_cinterion, MM_TYPE_BROADBAND_BEARER)
+
+/*****************************************************************************/
+/* WWAN interface mapping */
+
+typedef struct {
+ guint swwan_index;
+ guint usb_iface_num;
+} UsbInterfaceConfig;
+
+/* Map SWWAN index, USB interface number and preferred PDP context.
+ *
+ * The expected USB interface mapping is:
+ * INTERFACE=usb0 -> ID_USB_INTERFACE_NUM=0a
+ * INTERFACE=usb1 -> ID_USB_INTERFACE_NUM=0c
+ * INTERFACE=usb0 -> ID_USB_INTERFACE_NUM=08 (PLSx3w)
+ */
+static const UsbInterfaceConfig usb_interface_configs[] = {
+ {
+ .swwan_index = 1,
+ .usb_iface_num = 0x0a,
+ },
+ {
+ .swwan_index = 2,
+ .usb_iface_num = 0x0c,
+ },
+ {
+ .swwan_index = 1,
+ .usb_iface_num = 0x08,
+ },
+};
+
+static gint
+get_usb_interface_config_index (MMPort *data,
+ GError **error)
+{
+ guint usb_iface_num;
+ guint i;
+
+ usb_iface_num = (guint) mm_kernel_device_get_interface_number (mm_port_peek_kernel_device (data));
+
+ for (i = 0; i < G_N_ELEMENTS (usb_interface_configs); i++) {
+ if (usb_interface_configs[i].usb_iface_num == usb_iface_num)
+ return (gint) i;
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unsupported WWAN interface: unexpected interface number: 0x%02x", usb_iface_num);
+ return -1;
+}
+
+/*****************************************************************************/
+/* Connection status loading
+ * NOTE: only CONNECTED or DISCONNECTED should be reported here.
+ */
+
+static MMBearerConnectionStatus
+load_connection_status_finish (MMBaseBearer *bearer,
+ GAsyncResult *res,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ gssize aux;
+
+ aux = g_task_propagate_int (G_TASK (res), &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return MM_BEARER_CONNECTION_STATUS_UNKNOWN;
+ }
+ return (MMBearerConnectionStatus) aux;
+}
+
+typedef struct {
+ guint cid;
+ guint retries;
+ gboolean delay;
+ gboolean retry;
+} LoadConnectionContext;
+
+static void
+load_connection_context_free (LoadConnectionContext *ctx)
+{
+ g_slice_free (LoadConnectionContext, ctx);
+}
+
+static gboolean swwan_check_status (GTask *task);
+
+static void
+swwan_check_status_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerCinterion *self;
+ const gchar *response;
+ GError *error = NULL;
+ MMBearerConnectionStatus status;
+ LoadConnectionContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ goto out;
+ }
+
+ status = mm_cinterion_parse_swwan_response (response, ctx->cid, self, &error);
+ if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) {
+ g_task_return_error (task, error);
+ goto out;
+ } else if (ctx->retry && status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) {
+ mm_obj_dbg (self, "check status retry");
+ if (ctx->retries == 0) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "CID %u status check retry exceeded", ctx->cid);
+ goto out;
+ } else {
+ if (ctx->delay) {
+ g_timeout_add_seconds (1, (GSourceFunc)swwan_check_status, task);
+ } else {
+ g_idle_add ((GSourceFunc)swwan_check_status, task);
+ }
+ ctx->retries--;
+ return;
+ }
+ }
+
+ g_assert (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED ||
+ status == MM_BEARER_CONNECTION_STATUS_CONNECTED);
+ g_task_return_int (task, (gssize) status);
+
+out:
+ g_object_unref (task);
+}
+
+static gboolean
+swwan_check_status (GTask *task)
+{
+ MMBroadbandBearerCinterion *bearer;
+ g_autoptr(MMBaseModem) modem = NULL;
+
+ bearer = g_task_get_source_object (task);
+ g_object_get (bearer,
+ MM_BASE_BEARER_MODEM, &modem,
+ NULL);
+ mm_base_modem_at_command (modem,
+ "^SWWAN?",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback) swwan_check_status_ready,
+ task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+load_connection_status_by_cid (MMBroadbandBearerCinterion *bearer,
+ gint cid,
+ gboolean delay,
+ gboolean retry,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ LoadConnectionContext *ctx;
+
+ task = g_task_new (bearer, NULL, callback, user_data);
+ if (cid == MM_3GPP_PROFILE_ID_UNKNOWN) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown profile id to check connection status");
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_slice_new0 (LoadConnectionContext);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) load_connection_context_free);
+
+ /* Setup context */
+ ctx->cid = cid;
+ ctx->retries = 5;
+ ctx->delay = delay;
+ ctx->retry = retry;
+
+ /* Some modems require a delay before querying the SWWAN status
+ * This is only needed for step DIAL_3GPP_CONTEXT_STEP_VALIDATE_CONNECTION
+ * and DISCONNECT_3GPP_CONTEXT_STEP_CONNECTION_STATUS. */
+ if (delay) {
+ g_timeout_add_seconds (1, (GSourceFunc)swwan_check_status, task);
+ } else {
+ g_idle_add ((GSourceFunc)swwan_check_status, task);
+ }
+}
+
+static void
+load_connection_status (MMBaseBearer *bearer,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ load_connection_status_by_cid (MM_BROADBAND_BEARER_CINTERION (bearer),
+ mm_base_bearer_get_profile_id (bearer),
+ FALSE,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/******************************************************************************/
+/* Dial 3GPP */
+
+typedef enum {
+ DIAL_3GPP_CONTEXT_STEP_FIRST = 0,
+ DIAL_3GPP_CONTEXT_STEP_AUTH,
+ DIAL_3GPP_CONTEXT_STEP_START_SWWAN,
+ DIAL_3GPP_CONTEXT_STEP_VALIDATE_CONNECTION,
+ DIAL_3GPP_CONTEXT_STEP_LAST,
+} Dial3gppContextStep;
+
+typedef struct {
+ MMBroadbandBearerCinterion *self;
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ guint cid;
+ MMPort *data;
+ gint usb_interface_config_index;
+ Dial3gppContextStep step;
+} Dial3gppContext;
+
+static void
+dial_3gpp_context_free (Dial3gppContext *ctx)
+{
+ g_object_unref (ctx->modem);
+ g_object_unref (ctx->self);
+ g_object_unref (ctx->primary);
+ g_clear_object (&ctx->data);
+ g_slice_free (Dial3gppContext, ctx);
+}
+
+static MMPort *
+dial_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return MM_PORT (g_task_propagate_pointer (G_TASK (res), error));
+}
+
+static void dial_3gpp_context_step (GTask *task);
+
+static void
+dial_connection_status_ready (MMBroadbandBearerCinterion *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBearerConnectionStatus status;
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+
+ ctx = (Dial3gppContext *) g_task_get_task_data (task);
+
+ status = load_connection_status_finish (MM_BASE_BEARER (self), res, &error);
+ if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "CID %u is reported disconnected", ctx->cid);
+ g_object_unref (task);
+ return;
+ }
+
+ g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED);
+
+ /* Go to next step */
+ ctx->step++;
+ dial_3gpp_context_step (task);
+}
+
+static void
+common_dial_operation_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+
+ ctx = (Dial3gppContext *) g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_full_finish (modem, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Go to next step */
+ ctx->step++;
+ dial_3gpp_context_step (task);
+}
+
+static void
+swwan_dial_operation_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerCinterion *self) /* full ref! */
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_full_finish (modem, res, &error)) {
+ mm_obj_warn (self, "data connection attempt failed: %s", error->message);
+ mm_base_bearer_report_connection_status (MM_BASE_BEARER (self),
+ MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
+ g_error_free (error);
+ }
+
+ g_object_unref (self);
+}
+
+static void
+handle_cancel_dial (GTask *task)
+{
+ Dial3gppContext *ctx;
+ gchar *command;
+
+ ctx = (Dial3gppContext *) g_task_get_task_data (task);
+
+ /* Disconnect, may not succeed. Will not check response on cancel */
+ command = g_strdup_printf ("^SWWAN=0,%u,%u",
+ ctx->cid, usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ 3,
+ FALSE,
+ FALSE,
+ NULL,
+ NULL,
+ NULL);
+ g_free (command);
+}
+
+static void
+dial_3gpp_context_step (GTask *task)
+{
+ MMBroadbandBearerCinterion *self;
+ Dial3gppContext *ctx;
+ MMCinterionModemFamily modem_family;
+ gboolean default_swwan_behavior;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* Check for cancellation */
+ if (g_task_return_error_if_cancelled (task)) {
+ handle_cancel_dial (task);
+ g_object_unref (task);
+ return;
+ }
+
+ modem_family = mm_broadband_modem_cinterion_get_family (MM_BROADBAND_MODEM_CINTERION (ctx->modem));
+ default_swwan_behavior = modem_family == MM_CINTERION_MODEM_FAMILY_DEFAULT;
+
+ switch (ctx->step) {
+ case DIAL_3GPP_CONTEXT_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+
+ case DIAL_3GPP_CONTEXT_STEP_AUTH: {
+ g_autofree gchar *command = NULL;
+
+ command = mm_cinterion_build_auth_string (self,
+ modem_family,
+ mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self)),
+ ctx->cid);
+
+ if (command) {
+ mm_obj_dbg (self, "dial step %u/%u: authenticating...", ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST);
+ /* Send SGAUTH write, if User & Pass are provided.
+ * advance to next state by callback */
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ 10,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback) common_dial_operation_ready,
+ task);
+ return;
+ }
+
+ mm_obj_dbg (self, "dial step %u/%u: authentication not required", ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST);
+ ctx->step++;
+ } /* fall through */
+
+ case DIAL_3GPP_CONTEXT_STEP_START_SWWAN: {
+ g_autofree gchar *command = NULL;
+
+ mm_obj_dbg (self, "dial step %u/%u: starting SWWAN interface %u connection...",
+ ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST, usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
+ command = g_strdup_printf ("^SWWAN=1,%u,%u",
+ ctx->cid,
+ usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
+
+ if (default_swwan_behavior) {
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback) common_dial_operation_ready,
+ task);
+ return;
+ }
+
+ /* We "jump" to the last step here here since the modem expects the
+ * DHCP discover packet while ^SWWAN runs. If the command fails,
+ * we'll mark the bearer disconnected later in the callback.
+ */
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback) swwan_dial_operation_ready,
+ g_object_ref (self));
+ ctx->step = DIAL_3GPP_CONTEXT_STEP_LAST;
+ dial_3gpp_context_step (task);
+ return;
+ }
+
+ case DIAL_3GPP_CONTEXT_STEP_VALIDATE_CONNECTION:
+ g_assert (default_swwan_behavior);
+ mm_obj_dbg (self, "dial step %u/%u: checking SWWAN interface %u status...",
+ ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST, usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
+ load_connection_status_by_cid (ctx->self,
+ (gint) ctx->cid,
+ TRUE,
+ TRUE,
+ (GAsyncReadyCallback) dial_connection_status_ready,
+ task);
+ return;
+
+ case DIAL_3GPP_CONTEXT_STEP_LAST:
+ mm_obj_dbg (self, "dial step %u/%u: finished", ctx->step, DIAL_3GPP_CONTEXT_STEP_LAST);
+ g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+dial_3gpp (MMBroadbandBearer *self,
+ MMBaseModem *modem,
+ MMPortSerialAt *primary,
+ guint cid,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+
+ g_assert (primary != NULL);
+
+ /* Setup task and create connection context */
+ task = g_task_new (self, cancellable, callback, user_data);
+ ctx = g_slice_new0 (Dial3gppContext);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) dial_3gpp_context_free);
+
+ /* Setup context */
+ ctx->self = MM_BROADBAND_BEARER_CINTERION (g_object_ref (self));
+ ctx->modem = g_object_ref (modem);
+ ctx->primary = g_object_ref (primary);
+ ctx->cid = cid;
+ ctx->step = DIAL_3GPP_CONTEXT_STEP_FIRST;
+
+ /* Get a net port to setup the connection on */
+ ctx->data = mm_base_modem_peek_best_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET);
+ if (!ctx->data) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
+ "No valid data port found to launch connection");
+ g_object_unref (task);
+ return;
+ }
+ g_object_ref (ctx->data);
+
+ /* Validate configuration */
+ ctx->usb_interface_config_index = get_usb_interface_config_index (ctx->data, &error);
+ if (ctx->usb_interface_config_index < 0) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Run! */
+ dial_3gpp_context_step (task);
+}
+
+/*****************************************************************************/
+/* Disconnect 3GPP */
+
+typedef enum {
+ DISCONNECT_3GPP_CONTEXT_STEP_FIRST,
+ DISCONNECT_3GPP_CONTEXT_STEP_STOP_SWWAN,
+ DISCONNECT_3GPP_CONTEXT_STEP_CONNECTION_STATUS,
+ DISCONNECT_3GPP_CONTEXT_STEP_LAST,
+} Disconnect3gppContextStep;
+
+typedef struct {
+ MMBroadbandBearerCinterion *self;
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ MMPort *data;
+ guint cid;
+ gint usb_interface_config_index;
+ Disconnect3gppContextStep step;
+} Disconnect3gppContext;
+
+static void
+disconnect_3gpp_context_free (Disconnect3gppContext *ctx)
+{
+ g_object_unref (ctx->data);
+ g_object_unref (ctx->primary);
+ g_object_unref (ctx->self);
+ g_object_unref (ctx->modem);
+ g_slice_free (Disconnect3gppContext, ctx);
+}
+
+static gboolean
+disconnect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void disconnect_3gpp_context_step (GTask *task);
+
+static void
+disconnect_connection_status_ready (MMBroadbandBearerCinterion *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBearerConnectionStatus status;
+ Disconnect3gppContext *ctx;
+ GError *error = NULL;
+
+ ctx = (Disconnect3gppContext *) g_task_get_task_data (task);
+
+ status = load_connection_status_finish (MM_BASE_BEARER (self), res, &error);
+ switch (status) {
+ case MM_BEARER_CONNECTION_STATUS_UNKNOWN:
+ /* Assume disconnected */
+ mm_obj_dbg (self, "couldn't get CID %u status, assume disconnected: %s", ctx->cid, error->message);
+ g_clear_error (&error);
+ break;
+ case MM_BEARER_CONNECTION_STATUS_DISCONNECTED:
+ break;
+ case MM_BEARER_CONNECTION_STATUS_CONNECTED:
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "CID %u is reported connected", ctx->cid);
+ g_object_unref (task);
+ return;
+ case MM_BEARER_CONNECTION_STATUS_DISCONNECTING:
+ case MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED:
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* Go on to next step */
+ ctx->step++;
+ disconnect_3gpp_context_step (task);
+}
+
+static void
+swwan_disconnect_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Disconnect3gppContext *ctx;
+
+ ctx = (Disconnect3gppContext *) g_task_get_task_data (task);
+
+ /* We don't bother to check error or response here since, ctx flow's
+ * next step checks it */
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+
+ /* Go on to next step */
+ ctx->step++;
+ disconnect_3gpp_context_step (task);
+}
+
+static void
+disconnect_3gpp_context_step (GTask *task)
+{
+ MMBroadbandBearerCinterion *self;
+ Disconnect3gppContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case DISCONNECT_3GPP_CONTEXT_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+
+ case DISCONNECT_3GPP_CONTEXT_STEP_STOP_SWWAN: {
+ gchar *command;
+
+ command = g_strdup_printf ("^SWWAN=0,%u,%u",
+ ctx->cid, usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
+ mm_obj_dbg (self, "disconnect step %u/%u: disconnecting PDP CID %u...",
+ ctx->step, DISCONNECT_3GPP_CONTEXT_STEP_LAST, ctx->cid);
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback) swwan_disconnect_ready,
+ task);
+ g_free (command);
+ return;
+ }
+
+ case DISCONNECT_3GPP_CONTEXT_STEP_CONNECTION_STATUS:
+ mm_obj_dbg (self, "disconnect step %u/%u: checking SWWAN interface %u status...",
+ ctx->step, DISCONNECT_3GPP_CONTEXT_STEP_LAST,
+ usb_interface_configs[ctx->usb_interface_config_index].swwan_index);
+ load_connection_status_by_cid (MM_BROADBAND_BEARER_CINTERION (ctx->self),
+ (gint) ctx->cid,
+ TRUE,
+ FALSE,
+ (GAsyncReadyCallback) disconnect_connection_status_ready,
+ task);
+ return;
+
+ case DISCONNECT_3GPP_CONTEXT_STEP_LAST:
+ mm_obj_dbg (self, "disconnect step %u/%u: finished",
+ ctx->step, DISCONNECT_3GPP_CONTEXT_STEP_LAST);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+disconnect_3gpp (MMBroadbandBearer *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Disconnect3gppContext *ctx;
+ GError *error = NULL;
+
+ g_assert (primary != NULL);
+ g_assert (data != NULL);
+
+ /* Setup task and create connection context */
+ task = g_task_new (self, NULL, callback, user_data);
+ ctx = g_slice_new0 (Disconnect3gppContext);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) disconnect_3gpp_context_free);
+
+ /* Setup context */
+ ctx->self = MM_BROADBAND_BEARER_CINTERION (g_object_ref (self));
+ ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
+ ctx->primary = g_object_ref (primary);
+ ctx->data = g_object_ref (data);
+ ctx->cid = cid;
+ ctx->step = DISCONNECT_3GPP_CONTEXT_STEP_FIRST;
+
+ /* Validate configuration */
+ ctx->usb_interface_config_index = get_usb_interface_config_index (data, &error);
+ if (ctx->usb_interface_config_index < 0) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Start */
+ disconnect_3gpp_context_step (task);
+}
+
+/*****************************************************************************/
+/* Setup and Init Bearers */
+
+MMBaseBearer *
+mm_broadband_bearer_cinterion_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *bearer;
+ GObject *source;
+
+ source = g_async_result_get_source_object (res);
+ bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!bearer)
+ return NULL;
+
+ /* Only export valid bearers */
+ mm_base_bearer_export (MM_BASE_BEARER (bearer));
+
+ return MM_BASE_BEARER (bearer);
+}
+
+void
+mm_broadband_bearer_cinterion_new (MMBroadbandModemCinterion *modem,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (
+ MM_TYPE_BROADBAND_BEARER_CINTERION,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_BEARER_MODEM, modem,
+ MM_BASE_BEARER_CONFIG, config,
+ NULL);
+}
+
+static void
+mm_broadband_bearer_cinterion_init (MMBroadbandBearerCinterion *self)
+{
+}
+
+static void
+mm_broadband_bearer_cinterion_class_init (MMBroadbandBearerCinterionClass *klass)
+{
+ MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);
+ MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass);
+
+ base_bearer_class->load_connection_status = load_connection_status;
+ base_bearer_class->load_connection_status_finish = load_connection_status_finish;
+#if defined WITH_SUSPEND_RESUME
+ base_bearer_class->reload_connection_status = load_connection_status;
+ base_bearer_class->reload_connection_status_finish = load_connection_status_finish;
+#endif
+
+ broadband_bearer_class->dial_3gpp = dial_3gpp;
+ broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish;
+ broadband_bearer_class->disconnect_3gpp = disconnect_3gpp;
+ broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish;
+}
diff --git a/src/plugins/cinterion/mm-broadband-bearer-cinterion.h b/src/plugins/cinterion/mm-broadband-bearer-cinterion.h
new file mode 100644
index 00000000..d514759d
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-bearer-cinterion.h
@@ -0,0 +1,54 @@
+/* -*- 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) 2016 Trimble Navigation Limited
+ * Author: Matthew Stanger <Matthew_Stanger@trimble.com>
+ */
+
+#ifndef MM_BROADBAND_BEARER_CINTERION_H
+#define MM_BROADBAND_BEARER_CINTERION_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "mm-broadband-bearer.h"
+#include "mm-broadband-modem-cinterion.h"
+
+#define MM_TYPE_BROADBAND_BEARER_CINTERION (mm_broadband_bearer_cinterion_get_type ())
+#define MM_BROADBAND_BEARER_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_CINTERION, MMBroadbandBearerCinterion))
+#define MM_BROADBAND_BEARER_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_CINTERION, MMBroadbandBearerCinterionClass))
+#define MM_IS_BROADBAND_BEARER_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_CINTERION))
+#define MM_IS_BROADBAND_BEARER_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_CINTERION))
+#define MM_BROADBAND_BEARER_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_CINTERION, MMBroadbandBearerCinterionClass))
+
+typedef struct _MMBroadbandBearerCinterion MMBroadbandBearerCinterion;
+typedef struct _MMBroadbandBearerCinterionClass MMBroadbandBearerCinterionClass;
+
+struct _MMBroadbandBearerCinterion {
+ MMBroadbandBearer parent;
+};
+
+struct _MMBroadbandBearerCinterionClass {
+ MMBroadbandBearerClass parent;
+};
+
+GType mm_broadband_bearer_cinterion_get_type (void);
+
+void mm_broadband_bearer_cinterion_new (MMBroadbandModemCinterion *modem,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseBearer *mm_broadband_bearer_cinterion_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_BROADBAND_BEARER_CINTERION_H */
diff --git a/src/plugins/cinterion/mm-broadband-modem-cinterion.c b/src/plugins/cinterion/mm-broadband-modem-cinterion.c
new file mode 100644
index 00000000..b063d454
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-modem-cinterion.c
@@ -0,0 +1,3356 @@
+/* -*- 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) 2011 Ammonit Measurement GmbH
+ * Copyright (C) 2011 Google Inc.
+ * Copyright (C) 2016 Trimble Navigation Limited
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ * Contributor: Matthew Stanger <matthew_stanger@trimble.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-modem-helpers.h"
+#include "mm-serial-parsers.h"
+#include "mm-log-object.h"
+#include "mm-errors-types.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-messaging.h"
+#include "mm-iface-modem-location.h"
+#include "mm-iface-modem-voice.h"
+#include "mm-base-modem-at.h"
+#include "mm-broadband-modem-cinterion.h"
+#include "mm-modem-helpers-cinterion.h"
+#include "mm-shared-cinterion.h"
+#include "mm-broadband-bearer-cinterion.h"
+#include "mm-iface-modem-signal.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void iface_modem_messaging_init (MMIfaceModemMessaging *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void iface_modem_voice_init (MMIfaceModemVoice *iface);
+static void iface_modem_time_init (MMIfaceModemTime *iface);
+static void iface_modem_signal_init (MMIfaceModemSignal *iface);
+static void shared_cinterion_init (MMSharedCinterion *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+static MMIfaceModemLocation *iface_modem_location_parent;
+static MMIfaceModemVoice *iface_modem_voice_parent;
+static MMIfaceModemTime *iface_modem_time_parent;
+static MMIfaceModemSignal *iface_modem_signal_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemCinterion, mm_broadband_modem_cinterion, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init))
+
+typedef enum {
+ FEATURE_SUPPORT_UNKNOWN,
+ FEATURE_NOT_SUPPORTED,
+ FEATURE_SUPPORTED,
+} FeatureSupport;
+
+struct _MMBroadbandModemCinterionPrivate {
+ /* Command to go into sleep mode */
+ gchar *sleep_mode_cmd;
+
+ /* Cached supported bands in Cinterion format */
+ guint supported_bands[MM_CINTERION_RB_BLOCK_N];
+
+ /* Cached supported modes for SMS setup */
+ GArray *cnmi_supported_mode;
+ GArray *cnmi_supported_mt;
+ GArray *cnmi_supported_bm;
+ GArray *cnmi_supported_ds;
+ GArray *cnmi_supported_bfr;
+
+ /* Cached supported rats for SXRAT */
+ GArray *sxrat_supported_rat;
+ GArray *sxrat_supported_pref1;
+
+ /* ignore regex */
+ GRegex *sysstart_regex;
+ /* +CIEV indications as configured via AT^SIND */
+ GRegex *ciev_regex;
+ /* Ignore SIM hotswap SCKS msg, until ready */
+ GRegex *scks_regex;
+
+ /* Flags for feature support checks */
+ FeatureSupport swwan_support;
+ FeatureSupport sind_psinfo_support;
+ FeatureSupport smoni_support;
+ FeatureSupport sind_simstatus_support;
+ FeatureSupport sxrat_support;
+
+ /* Mode combination to apply if "any" requested */
+ MMModemMode any_allowed;
+
+ /* Flags for model-based behaviors */
+ MMCinterionModemFamily modem_family;
+ MMCinterionRadioBandFormat rb_format;
+
+ /* Initial EPS bearer context number */
+ gint initial_eps_bearer_cid;
+};
+
+/*****************************************************************************/
+
+MMCinterionModemFamily
+mm_broadband_modem_cinterion_get_family (MMBroadbandModemCinterion *self)
+{
+ return self->priv->modem_family;
+}
+
+/*****************************************************************************/
+/* Check support (Signal interface) */
+
+static gboolean
+signal_check_support_finish (MMIfaceModemSignal *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_signal_check_support_ready (MMIfaceModemSignal *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_signal_parent->check_support_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+check_smoni_support (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+
+ /* Fetch the result to the SMONI test. If no response given (error triggered), assume unsupported */
+ if (mm_base_modem_at_command_finish (_self, res, NULL)) {
+ mm_obj_dbg (self, "SMONI supported");
+ self->priv->smoni_support = FEATURE_SUPPORTED;
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "SMONI unsupported");
+ self->priv->smoni_support = FEATURE_NOT_SUPPORTED;
+
+ /* Otherwise, check if the parent CESQ-based implementation works */
+ g_assert (iface_modem_signal_parent->check_support && iface_modem_signal_parent->check_support_finish);
+ iface_modem_signal_parent->check_support (MM_IFACE_MODEM_SIGNAL (self),
+ (GAsyncReadyCallback) parent_signal_check_support_ready,
+ task);
+}
+
+static void
+signal_check_support (MMIfaceModemSignal *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),
+ "^SMONI=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback) check_smoni_support,
+ task);
+}
+
+/*****************************************************************************/
+/* Load extended signal information (Signal interface) */
+
+static gboolean
+signal_load_values_finish (MMIfaceModemSignal *_self,
+ GAsyncResult *res,
+ MMSignal **cdma,
+ MMSignal **evdo,
+ MMSignal **gsm,
+ MMSignal **umts,
+ MMSignal **lte,
+ MMSignal **nr5g,
+ GError **error)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ const gchar *response;
+
+ if (self->priv->smoni_support == FEATURE_NOT_SUPPORTED)
+ return iface_modem_signal_parent->load_values_finish (_self, res, cdma, evdo, gsm, umts, lte, nr5g, error);
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, error);
+ if (!response || !mm_cinterion_smoni_response_to_signal_info (response, gsm, umts, lte, error))
+ return FALSE;
+
+ if (cdma)
+ *cdma = NULL;
+ if (evdo)
+ *evdo = NULL;
+ if (nr5g)
+ *nr5g = NULL;
+
+ return TRUE;
+}
+
+static void
+signal_load_values (MMIfaceModemSignal *_self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+
+ if (self->priv->smoni_support == FEATURE_SUPPORTED) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SMONI",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+ return;
+ }
+
+ /* ^SMONI not supported, fallback to the parent */
+ iface_modem_signal_parent->load_values (_self, cancellable, callback, user_data);
+}
+
+/*****************************************************************************/
+/* Enable unsolicited events (SMS indications) (Messaging interface) */
+
+static gboolean
+messaging_enable_unsolicited_events_finish (MMIfaceModemMessaging *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+cnmi_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static gboolean
+value_supported (const GArray *array,
+ const guint value)
+{
+ guint i;
+
+ if (!array)
+ return FALSE;
+
+ for (i = 0; i < array->len; i++) {
+ if (g_array_index (array, guint, i) == value)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+messaging_enable_unsolicited_events (MMIfaceModemMessaging *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GString *cmd;
+ GError *error = NULL;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* AT+CNMI=<mode>,[<mt>[,<bm>[,<ds>[,<bfr>]]]] */
+ cmd = g_string_new ("+CNMI=");
+
+ /* Mode 2 or 1 */
+ if (value_supported (self->priv->cnmi_supported_mode, 2))
+ g_string_append_printf (cmd, "%u,", 2);
+ else if (value_supported (self->priv->cnmi_supported_mode, 1))
+ g_string_append_printf (cmd, "%u,", 1);
+ else {
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "SMS settings don't accept [2,1] <mode>");
+ goto out;
+ }
+
+ /* mt 2 or 1 */
+ if (value_supported (self->priv->cnmi_supported_mt, 2))
+ g_string_append_printf (cmd, "%u,", 2);
+ else if (value_supported (self->priv->cnmi_supported_mt, 1))
+ g_string_append_printf (cmd, "%u,", 1);
+ else {
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "SMS settings don't accept [2,1] <mt>");
+ goto out;
+ }
+
+ /* bm 2 or 0 */
+ if (value_supported (self->priv->cnmi_supported_bm, 2))
+ g_string_append_printf (cmd, "%u,", 2);
+ else if (value_supported (self->priv->cnmi_supported_bm, 0))
+ g_string_append_printf (cmd, "%u,", 0);
+ else {
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "SMS settings don't accept [2,0] <bm>");
+ goto out;
+ }
+
+ /* ds 2, 1 or 0 */
+ if (value_supported (self->priv->cnmi_supported_ds, 2))
+ g_string_append_printf (cmd, "%u,", 2);
+ else if (value_supported (self->priv->cnmi_supported_ds, 1))
+ g_string_append_printf (cmd, "%u,", 1);
+ else if (value_supported (self->priv->cnmi_supported_ds, 0))
+ g_string_append_printf (cmd, "%u,", 0);
+ else {
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "SMS settings don't accept [2,1,0] <ds>");
+ goto out;
+ }
+
+ /* bfr 1 */
+ if (value_supported (self->priv->cnmi_supported_bfr, 1))
+ g_string_append_printf (cmd, "%u", 1);
+ /* otherwise, skip setting it */
+
+out:
+ /* Early error report */
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ g_string_free (cmd, TRUE);
+ return;
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd->str,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)cnmi_test_ready,
+ task);
+ g_string_free (cmd, TRUE);
+}
+
+/*****************************************************************************/
+/* Check if Messaging supported (Messaging interface) */
+
+static gboolean
+messaging_check_support_finish (MMIfaceModemMessaging *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+cnmi_format_check_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GError *error = NULL;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Parse */
+ if (!mm_cinterion_parse_cnmi_test (response,
+ &self->priv->cnmi_supported_mode,
+ &self->priv->cnmi_supported_mt,
+ &self->priv->cnmi_supported_bm,
+ &self->priv->cnmi_supported_ds,
+ &self->priv->cnmi_supported_bfr,
+ &error)) {
+ mm_obj_warn (self, "error reading SMS setup: %s", error->message);
+ g_error_free (error);
+ }
+
+ /* CNMI command is supported; assume we have full messaging capabilities */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+messaging_check_support (MMIfaceModemMessaging *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* We assume that CDMA-only modems don't have messaging capabilities */
+ if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (self))) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "CDMA-only modems don't have messaging capabilities");
+ g_object_unref (task);
+ return;
+ }
+
+ /* Check CNMI support */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CNMI=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)cnmi_format_check_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Power down */
+
+static gboolean
+modem_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+sleep_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
+ mm_obj_dbg (self, "couldn't send power down command: %s", error->message);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+send_sleep_mode_command (GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+
+ self = g_task_get_source_object (task);
+
+ if (self->priv->sleep_mode_cmd && self->priv->sleep_mode_cmd[0]) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ self->priv->sleep_mode_cmd,
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)sleep_ready,
+ task);
+ return;
+ }
+
+ /* No default command; just finish without sending anything */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+supported_functionality_status_query_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ const gchar *response;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (self->priv->sleep_mode_cmd == NULL);
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response) {
+ mm_obj_warn (self, "couldn't query supported functionality status: %s", error->message);
+ self->priv->sleep_mode_cmd = g_strdup ("");
+ } else {
+ /* We need to get which power-off command to use to put the modem in low
+ * power mode (with serial port open for AT commands, but with RF switched
+ * off). According to the documentation of various Cinterion modems, some
+ * support AT+CFUN=4 (HC25) and those which don't support it can use
+ * AT+CFUN=7 (CYCLIC SLEEP mode with 2s timeout after last character
+ * received in the serial port).
+ *
+ * So, just look for '4' in the reply; if not found, look for '7', and if
+ * not found, report warning and don't use any.
+ */
+ if (strstr (response, "4") != NULL) {
+ mm_obj_dbg (self, "device supports CFUN=4 sleep mode");
+ self->priv->sleep_mode_cmd = g_strdup ("+CFUN=4");
+ } else if (strstr (response, "7") != NULL) {
+ mm_obj_dbg (self, "device supports CFUN=7 sleep mode");
+ self->priv->sleep_mode_cmd = g_strdup ("+CFUN=7");
+ } else {
+ mm_obj_warn (self, "unknown functionality mode to go into sleep mode");
+ self->priv->sleep_mode_cmd = g_strdup ("");
+ }
+ }
+
+ send_sleep_mode_command (task);
+}
+
+static void
+modem_power_down (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* If sleep command already decided, use it. */
+ if (self->priv->sleep_mode_cmd)
+ send_sleep_mode_command (task);
+ else
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CFUN=?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)supported_functionality_status_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Modem Power Off */
+
+#define MAX_POWER_OFF_WAIT_TIME_SECS 20
+
+typedef struct {
+ MMPortSerialAt *port;
+ GRegex *shutdown_regex;
+ gboolean shutdown_received;
+ gboolean smso_replied;
+ gboolean serial_open;
+ guint timeout_id;
+} PowerOffContext;
+
+static void
+power_off_context_free (PowerOffContext *ctx)
+{
+ if (ctx->serial_open)
+ mm_port_serial_close (MM_PORT_SERIAL (ctx->port));
+ if (ctx->timeout_id)
+ g_source_remove (ctx->timeout_id);
+ mm_port_serial_at_add_unsolicited_msg_handler (ctx->port, ctx->shutdown_regex, NULL, NULL, NULL);
+ g_object_unref (ctx->port);
+ g_regex_unref (ctx->shutdown_regex);
+ g_slice_free (PowerOffContext, ctx);
+}
+
+static gboolean
+modem_power_off_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+complete_power_off (GTask *task)
+{
+ PowerOffContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!ctx->shutdown_received || !ctx->smso_replied)
+ return;
+
+ /* remove timeout right away */
+ g_assert (ctx->timeout_id);
+ g_source_remove (ctx->timeout_id);
+ ctx->timeout_id = 0;
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+smso_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ PowerOffContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_full_finish (MM_BASE_MODEM (self), res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Set as replied and see if we can complete */
+ ctx->smso_replied = TRUE;
+ complete_power_off (task);
+}
+
+static void
+shutdown_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ GTask *task)
+{
+ PowerOffContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ /* Cleanup handler right away, we don't want it called any more */
+ mm_port_serial_at_add_unsolicited_msg_handler (port, ctx->shutdown_regex, NULL, NULL, NULL);
+
+ /* Set as received and see if we can complete */
+ ctx->shutdown_received = TRUE;
+ complete_power_off (task);
+}
+
+static gboolean
+power_off_timeout_cb (GTask *task)
+{
+ PowerOffContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ ctx->timeout_id = 0;
+
+ /* The SMSO reply should have come earlier */
+ g_warn_if_fail (ctx->smso_replied == TRUE);
+
+ /* Cleanup handler right away, we no longer want to receive it */
+ mm_port_serial_at_add_unsolicited_msg_handler (ctx->port, ctx->shutdown_regex, NULL, NULL, NULL);
+
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Power off operation timed out");
+ g_object_unref (task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+modem_power_off (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ PowerOffContext *ctx;
+ GError *error = NULL;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_slice_new0 (PowerOffContext);
+ ctx->port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+ ctx->shutdown_regex = g_regex_new ("\\r\\n\\^SHUTDOWN\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ ctx->timeout_id = g_timeout_add_seconds (MAX_POWER_OFF_WAIT_TIME_SECS,
+ (GSourceFunc)power_off_timeout_cb,
+ task);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) power_off_context_free);
+
+ /* We'll need to wait for a ^SHUTDOWN before returning the action, which is
+ * when the modem tells us that it is ready to be shutdown */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ctx->port,
+ ctx->shutdown_regex,
+ (MMPortSerialAtUnsolicitedMsgFn)shutdown_received,
+ task,
+ NULL);
+
+ /* In order to get the ^SHUTDOWN notification, we must keep the port open
+ * during the wait time */
+ ctx->serial_open = mm_port_serial_open (MM_PORT_SERIAL (ctx->port), &error);
+ if (G_UNLIKELY (error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Note: we'll use a timeout < MAX_POWER_OFF_WAIT_TIME_SECS for the AT command,
+ * so we're sure that the AT command reply will always come before the timeout
+ * fires */
+ g_assert (MAX_POWER_OFF_WAIT_TIME_SECS > 5);
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ ctx->port,
+ "^SMSO",
+ 5,
+ FALSE, /* allow_cached */
+ FALSE, /* is_raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)smso_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Access technologies polling */
+
+static gboolean
+load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ gssize val;
+
+ val = g_task_propagate_int (G_TASK (res), &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ *access_technologies = (MMModemAccessTechnology) val;
+ *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY;
+ return TRUE;
+}
+
+static void
+smong_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ MMModemAccessTechnology access_tech;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response || !mm_cinterion_parse_smong_response (response, &access_tech, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_int (task, (gssize) access_tech);
+ g_object_unref (task);
+}
+
+static void
+load_access_technologies (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Abort access technology polling if ^SIND psinfo URCs are enabled */
+ if (self->priv->sind_psinfo_support == FEATURE_SUPPORTED) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "No need to poll access technologies");
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "^SMONG",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)smong_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Disable unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't disable parent 3GPP unsolicited events: %s", error->message);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_disable_unsolicited_messages (GTask *task)
+{
+ /* Chain up parent's disable */
+ iface_modem_3gpp_parent->disable_unsolicited_events (
+ MM_IFACE_MODEM_3GPP (g_task_get_source_object (task)),
+ (GAsyncReadyCallback)parent_disable_unsolicited_events_ready,
+ task);
+}
+
+static void
+sind_psinfo_disable_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 disable ^SIND psinfo notifications: %s", error->message);
+
+ parent_disable_unsolicited_messages (task);
+}
+
+static void
+modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self;
+ GTask *task;
+
+ self = MM_BROADBAND_MODEM_CINTERION (_self);
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (self->priv->sind_psinfo_support == FEATURE_SUPPORTED) {
+ /* Disable access technology update reporting */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SIND=\"psinfo\",0",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)sind_psinfo_disable_ready,
+ task);
+ return;
+ }
+
+ parent_disable_unsolicited_messages (task);
+}
+
+/*****************************************************************************/
+/* Enable unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+sind_psinfo_enable_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+ g_autoptr(GError) error = NULL;
+ const gchar *response;
+ guint mode;
+ guint val;
+
+ self = MM_BROADBAND_MODEM_CINTERION (_self);
+ if (!(response = mm_base_modem_at_command_finish (_self, res, &error))) {
+ /* something went wrong, disable indicator */
+ self->priv->sind_psinfo_support = FEATURE_NOT_SUPPORTED;
+ mm_obj_warn (self, "couldn't enable ^SIND psinfo notifications: %s", error->message);
+ } else if (!mm_cinterion_parse_sind_response (response, NULL, &mode, &val, &error)) {
+ /* problem with parsing, disable indicator */
+ self->priv->sind_psinfo_support = FEATURE_NOT_SUPPORTED;
+ mm_obj_warn (self, "couldn't parse ^SIND psinfo response: %s", error->message);
+ } else {
+ /* Report initial access technology gathered right away */
+ mm_obj_dbg (self, "reporting initial access technologies...");
+ mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
+ mm_cinterion_get_access_technology_from_sind_psinfo (val, self),
+ MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+set_urc_dest_port_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+ g_autoptr(GError) error = NULL;
+
+ self = MM_BROADBAND_MODEM_CINTERION (_self);
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, &error))
+ mm_obj_dbg (self, "couldn't guarantee unsolicited events are sent to the correct port: %s", error->message);
+
+ if (self->priv->sind_psinfo_support == FEATURE_SUPPORTED) {
+ /* Enable access technology update reporting */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SIND=\"psinfo\",1",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)sind_psinfo_enable_ready,
+ task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't enable parent 3GPP unsolicited events: %s", error->message);
+
+ /* Make sure unsolicited events are sent to an AT port (PLS9 can default to DATA port) */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SCFG=\"URC/DstIfc\",\"app\"",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)set_urc_dest_port_ready,
+ task);
+}
+
+static void
+modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's enable */
+ iface_modem_3gpp_parent->enable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_enable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (3GPP interface) */
+
+static void
+sind_ciev_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemCinterion *self)
+{
+ guint val = 0;
+ gchar *indicator;
+
+ indicator = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (!mm_get_uint_from_match_info (match_info, 2, &val))
+ mm_obj_dbg (self, "couldn't parse indicator '%s' value", indicator);
+ else {
+ mm_obj_dbg (self, "received indicator '%s' update: %u", indicator, val);
+ if (g_strcmp0 (indicator, "psinfo") == 0) {
+ mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
+ mm_cinterion_get_access_technology_from_sind_psinfo (val, self),
+ MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
+ }
+ }
+ g_free (indicator);
+}
+
+static void
+set_unsolicited_events_handlers (MMBroadbandModemCinterion *self,
+ gboolean enable)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ 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 unsolicited events in given port */
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->ciev_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)sind_ciev_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+static gboolean
+modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else {
+ /* Our own setup now */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_CINTERION (self), TRUE);
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's setup */
+ iface_modem_3gpp_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_setup_unsolicited_events_ready,
+ task);
+}
+
+static void
+parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Our own cleanup first */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_CINTERION (self), FALSE);
+
+ /* And now chain up parent's cleanup */
+ iface_modem_3gpp_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Common operation to load expected CID for the initial EPS bearer */
+
+static gboolean
+load_initial_eps_bearer_cid_finish (MMBroadbandModemCinterion *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+scfg_prov_cfg_query_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ g_autoptr(GError) error = NULL;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response)
+ mm_obj_dbg (self, "couldn't query MNO profiles: %s", error->message);
+
+ else if (!mm_cinterion_provcfg_response_to_cid (response,
+ MM_BROADBAND_MODEM_CINTERION (self)->priv->modem_family,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
+ self,
+ &self->priv->initial_eps_bearer_cid,
+ &error))
+ mm_obj_dbg (self, "failed processing list of MNO profiles: %s", error->message);
+
+ if (self->priv->initial_eps_bearer_cid < 0) {
+ mm_obj_dbg (self, "using default EPS bearer context id: 1");
+ self->priv->initial_eps_bearer_cid = 1;
+ } else
+ mm_obj_dbg (self, "loaded EPS bearer context id from list of MNO profiles: %d", self->priv->initial_eps_bearer_cid);
+
+ /* This operation really never fails */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+load_initial_eps_bearer_cid (MMBroadbandModemCinterion *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ g_assert (self->priv->initial_eps_bearer_cid < 0);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SCFG=\"MEopMode/Prov/Cfg\"",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)scfg_prov_cfg_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Set initial EPS bearer settings */
+
+typedef enum {
+ SET_INITIAL_EPS_STEP_FIRST = 0,
+ SET_INITIAL_EPS_STEP_CHECK_MODE,
+ SET_INITIAL_EPS_STEP_RF_OFF,
+ SET_INITIAL_EPS_STEP_APN,
+ SET_INITIAL_EPS_STEP_AUTH,
+ SET_INITIAL_EPS_STEP_RF_ON,
+ SET_INITIAL_EPS_STEP_LAST,
+} SetInitialEpsStep;
+
+typedef struct {
+ MMBearerProperties *properties;
+ SetInitialEpsStep step;
+ guint initial_cfun_mode;
+ GError *saved_error;
+} SetInitialEpsContext;
+
+static void
+set_initial_eps_context_free (SetInitialEpsContext *ctx)
+{
+ g_assert (!ctx->saved_error);
+ g_object_unref (ctx->properties);
+ g_slice_free (SetInitialEpsContext, ctx);
+}
+
+static gboolean
+modem_3gpp_set_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void set_initial_eps_step (GTask *task);
+
+static void
+set_initial_eps_rf_on_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ SetInitialEpsContext *ctx;
+
+ ctx = (SetInitialEpsContext *) g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ mm_obj_warn (self, "couldn't set RF back on: %s", error->message);
+ if (!ctx->saved_error)
+ ctx->saved_error = g_steal_pointer (&error);
+ }
+
+ /* Go to next step */
+ ctx->step++;
+ set_initial_eps_step (task);
+}
+
+static void
+set_initial_eps_auth_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ SetInitialEpsContext *ctx;
+
+ ctx = (SetInitialEpsContext *) g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (_self, res, &ctx->saved_error)) {
+ mm_obj_warn (self, "couldn't configure context %d auth settings: %s",
+ self->priv->initial_eps_bearer_cid, ctx->saved_error->message);
+ /* Fallback to recover RF before returning the error */
+ ctx->step = SET_INITIAL_EPS_STEP_RF_ON;
+ } else {
+ /* Go to next step */
+ ctx->step++;
+ }
+ set_initial_eps_step (task);
+}
+
+static void
+set_initial_eps_cgdcont_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ SetInitialEpsContext *ctx;
+
+ ctx = (SetInitialEpsContext *) g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (_self, res, &ctx->saved_error)) {
+ mm_obj_warn (self, "couldn't configure context %d settings: %s",
+ self->priv->initial_eps_bearer_cid, ctx->saved_error->message);
+ /* Fallback to recover RF before returning the error */
+ ctx->step = SET_INITIAL_EPS_STEP_RF_ON;
+ } else {
+ /* Go to next step */
+ ctx->step++;
+ }
+ set_initial_eps_step (task);
+}
+
+static void
+set_initial_eps_rf_off_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ SetInitialEpsContext *ctx;
+
+ ctx = (SetInitialEpsContext *) g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ mm_obj_warn (self, "couldn't set RF off: %s", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Go to next step */
+ ctx->step++;
+ set_initial_eps_step (task);
+}
+
+static void
+set_initial_eps_cfun_mode_load_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+ SetInitialEpsContext *ctx;
+ guint mode;
+
+ ctx = (SetInitialEpsContext *) g_task_get_task_data (task);
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response || !mm_3gpp_parse_cfun_query_response (response, &mode, &error)) {
+ mm_obj_warn (self, "couldn't load initial functionality mode: %s", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "current functionality mode: %u", mode);
+ if (mode != 1 && mode != 4) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
+ "cannot setup the default LTE bearer settings: "
+ "the SIM must be powered");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->initial_cfun_mode = mode;
+ ctx->step++;
+ set_initial_eps_step (task);
+}
+
+static void
+set_initial_eps_step (GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+ SetInitialEpsContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case SET_INITIAL_EPS_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+
+ case SET_INITIAL_EPS_STEP_CHECK_MODE:
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CFUN?",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)set_initial_eps_cfun_mode_load_ready,
+ task);
+ return;
+
+ case SET_INITIAL_EPS_STEP_RF_OFF:
+ if (ctx->initial_cfun_mode != 4) {
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CFUN=4",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)set_initial_eps_rf_off_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case SET_INITIAL_EPS_STEP_APN: {
+ const gchar *apn;
+ g_autofree gchar *quoted_apn = NULL;
+ g_autofree gchar *apn_cmd = NULL;
+ const gchar *ip_family_str;
+ MMBearerIpFamily ip_family;
+
+ ip_family = mm_bearer_properties_get_ip_type (ctx->properties);
+ if (ip_family == MM_BEARER_IP_FAMILY_NONE || ip_family == MM_BEARER_IP_FAMILY_ANY)
+ ip_family = MM_BEARER_IP_FAMILY_IPV4;
+
+ ip_family_str = mm_3gpp_get_pdp_type_from_ip_family (ip_family);
+ apn = mm_bearer_properties_get_apn (ctx->properties);
+ mm_obj_dbg (self, "context %d with APN '%s' and PDP type '%s'",
+ self->priv->initial_eps_bearer_cid, apn, ip_family_str);
+ quoted_apn = mm_port_serial_at_quote_string (apn);
+ apn_cmd = g_strdup_printf ("+CGDCONT=%u,\"%s\",%s",
+ self->priv->initial_eps_bearer_cid, ip_family_str, quoted_apn);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ apn_cmd,
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)set_initial_eps_cgdcont_ready,
+ task);
+ return;
+ }
+
+ case SET_INITIAL_EPS_STEP_AUTH: {
+ g_autofree gchar *auth_cmd = NULL;
+
+ auth_cmd = mm_cinterion_build_auth_string (self,
+ MM_BROADBAND_MODEM_CINTERION (self)->priv->modem_family,
+ ctx->properties,
+ self->priv->initial_eps_bearer_cid);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ auth_cmd,
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)set_initial_eps_auth_ready,
+ task);
+ return;
+ }
+
+ case SET_INITIAL_EPS_STEP_RF_ON:
+ if (ctx->initial_cfun_mode == 1) {
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CFUN=1",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)set_initial_eps_rf_on_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case SET_INITIAL_EPS_STEP_LAST:
+ if (ctx->saved_error)
+ g_task_return_error (task, g_steal_pointer (&ctx->saved_error));
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+modem_3gpp_set_initial_eps_bearer_settings (MMIfaceModem3gpp *self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ SetInitialEpsContext *ctx;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* The initial EPS bearer settings should have already been loaded */
+ g_assert (MM_BROADBAND_MODEM_CINTERION (self)->priv->initial_eps_bearer_cid >= 0);
+
+ /* Setup context */
+ ctx = g_slice_new0 (SetInitialEpsContext);
+ ctx->properties = g_object_ref (properties);
+ ctx->step = SET_INITIAL_EPS_STEP_FIRST;
+ g_task_set_task_data (task, ctx, (GDestroyNotify) set_initial_eps_context_free);
+
+ set_initial_eps_step (task);
+}
+
+/*****************************************************************************/
+/* Common initial EPS bearer info loading for both:
+ * - runtime status
+ * - configuration settings
+ */
+
+typedef enum {
+ COMMON_LOAD_INITIAL_EPS_STEP_FIRST = 0,
+ COMMON_LOAD_INITIAL_EPS_STEP_PROFILE,
+ COMMON_LOAD_INITIAL_EPS_STEP_APN,
+ COMMON_LOAD_INITIAL_EPS_STEP_AUTH,
+ COMMON_LOAD_INITIAL_EPS_STEP_LAST,
+} CommonLoadInitialEpsStep;
+
+typedef struct {
+ MMBearerProperties *properties;
+ CommonLoadInitialEpsStep step;
+ gboolean runtime;
+} CommonLoadInitialEpsContext;
+
+static void
+common_load_initial_eps_context_free (CommonLoadInitialEpsContext *ctx)
+{
+ g_clear_object (&ctx->properties);
+ g_slice_free (CommonLoadInitialEpsContext, ctx);
+}
+
+static MMBearerProperties *
+common_load_initial_eps_bearer_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return MM_BEARER_PROPERTIES (g_task_propagate_pointer (G_TASK (res), error));
+}
+
+static void common_load_initial_eps_step (GTask *task);
+
+static void
+common_load_initial_eps_auth_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ const gchar *response;
+ CommonLoadInitialEpsContext *ctx;
+ g_autoptr(GError) error = NULL;
+ MMBearerAllowedAuth auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN;
+ g_autofree gchar *username = NULL;
+
+ ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response)
+ mm_obj_dbg (self, "couldn't load context %d auth settings: %s",
+ self->priv->initial_eps_bearer_cid, error->message);
+ else if (!mm_cinterion_parse_sgauth_response (response, self->priv->initial_eps_bearer_cid, &auth, &username, &error))
+ mm_obj_dbg (self, "couldn't parse context %d auth settings: %s", self->priv->initial_eps_bearer_cid, error->message);
+ else {
+ mm_bearer_properties_set_allowed_auth (ctx->properties, auth);
+ mm_bearer_properties_set_user (ctx->properties, username);
+ }
+
+ /* Go to next step */
+ ctx->step++;
+ common_load_initial_eps_step (task);
+}
+
+static void
+common_load_initial_eps_load_cid_ready (MMBroadbandModemCinterion *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ CommonLoadInitialEpsContext *ctx;
+
+ ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task);
+
+ load_initial_eps_bearer_cid_finish (self, res, NULL);
+ g_assert (self->priv->initial_eps_bearer_cid >= 0);
+
+ /* Go to next step */
+ ctx->step++;
+ common_load_initial_eps_step (task);
+}
+
+static void
+common_load_initial_eps_cgcontrdp_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ const gchar *response;
+ CommonLoadInitialEpsContext *ctx;
+ g_autofree gchar *apn = NULL;
+ g_autoptr(GError) error = NULL;
+
+ ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task);
+
+ /* errors aren't fatal */
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response)
+ mm_obj_dbg (self, "couldn't load context %d settings: %s",
+ self->priv->initial_eps_bearer_cid, error->message);
+ else if (!mm_3gpp_parse_cgcontrdp_response (response, NULL, NULL, &apn, NULL, NULL, NULL, NULL, NULL, &error))
+ mm_obj_dbg (self, "couldn't parse CGDCONTRDP response: %s", error->message);
+ else
+ mm_bearer_properties_set_apn (ctx->properties, apn);
+
+ /* Go to next step */
+ ctx->step++;
+ common_load_initial_eps_step (task);
+}
+
+static void
+common_load_initial_eps_cgdcont_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ const gchar *response;
+ CommonLoadInitialEpsContext *ctx;
+ g_autoptr(GError) error = NULL;
+
+ ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task);
+
+ /* errors aren't fatal */
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response)
+ mm_obj_dbg (self, "couldn't load context %d status: %s",
+ self->priv->initial_eps_bearer_cid, error->message);
+ else {
+ GList *context_list;
+
+ context_list = mm_3gpp_parse_cgdcont_read_response (response, &error);
+ if (!context_list)
+ mm_obj_dbg (self, "couldn't parse CGDCONT response: %s", error->message);
+ else {
+ GList *l;
+
+ for (l = context_list; l; l = g_list_next (l)) {
+ MM3gppPdpContext *pdp = l->data;
+
+ if (pdp->cid == (guint) self->priv->initial_eps_bearer_cid) {
+ mm_bearer_properties_set_ip_type (ctx->properties, pdp->pdp_type);
+ mm_bearer_properties_set_apn (ctx->properties, pdp->apn ? pdp->apn : "");
+ break;
+ }
+ }
+ if (!l)
+ mm_obj_dbg (self, "no status reported for context %d", self->priv->initial_eps_bearer_cid);
+ mm_3gpp_pdp_context_list_free (context_list);
+ }
+ }
+
+ /* Go to next step */
+ ctx->step++;
+ common_load_initial_eps_step (task);
+}
+
+static void
+common_load_initial_eps_step (GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+ CommonLoadInitialEpsContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case COMMON_LOAD_INITIAL_EPS_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+
+ case COMMON_LOAD_INITIAL_EPS_STEP_PROFILE:
+ /* Initial EPS bearer CID initialization run once only */
+ if (G_UNLIKELY (self->priv->initial_eps_bearer_cid < 0)) {
+ load_initial_eps_bearer_cid (
+ self,
+ (GAsyncReadyCallback)common_load_initial_eps_load_cid_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case COMMON_LOAD_INITIAL_EPS_STEP_APN:
+ if (ctx->runtime) {
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CGDCONT?",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)common_load_initial_eps_cgdcont_ready,
+ task);
+ } else {
+ g_autofree gchar *cmd = NULL;
+
+ cmd = g_strdup_printf ("+CGCONTRDP=%u", self->priv->initial_eps_bearer_cid);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CGCONTRDP",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)common_load_initial_eps_cgcontrdp_ready,
+ task);
+ }
+ return;
+
+ case COMMON_LOAD_INITIAL_EPS_STEP_AUTH:
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "^SGAUTH?",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)common_load_initial_eps_auth_ready,
+ task);
+ return;
+
+ case COMMON_LOAD_INITIAL_EPS_STEP_LAST:
+ g_task_return_pointer (task, g_steal_pointer (&ctx->properties), g_object_unref);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+common_load_initial_eps_bearer (MMIfaceModem3gpp *self,
+ gboolean runtime,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ CommonLoadInitialEpsContext *ctx;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Setup context */
+ ctx = g_slice_new0 (CommonLoadInitialEpsContext);
+ ctx->runtime = runtime;
+ ctx->properties = mm_bearer_properties_new ();
+ ctx->step = COMMON_LOAD_INITIAL_EPS_STEP_FIRST;
+ g_task_set_task_data (task, ctx, (GDestroyNotify) common_load_initial_eps_context_free);
+
+ common_load_initial_eps_step (task);
+}
+
+/*****************************************************************************/
+/* Initial EPS bearer runtime status loading */
+
+static MMBearerProperties *
+modem_3gpp_load_initial_eps_bearer_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return common_load_initial_eps_bearer_finish (self, res, error);
+}
+
+static void
+modem_3gpp_load_initial_eps_bearer (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_load_initial_eps_bearer (self, TRUE, callback, user_data);
+}
+
+/*****************************************************************************/
+/* Initial EPS bearer settings loading -> set configuration */
+
+static MMBearerProperties *
+modem_3gpp_load_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return common_load_initial_eps_bearer_finish (self, res, error);
+}
+
+static void
+modem_3gpp_load_initial_eps_bearer_settings (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_load_initial_eps_bearer (self, FALSE, callback, user_data);
+}
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_load_supported_modes_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ GArray *all;
+ GArray *combinations;
+ GArray *filtered;
+ MMModemModeCombination mode;
+
+ all = iface_modem_parent->load_supported_modes_finish (self, res, &error);
+ if (!all) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3);
+
+ /* 2G only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 3G only */
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ if (mm_iface_modem_is_4g (self)) {
+ /* 4G only */
+ mode.allowed = MM_MODEM_MODE_4G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G, 3G and 4G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ } else {
+ /* 2G and 3G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ /* Filter out those unsupported modes */
+ filtered = mm_filter_supported_modes (all, combinations, self);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+sxrat_load_supported_modes_ready (MMBroadbandModemCinterion *self,
+ GTask *task)
+{
+ GArray *combinations;
+ MMModemModeCombination mode;
+
+ g_assert (self->priv->sxrat_supported_rat);
+ g_assert (self->priv->sxrat_supported_pref1);
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3);
+
+ if (value_supported (self->priv->sxrat_supported_rat, 0)) {
+ /* 2G only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_rat, 1)) {
+ /* 2G+3G with none preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ self->priv->any_allowed = mode.allowed;
+
+ if (value_supported (self->priv->sxrat_supported_pref1, 0)) {
+ /* 2G preferred */
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_pref1, 2)) {
+ /* 3G preferred */
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+ }
+ }
+ if (value_supported (self->priv->sxrat_supported_rat, 2)) {
+ /* 3G only */
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_rat, 3)) {
+ /* 4G only */
+ mode.allowed = MM_MODEM_MODE_4G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_rat, 4)) {
+ /* 3G+4G with none preferred */
+ mode.allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ self->priv->any_allowed = mode.allowed;
+
+ if (value_supported (self->priv->sxrat_supported_pref1, 2)) {
+ /* 3G preferred */
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_pref1, 3)) {
+ /* 4G preferred */
+ mode.preferred = MM_MODEM_MODE_4G;
+ g_array_append_val (combinations, mode);
+ }
+ }
+ if (value_supported (self->priv->sxrat_supported_rat, 5)) {
+ /* 2G+4G with none preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ self->priv->any_allowed = mode.allowed;
+
+ if (value_supported (self->priv->sxrat_supported_pref1, 0)) {
+ /* 2G preferred */
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_pref1, 3)) {
+ /* 4G preferred */
+ mode.preferred = MM_MODEM_MODE_4G;
+ g_array_append_val (combinations, mode);
+ }
+ }
+ if (value_supported (self->priv->sxrat_supported_rat, 6)) {
+ /* 2G+3G+4G with none preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ self->priv->any_allowed = mode.allowed;
+
+ if (value_supported (self->priv->sxrat_supported_pref1, 0)) {
+ /* 2G preferred */
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_pref1, 2)) {
+ /* 3G preferred */
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+ }
+ if (value_supported (self->priv->sxrat_supported_pref1, 3)) {
+ /* 4G preferred */
+ mode.preferred = MM_MODEM_MODE_4G;
+ g_array_append_val (combinations, mode);
+ }
+ }
+
+ g_task_return_pointer (task, combinations, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+sxrat_test_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ g_autoptr(GError) error = NULL;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!error) {
+ mm_cinterion_parse_sxrat_test (response,
+ &self->priv->sxrat_supported_rat,
+ &self->priv->sxrat_supported_pref1,
+ NULL,
+ &error);
+ if (!error) {
+ self->priv->sxrat_support = FEATURE_SUPPORTED;
+ sxrat_load_supported_modes_ready (self, task);
+ return;
+ }
+ mm_obj_warn (self, "error reading SXRAT response: %s", error->message);
+ }
+
+ self->priv->sxrat_support = FEATURE_NOT_SUPPORTED;
+
+ /* Run parent's loading in case SXRAT is not supported */
+ iface_modem_parent->load_supported_modes (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_supported_modes_ready,
+ task);
+}
+
+static void
+load_supported_modes (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* First check SXRAT support, if not already done */
+ if (self->priv->sxrat_support == FEATURE_SUPPORT_UNKNOWN) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SXRAT=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)sxrat_test_ready,
+ task);
+ return;
+ }
+
+ if (self->priv->sxrat_support == FEATURE_SUPPORTED) {
+ sxrat_load_supported_modes_ready (self, task);
+ return;
+ }
+
+ /* Run parent's loading */
+ iface_modem_parent->load_supported_modes (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_supported_modes_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Set current modes (Modem interface) */
+
+static gboolean
+set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+set_current_modes_reregister_in_network_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_iface_modem_3gpp_reregister_in_network_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+allowed_access_technology_update_ready (MMBroadbandModemCinterion *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+cops_set_current_modes (MMBroadbandModemCinterion *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GTask *task)
+{
+ gchar *command;
+
+ g_assert (preferred == MM_MODEM_MODE_NONE);
+
+ /* We will try to simulate the possible allowed modes here. The
+ * Cinterion devices do not seem to allow setting preferred access
+ * technology in devices, but they allow restricting to a given
+ * one:
+ * - 2G-only is forced by forcing GERAN RAT (AcT=0)
+ * - 3G-only is forced by forcing UTRAN RAT (AcT=2)
+ * - 4G-only is forced by forcing E-UTRAN RAT (AcT=7)
+ * - for the remaining ones, we default to automatic selection of RAT,
+ * which is based on the quality of the connection.
+ */
+
+ if (mm_iface_modem_is_4g (MM_IFACE_MODEM (self)) && allowed == MM_MODEM_MODE_4G)
+ command = g_strdup ("+COPS=,,,7");
+ else if (mm_iface_modem_is_3g (MM_IFACE_MODEM (self)) && allowed == MM_MODEM_MODE_3G)
+ command = g_strdup ("+COPS=,,,2");
+ else if (mm_iface_modem_is_2g (MM_IFACE_MODEM (self)) && allowed == MM_MODEM_MODE_2G)
+ command = g_strdup ("+COPS=,,,0");
+ else {
+ /* For any other combination (e.g. ANY or no AcT given, defaults to Auto. For this case, we cannot provide
+ * AT+COPS=,,, (i.e. just without a last value). Instead, we need to
+ * re-run the last manual/automatic selection command which succeeded,
+ * (or auto by default if none was launched) */
+ mm_iface_modem_3gpp_reregister_in_network (MM_IFACE_MODEM_3GPP (self),
+ (GAsyncReadyCallback) set_current_modes_reregister_in_network_ready,
+ task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)allowed_access_technology_update_ready,
+ task);
+
+ g_free (command);
+}
+
+static void
+sxrat_set_current_modes (MMBroadbandModemCinterion *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GTask *task)
+{
+ gchar *command;
+ GError *error = NULL;
+
+ g_assert (self->priv->any_allowed != MM_MODEM_MODE_NONE);
+
+ /* Handle ANY */
+ if (allowed == MM_MODEM_MODE_ANY)
+ allowed = self->priv->any_allowed;
+
+ command = mm_cinterion_build_sxrat_set_command (allowed, preferred, &error);
+
+ if (!command) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 30,
+ FALSE,
+ (GAsyncReadyCallback)allowed_access_technology_update_ready,
+ task);
+
+ g_free (command);
+}
+
+static void
+set_current_modes (MMIfaceModem *_self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (self->priv->sxrat_support == FEATURE_SUPPORTED)
+ sxrat_set_current_modes (self, allowed, preferred, task);
+ else if (self->priv->sxrat_support == FEATURE_NOT_SUPPORTED)
+ cops_set_current_modes (self, allowed, preferred, task);
+ else
+ g_assert_not_reached ();
+}
+
+/*****************************************************************************/
+/* Supported bands (Modem interface) */
+
+static GArray *
+load_supported_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+scfg_test_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ const gchar *response;
+ GError *error = NULL;
+ GArray *bands;
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response ||
+ !mm_cinterion_parse_scfg_test (response,
+ self->priv->modem_family,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
+ &bands,
+ &self->priv->rb_format,
+ &error))
+ g_task_return_error (task, error);
+ else {
+ if (!mm_cinterion_build_band (bands,
+ NULL,
+ FALSE,
+ self->priv->rb_format,
+ self->priv->modem_family,
+ self->priv->supported_bands,
+ &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
+ }
+ g_object_unref (task);
+}
+
+static void
+load_supported_bands (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GTask *task;
+ MMPort *primary;
+ MMKernelDevice *port;
+ const gchar *family = NULL;
+
+ /* Lookup for the tag specifying which modem family the current device belongs */
+ primary = MM_PORT (mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)));
+ port = mm_port_peek_kernel_device (primary);
+ family = mm_kernel_device_get_global_property (port, "ID_MM_CINTERION_MODEM_FAMILY");
+
+ /* if the property is not set, default family */
+ self->priv->modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT;
+
+ /* set used family also in the string for mm_obj_dbg */
+ if (!family)
+ family = "default";
+
+ if (g_ascii_strcasecmp (family, "imt") == 0)
+ self->priv->modem_family = MM_CINTERION_MODEM_FAMILY_IMT;
+ else if (g_ascii_strcasecmp (family, "default") != 0) {
+ mm_obj_dbg (self, "cinterion modem family '%s' unknown", family);
+ family = "default";
+ }
+
+ mm_obj_dbg (self, "Using cinterion %s modem family", family);
+
+ task = g_task_new (_self, NULL, callback, user_data);
+ mm_base_modem_at_command (MM_BASE_MODEM (_self),
+ "AT^SCFG=?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)scfg_test_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load current bands (Modem interface) */
+
+static GArray *
+load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+get_band_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ const gchar *response;
+ GError *error = NULL;
+ GArray *bands = NULL;
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response ||
+ !mm_cinterion_parse_scfg_response (response,
+ self->priv->modem_family,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
+ &bands,
+ self->priv->rb_format,
+ &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bands, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_current_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* The timeout in this command is extremely large, because there are some
+ * modules like the EGS5 that build the response based on the current network
+ * registration, and that implies the module needs to be registered. If for
+ * any reason there is no serving network where to register, the response
+ * comes after a very long time, up to 100s. */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SCFG?",
+ 120,
+ FALSE,
+ (GAsyncReadyCallback)get_band_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Set current bands (Modem interface) */
+
+typedef struct {
+ MMBaseModemAtCommandAlloc *cmds;
+} SetCurrentBandsContext;
+
+static void
+set_current_bands_context_free (SetCurrentBandsContext *ctx)
+{
+ if (ctx->cmds) {
+ guint i;
+
+ for (i = 0; ctx->cmds[i].command; i++)
+ mm_base_modem_at_command_alloc_clear (&ctx->cmds[i]);
+ g_free (ctx->cmds);
+ }
+ g_slice_free (SetCurrentBandsContext, ctx);
+}
+
+static gboolean
+set_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+scfg_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+scfg_set_ready_sequence (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_finish (self, res, NULL, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+set_bands_3g (GTask *task,
+ GArray *bands_array)
+{
+ MMBroadbandModemCinterion *self;
+ GError *error = NULL;
+ guint band[MM_CINTERION_RB_BLOCK_N] = { 0 };
+
+ self = g_task_get_source_object (task);
+
+ if (!mm_cinterion_build_band (bands_array,
+ self->priv->supported_bands,
+ FALSE, /* 2G and 3G */
+ self->priv->rb_format,
+ self->priv->modem_family,
+ band,
+ &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ if (self->priv->rb_format == MM_CINTERION_RADIO_BAND_FORMAT_SINGLE) {
+ g_autofree gchar *cmd = NULL;
+
+ /* Following the setup:
+ * AT^SCFG="Radion/Band",<rba>
+ * We will set the preferred band equal to the allowed band, so that we force
+ * the modem to connect at that specific frequency only. Note that we will be
+ * passing a number here!
+ *
+ * The optional <rbe> field is set to 1, so that changes take effect
+ * immediately.
+ */
+ cmd = g_strdup_printf ("^SCFG=\"Radio/Band\",%u,1", band[MM_CINTERION_RB_BLOCK_LEGACY]);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 15,
+ FALSE,
+ (GAsyncReadyCallback)scfg_set_ready,
+ task);
+ return;
+ }
+
+ if (self->priv->rb_format == MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE) {
+ SetCurrentBandsContext *ctx;
+
+ ctx = g_slice_new0 (SetCurrentBandsContext);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)set_current_bands_context_free);
+
+ if (self->priv->modem_family == MM_CINTERION_MODEM_FAMILY_IMT) {
+ g_autofree gchar *bandstr2G = NULL;
+ g_autofree gchar *bandstr3G = NULL;
+ g_autofree gchar *bandstr4G = NULL;
+ g_autofree gchar *bandstr2G_enc = NULL;
+ g_autofree gchar *bandstr3G_enc = NULL;
+ g_autofree gchar *bandstr4G_enc = NULL;
+
+ bandstr2G = g_strdup_printf ("0x%08X", band[MM_CINTERION_RB_BLOCK_GSM]);
+ bandstr3G = g_strdup_printf ("0x%08X", band[MM_CINTERION_RB_BLOCK_UMTS]);
+ bandstr4G = g_strdup_printf ("0x%08X", band[MM_CINTERION_RB_BLOCK_LTE_LOW]);
+
+ bandstr2G_enc = mm_modem_charset_str_from_utf8 (bandstr2G,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
+ FALSE,
+ &error);
+ if (!bandstr2G_enc) {
+ g_prefix_error (&error, "Couldn't convert 2G band string to current charset: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ bandstr3G_enc = mm_modem_charset_str_from_utf8 (bandstr3G,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
+ FALSE,
+ &error);
+ if (!bandstr3G_enc) {
+ g_prefix_error (&error, "Couldn't convert 3G band string to current charset: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ bandstr4G_enc = mm_modem_charset_str_from_utf8 (bandstr4G,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
+ FALSE,
+ &error);
+ if (!bandstr4G_enc) {
+ g_prefix_error (&error, "Couldn't convert 4G band string to current charset: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->cmds = g_new0 (MMBaseModemAtCommandAlloc, 3 + 1);
+ ctx->cmds[0].command = g_strdup_printf ("^SCFG=\"Radio/Band/2G\",\"%s\"", bandstr2G_enc);
+ ctx->cmds[1].command = g_strdup_printf ("^SCFG=\"Radio/Band/3G\",\"%s\"", bandstr3G_enc);
+ ctx->cmds[2].command = g_strdup_printf ("^SCFG=\"Radio/Band/4G\",\"%s\"", bandstr4G_enc);
+ ctx->cmds[0].timeout = ctx->cmds[1].timeout = ctx->cmds[2].timeout = 60;
+ } else {
+ ctx->cmds = g_new0 (MMBaseModemAtCommandAlloc, 3 + 1);
+ ctx->cmds[0].command = g_strdup_printf ("^SCFG=\"Radio/Band/2G\",\"%08x\",,1", band[MM_CINTERION_RB_BLOCK_GSM]);
+ ctx->cmds[1].command = g_strdup_printf ("^SCFG=\"Radio/Band/3G\",\"%08x\",,1", band[MM_CINTERION_RB_BLOCK_UMTS]);
+ ctx->cmds[2].command = g_strdup_printf ("^SCFG=\"Radio/Band/4G\",\"%08x\",\"%08x\",1", band[MM_CINTERION_RB_BLOCK_LTE_LOW], band[MM_CINTERION_RB_BLOCK_LTE_HIGH]);
+ ctx->cmds[0].timeout = ctx->cmds[1].timeout = ctx->cmds[2].timeout = 15;
+ }
+
+ mm_base_modem_at_sequence (MM_BASE_MODEM (self),
+ (const MMBaseModemAtCommand *)ctx->cmds,
+ NULL,
+ NULL,
+ (GAsyncReadyCallback)scfg_set_ready_sequence,
+ task);
+ return;
+ }
+
+ g_assert_not_reached ();
+}
+
+static void
+set_bands_2g (GTask *task,
+ GArray *bands_array)
+{
+ MMBroadbandModemCinterion *self;
+ GError *error = NULL;
+ guint band[MM_CINTERION_RB_BLOCK_N] = { 0 };
+ g_autofree gchar *cmd = NULL;
+ g_autofree gchar *bandstr = NULL;
+ g_autofree gchar *bandstr_enc = NULL;
+
+ self = g_task_get_source_object (task);
+
+ if (!mm_cinterion_build_band (bands_array,
+ self->priv->supported_bands,
+ TRUE, /* 2G only */
+ MM_CINTERION_RADIO_BAND_FORMAT_SINGLE,
+ 0,
+ band,
+ &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Build string with the value, in the proper charset */
+ bandstr = g_strdup_printf ("%u", band[MM_CINTERION_RB_BLOCK_LEGACY]);
+ bandstr_enc = mm_modem_charset_str_from_utf8 (bandstr,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)),
+ FALSE,
+ &error);
+ if (!bandstr_enc) {
+ g_prefix_error (&error, "Couldn't convert band string to current charset: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Following the setup:
+ * AT^SCFG="Radion/Band",<rbp>,<rba>
+ * We will set the preferred band equal to the allowed band, so that we force
+ * the modem to connect at that specific frequency only. Note that we will be
+ * passing double-quote enclosed strings here!
+ */
+ cmd = g_strdup_printf ("^SCFG=\"Radio/Band\",\"%s\",\"%s\"", bandstr_enc, bandstr_enc);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 15,
+ FALSE,
+ (GAsyncReadyCallback)scfg_set_ready,
+ task);
+}
+
+static void
+set_current_bands (MMIfaceModem *self,
+ GArray *bands_array,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ /* The bands that we get here are previously validated by the interface, and
+ * that means that ALL the bands given here were also given in the list of
+ * supported bands. BUT BUT, that doesn't mean that the exact list of bands
+ * will end up being valid, as not all combinations are possible. E.g,
+ * Cinterion modems supporting only 2G have specific combinations allowed.
+ */
+ task = g_task_new (self, NULL, callback, user_data);
+ if (mm_iface_modem_is_3g (self))
+ set_bands_3g (task, bands_array);
+ else
+ set_bands_2g (task, bands_array);
+}
+
+/*****************************************************************************/
+/* Flow control */
+
+static gboolean
+setup_flow_control_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+setup_flow_control_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (self, res, &error))
+ /* Let the error be critical. We DO need RTS/CTS in order to have
+ * proper modem disabling. */
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+setup_flow_control (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* We need to enable RTS/CTS so that CYCLIC SLEEP mode works */
+ g_object_set (self, MM_BROADBAND_MODEM_FLOW_CONTROL, MM_FLOW_CONTROL_RTS_CTS, NULL);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "\\Q3",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)setup_flow_control_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load unlock retries (Modem interface) */
+
+typedef struct {
+ MMUnlockRetries *retries;
+ guint i;
+} LoadUnlockRetriesContext;
+
+typedef struct {
+ MMModemLock lock;
+ const gchar *command;
+} UnlockRetriesMap;
+
+static const UnlockRetriesMap unlock_retries_map [] = {
+ { MM_MODEM_LOCK_SIM_PIN, "^SPIC=\"SC\"" },
+ { MM_MODEM_LOCK_SIM_PUK, "^SPIC=\"SC\",1" },
+ { MM_MODEM_LOCK_SIM_PIN2, "^SPIC=\"P2\"" },
+ { MM_MODEM_LOCK_SIM_PUK2, "^SPIC=\"P2\",1" },
+ { MM_MODEM_LOCK_PH_FSIM_PIN, "^SPIC=\"PS\"" },
+ { MM_MODEM_LOCK_PH_FSIM_PUK, "^SPIC=\"PS\",1" },
+ { MM_MODEM_LOCK_PH_NET_PIN, "^SPIC=\"PN\"" },
+ { MM_MODEM_LOCK_PH_NET_PUK, "^SPIC=\"PN\",1" },
+};
+
+static void
+load_unlock_retries_context_free (LoadUnlockRetriesContext *ctx)
+{
+ g_object_unref (ctx->retries);
+ g_slice_free (LoadUnlockRetriesContext, ctx);
+}
+
+static MMUnlockRetries *
+load_unlock_retries_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void load_unlock_retries_context_step (GTask *task);
+
+static void
+spic_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LoadUnlockRetriesContext *ctx;
+ const gchar *response;
+ g_autoptr(GError) error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ mm_obj_dbg (self, "Couldn't load retry count for lock '%s': %s",
+ mm_modem_lock_get_string (unlock_retries_map[ctx->i].lock),
+ error->message);
+ } else {
+ guint val;
+
+ response = mm_strip_tag (response, "^SPIC:");
+ if (!mm_get_uint_from_str (response, &val))
+ mm_obj_dbg (self, "couldn't parse retry count value for lock '%s'",
+ mm_modem_lock_get_string (unlock_retries_map[ctx->i].lock));
+ else
+ mm_unlock_retries_set (ctx->retries, unlock_retries_map[ctx->i].lock, val);
+ }
+
+ /* Go to next lock value */
+ ctx->i++;
+ load_unlock_retries_context_step (task);
+}
+
+static void
+load_unlock_retries_context_step (GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+ LoadUnlockRetriesContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ if (ctx->i == G_N_ELEMENTS (unlock_retries_map)) {
+ g_task_return_pointer (task, g_object_ref (ctx->retries), g_object_unref);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ unlock_retries_map[ctx->i].command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)spic_ready,
+ task);
+}
+
+static void
+load_unlock_retries (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ LoadUnlockRetriesContext *ctx;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_slice_new0 (LoadUnlockRetriesContext);
+ ctx->retries = mm_unlock_retries_new ();
+ ctx->i = 0;
+ g_task_set_task_data (task, ctx, (GDestroyNotify)load_unlock_retries_context_free);
+
+ load_unlock_retries_context_step (task);
+}
+
+/*****************************************************************************/
+/* After SIM unlock (Modem interface) */
+
+#define MAX_AFTER_SIM_UNLOCK_RETRIES 15
+
+typedef enum {
+ CINTERION_SIM_STATUS_REMOVED = 0,
+ CINTERION_SIM_STATUS_INSERTED = 1,
+ CINTERION_SIM_STATUS_INIT_COMPLETED = 5,
+} CinterionSimStatus;
+
+typedef struct {
+ guint retries;
+ guint timeout_id;
+} AfterSimUnlockContext;
+
+static gboolean
+after_sim_unlock_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void after_sim_unlock_context_step (GTask *task);
+
+static gboolean
+simstatus_timeout_cb (GTask *task)
+{
+ AfterSimUnlockContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ ctx->timeout_id = 0;
+ after_sim_unlock_context_step (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+simstatus_check_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ AfterSimUnlockContext *ctx;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (self, res, NULL);
+ if (response) {
+ gchar *descr = NULL;
+ guint val = 0;
+
+ if (mm_cinterion_parse_sind_response (response, &descr, NULL, &val, NULL) &&
+ g_str_equal (descr, "simstatus") &&
+ val == CINTERION_SIM_STATUS_INIT_COMPLETED) {
+ /* SIM ready! */
+ g_free (descr);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ g_free (descr);
+ }
+
+ /* Need to retry after 1 sec */
+ ctx = g_task_get_task_data (task);
+ g_assert (ctx->timeout_id == 0);
+ ctx->timeout_id = g_timeout_add_seconds (1, (GSourceFunc)simstatus_timeout_cb, task);
+}
+
+static void
+after_sim_unlock_context_step (GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+ AfterSimUnlockContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* if not supported or too much wait, skip */
+ if (self->priv->sind_simstatus_support != FEATURE_SUPPORTED || ctx->retries == 0) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Recheck */
+ ctx->retries--;
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SIND=\"simstatus\",2",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)simstatus_check_ready,
+ task);
+}
+
+static void
+sind_indicators_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+ g_autoptr(GError) error = NULL;
+ const gchar *response;
+
+ self = MM_BROADBAND_MODEM_CINTERION (_self);
+ if (!(response = mm_base_modem_at_command_finish (_self, res, &error))) {
+ self->priv->sind_psinfo_support = FEATURE_NOT_SUPPORTED;
+ mm_obj_dbg (self, "psinfo support? no");
+
+ self->priv->sind_simstatus_support = FEATURE_NOT_SUPPORTED;
+ mm_obj_dbg (self, "simstatus support? no");
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+
+ return;
+ }
+
+ if (g_regex_match_simple ("\\(\\s*psinfo\\s*,", response, 0, 0))
+ self->priv->sind_psinfo_support = FEATURE_SUPPORTED;
+ mm_obj_dbg (self, "psinfo support? %s", self->priv->sind_psinfo_support == FEATURE_SUPPORTED ? "yes":"no");
+
+ if (g_regex_match_simple ("\\(\\s*simstatus\\s*,", response, 0, 0))
+ self->priv->sind_simstatus_support = FEATURE_SUPPORTED;
+ mm_obj_dbg (self, "simstatus support? %s", self->priv->sind_simstatus_support == FEATURE_SUPPORTED ? "yes":"no");
+
+ after_sim_unlock_context_step (task);
+}
+
+static void
+after_sim_unlock (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ AfterSimUnlockContext *ctx;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ ctx = g_new0 (AfterSimUnlockContext, 1);
+ ctx->retries = MAX_AFTER_SIM_UNLOCK_RETRIES;
+ g_task_set_task_data (task, ctx, g_free);
+
+ /* check which indicators are available */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SIND=?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)sind_indicators_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup SIM hot swap (Modem interface) */
+
+static void
+cinterion_scks_unsolicited_handler (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemCinterion *self)
+{
+ guint scks;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &scks))
+ return;
+
+ switch (scks) {
+ case 0:
+ mm_obj_msg (self, "SIM removal detected");
+ break;
+ case 1:
+ mm_obj_msg (self, "SIM insertion detected");
+ break;
+ case 2:
+ mm_obj_msg (self, "SIM interface hardware deactivated (potentially non-electrically compatible SIM inserted)");
+ break;
+ case 3:
+ mm_obj_msg (self, "SIM interface hardware deactivated (technical problem, no precise diagnosis)");
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self));
+}
+
+static gboolean
+modem_setup_sim_hot_swap_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+cinterion_hot_swap_init_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ g_autoptr(GError) error = NULL;
+ MMPortSerialAt *primary;
+ MMPortSerialAt *secondary;
+
+ if (!mm_base_modem_at_command_finish (_self, res, &error)) {
+ g_prefix_error (&error, "Could not enable SCKS: ");
+ g_task_return_error (task, g_steal_pointer (&error));
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "SIM hot swap detect successfully enabled");
+
+ primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ primary,
+ self->priv->scks_regex,
+ (MMPortSerialAtUnsolicitedMsgFn) cinterion_scks_unsolicited_handler,
+ self,
+ NULL);
+
+ secondary = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+ if (secondary)
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ secondary,
+ self->priv->scks_regex,
+ (MMPortSerialAtUnsolicitedMsgFn) cinterion_scks_unsolicited_handler,
+ self,
+ NULL);
+
+ 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);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_setup_sim_hot_swap (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ mm_obj_dbg (self, "Enabling SCKS URCs for SIM hot swap detection");
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SCKS=1",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) cinterion_hot_swap_init_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* SIM hot swap cleanup (Modem interface) */
+
+static void
+modem_cleanup_sim_hot_swap (MMIfaceModem *self)
+{
+ mm_broadband_modem_sim_hot_swap_ports_context_reset (MM_BROADBAND_MODEM (self));
+}
+
+/*****************************************************************************/
+/* Create Bearer (Modem interface) */
+
+static MMBaseBearer *
+cinterion_modem_create_bearer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+broadband_bearer_cinterion_new_ready (GObject *unused,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_cinterion_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+ g_object_unref (task);
+}
+
+static void
+broadband_bearer_new_ready (GObject *unused,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+ g_object_unref (task);
+}
+
+static void
+common_create_bearer (GTask *task)
+{
+ MMBroadbandModemCinterion *self;
+
+ self = g_task_get_source_object (task);
+
+ switch (self->priv->swwan_support) {
+ case FEATURE_NOT_SUPPORTED:
+ mm_obj_dbg (self, "^SWWAN not supported, creating default bearer...");
+ mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
+ g_task_get_task_data (task),
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_new_ready,
+ task);
+ return;
+ case FEATURE_SUPPORTED:
+ mm_obj_dbg (self, "^SWWAN supported, creating cinterion bearer...");
+ mm_broadband_bearer_cinterion_new (MM_BROADBAND_MODEM_CINTERION (self),
+ g_task_get_task_data (task),
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_cinterion_new_ready,
+ task);
+ return;
+ case FEATURE_SUPPORT_UNKNOWN:
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+swwan_test_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+
+ /* Fetch the result to the SWWAN test. If no response given (error triggered),
+ * assume unsupported */
+ if (!mm_base_modem_at_command_finish (_self, res, NULL)) {
+ mm_obj_dbg (self, "SWWAN unsupported");
+ self->priv->swwan_support = FEATURE_NOT_SUPPORTED;
+ } else {
+ mm_obj_dbg (self, "SWWAN supported");
+ self->priv->swwan_support = FEATURE_SUPPORTED;
+ }
+
+ /* Go on and create the bearer */
+ common_create_bearer (task);
+}
+
+static void
+cinterion_modem_create_bearer (MMIfaceModem *_self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, g_object_ref (properties), g_object_unref);
+
+ /* Newer Cinterion modems may support SWWAN, which is the same as WWAN.
+ * Check to see if current modem supports it.*/
+ if (self->priv->swwan_support != FEATURE_SUPPORT_UNKNOWN) {
+ common_create_bearer (task);
+ return;
+ }
+
+ /* If we don't have a data port, don't even bother checking for ^SWWAN
+ * support. */
+ if (!mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET)) {
+ mm_obj_dbg (self, "skipping ^SWWAN check as no data port is available");
+ self->priv->swwan_support = FEATURE_NOT_SUPPORTED;
+ common_create_bearer (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "checking ^SWWAN support...");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SWWAN=?",
+ 6,
+ TRUE, /* may be cached */
+ (GAsyncReadyCallback) swwan_test_ready,
+ task);
+}
+
+/*****************************************************************************/
+
+static void
+setup_ports (MMBroadbandModem *_self)
+{
+ MMBroadbandModemCinterion *self = (MM_BROADBAND_MODEM_CINTERION (_self));
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_cinterion_parent_class)->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));
+
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->sysstart_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->scks_regex,
+ NULL, NULL, NULL);
+ }
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemCinterion *
+mm_broadband_modem_cinterion_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_CINTERION,
+ 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 (TTY) or Cinterion bearer (NET) supported */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+ MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_cinterion_init (MMBroadbandModemCinterion *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
+ MM_TYPE_BROADBAND_MODEM_CINTERION,
+ MMBroadbandModemCinterionPrivate);
+
+ /* Initialize private variables */
+ self->priv->initial_eps_bearer_cid = -1;
+ self->priv->sind_psinfo_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->swwan_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->smoni_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->sind_simstatus_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->sxrat_support = FEATURE_SUPPORT_UNKNOWN;
+
+ self->priv->ciev_regex = g_regex_new ("\\r\\n\\+CIEV:\\s*([a-z]+),(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->sysstart_regex = g_regex_new ("\\r\\n\\^SYSSTART.*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->scks_regex = g_regex_new ("\\^SCKS:\\s*([0-3])\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ self->priv->any_allowed = MM_MODEM_MODE_NONE;
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (object);
+
+ g_free (self->priv->sleep_mode_cmd);
+
+ if (self->priv->cnmi_supported_mode)
+ g_array_unref (self->priv->cnmi_supported_mode);
+ if (self->priv->cnmi_supported_mt)
+ g_array_unref (self->priv->cnmi_supported_mt);
+ if (self->priv->cnmi_supported_bm)
+ g_array_unref (self->priv->cnmi_supported_bm);
+ if (self->priv->cnmi_supported_ds)
+ g_array_unref (self->priv->cnmi_supported_ds);
+ if (self->priv->cnmi_supported_bfr)
+ g_array_unref (self->priv->cnmi_supported_bfr);
+ if (self->priv->sxrat_supported_rat)
+ g_array_unref (self->priv->sxrat_supported_rat);
+ if (self->priv->sxrat_supported_pref1)
+ g_array_unref (self->priv->sxrat_supported_pref1);
+
+ g_regex_unref (self->priv->ciev_regex);
+ g_regex_unref (self->priv->sysstart_regex);
+ g_regex_unref (self->priv->scks_regex);
+
+ G_OBJECT_CLASS (mm_broadband_modem_cinterion_parent_class)->finalize (object);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->create_bearer = cinterion_modem_create_bearer;
+ iface->create_bearer_finish = cinterion_modem_create_bearer_finish;
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = set_current_modes_finish;
+ iface->load_supported_bands = load_supported_bands;
+ iface->load_supported_bands_finish = load_supported_bands_finish;
+ iface->load_current_bands = load_current_bands;
+ iface->load_current_bands_finish = load_current_bands_finish;
+ iface->set_current_bands = set_current_bands;
+ iface->set_current_bands_finish = set_current_bands_finish;
+ iface->load_access_technologies = load_access_technologies;
+ iface->load_access_technologies_finish = load_access_technologies_finish;
+ iface->setup_flow_control = setup_flow_control;
+ iface->setup_flow_control_finish = setup_flow_control_finish;
+ iface->modem_after_sim_unlock = after_sim_unlock;
+ iface->modem_after_sim_unlock_finish = after_sim_unlock_finish;
+ iface->load_unlock_retries = load_unlock_retries;
+ iface->load_unlock_retries_finish = load_unlock_retries_finish;
+ iface->reset = mm_shared_cinterion_modem_reset;
+ iface->reset_finish = mm_shared_cinterion_modem_reset_finish;
+ iface->modem_power_down = modem_power_down;
+ iface->modem_power_down_finish = modem_power_down_finish;
+ iface->modem_power_off = modem_power_off;
+ iface->modem_power_off_finish = modem_power_off_finish;
+ iface->setup_sim_hot_swap = modem_setup_sim_hot_swap;
+ iface->setup_sim_hot_swap_finish = modem_setup_sim_hot_swap_finish;
+ iface->cleanup_sim_hot_swap = modem_cleanup_sim_hot_swap;
+}
+
+static MMIfaceModem *
+peek_parent_interface (MMSharedCinterion *self)
+{
+ return iface_modem_parent;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish;
+
+ iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+
+ iface->load_initial_eps_bearer = modem_3gpp_load_initial_eps_bearer;
+ iface->load_initial_eps_bearer_finish = modem_3gpp_load_initial_eps_bearer_finish;
+ iface->load_initial_eps_bearer_settings = modem_3gpp_load_initial_eps_bearer_settings;
+ iface->load_initial_eps_bearer_settings_finish = modem_3gpp_load_initial_eps_bearer_settings_finish;
+ iface->set_initial_eps_bearer_settings = modem_3gpp_set_initial_eps_bearer_settings;
+ iface->set_initial_eps_bearer_settings_finish = modem_3gpp_set_initial_eps_bearer_settings_finish;
+
+}
+
+static void
+iface_modem_messaging_init (MMIfaceModemMessaging *iface)
+{
+ iface->check_support = messaging_check_support;
+ iface->check_support_finish = messaging_check_support_finish;
+ iface->enable_unsolicited_events = messaging_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = messaging_enable_unsolicited_events_finish;
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = mm_shared_cinterion_location_load_capabilities;
+ iface->load_capabilities_finish = mm_shared_cinterion_location_load_capabilities_finish;
+ iface->enable_location_gathering = mm_shared_cinterion_enable_location_gathering;
+ iface->enable_location_gathering_finish = mm_shared_cinterion_enable_location_gathering_finish;
+ iface->disable_location_gathering = mm_shared_cinterion_disable_location_gathering;
+ iface->disable_location_gathering_finish = mm_shared_cinterion_disable_location_gathering_finish;
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedCinterion *self)
+{
+ return iface_modem_location_parent;
+}
+
+static void
+iface_modem_voice_init (MMIfaceModemVoice *iface)
+{
+ iface_modem_voice_parent = g_type_interface_peek_parent (iface);
+
+ iface->create_call = mm_shared_cinterion_create_call;
+
+ iface->check_support = mm_shared_cinterion_voice_check_support;
+ iface->check_support_finish = mm_shared_cinterion_voice_check_support_finish;
+ iface->enable_unsolicited_events = mm_shared_cinterion_voice_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = mm_shared_cinterion_voice_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = mm_shared_cinterion_voice_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = mm_shared_cinterion_voice_disable_unsolicited_events_finish;
+ iface->setup_unsolicited_events = mm_shared_cinterion_voice_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_cinterion_voice_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_cinterion_voice_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_voice_cleanup_unsolicited_events_finish;
+}
+
+static MMIfaceModemVoice *
+peek_parent_voice_interface (MMSharedCinterion *self)
+{
+ return iface_modem_voice_parent;
+}
+
+static void
+iface_modem_time_init (MMIfaceModemTime *iface)
+{
+ iface_modem_time_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = mm_shared_cinterion_time_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_cinterion_time_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_cinterion_time_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_time_cleanup_unsolicited_events_finish;
+}
+
+static MMIfaceModemTime *
+peek_parent_time_interface (MMSharedCinterion *self)
+{
+ return iface_modem_time_parent;
+}
+
+static void
+shared_cinterion_init (MMSharedCinterion *iface)
+{
+ iface->peek_parent_interface = peek_parent_interface;
+ iface->peek_parent_location_interface = peek_parent_location_interface;
+ iface->peek_parent_voice_interface = peek_parent_voice_interface;
+ iface->peek_parent_time_interface = peek_parent_time_interface;
+}
+
+static void
+iface_modem_signal_init (MMIfaceModemSignal *iface)
+{
+ iface_modem_signal_parent = g_type_interface_peek_parent (iface);
+
+ iface->check_support = signal_check_support;
+ iface->check_support_finish = signal_check_support_finish;
+ iface->load_values = signal_load_values;
+ iface->load_values_finish = signal_load_values_finish;
+}
+
+static void
+mm_broadband_modem_cinterion_class_init (MMBroadbandModemCinterionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemCinterionPrivate));
+
+ /* Virtual methods */
+ object_class->finalize = finalize;
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/cinterion/mm-broadband-modem-cinterion.h b/src/plugins/cinterion/mm-broadband-modem-cinterion.h
new file mode 100644
index 00000000..555ee084
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-modem-cinterion.h
@@ -0,0 +1,54 @@
+/* -*- 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) 2011 Ammonit Measurement GmbH
+ * Copyright (C) 2011 Google Inc.
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#ifndef MM_BROADBAND_MODEM_CINTERION_H
+#define MM_BROADBAND_MODEM_CINTERION_H
+
+#include "mm-broadband-modem.h"
+#include "mm-modem-helpers-cinterion.h"
+
+#define MM_TYPE_BROADBAND_MODEM_CINTERION (mm_broadband_modem_cinterion_get_type ())
+#define MM_BROADBAND_MODEM_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_CINTERION, MMBroadbandModemCinterion))
+#define MM_BROADBAND_MODEM_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_CINTERION, MMBroadbandModemCinterionClass))
+#define MM_IS_BROADBAND_MODEM_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_CINTERION))
+#define MM_IS_BROADBAND_MODEM_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_CINTERION))
+#define MM_BROADBAND_MODEM_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_CINTERION, MMBroadbandModemCinterionClass))
+
+typedef struct _MMBroadbandModemCinterion MMBroadbandModemCinterion;
+typedef struct _MMBroadbandModemCinterionClass MMBroadbandModemCinterionClass;
+typedef struct _MMBroadbandModemCinterionPrivate MMBroadbandModemCinterionPrivate;
+
+struct _MMBroadbandModemCinterion {
+ MMBroadbandModem parent;
+ MMBroadbandModemCinterionPrivate *priv;
+};
+
+struct _MMBroadbandModemCinterionClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_cinterion_get_type (void);
+
+MMBroadbandModemCinterion *mm_broadband_modem_cinterion_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+MMCinterionModemFamily mm_broadband_modem_cinterion_get_family (MMBroadbandModemCinterion * modem);
+
+#endif /* MM_BROADBAND_MODEM_CINTERION_H */
diff --git a/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.c b/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.c
new file mode 100644
index 00000000..740909b1
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.c
@@ -0,0 +1,167 @@
+/* -*- 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) 2021 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log.h"
+#include "mm-errors-types.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-location.h"
+#include "mm-iface-modem-voice.h"
+#include "mm-broadband-modem-mbim-cinterion.h"
+#include "mm-shared-cinterion.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void iface_modem_voice_init (MMIfaceModemVoice *iface);
+static void iface_modem_time_init (MMIfaceModemTime *iface);
+static void shared_cinterion_init (MMSharedCinterion *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModemLocation *iface_modem_location_parent;
+static MMIfaceModemVoice *iface_modem_voice_parent;
+static MMIfaceModemTime *iface_modem_time_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimCinterion, mm_broadband_modem_mbim_cinterion, MM_TYPE_BROADBAND_MODEM_MBIM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init))
+
+/*****************************************************************************/
+
+MMBroadbandModemMbimCinterion *
+mm_broadband_modem_mbim_cinterion_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_CINTERION,
+ 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,
+ /* 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,
+ MM_BROADBAND_MODEM_MBIM_INTEL_FIRMWARE_UPDATE_UNSUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_mbim_cinterion_init (MMBroadbandModemMbimCinterion *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->reset = mm_shared_cinterion_modem_reset;
+ iface->reset_finish = mm_shared_cinterion_modem_reset_finish;
+}
+
+static MMIfaceModem *
+peek_parent_interface (MMSharedCinterion *self)
+{
+ return iface_modem_parent;
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = mm_shared_cinterion_location_load_capabilities;
+ iface->load_capabilities_finish = mm_shared_cinterion_location_load_capabilities_finish;
+ iface->enable_location_gathering = mm_shared_cinterion_enable_location_gathering;
+ iface->enable_location_gathering_finish = mm_shared_cinterion_enable_location_gathering_finish;
+ iface->disable_location_gathering = mm_shared_cinterion_disable_location_gathering;
+ iface->disable_location_gathering_finish = mm_shared_cinterion_disable_location_gathering_finish;
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedCinterion *self)
+{
+ return iface_modem_location_parent;
+}
+
+static void
+iface_modem_voice_init (MMIfaceModemVoice *iface)
+{
+ iface_modem_voice_parent = g_type_interface_peek_parent (iface);
+
+ iface->create_call = mm_shared_cinterion_create_call;
+
+ iface->check_support = mm_shared_cinterion_voice_check_support;
+ iface->check_support_finish = mm_shared_cinterion_voice_check_support_finish;
+ iface->enable_unsolicited_events = mm_shared_cinterion_voice_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = mm_shared_cinterion_voice_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = mm_shared_cinterion_voice_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = mm_shared_cinterion_voice_disable_unsolicited_events_finish;
+ iface->setup_unsolicited_events = mm_shared_cinterion_voice_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_cinterion_voice_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_cinterion_voice_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_voice_cleanup_unsolicited_events_finish;
+}
+
+static MMIfaceModemVoice *
+peek_parent_voice_interface (MMSharedCinterion *self)
+{
+ return iface_modem_voice_parent;
+}
+
+static void
+iface_modem_time_init (MMIfaceModemTime *iface)
+{
+ iface_modem_time_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = mm_shared_cinterion_time_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_cinterion_time_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_cinterion_time_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_time_cleanup_unsolicited_events_finish;
+}
+
+static MMIfaceModemTime *
+peek_parent_time_interface (MMSharedCinterion *self)
+{
+ return iface_modem_time_parent;
+}
+
+static void
+shared_cinterion_init (MMSharedCinterion *iface)
+{
+ iface->peek_parent_interface = peek_parent_interface;
+ iface->peek_parent_location_interface = peek_parent_location_interface;
+ iface->peek_parent_voice_interface = peek_parent_voice_interface;
+ iface->peek_parent_time_interface = peek_parent_time_interface;
+}
+
+static void
+mm_broadband_modem_mbim_cinterion_class_init (MMBroadbandModemMbimCinterionClass *klass)
+{
+}
diff --git a/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.h b/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.h
new file mode 100644
index 00000000..a2f2ef68
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-modem-mbim-cinterion.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) 2021 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_BROADBAND_MODEM_MBIM_CINTERION_MBIM_H
+#define MM_BROADBAND_MODEM_MBIM_CINTERION_MBIM_H
+
+#include "mm-broadband-modem-mbim.h"
+
+#define MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION (mm_broadband_modem_mbim_cinterion_get_type ())
+#define MM_BROADBAND_MODEM_MBIM_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION, MMBroadbandModemMbimCinterion))
+#define MM_BROADBAND_MODEM_MBIM_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION, MMBroadbandModemMbimCinterionClass))
+#define MM_IS_BROADBAND_MODEM_MBIM_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION))
+#define MM_IS_BROADBAND_MODEM_MBIM_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION))
+#define MM_BROADBAND_MODEM_MBIM_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_CINTERION, MMBroadbandModemMbimCinterionClass))
+
+typedef struct _MMBroadbandModemMbimCinterion MMBroadbandModemMbimCinterion;
+typedef struct _MMBroadbandModemMbimCinterionClass MMBroadbandModemMbimCinterionClass;
+
+struct _MMBroadbandModemMbimCinterion {
+ MMBroadbandModemMbim parent;
+};
+
+struct _MMBroadbandModemMbimCinterionClass{
+ MMBroadbandModemMbimClass parent;
+};
+
+GType mm_broadband_modem_mbim_cinterion_get_type (void);
+
+MMBroadbandModemMbimCinterion *mm_broadband_modem_mbim_cinterion_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_MBIM_CINTERION_H */
diff --git a/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c b/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c
new file mode 100644
index 00000000..b94e63d3
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.c
@@ -0,0 +1,167 @@
+/* -*- 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) 2014 Ammonit Measurement GmbH
+ * Author: Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log.h"
+#include "mm-errors-types.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-location.h"
+#include "mm-iface-modem-voice.h"
+#include "mm-broadband-modem-qmi-cinterion.h"
+#include "mm-shared-cinterion.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void iface_modem_voice_init (MMIfaceModemVoice *iface);
+static void iface_modem_time_init (MMIfaceModemTime *iface);
+static void shared_cinterion_init (MMSharedCinterion *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModemLocation *iface_modem_location_parent;
+static MMIfaceModemVoice *iface_modem_voice_parent;
+static MMIfaceModemTime *iface_modem_time_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQmiCinterion, mm_broadband_modem_qmi_cinterion, MM_TYPE_BROADBAND_MODEM_QMI, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_CINTERION, shared_cinterion_init))
+
+/*****************************************************************************/
+
+MMBroadbandModemQmiCinterion *
+mm_broadband_modem_qmi_cinterion_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_CINTERION,
+ 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,
+ /* 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_cinterion_init (MMBroadbandModemQmiCinterion *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->reset = mm_shared_cinterion_modem_reset;
+ iface->reset_finish = mm_shared_cinterion_modem_reset_finish;
+}
+
+static MMIfaceModem *
+peek_parent_interface (MMSharedCinterion *self)
+{
+ return iface_modem_parent;
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = mm_shared_cinterion_location_load_capabilities;
+ iface->load_capabilities_finish = mm_shared_cinterion_location_load_capabilities_finish;
+ iface->enable_location_gathering = mm_shared_cinterion_enable_location_gathering;
+ iface->enable_location_gathering_finish = mm_shared_cinterion_enable_location_gathering_finish;
+ iface->disable_location_gathering = mm_shared_cinterion_disable_location_gathering;
+ iface->disable_location_gathering_finish = mm_shared_cinterion_disable_location_gathering_finish;
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedCinterion *self)
+{
+ return iface_modem_location_parent;
+}
+
+static void
+iface_modem_voice_init (MMIfaceModemVoice *iface)
+{
+ iface_modem_voice_parent = g_type_interface_peek_parent (iface);
+
+ iface->create_call = mm_shared_cinterion_create_call;
+
+ iface->check_support = mm_shared_cinterion_voice_check_support;
+ iface->check_support_finish = mm_shared_cinterion_voice_check_support_finish;
+ iface->enable_unsolicited_events = mm_shared_cinterion_voice_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = mm_shared_cinterion_voice_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = mm_shared_cinterion_voice_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = mm_shared_cinterion_voice_disable_unsolicited_events_finish;
+ iface->setup_unsolicited_events = mm_shared_cinterion_voice_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_cinterion_voice_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_cinterion_voice_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_voice_cleanup_unsolicited_events_finish;
+}
+
+static MMIfaceModemVoice *
+peek_parent_voice_interface (MMSharedCinterion *self)
+{
+ return iface_modem_voice_parent;
+}
+
+static void
+iface_modem_time_init (MMIfaceModemTime *iface)
+{
+ iface_modem_time_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = mm_shared_cinterion_time_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_cinterion_time_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_cinterion_time_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_time_cleanup_unsolicited_events_finish;
+}
+
+static MMIfaceModemTime *
+peek_parent_time_interface (MMSharedCinterion *self)
+{
+ return iface_modem_time_parent;
+}
+
+static void
+shared_cinterion_init (MMSharedCinterion *iface)
+{
+ iface->peek_parent_interface = peek_parent_interface;
+ iface->peek_parent_location_interface = peek_parent_location_interface;
+ iface->peek_parent_voice_interface = peek_parent_voice_interface;
+ iface->peek_parent_time_interface = peek_parent_time_interface;
+}
+
+static void
+mm_broadband_modem_qmi_cinterion_class_init (MMBroadbandModemQmiCinterionClass *klass)
+{
+}
diff --git a/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.h b/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.h
new file mode 100644
index 00000000..ac8f68be
--- /dev/null
+++ b/src/plugins/cinterion/mm-broadband-modem-qmi-cinterion.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) 2014 Ammonit Measurement GmbH
+ * Author: Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_BROADBAND_MODEM_QMI_CINTERION_QMI_H
+#define MM_BROADBAND_MODEM_QMI_CINTERION_QMI_H
+
+#include "mm-broadband-modem-qmi.h"
+
+#define MM_TYPE_BROADBAND_MODEM_QMI_CINTERION (mm_broadband_modem_qmi_cinterion_get_type ())
+#define MM_BROADBAND_MODEM_QMI_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_QMI_CINTERION, MMBroadbandModemQmiCinterion))
+#define MM_BROADBAND_MODEM_QMI_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_QMI_CINTERION, MMBroadbandModemQmiCinterionClass))
+#define MM_IS_BROADBAND_MODEM_QMI_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_QMI_CINTERION))
+#define MM_IS_BROADBAND_MODEM_QMI_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_QMI_CINTERION))
+#define MM_BROADBAND_MODEM_QMI_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_QMI_CINTERION, MMBroadbandModemQmiCinterionClass))
+
+typedef struct _MMBroadbandModemQmiCinterion MMBroadbandModemQmiCinterion;
+typedef struct _MMBroadbandModemQmiCinterionClass MMBroadbandModemQmiCinterionClass;
+
+struct _MMBroadbandModemQmiCinterion {
+ MMBroadbandModemQmi parent;
+};
+
+struct _MMBroadbandModemQmiCinterionClass{
+ MMBroadbandModemQmiClass parent;
+};
+
+GType mm_broadband_modem_qmi_cinterion_get_type (void);
+
+MMBroadbandModemQmiCinterion *mm_broadband_modem_qmi_cinterion_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_QMI_CINTERION_H */
diff --git a/src/plugins/cinterion/mm-modem-helpers-cinterion.c b/src/plugins/cinterion/mm-modem-helpers-cinterion.c
new file mode 100644
index 00000000..f22a998c
--- /dev/null
+++ b/src/plugins/cinterion/mm-modem-helpers-cinterion.c
@@ -0,0 +1,1804 @@
+/* -*- 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) 2014 Aleksander Morgado <aleksander@aleksander.es>
+ * Copyright (C) 2016 Trimble Navigation Limited
+ * Copyright (C) 2016 Matthew Stanger <matthew_stanger@trimble.com>
+ * Copyright (C) 2019 Purism SPC
+ */
+
+#include <config.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "ModemManager.h"
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+#include "mm-log-object.h"
+#include "mm-charsets.h"
+#include "mm-errors-types.h"
+#include "mm-modem-helpers-cinterion.h"
+#include "mm-modem-helpers.h"
+#include "mm-common-helpers.h"
+#include "mm-port-serial-at.h"
+
+/* Setup relationship between the 3G band bitmask in the modem and the bitmask
+ * in ModemManager. */
+typedef struct {
+ guint32 cinterion_band_flag;
+ MMModemBand mm_band;
+} CinterionBand;
+
+typedef struct {
+ MMCinterionRbBlock cinterion_band_block;
+ guint32 cinterion_band_flag;
+ MMModemBand mm_band;
+} CinterionBandEx;
+
+/* Table checked in PLS8-X/E/J/V/US, HC25 & PHS8 references. The table includes 2/3/4G
+ * frequencies. Depending on which one is configured, one access technology or
+ * the other will be used. This may conflict with the allowed mode configuration
+ * set, so you shouldn't for example set 3G frequency bands, and then use a
+ * 2G-only allowed mode. */
+static const CinterionBand cinterion_bands[] = {
+ { (1 << 0), MM_MODEM_BAND_EGSM },
+ { (1 << 1), MM_MODEM_BAND_DCS },
+ { (1 << 2), MM_MODEM_BAND_G850 },
+ { (1 << 3), MM_MODEM_BAND_PCS },
+ { (1 << 4), MM_MODEM_BAND_UTRAN_1 },
+ { (1 << 5), MM_MODEM_BAND_UTRAN_2 },
+ { (1 << 6), MM_MODEM_BAND_UTRAN_5 },
+ { (1 << 7), MM_MODEM_BAND_UTRAN_8 },
+ { (1 << 8), MM_MODEM_BAND_UTRAN_6 },
+ { (1 << 9), MM_MODEM_BAND_UTRAN_4 },
+ { (1 << 10), MM_MODEM_BAND_UTRAN_19 },
+ { (1 << 12), MM_MODEM_BAND_UTRAN_3 },
+ { (1 << 13), MM_MODEM_BAND_EUTRAN_1 },
+ { (1 << 14), MM_MODEM_BAND_EUTRAN_2 },
+ { (1 << 15), MM_MODEM_BAND_EUTRAN_3 },
+ { (1 << 16), MM_MODEM_BAND_EUTRAN_4 },
+ { (1 << 17), MM_MODEM_BAND_EUTRAN_5 },
+ { (1 << 18), MM_MODEM_BAND_EUTRAN_7 },
+ { (1 << 19), MM_MODEM_BAND_EUTRAN_8 },
+ { (1 << 20), MM_MODEM_BAND_EUTRAN_17 },
+ { (1 << 21), MM_MODEM_BAND_EUTRAN_20 },
+ { (1 << 22), MM_MODEM_BAND_EUTRAN_13 },
+ { (1 << 24), MM_MODEM_BAND_EUTRAN_19 }
+};
+
+static const CinterionBandEx cinterion_bands_ex[] = {
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000001, MM_MODEM_BAND_EGSM },
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000002, MM_MODEM_BAND_DCS },
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000004, MM_MODEM_BAND_G850 },
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000008, MM_MODEM_BAND_PCS },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000001, MM_MODEM_BAND_UTRAN_1 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000002, MM_MODEM_BAND_UTRAN_2 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000004, MM_MODEM_BAND_UTRAN_3 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000008, MM_MODEM_BAND_UTRAN_4 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000010, MM_MODEM_BAND_UTRAN_5 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000020, MM_MODEM_BAND_UTRAN_6 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000080, MM_MODEM_BAND_UTRAN_8 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000100, MM_MODEM_BAND_UTRAN_9 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00040000, MM_MODEM_BAND_UTRAN_19 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000001, MM_MODEM_BAND_EUTRAN_1 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000002, MM_MODEM_BAND_EUTRAN_2 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000004, MM_MODEM_BAND_EUTRAN_3 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000008, MM_MODEM_BAND_EUTRAN_4 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000010, MM_MODEM_BAND_EUTRAN_5 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000040, MM_MODEM_BAND_EUTRAN_7 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000080, MM_MODEM_BAND_EUTRAN_8 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000800, MM_MODEM_BAND_EUTRAN_12 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00001000, MM_MODEM_BAND_EUTRAN_13 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00010000, MM_MODEM_BAND_EUTRAN_17 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00020000, MM_MODEM_BAND_EUTRAN_18 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00040000, MM_MODEM_BAND_EUTRAN_19 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00080000, MM_MODEM_BAND_EUTRAN_20 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x02000000, MM_MODEM_BAND_EUTRAN_26 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x08000000, MM_MODEM_BAND_EUTRAN_28 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x10000000, MM_MODEM_BAND_EUTRAN_29 },
+ { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000020, MM_MODEM_BAND_EUTRAN_38 },
+ { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000040, MM_MODEM_BAND_EUTRAN_39 },
+ { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000080, MM_MODEM_BAND_EUTRAN_40 },
+ { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000100, MM_MODEM_BAND_EUTRAN_41 }
+};
+
+static const CinterionBandEx cinterion_bands_imt[] = {
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000004, MM_MODEM_BAND_EGSM },
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000010, MM_MODEM_BAND_DCS },
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000020, MM_MODEM_BAND_PCS },
+ { MM_CINTERION_RB_BLOCK_GSM, 0x00000040, MM_MODEM_BAND_G850 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000001, MM_MODEM_BAND_UTRAN_1 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000002, MM_MODEM_BAND_UTRAN_2 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000008, MM_MODEM_BAND_UTRAN_4 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000010, MM_MODEM_BAND_UTRAN_5 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000080, MM_MODEM_BAND_UTRAN_8 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00000100, MM_MODEM_BAND_UTRAN_9 },
+ { MM_CINTERION_RB_BLOCK_UMTS, 0x00040000, MM_MODEM_BAND_UTRAN_19 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000001, MM_MODEM_BAND_EUTRAN_1 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000002, MM_MODEM_BAND_EUTRAN_2 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000004, MM_MODEM_BAND_EUTRAN_3 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000008, MM_MODEM_BAND_EUTRAN_4 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000010, MM_MODEM_BAND_EUTRAN_5 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000040, MM_MODEM_BAND_EUTRAN_7 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000080, MM_MODEM_BAND_EUTRAN_8 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000800, MM_MODEM_BAND_EUTRAN_12 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00020000, MM_MODEM_BAND_EUTRAN_18 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00040000, MM_MODEM_BAND_EUTRAN_19 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00080000, MM_MODEM_BAND_EUTRAN_20 },
+ { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x08000000, MM_MODEM_BAND_EUTRAN_28 }
+};
+
+/* Check valid combinations in 2G-only devices */
+#define VALIDATE_2G_BAND(cinterion_mask) \
+ (cinterion_mask == 1 || \
+ cinterion_mask == 2 || \
+ cinterion_mask == 4 || \
+ cinterion_mask == 8 || \
+ cinterion_mask == 3 || \
+ cinterion_mask == 5 || \
+ cinterion_mask == 10 || \
+ cinterion_mask == 12 || \
+ cinterion_mask == 15)
+
+/*****************************************************************************/
+/* ^SCFG (3G+LTE) test parser
+ *
+ * Example 3G:
+ * AT^SCFG=?
+ * ...
+ * ^SCFG: "MEShutdown/OnIgnition",("on","off")
+ * ^SCFG: "Radio/Band",("1-511","0-1")
+ * ^SCFG: "Radio/NWSM",("0","1","2")
+ * ...
+ * ^SCFG: "Radio/Band\",("1"-"147")
+ *
+ * Example LTE1 (GSM charset):
+ * AT^SCFG=?
+ * ...
+ * ^SCFG: "Radio/Band/2G",("0x00000004"-"0x00000074")
+ * ^SCFG: "Radio/Band/3G",("0x00000001"-"0x0004019B")
+ * ^SCFG: "Radio/Band/4G",("0x00000001"-"0x080E08DF")
+ * ...
+ *
+ * Example LTE1 (UCS2 charset):
+ * AT^SCFG=?
+ * ...
+ * ^SCFG: "Radio/Band/2G",("0030007800300030003000300030003000300034"-"0030007800300030003000300030003000370034")
+ * ^SCFG: "Radio/Band/3G",("0030007800300030003000300030003000300031"-"0030007800300030003000340030003100390042")
+ * ^SCFG: "Radio/Band/4G",("0030007800300030003000300030003000300031"-"0030007800300038003000450030003800440046")
+ * ...
+ *
+ * Example LTE2 (all charsets):
+ * AT^SCFG=?
+ * ...
+ * ^SCFG: "Radio/Band/2G",("00000001-0000000f"),,("0","1")
+ * ^SCFG: "Radio/Band/3G",("00000001-000400b5"),,("0","1")
+ * ^SCFG: "Radio/Band/4G",("00000001-8a0e00d5"),("00000002-000001e2"),("0","1")
+ * ...
+ */
+
+static void
+parse_bands (guint bandlist,
+ GArray **bands,
+ MMCinterionRbBlock block,
+ MMCinterionModemFamily modem_family)
+{
+ guint i;
+ const CinterionBandEx *ref_bands;
+ guint nb_ref_bands;
+
+ if (!bandlist)
+ return;
+
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) {
+ ref_bands = cinterion_bands_imt;
+ nb_ref_bands = G_N_ELEMENTS (cinterion_bands_imt);
+ } else {
+ ref_bands = cinterion_bands_ex;
+ nb_ref_bands = G_N_ELEMENTS (cinterion_bands_ex);
+ }
+
+ for (i = 0; i < nb_ref_bands; i++) {
+ if (block == ref_bands[i].cinterion_band_block && (bandlist & ref_bands[i].cinterion_band_flag)) {
+ if (G_UNLIKELY (!*bands))
+ *bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 23);
+ g_array_append_val (*bands, ref_bands[i].mm_band);
+ }
+ }
+}
+
+static guint
+take_and_convert_from_matched_string (gchar *str,
+ MMModemCharset charset,
+ MMCinterionModemFamily modem_family,
+ GError **error)
+{
+ guint val = 0;
+ g_autofree gchar *utf8 = NULL;
+ g_autofree gchar *taken_str = str;
+
+ if (!taken_str) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "Couldn't convert to integer number: no input string");
+ return 0;
+ }
+
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) {
+ utf8 = mm_modem_charset_str_to_utf8 (taken_str, -1, charset, FALSE, error);
+ if (!utf8) {
+ g_prefix_error (error, "Couldn't convert to integer number: ");
+ return 0;
+ }
+ }
+
+ if (!mm_get_uint_from_hex_str (utf8 ? utf8 : taken_str, &val)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't convert to integer number: wrong hex encoding: %s", utf8 ? utf8 : taken_str);
+ return 0;
+ }
+
+ return val;
+}
+
+gboolean
+mm_cinterion_parse_scfg_test (const gchar *response,
+ MMCinterionModemFamily modem_family,
+ MMModemCharset charset,
+ GArray **supported_bands,
+ MMCinterionRadioBandFormat *format,
+ GError **error)
+{
+ g_autoptr(GRegex) r1 = NULL;
+ g_autoptr(GMatchInfo) match_info1 = NULL;
+ g_autoptr(GRegex) r2 = NULL;
+ g_autoptr(GMatchInfo) match_info2 = NULL;
+ GError *inner_error = NULL;
+ GArray *bands = NULL;
+
+ g_assert (format);
+
+ if (!response) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response");
+ return FALSE;
+ }
+
+ r1 = g_regex_new ("\\^SCFG:\\s*\"Radio/Band\",\\((?:\")?([0-9]*)(?:\")?-(?:\")?([0-9]*)(?:\")?.*\\)",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL);
+ g_assert (r1 != NULL);
+
+ g_regex_match_full (r1, response, strlen (response), 0, 0, &match_info1, &inner_error);
+ if (inner_error)
+ goto finish;
+ if (g_match_info_matches (match_info1)) {
+ g_autofree gchar *maxbandstr = NULL;
+ guint maxband = 0;
+
+ *format = MM_CINTERION_RADIO_BAND_FORMAT_SINGLE;
+
+ maxbandstr = mm_get_string_unquoted_from_match_info (match_info1, 2);
+ if (maxbandstr)
+ mm_get_uint_from_str (maxbandstr, &maxband);
+
+ if (maxband == 0) {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^SCFG=? response");
+ } else {
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cinterion_bands); i++) {
+ if (maxband & cinterion_bands[i].cinterion_band_flag) {
+ if (G_UNLIKELY (!bands))
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9);
+ g_array_append_val (bands, cinterion_bands[i].mm_band);
+ }
+ }
+ }
+ goto finish;
+ }
+
+ r2 = g_regex_new ("\\^SCFG:\\s*\"Radio/Band/([234]G)\","
+ "\\(\"?([0-9A-Fa-fx]*)\"?-\"?([0-9A-Fa-fx]*)\"?\\)"
+ "(,*\\(\"?([0-9A-Fa-fx]*)\"?-\"?([0-9A-Fa-fx]*)\"?\\))?",
+ 0, 0, NULL);
+ g_assert (r2 != NULL);
+
+ g_regex_match_full (r2, response, strlen (response), 0, 0, &match_info2, &inner_error);
+ if (inner_error)
+ goto finish;
+
+ while (g_match_info_matches (match_info2)) {
+ g_autofree gchar *techstr = NULL;
+ guint maxband;
+
+ *format = MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE;
+
+ techstr = mm_get_string_unquoted_from_match_info (match_info2, 1);
+ if (g_strcmp0 (techstr, "2G") == 0) {
+ maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 3),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_GSM, modem_family);
+ } else if (g_strcmp0 (techstr, "3G") == 0) {
+ maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 3),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_UMTS, modem_family);
+ } else if (g_strcmp0 (techstr, "4G") == 0) {
+ maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 3),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_LTE_LOW, modem_family);
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_DEFAULT) {
+ maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 6),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_LTE_HIGH, modem_family);
+ }
+ } else {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^SCFG=? response");
+ break;
+ }
+
+ g_match_info_next (match_info2, NULL);
+ }
+
+finish:
+ /* set error only if not already given */
+ if (!bands && !inner_error)
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "No valid bands found in ^SCFG=? response");
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ g_assert (bands != NULL && bands->len > 0);
+ *supported_bands = bands;
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* ^SCFG response parser (2 types: 2G/3G and LTE)
+ *
+ * Example (3G):
+ * AT^SCFG="Radio/Band"
+ * ^SCFG: "Radio/Band",127
+ *
+ * Example (2G, UCS-2):
+ * AT+SCFG="Radio/Band"
+ * ^SCFG: "Radio/Band","0031","0031"
+ *
+ * Example (2G):
+ * AT+SCFG="Radio/Band"
+ * ^SCFG: "Radio/Band","3","3"
+ *
+ * Example LTE1 (GSM charset):
+ * AT^SCFG=?
+ * ...
+ * ^SCFG: "Radio/Band/2G","0x00000074"
+ * ^SCFG: "Radio/Band/3G","0x0004019B"
+ * ^SCFG: "Radio/Band/4G","0x080E08DF"
+ * ...
+ * AT^SCFG=?
+ * ...
+ * Example LTE1 (UCS2 charset):
+ * AT^SCFG=?
+ * ...
+ * ^SCFG: "Radio/Band/2G","0030007800300030003000300030003000370034"
+ * ^SCFG: "Radio/Band/3G","0030007800300030003000340030003100390042"
+ * ^SCFG: "Radio/Band/4G","0030007800300038003000450030003800440046"
+ * ...
+ * Example LTE2 (all charsets):
+ * AT^SCFG=?
+ * ...
+ * ^SCFG: "Radio/Band/2G","0000000f"
+ * ^SCFG: "Radio/Band/3G","000400b5"
+ * ^SCFG: "Radio/Band/4G","8a0e00d5","000000e2"
+ * ...
+ */
+
+gboolean
+mm_cinterion_parse_scfg_response (const gchar *response,
+ MMCinterionModemFamily modem_family,
+ MMModemCharset charset,
+ GArray **current_bands,
+ MMCinterionRadioBandFormat format,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ GArray *bands = NULL;
+
+ if (!response) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response");
+ return FALSE;
+ }
+
+ if (format == MM_CINTERION_RADIO_BAND_FORMAT_SINGLE) {
+ r = g_regex_new ("\\^SCFG:\\s*\"Radio/Band\",\\s*\"?([0-9a-fA-F]*)\"?", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (inner_error)
+ goto finish;
+
+ if (g_match_info_matches (match_info)) {
+ g_autofree gchar *currentstr = NULL;
+ guint current = 0;
+
+ currentstr = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (currentstr)
+ mm_get_uint_from_str (currentstr, &current);
+
+ if (current == 0) {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^SCFG? response");
+ } else {
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cinterion_bands); i++) {
+ if (current & cinterion_bands[i].cinterion_band_flag) {
+ if (G_UNLIKELY (!bands))
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9);
+ g_array_append_val (bands, cinterion_bands[i].mm_band);
+ }
+ }
+ }
+ }
+ } else if (format == MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE) {
+ r = g_regex_new ("\\^SCFG:\\s*\"Radio/Band/([234]G)\",\"?([0-9A-Fa-fx]*)\"?,?\"?([0-9A-Fa-fx]*)?\"?",
+ 0, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (inner_error)
+ goto finish;
+
+ while (g_match_info_matches (match_info)) {
+ g_autofree gchar *techstr = NULL;
+ guint current;
+
+ techstr = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (g_strcmp0 (techstr, "2G") == 0) {
+ current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 2),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_GSM, modem_family);
+
+ } else if (g_strcmp0 (techstr, "3G") == 0) {
+ current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 2),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_UMTS, modem_family);
+ } else if (g_strcmp0 (techstr, "4G") == 0) {
+ current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 2),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_LTE_LOW, modem_family);
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_DEFAULT) {
+ current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 3),
+ charset, modem_family, &inner_error);
+ if (inner_error)
+ break;
+ parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_LTE_HIGH, modem_family);
+ }
+ } else {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^SCFG? response");
+ break;
+ }
+
+ g_match_info_next (match_info, NULL);
+ }
+ } else
+ g_assert_not_reached ();
+
+finish:
+ /* set error only if not already given */
+ if (!bands && !inner_error)
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "No valid bands found in ^SCFG response");
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ g_assert (bands != NULL && bands->len > 0);
+ *current_bands = bands;
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* +CNMI test parser
+ *
+ * Example (PHS8):
+ * AT+CNMI=?
+ * +CNMI: (0,1,2),(0,1),(0,2),(0),(1)
+ */
+
+gboolean
+mm_cinterion_parse_cnmi_test (const gchar *response,
+ GArray **supported_mode,
+ GArray **supported_mt,
+ GArray **supported_bm,
+ GArray **supported_ds,
+ GArray **supported_bfr,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autoptr(GArray) tmp_supported_mode = NULL;
+ g_autoptr(GArray) tmp_supported_mt = NULL;
+ g_autoptr(GArray) tmp_supported_bm = NULL;
+ g_autoptr(GArray) tmp_supported_ds = NULL;
+ g_autoptr(GArray) tmp_supported_bfr = NULL;
+ GError *inner_error = NULL;
+
+ if (!response) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response");
+ return FALSE;
+ }
+
+ r = g_regex_new ("\\+CNMI:\\s*\\((.*)\\),\\((.*)\\),\\((.*)\\),\\((.*)\\),\\((.*)\\)",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+ 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ if (supported_mode) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 1);
+ tmp_supported_mode = mm_parse_uint_list (str, &inner_error);
+ if (inner_error)
+ goto out;
+ }
+ if (supported_mt) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 2);
+ tmp_supported_mt = mm_parse_uint_list (str, &inner_error);
+ if (inner_error)
+ goto out;
+ }
+ if (supported_bm) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 3);
+ tmp_supported_bm = mm_parse_uint_list (str, &inner_error);
+ if (inner_error)
+ goto out;
+ }
+ if (supported_ds) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 4);
+ tmp_supported_ds = mm_parse_uint_list (str, &inner_error);
+ if (inner_error)
+ goto out;
+ }
+ if (supported_bfr) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 5);
+ tmp_supported_bfr = mm_parse_uint_list (str, &inner_error);
+ if (inner_error)
+ goto out;
+ }
+ }
+
+out:
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (supported_mode)
+ *supported_mode = g_steal_pointer (&tmp_supported_mode);
+ if (supported_mt)
+ *supported_mt = g_steal_pointer (&tmp_supported_mt);
+ if (supported_bm)
+ *supported_bm = g_steal_pointer (&tmp_supported_bm);
+ if (supported_ds)
+ *supported_ds = g_steal_pointer (&tmp_supported_ds);
+ if (supported_bfr)
+ *supported_bfr = g_steal_pointer (&tmp_supported_bfr);
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* ^SXRAT test parser
+ *
+ * Example (ELS61-E2):
+ * AT^SXRAT=?
+ * ^SXRAT: (0-6),(0,2,3),(0,2,3)
+ */
+
+gboolean
+mm_cinterion_parse_sxrat_test (const gchar *response,
+ GArray **supported_rat,
+ GArray **supported_pref1,
+ GArray **supported_pref2,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ GArray *tmp_supported_rat = NULL;
+ GArray *tmp_supported_pref1 = NULL;
+ GArray *tmp_supported_pref2 = NULL;
+
+ if (!response) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response");
+ return FALSE;
+ }
+
+ r = g_regex_new ("\\^SXRAT:\\s*\\(([^\\)]*)\\),\\(([^\\)]*)\\)(,\\(([^\\)]*)\\))?(?:\\r\\n)?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+ 0, NULL);
+
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+
+ if (!inner_error && g_match_info_matches (match_info)) {
+ if (supported_rat) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 1);
+ tmp_supported_rat = mm_parse_uint_list (str, &inner_error);
+
+ if (inner_error)
+ goto out;
+ }
+ if (supported_pref1) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 2);
+ tmp_supported_pref1 = mm_parse_uint_list (str, &inner_error);
+
+ if (inner_error)
+ goto out;
+ }
+ if (supported_pref2) {
+ g_autofree gchar *str = NULL;
+
+ /* this match is optional */
+ str = mm_get_string_unquoted_from_match_info (match_info, 4);
+ if (str) {
+ tmp_supported_pref2 = mm_parse_uint_list (str, &inner_error);
+
+ if (inner_error)
+ goto out;
+ }
+ }
+ }
+
+out:
+
+ if (inner_error) {
+ g_clear_pointer (&tmp_supported_rat, g_array_unref);
+ g_clear_pointer (&tmp_supported_pref1, g_array_unref);
+ g_clear_pointer (&tmp_supported_pref2, g_array_unref);
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (supported_rat)
+ *supported_rat = tmp_supported_rat;
+ if (supported_pref1)
+ *supported_pref1 = tmp_supported_pref1;
+ if (supported_pref2)
+ *supported_pref2 = tmp_supported_pref2;
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Build Cinterion-specific band value */
+
+gboolean
+mm_cinterion_build_band (GArray *bands,
+ guint *supported,
+ gboolean only_2g,
+ MMCinterionRadioBandFormat format,
+ MMCinterionModemFamily modem_family,
+ guint *out_band,
+ GError **error)
+{
+ guint band[MM_CINTERION_RB_BLOCK_N] = { 0 };
+
+ if (format == MM_CINTERION_RADIO_BAND_FORMAT_SINGLE) {
+ /* The special case of ANY should be treated separately. */
+ if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
+ if (supported)
+ band[MM_CINTERION_RB_BLOCK_LEGACY] = supported[MM_CINTERION_RB_BLOCK_LEGACY];
+ } else {
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cinterion_bands); i++) {
+ guint j;
+
+ for (j = 0; j < bands->len; j++) {
+ if (g_array_index (bands, MMModemBand, j) == cinterion_bands[i].mm_band) {
+ band[MM_CINTERION_RB_BLOCK_LEGACY] |= cinterion_bands[i].cinterion_band_flag;
+ break;
+ }
+ }
+ }
+
+ /* 2G-only modems only support a subset of the possible band
+ * combinations. Detect it early and error out.
+ */
+ if (only_2g && !VALIDATE_2G_BAND (band[MM_CINTERION_RB_BLOCK_LEGACY]))
+ band[MM_CINTERION_RB_BLOCK_LEGACY] = 0;
+ }
+
+ if (band[MM_CINTERION_RB_BLOCK_LEGACY] == 0) {
+ g_autofree gchar *bands_string = NULL;
+
+ bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands->data, bands->len);
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "The given band combination is not supported: '%s'",
+ bands_string);
+ return FALSE;
+ }
+
+ } else { /* format == MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE */
+ if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
+ if (supported)
+ memcpy (band, supported, sizeof (guint) * MM_CINTERION_RB_BLOCK_N);
+ } else {
+ guint i;
+ const CinterionBandEx *ref_bands;
+ guint nb_ref_bands;
+
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) {
+ ref_bands = cinterion_bands_imt;
+ nb_ref_bands = G_N_ELEMENTS (cinterion_bands_imt);
+ } else {
+ ref_bands = cinterion_bands_ex;
+ nb_ref_bands = G_N_ELEMENTS (cinterion_bands_ex);
+ }
+
+ for (i = 0; i < nb_ref_bands; i++) {
+ guint j;
+
+ for (j = 0; j < bands->len; j++) {
+ if (g_array_index (bands, MMModemBand, j) == ref_bands[i].mm_band) {
+ band[ref_bands[i].cinterion_band_block] |= ref_bands[i].cinterion_band_flag;
+ break;
+ }
+ }
+ }
+ }
+
+ /* this modem family does not allow disabling all bands in a given technology through this command */
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT &&
+ (!band[MM_CINTERION_RB_BLOCK_GSM] ||
+ !band[MM_CINTERION_RB_BLOCK_UMTS] ||
+ !band[MM_CINTERION_RB_BLOCK_LTE_LOW])) {
+ g_autofree gchar *bands_string = NULL;
+
+ bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands->data, bands->len);
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "The given band combination is not supported: '%s'",
+ bands_string);
+ return FALSE;
+ }
+ }
+
+ memcpy (out_band, band, sizeof (guint) * MM_CINTERION_RB_BLOCK_N);
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Single ^SIND response parser */
+
+gboolean
+mm_cinterion_parse_sind_response (const gchar *response,
+ gchar **description,
+ guint *mode,
+ guint *value,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ guint errors = 0;
+
+ if (!response) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response");
+ return FALSE;
+ }
+
+ r = g_regex_new ("\\^SIND:\\s*(.*),(\\d+),(\\d+)(\\r\\n)?", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (g_regex_match (r, response, 0, &match_info)) {
+ if (description) {
+ *description = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (*description == NULL)
+ errors++;
+ }
+ if (mode && !mm_get_uint_from_match_info (match_info, 2, mode))
+ errors++;
+ if (value && !mm_get_uint_from_match_info (match_info, 3, value))
+ errors++;
+ } else
+ errors++;
+
+ if (errors > 0) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed parsing ^SIND response");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* ^SWWAN read parser
+ *
+ * Description: Parses <cid>, <state>[, <WWAN adapter>] or CME ERROR from SWWAN.
+ *
+ * The method returns a MMSwwanState with the connection status of a single
+ * PDP context, the one being queried via the cid given as input.
+ *
+ * Note that we use CID for matching because the WWAN adapter field is optional
+ * it seems.
+ *
+ * Read Command
+ * AT^SWWAN?
+ * Response(s)
+ * [^SWWAN: <cid>, <state>[, <WWAN adapter>]]
+ * [^SWWAN: ...]
+ * OK
+ * ERROR
+ * +CME ERROR: <err>
+ *
+ * Examples:
+ * OK - If no WWAN connection is active, then read command just returns OK
+ * ^SWWAN: 3,1,1 - 3rd PDP Context, Activated, First WWAN Adaptor
+ * +CME ERROR: ? -
+ */
+
+enum {
+ MM_SWWAN_STATE_DISCONNECTED = 0,
+ MM_SWWAN_STATE_CONNECTED = 1,
+};
+
+MMBearerConnectionStatus
+mm_cinterion_parse_swwan_response (const gchar *response,
+ guint cid,
+ gpointer log_object,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ MMBearerConnectionStatus status;
+
+ g_assert (response);
+
+ /* If no WWAN connection is active, then ^SWWAN read command just returns OK
+ * (which we receive as an empty string) */
+ if (!response[0])
+ return MM_BEARER_CONNECTION_STATUS_DISCONNECTED;
+
+ if (!g_str_has_prefix (response, "^SWWAN:")) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^SWWAN response: '%s'", response);
+ return MM_BEARER_CONNECTION_STATUS_UNKNOWN;
+ }
+
+ r = g_regex_new ("\\^SWWAN:\\s*(\\d+),\\s*(\\d+)(?:,\\s*(\\d+))?(?:\\r\\n)?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL);
+ g_assert (r != NULL);
+
+ status = MM_BEARER_CONNECTION_STATUS_UNKNOWN;
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ while (!inner_error && g_match_info_matches (match_info)) {
+ guint read_state;
+ guint read_cid;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &read_cid))
+ mm_obj_warn (log_object, "couldn't read cid in ^SWWAN response: %s", response);
+ else if (!mm_get_uint_from_match_info (match_info, 2, &read_state))
+ mm_obj_warn (log_object, "couldn't read state in ^SWWAN response: %s", response);
+ else if (read_cid == cid) {
+ if (read_state == MM_SWWAN_STATE_CONNECTED) {
+ status = MM_BEARER_CONNECTION_STATUS_CONNECTED;
+ break;
+ }
+ if (read_state == MM_SWWAN_STATE_DISCONNECTED) {
+ status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED;
+ break;
+ }
+ mm_obj_warn (log_object, "invalid state read in ^SWWAN response: %u", read_state);
+ break;
+ }
+ g_match_info_next (match_info, &inner_error);
+ }
+
+ if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN)
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No state returned for CID %u", cid);
+
+ return status;
+}
+
+/*****************************************************************************/
+/* ^SGAUTH response parser */
+
+/* at^sgauth?
+ * ^SGAUTH: 1,2,"vf"
+ * ^SGAUTH: 3,0,""
+ * ^SGAUTH: 4,0
+ *
+ * OK
+ */
+
+gboolean
+mm_cinterion_parse_sgauth_response (const gchar *response,
+ guint cid,
+ MMBearerAllowedAuth *out_auth,
+ gchar **out_username,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+
+ r = g_regex_new ("\\^SGAUTH:\\s*(\\d+),(\\d+),?\"?([a-zA-Z0-9_-]+)?\"?", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, NULL);
+ while (g_match_info_matches (match_info)) {
+ guint sgauth_cid = 0;
+
+ if (mm_get_uint_from_match_info (match_info, 1, &sgauth_cid) &&
+ (sgauth_cid == cid)) {
+ guint cinterion_auth_type = 0;
+
+ mm_get_uint_from_match_info (match_info, 2, &cinterion_auth_type);
+ *out_auth = mm_auth_type_from_cinterion_auth_type (cinterion_auth_type);
+ *out_username = mm_get_string_unquoted_from_match_info (match_info, 3);
+ return TRUE;
+ }
+ g_match_info_next (match_info, NULL);
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
+ "Auth settings for context %u not found", cid);
+ return FALSE;
+}
+
+/*****************************************************************************/
+/* ^SMONG response parser */
+
+static gboolean
+get_access_technology_from_smong_gprs_status (guint gprs_status,
+ MMModemAccessTechnology *out,
+ GError **error)
+{
+ switch (gprs_status) {
+ case 0:
+ *out = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ return TRUE;
+ case 1:
+ case 2:
+ *out = MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
+ return TRUE;
+ case 3:
+ case 4:
+ *out = MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
+ return TRUE;
+ default:
+ break;
+ }
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_INVALID_ARGS,
+ "Couldn't get network capabilities, "
+ "unsupported GPRS status value: '%u'",
+ gprs_status);
+ return FALSE;
+}
+
+gboolean
+mm_cinterion_parse_smong_response (const gchar *response,
+ MMModemAccessTechnology *access_tech,
+ GError **error)
+{
+ guint value = 0;
+ GError *inner_error = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autoptr(GRegex) regex = NULL;
+
+ /* The AT^SMONG command returns a cell info table, where the second
+ * column identifies the "GPRS status", which is exactly what we want.
+ * So we'll try to read that second number in the values row.
+ *
+ * AT^SMONG
+ * GPRS Monitor
+ * BCCH G PBCCH PAT MCC MNC NOM TA RAC # Cell #
+ * 0776 1 - - 214 03 2 00 01
+ * OK
+ */
+ regex = g_regex_new (".*GPRS Monitor(?:\r\n)*"
+ "BCCH\\s*G.*\\r\\n"
+ "\\s*(\\d+)\\s*(\\d+)\\s*",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+ 0, NULL);
+ g_assert (regex);
+
+ g_regex_match_full (regex, response, strlen (response), 0, 0, &match_info, &inner_error);
+
+ if (inner_error) {
+ g_prefix_error (&inner_error, "Failed to match AT^SMONG response: ");
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (!g_match_info_matches (match_info) || !mm_get_uint_from_match_info (match_info, 2, &value)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't read 'GPRS status' field from AT^SMONG response");
+ return FALSE;
+ }
+
+ return get_access_technology_from_smong_gprs_status (value, access_tech, error);
+}
+
+/*****************************************************************************/
+/* ^SIND psinfo helper */
+
+MMModemAccessTechnology
+mm_cinterion_get_access_technology_from_sind_psinfo (guint val,
+ gpointer log_object)
+{
+ switch (val) {
+ case 0:
+ return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ case 1:
+ case 2:
+ return MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
+ case 3:
+ case 4:
+ return MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
+ case 5:
+ case 6:
+ return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ case 7:
+ case 8:
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
+ case 9:
+ case 10:
+ return (MM_MODEM_ACCESS_TECHNOLOGY_HSDPA | MM_MODEM_ACCESS_TECHNOLOGY_HSUPA);
+ case 16:
+ case 17:
+ return MM_MODEM_ACCESS_TECHNOLOGY_LTE;
+ default:
+ mm_obj_dbg (log_object, "unable to identify access technology from psinfo reported value: %u", val);
+ return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ }
+}
+
+/*****************************************************************************/
+/* ^SLCC psinfo helper */
+
+GRegex *
+mm_cinterion_get_slcc_regex (void)
+{
+ /* The list of active calls displayed with this URC will always be terminated
+ * with an empty line preceded by prefix "^SLCC: ", in order to indicate the end
+ * of the list.
+ */
+ return g_regex_new ("\\r\\n(\\^SLCC: .*\\r\\n)*\\^SLCC: \\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+static void
+cinterion_call_info_free (MMCallInfo *info)
+{
+ if (!info)
+ return;
+ g_free (info->number);
+ g_slice_free (MMCallInfo, info);
+}
+
+gboolean
+mm_cinterion_parse_slcc_list (const gchar *str,
+ gpointer log_object,
+ GList **out_list,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GList *list = NULL;
+ GError *inner_error = NULL;
+
+ static const MMCallDirection cinterion_call_direction[] = {
+ [0] = MM_CALL_DIRECTION_OUTGOING,
+ [1] = MM_CALL_DIRECTION_INCOMING,
+ };
+
+ static const MMCallState cinterion_call_state[] = {
+ [0] = MM_CALL_STATE_ACTIVE,
+ [1] = MM_CALL_STATE_HELD,
+ [2] = MM_CALL_STATE_DIALING, /* Dialing (MOC) */
+ [3] = MM_CALL_STATE_RINGING_OUT, /* Alerting (MOC) */
+ [4] = MM_CALL_STATE_RINGING_IN, /* Incoming (MTC) */
+ [5] = MM_CALL_STATE_WAITING, /* Waiting (MTC) */
+ };
+
+ g_assert (out_list);
+
+ /*
+ * 1 2 3 4 5 6 7 8 9
+ * ^SLCC: <idx>, <dir>, <stat>, <mode>, <mpty>, <Reserved>[, <number>, <type>[,<alpha>]]
+ * [^SLCC: <idx>, <dir>, <stat>, <mode>, <mpty>, <Reserved>[, <number>, <type>[,<alpha>]]]
+ * [... ]
+ * ^SLCC :
+ */
+
+ r = g_regex_new ("\\^SLCC:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)" /* mandatory fields */
+ "(?:,\\s*([^,]*),\\s*(\\d+)" /* number and type */
+ "(?:,\\s*([^,]*)" /* alpha */
+ ")?)?$",
+ G_REGEX_RAW | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF,
+ G_REGEX_MATCH_NEWLINE_CRLF,
+ NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, str, strlen (str), 0, 0, &match_info, &inner_error);
+ if (inner_error)
+ goto out;
+
+ /* Parse the results */
+ while (g_match_info_matches (match_info)) {
+ MMCallInfo *call_info;
+ guint aux;
+
+ call_info = g_slice_new0 (MMCallInfo);
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &call_info->index)) {
+ mm_obj_warn (log_object, "couldn't parse call index from ^SLCC line");
+ goto next;
+ }
+
+ if (!mm_get_uint_from_match_info (match_info, 2, &aux) ||
+ (aux >= G_N_ELEMENTS (cinterion_call_direction))) {
+ mm_obj_warn (log_object, "couldn't parse call direction from ^SLCC line");
+ goto next;
+ }
+ call_info->direction = cinterion_call_direction[aux];
+
+ if (!mm_get_uint_from_match_info (match_info, 3, &aux) ||
+ (aux >= G_N_ELEMENTS (cinterion_call_state))) {
+ mm_obj_warn (log_object, "couldn't parse call state from ^SLCC line");
+ goto next;
+ }
+ call_info->state = cinterion_call_state[aux];
+
+ if (g_match_info_get_match_count (match_info) >= 8)
+ call_info->number = mm_get_string_unquoted_from_match_info (match_info, 7);
+
+ list = g_list_append (list, call_info);
+ call_info = NULL;
+
+ next:
+ cinterion_call_info_free (call_info);
+ g_match_info_next (match_info, NULL);
+ }
+
+out:
+ if (inner_error) {
+ mm_cinterion_call_info_list_free (list);
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ *out_list = list;
+
+ return TRUE;
+}
+
+void
+mm_cinterion_call_info_list_free (GList *call_info_list)
+{
+ g_list_free_full (call_info_list, (GDestroyNotify) cinterion_call_info_free);
+}
+
+/*****************************************************************************/
+/* +CTZU URC helpers */
+
+GRegex *
+mm_cinterion_get_ctzu_regex (void)
+{
+ /*
+ * From PLS-8 AT command spec:
+ * +CTZU:<nitzUT>, <nitzTZ>[, <nitzDST>]
+ * E.g.:
+ * +CTZU: "19/07/09,10:19:15",+08,1
+ */
+
+ return g_regex_new ("\\r\\n\\+CTZU:\\s*\"(\\d+)\\/(\\d+)\\/(\\d+),(\\d+):(\\d+):(\\d+)\",([\\-\\+\\d]+)(?:,(\\d+))?(?:\\r\\n)?",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+gboolean
+mm_cinterion_parse_ctzu_urc (GMatchInfo *match_info,
+ gchar **iso8601p,
+ MMNetworkTimezone **tzp,
+ GError **error)
+{
+ gboolean ret = TRUE;
+ guint year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, dst = 0;
+ gint tz = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &year) ||
+ !mm_get_uint_from_match_info (match_info, 2, &month) ||
+ !mm_get_uint_from_match_info (match_info, 3, &day) ||
+ !mm_get_uint_from_match_info (match_info, 4, &hour) ||
+ !mm_get_uint_from_match_info (match_info, 5, &minute) ||
+ !mm_get_uint_from_match_info (match_info, 6, &second) ||
+ !mm_get_int_from_match_info (match_info, 7, &tz)) {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse +CTZU URC");
+ return FALSE;
+ }
+
+ /* adjust year */
+ if (year < 100)
+ year += 2000;
+
+ /*
+ * tz = timezone offset in 15 minute intervals
+ */
+ if (iso8601p) {
+ /* Return ISO-8601 format date/time string */
+ *iso8601p = mm_new_iso8601_time (year, month, day, hour,
+ minute, second,
+ TRUE, tz * 15,
+ error);
+ ret = (*iso8601p != NULL);
+ }
+
+ if (tzp) {
+ *tzp = mm_network_timezone_new ();
+ mm_network_timezone_set_offset (*tzp, tz * 15);
+ }
+
+ /* dst flag is optional in the URC
+ *
+ * tz = timezone offset in 15 minute intervals
+ * dst = daylight adjustment, 0 = none, 1 = 1 hour, 2 = 2 hours
+ */
+ if (tzp && mm_get_uint_from_match_info (match_info, 8, &dst))
+ mm_network_timezone_set_dst_offset (*tzp, dst * 60);
+
+ return ret;
+}
+
+/*****************************************************************************/
+/* ^SMONI response parser */
+
+gboolean
+mm_cinterion_parse_smoni_query_response (const gchar *response,
+ MMCinterionRadioGen *out_tech,
+ gdouble *out_rssi,
+ gdouble *out_ecn0,
+ gdouble *out_rscp,
+ gdouble *out_rsrp,
+ gdouble *out_rsrq,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GRegex) pre = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autoptr(GMatchInfo) match_info_pre = NULL;
+ GError *inner_error = NULL;
+ MMCinterionRadioGen tech = MM_CINTERION_RADIO_GEN_NONE;
+ gdouble rssi = -G_MAXDOUBLE;
+ gdouble ecn0 = -G_MAXDOUBLE;
+ gdouble rscp = -G_MAXDOUBLE;
+ gdouble rsrq = -G_MAXDOUBLE;
+ gdouble rsrp = -G_MAXDOUBLE;
+ gboolean success = FALSE;
+
+ g_assert (out_tech);
+ g_assert (out_rssi);
+ g_assert (out_ecn0);
+ g_assert (out_rscp);
+ g_assert (out_rsrp);
+ g_assert (out_rsrq);
+ g_assert (out_rssi);
+
+ /* Possible Responses:
+ * 2G
+ * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,Conn_state // registered
+ * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,ARFCN,TS,timAdv,dBm,Q,ChMod // searching
+ * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,PWR,RXLev,ARFCN,TS,timAdv,dBm,Q,ChMod // limsrv
+ * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,ARFCN,TS,timAdv,dBm,Q,ChMod // dedicated channel
+ *
+ * ^SMONI: 2G,71,-61,262,02,0143,83BA,33,33,3,6,G,NOCONN
+ * ^^^
+ * ^SMONI: 2G,SEARCH,SEARCH
+ * ^SMONI: 2G,673,-89,262,07,4EED,A500,16,16,7,4,G,5,-107,LIMSRV
+ * ^^^ ^^^^ RXLev dBm
+ * ^SMONI: 2G,673,-80,262,07,4EED,A500,35,35,7,4,G,643,4,0,-80,0,S_FR
+ * ^^^ ^^^ dBm: Receiving level of the traffic channel carrier in dBm
+ * BCCH: Receiving level of the BCCH carrier in dBm (level is limited from -110dBm to -47dBm)
+ * -> rssi for 2G, directly without mm_3gpp_rxlev_to_rssi
+ *
+ *
+ * 3G
+ * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,,Conn_state",
+ * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,PhysCh, SF,Slot,EC/n0,RSCP,ComMod,HSUPA,HSDPA",
+ * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,PhysCh, SF,Slot,EC/n0,RSCP,ComMod,HSUPA,HSDPA",
+ * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,PhysCh, SF,Slot,EC/n0,RSCP,ComMod,HSUPA,HSDPA",
+ *
+ * ^SMONI: 3G,10564,296,-7.5,-79,262,02,0143,00228FF,-92,-78,NOCONN
+ * ^^^^ ^^^
+ * ^SMONI: 3G,SEARCH,SEARCH
+ * ^SMONI: 3G,10564,96,-7.5,-79,262,02,0143,00228FF,-92,-78,LIMSRV
+ * ^^^^ ^^^
+ * ^SMONI: 3G,10737,131,-5,-93,260,01,7D3D,C80BC9A,--,--,----,---,-,-5,-93,0,01,06
+ * ^^ ^^^
+ * RSCP: Received Signal Code Power in dBm -> no need for mm_3gpp_rscp_level_to_rscp
+ * EC/n0: EC/n0 Carrier to noise ratio in dB = measured Ec/Io value in dB. Please refer to 3GPP 25.133, section 9.1.2.3, Table 9.9 for details on the mapping from EC/n0 to EC/Io.
+ * -> direct value, without need for mm_3gpp_ecn0_level_to_ecio
+ *
+ *
+ * 4G
+ * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,Srxlev,RSRP,RSRQ,Conn_state
+ * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,Srxlev,RSRP,RSRQ,Conn_state
+ * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,Srxlev,RSRP,RSRQ,Conn_state
+ * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,TX_power,RSRP,RSRQ,Conn_state
+ *
+ * ^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,33,-94,-7,NOCONN
+ * ^^^ ^^
+ * ^SMONI: 4G,SEARCH
+ * ^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,33,-94,-7,LIMSRV
+ * ^^^ ^^
+ * ^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,90,-94,-7,CONN
+ * ^^^ ^^
+ * RSRP Reference Signal Received Power (see 3GPP 36.214 Section 5.1.1.) -> directly the value without mm_3gpp_rsrq_level_to_rsrp
+ * RSRQ Reference Signal Received Quality (see 3GPP 36.214 Section 5.1.2.) -> directly the value without mm_3gpp_rsrq_level_to_rsrq
+ */
+ if (g_regex_match_simple ("\\^SMONI:\\s*[234]G,SEARCH", response, 0, 0)) {
+ success = TRUE;
+ goto out;
+ }
+ pre = g_regex_new ("\\^SMONI:\\s*([234])", 0, 0, NULL);
+ g_assert (pre != NULL);
+ g_regex_match_full (pre, response, strlen (response), 0, 0, &match_info_pre, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info_pre)) {
+ if (!mm_get_uint_from_match_info (match_info_pre, 1, &tech)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read tech");
+ goto out;
+ }
+ #define FLOAT "([-+]?[0-9]+\\.?[0-9]*)"
+ switch (tech) {
+ case MM_CINTERION_RADIO_GEN_2G:
+ r = g_regex_new ("\\^SMONI:\\s*2G,(\\d+),"FLOAT, 0, 0, NULL);
+ g_assert (r != NULL);
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ /* skip ARFCN */
+ if (!mm_get_double_from_match_info (match_info, 2, &rssi)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read BCCH=rssi");
+ goto out;
+ }
+ }
+ break;
+ case MM_CINTERION_RADIO_GEN_3G:
+ r = g_regex_new ("\\^SMONI:\\s*3G,(\\d+),(\\d+),"FLOAT","FLOAT, 0, 0, NULL);
+ g_assert (r != NULL);
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ /* skip UARFCN */
+ /* skip PSC (Primary scrambling code) */
+ if (!mm_get_double_from_match_info (match_info, 3, &ecn0)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read EcN0");
+ goto out;
+ }
+ if (!mm_get_double_from_match_info (match_info, 4, &rscp)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSCP");
+ goto out;
+ }
+ }
+ break;
+ case MM_CINTERION_RADIO_GEN_4G:
+ r = g_regex_new ("\\^SMONI:\\s*4G,(\\d+),(\\d+),(\\d+),(\\d+),(\\w+),(\\d+),(\\d+),(\\w+),(\\w+),(\\d+),([^,]*),"FLOAT","FLOAT, 0, 0, NULL);
+ g_assert (r != NULL);
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ /* skip EARFCN */
+ /* skip Band */
+ /* skip DL bandwidth */
+ /* skip UL bandwidth */
+ /* skip Mode */
+ /* skip MCC */
+ /* skip MNC */
+ /* skip TAC */
+ /* skip Global Cell ID */
+ /* skip Physical Cell ID */
+ /* skip Srxlev/TX_power */
+ if (!mm_get_double_from_match_info (match_info, 12, &rsrp)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRQ");
+ goto out;
+ }
+ if (!mm_get_double_from_match_info (match_info, 13, &rsrq)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRP");
+ goto out;
+ }
+ }
+ break;
+ case MM_CINTERION_RADIO_GEN_NONE:
+ default:
+ goto out;
+ }
+ #undef FLOAT
+ success = TRUE;
+ }
+
+out:
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (!success) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^SMONI response: %s", response);
+ return FALSE;
+ }
+
+ *out_tech = tech;
+ *out_rssi = rssi;
+ *out_rscp = rscp;
+ *out_ecn0 = ecn0;
+ *out_rsrq = rsrq;
+ *out_rsrp = rsrp;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Get extended signal information */
+
+gboolean
+mm_cinterion_smoni_response_to_signal_info (const gchar *response,
+ MMSignal **out_gsm,
+ MMSignal **out_umts,
+ MMSignal **out_lte,
+ GError **error)
+{
+ MMCinterionRadioGen tech = MM_CINTERION_RADIO_GEN_NONE;
+ gdouble rssi = MM_SIGNAL_UNKNOWN;
+ gdouble ecn0 = MM_SIGNAL_UNKNOWN;
+ gdouble rscp = MM_SIGNAL_UNKNOWN;
+ gdouble rsrq = MM_SIGNAL_UNKNOWN;
+ gdouble rsrp = MM_SIGNAL_UNKNOWN;
+ MMSignal *gsm = NULL;
+ MMSignal *umts = NULL;
+ MMSignal *lte = NULL;
+
+ if (!mm_cinterion_parse_smoni_query_response (response,
+ &tech, &rssi,
+ &ecn0, &rscp,
+ &rsrp, &rsrq,
+ error))
+ return FALSE;
+
+ switch (tech) {
+ case MM_CINTERION_RADIO_GEN_2G:
+ gsm = mm_signal_new ();
+ mm_signal_set_rssi (gsm, rssi);
+ break;
+ case MM_CINTERION_RADIO_GEN_3G:
+ umts = mm_signal_new ();
+ mm_signal_set_rscp (umts, rscp);
+ mm_signal_set_ecio (umts, ecn0); /* UMTS EcIo (assumed EcN0) */
+ break;
+ case MM_CINTERION_RADIO_GEN_4G:
+ lte = mm_signal_new ();
+ mm_signal_set_rsrp (lte, rsrp);
+ mm_signal_set_rsrq (lte, rsrq);
+ break;
+ case MM_CINTERION_RADIO_GEN_NONE: /* not registered, searching */
+ break; /* no error case */
+ default: /* should not happen, so if it does, error */
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't build detailed signal info");
+ return FALSE;
+ }
+
+ if (out_gsm)
+ *out_gsm = gsm;
+ if (out_umts)
+ *out_umts = umts;
+ if (out_lte)
+ *out_lte = lte;
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* provider cfg information to CID number for EPS initial settings */
+
+/*
+ * at^scfg="MEopMode/Prov/Cfg"
+ * ^SCFG: "MEopMode/Prov/Cfg","vdfde"
+ * ^SCFG: "MEopMode/Prov/Cfg","attus"
+ * ^SCFG: "MEopMode/Prov/Cfg","2" -> PLS8-X vzw
+ * ^SCFG: "MEopMode/Prov/Cfg","vzwdcus" -> PLAS9-x vzw
+ * ^SCFG: "MEopMode/Prov/Cfg","tmode" -> t-mob germany
+ * OK
+ */
+gboolean
+mm_cinterion_provcfg_response_to_cid (const gchar *response,
+ MMCinterionModemFamily modem_family,
+ MMModemCharset charset,
+ gpointer log_object,
+ gint *cid,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autofree gchar *mno = NULL;
+ GError *inner_error = NULL;
+
+ r = g_regex_new ("\\^SCFG:\\s*\"MEopMode/Prov/Cfg\",\\s*\"([0-9a-zA-Z*]*)\"", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+
+ if (inner_error) {
+ g_prefix_error (&inner_error, "Failed to match Prov/Cfg response: ");
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (!g_match_info_matches (match_info)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't match Prov/Cfg response");
+ return FALSE;
+ }
+
+ mno = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (mno && modem_family == MM_CINTERION_MODEM_FAMILY_IMT) {
+ gchar *mno_utf8;
+
+ mno_utf8 = mm_modem_charset_str_to_utf8 (mno, -1, charset, FALSE, error);
+ if (!mno_utf8)
+ return FALSE;
+ g_free (mno);
+ mno = mno_utf8;
+ }
+ mm_obj_dbg (log_object, "current mno: %s", mno ? mno : "none");
+
+ /* for Cinterion LTE modules, some CID numbers have special meaning.
+ * This is dictated by the chipset and by the MNO:
+ * - the chipset uses a special one, CID 1, as a LTE combined attach chipset
+ * - the MNOs can define the sequence and number of APN to be used for their network.
+ * This takes priority over the chipset preferences, and therefore for some of them
+ * the CID for the initial EPS context must be changed.
+ */
+ if (g_strcmp0 (mno, "2") == 0 || g_strcmp0 (mno, "vzwdcus") == 0)
+ *cid = 3;
+ else if (g_strcmp0 (mno, "tmode") == 0)
+ *cid = 2;
+ else
+ *cid = 1;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Auth related helpers */
+
+typedef enum {
+ BEARER_CINTERION_AUTH_UNKNOWN = -1,
+ BEARER_CINTERION_AUTH_NONE = 0,
+ BEARER_CINTERION_AUTH_PAP = 1,
+ BEARER_CINTERION_AUTH_CHAP = 2,
+ BEARER_CINTERION_AUTH_MSCHAPV2 = 3,
+} BearerCinterionAuthType;
+
+static BearerCinterionAuthType
+parse_auth_type (MMBearerAllowedAuth mm_auth)
+{
+ switch (mm_auth) {
+ case MM_BEARER_ALLOWED_AUTH_NONE:
+ return BEARER_CINTERION_AUTH_NONE;
+ case MM_BEARER_ALLOWED_AUTH_PAP:
+ return BEARER_CINTERION_AUTH_PAP;
+ case MM_BEARER_ALLOWED_AUTH_CHAP:
+ return BEARER_CINTERION_AUTH_CHAP;
+ case MM_BEARER_ALLOWED_AUTH_MSCHAPV2:
+ return BEARER_CINTERION_AUTH_MSCHAPV2;
+ case MM_BEARER_ALLOWED_AUTH_UNKNOWN:
+ case MM_BEARER_ALLOWED_AUTH_MSCHAP:
+ case MM_BEARER_ALLOWED_AUTH_EAP:
+ default:
+ return BEARER_CINTERION_AUTH_UNKNOWN;
+ }
+}
+
+MMBearerAllowedAuth
+mm_auth_type_from_cinterion_auth_type (guint cinterion_auth)
+{
+ switch (cinterion_auth) {
+ case BEARER_CINTERION_AUTH_NONE:
+ return MM_BEARER_ALLOWED_AUTH_NONE;
+ case BEARER_CINTERION_AUTH_PAP:
+ return MM_BEARER_ALLOWED_AUTH_PAP;
+ case BEARER_CINTERION_AUTH_CHAP:
+ return MM_BEARER_ALLOWED_AUTH_CHAP;
+ default:
+ return MM_BEARER_ALLOWED_AUTH_UNKNOWN;
+ }
+}
+
+/* Cinterion authentication is done with the command AT^SGAUTH,
+ whose syntax depends on the modem family, as follow:
+ - AT^SGAUTH=<cid>[, <auth_type>[, <user>, <passwd>]] for the IMT family
+ - AT^SGAUTH=<cid>[, <auth_type>[, <passwd>, <user>]] for the rest */
+gchar *
+mm_cinterion_build_auth_string (gpointer log_object,
+ MMCinterionModemFamily modem_family,
+ MMBearerProperties *config,
+ guint cid)
+{
+ MMBearerAllowedAuth auth;
+ BearerCinterionAuthType encoded_auth = BEARER_CINTERION_AUTH_UNKNOWN;
+ gboolean has_user;
+ gboolean has_passwd;
+ const gchar *user;
+ const gchar *passwd;
+ g_autofree gchar *quoted_user = NULL;
+ g_autofree gchar *quoted_passwd = NULL;
+
+ user = mm_bearer_properties_get_user (config);
+ passwd = mm_bearer_properties_get_password (config);
+ auth = mm_bearer_properties_get_allowed_auth (config);
+
+ has_user = (user && user[0]);
+ has_passwd = (passwd && passwd[0]);
+ encoded_auth = parse_auth_type (auth);
+
+ /* When 'none' requested, we won't require user/password */
+ if (encoded_auth == BEARER_CINTERION_AUTH_NONE) {
+ if (has_user || has_passwd)
+ mm_obj_warn (log_object, "APN user/password given but 'none' authentication requested");
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT)
+ return g_strdup_printf ("^SGAUTH=%u,%d,\"\",\"\"", cid, encoded_auth);
+ return g_strdup_printf ("^SGAUTH=%u,%d", cid, encoded_auth);
+ }
+
+ /* No explicit auth type requested? */
+ if (encoded_auth == BEARER_CINTERION_AUTH_UNKNOWN) {
+ /* If no user/passwd given, do nothing */
+ if (!has_user && !has_passwd)
+ return NULL;
+
+ /* If user/passwd given, default to CHAP (more common than PAP) */
+ mm_obj_dbg (log_object, "APN user/password given but no authentication type explicitly requested: defaulting to 'CHAP'");
+ encoded_auth = BEARER_CINTERION_AUTH_CHAP;
+ }
+
+ quoted_user = mm_port_serial_at_quote_string (user ? user : "");
+ quoted_passwd = mm_port_serial_at_quote_string (passwd ? passwd : "");
+
+ if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT)
+ return g_strdup_printf ("^SGAUTH=%u,%d,%s,%s",
+ cid,
+ encoded_auth,
+ quoted_user,
+ quoted_passwd);
+
+ return g_strdup_printf ("^SGAUTH=%u,%d,%s,%s",
+ cid,
+ encoded_auth,
+ quoted_passwd,
+ quoted_user);
+}
+
+/*****************************************************************************/
+/* ^SXRAT set command builder */
+
+/* Index of the array is the centerion-specific sxrat value */
+static const MMModemMode sxrat_combinations[] = {
+ [0] = ( MM_MODEM_MODE_2G ),
+ [1] = ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G ),
+ [2] = ( MM_MODEM_MODE_3G ),
+ [3] = ( MM_MODEM_MODE_4G ),
+ [4] = ( MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ),
+ [5] = ( MM_MODEM_MODE_2G | MM_MODEM_MODE_4G ),
+ [6] = ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ),
+};
+
+static gboolean
+append_sxrat_rat_value (GString *str,
+ MMModemMode mode,
+ GError **error)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (sxrat_combinations); i++) {
+ if (sxrat_combinations[i] == mode) {
+ g_string_append_printf (str, "%u", i);
+ return TRUE;
+ }
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No AcT value matches requested mode");
+ return FALSE;
+}
+
+gchar *
+mm_cinterion_build_sxrat_set_command (MMModemMode allowed,
+ MMModemMode preferred,
+ GError **error)
+{
+ GString *command;
+
+ command = g_string_new ("^SXRAT=");
+ if (!append_sxrat_rat_value (command, allowed, error)) {
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+
+ if (preferred != MM_MODEM_MODE_NONE) {
+ if (mm_count_bits_set (preferred) != 1) {
+ *error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "AcT preferred value should be a single AcT");
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+ g_string_append (command, ",");
+ if (!append_sxrat_rat_value (command, preferred, error)) {
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+ }
+
+ return g_string_free (command, FALSE);
+}
diff --git a/src/plugins/cinterion/mm-modem-helpers-cinterion.h b/src/plugins/cinterion/mm-modem-helpers-cinterion.h
new file mode 100644
index 00000000..3155d0c1
--- /dev/null
+++ b/src/plugins/cinterion/mm-modem-helpers-cinterion.h
@@ -0,0 +1,211 @@
+/* -*- 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) 2014 Aleksander Morgado <aleksander@aleksander.es>
+ * Copyright (C) 2016 Trimble Navigation Limited
+ * Copyright (C) 2016 Matthew Stanger <matthew_stanger@trimble.com>
+ * Copyright (C) 2019 Purism SPC
+ */
+
+#ifndef MM_MODEM_HELPERS_CINTERION_H
+#define MM_MODEM_HELPERS_CINTERION_H
+
+#include <glib.h>
+
+#include <ModemManager.h>
+#include <mm-base-bearer.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+typedef enum {
+ MM_CINTERION_MODEM_FAMILY_DEFAULT = 0,
+ MM_CINTERION_MODEM_FAMILY_IMT = 1,
+} MMCinterionModemFamily;
+
+typedef enum {
+ MM_CINTERION_RADIO_BAND_FORMAT_SINGLE = 0,
+ MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE = 1,
+} MMCinterionRadioBandFormat;
+
+typedef enum {
+ MM_CINTERION_RB_BLOCK_LEGACY = 0,
+ MM_CINTERION_RB_BLOCK_GSM = 0,
+ MM_CINTERION_RB_BLOCK_UMTS = 1,
+ MM_CINTERION_RB_BLOCK_LTE_LOW = 2,
+ MM_CINTERION_RB_BLOCK_LTE_HIGH = 3,
+ MM_CINTERION_RB_BLOCK_N = 4
+} MMCinterionRbBlock;
+
+typedef enum {
+ MM_CINTERION_RADIO_GEN_NONE = 0,
+ MM_CINTERION_RADIO_GEN_2G = 2,
+ MM_CINTERION_RADIO_GEN_3G = 3,
+ MM_CINTERION_RADIO_GEN_4G = 4,
+} MMCinterionRadioGen;
+
+/*****************************************************************************/
+/* ^SCFG test parser */
+
+gboolean mm_cinterion_parse_scfg_test (const gchar *response,
+ MMCinterionModemFamily modem_family,
+ MMModemCharset charset,
+ GArray **supported_bands,
+ MMCinterionRadioBandFormat *format,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SCFG response parser */
+
+gboolean mm_cinterion_parse_scfg_response (const gchar *response,
+ MMCinterionModemFamily modem_family,
+ MMModemCharset charset,
+ GArray **bands,
+ MMCinterionRadioBandFormat format,
+ GError **error);
+
+/*****************************************************************************/
+/* +CNMI test parser */
+
+gboolean mm_cinterion_parse_cnmi_test (const gchar *response,
+ GArray **supported_mode,
+ GArray **supported_mt,
+ GArray **supported_bm,
+ GArray **supported_ds,
+ GArray **supported_bfr,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SXRAT test parser */
+
+gboolean mm_cinterion_parse_sxrat_test (const gchar *response,
+ GArray **supported_rat,
+ GArray **supported_pref1,
+ GArray **supported_pref2,
+ GError **error);
+
+/*****************************************************************************/
+/* Build Cinterion-specific band value */
+
+gboolean mm_cinterion_build_band (GArray *bands,
+ guint *supported,
+ gboolean only_2g,
+ MMCinterionRadioBandFormat format,
+ MMCinterionModemFamily modem_family,
+ guint *out_band,
+ GError **error);
+
+/*****************************************************************************/
+/* Single ^SIND response parser */
+
+gboolean mm_cinterion_parse_sind_response (const gchar *response,
+ gchar **description,
+ guint *mode,
+ guint *value,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SWWAN response parser */
+
+MMBearerConnectionStatus mm_cinterion_parse_swwan_response (const gchar *response,
+ guint swwan_index,
+ gpointer log_object,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SGAUTH response parser */
+
+gboolean mm_cinterion_parse_sgauth_response (const gchar *response,
+ guint cid,
+ MMBearerAllowedAuth *out_auth,
+ gchar **out_username,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SMONG response parser */
+
+gboolean mm_cinterion_parse_smong_response (const gchar *response,
+ MMModemAccessTechnology *access_tech,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SIND psinfo helper */
+
+MMModemAccessTechnology mm_cinterion_get_access_technology_from_sind_psinfo (guint val,
+ gpointer log_object);
+
+/*****************************************************************************/
+/* ^SLCC URC helpers */
+
+GRegex *mm_cinterion_get_slcc_regex (void);
+
+/* MMCallInfo list management */
+gboolean mm_cinterion_parse_slcc_list (const gchar *str,
+ gpointer log_object,
+ GList **out_list,
+ GError **error);
+void mm_cinterion_call_info_list_free (GList *call_info_list);
+
+/*****************************************************************************/
+/* +CTZU URC helpers */
+
+GRegex *mm_cinterion_get_ctzu_regex (void);
+gboolean mm_cinterion_parse_ctzu_urc (GMatchInfo *match_info,
+ gchar **iso8601p,
+ MMNetworkTimezone **tzp,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SMONI helper */
+
+gboolean mm_cinterion_parse_smoni_query_response (const gchar *response,
+ MMCinterionRadioGen *out_tech,
+ gdouble *out_rssi,
+ gdouble *out_ecn0,
+ gdouble *out_rscp,
+ gdouble *out_rsrp,
+ gdouble *out_rsrq,
+ GError **error);
+
+gboolean mm_cinterion_smoni_response_to_signal_info (const gchar *response,
+ MMSignal **out_gsm,
+ MMSignal **out_umts,
+ MMSignal **out_lte,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SCFG="MEopMode/Prov/Cfg" helper */
+
+gboolean mm_cinterion_provcfg_response_to_cid (const gchar *response,
+ MMCinterionModemFamily modem_family,
+ MMModemCharset charset,
+ gpointer log_object,
+ gint *cid,
+ GError **error);
+
+/*****************************************************************************/
+/* Auth related helpers */
+
+MMBearerAllowedAuth mm_auth_type_from_cinterion_auth_type (guint cinterion_auth);
+
+gchar *mm_cinterion_build_auth_string (gpointer log_object,
+ MMCinterionModemFamily modem_family,
+ MMBearerProperties *config,
+ guint cid);
+
+/*****************************************************************************/
+/* ^SXRAT set command helper */
+
+gchar *mm_cinterion_build_sxrat_set_command (MMModemMode allowed,
+ MMModemMode preferred,
+ GError **error);
+
+#endif /* MM_MODEM_HELPERS_CINTERION_H */
diff --git a/src/plugins/cinterion/mm-plugin-cinterion.c b/src/plugins/cinterion/mm-plugin-cinterion.c
new file mode 100644
index 00000000..b0f3f992
--- /dev/null
+++ b/src/plugins/cinterion/mm-plugin-cinterion.c
@@ -0,0 +1,218 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2011 Ammonit Measurement GmbH
+ * Copyright (C) 2011 - 2012 Google Inc.
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-cinterion.h"
+#include "mm-broadband-modem-cinterion.h"
+#include "mm-log-object.h"
+
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi-cinterion.h"
+#endif
+
+#if defined WITH_MBIM
+#include "mm-broadband-modem-mbim-cinterion.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginCinterion, mm_plugin_cinterion, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+/* Custom init */
+
+#define TAG_CINTERION_APP_PORT "cinterion-app-port"
+#define TAG_CINTERION_MODEM_PORT "cinterion-modem-port"
+
+static gboolean
+cinterion_custom_init_finish (MMPortProbe *probe,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+sqport_ready (MMPortSerialAt *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMPortProbe *probe;
+ const gchar *response;
+
+ probe = g_task_get_source_object (task);
+
+ /* Ignore errors, just avoid tagging */
+ response = mm_port_serial_at_command_finish (port, res, NULL);
+ if (response) {
+ /* A valid reply to AT^SQPORT tells us this is an AT port already */
+ mm_port_probe_set_result_at (probe, TRUE);
+
+ if (strstr (response, "Application"))
+ g_object_set_data (G_OBJECT (probe), TAG_CINTERION_APP_PORT, GUINT_TO_POINTER (TRUE));
+ else if (strstr (response, "Modem"))
+ g_object_set_data (G_OBJECT (probe), TAG_CINTERION_MODEM_PORT, GUINT_TO_POINTER (TRUE));
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+cinterion_custom_init (MMPortProbe *probe,
+ MMPortSerialAt *port,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (probe, cancellable, callback, user_data);
+
+ mm_port_serial_at_command (
+ port,
+ "AT^SQPORT?",
+ 3,
+ FALSE, /* raw */
+ FALSE, /* allow cached */
+ cancellable,
+ (GAsyncReadyCallback) sqport_ready,
+ task);
+}
+
+/*****************************************************************************/
+
+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 Cinterion modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_cinterion_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 Cinterion modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_cinterion_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_cinterion_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+static gboolean
+grab_port (MMPlugin *self,
+ MMBaseModem *modem,
+ MMPortProbe *probe,
+ GError **error)
+{
+ MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE;
+ MMPortType ptype;
+
+ ptype = mm_port_probe_get_port_type (probe);
+
+ if (g_object_get_data (G_OBJECT (probe), TAG_CINTERION_APP_PORT)) {
+ mm_obj_dbg (self, "port '%s/%s' flagged as primary",
+ mm_port_probe_get_port_subsys (probe),
+ mm_port_probe_get_port_name (probe));
+ pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY;
+ } else if (g_object_get_data (G_OBJECT (probe), TAG_CINTERION_MODEM_PORT)) {
+ mm_obj_dbg (self, "port '%s/%s' flagged as PPP",
+ mm_port_probe_get_port_subsys (probe),
+ mm_port_probe_get_port_name (probe));
+ pflags = MM_PORT_SERIAL_AT_FLAG_PPP;
+ }
+
+ return mm_base_modem_grab_port (modem,
+ mm_port_probe_peek_port (probe),
+ ptype,
+ pflags,
+ error);
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", "usbmisc", "wwan", NULL };
+ static const gchar *vendor_strings[] = { "cinterion", "siemens", NULL };
+ static const guint16 vendor_ids[] = { 0x1e2d, 0x0681, 0x1269, 0 };
+ static const MMAsyncMethod custom_init = {
+ .async = G_CALLBACK (cinterion_custom_init),
+ .finish = G_CALLBACK (cinterion_custom_init_finish),
+ };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_CINTERION,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_STRINGS, vendor_strings,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ MM_PLUGIN_ALLOWED_MBIM, TRUE,
+ MM_PLUGIN_CUSTOM_INIT, &custom_init,
+ NULL));
+}
+
+static void
+mm_plugin_cinterion_init (MMPluginCinterion *self)
+{
+}
+
+static void
+mm_plugin_cinterion_class_init (MMPluginCinterionClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+ plugin_class->grab_port = grab_port;
+}
diff --git a/src/plugins/cinterion/mm-plugin-cinterion.h b/src/plugins/cinterion/mm-plugin-cinterion.h
new file mode 100644
index 00000000..a8a3b6bb
--- /dev/null
+++ b/src/plugins/cinterion/mm-plugin-cinterion.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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2011 Ammonit Measurement GmbH
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#ifndef MM_PLUGIN_CINTERION_H
+#define MM_PLUGIN_CINTERION_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_CINTERION (mm_plugin_cinterion_get_type ())
+#define MM_PLUGIN_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_CINTERION, MMPluginCinterion))
+#define MM_PLUGIN_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_CINTERION, MMPluginCinterionClass))
+#define MM_IS_PLUGIN_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_CINTERION))
+#define MM_IS_PLUGIN_CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_CINTERION))
+#define MM_PLUGIN_CINTERION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_CINTERION, MMPluginCinterionClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginCinterion;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginCinterionClass;
+
+GType mm_plugin_cinterion_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_CINTERION_H */
diff --git a/src/plugins/cinterion/mm-shared-cinterion.c b/src/plugins/cinterion/mm-shared-cinterion.c
new file mode 100644
index 00000000..36cf60c9
--- /dev/null
+++ b/src/plugins/cinterion/mm-shared-cinterion.c
@@ -0,0 +1,1601 @@
+/* -*- 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) 2014 Ammonit Measurement GmbH
+ * Copyright (C) 2014 - 2018 Aleksander Morgado <aleksander@aleksander.es>
+ * Copyright (C) 2019 Purism SPC
+ */
+
+#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-location.h"
+#include "mm-base-modem.h"
+#include "mm-base-modem-at.h"
+#include "mm-shared-cinterion.h"
+#include "mm-modem-helpers-cinterion.h"
+
+/*****************************************************************************/
+/* Private data context */
+
+#define PRIVATE_TAG "shared-cinterion-private-tag"
+static GQuark private_quark;
+
+typedef enum {
+ FEATURE_SUPPORT_UNKNOWN,
+ FEATURE_NOT_SUPPORTED,
+ FEATURE_SUPPORTED,
+} FeatureSupport;
+
+typedef struct {
+ /* modem */
+ MMIfaceModem *iface_modem_parent;
+ /* location */
+ MMIfaceModemLocation *iface_modem_location_parent;
+ MMModemLocationSource supported_sources;
+ MMModemLocationSource enabled_sources;
+ FeatureSupport sgpss_support;
+ FeatureSupport sgpsc_support;
+ /* voice */
+ MMIfaceModemVoice *iface_modem_voice_parent;
+ FeatureSupport slcc_support;
+ GRegex *slcc_regex;
+ /* time */
+ MMIfaceModemTime *iface_modem_time_parent;
+ GRegex *ctzu_regex;
+} Private;
+
+static void
+private_free (Private *ctx)
+{
+ g_regex_unref (ctx->ctzu_regex);
+ g_regex_unref (ctx->slcc_regex);
+ g_slice_free (Private, ctx);
+}
+
+static Private *
+get_private (MMSharedCinterion *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_new (Private);
+
+ priv->supported_sources = MM_MODEM_LOCATION_SOURCE_NONE;
+ priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE;
+ priv->sgpss_support = FEATURE_SUPPORT_UNKNOWN;
+ priv->sgpsc_support = FEATURE_SUPPORT_UNKNOWN;
+ priv->slcc_support = FEATURE_SUPPORT_UNKNOWN;
+ priv->slcc_regex = mm_cinterion_get_slcc_regex ();
+ priv->ctzu_regex = mm_cinterion_get_ctzu_regex ();
+
+ /* Setup parent class' MMIfaceModem, MMIfaceModemLocation, MMIfaceModemVoice
+ * and MMIfaceModemTime */
+
+ g_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_interface);
+ priv->iface_modem_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_interface (self);
+
+ g_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_location_interface);
+ priv->iface_modem_location_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_location_interface (self);
+
+ g_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_voice_interface);
+ priv->iface_modem_voice_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_voice_interface (self);
+
+ g_assert (MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_time_interface);
+ priv->iface_modem_time_parent = MM_SHARED_CINTERION_GET_INTERFACE (self)->peek_parent_time_interface (self);
+
+ g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
+ }
+
+ return priv;
+}
+
+/*****************************************************************************/
+/* Modem interface */
+
+gboolean
+mm_shared_cinterion_modem_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+modem_reset_at_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
+modem_reset_at (GTask *task)
+{
+ MMSharedCinterion *self;
+
+ self = g_task_get_source_object (task);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=1,1",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) modem_reset_at_ready,
+ task);
+}
+
+static void
+parent_modem_reset_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ if (!priv->iface_modem_parent->reset_finish (self, res, NULL)) {
+ modem_reset_at (task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_cinterion_modem_reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (priv->iface_modem_parent->reset &&
+ priv->iface_modem_parent->reset_finish) {
+ priv->iface_modem_parent->reset (self,
+ (GAsyncReadyCallback) parent_modem_reset_ready,
+ task);
+ return;
+ }
+
+ modem_reset_at (task);
+}
+
+/*****************************************************************************/
+/* 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_cinterion_location_load_capabilities_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ gssize aux;
+
+ aux = 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) aux;
+}
+
+static void probe_gps_features (GTask *task);
+
+static void
+sgpsc_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!mm_base_modem_at_command_finish (self, res, NULL))
+ priv->sgpsc_support = FEATURE_NOT_SUPPORTED;
+ else {
+ /* ^SGPSC supported! */
+ priv->sgpsc_support = FEATURE_SUPPORTED;
+ /* It may happen that the modem was started with GPS already enabled, or
+ * maybe ModemManager got rebooted and it was left enabled before. We'll
+ * make sure that it is disabled when we initialize the modem. */
+ mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSC=\"Engine\",\"0\"", 3, FALSE, NULL, NULL);
+ mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSC=\"Power/Antenna\",\"off\"", 3, FALSE, NULL, NULL);
+ mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSC=\"NMEA/Output\",\"off\"", 3, FALSE, NULL, NULL);
+ }
+
+ probe_gps_features (task);
+}
+
+static void
+sgpss_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!mm_base_modem_at_command_finish (self, res, NULL))
+ priv->sgpss_support = FEATURE_NOT_SUPPORTED;
+ else {
+ /* ^SGPSS supported! */
+ priv->sgpss_support = FEATURE_SUPPORTED;
+
+ /* Flag ^SGPSC as unsupported, even if it may be supported, so that we
+ * only use one set of commands to enable/disable GPS. */
+ priv->sgpsc_support = FEATURE_NOT_SUPPORTED;
+
+ /* It may happen that the modem was started with GPS already enabled, or
+ * maybe ModemManager got rebooted and it was left enabled before. We'll
+ * make sure that it is disabled when we initialize the modem. */
+ mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSS=0", 3, FALSE, NULL, NULL);
+ }
+
+ probe_gps_features (task);
+}
+
+static void
+probe_gps_features (GTask *task)
+{
+ MMSharedCinterion *self;
+ MMModemLocationSource sources;
+ Private *priv;
+
+ self = MM_SHARED_CINTERION (g_task_get_source_object (task));
+ priv = get_private (self);
+
+ /* Need to check if SGPSS supported... */
+ if (priv->sgpss_support == FEATURE_SUPPORT_UNKNOWN) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSS=?", 3, TRUE, (GAsyncReadyCallback) sgpss_test_ready, task);
+ return;
+ }
+
+ /* Need to check if SGPSC supported... */
+ if (priv->sgpsc_support == FEATURE_SUPPORT_UNKNOWN) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self), "AT^SGPSC=?", 3, TRUE, (GAsyncReadyCallback) sgpsc_test_ready, task);
+ return;
+ }
+
+ /* All GPS features probed */
+
+ /* Recover parent sources */
+ sources = GPOINTER_TO_UINT (g_task_get_task_data (task));
+
+ if (priv->sgpss_support == FEATURE_SUPPORTED || priv->sgpsc_support == FEATURE_SUPPORTED) {
+ mm_obj_dbg (self, "GPS commands supported: GPS capabilities enabled");
+
+ /* We only flag as supported by this implementation those sources not already
+ * supported by the parent implementation */
+ if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_NMEA))
+ priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA;
+ if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_RAW))
+ priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW;
+ if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))
+ priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED;
+
+ sources |= priv->supported_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);
+ } else
+ mm_obj_dbg (self, "no GPS command supported: no GPS capabilities");
+
+ g_task_return_int (task, (gssize) sources);
+ g_object_unref (task);
+}
+
+static void
+parent_load_capabilities_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource sources;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (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;
+ }
+
+ /* Cache sources supported by the parent */
+ g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL);
+
+ /* Probe all GPS features */
+ probe_gps_features (task);
+}
+
+void
+mm_shared_cinterion_location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ task = g_task_new (self, NULL, callback, user_data);
+
+ g_assert (priv->iface_modem_location_parent);
+ g_assert (priv->iface_modem_location_parent->load_capabilities);
+ g_assert (priv->iface_modem_location_parent->load_capabilities_finish);
+
+ priv->iface_modem_location_parent->load_capabilities (self,
+ (GAsyncReadyCallback)parent_load_capabilities_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Disable location gathering (Location interface) */
+
+typedef enum {
+ DISABLE_LOCATION_GATHERING_GPS_STEP_FIRST,
+ DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSS,
+ DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE,
+ DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ANTENNA,
+ DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_OUTPUT,
+ DISABLE_LOCATION_GATHERING_GPS_STEP_LAST,
+} DisableLocationGatheringGpsStep;
+
+typedef struct {
+ MMModemLocationSource source;
+ DisableLocationGatheringGpsStep gps_step;
+ GError *sgpss_error;
+ GError *sgpsc_error;
+} DisableLocationGatheringContext;
+
+static void
+disable_location_gathering_context_free (DisableLocationGatheringContext *ctx)
+{
+ if (ctx->sgpss_error)
+ g_error_free (ctx->sgpss_error);
+ if (ctx->sgpsc_error)
+ g_error_free (ctx->sgpsc_error);
+ g_slice_free (DisableLocationGatheringContext, ctx);
+}
+
+gboolean
+mm_shared_cinterion_disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void disable_location_gathering_context_gps_step (GTask *task);
+
+static void
+disable_sgpsc_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ DisableLocationGatheringContext *ctx;
+ GError *error = NULL;
+
+ ctx = (DisableLocationGatheringContext *) g_task_get_task_data (task);
+
+ /* Store error, if not one available already, and continue */
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ if (!ctx->sgpsc_error)
+ ctx->sgpsc_error = error;
+ else
+ g_error_free (error);
+ }
+
+ ctx->gps_step++;
+ disable_location_gathering_context_gps_step (task);
+}
+
+static void
+disable_sgpss_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ DisableLocationGatheringContext *ctx;
+
+ ctx = (DisableLocationGatheringContext *) g_task_get_task_data (task);
+
+ /* Store error, if any, and continue */
+ g_assert (!ctx->sgpss_error);
+ mm_base_modem_at_command_finish (self, res, &ctx->sgpss_error);
+
+ ctx->gps_step++;
+ disable_location_gathering_context_gps_step (task);
+}
+
+static void
+disable_location_gathering_context_gps_step (GTask *task)
+{
+ DisableLocationGatheringContext *ctx;
+ MMSharedCinterion *self;
+ Private *priv;
+
+ self = MM_SHARED_CINTERION (g_task_get_source_object (task));
+ priv = get_private (self);
+ ctx = (DisableLocationGatheringContext *) g_task_get_task_data (task);
+
+ /* Only one of both supported */
+ g_assert ((priv->sgpss_support == FEATURE_SUPPORTED) || (priv->sgpsc_support == FEATURE_SUPPORTED));
+ g_assert (!((priv->sgpss_support == FEATURE_SUPPORTED) && (priv->sgpsc_support == FEATURE_SUPPORTED)));
+
+ switch (ctx->gps_step) {
+ case DISABLE_LOCATION_GATHERING_GPS_STEP_FIRST:
+ ctx->gps_step++;
+ /* fall through */
+
+ case DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSS:
+ if (priv->sgpss_support == FEATURE_SUPPORTED) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSS=0",
+ 3, FALSE, (GAsyncReadyCallback) disable_sgpss_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE:
+ if (priv->sgpsc_support == FEATURE_SUPPORTED) {
+ /* Engine off */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSC=\"Engine\",\"0\"",
+ 3, FALSE, (GAsyncReadyCallback) disable_sgpsc_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ANTENNA:
+ if (priv->sgpsc_support == FEATURE_SUPPORTED) {
+ /* Antenna off */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSC=\"Power/Antenna\",\"off\"",
+ 3, FALSE, (GAsyncReadyCallback) disable_sgpsc_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case DISABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_OUTPUT:
+ if (priv->sgpsc_support == FEATURE_SUPPORTED) {
+ /* NMEA output off */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSC=\"NMEA/Output\",\"off\"",
+ 3, FALSE, (GAsyncReadyCallback) disable_sgpsc_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case DISABLE_LOCATION_GATHERING_GPS_STEP_LAST:
+ /* Only use the GPS port in NMEA/RAW setups */
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ MMPortSerialGps *gps_port;
+
+ /* Even if we get an error here, we try to close the 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));
+ }
+
+ if (ctx->sgpss_error) {
+ g_task_return_error (task, ctx->sgpss_error);
+ g_clear_error (&ctx->sgpss_error);
+ } else if (ctx->sgpsc_error) {
+ g_task_return_error (task, ctx->sgpsc_error);
+ g_clear_error (&ctx->sgpsc_error);
+ } else {
+ priv->enabled_sources &= ~ctx->source;
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+parent_disable_location_gathering_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ g_assert (priv->iface_modem_location_parent);
+ 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_cinterion_disable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ DisableLocationGatheringContext *ctx;
+ MMModemLocationSource enabled_sources;
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_location_parent);
+
+ /* Only consider request if it applies to one of the sources we are
+ * supporting, otherwise run parent disable */
+ if (!(priv->supported_sources & source)) {
+ /* If disabling implemented by the parent, run it. */
+ 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)parent_disable_location_gathering_ready,
+ task);
+ return;
+ }
+ /* Otherwise, we're done */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* We only expect GPS sources here */
+ g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED));
+
+ /* Flag as disabled to see how many others we would have left enabled */
+ enabled_sources = priv->enabled_sources;
+ enabled_sources &= ~source;
+
+ /* If there are still GPS-related sources enabled, do nothing else */
+ if (enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ priv->enabled_sources &= ~source;
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Stop GPS engine if all GPS-related sources are disabled */
+ ctx = g_slice_new0 (DisableLocationGatheringContext);
+ ctx->source = source;
+ ctx->gps_step = DISABLE_LOCATION_GATHERING_GPS_STEP_FIRST;
+ g_task_set_task_data (task, ctx, (GDestroyNotify) disable_location_gathering_context_free);
+ disable_location_gathering_context_gps_step (task);
+}
+
+/*****************************************************************************/
+/* Enable location gathering (Location interface) */
+
+/* We will retry the SGPSC command that enables the Engine */
+#define MAX_SGPSC_ENGINE_RETRIES 3
+
+/* Cinterion asks for 100ms some time between GPS commands, but we'll give up
+ * to 2000ms before setting the Engine configuration as 100ms didn't seem always
+ * enough (we would get +CME ERROR: 767 errors reported). */
+#define GPS_COMMAND_TIMEOUT_DEFAULT_MS 100
+#define GPS_COMMAND_TIMEOUT_ENGINE_MS 2000
+
+typedef enum {
+ ENABLE_LOCATION_GATHERING_GPS_STEP_FIRST,
+ ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSS,
+ ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_OUTPUT,
+ ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ANTENNA,
+ ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE,
+ ENABLE_LOCATION_GATHERING_GPS_STEP_LAST,
+} EnableLocationGatheringGpsStep;
+
+typedef struct {
+ MMModemLocationSource source;
+ EnableLocationGatheringGpsStep gps_step;
+ guint sgpsc_engine_retries;
+} EnableLocationGatheringContext;
+
+static void
+enable_location_gathering_context_free (EnableLocationGatheringContext *ctx)
+{
+ g_slice_free (EnableLocationGatheringContext, ctx);
+}
+
+gboolean
+mm_shared_cinterion_enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void enable_location_gathering_context_gps_step (GTask *task);
+
+static gboolean
+enable_location_gathering_context_gps_step_schedule_cb (GTask *task)
+{
+ /* Run the scheduled step */
+ enable_location_gathering_context_gps_step (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+enable_sgpsc_or_sgpss_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ EnableLocationGatheringContext *ctx;
+ GError *error = NULL;
+
+ ctx = (EnableLocationGatheringContext *) g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ /* The GPS setup may sometimes report "+CME ERROR 767" when enabling the
+ * Engine; so we'll run some retries of the same command ourselves. */
+ if (ctx->gps_step == ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE) {
+ ctx->sgpsc_engine_retries++;
+ mm_obj_dbg (self, "GPS engine setup failed (%u/%u)", ctx->sgpsc_engine_retries, MAX_SGPSC_ENGINE_RETRIES);
+ if (ctx->sgpsc_engine_retries < MAX_SGPSC_ENGINE_RETRIES) {
+ g_clear_error (&error);
+ goto schedule;
+ }
+ }
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Go on to next step */
+ ctx->gps_step++;
+
+schedule:
+ g_timeout_add (ctx->gps_step == ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE ? GPS_COMMAND_TIMEOUT_ENGINE_MS : GPS_COMMAND_TIMEOUT_DEFAULT_MS,
+ (GSourceFunc) enable_location_gathering_context_gps_step_schedule_cb, task);
+}
+
+static void
+enable_location_gathering_context_gps_step (GTask *task)
+{
+ EnableLocationGatheringContext *ctx;
+ MMSharedCinterion *self;
+ Private *priv;
+
+ self = MM_SHARED_CINTERION (g_task_get_source_object (task));
+ priv = get_private (self);
+ ctx = (EnableLocationGatheringContext *) g_task_get_task_data (task);
+
+ /* Only one of both supported */
+ g_assert ((priv->sgpss_support == FEATURE_SUPPORTED) || (priv->sgpsc_support == FEATURE_SUPPORTED));
+ g_assert (!((priv->sgpss_support == FEATURE_SUPPORTED) && (priv->sgpsc_support == FEATURE_SUPPORTED)));
+
+ switch (ctx->gps_step) {
+ case ENABLE_LOCATION_GATHERING_GPS_STEP_FIRST:
+ ctx->gps_step++;
+ /* fall through */
+
+ case ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSS:
+ if (priv->sgpss_support == FEATURE_SUPPORTED) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSS=4",
+ 3, FALSE, (GAsyncReadyCallback) enable_sgpsc_or_sgpss_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_OUTPUT:
+ if (priv->sgpsc_support == FEATURE_SUPPORTED) {
+ /* NMEA output off */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSC=\"NMEA/Output\",\"on\"",
+ 3, FALSE, (GAsyncReadyCallback) enable_sgpsc_or_sgpss_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ANTENNA:
+ if (priv->sgpsc_support == FEATURE_SUPPORTED) {
+ /* Antenna off */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSC=\"Power/Antenna\",\"on\"",
+ 3, FALSE, (GAsyncReadyCallback) enable_sgpsc_or_sgpss_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case ENABLE_LOCATION_GATHERING_GPS_STEP_SGPSC_ENGINE:
+ if (priv->sgpsc_support == FEATURE_SUPPORTED) {
+ /* Engine off */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT^SGPSC=\"Engine\",\"1\"",
+ 3, FALSE, (GAsyncReadyCallback) enable_sgpsc_or_sgpss_ready, task);
+ return;
+ }
+ ctx->gps_step++;
+ /* fall through */
+
+ case ENABLE_LOCATION_GATHERING_GPS_STEP_LAST:
+ /* Only use the GPS port in NMEA/RAW setups */
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ MMPortSerialGps *gps_port;
+ GError *error = NULL;
+
+ 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");
+ g_object_unref (task);
+ return;
+ }
+ }
+
+ /* Success */
+ priv->enabled_sources |= ctx->source;
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+parent_enable_location_gathering_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ g_assert (priv->iface_modem_location_parent);
+ 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_cinterion_enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+ EnableLocationGatheringContext *ctx;
+
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (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);
+
+ /* Only consider request if it applies to one of the sources we are
+ * supporting, otherwise run parent enable */
+ if (!(priv->supported_sources & source)) {
+ priv->iface_modem_location_parent->enable_location_gathering (self,
+ source,
+ (GAsyncReadyCallback)parent_enable_location_gathering_ready,
+ task);
+ return;
+ }
+
+ /* We only expect GPS sources here */
+ g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED));
+
+ /* If GPS already started, store new flag and we're done */
+ if (priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ priv->enabled_sources |= source;
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_slice_new0 (EnableLocationGatheringContext);
+ ctx->source = source;
+ ctx->gps_step = ENABLE_LOCATION_GATHERING_GPS_STEP_FIRST;
+ g_task_set_task_data (task, ctx, (GDestroyNotify) enable_location_gathering_context_free);
+
+ enable_location_gathering_context_gps_step (task);
+}
+
+/*****************************************************************************/
+
+MMBaseCall *
+mm_shared_cinterion_create_call (MMIfaceModemVoice *self,
+ MMCallDirection direction,
+ const gchar *number)
+{
+ Private *priv;
+
+ /* If ^SLCC is supported create a cinterion call object */
+ priv = get_private (MM_SHARED_CINTERION (self));
+ if (priv->slcc_support == FEATURE_SUPPORTED) {
+ mm_obj_dbg (self, "created new call with ^SLCC support");
+ return mm_base_call_new (MM_BASE_MODEM (self),
+ direction,
+ number,
+ /* When SLCC is supported we have support for detailed
+ * call list events via call list report URCs */
+ TRUE, /* incoming timeout not required */
+ TRUE, /* dialing->ringing supported */
+ TRUE); /* ringing->active supported */
+ }
+
+ /* otherwise, run parent's generic base call logic */
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->create_call);
+ return priv->iface_modem_voice_parent->create_call (self, direction, number);
+}
+
+/*****************************************************************************/
+/* Common enable/disable voice unsolicited events */
+
+typedef struct {
+ gboolean enable;
+ MMPortSerialAt *primary;
+ MMPortSerialAt *secondary;
+ gchar *slcc_command;
+ gboolean slcc_primary_done;
+ gboolean slcc_secondary_done;
+} VoiceUnsolicitedEventsContext;
+
+static void
+voice_unsolicited_events_context_free (VoiceUnsolicitedEventsContext *ctx)
+{
+ g_clear_object (&ctx->secondary);
+ g_clear_object (&ctx->primary);
+ g_free (ctx->slcc_command);
+ g_slice_free (VoiceUnsolicitedEventsContext, ctx);
+}
+
+static gboolean
+common_voice_enable_disable_unsolicited_events_finish (MMSharedCinterion *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void run_voice_enable_disable_unsolicited_events (GTask *task);
+
+static void
+slcc_command_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ VoiceUnsolicitedEventsContext *ctx;
+ g_autoptr(GError) error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_full_finish (self, res, &error))
+ mm_obj_dbg (self, "couldn't %s ^SLCC reporting: %s",
+ ctx->enable ? "enable" : "disable",
+ error->message);
+
+ /* Continue on next port */
+ run_voice_enable_disable_unsolicited_events (task);
+}
+
+static void
+run_voice_enable_disable_unsolicited_events (GTask *task)
+{
+ MMSharedCinterion *self;
+ Private *priv;
+ VoiceUnsolicitedEventsContext *ctx;
+ MMPortSerialAt *port = NULL;
+
+ self = MM_SHARED_CINTERION (g_task_get_source_object (task));
+ priv = get_private (self);
+ ctx = g_task_get_task_data (task);
+
+ /* If not ^SLCC supported, we're done */
+ if (priv->slcc_support == FEATURE_NOT_SUPPORTED) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ if (!ctx->slcc_primary_done && ctx->primary) {
+ mm_obj_dbg (self, "%s ^SLCC extended list of current calls reporting in primary port...",
+ ctx->enable ? "enabling" : "disabling");
+ ctx->slcc_primary_done = TRUE;
+ port = ctx->primary;
+ } else if (!ctx->slcc_secondary_done && ctx->secondary) {
+ mm_obj_dbg (self, "%s ^SLCC extended list of current calls reporting in secondary port...",
+ ctx->enable ? "enabling" : "disabling");
+ ctx->slcc_secondary_done = TRUE;
+ port = ctx->secondary;
+ }
+
+ if (port) {
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ port,
+ ctx->slcc_command,
+ 3,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback)slcc_command_ready,
+ task);
+ return;
+ }
+
+ /* Fully done now */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+common_voice_enable_disable_unsolicited_events (MMSharedCinterion *self,
+ gboolean enable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ VoiceUnsolicitedEventsContext *ctx;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_slice_new0 (VoiceUnsolicitedEventsContext);
+ ctx->enable = enable;
+ if (enable)
+ ctx->slcc_command = g_strdup ("^SLCC=1");
+ else
+ ctx->slcc_command = g_strdup ("^SLCC=0");
+ ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+ ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
+ g_task_set_task_data (task, ctx, (GDestroyNotify) voice_unsolicited_events_context_free);
+
+ run_voice_enable_disable_unsolicited_events (task);
+}
+
+/*****************************************************************************/
+/* Disable unsolicited events (Voice interface) */
+
+gboolean
+mm_shared_cinterion_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!priv->iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't disable parent voice unsolicited events: %s", error->message);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+voice_disable_unsolicited_events_ready (MMSharedCinterion *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+ g_autoptr(GError) error = NULL;
+
+ if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't disable Cinterion-specific voice unsolicited events: %s", error->message);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events);
+ g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events_finish);
+
+ /* Chain up parent's disable */
+ priv->iface_modem_voice_parent->disable_unsolicited_events (
+ MM_IFACE_MODEM_VOICE (self),
+ (GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready,
+ task);
+}
+
+void
+mm_shared_cinterion_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* our own disabling first */
+ common_voice_enable_disable_unsolicited_events (MM_SHARED_CINTERION (self),
+ FALSE,
+ (GAsyncReadyCallback) voice_disable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Enable unsolicited events (Voice interface) */
+
+gboolean
+mm_shared_cinterion_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+voice_enable_unsolicited_events_ready (MMSharedCinterion *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't enable Cinterion-specific voice unsolicited events: %s", error->message);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!priv->iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't enable parent voice unsolicited events: %s", error->message);
+
+ /* our own enabling next */
+ common_voice_enable_disable_unsolicited_events (MM_SHARED_CINTERION (self),
+ TRUE,
+ (GAsyncReadyCallback) voice_enable_unsolicited_events_ready,
+ task);
+}
+
+void
+mm_shared_cinterion_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events);
+ g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events_finish);
+
+ /* chain up parent's enable first */
+ priv->iface_modem_voice_parent->enable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Common setup/cleanup voice unsolicited events */
+
+static void
+slcc_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMSharedCinterion *self)
+{
+ g_autofree gchar *full = NULL;
+ g_autoptr(GError) error = NULL;
+ GList *call_info_list = NULL;
+
+ full = g_match_info_fetch (match_info, 0);
+ if (!mm_cinterion_parse_slcc_list (full, self, &call_info_list, &error))
+ mm_obj_warn (self, "couldn't parse ^SLCC list: %s", error->message);
+ else
+ mm_iface_modem_voice_report_all_calls (MM_IFACE_MODEM_VOICE (self), call_info_list);
+ mm_cinterion_call_info_list_free (call_info_list);
+}
+
+static void
+common_voice_setup_cleanup_unsolicited_events (MMSharedCinterion *self,
+ gboolean enable)
+{
+ Private *priv;
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ priv = get_private (MM_SHARED_CINTERION (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));
+
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ priv->slcc_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)slcc_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+/*****************************************************************************/
+/* Cleanup unsolicited events (Voice interface) */
+
+gboolean
+mm_shared_cinterion_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't cleanup parent voice unsolicited events: %s", error->message);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_cinterion_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events);
+ g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish);
+
+ /* our own cleanup first */
+ common_voice_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), FALSE);
+
+ /* Chain up parent's cleanup */
+ priv->iface_modem_voice_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup unsolicited events (Voice interface) */
+
+gboolean
+mm_shared_cinterion_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!priv->iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "Couldn't setup parent voice unsolicited events: %s", error->message);
+
+ /* our own setup next */
+ common_voice_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), TRUE);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_cinterion_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events);
+ g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events_finish);
+
+ /* chain up parent's setup first */
+ priv->iface_modem_voice_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Check if Voice supported (Voice interface) */
+
+gboolean
+mm_shared_cinterion_voice_check_support_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+slcc_format_check_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ /* ^SLCC supported unless we got any error response */
+ priv->slcc_support = (!!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL) ?
+ FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED);
+
+ /* If ^SLCC supported we won't need polling in the parent */
+ g_object_set (self,
+ MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED, (priv->slcc_support == FEATURE_SUPPORTED),
+ NULL);
+
+ /* ^SLCC command is supported; assume we have full voice capabilities */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_voice_check_support_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+ GError *error = NULL;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ if (!priv->iface_modem_voice_parent->check_support_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* voice is supported, check if ^SLCC is available */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SLCC=?",
+ 3,
+ /* Do NOT cache as the reply may be different if PIN locked
+ * or unlocked. E.g. we may not support ^SLCC for emergency
+ * voice calls. */
+ FALSE,
+ (GAsyncReadyCallback) slcc_format_check_ready,
+ task);
+}
+
+void
+mm_shared_cinterion_voice_check_support (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->check_support);
+ g_assert (priv->iface_modem_voice_parent->check_support_finish);
+
+ /* chain up parent's setup first */
+ priv->iface_modem_voice_parent->check_support (
+ self,
+ (GAsyncReadyCallback)parent_voice_check_support_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Common setup/cleanup time unsolicited events */
+
+static void
+ctzu_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMSharedCinterion *self)
+{
+ g_autofree gchar *iso8601 = NULL;
+ g_autoptr(MMNetworkTimezone) tz = NULL;
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_cinterion_parse_ctzu_urc (match_info, &iso8601, &tz, &error)) {
+ mm_obj_dbg (self, "couldn't process +CTZU URC: %s", error->message);
+ return;
+ }
+
+ mm_obj_dbg (self, "+CTZU URC received: %s", iso8601);
+ mm_iface_modem_time_update_network_time (MM_IFACE_MODEM_TIME (self), iso8601);
+ mm_iface_modem_time_update_network_timezone (MM_IFACE_MODEM_TIME (self), tz);
+}
+
+static void
+common_time_setup_cleanup_unsolicited_events (MMSharedCinterion *self,
+ gboolean enable)
+{
+ Private *priv;
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ priv = get_private (MM_SHARED_CINTERION (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));
+
+ mm_obj_dbg (self, "%s up time unsolicited events...",
+ enable ? "setting" : "cleaning");
+
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ priv->ctzu_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)ctzu_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+/*****************************************************************************/
+/* Cleanup unsolicited events (Time interface) */
+
+gboolean
+mm_shared_cinterion_time_cleanup_unsolicited_events_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_time_cleanup_unsolicited_events_ready (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!priv->iface_modem_time_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "couldn't cleanup parent time unsolicited events: %s", error->message);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_cinterion_time_cleanup_unsolicited_events (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_time_parent);
+
+ /* our own cleanup first */
+ common_time_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), FALSE);
+
+ if (priv->iface_modem_time_parent->cleanup_unsolicited_events &&
+ priv->iface_modem_time_parent->cleanup_unsolicited_events_finish) {
+ /* Chain up parent's cleanup */
+ priv->iface_modem_time_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_time_cleanup_unsolicited_events_ready,
+ task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Setup unsolicited events (Time interface) */
+
+gboolean
+mm_shared_cinterion_time_setup_unsolicited_events_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+own_time_setup_unsolicited_events (GTask *task)
+{
+ MMSharedCinterion *self;
+
+ self = g_task_get_source_object (task);
+
+ /* our own setup next */
+ common_time_setup_cleanup_unsolicited_events (MM_SHARED_CINTERION (self), TRUE);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_time_setup_unsolicited_events_ready (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+
+ if (!priv->iface_modem_time_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ mm_obj_warn (self, "Couldn't cleanup parent time unsolicited events: %s", error->message);
+
+ own_time_setup_unsolicited_events (task);
+}
+
+void
+mm_shared_cinterion_time_setup_unsolicited_events (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_CINTERION (self));
+ g_assert (priv->iface_modem_time_parent);
+
+ if (priv->iface_modem_time_parent->setup_unsolicited_events &&
+ priv->iface_modem_time_parent->setup_unsolicited_events_finish) {
+ /* chain up parent's setup first */
+ priv->iface_modem_time_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_time_setup_unsolicited_events_ready,
+ task);
+ return;
+ }
+
+ own_time_setup_unsolicited_events (task);
+}
+
+/*****************************************************************************/
+
+static void
+shared_cinterion_init (gpointer g_iface)
+{
+}
+
+GType
+mm_shared_cinterion_get_type (void)
+{
+ static GType shared_cinterion_type = 0;
+
+ if (!G_UNLIKELY (shared_cinterion_type)) {
+ static const GTypeInfo info = {
+ sizeof (MMSharedCinterion), /* class_size */
+ shared_cinterion_init, /* base_init */
+ NULL, /* base_finalize */
+ };
+
+ shared_cinterion_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedCinterion", &info, 0);
+ g_type_interface_add_prerequisite (shared_cinterion_type, MM_TYPE_IFACE_MODEM);
+ g_type_interface_add_prerequisite (shared_cinterion_type, MM_TYPE_IFACE_MODEM_VOICE);
+ g_type_interface_add_prerequisite (shared_cinterion_type, MM_TYPE_IFACE_MODEM_TIME);
+ g_type_interface_add_prerequisite (shared_cinterion_type, MM_TYPE_IFACE_MODEM_LOCATION);
+ }
+
+ return shared_cinterion_type;
+}
diff --git a/src/plugins/cinterion/mm-shared-cinterion.h b/src/plugins/cinterion/mm-shared-cinterion.h
new file mode 100644
index 00000000..eb6beac8
--- /dev/null
+++ b/src/plugins/cinterion/mm-shared-cinterion.h
@@ -0,0 +1,153 @@
+/* -*- 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) 2014 Ammonit Measurement GmbH
+ * Copyright (C) 2014 - 2018 Aleksander Morgado <aleksander@aleksander.es>
+ * Copyright (C) 2019 Purism SPC
+ */
+
+#ifndef MM_SHARED_CINTERION_H
+#define MM_SHARED_CINTERION_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-location.h"
+#include "mm-iface-modem-voice.h"
+#include "mm-iface-modem-time.h"
+
+#define MM_TYPE_SHARED_CINTERION (mm_shared_cinterion_get_type ())
+#define MM_SHARED_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_CINTERION, MMSharedCinterion))
+#define MM_IS_SHARED_CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_CINTERION))
+#define MM_SHARED_CINTERION_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_CINTERION, MMSharedCinterion))
+
+typedef struct _MMSharedCinterion MMSharedCinterion;
+
+struct _MMSharedCinterion {
+ GTypeInterface g_iface;
+
+ /* Peek modem interface of the parent class of the object */
+ MMIfaceModem * (* peek_parent_interface) (MMSharedCinterion *self);
+
+ /* Peek location interface of the parent class of the object */
+ MMIfaceModemLocation * (* peek_parent_location_interface) (MMSharedCinterion *self);
+
+ /* Peek voice interface of the parent class of the object */
+ MMIfaceModemVoice * (* peek_parent_voice_interface) (MMSharedCinterion *self);
+
+ /* Peek time interface of the parent class of the object */
+ MMIfaceModemTime * (* peek_parent_time_interface) (MMSharedCinterion *self);
+};
+
+GType mm_shared_cinterion_get_type (void);
+
+/*****************************************************************************/
+/* Modem interface */
+
+void mm_shared_cinterion_modem_reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_modem_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+/*****************************************************************************/
+/* Location interface */
+
+void mm_shared_cinterion_location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMModemLocationSource mm_shared_cinterion_location_load_capabilities_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_cinterion_enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_cinterion_disable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+
+/*****************************************************************************/
+/* Voice interface */
+
+MMBaseCall *mm_shared_cinterion_create_call (MMIfaceModemVoice *self,
+ MMCallDirection direction,
+ const gchar *number);
+
+void mm_shared_cinterion_voice_check_support (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_voice_check_support_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_cinterion_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_cinterion_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_cinterion_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_cinterion_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+/*****************************************************************************/
+/* Time interface */
+
+void mm_shared_cinterion_time_setup_unsolicited_events (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_time_setup_unsolicited_events_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_cinterion_time_cleanup_unsolicited_events (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_cinterion_time_cleanup_unsolicited_events_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SHARED_CINTERION_H */
diff --git a/src/plugins/cinterion/tests/test-modem-helpers-cinterion.c b/src/plugins/cinterion/tests/test-modem-helpers-cinterion.c
new file mode 100644
index 00000000..d4816199
--- /dev/null
+++ b/src/plugins/cinterion/tests/test-modem-helpers-cinterion.c
@@ -0,0 +1,1967 @@
+/* -*- 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) 2014 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-cinterion.h"
+
+#define g_assert_cmpfloat_tolerance(val1, val2, tolerance) \
+ g_assert_cmpfloat (fabs (val1 - val2), <, tolerance)
+
+/*****************************************************************************/
+/* Test ^SCFG test responses */
+
+static void
+common_test_scfg (const gchar *response,
+ GArray *expected_bands,
+ MMModemCharset charset,
+ MMCinterionModemFamily modem_family)
+{
+ GArray *bands = NULL;
+ gchar *expected_bands_str;
+ gchar *bands_str;
+ GError *error = NULL;
+ gboolean res;
+ MMCinterionRadioBandFormat format;
+
+ res = mm_cinterion_parse_scfg_test (response,
+ modem_family,
+ charset,
+ &bands,
+ &format,
+ &error);
+ g_assert_no_error (error);
+ g_assert (res == TRUE);
+ g_assert (bands != NULL);
+
+ mm_common_bands_garray_sort (bands);
+ mm_common_bands_garray_sort (expected_bands);
+
+ expected_bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)expected_bands->data,
+ expected_bands->len);
+ bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)bands->data,
+ bands->len);
+
+ /* Instead of comparing the array one by one, compare the strings built from the mask
+ * (we get a nicer error if it fails) */
+ g_assert_cmpstr (bands_str, ==, expected_bands_str);
+
+ g_free (bands_str);
+ g_free (expected_bands_str);
+ g_array_unref (bands);
+}
+
+static void
+test_scfg (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"Audio/Loop\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Call/ECC\",(\"0\"-\"255\")\r\n"
+ "^SCFG: \"Call/Speech/Codec\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"GPRS/Auth\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",(\"disabled\",\"enabled\")\r\n"
+ "^SCFG: \"GPRS/MaxDataRate/HSDPA\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"GPRS/MaxDataRate/HSUPA\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Ident/Manufacturer\",(25)\r\n"
+ "^SCFG: \"Ident/Product\",(25)\r\n"
+ "^SCFG: \"MEopMode/Airplane\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"MEopMode/CregRoam\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/CFUN\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/PowerMgmt/LCI\",(\"disabled\",\"enabled\")\r\n"
+ "^SCFG: \"MEopMode/PowerMgmt/VExt\",(\"high\",\"low\")\r\n"
+ "^SCFG: \"MEopMode/PwrSave\",(\"disabled\",\"enabled\"),(\"0-600\"),(\"1-36000\")\r\n"
+ "^SCFG: \"MEopMode/RingOnData\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"MEopMode/RingUrcOnCall\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"MEShutdown/OnIgnition\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"Radio/Band\",(\"1-511\",\"0-1\")\r\n"
+ "^SCFG: \"Radio/NWSM\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",(\"4\"-\"8\")\r\n"
+ "^SCFG: \"Serial/USB/DDD\",(\"0\",\"1\"),(\"0\"),(4),(4),(4),(63),(63),(4)\r\n"
+ "^SCFG: \"URC/DstIfc\",(\"mdm\",\"app\")\r\n"
+ "^SCFG: \"URC/Datamode/Ringline\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"URC/Ringline\",(\"off\",\"local\",\"asc0\",\"wakeup\")\r\n"
+ "^SCFG: \"URC/Ringline/ActiveTime\",(\"0\",\"1\",\"2\",\"keep\")\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_6, g_array_append_val (expected_bands, single);
+
+ common_test_scfg (response, expected_bands, MM_MODEM_CHARSET_UNKNOWN, MM_CINTERION_MODEM_FAMILY_DEFAULT);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_ehs5 (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"Audio/Loop\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Call/ECC\",(\"0\"-\"255\")\r\n"
+ "^SCFG: \"Call/Ecall/AckTimeout\",(\"0-2147483646\")\r\n"
+ "^SCFG: \"Call/Ecall/Callback\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Call/Ecall/CallbackTimeout\",(\"0-2147483646\")\r\n"
+ "^SCFG: \"Call/Ecall/Msd\",(\"280\")\r\n"
+ "^SCFG: \"Call/Ecall/Pullmode\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Call/Ecall/SessionTimeout\",(\"0-2147483646\")\r\n"
+ "^SCFG: \"Call/Ecall/StartTimeout\",(\"0-2147483646\")\r\n"
+ "^SCFG: \"Call/Speech/Codec\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",(\"disabled\",\"enabled\")\r\n"
+ "^SCFG: \"Gpio/mode/ASC1\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/DAI\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/DCD0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/DSR0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/DTR0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/FSR\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/PULSE\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/PWM\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/RING0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/SPI\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/SYNC\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Ident/Manufacturer\",(25)\r\n"
+ "^SCFG: \"Ident/Product\",(25)\r\n"
+ "^SCFG: \"MEShutdown/Fso\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEShutdown/sVsup/threshold\",(\"-4\",\"-3\",\"-2\",\"-1\",\"0\",\"1\",\"2\",\"3\",\"4\"),(\"0\")\r\n"
+ "^SCFG: \"MEopMode/CFUN\",(\"0\",\"1\"),(\"1\",\"4\")\r\n"
+ "^SCFG: \"MEopMode/Dormancy\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/SoR\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"Radio/Band\",(\"1\"-\"147\")\r\n"
+ "^SCFG: \"Radio/Mtpl\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"1\",\"8\"),(\"18\"-\"33\"),(\"18\"-\"27\")\r\n"
+ "^SCFG: \"Radio/Mtpl\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"16\",\"32\",\"64\",\"128\",\"256\"),(\"18\"-\"24\")\r\n"
+ "^SCFG: \"Radio/Mtpl\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"2\",\"4\"),(\"18\"-\"30\"),(\"18\"-\"26\")\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",(\"0\",\"1\",\"2\",\"3\",\"4\")\r\n"
+ "^SCFG: \"Serial/Interface/Allocation\",(\"0\",\"1\",\"2\"),(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"Serial/USB/DDD\",(\"0\",\"1\"),(\"0\"),(4),(4),(4),(63),(63),(4)\r\n"
+ "^SCFG: \"Tcp/IRT\",(\"1\"-\"60\")\r\n"
+ "^SCFG: \"Tcp/MR\",(\"1\"-\"30\")\r\n"
+ "^SCFG: \"Tcp/OT\",(\"1\"-\"6000\")\r\n"
+ "^SCFG: \"Tcp/WithURCs\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"Trace/Syslog/OTAP\",(\"0\",\"1\"),(\"null\",\"asc0\",\"asc1\",\"usb\",\"usb1\",\"usb2\",\"usb3\",\"usb4\",\"usb5\",\"file\",\"udp\",\"system\"),(\"1\"-\"65535\"),(125),(\"buffered\",\"secure\"),(\"off\",\"on\")\r\n"
+ "^SCFG: \"URC/Ringline\",(\"off\",\"local\",\"asc0\")\r\n"
+ "^SCFG: \"URC/Ringline/ActiveTime\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"Userware/Autostart\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Userware/Autostart/Delay\",(\"0\"-\"10000\")\r\n"
+ "^SCFG: \"Userware/DebugInterface\",(\"0\"-\"255\")|(\"FE80::\"-\"FE80::FFFFFFFFFFFFFFFF\"),(\"0\"-\"255\")|(\"FE80::\"-\"FE80::FFFFFFFFFFFFFFFF\"),(\"0\",\"1\")\r\n"
+ "^SCFG: \"Userware/DebugMode\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"Userware/Passwd\",(\"0\"-\"8\")\r\n"
+ "^SCFG: \"Userware/Stdout\",(\"null\",\"asc0\",\"asc1\",\"usb\",\"usb1\",\"usb2\",\"usb3\",\"usb4\",\"usb5\",\"file\",\"udp\",\"system\"),(\"1\"-\"65535\"),(\"0\"-\"125\"),(\"buffered\",\"secure\"),(\"off\",\"on\")\r\n"
+ "^SCFG: \"Userware/Watchdog\",(\"0\",\"1\",\"2\")\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 4);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+
+ common_test_scfg (response, expected_bands, MM_MODEM_CHARSET_UNKNOWN, MM_CINTERION_MODEM_FAMILY_DEFAULT);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_pls62_gsm (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"MEopMode/Prov/AutoSelect\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"MEopMode/Prov/Cfg\",(\"fallback\",\"attus\")\r\n"
+ "^SCFG: \"Serial/Ifc\",(\"Current\",\"ASC0\",\"USB0\",\"USB1\",\"USB2\",\"MUX1\",\"MUX2\",\"MUX3\",\"0\"),(\"0\",\"3\"),(\"1200\",\"2400\",\"4800\",\"9600\",\"19200\",\"38400\",\"57600\",\"115200\",\"230400\",\"460800\",\"500000\",\"750000\",\"921600\"),(\"0)\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",(\"current\",\"powerup\"),(\"asc0\",\"acm1\",\"acm2\",\"acm3\",\"rmnet0\",\"rmnet1\")\r\n"
+ "^SCFG: \"Gpio/mode/ASC1\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/DCD0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/DSR0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/DTR0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/FSR\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/PULSE\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/PWM\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/HWAKEUP\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/RING0\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/SPI\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"Gpio/mode/SYNC\",(\"std\",\"gpio\",\"rsv\")\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",(\"disabled\",\"enabled\")\r\n"
+ "^SCFG: \"Ident/Manufacturer\",(25)\r\n"
+ "^SCFG: \"Ident/Product\",(25)\r\n"
+ "^SCFG: \"MEopMode/SoR\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"MEopMode/CregRoam\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MeOpMode/SRPOM\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/RingOnData\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"MEShutdown/Fso\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEShutdown/sVsup/threshold\",(\"-4\",\"-3\",\"-2\",\"-1\",\"0\",\"1\",\"2\",\"3\",\"4\"),(\"0\")\r\n"
+ "^SCFG: \"Radio/Band/2G\",(\"0x00000004\"-\"0x00000074\")\r\n"
+ "^SCFG: \"Radio/Band/3G\",(\"0x00000001\"-\"0x0004019B\")\r\n"
+ "^SCFG: \"Radio/Band/4G\",(\"0x00000001\"-\"0x080E08DF\")\r\n"
+ "^SCFG: \"Radio/Mtpl/2G\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"0x00000004\",\"0x00000010\",\"0x00000020\",\"0x00000040\"),,(\"18\"-\"33\"),(\"18\"-\"27\")\r\n"
+ "^SCFG: \"Radio/Mtpl/3G\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"0x00000001\",\"0x00000002\",\"0x00000008\",\"0x00000010\",\"0x00000080\",\"0x00000100\",\"0x00040000\"),,(\"18\"-\"24\")\r\n"
+ "^SCFG: \"Radio/Mtpl/4G\",(\"0\"-\"3\"),(\"1\"-\"8\"),(\"0x00000001\",\"0x00000002\",\"0x00000004\",\"0x00000008\",\"0x00000010\",\"0x00000040\",\"0x00000080\",\"0x00000800\",\"0x00020000\",\"0x00040000\",\"0x00080000\",\"0x08000000\"),,(\"18)\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",(\"0\",\"1\",\"2\",\"3\",\"4\")\r\n"
+ "^SCFG: \"Serial/Interface/Allocation\",(\"0\",\"1\"),(\"0\",\"1\")\r\n"
+ "^SCFG: \"Serial/USB/DDD\",(\"0\",\"1\"),(\"0\"),(4),(4),(4),(63),(63),(4)\r\n"
+ "^SCFG: \"Tcp/IRT\",(\"1\"-\"60\")\r\n"
+ "^SCFG: \"Tcp/MR\",(\"2\"-\"30\")\r\n"
+ "^SCFG: \"Tcp/OT\",(\"1\"-\"6000\")\r\n"
+ "^SCFG: \"Tcp/WithURCs\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"Trace/Syslog/OTAP\",(\"0\",\"1\"),(\"null\",\"asc0\",\"asc1\",\"usb\",\"usb1\",\"usb2\",\"file\",\"system\"),(\"1\"-\"65535\"),(125),(\"buffered\",\"secure\"),(\"off\",\"on\")\r\n"
+ "^SCFG: \"Urc/Ringline\",(\"off\",\"local\",\"asc0\",\"wakeup\")\r\n"
+ "^SCFG: \"Urc/Ringline/ActiveTime\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"Userware/Autostart\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Userware/Autostart/Delay\",(\"0\"-\"10000\")\r\n"
+ "^SCFG: \"Userware/DebugInterface\",(\"0\"-\"255\")|(\"FE80::\"-\"FE80::FFFFFFFFFFFFFFFF\"),(\"0\"-\"255\")|(\"FE80::\"-\"FE80::FFFFFFFFFFFFFFFF\"),(\"0\",\"1\")\r\n"
+ "^SCFG: \"Userware/DebugMode\",(\"off\",\"on\")\r\n"
+ "^SCFG: \"Userware/Passwd\",(\"0\"-\"8\")\r\n"
+ "^SCFG: \"Userware/Stdout\",(\"null\",\"asc0\",\"asc1\",\"usb\",\"usb1\",\"usb2\",\"file\",\"system\"),(\"1\"-\"65535\"),(\"0\"-\"125\"),(\"buffered\",\"secure\"),(\"off\",\"on\")\r\n"
+ "^SCFG: \"Userware/Watchdog\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",(\"current\",\"powerup\"),(\"asc0\",\"acm1\",\"acm2\",\"acm3\")\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 23);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_4, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_9, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_3, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_4, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_7, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_12, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single);
+
+ common_test_scfg (response, expected_bands, MM_MODEM_CHARSET_GSM, MM_CINTERION_MODEM_FAMILY_IMT);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_pls62_ucs2 (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"MEopMode/Prov/AutoSelect\",(\"006F00660066\",\"006F006E\")\r\n"
+ "^SCFG: \"MEopMode/Prov/Cfg\",(\"fallback\",\"attus\")\r\n"
+ "^SCFG: \"Serial/Ifc\",(\"00430075007200720065006E0074\",\"0041005300430030\",\"0055005300420030\",\"0055005300420031\",\"0055005300420032\",\"004D005500580031\",\"004D005500580032\",\"004D005500580033\",\"0030\"),(\"0030\",\"0033)\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",(\"00630075007200720065006E0074\",\"0070006F00770065007200750070\"),(\"0061007300630030\",\"00610063006D0031\",\"00610063006D0032\",\"00610063006D0033\",\"0072006D006E006500740030\",\"0072006D0)\r\n"
+ "^SCFG: \"Gpio/mode/ASC1\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/DCD0\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/DSR0\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/DTR0\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/FSR\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/PULSE\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/PWM\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/HWAKEUP\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/RING0\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/SPI\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"Gpio/mode/SYNC\",(\"007300740064\",\"006700700069006F\",\"007200730076\")\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",(\"00640069007300610062006C00650064\",\"0065006E00610062006C00650064\")\r\n"
+ "^SCFG: \"Ident/Manufacturer\",(25)\r\n"
+ "^SCFG: \"Ident/Product\",(25)\r\n"
+ "^SCFG: \"MEopMode/SoR\",(\"006F00660066\",\"006F006E\")\r\n"
+ "^SCFG: \"MEopMode/CregRoam\",(\"0030\",\"0031\")\r\n"
+ "^SCFG: \"MeOpMode/SRPOM\",(\"0030\",\"0031\")\r\n"
+ "^SCFG: \"MEopMode/RingOnData\",(\"006F00660066\",\"006F006E\")\r\n"
+ "^SCFG: \"MEShutdown/Fso\",(\"0030\",\"0031\")\r\n"
+ "^SCFG: \"MEShutdown/sVsup/threshold\",(\"002D0034\",\"002D0033\",\"002D0032\",\"002D0031\",\"0030\",\"0031\",\"0032\",\"0033\",\"0034\"),(\"0030\")\r\n"
+ "^SCFG: \"Radio/Band/2G\",(\"0030007800300030003000300030003000300034\"-\"0030007800300030003000300030003000370034\")\r\n"
+ "^SCFG: \"Radio/Band/3G\",(\"0030007800300030003000300030003000300031\"-\"0030007800300030003000340030003100390042\")\r\n"
+ "^SCFG: \"Radio/Band/4G\",(\"0030007800300030003000300030003000300031\"-\"0030007800300038003000450030003800440046\")\r\n"
+ "^SCFG: \"Radio/Mtpl/2G\",(\"00300022002D00220033\"),(\"00310022002D00220038\"),(\"00300078003000300030003000300030003000340022002C002200300078003000300030003000300030003100300022002C0022003000780030003000300030003)\r\n"
+ "^SCFG: \"Radio/Mtpl/3G\",(\"00300022002D00220033\"),(\"00310022002D00220038\"),(\"00300078003000300030003000300030003000310022002C002200300078003000300030003000300030003000320022002C0022003000780030003000300030003)\r\n"
+ "^SCFG: \"Radio/Mtpl/4G\",(\"00300022002D00220033\"),(\"00310022002D00220038\"),(\"00310022002D00220038\"),,(\"003100380022002D002200320033\")\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",(\"0030\",\"0031\",\"0032\",\"0033\",\"0034\")\r\n"
+ "^SCFG: \"Serial/Interface/Allocation\",(\"0030\",\"0031\"),(\"0030\",\"0031\")\r\n"
+ "^SCFG: \"Serial/USB/DDD\",(\"0030\",\"0031\"),(\"0030\"),(4),(4),(4),(63),(63),(4)\r\n"
+ "^SCFG: \"Tcp/IRT\",(\"0031\"-\"00360030\")\r\n"
+ "^SCFG: \"Tcp/MR\",(\"0032\"-\"00330030\")\r\n"
+ "^SCFG: \"Tcp/OT\",(\"0031\"-\"0036003000300030\")\r\n"
+ "^SCFG: \"Tcp/WithURCs\",(\"006F006E\",\"006F00660066\")\r\n"
+ "^SCFG: \"Trace/Syslog/OTAP\",(\"0030\",\"0031\"),(\"006E0075006C006C\",\"0061007300630030\",\"0061007300630031\",\"007500730062\",\"0075007300620031\",\"0075007300620032\",\"00660069006C0065\",\"00730079007300740065006D\"),(\"003)\r\n"
+ "^SCFG: \"Urc/Ringline\",(\"006F00660066\",\"006C006F00630061006C\",\"0061007300630030\",\"00770061006B006500750070\")\r\n"
+ "^SCFG: \"Urc/Ringline/ActiveTime\",(\"0030\",\"0031\",\"0032\")\r\n"
+ "^SCFG: \"Userware/Autostart\",(\"0030\",\"0031\")\r\n"
+ "^SCFG: \"Userware/Autostart/Delay\",(\"00300022002D002200310030003000300030\")\r\n"
+ "^SCFG: \"Userware/DebugInterface\",(\"0030\"-\"003200350035\")|(\"0046004500380030003A003A\"-\"0046004500380030003A003A0046004600460046004600460046004600460046004600460046004600460046\"),(\"0030\"-\"003200350035\")|(\"004)\r\n"
+ "^SCFG: \"Userware/DebugMode\",(\"006F00660066\",\"006F006E\")\r\n"
+ "^SCFG: \"Userware/Passwd\",(\"0030\"-\"0038\")\r\n"
+ "^SCFG: \"Userware/Stdout\",(\"006E0075006C006C\",\"0061007300630030\",\"0061007300630031\",\"007500730062\",\"0075007300620031\",\"0075007300620032\",\"00660069006C0065\",\"00730079007300740065006D\"),(\"0031\"-\"00360035003500)\r\n"
+ "^SCFG: \"Userware/Watchdog\",(\"0030\",\"0031\",\"0032\")\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",(\"00630075007200720065006E0074\",\"0070006F00770065007200750070\"),(\"0061007300630030\",\"00610063006D0031\",\"00610063006D0032\",\"00610063006D0033\")\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 23);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_4, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_9, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_3, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_4, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_7, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_12, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single);
+
+ common_test_scfg (response, expected_bands, MM_MODEM_CHARSET_UCS2, MM_CINTERION_MODEM_FAMILY_IMT);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_alas5 (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"Audio/Loop\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Audio/SvTone\",(\"0-2047\")\r\n"
+ "^SCFG: \"Call/Ecall/AckTimeout\",(\"0-60000\")\r\n"
+ "^SCFG: \"Call/Ecall/BlockSMSPP\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Call/Ecall/Callback\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Call/Ecall/CallbackTimeout\",(\"0-86400000\")\r\n"
+ "^SCFG: \"Call/Ecall/Force\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"Call/Ecall/Msd\",(280)\r\n"
+ "^SCFG: \"Call/Ecall/Pullmode\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Call/Ecall/SessionTimeout\",(\"0-300000\")\r\n"
+ "^SCFG: \"Call/Ecall/StartTimeout\",(\"0-600000\")\r\n"
+ "^SCFG: \"Call/ECC\",(\"0\"-\"255\")\r\n"
+ "^SCFG: \"Call/Speech/Codec\",(\"0\",\"2\")\r\n"
+ "^SCFG: \"GPRS/Auth\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",(\"disabled\",\"enabled\")\r\n"
+ "^SCFG: \"GPRS/MTU/Mode\",(\"0-1\")\r\n"
+ "^SCFG: \"GPRS/MTU/Size\",(\"1280-4096\")\r\n"
+ "^SCFG: \"MEopMode/CFUN\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/CregRoam\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/Dormancy\",(\"0\",\"1\",\"9\")\r\n"
+ "^SCFG: \"MEopMode/DTM/Mode\",(\"0\",\"1\",\"2\")\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",(\"current\",\"powerup\"),(\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\")\r\n"
+ "^SCFG: \"MEopMode/FGI/Split\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/IMS\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/NonBlock/Cops\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/PowerMgmt/LCI\",(\"disabled\",\"enabled\"),(\"GPIO1\",\"GPIO3\",\"GPIO4\",\"GPIO5\",\"GPIO6\",\"GPIO7\",\"GPIO8\",\"GPIO11\",\"GPIO12\",\"GPIO13\",\"GPIO14\",\"GPIO15\",\"GPIO16\",\"GPIO17\",\"GPIO22\")\r\n"
+ "^SCFG: \"MEopMode/Prov/AutoFallback\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"MEopMode/Prov/AutoSelect\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"MEopMode/Prov/Cfg\",(\"vdfde\",\"tmode\",\"clarobr\",\"telenorno\",\"telenorse\",\"vdfpt\",\"fallb3gpp*\",\"vdfww\",\"vdfes\",\"swisscomch\",\"eeuk\",\"orangero\",\"orangees\",\"tefde\",\"telenordk\",\"timit\",\"tn1de\",\"tefes\",\"tels)\r\n"
+ "^SCFG: \"MEopMode/PwrSave\",(\"disabled\",\"enabled\"),(\"0-36000\"),(\"0-36000\"),(\"CPU-A\",\"CPU-M\"),(\"powerup\",\"current\")\r\n"
+ "^SCFG: \"MEopMode/SRPOM\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"MEopMode/USB/KeepData\",(\"current\",\"powerup\"),(\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\")\r\n"
+ "^SCFG: \"MEShutdown/OnIgnition\",(\"on\",\"off\")\r\n"
+ "^SCFG: \"MEShutdown/Timer\",(\"off\",\"0\"-\"525600\")\r\n"
+ "^SCFG: \"Misc/CId\",(290)\r\n"
+ "^SCFG: \"Radio/Band/2G\",(\"00000001-0000000f\"),,(\"0\",\"1\")\r\n"
+ "^SCFG: \"Radio/Band/3G\",(\"00000001-000400b5\"),,(\"0\",\"1\")\r\n"
+ "^SCFG: \"Radio/Band/4G\",(\"00000001-8a0e00d5\"),(\"00000002-000001e2\"),(\"0\",\"1\")\r\n"
+ "^SCFG: \"Radio/CNS\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"Radio/Mtpl\",(\"0-1\"),(\"1-8\")\r\n"
+ "^SCFG: \"Radio/Mtpl/2G\",(\"2-3\"),(\"1-8\"),(\"00000001-0000000f\"),,(\"18-33\"),(\"18-27\")\r\n"
+ "^SCFG: \"Radio/Mtpl/3G\",(\"2-3\"),(\"1-8\"),(\"00000001-000000b5\"),,(\"18-24\")\r\n"
+ "^SCFG: \"Radio/Mtpl/4G\",(\"2-3\"),(\"1-8\"),(\"00000001-8a0e00d5\"),(\"00000002-000000e2\"),(\"18-24\")\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",(\"4\"-\"8\")\r\n"
+ "^SCFG: \"RemoteWakeUp/Event/ASC\",(\"none\",\"GPIO1\",\"GPIO3\",\"GPIO4\",\"GPIO5\",\"GPIO6\",\"GPIO7\",\"GPIO8\",\"GPIO11\",\"GPIO12\",\"GPIO13\",\"GPIO14\",\"GPIO15\",\"GPIO16\",\"GPIO17\",\"GPIO22\")\r\n"
+ "^SCFG: \"RemoteWakeUp/Event/URC\",(\"none\",\"GPIO1\",\"GPIO3\",\"GPIO4\",\"GPIO5\",\"GPIO6\",\"GPIO7\",\"GPIO8\",\"GPIO11\",\"GPIO12\",\"GPIO13\",\"GPIO14\",\"GPIO15\",\"GPIO16\",\"GPIO17\",\"GPIO22\")\r\n"
+ "^SCFG: \"RemoteWakeUp/Event/USB\",(\"none\",\"GPIO1\",\"GPIO3\",\"GPIO4\",\"GPIO5\",\"GPIO6\",\"GPIO7\",\"GPIO8\",\"GPIO11\",\"GPIO12\",\"GPIO13\",\"GPIO14\",\"GPIO15\",\"GPIO16\",\"GPIO17\",\"GPIO22\")\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",(\"current\",\"powerup\"),(\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\")\r\n"
+ "^SCFG: \"RemoteWakeUp/Pulse\",(\"1\"-\"100\")\r\n"
+ "^SCFG: \"Serial/USB/DDD\",(\"0-1\"),(\"0\"),(\"0001-ffff\"),(\"0000-ffff\"),(\"0000-ffff\"),(63),(63),(4)\r\n"
+ "^SCFG: \"SIM/CS\",(\"NOSIM\",\"SIM1\",\"SIM2\")\r\n"
+ "^SCFG: \"SMS/4GPREF\",(\"IMS\",\"CSPS\")\r\n"
+ "^SCFG: \"SMS/AutoAck\",(\"0\",\"1\")\r\n"
+ "^SCFG: \"SMS/RETRM\",(\"1-45\")\r\n"
+ "^SCFG: \"URC/Ringline\",(\"off\",\"local\",\"asc0\")\r\n"
+ "^SCFG: \"URC/Ringline/ActiveTime\",(\"2\",\"on\",\"off\")\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 23);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_3, g_array_append_val (expected_bands, single); //
+ single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_6, g_array_append_val (expected_bands, single); //
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_3, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_7, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_26, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_38, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_39, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_40, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_41, g_array_append_val (expected_bands, single);
+
+ common_test_scfg (response, expected_bands, MM_MODEM_CHARSET_GSM, MM_CINTERION_MODEM_FAMILY_DEFAULT);
+
+ g_array_unref (expected_bands);
+}
+
+/*****************************************************************************/
+/* Test ^SCFG responses */
+
+static void
+common_test_scfg_response (const gchar *response,
+ MMModemCharset charset,
+ GArray *expected_bands,
+ MMCinterionModemFamily modem_family,
+ MMCinterionRadioBandFormat rbf)
+{
+ GArray *bands = NULL;
+ gchar *expected_bands_str;
+ gchar *bands_str;
+ GError *error = NULL;
+ gboolean res;
+
+ res = mm_cinterion_parse_scfg_response (response, modem_family, charset, &bands, rbf, &error);
+ g_assert_no_error (error);
+ g_assert (res == TRUE);
+ g_assert (bands != NULL);
+
+ mm_common_bands_garray_sort (bands);
+ mm_common_bands_garray_sort (expected_bands);
+
+ expected_bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)expected_bands->data,
+ expected_bands->len);
+ bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)bands->data,
+ bands->len);
+
+ /* Instead of comparing the array one by one, compare the strings built from the mask
+ * (we get a nicer error if it fails) */
+ g_assert_cmpstr (bands_str, ==, expected_bands_str);
+
+ g_free (bands_str);
+ g_free (expected_bands_str);
+ g_array_unref (bands);
+}
+
+static void
+test_scfg_response_2g (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"Radio/Band\",\"3\",\"3\"\r\n"
+ "\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+
+ common_test_scfg_response (response, MM_MODEM_CHARSET_UNKNOWN, expected_bands, MM_CINTERION_MODEM_FAMILY_DEFAULT, MM_CINTERION_RADIO_BAND_FORMAT_SINGLE);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_response_3g (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"Radio/Band\",127\r\n"
+ "\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single);
+
+ common_test_scfg_response (response, MM_MODEM_CHARSET_UNKNOWN, expected_bands, MM_CINTERION_MODEM_FAMILY_DEFAULT, MM_CINTERION_RADIO_BAND_FORMAT_SINGLE);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_response_pls62_gsm (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"MEopMode/Prov/AutoSelect\",\"off\"\r\n"
+ "^SCFG: \"MEopMode/Prov/Cfg\",\"attus\"\r\n"
+ "^SCFG: \"Serial/Ifc\",\"0\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",\"current\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",\"powerup\"\r\n"
+ "^SCFG: \"Gpio/mode/ASC1\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/DCD0\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/DSR0\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/DTR0\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/FSR\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/PULSE\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/PWM\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/HWAKEUP\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/RING0\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/SPI\",\"gpio\"\r\n"
+ "^SCFG: \"Gpio/mode/SYNC\",\"gpio\"\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",\"enabled\"\r\n"
+ "^SCFG: \"Ident/Manufacturer\",\"Cinterion\"\r\n"
+ "^SCFG: \"Ident/Product\",\"PLS62-W\"\r\n"
+ "^SCFG: \"MEopMode/SoR\",\"off\"\r\n"
+ "^SCFG: \"MEopMode/CregRoam\",\"0\"\r\n"
+ "^SCFG: \"MeOpMode/SRPOM\",\"0\"\r\n"
+ "^SCFG: \"MEopMode/RingOnData\",\"off\"\r\n"
+ "^SCFG: \"MEShutdown/Fso\",\"0\"\r\n"
+ "^SCFG: \"MEShutdown/sVsup/threshold\",\"0\",\"0\"\r\n"
+ "^SCFG: \"Radio/Band/2G\",\"0x00000014\"\r\n"
+ "^SCFG: \"Radio/Band/3G\",\"0x00000182\"\r\n"
+ "^SCFG: \"Radio/Band/4G\",\"0x080E0000\"\r\n"
+ "^SCFG: \"Radio/Mtpl/2G\",\"0\"\r\n"
+ "^SCFG: \"Radio/Mtpl/3G\",\"0\"\r\n"
+ "^SCFG: \"Radio/Mtpl/4G\",\"0\"\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",\"4\"\r\n"
+ "^SCFG: \"Serial/Interface/Allocation\",\"0\",\"0\"\r\n"
+ "^SCFG: \"Serial/USB/DDD\",\"0\",\"0\",\"0409\",\"1E2D\",\"005B\",\"Cinterion Wireless Modules\",\"PLSx\",\"\"\r\n"
+ "^SCFG: \"Tcp/IRT\",\"3\"\r\n"
+ "^SCFG: \"Tcp/MR\",\"10\"\r\n"
+ "^SCFG: \"Tcp/OT\",\"6000\"\r\n"
+ "^SCFG: \"Tcp/WithURCs\",\"on\"\r\n"
+ "^SCFG: \"Trace/Syslog/OTAP\",\"0\"\r\n"
+ "^SCFG: \"Urc/Ringline\",\"local\"\r\n"
+ "^SCFG: \"Urc/Ringline/ActiveTime\",\"2\"\r\n"
+ "^SCFG: \"Userware/Autostart\",\"0\"\r\n"
+ "^SCFG: \"Userware/Autostart/Delay\",\"0\"\r\n"
+ "^SCFG: \"Userware/DebugInterface\",\"0.0.0.0\",\"0.0.0.0\",\"0\"\r\n"
+ "^SCFG: \"Userware/DebugMode\",\"off\"\r\n"
+ "^SCFG: \"Userware/Passwd\",\r\n"
+ "^SCFG: \"Userware/Stdout\",\"null\",,,,\"off\"\r\n"
+ "^SCFG: \"Userware/Watchdog\",\"0\"\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",\"current\"\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",\"powerup\"\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_9, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single);
+
+ common_test_scfg_response (response, MM_MODEM_CHARSET_GSM, expected_bands, MM_CINTERION_MODEM_FAMILY_IMT, MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_response_pls62_ucs2 (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"MEopMode/Prov/AutoSelect\",\"006F00660066\"\r\n"
+ "^SCFG: \"MEopMode/Prov/Cfg\",\"00610074007400750073\"\r\n"
+ "^SCFG: \"Serial/Ifc\",\"0\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",\"00630075007200720065006E0074\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",\"0070006F00770065007200750070\"\r\n"
+ "^SCFG: \"Gpio/mode/ASC1\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/DCD0\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/DSR0\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/DTR0\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/FSR\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/PULSE\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/PWM\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/HWAKEUP\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/RING0\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/SPI\",\"006700700069006F\"\r\n"
+ "^SCFG: \"Gpio/mode/SYNC\",\"006700700069006F\"\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",\"0065006E00610062006C00650064\"\r\n"
+ "^SCFG: \"Ident/Manufacturer\",\"Cinterion\"\r\n"
+ "^SCFG: \"Ident/Product\",\"PLS62-W\"\r\n"
+ "^SCFG: \"MEopMode/SoR\",\"006F00660066\"\r\n"
+ "^SCFG: \"MEopMode/CregRoam\",\"0030\"\r\n"
+ "^SCFG: \"MeOpMode/SRPOM\",\"0030\"\r\n"
+ "^SCFG: \"MEopMode/RingOnData\",\"006F00660066\"\r\n"
+ "^SCFG: \"MEShutdown/Fso\",\"0030\"\r\n"
+ "^SCFG: \"MEShutdown/sVsup/threshold\",\"0030\",\"0030\"\r\n"
+ "^SCFG: \"Radio/Band/2G\",\"0030007800300030003000300030003000310034\"\r\n"
+ "^SCFG: \"Radio/Band/3G\",\"0030007800300030003000300030003100380032\"\r\n"
+ "^SCFG: \"Radio/Band/4G\",\"0030007800300038003000450030003000300030\"\r\n"
+ "^SCFG: \"Radio/Mtpl/2G\",\"0030\"\r\n"
+ "^SCFG: \"Radio/Mtpl/3G\",\"0030\"\r\n"
+ "^SCFG: \"Radio/Mtpl/4G\",\"0030\"\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",\"0034\"\r\n"
+ "^SCFG: \"Serial/Interface/Allocation\",\"0030\",\"0030\"\r\n"
+ "^SCFG: \"Serial/USB/DDD\",\"0030\",\"0030\",\"0030003400300039\",\"0031004500320044\",\"0030003000350042\",\"00430069006E0074006500720069006F006E00200057006900720065006C0065007300730020004D006F00640075006C00650073\",\"005\"\r\n"
+ "^SCFG: \"Tcp/IRT\",\"0033\"\r\n"
+ "^SCFG: \"Tcp/MR\",\"00310030\"\r\n"
+ "^SCFG: \"Tcp/OT\",\"0036003000300030\"\r\n"
+ "^SCFG: \"Tcp/WithURCs\",\"006F006E\"\r\n"
+ "^SCFG: \"Trace/Syslog/OTAP\",\"0030\"\r\n"
+ "^SCFG: \"Urc/Ringline\",\"006C006F00630061006C\"\r\n"
+ "^SCFG: \"Urc/Ringline/ActiveTime\",\"0032\"\r\n"
+ "^SCFG: \"Userware/Autostart\",\"0030\"\r\n"
+ "^SCFG: \"Userware/Autostart/Delay\",\"0030\"\r\n"
+ "^SCFG: \"Userware/DebugInterface\",\"0030002E0030002E0030002E0030\",\"0030002E0030002E0030002E0030\",\"0030\"\r\n"
+ "^SCFG: \"Userware/DebugMode\",\"006F00660066\"\r\n"
+ "^SCFG: \"Userware/Passwd\",\r\n"
+ "^SCFG: \"Userware/Stdout\",\"006E0075006C006C\",,,,\"006F00660066\"\r\n"
+ "^SCFG: \"Userware/Watchdog\",\"0030\"\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",\"00630075007200720065006E0074\"\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",\"0070006F00770065007200750070\"\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_2, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_9, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single);
+
+ common_test_scfg_response (response, MM_MODEM_CHARSET_UCS2, expected_bands, MM_CINTERION_MODEM_FAMILY_IMT, MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE);
+
+ g_array_unref (expected_bands);
+}
+
+static void
+test_scfg_response_alas5 (void)
+{
+ GArray *expected_bands;
+ MMModemBand single;
+ const gchar *response =
+ "^SCFG: \"Audio/Loop\",\"0\"\r\n"
+ "^SCFG: \"Audio/SvTone\",\"0\"\r\n"
+ "^SCFG: \"Call/Ecall/AckTimeout\",\"5000\"\r\n"
+ "^SCFG: \"Call/Ecall/BlockSMSPP\",\"0\"\r\n"
+ "^SCFG: \"Call/Ecall/Callback\",\"0\"\r\n"
+ "^SCFG: \"Call/Ecall/CallbackTimeout\",\"43200000\"\r\n"
+ "^SCFG: \"Call/Ecall/Force\",\"1\"\r\n"
+ "^SCFG: \"Call/Ecall/Msd\",\"\"\r\n"
+ "^SCFG: \"Call/Ecall/Pullmode\",\"0\"\r\n"
+ "^SCFG: \"Call/Ecall/SessionTimeout\",\"20000\"\r\n"
+ "^SCFG: \"Call/Ecall/StartTimeout\",\"5000\"\r\n"
+ "^SCFG: \"Call/ECC\",\"0\"\r\n"
+ "^SCFG: \"Call/Speech/Codec\",\"0\"\r\n"
+ "^SCFG: \"GPRS/Auth\",\"2\"\r\n"
+ "^SCFG: \"GPRS/AutoAttach\",\"enabled\"\r\n"
+ "^SCFG: \"GPRS/MTU/Mode\",\"0\"\r\n"
+ "^SCFG: \"GPRS/MTU/Size\",1500\r\n"
+ "^SCFG: \"MEopMode/CFUN\",\"1\",\"1\"\r\n"
+ "^SCFG: \"MEopMode/CregRoam\",\"0\"\r\n"
+ "^SCFG: \"MEopMode/Dormancy\",\"0\",\"0\"\r\n"
+ "^SCFG: \"MEopMode/DTM/Mode\",\"2\"\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",\"current\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"mbim\",\"asc0\"\r\n"
+ "^SCFG: \"MEopMode/ExpectDTR\",\"powerup\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"mbim\",\"asc0\"\r\n"
+ "^SCFG: \"MEopMode/FGI/Split\",\"1\"\r\n"
+ "^SCFG: \"MEopMode/IMS\",\"1\"\r\n"
+ "^SCFG: \"MEopMode/NonBlock/Cops\",\"0\"\r\n"
+ "^SCFG: \"MEopMode/PowerMgmt/LCI\",\"disabled\"\r\n"
+ "^SCFG: \"MEopMode/Prov/AutoFallback\",\"off\"\r\n"
+ "^SCFG: \"MEopMode/Prov/AutoSelect\",\"on\"\r\n"
+ "^SCFG: \"MEopMode/Prov/Cfg\",\"vdfde\"\r\n"
+ "^SCFG: \"MEopMode/PwrSave\",\"enabled\",\"52\",\"50\",\"CPU-A\",\"powerup\"\r\n"
+ "^SCFG: \"MEopMode/PwrSave\",\"enabled\",\"52\",\"50\",\"CPU-A\",\"current\"\r\n"
+ "^SCFG: \"MEopMode/PwrSave\",\"enabled\",\"0\",\"0\",\"CPU-M\",\"powerup\"\r\n"
+ "^SCFG: \"MEopMode/PwrSave\",\"enabled\",\"0\",\"0\",\"CPU-M\",\"current\"\r\n"
+ "^SCFG: \"MEopMode/SRPOM\",\"0\"\r\n"
+ "^SCFG: \"MEopMode/USB/KeepData\",\"current\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\"\r\n"
+ "^SCFG: \"MEopMode/USB/KeepData\",\"powerup\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\"\r\n"
+ "^SCFG: \"MEShutdown/OnIgnition\",\"off\"\r\n"
+ "^SCFG: \"MEShutdown/Timer\",\"off\"\r\n"
+ "^SCFG: \"Misc/CId\",\"\"\r\n"
+ "^SCFG: \"Radio/Band/2G\",\"0000000f\"\r\n"
+ "^SCFG: \"Radio/Band/3G\",\"000400b5\"\r\n"
+ "^SCFG: \"Radio/Band/4G\",\"8a0e00d5\",\"000000e2\"\r\n"
+ "^SCFG: \"Radio/CNS\",\"0\"\r\n"
+ "^SCFG: \"Radio/Mtpl\",\"0\"\r\n"
+ "^SCFG: \"Radio/Mtpl/2G\",\"0\"\r\n"
+ "^SCFG: \"Radio/Mtpl/3G\",\"0\"\r\n"
+ "^SCFG: \"Radio/Mtpl/4G\",\"0\"\r\n"
+ "^SCFG: \"Radio/OutputPowerReduction\",\"4\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Event/ASC\",\"none\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Event/URC\",\"none\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Event/USB\",\"GPIO4\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",\"current\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Ports\",\"powerup\",\"acm0\",\"acm1\",\"acm2\",\"acm3\",\"diag\",\"mbim\",\"asc0\"\r\n"
+ "^SCFG: \"RemoteWakeUp/Pulse\",\"10\"\r\n"
+ "^SCFG: \"Serial/USB/DDD\",\"0\",\"0\",\"0409\",\"1e2d\",\"0065\",\"Cinterion\",\"LTE Modem\",\"8d8f\"\r\n"
+ "^SCFG: \"SIM/CS\",\"SIM1\"\r\n"
+ "^SCFG: \"SMS/4GPREF\",\"IMS\"\r\n"
+ "^SCFG: \"SMS/AutoAck\",\"0\"\r\n"
+ "^SCFG: \"SMS/RETRM\",\"30\"\r\n"
+ "^SCFG: \"URC/Ringline\",\"local\"\r\n"
+ "^SCFG: \"URC/Ringline/ActiveTime\",\"2\"\r\n";
+
+ expected_bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 25);
+ single = MM_MODEM_BAND_EGSM, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_DCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_PCS, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_G850, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_3, g_array_append_val (expected_bands, single); //
+ single = MM_MODEM_BAND_UTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_6, g_array_append_val (expected_bands, single); //
+ single = MM_MODEM_BAND_UTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_UTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_1, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_3, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_5, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_7, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_8, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_18, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_19, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_20, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_26, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_28, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_38, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_39, g_array_append_val (expected_bands, single);
+ single = MM_MODEM_BAND_EUTRAN_40, g_array_append_val (expected_bands, single);
+
+ common_test_scfg_response (response, MM_MODEM_CHARSET_GSM, expected_bands, MM_CINTERION_MODEM_FAMILY_DEFAULT, MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE);
+
+ g_array_unref (expected_bands);
+}
+
+/*****************************************************************************/
+/* Test ^SCFG test */
+
+static void
+compare_arrays (const GArray *supported,
+ const GArray *expected)
+{
+ guint i;
+
+ g_assert_cmpuint (supported->len, ==, expected->len);
+ for (i = 0; i < supported->len; i++) {
+ gboolean found = FALSE;
+ guint j;
+
+ for (j = 0; j < expected->len && !found; j++) {
+ if (g_array_index (supported, guint, i) == g_array_index (expected, guint, j))
+ found = TRUE;
+ }
+ g_assert (found);
+ }
+}
+
+static void
+common_test_cnmi (const gchar *response,
+ const GArray *expected_mode,
+ const GArray *expected_mt,
+ const GArray *expected_bm,
+ const GArray *expected_ds,
+ const GArray *expected_bfr)
+{
+ GArray *supported_mode = NULL;
+ GArray *supported_mt = NULL;
+ GArray *supported_bm = NULL;
+ GArray *supported_ds = NULL;
+ GArray *supported_bfr = NULL;
+ GError *error = NULL;
+ gboolean res;
+
+ g_assert (expected_mode != NULL);
+ g_assert (expected_mt != NULL);
+ g_assert (expected_bm != NULL);
+ g_assert (expected_ds != NULL);
+ g_assert (expected_bfr != NULL);
+
+ res = mm_cinterion_parse_cnmi_test (response,
+ &supported_mode,
+ &supported_mt,
+ &supported_bm,
+ &supported_ds,
+ &supported_bfr,
+ &error);
+ g_assert_no_error (error);
+ g_assert (res == TRUE);
+ g_assert (supported_mode != NULL);
+ g_assert (supported_mt != NULL);
+ g_assert (supported_bm != NULL);
+ g_assert (supported_ds != NULL);
+ g_assert (supported_bfr != NULL);
+
+ compare_arrays (supported_mode, expected_mode);
+ compare_arrays (supported_mt, expected_mt);
+ compare_arrays (supported_bm, expected_bm);
+ compare_arrays (supported_ds, expected_ds);
+ compare_arrays (supported_bfr, expected_bfr);
+
+ g_array_unref (supported_mode);
+ g_array_unref (supported_mt);
+ g_array_unref (supported_bm);
+ g_array_unref (supported_ds);
+ g_array_unref (supported_bfr);
+}
+
+static void
+test_cnmi_phs8 (void)
+{
+ GArray *expected_mode;
+ GArray *expected_mt;
+ GArray *expected_bm;
+ GArray *expected_ds;
+ GArray *expected_bfr;
+ guint val;
+ const gchar *response =
+ "+CNMI: (0,1,2),(0,1),(0,2),(0),(1)\r\n"
+ "\r\n";
+
+ expected_mode = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3);
+ val = 0, g_array_append_val (expected_mode, val);
+ val = 1, g_array_append_val (expected_mode, val);
+ val = 2, g_array_append_val (expected_mode, val);
+
+ expected_mt = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2);
+ val = 0, g_array_append_val (expected_mt, val);
+ val = 1, g_array_append_val (expected_mt, val);
+
+ expected_bm = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2);
+ val = 0, g_array_append_val (expected_bm, val);
+ val = 2, g_array_append_val (expected_bm, val);
+
+ expected_ds = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+ val = 0, g_array_append_val (expected_ds, val);
+
+ expected_bfr = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+ val = 1, g_array_append_val (expected_bfr, val);
+
+ common_test_cnmi (response,
+ expected_mode,
+ expected_mt,
+ expected_bm,
+ expected_ds,
+ expected_bfr);
+
+ g_array_unref (expected_mode);
+ g_array_unref (expected_mt);
+ g_array_unref (expected_bm);
+ g_array_unref (expected_ds);
+ g_array_unref (expected_bfr);
+}
+
+static void
+test_cnmi_other (void)
+{
+ GArray *expected_mode;
+ GArray *expected_mt;
+ GArray *expected_bm;
+ GArray *expected_ds;
+ GArray *expected_bfr;
+ guint val;
+ const gchar *response =
+ "+CNMI: (0-3),(0,1),(0,2,3),(0,2),(1)\r\n"
+ "\r\n";
+
+ expected_mode = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3);
+ val = 0, g_array_append_val (expected_mode, val);
+ val = 1, g_array_append_val (expected_mode, val);
+ val = 2, g_array_append_val (expected_mode, val);
+ val = 3, g_array_append_val (expected_mode, val);
+
+ expected_mt = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2);
+ val = 0, g_array_append_val (expected_mt, val);
+ val = 1, g_array_append_val (expected_mt, val);
+
+ expected_bm = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2);
+ val = 0, g_array_append_val (expected_bm, val);
+ val = 2, g_array_append_val (expected_bm, val);
+ val = 3, g_array_append_val (expected_bm, val);
+
+ expected_ds = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+ val = 0, g_array_append_val (expected_ds, val);
+ val = 2, g_array_append_val (expected_ds, val);
+
+ expected_bfr = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+ val = 1, g_array_append_val (expected_bfr, val);
+
+ common_test_cnmi (response,
+ expected_mode,
+ expected_mt,
+ expected_bm,
+ expected_ds,
+ expected_bfr);
+
+ g_array_unref (expected_mode);
+ g_array_unref (expected_mt);
+ g_array_unref (expected_bm);
+ g_array_unref (expected_ds);
+ g_array_unref (expected_bfr);
+}
+
+/*****************************************************************************/
+/* Test ^SWWAN read */
+
+#define SWWAN_TEST_MAX_CIDS 2
+
+typedef struct {
+ guint cid;
+ MMBearerConnectionStatus state;
+} PdpContextState;
+
+typedef struct {
+ const gchar *response;
+ PdpContextState expected_items[SWWAN_TEST_MAX_CIDS];
+ gboolean skip_test_other_cids;
+} SwwanTest;
+
+/* Note: all tests are based on checking CIDs 2 and 3 */
+static const SwwanTest swwan_tests[] = {
+ /* No active PDP context reported (all disconnected) */
+ {
+ .response = "",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED }
+ },
+ /* Don't test other CIDs because for those we would also return
+ * DISCONNECTED, not UNKNOWN. */
+ .skip_test_other_cids = TRUE
+ },
+ /* Single PDP context active (short version without interface index) */
+ {
+ .response = "^SWWAN: 3,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_UNKNOWN },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED }
+ }
+ },
+ /* Single PDP context active (long version with interface index) */
+ {
+ .response = "^SWWAN: 3,1,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_UNKNOWN },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED }
+ }
+ },
+ /* Single PDP context inactive (short version without interface index) */
+ {
+ .response = "^SWWAN: 3,0\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_UNKNOWN },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED }
+ }
+ },
+ /* Single PDP context inactive (long version with interface index) */
+ {
+ .response = "^SWWAN: 3,0,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_UNKNOWN },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED }
+ }
+ },
+ /* Multiple PDP contexts active (short version without interface index) */
+ {
+ .response = "^SWWAN: 2,1\r\n^SWWAN: 3,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED }
+ }
+ },
+ /* Multiple PDP contexts active (long version with interface index) */
+ {
+ .response = "^SWWAN: 2,1,3\r\n^SWWAN: 3,1,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED }
+ }
+ },
+ /* Multiple PDP contexts inactive (short version without interface index) */
+ {
+ .response = "^SWWAN: 2,0\r\n^SWWAN: 3,0\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED }
+ }
+ },
+ /* Multiple PDP contexts inactive (long version with interface index) */
+ {
+ .response = "^SWWAN: 2,0,3\r\n^SWWAN: 3,0,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED }
+ }
+ },
+ /* Multiple PDP contexts active/inactive (short version without interface index) */
+ {
+ .response = "^SWWAN: 2,0\r\n^SWWAN: 3,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED }
+ }
+ },
+ /* Multiple PDP contexts active/inactive (long version with interface index) */
+ {
+ .response = "^SWWAN: 2,0,3\r\n^SWWAN: 3,1,1\r\n",
+ .expected_items = {
+ { .cid = 2, .state = MM_BEARER_CONNECTION_STATUS_DISCONNECTED },
+ { .cid = 3, .state = MM_BEARER_CONNECTION_STATUS_CONNECTED }
+ }
+ }
+};
+
+static void
+test_swwan_pls8 (void)
+{
+ MMBearerConnectionStatus read_state;
+ GError *error = NULL;
+ guint i;
+
+ /* Base tests for successful responses */
+ for (i = 0; i < G_N_ELEMENTS (swwan_tests); i++) {
+ guint j;
+
+ /* Query for the expected items (CIDs 2 and 3) */
+ for (j = 0; j < SWWAN_TEST_MAX_CIDS; j++) {
+ read_state = mm_cinterion_parse_swwan_response (swwan_tests[i].response, swwan_tests[i].expected_items[j].cid, NULL, &error);
+ if (swwan_tests[i].expected_items[j].state == MM_BEARER_CONNECTION_STATUS_UNKNOWN) {
+ g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED);
+ g_clear_error (&error);
+ } else
+ g_assert_no_error (error);
+ g_assert_cmpint (read_state, ==, swwan_tests[i].expected_items[j].state);
+ }
+
+ /* Query for a CID which isn't replied (e.g. 12) */
+ if (!swwan_tests[i].skip_test_other_cids) {
+ read_state = mm_cinterion_parse_swwan_response (swwan_tests[i].response, 12, NULL, &error);
+ g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED);
+ g_assert_cmpint (read_state, ==, MM_BEARER_CONNECTION_STATUS_UNKNOWN);
+ g_clear_error (&error);
+ }
+ }
+
+ /* Additional tests for errors */
+ read_state = mm_cinterion_parse_swwan_response ("^GARBAGE", 2, NULL, &error);
+ g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED);
+ g_assert_cmpint (read_state, ==, MM_BEARER_CONNECTION_STATUS_UNKNOWN);
+ g_clear_error (&error);
+}
+
+/*****************************************************************************/
+/* Test ^SIND responses */
+
+static void
+common_test_sind_response (const gchar *response,
+ const gchar *expected_description,
+ guint expected_mode,
+ guint expected_value)
+{
+ GError *error = NULL;
+ gboolean res;
+ gchar *description;
+ guint mode;
+ guint value;
+
+ res = mm_cinterion_parse_sind_response (response,
+ &description,
+ &mode,
+ &value,
+ &error);
+ g_assert_no_error (error);
+ g_assert (res == TRUE);
+
+ g_assert_cmpstr (description, ==, expected_description);
+ g_assert_cmpuint (mode, ==, expected_mode);
+ g_assert_cmpuint (value, ==, expected_value);
+
+ g_free (description);
+}
+
+static void
+test_sind_response_simstatus (void)
+{
+ common_test_sind_response ("^SIND: simstatus,1,5", "simstatus", 1, 5);
+}
+
+/*****************************************************************************/
+/* Test ^SMONG responses */
+
+static void
+common_test_smong_response (const gchar *response,
+ gboolean success,
+ MMModemAccessTechnology expected_access_tech)
+{
+ GError *error = NULL;
+ gboolean res;
+ MMModemAccessTechnology access_tech;
+
+ res = mm_cinterion_parse_smong_response (response, &access_tech, &error);
+
+ if (success) {
+ g_assert_no_error (error);
+ g_assert (res);
+ g_assert_cmpuint (access_tech, ==, expected_access_tech);
+ } else {
+ g_assert (error);
+ g_assert (!res);
+ }
+}
+
+static void
+test_smong_response_tc63i (void)
+{
+ const gchar *response =
+ "\r\n"
+ "GPRS Monitor\r\n"
+ "BCCH G PBCCH PAT MCC MNC NOM TA RAC # Cell #\r\n"
+ "0073 1 - - 262 02 2 00 01\r\n";
+ common_test_smong_response (response, TRUE, MM_MODEM_ACCESS_TECHNOLOGY_GPRS);
+}
+
+static void
+test_smong_response_other (void)
+{
+ const gchar *response =
+ "\r\n"
+ "GPRS Monitor\r\n"
+ "\r\n"
+ "BCCH G PBCCH PAT MCC MNC NOM TA RAC # Cell #\r\n"
+ " 44 1 - - 234 10 - - - \r\n";
+ common_test_smong_response (response, TRUE, MM_MODEM_ACCESS_TECHNOLOGY_GPRS);
+}
+
+static void
+test_smong_response_no_match (void)
+{
+ const gchar *response =
+ "\r\n"
+ "GPRS Monitor\r\n"
+ "\r\n"
+ "BCCH K PBCCH PAT MCC MNC NOM TA RAC # Cell #\r\n"
+ " 44 1 - - 234 10 - - - \r\n";
+ common_test_smong_response (response, FALSE, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN);
+}
+
+/*****************************************************************************/
+/* Test ^SLCC URCs */
+
+static void
+common_test_slcc_urc (const gchar *urc,
+ const MMCallInfo *expected_call_info_list,
+ guint expected_call_info_list_size)
+{
+ g_autoptr(GRegex) slcc_regex = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autofree gchar *str = NULL;
+ GError *error = NULL;
+ GList *call_info_list = NULL;
+ GList *l;
+ gboolean result;
+
+ slcc_regex = mm_cinterion_get_slcc_regex ();
+
+ /* Same matching logic as done in MMSerialPortAt when processing URCs! */
+ result = g_regex_match_full (slcc_regex, urc, -1, 0, 0, &match_info, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ /* read full matched content */
+ str = g_match_info_fetch (match_info, 0);
+ g_assert (str);
+
+ result = mm_cinterion_parse_slcc_list (str, NULL, &call_info_list, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ g_debug ("found %u calls", g_list_length (call_info_list));
+
+ if (expected_call_info_list) {
+ g_assert (call_info_list);
+ g_assert_cmpuint (g_list_length (call_info_list), ==, expected_call_info_list_size);
+ } else
+ g_assert (!call_info_list);
+
+ for (l = call_info_list; l; l = g_list_next (l)) {
+ const MMCallInfo *call_info = (const MMCallInfo *)(l->data);
+ gboolean found = FALSE;
+ guint i;
+
+ g_debug ("call at index %u: direction %s, state %s, number %s",
+ call_info->index,
+ mm_call_direction_get_string (call_info->direction),
+ mm_call_state_get_string (call_info->state),
+ call_info->number ? call_info->number : "n/a");
+
+ for (i = 0; !found && i < expected_call_info_list_size; i++)
+ found = ((call_info->index == expected_call_info_list[i].index) &&
+ (call_info->direction == expected_call_info_list[i].direction) &&
+ (call_info->state == expected_call_info_list[i].state) &&
+ (g_strcmp0 (call_info->number, expected_call_info_list[i].number) == 0));
+
+ g_assert (found);
+ }
+
+ mm_cinterion_call_info_list_free (call_info_list);
+}
+
+static void
+test_slcc_urc_empty (void)
+{
+ const gchar *urc = "\r\n^SLCC: \r\n";
+
+ common_test_slcc_urc (urc, NULL, 0);
+}
+
+static void
+test_slcc_urc_single (void)
+{
+ static const MMCallInfo expected_call_info_list[] = {
+ { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "123456789" }
+ };
+
+ const gchar *urc =
+ "\r\n^SLCC: 1,1,0,0,0,0,\"123456789\",161"
+ "\r\n^SLCC: \r\n";
+
+ common_test_slcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
+}
+
+static void
+test_slcc_urc_multiple (void)
+{
+ static const MMCallInfo expected_call_info_list[] = {
+ { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, NULL },
+ { 2, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "123456789" },
+ { 3, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "987654321" },
+ };
+
+ const gchar *urc =
+ "\r\n^SLCC: 1,1,0,0,1,0" /* number unknown */
+ "\r\n^SLCC: 2,1,0,0,1,0,\"123456789\",161"
+ "\r\n^SLCC: 3,1,0,0,1,0,\"987654321\",161,\"Alice\""
+ "\r\n^SLCC: \r\n";
+
+ common_test_slcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
+}
+
+static void
+test_slcc_urc_complex (void)
+{
+ static const MMCallInfo expected_call_info_list[] = {
+ { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "123456789" },
+ { 2, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_WAITING, (gchar *) "987654321" },
+ };
+
+ const gchar *urc =
+ "\r\n^CIEV: 1,0" /* some different URC before our match */
+ "\r\n^SLCC: 1,1,0,0,0,0,\"123456789\",161"
+ "\r\n^SLCC: 2,1,5,0,0,0,\"987654321\",161"
+ "\r\n^SLCC: \r\n"
+ "\r\n^CIEV: 1,0" /* some different URC after our match */;
+
+ common_test_slcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
+}
+
+/*****************************************************************************/
+/* Test +CTZU URCs */
+
+static void
+common_test_ctzu_urc (const gchar *urc,
+ const gchar *expected_iso8601,
+ gint expected_offset,
+ gint expected_dst_offset)
+{
+ g_autoptr(GRegex) ctzu_regex = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autofree gchar *iso8601 = NULL;
+ GError *error = NULL;
+ gboolean result;
+ MMNetworkTimezone *tz = NULL;
+
+ ctzu_regex = mm_cinterion_get_ctzu_regex ();
+
+ /* Same matching logic as done in MMSerialPortAt when processing URCs! */
+ result = g_regex_match_full (ctzu_regex, urc, -1, 0, 0, &match_info, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ result = mm_cinterion_parse_ctzu_urc (match_info, &iso8601, &tz, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ g_assert (iso8601);
+ g_assert_cmpstr (expected_iso8601, ==, iso8601);
+
+ g_assert (tz);
+ g_assert_cmpint (expected_offset, ==, mm_network_timezone_get_offset (tz));
+
+ if (expected_dst_offset >= 0)
+ g_assert_cmpuint ((guint)expected_dst_offset, ==, mm_network_timezone_get_dst_offset (tz));
+
+ g_object_unref (tz);
+}
+
+static void
+test_ctzu_urc_simple (void)
+{
+ const gchar *urc = "\r\n+CTZU: \"19/07/09,11:15:40\",+08\r\n";
+ const gchar *expected_iso8601 = "2019-07-09T11:15:40+02";
+ gint expected_offset = 120;
+ gint expected_dst_offset = -1; /* not given */
+
+ common_test_ctzu_urc (urc, expected_iso8601, expected_offset, expected_dst_offset);
+}
+
+static void
+test_ctzu_urc_full (void)
+{
+ const gchar *urc = "\r\n+CTZU: \"19/07/09,11:15:40\",+08,1\r\n";
+ const gchar *expected_iso8601 = "2019-07-09T11:15:40+02";
+ gint expected_offset = 120;
+ gint expected_dst_offset = 60;
+
+ common_test_ctzu_urc (urc, expected_iso8601, expected_offset, expected_dst_offset);
+}
+
+/*****************************************************************************/
+/* Test ^SMONI responses */
+
+typedef struct {
+ const gchar *str;
+ MMCinterionRadioGen tech;
+ gdouble rssi;
+ gdouble ecn0;
+ gdouble rscp;
+ gdouble rsrp;
+ gdouble rsrq;
+} SMoniResponseTest;
+
+static const SMoniResponseTest smoni_response_tests[] = {
+ {
+ .str = "^SMONI: 2G,71,-61,262,02,0143,83BA,33,33,3,6,G,NOCONN",
+ .tech = MM_CINTERION_RADIO_GEN_2G,
+ .rssi = -61.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 2G,SEARCH,SEARCH",
+ .tech = MM_CINTERION_RADIO_GEN_NONE,
+ .rssi = 0.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 2G,673,-89,262,07,4EED,A500,16,16,7,4,G,5,-107,LIMSRV",
+ .tech = MM_CINTERION_RADIO_GEN_2G,
+ .rssi = -89.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 2G,673,-80,262,07,4EED,A500,35,35,7,4,G,643,4,0,-80,0,S_FR",
+ .tech = MM_CINTERION_RADIO_GEN_2G,
+ .rssi = -80.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 3G,10564,296,-7.5,-79,262,02,0143,00228FF,-92,-78,NOCONN",
+ .tech = MM_CINTERION_RADIO_GEN_3G,
+ .rssi = 0.0,
+ .ecn0 = -7.5,
+ .rscp = -79.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 3G,SEARCH,SEARCH",
+ .tech = MM_CINTERION_RADIO_GEN_NONE,
+ .rssi = 0.0,
+ .ecn0 = 0,
+ .rscp = 0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 3G,10564,96,-6.5,-77,262,02,0143,00228FF,-92,-78,LIMSRV",
+ .tech = MM_CINTERION_RADIO_GEN_3G,
+ .rssi = 0.0,
+ .ecn0 = -6.5,
+ .rscp = -77.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 3G,10737,131,-5,-93,260,01,7D3D,C80BC9A,--,--,----,---,-,-5,-93,0,01,06",
+ .tech = MM_CINTERION_RADIO_GEN_3G,
+ .rssi = 0.0,
+ .ecn0 = -5.0,
+ .rscp = -93.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,33,-94,-7,NOCONN",
+ .tech = MM_CINTERION_RADIO_GEN_4G,
+ .rssi = 0.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = -94.0,
+ .rsrq = -7.0
+ },
+ {
+ .str = "^SMONI: 4G,SEARCH",
+ .tech = MM_CINTERION_RADIO_GEN_NONE,
+ .rssi = 0.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = 0.0,
+ .rsrq = 0.0
+ },
+ {
+ .str = "^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,33,-90,-6,LIMSRV",
+ .tech = MM_CINTERION_RADIO_GEN_4G,
+ .rssi = 0.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = -90.0,
+ .rsrq = -6.0
+ },
+ {
+ .str = "^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,90,-101,-7,CONN",
+ .tech = MM_CINTERION_RADIO_GEN_4G,
+ .rssi = 0.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = -101.0,
+ .rsrq = -7.0
+ },
+ {
+ .str = "^SMONI: 4G,2850,7,20,20,FDD,262,02,C096,027430F,275,11,-114,-9,NOCONN",
+ .tech = MM_CINTERION_RADIO_GEN_4G,
+ .rssi = 0.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = -114.0,
+ .rsrq = -9.0
+ },
+ {
+ .str = "^SMONI: 4G,2850,7,20,20,FDD,262,02,C096,027430F,275,-,-113,-8,CONN",
+ .tech = MM_CINTERION_RADIO_GEN_4G,
+ .rssi = 0.0,
+ .ecn0 = 0.0,
+ .rscp = 0.0,
+ .rsrp = -113.0,
+ .rsrq = -8.0
+ }
+};
+
+static void
+test_smoni_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (smoni_response_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ MMCinterionRadioGen tech = MM_CINTERION_RADIO_GEN_NONE;
+ gdouble rssi = MM_SIGNAL_UNKNOWN;
+ gdouble ecn0 = MM_SIGNAL_UNKNOWN;
+ gdouble rscp = MM_SIGNAL_UNKNOWN;
+ gdouble rsrp = MM_SIGNAL_UNKNOWN;
+ gdouble rsrq = MM_SIGNAL_UNKNOWN;
+
+ success = mm_cinterion_parse_smoni_query_response (smoni_response_tests[i].str,
+ &tech, &rssi,
+ &ecn0, &rscp,
+ &rsrp, &rsrq,
+ &error);
+ g_assert_no_error (error);
+ g_assert (success);
+
+ g_assert_cmpuint (smoni_response_tests[i].tech, ==, tech);
+ switch (smoni_response_tests[i].tech) {
+ case MM_CINTERION_RADIO_GEN_2G:
+ g_assert_cmpfloat_tolerance (rssi, smoni_response_tests[i].rssi, 0.1);
+ break;
+ case MM_CINTERION_RADIO_GEN_3G:
+ g_assert_cmpfloat_tolerance (ecn0, smoni_response_tests[i].ecn0, 0.1);
+ g_assert_cmpfloat_tolerance (rscp, smoni_response_tests[i].rscp, 0.1);
+ break;
+ case MM_CINTERION_RADIO_GEN_4G:
+ g_assert_cmpfloat_tolerance (rsrp, smoni_response_tests[i].rsrp, 0.1);
+ g_assert_cmpfloat_tolerance (rsrq, smoni_response_tests[i].rsrq, 0.1);
+ break;
+ case MM_CINTERION_RADIO_GEN_NONE:
+ default:
+ break;
+ }
+ }
+}
+
+static void
+test_smoni_response_to_signal (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (smoni_response_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ MMSignal *gsm = NULL;
+ MMSignal *umts = NULL;
+ MMSignal *lte = NULL;
+
+ success = mm_cinterion_smoni_response_to_signal_info (smoni_response_tests[i].str,
+ &gsm, &umts, &lte,
+ &error);
+ g_assert_no_error (error);
+ g_assert (success);
+
+ switch (smoni_response_tests[i].tech) {
+ case MM_CINTERION_RADIO_GEN_2G:
+ g_assert (gsm);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rssi (gsm), smoni_response_tests[i].rssi, 0.1);
+ g_object_unref (gsm);
+ g_assert (!umts);
+ g_assert (!lte);
+ break;
+ case MM_CINTERION_RADIO_GEN_3G:
+ g_assert (umts);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rscp (umts), smoni_response_tests[i].rscp, 0.1);
+ g_assert_cmpfloat_tolerance (mm_signal_get_ecio (umts), smoni_response_tests[i].ecn0, 0.1);
+ g_object_unref (umts);
+ g_assert (!gsm);
+ g_assert (!lte);
+ break;
+ case MM_CINTERION_RADIO_GEN_4G:
+ g_assert (lte);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rsrp (lte), smoni_response_tests[i].rsrp, 0.1);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rsrq (lte), smoni_response_tests[i].rsrq, 0.1);
+ g_object_unref (lte);
+ g_assert (!gsm);
+ g_assert (!umts);
+ break;
+ case MM_CINTERION_RADIO_GEN_NONE:
+ default:
+ g_assert (!gsm);
+ g_assert (!umts);
+ g_assert (!lte);
+ break;
+ }
+ }
+}
+
+/*****************************************************************************/
+/* Test ^SCFG="MEopMode/Prov/Cfg" responses */
+
+typedef struct {
+ const gchar *str;
+ MMCinterionModemFamily modem_family;
+ gboolean success;
+ guint expected_cid;
+} ProvcfgResponseTest;
+
+static const ProvcfgResponseTest provcfg_response_tests[] = {
+ {
+
+ .str = "^SCFG: \"MEopMode/Prov/Cfg\",\"vdfde\"",
+ .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT,
+ .success = TRUE,
+ .expected_cid = 1,
+ },
+ {
+
+ .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"attus\"",
+ .modem_family = MM_CINTERION_MODEM_FAMILY_IMT,
+ .success = TRUE,
+ .expected_cid = 1,
+ },
+ {
+
+ .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"2\"",
+ .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT,
+ .success = TRUE,
+ .expected_cid = 3,
+ },
+ {
+
+ .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"vzwdcus\"",
+ .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT,
+ .success = TRUE,
+ .expected_cid = 3,
+ },
+ {
+
+ .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"tmode\"",
+ .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT,
+ .success = TRUE,
+ .expected_cid = 2,
+ },
+ {
+ .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"fallback*\"",
+ .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT,
+ .success = TRUE,
+ .expected_cid = 1,
+ },
+ {
+ /* commas not allowed by the regex */
+ .str = "* ^SCFG: \"MEopMode/Prov/Cfg\",\"something,with,commas\"",
+ .modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT,
+ .success = FALSE,
+ }
+};
+
+static void
+test_provcfg_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (provcfg_response_tests); i++) {
+ gint cid = -1;
+ gboolean result;
+ GError *error = NULL;
+
+ result = mm_cinterion_provcfg_response_to_cid (provcfg_response_tests[i].str,
+ provcfg_response_tests[i].modem_family,
+ MM_MODEM_CHARSET_GSM,
+ NULL,
+ &cid,
+ &error);
+ if (provcfg_response_tests[i].success) {
+ g_assert_no_error (error);
+ g_assert (result);
+ g_assert_cmpuint (cid, ==, provcfg_response_tests[i].expected_cid);
+ } else {
+ g_assert (error);
+ g_assert (!result);
+ }
+ }
+}
+
+/*****************************************************************************/
+/* Test ^SGAUTH responses */
+
+static void
+test_sgauth_response (void)
+{
+ gboolean result;
+ MMBearerAllowedAuth auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN;
+ gchar *username = NULL;
+ GError *error = NULL;
+
+ const gchar *response =
+ "^SGAUTH: 1,2,\"vf\"\r\n"
+ "^SGAUTH: 2,1,\"\"\r\n"
+ "^SGAUTH: 3,0\r\n";
+
+ /* CID 1 */
+ result = mm_cinterion_parse_sgauth_response (response, 1, &auth, &username, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+ g_assert_cmpuint (auth, ==, MM_BEARER_ALLOWED_AUTH_CHAP);
+ g_assert_cmpstr (username, ==, "vf");
+
+ auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN;
+ g_clear_pointer (&username, g_free);
+
+ /* CID 2 */
+ result = mm_cinterion_parse_sgauth_response (response, 2, &auth, &username, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+ g_assert_cmpuint (auth, ==, MM_BEARER_ALLOWED_AUTH_PAP);
+ g_assert_null (username);
+
+ auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN;
+
+ /* CID 3 */
+ result = mm_cinterion_parse_sgauth_response (response, 3, &auth, &username, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+ g_assert_cmpuint (auth, ==, MM_BEARER_ALLOWED_AUTH_NONE);
+ g_assert_null (username);
+
+ auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN;
+
+ /* CID 4 */
+ result = mm_cinterion_parse_sgauth_response (response, 4, &auth, &username, &error);
+ g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND);
+ g_assert (!result);
+}
+
+/*****************************************************************************/
+/* Test ^SXRAT responses */
+
+static void
+common_test_sxrat (const gchar *response,
+ const GArray *expected_rat,
+ const GArray *expected_pref1,
+ const GArray *expected_pref2)
+{
+ GArray *supported_rat = NULL;
+ GArray *supported_pref1 = NULL;
+ GArray *supported_pref2 = NULL;
+ GError *error = NULL;
+ gboolean res;
+
+ g_assert (expected_rat != NULL);
+ g_assert (expected_pref1 != NULL);
+
+ res = mm_cinterion_parse_sxrat_test (response,
+ &supported_rat,
+ &supported_pref1,
+ &supported_pref2,
+ &error);
+ g_assert_no_error (error);
+ g_assert (res == TRUE);
+ g_assert (supported_rat != NULL);
+ g_assert (supported_pref1 != NULL);
+ if (expected_pref2)
+ g_assert (supported_pref2 != NULL);
+ else
+ g_assert (supported_pref2 == NULL);
+
+ compare_arrays (supported_rat, expected_rat);
+ compare_arrays (supported_pref1, expected_pref1);
+ if (expected_pref2)
+ compare_arrays (supported_pref2, expected_pref2);
+
+ g_array_unref (supported_rat);
+ g_array_unref (supported_pref1);
+ if (supported_pref2)
+ g_array_unref (supported_pref2);
+}
+
+static void
+test_sxrat_response_els61 (void)
+{
+ GArray *expected_rat;
+ GArray *expected_pref1;
+ GArray *expected_pref2;
+ guint val;
+ const gchar *response =
+ "^SXRAT: (0-6),(0,2,3),(0,2,3)\r\n"
+ "\r\n";
+
+ expected_rat = g_array_sized_new (FALSE, FALSE, sizeof (guint), 7);
+ val = 0, g_array_append_val (expected_rat, val);
+ val = 1, g_array_append_val (expected_rat, val);
+ val = 2, g_array_append_val (expected_rat, val);
+ val = 3, g_array_append_val (expected_rat, val);
+ val = 4, g_array_append_val (expected_rat, val);
+ val = 5, g_array_append_val (expected_rat, val);
+ val = 6, g_array_append_val (expected_rat, val);
+
+ expected_pref1 = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3);
+ val = 0, g_array_append_val (expected_pref1, val);
+ val = 2, g_array_append_val (expected_pref1, val);
+ val = 3, g_array_append_val (expected_pref1, val);
+
+ expected_pref2 = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3);
+ val = 0, g_array_append_val (expected_pref2, val);
+ val = 2, g_array_append_val (expected_pref2, val);
+ val = 3, g_array_append_val (expected_pref2, val);
+
+ common_test_sxrat (response,
+ expected_rat,
+ expected_pref1,
+ expected_pref2);
+
+ g_array_unref (expected_rat);
+ g_array_unref (expected_pref1);
+ g_array_unref (expected_pref2);
+}
+
+static void
+test_sxrat_response_other (void)
+{
+ GArray *expected_rat;
+ GArray *expected_pref1;
+ GArray *expected_pref2 = NULL;
+ guint val;
+ const gchar *response =
+ "^SXRAT: (0-2),(0,2)\r\n"
+ "\r\n";
+
+ expected_rat = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3);
+ val = 0, g_array_append_val (expected_rat, val);
+ val = 1, g_array_append_val (expected_rat, val);
+ val = 2, g_array_append_val (expected_rat, val);
+
+ expected_pref1 = g_array_sized_new (FALSE, FALSE, sizeof (guint), 3);
+ val = 0, g_array_append_val (expected_pref1, val);
+ val = 2, g_array_append_val (expected_pref1, val);
+
+ common_test_sxrat (response,
+ expected_rat,
+ expected_pref1,
+ expected_pref2);
+
+ g_array_unref (expected_rat);
+ g_array_unref (expected_pref1);
+}
+
+typedef struct {
+ const gchar *str;
+ MMModemMode allowed;
+ MMModemMode preferred;
+ gboolean success;
+} SxratBuildTest;
+
+static const SxratBuildTest sxrat_build_tests[] = {
+ {
+ .str = "^SXRAT=0",
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .success = TRUE,
+ },
+ {
+ .str = "^SXRAT=3",
+ .allowed = MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .success = TRUE,
+ },
+ {
+ .str = "^SXRAT=1,2",
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_3G,
+ .success = TRUE,
+ },
+ {
+ .str = "^SXRAT=6,3",
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_4G,
+ .success = TRUE,
+ },
+ {
+ .str = NULL,
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .success = FALSE,
+ },
+ {
+ .str = NULL,
+ .allowed = MM_MODEM_MODE_5G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .success = FALSE,
+ },
+
+};
+
+static void
+test_sxrat (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (sxrat_build_tests); i++) {
+ GError *error = NULL;
+ gchar* result;
+
+ result = mm_cinterion_build_sxrat_set_command (sxrat_build_tests[i].allowed,
+ sxrat_build_tests[i].preferred,
+ &error);
+ if (sxrat_build_tests[i].success) {
+ g_assert_no_error (error);
+ g_assert (result);
+ g_assert_cmpstr (result, ==, sxrat_build_tests[i].str);
+ } else {
+ g_assert (error);
+ g_assert (!result);
+ }
+ }
+}
+/*****************************************************************************/
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/cinterion/scfg", test_scfg);
+ g_test_add_func ("/MM/cinterion/scfg/ehs5", test_scfg_ehs5);
+ g_test_add_func ("/MM/cinterion/scfg/pls62/gsm", test_scfg_pls62_gsm);
+ g_test_add_func ("/MM/cinterion/scfg/pls62/ucs2", test_scfg_pls62_ucs2);
+ g_test_add_func ("/MM/cinterion/scfg/alas5", test_scfg_alas5);
+ g_test_add_func ("/MM/cinterion/scfg/response/3g", test_scfg_response_3g);
+ g_test_add_func ("/MM/cinterion/scfg/response/2g", test_scfg_response_2g);
+ g_test_add_func ("/MM/cinterion/scfg/response/pls62/gsm", test_scfg_response_pls62_gsm);
+ g_test_add_func ("/MM/cinterion/scfg/response/pls62/ucs2",test_scfg_response_pls62_ucs2);
+ g_test_add_func ("/MM/cinterion/scfg/response/alas5", test_scfg_response_alas5);
+ g_test_add_func ("/MM/cinterion/cnmi/phs8", test_cnmi_phs8);
+ g_test_add_func ("/MM/cinterion/cnmi/other", test_cnmi_other);
+ g_test_add_func ("/MM/cinterion/swwan/pls8", test_swwan_pls8);
+ g_test_add_func ("/MM/cinterion/sind/response/simstatus", test_sind_response_simstatus);
+ g_test_add_func ("/MM/cinterion/smong/response/tc63i", test_smong_response_tc63i);
+ g_test_add_func ("/MM/cinterion/smong/response/other", test_smong_response_other);
+ g_test_add_func ("/MM/cinterion/smong/response/no-match", test_smong_response_no_match);
+ g_test_add_func ("/MM/cinterion/slcc/urc/empty", test_slcc_urc_empty);
+ g_test_add_func ("/MM/cinterion/slcc/urc/single", test_slcc_urc_single);
+ g_test_add_func ("/MM/cinterion/slcc/urc/multiple", test_slcc_urc_multiple);
+ g_test_add_func ("/MM/cinterion/slcc/urc/complex", test_slcc_urc_complex);
+ g_test_add_func ("/MM/cinterion/ctzu/urc/simple", test_ctzu_urc_simple);
+ g_test_add_func ("/MM/cinterion/ctzu/urc/full", test_ctzu_urc_full);
+ g_test_add_func ("/MM/cinterion/smoni/query_response", test_smoni_response);
+ g_test_add_func ("/MM/cinterion/smoni/query_response_to_signal", test_smoni_response_to_signal);
+ g_test_add_func ("/MM/cinterion/scfg/provcfg", test_provcfg_response);
+ g_test_add_func ("/MM/cinterion/sgauth", test_sgauth_response);
+ g_test_add_func ("/MM/cinterion/sxrat", test_sxrat);
+ g_test_add_func ("/MM/cinterion/sxrat/response/els61", test_sxrat_response_els61);
+ g_test_add_func ("/MM/cinterion/sxrat/response/other", test_sxrat_response_other);
+
+ return g_test_run ();
+}
diff --git a/src/plugins/dell/77-mm-dell-port-types.rules b/src/plugins/dell/77-mm-dell-port-types.rules
new file mode 100644
index 00000000..fa01c116
--- /dev/null
+++ b/src/plugins/dell/77-mm-dell-port-types.rules
@@ -0,0 +1,32 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_dell_port_types_end"
+
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="413c", GOTO="mm_dell_vendorcheck"
+GOTO="mm_dell_port_types_end"
+
+LABEL="mm_dell_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Dell DW5821e (default 0x81d7, with esim support 0x81e0)
+# if 02: primary port
+# if 03: secondary port
+# if 04: raw NMEA port
+# if 05: diag/qcdm port
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d7", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d7", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d7", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d7", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81e0", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81e0", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81e0", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81e0", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+
+# Dell DW5820e
+# if 02: AT port
+# if 04: debug port (ignore)
+# if 06: AT port
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d9", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
+
+GOTO="mm_dell_port_types_end"
+LABEL="mm_dell_port_types_end"
diff --git a/src/plugins/dell/mm-plugin-dell.c b/src/plugins/dell/mm-plugin-dell.c
new file mode 100644
index 00000000..63d8b4da
--- /dev/null
+++ b/src/plugins/dell/mm-plugin-dell.c
@@ -0,0 +1,528 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2015-2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-dell.h"
+#include "mm-common-novatel.h"
+#include "mm-private-boxed-types.h"
+#include "mm-broadband-modem.h"
+#include "mm-broadband-modem-novatel.h"
+#include "mm-common-novatel.h"
+#include "mm-broadband-modem-sierra.h"
+#include "mm-common-sierra.h"
+#include "mm-broadband-modem-telit.h"
+#include "mm-broadband-modem-xmm.h"
+#include "mm-common-telit.h"
+#include "mm-log-object.h"
+
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi.h"
+#endif
+
+#if defined WITH_MBIM
+#include "mm-broadband-modem-mbim.h"
+#include "mm-broadband-modem-mbim-xmm.h"
+#include "mm-broadband-modem-mbim-foxconn.h"
+#endif
+
+#define MAX_PORT_PROBE_TIMEOUTS 3
+
+G_DEFINE_TYPE (MMPluginDell, mm_plugin_dell, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+#define TAG_DELL_MANUFACTURER "dell-manufacturer"
+typedef enum {
+ DELL_MANUFACTURER_UNKNOWN = 0,
+ DELL_MANUFACTURER_NOVATEL = 1,
+ DELL_MANUFACTURER_SIERRA = 2,
+ DELL_MANUFACTURER_ERICSSON = 3,
+ DELL_MANUFACTURER_TELIT = 4
+} DellManufacturer;
+
+/*****************************************************************************/
+/* Custom init */
+
+typedef struct {
+ MMPortSerialAt *port;
+ guint gmi_retries;
+ guint cgmi_retries;
+ guint ati_retries;
+ guint timeouts;
+} CustomInitContext;
+
+static void
+custom_init_context_free (CustomInitContext *ctx)
+{
+ g_object_unref (ctx->port);
+ g_slice_free (CustomInitContext, ctx);
+}
+
+static gboolean
+dell_custom_init_finish (MMPortProbe *probe,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+novatel_custom_init_ready (MMPortProbe *probe,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_common_novatel_custom_init_finish (probe, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+sierra_custom_init_ready (MMPortProbe *probe,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_common_sierra_custom_init_finish (probe, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+telit_custom_init_ready (MMPortProbe *probe,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!telit_custom_init_finish (probe, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void custom_init_step (GTask *task);
+
+static void
+custom_init_step_next_command (GTask *task)
+{
+ CustomInitContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ ctx->timeouts = 0;
+ if (ctx->gmi_retries > 0)
+ ctx->gmi_retries = 0;
+ else if (ctx->cgmi_retries > 0)
+ ctx->cgmi_retries = 0;
+ else if (ctx->ati_retries > 0)
+ ctx->ati_retries = 0;
+ custom_init_step (task);
+}
+
+static void
+response_ready (MMPortSerialAt *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ CustomInitContext *ctx;
+ MMPortProbe *probe;
+ const gchar *response;
+ GError *error = NULL;
+ gchar *lower;
+ DellManufacturer manufacturer;
+
+ probe = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ response = mm_port_serial_at_command_finish (port, res, &error);
+ if (error) {
+ /* Non-timeout error, jump to next command */
+ if (!g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) {
+ mm_obj_dbg (probe, "error probing AT port: %s", error->message);
+ g_error_free (error);
+ custom_init_step_next_command (task);
+ return;
+ }
+ /* Directly retry same command on timeout */
+ ctx->timeouts++;
+ custom_init_step (task);
+ g_error_free (error);
+ return;
+ }
+
+ /* Guess manufacturer from response */
+ lower = g_ascii_strdown (response, -1);
+ if (strstr (lower, "novatel"))
+ manufacturer = DELL_MANUFACTURER_NOVATEL;
+ else if (strstr (lower, "sierra"))
+ manufacturer = DELL_MANUFACTURER_SIERRA;
+ else if (strstr (lower, "ericsson"))
+ manufacturer = DELL_MANUFACTURER_ERICSSON;
+ else if (strstr (lower, "telit"))
+ manufacturer = DELL_MANUFACTURER_TELIT;
+ else
+ manufacturer = DELL_MANUFACTURER_UNKNOWN;
+ g_free (lower);
+
+ /* Tag based on manufacturer */
+ if (manufacturer != DELL_MANUFACTURER_UNKNOWN) {
+ g_object_set_data (G_OBJECT (probe), TAG_DELL_MANUFACTURER, GUINT_TO_POINTER (manufacturer));
+
+ /* Run additional custom init, if needed */
+
+ if (manufacturer == DELL_MANUFACTURER_NOVATEL) {
+ mm_common_novatel_custom_init (probe,
+ ctx->port,
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback) novatel_custom_init_ready,
+ task);
+ return;
+ }
+
+ if (manufacturer == DELL_MANUFACTURER_SIERRA) {
+ mm_common_sierra_custom_init (probe,
+ ctx->port,
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback) sierra_custom_init_ready,
+ task);
+ return;
+ }
+
+ if (manufacturer == DELL_MANUFACTURER_TELIT) {
+ telit_custom_init (probe,
+ ctx->port,
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback) telit_custom_init_ready,
+ task);
+ return;
+ }
+
+ /* Finish custom_init */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* If we got a response, but we didn't get an expected string, try with next command */
+ custom_init_step_next_command (task);
+}
+
+static void
+custom_init_step (GTask *task)
+{
+ CustomInitContext *ctx;
+ MMPortProbe *probe;
+
+ probe = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* If cancelled, end without error right away */
+ if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) {
+ mm_obj_dbg (probe, "no need to keep on running custom init: cancelled");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+#if defined WITH_QMI
+ /* If device has a QMI port, don't run anything else, as we don't care */
+ if (mm_port_probe_list_has_qmi_port (mm_device_peek_port_probe_list (mm_port_probe_peek_device (probe)))) {
+ mm_obj_dbg (probe, "no need to run custom init: device has QMI port");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+#endif
+
+#if defined WITH_MBIM
+ /* If device has a MBIM port, don't run anything else, as we don't care */
+ if (mm_port_probe_list_has_mbim_port (mm_device_peek_port_probe_list (mm_port_probe_peek_device (probe)))) {
+ mm_obj_dbg (probe, "no need to run custom init: device has MBIM port");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+#endif
+
+ if (ctx->timeouts >= MAX_PORT_PROBE_TIMEOUTS) {
+ mm_obj_dbg (probe, "couldn't detect real manufacturer: too many timeouts");
+ mm_port_probe_set_result_at (probe, FALSE);
+ goto out;
+ }
+
+ if (ctx->gmi_retries > 0) {
+ ctx->gmi_retries--;
+ mm_port_serial_at_command (ctx->port,
+ "AT+GMI",
+ 3,
+ FALSE, /* raw */
+ FALSE, /* allow_cached */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)response_ready,
+ task);
+ return;
+ }
+
+ if (ctx->cgmi_retries > 0) {
+ ctx->cgmi_retries--;
+ mm_port_serial_at_command (ctx->port,
+ "AT+CGMI",
+ 3,
+ FALSE, /* raw */
+ FALSE, /* allow_cached */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)response_ready,
+ task);
+ return;
+ }
+
+ if (ctx->ati_retries > 0) {
+ ctx->ati_retries--;
+ /* Note: in Ericsson devices, ATI3 seems to reply the vendor string */
+ mm_port_serial_at_command (ctx->port,
+ "ATI1I2I3",
+ 3,
+ FALSE, /* raw */
+ FALSE, /* allow_cached */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)response_ready,
+ task);
+ return;
+ }
+
+ mm_obj_dbg (probe, "couldn't detect real manufacturer: all retries consumed");
+out:
+ /* Finish custom_init */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+dell_custom_init (MMPortProbe *probe,
+ MMPortSerialAt *port,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ CustomInitContext *ctx;
+
+ ctx = g_slice_new0 (CustomInitContext);
+ ctx->port = g_object_ref (port);
+ ctx->gmi_retries = 3;
+ ctx->cgmi_retries = 1;
+ ctx->ati_retries = 1;
+
+ task = g_task_new (probe, cancellable, callback, user_data);
+ g_task_set_check_cancellable (task, FALSE);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) custom_init_context_free);
+
+ custom_init_step (task);
+}
+
+/*****************************************************************************/
+
+static gboolean
+port_probe_list_has_manufacturer_port (GList *probes,
+ DellManufacturer manufacturer)
+{
+ GList *l;
+
+ for (l = probes; l; l = g_list_next (l)) {
+ if (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (l->data), TAG_DELL_MANUFACTURER)) == manufacturer)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static MMBaseModem *
+create_modem (MMPlugin *self,
+ const gchar *uid,
+ const gchar **drivers,
+ guint16 vendor,
+ guint16 product,
+ guint16 subsystem_vendor,
+ GList *probes,
+ GError **error)
+{
+ /* Note: at this point we don't make any difference between different
+ * Dell-branded QMI or MBIM modems; they may come from Novatel, Ericsson or
+ * Sierra. */
+
+#if defined WITH_QMI
+ if (mm_port_probe_list_has_qmi_port (probes)) {
+ mm_obj_dbg (self, "QMI-powered Dell-branded modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+#if defined WITH_MBIM
+ if (mm_port_probe_list_has_mbim_port (probes)) {
+ /* Specific implementation for the DW5821e and DW5829e */
+ if (vendor == 0x413c && (product == 0x81d7 || product == 0x81e0 || product == 0x81e4 || product == 0x81e6)) {
+ mm_obj_dbg (self, "MBIM-powered DW5821e/DW5829e (T77W968) modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_foxconn_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+
+ if (mm_port_probe_list_is_xmm (probes)) {
+ mm_obj_dbg (self, "MBIM-powered XMM-based modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_xmm_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+
+ mm_obj_dbg (self, "MBIM-powered Dell-branded modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ if (port_probe_list_has_manufacturer_port (probes, DELL_MANUFACTURER_NOVATEL)) {
+ mm_obj_dbg (self, "Novatel-powered Dell-branded modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_novatel_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+
+ if (port_probe_list_has_manufacturer_port (probes, DELL_MANUFACTURER_SIERRA)) {
+ mm_obj_dbg (self, "Sierra-powered Dell-branded modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_sierra_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+
+ if (port_probe_list_has_manufacturer_port (probes, DELL_MANUFACTURER_TELIT)) {
+ mm_obj_dbg (self, "Telit-powered Dell-branded modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_telit_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+
+ if (mm_port_probe_list_is_xmm (probes)) {
+ mm_obj_dbg (self, "XMM-based modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_xmm_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+
+ mm_obj_dbg (self, "Dell-branded generic modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+static gboolean
+grab_port (MMPlugin *self,
+ MMBaseModem *modem,
+ MMPortProbe *probe,
+ GError **error)
+{
+ if (MM_IS_BROADBAND_MODEM_SIERRA (modem))
+ return mm_common_sierra_grab_port (self, modem, probe, error);
+
+ if (MM_IS_BROADBAND_MODEM_TELIT (modem))
+ return telit_grab_port (self, modem, probe, error);
+
+ return mm_base_modem_grab_port (modem,
+ mm_port_probe_peek_port (probe),
+ mm_port_probe_get_port_type (probe),
+ MM_PORT_SERIAL_AT_FLAG_NONE,
+ error);
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL };
+ static const guint16 vendors[] = { 0x413c, 0 };
+ static const MMAsyncMethod custom_init = {
+ .async = G_CALLBACK (dell_custom_init),
+ .finish = G_CALLBACK (dell_custom_init_finish),
+ };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_DELL,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendors,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_CUSTOM_INIT, &custom_init,
+ MM_PLUGIN_ALLOWED_QCDM, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ MM_PLUGIN_ALLOWED_MBIM, TRUE,
+ MM_PLUGIN_XMM_PROBE, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_dell_init (MMPluginDell *self)
+{
+}
+
+static void
+mm_plugin_dell_class_init (MMPluginDellClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+ plugin_class->grab_port = grab_port;
+}
diff --git a/src/plugins/dell/mm-plugin-dell.h b/src/plugins/dell/mm-plugin-dell.h
new file mode 100644
index 00000000..cc1a539e
--- /dev/null
+++ b/src/plugins/dell/mm-plugin-dell.h
@@ -0,0 +1,46 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_PLUGIN_DELL_H
+#define MM_PLUGIN_DELL_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_DELL (mm_plugin_dell_get_type ())
+#define MM_PLUGIN_DELL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_DELL, MMPluginDell))
+#define MM_PLUGIN_DELL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_DELL, MMPluginDellClass))
+#define MM_IS_PLUGIN_DELL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_DELL))
+#define MM_IS_PLUGIN_DELL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_DELL))
+#define MM_PLUGIN_DELL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_DELL, MMPluginDellClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginDell;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginDellClass;
+
+GType mm_plugin_dell_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_DELL_H */
diff --git a/src/plugins/dlink/77-mm-dlink-port-types.rules b/src/plugins/dlink/77-mm-dlink-port-types.rules
new file mode 100644
index 00000000..0dc7afce
--- /dev/null
+++ b/src/plugins/dlink/77-mm-dlink-port-types.rules
@@ -0,0 +1,16 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_dlink_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="2001", GOTO="mm_dlink_port_types"
+GOTO="mm_dlink_port_types_end"
+
+LABEL="mm_dlink_port_types"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# D-Link DWM-222
+ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7e35", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7e35", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7e35", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7e35", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1"
+
+LABEL="mm_dlink_port_types_end"
diff --git a/src/plugins/dlink/mm-plugin-dlink.c b/src/plugins/dlink/mm-plugin-dlink.c
new file mode 100644
index 00000000..72476e2e
--- /dev/null
+++ b/src/plugins/dlink/mm-plugin-dlink.c
@@ -0,0 +1,94 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-plugin-dlink.h"
+#include "mm-broadband-modem.h"
+
+#if defined WITH_QMI
+# include "mm-broadband-modem-qmi.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginDlink, mm_plugin_dlink, 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 D-Link modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_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", NULL };
+ static const guint16 vendor_ids[] = { 0x2001, 0 };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_DLINK,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_dlink_init (MMPluginDlink *self)
+{
+}
+
+static void
+mm_plugin_dlink_class_init (MMPluginDlinkClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/dlink/mm-plugin-dlink.h b/src/plugins/dlink/mm-plugin-dlink.h
new file mode 100644
index 00000000..13453cb0
--- /dev/null
+++ b/src/plugins/dlink/mm-plugin-dlink.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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_PLUGIN_DLINK_H
+#define MM_PLUGIN_DLINK_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_DLINK (mm_plugin_dlink_get_type ())
+#define MM_PLUGIN_DLINK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_DLINK, MMPluginDlink))
+#define MM_PLUGIN_DLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_DLINK, MMPluginDlinkClass))
+#define MM_IS_PLUGIN_DLINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_DLINK))
+#define MM_IS_PLUGIN_DLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_DLINK))
+#define MM_PLUGIN_DLINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_DLINK, MMPluginDlinkClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginDlink;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginDlinkClass;
+
+GType mm_plugin_dlink_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_DLINK_H */
diff --git a/src/plugins/fibocom/77-mm-fibocom-port-types.rules b/src/plugins/fibocom/77-mm-fibocom-port-types.rules
new file mode 100644
index 00000000..1fb26628
--- /dev/null
+++ b/src/plugins/fibocom/77-mm-fibocom-port-types.rules
@@ -0,0 +1,109 @@
+# do not edit this file, it will be overwritten on update
+ACTION!="add|change|move|bind", GOTO="mm_fibocom_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="2cb7", GOTO="mm_fibocom_port_types"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1782", GOTO="mm_fibocom_port_types"
+GOTO="mm_fibocom_port_types_end"
+
+LABEL="mm_fibocom_port_types"
+
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Fibocom L850-GL attach APN with toggle modem power
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0007", ENV{ID_MM_FIBOCOM_INITIAL_EPS_OFF_ON}="1"
+
+# Fibocom L850-GL
+# ttyACM0 (if #2): AT port
+# ttyACM1 (if #4): debug port (ignore)
+# ttyACM2 (if #6): AT port
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0007", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
+
+# Fibocom NL668-AM
+# ttyACM0 (if #2): AT port
+# ttyACM1 (if #3): AT port
+# ttyACM2 (if #4): debug port (ignore)
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a0", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a0", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a0", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
+
+# Fibocom FM150
+# ttyUSB0 (if #0): QCDM port
+# ttyUSB1 (if #1): AT port
+# ttyUSB2 (if #2): AT port
+# ttyUSB2 (if #3): Ignore
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0104", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0104", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0104", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0104", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1"
+
+# Fibocom FM101-GL (MBIM)
+# ttyUSB0 (if #2): AT port
+# ttyUSB1 (if #3): AT port
+# ttyUSB2 (if #4): debug port (ignore)
+# ttyUSB3 (if #5): debug port (ignore)
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a2", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a2", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a2", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a2", ENV{.MM_USBIFNUM}=="05", ENV{ID_MM_PORT_IGNORE}="1"
+
+# Fibocom FM101-GL (ADB)
+# ttyUSB0 (if #2): debug port (ignore)
+# ttyUSB1 (if #3): AT port
+# ttyUSB2 (if #4): debug port (ignore)
+# ttyUSB3 (if #5): debug port (ignore)
+# ttyUSB4 (if #6): debug port (ignore)
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a4", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a4", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a4", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a4", ENV{.MM_USBIFNUM}=="05", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a4", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+
+
+# Fibocom MA510-GL (GTUSBMODE=31)
+# ttyUSB0 (if #0): debug port (ignore)
+# ttyUSB1 (if #1): AT port
+# ttyUSB2 (if #2): AT port
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0106", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0106", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0106", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0106", ENV{ID_MM_FIBOCOM_INITIAL_EPS_CID}="1"
+
+# Fibocom MA510-GL (GTUSBMODE=32)
+# ttyUSB1 (if #0): AT port
+# ttyUSB2 (if #1): AT port
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="010a", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="010a", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="010a", ENV{ID_MM_FIBOCOM_INITIAL_EPS_CID}="1"
+
+# Fibocom L610 (GTUSBMODE=31)
+# ttyUSB0 (if #0): AT port
+# ttyUSB1 (if #1): NV
+# ttyUSB2 (if #2): MOS
+# ttyUSB3 (if #3): Diagnostic
+# ttyUSB4 (if #4): Logging
+# ttyUSB5 (if #5): AT port
+# ttyUSB6 (if #6): AT port
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d10", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d10", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d10", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d10", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d10", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d10", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d10", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# Fibocom L610 (GTUSBMODE={32,33} - ECM/RNDIS)
+# ttyUSB0 (if #2): AT port
+# ttyUSB1 (if #3): NV
+# ttyUSB2 (if #4): MOS
+# ttyUSB3 (if #5): Diagnostic
+# ttyUSB4 (if #6): Logging
+# ttyUSB5 (if #7): AT port
+# ttyUSB6 (if #8): AT port
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="05", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="07", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="08", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+LABEL="mm_fibocom_port_types_end"
diff --git a/src/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.c b/src/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.c
new file mode 100644
index 00000000..bc4ee1ec
--- /dev/null
+++ b/src/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.c
@@ -0,0 +1,540 @@
+/* -*- 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) 2022 Disruptive Technologies Research AS
+ */
+
+#include <config.h>
+
+#include "mm-broadband-bearer-fibocom-ecm.h"
+#include "mm-broadband-modem-fibocom.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-log.h"
+
+G_DEFINE_TYPE (MMBroadbandBearerFibocomEcm, mm_broadband_bearer_fibocom_ecm, MM_TYPE_BROADBAND_BEARER)
+
+/*****************************************************************************/
+/* Common helper functions */
+
+static gboolean
+parse_gtrndis_read_response (const gchar *response,
+ guint *state,
+ guint *cid,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+
+ r = g_regex_new ("\\+GTRNDIS:\\s*(\\d+)(?:,(\\d+))?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match (r, response, 0, &match_info)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Invalid +GTRNDIS response: %s", response);
+ return FALSE;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 1, state)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Failed to match state in +GTRNDIS response: %s", response);
+ return FALSE;
+ }
+
+ if (*state) {
+ if (!mm_get_uint_from_match_info (match_info, 2, cid)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Failed to match cid in +GTRNDIS response: %s", response);
+ return FALSE;
+ }
+ } else {
+ *cid = 0;
+ }
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Connection status monitoring */
+
+static MMBearerConnectionStatus
+load_connection_status_finish (MMBaseBearer *bearer,
+ 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_BEARER_CONNECTION_STATUS_UNKNOWN;
+ }
+ return (MMBearerConnectionStatus) value;
+}
+
+static void
+gtrndis_query_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer;
+ GError *error = NULL;
+ const gchar *result;
+ guint state;
+ guint cid;
+
+ bearer = g_task_get_source_object (task);
+ result = mm_base_modem_at_command_finish (modem, res, &error);
+ if (!result)
+ g_task_return_error (task, error);
+ else if (!parse_gtrndis_read_response (result, &state, &cid, &error))
+ g_task_return_error (task, error);
+ else if (!state || (gint) cid != mm_base_bearer_get_profile_id (bearer))
+ g_task_return_int (task, MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
+ else
+ g_task_return_int (task, MM_BEARER_CONNECTION_STATUS_CONNECTED);
+
+ g_object_unref (task);
+}
+
+static void
+load_connection_status (MMBaseBearer *bearer,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ MMBaseModem *modem = NULL;
+
+ task = g_task_new (bearer, NULL, callback, user_data);
+
+ g_object_get (MM_BASE_BEARER (bearer),
+ MM_BASE_BEARER_MODEM, &modem,
+ NULL);
+
+ mm_base_modem_at_command (modem,
+ "+GTRNDIS?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) gtrndis_query_ready,
+ task);
+ g_object_unref (modem);
+}
+
+/*****************************************************************************/
+/* 3GPP Connect */
+
+typedef struct {
+ MMBroadbandModem *modem;
+ MMPortSerialAt *primary;
+ MMPortSerialAt *secondary;
+ MMBearerIpFamily ip_family;
+} ConnectContext;
+
+static void
+connect_context_free (ConnectContext *ctx)
+{
+ g_clear_object (&ctx->modem);
+ g_clear_object (&ctx->primary);
+ g_clear_object (&ctx->secondary);
+ g_slice_free (ConnectContext, ctx);
+}
+
+static MMBearerConnectResult *
+connect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_connect_3gpp_ready (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ MMBearerConnectResult *result;
+
+ result = MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_fibocom_ecm_parent_class)->connect_3gpp_finish (self, res, &error);
+ if (result)
+ g_task_return_pointer (task, result, (GDestroyNotify) mm_bearer_connect_result_unref);
+ else
+ g_task_return_error (task, error);
+ g_object_unref (task);
+}
+
+static void
+disconnect_3gpp_ready (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ gboolean result;
+ ConnectContext *ctx;
+
+ result = MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp_finish (self, res, &error);
+ if (!result) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_task_get_task_data (task);
+ MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_fibocom_ecm_parent_class)->connect_3gpp (
+ self,
+ ctx->modem,
+ ctx->primary,
+ ctx->secondary,
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback) parent_connect_3gpp_ready,
+ task);
+}
+
+static void
+gtrndis_check_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearer *self;
+ ConnectContext *ctx;
+ GError *error = NULL;
+ const gchar *response;
+ guint state;
+ guint cid;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ if (!parse_gtrndis_read_response (response, &state, &cid, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ if (state) {
+ /* RNDIS is already active, disconnect first. */
+ mm_obj_dbg (self, "RNDIS active, tearing down existing connection...");
+ MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp (
+ MM_BROADBAND_BEARER (self),
+ ctx->modem,
+ ctx->primary,
+ ctx->secondary,
+ NULL, /* data port */
+ cid,
+ (GAsyncReadyCallback) disconnect_3gpp_ready,
+ task);
+ return;
+ }
+
+ /* Execute the regular connection flow if RNDIS is inactive. */
+ mm_obj_dbg (self, "RNDIS inactive");
+ MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_fibocom_ecm_parent_class)->connect_3gpp (
+ MM_BROADBAND_BEARER (self),
+ ctx->modem,
+ ctx->primary,
+ ctx->secondary,
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback) parent_connect_3gpp_ready,
+ task);
+}
+
+static void
+connect_3gpp (MMBroadbandBearer *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ConnectContext *ctx;
+ GTask *task;
+
+ ctx = g_slice_new0 (ConnectContext);
+ ctx->modem = g_object_ref (modem);
+ ctx->primary = g_object_ref (primary);
+ ctx->secondary = secondary ? g_object_ref (secondary) : NULL;
+ ctx->ip_family = mm_bearer_properties_get_ip_type (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ mm_3gpp_normalize_ip_family (&ctx->ip_family);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) connect_context_free);
+
+ /* First, we must check whether RNDIS is already active */
+ mm_base_modem_at_command (MM_BASE_MODEM (modem),
+ "+GTRNDIS?",
+ 3,
+ FALSE, /* allow_cached */
+ (GAsyncReadyCallback) gtrndis_check_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Dial context and task */
+
+typedef struct {
+ MMBroadbandModem *modem;
+ MMPortSerialAt *primary;
+ guint cid;
+ MMPort *data;
+} DialContext;
+
+static void
+dial_task_free (DialContext *ctx)
+{
+ g_object_unref (ctx->modem);
+ g_object_unref (ctx->primary);
+ if (ctx->data)
+ g_object_unref (ctx->data);
+ g_slice_free (DialContext, ctx);
+}
+
+static GTask *
+dial_task_new (MMBroadbandBearerFibocomEcm *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ guint cid,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ DialContext *ctx;
+ GTask *task;
+
+ ctx = g_slice_new0 (DialContext);
+ ctx->modem = g_object_ref (modem);
+ ctx->primary = g_object_ref (primary);
+ ctx->cid = cid;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) dial_task_free);
+
+ ctx->data = mm_base_modem_get_best_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET);
+ if (!ctx->data) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_NOT_FOUND,
+ "No valid data port found to launch connection");
+ g_object_unref (task);
+ return NULL;
+ }
+
+ return task;
+}
+
+/*****************************************************************************/
+/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */
+
+static MMPort *
+dial_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+gtrndis_verify_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ DialContext *ctx;
+ GError *error = NULL;
+ const gchar *response;
+
+ ctx = g_task_get_task_data (task);
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+
+ if (!response)
+ g_task_return_error (task, error);
+ else {
+ response = mm_strip_tag (response, "+GTRNDIS:");
+ if (strtol (response, NULL, 10) != 1)
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Connection status verification failed");
+ else
+ g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref);
+ }
+
+ g_object_unref (task);
+}
+
+static void
+gtrndis_activate_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (modem, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (modem,
+ "+GTRNDIS?",
+ 6, /* timeout [s] */
+ FALSE, /* allow_cached */
+ (GAsyncReadyCallback) gtrndis_verify_ready,
+ task);
+}
+
+static void
+dial_3gpp (MMBroadbandBearer *self,
+ MMBaseModem *modem,
+ MMPortSerialAt *primary,
+ guint cid,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ g_autofree gchar *cmd = NULL;
+
+ task = dial_task_new (MM_BROADBAND_BEARER_FIBOCOM_ECM (self),
+ MM_BROADBAND_MODEM (modem),
+ primary,
+ cid,
+ cancellable,
+ callback,
+ user_data);
+ if (!task)
+ return;
+
+ cmd = g_strdup_printf ("+GTRNDIS=1,%u", cid);
+ mm_base_modem_at_command (modem,
+ cmd,
+ MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT,
+ FALSE, /* allow_cached */
+ (GAsyncReadyCallback) gtrndis_activate_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* 3GPP Disconnect sequence */
+
+static gboolean
+disconnect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+gtrndis_deactivate_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (modem, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+disconnect_3gpp (MMBroadbandBearer *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ g_autofree gchar *cmd = NULL;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ cmd = g_strdup_printf ("+GTRNDIS=0,%u", cid);
+ mm_base_modem_at_command (MM_BASE_MODEM (modem),
+ cmd,
+ MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT,
+ FALSE, /* allow_cached */
+ (GAsyncReadyCallback) gtrndis_deactivate_ready,
+ task);
+}
+
+/*****************************************************************************/
+
+MMBaseBearer *
+mm_broadband_bearer_fibocom_ecm_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *bearer;
+ GObject *source;
+
+ source = g_async_result_get_source_object (res);
+ bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!bearer)
+ return NULL;
+
+ /* Only export valid bearers */
+ mm_base_bearer_export (MM_BASE_BEARER (bearer));
+
+ return MM_BASE_BEARER (bearer);
+}
+
+void
+mm_broadband_bearer_fibocom_ecm_new (MMBroadbandModemFibocom *modem,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (
+ MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_BEARER_MODEM, modem,
+ MM_BASE_BEARER_CONFIG, config,
+ NULL);
+}
+
+static void
+mm_broadband_bearer_fibocom_ecm_init (MMBroadbandBearerFibocomEcm *self)
+{
+}
+
+static void
+mm_broadband_bearer_fibocom_ecm_class_init (MMBroadbandBearerFibocomEcmClass *klass)
+{
+ MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);
+ MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass);
+
+ base_bearer_class->load_connection_status = load_connection_status;
+ base_bearer_class->load_connection_status_finish = load_connection_status_finish;
+
+ broadband_bearer_class->connect_3gpp = connect_3gpp;
+ broadband_bearer_class->connect_3gpp_finish = connect_3gpp_finish;
+ broadband_bearer_class->dial_3gpp = dial_3gpp;
+ broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish;
+ broadband_bearer_class->disconnect_3gpp = disconnect_3gpp;
+ broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish;
+}
diff --git a/src/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.h b/src/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.h
new file mode 100644
index 00000000..ea367aeb
--- /dev/null
+++ b/src/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.h
@@ -0,0 +1,50 @@
+/* -*- 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) 2022 Disruptive Technologies Research AS
+ */
+
+#ifndef MM_BROADBAND_BEARER_FIBOCOM_ECM_H
+#define MM_BROADBAND_BEARER_FIBOCOM_ECM_H
+
+#include "mm-broadband-bearer.h"
+#include "mm-broadband-modem-fibocom.h"
+
+#define MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM (mm_broadband_bearer_fibocom_ecm_get_type ())
+#define MM_BROADBAND_BEARER_FIBOCOM_ECM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM, MMBroadbandBearerFibocomEcm))
+#define MM_BROADBAND_BEARER_FIBOCOM_ECM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM, MMBroadbandBearerFibocomEcmClass))
+#define MM_IS_BROADBAND_BEARER_FIBOCOM_ECM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM))
+#define MM_IS_BROADBAND_BEARER_FIBOCOM_ECM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM))
+#define MM_BROADBAND_BEARER_FIBOCOM_ECM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM, MMBroadbandBearerFibocomEcmClass))
+
+typedef struct _MMBroadbandBearerFibocomEcm MMBroadbandBearerFibocomEcm;
+typedef struct _MMBroadbandBearerFibocomEcmClass MMBroadbandBearerFibocomEcmClass;
+
+struct _MMBroadbandBearerFibocomEcm {
+ MMBroadbandBearer parent;
+};
+
+struct _MMBroadbandBearerFibocomEcmClass {
+ MMBroadbandBearerClass parent;
+};
+
+GType mm_broadband_bearer_fibocom_ecm_get_type (void);
+
+void mm_broadband_bearer_fibocom_ecm_new (MMBroadbandModemFibocom *modem,
+ MMBearerProperties *properties,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseBearer *mm_broadband_bearer_fibocom_ecm_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_BROADBAND_BEARER_FIBOCOM_ECM_H */
diff --git a/src/plugins/fibocom/mm-broadband-modem-fibocom.c b/src/plugins/fibocom/mm-broadband-modem-fibocom.c
new file mode 100644
index 00000000..9d659698
--- /dev/null
+++ b/src/plugins/fibocom/mm-broadband-modem-fibocom.c
@@ -0,0 +1,763 @@
+/* -*- 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) 2022 Disruptive Technologies Research AS
+ */
+
+#include <config.h>
+
+#include "mm-broadband-modem-fibocom.h"
+#include "mm-broadband-bearer-fibocom-ecm.h"
+#include "mm-broadband-modem.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-3gpp-profile-manager.h"
+#include "mm-log.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void iface_modem_3gpp_profile_manager_init (MMIfaceModem3gppProfileManager *iface);
+
+static MMIfaceModem3gppProfileManager *iface_modem_3gpp_profile_manager_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemFibocom, mm_broadband_modem_fibocom, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_PROFILE_MANAGER, iface_modem_3gpp_profile_manager_init))
+
+typedef enum {
+ FEATURE_SUPPORT_UNKNOWN,
+ FEATURE_NOT_SUPPORTED,
+ FEATURE_SUPPORTED,
+} FeatureSupport;
+
+struct _MMBroadbandModemFibocomPrivate {
+ FeatureSupport gtrndis_support;
+ GRegex *sim_ready_regex;
+ FeatureSupport initial_eps_bearer_support;
+ gint initial_eps_bearer_cid;
+};
+
+/*****************************************************************************/
+/* Create Bearer (Modem interface) */
+
+static MMBaseBearer *
+modem_create_bearer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+broadband_bearer_fibocom_ecm_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer = NULL;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_fibocom_ecm_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+ g_object_unref (task);
+}
+
+static void
+broadband_bearer_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer = NULL;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+ g_object_unref (task);
+}
+
+static void
+common_create_bearer (GTask *task)
+{
+ MMBroadbandModemFibocom *self;
+
+ self = g_task_get_source_object (task);
+
+ switch (self->priv->gtrndis_support) {
+ case FEATURE_SUPPORTED:
+ mm_obj_dbg (self, "+GTRNDIS supported, creating Fibocom ECM bearer");
+ mm_broadband_bearer_fibocom_ecm_new (self,
+ g_task_get_task_data (task),
+ NULL, /* cancellable */
+ (GAsyncReadyCallback) broadband_bearer_fibocom_ecm_new_ready,
+ task);
+ return;
+ case FEATURE_NOT_SUPPORTED:
+ mm_obj_dbg (self, "+GTRNDIS not supported, creating generic PPP bearer");
+ mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
+ g_task_get_task_data (task),
+ NULL, /* cancellable */
+ (GAsyncReadyCallback) broadband_bearer_new_ready,
+ task);
+ return;
+ case FEATURE_SUPPORT_UNKNOWN:
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+gtrndis_test_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self);
+
+ if (!mm_base_modem_at_command_finish (_self, res, NULL)) {
+ mm_obj_dbg (self, "+GTRNDIS unsupported");
+ self->priv->gtrndis_support = FEATURE_NOT_SUPPORTED;
+ } else {
+ mm_obj_dbg (self, "+GTRNDIS supported");
+ self->priv->gtrndis_support = FEATURE_SUPPORTED;
+ }
+
+ /* Go on and create the bearer */
+ common_create_bearer (task);
+}
+
+static void
+modem_create_bearer (MMIfaceModem *_self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, g_object_ref (properties), g_object_unref);
+
+ if (self->priv->gtrndis_support != FEATURE_SUPPORT_UNKNOWN) {
+ common_create_bearer (task);
+ return;
+ }
+
+ if (!mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET)) {
+ mm_obj_dbg (self, "skipping +GTRNDIS check as no data port is available");
+ self->priv->gtrndis_support = FEATURE_NOT_SUPPORTED;
+ common_create_bearer (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "checking +GTRNDIS support...");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+GTRNDIS=?",
+ 6, /* timeout [s] */
+ TRUE, /* allow_cached */
+ (GAsyncReadyCallback) gtrndis_test_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Reset / Power (Modem interface) */
+
+static gboolean
+modem_common_power_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=15",
+ 15,
+ FALSE,
+ callback,
+ user_data);
+}
+
+static void
+modem_power_down (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=4",
+ 15,
+ FALSE,
+ callback,
+ user_data);
+}
+
+static void
+modem_power_off (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CPWROFF",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load initial EPS bearer properties (as agreed with network) */
+
+static MMBearerProperties *
+modem_3gpp_load_initial_eps_bearer_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return MM_BEARER_PROPERTIES (g_task_propagate_pointer (G_TASK (res), error));
+}
+
+static void
+load_initial_eps_cgcontrdp_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+ g_autofree gchar *apn = NULL;
+ MMBearerProperties *properties;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response || !mm_3gpp_parse_cgcontrdp_response (response, NULL, NULL, &apn, NULL, NULL, NULL, NULL, NULL, &error))
+ g_task_return_error (task, error);
+ else {
+ properties = mm_bearer_properties_new ();
+ mm_bearer_properties_set_apn (properties, apn);
+ g_task_return_pointer (task, properties, g_object_unref);
+ }
+
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_load_initial_eps_bearer (MMIfaceModem3gpp *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self);
+ GTask *task;
+ g_autofree gchar *cmd = NULL;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (self->priv->initial_eps_bearer_support != FEATURE_SUPPORTED) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Initial EPS bearer context ID unknown");
+ g_object_unref (task);
+ return;
+ }
+
+ g_assert (self->priv->initial_eps_bearer_cid >= 0);
+ cmd = g_strdup_printf ("+CGCONTRDP=%d", self->priv->initial_eps_bearer_cid);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) load_initial_eps_cgcontrdp_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load initial EPS bearer settings (currently configured in modem) */
+
+static MMBearerProperties *
+modem_3gpp_load_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return MM_BEARER_PROPERTIES (g_task_propagate_pointer (G_TASK (res), error));
+}
+
+static void
+load_initial_eps_bearer_get_profile_ready (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ g_autoptr(MM3gppProfile) profile = NULL;
+ MMBearerProperties *properties;
+
+ profile = mm_iface_modem_3gpp_profile_manager_get_profile_finish (self, res, &error);
+ if (!profile) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ properties = mm_bearer_properties_new_from_profile (profile, &error);
+ if (!properties)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, properties, g_object_unref);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_load_initial_eps_bearer_settings (MMIfaceModem3gpp *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self);
+ MMPortSerialAt *port;
+ MMKernelDevice *device;
+ GTask *task;
+
+ /* Initial EPS bearer CID initialization run once only */
+ if (G_UNLIKELY (self->priv->initial_eps_bearer_support == FEATURE_SUPPORT_UNKNOWN)) {
+ /* There doesn't seem to be a programmatic way to find the initial EPS
+ * bearer's CID, so we'll use a udev variable. */
+ port = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ device = mm_port_peek_kernel_device (MM_PORT (port));
+ if (mm_kernel_device_has_global_property (device, "ID_MM_FIBOCOM_INITIAL_EPS_CID")) {
+ self->priv->initial_eps_bearer_support = FEATURE_SUPPORTED;
+ self->priv->initial_eps_bearer_cid = mm_kernel_device_get_global_property_as_int (
+ device, "ID_MM_FIBOCOM_INITIAL_EPS_CID");
+ }
+ else
+ self->priv->initial_eps_bearer_support = FEATURE_NOT_SUPPORTED;
+ }
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (self->priv->initial_eps_bearer_support != FEATURE_SUPPORTED) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Initial EPS bearer context ID unknown");
+ g_object_unref (task);
+ return;
+ }
+
+ g_assert (self->priv->initial_eps_bearer_cid >= 0);
+ mm_iface_modem_3gpp_profile_manager_get_profile (
+ MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (self),
+ self->priv->initial_eps_bearer_cid,
+ (GAsyncReadyCallback) load_initial_eps_bearer_get_profile_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Set initial EPS bearer settings */
+
+typedef enum {
+ SET_INITIAL_EPS_BEARER_SETTINGS_STEP_LOAD_POWER_STATE = 0,
+ SET_INITIAL_EPS_BEARER_SETTINGS_STEP_POWER_DOWN,
+ SET_INITIAL_EPS_BEARER_SETTINGS_STEP_MODIFY_PROFILE,
+ SET_INITIAL_EPS_BEARER_SETTINGS_STEP_POWER_UP,
+ SET_INITIAL_EPS_BEARER_SETTINGS_STEP_FINISH,
+} SetInitialEpsStep;
+
+typedef struct {
+ MM3gppProfile *profile;
+ SetInitialEpsStep step;
+ MMModemPowerState power_state;
+} SetInitialEpsContext;
+
+static void
+set_initial_eps_context_free (SetInitialEpsContext *ctx)
+{
+ g_object_unref (ctx->profile);
+ g_slice_free (SetInitialEpsContext, ctx);
+}
+
+static gboolean
+modem_3gpp_set_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void set_initial_eps_step (GTask *task);
+
+static void
+set_initial_eps_bearer_power_up_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self);
+ SetInitialEpsContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_up_finish (MM_IFACE_MODEM (self), res, &error)) {
+ g_prefix_error (&error, "Couldn't power up modem: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->step++;
+ set_initial_eps_step (task);
+}
+
+static void
+set_initial_eps_bearer_modify_profile_ready (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ SetInitialEpsContext *ctx;
+ g_autoptr(MM3gppProfile) stored = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ stored = mm_iface_modem_3gpp_profile_manager_set_profile_finish (self, res, &error);
+ if (!stored) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->step++;
+ set_initial_eps_step (task);
+}
+
+static void
+set_initial_eps_bearer_power_down_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetInitialEpsContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_down_finish (MM_IFACE_MODEM (self), res, &error)) {
+ g_prefix_error (&error, "Couldn't power down modem: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->step++;
+ set_initial_eps_step (task);
+}
+
+static void
+set_initial_eps_bearer_load_power_state_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetInitialEpsContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ ctx->power_state = MM_IFACE_MODEM_GET_INTERFACE (self)->load_power_state_finish (MM_IFACE_MODEM (self), res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->step++;
+ set_initial_eps_step (task);
+}
+
+static void
+set_initial_eps_step (GTask *task)
+{
+ MMBroadbandModemFibocom *self;
+ SetInitialEpsContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case SET_INITIAL_EPS_BEARER_SETTINGS_STEP_LOAD_POWER_STATE:
+ mm_obj_dbg (self, "querying current power state...");
+ g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->load_power_state);
+ g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->load_power_state_finish);
+ MM_IFACE_MODEM_GET_INTERFACE (self)->load_power_state (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback) set_initial_eps_bearer_load_power_state_ready,
+ task);
+ return;
+
+ case SET_INITIAL_EPS_BEARER_SETTINGS_STEP_POWER_DOWN:
+ if (ctx->power_state == MM_MODEM_POWER_STATE_ON) {
+ mm_obj_dbg (self, "powering down before changing initial EPS bearer settings...");
+ g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_down);
+ g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_down_finish);
+ MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_down (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback) set_initial_eps_bearer_power_down_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case SET_INITIAL_EPS_BEARER_SETTINGS_STEP_MODIFY_PROFILE:
+ mm_obj_dbg (self, "modifying initial EPS bearer settings profile...");
+ mm_iface_modem_3gpp_profile_manager_set_profile (MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (self),
+ ctx->profile,
+ "profile-id",
+ TRUE,
+ (GAsyncReadyCallback) set_initial_eps_bearer_modify_profile_ready,
+ task);
+ return;
+
+ case SET_INITIAL_EPS_BEARER_SETTINGS_STEP_POWER_UP:
+ if (ctx->power_state == MM_MODEM_POWER_STATE_ON) {
+ mm_obj_dbg (self, "powering up after changing initial EPS bearer settings...");
+ g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_up);
+ g_assert (MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_up_finish);
+ MM_IFACE_MODEM_GET_INTERFACE (self)->modem_power_up (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback) set_initial_eps_bearer_power_up_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case SET_INITIAL_EPS_BEARER_SETTINGS_STEP_FINISH:
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+modem_3gpp_set_initial_eps_bearer_settings (MMIfaceModem3gpp *_self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self);
+ GTask *task;
+ MM3gppProfile *profile;
+ MMBearerIpFamily ip_family;
+ SetInitialEpsContext *ctx;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (self->priv->initial_eps_bearer_support != FEATURE_SUPPORTED) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Initial EPS bearer context ID unknown");
+ g_object_unref (task);
+ return;
+ }
+
+ profile = mm_bearer_properties_peek_3gpp_profile (properties);
+ g_assert (self->priv->initial_eps_bearer_cid >= 0);
+ mm_3gpp_profile_set_profile_id (profile, self->priv->initial_eps_bearer_cid);
+ ip_family = mm_3gpp_profile_get_ip_type (profile);
+ if (ip_family == MM_BEARER_IP_FAMILY_NONE || ip_family == MM_BEARER_IP_FAMILY_ANY)
+ mm_3gpp_profile_set_ip_type (profile, MM_BEARER_IP_FAMILY_IPV4);
+
+ /* Setup context */
+ ctx = g_slice_new0 (SetInitialEpsContext);
+ ctx->profile = g_object_ref (profile);
+ ctx->step = SET_INITIAL_EPS_BEARER_SETTINGS_STEP_LOAD_POWER_STATE;
+ g_task_set_task_data (task, ctx, (GDestroyNotify) set_initial_eps_context_free);
+
+ set_initial_eps_step (task);
+}
+
+/*****************************************************************************/
+/* Deactivate profile (3GPP profile management interface) */
+
+static gboolean
+modem_3gpp_profile_manager_deactivate_profile_finish (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+profile_manager_parent_deactivate_profile_ready (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ if (iface_modem_3gpp_profile_manager_parent->deactivate_profile_finish(self, res, &error))
+ g_task_return_boolean (task, TRUE);
+ else
+ g_task_return_error (task, error);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_profile_manager_deactivate_profile (MMIfaceModem3gppProfileManager *_self,
+ MM3gppProfile *profile,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self);
+ GTask *task;
+ gint profile_id;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ profile_id = mm_3gpp_profile_get_profile_id (profile);
+
+ if (self->priv->initial_eps_bearer_support == FEATURE_SUPPORTED) {
+ g_assert (self->priv->initial_eps_bearer_cid >= 0);
+ if (self->priv->initial_eps_bearer_cid == profile_id) {
+ mm_obj_dbg (self, "skipping profile deactivation (initial EPS bearer)");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+ }
+
+ iface_modem_3gpp_profile_manager_parent->deactivate_profile (
+ _self,
+ profile,
+ (GAsyncReadyCallback) profile_manager_parent_deactivate_profile_ready,
+ task);
+}
+
+/*****************************************************************************/
+
+static void
+setup_ports (MMBroadbandModem *_self)
+{
+ MMBroadbandModemFibocom *self = (MM_BROADBAND_MODEM_FIBOCOM (_self));
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_fibocom_parent_class)->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));
+
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->sim_ready_regex,
+ NULL, NULL, NULL);
+ }
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemFibocom *
+mm_broadband_modem_fibocom_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_FIBOCOM,
+ 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,
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_fibocom_init (MMBroadbandModemFibocom *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_MODEM_FIBOCOM,
+ MMBroadbandModemFibocomPrivate);
+
+ self->priv->gtrndis_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->sim_ready_regex = g_regex_new ("\\r\\n\\+SIM READY\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->initial_eps_bearer_support = FEATURE_SUPPORT_UNKNOWN;
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (object);
+
+ g_regex_unref (self->priv->sim_ready_regex);
+
+ G_OBJECT_CLASS (mm_broadband_modem_fibocom_parent_class)->finalize (object);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface->create_bearer = modem_create_bearer;
+ iface->create_bearer_finish = modem_create_bearer_finish;
+ iface->reset = modem_reset;
+ iface->reset_finish = modem_common_power_finish;
+ iface->modem_power_down = modem_power_down;
+ iface->modem_power_down_finish = modem_common_power_finish;
+ iface->modem_power_off = modem_power_off;
+ iface->modem_power_off_finish = modem_common_power_finish;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface->load_initial_eps_bearer = modem_3gpp_load_initial_eps_bearer;
+ iface->load_initial_eps_bearer_finish = modem_3gpp_load_initial_eps_bearer_finish;
+ iface->load_initial_eps_bearer_settings = modem_3gpp_load_initial_eps_bearer_settings;
+ iface->load_initial_eps_bearer_settings_finish = modem_3gpp_load_initial_eps_bearer_settings_finish;
+ iface->set_initial_eps_bearer_settings = modem_3gpp_set_initial_eps_bearer_settings;
+ iface->set_initial_eps_bearer_settings_finish = modem_3gpp_set_initial_eps_bearer_settings_finish;
+}
+
+static void
+iface_modem_3gpp_profile_manager_init (MMIfaceModem3gppProfileManager *iface)
+{
+ iface_modem_3gpp_profile_manager_parent = g_type_interface_peek_parent (iface);
+
+ iface->deactivate_profile = modem_3gpp_profile_manager_deactivate_profile;
+ iface->deactivate_profile_finish = modem_3gpp_profile_manager_deactivate_profile_finish;
+}
+
+static void
+mm_broadband_modem_fibocom_class_init (MMBroadbandModemFibocomClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (G_OBJECT_CLASS (klass),
+ sizeof (MMBroadbandModemFibocomPrivate));
+
+ /* Virtual methods */
+ object_class->finalize = finalize;
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/fibocom/mm-broadband-modem-fibocom.h b/src/plugins/fibocom/mm-broadband-modem-fibocom.h
new file mode 100644
index 00000000..958841b7
--- /dev/null
+++ b/src/plugins/fibocom/mm-broadband-modem-fibocom.h
@@ -0,0 +1,49 @@
+/* -*- 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) 2022 Disruptive Technologies Research AS
+ */
+
+#ifndef MM_BROADBAND_MODEM_FIBOCOM_H
+#define MM_BROADBAND_MODEM_FIBOCOM_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_FIBOCOM (mm_broadband_modem_fibocom_get_type ())
+#define MM_BROADBAND_MODEM_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_FIBOCOM, MMBroadbandModemFibocom))
+#define MM_BROADBAND_MODEM_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_FIBOCOM, MMBroadbandModemFibocomClass))
+#define MM_IS_BROADBAND_MODEM_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_FIBOCOM))
+#define MM_IS_BROADBAND_MODEM_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_FIBOCOM))
+#define MM_BROADBAND_MODEM_FIBOCOM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_FIBOCOM, MMBroadbandModemFibocomClass))
+
+typedef struct _MMBroadbandModemFibocom MMBroadbandModemFibocom;
+typedef struct _MMBroadbandModemFibocomClass MMBroadbandModemFibocomClass;
+typedef struct _MMBroadbandModemFibocomPrivate MMBroadbandModemFibocomPrivate;
+
+struct _MMBroadbandModemFibocom {
+ MMBroadbandModem parent;
+ MMBroadbandModemFibocomPrivate *priv;
+};
+
+struct _MMBroadbandModemFibocomClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_fibocom_get_type (void);
+
+MMBroadbandModemFibocom *mm_broadband_modem_fibocom_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_FIBOCOM_H */
diff --git a/src/plugins/fibocom/mm-broadband-modem-mbim-fibocom.c b/src/plugins/fibocom/mm-broadband-modem-mbim-fibocom.c
new file mode 100644
index 00000000..b4655db7
--- /dev/null
+++ b/src/plugins/fibocom/mm-broadband-modem-mbim-fibocom.c
@@ -0,0 +1,95 @@
+/* -*- 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) 2022 Fibocom Wireless Inc.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log-object.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-broadband-modem-mbim-fibocom.h"
+#include "mm-shared-fibocom.h"
+
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void shared_fibocom_init (MMSharedFibocom *iface);
+
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimFibocom, mm_broadband_modem_mbim_fibocom, MM_TYPE_BROADBAND_MODEM_MBIM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_FIBOCOM, shared_fibocom_init))
+
+/******************************************************************************/
+
+MMBroadbandModemMbimFibocom *
+mm_broadband_modem_mbim_fibocom_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_FIBOCOM,
+ 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,
+ /* 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,
+ MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, TRUE,
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
+ MM_BROADBAND_MODEM_MBIM_QMI_UNSUPPORTED, TRUE,
+#endif
+ NULL);
+}
+
+static void
+mm_broadband_modem_mbim_fibocom_init (MMBroadbandModemMbimFibocom *self)
+{
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->set_initial_eps_bearer_settings = mm_shared_fibocom_set_initial_eps_bearer_settings;
+ iface->set_initial_eps_bearer_settings_finish = mm_shared_fibocom_set_initial_eps_bearer_settings_finish;
+}
+
+static MMIfaceModem3gpp *
+peek_parent_3gpp_interface (MMSharedFibocom *self)
+{
+ return iface_modem_3gpp_parent;
+}
+
+static void
+shared_fibocom_init (MMSharedFibocom *iface)
+{
+ iface->peek_parent_3gpp_interface = peek_parent_3gpp_interface;
+}
+
+static void
+mm_broadband_modem_mbim_fibocom_class_init (MMBroadbandModemMbimFibocomClass *klass)
+{
+}
diff --git a/src/plugins/fibocom/mm-broadband-modem-mbim-fibocom.h b/src/plugins/fibocom/mm-broadband-modem-mbim-fibocom.h
new file mode 100644
index 00000000..b5c5434f
--- /dev/null
+++ b/src/plugins/fibocom/mm-broadband-modem-mbim-fibocom.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) 2022 Fibocom Wireless Inc.
+ */
+
+#ifndef MM_BROADBAND_MODEM_MBIM_FIBOCOM_H
+#define MM_BROADBAND_MODEM_MBIM_FIBOCOM_H
+
+#include "mm-broadband-modem-mbim.h"
+
+#define MM_TYPE_BROADBAND_MODEM_MBIM_FIBOCOM (mm_broadband_modem_mbim_fibocom_get_type ())
+#define MM_BROADBAND_MODEM_MBIM_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_FIBOCOM, MMBroadbandModemMbimFibocom))
+#define MM_BROADBAND_MODEM_MBIM_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_FIBOCOM, MMBroadbandModemMbimFibocomClass))
+#define MM_IS_BROADBAND_MODEM_MBIM_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_FIBOCOM))
+#define MM_IS_BROADBAND_MODEM_MBIM_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_FIBOCOM))
+#define MM_BROADBAND_MODEM_MBIM_FIBOCOM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_FIBOCOM, MMBroadbandModemMbimFibocomClass))
+
+typedef struct _MMBroadbandModemMbimFibocom MMBroadbandModemMbimFibocom;
+typedef struct _MMBroadbandModemMbimFibocomClass MMBroadbandModemMbimFibocomClass;
+
+struct _MMBroadbandModemMbimFibocom {
+ MMBroadbandModemMbim parent;
+};
+
+struct _MMBroadbandModemMbimFibocomClass{
+ MMBroadbandModemMbimClass parent;
+};
+
+GType mm_broadband_modem_mbim_fibocom_get_type (void);
+
+MMBroadbandModemMbimFibocom *mm_broadband_modem_mbim_fibocom_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_MBIM_FIBOCOM_H */
diff --git a/src/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.c b/src/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.c
new file mode 100644
index 00000000..7ed39362
--- /dev/null
+++ b/src/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.c
@@ -0,0 +1,95 @@
+/* -*- 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) 2022 Fibocom Wireless Inc.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log-object.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-broadband-modem-mbim-xmm-fibocom.h"
+#include "mm-shared-fibocom.h"
+
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void shared_fibocom_init (MMSharedFibocom *iface);
+
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimXmmFibocom, mm_broadband_modem_mbim_xmm_fibocom, MM_TYPE_BROADBAND_MODEM_MBIM_XMM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_FIBOCOM, shared_fibocom_init))
+
+/******************************************************************************/
+
+MMBroadbandModemMbimXmmFibocom *
+mm_broadband_modem_mbim_xmm_fibocom_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_XMM_FIBOCOM,
+ 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,
+ /* 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,
+ MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, TRUE,
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
+ MM_BROADBAND_MODEM_MBIM_QMI_UNSUPPORTED, TRUE,
+#endif
+ NULL);
+}
+
+static void
+mm_broadband_modem_mbim_xmm_fibocom_init (MMBroadbandModemMbimXmmFibocom *self)
+{
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->set_initial_eps_bearer_settings = mm_shared_fibocom_set_initial_eps_bearer_settings;
+ iface->set_initial_eps_bearer_settings_finish = mm_shared_fibocom_set_initial_eps_bearer_settings_finish;
+}
+
+static MMIfaceModem3gpp *
+peek_parent_3gpp_interface (MMSharedFibocom *self)
+{
+ return iface_modem_3gpp_parent;
+}
+
+static void
+shared_fibocom_init (MMSharedFibocom *iface)
+{
+ iface->peek_parent_3gpp_interface = peek_parent_3gpp_interface;
+}
+
+static void
+mm_broadband_modem_mbim_xmm_fibocom_class_init (MMBroadbandModemMbimXmmFibocomClass *klass)
+{
+}
diff --git a/src/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.h b/src/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.h
new file mode 100644
index 00000000..db51cfc8
--- /dev/null
+++ b/src/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.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) 2022 Fibocom Wireless Inc.
+ */
+
+#ifndef MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_H
+#define MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_H
+
+#include "mm-broadband-modem-mbim-xmm.h"
+
+#define MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM (mm_broadband_modem_mbim_xmm_fibocom_get_type ())
+#define MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM, MMBroadbandModemMbimXmmFibocom))
+#define MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM, MMBroadbandModemMbimXmmFibocomClass))
+#define MM_IS_BROADBAND_MODEM_MBIM_XMM_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM))
+#define MM_IS_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM))
+#define MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM, MMBroadbandModemMbimXmmFibocomClass))
+
+typedef struct _MMBroadbandModemMbimXmmFibocom MMBroadbandModemMbimXmmFibocom;
+typedef struct _MMBroadbandModemMbimXmmFibocomClass MMBroadbandModemMbimXmmFibocomClass;
+
+struct _MMBroadbandModemMbimXmmFibocom {
+ MMBroadbandModemMbimXmm parent;
+};
+
+struct _MMBroadbandModemMbimXmmFibocomClass{
+ MMBroadbandModemMbimXmmClass parent;
+};
+
+GType mm_broadband_modem_mbim_xmm_fibocom_get_type (void);
+
+MMBroadbandModemMbimXmmFibocom *mm_broadband_modem_mbim_xmm_fibocom_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_H */
diff --git a/src/plugins/fibocom/mm-plugin-fibocom.c b/src/plugins/fibocom/mm-plugin-fibocom.c
new file mode 100644
index 00000000..4ef4d483
--- /dev/null
+++ b/src/plugins/fibocom/mm-plugin-fibocom.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 <stdlib.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-plugin-fibocom.h"
+#include "mm-broadband-modem.h"
+#include "mm-broadband-modem-xmm.h"
+#include "mm-broadband-modem-fibocom.h"
+
+#if defined WITH_MBIM
+#include "mm-broadband-modem-mbim.h"
+#include "mm-broadband-modem-mbim-fibocom.h"
+#include "mm-broadband-modem-mbim-xmm.h"
+#include "mm-broadband-modem-mbim-xmm-fibocom.h"
+#endif
+
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginFibocom, mm_plugin_fibocom, 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_MBIM
+ if (mm_port_probe_list_has_mbim_port (probes)) {
+ if (mm_port_probe_list_is_xmm (probes)) {
+ mm_obj_dbg (self, "MBIM-powered XMM-based Fibocom modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_xmm_fibocom_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+ mm_obj_dbg (self, "MBIM-powered Fibocom modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_fibocom_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+#if defined WITH_QMI
+ if (mm_port_probe_list_has_qmi_port (probes)) {
+ mm_obj_dbg (self, "QMI-powered Fibocom modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ if (mm_port_probe_list_is_xmm (probes)) {
+ mm_obj_dbg (self, "XMM-based Fibocom modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_xmm_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+
+ mm_obj_dbg (self, "Fibocom modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_fibocom_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", NULL };
+ static const guint16 vendor_ids[] = { 0x2cb7, 0x1782, 0 };
+ static const gchar *drivers[] = { "cdc_mbim", "qmi_wwan", "cdc_ether", "option", NULL };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_FIBOCOM,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_DRIVERS, drivers,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_MBIM, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ MM_PLUGIN_XMM_PROBE, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_fibocom_init (MMPluginFibocom *self)
+{
+}
+
+static void
+mm_plugin_fibocom_class_init (MMPluginFibocomClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/fibocom/mm-plugin-fibocom.h b/src/plugins/fibocom/mm-plugin-fibocom.h
new file mode 100644
index 00000000..e5289979
--- /dev/null
+++ b/src/plugins/fibocom/mm-plugin-fibocom.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) 2018 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_PLUGIN_FIBOCOM_H
+#define MM_PLUGIN_FIBOCOM_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_FIBOCOM (mm_plugin_fibocom_get_type ())
+#define MM_PLUGIN_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_FIBOCOM, MMPluginFibocom))
+#define MM_PLUGIN_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_FIBOCOM, MMPluginFibocomClass))
+#define MM_IS_PLUGIN_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_FIBOCOM))
+#define MM_IS_PLUGIN_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_FIBOCOM))
+#define MM_PLUGIN_FIBOCOM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_FIBOCOM, MMPluginFibocomClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginFibocom;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginFibocomClass;
+
+GType mm_plugin_fibocom_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_FIBOCOM_H */
diff --git a/src/plugins/fibocom/mm-shared-fibocom.c b/src/plugins/fibocom/mm-shared-fibocom.c
new file mode 100644
index 00000000..10b82c59
--- /dev/null
+++ b/src/plugins/fibocom/mm-shared-fibocom.c
@@ -0,0 +1,246 @@
+/* -*- 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) 2022 Fibocom Wireless Inc.
+ */
+
+#include <config.h>
+#include <arpa/inet.h>
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-broadband-modem.h"
+#include "mm-broadband-modem-mbim.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-shared-fibocom.h"
+
+/*****************************************************************************/
+/* Private data context */
+
+#define PRIVATE_TAG "shared-intel-private-tag"
+static GQuark private_quark;
+
+typedef struct {
+ /* 3GPP interface support */
+ MMIfaceModem3gpp *iface_modem_3gpp_parent;
+} Private;
+
+static void
+private_free (Private *priv)
+{
+ g_slice_free (Private, priv);
+}
+
+static Private *
+get_private (MMSharedFibocom *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);
+
+ /* Setup parent class' MMIfaceModem3gpp */
+ g_assert (MM_SHARED_FIBOCOM_GET_INTERFACE (self)->peek_parent_3gpp_interface);
+ priv->iface_modem_3gpp_parent = MM_SHARED_FIBOCOM_GET_INTERFACE (self)->peek_parent_3gpp_interface (self);
+
+ g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
+ }
+
+ return priv;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ MMBearerProperties *config;
+ gboolean initial_eps_off_on;
+} SetInitialEpsBearerSettingsContext;
+
+static void
+set_initial_eps_bearer_settings_context_free (SetInitialEpsBearerSettingsContext *ctx)
+{
+ g_clear_object (&ctx->config);
+ g_slice_free (SetInitialEpsBearerSettingsContext, ctx);
+}
+
+gboolean
+mm_shared_fibocom_set_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+after_attach_apn_modem_power_up_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_iface_modem_set_power_state_finish (self, res, &error)) {
+ mm_obj_warn (self, "failed to power up modem after attach APN settings update: %s", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "success toggling modem power up after attach APN");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_set_initial_eps_bearer_settings_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetInitialEpsBearerSettingsContext *ctx;
+ Private *priv;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+ priv = get_private (MM_SHARED_FIBOCOM (self));
+
+ if (!priv->iface_modem_3gpp_parent->set_initial_eps_bearer_settings_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ if (ctx->initial_eps_off_on) {
+ mm_obj_dbg (self, "toggle modem power up after attach APN");
+ mm_iface_modem_set_power_state (MM_IFACE_MODEM (self),
+ MM_MODEM_POWER_STATE_ON,
+ (GAsyncReadyCallback) after_attach_apn_modem_power_up_ready,
+ task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_set_initial_eps_bearer_settings (GTask *task)
+{
+ MMSharedFibocom *self;
+ SetInitialEpsBearerSettingsContext *ctx;
+ Private *priv;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+ priv = get_private (self);
+
+ g_assert (priv->iface_modem_3gpp_parent);
+ g_assert (priv->iface_modem_3gpp_parent->set_initial_eps_bearer_settings);
+ g_assert (priv->iface_modem_3gpp_parent->set_initial_eps_bearer_settings_finish);
+
+ priv->iface_modem_3gpp_parent->set_initial_eps_bearer_settings (MM_IFACE_MODEM_3GPP (self),
+ ctx->config,
+ (GAsyncReadyCallback)parent_set_initial_eps_bearer_settings_ready,
+ task);
+}
+
+static void
+before_attach_apn_modem_power_down_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_iface_modem_set_power_state_finish (self, res, &error)) {
+ mm_obj_warn (self, "failed to power down modem before attach APN settings update: %s", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+ mm_obj_dbg (self, "success toggling modem power down before attach APN");
+
+ parent_set_initial_eps_bearer_settings (task);
+}
+
+void
+mm_shared_fibocom_set_initial_eps_bearer_settings (MMIfaceModem3gpp *self,
+ MMBearerProperties *config,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SetInitialEpsBearerSettingsContext *ctx;
+ GTask *task;
+ MMPortMbim *port;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* This shared logic is only expected in MBIM capable devices */
+ g_assert (MM_IS_BROADBAND_MODEM_MBIM (self));
+ port = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self));
+ if (!port) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No valid MBIM port found");
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_slice_new0 (SetInitialEpsBearerSettingsContext);
+ ctx->config = g_object_ref (config);
+ ctx->initial_eps_off_on = mm_kernel_device_get_property_as_boolean (mm_port_peek_kernel_device (MM_PORT (port)), "ID_MM_FIBOCOM_INITIAL_EPS_OFF_ON");
+ g_task_set_task_data (task, ctx, (GDestroyNotify)set_initial_eps_bearer_settings_context_free);
+
+ if (ctx->initial_eps_off_on) {
+ mm_obj_dbg (self, "toggle modem power down before attach APN");
+ mm_iface_modem_set_power_state (MM_IFACE_MODEM (self),
+ MM_MODEM_POWER_STATE_LOW,
+ (GAsyncReadyCallback) before_attach_apn_modem_power_down_ready,
+ task);
+ return;
+ }
+
+ parent_set_initial_eps_bearer_settings (task);
+}
+
+/*****************************************************************************/
+
+static void
+shared_fibocom_init (gpointer g_iface)
+{
+}
+
+GType
+mm_shared_fibocom_get_type (void)
+{
+ static GType shared_fibocom_type = 0;
+
+ if (!G_UNLIKELY (shared_fibocom_type)) {
+ static const GTypeInfo info = {
+ sizeof (MMSharedFibocom), /* class_size */
+ shared_fibocom_init, /* base_init */
+ NULL, /* base_finalize */
+ };
+
+ shared_fibocom_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedFibocom", &info, 0);
+ g_type_interface_add_prerequisite (shared_fibocom_type, MM_TYPE_IFACE_MODEM);
+ g_type_interface_add_prerequisite (shared_fibocom_type, MM_TYPE_IFACE_MODEM_3GPP);
+ }
+
+ return shared_fibocom_type;
+}
diff --git a/src/plugins/fibocom/mm-shared-fibocom.h b/src/plugins/fibocom/mm-shared-fibocom.h
new file mode 100644
index 00000000..cc4348d2
--- /dev/null
+++ b/src/plugins/fibocom/mm-shared-fibocom.h
@@ -0,0 +1,53 @@
+/* -*- 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) 2022 Fibocom Wireless Inc.
+ */
+
+#ifndef MM_SHARED_FIBOCOM_H
+#define MM_SHARED_FIBOCOM_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-3gpp.h"
+#include "mm-iface-modem.h"
+
+#define MM_TYPE_SHARED_FIBOCOM (mm_shared_fibocom_get_type ())
+#define MM_SHARED_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_FIBOCOM, MMSharedFibocom))
+#define MM_IS_SHARED_FIBOCOM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_FIBOCOM))
+#define MM_SHARED_FIBOCOM_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_FIBOCOM, MMSharedFibocom))
+
+typedef struct _MMSharedFibocom MMSharedFibocom;
+
+struct _MMSharedFibocom {
+ GTypeInterface g_iface;
+
+ /* Peek 3GPP interface of the parent class of the object */
+ MMIfaceModem3gpp * (* peek_parent_3gpp_interface) (MMSharedFibocom *self);
+};
+
+GType mm_shared_fibocom_get_type (void);
+
+void mm_shared_fibocom_set_initial_eps_bearer_settings (MMIfaceModem3gpp *self,
+ MMBearerProperties *config,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_fibocom_set_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SHARED_FIBOCOM_H */
diff --git a/src/plugins/fibocom/mm-shared.c b/src/plugins/fibocom/mm-shared.c
new file mode 100644
index 00000000..b99bc3aa
--- /dev/null
+++ b/src/plugins/fibocom/mm-shared.c
@@ -0,0 +1,20 @@
+/* -*- 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) 2022 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-shared.h"
+
+MM_SHARED_DEFINE_MAJOR_VERSION
+MM_SHARED_DEFINE_MINOR_VERSION
+MM_SHARED_DEFINE_NAME(Intel)
diff --git a/src/plugins/foxconn/77-mm-foxconn-port-types.rules b/src/plugins/foxconn/77-mm-foxconn-port-types.rules
new file mode 100644
index 00000000..344df152
--- /dev/null
+++ b/src/plugins/foxconn/77-mm-foxconn-port-types.rules
@@ -0,0 +1,26 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_foxconn_port_types_end"
+
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="0489", GOTO="mm_foxconn_vendorcheck"
+GOTO="mm_foxconn_port_types_end"
+
+LABEL="mm_foxconn_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Foxconn T77w968 (default 0xe0b4, with esim support 0xe0b5)
+# if 02: primary port
+# if 03: secondary port
+# if 04: raw NMEA port
+# if 05: diag/qcdm port
+ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b4", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b4", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b4", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b4", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b5", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b5", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b5", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0489", ATTRS{idProduct}=="e0b5", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+
+GOTO="mm_foxconn_port_types_end"
+LABEL="mm_foxconn_port_types_end"
diff --git a/src/plugins/foxconn/mm-broadband-modem-mbim-foxconn.c b/src/plugins/foxconn/mm-broadband-modem-mbim-foxconn.c
new file mode 100644
index 00000000..cec1c617
--- /dev/null
+++ b/src/plugins/foxconn/mm-broadband-modem-mbim-foxconn.c
@@ -0,0 +1,612 @@
+/* -*- 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-2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-errors-types.h"
+#include "mm-modem-helpers.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-location.h"
+#include "mm-broadband-modem-mbim-foxconn.h"
+
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
+# include "mm-iface-modem-firmware.h"
+# include "mm-shared-qmi.h"
+# include "mm-log.h"
+#endif
+
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
+static void iface_modem_firmware_init (MMIfaceModemFirmware *iface);
+#endif
+
+static MMIfaceModemLocation *iface_modem_location_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimFoxconn, mm_broadband_modem_mbim_foxconn, MM_TYPE_BROADBAND_MODEM_MBIM, 0,
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_FIRMWARE, iface_modem_firmware_init)
+#endif
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init))
+
+typedef enum {
+ FEATURE_SUPPORT_UNKNOWN,
+ FEATURE_NOT_SUPPORTED,
+ FEATURE_SUPPORTED
+} FeatureSupport;
+
+struct _MMBroadbandModemMbimFoxconnPrivate {
+ FeatureSupport unmanaged_gps_support;
+};
+
+
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
+
+/*****************************************************************************/
+/* Firmware update settings
+ *
+ * We only support reporting firmware update settings when QMI support is built,
+ * because this is the only clean way to get the expected firmware version to
+ * report.
+ */
+
+static MMFirmwareUpdateSettings *
+firmware_load_update_settings_finish (MMIfaceModemFirmware *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static gboolean
+needs_qdu_and_mcfg_apps_version (MMIfaceModemFirmware *self)
+{
+ guint vendor_id;
+ guint product_id;
+
+ /* 0x105b is the T99W175 module, T99W175 supports QDU and requires MCFG+APPS version.
+ * T99W265(0x0489:0xe0da ; 0x0489:0xe0db): supports QDU and requires MCFG+APPS version.
+ * else support FASTBOOT and QMI PDC, and require only MCFG version.
+ */
+ vendor_id = mm_base_modem_get_vendor_id (MM_BASE_MODEM (self));
+ product_id = mm_base_modem_get_product_id (MM_BASE_MODEM (self));
+ return (vendor_id == 0x105b || (vendor_id == 0x0489 && (product_id == 0xe0da || product_id == 0xe0db)));
+}
+
+/*****************************************************************************/
+/* Need APPS version for the development of different functions when T77W968 support FASTBOOT and QMI PDC.
+ * Such as: T77W968.F1.0.0.5.2.GC.013.037 and T77W968.F1.0.0.5.2.GC.013.049, the MCFG version(T77W968.F1.0.0.5.2.GC.013) is same,
+ * but the APPS version(037 and 049) is different.
+ *
+ * For T77W968.F1.0.0.5.2.GC.013.049, before the change, "fwupdmgr get-devices" can obtain Current version is T77W968.F1.0.0.5.2.GC.013,
+ * it only include the MCFG version.
+ * After add need APPS version, it shows Current version is T77W968.F1.0.0.5.2.GC.013.049, including the MCFG+APPS version.
+ */
+
+static gboolean
+needs_fastboot_and_qmi_pdc_mcfg_apps_version (MMIfaceModemFirmware *self)
+{
+ guint vendor_id;
+ guint product_id;
+
+ /* T77W968(0x413c:0x81d7 ; 0x413c:0x81e0 ; 0x413c:0x81e4 ; 0x413c:0x81e6): supports FASTBOOT and QMI PDC,
+ * and requires MCFG+APPS version.
+ * else support FASTBOOT and QMI PDC, and require only MCFG version.
+ */
+ vendor_id = mm_base_modem_get_vendor_id (MM_BASE_MODEM (self));
+ product_id = mm_base_modem_get_product_id (MM_BASE_MODEM (self));
+ return (vendor_id == 0x413c && (product_id == 0x81d7 || product_id == 0x81e0 || product_id == 0x81e4 || product_id == 0x81e6));
+}
+
+static MMFirmwareUpdateSettings *
+create_update_settings (MMIfaceModemFirmware *self,
+ const gchar *version_str)
+{
+ MMModemFirmwareUpdateMethod methods = MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE;
+ MMFirmwareUpdateSettings *update_settings = NULL;
+
+ if (needs_qdu_and_mcfg_apps_version (self))
+ methods = MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU;
+ else
+ methods = MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT | MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC;
+
+ update_settings = mm_firmware_update_settings_new (methods);
+ if (methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT)
+ mm_firmware_update_settings_set_fastboot_at (update_settings, "AT^FASTBOOT");
+ mm_firmware_update_settings_set_version (update_settings, version_str);
+ return update_settings;
+}
+
+static void
+dms_foxconn_get_firmware_version_ready (QmiClientDms *client,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(QmiMessageDmsFoxconnGetFirmwareVersionOutput) output = NULL;
+ GError *error = NULL;
+ const gchar *str;
+
+ output = qmi_client_dms_foxconn_get_firmware_version_finish (client, res, &error);
+ if (!output || !qmi_message_dms_foxconn_get_firmware_version_output_get_result (output, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ qmi_message_dms_foxconn_get_firmware_version_output_get_version (output, &str, NULL);
+
+ g_task_return_pointer (task,
+ create_update_settings (g_task_get_source_object (task), str),
+ g_object_unref);
+ g_object_unref (task);
+}
+
+static void
+fox_get_firmware_version_ready (QmiClientFox *client,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(QmiMessageFoxGetFirmwareVersionOutput) output = NULL;
+ GError *error = NULL;
+ const gchar *str;
+
+ output = qmi_client_fox_get_firmware_version_finish (client, res, &error);
+ if (!output || !qmi_message_fox_get_firmware_version_output_get_result (output, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ qmi_message_fox_get_firmware_version_output_get_version (output, &str, NULL);
+
+ g_task_return_pointer (task,
+ create_update_settings (g_task_get_source_object (task), str),
+ g_object_unref);
+ g_object_unref (task);
+}
+
+static void
+mbim_port_allocate_qmi_client_ready (MMPortMbim *mbim,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMIfaceModemFirmware *self;
+ QmiClient *fox_client = NULL;
+ QmiClient *dms_client = NULL;
+ g_autoptr(GError) error = NULL;
+
+ self = g_task_get_source_object (task);
+
+ if (!mm_port_mbim_allocate_qmi_client_finish (mbim, res, &error))
+ mm_obj_dbg (self, "Allocate FOX client failed: %s", error->message);
+
+ /* Try to get firmware version over fox service, if it failed to peek client, try dms service. */
+ fox_client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_FOX, MM_PORT_QMI_FLAG_DEFAULT, NULL);
+ if (!fox_client) {
+ dms_client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_DMS, MM_PORT_QMI_FLAG_DEFAULT, NULL);
+ if (!dms_client) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unable to load version info: no FOX or DMS client available");
+ g_object_unref (task);
+ return;
+ }
+ }
+
+ if (fox_client) {
+ g_autoptr(QmiMessageFoxGetFirmwareVersionInput) input = NULL;
+
+ input = qmi_message_fox_get_firmware_version_input_new ();
+ qmi_message_fox_get_firmware_version_input_set_version_type (input,
+ (needs_qdu_and_mcfg_apps_version (self) ?
+ QMI_FOX_FIRMWARE_VERSION_TYPE_FIRMWARE_MCFG_APPS :
+ QMI_FOX_FIRMWARE_VERSION_TYPE_FIRMWARE_MCFG),
+ NULL);
+ qmi_client_fox_get_firmware_version (QMI_CLIENT_FOX (fox_client),
+ input,
+ 10,
+ NULL,
+ (GAsyncReadyCallback)fox_get_firmware_version_ready,
+ task);
+ return;
+ }
+
+ if (dms_client) {
+ g_autoptr(QmiMessageDmsFoxconnGetFirmwareVersionInput) input = NULL;
+
+ input = qmi_message_dms_foxconn_get_firmware_version_input_new ();
+ qmi_message_dms_foxconn_get_firmware_version_input_set_version_type (input,
+ ((needs_qdu_and_mcfg_apps_version (self) || needs_fastboot_and_qmi_pdc_mcfg_apps_version (self)) ?
+ QMI_DMS_FOXCONN_FIRMWARE_VERSION_TYPE_FIRMWARE_MCFG_APPS:
+ QMI_DMS_FOXCONN_FIRMWARE_VERSION_TYPE_FIRMWARE_MCFG),
+ NULL);
+ qmi_client_dms_foxconn_get_firmware_version (QMI_CLIENT_DMS (dms_client),
+ input,
+ 10,
+ NULL,
+ (GAsyncReadyCallback)dms_foxconn_get_firmware_version_ready,
+ task);
+ return;
+ }
+
+ g_assert_not_reached ();
+}
+
+static void
+firmware_load_update_settings (MMIfaceModemFirmware *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ MMPortMbim *mbim;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ mbim = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self));
+ mm_port_mbim_allocate_qmi_client (mbim,
+ QMI_SERVICE_FOX,
+ NULL,
+ (GAsyncReadyCallback)mbim_port_allocate_qmi_client_ready,
+ task);
+}
+
+#endif
+
+/*****************************************************************************/
+/* Location capabilities loading (Location interface) */
+
+static MMModemLocationSource
+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
+custom_location_load_capabilities (GTask *task,
+ MMModemLocationSource sources)
+{
+ MMBroadbandModemMbimFoxconn *self;
+
+ self = g_task_get_source_object (task);
+
+ /* If we have a GPS port and an AT port, enable unmanaged GPS support */
+ if (mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)) &&
+ mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) {
+ self->priv->unmanaged_gps_support = FEATURE_SUPPORTED;
+ sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED;
+ }
+
+ /* 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)
+{
+ MMModemLocationSource sources;
+ GError *error = NULL;
+
+ sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ custom_location_load_capabilities (task, sources);
+}
+
+static void
+location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's setup, if any. If MM is built without QMI support,
+ * the MBIM modem won't have any location capabilities. */
+ if (iface_modem_location_parent &&
+ iface_modem_location_parent->load_capabilities &&
+ iface_modem_location_parent->load_capabilities_finish) {
+ iface_modem_location_parent->load_capabilities (self,
+ (GAsyncReadyCallback)parent_load_capabilities_ready,
+ task);
+ return;
+ }
+
+ custom_location_load_capabilities (task, MM_MODEM_LOCATION_SOURCE_NONE);
+}
+
+/*****************************************************************************/
+/* Disable location gathering (Location interface) */
+
+static gboolean
+disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_disable_location_gathering_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!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);
+}
+
+static void
+parent_disable_location_gathering (GTask *task)
+{
+ MMIfaceModemLocation *self;
+ MMModemLocationSource source;
+
+ self = MM_IFACE_MODEM_LOCATION (g_task_get_source_object (task));
+ source = GPOINTER_TO_UINT (g_task_get_task_data (task));
+
+ if (iface_modem_location_parent &&
+ iface_modem_location_parent->disable_location_gathering &&
+ iface_modem_location_parent->disable_location_gathering_finish) {
+ iface_modem_location_parent->disable_location_gathering (
+ self,
+ source,
+ (GAsyncReadyCallback)parent_disable_location_gathering_ready,
+ task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+unmanaged_gps_disabled_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);
+ g_object_unref (task);
+ return;
+ }
+
+ parent_disable_location_gathering (task);
+}
+
+static void
+disable_location_gathering (MMIfaceModemLocation *_self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemMbimFoxconn *self = MM_BROADBAND_MODEM_MBIM_FOXCONN (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
+
+ /* We only support Unmanaged GPS at this level */
+ if ((self->priv->unmanaged_gps_support != FEATURE_SUPPORTED) ||
+ (source != MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ parent_disable_location_gathering (task);
+ return;
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (_self),
+ "^NV=30007,01,\"00\"",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)unmanaged_gps_disabled_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Enable location gathering (Location interface) */
+
+static gboolean
+enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+unmanaged_gps_enabled_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
+custom_enable_location_gathering (GTask *task)
+{
+ MMBroadbandModemMbimFoxconn *self;
+ MMModemLocationSource source;
+
+ self = g_task_get_source_object (task);
+ source = GPOINTER_TO_UINT (g_task_get_task_data (task));
+
+ /* We only support Unmanaged GPS at this level */
+ if ((self->priv->unmanaged_gps_support != FEATURE_SUPPORTED) ||
+ (source != MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^NV=30007,01,\"01\"",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)unmanaged_gps_enabled_ready,
+ task);
+}
+
+static void
+parent_enable_location_gathering_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_location_parent->enable_location_gathering_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ custom_enable_location_gathering (task);
+}
+
+static void
+enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
+
+ /* Chain up parent's gathering enable */
+ if (iface_modem_location_parent &&
+ iface_modem_location_parent->enable_location_gathering &&
+ iface_modem_location_parent->enable_location_gathering_finish) {
+ iface_modem_location_parent->enable_location_gathering (
+ self,
+ source,
+ (GAsyncReadyCallback)parent_enable_location_gathering_ready,
+ task);
+ return;
+ }
+
+ custom_enable_location_gathering (task);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemMbimFoxconn *
+mm_broadband_modem_mbim_foxconn_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ const gchar *carrier_config_mapping = NULL;
+
+ /* T77W968 (DW5821e/DW5829e is also T77W968) modules use t77w968 carrier mapping table. */
+ if ((vendor_id == 0x0489 && (product_id == 0xe0b4 || product_id == 0xe0b5)) ||
+ (vendor_id == 0x413c && (product_id == 0x81d7 || product_id == 0x81e0 || product_id == 0x81e4 || product_id == 0x81e6)))
+ carrier_config_mapping = PKGDATADIR "/mm-foxconn-t77w968-carrier-mapping.conf";
+
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN,
+ 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,
+ /* 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,
+ MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, TRUE,
+ MM_IFACE_MODEM_LOCATION_ALLOW_GPS_UNMANAGED_ALWAYS, TRUE,
+ MM_IFACE_MODEM_CARRIER_CONFIG_MAPPING, carrier_config_mapping,
+ NULL);
+}
+
+static void
+mm_broadband_modem_mbim_foxconn_init (MMBroadbandModemMbimFoxconn *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN, MMBroadbandModemMbimFoxconnPrivate);
+ self->priv->unmanaged_gps_support = FEATURE_SUPPORT_UNKNOWN;
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = location_load_capabilities;
+ iface->load_capabilities_finish = location_load_capabilities_finish;
+ iface->enable_location_gathering = enable_location_gathering;
+ iface->enable_location_gathering_finish = enable_location_gathering_finish;
+ iface->disable_location_gathering = disable_location_gathering;
+ iface->disable_location_gathering_finish = disable_location_gathering_finish;
+}
+
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
+
+static void
+iface_modem_firmware_init (MMIfaceModemFirmware *iface)
+{
+ iface->load_update_settings = firmware_load_update_settings;
+ iface->load_update_settings_finish = firmware_load_update_settings_finish;
+}
+
+#endif
+
+static void
+mm_broadband_modem_mbim_foxconn_class_init (MMBroadbandModemMbimFoxconnClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemMbimFoxconnPrivate));
+}
diff --git a/src/plugins/foxconn/mm-broadband-modem-mbim-foxconn.h b/src/plugins/foxconn/mm-broadband-modem-mbim-foxconn.h
new file mode 100644
index 00000000..374599e4
--- /dev/null
+++ b/src/plugins/foxconn/mm-broadband-modem-mbim-foxconn.h
@@ -0,0 +1,49 @@
+/* -*- 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-2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_BROADBAND_MODEM_MBIM_FOXCONN_H
+#define MM_BROADBAND_MODEM_MBIM_FOXCONN_H
+
+#include "mm-broadband-modem-mbim.h"
+
+#define MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN (mm_broadband_modem_mbim_foxconn_get_type ())
+#define MM_BROADBAND_MODEM_MBIM_FOXCONN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN, MMBroadbandModemMbimFoxconn))
+#define MM_BROADBAND_MODEM_MBIM_FOXCONN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN, MMBroadbandModemMbimFoxconnClass))
+#define MM_IS_BROADBAND_MODEM_MBIM_FOXCONN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN))
+#define MM_IS_BROADBAND_MODEM_MBIM_FOXCONN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN))
+#define MM_BROADBAND_MODEM_MBIM_FOXCONN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_FOXCONN, MMBroadbandModemMbimFoxconnClass))
+
+typedef struct _MMBroadbandModemMbimFoxconn MMBroadbandModemMbimFoxconn;
+typedef struct _MMBroadbandModemMbimFoxconnClass MMBroadbandModemMbimFoxconnClass;
+typedef struct _MMBroadbandModemMbimFoxconnPrivate MMBroadbandModemMbimFoxconnPrivate;
+
+struct _MMBroadbandModemMbimFoxconn {
+ MMBroadbandModemMbim parent;
+ MMBroadbandModemMbimFoxconnPrivate *priv;
+};
+
+struct _MMBroadbandModemMbimFoxconnClass{
+ MMBroadbandModemMbimClass parent;
+};
+
+GType mm_broadband_modem_mbim_foxconn_get_type (void);
+
+MMBroadbandModemMbimFoxconn *mm_broadband_modem_mbim_foxconn_new (const gchar *device,
+ const gchar **driver,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_MBIM_FOXCONN_H */
diff --git a/src/plugins/foxconn/mm-foxconn-t77w968-carrier-mapping.conf b/src/plugins/foxconn/mm-foxconn-t77w968-carrier-mapping.conf
new file mode 100644
index 00000000..79b12c1e
--- /dev/null
+++ b/src/plugins/foxconn/mm-foxconn-t77w968-carrier-mapping.conf
@@ -0,0 +1,319 @@
+
+#
+# T77W968 carrier mapping table
+#
+# This table maps the MCCMNC of the SIM card with the corresponding
+# configuration description as reported by the QMI PDC service in
+# this module.
+#
+
+[foxconn t77w968]
+
+# AT&T
+302220=ATT
+302221=ATT
+31030=ATT
+31070=ATT
+31090=ATT
+310150=ATT
+310170=ATT
+310280=ATT
+310380=ATT
+310410=ATT
+310560=ATT
+310650=ATT
+310680=ATT
+310980=ATT
+311180=ATT
+90118=ATT
+
+# FirstNet
+312670=A2
+313100=A2
+313110=A2
+313120=A2
+313130=A2
+313140=A2
+
+# Verizon
+310590=Verizon
+310890=Verizon
+311270=Verizon
+311480=Verizon
+312770=Verizon
+
+# Vodafone
+20205=Vodafone
+20404=Vodafone
+20601=Vodafone
+20810=Vodafone
+21401=Vodafone
+21670=Vodafone
+21910=Vodafone
+22005=Vodafone
+22210=Vodafone
+22601=Vodafone
+23003=Vodafone
+23201=Vodafone
+23415=Vodafone
+23801=Vodafone
+24405=Vodafone
+24602=Vodafone
+24705=Vodafone
+24802=Vodafone
+25001=Vodafone
+26202=Vodafone
+26209=Vodafone
+26801=Vodafone
+27077=Vodafone
+27201=Vodafone
+27402=Vodafone
+27602=Vodafone
+27801=Vodafone
+28001=Vodafone
+28401=Vodafone
+28602=Vodafone
+28802=Vodafone
+29340=Vodafone
+29403=Vodafone
+40004=Vodafone
+40401=Vodafone
+40405=Vodafone
+40411=Vodafone
+40413=Vodafone
+40415=Vodafone
+40420=Vodafone
+40427=Vodafone
+40430=Vodafone
+40443=Vodafone
+40446=Vodafone
+40460=Vodafone
+40484=Vodafone
+40486=Vodafone
+40488=Vodafone
+40566=Vodafone
+40567=Vodafone
+405750=Vodafone
+405751=Vodafone
+405752=Vodafone
+405753=Vodafone
+405754=Vodafone
+405755=Vodafone
+405756=Vodafone
+41302=Vodafone
+42403=Vodafone
+42602=Vodafone
+42702=Vodafone
+46601=Vodafone
+46603=Vodafone
+50213=Vodafone
+50219=Vodafone
+50503=Vodafone
+52503=Vodafone
+52505=Vodafone
+53001=Vodafone
+54201=Vodafone
+60202=Vodafone
+62002=Vodafone
+63001=Vodafone
+63902=Vodafone
+64004=Vodafone
+64304=Vodafone
+64710=Vodafone
+65101=Vodafone
+65501=Vodafone
+73001=Vodafone
+90128=Vodafone
+
+# Orange
+20610=Orange
+20801=Orange
+20802=Orange
+21403=Orange
+21409=Orange
+22610=Orange
+23101=Orange
+23105=Orange
+23430=Orange
+23433=Orange
+23434=Orange
+25901=Orange
+26003=Orange
+26005=Orange
+27099=Orange
+28310=Orange
+
+# Telefonica Movistar
+21405=Telefonica
+21407=Telefonica
+
+# Swisscom
+22801=Swisscom
+29501=Swisscom
+
+# Telstra
+50501=Telstra
+50506=Telstra
+50571=Telstra
+50572=Telstra
+
+# Sprint
+310120=Sprint
+
+# Optus
+50202=Optus
+
+# NTT DoCoMo
+44002=Docomo
+44003=Docomo
+44009=Docomo
+44010=Docomo
+44011=Docomo
+44012=Docomo
+44013=Docomo
+44014=Docomo
+44015=Docomo
+44016=Docomo
+44017=Docomo
+44018=Docomo
+44019=Docomo
+44022=Docomo
+44023=Docomo
+44024=Docomo
+44025=Docomo
+44026=Docomo
+44027=Docomo
+44028=Docomo
+44029=Docomo
+44030=Docomo
+44031=Docomo
+44032=Docomo
+44033=Docomo
+44034=Docomo
+44035=Docomo
+44036=Docomo
+44037=Docomo
+44038=Docomo
+44039=Docomo
+44049=Docomo
+44058=Docomo
+44060=Docomo
+44061=Docomo
+44062=Docomo
+44063=Docomo
+44064=Docomo
+44065=Docomo
+44066=Docomo
+44067=Docomo
+44068=Docomo
+44069=Docomo
+44087=Docomo
+44099=Docomo
+44140=Docomo
+44141=Docomo
+44142=Docomo
+44143=Docomo
+44144=Docomo
+44145=Docomo
+44190=Docomo
+44101=Docomo
+44192=Docomo
+44193=Docomo
+44194=Docomo
+44198=Docomo
+44199=Docomo
+
+# KDDI
+44007=KDDI
+44008=KDDI
+44050=KDDI
+44051=KDDI
+44052=KDDI
+44053=KDDI
+44054=KDDI
+44055=KDDI
+44056=KDDI
+44070=KDDI
+44071=KDDI
+44072=KDDI
+44073=KDDI
+44074=KDDI
+44075=KDDI
+44076=KDDI
+44077=KDDI
+44078=KDDI
+44079=KDDI
+44080=KDDI
+44081=KDDI
+44082=KDDI
+44083=KDDI
+44084=KDDI
+44085=KDDI
+44086=KDDI
+44088=KDDI
+44089=KDDI
+44150=KDDI
+44151=KDDI
+44170=KDDI
+
+# SoftBank
+44000=SBM
+44004=SBM
+44006=SBM
+44020=SBM
+44021=SBM
+44040=SBM
+44041=SBM
+44042=SBM
+44043=SBM
+44044=SBM
+44045=SBM
+44046=SBM
+44047=SBM
+44048=SBM
+44090=SBM
+44092=SBM
+44093=SBM
+44094=SBM
+44095=SBM
+44096=SBM
+44097=SBM
+44098=SBM
+44101=SBM
+44161=SBM
+44162=SBM
+44163=SBM
+44164=SBM
+44165=SBM
+
+# EE UK
+23430=EE
+23431=EE
+23432=EE
+23433=EE
+23434=EE
+23476=EE
+23501=EE
+23502=EE
+23577=EE
+
+# Deutsche Telekom
+20201=DT
+20416=DT
+20420=DT
+21630=DT
+21901=DT
+22603=DT
+22606=DT
+23001=DT
+23102=DT
+23203=DT
+23207=DT
+26002=DT
+26201=DT
+27601=DT
+29401=DT
+29702=DT
+
+# Others
+generic=GCF
diff --git a/src/plugins/foxconn/mm-plugin-foxconn.c b/src/plugins/foxconn/mm-plugin-foxconn.c
new file mode 100644
index 00000000..d248fb05
--- /dev/null
+++ b/src/plugins/foxconn/mm-plugin-foxconn.c
@@ -0,0 +1,121 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-foxconn.h"
+#include "mm-log-object.h"
+#include "mm-broadband-modem.h"
+
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi.h"
+#endif
+
+#if defined WITH_MBIM
+#include "mm-broadband-modem-mbim-foxconn.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginFoxconn, mm_plugin_foxconn, 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 Foxconn-branded modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_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 Foxconn-branded modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_foxconn_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ mm_obj_dbg (self, "Foxconn-branded generic modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_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 guint16 vendor_ids[] = {
+ 0x0489, /* usb vid */
+ 0x105b, /* pci vid */
+ 0 };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_FOXCONN,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_QCDM, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ MM_PLUGIN_ALLOWED_MBIM, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_foxconn_init (MMPluginFoxconn *self)
+{
+}
+
+static void
+mm_plugin_foxconn_class_init (MMPluginFoxconnClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/foxconn/mm-plugin-foxconn.h b/src/plugins/foxconn/mm-plugin-foxconn.h
new file mode 100644
index 00000000..4a22ceeb
--- /dev/null
+++ b/src/plugins/foxconn/mm-plugin-foxconn.h
@@ -0,0 +1,46 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_PLUGIN_FOXCONN_H
+#define MM_PLUGIN_FOXCONN_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_FOXCONN (mm_plugin_foxconn_get_type ())
+#define MM_PLUGIN_FOXCONN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_FOXCONN, MMPluginFoxconn))
+#define MM_PLUGIN_FOXCONN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_FOXCONN, MMPluginFoxconnClass))
+#define MM_IS_PLUGIN_FOXCONN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_FOXCONN))
+#define MM_IS_PLUGIN_FOXCONN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_FOXCONN))
+#define MM_PLUGIN_FOXCONN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_FOXCONN, MMPluginFoxconnClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginFoxconn;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginFoxconnClass;
+
+GType mm_plugin_foxconn_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_FOXCONN_H */
diff --git a/src/plugins/foxconn/mm-shared.c b/src/plugins/foxconn/mm-shared.c
new file mode 100644
index 00000000..3b017574
--- /dev/null
+++ b/src/plugins/foxconn/mm-shared.c
@@ -0,0 +1,20 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-shared.h"
+
+MM_SHARED_DEFINE_MAJOR_VERSION
+MM_SHARED_DEFINE_MINOR_VERSION
+MM_SHARED_DEFINE_NAME(Foxconn)
diff --git a/src/plugins/generic/mm-plugin-generic.c b/src/plugins/generic/mm-plugin-generic.c
new file mode 100644
index 00000000..6dd37d59
--- /dev/null
+++ b/src/plugins/generic/mm-plugin-generic.c
@@ -0,0 +1,120 @@
+/* -*- 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) 2008 - 2010 Dan Williams <dcbw@redhat.com>
+ */
+
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <time.h>
+
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-generic.h"
+#include "mm-broadband-modem.h"
+#include "mm-serial-parsers.h"
+#include "mm-log-object.h"
+
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi.h"
+#endif
+
+#if defined WITH_MBIM
+#include "mm-broadband-modem-mbim.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginGeneric, mm_plugin_generic, 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 generic modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_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 generic modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_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 };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_GENERIC,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_IS_GENERIC, TRUE,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ 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_generic_init (MMPluginGeneric *self)
+{
+}
+
+static void
+mm_plugin_generic_class_init (MMPluginGenericClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/generic/mm-plugin-generic.h b/src/plugins/generic/mm-plugin-generic.h
new file mode 100644
index 00000000..12f9dd9d
--- /dev/null
+++ b/src/plugins/generic/mm-plugin-generic.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) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_PLUGIN_GENERIC_H
+#define MM_PLUGIN_GENERIC_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_GENERIC (mm_plugin_generic_get_type ())
+#define MM_PLUGIN_GENERIC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_GENERIC, MMPluginGeneric))
+#define MM_PLUGIN_GENERIC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_GENERIC, MMPluginGenericClass))
+#define MM_IS_PLUGIN_GENERIC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_GENERIC))
+#define MM_IS_PLUGIN_GENERIC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_GENERIC))
+#define MM_PLUGIN_GENERIC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_GENERIC, MMPluginGenericClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginGeneric;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginGenericClass;
+
+GType mm_plugin_generic_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_GENERIC_H */
diff --git a/src/plugins/generic/tests/test-service-generic.c b/src/plugins/generic/tests/test-service-generic.c
new file mode 100644
index 00000000..d7bc4e01
--- /dev/null
+++ b/src/plugins/generic/tests/test-service-generic.c
@@ -0,0 +1,90 @@
+/* -*- 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) 2016 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <libmm-glib.h>
+
+#include "test-port-context.h"
+#include "test-fixture.h"
+
+/*****************************************************************************/
+
+static void
+test_enable_disable (TestFixture *fixture)
+{
+ GError *error = NULL;
+ MMObject *obj;
+ MMModem *modem;
+ TestPortContext *port0;
+ gchar *ports [] = { NULL, NULL };
+
+ /* Create port name, and add process ID so that multiple runs of this test
+ * in the same system don't clash with each other */
+ ports[0] = g_strdup_printf ("abstract:port0:%ld", (glong) getpid ());
+ g_debug ("test service generic: using abstract port at '%s'", ports[0]);
+
+ /* Setup new port context */
+ port0 = test_port_context_new (ports[0]);
+ test_port_context_load_commands (port0, COMMON_GSM_PORT_CONF);
+ test_port_context_start (port0);
+
+ /* Ensure no modem is modem exported */
+ test_fixture_no_modem (fixture);
+
+ /* Set the test profile */
+ test_fixture_set_profile (fixture,
+ "test-enable-disable",
+ "generic",
+ (const gchar *const *)ports);
+
+ /* Wait and get the modem object */
+ obj = test_fixture_get_modem (fixture);
+
+ /* Get Modem interface, and enable */
+ modem = mm_object_get_modem (obj);
+ g_assert (modem != NULL);
+ mm_modem_enable_sync (modem, NULL, &error);
+ g_assert_no_error (error);
+
+ /* And disable */
+ mm_modem_disable_sync (modem, NULL, &error);
+ g_assert_no_error (error);
+
+ g_object_unref (modem);
+ g_object_unref (obj);
+
+ /* Stop port context */
+ test_port_context_stop (port0);
+ test_port_context_free (port0);
+
+ g_free (ports[0]);
+}
+
+/*****************************************************************************/
+
+int main (int argc,
+ char *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+
+ TEST_ADD ("/MM/Service/Generic/enable-disable", test_enable_disable);
+
+ return g_test_run ();
+}
diff --git a/src/plugins/gosuncn/77-mm-gosuncn-port-types.rules b/src/plugins/gosuncn/77-mm-gosuncn-port-types.rules
new file mode 100644
index 00000000..122c6666
--- /dev/null
+++ b/src/plugins/gosuncn/77-mm-gosuncn-port-types.rules
@@ -0,0 +1,17 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_gosuncn_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="305a", GOTO="mm_gosuncn_port_types"
+GOTO="mm_gosuncn_port_types_end"
+
+LABEL="mm_gosuncn_port_types"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Gosuncn GM800
+# Interfaces #3 and #4 are MBIM
+ATTRS{idVendor}=="305a", ATTRS{idProduct}=="1405", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="305a", ATTRS{idProduct}=="1405", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="305a", ATTRS{idProduct}=="1405", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="305a", ATTRS{idProduct}=="1405", ENV{.MM_USBIFNUM}=="05", ENV{ID_MM_PORT_IGNORE}="1"
+
+LABEL="mm_gosuncn_port_types_end"
diff --git a/src/plugins/gosuncn/mm-plugin-gosuncn.c b/src/plugins/gosuncn/mm-plugin-gosuncn.c
new file mode 100644
index 00000000..010e93eb
--- /dev/null
+++ b/src/plugins/gosuncn/mm-plugin-gosuncn.c
@@ -0,0 +1,114 @@
+/* -*- 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 <stdlib.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-plugin-gosuncn.h"
+#include "mm-broadband-modem.h"
+
+#if defined WITH_QMI
+# include "mm-broadband-modem-qmi.h"
+#endif
+
+#if defined WITH_MBIM
+# include "mm-broadband-modem-mbim.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginGosuncn, mm_plugin_gosuncn, 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 Gosuncn modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_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 Gosuncn modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ /* Fallback to default modem in the worst case */
+ return MM_BASE_MODEM (mm_broadband_modem_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", NULL };
+ static const guint16 vendor_ids[] = { 0x305a, 0 };
+ static const gchar *drivers[] = { "qmi_wwan", "cdc_mbim", NULL };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_GOSUNCN,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_DRIVERS, drivers,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_QCDM, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ MM_PLUGIN_ALLOWED_MBIM, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_gosuncn_init (MMPluginGosuncn *self)
+{
+}
+
+static void
+mm_plugin_gosuncn_class_init (MMPluginGosuncnClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/gosuncn/mm-plugin-gosuncn.h b/src/plugins/gosuncn/mm-plugin-gosuncn.h
new file mode 100644
index 00000000..a50e3089
--- /dev/null
+++ b/src/plugins/gosuncn/mm-plugin-gosuncn.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) 2020 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_PLUGIN_GOSUNCN_H
+#define MM_PLUGIN_GOSUNCN_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_GOSUNCN (mm_plugin_gosuncn_get_type ())
+#define MM_PLUGIN_GOSUNCN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_GOSUNCN, MMPluginGosuncn))
+#define MM_PLUGIN_GOSUNCN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_GOSUNCN, MMPluginGosuncnClass))
+#define MM_IS_PLUGIN_GOSUNCN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_GOSUNCN))
+#define MM_IS_PLUGIN_GOSUNCN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_GOSUNCN))
+#define MM_PLUGIN_GOSUNCN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_GOSUNCN, MMPluginGosuncnClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginGosuncn;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginGosuncnClass;
+
+GType mm_plugin_gosuncn_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_GOSUNCN_H */
diff --git a/src/plugins/haier/77-mm-haier-port-types.rules b/src/plugins/haier/77-mm-haier-port-types.rules
new file mode 100644
index 00000000..0d969fca
--- /dev/null
+++ b/src/plugins/haier/77-mm-haier-port-types.rules
@@ -0,0 +1,13 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_haier_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="201e", GOTO="mm_haier_port_types"
+GOTO="mm_haier_port_types_end"
+
+LABEL="mm_haier_port_types"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Haier CE81B
+ATTRS{idVendor}=="201e", ATTRS{idProduct}=="10f8", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+LABEL="mm_haier_port_types_end"
diff --git a/src/plugins/haier/mm-plugin-haier.c b/src/plugins/haier/mm-plugin-haier.c
new file mode 100644
index 00000000..a0951c27
--- /dev/null
+++ b/src/plugins/haier/mm-plugin-haier.c
@@ -0,0 +1,76 @@
+/* -*- 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) 2014 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-broadband-modem.h"
+#include "mm-plugin-haier.h"
+
+G_DEFINE_TYPE (MMPluginHaier, mm_plugin_haier, 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)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", NULL };
+ static const guint16 vendor_ids[] = { 0x201e, 0 };
+
+ return MM_PLUGIN (g_object_new (MM_TYPE_PLUGIN_HAIER,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_haier_init (MMPluginHaier *self)
+{
+}
+
+static void
+mm_plugin_haier_class_init (MMPluginHaierClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/haier/mm-plugin-haier.h b/src/plugins/haier/mm-plugin-haier.h
new file mode 100644
index 00000000..e1fdfbb1
--- /dev/null
+++ b/src/plugins/haier/mm-plugin-haier.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) 2014 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_PLUGIN_HAIER_H
+#define MM_PLUGIN_HAIER_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_HAIER (mm_plugin_haier_get_type ())
+#define MM_PLUGIN_HAIER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_HAIER, MMPluginHaier))
+#define MM_PLUGIN_HAIER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_HAIER, MMPluginHaierClass))
+#define MM_IS_PLUGIN_HAIER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_HAIER))
+#define MM_IS_PLUGIN_HAIER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_HAIER))
+#define MM_PLUGIN_HAIER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_HAIER, MMPluginHaierClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginHaier;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginHaierClass;
+
+GType mm_plugin_haier_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_HAIER_H */
diff --git a/src/plugins/huawei/77-mm-huawei-net-port-types.rules b/src/plugins/huawei/77-mm-huawei-net-port-types.rules
new file mode 100644
index 00000000..fed7da06
--- /dev/null
+++ b/src/plugins/huawei/77-mm-huawei-net-port-types.rules
@@ -0,0 +1,37 @@
+# do not edit this file, it will be overwritten on update
+ACTION!="add|change|move|bind", GOTO="mm_huawei_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="12d1", GOTO="mm_huawei_port_types"
+GOTO="mm_huawei_port_types_end"
+
+LABEL="mm_huawei_port_types"
+
+# MU609 does not support getportmode (crashes modem with default firmware)
+ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="1573", ENV{ID_MM_HUAWEI_DISABLE_GETPORTMODE}="1"
+
+# Mark the modem and at port flags for ModemManager
+SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="ff", ATTRS{bInterfaceSubClass}=="01", ATTRS{bInterfaceProtocol}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PPP}="1"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="ff", ATTRS{bInterfaceSubClass}=="01", ATTRS{bInterfaceProtocol}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="ff", ATTRS{bInterfaceSubClass}=="02", ATTRS{bInterfaceProtocol}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PPP}="1"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="ff", ATTRS{bInterfaceSubClass}=="02", ATTRS{bInterfaceProtocol}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+# GPS NMEA port on MU609
+SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="ff", ATTRS{bInterfaceSubClass}=="01", ATTRS{bInterfaceProtocol}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+# GPS NMEA port on MU909
+SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="ff", ATTRS{bInterfaceSubClass}=="01", ATTRS{bInterfaceProtocol}=="14", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+# GPS NMEA port on MU906e
+SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="ff", ATTRS{bInterfaceSubClass}=="06", ATTRS{bInterfaceProtocol}=="14", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+
+# Only the standard ECM or NCM port can support dial-up with AT NDISDUP through AT port
+SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="02", ATTRS{bInterfaceSubClass}=="06",ATTRS{bInterfaceProtocol}=="00", ENV{ID_MM_HUAWEI_NDISDUP_SUPPORTED}="1"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="02", ATTRS{bInterfaceSubClass}=="0d",ATTRS{bInterfaceProtocol}=="00", ENV{ID_MM_HUAWEI_NDISDUP_SUPPORTED}="1"
+
+# Airtel branded E3372h-607, using huawei-cdc-ncm driver but with unresponsive cdc-wdm port
+ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="1506", ENV{ID_MM_HUAWEI_NDISDUP_SUPPORTED}="1"
+
+# R215, Disable CPOL based features
+ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="1588", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1"
+
+# E226, Disable CPOL based features
+ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="1003", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1"
+
+LABEL="mm_huawei_port_types_end"
diff --git a/src/plugins/huawei/mm-broadband-bearer-huawei.c b/src/plugins/huawei/mm-broadband-bearer-huawei.c
new file mode 100644
index 00000000..f166efa5
--- /dev/null
+++ b/src/plugins/huawei/mm-broadband-bearer-huawei.c
@@ -0,0 +1,879 @@
+/* -*- 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) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ * Copyright (C) 2012 Huawei Technologies Co., Ltd
+ *
+ * Author: Franko fang <huanahu@huawei.com>
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <arpa/inet.h>
+#include <ModemManager.h>
+#include "mm-base-modem-at.h"
+#include "mm-broadband-bearer-huawei.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-huawei.h"
+#include "mm-daemon-enums-types.h"
+
+G_DEFINE_TYPE (MMBroadbandBearerHuawei, mm_broadband_bearer_huawei, MM_TYPE_BROADBAND_BEARER)
+
+struct _MMBroadbandBearerHuaweiPrivate {
+ gpointer connect_pending;
+ gpointer disconnect_pending;
+};
+
+/*****************************************************************************/
+
+static MMPortSerialAt *
+get_dial_port (MMBroadbandModemHuawei *modem,
+ MMPort *data,
+ MMPortSerialAt *primary)
+{
+ MMPortSerialAt *dial_port;
+
+ /* See if we have a cdc-wdm AT port for the interface */
+ dial_port = (mm_broadband_modem_huawei_peek_port_at_for_data (
+ MM_BROADBAND_MODEM_HUAWEI (modem), data));
+ if (dial_port)
+ return g_object_ref (dial_port);
+
+ /* Otherwise, fallback to using the primary port for dialing */
+ return g_object_ref (primary);
+}
+
+/*****************************************************************************/
+/* Connect 3GPP */
+
+typedef enum {
+ CONNECT_3GPP_CONTEXT_STEP_FIRST = 0,
+ CONNECT_3GPP_CONTEXT_STEP_NDISDUP,
+ CONNECT_3GPP_CONTEXT_STEP_NDISSTATQRY,
+ CONNECT_3GPP_CONTEXT_STEP_IP_CONFIG,
+ CONNECT_3GPP_CONTEXT_STEP_LAST
+} Connect3gppContextStep;
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ MMPort *data;
+ Connect3gppContextStep step;
+ guint check_count;
+ guint failed_ndisstatqry_count;
+ MMBearerIpConfig *ipv4_config;
+} Connect3gppContext;
+
+static void
+connect_3gpp_context_free (Connect3gppContext *ctx)
+{
+ g_object_unref (ctx->modem);
+
+ g_clear_object (&ctx->ipv4_config);
+ g_clear_object (&ctx->data);
+ g_clear_object (&ctx->primary);
+
+ g_slice_free (Connect3gppContext, ctx);
+}
+
+static MMBearerConnectResult *
+connect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void connect_3gpp_context_step (GTask *task);
+
+static void
+connect_dhcp_check_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerHuawei *self)
+{
+ GTask *task;
+ Connect3gppContext *ctx;
+ const gchar *response;
+ GError *error = NULL;
+
+ task = self->priv->connect_pending;
+ g_assert (task != NULL);
+
+ ctx = g_task_get_task_data (task);
+
+ /* Balance refcount */
+ g_object_unref (self);
+
+ /* Cache IPv4 details if available, otherwise clients will have to use DHCP */
+ response = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (response) {
+ guint address = 0;
+ guint prefix = 0;
+ guint gateway = 0;
+ guint dns1 = 0;
+ guint dns2 = 0;
+
+ if (mm_huawei_parse_dhcp_response (response,
+ &address,
+ &prefix,
+ &gateway,
+ &dns1,
+ &dns2,
+ &error)) {
+ GInetAddress *addr;
+ gchar *strarr[3] = { NULL, NULL, NULL };
+ guint n = 0;
+ gchar *str;
+
+ mm_bearer_ip_config_set_method (ctx->ipv4_config, MM_BEARER_IP_METHOD_STATIC);
+
+ addr = g_inet_address_new_from_bytes ((guint8 *)&address, G_SOCKET_FAMILY_IPV4);
+ str = g_inet_address_to_string (addr);
+ mm_bearer_ip_config_set_address (ctx->ipv4_config, str);
+ g_free (str);
+ g_object_unref (addr);
+
+ /* Netmask */
+ mm_bearer_ip_config_set_prefix (ctx->ipv4_config, prefix);
+
+ /* Gateway */
+ addr = g_inet_address_new_from_bytes ((guint8 *)&gateway, G_SOCKET_FAMILY_IPV4);
+ str = g_inet_address_to_string (addr);
+ mm_bearer_ip_config_set_gateway (ctx->ipv4_config, str);
+ g_free (str);
+ g_object_unref (addr);
+
+ /* DNS */
+ if (dns1) {
+ addr = g_inet_address_new_from_bytes ((guint8 *)&dns1, G_SOCKET_FAMILY_IPV4);
+ strarr[n++] = g_inet_address_to_string (addr);
+ g_object_unref (addr);
+ }
+ if (dns2) {
+ addr = g_inet_address_new_from_bytes ((guint8 *)&dns2, G_SOCKET_FAMILY_IPV4);
+ strarr[n++] = g_inet_address_to_string (addr);
+ g_object_unref (addr);
+ }
+ mm_bearer_ip_config_set_dns (ctx->ipv4_config, (const gchar **)strarr);
+ g_free (strarr[0]);
+ g_free (strarr[1]);
+ } else {
+ mm_obj_dbg (self, "unexpected response to ^DHCP command: %s", error->message);
+ }
+ }
+
+ g_clear_error (&error);
+ ctx->step++;
+ connect_3gpp_context_step (task);
+}
+
+static gboolean
+connect_retry_ndisstatqry_check_cb (MMBroadbandBearerHuawei *self)
+{
+ GTask *task;
+
+ /* Recover context */
+ task = self->priv->connect_pending;
+ g_assert (task != NULL);
+
+ /* Balance refcount */
+ g_object_unref (self);
+
+ /* Retry same step */
+ connect_3gpp_context_step (task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+connect_ndisstatqry_check_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerHuawei *self)
+{
+ GTask *task;
+ Connect3gppContext *ctx;
+ const gchar *response;
+ GError *error = NULL;
+ gboolean ipv4_available = FALSE;
+ gboolean ipv4_connected = FALSE;
+ gboolean ipv6_available = FALSE;
+ gboolean ipv6_connected = FALSE;
+
+ task = self->priv->connect_pending;
+ g_assert (task != NULL);
+
+ ctx = g_task_get_task_data (task);
+
+ /* Balance refcount */
+ g_object_unref (self);
+
+ response = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (!response ||
+ !mm_huawei_parse_ndisstatqry_response (response,
+ &ipv4_available,
+ &ipv4_connected,
+ &ipv6_available,
+ &ipv6_connected,
+ &error)) {
+ ctx->failed_ndisstatqry_count++;
+ mm_obj_dbg (self, "unexpected response to ^NDISSTATQRY command: %s (%u attempts so far)",
+ error->message, ctx->failed_ndisstatqry_count);
+ g_error_free (error);
+ }
+
+ /* Connected in IPv4? */
+ if (ipv4_available && ipv4_connected) {
+ /* Success! */
+ ctx->step++;
+ connect_3gpp_context_step (task);
+ return;
+ }
+
+ /* Setup timeout to retry the same step */
+ g_timeout_add_seconds (1,
+ (GSourceFunc)connect_retry_ndisstatqry_check_cb,
+ g_object_ref (self));
+}
+
+static void
+connect_ndisdup_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerHuawei *self)
+{
+ GTask *task;
+ Connect3gppContext *ctx;
+ GError *error = NULL;
+
+ task = self->priv->connect_pending;
+ g_assert (task != NULL);
+
+ ctx = g_task_get_task_data (task);
+
+ /* Balance refcount */
+ g_object_unref (self);
+
+ if (!mm_base_modem_at_command_full_finish (modem, res, &error)) {
+ /* Clear task */
+ self->priv->connect_pending = NULL;
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Go to next step */
+ ctx->step++;
+ connect_3gpp_context_step (task);
+}
+
+typedef enum {
+ MM_BEARER_HUAWEI_AUTH_UNKNOWN = -1,
+ MM_BEARER_HUAWEI_AUTH_NONE = 0,
+ MM_BEARER_HUAWEI_AUTH_PAP = 1,
+ MM_BEARER_HUAWEI_AUTH_CHAP = 2,
+ MM_BEARER_HUAWEI_AUTH_MSCHAPV2 = 3,
+} MMBearerHuaweiAuthPref;
+
+static gint
+huawei_parse_auth_type (MMBearerAllowedAuth mm_auth)
+{
+ switch (mm_auth) {
+ case MM_BEARER_ALLOWED_AUTH_NONE:
+ return MM_BEARER_HUAWEI_AUTH_NONE;
+ case MM_BEARER_ALLOWED_AUTH_PAP:
+ return MM_BEARER_HUAWEI_AUTH_PAP;
+ case MM_BEARER_ALLOWED_AUTH_CHAP:
+ return MM_BEARER_HUAWEI_AUTH_CHAP;
+ case MM_BEARER_ALLOWED_AUTH_MSCHAPV2:
+ return MM_BEARER_HUAWEI_AUTH_MSCHAPV2;
+ default:
+ case MM_BEARER_ALLOWED_AUTH_UNKNOWN:
+ case MM_BEARER_ALLOWED_AUTH_MSCHAP:
+ case MM_BEARER_ALLOWED_AUTH_EAP:
+ return MM_BEARER_HUAWEI_AUTH_UNKNOWN;
+ }
+}
+
+static void
+connect_3gpp_context_step (GTask *task)
+{
+ MMBroadbandBearerHuawei *self;
+ Connect3gppContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* Check for cancellation */
+ if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) {
+ /* Clear task */
+ self->priv->connect_pending = NULL;
+
+ /* If we already sent the connetion command, send the disconnection one */
+ if (ctx->step > CONNECT_3GPP_CONTEXT_STEP_NDISDUP)
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ "^NDISDUP=1,0",
+ MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT,
+ FALSE,
+ FALSE,
+ NULL,
+ NULL, /* Do not care the AT response */
+ NULL);
+
+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+ "Huawei connection operation has been cancelled");
+ g_object_unref (task);
+ return;
+ }
+
+ switch (ctx->step) {
+ case CONNECT_3GPP_CONTEXT_STEP_FIRST: {
+ MMBearerIpFamily ip_family;
+
+ ip_family = mm_bearer_properties_get_ip_type (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ mm_3gpp_normalize_ip_family (&ip_family);
+ if (ip_family != MM_BEARER_IP_FAMILY_IPV4) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Only IPv4 is supported by this modem");
+ g_object_unref (task);
+ return;
+ }
+
+ /* Store the task */
+ self->priv->connect_pending = task;
+
+ ctx->step++;
+ } /* fall through */
+
+ case CONNECT_3GPP_CONTEXT_STEP_NDISDUP: {
+ const gchar *apn;
+ const gchar *user;
+ const gchar *passwd;
+ MMBearerAllowedAuth auth;
+ gint encoded_auth = MM_BEARER_HUAWEI_AUTH_UNKNOWN;
+ gchar *command;
+
+ apn = mm_bearer_properties_get_apn (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ passwd = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ auth = mm_bearer_properties_get_allowed_auth (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ encoded_auth = huawei_parse_auth_type (auth);
+
+ /* Default to no authentication if not specified */
+ if (encoded_auth == MM_BEARER_HUAWEI_AUTH_UNKNOWN)
+ encoded_auth = MM_BEARER_HUAWEI_AUTH_NONE;
+
+ if (!user && !passwd)
+ command = g_strdup_printf ("AT^NDISDUP=1,1,\"%s\"",
+ apn == NULL ? "" : apn);
+ else {
+ if (encoded_auth == MM_BEARER_HUAWEI_AUTH_NONE) {
+ encoded_auth = MM_BEARER_HUAWEI_AUTH_CHAP;
+ mm_obj_dbg (self, "using default (CHAP) authentication method");
+ }
+ command = g_strdup_printf ("AT^NDISDUP=1,1,\"%s\",\"%s\",\"%s\",%d",
+ apn == NULL ? "" : apn,
+ user == NULL ? "" : user,
+ passwd == NULL ? "" : passwd,
+ encoded_auth);
+ }
+
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ 3,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback)connect_ndisdup_ready,
+ g_object_ref (self));
+ g_free (command);
+ return;
+ }
+
+ case CONNECT_3GPP_CONTEXT_STEP_NDISSTATQRY:
+ /* Wait for dial up timeout, retries for 180 times
+ * (1s between the retries, so it means 3 minutes).
+ * If too many retries, failed
+ */
+ if (ctx->check_count > MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT) {
+ /* Clear context */
+ self->priv->connect_pending = NULL;
+ g_task_return_new_error (task,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT,
+ "Connection attempt timed out");
+ g_object_unref (task);
+ return;
+ }
+
+ /* Give up if too many unexpected responses to NIDSSTATQRY are encountered. */
+ if (ctx->failed_ndisstatqry_count > 10) {
+ /* Clear context */
+ self->priv->connect_pending = NULL;
+ g_task_return_new_error (task,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED,
+ "Connection attempt not supported.");
+ g_object_unref (task);
+ return;
+ }
+
+ /* Check if connected */
+ ctx->check_count++;
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ "^NDISSTATQRY?",
+ 3,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback)connect_ndisstatqry_check_ready,
+ g_object_ref (self));
+ return;
+
+ case CONNECT_3GPP_CONTEXT_STEP_IP_CONFIG:
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ "^DHCP?",
+ 3,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback)connect_dhcp_check_ready,
+ g_object_ref (self));
+ return;
+
+ case CONNECT_3GPP_CONTEXT_STEP_LAST:
+ /* Clear context */
+ self->priv->connect_pending = NULL;
+
+ /* Setup result */
+ g_task_return_pointer (
+ task,
+ mm_bearer_connect_result_new (ctx->data, ctx->ipv4_config, NULL),
+ (GDestroyNotify)mm_bearer_connect_result_unref);
+
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+connect_3gpp (MMBroadbandBearer *_self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandBearerHuawei *self = MM_BROADBAND_BEARER_HUAWEI (_self);
+ Connect3gppContext *ctx;
+ GTask *task;
+ MMPort *data;
+
+ g_assert (primary != NULL);
+
+ /* We need a net data port */
+ data = mm_base_modem_peek_best_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET);
+ if (!data) {
+ g_task_report_new_error (self,
+ callback,
+ user_data,
+ connect_3gpp,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_NOT_FOUND,
+ "No valid data port found to launch connection");
+ return;
+ }
+
+ /* Setup connection context */
+ ctx = g_slice_new0 (Connect3gppContext);
+ ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
+ ctx->data = g_object_ref (data);
+ ctx->step = CONNECT_3GPP_CONTEXT_STEP_FIRST;
+
+ g_assert (self->priv->connect_pending == NULL);
+ g_assert (self->priv->disconnect_pending == NULL);
+
+ /* Get correct dial port to use */
+ ctx->primary = get_dial_port (MM_BROADBAND_MODEM_HUAWEI (ctx->modem), ctx->data, primary);
+
+
+ /* Default to automatic/DHCP addressing */
+ ctx->ipv4_config = mm_bearer_ip_config_new ();
+ mm_bearer_ip_config_set_method (ctx->ipv4_config, MM_BEARER_IP_METHOD_DHCP);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)connect_3gpp_context_free);
+ g_task_set_check_cancellable (task, FALSE);
+
+ /* Run! */
+ connect_3gpp_context_step (task);
+}
+
+/*****************************************************************************/
+/* Disconnect 3GPP */
+
+typedef enum {
+ DISCONNECT_3GPP_CONTEXT_STEP_FIRST = 0,
+ DISCONNECT_3GPP_CONTEXT_STEP_NDISDUP,
+ DISCONNECT_3GPP_CONTEXT_STEP_NDISSTATQRY,
+ DISCONNECT_3GPP_CONTEXT_STEP_LAST
+} Disconnect3gppContextStep;
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ Disconnect3gppContextStep step;
+ guint check_count;
+ guint failed_ndisstatqry_count;
+} Disconnect3gppContext;
+
+static void
+disconnect_3gpp_context_free (Disconnect3gppContext *ctx)
+{
+ g_object_unref (ctx->primary);
+ g_object_unref (ctx->modem);
+ g_slice_free (Disconnect3gppContext, ctx);
+}
+
+static gboolean
+disconnect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void disconnect_3gpp_context_step (GTask *task);
+
+static gboolean
+disconnect_retry_ndisstatqry_check_cb (MMBroadbandBearerHuawei *self)
+{
+ GTask *task;
+
+ /* Recover context */
+ task = self->priv->disconnect_pending;
+ g_assert (task != NULL);
+
+ /* Balance refcount */
+ g_object_unref (self);
+
+ /* Retry same step */
+ disconnect_3gpp_context_step (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+disconnect_ndisstatqry_check_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerHuawei *self)
+{
+ GTask *task;
+ Disconnect3gppContext *ctx;
+ const gchar *response;
+ GError *error = NULL;
+ gboolean ipv4_available = FALSE;
+ gboolean ipv4_connected = FALSE;
+ gboolean ipv6_available = FALSE;
+ gboolean ipv6_connected = FALSE;
+
+ task = self->priv->disconnect_pending;
+ g_assert (task != NULL);
+
+ ctx = g_task_get_task_data (task);
+
+ /* Balance refcount */
+ g_object_unref (self);
+
+ response = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (!response ||
+ !mm_huawei_parse_ndisstatqry_response (response,
+ &ipv4_available,
+ &ipv4_connected,
+ &ipv6_available,
+ &ipv6_connected,
+ &error)) {
+ ctx->failed_ndisstatqry_count++;
+ mm_obj_dbg (self, "unexpected response to ^NDISSTATQRY command: %s (%u attempts so far)",
+ error->message, ctx->failed_ndisstatqry_count);
+ g_error_free (error);
+ }
+
+ /* Disconnected IPv4? */
+ if (ipv4_available && !ipv4_connected) {
+ /* Success! */
+ ctx->step++;
+ disconnect_3gpp_context_step (task);
+ return;
+ }
+
+ /* Setup timeout to retry the same step */
+ g_timeout_add_seconds (1,
+ (GSourceFunc)disconnect_retry_ndisstatqry_check_cb,
+ g_object_ref (self));
+}
+
+static void
+disconnect_ndisdup_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerHuawei *self)
+{
+ GTask *task;
+ Disconnect3gppContext *ctx;
+
+ task = self->priv->disconnect_pending;
+ g_assert (task != NULL);
+
+ ctx = g_task_get_task_data (task);
+
+ /* Balance refcount */
+ g_object_unref (self);
+
+ /* Running NDISDUP=1,0 on an already disconnected bearer/context will
+ * return ERROR! Ignore errors in the NDISDUP disconnection command,
+ * because we're anyway going to check the bearer/context status
+ * afterwards. */
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+
+ /* Go to next step */
+ ctx->step++;
+ disconnect_3gpp_context_step (task);
+}
+
+static void
+disconnect_3gpp_context_step (GTask *task)
+{
+ MMBroadbandBearerHuawei *self;
+ Disconnect3gppContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case DISCONNECT_3GPP_CONTEXT_STEP_FIRST:
+ /* Store the task */
+ self->priv->disconnect_pending = task;
+ ctx->step++;
+ /* fall through */
+
+ case DISCONNECT_3GPP_CONTEXT_STEP_NDISDUP:
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ "^NDISDUP=1,0",
+ 3,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback)disconnect_ndisdup_ready,
+ g_object_ref (self));
+ return;
+
+ case DISCONNECT_3GPP_CONTEXT_STEP_NDISSTATQRY:
+ /* If too many retries (1s of wait between the retries), failed */
+ if (ctx->check_count > MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT) {
+ /* Clear task */
+ self->priv->disconnect_pending = NULL;
+ g_task_return_new_error (task,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT,
+ "Disconnection attempt timed out");
+ g_object_unref (task);
+ return;
+ }
+
+ /* Give up if too many unexpected responses to NIDSSTATQRY are encountered. */
+ if (ctx->failed_ndisstatqry_count > 10) {
+ /* Clear task */
+ self->priv->disconnect_pending = NULL;
+ g_task_return_new_error (task,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED,
+ "Disconnection attempt not supported.");
+ g_object_unref (task);
+ return;
+ }
+
+ /* Check if disconnected */
+ ctx->check_count++;
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ "^NDISSTATQRY?",
+ 3,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback)disconnect_ndisstatqry_check_ready,
+ g_object_ref (self));
+ return;
+
+ case DISCONNECT_3GPP_CONTEXT_STEP_LAST:
+ /* Clear task */
+ self->priv->disconnect_pending = NULL;
+ /* Set data port as result */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+disconnect_3gpp (MMBroadbandBearer *_self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandBearerHuawei *self = MM_BROADBAND_BEARER_HUAWEI (_self);
+ Disconnect3gppContext *ctx;
+ GTask *task;
+
+ g_assert (primary != NULL);
+
+ ctx = g_slice_new0 (Disconnect3gppContext);
+ ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
+ ctx->step = DISCONNECT_3GPP_CONTEXT_STEP_FIRST;
+
+ g_assert (self->priv->connect_pending == NULL);
+ g_assert (self->priv->disconnect_pending == NULL);
+
+ /* Get correct dial port to use */
+ ctx->primary = get_dial_port (MM_BROADBAND_MODEM_HUAWEI (ctx->modem), data, primary);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)disconnect_3gpp_context_free);
+
+ /* Start! */
+ disconnect_3gpp_context_step (task);
+}
+
+/*****************************************************************************/
+
+static void
+report_connection_status (MMBaseBearer *bearer,
+ MMBearerConnectionStatus status,
+ const GError *connection_error)
+{
+ MMBroadbandBearerHuawei *self = MM_BROADBAND_BEARER_HUAWEI (bearer);
+
+ g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED ||
+ status == MM_BEARER_CONNECTION_STATUS_DISCONNECTING ||
+ status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
+
+ /* When a pending connection / disconnection attempt is in progress, we use
+ * ^NDISSTATQRY? to check the connection status and thus temporarily ignore
+ * ^NDISSTAT unsolicited messages */
+ if (self->priv->connect_pending || self->priv->disconnect_pending)
+ return;
+
+ mm_obj_dbg (self, "received spontaneous ^NDISSTAT (%s)", mm_bearer_connection_status_get_string (status));
+
+ /* Ignore 'CONNECTED' */
+ if (status == MM_BEARER_CONNECTION_STATUS_CONNECTED)
+ return;
+
+ /* Report disconnected right away */
+ MM_BASE_BEARER_CLASS (mm_broadband_bearer_huawei_parent_class)->report_connection_status (
+ bearer,
+ MM_BEARER_CONNECTION_STATUS_DISCONNECTED,
+ NULL);
+}
+
+/*****************************************************************************/
+
+MMBaseBearer *
+mm_broadband_bearer_huawei_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *bearer;
+ GObject *source;
+
+ source = g_async_result_get_source_object (res);
+ bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!bearer)
+ return NULL;
+
+ /* Only export valid bearers */
+ mm_base_bearer_export (MM_BASE_BEARER (bearer));
+
+ return MM_BASE_BEARER (bearer);
+}
+
+void
+mm_broadband_bearer_huawei_new (MMBroadbandModemHuawei *modem,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (
+ MM_TYPE_BROADBAND_BEARER_HUAWEI,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_BEARER_MODEM, modem,
+ MM_BASE_BEARER_CONFIG, config,
+ NULL);
+}
+
+static void
+mm_broadband_bearer_huawei_init (MMBroadbandBearerHuawei *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_BEARER_HUAWEI,
+ MMBroadbandBearerHuaweiPrivate);
+}
+
+static void
+mm_broadband_bearer_huawei_class_init (MMBroadbandBearerHuaweiClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);
+ MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandBearerHuaweiPrivate));
+
+ base_bearer_class->report_connection_status = report_connection_status;
+ base_bearer_class->load_connection_status = NULL;
+ base_bearer_class->load_connection_status_finish = NULL;
+#if defined WITH_SUSPEND_RESUME
+ base_bearer_class->reload_connection_status = NULL;
+ base_bearer_class->reload_connection_status_finish = NULL;
+#endif
+
+ broadband_bearer_class->connect_3gpp = connect_3gpp;
+ broadband_bearer_class->connect_3gpp_finish = connect_3gpp_finish;
+ broadband_bearer_class->disconnect_3gpp = disconnect_3gpp;
+ broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish;
+}
diff --git a/src/plugins/huawei/mm-broadband-bearer-huawei.h b/src/plugins/huawei/mm-broadband-bearer-huawei.h
new file mode 100644
index 00000000..d3f43abc
--- /dev/null
+++ b/src/plugins/huawei/mm-broadband-bearer-huawei.h
@@ -0,0 +1,59 @@
+/* -*- 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) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ * Copyright (C) 2012 Huawei Technologies Co., Ltd
+ *
+ * Author: Franko Fang <huananhu@huawei.com>
+ */
+
+#ifndef MM_BROADBAND_BEARER_HUAWEI_H
+#define MM_BROADBAND_BEARER_HUAWEI_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "mm-broadband-bearer.h"
+#include "mm-broadband-modem-huawei.h"
+
+#define MM_TYPE_BROADBAND_BEARER_HUAWEI (mm_broadband_bearer_huawei_get_type ())
+#define MM_BROADBAND_BEARER_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_HUAWEI, MMBroadbandBearerHuawei))
+#define MM_BROADBAND_BEARER_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_HUAWEI, MMBroadbandBearerHuaweiClass))
+#define MM_IS_BROADBAND_BEARER_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_HUAWEI))
+#define MM_IS_BROADBAND_BEARER_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_HUAWEI))
+#define MM_BROADBAND_BEARER_HUAWEI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_HUAWEI, MMBroadbandBearerHuaweiClass))
+
+typedef struct _MMBroadbandBearerHuawei MMBroadbandBearerHuawei;
+typedef struct _MMBroadbandBearerHuaweiClass MMBroadbandBearerHuaweiClass;
+typedef struct _MMBroadbandBearerHuaweiPrivate MMBroadbandBearerHuaweiPrivate;
+
+struct _MMBroadbandBearerHuawei {
+ MMBroadbandBearer parent;
+ MMBroadbandBearerHuaweiPrivate *priv;
+};
+
+struct _MMBroadbandBearerHuaweiClass {
+ MMBroadbandBearerClass parent;
+};
+
+GType mm_broadband_bearer_huawei_get_type (void);
+
+void mm_broadband_bearer_huawei_new (MMBroadbandModemHuawei *modem,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseBearer *mm_broadband_bearer_huawei_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_BROADBAND_BEARER_HUAWEI_H */
diff --git a/src/plugins/huawei/mm-broadband-modem-huawei.c b/src/plugins/huawei/mm-broadband-modem-huawei.c
new file mode 100644
index 00000000..c7c68b9f
--- /dev/null
+++ b/src/plugins/huawei/mm-broadband-modem-huawei.c
@@ -0,0 +1,4732 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2011 - 2012 Google Inc.
+ * Copyright (C) 2012 Huawei Technologies Co., Ltd
+ * Copyright (C) 2015 Marco Bascetta <marco.bascetta@sadel.it>
+ * Copyright (C) 2012 - 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-errors-types.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-huawei.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-3gpp-ussd.h"
+#include "mm-iface-modem-location.h"
+#include "mm-iface-modem-time.h"
+#include "mm-iface-modem-cdma.h"
+#include "mm-iface-modem-signal.h"
+#include "mm-iface-modem-voice.h"
+#include "mm-broadband-modem-huawei.h"
+#include "mm-broadband-bearer-huawei.h"
+#include "mm-broadband-bearer.h"
+#include "mm-bearer-list.h"
+#include "mm-sim-huawei.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void iface_modem_cdma_init (MMIfaceModemCdma *iface);
+static void iface_modem_time_init (MMIfaceModemTime *iface);
+static void iface_modem_voice_init (MMIfaceModemVoice *iface);
+static void iface_modem_signal_init (MMIfaceModemSignal *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+static MMIfaceModemLocation *iface_modem_location_parent;
+static MMIfaceModemCdma *iface_modem_cdma_parent;
+static MMIfaceModemVoice *iface_modem_voice_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemHuawei, mm_broadband_modem_huawei, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_USSD, iface_modem_3gpp_ussd_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_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_IFACE_MODEM_VOICE, iface_modem_voice_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init))
+
+typedef enum {
+ FEATURE_SUPPORT_UNKNOWN,
+ FEATURE_NOT_SUPPORTED,
+ FEATURE_SUPPORTED
+} FeatureSupport;
+
+typedef struct {
+ MMSignal *cdma;
+ MMSignal *evdo;
+ MMSignal *gsm;
+ MMSignal *umts;
+ MMSignal *lte;
+ MMSignal *nr5g;
+} DetailedSignal;
+
+struct _MMBroadbandModemHuaweiPrivate {
+ /* Regex for signal quality related notifications */
+ GRegex *rssi_regex;
+ GRegex *rssilvl_regex;
+ GRegex *hrssilvl_regex;
+
+ /* Regex for access-technology related notifications */
+ GRegex *mode_regex;
+
+ /* Regex for connection status related notifications */
+ GRegex *dsflowrpt_regex;
+ GRegex *ndisstat_regex;
+
+ /* Regex for voice management notifications */
+ GRegex *orig_regex;
+ GRegex *conf_regex;
+ GRegex *conn_regex;
+ GRegex *cend_regex;
+ GRegex *ddtmf_regex;
+
+ /* Regex to ignore */
+ GRegex *boot_regex;
+ GRegex *connect_regex;
+ GRegex *csnr_regex;
+ GRegex *cusatp_regex;
+ GRegex *cusatend_regex;
+ GRegex *dsdormant_regex;
+ GRegex *simst_regex;
+ GRegex *srvst_regex;
+ GRegex *stin_regex;
+ GRegex *hcsq_regex;
+ GRegex *pdpdeact_regex;
+ GRegex *ndisend_regex;
+ GRegex *rfswitch_regex;
+ GRegex *position_regex;
+ GRegex *posend_regex;
+ GRegex *ecclist_regex;
+ GRegex *ltersrp_regex;
+ GRegex *cschannelinfo_regex;
+ GRegex *ccallstate_regex;
+ GRegex *eons_regex;
+ GRegex *lwurc_regex;
+
+ FeatureSupport ndisdup_support;
+ FeatureSupport rfswitch_support;
+ FeatureSupport sysinfoex_support;
+ FeatureSupport syscfg_support;
+ FeatureSupport syscfgex_support;
+ FeatureSupport prefmode_support;
+ FeatureSupport time_support;
+ FeatureSupport nwtime_support;
+ FeatureSupport cvoice_support;
+
+ MMModemLocationSource enabled_sources;
+
+ GArray *syscfg_supported_modes;
+ GArray *syscfgex_supported_modes;
+ GArray *prefmode_supported_modes;
+
+ DetailedSignal detailed_signal;
+
+ /* Voice call audio related properties */
+ guint audio_hz;
+ guint audio_bits;
+};
+
+/*****************************************************************************/
+
+GList *
+mm_broadband_modem_huawei_get_at_port_list (MMBroadbandModemHuawei *self)
+{
+ GList *out = NULL;
+ MMPortSerialAt *port;
+ GList *cdc_wdm_at_ports;
+
+ /* Primary */
+ port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+ if (port)
+ out = g_list_append (out, port);
+
+ /* Secondary */
+ port = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
+ if (port)
+ out = g_list_append (out, port);
+
+ /* Additional cdc-wdm ports used for dialing */
+ cdc_wdm_at_ports = mm_base_modem_find_ports (MM_BASE_MODEM (self),
+ MM_PORT_SUBSYS_USBMISC,
+ MM_PORT_TYPE_AT);
+
+ return g_list_concat (out, cdc_wdm_at_ports);
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ gboolean extended;
+ guint srv_status;
+ guint srv_domain;
+ guint roam_status;
+ guint sim_state;
+ guint sys_mode;
+ gboolean sys_submode_valid;
+ guint sys_submode;
+} SysinfoResult;
+
+static gboolean
+sysinfo_finish (MMBroadbandModemHuawei *self,
+ GAsyncResult *res,
+ gboolean *extended,
+ guint *srv_status,
+ guint *srv_domain,
+ guint *roam_status,
+ guint *sim_state,
+ guint *sys_mode,
+ gboolean *sys_submode_valid,
+ guint *sys_submode,
+ GError **error)
+{
+ SysinfoResult *result;
+
+ result = g_task_propagate_pointer (G_TASK (res), error);
+ if (!result)
+ return FALSE;
+
+ if (extended)
+ *extended = result->extended;
+ if (srv_status)
+ *srv_status = result->srv_status;
+ if (srv_domain)
+ *srv_domain = result->srv_domain;
+ if (roam_status)
+ *roam_status = result->roam_status;
+ if (sim_state)
+ *sim_state = result->sim_state;
+ if (sys_mode)
+ *sys_mode = result->sys_mode;
+ if (sys_submode_valid)
+ *sys_submode_valid = result->sys_submode_valid;
+ if (sys_submode)
+ *sys_submode = result->sys_submode;
+
+ g_free (result);
+ return TRUE;
+}
+
+static void
+run_sysinfo_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+ SysinfoResult *result;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ mm_obj_dbg (self, "^SYSINFO failed: %s", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ result = g_new0 (SysinfoResult, 1);
+ result->extended = FALSE;
+ if (!mm_huawei_parse_sysinfo_response (response,
+ &result->srv_status,
+ &result->srv_domain,
+ &result->roam_status,
+ &result->sys_mode,
+ &result->sim_state,
+ &result->sys_submode_valid,
+ &result->sys_submode,
+ &error)) {
+ mm_obj_dbg (self, "^SYSINFO parsing failed: %s", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ g_free (result);
+ return;
+ }
+
+ g_task_return_pointer (task, result, g_free);
+ g_object_unref (task);
+}
+
+static void
+run_sysinfo (MMBroadbandModemHuawei *self,
+ GTask *task)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SYSINFO",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)run_sysinfo_ready,
+ task);
+}
+
+static void
+run_sysinfoex_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ GError *error = NULL;
+ const gchar *response;
+ SysinfoResult *result;
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response) {
+ /* First time we try, we fallback to ^SYSINFO */
+ if (self->priv->sysinfoex_support == FEATURE_SUPPORT_UNKNOWN) {
+ self->priv->sysinfoex_support = FEATURE_NOT_SUPPORTED;
+ mm_obj_dbg (self, "^SYSINFOEX failed: %s, assuming unsupported", error->message);
+ g_error_free (error);
+ run_sysinfo (self, task);
+ return;
+ }
+
+ /* Otherwise, propagate error */
+ mm_obj_dbg (self, "^SYSINFOEX failed: %s", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ if (self->priv->sysinfoex_support == FEATURE_SUPPORT_UNKNOWN)
+ self->priv->sysinfoex_support = FEATURE_SUPPORTED;
+
+ result = g_new0 (SysinfoResult, 1);
+ result->extended = TRUE;
+ if (!mm_huawei_parse_sysinfoex_response (response,
+ &result->srv_status,
+ &result->srv_domain,
+ &result->roam_status,
+ &result->sim_state,
+ &result->sys_mode,
+ &result->sys_submode,
+ &error)) {
+ mm_obj_dbg (self, "^SYSINFOEX parsing failed: %s", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ g_free (result);
+ return;
+ }
+
+ /* Submode from SYSINFOEX always valid */
+ result->sys_submode_valid = TRUE;
+ g_task_return_pointer (task, result, g_free);
+ g_object_unref (task);
+}
+
+static void
+run_sysinfoex (MMBroadbandModemHuawei *self,
+ GTask *task)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SYSINFOEX",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)run_sysinfoex_ready,
+ task);
+}
+
+static void
+sysinfo (MMBroadbandModemHuawei *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (self->priv->sysinfoex_support == FEATURE_SUPPORT_UNKNOWN ||
+ self->priv->sysinfoex_support == FEATURE_SUPPORTED)
+ run_sysinfoex (self, task);
+ else
+ run_sysinfo (self, task);
+}
+
+/*****************************************************************************/
+/* Reset (Modem interface) */
+
+static gboolean
+reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ const gchar *command;
+
+ /* Unlike other Huawei modems that support AT^RESET for resetting the modem,
+ * Huawei MU736 supports AT^RESET but does not reset the modem upon receiving
+ * AT^RESET. It does, however, support resetting itself via AT+CFUN=16.
+ */
+ if (g_strcmp0 (mm_iface_modem_get_model (self), "MU736") == 0)
+ command = "+CFUN=16";
+ else
+ command = "^RESET";
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load access technologies (Modem interface) */
+
+static MMModemAccessTechnology
+huawei_sysinfo_submode_to_act (guint submode)
+{
+ /* new more detailed system mode/access technology */
+ switch (submode) {
+ case 1:
+ return MM_MODEM_ACCESS_TECHNOLOGY_GSM;
+ case 2:
+ return MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
+ case 3:
+ return MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
+ case 4:
+ return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ case 5:
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
+ case 6:
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSUPA;
+ case 7:
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSPA;
+ case 8: /* TD-SCDMA */
+ break;
+ case 9: /* HSPA+ */
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS;
+ case 10:
+ return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
+ case 11:
+ return MM_MODEM_ACCESS_TECHNOLOGY_EVDOA;
+ case 12:
+ return MM_MODEM_ACCESS_TECHNOLOGY_EVDOB;
+ case 13: /* 1xRTT */
+ return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
+ case 16: /* 3xRTT */
+ return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
+ case 17: /* HSPA+ (64QAM) */
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS;
+ case 18: /* HSPA+ (MIMO) */
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS;
+ default:
+ break;
+ }
+
+ return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+}
+
+static MMModemAccessTechnology
+huawei_sysinfo_mode_to_act (guint mode)
+{
+ /* Older, less detailed system mode/access technology */
+ switch (mode) {
+ case 1: /* AMPS */
+ break;
+ case 2: /* CDMA */
+ return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
+ case 3: /* GSM/GPRS */
+ return MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
+ case 4: /* HDR */
+ return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
+ case 5: /* WCDMA */
+ return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ case 6: /* GPS */
+ break;
+ case 7: /* GSM/WCDMA */
+ return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ case 8: /* CDMA/HDR hybrid */
+ return (MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 | MM_MODEM_ACCESS_TECHNOLOGY_1XRTT);
+ case 15: /* TD-SCDMA */
+ break;
+ default:
+ break;
+ }
+
+ return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+}
+
+static MMModemAccessTechnology
+huawei_sysinfoex_submode_to_act (guint submode)
+{
+ switch (submode) {
+ case 1: /* GSM */
+ return MM_MODEM_ACCESS_TECHNOLOGY_GSM;
+ case 2: /* GPRS */
+ return MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
+ case 3: /* EDGE */
+ return MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
+
+ case 21: /* IS95A */
+ return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
+ case 22: /* IS95B */
+ return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
+ case 23: /* CDMA2000 1x */
+ return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
+ case 24: /* EVDO rel0 */
+ return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
+ case 25: /* EVDO relA */
+ return MM_MODEM_ACCESS_TECHNOLOGY_EVDOA;
+ case 26: /* EVDO relB */
+ return MM_MODEM_ACCESS_TECHNOLOGY_EVDOB;
+ case 27: /* Hybrid CDMA2000 1x */
+ return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
+ case 28: /* Hybrid EVDO rel0 */
+ return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
+ case 29: /* Hybrid EVDO relA */
+ return MM_MODEM_ACCESS_TECHNOLOGY_EVDOA;
+ case 30: /* Hybrid EVDO relB */
+ return MM_MODEM_ACCESS_TECHNOLOGY_EVDOB;
+
+ case 41: /* WCDMA */
+ return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ case 42: /* HSDPA */
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
+ case 43: /* HSUPA */
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSUPA;
+ case 44: /* HSPA */
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSPA;
+ case 45: /* HSPA+ */
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS;
+ case 46: /* DC-HSPA+ */
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS;
+
+ case 61: /* TD-SCDMA */
+ break;
+
+ case 81: /* 802.16e (WiMAX) */
+ break;
+
+ case 101: /* LTE */
+ return MM_MODEM_ACCESS_TECHNOLOGY_LTE;
+
+ default:
+ break;
+ }
+
+ return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+}
+
+static MMModemAccessTechnology
+huawei_sysinfoex_mode_to_act (guint mode)
+{
+ /* Older, less detailed system mode/access technology */
+ switch (mode) {
+ case 1: /* GSM */
+ return MM_MODEM_ACCESS_TECHNOLOGY_GSM;
+ case 2: /* CDMA */
+ return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
+ case 3: /* WCDMA */
+ return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ case 4: /* TD-SCDMA */
+ break;
+ case 5: /* WIMAX */
+ break;
+ case 6: /* LTE */
+ return MM_MODEM_ACCESS_TECHNOLOGY_LTE;
+ default:
+ break;
+ }
+
+ return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+}
+
+static gboolean
+load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ GError **error)
+{
+ MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ gboolean extended = FALSE;
+ guint srv_status = 0;
+ gboolean sys_submode_valid = FALSE;
+ guint sys_submode = 0;
+ guint sys_mode = 0;
+
+ if (!sysinfo_finish (MM_BROADBAND_MODEM_HUAWEI (self),
+ res,
+ &extended,
+ &srv_status,
+ NULL, /* srv_domain */
+ NULL, /* roam_status */
+ NULL, /* sim_state */
+ &sys_mode,
+ &sys_submode_valid,
+ &sys_submode,
+ error))
+ return FALSE;
+
+ if (srv_status != 0) {
+ /* Valid service */
+ if (sys_submode_valid)
+ act = (extended ?
+ huawei_sysinfoex_submode_to_act (sys_submode) :
+ huawei_sysinfo_submode_to_act (sys_submode));
+
+ if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN)
+ act = (extended ?
+ huawei_sysinfoex_mode_to_act (sys_mode) :
+ huawei_sysinfo_mode_to_act (sys_mode));
+ }
+
+ *access_technologies = act;
+ *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY;
+ return TRUE;
+}
+
+static void
+load_access_technologies (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ sysinfo (MM_BROADBAND_MODEM_HUAWEI (self), callback, user_data);
+}
+
+/*****************************************************************************/
+/* Load unlock retries (Modem interface) */
+
+static MMUnlockRetries *
+load_unlock_retries_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ MMUnlockRetries *unlock_retries;
+ const gchar *result;
+ GError *match_error = NULL;
+ guint i;
+
+ MMModemLock locks[4] = {
+ MM_MODEM_LOCK_SIM_PUK,
+ MM_MODEM_LOCK_SIM_PIN,
+ MM_MODEM_LOCK_SIM_PUK2,
+ MM_MODEM_LOCK_SIM_PIN2
+ };
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!result)
+ return NULL;
+
+ r = g_regex_new ("\\^CPIN:\\s*([^,]+),[^,]*,(\\d+),(\\d+),(\\d+),(\\d+)",
+ G_REGEX_UNGREEDY, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, result, strlen (result), 0, 0, &match_info, &match_error)) {
+ if (match_error)
+ g_propagate_error (error, match_error);
+ else
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Could not parse ^CPIN results: Response didn't match (%s)",
+ result);
+ return NULL;
+ }
+
+ unlock_retries = mm_unlock_retries_new ();
+ for (i = 0; i <= 3; i++) {
+ guint num;
+
+ if (!mm_get_uint_from_match_info (match_info, i + 2, &num) ||
+ num > 10) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Could not parse ^CPIN results: "
+ "Missing or invalid match info for lock '%s'",
+ mm_modem_lock_get_string (locks[i]));
+ g_object_unref (unlock_retries);
+ unlock_retries = NULL;
+ break;
+ }
+
+ mm_unlock_retries_set (unlock_retries, locks[i], num);
+ }
+
+ return unlock_retries;
+}
+
+static void
+load_unlock_retries (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^CPIN?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* After SIM unlock (Modem interface) */
+
+static gboolean
+modem_after_sim_unlock_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static gboolean
+after_sim_unlock_wait_cb (GTask *task)
+{
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+modem_after_sim_unlock (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* A 3-second wait is necessary for SIM to become ready, or the firmware may
+ * fail miserably and reboot itself */
+ g_timeout_add_seconds (3, (GSourceFunc)after_sim_unlock_wait_cb, task);
+}
+
+/*****************************************************************************/
+/* Common band/mode handling code */
+
+typedef struct {
+ MMModemBand mm;
+ guint32 huawei;
+} BandTable;
+
+static BandTable bands[] = {
+ /* Sort 3G first since it's preferred */
+ { MM_MODEM_BAND_UTRAN_1, 0x00400000 },
+ { MM_MODEM_BAND_UTRAN_2, 0x00800000 },
+ { MM_MODEM_BAND_UTRAN_5, 0x04000000 },
+ { MM_MODEM_BAND_UTRAN_8, 0x00020000 },
+ /* 2G second */
+ { MM_MODEM_BAND_G850, 0x00080000 },
+ { MM_MODEM_BAND_DCS, 0x00000080 },
+ { MM_MODEM_BAND_EGSM, 0x00000100 },
+ { MM_MODEM_BAND_PCS, 0x00200000 }
+};
+
+static gboolean
+bands_array_to_huawei (GArray *bands_array,
+ guint32 *out_huawei)
+{
+ guint i;
+
+ /* Treat ANY as a special case: All huawei flags enabled */
+ if (bands_array->len == 1 &&
+ g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
+ *out_huawei = 0x3FFFFFFF;
+ return TRUE;
+ }
+
+ *out_huawei = 0;
+ for (i = 0; i < bands_array->len; i++) {
+ guint j;
+
+ for (j = 0; j < G_N_ELEMENTS (bands); j++) {
+ if (g_array_index (bands_array, MMModemBand, i) == bands[j].mm)
+ *out_huawei |= bands[j].huawei;
+ }
+ }
+
+ return (*out_huawei > 0 ? TRUE : FALSE);
+}
+
+static gboolean
+huawei_to_bands_array (guint32 huawei,
+ GArray **bands_array,
+ GError **error)
+{
+ guint i;
+
+ *bands_array = NULL;
+ for (i = 0; i < G_N_ELEMENTS (bands); i++) {
+ if (huawei & bands[i].huawei) {
+ if (G_UNLIKELY (!*bands_array))
+ *bands_array = g_array_new (FALSE, FALSE, sizeof (MMModemBand));
+ g_array_append_val (*bands_array, bands[i].mm);
+ }
+ }
+
+ if (!*bands_array) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't build bands array from '%u'",
+ huawei);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+parse_syscfg (const gchar *response,
+ GArray **bands_array,
+ GError **error)
+{
+ gint mode;
+ gint acquisition_order;
+ guint32 band;
+ gint roaming;
+ gint srv_domain;
+
+ if (!response ||
+ strncmp (response, "^SYSCFG:", 8) != 0 ||
+ !sscanf (response + 8, "%d,%d,%x,%d,%d", &mode, &acquisition_order, &band, &roaming, &srv_domain)) {
+ /* Dump error to upper layer */
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unexpected SYSCFG response: '%s'",
+ response);
+ return FALSE;
+ }
+
+ /* Band */
+ if (bands_array &&
+ !huawei_to_bands_array (band, bands_array, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Load current bands (Modem interface) */
+
+static GArray *
+load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *response;
+ GArray *bands_array = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return NULL;
+
+ if (!parse_syscfg (response, &bands_array, error))
+ return NULL;
+
+ return bands_array;
+}
+
+static void
+load_current_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SYSCFG?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Set current bands (Modem interface) */
+
+static gboolean
+set_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+syscfg_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
+ /* Let the error be critical */
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+set_current_bands (MMIfaceModem *self,
+ GArray *bands_array,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *cmd;
+ guint32 huawei_band = 0x3FFFFFFF;
+ gchar *bands_string;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands_array->data,
+ bands_array->len);
+
+ if (!bands_array_to_huawei (bands_array, &huawei_band)) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Invalid bands requested: '%s'",
+ bands_string);
+ g_object_unref (task);
+ g_free (bands_string);
+ return;
+ }
+
+ cmd = g_strdup_printf ("AT^SYSCFG=16,3,%X,2,4", huawei_band);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)syscfg_set_ready,
+ task);
+ g_free (cmd);
+ g_free (bands_string);
+}
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+syscfg_test_ready (MMBroadbandModemHuawei *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (response) {
+ /* There are 2G+3G Huawei modems out there which support mode switching with
+ * AT^SYSCFG, but fail to provide a valid response for AT^SYSCFG=? (they just
+ * return an empty string). So handle that case by providing a default response
+ * string to get parsed. Ugly, ugly, blame Huawei.
+ */
+ if (response[0])
+ self->priv->syscfg_supported_modes = mm_huawei_parse_syscfg_test (response, self, &error);
+ else {
+ self->priv->syscfg_supported_modes = mm_huawei_parse_syscfg_test (MM_HUAWEI_DEFAULT_SYSCFG_FMT, self, NULL);
+ g_assert (self->priv->syscfg_supported_modes != NULL);
+ }
+ }
+
+ if (self->priv->syscfg_supported_modes) {
+ MMModemModeCombination mode;
+ guint i;
+ GArray *combinations;
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE,
+ FALSE,
+ sizeof (MMModemModeCombination),
+ self->priv->syscfg_supported_modes->len);
+ for (i = 0; i < self->priv->syscfg_supported_modes->len; i++) {
+ MMHuaweiSyscfgCombination *huawei_mode;
+
+ huawei_mode = &g_array_index (self->priv->syscfg_supported_modes,
+ MMHuaweiSyscfgCombination,
+ i);
+ mode.allowed = huawei_mode->allowed;
+ mode.preferred = huawei_mode->preferred;
+ g_array_append_val (combinations, mode);
+ }
+
+ self->priv->syscfg_support = FEATURE_SUPPORTED;
+ g_task_return_pointer (task,
+ combinations,
+ (GDestroyNotify)g_array_unref);
+ } else {
+ mm_obj_dbg (self, "error while checking ^SYSCFG format: %s", error->message);
+ /* If SIM-PIN error, don't mark as feature unsupported; we'll retry later */
+ if (!g_error_matches (error,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN))
+ self->priv->syscfg_support = FEATURE_NOT_SUPPORTED;
+ g_task_return_error (task, error);
+ }
+
+ g_object_unref (task);
+}
+
+static void
+syscfgex_test_ready (MMBroadbandModemHuawei *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (response)
+ self->priv->syscfgex_supported_modes = mm_huawei_parse_syscfgex_test (response, &error);
+
+ if (self->priv->syscfgex_supported_modes) {
+ MMModemModeCombination mode;
+ guint i;
+ GArray *combinations;
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE,
+ FALSE,
+ sizeof (MMModemModeCombination),
+ self->priv->syscfgex_supported_modes->len);
+ for (i = 0; i < self->priv->syscfgex_supported_modes->len; i++) {
+ MMHuaweiSyscfgexCombination *huawei_mode;
+
+ huawei_mode = &g_array_index (self->priv->syscfgex_supported_modes,
+ MMHuaweiSyscfgexCombination,
+ i);
+ mode.allowed = huawei_mode->allowed;
+ mode.preferred = huawei_mode->preferred;
+ g_array_append_val (combinations, mode);
+ }
+
+ self->priv->syscfgex_support = FEATURE_SUPPORTED;
+
+ g_task_return_pointer (task,
+ combinations,
+ (GDestroyNotify)g_array_unref);
+ g_object_unref (task);
+ return;
+ }
+
+ /* If SIM-PIN error, don't mark as feature unsupported; we'll retry later */
+ if (error) {
+ mm_obj_dbg (self, "error while checking ^SYSCFGEX format: %s", error->message);
+ if (g_error_matches (error,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+ g_error_free (error);
+ }
+
+ self->priv->syscfgex_support = FEATURE_NOT_SUPPORTED;
+
+ /* Try with SYSCFG */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SYSCFG=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)syscfg_test_ready,
+ task);
+}
+
+static void
+prefmode_test_ready (MMBroadbandModemHuawei *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (response)
+ self->priv->prefmode_supported_modes = mm_huawei_parse_prefmode_test (response, self, &error);
+
+ if (self->priv->prefmode_supported_modes) {
+ MMModemModeCombination mode;
+ guint i;
+ GArray *combinations;
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE,
+ FALSE,
+ sizeof (MMModemModeCombination),
+ self->priv->prefmode_supported_modes->len);
+ for (i = 0; i < self->priv->prefmode_supported_modes->len; i++) {
+ MMHuaweiPrefmodeCombination *huawei_mode;
+
+ huawei_mode = &g_array_index (self->priv->prefmode_supported_modes,
+ MMHuaweiPrefmodeCombination,
+ i);
+ mode.allowed = huawei_mode->allowed;
+ mode.preferred = huawei_mode->preferred;
+ g_array_append_val (combinations, mode);
+ }
+
+ self->priv->prefmode_support = FEATURE_SUPPORTED;
+ g_task_return_pointer (task,
+ combinations,
+ (GDestroyNotify)g_array_unref);
+ } else {
+ mm_obj_dbg (self, "error while checking ^PREFMODE format: %s", error->message);
+ /* If SIM-PIN error, don't mark as feature unsupported; we'll retry later */
+ if (!g_error_matches (error,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN))
+ self->priv->prefmode_support = FEATURE_NOT_SUPPORTED;
+ g_task_return_error (task, error);
+ }
+
+ g_object_unref (task);
+}
+
+static void
+load_supported_modes (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (mm_iface_modem_is_cdma_only (_self)) {
+ /* ^PREFMODE only in CDMA-only modems */
+ self->priv->syscfg_support = FEATURE_NOT_SUPPORTED;
+ self->priv->syscfgex_support = FEATURE_NOT_SUPPORTED;
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^PREFMODE=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)prefmode_test_ready,
+ task);
+ return;
+ }
+
+ /* Check SYSCFGEX */
+ self->priv->prefmode_support = FEATURE_NOT_SUPPORTED;
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SYSCFGEX=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)syscfgex_test_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load initial allowed/preferred modes (Modem interface) */
+
+static gboolean
+load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ MMModemModeCombination *out;
+
+ out = g_task_propagate_pointer (G_TASK (res), error);
+ if (!out)
+ return FALSE;
+
+ *allowed = out->allowed;
+ *preferred = out->preferred;
+
+ g_free (out);
+ return TRUE;
+}
+
+static void
+prefmode_load_current_modes_ready (MMBroadbandModemHuawei *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ const MMHuaweiPrefmodeCombination *current = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (response)
+ current = mm_huawei_parse_prefmode_response (response,
+ self->priv->prefmode_supported_modes,
+ &error);
+
+ if (error)
+ g_task_return_error (task, error);
+ else {
+ MMModemModeCombination *out;
+
+ out = g_new (MMModemModeCombination, 1);
+ out->allowed = current->allowed;
+ out->preferred = current->preferred;
+ g_task_return_pointer (task, out, g_free);
+ }
+ g_object_unref (task);
+}
+
+static void
+syscfg_load_current_modes_ready (MMBroadbandModemHuawei *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ const MMHuaweiSyscfgCombination *current = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (response)
+ current = mm_huawei_parse_syscfg_response (response,
+ self->priv->syscfg_supported_modes,
+ &error);
+
+ if (error)
+ g_task_return_error (task, error);
+ else {
+ MMModemModeCombination *out;
+
+ out = g_new (MMModemModeCombination, 1);
+ out->allowed = current->allowed;
+ out->preferred = current->preferred;
+ g_task_return_pointer (task, out, g_free);
+ }
+ g_object_unref (task);
+}
+
+static void
+syscfgex_load_current_modes_ready (MMBroadbandModemHuawei *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ const MMHuaweiSyscfgexCombination *current = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (response)
+ current = mm_huawei_parse_syscfgex_response (response,
+ self->priv->syscfgex_supported_modes,
+ &error);
+ if (error)
+ g_task_return_error (task, error);
+ else {
+ MMModemModeCombination *out;
+
+ out = g_new (MMModemModeCombination, 1);
+ out->allowed = current->allowed;
+ out->preferred = current->preferred;
+ g_task_return_pointer (task, out, g_free);
+ }
+ g_object_unref (task);
+}
+
+static void
+load_current_modes (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (self->priv->syscfgex_support == FEATURE_SUPPORTED) {
+ g_assert (self->priv->syscfgex_supported_modes != NULL);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "^SYSCFGEX?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)syscfgex_load_current_modes_ready,
+ task);
+ return;
+ }
+
+ if (self->priv->syscfg_support == FEATURE_SUPPORTED) {
+ g_assert (self->priv->syscfg_supported_modes != NULL);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "^SYSCFG?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)syscfg_load_current_modes_ready,
+ task);
+ return;
+ }
+
+ if (self->priv->prefmode_support == FEATURE_SUPPORTED) {
+ g_assert (self->priv->prefmode_supported_modes != NULL);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "^PREFMODE?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)prefmode_load_current_modes_ready,
+ task);
+ return;
+ }
+
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unable to load current modes");
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Set current modes (Modem interface) */
+
+static gboolean
+set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+set_current_modes_ready (MMBroadbandModemHuawei *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error)
+ /* Let the error be critical. */
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static gboolean
+prefmode_set_current_modes (MMBroadbandModemHuawei *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GTask *task,
+ GError **error)
+{
+ guint i;
+ MMHuaweiPrefmodeCombination *found = NULL;
+ gchar *command;
+
+ for (i = 0; i < self->priv->prefmode_supported_modes->len; i++) {
+ MMHuaweiPrefmodeCombination *single;
+
+ single = &g_array_index (self->priv->prefmode_supported_modes,
+ MMHuaweiPrefmodeCombination,
+ i);
+ if (single->allowed == allowed && single->preferred == preferred) {
+ found = single;
+ break;
+ }
+ }
+
+ if (!found) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_NOT_FOUND,
+ "Requested mode ^PREFMODE combination not found");
+ return FALSE;
+ }
+
+ command = g_strdup_printf ("^PREFMODE=%u", found->prefmode);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)set_current_modes_ready,
+ task);
+ g_free (command);
+ return TRUE;
+}
+
+static gboolean
+syscfg_set_current_modes (MMBroadbandModemHuawei *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GTask *task,
+ GError **error)
+{
+ guint i;
+ MMHuaweiSyscfgCombination *found = NULL;
+ gchar *command;
+
+ for (i = 0; i < self->priv->syscfg_supported_modes->len; i++) {
+ MMHuaweiSyscfgCombination *single;
+
+ single = &g_array_index (self->priv->syscfg_supported_modes,
+ MMHuaweiSyscfgCombination,
+ i);
+ if (single->allowed == allowed && single->preferred == preferred) {
+ found = single;
+ break;
+ }
+ }
+
+ if (!found) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_NOT_FOUND,
+ "Requested mode ^SYSCFG combination not found");
+ return FALSE;
+ }
+
+ command = g_strdup_printf ("^SYSCFG=%u,%u,40000000,2,4",
+ found->mode,
+ found->acqorder);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)set_current_modes_ready,
+ task);
+ g_free (command);
+ return TRUE;
+}
+
+static gboolean
+syscfgex_set_current_modes (MMBroadbandModemHuawei *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GTask *task,
+ GError **error)
+{
+ guint i;
+ MMHuaweiSyscfgexCombination *found = NULL;
+ gchar *command;
+
+ for (i = 0; i < self->priv->syscfgex_supported_modes->len; i++) {
+ MMHuaweiSyscfgexCombination *single;
+
+ single = &g_array_index (self->priv->syscfgex_supported_modes,
+ MMHuaweiSyscfgexCombination,
+ i);
+ if (single->allowed == allowed && single->preferred == preferred) {
+ found = single;
+ break;
+ }
+ }
+
+ if (!found) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_NOT_FOUND,
+ "Requested mode ^SYSCFGEX combination not found");
+ return FALSE;
+ }
+
+ command = g_strdup_printf ("^SYSCFGEX=\"%s\",3fffffff,2,4,7fffffffffffffff,,",
+ found->mode_str);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)set_current_modes_ready,
+ task);
+ g_free (command);
+ return TRUE;
+}
+
+static void
+set_current_modes (MMIfaceModem *_self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ GTask *task;
+ GError *error = NULL;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (self->priv->syscfgex_support == FEATURE_SUPPORTED)
+ syscfgex_set_current_modes (self, allowed, preferred, task, &error);
+ else if (self->priv->syscfg_support == FEATURE_SUPPORTED)
+ syscfg_set_current_modes (self, allowed, preferred, task, &error);
+ else if (self->priv->prefmode_support == FEATURE_SUPPORTED)
+ prefmode_set_current_modes (self, allowed, preferred, task, &error);
+ else
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Setting current modes is not supported");
+
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ }
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (3GPP interface) */
+
+static void
+huawei_signal_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ guint quality = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &quality))
+ return;
+
+ if (quality == 99) {
+ /* 99 means unknown */
+ quality = 0;
+ } else {
+ /* Normalize the quality */
+ quality = MM_CLAMP_HIGH (quality, 31) * 100 / 31;
+ }
+
+ mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);
+}
+
+static void
+huawei_mode_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ gchar *str;
+ gint a;
+ guint32 mask = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+
+ str = g_match_info_fetch (match_info, 1);
+ a = atoi (str);
+ g_free (str);
+
+ /* CDMA/EVDO devices may not send this */
+ str = g_match_info_fetch (match_info, 2);
+ if (str[0])
+ act = huawei_sysinfo_submode_to_act (atoi (str));
+ g_free (str);
+
+ switch (a) {
+ case 3:
+ /* GSM/GPRS mode */
+ if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN &&
+ (act < MM_MODEM_ACCESS_TECHNOLOGY_GSM ||
+ act > MM_MODEM_ACCESS_TECHNOLOGY_EDGE)) {
+ str = mm_modem_access_technology_build_string_from_mask (act);
+ mm_obj_warn (self, "unexpected access technology (%s) in GSM/GPRS mode", str);
+ g_free (str);
+ act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ }
+ mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK;
+ break;
+
+ case 5:
+ /* WCDMA mode */
+ if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN &&
+ (act < MM_MODEM_ACCESS_TECHNOLOGY_UMTS ||
+ act > MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS)) {
+ str = mm_modem_access_technology_build_string_from_mask (act);
+ mm_obj_warn (self, "unexpected access technology (%s) in WCDMA mode", str);
+ g_free (str);
+ act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ }
+ mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK;
+ break;
+
+ case 2:
+ /* CDMA mode */
+ if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN &&
+ act != MM_MODEM_ACCESS_TECHNOLOGY_1XRTT) {
+ str = mm_modem_access_technology_build_string_from_mask (act);
+ mm_obj_warn (self, "unexpected access technology (%s) in CDMA mode", str);
+ g_free (str);
+ act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ }
+ if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN)
+ act = MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
+ mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK;
+ break;
+
+ case 4: /* HDR mode */
+ case 8: /* CDMA/HDR hybrid mode */
+ if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN &&
+ (act < MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 ||
+ act > MM_MODEM_ACCESS_TECHNOLOGY_EVDOB)) {
+ str = mm_modem_access_technology_build_string_from_mask (act);
+ mm_obj_warn (self, "unexpected access technology (%s) in EVDO mode", str);
+ g_free (str);
+ act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ }
+ if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN)
+ act = MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
+ mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK;
+ break;
+
+ case 0:
+ act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ break;
+
+ default:
+ mm_obj_warn (self, "unexpected mode change value reported: '%d'", a);
+ return;
+ }
+
+ mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), act, mask);
+}
+
+static void
+huawei_status_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ gchar *str;
+ gint n1, n2, n3, n4, n5, n6, n7;
+
+ str = g_match_info_fetch (match_info, 1);
+ if (sscanf (str, "%x,%x,%x,%x,%x,%x,%x", &n1, &n2, &n3, &n4, &n5, &n6, &n7))
+ mm_obj_dbg (self, "duration: %d up: %d Kbps down: %d Kbps total: %d total: %d\n",
+ n1, n2 * 8 / 1000, n3 * 8 / 1000, n4 / 1024, n5 / 1024);
+ g_free (str);
+}
+
+typedef struct {
+ gboolean ipv4_available;
+ gboolean ipv4_connected;
+ gboolean ipv6_available;
+ gboolean ipv6_connected;
+} NdisstatResult;
+
+static void
+bearer_report_connection_status (MMBaseBearer *bearer,
+ NdisstatResult *ndisstat_result)
+{
+ if (ndisstat_result->ipv4_available) {
+ /* TODO: MMBroadbandBearerHuawei does not currently support IPv6.
+ * When it does, we should check the IP family associated with each bearer. */
+ mm_base_bearer_report_connection_status (bearer,
+ ndisstat_result->ipv4_connected ?
+ MM_BEARER_CONNECTION_STATUS_CONNECTED :
+ MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
+ }
+}
+
+static void
+huawei_ndisstat_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ gchar *str;
+ NdisstatResult ndisstat_result;
+ GError *error = NULL;
+ MMBearerList *list = NULL;
+
+ str = g_match_info_fetch (match_info, 1);
+ if (!mm_huawei_parse_ndisstatqry_response (str,
+ &ndisstat_result.ipv4_available,
+ &ndisstat_result.ipv4_connected,
+ &ndisstat_result.ipv6_available,
+ &ndisstat_result.ipv6_connected,
+ &error)) {
+ mm_obj_dbg (self, "ignored invalid ^NDISSTAT unsolicited message '%s': %s",
+ str, error->message);
+ g_error_free (error);
+ g_free (str);
+ return;
+ }
+ g_free (str);
+
+ mm_obj_dbg (self, "NDIS status: IPv4 %s, IPv6 %s",
+ ndisstat_result.ipv4_available ?
+ (ndisstat_result.ipv4_connected ? "connected" : "disconnected") : "not available",
+ ndisstat_result.ipv6_available ?
+ (ndisstat_result.ipv6_connected ? "connected" : "disconnected") : "not available");
+
+ /* If empty bearer list, nothing else to do */
+ g_object_get (self,
+ MM_IFACE_MODEM_BEARER_LIST, &list,
+ NULL);
+ if (!list)
+ return;
+
+ mm_bearer_list_foreach (list,
+ (MMBearerListForeachFunc)bearer_report_connection_status,
+ &ndisstat_result);
+
+ g_object_unref (list);
+}
+
+static void
+detailed_signal_clear (DetailedSignal *signal)
+{
+ g_clear_object (&signal->cdma);
+ g_clear_object (&signal->evdo);
+ g_clear_object (&signal->gsm);
+ g_clear_object (&signal->umts);
+ g_clear_object (&signal->lte);
+}
+
+static gboolean
+get_rssi_dbm (guint rssi, gdouble *out_val)
+{
+ if (rssi <= 96) {
+ *out_val = (double) (-121.0 + rssi);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+get_ecio_db (guint ecio, gdouble *out_val)
+{
+ if (ecio <= 65) {
+ *out_val = -32.5 + ((double) ecio / 2.0);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+get_rsrp_dbm (guint rsrp, gdouble *out_val)
+{
+ if (rsrp <= 97) {
+ *out_val = (double) (-141.0 + rsrp);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+get_sinr_db (guint sinr, gdouble *out_val)
+{
+ if (sinr <= 251) {
+ *out_val = -20.2 + (double) (sinr / 5.0);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+get_rsrq_db (guint rsrq, gdouble *out_val)
+{
+ if (rsrq <= 34) {
+ *out_val = -20 + (double) (rsrq / 2.0);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+huawei_hcsq_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ gchar *str;
+ MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ guint value1 = 0;
+ guint value2 = 0;
+ guint value3 = 0;
+ guint value4 = 0;
+ guint value5 = 0;
+ gdouble v;
+ GError *error = NULL;
+
+ str = g_match_info_fetch (match_info, 1);
+ if (!mm_huawei_parse_hcsq_response (str,
+ &act,
+ &value1,
+ &value2,
+ &value3,
+ &value4,
+ &value5,
+ &error)) {
+ mm_obj_dbg (self, "ignored invalid ^HCSQ message '%s': %s", str, error->message);
+ g_error_free (error);
+ g_free (str);
+ return;
+ }
+ g_free (str);
+
+ detailed_signal_clear (&self->priv->detailed_signal);
+
+ /* 2G */
+ if (act == MM_MODEM_ACCESS_TECHNOLOGY_GSM) {
+ self->priv->detailed_signal.gsm = mm_signal_new ();
+ /* value1: gsm_rssi */
+ if (get_rssi_dbm (value1, &v))
+ mm_signal_set_rssi (self->priv->detailed_signal.gsm, v);
+ return;
+ }
+
+ /* 3G */
+ if (act == MM_MODEM_ACCESS_TECHNOLOGY_UMTS) {
+ self->priv->detailed_signal.umts = mm_signal_new ();
+ /* value1: wcdma_rssi */
+ if (get_rssi_dbm (value1, &v))
+ mm_signal_set_rssi (self->priv->detailed_signal.umts, v);
+ /* value2: wcdma_rscp; unused */
+ /* value3: wcdma_ecio */
+ if (get_ecio_db (value3, &v))
+ mm_signal_set_ecio (self->priv->detailed_signal.umts, v);
+ return;
+ }
+
+ /* 4G */
+ if (act == MM_MODEM_ACCESS_TECHNOLOGY_LTE) {
+ self->priv->detailed_signal.lte = mm_signal_new ();
+ /* value1: lte_rssi */
+ if (get_rssi_dbm (value1, &v))
+ mm_signal_set_rssi (self->priv->detailed_signal.lte, v);
+ /* value2: lte_rsrp */
+ if (get_rsrp_dbm (value2, &v))
+ mm_signal_set_rsrp (self->priv->detailed_signal.lte, v);
+ /* value3: lte_sinr -> SNR? */
+ if (get_sinr_db (value3, &v))
+ mm_signal_set_snr (self->priv->detailed_signal.lte, v);
+ /* value4: lte_rsrq */
+ if (get_rsrq_db (value4, &v))
+ mm_signal_set_rsrq (self->priv->detailed_signal.lte, v);
+ return;
+ }
+
+ /* CDMA and EVDO not yet supported */
+}
+
+static void
+set_3gpp_unsolicited_events_handlers (MMBroadbandModemHuawei *self,
+ gboolean enable)
+{
+ GList *ports, *l;
+
+ ports = mm_broadband_modem_huawei_get_at_port_list (self);
+
+ /* Enable/disable unsolicited events in given port */
+ for (l = ports; l; l = g_list_next (l)) {
+ MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data);
+
+ /* Signal quality related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->rssi_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_signal_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ /* Access technology related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->mode_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_mode_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ /* Connection status related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->dsflowrpt_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_status_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->ndisstat_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_ndisstat_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->hcsq_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_hcsq_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+
+ g_list_free_full (ports, g_object_unref);
+}
+
+static gboolean
+modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_3gpp_setup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else {
+ /* Our own setup now */
+ set_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), TRUE);
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's setup */
+ iface_modem_3gpp_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_3gpp_setup_unsolicited_events_ready,
+ task);
+}
+
+static void
+parent_3gpp_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Our own cleanup first */
+ set_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE);
+
+ /* And now chain up parent's cleanup */
+ iface_modem_3gpp_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_3gpp_cleanup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Enabling unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+own_enable_unsolicited_events_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_full_finish (self, res, NULL, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static const MMBaseModemAtCommand unsolicited_enable_sequence[] = {
+ /* With ^PORTSEL we specify whether we want the PCUI port (0) or the
+ * modem port (1) to receive the unsolicited messages */
+ { "^PORTSEL=0", 5, FALSE, NULL },
+ { "^CURC=1", 3, FALSE, NULL },
+ { NULL }
+};
+
+static void
+parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ }
+
+ /* Our own enable now */
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ unsolicited_enable_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)own_enable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's enable */
+ iface_modem_3gpp_parent->enable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_enable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Disabling unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+own_disable_unsolicited_events_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_full_finish (self, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Next, chain up parent's disable */
+ iface_modem_3gpp_parent->disable_unsolicited_events (
+ MM_IFACE_MODEM_3GPP (self),
+ (GAsyncReadyCallback)parent_disable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Our own disable first */
+ mm_base_modem_at_command_full (
+ MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ "^CURC=0",
+ 5,
+ FALSE, /* allow_cached */
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)own_disable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Create Bearer (Modem interface) */
+
+static MMBaseBearer *
+huawei_modem_create_bearer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+broadband_bearer_huawei_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_huawei_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+ g_object_unref (task);
+}
+
+static void
+broadband_bearer_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+ g_object_unref (task);
+}
+
+static void
+create_bearer_for_net_port (GTask *task)
+{
+ MMBroadbandModemHuawei *self;
+ MMBearerProperties *properties;
+
+ self = g_task_get_source_object (task);
+ properties = g_task_get_task_data (task);
+
+ switch (self->priv->ndisdup_support) {
+ case FEATURE_NOT_SUPPORTED:
+ mm_obj_dbg (self, "^NDISDUP not supported, creating default bearer...");
+ mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
+ properties,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_new_ready,
+ task);
+ return;
+ case FEATURE_SUPPORTED:
+ mm_obj_dbg (self, "^NDISDUP supported, creating huawei bearer...");
+ mm_broadband_bearer_huawei_new (MM_BROADBAND_MODEM_HUAWEI (self),
+ properties,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_huawei_new_ready,
+ task);
+ return;
+ case FEATURE_SUPPORT_UNKNOWN:
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static MMPortSerialAt *
+peek_port_at_for_data (MMBroadbandModemHuawei *self,
+ MMPort *port)
+{
+ GList *cdc_wdm_at_ports, *l;
+ const gchar *net_port_parent_path;
+ MMPortSerialAt *found = NULL;
+
+ g_warn_if_fail (mm_port_get_subsys (port) == MM_PORT_SUBSYS_NET);
+ net_port_parent_path = mm_kernel_device_get_interface_sysfs_path (mm_port_peek_kernel_device (port));
+ if (!net_port_parent_path) {
+ mm_obj_warn (self, "no parent path for net port %s", mm_port_get_device (port));
+ return NULL;
+ }
+
+ /* Find the CDC-WDM port on the same USB interface as the given net port */
+ cdc_wdm_at_ports = mm_base_modem_find_ports (MM_BASE_MODEM (self),
+ MM_PORT_SUBSYS_USBMISC,
+ MM_PORT_TYPE_AT);
+ for (l = cdc_wdm_at_ports; l && !found; l = g_list_next (l)) {
+ const gchar *wdm_port_parent_path;
+
+ g_assert (MM_IS_PORT_SERIAL_AT (l->data));
+ wdm_port_parent_path = mm_kernel_device_get_interface_sysfs_path (mm_port_peek_kernel_device (MM_PORT (l->data)));
+ if (wdm_port_parent_path && g_str_equal (wdm_port_parent_path, net_port_parent_path))
+ found = MM_PORT_SERIAL_AT (l->data);
+ }
+
+ g_list_free_full (cdc_wdm_at_ports, g_object_unref);
+ return found;
+}
+
+
+MMPortSerialAt *
+mm_broadband_modem_huawei_peek_port_at_for_data (MMBroadbandModemHuawei *self,
+ MMPort *port)
+{
+ MMPortSerialAt *found;
+
+ g_assert (self->priv->ndisdup_support == FEATURE_SUPPORTED);
+
+ found = peek_port_at_for_data (self, port);
+ if (!found)
+ mm_obj_dbg (self, "couldn't find associated cdc-wdm port for %s", mm_port_get_device (port));
+ return found;
+}
+
+static void
+ensure_ndisdup_support_checked (MMBroadbandModemHuawei *self,
+ MMPort *port)
+{
+ /* Check NDISDUP support the first time we need it */
+ if (self->priv->ndisdup_support != FEATURE_SUPPORT_UNKNOWN)
+ return;
+
+ /* First, check for devices which support NDISDUP on any AT port. These
+ * devices are tagged by udev */
+ if (mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (port), "ID_MM_HUAWEI_NDISDUP_SUPPORTED")) {
+ mm_obj_dbg (self, "^NDISDUP is supported");
+ self->priv->ndisdup_support = FEATURE_SUPPORTED;
+ }
+ /* Then, look for devices which have both a net port and a cdc-wdm
+ * AT-capable port. We assume that these devices allow NDISDUP only
+ * when issued in the cdc-wdm port. */
+ else if (peek_port_at_for_data (self, port)) {
+ mm_obj_dbg (self, "^NDISDUP is supported on non-serial AT port");
+ self->priv->ndisdup_support = FEATURE_SUPPORTED;
+ }
+
+ if (self->priv->ndisdup_support != FEATURE_SUPPORT_UNKNOWN)
+ return;
+
+ mm_obj_dbg (self, "^NDISDUP is not supported");
+ self->priv->ndisdup_support = FEATURE_NOT_SUPPORTED;
+}
+
+static void
+huawei_modem_create_bearer (MMIfaceModem *self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ MMPort *port;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, g_object_ref (properties), g_object_unref);
+
+ port = mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET);
+ if (port) {
+ ensure_ndisdup_support_checked (MM_BROADBAND_MODEM_HUAWEI (self), port);
+ create_bearer_for_net_port (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "creating default bearer...");
+ mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
+ properties,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_new_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* USSD encode/decode (3GPP-USSD interface)
+ *
+ * Huawei devices don't use the current charset (as per AT+CSCS) in the CUSD
+ * command, they instead expect data encoded in GSM-7 already, given as a
+ * hex string.
+ */
+
+static gchar *
+encode (MMIfaceModem3gppUssd *self,
+ const gchar *command,
+ guint *scheme,
+ GError **error)
+{
+ g_autoptr(GByteArray) gsm = NULL;
+ g_autofree guint8 *packed = NULL;
+ guint32 packed_len = 0;
+
+ gsm = mm_modem_charset_bytearray_from_utf8 (command, MM_MODEM_CHARSET_GSM, FALSE, error);
+ if (!gsm)
+ return NULL;
+
+ *scheme = MM_MODEM_GSM_USSD_SCHEME_7BIT;
+
+ /* If command is a multiple of 7 characters long, Huawei firmwares
+ * apparently want that padded. Maybe all modems?
+ */
+ if (gsm->len % 7 == 0) {
+ static const guint8 padding = 0x0d;
+
+ g_byte_array_append (gsm, &padding, 1);
+ }
+
+ packed = mm_charset_gsm_pack (gsm->data, gsm->len, 0, &packed_len);
+ return mm_utils_bin2hexstr (packed, packed_len);
+}
+
+static gchar *
+decode (MMIfaceModem3gppUssd *self,
+ const gchar *reply,
+ GError **error)
+{
+ g_autofree guint8 *bin = NULL;
+ gsize bin_len = 0;
+ g_autofree guint8 *unpacked = NULL;
+ guint32 unpacked_len;
+ g_autoptr(GByteArray) unpacked_array = NULL;
+
+ bin = mm_utils_hexstr2bin (reply, -1, &bin_len, error);
+ if (!bin)
+ return NULL;
+
+ unpacked = mm_charset_gsm_unpack (bin, (bin_len * 8) / 7, 0, &unpacked_len);
+ /* if the last character in a 7-byte block is padding, then drop it */
+ if ((bin_len % 7 == 0) && (unpacked[unpacked_len - 1] == 0x0d))
+ unpacked_len--;
+
+ unpacked_array = g_byte_array_sized_new (unpacked_len);
+ g_byte_array_append (unpacked_array, unpacked, unpacked_len);
+
+ return mm_modem_charset_bytearray_to_utf8 (unpacked_array, MM_MODEM_CHARSET_GSM, FALSE, error);
+}
+
+/*****************************************************************************/
+
+static void
+huawei_1x_signal_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ guint quality = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &quality))
+ return;
+
+ quality = MM_CLAMP_HIGH (quality, 100);
+ mm_obj_dbg (self, "1X signal quality: %u", quality);
+ mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);
+}
+
+static void
+huawei_evdo_signal_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ guint quality = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &quality))
+ return;
+
+ quality = MM_CLAMP_HIGH (quality, 100);
+ mm_obj_dbg (self, "EVDO signal quality: %u", quality);
+ mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);
+}
+
+/* Signal quality loading (Modem interface) */
+
+static guint
+modem_load_signal_quality_finish (MMIfaceModem *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 0;
+ }
+ return (guint)value;
+}
+
+static void
+parent_load_signal_quality_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ guint signal_quality;
+
+ signal_quality = iface_modem_parent->load_signal_quality_finish (self, res, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_int (task, signal_quality);
+ g_object_unref (task);
+}
+
+static void
+signal_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response, *command;
+ gchar buf[5];
+ guint quality = 0, i = 0;
+
+ response = mm_base_modem_at_command_finish (self, res, NULL);
+ if (!response) {
+ /* Fallback to parent's method */
+ iface_modem_parent->load_signal_quality (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_signal_quality_ready,
+ task);
+ return;
+ }
+
+ command = g_task_get_task_data (task);
+ g_assert (command);
+ response = mm_strip_tag (response, command);
+ /* 'command' won't include the trailing ':' in the response, so strip that */
+ while ((*response == ':') || isspace (*response))
+ response++;
+
+ /* Sanitize response for mm_get_uint_from_str() which wants only digits */
+ memset (buf, 0, sizeof (buf));
+ while (i < (sizeof (buf) - 1) && isdigit (*response))
+ buf[i++] = *response++;
+
+ if (mm_get_uint_from_str (buf, &quality)) {
+ quality = MM_CLAMP_HIGH (quality, 100);
+ g_task_return_int (task, quality);
+ } else {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse %s response: '%s'",
+ command, response);
+ }
+
+ g_object_unref (task);
+}
+
+static void
+modem_load_signal_quality (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+ const char *command = "^CSQLVL";
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* 3GPP modems can just run parent's signal quality loading */
+ if (mm_iface_modem_is_3gpp (self)) {
+ iface_modem_parent->load_signal_quality (
+ self,
+ (GAsyncReadyCallback)parent_load_signal_quality_ready,
+ task);
+ return;
+ }
+
+ /* CDMA modems need custom signal quality loading */
+
+ g_object_get (G_OBJECT (self),
+ MM_IFACE_MODEM_CDMA_EVDO_REGISTRATION_STATE, &evdo_state,
+ NULL);
+ if (evdo_state > MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
+ command = "^HDRCSQLVL";
+
+ g_task_set_task_data (task, g_strdup (command), g_free);
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)signal_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (CDMA interface) */
+
+static void
+set_cdma_unsolicited_events_handlers (MMBroadbandModemHuawei *self,
+ gboolean enable)
+{
+ GList *ports, *l;
+
+ ports = mm_broadband_modem_huawei_get_at_port_list (self);
+
+ /* Enable/disable unsolicited events in given port */
+ for (l = ports; l; l = g_list_next (l)) {
+ MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data);
+
+ /* Signal quality related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->rssilvl_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_1x_signal_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->hrssilvl_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_evdo_signal_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+ /* Access technology related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->mode_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_mode_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+
+ g_list_free_full (ports, g_object_unref);
+}
+
+static gboolean
+modem_cdma_setup_cleanup_unsolicited_events_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_cdma_setup_unsolicited_events_ready (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_cdma_parent->setup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else {
+ /* Our own setup now */
+ set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), TRUE);
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_cdma_setup_unsolicited_events (MMIfaceModemCdma *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's setup if needed */
+ if (iface_modem_cdma_parent->setup_unsolicited_events &&
+ iface_modem_cdma_parent->setup_unsolicited_events_finish) {
+ iface_modem_cdma_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_cdma_setup_unsolicited_events_ready,
+ task);
+ return;
+ }
+
+ /* Otherwise just run our setup and complete */
+ set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), TRUE);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_cdma_cleanup_unsolicited_events_ready (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_cdma_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_cdma_cleanup_unsolicited_events (MMIfaceModemCdma *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Our own cleanup first */
+ set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE);
+
+ /* Chain up parent's setup if needed */
+ if (iface_modem_cdma_parent->cleanup_unsolicited_events &&
+ iface_modem_cdma_parent->cleanup_unsolicited_events_finish) {
+ iface_modem_cdma_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_cdma_cleanup_unsolicited_events_ready,
+ task);
+ return;
+ }
+
+ /* Otherwise we're done */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Setup registration checks (CDMA interface) */
+
+typedef struct {
+ gboolean skip_qcdm_call_manager_step;
+ gboolean skip_qcdm_hdr_step;
+ gboolean skip_at_cdma_service_status_step;
+ gboolean skip_at_cdma1x_serving_system_step;
+ gboolean skip_detailed_registration_state;
+} SetupRegistrationChecksResults;
+
+static gboolean
+setup_registration_checks_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ gboolean *skip_qcdm_call_manager_step,
+ gboolean *skip_qcdm_hdr_step,
+ gboolean *skip_at_cdma_service_status_step,
+ gboolean *skip_at_cdma1x_serving_system_step,
+ gboolean *skip_detailed_registration_state,
+ GError **error)
+{
+ SetupRegistrationChecksResults *results;
+
+ results = g_task_propagate_pointer (G_TASK (res), error);
+ if (!results)
+ return FALSE;
+
+ *skip_qcdm_call_manager_step = results->skip_qcdm_call_manager_step;
+ *skip_qcdm_hdr_step = results->skip_qcdm_hdr_step;
+ *skip_at_cdma_service_status_step = results->skip_at_cdma_service_status_step;
+ *skip_at_cdma1x_serving_system_step = results->skip_at_cdma1x_serving_system_step;
+ *skip_detailed_registration_state = results->skip_detailed_registration_state;
+ g_free (results);
+ return TRUE;
+}
+
+static void
+parent_setup_registration_checks_ready (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetupRegistrationChecksResults *results;
+ GError *error = NULL;
+
+ results = g_new0 (SetupRegistrationChecksResults, 1);
+
+ if (!iface_modem_cdma_parent->setup_registration_checks_finish (self,
+ res,
+ &results->skip_qcdm_call_manager_step,
+ &results->skip_qcdm_hdr_step,
+ &results->skip_at_cdma_service_status_step,
+ &results->skip_at_cdma1x_serving_system_step,
+ &results->skip_detailed_registration_state,
+ &error)) {
+ g_free (results);
+ g_task_return_error (task, error);
+ } else {
+ gboolean evdo_supported = FALSE;
+
+ g_object_get (self,
+ MM_IFACE_MODEM_CDMA_EVDO_NETWORK_SUPPORTED, &evdo_supported,
+ NULL);
+
+ /* Don't use AT+CSS on EVDO-capable hardware for determining registration
+ * status, because often the device will have only an EVDO connection and
+ * AT+CSS won't necessarily report EVDO registration status, only 1X.
+ */
+ if (evdo_supported)
+ results->skip_at_cdma1x_serving_system_step = TRUE;
+
+ /* Force to always use the detailed registration checks, as we have
+ * ^SYSINFO for that */
+ results->skip_detailed_registration_state = FALSE;
+
+ g_task_return_pointer (task, results, g_free);
+ }
+
+ g_object_unref (task);
+}
+
+static void
+setup_registration_checks (MMIfaceModemCdma *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Run parent's checks first */
+ iface_modem_cdma_parent->setup_registration_checks (self,
+ (GAsyncReadyCallback)parent_setup_registration_checks_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Detailed registration state (CDMA interface) */
+
+typedef struct {
+ MMModemCdmaRegistrationState detailed_cdma1x_state;
+ MMModemCdmaRegistrationState detailed_evdo_state;
+} DetailedRegistrationStateResults;
+
+typedef struct {
+ DetailedRegistrationStateResults state;
+} DetailedRegistrationStateContext;
+
+static gboolean
+get_detailed_registration_state_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ MMModemCdmaRegistrationState *detailed_cdma1x_state,
+ MMModemCdmaRegistrationState *detailed_evdo_state,
+ GError **error)
+{
+ DetailedRegistrationStateResults *results;
+
+ results = g_task_propagate_pointer (G_TASK (res), error);
+ if (!results)
+ return FALSE;
+
+ *detailed_cdma1x_state = results->detailed_cdma1x_state;
+ *detailed_evdo_state = results->detailed_evdo_state;
+ g_free (results);
+ return TRUE;
+}
+
+static void
+registration_state_sysinfo_ready (MMBroadbandModemHuawei *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ DetailedRegistrationStateContext *ctx;
+ gboolean extended = FALSE;
+ guint srv_status = 0;
+ guint sys_mode = 0;
+ guint roam_status = 0;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!sysinfo_finish (self,
+ res,
+ &extended,
+ &srv_status,
+ NULL, /* srv_domain */
+ &roam_status,
+ NULL, /* sim_state */
+ &sys_mode,
+ NULL, /* sys_submode_valid */
+ NULL, /* sys_submode */
+ NULL)) {
+ /* If error, leave superclass' reg state alone if ^SYSINFO isn't supported. */
+ g_task_return_pointer (task,
+ g_memdup (&ctx->state, sizeof (ctx->state)),
+ g_free);
+ g_object_unref (task);
+ return;
+ }
+
+ if (srv_status == 2) {
+ MMModemCdmaRegistrationState reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
+ MMModemAccessTechnology act;
+ gboolean cdma1x = FALSE;
+ gboolean evdo = FALSE;
+
+ /* Service available, check roaming state */
+ if (roam_status == 0)
+ reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_HOME;
+ else if (roam_status == 1)
+ reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING;
+
+ /* Check service type */
+ act = (extended ?
+ huawei_sysinfoex_mode_to_act (sys_mode):
+ huawei_sysinfo_mode_to_act (sys_mode));
+
+ if (act & MM_MODEM_ACCESS_TECHNOLOGY_1XRTT) {
+ cdma1x = TRUE;
+ ctx->state.detailed_cdma1x_state = reg_state;
+ }
+
+ if (act & MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 ||
+ act & MM_MODEM_ACCESS_TECHNOLOGY_EVDOA ||
+ act & MM_MODEM_ACCESS_TECHNOLOGY_EVDOB) {
+ evdo = TRUE;
+ ctx->state.detailed_evdo_state = reg_state;
+ }
+
+ if (!cdma1x && !evdo) {
+ /* Say we're registered to something even though sysmode parsing failed */
+ mm_obj_dbg (self, "assuming registered at least in CDMA1x");
+ ctx->state.detailed_cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
+ }
+ }
+
+ g_task_return_pointer (task,
+ g_memdup (&ctx->state, sizeof (ctx->state)),
+ g_free);
+ g_object_unref (task);
+}
+
+static void
+get_detailed_registration_state (MMIfaceModemCdma *self,
+ MMModemCdmaRegistrationState cdma1x_state,
+ MMModemCdmaRegistrationState evdo_state,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ DetailedRegistrationStateContext *ctx;
+ GTask *task;
+
+ /* Setup context */
+ ctx = g_new (DetailedRegistrationStateContext, 1);
+ ctx->state.detailed_cdma1x_state = cdma1x_state;
+ ctx->state.detailed_evdo_state = evdo_state;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ sysinfo (MM_BROADBAND_MODEM_HUAWEI (self),
+ (GAsyncReadyCallback)registration_state_sysinfo_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Check if Voice supported (Voice interface) */
+
+static gboolean
+modem_voice_check_support_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+voice_parent_check_support_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ gboolean parent_support;
+
+ parent_support = iface_modem_voice_parent->check_support_finish (self, res, NULL);
+ g_task_return_boolean (task, parent_support);
+ g_object_unref (task);
+}
+
+static void
+cvoice_check_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ GError *error = NULL;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response ||
+ !mm_huawei_parse_cvoice_response (response,
+ &self->priv->audio_hz,
+ &self->priv->audio_bits,
+ &error)) {
+ self->priv->cvoice_support = FEATURE_NOT_SUPPORTED;
+ mm_obj_dbg (self, "CVOICE is unsupported: %s", error->message);
+ g_clear_error (&error);
+
+ /* Now check generic support */
+ iface_modem_voice_parent->check_support (MM_IFACE_MODEM_VOICE (self),
+ (GAsyncReadyCallback)voice_parent_check_support_ready,
+ task);
+ return;
+ }
+
+ mm_obj_dbg (self, "CVOICE is supported");
+ self->priv->cvoice_support = FEATURE_SUPPORTED;
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_voice_check_support (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ /* Check for Huawei-specific ^CVOICE support */
+ task = g_task_new (self, NULL, callback, user_data);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^CVOICE?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)cvoice_check_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* In-call audio channel setup/cleanup */
+
+static gboolean
+modem_voice_cleanup_in_call_audio_channel_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+modem_voice_cleanup_in_call_audio_channel (MMIfaceModemVoice *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* If there is no CVOICE support, no custom audio setup required
+ * (i.e. audio path is externally managed) */
+ if (self->priv->cvoice_support == FEATURE_SUPPORTED) {
+ MMPort *port;
+
+ /* The QCDM port, if present, switches back from voice to QCDM after
+ * the voice call is dropped. */
+ port = MM_PORT (mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self)));
+ if (port) {
+ /* During a voice call, we'll set the QCDM port as connected, and that
+ * will make us ignore all incoming data and avoid sending any outgoing
+ * data. */
+ mm_port_set_connected (port, FALSE);
+ }
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static gboolean
+modem_voice_setup_in_call_audio_channel_finish (MMIfaceModemVoice *_self,
+ GAsyncResult *res,
+ MMPort **audio_port,
+ MMCallAudioFormat **audio_format,
+ GError **error)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return FALSE;
+
+ if (self->priv->cvoice_support == FEATURE_SUPPORTED) {
+ MMPort *port;
+
+ /* Setup audio format */
+ if (audio_format) {
+ gchar *resolution_str;
+
+ resolution_str = g_strdup_printf ("s%ule", self->priv->audio_bits);
+ *audio_format = mm_call_audio_format_new ();
+ mm_call_audio_format_set_encoding (*audio_format, "pcm");
+ mm_call_audio_format_set_resolution (*audio_format, resolution_str);
+ mm_call_audio_format_set_rate (*audio_format, self->priv->audio_hz);
+ g_free (resolution_str);
+ }
+
+ /* The QCDM port, if present, switches from QCDM to voice while
+ * a voice call is active. */
+ port = MM_PORT (mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self)));
+ if (port) {
+ /* During a voice call, we'll set the QCDM port as connected, and that
+ * will make us ignore all incoming data and avoid sending any outgoing
+ * data. */
+ mm_port_set_connected (port, TRUE);
+ }
+
+ if (audio_port)
+ *audio_port = (port ? g_object_ref (port) : NULL);;
+ } else {
+ if (audio_format)
+ *audio_format = NULL;
+ if (audio_port)
+ *audio_port = NULL;
+ }
+
+ return TRUE;
+}
+
+static void
+ddsetex_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
+modem_voice_setup_in_call_audio_channel (MMIfaceModemVoice *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* If there is no CVOICE support, no custom audio setup required
+ * (i.e. audio path is externally managed) */
+ if (self->priv->cvoice_support != FEATURE_SUPPORTED) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Enable audio streaming on the audio port */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^DDSETEX=2",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)ddsetex_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Common setup/cleanup voice unsolicited events */
+
+typedef enum {
+ HUAWEI_CALL_TYPE_VOICE = 0,
+ HUAWEI_CALL_TYPE_CS_DATA = 1,
+ HUAWEI_CALL_TYPE_PS_DATA = 2,
+ HUAWEI_CALL_TYPE_CDMA_SMS = 3,
+ HUAWEI_CALL_TYPE_OTA_STANDARD_OTASP = 7,
+ HUAWEI_CALL_TYPE_OTA_NON_STANDARD_OTASP = 8,
+ HUAWEI_CALL_TYPE_EMERGENCY = 9,
+} HuaweiCallType;
+
+static void
+orig_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ MMCallInfo call_info = { 0 };
+ guint aux = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 2, &aux)) {
+ mm_obj_warn (self, "couldn't parse call type from ^ORIG");
+ return;
+ }
+ if (aux != HUAWEI_CALL_TYPE_VOICE && aux != HUAWEI_CALL_TYPE_EMERGENCY) {
+ mm_obj_dbg (self, "ignored ^ORIG for non-voice call");
+ return;
+ }
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &aux)) {
+ mm_obj_warn (self, "couldn't parse call index from ^ORIG");
+ return;
+ }
+ call_info.index = aux;
+ call_info.state = MM_CALL_STATE_DIALING;
+ call_info.direction = MM_CALL_DIRECTION_OUTGOING;
+
+ mm_obj_dbg (self, "call %u state updated: dialing", call_info.index);
+
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
+}
+
+static void
+conf_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ MMCallInfo call_info = { 0 };
+ guint aux = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &aux)) {
+ mm_obj_warn (self, "couldn't parse call index from ^CONF");
+ return;
+ }
+ call_info.index = aux;
+ call_info.state = MM_CALL_STATE_RINGING_OUT;
+ call_info.direction = MM_CALL_DIRECTION_OUTGOING;
+
+ mm_obj_dbg (self, "call %u state updated: ringing-out", call_info.index);
+
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
+}
+
+static void
+conn_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ MMCallInfo call_info = { 0 };
+ guint aux = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &aux)) {
+ mm_obj_warn (self, "couldn't parse call index from ^CONN");
+ return;
+ }
+ call_info.index = aux;
+ call_info.state = MM_CALL_STATE_ACTIVE;
+ call_info.direction = MM_CALL_DIRECTION_UNKNOWN;
+
+ mm_obj_dbg (self, "call %u state updated: active", aux);
+
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
+}
+
+static void
+cend_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ MMCallInfo call_info = { 0 };
+ guint aux = 0;
+
+ /* only index is mandatory */
+ if (!mm_get_uint_from_match_info (match_info, 1, &aux)) {
+ mm_obj_warn (self, "couldn't parse call index from ^CEND");
+ return;
+ }
+ call_info.index = aux;
+ call_info.state = MM_CALL_STATE_TERMINATED;
+ call_info.direction = MM_CALL_DIRECTION_UNKNOWN;
+
+ mm_obj_dbg (self, "call %u state updated: terminated", call_info.index);
+ if (mm_get_uint_from_match_info (match_info, 2, &aux))
+ mm_obj_dbg (self, " call duration: %u seconds", aux);
+ if (mm_get_uint_from_match_info (match_info, 3, &aux))
+ mm_obj_dbg (self, " end status code: %u", aux);
+ if (mm_get_uint_from_match_info (match_info, 4, &aux))
+ mm_obj_dbg (self, " call control cause: %u", aux);
+
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
+}
+
+static void
+ddtmf_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHuawei *self)
+{
+ gchar *dtmf;
+
+ dtmf = g_match_info_fetch (match_info, 1);
+ mm_obj_dbg (self, "received DTMF: %s", dtmf);
+ /* call index unknown */
+ mm_iface_modem_voice_received_dtmf (MM_IFACE_MODEM_VOICE (self), 0, dtmf);
+ g_free (dtmf);
+}
+
+static void
+common_voice_setup_cleanup_unsolicited_events (MMBroadbandModemHuawei *self,
+ gboolean enable)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ self->priv->orig_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)orig_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ self->priv->conf_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)conf_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ self->priv->conn_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)conn_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ self->priv->cend_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)cend_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ self->priv->ddtmf_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)ddtmf_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+/*****************************************************************************/
+/* Setup unsolicited events (Voice interface) */
+
+static gboolean
+modem_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Our own setup now */
+ common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_HUAWEI (self), TRUE);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's setup */
+ iface_modem_voice_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Cleanup unsolicited events (Voice interface) */
+
+static gboolean
+modem_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* cleanup our own */
+ common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_HUAWEI (self), FALSE);
+
+ /* Chain up parent's cleanup */
+ iface_modem_voice_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Enabling unsolicited events (Voice interface) */
+
+static gboolean
+modem_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+own_voice_enable_unsolicited_events_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_full_finish (self, res, NULL, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static const MMBaseModemAtCommand unsolicited_voice_enable_sequence[] = {
+ /* With ^DDTMFCFG we active the DTMF Decoder */
+ { "^DDTMFCFG=0,1", 3, FALSE, NULL },
+ { NULL }
+};
+
+static void
+parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Our own enable now */
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ unsolicited_voice_enable_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)own_voice_enable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's enable */
+ iface_modem_voice_parent->enable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Disabling unsolicited events (Voice interface) */
+
+static gboolean
+modem_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+own_voice_disable_unsolicited_events_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_full_finish (self, res, NULL, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static const MMBaseModemAtCommand unsolicited_voice_disable_sequence[] = {
+ /* With ^DDTMFCFG we deactivate the DTMF Decoder */
+ { "^DDTMFCFG=1,0", 3, FALSE, NULL },
+ { NULL }
+};
+
+static void
+parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* our own disable now */
+
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ unsolicited_voice_disable_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)own_voice_disable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's disable */
+ iface_modem_voice_parent->disable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Create call (Voice interface) */
+
+static MMBaseCall *
+create_call (MMIfaceModemVoice *self,
+ MMCallDirection direction,
+ const gchar *number)
+{
+ return mm_base_call_new (MM_BASE_MODEM (self),
+ direction,
+ number,
+ TRUE, /* skip_incoming_timeout */
+ TRUE, /* supports_dialing_to_ringing */
+ TRUE); /* supports_ringing_to_active) */
+}
+
+/*****************************************************************************/
+/* Load network time (Time interface) */
+
+static MMNetworkTimezone *
+modem_time_load_network_timezone_finish (MMIfaceModemTime *_self,
+ GAsyncResult *res,
+ GError **error)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ MMNetworkTimezone *tz = NULL;
+ const gchar *response;
+
+ g_assert (self->priv->nwtime_support == FEATURE_SUPPORTED ||
+ self->priv->time_support == FEATURE_SUPPORTED);
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, error);
+ if (!response)
+ return NULL;
+
+ if (self->priv->nwtime_support == FEATURE_SUPPORTED)
+ mm_huawei_parse_nwtime_response (response, NULL, &tz, error);
+ else if (self->priv->time_support == FEATURE_SUPPORTED)
+ mm_huawei_parse_time_response (response, NULL, &tz, error);
+ return tz;
+}
+
+
+static gchar *
+modem_time_load_network_time_finish (MMIfaceModemTime *_self,
+ GAsyncResult *res,
+ GError **error)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ const gchar *response;
+ gchar *iso8601 = NULL;
+
+ g_assert (self->priv->nwtime_support == FEATURE_SUPPORTED ||
+ self->priv->time_support == FEATURE_SUPPORTED);
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, error);
+ if (!response)
+ return NULL;
+
+ if (self->priv->nwtime_support == FEATURE_SUPPORTED)
+ mm_huawei_parse_nwtime_response (response, &iso8601, NULL, error);
+ else if (self->priv->time_support == FEATURE_SUPPORTED)
+ mm_huawei_parse_time_response (response, &iso8601, NULL, error);
+ return iso8601;
+}
+
+static void
+modem_time_load_network_time_or_zone (MMIfaceModemTime *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ const char *command = NULL;
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+
+ if (self->priv->nwtime_support == FEATURE_SUPPORTED)
+ command = "^NWTIME?";
+ else if (self->priv->time_support == FEATURE_SUPPORTED)
+ command = "^TIME";
+
+ g_assert (command != NULL);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Power state loading (Modem interface) */
+
+static void
+enable_disable_unsolicited_rfswitch_event_handler (MMBroadbandModemHuawei *self,
+ gboolean enable)
+{
+ GList *ports, *l;
+
+ ports = mm_broadband_modem_huawei_get_at_port_list (self);
+
+ mm_obj_dbg (self, "%s ^RFSWITCH unsolicited event handler",
+ enable ? "enable" : "disable");
+
+ for (l = ports; l; l = g_list_next (l)) {
+ MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data);
+
+ mm_port_serial_at_enable_unsolicited_msg_handler (
+ port,
+ self->priv->rfswitch_regex,
+ enable);
+ }
+
+ g_list_free_full (ports, g_object_unref);
+}
+
+static void
+parent_load_power_state_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ MMModemPowerState power_state;
+
+ power_state = iface_modem_parent->load_power_state_finish (self, res, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else {
+ /* As modem_power_down uses +CFUN=0 to put the modem in low state, we treat
+ * CFUN 0 as 'LOW' power state instead of 'OFF'. Otherwise, MMIfaceModem
+ * would prevent the modem from transitioning back to the 'ON' power state. */
+ if (power_state == MM_MODEM_POWER_STATE_OFF)
+ power_state = MM_MODEM_POWER_STATE_LOW;
+
+ g_task_return_int (task, power_state);
+ }
+
+ g_object_unref (task);
+}
+
+static void
+huawei_rfswitch_check_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ GError *error = NULL;
+ const gchar *response;
+ gint sw_state;
+
+ enable_disable_unsolicited_rfswitch_event_handler (MM_BROADBAND_MODEM_HUAWEI (self),
+ TRUE /* enable */);
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (response) {
+ response = mm_strip_tag (response, "^RFSWITCH:");
+ if (sscanf (response, "%d", &sw_state) != 1 ||
+ (sw_state != 0 && sw_state != 1)) {
+ mm_obj_warn (self, "couldn't parse ^RFSWITCH response '%s'", response);
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^RFSWITCH response '%s'",
+ response);
+ }
+ }
+
+ if (self->priv->rfswitch_support == FEATURE_SUPPORT_UNKNOWN) {
+ if (error) {
+ mm_obj_dbg (self, "^RFSWITCH is not supported");
+ self->priv->rfswitch_support = FEATURE_NOT_SUPPORTED;
+ g_error_free (error);
+ /* Fall back to parent's load_power_state */
+ iface_modem_parent->load_power_state (MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_power_state_ready,
+ task);
+ return;
+ }
+
+ mm_obj_dbg (self, "^RFSWITCH is supported");
+ self->priv->rfswitch_support = FEATURE_SUPPORTED;
+ }
+
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_int (task,
+ sw_state ? MM_MODEM_POWER_STATE_ON : MM_MODEM_POWER_STATE_LOW);
+
+ g_object_unref (task);
+}
+
+static MMModemPowerState
+load_power_state_finish (MMIfaceModem *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_POWER_STATE_UNKNOWN;
+ }
+ return (MMModemPowerState)value;
+}
+
+static void
+load_power_state (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ switch (MM_BROADBAND_MODEM_HUAWEI (self)->priv->rfswitch_support) {
+ case FEATURE_SUPPORT_UNKNOWN:
+ case FEATURE_SUPPORTED: {
+ /* Temporarily disable the unsolicited ^RFSWITCH event handler in order to
+ * prevent it from discarding the response to the ^RFSWITCH? command.
+ * It will be re-enabled in huawei_rfswitch_check_ready.
+ */
+ enable_disable_unsolicited_rfswitch_event_handler (MM_BROADBAND_MODEM_HUAWEI (self),
+ FALSE /* enable */);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^RFSWITCH?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)huawei_rfswitch_check_ready,
+ task);
+ break;
+ }
+ case FEATURE_NOT_SUPPORTED:
+ /* Run parent's load_power_state */
+ iface_modem_parent->load_power_state (self,
+ (GAsyncReadyCallback)parent_load_power_state_ready,
+ task);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+/*****************************************************************************/
+/* Modem power up (Modem interface) */
+
+static gboolean
+huawei_modem_power_up_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+huawei_modem_power_up (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ switch (MM_BROADBAND_MODEM_HUAWEI (self)->priv->rfswitch_support) {
+ case FEATURE_NOT_SUPPORTED:
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=1",
+ 30,
+ FALSE,
+ callback,
+ user_data);
+ break;
+ case FEATURE_SUPPORTED:
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^RFSWITCH=1",
+ 30,
+ FALSE,
+ callback,
+ user_data);
+ break;
+ case FEATURE_SUPPORT_UNKNOWN:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+/*****************************************************************************/
+/* Modem power down (Modem interface) */
+
+static gboolean
+huawei_modem_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+huawei_modem_power_down (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ switch (MM_BROADBAND_MODEM_HUAWEI (self)->priv->rfswitch_support) {
+ case FEATURE_NOT_SUPPORTED:
+ /* +CFUN=0 is supported on all Huawei modems but +CFUN=4 isn't,
+ * thus we use +CFUN=0 to put the modem in low power state. */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=0",
+ 30,
+ FALSE,
+ callback,
+ user_data);
+ break;
+ case FEATURE_SUPPORTED:
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^RFSWITCH=0",
+ 30,
+ FALSE,
+ callback,
+ user_data);
+ break;
+ case FEATURE_SUPPORT_UNKNOWN:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+/*****************************************************************************/
+/* Create SIM (Modem interface) */
+
+static MMBaseSim *
+huawei_modem_create_sim_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return mm_sim_huawei_new_finish (res, error);
+}
+
+static void
+huawei_modem_create_sim (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* New Sierra SIM */
+ mm_sim_huawei_new (MM_BASE_MODEM (self),
+ NULL, /* cancellable */
+ callback,
+ user_data);
+}
+
+
+/*****************************************************************************/
+/* Location capabilities loading (Location interface) */
+
+static MMModemLocationSource
+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
+parent_load_capabilities_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource sources;
+ GError *error = NULL;
+
+ sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* not sure how to check if GPS is supported, just allow it */
+ if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)))
+ sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED);
+
+ /* So we're done, complete */
+ g_task_return_int (task, sources);
+ g_object_unref (task);
+}
+
+static void
+location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's setup */
+ iface_modem_location_parent->load_capabilities (self,
+ (GAsyncReadyCallback)parent_load_capabilities_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Disable location gathering (Location interface) */
+
+static gboolean
+disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+gps_disabled_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 (MMIfaceModemLocation *_self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ GTask *task;
+
+ /* NOTE: no parent disable_location_gathering() implementation */
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ self->priv->enabled_sources &= ~source;
+
+ /* Only stop GPS engine if no GPS-related sources enabled */
+ if ((source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) &&
+ !(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) {
+ MMPortSerialGps *gps_port;
+
+ /* 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)) {
+ 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),
+ "^WPEND",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)gps_disabled_ready,
+ task);
+ return;
+ }
+
+ /* For any other location (e.g. 3GPP), or if still some GPS needed, just return */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Enable location gathering (Location interface) */
+
+static const MMBaseModemAtCommand gps_startup[] = {
+ { "^WPDOM=0", 3, FALSE, mm_base_modem_response_processor_no_result_continue },
+ { "^WPDST=1", 3, FALSE, mm_base_modem_response_processor_no_result_continue },
+ { "^WPDFR=65535,30", 3, FALSE, mm_base_modem_response_processor_no_result_continue },
+ { "^WPDGP", 3, FALSE, mm_base_modem_response_processor_no_result_continue },
+ { NULL }
+};
+
+static gboolean
+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)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ MMModemLocationSource source;
+ GError *error = NULL;
+
+ 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));
+
+ /* Only open the GPS port in NMEA/RAW setups */
+ 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 */
+ self->priv->enabled_sources |= source;
+ g_task_return_boolean (task, TRUE);
+ }
+ } else {
+ /* No need to open GPS port */
+ self->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)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ GError *error = NULL;
+ MMModemLocationSource source;
+ gboolean start_gps = FALSE;
+
+ if (!iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Now our own enabling */
+
+ source = GPOINTER_TO_UINT (g_task_get_task_data (task));
+
+ /* 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)) &&
+ !(self->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;
+ }
+
+ /* For any other location (e.g. 3GPP), or if GPS already running just return */
+ self->priv->enabled_sources |= source;
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
+
+ /* Chain up parent's gathering enable */
+ iface_modem_location_parent->enable_location_gathering (self,
+ source,
+ (GAsyncReadyCallback)parent_enable_location_gathering_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Check support (Time interface) */
+
+static gboolean
+modem_time_check_support_finish (MMIfaceModemTime *_self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+modem_time_check_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+
+ /* Responses are checked in the sequence parser, ignore overall result */
+ mm_base_modem_at_sequence_finish (_self, res, NULL, NULL);
+
+ g_task_return_boolean (task,
+ (self->priv->nwtime_support == FEATURE_SUPPORTED ||
+ self->priv->time_support == FEATURE_SUPPORTED));
+ g_object_unref (task);
+}
+
+static MMBaseModemAtResponseProcessorResult
+modem_check_time_reply (MMBaseModem *_self,
+ gpointer none,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+
+ if (!error) {
+ if (strstr (response, "^NTCT"))
+ self->priv->nwtime_support = FEATURE_SUPPORTED;
+ else if (strstr (response, "^TIME"))
+ self->priv->time_support = FEATURE_SUPPORTED;
+ } else {
+ if (strstr (command, "^NTCT"))
+ self->priv->nwtime_support = FEATURE_NOT_SUPPORTED;
+ else if (strstr (command, "^TIME"))
+ self->priv->time_support = FEATURE_NOT_SUPPORTED;
+ }
+
+ *result = NULL;
+ *result_error = NULL;
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
+}
+
+static const MMBaseModemAtCommand time_cmd_sequence[] = {
+ { "^NTCT?", 3, FALSE, modem_check_time_reply }, /* 3GPP/LTE */
+ { "^TIME", 3, FALSE, modem_check_time_reply }, /* CDMA */
+ { NULL }
+};
+
+static void
+modem_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_sequence (MM_BASE_MODEM (self),
+ time_cmd_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ (GAsyncReadyCallback)modem_time_check_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Check support (Signal interface) */
+
+static void
+hcsq_check_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (response)
+ g_task_return_boolean (task, TRUE);
+ else
+ g_task_return_error (task, error);
+
+ g_object_unref (task);
+}
+
+static gboolean
+signal_check_support_finish (MMIfaceModemSignal *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+signal_check_support (MMIfaceModemSignal *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^HCSQ?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)hcsq_check_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load extended signal information */
+
+static void
+detailed_signal_free (DetailedSignal *signal)
+{
+ detailed_signal_clear (signal);
+ g_slice_free (DetailedSignal, signal);
+}
+
+static gboolean
+signal_load_values_finish (MMIfaceModemSignal *self,
+ GAsyncResult *res,
+ MMSignal **cdma,
+ MMSignal **evdo,
+ MMSignal **gsm,
+ MMSignal **umts,
+ MMSignal **lte,
+ MMSignal **nr5g,
+ GError **error)
+{
+ DetailedSignal *signals;
+
+ signals = g_task_propagate_pointer (G_TASK (res), error);
+ if (!signals)
+ return FALSE;
+
+ *cdma = signals->cdma ? g_object_ref (signals->cdma) : NULL;
+ *evdo = signals->evdo ? g_object_ref (signals->evdo) : NULL;
+ *gsm = signals->gsm ? g_object_ref (signals->gsm) : NULL;
+ *umts = signals->umts ? g_object_ref (signals->umts) : NULL;
+ *lte = signals->lte ? g_object_ref (signals->lte) : NULL;
+ *nr5g = signals->nr5g ? g_object_ref (signals->nr5g) : NULL;
+
+ detailed_signal_free (signals);
+ return TRUE;
+}
+
+static void
+hcsq_get_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ DetailedSignal *signals;
+ GError *error = NULL;
+
+ /* Don't care about the response; it will have been parsed by the HCSQ
+ * unsolicited event handler and self->priv->detailed_signal will already
+ * be updated.
+ */
+ if (!mm_base_modem_at_command_finish (_self, res, &error)) {
+ mm_obj_dbg (self, "^HCSQ failed: %s", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ signals = g_slice_new0 (DetailedSignal);
+ signals->cdma = self->priv->detailed_signal.cdma ? g_object_ref (self->priv->detailed_signal.cdma) : NULL;
+ signals->evdo = self->priv->detailed_signal.evdo ? g_object_ref (self->priv->detailed_signal.evdo) : NULL;
+ signals->gsm = self->priv->detailed_signal.gsm ? g_object_ref (self->priv->detailed_signal.gsm) : NULL;
+ signals->umts = self->priv->detailed_signal.umts ? g_object_ref (self->priv->detailed_signal.umts) : NULL;
+ signals->lte = self->priv->detailed_signal.lte ? g_object_ref (self->priv->detailed_signal.lte) : NULL;
+
+ g_task_return_pointer (task, signals, (GDestroyNotify)detailed_signal_free);
+ g_object_unref (task);
+}
+
+static void
+signal_load_values (MMIfaceModemSignal *_self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
+ GTask *task;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ /* Clear any previous detailed signal values to get new ones */
+ detailed_signal_clear (&self->priv->detailed_signal);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^HCSQ?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)hcsq_get_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+set_ignored_unsolicited_events_handlers (MMBroadbandModemHuawei *self)
+{
+ GList *ports, *l;
+
+ ports = mm_broadband_modem_huawei_get_at_port_list (self);
+
+ /* Enable/disable unsolicited events in given port */
+ for (l = ports; l; l = g_list_next (l)) {
+ MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->boot_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->connect_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->csnr_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->cusatp_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->cusatend_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->dsdormant_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->simst_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->srvst_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->stin_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->pdpdeact_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->ndisend_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->rfswitch_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->position_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->posend_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->ecclist_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->ltersrp_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->cschannelinfo_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->ccallstate_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->eons_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ self->priv->lwurc_regex,
+ NULL, NULL, NULL);
+ }
+
+ g_list_free_full (ports, g_object_unref);
+}
+
+static void
+gps_trace_received (MMPortSerialGps *port,
+ const gchar *trace,
+ MMIfaceModemLocation *self)
+{
+ mm_iface_modem_location_gps_update (self, trace);
+}
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ MMPortSerialGps *gps_data_port;
+
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_huawei_parent_class)->setup_ports (self);
+
+ /* Unsolicited messages to always ignore */
+ set_ignored_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self));
+
+ /* Now reset the unsolicited messages we'll handle when enabled */
+ set_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE);
+ set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE);
+
+ /* NMEA GPS monitoring */
+ gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self));
+ if (gps_data_port) {
+ /* make sure GPS is stopped incase it was left enabled */
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ "^WPEND",
+ 3, FALSE, FALSE, NULL, NULL, NULL);
+ /* Add handler for the NMEA traces */
+ mm_port_serial_gps_add_trace_handler (gps_data_port,
+ (MMPortSerialGpsTraceFn)gps_trace_received,
+ self, NULL);
+ }
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemHuawei *
+mm_broadband_modem_huawei_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_HUAWEI,
+ 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 (TTY) or Huawei bearer (NET) supported */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_huawei_init (MMBroadbandModemHuawei *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_MODEM_HUAWEI,
+ MMBroadbandModemHuaweiPrivate);
+ /* Prepare regular expressions to setup */
+ self->priv->rssi_regex = g_regex_new ("\\r\\n\\^RSSI:\\s*(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->rssilvl_regex = g_regex_new ("\\r\\n\\^RSSILVL:\\s*(\\d+)\\r+\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->hrssilvl_regex = g_regex_new ("\\r\\n\\^HRSSILVL:\\s*(\\d+)\\r+\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ /* 3GPP: <cr><lf>^MODE:5<cr><lf>
+ * CDMA: <cr><lf>^MODE: 2<cr><cr><lf>
+ */
+ self->priv->mode_regex = g_regex_new ("\\r\\n\\^MODE:\\s*(\\d*),?(\\d*)\\r+\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->dsflowrpt_regex = g_regex_new ("\\r\\n\\^DSFLOWRPT:(.+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->ndisstat_regex = g_regex_new ("\\r\\n(\\^NDISSTAT:.+)\\r+\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ self->priv->orig_regex = g_regex_new ("\\r\\n\\^ORIG:\\s*(\\d+),\\s*(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->conf_regex = g_regex_new ("\\r\\n\\^CONF:\\s*(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->conn_regex = g_regex_new ("\\r\\n\\^CONN:\\s*(\\d+),\\s*(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->cend_regex = g_regex_new ("\\r\\n\\^CEND:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*(\\d*))?\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->ddtmf_regex = g_regex_new ("\\r\\n\\^DDTMF:\\s*([0-9A-D\\*\\#])\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ self->priv->boot_regex = g_regex_new ("\\r\\n\\^BOOT:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->connect_regex = g_regex_new ("\\r\\n\\^CONNECT .+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->csnr_regex = g_regex_new ("\\r\\n\\^CSNR:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->cusatp_regex = g_regex_new ("\\r\\n\\+CUSATP:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->cusatend_regex = g_regex_new ("\\r\\n\\+CUSATEND\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->dsdormant_regex = g_regex_new ("\\r\\n\\^DSDORMANT:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->simst_regex = g_regex_new ("\\r\\n\\^SIMST:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->srvst_regex = g_regex_new ("\\r\\n\\^SRVST:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->stin_regex = g_regex_new ("\\r\\n\\^STIN:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->hcsq_regex = g_regex_new ("\\r\\n(\\^HCSQ:.+)\\r+\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->pdpdeact_regex = g_regex_new ("\\r\\n\\^PDPDEACT:.+\\r+\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->ndisend_regex = g_regex_new ("\\r\\n\\^NDISEND:.+\\r+\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->rfswitch_regex = g_regex_new ("\\r\\n\\^RFSWITCH:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->position_regex = g_regex_new ("\\r\\n\\^POSITION:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->posend_regex = g_regex_new ("\\r\\n\\^POSEND:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->ecclist_regex = g_regex_new ("\\r\\n\\^ECCLIST:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->ltersrp_regex = g_regex_new ("\\r\\n\\^LTERSRP:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->cschannelinfo_regex = g_regex_new ("\\r\\n\\^CSCHANNELINFO:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->ccallstate_regex = g_regex_new ("\\r\\n\\^CCALLSTATE:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->eons_regex = g_regex_new ("\\r\\n\\^EONS:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->lwurc_regex = g_regex_new ("\\r\\n\\^LWURC:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ self->priv->ndisdup_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->rfswitch_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->sysinfoex_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->syscfg_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->syscfgex_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->prefmode_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->nwtime_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->time_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->cvoice_support = FEATURE_SUPPORT_UNKNOWN;
+}
+
+static void
+dispose (GObject *object)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (object);
+
+ detailed_signal_clear (&self->priv->detailed_signal);
+
+ G_OBJECT_CLASS (mm_broadband_modem_huawei_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (object);
+
+ g_regex_unref (self->priv->rssi_regex);
+ g_regex_unref (self->priv->rssilvl_regex);
+ g_regex_unref (self->priv->hrssilvl_regex);
+ g_regex_unref (self->priv->mode_regex);
+ g_regex_unref (self->priv->dsflowrpt_regex);
+ g_regex_unref (self->priv->ndisstat_regex);
+ g_regex_unref (self->priv->orig_regex);
+ g_regex_unref (self->priv->conf_regex);
+ g_regex_unref (self->priv->conn_regex);
+ g_regex_unref (self->priv->cend_regex);
+ g_regex_unref (self->priv->ddtmf_regex);
+
+ g_regex_unref (self->priv->boot_regex);
+ g_regex_unref (self->priv->connect_regex);
+ g_regex_unref (self->priv->csnr_regex);
+ g_regex_unref (self->priv->cusatp_regex);
+ g_regex_unref (self->priv->cusatend_regex);
+ g_regex_unref (self->priv->dsdormant_regex);
+ g_regex_unref (self->priv->simst_regex);
+ g_regex_unref (self->priv->srvst_regex);
+ g_regex_unref (self->priv->stin_regex);
+ g_regex_unref (self->priv->hcsq_regex);
+ g_regex_unref (self->priv->pdpdeact_regex);
+ g_regex_unref (self->priv->ndisend_regex);
+ g_regex_unref (self->priv->rfswitch_regex);
+ g_regex_unref (self->priv->position_regex);
+ g_regex_unref (self->priv->posend_regex);
+ g_regex_unref (self->priv->ecclist_regex);
+ g_regex_unref (self->priv->ltersrp_regex);
+ g_regex_unref (self->priv->cschannelinfo_regex);
+ g_regex_unref (self->priv->ccallstate_regex);
+ g_regex_unref (self->priv->eons_regex);
+ g_regex_unref (self->priv->lwurc_regex);
+
+ if (self->priv->syscfg_supported_modes)
+ g_array_unref (self->priv->syscfg_supported_modes);
+ if (self->priv->syscfgex_supported_modes)
+ g_array_unref (self->priv->syscfgex_supported_modes);
+ if (self->priv->prefmode_supported_modes)
+ g_array_unref (self->priv->prefmode_supported_modes);
+
+ G_OBJECT_CLASS (mm_broadband_modem_huawei_parent_class)->finalize (object);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->reset = reset;
+ iface->reset_finish = reset_finish;
+ iface->load_access_technologies = load_access_technologies;
+ iface->load_access_technologies_finish = load_access_technologies_finish;
+ iface->load_unlock_retries = load_unlock_retries;
+ iface->load_unlock_retries_finish = load_unlock_retries_finish;
+ iface->modem_after_sim_unlock = modem_after_sim_unlock;
+ iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish;
+ iface->load_current_bands = load_current_bands;
+ iface->load_current_bands_finish = load_current_bands_finish;
+ iface->set_current_bands = set_current_bands;
+ iface->set_current_bands_finish = set_current_bands_finish;
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = load_current_modes;
+ iface->load_current_modes_finish = load_current_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = set_current_modes_finish;
+ iface->load_signal_quality = modem_load_signal_quality;
+ iface->load_signal_quality_finish = modem_load_signal_quality_finish;
+ iface->create_bearer = huawei_modem_create_bearer;
+ iface->create_bearer_finish = huawei_modem_create_bearer_finish;
+ iface->load_power_state = load_power_state;
+ iface->load_power_state_finish = load_power_state_finish;
+ iface->modem_power_up = huawei_modem_power_up;
+ iface->modem_power_up_finish = huawei_modem_power_up_finish;
+ iface->modem_power_down = huawei_modem_power_down;
+ iface->modem_power_down_finish = huawei_modem_power_down_finish;
+ iface->create_sim = huawei_modem_create_sim;
+ iface->create_sim_finish = huawei_modem_create_sim_finish;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish;
+}
+
+static void
+iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface)
+{
+ iface->encode = encode;
+ iface->decode = decode;
+}
+
+static void
+iface_modem_cdma_init (MMIfaceModemCdma *iface)
+{
+ iface_modem_cdma_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = modem_cdma_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_cdma_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish;
+ iface->setup_registration_checks = setup_registration_checks;
+ iface->setup_registration_checks_finish = setup_registration_checks_finish;
+ iface->get_detailed_registration_state = get_detailed_registration_state;
+ iface->get_detailed_registration_state_finish = get_detailed_registration_state_finish;
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = location_load_capabilities;
+ iface->load_capabilities_finish = location_load_capabilities_finish;
+ iface->enable_location_gathering = enable_location_gathering;
+ iface->enable_location_gathering_finish = enable_location_gathering_finish;
+ iface->disable_location_gathering = disable_location_gathering;
+ iface->disable_location_gathering_finish = disable_location_gathering_finish;
+}
+
+static void
+iface_modem_time_init (MMIfaceModemTime *iface)
+{
+ iface->check_support = modem_time_check_support;
+ iface->check_support_finish = modem_time_check_support_finish;
+ iface->load_network_time = modem_time_load_network_time_or_zone;
+ iface->load_network_time_finish = modem_time_load_network_time_finish;
+ iface->load_network_timezone = modem_time_load_network_time_or_zone;
+ iface->load_network_timezone_finish = modem_time_load_network_timezone_finish;
+}
+
+static void
+iface_modem_voice_init (MMIfaceModemVoice *iface)
+{
+ iface_modem_voice_parent = g_type_interface_peek_parent (iface);
+
+ iface->check_support = modem_voice_check_support;
+ iface->check_support_finish = modem_voice_check_support_finish;
+ iface->setup_unsolicited_events = modem_voice_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_voice_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_voice_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_voice_cleanup_unsolicited_events_finish;
+ iface->enable_unsolicited_events = modem_voice_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = modem_voice_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = modem_voice_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = modem_voice_disable_unsolicited_events_finish;
+ iface->setup_in_call_audio_channel = modem_voice_setup_in_call_audio_channel;
+ iface->setup_in_call_audio_channel_finish = modem_voice_setup_in_call_audio_channel_finish;
+ iface->cleanup_in_call_audio_channel = modem_voice_cleanup_in_call_audio_channel;
+ iface->cleanup_in_call_audio_channel_finish = modem_voice_cleanup_in_call_audio_channel_finish;
+
+ iface->create_call = create_call;
+}
+
+static void
+iface_modem_signal_init (MMIfaceModemSignal *iface)
+{
+ iface->check_support = signal_check_support;
+ iface->check_support_finish = signal_check_support_finish;
+ iface->load_values = signal_load_values;
+ iface->load_values_finish = signal_load_values_finish;
+}
+
+static void
+mm_broadband_modem_huawei_class_init (MMBroadbandModemHuaweiClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemHuaweiPrivate));
+
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/huawei/mm-broadband-modem-huawei.h b/src/plugins/huawei/mm-broadband-modem-huawei.h
new file mode 100644
index 00000000..9fb16811
--- /dev/null
+++ b/src/plugins/huawei/mm-broadband-modem-huawei.h
@@ -0,0 +1,55 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_MODEM_HUAWEI_H
+#define MM_BROADBAND_MODEM_HUAWEI_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_HUAWEI (mm_broadband_modem_huawei_get_type ())
+#define MM_BROADBAND_MODEM_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_HUAWEI, MMBroadbandModemHuawei))
+#define MM_BROADBAND_MODEM_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_HUAWEI, MMBroadbandModemHuaweiClass))
+#define MM_IS_BROADBAND_MODEM_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_HUAWEI))
+#define MM_IS_BROADBAND_MODEM_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_HUAWEI))
+#define MM_BROADBAND_MODEM_HUAWEI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_HUAWEI, MMBroadbandModemHuaweiClass))
+
+typedef struct _MMBroadbandModemHuawei MMBroadbandModemHuawei;
+typedef struct _MMBroadbandModemHuaweiClass MMBroadbandModemHuaweiClass;
+typedef struct _MMBroadbandModemHuaweiPrivate MMBroadbandModemHuaweiPrivate;
+
+struct _MMBroadbandModemHuawei {
+ MMBroadbandModem parent;
+ MMBroadbandModemHuaweiPrivate *priv;
+};
+
+struct _MMBroadbandModemHuaweiClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_huawei_get_type (void);
+
+MMBroadbandModemHuawei *mm_broadband_modem_huawei_new (const gchar *device,
+ const gchar **driver,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+MMPortSerialAt *mm_broadband_modem_huawei_peek_port_at_for_data (MMBroadbandModemHuawei *self,
+ MMPort *port);
+GList *mm_broadband_modem_huawei_get_at_port_list (MMBroadbandModemHuawei *self);
+
+#endif /* MM_BROADBAND_MODEM_HUAWEI_H */
diff --git a/src/plugins/huawei/mm-modem-helpers-huawei.c b/src/plugins/huawei/mm-modem-helpers-huawei.c
new file mode 100644
index 00000000..67bb7089
--- /dev/null
+++ b/src/plugins/huawei/mm-modem-helpers-huawei.c
@@ -0,0 +1,1546 @@
+/* -*- 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) 2013 Huawei Technologies Co., Ltd
+ * Copyright (C) 2013 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-common-helpers.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-huawei.h"
+#include "mm-huawei-enums-types.h"
+
+/*****************************************************************************/
+/* ^NDISSTAT / ^NDISSTATQRY response parser */
+
+gboolean
+mm_huawei_parse_ndisstatqry_response (const gchar *response,
+ gboolean *ipv4_available,
+ gboolean *ipv4_connected,
+ gboolean *ipv6_available,
+ gboolean *ipv6_connected,
+ GError **error)
+{
+ GError *inner_error = NULL;
+
+ if (!response ||
+ !(g_ascii_strncasecmp (response, "^NDISSTAT:", strlen ("^NDISSTAT:")) == 0 ||
+ g_ascii_strncasecmp (response, "^NDISSTATQRY:", strlen ("^NDISSTATQRY:")) == 0)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing ^NDISSTAT / ^NDISSTATQRY prefix");
+ return FALSE;
+ }
+
+ *ipv4_available = FALSE;
+ *ipv6_available = FALSE;
+
+ /* The response maybe as:
+ * ^NDISSTAT: 1,,,IPV4
+ * ^NDISSTAT: 0,33,,IPV6
+ * ^NDISSTATQRY: 1,,,IPV4
+ * ^NDISSTATQRY: 0,33,,IPV6
+ * OK
+ *
+ * Or, in newer firmwares:
+ * ^NDISSTATQRY:0,,,"IPV4",0,,,"IPV6"
+ * OK
+ *
+ * Or, even (handled separately):
+ * ^NDISSTATQry:1
+ * OK
+ */
+
+ /* If multiple fields available, try first parsing method */
+ if (strchr (response, ',')) {
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+
+ r = g_regex_new ("\\^NDISSTAT(?:QRY)?(?:Qry)?:\\s*(\\d),([^,]*),([^,]*),([^,\\r\\n]*)(?:\\r\\n)?"
+ "(?:\\^NDISSTAT:|\\^NDISSTATQRY:)?\\s*,?(\\d)?,?([^,]*)?,?([^,]*)?,?([^,\\r\\n]*)?(?:\\r\\n)?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+ 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ guint ip_type_field = 4;
+
+ /* IPv4 and IPv6 are fields 4 and (if available) 8 */
+
+ while (!inner_error && ip_type_field <= 8) {
+ gchar *ip_type_str;
+ guint connected;
+
+ ip_type_str = mm_get_string_unquoted_from_match_info (match_info, ip_type_field);
+ if (!ip_type_str)
+ break;
+
+ if (!mm_get_uint_from_match_info (match_info, (ip_type_field - 3), &connected) ||
+ (connected != 0 && connected != 1)) {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^NDISSTAT / ^NDISSTATQRY fields");
+ } else if (g_ascii_strcasecmp (ip_type_str, "IPV4") == 0) {
+ *ipv4_available = TRUE;
+ *ipv4_connected = (gboolean)connected;
+ } else if (g_ascii_strcasecmp (ip_type_str, "IPV6") == 0) {
+ *ipv6_available = TRUE;
+ *ipv6_connected = (gboolean)connected;
+ }
+
+ g_free (ip_type_str);
+ ip_type_field += 4;
+ }
+ }
+ }
+ /* No separate IPv4/IPv6 info given just connected/not connected */
+ else {
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+
+ r = g_regex_new ("\\^NDISSTAT(?:QRY)?(?:Qry)?:\\s*(\\d)(?:\\r\\n)?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+ 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ guint connected;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &connected) ||
+ (connected != 0 && connected != 1)) {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^NDISSTAT / ^NDISSTATQRY fields");
+ } else {
+ /* We'll assume IPv4 */
+ *ipv4_available = TRUE;
+ *ipv4_connected = (gboolean)connected;
+ }
+ }
+ }
+
+ if (!ipv4_available && !ipv6_available) {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't find IPv4 or IPv6 info in ^NDISSTAT / ^NDISSTATQRY response");
+ }
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* ^DHCP response parser */
+
+static gboolean
+match_info_to_ip4_addr (GMatchInfo *match_info,
+ guint match_index,
+ guint *out_addr)
+{
+ g_autofree gchar *s = NULL;
+ g_autofree guint8 *bin = NULL;
+ gchar buf[9];
+ gsize len;
+ gsize bin_len;
+ guint32 aux;
+
+ s = g_match_info_fetch (match_info, match_index);
+ g_return_val_if_fail (s != NULL, FALSE);
+
+ len = strlen (s);
+ if (len == 1 && s[0] == '0') {
+ *out_addr = 0;
+ return TRUE;
+ }
+
+ if (len < 7 || len > 8)
+ return FALSE;
+
+ /* Handle possibly missing leading zero */
+ memset (buf, 0, sizeof (buf));
+ if (len == 7) {
+ strcpy (&buf[1], s);
+ buf[0] = '0';
+ } else if (len == 8)
+ strcpy (buf, s);
+ else
+ g_assert_not_reached ();
+
+ bin = mm_utils_hexstr2bin (buf, -1, &bin_len, NULL);
+ if (!bin || bin_len != 4)
+ return FALSE;
+
+ memcpy (&aux, bin, 4);
+ *out_addr = GUINT32_SWAP_LE_BE (aux);
+ return TRUE;
+}
+
+gboolean
+mm_huawei_parse_dhcp_response (const char *reply,
+ guint *out_address,
+ guint *out_prefix,
+ guint *out_gateway,
+ guint *out_dns1,
+ guint *out_dns2,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ gboolean matched;
+ GError *match_error = NULL;
+
+ g_assert (reply != NULL);
+ g_assert (out_address != NULL);
+ g_assert (out_prefix != NULL);
+ g_assert (out_gateway != NULL);
+ g_assert (out_dns1 != NULL);
+ g_assert (out_dns2 != NULL);
+
+ /* Format:
+ *
+ * ^DHCP: <address>,<netmask>,<gateway>,<?>,<dns1>,<dns2>,<uplink>,<downlink>
+ *
+ * All numbers are hexadecimal representations of IPv4 addresses, with
+ * least-significant byte first. eg, 192.168.50.32 is expressed as
+ * "2032A8C0". Sometimes leading zeros are stripped, so "1010A0A" is
+ * actually 10.10.1.1.
+ */
+
+ r = g_regex_new ("\\^DHCP:\\s*(?:0[xX])?([0-9a-fA-F]+),(?:0[xX])?([0-9a-fA-F]+),(?:0[xX])?([0-9a-fA-F]+),(?:0[xX])?([0-9a-fA-F]+),(?:0[xX])?([0-9a-fA-F]+),(?:0[xX])?([0-9a-fA-F]+),.*$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ matched = g_regex_match_full (r, reply, -1, 0, 0, &match_info, &match_error);
+ if (!matched) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse ^DHCP results: ");
+ } else {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't match ^DHCP reply");
+ }
+ } else {
+ guint netmask;
+
+ if (match_info_to_ip4_addr (match_info, 1, out_address) &&
+ match_info_to_ip4_addr (match_info, 2, &netmask) &&
+ match_info_to_ip4_addr (match_info, 3, out_gateway) &&
+ match_info_to_ip4_addr (match_info, 5, out_dns1) &&
+ match_info_to_ip4_addr (match_info, 6, out_dns2)) {
+ *out_prefix = mm_count_bits_set (netmask);
+ matched = TRUE;
+ }
+ }
+
+ return matched;
+}
+
+/*****************************************************************************/
+/* ^SYSINFO response parser */
+
+gboolean
+mm_huawei_parse_sysinfo_response (const char *reply,
+ guint *out_srv_status,
+ guint *out_srv_domain,
+ guint *out_roam_status,
+ guint *out_sys_mode,
+ guint *out_sim_state,
+ gboolean *out_sys_submode_valid,
+ guint *out_sys_submode,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ gboolean matched;
+ GError *match_error = NULL;
+
+ g_assert (out_srv_status != NULL);
+ g_assert (out_srv_domain != NULL);
+ g_assert (out_roam_status != NULL);
+ g_assert (out_sys_mode != NULL);
+ g_assert (out_sim_state != NULL);
+ g_assert (out_sys_submode_valid != NULL);
+ g_assert (out_sys_submode != NULL);
+
+ /* Format:
+ *
+ * ^SYSINFO: <srv_status>,<srv_domain>,<roam_status>,<sys_mode>,<sim_state>[,<reserved>,<sys_submode>]
+ */
+
+ /* Can't just use \d here since sometimes you get "^SYSINFO:2,1,0,3,1,,3" */
+ r = g_regex_new ("\\^SYSINFO:\\s*(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),?(\\d+)?,?(\\d+)?$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ matched = g_regex_match_full (r, reply, -1, 0, 0, &match_info, &match_error);
+ if (!matched) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse ^SYSINFO results: ");
+ } else {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't match ^SYSINFO reply");
+ }
+ } else {
+ mm_get_uint_from_match_info (match_info, 1, out_srv_status);
+ mm_get_uint_from_match_info (match_info, 2, out_srv_domain);
+ mm_get_uint_from_match_info (match_info, 3, out_roam_status);
+ mm_get_uint_from_match_info (match_info, 4, out_sys_mode);
+ mm_get_uint_from_match_info (match_info, 5, out_sim_state);
+
+ /* Remember that g_match_info_get_match_count() includes match #0 */
+ if (g_match_info_get_match_count (match_info) >= 8) {
+ *out_sys_submode_valid = TRUE;
+ mm_get_uint_from_match_info (match_info, 7, out_sys_submode);
+ }
+ }
+
+ return matched;
+}
+
+/*****************************************************************************/
+/* ^SYSINFOEX response parser */
+
+gboolean
+mm_huawei_parse_sysinfoex_response (const char *reply,
+ guint *out_srv_status,
+ guint *out_srv_domain,
+ guint *out_roam_status,
+ guint *out_sim_state,
+ guint *out_sys_mode,
+ guint *out_sys_submode,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ gboolean matched;
+ GError *match_error = NULL;
+
+ g_assert (out_srv_status != NULL);
+ g_assert (out_srv_domain != NULL);
+ g_assert (out_roam_status != NULL);
+ g_assert (out_sim_state != NULL);
+ g_assert (out_sys_mode != NULL);
+ g_assert (out_sys_submode != NULL);
+
+ /* Format:
+ *
+ * ^SYSINFOEX: <srv_status>,<srv_domain>,<roam_status>,<sim_state>,<reserved>,<sysmode>,<sysmode_name>,<submode>,<submode_name>
+ *
+ * <sysmode_name> and <submode_name> may not be quoted on some Huawei modems (e.g. E303).
+ */
+
+ /* ^SYSINFOEX:2,3,0,1,,3,"WCDMA",41,"HSPA+" */
+
+ r = g_regex_new ("\\^SYSINFOEX:\\s*(\\d+),(\\d+),(\\d+),(\\d+),?(\\d*),(\\d+),\"?([^\"]*)\"?,(\\d+),\"?([^\"]*)\"?$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ matched = g_regex_match_full (r, reply, -1, 0, 0, &match_info, &match_error);
+ if (!matched) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse ^SYSINFOEX results: ");
+ } else {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't match ^SYSINFOEX reply");
+ }
+ } else {
+ mm_get_uint_from_match_info (match_info, 1, out_srv_status);
+ mm_get_uint_from_match_info (match_info, 2, out_srv_domain);
+ mm_get_uint_from_match_info (match_info, 3, out_roam_status);
+ mm_get_uint_from_match_info (match_info, 4, out_sim_state);
+
+ /* We just ignore the sysmode and submode name strings */
+ mm_get_uint_from_match_info (match_info, 6, out_sys_mode);
+ mm_get_uint_from_match_info (match_info, 8, out_sys_submode);
+ }
+
+ return matched;
+}
+
+/*****************************************************************************/
+/* ^PREFMODE test parser
+ *
+ * AT^PREFMODE=?
+ * ^PREFMODE:(2,4,8)
+ */
+
+static gboolean
+mode_from_prefmode (guint huawei_mode,
+ MMModemMode *modem_mode,
+ GError **error)
+{
+ g_assert (modem_mode != NULL);
+
+ *modem_mode = MM_MODEM_MODE_NONE;
+ switch (huawei_mode) {
+ case 2:
+ *modem_mode = MM_MODEM_MODE_2G;
+ break;
+ case 4:
+ *modem_mode = MM_MODEM_MODE_3G;
+ break;
+ case 8:
+ *modem_mode = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ break;
+ default:
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "No translation from huawei prefmode '%u' to mode",
+ huawei_mode);
+ }
+
+ return *modem_mode != MM_MODEM_MODE_NONE ? TRUE : FALSE;
+}
+
+GArray *
+mm_huawei_parse_prefmode_test (const gchar *response,
+ gpointer log_object,
+ GError **error)
+{
+ gchar **split;
+ guint i;
+ MMModemMode all = MM_MODEM_MODE_NONE;
+ GArray *out;
+
+ response = mm_strip_tag (response, "^PREFMODE:");
+ split = g_strsplit_set (response, " (,)\r\n", -1);
+ if (!split) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unexpected ^PREFMODE format output");
+ return NULL;
+ }
+
+ out = g_array_sized_new (FALSE,
+ FALSE,
+ sizeof (MMHuaweiPrefmodeCombination),
+ 3);
+ for (i = 0; split[i]; i++) {
+ guint val;
+ MMModemMode preferred = MM_MODEM_MODE_NONE;
+ GError *inner_error = NULL;
+ MMHuaweiPrefmodeCombination combination;
+
+ if (split[i][0] == '\0')
+ continue;
+
+ if (!mm_get_uint_from_str (split[i], &val)) {
+ mm_obj_dbg (log_object, "error parsing ^PREFMODE value '%s'", split[i]);
+ continue;
+ }
+
+ if (!mode_from_prefmode (val, &preferred, &inner_error)) {
+ mm_obj_dbg (log_object, "unhandled ^PREFMODE value: %s", inner_error->message);
+ g_error_free (inner_error);
+ continue;
+ }
+
+ combination.prefmode = val;
+ combination.allowed = MM_MODEM_MODE_NONE; /* reset it later */
+ combination.preferred = preferred;
+
+ all |= preferred;
+
+ g_array_append_val (out, combination);
+ }
+ g_strfreev (split);
+
+ /* No value */
+ if (out->len == 0) {
+ g_array_unref (out);
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "^PREFMODE response contains no valid values");
+ return NULL;
+ }
+
+ /* Single value listed; PREFERRED=NONE... */
+ if (out->len == 1) {
+ MMHuaweiPrefmodeCombination *combination;
+
+ combination = &g_array_index (out, MMHuaweiPrefmodeCombination, 0);
+ combination->allowed = all;
+ combination->preferred = MM_MODEM_MODE_NONE;
+ } else {
+ /* Multiple values, reset ALLOWED */
+ for (i = 0; i < out->len; i++) {
+ MMHuaweiPrefmodeCombination *combination;
+
+ combination = &g_array_index (out, MMHuaweiPrefmodeCombination, i);
+ combination->allowed = all;
+ if (combination->preferred == all)
+ combination->preferred = MM_MODEM_MODE_NONE;
+ }
+ }
+
+ return out;
+}
+
+/*****************************************************************************/
+/* ^PREFMODE response parser */
+
+const MMHuaweiPrefmodeCombination *
+mm_huawei_parse_prefmode_response (const gchar *response,
+ const GArray *supported_mode_combinations,
+ GError **error)
+{
+ guint mode;
+ guint i;
+
+ /* Format:
+ *
+ * ^PREFMODE: <mode>
+ */
+
+ response = mm_strip_tag (response, "^PREFMODE:");
+ if (!mm_get_uint_from_str (response, &mode)) {
+ /* Dump error to upper layer */
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unexpected PREFMODE response: '%s'",
+ response);
+ return NULL;
+ }
+
+ /* Look for current modes among the supported ones */
+ for (i = 0; i < supported_mode_combinations->len; i++) {
+ const MMHuaweiPrefmodeCombination *combination;
+
+ combination = &g_array_index (supported_mode_combinations,
+ MMHuaweiPrefmodeCombination,
+ i);
+ if (mode == combination->prefmode)
+ return combination;
+ }
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "No PREFMODE combination found matching the current one (%d)",
+ mode);
+ return NULL;
+}
+
+/*****************************************************************************/
+/* ^SYSCFG test parser */
+
+static gchar **
+split_groups (const gchar *str,
+ GError **error)
+{
+ const gchar *p = str;
+ GPtrArray *out;
+ guint groups = 0;
+
+ /*
+ * Split string: (a),((b1),(b2)),,(d),((e1),(e2))
+ * Into:
+ * - a
+ * - (b1),(b2)
+ * -
+ * - d
+ * - (e1),(e2)
+ */
+
+ out = g_ptr_array_new_with_free_func (g_free);
+
+ while (TRUE) {
+ const gchar *start;
+ guint inner_groups;
+
+ /* Skip whitespaces */
+ while (*p == ' ' || *p == '\r' || *p == '\n')
+ p++;
+
+ /* We're done, return */
+ if (*p == '\0') {
+ g_ptr_array_set_size (out, out->len + 1);
+ return (gchar **) g_ptr_array_free (out, FALSE);
+ }
+
+ /* Group separators */
+ if (groups > 0) {
+ if (*p != ',') {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unexpected group separator");
+ g_ptr_array_unref (out);
+ return NULL;
+ }
+ p++;
+ }
+
+ /* Skip whitespaces */
+ while (*p == ' ' || *p == '\r' || *p == '\n')
+ p++;
+
+ /* New group */
+ groups++;
+
+ /* Empty group? */
+ if (*p == ',' || *p == '\0') {
+ g_ptr_array_add (out, g_strdup (""));
+ continue;
+ }
+
+ /* No group start? */
+ if (*p != '(') {
+ /* Error */
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Expected '(' not found");
+ g_ptr_array_unref (out);
+ return NULL;
+ }
+ p++;
+
+ inner_groups = 0;
+ start = p;
+ while (TRUE) {
+ if (*p == '(') {
+ inner_groups++;
+ p++;
+ continue;
+ }
+
+ if (*p == '\0') {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Early end of string found, unfinished group");
+ g_ptr_array_unref (out);
+ return NULL;
+ }
+
+ if (*p == ')') {
+ gchar *group;
+
+ if (inner_groups > 0) {
+ inner_groups--;
+ p++;
+ continue;
+ }
+
+ group = g_strndup (start, p - start);
+ g_ptr_array_add (out, group);
+ p++;
+ break;
+ }
+
+ /* keep on */
+ p++;
+ }
+ }
+
+ g_assert_not_reached ();
+}
+
+static gboolean
+mode_from_syscfg (guint huawei_mode,
+ MMModemMode *modem_mode,
+ GError **error)
+{
+ g_assert (modem_mode != NULL);
+
+ *modem_mode = MM_MODEM_MODE_NONE;
+ switch (huawei_mode) {
+ case 2:
+ *modem_mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G;
+ break;
+ case 13:
+ *modem_mode = MM_MODEM_MODE_2G;
+ break;
+ case 14:
+ *modem_mode = MM_MODEM_MODE_3G;
+ break;
+ case 16:
+ /* ignore */
+ break;
+ default:
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "No translation from huawei prefmode '%u' to mode",
+ huawei_mode);
+ }
+
+ return *modem_mode != MM_MODEM_MODE_NONE ? TRUE : FALSE;
+}
+
+static GArray *
+parse_syscfg_modes (const gchar *modes_str,
+ const gchar *acqorder_str,
+ gpointer log_object,
+ GError **error)
+{
+ GArray *out;
+ gchar **split;
+ guint i;
+ gint min_acqorder = 0;
+ gint max_acqorder = 0;
+
+ /* Start parsing acquisition order */
+ if (!sscanf (acqorder_str, "%d-%d", &min_acqorder, &max_acqorder))
+ mm_obj_dbg (log_object, "error parsing ^SYSCFG acquisition order range '%s'", acqorder_str);
+
+ /* Just in case, we default to supporting only auto */
+ if (max_acqorder < min_acqorder) {
+ min_acqorder = 0;
+ max_acqorder = 0;
+ }
+
+ /* Now parse modes */
+ split = g_strsplit (modes_str, ",", -1);
+ out = g_array_sized_new (FALSE,
+ FALSE,
+ sizeof (MMHuaweiSyscfgCombination),
+ g_strv_length (split));
+ for (i = 0; split[i]; i++) {
+ guint val;
+ guint allowed = MM_MODEM_MODE_NONE;
+ GError *inner_error = NULL;
+ MMHuaweiSyscfgCombination combination;
+
+ if (!mm_get_uint_from_str (mm_strip_quotes (split[i]), &val)) {
+ mm_obj_dbg (log_object, "error parsing ^SYSCFG mode value: %s", split[i]);
+ continue;
+ }
+
+ if (!mode_from_syscfg (val, &allowed, &inner_error)) {
+ if (inner_error) {
+ mm_obj_dbg (log_object, "unhandled ^SYSCFG: %s", inner_error->message);
+ g_error_free (inner_error);
+ }
+ continue;
+ }
+
+ switch (allowed) {
+ case MM_MODEM_MODE_2G:
+ case MM_MODEM_MODE_3G:
+ /* single mode */
+ combination.allowed = allowed;
+ combination.preferred = MM_MODEM_MODE_NONE;
+ combination.mode = val;
+ combination.acqorder = 0;
+ g_array_append_val (out, combination);
+ break;
+ case (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G):
+ /* 2G and 3G; auto */
+ combination.allowed = allowed;
+ combination.mode = val;
+ if (min_acqorder == 0) {
+ combination.preferred = MM_MODEM_MODE_NONE;
+ combination.acqorder = 0;
+ g_array_append_val (out, combination);
+ }
+ /* 2G and 3G; 2G preferred */
+ if (min_acqorder <= 1 && max_acqorder >= 1) {
+ combination.preferred = MM_MODEM_MODE_2G;
+ combination.acqorder = 1;
+ g_array_append_val (out, combination);
+ }
+ /* 2G and 3G; 3G preferred */
+ if (min_acqorder <= 2 && max_acqorder >= 2) {
+ combination.preferred = MM_MODEM_MODE_3G;
+ combination.acqorder = 2;
+ g_array_append_val (out, combination);
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ }
+
+ g_strfreev (split);
+
+ /* If we didn't build a valid array of combinations, return an error */
+ if (out->len == 0) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Cannot parse list of allowed mode combinations: '%s,%s'",
+ modes_str,
+ acqorder_str);
+ g_array_unref (out);
+ return NULL;
+ }
+
+ return out;
+}
+
+GArray *
+mm_huawei_parse_syscfg_test (const gchar *response,
+ gpointer log_object,
+ GError **error)
+{
+ gchar **split;
+ GError *inner_error = NULL;
+ GArray *out;
+
+ if (!response || !g_str_has_prefix (response, "^SYSCFG:")) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Missing ^SYSCFG prefix");
+ return NULL;
+ }
+
+ /* Examples:
+ *
+ * ^SYSCFG:(2,13,14,16),
+ * (0-3),
+ * ((400000,"WCDMA2100")),
+ * (0-2),
+ * (0-4)
+ */
+ split = split_groups (mm_strip_tag (response, "^SYSCFG:"), error);
+ if (!split)
+ return NULL;
+
+ /* We expect 5 string chunks */
+ if (g_strv_length (split) < 5) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unexpected ^SYSCFG format");
+ g_strfreev (split);
+ return FALSE;
+ }
+
+ /* Parse supported mode combinations */
+ out = parse_syscfg_modes (split[0], split[1], log_object, &inner_error);
+
+ g_strfreev (split);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return NULL;
+ }
+
+ return out;
+}
+
+/*****************************************************************************/
+/* ^SYSCFG response parser */
+
+const MMHuaweiSyscfgCombination *
+mm_huawei_parse_syscfg_response (const gchar *response,
+ const GArray *supported_mode_combinations,
+ GError **error)
+{
+ gchar **split;
+ guint mode;
+ guint acqorder;
+ guint i;
+
+ if (!response || !g_str_has_prefix (response, "^SYSCFG:")) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Missing ^SYSCFG prefix");
+ return NULL;
+ }
+
+ /* Format:
+ *
+ * ^SYSCFG: <mode>,<acqorder>,<band>,<roam>,<srvdomain>
+ */
+
+ response = mm_strip_tag (response, "^SYSCFG:");
+ split = g_strsplit (response, ",", -1);
+
+ /* We expect 5 string chunks */
+ if (g_strv_length (split) < 5 ||
+ !mm_get_uint_from_str (split[0], &mode) ||
+ !mm_get_uint_from_str (split[1], &acqorder)) {
+ /* Dump error to upper layer */
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unexpected ^SYSCFG response: '%s'",
+ response);
+ g_strfreev (split);
+ return NULL;
+ }
+
+ /* Fix invalid modes with non-sensical acquisition orders */
+ if (mode == 14 && acqorder != 0) /* WCDMA only but acqorder != "Automatic" */
+ acqorder = 0;
+ else if (mode == 13 && acqorder != 0) /* GSM only but acqorder != "Automatic" */
+ acqorder = 0;
+
+ /* Look for current modes among the supported ones */
+ for (i = 0; i < supported_mode_combinations->len; i++) {
+ const MMHuaweiSyscfgCombination *combination;
+
+ combination = &g_array_index (supported_mode_combinations,
+ MMHuaweiSyscfgCombination,
+ i);
+ if (mode == combination->mode && acqorder == combination->acqorder) {
+ g_strfreev (split);
+ return combination;
+ }
+ }
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "No SYSCFG combination found matching the current one (%d,%d)",
+ mode,
+ acqorder);
+ g_strfreev (split);
+ return NULL;
+}
+
+/*****************************************************************************/
+/* ^SYSCFGEX test parser */
+
+static void
+huawei_syscfgex_combination_free (MMHuaweiSyscfgexCombination *item)
+{
+ /* Just the contents, not the item itself! */
+ g_free (item->mode_str);
+}
+
+static gboolean
+parse_mode_combination_string (const gchar *mode_str,
+ MMModemMode *allowed,
+ MMModemMode *preferred)
+{
+ guint n;
+
+ if (g_str_equal (mode_str, "00")) {
+ *allowed = MM_MODEM_MODE_ANY;
+ *preferred = MM_MODEM_MODE_NONE;
+ return TRUE;
+ }
+
+ *allowed = MM_MODEM_MODE_NONE;
+ *preferred = MM_MODEM_MODE_NONE;
+
+ for (n = 0; n < strlen (mode_str); n+=2) {
+ MMModemMode mode;
+
+ if (g_ascii_strncasecmp (&mode_str[n], "01", 2) == 0)
+ /* GSM */
+ mode = MM_MODEM_MODE_2G;
+ else if (g_ascii_strncasecmp (&mode_str[n], "02", 2) == 0)
+ /* WCDMA */
+ mode = MM_MODEM_MODE_3G;
+ else if (g_ascii_strncasecmp (&mode_str[n], "03", 2) == 0)
+ /* LTE */
+ mode = MM_MODEM_MODE_4G;
+ else if (g_ascii_strncasecmp (&mode_str[n], "04", 2) == 0)
+ /* CDMA Note: no EV-DO, just return single value, so assume CDMA1x*/
+ mode = MM_MODEM_MODE_2G;
+ else
+ mode = MM_MODEM_MODE_NONE;
+
+ if (mode != MM_MODEM_MODE_NONE) {
+ /* The first one in the list is the preferred combination */
+ if (n == 0)
+ *preferred |= mode;
+ *allowed |= mode;
+ }
+ }
+
+ switch (mm_count_bits_set (*allowed)) {
+ case 0:
+ /* No allowed, error */
+ return FALSE;
+ case 1:
+ /* If only one mode allowed, NONE preferred */
+ *preferred = MM_MODEM_MODE_NONE;
+ /* fall through */
+ default:
+ return TRUE;
+ }
+}
+
+static GArray *
+parse_mode_combination_string_list (const gchar *modes_str,
+ GError **error)
+{
+ GArray *supported_mode_combinations;
+ gchar **mode_combinations;
+ MMModemMode all = MM_MODEM_MODE_NONE;
+ gboolean has_all = FALSE;
+ guint i;
+
+ mode_combinations = g_strsplit (modes_str, ",", -1);
+ supported_mode_combinations = g_array_sized_new (FALSE,
+ FALSE,
+ sizeof (MMHuaweiSyscfgexCombination),
+ g_strv_length (mode_combinations));
+ g_array_set_clear_func (supported_mode_combinations,
+ (GDestroyNotify)huawei_syscfgex_combination_free);
+
+ for (i = 0; mode_combinations[i]; i++) {
+ MMHuaweiSyscfgexCombination combination;
+
+ mode_combinations[i] = mm_strip_quotes (mode_combinations[i]);
+ if (!parse_mode_combination_string (mode_combinations[i],
+ &combination.allowed,
+ &combination.preferred))
+ continue;
+
+ if (combination.allowed != MM_MODEM_MODE_ANY) {
+ combination.mode_str = g_strdup (mode_combinations[i]);
+ g_array_append_val (supported_mode_combinations, combination);
+
+ all |= combination.allowed;
+ } else {
+ /* don't add the all_combination here, we may have more
+ * combinations in the loop afterwards */
+ has_all = TRUE;
+ }
+ }
+
+ g_strfreev (mode_combinations);
+
+ /* Add here the all_combination */
+ if (has_all) {
+ MMHuaweiSyscfgexCombination combination;
+
+ combination.allowed = all;
+ combination.preferred = MM_MODEM_MODE_NONE;
+ combination.mode_str = g_strdup ("00");
+ g_array_append_val (supported_mode_combinations, combination);
+ }
+
+ /* If we didn't build a valid array of combinations, return an error */
+ if (supported_mode_combinations->len == 0) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Cannot parse list of allowed mode combinations: '%s'",
+ modes_str);
+ g_array_unref (supported_mode_combinations);
+ return NULL;
+ }
+
+ return supported_mode_combinations;
+}
+
+GArray *
+mm_huawei_parse_syscfgex_test (const gchar *response,
+ GError **error)
+{
+ gchar **split;
+ GError *inner_error = NULL;
+ GArray *out;
+
+ if (!response || !g_str_has_prefix (response, "^SYSCFGEX:")) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Missing ^SYSCFGEX prefix");
+ return NULL;
+ }
+
+ /* Examples:
+ *
+ * ^SYSCFGEX: ("00","03","02","01","99"),
+ * ((2000004e80380,"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100"),
+ * (3fffffff,"All Bands")),
+ * (0-3),
+ * (0-4),
+ * ((800c5,"LTE2100/LTE1800/LTE2600/LTE900/LTE800"),
+ * (7fffffffffffffff,"All bands"))
+ */
+ split = split_groups (mm_strip_tag (response, "^SYSCFGEX:"), error);
+ if (!split)
+ return NULL;
+
+ /* We expect 5 string chunks */
+ if (g_strv_length (split) < 5) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unexpected ^SYSCFGEX format");
+ g_strfreev (split);
+ return NULL;
+ }
+
+ out = parse_mode_combination_string_list (split[0], &inner_error);
+
+ g_strfreev (split);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return NULL;
+ }
+
+ return out;
+}
+
+/*****************************************************************************/
+/* ^SYSCFGEX response parser */
+
+const MMHuaweiSyscfgexCombination *
+mm_huawei_parse_syscfgex_response (const gchar *response,
+ const GArray *supported_mode_combinations,
+ GError **error)
+{
+ gchar **split;
+ guint i;
+ gsize len;
+ gchar *str;
+
+ if (!response || !g_str_has_prefix (response, "^SYSCFGEX:")) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Missing ^SYSCFGEX prefix");
+ return NULL;
+ }
+
+ /* Format:
+ *
+ * ^SYSCFGEX: "00",3FFFFFFF,1,2,7FFFFFFFFFFFFFFF
+ * ^SYSCFGEX: <mode>,<band>,<roam>,<srvdomain>,<lte-band>
+ */
+
+ response = mm_strip_tag (response, "^SYSCFGEX:");
+ split = g_strsplit (response, ",", -1);
+
+ /* We expect 5 string chunks */
+ if (g_strv_length (split) < 5) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unexpected ^SYSCFGEX response format");
+ g_strfreev (split);
+ return NULL;
+ }
+
+ /* Unquote */
+ str = split[0];
+ len = strlen (str);
+ if ((len >= 2) && (str[0] == '"') && (str[len - 1] == '"')) {
+ str[0] = ' ';
+ str[len - 1] = ' ';
+ str = g_strstrip (str);
+ }
+
+ /* Look for current modes among the supported ones */
+ for (i = 0; i < supported_mode_combinations->len; i++) {
+ const MMHuaweiSyscfgexCombination *combination;
+
+ combination = &g_array_index (supported_mode_combinations,
+ MMHuaweiSyscfgexCombination,
+ i);
+ if (g_str_equal (str, combination->mode_str)) {
+ g_strfreev (split);
+ return combination;
+ }
+ }
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "No SYSCFGEX combination found matching the current one (%s)",
+ str);
+ g_strfreev (split);
+ return NULL;
+}
+
+/*****************************************************************************/
+/* ^NWTIME response parser */
+
+gboolean
+mm_huawei_parse_nwtime_response (const gchar *response,
+ gchar **iso8601p,
+ MMNetworkTimezone **tzp,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *match_error = NULL;
+ guint year = 0;
+ guint month = 0;
+ guint day = 0;
+ guint hour = 0;
+ guint minute = 0;
+ guint second = 0;
+ guint dt = 0;
+ gint tz = 0;
+
+ g_assert (iso8601p || tzp); /* at least one */
+
+ r = g_regex_new ("\\^NWTIME:\\s*(\\d+)/(\\d+)/(\\d+),(\\d+):(\\d+):(\\d*)([\\-\\+\\d]+),(\\d+)$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse ^NWTIME results: ");
+ } else {
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't match ^NWTIME reply");
+ }
+ return FALSE;
+ }
+
+ /* Remember that g_match_info_get_match_count() includes match #0 */
+ g_assert (g_match_info_get_match_count (match_info) >= 9);
+
+ if (mm_get_uint_from_match_info (match_info, 1, &year) &&
+ mm_get_uint_from_match_info (match_info, 2, &month) &&
+ mm_get_uint_from_match_info (match_info, 3, &day) &&
+ mm_get_uint_from_match_info (match_info, 4, &hour) &&
+ mm_get_uint_from_match_info (match_info, 5, &minute) &&
+ mm_get_uint_from_match_info (match_info, 6, &second) &&
+ mm_get_int_from_match_info (match_info, 7, &tz) &&
+ mm_get_uint_from_match_info (match_info, 8, &dt)) {
+
+ /* adjust year */
+ if (year < 100)
+ year += 2000;
+ /*
+ * tz = timezone offset in 15 minute intervals
+ * dt = daylight adjustment, 0 = none, 1 = 1 hour, 2 = 2 hours
+ * other values are marked reserved.
+ */
+ if (tzp) {
+ *tzp = mm_network_timezone_new ();
+ mm_network_timezone_set_offset (*tzp, tz * 15);
+ mm_network_timezone_set_dst_offset (*tzp, dt * 60);
+ }
+ if (iso8601p) {
+ /* Return ISO-8601 format date/time string */
+ *iso8601p = mm_new_iso8601_time (year, month, day, hour,
+ minute, second,
+ TRUE, (tz * 15) + (dt * 60),
+ error);
+ return (*iso8601p != NULL);
+ }
+
+ return TRUE;
+ }
+
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Failed to parse ^NWTIME reply");
+ return FALSE;
+}
+
+/*****************************************************************************/
+/* ^TIME response parser */
+
+gboolean
+mm_huawei_parse_time_response (const gchar *response,
+ gchar **iso8601p,
+ MMNetworkTimezone **tzp,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *match_error = NULL;
+ guint year = 0;
+ guint month = 0;
+ guint day = 0;
+ guint hour = 0;
+ guint minute = 0;
+ guint second = 0;
+
+ g_assert (iso8601p || tzp); /* at least one */
+
+ /* TIME response cannot ever provide TZ info */
+ if (tzp) {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "^TIME does not provide timezone information");
+ return FALSE;
+ }
+
+ /* Already in ISO-8601 format, but verify just to be sure */
+ r = g_regex_new ("\\^TIME:\\s*(\\d+)/(\\d+)/(\\d+)\\s*(\\d+):(\\d+):(\\d*)$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse ^TIME results: ");
+ } else {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't match ^TIME reply");
+ }
+ return FALSE;
+ }
+
+ /* Remember that g_match_info_get_match_count() includes match #0 */
+ g_assert (g_match_info_get_match_count (match_info) >= 7);
+
+ if (mm_get_uint_from_match_info (match_info, 1, &year) &&
+ mm_get_uint_from_match_info (match_info, 2, &month) &&
+ mm_get_uint_from_match_info (match_info, 3, &day) &&
+ mm_get_uint_from_match_info (match_info, 4, &hour) &&
+ mm_get_uint_from_match_info (match_info, 5, &minute) &&
+ mm_get_uint_from_match_info (match_info, 6, &second)) {
+ /* adjust year */
+ if (year < 100)
+ year += 2000;
+
+ /* Return ISO-8601 format date/time string */
+ if (iso8601p) {
+ *iso8601p = mm_new_iso8601_time (year, month, day, hour,
+ minute, second, FALSE, 0,
+ error);
+ return (*iso8601p != NULL);
+ }
+ return TRUE;
+ }
+
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Failed to parse ^TIME reply");
+ return FALSE;
+}
+
+/*****************************************************************************/
+/* ^HCSQ response parser */
+
+gboolean
+mm_huawei_parse_hcsq_response (const gchar *response,
+ MMModemAccessTechnology *out_act,
+ guint *out_value1,
+ guint *out_value2,
+ guint *out_value3,
+ guint *out_value4,
+ guint *out_value5,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *match_error = NULL;
+
+ r = g_regex_new ("\\^HCSQ:\\s*\"?([a-zA-Z]*)\"?,(\\d+),?(\\d+)?,?(\\d+)?,?(\\d+)?,?(\\d+)?$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse ^HCSQ results: ");
+ } else {
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't match ^HCSQ reply");
+ }
+ return FALSE;
+ }
+
+ /* Remember that g_match_info_get_match_count() includes match #0 */
+ if (g_match_info_get_match_count (match_info) < 3) {
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Not enough elements in ^HCSQ reply");
+ return FALSE;
+ }
+
+ if (out_act) {
+ g_autofree gchar *s = NULL;
+
+ s = g_match_info_fetch (match_info, 1);
+ *out_act = mm_string_to_access_tech (s);
+ }
+
+ if (out_value1)
+ mm_get_uint_from_match_info (match_info, 2, out_value1);
+ if (out_value2)
+ mm_get_uint_from_match_info (match_info, 3, out_value2);
+ if (out_value3)
+ mm_get_uint_from_match_info (match_info, 4, out_value3);
+ if (out_value4)
+ mm_get_uint_from_match_info (match_info, 5, out_value4);
+ if (out_value5)
+ mm_get_uint_from_match_info (match_info, 6, out_value5);
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* ^CVOICE response parser */
+
+gboolean
+mm_huawei_parse_cvoice_response (const gchar *response,
+ guint *out_hz,
+ guint *out_bits,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *match_error = NULL;
+ guint supported = 0;
+ guint hz = 0;
+ guint bits = 0;
+
+ /* ^CVOICE: <0=supported,1=unsupported>,<hz>,<bits>,<unknown> */
+ r = g_regex_new ("\\^CVOICE:\\s*(\\d)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse ^CVOICE results: ");
+ } else {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't match ^CVOICE reply");
+ }
+ return FALSE;
+ }
+
+ /* Remember that g_match_info_get_match_count() includes match #0 */
+ g_assert (g_match_info_get_match_count (match_info) >= 5);
+
+ if (mm_get_uint_from_match_info (match_info, 1, &supported) &&
+ mm_get_uint_from_match_info (match_info, 2, &hz) &&
+ mm_get_uint_from_match_info (match_info, 3, &bits)) {
+ if (supported == 0) {
+ if (out_hz)
+ *out_hz = hz;
+ if (out_bits)
+ *out_bits = bits;
+ return TRUE;
+ }
+
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "^CVOICE not supported by this device");
+ return FALSE;
+ }
+
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Failed to parse ^CVOICE reply");
+ return FALSE;
+}
+
+/*****************************************************************************/
+/* ^GETPORTMODE response parser */
+
+#define GETPORTMODE_PREFIX "^GETPORTMODE:"
+
+GArray *
+mm_huawei_parse_getportmode_response (const gchar *response,
+ gpointer log_object,
+ GError **error)
+{
+ g_autoptr(GArray) modes = NULL;
+ g_auto(GStrv) split = NULL;
+ guint i;
+ gint n_items;
+
+ split = g_strsplit (response, ",", -1);
+ n_items = g_strv_length (split) - 1;
+ if (n_items < 1) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unexpected number of items in response");
+ return NULL;
+ }
+
+ /* validate response prefix */
+ if (g_ascii_strncasecmp (split[0], GETPORTMODE_PREFIX, strlen (GETPORTMODE_PREFIX)) != 0) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unexpected response prefix");
+ return NULL;
+ }
+
+ mm_obj_dbg (log_object, "processing ^GETPORTMODE response...");
+
+ modes = g_array_sized_new (FALSE, FALSE, sizeof (MMHuaweiPortMode), n_items);
+
+ /* iterate all port items found */
+ for (i = 1; split[i]; i++) {
+ MMHuaweiPortMode mode = MM_HUAWEI_PORT_MODE_NONE;
+ gchar *separator;
+ guint port_number;
+
+ separator = strchr (split[i], ':');
+ if (!separator)
+ continue;
+
+ /* the reported port number may start either by 0 or by 1; the important
+ * thing is therefore no the number itself, only that it's a number */
+ g_strstrip (&separator[1]);
+ if (!mm_get_uint_from_str (&separator[1], &port_number)) {
+ mm_obj_warn (log_object, " couldn't parse port number: %s", split[i]);
+ break;
+ }
+
+ *separator = '\0';
+ g_strstrip (split[i]);
+
+ if (g_ascii_strcasecmp (split[i], "pcui") == 0)
+ mode = MM_HUAWEI_PORT_MODE_PCUI;
+ else if ((g_ascii_strcasecmp (split[i], "mdm") == 0) ||
+ (g_ascii_strcasecmp (split[i], "modem") == 0) ||
+ (g_ascii_strcasecmp (split[i], "3g_modem") == 0))
+ mode = MM_HUAWEI_PORT_MODE_MODEM;
+ else if ((g_ascii_strcasecmp (split[i], "diag") == 0) ||
+ (g_ascii_strcasecmp (split[i], "3g_diag") == 0) ||
+ (g_ascii_strcasecmp (split[i], "4g_diag") == 0))
+ mode = MM_HUAWEI_PORT_MODE_DIAG;
+ else if (g_ascii_strcasecmp (split[i], "gps") == 0)
+ mode = MM_HUAWEI_PORT_MODE_GPS;
+ else if ((g_ascii_strcasecmp (split[i], "ndis") == 0) ||
+ (g_ascii_strcasecmp (split[i], "rndis") == 0) ||
+ (g_ascii_strcasecmp (split[i], "ncm") == 0) ||
+ (g_ascii_strcasecmp (split[i], "ecm") == 0))
+ mode = MM_HUAWEI_PORT_MODE_NET;
+ else if (g_ascii_strcasecmp (split[i], "cdrom") == 0)
+ mode = MM_HUAWEI_PORT_MODE_CDROM;
+ else if ((g_ascii_strcasecmp (split[i], "sd") == 0) ||
+ (g_ascii_strncasecmp (split[i], "mass", 4) == 0))
+ mode = MM_HUAWEI_PORT_MODE_SD;
+ else if (g_ascii_strcasecmp (split[i], "bt") == 0)
+ mode = MM_HUAWEI_PORT_MODE_BT;
+ else if ((g_ascii_strcasecmp (split[i], "a_shell") == 0) ||
+ (g_ascii_strcasecmp (split[i], "c_shell") == 0))
+ mode = MM_HUAWEI_PORT_MODE_SHELL;
+
+ mm_obj_dbg (log_object, " port mode %s reported at port number %u",
+ mm_huawei_port_mode_get_string (mode), port_number);
+ g_array_append_val (modes, mode);
+ }
+
+ if (!modes->len) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No port modes loaded");
+ return NULL;
+ }
+
+ return g_steal_pointer (&modes);
+}
diff --git a/src/plugins/huawei/mm-modem-helpers-huawei.h b/src/plugins/huawei/mm-modem-helpers-huawei.h
new file mode 100644
index 00000000..3d1a4b22
--- /dev/null
+++ b/src/plugins/huawei/mm-modem-helpers-huawei.h
@@ -0,0 +1,193 @@
+/* -*- 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) 2013 Huawei Technologies Co., Ltd
+ * Copyright (C) 2013 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_MODEM_HELPERS_HUAWEI_H
+#define MM_MODEM_HELPERS_HUAWEI_H
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+/*****************************************************************************/
+/* ^NDISSTAT / ^NDISSTATQRY response parser */
+gboolean mm_huawei_parse_ndisstatqry_response (const gchar *response,
+ gboolean *ipv4_available,
+ gboolean *ipv4_connected,
+ gboolean *ipv6_available,
+ gboolean *ipv6_connected,
+ GError **error);
+
+/*****************************************************************************/
+/* ^DHCP response parser */
+gboolean mm_huawei_parse_dhcp_response (const char *reply,
+ guint *out_address,
+ guint *out_prefix,
+ guint *out_gateway,
+ guint *out_dns1,
+ guint *out_dns2,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SYSINFO response parser */
+gboolean mm_huawei_parse_sysinfo_response (const char *reply,
+ guint *out_srv_status,
+ guint *out_srv_domain,
+ guint *out_roam_status,
+ guint *out_sys_mode,
+ guint *out_sim_state,
+ gboolean *out_sys_submode_valid,
+ guint *out_sys_submode,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SYSINFOEX response parser */
+gboolean mm_huawei_parse_sysinfoex_response (const char *reply,
+ guint *out_srv_status,
+ guint *out_srv_domain,
+ guint *out_roam_status,
+ guint *out_sim_state,
+ guint *out_sys_mode,
+ guint *out_sys_submode,
+ GError **error);
+
+/*****************************************************************************/
+/* ^PREFMODE test parser */
+
+typedef struct {
+ guint prefmode;
+ MMModemMode allowed;
+ MMModemMode preferred;
+} MMHuaweiPrefmodeCombination;
+
+GArray *mm_huawei_parse_prefmode_test (const gchar *response,
+ gpointer log_object,
+ GError **error);
+
+/*****************************************************************************/
+/* ^PREFMODE response parser */
+
+const MMHuaweiPrefmodeCombination *mm_huawei_parse_prefmode_response (const gchar *response,
+ const GArray *supported_mode_combinations,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SYSCFG test parser */
+
+/* This is the default string we use as fallback when the modem gives
+ * an empty response to AT^SYSCFG=? */
+#define MM_HUAWEI_DEFAULT_SYSCFG_FMT "^SYSCFG:(2,13,14,16),(0-3),,,"
+
+typedef struct {
+ guint mode;
+ guint acqorder;
+ MMModemMode allowed;
+ MMModemMode preferred;
+} MMHuaweiSyscfgCombination;
+
+GArray *mm_huawei_parse_syscfg_test (const gchar *response,
+ gpointer log_object,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SYSCFG response parser */
+
+const MMHuaweiSyscfgCombination *mm_huawei_parse_syscfg_response (const gchar *response,
+ const GArray *supported_mode_combinations,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SYSCFGEX test parser */
+
+typedef struct {
+ gchar *mode_str;
+ MMModemMode allowed;
+ MMModemMode preferred;
+} MMHuaweiSyscfgexCombination;
+
+GArray *mm_huawei_parse_syscfgex_test (const gchar *response,
+ GError **error);
+
+/*****************************************************************************/
+/* ^SYSCFGEX response parser */
+
+const MMHuaweiSyscfgexCombination *mm_huawei_parse_syscfgex_response (const gchar *response,
+ const GArray *supported_mode_combinations,
+ GError **error);
+
+/*****************************************************************************/
+/* ^NWTIME response parser */
+
+gboolean mm_huawei_parse_nwtime_response (const gchar *response,
+ gchar **iso8601p,
+ MMNetworkTimezone **tzp,
+ GError **error);
+
+/*****************************************************************************/
+/* ^TIME response parser */
+
+gboolean mm_huawei_parse_time_response (const gchar *response,
+ gchar **iso8601p,
+ MMNetworkTimezone **tzp,
+ GError **error);
+
+/*****************************************************************************/
+/* ^HCSQ response parser */
+
+gboolean mm_huawei_parse_hcsq_response (const gchar *response,
+ MMModemAccessTechnology *out_act,
+ guint *out_value1,
+ guint *out_value2,
+ guint *out_value3,
+ guint *out_value4,
+ guint *out_value5,
+ GError **error);
+
+/*****************************************************************************/
+/* ^CVOICE response parser */
+
+gboolean mm_huawei_parse_cvoice_response (const gchar *response,
+ guint *hz,
+ guint *bits,
+ GError **error);
+
+/*****************************************************************************/
+/* ^GETPORTMODE response parser */
+
+typedef enum { /*< underscore_name=mm_huawei_port_mode >*/
+ MM_HUAWEI_PORT_MODE_NONE,
+ MM_HUAWEI_PORT_MODE_PCUI,
+ MM_HUAWEI_PORT_MODE_MODEM,
+ MM_HUAWEI_PORT_MODE_DIAG,
+ MM_HUAWEI_PORT_MODE_GPS,
+ MM_HUAWEI_PORT_MODE_NET,
+ MM_HUAWEI_PORT_MODE_CDROM,
+ MM_HUAWEI_PORT_MODE_SD,
+ MM_HUAWEI_PORT_MODE_BT,
+ MM_HUAWEI_PORT_MODE_SHELL,
+} MMHuaweiPortMode;
+
+#define MM_HUAWEI_PORT_MODE_IS_SERIAL(mode) \
+ (mode == MM_HUAWEI_PORT_MODE_PCUI || \
+ mode == MM_HUAWEI_PORT_MODE_MODEM || \
+ mode == MM_HUAWEI_PORT_MODE_DIAG || \
+ mode == MM_HUAWEI_PORT_MODE_GPS || \
+ mode == MM_HUAWEI_PORT_MODE_SHELL)
+
+GArray *mm_huawei_parse_getportmode_response (const gchar *response,
+ gpointer log_object,
+ GError **error);
+
+#endif /* MM_MODEM_HELPERS_HUAWEI_H */
diff --git a/src/plugins/huawei/mm-plugin-huawei.c b/src/plugins/huawei/mm-plugin-huawei.c
new file mode 100644
index 00000000..ad8d32b0
--- /dev/null
+++ b/src/plugins/huawei/mm-plugin-huawei.c
@@ -0,0 +1,735 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <gmodule.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include <ModemManager-tags.h>
+#include "mm-port-enums-types.h"
+#include "mm-log.h"
+#include "mm-plugin-huawei.h"
+#include "mm-broadband-modem-huawei.h"
+#include "mm-modem-helpers-huawei.h"
+#include "mm-huawei-enums-types.h"
+
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi.h"
+#endif
+
+#if defined WITH_MBIM
+#include "mm-broadband-modem-mbim.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginHuawei, mm_plugin_huawei, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+/* Custom init */
+
+#define TAG_FIRST_INTERFACE_CONTEXT "first-interface-context"
+
+/* Maximum time to wait for the first interface 0 to appear and get probed.
+ * If it doesn't appear in this time, we'll decide which will be considered the
+ * first interface. */
+#define MAX_WAIT_TIME 5
+
+typedef struct {
+ MMPortProbe *probe;
+ gint first_usbif;
+ guint timeout_id;
+ gboolean custom_init_run;
+} FirstInterfaceContext;
+
+static void
+first_interface_context_free (FirstInterfaceContext *ctx)
+{
+ if (ctx->timeout_id)
+ g_source_remove (ctx->timeout_id);
+ g_object_unref (ctx->probe);
+ g_slice_free (FirstInterfaceContext, ctx);
+}
+
+#define TAG_GETPORTMODE_RESULT "getportmode-result"
+#define TAG_AT_PORT_FLAGS "at-port-flags"
+
+typedef struct {
+ MMPortSerialAt *port;
+ gboolean curc_done;
+ guint curc_retries;
+ gboolean getportmode_done;
+ guint getportmode_retries;
+} HuaweiCustomInitContext;
+
+static void
+huawei_custom_init_context_free (HuaweiCustomInitContext *ctx)
+{
+ g_object_unref (ctx->port);
+ g_slice_free (HuaweiCustomInitContext, ctx);
+}
+
+static gboolean
+huawei_custom_init_finish (MMPortProbe *probe,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void huawei_custom_init_step (GTask *task);
+
+static void
+getportmode_ready (MMPortSerialAt *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMDevice *device;
+ MMPortProbe *probe;
+ HuaweiCustomInitContext *ctx;
+ const gchar *response;
+ GArray *modes;
+ g_autoptr(GError) error = NULL;
+
+ probe = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+ device = mm_port_probe_peek_device (probe);
+
+ response = mm_port_serial_at_command_finish (port, res, &error);
+ if (error) {
+ mm_obj_dbg (probe, "couldn't get port mode: '%s'", error->message);
+
+ /* If any error occurred that was not ERROR or COMMAND NOT SUPPORT then
+ * retry the command.
+ */
+ if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN))
+ ctx->getportmode_done = TRUE;
+ huawei_custom_init_step (task);
+ return;
+ }
+
+ /* Mark port as being AT already */
+ mm_port_probe_set_result_at (probe, TRUE);
+
+ /* Flag as GETPORTMODE already done */
+ ctx->getportmode_done = TRUE;
+
+ modes = mm_huawei_parse_getportmode_response (response, probe, &error);
+ if (!modes)
+ mm_obj_warn (probe, "failed to parse ^GETPORTMODE response: %s", error->message);
+ else
+ g_object_set_data_full (G_OBJECT (device), TAG_GETPORTMODE_RESULT, modes, (GDestroyNotify) g_array_unref);
+ huawei_custom_init_step (task);
+}
+
+static void
+curc_ready (MMPortSerialAt *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMPortProbe *probe;
+ HuaweiCustomInitContext *ctx;
+ g_autoptr(GError) error = NULL;
+
+ probe = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ mm_port_serial_at_command_finish (port, res, &error);
+ if (error) {
+ /* Retry if we get a timeout error */
+ if (g_error_matches (error,
+ MM_SERIAL_ERROR,
+ MM_SERIAL_ERROR_RESPONSE_TIMEOUT))
+ goto out;
+
+ mm_obj_dbg (probe, "couldn't turn off unsolicited messages in secondary ports: %s", error->message);
+ }
+
+ mm_obj_dbg (probe, "unsolicited messages in secondary ports turned off");
+
+ ctx->curc_done = TRUE;
+
+out:
+ huawei_custom_init_step (task);
+}
+
+static void
+try_next_usbif (MMPortProbe *probe,
+ MMDevice *device)
+{
+ FirstInterfaceContext *fi_ctx;
+ GList *l;
+ gint closest;
+
+ fi_ctx = g_object_get_data (G_OBJECT (device), TAG_FIRST_INTERFACE_CONTEXT);
+ g_assert (fi_ctx != NULL);
+
+ /* Look for the next closest one among the list of interfaces in the device,
+ * and enable that one as being first */
+ closest = G_MAXINT;
+ for (l = mm_device_peek_port_probe_list (device); l; l = g_list_next (l)) {
+ MMPortProbe *iter = MM_PORT_PROBE (l->data);
+
+ /* Only expect ttys for next probing attempt */
+ if (g_str_equal (mm_port_probe_get_port_subsys (iter), "tty")) {
+ gint usbif;
+
+ usbif = mm_kernel_device_get_interface_number (mm_port_probe_peek_port (iter));
+ if (usbif == fi_ctx->first_usbif) {
+ /* This is the one we just probed, which wasn't yet removed, so just skip it */
+ } else if (usbif > fi_ctx->first_usbif &&
+ usbif < closest) {
+ closest = usbif;
+ }
+ }
+ }
+
+ if (closest == G_MAXINT) {
+ /* No more ttys to try! Just return something */
+ closest = 0;
+ mm_obj_dbg (probe, "no more ports to run initial probing");
+ } else
+ mm_obj_dbg (probe, "will try initial probing with interface '%d' instead", closest);
+
+ fi_ctx->first_usbif = closest;
+}
+
+static void
+huawei_custom_init_step (GTask *task)
+{
+ MMPortProbe *probe;
+ HuaweiCustomInitContext *ctx;
+ FirstInterfaceContext *fi_ctx;
+ MMKernelDevice *port;
+
+ probe = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* If cancelled, end */
+ if (g_task_return_error_if_cancelled (task)) {
+ mm_obj_dbg (probe, "no need to keep on running custom init");
+ g_object_unref (task);
+ return;
+ }
+
+ if (!ctx->curc_done) {
+ if (ctx->curc_retries == 0) {
+ /* All retries consumed, probably not an AT port */
+ mm_port_probe_set_result_at (probe, FALSE);
+ /* Try with next */
+ try_next_usbif (probe, mm_port_probe_peek_device (probe));
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->curc_retries--;
+ /* Turn off unsolicited messages on secondary ports until needed */
+ mm_port_serial_at_command (
+ ctx->port,
+ "AT^CURC=0",
+ 3,
+ FALSE, /* raw */
+ FALSE, /* allow_cached */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)curc_ready,
+ task);
+ return;
+ }
+
+ /* Try to get a port map from the modem */
+ port = mm_port_probe_peek_port (probe);
+ if (!ctx->getportmode_done && !mm_kernel_device_get_global_property_as_boolean (port, "ID_MM_HUAWEI_DISABLE_GETPORTMODE")) {
+ if (ctx->getportmode_retries == 0) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->getportmode_retries--;
+ mm_port_serial_at_command (
+ ctx->port,
+ "AT^GETPORTMODE",
+ 3,
+ FALSE, /* raw */
+ FALSE, /* allow_cached */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)getportmode_ready,
+ task);
+ return;
+ }
+
+ /* All done it seems */
+ fi_ctx = g_object_get_data (G_OBJECT (mm_port_probe_peek_device (probe)), TAG_FIRST_INTERFACE_CONTEXT);
+ g_assert (fi_ctx != NULL);
+ fi_ctx->custom_init_run = TRUE;
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static gboolean
+first_interface_missing_timeout_cb (MMDevice *device)
+{
+ FirstInterfaceContext *fi_ctx;
+
+ fi_ctx = g_object_get_data (G_OBJECT (device), TAG_FIRST_INTERFACE_CONTEXT);
+ g_assert (fi_ctx != NULL);
+ try_next_usbif (fi_ctx->probe, device);
+
+ /* Reload the timeout, just in case we end up not having the next interface to probe...
+ * which is anyway very unlikely as we got it by looking at the real probe list, but anyway... */
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+huawei_custom_init (MMPortProbe *probe,
+ MMPortSerialAt *port,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMDevice *device;
+ FirstInterfaceContext *fi_ctx;
+ HuaweiCustomInitContext *ctx;
+ GTask *task;
+
+ device = mm_port_probe_peek_device (probe);
+
+ /* The primary port (called the "modem" port in the Windows drivers) is
+ * always USB interface 0, and we need to detect that interface first for
+ * two reasons: (1) to disable unsolicited messages on other ports that
+ * may fill up the buffer and crash the device, and (2) to attempt to get
+ * the port layout for hints about what the secondary port is (called the
+ * "pcui" port in Windows). Thus we probe USB interface 0 first and defer
+ * probing other interfaces until we've got if0, at which point we allow
+ * the other ports to be probed too.
+ */
+ fi_ctx = g_object_get_data (G_OBJECT (device), TAG_FIRST_INTERFACE_CONTEXT);
+ if (!fi_ctx) {
+ /* This is the first time we ask for the context. Set it up. */
+ fi_ctx = g_slice_new0 (FirstInterfaceContext);
+ fi_ctx->probe = g_object_ref (probe);
+ g_object_set_data_full (G_OBJECT (device),
+ TAG_FIRST_INTERFACE_CONTEXT,
+ fi_ctx,
+ (GDestroyNotify)first_interface_context_free);
+ /* The timeout is controlled in the data set in 'device', and therefore
+ * it should be safe to assume that the timeout will not get fired after
+ * having disposed 'device' */
+ fi_ctx->timeout_id = g_timeout_add_seconds (MAX_WAIT_TIME,
+ (GSourceFunc)first_interface_missing_timeout_cb,
+ device);
+
+ /* By default, we'll ask the Huawei plugin to start probing usbif 0 */
+ fi_ctx->first_usbif = 0;
+
+ /* Custom init of the Huawei plugin is to be run only in the first
+ * interface. We'll control here whether we did run it already or not. */
+ fi_ctx->custom_init_run = FALSE;
+ }
+
+ ctx = g_slice_new (HuaweiCustomInitContext);
+ ctx->port = g_object_ref (port);
+ ctx->curc_done = FALSE;
+ ctx->curc_retries = 3;
+ ctx->getportmode_done = FALSE;
+ ctx->getportmode_retries = 3;
+
+ task = g_task_new (probe, cancellable, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)huawei_custom_init_context_free);
+
+ /* Custom init only to be run in the first interface */
+ if (mm_kernel_device_get_interface_number (mm_port_probe_peek_port (probe)) != fi_ctx->first_usbif) {
+ if (fi_ctx->custom_init_run)
+ /* If custom init was run already, we can consider this as successfully run */
+ g_task_return_boolean (task, TRUE);
+ else
+ /* Otherwise, we'll need to defer the probing a bit more */
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_RETRY,
+ "Defer needed");
+
+ g_object_unref (task);
+ return;
+ }
+
+ /* We can run custom init in the first interface! clear the timeout as it is no longer needed */
+ if (fi_ctx->timeout_id) {
+ g_source_remove (fi_ctx->timeout_id);
+ fi_ctx->timeout_id = 0;
+ }
+
+ huawei_custom_init_step (task);
+}
+
+/*****************************************************************************/
+
+static gint
+probe_cmp_by_usbif (MMPortProbe *a,
+ MMPortProbe *b)
+{
+ return (mm_kernel_device_get_interface_number (mm_port_probe_peek_port (a)) -
+ mm_kernel_device_get_interface_number (mm_port_probe_peek_port (b)));
+}
+
+static guint
+propagate_getportmode_hints (MMPlugin *self,
+ GList *probes,
+ gboolean *primary_flagged)
+{
+ MMDevice *device;
+ GArray *modes;
+ GList *l;
+ GList *tty_probes = NULL;
+ guint n_ports_with_hints = 0;
+ guint mode_i = 0;
+
+ g_assert (probes != NULL);
+ device = mm_port_probe_peek_device (MM_PORT_PROBE (probes->data));
+ modes = g_object_get_data (G_OBJECT (device), TAG_GETPORTMODE_RESULT);
+
+ /* Nothing to do if GETPORTMODE is flagged as not supported */
+ if (!modes)
+ return 0;
+
+ /* Build a list of TTY port probes (AT and not-AT) sorted by interface number */
+ for (l = probes; l; l = g_list_next (l)) {
+ MMPortProbe *probe;
+
+ probe = MM_PORT_PROBE (l->data);
+ if (g_str_equal (mm_port_probe_get_port_subsys (probe), "tty"))
+ tty_probes = g_list_insert_sorted (tty_probes, probe, (GCompareFunc) probe_cmp_by_usbif);
+ }
+
+ /* Propagate the getportmode tags to the specific port probes */
+ for (l = tty_probes, mode_i = 0; l; l = g_list_next (l)) {
+ MMPortProbe *probe;
+ MMPortSerialAtFlag at_port_flags = MM_PORT_SERIAL_AT_FLAG_NONE;
+ MMHuaweiPortMode port_mode;
+
+ probe = MM_PORT_PROBE (l->data);
+
+ /* Look for the next serial port mode applicable */
+ while (!MM_HUAWEI_PORT_MODE_IS_SERIAL (g_array_index (modes, MMHuaweiPortMode, mode_i)) && (mode_i < modes->len))
+ mode_i++;
+ if (mode_i == modes->len) {
+ mm_obj_dbg (probe, "missing port mode hint");
+ continue;
+ }
+
+ port_mode = g_array_index (modes, MMHuaweiPortMode, mode_i);
+ if (!mm_port_probe_is_at (probe)) {
+ mm_obj_dbg (probe, "port mode hint for non-AT port: %s", mm_huawei_port_mode_get_string (port_mode));
+ mode_i++;
+ continue;
+ }
+
+ mm_obj_dbg (probe, "port mode hint for AT port: %s", mm_huawei_port_mode_get_string (port_mode));
+ if (port_mode == MM_HUAWEI_PORT_MODE_PCUI)
+ at_port_flags = MM_PORT_SERIAL_AT_FLAG_PRIMARY;
+ else if (port_mode == MM_HUAWEI_PORT_MODE_MODEM)
+ at_port_flags = MM_PORT_SERIAL_AT_FLAG_PPP;
+
+ if (at_port_flags != MM_PORT_SERIAL_AT_FLAG_NONE) {
+ n_ports_with_hints++;
+ g_object_set_data (G_OBJECT (probe), TAG_AT_PORT_FLAGS, GUINT_TO_POINTER (at_port_flags));
+ }
+ mode_i++;
+ }
+
+ g_list_free (tty_probes);
+
+ return n_ports_with_hints;
+}
+
+static guint
+propagate_description_hints (MMPlugin *self,
+ GList *probes,
+ gboolean *primary_flagged)
+{
+ GList *l;
+ guint n_ports_with_hints = 0;
+
+ for (l = probes; l; l = g_list_next (l)) {
+ MMPortProbe *probe;
+ MMPortSerialAtFlag at_port_flags = MM_PORT_SERIAL_AT_FLAG_NONE;
+ const gchar *description;
+ g_autofree gchar *lower_description = NULL;
+
+ probe = MM_PORT_PROBE (l->data);
+
+ if (!mm_port_probe_is_at (probe))
+ continue;
+
+ description = mm_kernel_device_get_interface_description (mm_port_probe_peek_port (probe));
+ if (!description)
+ continue;
+
+ mm_obj_dbg (probe, "%s interface description: %s", mm_port_probe_get_port_name (probe), description);
+
+ lower_description = g_ascii_strdown (description, -1);
+ if (strstr (lower_description, "modem"))
+ at_port_flags = MM_PORT_SERIAL_AT_FLAG_PPP;
+ else if (strstr (lower_description, "pcui")) {
+ at_port_flags = MM_PORT_SERIAL_AT_FLAG_PRIMARY;
+ *primary_flagged = TRUE;
+ }
+
+ if (at_port_flags != MM_PORT_SERIAL_AT_FLAG_NONE) {
+ n_ports_with_hints++;
+ g_object_set_data (G_OBJECT (probe), TAG_AT_PORT_FLAGS, GUINT_TO_POINTER (at_port_flags));
+ }
+ }
+
+ return n_ports_with_hints;
+}
+
+static guint
+propagate_generic_hints (MMPlugin *self,
+ GList *probes,
+ gboolean *primary_flagged)
+{
+ GList *l;
+ guint n_ports_with_hints = 0;
+
+ for (l = probes; l; l = g_list_next (l)) {
+ MMPortProbe *probe;
+ MMKernelDevice *kernel_device;
+ MMPortSerialAtFlag at_port_flags = MM_PORT_SERIAL_AT_FLAG_NONE;
+
+ probe = MM_PORT_PROBE (l->data);
+
+ if (!mm_port_probe_is_at (probe))
+ continue;
+
+ kernel_device = mm_port_probe_peek_port (probe);
+ if (mm_kernel_device_get_property_as_boolean (kernel_device, ID_MM_PORT_TYPE_AT_PRIMARY)) {
+ at_port_flags = MM_PORT_SERIAL_AT_FLAG_PRIMARY;
+ *primary_flagged = TRUE;
+ }
+ else if (mm_kernel_device_get_property_as_boolean (kernel_device, ID_MM_PORT_TYPE_AT_SECONDARY))
+ at_port_flags = MM_PORT_SERIAL_AT_FLAG_SECONDARY;
+ else if (mm_kernel_device_get_property_as_boolean (kernel_device, ID_MM_PORT_TYPE_AT_PPP))
+ at_port_flags = MM_PORT_SERIAL_AT_FLAG_PPP;
+
+ if (at_port_flags != MM_PORT_SERIAL_AT_FLAG_NONE) {
+ n_ports_with_hints++;
+ g_object_set_data (G_OBJECT (probe), TAG_AT_PORT_FLAGS, GUINT_TO_POINTER (at_port_flags));
+ }
+ }
+
+ return n_ports_with_hints;
+}
+
+static guint
+fallback_primary_cdcwdm (MMPlugin *self,
+ GList *probes)
+{
+ GList *l;
+
+ for (l = probes; l; l = g_list_next (l)) {
+ MMPortProbe *probe;
+
+ probe = MM_PORT_PROBE (l->data);
+
+ if (!mm_port_probe_is_at (probe))
+ continue;
+
+ if (g_str_equal (mm_port_probe_get_port_subsys (probe), "usbmisc")) {
+ mm_obj_dbg (self, "fallback port type hint applied to first cdc-wmd port found");
+ g_object_set_data (G_OBJECT (probe), TAG_AT_PORT_FLAGS, GUINT_TO_POINTER (MM_PORT_SERIAL_AT_FLAG_PRIMARY));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static guint
+fallback_usbif0 (MMPlugin *self,
+ GList *probes)
+{
+ GList *l;
+
+ for (l = probes; l; l = g_list_next (l)) {
+ MMPortProbe *probe;
+ guint usbif;
+
+ probe = MM_PORT_PROBE (l->data);
+
+ if (!mm_port_probe_is_at (probe))
+ continue;
+
+ usbif = mm_kernel_device_get_property_as_int_hex (mm_port_probe_peek_port (probe), "ID_USB_INTERFACE_NUM");
+ if (usbif == 0) {
+ mm_obj_dbg (self, "fallback port type hint applied to interface 0");
+ g_object_set_data (G_OBJECT (probe), TAG_AT_PORT_FLAGS, GUINT_TO_POINTER (MM_PORT_SERIAL_AT_FLAG_PPP));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void
+propagate_port_type_hints (MMPlugin *self,
+ GList *probes)
+{
+ gboolean primary_flagged = FALSE;
+ guint n_ports_with_hints;
+
+ g_assert (probes != NULL);
+
+ if ((n_ports_with_hints = propagate_getportmode_hints (self, probes, &primary_flagged)) > 0)
+ mm_obj_dbg (self, "port type hints set by GETPORTMODE");
+ else if ((n_ports_with_hints = propagate_description_hints (self, probes, &primary_flagged)) > 0)
+ mm_obj_dbg (self, "port type hints set by interface descriptions");
+ else if ((n_ports_with_hints = propagate_generic_hints (self, probes, &primary_flagged)) > 0)
+ mm_obj_dbg (self, "port type hints set by generic udev tags");
+
+ /* Fallback hint for the first cdc-wdm port if no other port has been flagged as primary */
+ if (!primary_flagged)
+ n_ports_with_hints += fallback_primary_cdcwdm (self, probes);
+
+ /* If not a single port type hint available (not plugin-provided and not generic)
+ * then we'll assume usbif 0 is the modem port */
+ if (!n_ports_with_hints)
+ n_ports_with_hints = fallback_usbif0 (self, probes);
+
+ mm_obj_dbg (self, "%u port hints have been set", n_ports_with_hints);
+}
+
+static MMBaseModem *
+create_modem (MMPlugin *self,
+ const gchar *uid,
+ const gchar **drivers,
+ guint16 vendor,
+ guint16 product,
+ guint16 subsystem_vendor,
+ GList *probes,
+ GError **error)
+{
+ propagate_port_type_hints (self, probes);
+
+#if defined WITH_QMI
+ if (mm_port_probe_list_has_qmi_port (probes)) {
+ mm_obj_dbg (self, "QMI-powered Huawei modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_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 Huawei modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_huawei_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+static gboolean
+grab_port (MMPlugin *self,
+ MMBaseModem *modem,
+ MMPortProbe *probe,
+ GError **error)
+{
+ MMPortSerialAtFlag pflags;
+ MMKernelDevice *port;
+ MMPortType port_type;
+
+ port_type = mm_port_probe_get_port_type (probe);
+ port = mm_port_probe_peek_port (probe);
+
+ pflags = (MMPortSerialAtFlag) GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (probe), TAG_AT_PORT_FLAGS));
+ if (pflags != MM_PORT_SERIAL_AT_FLAG_NONE) {
+ gchar *str;
+
+ str = mm_port_serial_at_flag_build_string_from_mask (pflags);
+ mm_obj_dbg (self, "(%s/%s) port will have AT flags '%s'",
+ mm_port_probe_get_port_subsys (probe),
+ mm_port_probe_get_port_name (probe),
+ str);
+ g_free (str);
+ } else {
+ /* The huawei plugin handles the generic udev tags itself, so explicitly request
+ * to avoid processing them by the generic modem. */
+ pflags = MM_PORT_SERIAL_AT_FLAG_NONE_NO_GENERIC;
+ }
+
+ return mm_base_modem_grab_port (modem,
+ port,
+ port_type,
+ pflags,
+ error);
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL };
+ static const guint16 vendor_ids[] = { 0x12d1, 0 };
+ static const MMAsyncMethod custom_init = {
+ .async = G_CALLBACK (huawei_custom_init),
+ .finish = G_CALLBACK (huawei_custom_init_finish),
+ };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_HUAWEI,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_REQUIRED_QCDM, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ MM_PLUGIN_ALLOWED_MBIM, TRUE,
+ MM_PLUGIN_CUSTOM_INIT, &custom_init,
+ NULL));
+}
+
+static void
+mm_plugin_huawei_init (MMPluginHuawei *self)
+{
+}
+
+static void
+mm_plugin_huawei_class_init (MMPluginHuaweiClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+ plugin_class->grab_port = grab_port;
+}
diff --git a/src/plugins/huawei/mm-plugin-huawei.h b/src/plugins/huawei/mm-plugin-huawei.h
new file mode 100644
index 00000000..daba2055
--- /dev/null
+++ b/src/plugins/huawei/mm-plugin-huawei.h
@@ -0,0 +1,42 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org
+ */
+
+#ifndef MM_PLUGIN_HUAWEI_H
+#define MM_PLUGIN_HUAWEI_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_HUAWEI (mm_plugin_huawei_get_type ())
+#define MM_PLUGIN_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_HUAWEI, MMPluginHuawei))
+#define MM_PLUGIN_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_HUAWEI, MMPluginHuaweiClass))
+#define MM_IS_PLUGIN_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_HUAWEI))
+#define MM_IS_PLUGIN_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_HUAWEI))
+#define MM_PLUGIN_HUAWEI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_HUAWEI, MMPluginHuaweiClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginHuawei;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginHuaweiClass;
+
+GType mm_plugin_huawei_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_HUAWEI_H */
diff --git a/src/plugins/huawei/mm-sim-huawei.c b/src/plugins/huawei/mm-sim-huawei.c
new file mode 100644
index 00000000..f937c773
--- /dev/null
+++ b/src/plugins/huawei/mm-sim-huawei.c
@@ -0,0 +1,167 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+#include "mm-modem-helpers.h"
+#include "mm-base-modem-at.h"
+
+#include "mm-sim-huawei.h"
+
+G_DEFINE_TYPE (MMSimHuawei, mm_sim_huawei, MM_TYPE_BASE_SIM)
+
+/*****************************************************************************/
+/* SIM identifier loading */
+
+static gchar *
+load_sim_identifier_finish (MMBaseSim *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_load_sim_identifier_ready (MMSimHuawei *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ gchar *simid;
+
+ simid = MM_BASE_SIM_CLASS (mm_sim_huawei_parent_class)->load_sim_identifier_finish (MM_BASE_SIM (self), res, &error);
+ if (simid)
+ g_task_return_pointer (task, simid, g_free);
+ else
+ g_task_return_error (task, error);
+
+ g_object_unref (task);
+}
+
+static void
+iccid_read_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseSim *self;
+ const gchar *response;
+ const gchar *p;
+ char *parsed;
+
+ response = mm_base_modem_at_command_finish (modem, res, NULL);
+ if (!response)
+ goto error;
+
+ p = mm_strip_tag (response, "^ICCID:");
+ if (!p)
+ goto error;
+
+ parsed = mm_3gpp_parse_iccid (p, NULL);
+ if (parsed) {
+ g_task_return_pointer (task, parsed, g_free);
+ g_object_unref (task);
+ return;
+ }
+
+error:
+ /* Chain up to parent method; older devices don't support ^ICCID */
+ self = g_task_get_source_object (task);
+ MM_BASE_SIM_CLASS (mm_sim_huawei_parent_class)->load_sim_identifier (self,
+ (GAsyncReadyCallback) parent_load_sim_identifier_ready,
+ task);
+}
+
+static void
+load_sim_identifier (MMBaseSim *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBaseModem *modem = NULL;
+
+ g_object_get (self,
+ MM_BASE_SIM_MODEM, &modem,
+ NULL);
+
+ mm_base_modem_at_command (
+ modem,
+ "^ICCID?",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)iccid_read_ready,
+ g_task_new (self, NULL, callback, user_data));
+ g_object_unref (modem);
+}
+
+/*****************************************************************************/
+
+MMBaseSim *
+mm_sim_huawei_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *source;
+ GObject *sim;
+
+ source = g_async_result_get_source_object (res);
+ sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!sim)
+ return NULL;
+
+ /* Only export valid SIMs */
+ mm_base_sim_export (MM_BASE_SIM (sim));
+
+ return MM_BASE_SIM (sim);
+}
+
+void
+mm_sim_huawei_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (MM_TYPE_SIM_HUAWEI,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_SIM_MODEM, modem,
+ "active", TRUE, /* by default always active */
+ NULL);
+}
+
+static void
+mm_sim_huawei_init (MMSimHuawei *self)
+{
+}
+
+static void
+mm_sim_huawei_class_init (MMSimHuaweiClass *klass)
+{
+ MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass);
+
+ base_sim_class->load_sim_identifier = load_sim_identifier;
+ base_sim_class->load_sim_identifier_finish = load_sim_identifier_finish;
+}
diff --git a/src/plugins/huawei/mm-sim-huawei.h b/src/plugins/huawei/mm-sim-huawei.h
new file mode 100644
index 00000000..eeeefbaf
--- /dev/null
+++ b/src/plugins/huawei/mm-sim-huawei.h
@@ -0,0 +1,53 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#ifndef MM_SIM_HUAWEI_H
+#define MM_SIM_HUAWEI_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "mm-base-sim.h"
+
+#define MM_TYPE_SIM_HUAWEI (mm_sim_huawei_get_type ())
+#define MM_SIM_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_HUAWEI, MMSimHuawei))
+#define MM_SIM_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_HUAWEI, MMSimHuaweiClass))
+#define MM_IS_SIM_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_HUAWEI))
+#define MM_IS_SIM_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_HUAWEI))
+#define MM_SIM_HUAWEI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_HUAWEI, MMSimHuaweiClass))
+
+typedef struct _MMSimHuawei MMSimHuawei;
+typedef struct _MMSimHuaweiClass MMSimHuaweiClass;
+
+struct _MMSimHuawei {
+ MMBaseSim parent;
+};
+
+struct _MMSimHuaweiClass {
+ MMBaseSimClass parent;
+};
+
+GType mm_sim_huawei_get_type (void);
+
+void mm_sim_huawei_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseSim *mm_sim_huawei_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SIM_HUAWEI_H */
diff --git a/src/plugins/huawei/tests/test-modem-helpers-huawei.c b/src/plugins/huawei/tests/test-modem-helpers-huawei.c
new file mode 100644
index 00000000..e6f2490b
--- /dev/null
+++ b/src/plugins/huawei/tests/test-modem-helpers-huawei.c
@@ -0,0 +1,1422 @@
+/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include <locale.h>
+#include <arpa/inet.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-test.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-huawei.h"
+
+/*****************************************************************************/
+/* Test ^NDISSTAT / ^NDISSTATQRY responses */
+
+typedef struct {
+ const gchar *str;
+ gboolean expected_ipv4_available;
+ gboolean expected_ipv4_connected;
+ gboolean expected_ipv6_available;
+ gboolean expected_ipv6_connected;
+} NdisstatqryTest;
+
+static const NdisstatqryTest ndisstatqry_tests[] = {
+ { "^NDISSTAT: 1,,,IPV4\r\n", TRUE, TRUE, FALSE, FALSE },
+ { "^NDISSTAT: 0,,,IPV4\r\n", TRUE, FALSE, FALSE, FALSE },
+ { "^NDISSTAT: 1,,,IPV6\r\n", FALSE, FALSE, TRUE, TRUE },
+ { "^NDISSTAT: 0,,,IPV6\r\n", FALSE, FALSE, TRUE, FALSE },
+ { "^NDISSTAT: 1,,,IPV4\r\n"
+ "^NDISSTAT: 1,,,IPV6\r\n", TRUE, TRUE, TRUE, TRUE },
+ { "^NDISSTAT: 1,,,IPV4\r\n"
+ "^NDISSTAT: 0,,,IPV6\r\n", TRUE, TRUE, TRUE, FALSE },
+ { "^NDISSTAT: 0,,,IPV4\r\n"
+ "^NDISSTAT: 1,,,IPV6\r\n", TRUE, FALSE, TRUE, TRUE },
+ { "^NDISSTAT: 0,,,IPV4\r\n"
+ "^NDISSTAT: 0,,,IPV6\r\n", TRUE, FALSE, TRUE, FALSE },
+ { "^NDISSTAT: 1,,,IPV4", TRUE, TRUE, FALSE, FALSE },
+ { "^NDISSTAT: 0,,,IPV4", TRUE, FALSE, FALSE, FALSE },
+ { "^NDISSTAT: 1,,,IPV6", FALSE, FALSE, TRUE, TRUE },
+ { "^NDISSTAT: 0,,,IPV6", FALSE, FALSE, TRUE, FALSE },
+ { "^NDISSTAT: 1,,,IPV4\r\n"
+ "^NDISSTAT: 1,,,IPV6", TRUE, TRUE, TRUE, TRUE },
+ { "^NDISSTAT: 1,,,IPV4\r\n"
+ "^NDISSTAT: 0,,,IPV6", TRUE, TRUE, TRUE, FALSE },
+ { "^NDISSTAT: 0,,,IPV4\r\n"
+ "^NDISSTAT: 1,,,IPV6", TRUE, FALSE, TRUE, TRUE },
+ { "^NDISSTAT: 0,,,IPV4\r\n"
+ "^NDISSTAT: 0,,,IPV6", TRUE, FALSE, TRUE, FALSE },
+ { "^NDISSTAT: 1,,,\"IPV4\",1,,,\"IPV6\"", TRUE, TRUE, TRUE, TRUE },
+ { "^NDISSTAT: 1,,,\"IPV4\",0,,,\"IPV6\"", TRUE, TRUE, TRUE, FALSE },
+ { "^NDISSTAT: 0,,,\"IPV4\",1,,,\"IPV6\"", TRUE, FALSE, TRUE, TRUE },
+ { "^NDISSTAT: 0,,,\"IPV4\",0,,,\"IPV6\"", TRUE, FALSE, TRUE, FALSE },
+ { "^NDISSTAT: 1,,,\"IPV4\",1,,,\"IPV6\"\r\n", TRUE, TRUE, TRUE, TRUE },
+ { "^NDISSTAT: 1,,,\"IPV4\",0,,,\"IPV6\"\r\n", TRUE, TRUE, TRUE, FALSE },
+ { "^NDISSTAT: 0,,,\"IPV4\",1,,,\"IPV6\"\r\n", TRUE, FALSE, TRUE, TRUE },
+ { "^NDISSTAT: 0,,,\"IPV4\",0,,,\"IPV6\"\r\n", TRUE, FALSE, TRUE, FALSE },
+ { "^NDISSTATQRY: 1,,,IPV4\r\n", TRUE, TRUE, FALSE, FALSE },
+ { "^NDISSTATQRY: 0,,,IPV4\r\n", TRUE, FALSE, FALSE, FALSE },
+ { "^NDISSTATQRY: 1,,,IPV6\r\n", FALSE, FALSE, TRUE, TRUE },
+ { "^NDISSTATQRY: 0,,,IPV6\r\n", FALSE, FALSE, TRUE, FALSE },
+ { "^NDISSTATQRY: 1,,,IPV4\r\n"
+ "^NDISSTATQRY: 1,,,IPV6\r\n", TRUE, TRUE, TRUE, TRUE },
+ { "^NDISSTATQRY: 1,,,IPV4\r\n"
+ "^NDISSTATQRY: 0,,,IPV6\r\n", TRUE, TRUE, TRUE, FALSE },
+ { "^NDISSTATQRY: 0,,,IPV4\r\n"
+ "^NDISSTATQRY: 1,,,IPV6\r\n", TRUE, FALSE, TRUE, TRUE },
+ { "^NDISSTATQRY: 0,,,IPV4\r\n"
+ "^NDISSTATQRY: 0,,,IPV6\r\n", TRUE, FALSE, TRUE, FALSE },
+ { "^NDISSTATQRY: 1,,,IPV4", TRUE, TRUE, FALSE, FALSE },
+ { "^NDISSTATQRY: 0,,,IPV4", TRUE, FALSE, FALSE, FALSE },
+ { "^NDISSTATQRY: 1,,,IPV6", FALSE, FALSE, TRUE, TRUE },
+ { "^NDISSTATQRY: 0,,,IPV6", FALSE, FALSE, TRUE, FALSE },
+ { "^NDISSTATQRY: 1,,,IPV4\r\n"
+ "^NDISSTATQRY: 1,,,IPV6", TRUE, TRUE, TRUE, TRUE },
+ { "^NDISSTATQRY: 1,,,IPV4\r\n"
+ "^NDISSTATQRY: 0,,,IPV6", TRUE, TRUE, TRUE, FALSE },
+ { "^NDISSTATQRY: 0,,,IPV4\r\n"
+ "^NDISSTATQRY: 1,,,IPV6", TRUE, FALSE, TRUE, TRUE },
+ { "^NDISSTATQRY: 0,,,IPV4\r\n"
+ "^NDISSTATQRY: 0,,,IPV6", TRUE, FALSE, TRUE, FALSE },
+ { "^NDISSTATQRY: 1,,,\"IPV4\",1,,,\"IPV6\"", TRUE, TRUE, TRUE, TRUE },
+ { "^NDISSTATQRY: 1,,,\"IPV4\",0,,,\"IPV6\"", TRUE, TRUE, TRUE, FALSE },
+ { "^NDISSTATQRY: 0,,,\"IPV4\",1,,,\"IPV6\"", TRUE, FALSE, TRUE, TRUE },
+ { "^NDISSTATQRY: 0,,,\"IPV4\",0,,,\"IPV6\"", TRUE, FALSE, TRUE, FALSE },
+ { "^NDISSTATQRY: 1,,,\"IPV4\",1,,,\"IPV6\"\r\n", TRUE, TRUE, TRUE, TRUE },
+ { "^NDISSTATQRY: 1,,,\"IPV4\",0,,,\"IPV6\"\r\n", TRUE, TRUE, TRUE, FALSE },
+ { "^NDISSTATQRY: 0,,,\"IPV4\",1,,,\"IPV6\"\r\n", TRUE, FALSE, TRUE, TRUE },
+ { "^NDISSTATQRY: 0,,,\"IPV4\",0,,,\"IPV6\"\r\n", TRUE, FALSE, TRUE, FALSE },
+ { "^NDISSTATQry:1", TRUE, TRUE, FALSE, FALSE },
+ { "^NDISSTATQry:1\r\n", TRUE, TRUE, FALSE, FALSE },
+ { "^NDISSTATQry:0", TRUE, FALSE, FALSE, FALSE },
+ { "^NDISSTATQry:0\r\n", TRUE, FALSE, FALSE, FALSE },
+ { NULL, FALSE, FALSE, FALSE, FALSE }
+};
+
+static void
+test_ndisstatqry (void)
+{
+ guint i;
+
+ for (i = 0; ndisstatqry_tests[i].str; i++) {
+ GError *error = NULL;
+ gboolean ipv4_available;
+ gboolean ipv4_connected;
+ gboolean ipv6_available;
+ gboolean ipv6_connected;
+
+ g_assert (mm_huawei_parse_ndisstatqry_response (
+ ndisstatqry_tests[i].str,
+ &ipv4_available,
+ &ipv4_connected,
+ &ipv6_available,
+ &ipv6_connected,
+ &error) == TRUE);
+ g_assert_no_error (error);
+
+ g_assert (ipv4_available == ndisstatqry_tests[i].expected_ipv4_available);
+ if (ipv4_available)
+ g_assert (ipv4_connected == ndisstatqry_tests[i].expected_ipv4_connected);
+ g_assert (ipv6_available == ndisstatqry_tests[i].expected_ipv6_available);
+ if (ipv6_available)
+ g_assert (ipv6_connected == ndisstatqry_tests[i].expected_ipv6_connected);
+ }
+}
+
+/*****************************************************************************/
+/* Test ^DHCP responses */
+
+typedef struct {
+ const gchar *str;
+ const gchar *expected_addr;
+ guint expected_prefix;
+ const gchar *expected_gateway;
+ const gchar *expected_dns1;
+ const gchar *expected_dns2;
+} DhcpTest;
+
+static const DhcpTest dhcp_tests[] = {
+ { "^DHCP:a3ec5c64,f8ffffff,a1ec5c64,a1ec5c64,2200b10a,74bba80a,236800,236800\r\n",
+ "100.92.236.163", 29, "100.92.236.161", "10.177.0.34", "10.168.187.116" },
+ { "^DHCP:0xa3ec5c64,0xf8ffffff,0xa1ec5c64,0xa1ec5c64,0x2200b10a,0x74bba80a,236800,236800\r\n",
+ "100.92.236.163", 29, "100.92.236.161", "10.177.0.34", "10.168.187.116" },
+ { "^DHCP: 1010A0A,FCFFFFFF,2010A0A,2010A0A,0,0,150000000,150000000\r\n",
+ "10.10.1.1", 30, "10.10.1.2", "0.0.0.0", "0.0.0.0" },
+ { "^DHCP: CCDB080A,F8FFFFFF,C9DB080A,C9DB080A,E67B59C0,E77B59C0,85600,85600\r\n",
+ "10.8.219.204", 29, "10.8.219.201", "192.89.123.230", "192.89.123.231" },
+ { "^DHCP: 0xCCDB080A,0xF8FFFFFF,0xC9DB080A,0xC9DB080A,0xE67B59C0,0xE77B59C0,85600,85600\r\n",
+ "10.8.219.204", 29, "10.8.219.201", "192.89.123.230", "192.89.123.231" },
+ { "^DHCP: 0XCCDB080A,0XF8FFFFFF,0XC9DB080A,0XC9DB080A,0XE67B59C0,0XE77B59C0,85600,85600\r\n",
+ "10.8.219.204", 29, "10.8.219.201", "192.89.123.230", "192.89.123.231" },
+ { NULL }
+};
+
+static void
+test_dhcp (void)
+{
+ guint i;
+
+ for (i = 0; dhcp_tests[i].str; i++) {
+ GError *error = NULL;
+ guint addr, prefix, gateway, dns1, dns2;
+
+ g_assert (mm_huawei_parse_dhcp_response (
+ dhcp_tests[i].str,
+ &addr,
+ &prefix,
+ &gateway,
+ &dns1,
+ &dns2,
+ &error) == TRUE);
+ g_assert_no_error (error);
+
+ g_assert_cmpstr (inet_ntoa (*((struct in_addr *) &addr)), ==, dhcp_tests[i].expected_addr);
+ g_assert_cmpint (prefix, ==, dhcp_tests[i].expected_prefix);
+ g_assert_cmpstr (inet_ntoa (*((struct in_addr *) &gateway)), ==, dhcp_tests[i].expected_gateway);
+ g_assert_cmpstr (inet_ntoa (*((struct in_addr *) &dns1)), ==, dhcp_tests[i].expected_dns1);
+ g_assert_cmpstr (inet_ntoa (*((struct in_addr *) &dns2)), ==, dhcp_tests[i].expected_dns2);
+ }
+}
+
+/*****************************************************************************/
+/* Test ^SYSINFO responses */
+
+typedef struct {
+ const gchar *str;
+ guint expected_srv_status;
+ guint expected_srv_domain;
+ guint expected_roam_status;
+ guint expected_sys_mode;
+ guint expected_sim_state;
+ gboolean expected_sys_submode_valid;
+ guint expected_sys_submode;
+} SysinfoTest;
+
+static const SysinfoTest sysinfo_tests[] = {
+ { "^SYSINFO:2,4,5,3,1", 2, 4, 5, 3, 1, FALSE, 0 },
+ { "^SYSINFO:2,4,5,3,1,", 2, 4, 5, 3, 1, FALSE, 0 },
+ { "^SYSINFO:2,4,5,3,1,,", 2, 4, 5, 3, 1, FALSE, 0 },
+ { "^SYSINFO:2,4,5,3,1,6", 2, 4, 5, 3, 1, FALSE, 6 },
+ { "^SYSINFO:2,4,5,3,1,6,", 2, 4, 5, 3, 1, FALSE, 6 },
+ { "^SYSINFO:2,4,5,3,1,,6", 2, 4, 5, 3, 1, TRUE, 6 },
+ { "^SYSINFO:2,4,5,3,1,0,6", 2, 4, 5, 3, 1, TRUE, 6 },
+ { "^SYSINFO: 2,4,5,3,1,0,6", 2, 4, 5, 3, 1, TRUE, 6 },
+ { NULL, 0, 0, 0, 0, 0, FALSE, 0 }
+};
+
+static void
+test_sysinfo (void)
+{
+ guint i;
+
+ for (i = 0; sysinfo_tests[i].str; i++) {
+ GError *error = NULL;
+ guint srv_status = 0;
+ guint srv_domain = 0;
+ guint roam_status = 0;
+ guint sys_mode = 0;
+ guint sim_state = 0;
+ gboolean sys_submode_valid = FALSE;
+ guint sys_submode = 0;
+
+ g_assert (mm_huawei_parse_sysinfo_response (sysinfo_tests[i].str,
+ &srv_status,
+ &srv_domain,
+ &roam_status,
+ &sys_mode,
+ &sim_state,
+ &sys_submode_valid,
+ &sys_submode,
+ &error) == TRUE);
+ g_assert_no_error (error);
+
+ g_assert (srv_status == sysinfo_tests[i].expected_srv_status);
+ g_assert (srv_domain == sysinfo_tests[i].expected_srv_domain);
+ g_assert (roam_status == sysinfo_tests[i].expected_roam_status);
+ g_assert (sys_mode == sysinfo_tests[i].expected_sys_mode);
+ g_assert (sim_state == sysinfo_tests[i].expected_sim_state);
+ g_assert (sys_submode_valid == sysinfo_tests[i].expected_sys_submode_valid);
+ if (sys_submode_valid)
+ g_assert (sys_submode == sysinfo_tests[i].expected_sys_submode);
+ }
+}
+
+/*****************************************************************************/
+/* Test ^SYSINFOEX responses */
+
+typedef struct {
+ const gchar *str;
+ guint expected_srv_status;
+ guint expected_srv_domain;
+ guint expected_roam_status;
+ guint expected_sim_state;
+ guint expected_sys_mode;
+ guint expected_sys_submode;
+} SysinfoexTest;
+
+static const SysinfoexTest sysinfoex_tests[] = {
+ { "^SYSINFOEX:2,4,5,1,,3,WCDMA,41,HSPA+", 2, 4, 5, 1, 3, 41 },
+ { "^SYSINFOEX:2,4,5,1,,3,\"WCDMA\",41,\"HSPA+\"", 2, 4, 5, 1, 3, 41 },
+ { "^SYSINFOEX: 2,4,5,1,0,3,\"WCDMA\",41,\"HSPA+\"", 2, 4, 5, 1, 3, 41 },
+ { NULL, 0, 0, 0, 0, 0, 0 }
+};
+
+static void
+test_sysinfoex (void)
+{
+ guint i;
+
+ for (i = 0; sysinfoex_tests[i].str; i++) {
+ GError *error = NULL;
+ guint srv_status = 0;
+ guint srv_domain = 0;
+ guint roam_status = 0;
+ guint sim_state = 0;
+ guint sys_mode = 0;
+ guint sys_submode = 0;
+
+ g_assert (mm_huawei_parse_sysinfoex_response (sysinfoex_tests[i].str,
+ &srv_status,
+ &srv_domain,
+ &roam_status,
+ &sim_state,
+ &sys_mode,
+ &sys_submode,
+ &error) == TRUE);
+ g_assert_no_error (error);
+
+ g_assert (srv_status == sysinfoex_tests[i].expected_srv_status);
+ g_assert (srv_domain == sysinfoex_tests[i].expected_srv_domain);
+ g_assert (roam_status == sysinfoex_tests[i].expected_roam_status);
+ g_assert (sim_state == sysinfoex_tests[i].expected_sim_state);
+ g_assert (sys_mode == sysinfoex_tests[i].expected_sys_mode);
+ g_assert (sys_submode == sysinfoex_tests[i].expected_sys_submode);
+ }
+}
+
+/*****************************************************************************/
+/* Test ^PREFMODE=? responses */
+
+#define MAX_PREFMODE_COMBINATIONS 3
+
+typedef struct {
+ const gchar *str;
+ MMHuaweiPrefmodeCombination expected_modes[MAX_PREFMODE_COMBINATIONS];
+} PrefmodeTest;
+
+static const PrefmodeTest prefmode_tests[] = {
+ {
+ "^PREFMODE:(2,4,8)\r\n",
+ {
+ {
+ .prefmode = 8,
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .prefmode = 4,
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_3G
+ },
+ {
+ .prefmode = 2,
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_2G
+ }
+ }
+ },
+ {
+ "^PREFMODE:(2,4)\r\n",
+ {
+ {
+ .prefmode = 4,
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_3G
+ },
+ {
+ .prefmode = 2,
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_2G
+ },
+ { 0, 0, 0}
+ }
+ },
+ {
+ "^PREFMODE:(2)\r\n",
+ {
+ {
+ .prefmode = 2,
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ { 0, 0, 0}
+ }
+ },
+};
+
+static void
+test_prefmode (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (prefmode_tests); i++) {
+ GError *error = NULL;
+ GArray *combinations = NULL;
+ guint j;
+ guint n_expected_combinations = 0;
+
+ for (j = 0; j < MAX_PREFMODE_COMBINATIONS; j++) {
+ if (prefmode_tests[i].expected_modes[j].prefmode != 0)
+ n_expected_combinations++;
+ }
+
+ combinations = mm_huawei_parse_prefmode_test (prefmode_tests[i].str, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (combinations != NULL);
+ g_assert_cmpuint (combinations->len, ==, n_expected_combinations);
+
+ for (j = 0; j < combinations->len; j++) {
+ MMHuaweiPrefmodeCombination *single;
+ g_autofree gchar *allowed_str = NULL;
+ g_autofree gchar *preferred_str = NULL;
+
+ single = &g_array_index (combinations, MMHuaweiPrefmodeCombination, j);
+ allowed_str = mm_modem_mode_build_string_from_mask (single->allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (single->preferred);
+ mm_obj_dbg (NULL, "test[%u], combination[%u]: %u, \"%s\", \"%s\"",
+ i, j, single->prefmode, allowed_str, preferred_str);
+ }
+
+ for (j = 0; j < combinations->len; j++) {
+ MMHuaweiPrefmodeCombination *single;
+ guint k;
+ gboolean found = FALSE;
+
+ single = &g_array_index (combinations, MMHuaweiPrefmodeCombination, j);
+ for (k = 0; k <= n_expected_combinations; k++) {
+ if (single->allowed == prefmode_tests[i].expected_modes[k].allowed &&
+ single->preferred == prefmode_tests[i].expected_modes[k].preferred &&
+ single->prefmode == prefmode_tests[i].expected_modes[k].prefmode) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ g_assert (found == TRUE);
+ }
+
+ g_array_unref (combinations);
+ }
+}
+
+/*****************************************************************************/
+/* Test ^PREFMODE? responses */
+
+typedef struct {
+ const gchar *str;
+ const gchar *format;
+ MMModemMode allowed;
+ MMModemMode preferred;
+} PrefmodeResponseTest;
+
+static const PrefmodeResponseTest prefmode_response_tests[] = {
+ {
+ .str = "^PREFMODE:2\r\n",
+ .format = "^PREFMODE:(2,4,8)\r\n",
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_2G
+ },
+ {
+ .str = "^PREFMODE:4\r\n",
+ .format = "^PREFMODE:(2,4,8)\r\n",
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_3G
+ },
+ {
+ .str = "^PREFMODE:8\r\n",
+ .format = "^PREFMODE:(2,4,8)\r\n",
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_NONE
+ }
+};
+
+static void
+test_prefmode_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (prefmode_response_tests); i++) {
+ GArray *combinations = NULL;
+ const MMHuaweiPrefmodeCombination *found;
+ GError *error = NULL;
+
+ combinations = mm_huawei_parse_prefmode_test (prefmode_response_tests[i].format, NULL, NULL);
+ g_assert (combinations != NULL);
+
+ found = mm_huawei_parse_prefmode_response (prefmode_response_tests[i].str,
+ combinations,
+ &error);
+ g_assert_no_error (error);
+ g_assert (found != NULL);
+ g_assert_cmpuint (found->allowed, ==, prefmode_response_tests[i].allowed);
+ g_assert_cmpuint (found->preferred, ==, prefmode_response_tests[i].preferred);
+
+ g_array_unref (combinations);
+ }
+}
+
+/*****************************************************************************/
+/* Test ^SYSCFG=? responses */
+
+#define MAX_SYSCFG_COMBINATIONS 5
+
+typedef struct {
+ const gchar *str;
+ MMHuaweiSyscfgCombination expected_modes[MAX_SYSCFG_COMBINATIONS];
+} SyscfgTest;
+
+static const SyscfgTest syscfg_tests[] = {
+ {
+ MM_HUAWEI_DEFAULT_SYSCFG_FMT,
+ {
+ {
+ .mode = 2,
+ .acqorder = 0,
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .mode = 2,
+ .acqorder = 1,
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_2G
+ },
+ {
+ .mode = 2,
+ .acqorder = 2,
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_3G
+ },
+ {
+ .mode = 13,
+ .acqorder = 0,
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .mode = 14,
+ .acqorder = 0,
+ .allowed = MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_NONE
+ }
+ }
+ },
+ {
+ "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n",
+ {
+ {
+ .mode = 2,
+ .acqorder = 0,
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .mode = 2,
+ .acqorder = 1,
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_2G
+ },
+ {
+ .mode = 2,
+ .acqorder = 2,
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_3G
+ },
+ {
+ .mode = 13,
+ .acqorder = 0,
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .mode = 14,
+ .acqorder = 0,
+ .allowed = MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_NONE
+ }
+ }
+ },
+ {
+ "^SYSCFG:(2,13,14,16),(0),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n",
+ {
+ {
+ .mode = 2,
+ .acqorder = 0,
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .mode = 13,
+ .acqorder = 0,
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .mode = 14,
+ .acqorder = 0,
+ .allowed = MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ { 0, 0, 0, 0 }
+ }
+ },
+ {
+ "^SYSCFG:(13),(0),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n",
+ {
+ {
+ .mode = 13,
+ .acqorder = 0,
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ { 0, 0, 0, 0 }
+ }
+ }
+};
+
+static void
+test_syscfg (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (syscfg_tests); i++) {
+ GError *error = NULL;
+ GArray *combinations = NULL;
+ guint j;
+ guint n_expected_combinations = 0;
+
+ for (j = 0; j < MAX_SYSCFG_COMBINATIONS; j++) {
+ if (syscfg_tests[i].expected_modes[j].mode != 0)
+ n_expected_combinations++;
+ }
+
+ combinations = mm_huawei_parse_syscfg_test (syscfg_tests[i].str, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (combinations != NULL);
+ g_assert_cmpuint (combinations->len, ==, n_expected_combinations);
+
+ for (j = 0; j < combinations->len; j++) {
+ MMHuaweiSyscfgCombination *single;
+ g_autofree gchar *allowed_str = NULL;
+ g_autofree gchar *preferred_str = NULL;
+
+ single = &g_array_index (combinations, MMHuaweiSyscfgCombination, j);
+ allowed_str = mm_modem_mode_build_string_from_mask (single->allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (single->preferred);
+ mm_obj_dbg (NULL, "test[%u], combination[%u]: %u, %u, \"%s\", \"%s\"",
+ i, j, single->mode, single->acqorder, allowed_str, preferred_str);
+ }
+
+ for (j = 0; j < combinations->len; j++) {
+ MMHuaweiSyscfgCombination *single;
+ guint k;
+ gboolean found = FALSE;
+
+ single = &g_array_index (combinations, MMHuaweiSyscfgCombination, j);
+ for (k = 0; k <= n_expected_combinations; k++) {
+ if (single->allowed == syscfg_tests[i].expected_modes[k].allowed &&
+ single->preferred == syscfg_tests[i].expected_modes[k].preferred &&
+ single->mode == syscfg_tests[i].expected_modes[k].mode &&
+ single->acqorder == syscfg_tests[i].expected_modes[k].acqorder) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ g_assert (found == TRUE);
+ }
+
+ g_array_unref (combinations);
+ }
+}
+
+/*****************************************************************************/
+/* Test ^SYSCFG? responses */
+
+typedef struct {
+ const gchar *str;
+ const gchar *format;
+ MMModemMode allowed;
+ MMModemMode preferred;
+} SyscfgResponseTest;
+
+static const SyscfgResponseTest syscfg_response_tests[] = {
+ {
+ .str = "^SYSCFG: 2,0,400000,0,3\r\n",
+ .format = "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n",
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .str = "^SYSCFG: 2,1,400000,0,3\r\n",
+ .format = "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n",
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_2G
+ },
+ {
+ .str = "^SYSCFG: 2,2,400000,0,3\r\n",
+ .format = "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n",
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_3G
+ },
+ {
+ .str = "^SYSCFG: 13,0,400000,0,3\r\n",
+ .format = "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n",
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .str = "^SYSCFG: 14,0,400000,0,3\r\n",
+ .format = "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n",
+ .allowed = MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ /* Non-sensical acquisition order (WCDMA-only but acquire WCDMA-then-GSM */
+ .str = "^SYSCFG: 14,2,400000,0,3\r\n",
+ .format = "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n",
+ .allowed = MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ /* Non-sensical acquisition order (GSM-only but acquire GSM-then-WCDMA */
+ .str = "^SYSCFG: 13,1,400000,0,3\r\n",
+ .format = "^SYSCFG:(2,13,14,16),(0-3),((400000,\"WCDMA2100\")),(0-2),(0-4)\r\n",
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE
+ }
+};
+
+static void
+test_syscfg_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (syscfg_response_tests); i++) {
+ GArray *combinations = NULL;
+ const MMHuaweiSyscfgCombination *found;
+ GError *error = NULL;
+
+ combinations = mm_huawei_parse_syscfg_test (syscfg_response_tests[i].format, NULL, NULL);
+ g_assert (combinations != NULL);
+
+ found = mm_huawei_parse_syscfg_response (syscfg_response_tests[i].str,
+ combinations,
+ &error);
+
+ g_assert_no_error (error);
+ g_assert (found != NULL);
+ g_assert_cmpuint (found->allowed, ==, syscfg_response_tests[i].allowed);
+ g_assert_cmpuint (found->preferred, ==, syscfg_response_tests[i].preferred);
+
+ g_array_unref (combinations);
+ }
+}
+
+/*****************************************************************************/
+/* Test ^SYSCFGEX=? responses */
+
+#define MAX_SYSCFGEX_COMBINATIONS 5
+
+typedef struct {
+ const gchar *str;
+ MMHuaweiSyscfgexCombination expected_modes[MAX_SYSCFGEX_COMBINATIONS];
+} SyscfgexTest;
+
+static const SyscfgexTest syscfgex_tests[] = {
+ {
+ "^SYSCFGEX: (\"00\",\"03\",\"02\",\"01\",\"99\"),"
+ "((2000004e80380,\"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100\"),(3fffffff,\"All Bands\")),"
+ "(0-3),"
+ "(0-4),"
+ "((800c5,\"LTE2100/LTE1800/LTE2600/LTE900/LTE800\"),(7fffffffffffffff,\"All bands\"))"
+ "\r\n",
+ {
+ {
+ .mode_str = (gchar *) "00",
+ .allowed = (MM_MODEM_MODE_4G | MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .mode_str = (gchar *) "03",
+ .allowed = MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .mode_str = (gchar *) "02",
+ .allowed = MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .mode_str = (gchar *) "01",
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ { NULL, 0, 0 }
+ }
+ },
+ {
+ "^SYSCFGEX: (\"030201\",\"0302\",\"03\",\"99\"),"
+ "((2000004e80380,\"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100\"),(3fffffff,\"All Bands\")),"
+ "(0-3),"
+ "(0-4),"
+ "((800c5,\"LTE2100/LTE1800/LTE2600/LTE900/LTE800\"),(7fffffffffffffff,\"All bands\"))"
+ "\r\n",
+ {
+ {
+ .mode_str = (gchar *) "030201",
+ .allowed = (MM_MODEM_MODE_4G | MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_4G
+ },
+ {
+ .mode_str = (gchar *) "0302",
+ .allowed = (MM_MODEM_MODE_4G | MM_MODEM_MODE_3G),
+ .preferred = MM_MODEM_MODE_4G
+ },
+ {
+ .mode_str = (gchar *) "03",
+ .allowed = MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ { NULL, 0, 0 }
+ }
+ },
+ {
+ "^SYSCFGEX: (\"03\"),"
+ "((2000004e80380,\"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100\"),(3fffffff,\"All Bands\")),"
+ "(0-3),"
+ "(0-4),"
+ "((800c5,\"LTE2100/LTE1800/LTE2600/LTE900/LTE800\"),(7fffffffffffffff,\"All bands\"))"
+ "\r\n",
+ {
+ {
+ .mode_str = (gchar *) "03",
+ .allowed = MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ { NULL, 0, 0 }
+ }
+ },
+ {
+ "^SYSCFGEX: (\"00\",\"01\",\"02\",\"0102\",\"0201\"),"
+ "((3fffffff,\"All Bands\"),(2000000400180,\"GSM900/GSM1800/WCDMA900/WCDMA2100\"),(6A80000,\"GSM850/GSM1900/WCDMA850/AWS/WCDMA1900\")),"
+ "(0-2),"
+ "(0-4),"
+ "," /* NOTE: Non-LTE modem, LTE Bands EMPTY */
+ "\r\n",
+ {
+ {
+ .mode_str = (gchar *) "00",
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .mode_str = (gchar *) "01",
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .mode_str = (gchar *) "02",
+ .allowed = MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .mode_str = (gchar *) "0102",
+ .allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G),
+ .preferred = MM_MODEM_MODE_2G
+ },
+ {
+ .mode_str = (gchar *) "0201",
+ .allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G),
+ .preferred = MM_MODEM_MODE_3G
+ }
+ }
+ }
+};
+
+static void
+test_syscfgex (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (syscfgex_tests); i++) {
+ GError *error = NULL;
+ GArray *combinations = NULL;
+ guint j;
+ guint n_expected_combinations = 0;
+
+ for (j = 0; j < MAX_SYSCFGEX_COMBINATIONS; j++) {
+ if (syscfgex_tests[i].expected_modes[j].mode_str != NULL)
+ n_expected_combinations++;
+ }
+
+ combinations = mm_huawei_parse_syscfgex_test (syscfgex_tests[i].str, &error);
+ g_assert_no_error (error);
+ g_assert (combinations != NULL);
+ g_assert_cmpuint (combinations->len, ==, n_expected_combinations);
+
+ for (j = 0; j < combinations->len; j++) {
+ MMHuaweiSyscfgexCombination *single;
+ g_autofree gchar *allowed_str = NULL;
+ g_autofree gchar *preferred_str = NULL;
+
+ single = &g_array_index (combinations, MMHuaweiSyscfgexCombination, j);
+ allowed_str = mm_modem_mode_build_string_from_mask (single->allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (single->preferred);
+ mm_obj_dbg (NULL, "test[%u], combination[%u]: \"%s\", \"%s\", \"%s\"",
+ i, j, single->mode_str, allowed_str, preferred_str);
+ }
+
+ for (j = 0; j < combinations->len; j++) {
+ MMHuaweiSyscfgexCombination *single;
+ guint k;
+ gboolean found = FALSE;
+
+ single = &g_array_index (combinations, MMHuaweiSyscfgexCombination, j);
+ for (k = 0; k <= n_expected_combinations; k++) {
+ if (g_str_equal (single->mode_str, syscfgex_tests[i].expected_modes[k].mode_str) &&
+ single->allowed == syscfgex_tests[i].expected_modes[k].allowed &&
+ single->preferred == syscfgex_tests[i].expected_modes[k].preferred) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ g_assert (found == TRUE);
+ }
+
+ g_array_unref (combinations);
+ }
+}
+
+/*****************************************************************************/
+/* Test ^SYSCFGEX? responses */
+
+typedef struct {
+ const gchar *str;
+ const gchar *format;
+ MMModemMode allowed;
+ MMModemMode preferred;
+} SyscfgexResponseTest;
+
+static const SyscfgexResponseTest syscfgex_response_tests[] = {
+ {
+ .str = "^SYSCFGEX: \"00\",3FFFFFFF,1,2,7FFFFFFFFFFFFFFF",
+ .format = "^SYSCFGEX: (\"00\",\"03\",\"02\",\"01\",\"99\"),"
+ "((2000004e80380,\"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100\"),(3fffffff,\"All Bands\")),"
+ "(0-3),"
+ "(0-4),"
+ "((800c5,\"LTE2100/LTE1800/LTE2600/LTE900/LTE800\"),(7fffffffffffffff,\"All bands\"))"
+ "\r\n",
+ .allowed = (MM_MODEM_MODE_4G | MM_MODEM_MODE_3G | MM_MODEM_MODE_2G),
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .str = "^SYSCFGEX: \"03\",3FFFFFFF,1,2,7FFFFFFFFFFFFFFF",
+ .format = "^SYSCFGEX: (\"00\",\"03\",\"02\",\"01\",\"99\"),"
+ "((2000004e80380,\"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100\"),(3fffffff,\"All Bands\")),"
+ "(0-3),"
+ "(0-4),"
+ "((800c5,\"LTE2100/LTE1800/LTE2600/LTE900/LTE800\"),(7fffffffffffffff,\"All bands\"))"
+ "\r\n",
+ .allowed = MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .str = "^SYSCFGEX: \"02\",3FFFFFFF,1,2,7FFFFFFFFFFFFFFF",
+ .format = "^SYSCFGEX: (\"00\",\"03\",\"02\",\"01\",\"99\"),"
+ "((2000004e80380,\"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100\"),(3fffffff,\"All Bands\")),"
+ "(0-3),"
+ "(0-4),"
+ "((800c5,\"LTE2100/LTE1800/LTE2600/LTE900/LTE800\"),(7fffffffffffffff,\"All bands\"))"
+ "\r\n",
+ .allowed = MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .str = "^SYSCFGEX: \"01\",3FFFFFFF,1,2,7FFFFFFFFFFFFFFF",
+ .format = "^SYSCFGEX: (\"00\",\"03\",\"02\",\"01\",\"99\"),"
+ "((2000004e80380,\"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100\"),(3fffffff,\"All Bands\")),"
+ "(0-3),"
+ "(0-4),"
+ "((800c5,\"LTE2100/LTE1800/LTE2600/LTE900/LTE800\"),(7fffffffffffffff,\"All bands\"))"
+ "\r\n",
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .str = "^SYSCFGEX: \"00\",3fffffff,1,2,",
+ .format = "^SYSCFGEX: (\"00\",\"01\",\"02\",\"0102\",\"0201\"),"
+ "((3fffffff,\"All Bands\"),(2000000400180,\"GSM900/GSM1800/WCDMA900/WCDMA2100\"),(6A80000,\"GSM850/GSM1900/WCDMA850/AWS/WCDMA1900\")),"
+ "(0-2),"
+ "(0-4),"
+ "," /* NOTE: Non-LTE modem, LTE Bands EMPTY */
+ "\r\n",
+ .allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G),
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .str = "^SYSCFGEX: \"01\",3fffffff,1,2,",
+ .format = "^SYSCFGEX: (\"00\",\"01\",\"02\",\"0102\",\"0201\"),"
+ "((3fffffff,\"All Bands\"),(2000000400180,\"GSM900/GSM1800/WCDMA900/WCDMA2100\"),(6A80000,\"GSM850/GSM1900/WCDMA850/AWS/WCDMA1900\")),"
+ "(0-2),"
+ "(0-4),"
+ "," /* NOTE: Non-LTE modem, LTE Bands EMPTY */
+ "\r\n",
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .str = "^SYSCFGEX: \"02\",3fffffff,1,2,",
+ .format = "^SYSCFGEX: (\"00\",\"01\",\"02\",\"0102\",\"0201\"),"
+ "((3fffffff,\"All Bands\"),(2000000400180,\"GSM900/GSM1800/WCDMA900/WCDMA2100\"),(6A80000,\"GSM850/GSM1900/WCDMA850/AWS/WCDMA1900\")),"
+ "(0-2),"
+ "(0-4),"
+ "," /* NOTE: Non-LTE modem, LTE Bands EMPTY */
+ "\r\n",
+ .allowed = MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_NONE
+ },
+ {
+ .str = "^SYSCFGEX: \"0102\",3fffffff,1,2,",
+ .format = "^SYSCFGEX: (\"00\",\"01\",\"02\",\"0102\",\"0201\"),"
+ "((3fffffff,\"All Bands\"),(2000000400180,\"GSM900/GSM1800/WCDMA900/WCDMA2100\"),(6A80000,\"GSM850/GSM1900/WCDMA850/AWS/WCDMA1900\")),"
+ "(0-2),"
+ "(0-4),"
+ "," /* NOTE: Non-LTE modem, LTE Bands EMPTY */
+ "\r\n",
+ .allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G),
+ .preferred = MM_MODEM_MODE_2G
+ },
+ {
+ .str = "^SYSCFGEX: \"0201\",3fffffff,1,2,",
+ .format = "^SYSCFGEX: (\"00\",\"01\",\"02\",\"0102\",\"0201\"),"
+ "((3fffffff,\"All Bands\"),(2000000400180,\"GSM900/GSM1800/WCDMA900/WCDMA2100\"),(6A80000,\"GSM850/GSM1900/WCDMA850/AWS/WCDMA1900\")),"
+ "(0-2),"
+ "(0-4),"
+ "," /* NOTE: Non-LTE modem, LTE Bands EMPTY */
+ "\r\n",
+ .allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G),
+ .preferred = MM_MODEM_MODE_3G
+ }
+};
+
+static void
+test_syscfgex_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (syscfgex_response_tests); i++) {
+ GArray *combinations = NULL;
+ const MMHuaweiSyscfgexCombination *found;
+ GError *error = NULL;
+
+ combinations = mm_huawei_parse_syscfgex_test (syscfgex_response_tests[i].format, NULL);
+ g_assert (combinations != NULL);
+
+ found = mm_huawei_parse_syscfgex_response (syscfgex_response_tests[i].str,
+ combinations,
+ &error);
+
+ g_assert_no_error (error);
+ g_assert (found != NULL);
+ g_assert_cmpuint (found->allowed, ==, syscfgex_response_tests[i].allowed);
+ g_assert_cmpuint (found->preferred, ==, syscfgex_response_tests[i].preferred);
+
+ g_array_unref (combinations);
+ }
+}
+
+/*****************************************************************************/
+/* Test ^NWTIME responses */
+
+typedef struct {
+ const gchar *str;
+ gboolean ret;
+ gboolean test_iso8601;
+ gboolean test_tz;
+ const gchar *iso8601;
+ gint32 offset;
+ gint32 dst_offset;
+ gint32 leap_seconds;
+} NwtimeTest;
+
+#define NWT_UNKNOWN MM_NETWORK_TIMEZONE_LEAP_SECONDS_UNKNOWN
+
+static const NwtimeTest nwtime_tests[] = {
+ { "^NWTIME: 14/08/05,04:00:21+40,00", TRUE, TRUE, FALSE,
+ "2014-08-05T04:00:21+10", 600, 0, NWT_UNKNOWN },
+ { "^NWTIME: 14/08/05,04:00:21+40,00", TRUE, FALSE, TRUE,
+ "2014-08-05T04:00:21+10", 600, 0, NWT_UNKNOWN },
+ { "^NWTIME: 14/08/05,04:00:21+40,00", TRUE, TRUE, TRUE,
+ "2014-08-05T04:00:21+10", 600, 0, NWT_UNKNOWN },
+
+ { "^NWTIME: 14/08/05,04:00:21+20,00", TRUE, TRUE, FALSE,
+ "2014-08-05T04:00:21+05", 300, 0, NWT_UNKNOWN },
+ { "^NWTIME: 14/08/05,04:00:21+20,00", TRUE, FALSE, TRUE,
+ "2014-08-05T04:00:21+05", 300, 0, NWT_UNKNOWN },
+ { "^NWTIME: 14/08/05,04:00:21+20,00", TRUE, TRUE, TRUE,
+ "2014-08-05T04:00:21+05", 300, 0, NWT_UNKNOWN },
+
+ { "^NWTIME: 14/08/05,04:00:21+40,01", TRUE, TRUE, FALSE,
+ "2014-08-05T04:00:21+11", 600, 60, NWT_UNKNOWN },
+ { "^NWTIME: 14/08/05,04:00:21+40,01", TRUE, FALSE, TRUE,
+ "2014-08-05T04:00:21+11", 600, 60, NWT_UNKNOWN },
+ { "^NWTIME: 14/08/05,04:00:21+40,01", TRUE, TRUE, TRUE,
+ "2014-08-05T04:00:21+11", 600, 60, NWT_UNKNOWN },
+
+ { "^NWTIME: 14/08/05,04:00:21+40,02", TRUE, TRUE, FALSE,
+ "2014-08-05T04:00:21+12", 600, 120, NWT_UNKNOWN },
+ { "^NWTIME: 14/08/05,04:00:21+40,02", TRUE, FALSE, TRUE,
+ "2014-08-05T04:00:21+12", 600, 120, NWT_UNKNOWN },
+ { "^NWTIME: 14/08/05,04:00:21+40,02", TRUE, TRUE, TRUE,
+ "2014-08-05T04:00:21+12", 600, 120, NWT_UNKNOWN },
+
+ { "^TIME: XX/XX/XX,XX:XX:XX+XX,XX", FALSE, TRUE, FALSE,
+ NULL, NWT_UNKNOWN, NWT_UNKNOWN, NWT_UNKNOWN },
+
+ { "^TIME: 14/08/05,04:00:21+40,00", FALSE, TRUE, FALSE,
+ NULL, NWT_UNKNOWN, NWT_UNKNOWN, NWT_UNKNOWN },
+
+ { NULL, FALSE, FALSE, FALSE, NULL, NWT_UNKNOWN, NWT_UNKNOWN, NWT_UNKNOWN }
+};
+
+static void
+test_nwtime (void)
+{
+ guint i;
+
+ for (i = 0; nwtime_tests[i].str; i++) {
+ GError *error = NULL;
+ gchar *iso8601 = NULL;
+ MMNetworkTimezone *tz = NULL;
+ gboolean ret;
+
+ ret = mm_huawei_parse_nwtime_response (nwtime_tests[i].str,
+ nwtime_tests[i].test_iso8601 ? &iso8601 : NULL,
+ nwtime_tests[i].test_tz ? &tz : NULL,
+ &error);
+
+ g_assert (ret == nwtime_tests[i].ret);
+ g_assert (ret == (error ? FALSE : TRUE));
+
+ g_clear_error (&error);
+
+ if (nwtime_tests[i].test_iso8601)
+ g_assert_cmpstr (nwtime_tests[i].iso8601, ==, iso8601);
+
+ if (nwtime_tests[i].test_tz) {
+ g_assert (nwtime_tests[i].offset == mm_network_timezone_get_offset (tz));
+ g_assert (nwtime_tests[i].dst_offset == mm_network_timezone_get_dst_offset (tz));
+ g_assert (nwtime_tests[i].leap_seconds == mm_network_timezone_get_leap_seconds (tz));
+ }
+
+ g_free (iso8601);
+
+ if (tz)
+ g_object_unref (tz);
+ }
+}
+
+/*****************************************************************************/
+/* Test ^TIME responses */
+
+typedef struct {
+ const gchar *str;
+ gboolean ret;
+ const gchar *iso8601;
+} TimeTest;
+
+static const TimeTest time_tests[] = {
+ { "^TIME: 14/08/05 04:00:21", TRUE, "2014-08-05T04:00:21Z" },
+ { "^TIME: 2014/08/05 04:00:21", TRUE, "2014-08-05T04:00:21Z" },
+ { "^TIME: 14-08-05 04:00:21", FALSE, NULL },
+ { "^TIME: 14-08-05,04:00:21", FALSE, NULL },
+ { "^TIME: 14/08/05 04:00:21 AEST", FALSE, NULL },
+ { NULL, FALSE, NULL }
+};
+
+static void
+test_time (void)
+{
+ guint i;
+
+ for (i = 0; time_tests[i].str; i++) {
+ GError *error = NULL;
+ gchar *iso8601 = NULL;
+ gboolean ret;
+
+ ret = mm_huawei_parse_time_response (time_tests[i].str,
+ &iso8601,
+ NULL,
+ &error);
+
+ g_assert (ret == time_tests[i].ret);
+ g_assert (ret == (error ? FALSE : TRUE));
+ g_clear_error (&error);
+
+ g_assert_cmpstr (time_tests[i].iso8601, ==, iso8601);
+ g_free (iso8601);
+ }
+}
+
+/*****************************************************************************/
+/* Test ^HCSQ responses */
+
+typedef struct {
+ const gchar *str;
+ gboolean ret;
+ MMModemAccessTechnology act;
+ guint value1;
+ guint value2;
+ guint value3;
+ guint value4;
+ guint value5;
+} HcsqTest;
+
+static const HcsqTest hcsq_tests[] = {
+ { "^HCSQ:\"LTE\",30,19,66,0\r\n", TRUE, MM_MODEM_ACCESS_TECHNOLOGY_LTE, 30, 19, 66, 0, 0 },
+ { "^HCSQ: \"WCDMA\",30,30,58\r\n", TRUE, MM_MODEM_ACCESS_TECHNOLOGY_UMTS, 30, 30, 58, 0, 0 },
+ { "^HCSQ: \"GSM\",36,255\r\n", TRUE, MM_MODEM_ACCESS_TECHNOLOGY_GSM, 36, 255, 0, 0, 0 },
+ { "^HCSQ: LTE,33,40,135,11\r\n", TRUE, MM_MODEM_ACCESS_TECHNOLOGY_LTE, 33, 40, 135, 11, 0 },
+ { "^HCSQ: \"NOSERVICE\"\r\n", FALSE, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN, 0, 0, 0, 0, 0 },
+ { "^HCSQ: NOSERVICE\r\n", FALSE, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN, 0, 0, 0, 0, 0 },
+ { NULL, FALSE, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN, 0, 0, 0, 0, 0 }
+};
+
+static void
+test_hcsq (void)
+{
+ guint i;
+
+ for (i = 0; hcsq_tests[i].str; i++) {
+ GError *error = NULL;
+ MMModemAccessTechnology act;
+ guint value1 = 0;
+ guint value2 = 0;
+ guint value3 = 0;
+ guint value4 = 0;
+ guint value5 = 0;
+ gboolean ret;
+
+ ret = mm_huawei_parse_hcsq_response (hcsq_tests[i].str,
+ &act,
+ &value1,
+ &value2,
+ &value3,
+ &value4,
+ &value5,
+ &error);
+ g_assert (ret == hcsq_tests[i].ret);
+ if (ret) {
+ g_assert_no_error (error);
+ g_assert_cmpint (hcsq_tests[i].act, ==, act);
+ g_assert_cmpint (hcsq_tests[i].value1, ==, value1);
+ g_assert_cmpint (hcsq_tests[i].value2, ==, value2);
+ g_assert_cmpint (hcsq_tests[i].value3, ==, value3);
+ g_assert_cmpint (hcsq_tests[i].value4, ==, value4);
+ g_assert_cmpint (hcsq_tests[i].value5, ==, value5);
+ } else
+ g_assert (error);
+ g_clear_error (&error);
+ }
+}
+
+/*****************************************************************************/
+/* Test ^GETPORTMODE response */
+
+typedef struct {
+ const gchar *str;
+ guint n_modes;
+ MMHuaweiPortMode modes[8];
+} GetportmodeTest;
+
+static const GetportmodeTest getportmode_tests[] = {
+ {
+ "^GETPORTMODE: TYPE: WCDMA: huawei,PCUI:0,MDM:1",
+ 2, { MM_HUAWEI_PORT_MODE_PCUI,
+ MM_HUAWEI_PORT_MODE_MODEM }
+ },
+ {
+ "^GETPORTMODE: TYPE: WCDMA: huawei,MDM:0,PCUI:1,NDIS:2,CDROM:3,SD:4,",
+ 5, { MM_HUAWEI_PORT_MODE_MODEM,
+ MM_HUAWEI_PORT_MODE_PCUI,
+ MM_HUAWEI_PORT_MODE_NET,
+ MM_HUAWEI_PORT_MODE_CDROM,
+ MM_HUAWEI_PORT_MODE_SD }
+ },
+ {
+ "^GETPORTMODE: TYPE: WCDMA: huawei,MDM:0,PCUI:1,NDIS:2,GPS:3,BT:4,",
+ 5, { MM_HUAWEI_PORT_MODE_MODEM,
+ MM_HUAWEI_PORT_MODE_PCUI,
+ MM_HUAWEI_PORT_MODE_NET,
+ MM_HUAWEI_PORT_MODE_GPS,
+ MM_HUAWEI_PORT_MODE_BT
+ }
+ },
+ {
+ "^GETPORTMODE: TYPE: WCDMA: huawei,PCUI:0,MDM:1,NDIS:2,CDROM:3,SD:4,GPS:5,BT:6",
+ 7, { MM_HUAWEI_PORT_MODE_PCUI,
+ MM_HUAWEI_PORT_MODE_MODEM,
+ MM_HUAWEI_PORT_MODE_NET,
+ MM_HUAWEI_PORT_MODE_CDROM,
+ MM_HUAWEI_PORT_MODE_SD,
+ MM_HUAWEI_PORT_MODE_GPS,
+ MM_HUAWEI_PORT_MODE_BT
+ }
+ },
+ {
+ "^getportmode:type:WCDMA:Qualcomm,NDIS:0,DIAG:1,PCUI:2,MDM:3,SD:4",
+ 5, { MM_HUAWEI_PORT_MODE_NET,
+ MM_HUAWEI_PORT_MODE_DIAG,
+ MM_HUAWEI_PORT_MODE_PCUI,
+ MM_HUAWEI_PORT_MODE_MODEM,
+ MM_HUAWEI_PORT_MODE_SD
+ }
+ },
+ {
+ "^GETPORTMODE: TYPE: WCDMA: ,pcui:1,modem:2,ncm:3,mass:4,mass_two:5,",
+ 5, { MM_HUAWEI_PORT_MODE_PCUI,
+ MM_HUAWEI_PORT_MODE_MODEM,
+ MM_HUAWEI_PORT_MODE_NET,
+ MM_HUAWEI_PORT_MODE_SD,
+ MM_HUAWEI_PORT_MODE_SD
+ }
+ },
+ {
+ "^GETPORTMODE: TYPE: WCDMA: huawei ,, rndis: 0, pcui: 1, c_shell: 2, a_shell: 3,3g_diag: 4, gps: 5, 4g_diag: 6, mass_two: 7",
+ 8, { MM_HUAWEI_PORT_MODE_NET,
+ MM_HUAWEI_PORT_MODE_PCUI,
+ MM_HUAWEI_PORT_MODE_SHELL,
+ MM_HUAWEI_PORT_MODE_SHELL,
+ MM_HUAWEI_PORT_MODE_DIAG,
+ MM_HUAWEI_PORT_MODE_GPS,
+ MM_HUAWEI_PORT_MODE_DIAG,
+ MM_HUAWEI_PORT_MODE_SD
+ }
+ },
+ {
+ "^GETPORTMODE: TYPE: WCDMA: huawei,ecm:1,pcui:2,c_shell:3,a_shell:4,3g_diag:5,gps:6,4g_diag:7,mass:8,",
+ 8, { MM_HUAWEI_PORT_MODE_NET,
+ MM_HUAWEI_PORT_MODE_PCUI,
+ MM_HUAWEI_PORT_MODE_SHELL,
+ MM_HUAWEI_PORT_MODE_SHELL,
+ MM_HUAWEI_PORT_MODE_DIAG,
+ MM_HUAWEI_PORT_MODE_GPS,
+ MM_HUAWEI_PORT_MODE_DIAG,
+ MM_HUAWEI_PORT_MODE_SD
+ }
+ },
+ {
+ "^GETPORTMODE: TYPE: WCDMA: huawei,rndis:1,pcui:2,c_shell:3,a_shell:4,3g_diag:5,gps:6,4g_diag:7,mass:8,",
+ 8, { MM_HUAWEI_PORT_MODE_NET,
+ MM_HUAWEI_PORT_MODE_PCUI,
+ MM_HUAWEI_PORT_MODE_SHELL,
+ MM_HUAWEI_PORT_MODE_SHELL,
+ MM_HUAWEI_PORT_MODE_DIAG,
+ MM_HUAWEI_PORT_MODE_GPS,
+ MM_HUAWEI_PORT_MODE_DIAG,
+ MM_HUAWEI_PORT_MODE_SD
+ }
+ },
+ {
+ "^GETPORTMODE: TYPE: WCDMA: huawei,,pcui:0,3g_modem:1,ncm:2,mass:3,mass_two:4",
+ 5, { MM_HUAWEI_PORT_MODE_PCUI,
+ MM_HUAWEI_PORT_MODE_MODEM,
+ MM_HUAWEI_PORT_MODE_NET,
+ MM_HUAWEI_PORT_MODE_SD,
+ MM_HUAWEI_PORT_MODE_SD
+ }
+ },
+ {
+ "^GETPORTMODE:TYPE:WCDMA:Qualcomm,MDM:0,NDIS:1,DIAG:2,PCUI:3,CDROM:4,SD:5",
+ 6, { MM_HUAWEI_PORT_MODE_MODEM,
+ MM_HUAWEI_PORT_MODE_NET,
+ MM_HUAWEI_PORT_MODE_DIAG,
+ MM_HUAWEI_PORT_MODE_PCUI,
+ MM_HUAWEI_PORT_MODE_CDROM,
+ MM_HUAWEI_PORT_MODE_SD
+ }
+ },
+ {
+ "^GETPORTMODE: TYPE: WCDMA: Huawei Technologies Co.,Ltd.,",
+ 0
+ },
+};
+
+static void
+test_getportmode (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (getportmode_tests); i++) {
+ g_autoptr(GArray) modes = NULL;
+ g_autoptr(GError) error = NULL;
+
+ mm_obj_dbg (NULL, "testing ^GETPORTMODE response: '%s'", getportmode_tests[i].str);
+
+ modes = mm_huawei_parse_getportmode_response (getportmode_tests[i].str, NULL, &error);
+ if (modes) {
+ guint j;
+
+ g_assert_no_error (error);
+ g_assert_cmpuint (modes->len, ==, getportmode_tests[i].n_modes);
+ for (j = 0; j < getportmode_tests[i].n_modes; j++)
+ g_assert_cmpuint (g_array_index (modes, MMHuaweiPortMode, j), ==, getportmode_tests[i].modes[j]);
+ } else
+ g_assert (error);
+ }
+}
+
+/*****************************************************************************/
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/huawei/ndisstatqry", test_ndisstatqry);
+ g_test_add_func ("/MM/huawei/dhcp", test_dhcp);
+ g_test_add_func ("/MM/huawei/sysinfo", test_sysinfo);
+ g_test_add_func ("/MM/huawei/sysinfoex", test_sysinfoex);
+ g_test_add_func ("/MM/huawei/prefmode", test_prefmode);
+ g_test_add_func ("/MM/huawei/prefmode/response", test_prefmode_response);
+ g_test_add_func ("/MM/huawei/syscfg", test_syscfg);
+ g_test_add_func ("/MM/huawei/syscfg/response", test_syscfg_response);
+ g_test_add_func ("/MM/huawei/syscfgex", test_syscfgex);
+ g_test_add_func ("/MM/huawei/syscfgex/response", test_syscfgex_response);
+ g_test_add_func ("/MM/huawei/nwtime", test_nwtime);
+ g_test_add_func ("/MM/huawei/time", test_time);
+ g_test_add_func ("/MM/huawei/hcsq", test_hcsq);
+ g_test_add_func ("/MM/huawei/getportmode", test_getportmode);
+
+ return g_test_run ();
+}
diff --git a/src/plugins/icera/mm-broadband-bearer-icera.c b/src/plugins/icera/mm-broadband-bearer-icera.c
new file mode 100644
index 00000000..f6477595
--- /dev/null
+++ b/src/plugins/icera/mm-broadband-bearer-icera.c
@@ -0,0 +1,882 @@
+/* -*- 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) 2012 Google, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <arpa/inet.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-broadband-bearer-icera.h"
+#include "mm-base-modem-at.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-error-helpers.h"
+#include "mm-daemon-enums-types.h"
+#include "mm-modem-helpers-icera.h"
+
+G_DEFINE_TYPE (MMBroadbandBearerIcera, mm_broadband_bearer_icera, MM_TYPE_BROADBAND_BEARER);
+
+enum {
+ PROP_0,
+ PROP_DEFAULT_IP_METHOD,
+ PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+struct _MMBroadbandBearerIceraPrivate {
+ MMBearerIpMethod default_ip_method;
+
+ /* Connection related */
+ gpointer connect_pending;
+ guint connect_pending_id;
+ gulong connect_cancellable_id;
+ gulong connect_port_closed_id;
+
+ /* Disconnection related */
+ gpointer disconnect_pending;
+ guint disconnect_pending_id;
+};
+
+/*****************************************************************************/
+/* 3GPP IP config retrieval (sub-step of the 3GPP Connection sequence) */
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ guint cid;
+} GetIpConfig3gppContext;
+
+static void
+get_ip_config_context_free (GetIpConfig3gppContext *ctx)
+{
+ g_object_unref (ctx->primary);
+ g_object_unref (ctx->modem);
+ g_free (ctx);
+}
+
+static gboolean
+get_ip_config_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ MMBearerIpConfig **ipv4_config,
+ MMBearerIpConfig **ipv6_config,
+ GError **error)
+{
+ MMBearerConnectResult *configs;
+ MMBearerIpConfig *ipv4, *ipv6;
+
+ configs = g_task_propagate_pointer (G_TASK (res), error);
+ if (!configs)
+ return FALSE;
+
+ ipv4 = mm_bearer_connect_result_peek_ipv4_config (configs);
+ ipv6 = mm_bearer_connect_result_peek_ipv6_config (configs);
+ g_assert (ipv4 || ipv6);
+ if (ipv4_config && ipv4)
+ *ipv4_config = g_object_ref (ipv4);
+ if (ipv6_config && ipv6)
+ *ipv6_config = g_object_ref (ipv6);
+
+ mm_bearer_connect_result_unref (configs);
+ return TRUE;
+}
+
+static void
+ip_config_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GetIpConfig3gppContext *ctx;
+ MMBearerIpConfig *ipv4_config = NULL;
+ MMBearerIpConfig *ipv6_config = NULL;
+ const gchar *response;
+ GError *error = NULL;
+ MMBearerConnectResult *connect_result;
+
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ goto out;
+ }
+
+ if (!mm_icera_parse_ipdpaddr_response (response,
+ ctx->cid,
+ &ipv4_config,
+ &ipv6_config,
+ &error)) {
+ g_task_return_error (task, error);
+ goto out;
+ }
+
+ if (!ipv4_config && !ipv6_config) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't get IP config: couldn't parse response '%s'",
+ response);
+ goto out;
+ }
+
+ connect_result = mm_bearer_connect_result_new (MM_PORT (ctx->primary),
+ ipv4_config,
+ ipv6_config);
+ g_task_return_pointer (task,
+ connect_result,
+ (GDestroyNotify)mm_bearer_connect_result_unref);
+
+out:
+ g_object_unref (task);
+ g_clear_object (&ipv4_config);
+ g_clear_object (&ipv6_config);
+}
+
+static void
+get_ip_config_3gpp (MMBroadbandBearer *_self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ MMBearerIpFamily ip_family,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandBearerIcera *self = MM_BROADBAND_BEARER_ICERA (_self);
+ GetIpConfig3gppContext *ctx;
+ GTask *task;
+
+ ctx = g_new0 (GetIpConfig3gppContext, 1);
+ ctx->modem = g_object_ref (MM_BASE_MODEM (modem));
+ ctx->primary = g_object_ref (primary);
+ ctx->cid = cid;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)get_ip_config_context_free);
+
+ if (self->priv->default_ip_method == MM_BEARER_IP_METHOD_STATIC) {
+ gchar *command;
+
+ command = g_strdup_printf ("%%IPDPADDR=%u", cid);
+ mm_base_modem_at_command_full (MM_BASE_MODEM (modem),
+ primary,
+ command,
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)ip_config_ready,
+ task);
+ g_free (command);
+ return;
+ }
+
+ /* Otherwise, DHCP */
+ if (self->priv->default_ip_method == MM_BEARER_IP_METHOD_DHCP) {
+ MMBearerConnectResult *connect_result;
+ MMBearerIpConfig *ipv4_config = NULL, *ipv6_config = NULL;
+
+ if (ip_family & MM_BEARER_IP_FAMILY_IPV4 || ip_family & MM_BEARER_IP_FAMILY_IPV4V6) {
+ ipv4_config = mm_bearer_ip_config_new ();
+ mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_DHCP);
+ }
+ if (ip_family & MM_BEARER_IP_FAMILY_IPV6 || ip_family & MM_BEARER_IP_FAMILY_IPV4V6) {
+ ipv6_config = mm_bearer_ip_config_new ();
+ mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP);
+ }
+ g_assert (ipv4_config || ipv6_config);
+
+ connect_result = mm_bearer_connect_result_new (MM_PORT (ctx->primary),
+ ipv4_config,
+ ipv6_config);
+ g_clear_object (&ipv4_config);
+ g_clear_object (&ipv6_config);
+
+ g_task_return_pointer (task,
+ connect_result,
+ (GDestroyNotify)mm_bearer_connect_result_unref);
+ g_object_unref (task);
+ return;
+ }
+
+ g_assert_not_reached ();
+}
+
+/*****************************************************************************/
+/* 3GPP disconnection */
+
+static gboolean
+disconnect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static gboolean
+disconnect_3gpp_timed_out_cb (MMBroadbandBearerIcera *self)
+{
+ GTask *task;
+
+ /* Recover disconnection task */
+ task = self->priv->disconnect_pending;
+
+ self->priv->disconnect_pending = NULL;
+ self->priv->disconnect_pending_id = 0;
+
+ g_task_return_new_error (task,
+ MM_SERIAL_ERROR,
+ MM_SERIAL_ERROR_RESPONSE_TIMEOUT,
+ "Disconnection attempt timed out");
+ g_object_unref (task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+process_pending_disconnect_attempt (MMBroadbandBearerIcera *self,
+ MMBearerConnectionStatus status)
+{
+ GTask *task;
+
+ /* Recover disconnection task */
+ task = self->priv->disconnect_pending;
+ self->priv->disconnect_pending = NULL;
+ g_assert (task != NULL);
+
+ /* Cleanup timeout, if any */
+ if (self->priv->disconnect_pending_id) {
+ g_source_remove (self->priv->disconnect_pending_id);
+ self->priv->disconnect_pending_id = 0;
+ }
+
+ /* Received 'CONNECTED' during a disconnection attempt? */
+ if (status == MM_BEARER_CONNECTION_STATUS_CONNECTED) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Disconnection failed");
+ g_object_unref (task);
+ return;
+ }
+
+ /* Received 'DISCONNECTED' during a disconnection attempt? */
+ if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED ||
+ status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* No other status is expected by this implementation */
+ g_assert_not_reached ();
+}
+
+static void
+disconnect_ipdpact_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerIcera *self)
+{
+ GError *error = NULL;
+ GTask *task;
+
+ /* Try to recover the disconnection task. If none found, it means the
+ * task was already completed and we have nothing else to do. */
+ task = g_steal_pointer (&self->priv->disconnect_pending);
+
+ if (!task) {
+ mm_obj_dbg (self, "disconnection context was finished already by an unsolicited message");
+ /* Run _finish() to finalize the async call, even if we don't care
+ * about the result */
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+ goto out;
+ }
+
+ mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ goto out;
+ }
+
+ /* Track again */
+ self->priv->disconnect_pending = task;
+
+ /* Set a 60-second disconnection-failure timeout */
+ self->priv->disconnect_pending_id = g_timeout_add_seconds (60,
+ (GSourceFunc)disconnect_3gpp_timed_out_cb,
+ self);
+
+out:
+ /* Balance refcount with the extra ref we passed to command_full() */
+ g_object_unref (self);
+}
+
+static void
+disconnect_3gpp (MMBroadbandBearer *bearer,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandBearerIcera *self = MM_BROADBAND_BEARER_ICERA (bearer);
+ gchar *command;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* The unsolicited response to %IPDPACT may come before the OK does.
+ * We will keep the disconnection task in the bearer private data so
+ * that it is accessible from the unsolicited message handler. Note
+ * also that we do NOT pass the task to the GAsyncReadyCallback, as it
+ * may not be valid any more when the callback is called (it may be
+ * already completed in the unsolicited handling) */
+ g_assert (self->priv->disconnect_pending == NULL);
+ self->priv->disconnect_pending = task;
+
+ command = g_strdup_printf ("%%IPDPACT=%d,0", cid);
+ mm_base_modem_at_command_full (
+ MM_BASE_MODEM (modem),
+ primary,
+ command,
+ MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)disconnect_ipdpact_ready,
+ g_object_ref (self)); /* we pass the bearer object! */
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ guint cid;
+ MMPort *data;
+ guint authentication_retries;
+ GError *saved_error;
+} Dial3gppContext;
+
+static void
+dial_3gpp_context_free (Dial3gppContext *ctx)
+{
+ g_assert (!ctx->saved_error);
+ g_clear_object (&ctx->data);
+ g_clear_object (&ctx->primary);
+ g_clear_object (&ctx->modem);
+ g_slice_free (Dial3gppContext, ctx);
+}
+
+static guint
+dial_3gpp_get_connecting_cid (GTask *task)
+{
+ Dial3gppContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ return ctx->cid;
+}
+
+static MMPort *
+dial_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return MM_PORT (g_task_propagate_pointer (G_TASK (res), error));
+}
+
+static void
+connect_reset_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Dial3gppContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+
+ /* When reset is requested, it was either cancelled or an error was stored */
+ if (!g_task_return_error_if_cancelled (task)) {
+ g_assert (ctx->saved_error);
+ g_task_return_error (task, ctx->saved_error);
+ ctx->saved_error = NULL;
+ }
+
+ g_object_unref (task);
+}
+
+static void
+connect_reset (GTask *task)
+{
+ Dial3gppContext *ctx;
+ gchar *command;
+
+ ctx = g_task_get_task_data (task);
+
+ /* Need to reset the connection attempt */
+ command = g_strdup_printf ("%%IPDPACT=%d,0", ctx->cid);
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)connect_reset_ready,
+ task);
+ g_free (command);
+}
+
+static gboolean
+connect_timed_out_cb (MMBroadbandBearerIcera *self)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+
+ /* Cleanup timeout ID */
+ self->priv->connect_pending_id = 0;
+
+ /* Recover task and own it */
+ task = self->priv->connect_pending;
+ self->priv->connect_pending = NULL;
+ g_assert (task);
+
+ ctx = g_task_get_task_data (task);
+
+ /* Remove closed port watch, if found */
+ if (self->priv->connect_port_closed_id) {
+ g_signal_handler_disconnect (ctx->primary, self->priv->connect_port_closed_id);
+ self->priv->connect_port_closed_id = 0;
+ }
+
+ /* Setup error to return after the reset */
+ g_assert (!ctx->saved_error);
+ ctx->saved_error = g_error_new (MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT,
+ "Connection attempt timed out");
+
+ /* It's probably pointless to try to reset this here, but anyway... */
+ connect_reset (task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+ier_query_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerIcera *self;
+ const gchar *response;
+ GError *activation_error = NULL;
+
+ self = g_task_get_source_object (task);
+
+ response = mm_base_modem_at_command_full_finish (modem, res, NULL);
+ if (response) {
+ gint nw_activation_err;
+
+ response = mm_strip_tag (response, "%IER:");
+ if (sscanf (response, "%*d,%*d,%d", &nw_activation_err)) {
+ /* 3GPP TS 24.008 Annex G error codes:
+ * 27 - Unknown or missing access point name
+ * 33 - Requested service option not subscribed
+ */
+ if (nw_activation_err == 27 || nw_activation_err == 33)
+ activation_error = mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_SERVICE_OPTION_NOT_SUBSCRIBED, self);
+ }
+ }
+
+ if (activation_error)
+ g_task_return_error (task, activation_error);
+ else
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Call setup failed");
+ g_object_unref (task);
+}
+
+static void
+process_pending_connect_attempt (MMBroadbandBearerIcera *self,
+ MMBearerConnectionStatus status)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+
+ /* Recover task and remove both cancellation and timeout (if any)*/
+ g_assert (self->priv->connect_pending);
+ task = self->priv->connect_pending;
+ self->priv->connect_pending = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (self->priv->connect_pending_id) {
+ g_source_remove (self->priv->connect_pending_id);
+ self->priv->connect_pending_id = 0;
+ }
+
+ if (self->priv->connect_port_closed_id) {
+ g_signal_handler_disconnect (ctx->primary, self->priv->connect_port_closed_id);
+ self->priv->connect_port_closed_id = 0;
+ }
+
+ /* Received 'CONNECTED' during a connection attempt? */
+ if (status == MM_BEARER_CONNECTION_STATUS_CONNECTED) {
+ /* If we wanted to get cancelled before, do it now. */
+ if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) {
+ connect_reset (task);
+ return;
+ }
+
+ g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref);
+ g_object_unref (task);
+ return;
+ }
+
+ /* If we wanted to get cancelled before and now we couldn't connect,
+ * use the cancelled error and return */
+ if (g_task_return_error_if_cancelled (task)) {
+ g_object_unref (task);
+ return;
+ }
+
+ /* Received CONNECTION_FAILED during a connection attempt? */
+ if (status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED) {
+ /* Try to gather additional info about the connection failure */
+ mm_base_modem_at_command_full (
+ ctx->modem,
+ ctx->primary,
+ "%IER?",
+ 60,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback) ier_query_ready,
+ task);
+ return;
+ }
+
+ /* Otherwise, received 'DISCONNECTED' during a connection attempt? */
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Call setup failed");
+ g_object_unref (task);
+}
+
+static void
+forced_close_cb (MMBroadbandBearerIcera *self)
+{
+ /* Just treat the forced close event as any other unsolicited message */
+ mm_base_bearer_report_connection_status (MM_BASE_BEARER (self),
+ MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED);
+}
+
+static void
+activate_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerIcera *self)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+
+ task = g_steal_pointer (&self->priv->connect_pending);
+
+ /* Try to recover the connection context. If none found, it means the
+ * context was already completed and we have nothing else to do. */
+ if (!task) {
+ mm_obj_dbg (self, "connection context was finished already by an unsolicited message");
+ /* Run _finish() to finalize the async call, even if we don't care
+ * the result */
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+ goto out;
+ }
+
+ /* Errors on the dial command are fatal */
+ if (!mm_base_modem_at_command_full_finish (modem, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ goto out;
+ }
+
+ /* Track again */
+ self->priv->connect_pending = task;
+
+ /* We will now setup a timeout and keep the context in the bearer's private.
+ * Reports of modem being connected will arrive via unsolicited messages.
+ * This timeout should be long enough. Actually... ideally should never get
+ * reached. */
+ self->priv->connect_pending_id = g_timeout_add_seconds (MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT,
+ (GSourceFunc)connect_timed_out_cb,
+ self);
+
+ /* If we get the port closed, we treat as a connect error */
+ ctx = g_task_get_task_data (task);
+ self->priv->connect_port_closed_id = g_signal_connect_swapped (ctx->primary,
+ "forced-close",
+ G_CALLBACK (forced_close_cb),
+ self);
+
+ out:
+ /* Balance refcount with the extra ref we passed to command_full() */
+ g_object_unref (self);
+}
+
+static void
+dial_3gpp (MMBroadbandBearer *_self,
+ MMBaseModem *modem,
+ MMPortSerialAt *primary,
+ guint cid,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandBearerIcera *self = MM_BROADBAND_BEARER_ICERA (_self);
+ GTask *task;
+ Dial3gppContext *ctx;
+ g_autofree gchar *cmd = NULL;
+
+ g_assert (primary != NULL);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ ctx = g_slice_new0 (Dial3gppContext);
+ ctx->modem = g_object_ref (modem);
+ ctx->primary = g_object_ref (primary);
+ ctx->cid = cid;
+ g_task_set_task_data (task, ctx, (GDestroyNotify)dial_3gpp_context_free);
+
+ /* We need a net data port */
+ ctx->data = mm_base_modem_get_best_data_port (modem, MM_PORT_TYPE_NET);
+ if (!ctx->data) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_NOT_FOUND,
+ "No valid data port found to launch connection");
+ g_object_unref (task);
+ return;
+ }
+
+ /* The unsolicited response to %IPDPACT may come before the OK does.
+ * We will keep the connection context in the bearer private data so
+ * that it is accessible from the unsolicited message handler. Note
+ * also that we do NOT pass the ctx to the GAsyncReadyCallback, as it
+ * may not be valid any more when the callback is called (it may be
+ * already completed in the unsolicited handling) */
+ g_assert (self->priv->connect_pending == NULL);
+ self->priv->connect_pending = task;
+
+ cmd = g_strdup_printf ("%%IPDPACT=%d,1", ctx->cid);
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ cmd,
+ MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback) activate_ready,
+ g_object_ref (self)); /* we pass the bearer object! */
+}
+
+/*****************************************************************************/
+
+gint
+mm_broadband_bearer_icera_get_connecting_profile_id (MMBroadbandBearerIcera *self)
+{
+ return (self->priv->connect_pending ?
+ (gint)dial_3gpp_get_connecting_cid (self->priv->connect_pending) :
+ MM_3GPP_PROFILE_ID_UNKNOWN);
+}
+
+/*****************************************************************************/
+
+static void
+report_connection_status (MMBaseBearer *_self,
+ MMBearerConnectionStatus status,
+ const GError *connection_error)
+{
+ MMBroadbandBearerIcera *self = MM_BROADBAND_BEARER_ICERA (_self);
+
+ g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED ||
+ status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED ||
+ status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
+
+ /* Process pending connection attempt */
+ if (self->priv->connect_pending) {
+ process_pending_connect_attempt (self, status);
+ return;
+ }
+
+ /* Process pending disconnection attempt */
+ if (self->priv->disconnect_pending) {
+ process_pending_disconnect_attempt (self, status);
+ return;
+ }
+
+ mm_obj_dbg (self, "received spontaneous %%IPDPACT (%s)", mm_bearer_connection_status_get_string (status));
+
+ /* Received a random 'DISCONNECTED'...*/
+ if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED ||
+ status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED) {
+ /* If no connection/disconnection attempt on-going, make sure we mark ourselves as
+ * disconnected. Make sure we only pass 'DISCONNECTED' to the parent */
+ MM_BASE_BEARER_CLASS (mm_broadband_bearer_icera_parent_class)->report_connection_status (
+ _self,
+ MM_BEARER_CONNECTION_STATUS_DISCONNECTED,
+ connection_error);
+ }
+}
+
+/*****************************************************************************/
+
+MMBaseBearer *
+mm_broadband_bearer_icera_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *source;
+ GObject *bearer;
+
+ source = g_async_result_get_source_object (res);
+ bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!bearer)
+ return NULL;
+
+ /* Only export valid bearers */
+ mm_base_bearer_export (MM_BASE_BEARER (bearer));
+
+ return MM_BASE_BEARER (bearer);
+}
+
+void
+mm_broadband_bearer_icera_new (MMBroadbandModem *modem,
+ MMBearerIpMethod ip_method,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (
+ MM_TYPE_BROADBAND_BEARER_ICERA,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_BEARER_MODEM, modem,
+ MM_BASE_BEARER_CONFIG, config,
+ MM_BROADBAND_BEARER_ICERA_DEFAULT_IP_METHOD, ip_method,
+ NULL);
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MMBroadbandBearerIcera *self = MM_BROADBAND_BEARER_ICERA (object);
+
+ switch (prop_id) {
+ case PROP_DEFAULT_IP_METHOD:
+ self->priv->default_ip_method = g_value_get_enum (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MMBroadbandBearerIcera *self = MM_BROADBAND_BEARER_ICERA (object);
+
+ switch (prop_id) {
+ case PROP_DEFAULT_IP_METHOD:
+ g_value_set_enum (value, self->priv->default_ip_method);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+mm_broadband_bearer_icera_init (MMBroadbandBearerIcera *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_BEARER_ICERA,
+ MMBroadbandBearerIceraPrivate);
+
+ /* Defaults */
+ self->priv->default_ip_method = MM_BEARER_IP_METHOD_STATIC;
+}
+
+static void
+mm_broadband_bearer_icera_class_init (MMBroadbandBearerIceraClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);
+ MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandBearerIceraPrivate));
+
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+
+ base_bearer_class->report_connection_status = report_connection_status;
+ base_bearer_class->load_connection_status = NULL;
+ base_bearer_class->load_connection_status_finish = NULL;
+#if defined WITH_SUSPEND_RESUME
+ base_bearer_class->reload_connection_status = NULL;
+ base_bearer_class->reload_connection_status_finish = NULL;
+#endif
+
+ broadband_bearer_class->dial_3gpp = dial_3gpp;
+ broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish;
+ broadband_bearer_class->get_ip_config_3gpp = get_ip_config_3gpp;
+ broadband_bearer_class->get_ip_config_3gpp_finish = get_ip_config_3gpp_finish;
+ broadband_bearer_class->disconnect_3gpp = disconnect_3gpp;
+ broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish;
+
+ properties[PROP_DEFAULT_IP_METHOD] =
+ g_param_spec_enum (MM_BROADBAND_BEARER_ICERA_DEFAULT_IP_METHOD,
+ "Default IP method",
+ "Default IP Method (static or DHCP) to use.",
+ MM_TYPE_BEARER_IP_METHOD,
+ MM_BEARER_IP_METHOD_STATIC,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_DEFAULT_IP_METHOD, properties[PROP_DEFAULT_IP_METHOD]);
+}
diff --git a/src/plugins/icera/mm-broadband-bearer-icera.h b/src/plugins/icera/mm-broadband-bearer-icera.h
new file mode 100644
index 00000000..e169cb7c
--- /dev/null
+++ b/src/plugins/icera/mm-broadband-bearer-icera.h
@@ -0,0 +1,66 @@
+/* -*- 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:
+ *
+ * Author: Nathan Williams <njw@google.com>
+ *
+ * Copyright (C) 2012 Google, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_BEARER_ICERA_H
+#define MM_BROADBAND_BEARER_ICERA_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-broadband-bearer.h"
+
+#define MM_TYPE_BROADBAND_BEARER_ICERA (mm_broadband_bearer_icera_get_type ())
+#define MM_BROADBAND_BEARER_ICERA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_ICERA, MMBroadbandBearerIcera))
+#define MM_BROADBAND_BEARER_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_ICERA, MMBroadbandBearerIceraClass))
+#define MM_IS_BROADBAND_BEARER_ICERA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_ICERA))
+#define MM_IS_BROADBAND_BEARER_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_ICERA))
+#define MM_BROADBAND_BEARER_ICERA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_ICERA, MMBroadbandBearerIceraClass))
+
+#define MM_BROADBAND_BEARER_ICERA_DEFAULT_IP_METHOD "broadband-bearer-icera-default-ip-method"
+
+typedef struct _MMBroadbandBearerIcera MMBroadbandBearerIcera;
+typedef struct _MMBroadbandBearerIceraClass MMBroadbandBearerIceraClass;
+typedef struct _MMBroadbandBearerIceraPrivate MMBroadbandBearerIceraPrivate;
+
+struct _MMBroadbandBearerIcera {
+ MMBroadbandBearer parent;
+ MMBroadbandBearerIceraPrivate *priv;
+};
+
+struct _MMBroadbandBearerIceraClass {
+ MMBroadbandBearerClass parent;
+};
+
+GType mm_broadband_bearer_icera_get_type (void);
+
+/* Default bearer creation implementation */
+void mm_broadband_bearer_icera_new (MMBroadbandModem *modem,
+ MMBearerIpMethod ip_method,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseBearer *mm_broadband_bearer_icera_new_finish (GAsyncResult *res,
+ GError **error);
+
+gint mm_broadband_bearer_icera_get_connecting_profile_id (MMBroadbandBearerIcera *self);
+
+#endif /* MM_BROADBAND_BEARER_ICERA_H */
diff --git a/src/plugins/icera/mm-broadband-modem-icera.c b/src/plugins/icera/mm-broadband-modem-icera.c
new file mode 100644
index 00000000..759985e0
--- /dev/null
+++ b/src/plugins/icera/mm-broadband-modem-icera.c
@@ -0,0 +1,2374 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-serial-parsers.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-errors-types.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-3gpp-profile-manager.h"
+#include "mm-iface-modem-time.h"
+#include "mm-common-helpers.h"
+#include "mm-base-modem-at.h"
+#include "mm-bearer-list.h"
+#include "mm-broadband-bearer-icera.h"
+#include "mm-broadband-modem-icera.h"
+#include "mm-modem-helpers-icera.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void iface_modem_3gpp_profile_manager_init (MMIfaceModem3gppProfileManager *iface);
+static void iface_modem_time_init (MMIfaceModemTime *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+static MMIfaceModem3gppProfileManager *iface_modem_3gpp_profile_manager_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemIcera, mm_broadband_modem_icera, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_PROFILE_MANAGER, iface_modem_3gpp_profile_manager_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init))
+
+enum {
+ PROP_0,
+ PROP_DEFAULT_IP_METHOD,
+ PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+struct _MMBroadbandModemIceraPrivate {
+ MMBearerIpMethod default_ip_method;
+
+ GRegex *nwstate_regex;
+ GRegex *pacsp_regex;
+ GRegex *ipdpact_regex;
+
+ /* Cache of the most recent value seen by the unsolicited message handler */
+ MMModemAccessTechnology last_act;
+};
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static void
+add_supported_mode (MMBroadbandModemIcera *self,
+ GArray **combinations,
+ guint mode)
+{
+ MMModemModeCombination combination;
+
+ switch (mode) {
+ case 0:
+ mm_obj_dbg (self, "2G-only mode supported");
+ combination.allowed = MM_MODEM_MODE_2G;
+ combination.preferred = MM_MODEM_MODE_NONE;
+ break;
+ case 1:
+ mm_obj_dbg (self, "3G-only mode supported");
+ combination.allowed = MM_MODEM_MODE_3G;
+ combination.preferred = MM_MODEM_MODE_NONE;
+ break;
+ case 2:
+ mm_obj_dbg (self, "2G/3G mode with 2G preferred supported");
+ combination.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ combination.preferred = MM_MODEM_MODE_2G;
+ break;
+ case 3:
+ mm_obj_dbg (self, "2G/3G mode with 3G preferred supported");
+ combination.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ combination.preferred = MM_MODEM_MODE_3G;
+ break;
+ case 5:
+ /* Any, no need to add it to the list */
+ return;
+ default:
+ mm_obj_warn (self, "unsupported mode found in %%IPSYS=?: %u", mode);
+ return;
+ }
+
+ if (*combinations == NULL)
+ *combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5);
+
+ g_array_append_val (*combinations, combination);
+}
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GArray *combinations = NULL;
+ const gchar *response;
+ gchar **split = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autoptr(GRegex) r = NULL;
+ guint i;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return NULL;
+
+ /* Reply goes like this:
+ * AT%IPSYS=?
+ * %IPSYS: (0-3,5),(0-3)
+ */
+
+ r = g_regex_new ("\\%IPSYS:\\s*\\((.*)\\)\\s*,\\((.*)\\)",
+ G_REGEX_RAW, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match (r, response, 0, &match_info);
+ if (g_match_info_matches (match_info)) {
+ g_autofree gchar *aux = NULL;
+
+ aux = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (aux)
+ split = g_strsplit (aux, ",", -1);
+ }
+
+ if (!split) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "%%IPSYS=? response didn't match");
+ return NULL;
+ }
+
+ for (i = 0; split[i]; i++) {
+ gchar *interval_separator;
+
+ g_strstrip (split[i]);
+ interval_separator = strstr (split[i], "-");
+ if (interval_separator) {
+ /* Add all in interval */
+ gchar *first, *last;
+ guint modefirst, modelast;
+
+ first = g_strdup (split[i]);
+ interval_separator = strstr (first, "-");
+ *(interval_separator++) = '\0';
+ last = interval_separator;
+
+ if (mm_get_uint_from_str (first, &modefirst) &&
+ mm_get_uint_from_str (last, &modelast) &&
+ modefirst < modelast &&
+ modelast <= 5) {
+ guint j;
+
+ for (j = modefirst; j <= modelast; j++)
+ add_supported_mode (MM_BROADBAND_MODEM_ICERA (self), &combinations, j);
+ } else
+ mm_obj_warn (self, "couldn't parse mode interval in %%IPSYS=? response: %s", split[i]);
+ g_free (first);
+ } else {
+ guint mode;
+
+ /* Add single */
+ if (mm_get_uint_from_str (split[i], &mode))
+ add_supported_mode (MM_BROADBAND_MODEM_ICERA (self), &combinations, mode);
+ else
+ mm_obj_warn (self, "couldn't parse mode in %%IPSYS=? response: %s", split[i]);
+ }
+ }
+
+ g_strfreev (split);
+
+ if (!combinations)
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "No mode combinations were parsed from the %%IPSYS=? response (%s)",
+ response);
+
+ return combinations;
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "%IPSYS=?",
+ 3,
+ TRUE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load initial allowed/preferred modes (Modem interface) */
+
+static gboolean
+modem_load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ const gchar *response;
+ const gchar *str;
+ gint mode, domain;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return FALSE;
+
+ str = mm_strip_tag (response, "%IPSYS:");
+
+ if (!sscanf (str, "%d,%d", &mode, &domain)) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse %%IPSYS response: '%s'",
+ response);
+ return FALSE;
+ }
+
+ switch (mode) {
+ case 0:
+ *allowed = MM_MODEM_MODE_2G;
+ *preferred = MM_MODEM_MODE_NONE;
+ return TRUE;
+ case 1:
+ *allowed = MM_MODEM_MODE_3G;
+ *preferred = MM_MODEM_MODE_NONE;
+ return TRUE;
+ case 2:
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ *preferred = MM_MODEM_MODE_2G;
+ return TRUE;
+ case 3:
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ *preferred = MM_MODEM_MODE_3G;
+ return TRUE;
+ case 5: /* any */
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ *preferred = MM_MODEM_MODE_NONE;
+ return TRUE;
+ default:
+ break;
+ }
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse unexpected %%IPSYS response: '%s'",
+ response);
+ return FALSE;
+}
+
+static void
+modem_load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "%IPSYS?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Set allowed modes (Modem interface) */
+
+static gboolean
+modem_set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+allowed_mode_update_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error)
+ /* Let the error be critical. */
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+modem_set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *command;
+ gint icera_mode = -1;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /*
+ * The core has checked the following:
+ * - that 'allowed' are a subset of the 'supported' modes
+ * - that 'preferred' is one mode, and a subset of 'allowed'
+ */
+ if (allowed == MM_MODEM_MODE_2G)
+ icera_mode = 0;
+ else if (allowed == MM_MODEM_MODE_3G)
+ icera_mode = 1;
+ else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) {
+ if (preferred == MM_MODEM_MODE_2G)
+ icera_mode = 2;
+ else if (preferred == MM_MODEM_MODE_3G)
+ icera_mode = 3;
+ else /* none preferred, so AUTO */
+ icera_mode = 5;
+ } else if (allowed == MM_MODEM_MODE_ANY && preferred == MM_MODEM_MODE_NONE)
+ icera_mode = 5;
+
+ if (icera_mode < 0) {
+ gchar *allowed_str;
+ gchar *preferred_str;
+
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Requested mode (allowed: '%s', preferred: '%s') not "
+ "supported by the modem.",
+ allowed_str,
+ preferred_str);
+ g_object_unref (task);
+ g_free (allowed_str);
+ g_free (preferred_str);
+ return;
+ }
+
+ command = g_strdup_printf ("%%IPSYS=%d", icera_mode);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)allowed_mode_update_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Icera-specific unsolicited events handling */
+
+typedef struct {
+ guint cid;
+ MMBearerConnectionStatus status;
+} BearerListReportStatusForeachContext;
+
+static void
+bearer_list_report_status_foreach (MMBaseBearer *bearer,
+ BearerListReportStatusForeachContext *ctx)
+{
+ gint profile_id;
+ gint connecting_profile_id;
+
+ if (!MM_IS_BROADBAND_BEARER_ICERA (bearer))
+ return;
+
+ /* The profile ID in the base bearer is set only once the modem is connected */
+ profile_id = mm_base_bearer_get_profile_id (bearer);
+
+ /* The profile ID in the icera bearer is available during the connecting phase */
+ connecting_profile_id = mm_broadband_bearer_icera_get_connecting_profile_id (MM_BROADBAND_BEARER_ICERA (bearer));
+
+ if ((profile_id != (gint)ctx->cid) && (connecting_profile_id != (gint)ctx->cid))
+ return;
+
+ mm_base_bearer_report_connection_status (bearer, ctx->status);
+}
+
+static void
+ipdpact_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemIcera *self)
+{
+ MMBearerList *list = NULL;
+ BearerListReportStatusForeachContext ctx;
+ guint cid;
+ guint status;
+
+ /* Ensure we got proper parsed values */
+ if (!mm_get_uint_from_match_info (match_info, 1, &cid) ||
+ !mm_get_uint_from_match_info (match_info, 2, &status))
+ return;
+
+ /* Setup context */
+ ctx.cid = cid;
+ ctx.status = MM_BEARER_CONNECTION_STATUS_UNKNOWN;
+
+ switch (status) {
+ case 0:
+ ctx.status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED;
+ break;
+ case 1:
+ ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTED;
+ break;
+ case 2:
+ /* activating */
+ break;
+ case 3:
+ ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED;
+ break;
+ default:
+ mm_obj_warn (self, "unknown %%IPDPACT connect status %d", status);
+ break;
+ }
+
+ /* If unknown status, don't try to report anything */
+ if (ctx.status == MM_BEARER_CONNECTION_STATUS_UNKNOWN)
+ return;
+
+ /* If empty bearer list, nothing else to do */
+ g_object_get (self,
+ MM_IFACE_MODEM_BEARER_LIST, &list,
+ NULL);
+ if (!list)
+ return;
+
+ /* Will report status only in the bearer with the specific CID */
+ mm_bearer_list_foreach (list,
+ (MMBearerListForeachFunc)bearer_list_report_status_foreach,
+ &ctx);
+ g_object_unref (list);
+}
+
+static MMModemAccessTechnology
+nwstate_to_act (const gchar *str)
+{
+ /* small 'g' means CS, big 'G' means PS */
+ if (!strcmp (str, "2g"))
+ return MM_MODEM_ACCESS_TECHNOLOGY_GSM;
+ else if (!strcmp (str, "2G-GPRS"))
+ return MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
+ else if (!strcmp (str, "2G-EDGE"))
+ return MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
+ else if (!strcmp (str, "3G"))
+ return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ else if (!strcmp (str, "3g"))
+ return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ else if (!strcmp (str, "R99"))
+ return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ else if (!strcmp (str, "3G-HSDPA") || !strcmp (str, "HSDPA"))
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
+ else if (!strcmp (str, "3G-HSUPA") || !strcmp (str, "HSUPA"))
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSUPA;
+ else if (!strcmp (str, "3G-HSDPA-HSUPA") || !strcmp (str, "HSDPA-HSUPA"))
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSPA;
+ else if (!strcmp (str, "3G-HSDPA-HSUPA-HSPA+") || !strcmp (str, "HSDPA-HSUPA-HSPA+"))
+ return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS;
+
+ return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+}
+
+static void
+nwstate_changed (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMBroadbandModemIcera *self)
+{
+ gchar *str;
+
+ /*
+ * %NWSTATE: <rssi>,<mccmnc>,<tech>,<connection state>,<regulation>
+ *
+ * <connection state> shows the actual access technology in-use when a
+ * PS connection is active.
+ */
+
+ /* Process signal quality... */
+ str = g_match_info_fetch (info, 1);
+ if (str) {
+ gint rssi;
+
+ rssi = atoi (str);
+ rssi = CLAMP (rssi, 0, 5) * 100 / 5;
+ g_free (str);
+
+ mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self),
+ (guint)rssi);
+ }
+
+ /* Process access technology... */
+ str = g_match_info_fetch (info, 4);
+ if (!str || (strcmp (str, "-") == 0)) {
+ g_free (str);
+ str = g_match_info_fetch (info, 3);
+ }
+ if (str) {
+ MMModemAccessTechnology act;
+
+ act = nwstate_to_act (str);
+ g_free (str);
+
+ /* Cache last received value, needed for explicit access technology
+ * query handling */
+ self->priv->last_act = act;
+ mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
+ act,
+ MM_MODEM_ACCESS_TECHNOLOGY_ANY);
+ }
+}
+
+static void
+set_unsolicited_events_handlers (MMBroadbandModemIcera *self,
+ gboolean enable)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ 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 unsolicited events in given port */
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ /* Access technology related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->nwstate_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)nwstate_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ /* Connection status related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->ipdpact_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)ipdpact_received : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ /* Always to ignore */
+ if (!enable) {
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->pacsp_regex,
+ NULL,
+ NULL,
+ NULL);
+ }
+ }
+}
+
+/*****************************************************************************/
+/* Load access technologies (Modem interface) */
+
+static gboolean
+modem_load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ 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 FALSE;
+ }
+
+ *access_technologies = (MMModemAccessTechnology) value;
+ *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY;
+ return TRUE;
+}
+
+static void
+nwstate_query_ready (MMBroadbandModemIcera *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else {
+ /*
+ * The unsolicited message handler will already have run and
+ * removed the NWSTATE response, so we use the result from there.
+ */
+ g_task_return_int (task, self->priv->last_act);
+ }
+
+ g_object_unref (task);
+}
+
+static void
+modem_load_access_technologies (MMIfaceModem *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),
+ "%NWSTATE",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)nwstate_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else {
+ /* Our own setup now */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_ICERA (self), TRUE);
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's setup */
+ iface_modem_3gpp_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_setup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+static void
+parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Our own cleanup first */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_ICERA (self), FALSE);
+
+ /* And now chain up parent's cleanup */
+ iface_modem_3gpp_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Enable/disable unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_enable_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+own_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Our own enable now */
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "%NWSTATE=1",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)own_enable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's enable */
+ iface_modem_3gpp_parent->enable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_enable_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+static void
+parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+own_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Next, chain up parent's disable */
+ iface_modem_3gpp_parent->disable_unsolicited_events (
+ MM_IFACE_MODEM_3GPP (self),
+ (GAsyncReadyCallback)parent_disable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "%NWSTATE=0",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)own_disable_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Create bearer (Modem interface) */
+
+static MMBaseBearer *
+modem_create_bearer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+broadband_bearer_icera_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer = NULL;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_icera_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+
+ g_object_unref (task);
+}
+
+static void
+broadband_bearer_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer = NULL;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+
+ g_object_unref (task);
+}
+
+static void
+modem_create_bearer (MMIfaceModem *self,
+ MMBearerProperties *props,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* If we get a NET port, create Icera bearer */
+ if (mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET)) {
+ mm_broadband_bearer_icera_new (
+ MM_BROADBAND_MODEM (self),
+ MM_BROADBAND_MODEM_ICERA (self)->priv->default_ip_method,
+ props,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_icera_new_ready,
+ task);
+ return;
+ }
+
+ /* Otherwise, plain generic broadband bearer */
+ mm_broadband_bearer_new (
+ MM_BROADBAND_MODEM (self),
+ props,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_new_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Modem power up (Modem interface) */
+
+static gboolean
+modem_power_up_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+cfun_enable_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) {
+ /* Ignore all errors except NOT_ALLOWED, which means Airplane Mode */
+ if (g_error_matches (error,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NOT_ALLOWED))
+ g_task_return_error (task, error);
+ else {
+ g_error_free (error);
+ g_task_return_boolean (task, TRUE);
+ }
+ }
+
+ g_object_unref (task);
+}
+
+static void
+modem_power_up (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=1",
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)cfun_enable_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Modem power down (Modem interface) */
+
+static gboolean
+modem_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_power_down (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Use AT+CFUN=4 for power down. It will stop the RF (IMSI detach), and
+ * keeps access to the SIM */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=4",
+ /* The modem usually completes +CFUN=4 within 1-2 seconds,
+ * but sometimes takes a ridiculously long time (~30-35 seconds).
+ * It's better to have a long timeout here than to have the
+ * modem not responding to subsequent AT commands until +CFUN=4
+ * completes. */
+ 40,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Reset (Modem interface) */
+
+static gboolean
+modem_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "%IRESET",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load unlock retries (Modem interface) */
+
+static MMUnlockRetries *
+modem_load_unlock_retries_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_unlock_retries_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ int pin1, puk1, pin2, puk2;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ response = mm_strip_tag (response, "%PINNUM:");
+ if (sscanf (response, " %d, %d, %d, %d", &pin1, &puk1, &pin2, &puk2) == 4) {
+ MMUnlockRetries *retries;
+ retries = mm_unlock_retries_new ();
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN2, pin2);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK2, puk2);
+ g_task_return_pointer (task, retries, g_object_unref);
+ } else {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Invalid unlock retries response: '%s'",
+ response);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_load_unlock_retries (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "%PINNUM?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)load_unlock_retries_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Generic band handling utilities */
+
+typedef struct {
+ MMModemBand band;
+ char *name;
+ gboolean enabled;
+} Band;
+
+static void
+band_free (Band *b)
+{
+ g_free (b->name);
+ g_free (b);
+}
+
+static const Band modem_bands[] = {
+ /* Sort 3G first since it's preferred */
+ { MM_MODEM_BAND_UTRAN_1, (gchar *) "FDD_BAND_I", FALSE },
+ { MM_MODEM_BAND_UTRAN_2, (gchar *) "FDD_BAND_II", FALSE },
+ { MM_MODEM_BAND_UTRAN_3, (gchar *) "FDD_BAND_III", FALSE },
+ { MM_MODEM_BAND_UTRAN_4, (gchar *) "FDD_BAND_IV", FALSE },
+ { MM_MODEM_BAND_UTRAN_5, (gchar *) "FDD_BAND_V", FALSE },
+ { MM_MODEM_BAND_UTRAN_6, (gchar *) "FDD_BAND_VI", FALSE },
+ { MM_MODEM_BAND_UTRAN_8, (gchar *) "FDD_BAND_VIII", FALSE },
+ /* 2G second */
+ { MM_MODEM_BAND_G850, (gchar *) "G850", FALSE },
+ { MM_MODEM_BAND_DCS, (gchar *) "DCS", FALSE },
+ { MM_MODEM_BAND_EGSM, (gchar *) "EGSM", FALSE },
+ { MM_MODEM_BAND_PCS, (gchar *) "PCS", FALSE },
+ /* And ANY last since it's most inclusive */
+ { MM_MODEM_BAND_ANY, (gchar *) "ANY", FALSE },
+};
+
+static const guint modem_band_any_bit = 1 << (G_N_ELEMENTS (modem_bands) - 1);
+
+static MMModemBand
+icera_band_to_mm (const char *icera)
+{
+ guint i;
+
+ for (i = 0 ; i < G_N_ELEMENTS (modem_bands); i++) {
+ if (g_strcmp0 (icera, modem_bands[i].name) == 0)
+ return modem_bands[i].band;
+ }
+ return MM_MODEM_BAND_UNKNOWN;
+}
+
+static GSList *
+parse_bands (const gchar *response,
+ guint32 *out_len)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) info = NULL;
+ GSList *bands = NULL;
+
+ g_return_val_if_fail (out_len != NULL, NULL);
+
+ /*
+ * Response is a number of lines of the form:
+ * "EGSM": 0
+ * "FDD_BAND_I": 1
+ * ...
+ * with 1 and 0 indicating whether the particular band is enabled or not.
+ */
+ r = g_regex_new ("^\"(\\w+)\": (\\d)",
+ G_REGEX_MULTILINE, G_REGEX_MATCH_NEWLINE_ANY,
+ NULL);
+ g_assert (r != NULL);
+
+ g_regex_match (r, response, 0, &info);
+ while (g_match_info_matches (info)) {
+ gchar *name, *enabled;
+ Band *b;
+ MMModemBand band;
+
+ name = g_match_info_fetch (info, 1);
+ enabled = g_match_info_fetch (info, 2);
+ band = icera_band_to_mm (name);
+ if (band != MM_MODEM_BAND_UNKNOWN) {
+ b = g_malloc0 (sizeof (Band));
+ b->band = band;
+ b->name = g_strdup (name);
+ b->enabled = (enabled[0] == '1' ? TRUE : FALSE);
+ bands = g_slist_append (bands, b);
+ *out_len = *out_len + 1;
+ }
+ g_free (name);
+ g_free (enabled);
+ g_match_info_next (info, NULL);
+ }
+
+ return bands;
+}
+
+/*****************************************************************************/
+/* Load supported bands (Modem interface) */
+
+typedef struct {
+ MMBaseModemAtCommandAlloc *cmds;
+ GSList *check_bands;
+ GSList *enabled_bands;
+ guint32 idx;
+} SupportedBandsContext;
+
+static void
+supported_bands_context_free (SupportedBandsContext *ctx)
+{
+ guint i;
+
+ for (i = 0; ctx->cmds[i].command; i++)
+ mm_base_modem_at_command_alloc_clear (&ctx->cmds[i]);
+ g_free (ctx->cmds);
+ g_slist_free_full (ctx->check_bands, (GDestroyNotify) band_free);
+ g_slist_free_full (ctx->enabled_bands, (GDestroyNotify) band_free);
+ g_free (ctx);
+}
+
+static GArray *
+modem_load_supported_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_supported_bands_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ SupportedBandsContext *ctx = NULL;
+ GArray *bands;
+ GSList *iter;
+
+ mm_base_modem_at_sequence_finish (self, res, (gpointer) &ctx, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else {
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), ctx->idx);
+
+ /* Add already enabled bands */
+ for (iter = ctx->enabled_bands; iter; iter = g_slist_next (iter)) {
+ Band *b = iter->data;
+
+ g_array_prepend_val (bands, b->band);
+ }
+
+ /* Add any checked bands that are supported */
+ for (iter = ctx->check_bands; iter; iter = g_slist_next (iter)) {
+ Band *b = iter->data;
+
+ /* 'enabled' here really means supported/unsupported */
+ if (b->enabled)
+ g_array_prepend_val (bands, b->band);
+ }
+
+ g_task_return_pointer (task, bands, (GDestroyNotify) g_array_unref);
+ }
+ g_object_unref (task);
+}
+
+static MMBaseModemAtResponseProcessorResult
+load_supported_bands_response_processor (MMBaseModem *self,
+ gpointer context,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ SupportedBandsContext *ctx;
+ Band *b;
+
+ ctx = context;
+ b = g_slist_nth_data (ctx->check_bands, ctx->idx++);
+
+ /* If there was no error setting the band, that band is supported. We
+ * abuse the 'enabled' item to mean supported/unsupported.
+ */
+ b->enabled = !error;
+
+ /* Continue to next band */
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
+}
+
+static void
+load_supported_bands_get_current_bands_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SupportedBandsContext *ctx;
+ const gchar *response;
+ GError *error = NULL;
+ GSList *iter, *new;
+ guint32 len = 0, i = 0;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_new0 (SupportedBandsContext, 1);
+
+ /* For each reported band, build up an AT command to set that band
+ * to its current enabled/disabled state.
+ */
+ iter = ctx->check_bands = parse_bands (response, &len);
+ ctx->cmds = g_new0 (MMBaseModemAtCommandAlloc, len + 1);
+
+ while (iter) {
+ Band *b = iter->data;
+
+ if (b->enabled || b->band == MM_MODEM_BAND_ANY) {
+ /* Move known-supported band to the enabled list */
+ new = g_slist_next (iter);
+ ctx->check_bands = g_slist_remove_link (ctx->check_bands, iter);
+ ctx->enabled_bands = g_slist_prepend (ctx->enabled_bands, iter->data);
+ g_slist_free (iter);
+ iter = new;
+ } else {
+ /* Check support for disabled band */
+ ctx->cmds[i].command = g_strdup_printf ("%%IPBM=\"%s\",0", b->name);
+ ctx->cmds[i].timeout = 10;
+ ctx->cmds[i].allow_cached = FALSE;
+ ctx->cmds[i].response_processor = load_supported_bands_response_processor;
+ i++;
+ iter = g_slist_next (iter);
+ }
+ }
+
+ mm_base_modem_at_sequence (MM_BASE_MODEM (self),
+ (const MMBaseModemAtCommand *)ctx->cmds,
+ ctx,
+ (GDestroyNotify) supported_bands_context_free,
+ (GAsyncReadyCallback) load_supported_bands_ready,
+ task);
+}
+
+static void
+modem_load_supported_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* The modems report some bands as disabled that they don't actually
+ * support enabling. Thanks Icera! So we have to try setting each
+ * band to it's current enabled/disabled value, and the modem will
+ * return an error if it doesn't support that band at all.
+ */
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "%IPBM?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)load_supported_bands_get_current_bands_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Load current bands (Modem interface) */
+
+static GArray *
+modem_load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_current_bands_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GArray *bands;
+ const gchar *response;
+ GError *error = NULL;
+ GSList *parsed, *iter;
+ guint32 len = 0;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ } else {
+ /* Parse bands from Icera response into MM band numbers */
+ parsed = parse_bands (response, &len);
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), len);
+ for (iter = parsed; iter; iter = g_slist_next (iter)) {
+ Band *b = iter->data;
+
+ if (b->enabled)
+ g_array_append_val (bands, b->band);
+ }
+ g_slist_free_full (parsed, (GDestroyNotify) band_free);
+
+ g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_load_current_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "%IPBM?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)load_current_bands_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Set current bands (Modem interface) */
+
+typedef struct {
+ guint bandbits;
+ guint enablebits;
+ guint disablebits;
+} SetCurrentBandsContext;
+
+/*
+ * The modem's band-setting command (%IPBM=) enables or disables one
+ * band at a time, and one band must always be enabled. Here, we first
+ * get the set of enabled bands, compute the difference between that
+ * set and the requested set, enable any added bands, and finally
+ * disable any removed bands.
+ */
+static gboolean
+modem_set_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void set_one_band (MMIfaceModem *self, GTask *task);
+
+static void
+set_current_bands_next (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ set_one_band (self, task);
+}
+
+static void
+set_one_band (MMIfaceModem *self,
+ GTask *task)
+{
+ SetCurrentBandsContext *ctx;
+ guint enable, band;
+ gchar *command;
+
+ ctx = g_task_get_task_data (task);
+
+ /* Find the next band to enable or disable, always doing enables first */
+ enable = 1;
+ band = ffs (ctx->enablebits);
+ if (band == 0) {
+ enable = 0;
+ band = ffs (ctx->disablebits);
+ }
+ if (band == 0) {
+ /* Both enabling and disabling are done */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Note that ffs() returning 2 corresponds to 1 << 1, not 1 << 2 */
+ band--;
+ mm_obj_dbg (self, "preparing %%IPBM command (1/2): enablebits %x, disablebits %x, band %d, enable %d",
+ ctx->enablebits, ctx->disablebits, band, enable);
+
+ if (enable)
+ ctx->enablebits &= ~(1 << band);
+ else
+ ctx->disablebits &= ~(1 << band);
+ mm_obj_dbg (self, "preparing %%IPBM command (2/2): enablebits %x, disablebits %x",
+ ctx->enablebits, ctx->disablebits);
+
+ command = g_strdup_printf ("%%IPBM=\"%s\",%d",
+ modem_bands[band].name,
+ enable);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)set_current_bands_next,
+ task);
+ g_free (command);
+}
+
+static guint
+band_array_to_bandbits (GArray *bands)
+{
+ MMModemBand band;
+ guint i, j, bandbits;
+
+ bandbits = 0;
+ for (i = 0 ; i < bands->len ; i++) {
+ band = g_array_index (bands, MMModemBand, i);
+ for (j = 0 ; j < G_N_ELEMENTS (modem_bands) ; j++) {
+ if (modem_bands[j].band == band) {
+ bandbits |= 1 << j;
+ break;
+ }
+ }
+ g_assert (j < G_N_ELEMENTS (modem_bands));
+ }
+
+ return bandbits;
+}
+
+static void
+set_current_bands_got_current_bands (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetCurrentBandsContext *ctx;
+ GArray *bands;
+ GError *error = NULL;
+ guint currentbits;
+
+ bands = modem_load_current_bands_finish (self, res, &error);
+ if (!bands) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_task_get_task_data (task);
+ currentbits = band_array_to_bandbits (bands);
+ ctx->enablebits = ctx->bandbits & ~currentbits;
+ ctx->disablebits = currentbits & ~ctx->bandbits;
+
+ set_one_band (self, task);
+}
+
+static void
+modem_set_current_bands (MMIfaceModem *self,
+ GArray *bands_array,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SetCurrentBandsContext *ctx;
+ GTask *task;
+
+ ctx = g_new0 (SetCurrentBandsContext, 1);
+ ctx->bandbits = band_array_to_bandbits (bands_array);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ /*
+ * If ANY is requested, simply enable ANY to activate all bands except for
+ * those forbidden. */
+ if (ctx->bandbits & modem_band_any_bit) {
+ ctx->enablebits = modem_band_any_bit;
+ ctx->disablebits = 0;
+ set_one_band (self, task);
+ return;
+ }
+
+ modem_load_current_bands (self,
+ (GAsyncReadyCallback)set_current_bands_got_current_bands,
+ task);
+}
+
+/*****************************************************************************/
+/* Load network timezone (Time interface) */
+
+static gboolean
+parse_tlts_query_reply (const gchar *response,
+ gchar **iso8601,
+ MMNetworkTimezone **tz,
+ GError **error)
+{
+ gboolean ret = TRUE;
+ gint year;
+ gint month;
+ gint day;
+ gint hour;
+ gint minute;
+ gint second;
+ gchar sign;
+ gint offset;
+ GDateTime *utc, *adjusted;
+
+ /* TLTS reports UTC time with the TZ offset to *local* time */
+ response = mm_strip_tag (response, "*TLTS: ");
+ if (sscanf (response,
+ "\"%02d/%02d/%02d,%02d:%02d:%02d%c%02d\"",
+ &year,
+ &month,
+ &day,
+ &hour,
+ &minute,
+ &second,
+ &sign,
+ &offset) != 8) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unknown *TLTS response: %s",
+ response);
+ return FALSE;
+ }
+
+ /* Icera modems only report a 2-digit year, while ISO-8601 requires
+ * a 4-digit year. Assume 2000.
+ */
+ if (year < 100)
+ year += 2000;
+
+ /* Offset comes in 15-min units */
+ offset *= 15;
+ /* Apply sign to offset; */
+ if (sign == '-')
+ offset *= -1;
+
+ utc = g_date_time_new_utc (year, month, day, hour, minute, second);
+ if (!utc) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Invalid *TLTS date/time: %s",
+ response);
+ return FALSE;
+ }
+
+ /* Convert UTC time to local time by adjusting by the timezone offset */
+ adjusted = g_date_time_add_minutes (utc, offset);
+ g_date_time_unref (utc);
+ if (!adjusted) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to convert modem time to local time (offset %d)",
+ offset);
+ return FALSE;
+ }
+
+ /* Convert offset from minutes-to-UTC to minutes-from-UTC */
+ offset *= -1;
+
+ if (tz) {
+ *tz = mm_network_timezone_new ();
+ mm_network_timezone_set_offset (*tz, offset);
+ }
+
+ if (iso8601) {
+ *iso8601 = mm_new_iso8601_time (g_date_time_get_year (adjusted),
+ g_date_time_get_month (adjusted),
+ g_date_time_get_day_of_month (adjusted),
+ g_date_time_get_hour (adjusted),
+ g_date_time_get_minute (adjusted),
+ g_date_time_get_second (adjusted),
+ TRUE,
+ offset,
+ error);
+ ret = (*iso8601 != NULL);
+ }
+
+ g_date_time_unref (adjusted);
+ return ret;
+}
+
+static MMNetworkTimezone *
+modem_time_load_network_timezone_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *response;
+ MMNetworkTimezone *tz;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL);
+ if (!response) {
+ /* We'll assume we can retry a bit later */
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_RETRY,
+ "Retry");
+ return NULL;
+ }
+
+ return (parse_tlts_query_reply (response, NULL, &tz, error) ? tz : NULL);
+}
+
+static void
+modem_time_load_network_timezone (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "*TLTS",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* List profiles (3GPP profile management interface) */
+
+typedef struct {
+ GList *profiles;
+} ListProfilesContext;
+
+static void
+list_profiles_context_free (ListProfilesContext *ctx)
+{
+ mm_3gpp_profile_list_free (ctx->profiles);
+ g_slice_free (ListProfilesContext, ctx);
+}
+
+static gboolean
+modem_3gpp_profile_manager_list_profiles_finish (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ GList **out_profiles,
+ GError **error)
+{
+ ListProfilesContext *ctx;
+
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return FALSE;
+
+ ctx = g_task_get_task_data (G_TASK (res));
+ if (out_profiles)
+ *out_profiles = g_steal_pointer (&ctx->profiles);
+ return TRUE;
+}
+
+static void
+profile_manager_ipdpcfg_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ ListProfilesContext *ctx;
+ const gchar *response;
+ g_autoptr(GError) error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response)
+ mm_obj_warn (self, "couldn't load PDP context auth settings: %s", error->message);
+ else if (!mm_icera_parse_ipdpcfg_query_response (response, ctx->profiles, self, &error))
+ mm_obj_warn (self, "couldn't update profile list with PDP context auth settings: %s", error->message);
+
+ /* complete successfully anyway */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+profile_manager_parent_list_profiles_ready (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ ListProfilesContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_slice_new0 (ListProfilesContext);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) list_profiles_context_free);
+
+ if (!iface_modem_3gpp_profile_manager_parent->list_profiles_finish (self, res, &ctx->profiles, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ if (!ctx->profiles) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "%IPDPCFG?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)profile_manager_ipdpcfg_query_ready,
+ task);
+}
+
+static void
+modem_3gpp_profile_manager_list_profiles (MMIfaceModem3gppProfileManager *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ iface_modem_3gpp_profile_manager_parent->list_profiles (
+ self,
+ (GAsyncReadyCallback)profile_manager_parent_list_profiles_ready,
+ task);
+}
+
+typedef struct {
+ gboolean new_id;
+ gint min_profile_id;
+ gint max_profile_id;
+ GEqualFunc apn_cmp;
+ MM3gppProfileCmpFlags profile_cmp_flags;
+} CheckFormatContext;
+
+static void
+check_format_context_free (CheckFormatContext *ctx)
+{
+ g_slice_free (CheckFormatContext, ctx);
+}
+
+static gboolean
+modem_3gpp_profile_manager_check_format_finish (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ gboolean *new_id,
+ gint *min_profile_id,
+ gint *max_profile_id,
+ GEqualFunc *apn_cmp,
+ MM3gppProfileCmpFlags *profile_cmp_flags,
+ GError **error)
+{
+ CheckFormatContext *ctx;
+
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return FALSE;
+
+ ctx = g_task_get_task_data (G_TASK (res));
+ if (new_id)
+ *new_id = ctx->new_id;
+ if (min_profile_id)
+ *min_profile_id = (gint) ctx->min_profile_id;
+ if (max_profile_id)
+ *max_profile_id = (gint) ctx->max_profile_id;
+ if (apn_cmp)
+ *apn_cmp = ctx->apn_cmp;
+ if (profile_cmp_flags)
+ *profile_cmp_flags = ctx->profile_cmp_flags;
+ return TRUE;
+}
+
+static void
+profile_manager_parent_check_format_ready (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ CheckFormatContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!iface_modem_3gpp_profile_manager_parent->check_format_finish (self,
+ res,
+ &ctx->new_id,
+ &ctx->min_profile_id,
+ &ctx->max_profile_id,
+ &ctx->apn_cmp,
+ &ctx->profile_cmp_flags,
+ &error)) {
+ g_task_return_error (task, error);
+ } else {
+ /* the icera implementation supports AUTH, so unset that cmp flag */
+ ctx->profile_cmp_flags &= ~MM_3GPP_PROFILE_CMP_FLAGS_NO_AUTH;
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_profile_manager_check_format (MMIfaceModem3gppProfileManager *self,
+ MMBearerIpFamily ip_type,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ CheckFormatContext *ctx;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ ctx = g_slice_new0 (CheckFormatContext);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)check_format_context_free);
+
+ iface_modem_3gpp_profile_manager_parent->check_format (
+ self,
+ ip_type,
+ (GAsyncReadyCallback)profile_manager_parent_check_format_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Deactivate profile (3GPP profile management interface) */
+
+static gboolean
+modem_3gpp_profile_manager_deactivate_profile_finish (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+deactivate_profile_ipdpact_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_profile_manager_deactivate_profile (MMIfaceModem3gppProfileManager *self,
+ MM3gppProfile *profile,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ g_autofree gchar *cmd = NULL;
+ gint profile_id;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ profile_id = mm_3gpp_profile_get_profile_id (profile);
+ mm_obj_dbg (self, "deactivating profile '%d'...", profile_id);
+
+ cmd = g_strdup_printf ("%%IPDPACT=%d,0", profile_id);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ cmd,
+ MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT,
+ FALSE,
+ (GAsyncReadyCallback)deactivate_profile_ipdpact_set_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Set profile (3GPP profile management interface) */
+
+#define IPDPCFG_SET_MAX_ATTEMPTS 3
+#define IPDPCFG_SET_RETRY_TIMEOUT_SECS 1
+
+typedef struct {
+ MM3gppProfile *profile;
+ gchar *cmd;
+ gint profile_id;
+ guint n_attempts;
+} StoreProfileContext;
+
+static void
+store_profile_context_free (StoreProfileContext *ctx)
+{
+ g_free (ctx->cmd);
+ g_clear_object (&ctx->profile);
+ g_slice_free (StoreProfileContext, ctx);
+}
+
+static gint
+modem_3gpp_profile_manager_store_profile_finish (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ gint *out_profile_id,
+ MMBearerApnType *out_apn_type,
+ GError **error)
+{
+ StoreProfileContext *ctx;
+
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return FALSE;
+
+ ctx = g_task_get_task_data (G_TASK (res));
+ if (out_profile_id)
+ *out_profile_id = ctx->profile_id;
+ if (out_apn_type)
+ *out_apn_type = MM_BEARER_APN_TYPE_NONE;
+ return TRUE;
+}
+
+static void profile_manager_store_profile_auth_settings (GTask *task);
+
+static gboolean
+profile_manager_ipdpcfg_set_retry (GTask *task)
+{
+ profile_manager_store_profile_auth_settings (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+profile_manager_ipdpcfg_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ StoreProfileContext *ctx;
+ g_autoptr(GError) error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ /* Retry configuring the context. It sometimes fails with a 583
+ * error ["a profile (CID) is currently active"] if a connect
+ * is attempted too soon after a disconnect. */
+ if (ctx->n_attempts < IPDPCFG_SET_MAX_ATTEMPTS) {
+ mm_obj_dbg (self, "couldn't store auth settings in profile '%d': %s; retrying...",
+ ctx->profile_id, error->message);
+ g_timeout_add_seconds (IPDPCFG_SET_RETRY_TIMEOUT_SECS, (GSourceFunc)profile_manager_ipdpcfg_set_retry, task);
+ return;
+ }
+ g_task_return_error (task, g_steal_pointer (&error));
+ } else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+profile_manager_store_profile_auth_settings (GTask *task)
+{
+ MMIfaceModem3gppProfileManager *self;
+ StoreProfileContext *ctx;
+ g_autofree gchar *cmd = NULL;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ if (!ctx->cmd) {
+ const gchar *user;
+ const gchar *password;
+ MMBearerAllowedAuth allowed_auth;
+
+ user = mm_3gpp_profile_get_user (ctx->profile);
+ password = mm_3gpp_profile_get_password (ctx->profile);
+ allowed_auth = mm_3gpp_profile_get_allowed_auth (ctx->profile);
+
+ /* Both user and password are required; otherwise firmware returns an error */
+ if (!user || !password || allowed_auth == MM_BEARER_ALLOWED_AUTH_NONE) {
+ mm_obj_dbg (self, "not using authentication");
+ ctx->cmd = g_strdup_printf ("%%IPDPCFG=%d,0,0,\"\",\"\"", ctx->profile_id);
+ } else {
+ g_autofree gchar *quoted_user = NULL;
+ g_autofree gchar *quoted_password = NULL;
+ guint icera_auth;
+
+ if (allowed_auth == MM_BEARER_ALLOWED_AUTH_UNKNOWN) {
+ mm_obj_dbg (self, "using default (CHAP) authentication method");
+ icera_auth = 2;
+ } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_CHAP) {
+ mm_obj_dbg (self, "using CHAP authentication method");
+ icera_auth = 2;
+ } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_PAP) {
+ mm_obj_dbg (self, "using PAP authentication method");
+ icera_auth = 1;
+ } else {
+ g_autofree gchar *str = NULL;
+
+ str = mm_bearer_allowed_auth_build_string_from_mask (allowed_auth);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot use any of the specified authentication methods (%s)",
+ str);
+ g_object_unref (task);
+ return;
+ }
+
+ quoted_user = mm_port_serial_at_quote_string (user);
+ quoted_password = mm_port_serial_at_quote_string (password);
+ ctx->cmd = g_strdup_printf ("%%IPDPCFG=%d,0,%u,%s,%s",
+ ctx->profile_id,
+ icera_auth,
+ quoted_user,
+ quoted_password);
+ }
+ }
+
+ ctx->n_attempts++;
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ ctx->cmd,
+ 6,
+ FALSE,
+ (GAsyncReadyCallback)profile_manager_ipdpcfg_set_ready,
+ task);
+}
+
+static void
+profile_manager_parent_store_profile_ready (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_profile_manager_parent->store_profile_finish (self, res, NULL, NULL, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ profile_manager_store_profile_auth_settings (task);
+}
+
+static void
+modem_3gpp_profile_manager_store_profile (MMIfaceModem3gppProfileManager *self,
+ MM3gppProfile *profile,
+ const gchar *index_field,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ StoreProfileContext *ctx;
+ GTask *task;
+
+ g_assert (g_strcmp0 (index_field, "profile-id") == 0);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ ctx = g_slice_new0 (StoreProfileContext);
+ ctx->profile = g_object_ref (profile);
+ ctx->profile_id = mm_3gpp_profile_get_profile_id (ctx->profile);
+ g_assert (ctx->profile_id != MM_3GPP_PROFILE_ID_UNKNOWN);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) store_profile_context_free);
+
+ iface_modem_3gpp_profile_manager_parent->store_profile (
+ self,
+ profile,
+ index_field,
+ (GAsyncReadyCallback)profile_manager_parent_store_profile_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load network time (Time interface) */
+
+static gchar *
+modem_time_load_network_time_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *response;
+ gchar *iso8601;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return NULL;
+
+ return (parse_tlts_query_reply (response, &iso8601, NULL, error) ? iso8601 : NULL);
+}
+
+static void
+modem_time_load_network_time (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "*TLTS",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Check support (Time interface) */
+
+static gboolean
+modem_time_check_support_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+modem_time_check_support (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* We assume Icera devices always support *TLTS, since they appear
+ * to return ERROR if the modem is not powered up, and thus we cannot
+ * check for *TLTS support during modem initialization.
+ */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_icera_parent_class)->setup_ports (self);
+
+ /* Now reset the unsolicited messages we'll handle when enabled */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_ICERA (self), FALSE);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemIcera *
+mm_broadband_modem_icera_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_ICERA,
+ 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 (AT) or Icera bearer (NET) supported */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MMBroadbandModemIcera *self = MM_BROADBAND_MODEM_ICERA (object);
+
+ switch (prop_id) {
+ case PROP_DEFAULT_IP_METHOD:
+ self->priv->default_ip_method = g_value_get_enum (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MMBroadbandModemIcera *self = MM_BROADBAND_MODEM_ICERA (object);
+
+ switch (prop_id) {
+ case PROP_DEFAULT_IP_METHOD:
+ g_value_set_enum (value, self->priv->default_ip_method);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+mm_broadband_modem_icera_init (MMBroadbandModemIcera *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_MODEM_ICERA,
+ MMBroadbandModemIceraPrivate);
+
+ self->priv->nwstate_regex = g_regex_new ("%NWSTATE:\\s*(-?\\d+),(\\d+),([^,]*),([^,]*),(\\d+)",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->pacsp_regex = g_regex_new ("\\r\\n\\+PACSP(\\d)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->ipdpact_regex = g_regex_new ("\\r\\n%IPDPACT:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ self->priv->default_ip_method = MM_BEARER_IP_METHOD_STATIC;
+ self->priv->last_act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemIcera *self = MM_BROADBAND_MODEM_ICERA (object);
+
+ g_regex_unref (self->priv->nwstate_regex);
+ g_regex_unref (self->priv->pacsp_regex);
+ g_regex_unref (self->priv->ipdpact_regex);
+
+ G_OBJECT_CLASS (mm_broadband_modem_icera_parent_class)->finalize (object);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = modem_load_current_modes;
+ iface->load_current_modes_finish = modem_load_current_modes_finish;
+ iface->set_current_modes = modem_set_current_modes;
+ iface->set_current_modes_finish = modem_set_current_modes_finish;
+ iface->load_access_technologies = modem_load_access_technologies;
+ iface->load_access_technologies_finish = modem_load_access_technologies_finish;
+ iface->load_unlock_retries = modem_load_unlock_retries;
+ iface->load_unlock_retries_finish = modem_load_unlock_retries_finish;
+ iface->load_supported_bands = modem_load_supported_bands;
+ iface->load_supported_bands_finish = modem_load_supported_bands_finish;
+ iface->load_current_bands = modem_load_current_bands;
+ iface->load_current_bands_finish = modem_load_current_bands_finish;
+ iface->modem_power_up = modem_power_up;
+ iface->modem_power_up_finish = modem_power_up_finish;
+ /* Note: don't implement modem_init_power_down, as CFUN=4 here may take
+ * looong to reply */
+ iface->modem_power_down = modem_power_down;
+ iface->modem_power_down_finish = modem_power_down_finish;
+ iface->reset = modem_reset;
+ iface->reset_finish = modem_reset_finish;
+ iface->set_current_bands = modem_set_current_bands;
+ iface->set_current_bands_finish = modem_set_current_bands_finish;
+ iface->create_bearer = modem_create_bearer;
+ iface->create_bearer_finish = modem_create_bearer_finish;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = modem_3gpp_enable_disable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = modem_3gpp_enable_disable_unsolicited_events_finish;
+}
+
+static void
+iface_modem_3gpp_profile_manager_init (MMIfaceModem3gppProfileManager *iface)
+{
+ iface_modem_3gpp_profile_manager_parent = g_type_interface_peek_parent (iface);
+
+ iface->list_profiles = modem_3gpp_profile_manager_list_profiles;
+ iface->list_profiles_finish = modem_3gpp_profile_manager_list_profiles_finish;
+ iface->check_format = modem_3gpp_profile_manager_check_format;
+ iface->check_format_finish = modem_3gpp_profile_manager_check_format_finish;
+ /* note: the parent check_activated_profile() implementation using +CGACT? seems to
+ * be perfectly valid. */
+ iface->deactivate_profile = modem_3gpp_profile_manager_deactivate_profile;
+ iface->deactivate_profile_finish = modem_3gpp_profile_manager_deactivate_profile_finish;
+ iface->store_profile = modem_3gpp_profile_manager_store_profile;
+ iface->store_profile_finish = modem_3gpp_profile_manager_store_profile_finish;
+}
+
+static void
+iface_modem_time_init (MMIfaceModemTime *iface)
+{
+ iface->check_support = modem_time_check_support;
+ iface->check_support_finish = modem_time_check_support_finish;
+ iface->load_network_time = modem_time_load_network_time;
+ iface->load_network_time_finish = modem_time_load_network_time_finish;
+ iface->load_network_timezone = modem_time_load_network_timezone;
+ iface->load_network_timezone_finish = modem_time_load_network_timezone_finish;
+}
+
+static void
+mm_broadband_modem_icera_class_init (MMBroadbandModemIceraClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemIceraPrivate));
+
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->finalize = finalize;
+ broadband_modem_class->setup_ports = setup_ports;
+
+ properties[PROP_DEFAULT_IP_METHOD] =
+ g_param_spec_enum (MM_BROADBAND_MODEM_ICERA_DEFAULT_IP_METHOD,
+ "Default IP method",
+ "Default IP Method (static or DHCP) to use.",
+ MM_TYPE_BEARER_IP_METHOD,
+ MM_BEARER_IP_METHOD_STATIC,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_DEFAULT_IP_METHOD, properties[PROP_DEFAULT_IP_METHOD]);
+}
diff --git a/src/plugins/icera/mm-broadband-modem-icera.h b/src/plugins/icera/mm-broadband-modem-icera.h
new file mode 100644
index 00000000..cb88aaf5
--- /dev/null
+++ b/src/plugins/icera/mm-broadband-modem-icera.h
@@ -0,0 +1,55 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_MODEM_ICERA_H
+#define MM_BROADBAND_MODEM_ICERA_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_ICERA (mm_broadband_modem_icera_get_type ())
+#define MM_BROADBAND_MODEM_ICERA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_ICERA, MMBroadbandModemIcera))
+#define MM_BROADBAND_MODEM_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_ICERA, MMBroadbandModemIceraClass))
+#define MM_IS_BROADBAND_MODEM_ICERA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_ICERA))
+#define MM_IS_BROADBAND_MODEM_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_ICERA))
+#define MM_BROADBAND_MODEM_ICERA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_ICERA, MMBroadbandModemIceraClass))
+
+#define MM_BROADBAND_MODEM_ICERA_DEFAULT_IP_METHOD "broadband-modem-icera-default-ip-method"
+
+typedef struct _MMBroadbandModemIcera MMBroadbandModemIcera;
+typedef struct _MMBroadbandModemIceraClass MMBroadbandModemIceraClass;
+typedef struct _MMBroadbandModemIceraPrivate MMBroadbandModemIceraPrivate;
+
+struct _MMBroadbandModemIcera {
+ MMBroadbandModem parent;
+ MMBroadbandModemIceraPrivate *priv;
+};
+
+struct _MMBroadbandModemIceraClass{
+ MMBroadbandModemClass parent;
+};
+
+G_MODULE_EXPORT
+GType mm_broadband_modem_icera_get_type (void);
+
+G_MODULE_EXPORT
+MMBroadbandModemIcera *mm_broadband_modem_icera_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_ICERA_H */
diff --git a/src/plugins/icera/mm-modem-helpers-icera.c b/src/plugins/icera/mm-modem-helpers-icera.c
new file mode 100644
index 00000000..da1cd873
--- /dev/null
+++ b/src/plugins/icera/mm-modem-helpers-icera.c
@@ -0,0 +1,389 @@
+/* -*- 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) 2012 Google, Inc.
+ * Copyright (C) 2012 - 2013 Aleksander Morgado <aleksander@gnu.org>
+ * Copyright (C) 2014 Dan Williams <dcbw@redhat.com>
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <netinet/in.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-icera.h"
+
+/*****************************************************************************/
+/* %IPDPADDR response parser */
+
+static MMBearerIpConfig *
+parse_ipdpaddr_v4 (const gchar **items, guint num_items, GError **error)
+{
+ MMBearerIpConfig *config;
+ const gchar *dns[3] = { 0 };
+ guint dns_i = 0, tmp;
+ const gchar *netmask = NULL;
+
+ /* IP address and prefix */
+ tmp = 0;
+ if (!inet_pton (AF_INET, items[1], &tmp)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse IPv4 address '%s'", items[1]);
+ return NULL;
+ }
+
+ if (!tmp) {
+ /* No IPv4 config */
+ return NULL;
+ }
+
+ config = mm_bearer_ip_config_new ();
+ mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_STATIC);
+ mm_bearer_ip_config_set_address (config, items[1]);
+ mm_bearer_ip_config_set_prefix (config, 32); /* default prefix */
+
+ /* Gateway */
+ tmp = 0;
+ if (inet_pton (AF_INET, items[2], &tmp)) {
+ if (tmp)
+ mm_bearer_ip_config_set_gateway (config, items[2]);
+ } else {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse gateway address '%s'", items[2]);
+ goto error;
+ }
+
+ /* DNS */
+ tmp = 0;
+ if (inet_pton (AF_INET, items[3], &tmp) && tmp)
+ dns[dns_i++] = items[3];
+ else {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse DNS address '%s'", items[3]);
+ goto error;
+ }
+
+ /* DNS2 - sometimes missing and set to 0.0.0.0 */
+ tmp = 0;
+ if (inet_pton (AF_INET, items[4], &tmp) && tmp)
+ dns[dns_i++] = items[4];
+ if (dns_i > 0)
+ mm_bearer_ip_config_set_dns (config, (const gchar **) dns);
+
+ /* Short form (eg, Sierra USB305) */
+ if (num_items < 9)
+ return config;
+
+ /* Devices return netmask and secondary gateway in one of two
+ * positions. The netmask may be either at index 7 or 8, while
+ * the secondary gateway may be at position 8 or 9.
+ */
+
+ if (items[7] && strstr (items[7], "255.") && !strstr (items[7], "255.0.0.0"))
+ netmask = items[7];
+ if (items[8] && strstr (items[8], "255.") && !strstr (items[8], "255.0.0.0"))
+ netmask = items[8];
+ if (netmask) {
+ if (!inet_pton (AF_INET, netmask, &tmp)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse netmask '%s'",
+ netmask);
+ goto error;
+ }
+ mm_bearer_ip_config_set_prefix (config, mm_netmask_to_cidr (netmask));
+ }
+
+ /* Secondary gateway */
+ if (!mm_bearer_ip_config_get_gateway (config)) {
+ const char *gw2 = NULL;
+
+ if (num_items >= 10 && items[9] && !strstr (items[9], "255.") && !strstr (items[9], "::"))
+ gw2 = items[9];
+ /* Prefer position 8 */
+ if (items[8] && !strstr (items[8], "255."))
+ gw2 = items[8];
+
+ if (gw2 && inet_pton (AF_INET, gw2, &tmp) && tmp)
+ mm_bearer_ip_config_set_gateway (config, gw2);
+ else {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse secondary gateway address '%s'",
+ gw2 ? gw2 : "(unknown)");
+ goto error;
+ }
+ }
+
+ return config;
+
+error:
+ g_object_unref (config);
+ return NULL;
+}
+
+static MMBearerIpConfig *
+parse_ipdpaddr_v6 (const gchar **items, guint num_items, GError **error)
+{
+ MMBearerIpConfig *config;
+ const gchar *dns[2] = { 0 };
+ struct in6_addr tmp6 = IN6ADDR_ANY_INIT;
+
+ if (num_items < 12)
+ return NULL;
+
+ /* No IPv6 IP and no IPv6 DNS, return NULL without error. */
+ if (g_strcmp0 (items[9], "::") == 0 && g_strcmp0 (items[11], "::") == 0)
+ return NULL;
+
+ config = mm_bearer_ip_config_new ();
+
+ /* It appears that for IPv6 %IPDPADDR returns only the expected
+ * link-local address and a DNS address, and that to retrieve the
+ * default router, extra DNS, and search domains, the host must listen
+ * for IPv6 Router Advertisements on the net port.
+ */
+ if (g_strcmp0 (items[9], "::") != 0) {
+ mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_STATIC);
+ /* IP address and prefix */
+ if (inet_pton (AF_INET6, items[9], &tmp6) != 1 ||
+ IN6_IS_ADDR_UNSPECIFIED (&tmp6)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse IPv6 address '%s'", items[9]);
+ goto error;
+ }
+ mm_bearer_ip_config_set_address (config, items[9]);
+ mm_bearer_ip_config_set_prefix (config, 64);
+
+ /* If the address is a link-local one, then SLAAC or DHCP must be used
+ * to get the real prefix and address. Change the method to DHCP to
+ * indicate this to clients.
+ */
+ if (IN6_IS_ADDR_LINKLOCAL (&tmp6))
+ mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_DHCP);
+ } else {
+ /* No IPv6 given, but DNS will be available, try with DHCP */
+ mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_DHCP);
+ }
+
+ /* DNS server */
+ if (g_strcmp0 (items[11], "::") != 0) {
+ memset (&tmp6, 0, sizeof (tmp6));
+ if (inet_pton (AF_INET6, items[11], &tmp6) == 1 &&
+ !IN6_IS_ADDR_UNSPECIFIED (&tmp6)) {
+ dns[0] = items[11];
+ dns[1] = NULL;
+ mm_bearer_ip_config_set_dns (config, (const gchar **) dns);
+ } else {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse DNS address '%s'", items[11]);
+ goto error;
+ }
+ }
+
+ return config;
+
+error:
+ g_object_unref (config);
+ return NULL;
+}
+
+#define IPDPADDR_TAG "%IPDPADDR: "
+
+gboolean
+mm_icera_parse_ipdpaddr_response (const gchar *response,
+ guint expected_cid,
+ MMBearerIpConfig **out_ip4_config,
+ MMBearerIpConfig **out_ip6_config,
+ GError **error)
+{
+ MMBearerIpConfig *ip4_config = NULL;
+ MMBearerIpConfig *ip6_config = NULL;
+ GError *local = NULL;
+ gboolean success = FALSE;
+ char **items;
+ guint num_items, i;
+ guint num;
+
+ g_return_val_if_fail (out_ip4_config, FALSE);
+ g_return_val_if_fail (out_ip6_config, FALSE);
+
+ if (!response || !g_str_has_prefix (response, IPDPADDR_TAG)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing %%IPDPADDR prefix");
+ return FALSE;
+ }
+
+ /* %IPDPADDR: <cid>,<ip>,<gw>,<dns1>,<dns2>[,<nbns1>,<nbns2>[,<??>,<netmask>,<gw>]]
+ * %IPDPADDR: <cid>,<ip>,<gw>,<dns1>,<dns2>,<nbns1>,<nbns2>,<netmask>,<gw>
+ * %IPDPADDR: <cid>,<ip>,<gw>,<dns1>,<dns2>,<nbns1>,<nbns2>,<??>,<gw>,<ip6>,::,<ip6_dns1>,::,::,::,::,::
+ *
+ * Sierra USB305: %IPDPADDR: 2, 21.93.217.11, 21.93.217.10, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0
+ * K3805-Z: %IPDPADDR: 2, 21.93.217.11, 21.93.217.10, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 255.255.255.0, 21.93.217.10,
+ * Nokia 21M: %IPDPADDR: 2, 33.196.7.127, 33.196.7.128, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 33.196.7.128, fe80::f:9135:5901, ::, fd00:976a::9, ::, ::, ::, ::, ::
+ * Nokia 21M: %IPDPADDR: 3, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, fe80::2e:437b:7901, ::, fd00:976a::9, ::, ::, ::, ::, ::
+ */
+ response = mm_strip_tag (response, IPDPADDR_TAG);
+ items = g_strsplit_set (response, ",", 0);
+
+ /* Strip any spaces on elements; inet_pton() doesn't like them */
+ num_items = g_strv_length (items);
+ for (i = 0; i < num_items; i++)
+ items[i] = g_strstrip (items[i]);
+
+ if (num_items < 7) {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Malformed IPDPADDR response (not enough items)");
+ goto out;
+ }
+
+ /* Validate context ID */
+ if (!mm_get_uint_from_str (items[0], &num) ||
+ num != expected_cid) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unknown CID in IPDPADDR response (got %d, expected %d)",
+ (guint) num,
+ expected_cid);
+ goto out;
+ }
+
+ ip4_config = parse_ipdpaddr_v4 ((const gchar **) items, num_items, &local);
+ if (local) {
+ g_propagate_error (error, local);
+ goto out;
+ }
+
+ ip6_config = parse_ipdpaddr_v6 ((const gchar **) items, num_items, &local);
+ if (local) {
+ g_propagate_error (error, local);
+ goto out;
+ }
+
+ success = TRUE;
+
+out:
+ g_strfreev (items);
+
+ *out_ip4_config = ip4_config;
+ *out_ip6_config = ip6_config;
+ return success;
+}
+
+/*****************************************************************************/
+/* %IPDPCFG? response parser.
+ * Modifies the input list of profiles in place
+ *
+ * AT%IPDPCFG?
+ * %IPDPCFG: 1,0,0,,,0
+ * %IPDPCFG: 2,0,0,,,0
+ * %IPDPCFG: 3,0,2,"user","pass",0
+ * %IPDPCFG: 4,0,0,,,0
+ * OK
+ */
+
+gboolean
+mm_icera_parse_ipdpcfg_query_response (const gchar *str,
+ GList *profiles,
+ gpointer log_object,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GError) inner_error = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ guint n_updates = 0;
+ guint n_profiles;
+
+ n_profiles = g_list_length (profiles);
+
+ r = g_regex_new ("%IPDPCFG:\\s*(\\d+),(\\d+),(\\d+),([^,]*),([^,]*),(\\d+)",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+ 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, str, strlen (str), 0, 0, &match_info, &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ /* Parse the results */
+ while (g_match_info_matches (match_info)) {
+ guint cid;
+ guint auth;
+ MMBearerAllowedAuth allowed_auth;
+ g_autofree gchar *user = NULL;
+ g_autofree gchar *password = NULL;
+ GList *l;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &cid)) {
+ mm_obj_warn (log_object, "couldn't parse cid from %%IPDPCFG line");
+ goto next;
+ }
+
+ if (!mm_get_uint_from_match_info (match_info, 3, &auth)) {
+ mm_obj_warn (log_object, "couldn't parse auth from %%IPDPCFG line");
+ goto next;
+ }
+
+ switch (auth) {
+ case 0:
+ allowed_auth = MM_BEARER_ALLOWED_AUTH_NONE;
+ break;
+ case 1:
+ allowed_auth = MM_BEARER_ALLOWED_AUTH_PAP;
+ break;
+ case 2:
+ allowed_auth = MM_BEARER_ALLOWED_AUTH_CHAP;
+ break;
+ default:
+ mm_obj_warn (log_object, "unexpected icera auth setting: %u", auth);
+ goto next;
+ }
+
+ user = mm_get_string_unquoted_from_match_info (match_info, 4);
+ password = mm_get_string_unquoted_from_match_info (match_info, 5);
+
+ mm_obj_dbg (log_object, "found icera auth settings for profile with id '%u'", cid);
+
+ /* Find profile and update in place */
+ for (l = profiles; l; l = g_list_next (l)) {
+ MM3gppProfile *iter = l->data;
+
+ if (mm_3gpp_profile_get_profile_id (iter) == (gint) cid) {
+ n_updates++;
+ mm_3gpp_profile_set_allowed_auth (iter, allowed_auth);
+ mm_3gpp_profile_set_user (iter, user);
+ mm_3gpp_profile_set_password (iter, password);
+ break;
+ }
+ }
+ if (!l)
+ mm_obj_warn (log_object, "couldn't update auth settings in profile with id '%d': not found", cid);
+
+ next:
+ g_match_info_next (match_info, NULL);
+ }
+
+ if (n_updates != n_profiles)
+ mm_obj_warn (log_object, "couldn't update auth settings in all profiles: %u/%u updated",
+ n_updates, n_profiles);
+
+ return TRUE;
+}
diff --git a/src/plugins/icera/mm-modem-helpers-icera.h b/src/plugins/icera/mm-modem-helpers-icera.h
new file mode 100644
index 00000000..fa4f9016
--- /dev/null
+++ b/src/plugins/icera/mm-modem-helpers-icera.h
@@ -0,0 +1,34 @@
+/* -*- 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) 2014 Dan Williams <dcbw@redhat.com>
+ */
+
+#ifndef MM_MODEM_HELPERS_ICERA_H
+#define MM_MODEM_HELPERS_ICERA_H
+
+#include "glib.h"
+
+/* %IPDPADDR response parser */
+gboolean mm_icera_parse_ipdpaddr_response (const gchar *response,
+ guint expected_cid,
+ MMBearerIpConfig **out_ip4_config,
+ MMBearerIpConfig **out_ip6_config,
+ GError **error);
+
+/* %IPDPCFG? response parser */
+gboolean mm_icera_parse_ipdpcfg_query_response (const gchar *response,
+ GList *profiles,
+ gpointer log_object,
+ GError **error);
+
+#endif /* MM_MODEM_HELPERS_HUAWEI_H */
diff --git a/src/plugins/icera/mm-shared.c b/src/plugins/icera/mm-shared.c
new file mode 100644
index 00000000..735d61bf
--- /dev/null
+++ b/src/plugins/icera/mm-shared.c
@@ -0,0 +1,20 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-shared.h"
+
+MM_SHARED_DEFINE_MAJOR_VERSION
+MM_SHARED_DEFINE_MINOR_VERSION
+MM_SHARED_DEFINE_NAME(Icera)
diff --git a/src/plugins/icera/tests/test-modem-helpers-icera.c b/src/plugins/icera/tests/test-modem-helpers-icera.c
new file mode 100644
index 00000000..dceb1e2b
--- /dev/null
+++ b/src/plugins/icera/tests/test-modem-helpers-icera.c
@@ -0,0 +1,262 @@
+/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org>
+ * Copyright (C) 2014 Dan Williams <dcbw@redhat.com>
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include <locale.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-test.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-icera.h"
+
+/*****************************************************************************/
+/* Test %IPDPADDR responses */
+
+typedef struct {
+ const gchar *str;
+ const guint expected_cid;
+
+ /* IPv4 */
+ const gchar *ipv4_addr;
+ const guint ipv4_prefix;
+ const gchar *ipv4_gw;
+ const gchar *ipv4_dns1;
+ const gchar *ipv4_dns2;
+
+ /* IPv6 */
+ const gchar *ipv6_addr;
+ const gchar *ipv6_dns1;
+} IpdpaddrTest;
+
+static const IpdpaddrTest ipdpaddr_tests[] = {
+ /* Sierra USB305 */
+ { "%IPDPADDR: 2, 21.93.217.11, 21.93.217.10, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0\r\n",
+ 2, "21.93.217.11", 32, "21.93.217.10", "10.177.0.34", "10.161.171.220",
+ NULL, NULL },
+
+ /* ZTE/Vodafone K3805-Z */
+ { "%IPDPADDR: 5, 21.93.217.11, 21.93.217.10, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 255.255.255.0, 21.93.217.10,\r\n",
+ 5, "21.93.217.11", 24, "21.93.217.10", "10.177.0.34", "10.161.171.220",
+ NULL, NULL },
+
+ /* Secondary gateway check */
+ { "%IPDPADDR: 5, 21.93.217.11, 0.0.0.0, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 255.255.255.0, 21.93.217.10,\r\n",
+ 5, "21.93.217.11", 24, "21.93.217.10", "10.177.0.34", "10.161.171.220",
+ NULL, NULL },
+
+ /* Secondary gateway check #2 */
+ { "%IPDPADDR: 5, 27.107.96.189, 0.0.0.0, 121.242.190.210, 121.242.190.181, 0.0.0.0, 0.0.0.0, 255.255.255.254, 27.107.96.188\r\n",
+ 5, "27.107.96.189", 31, "27.107.96.188", "121.242.190.210", "121.242.190.181",
+ NULL, NULL },
+
+ /* Nokia 21M */
+ { "%IPDPADDR: 1, 33.196.7.127, 33.196.7.128, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 33.196.7.128, fe80::f:9135:5901, ::, fd00:976a::9, ::, ::, ::, ::, ::\r\n",
+ 1, "33.196.7.127", 32, "33.196.7.128", "10.177.0.34", "10.161.171.220",
+ "fe80::f:9135:5901", "fd00:976a::9" },
+
+ { "%IPDPADDR: 3, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, fe80::2e:437b:7901, ::, fd00:976a::9, ::, ::, ::, ::, ::\r\n",
+ 3, NULL, 0, NULL, NULL, NULL,
+ "fe80::2e:437b:7901", "fd00:976a::9" },
+
+ /* Some development chip (cnsbg.p1001.rev2, CL477342) */
+ { "%IPDPADDR: 5, 27.107.96.189, 27.107.96.188, 121.242.190.210, 121.242.190.181, 0.0.0.0, 0.0.0.0, 255.255.255.254, 27.107.96.188\r\n",
+ 5, "27.107.96.189", 31, "27.107.96.188", "121.242.190.210", "121.242.190.181",
+ NULL, NULL },
+
+ /* 21M with newer firmware */
+ { "%IPDPADDR: 2, 188.150.116.13, 188.150.116.14, 188.149.250.16, 0.0.0.0, 0.0.0.0, 0.0.0.0, 255.255.0.0, 188.150.116.14, fe80::1:e414:eb01, ::, 2a00:e18:0:3::6, ::, ::, ::, ::, ::\r\n",
+ 2, "188.150.116.13", 16, "188.150.116.14", "188.149.250.16", NULL,
+ "fe80::1:e414:eb01", "2a00:e18:0:3::6" },
+
+ { "%IPDPADDR: 1, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, fe80::1f:fad1:4c01, ::, 2001:4600:4:fff::54, 2001:4600:4:1fff::54, ::, ::, ::, ::\r\n",
+ 1, NULL, 0, NULL, NULL, NULL,
+ "fe80::1f:fad1:4c01", "2001:4600:4:fff::54" },
+
+ { "%IPDPADDR: 1, 46.157.76.179, 46.157.76.180, 193.213.112.4, 130.67.15.198, 0.0.0.0, 0.0.0.0, 255.0.0.0, 46.157.76.180, ::, ::, ::, ::, ::, ::, ::, ::\r\n",
+ 1, "46.157.76.179", 32, "46.157.76.180", "193.213.112.4", "130.67.15.198",
+ NULL, NULL },
+
+ { "%IPDPADDR: 1, 0.0.0.0, 0.0.0.0, 193.213.112.4, 130.67.15.198, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, ::, ::, 2001:4600:4:fff::52, 2001:4600:4:1fff::52, ::, ::, ::, ::",
+ 1, NULL, 0, NULL, NULL, NULL,
+ NULL, "2001:4600:4:fff::52" },
+
+ { NULL }
+};
+
+static void
+test_ipdpaddr (void)
+{
+ guint i;
+
+ for (i = 0; ipdpaddr_tests[i].str; i++) {
+ gboolean success;
+ GError *error = NULL;
+ MMBearerIpConfig *ipv4 = NULL;
+ MMBearerIpConfig *ipv6 = NULL;
+ const gchar **dns;
+ guint dnslen;
+
+ success = mm_icera_parse_ipdpaddr_response (
+ ipdpaddr_tests[i].str,
+ ipdpaddr_tests[i].expected_cid,
+ &ipv4,
+ &ipv6,
+ &error);
+ g_assert_no_error (error);
+ g_assert (success);
+
+ /* IPv4 */
+ if (ipdpaddr_tests[i].ipv4_addr) {
+ g_assert (ipv4);
+ g_assert_cmpint (mm_bearer_ip_config_get_method (ipv4), ==, MM_BEARER_IP_METHOD_STATIC);
+ g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv4), ==, ipdpaddr_tests[i].ipv4_addr);
+ g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv4), ==, ipdpaddr_tests[i].ipv4_prefix);
+ g_assert_cmpstr (mm_bearer_ip_config_get_gateway (ipv4), ==, ipdpaddr_tests[i].ipv4_gw);
+
+ dns = mm_bearer_ip_config_get_dns (ipv4);
+ g_assert (dns);
+ dnslen = g_strv_length ((gchar **) dns);
+ if (ipdpaddr_tests[i].ipv4_dns2 != NULL)
+ g_assert_cmpint (dnslen, ==, 2);
+ else
+ g_assert_cmpint (dnslen, ==, 1);
+ g_assert_cmpstr (dns[0], ==, ipdpaddr_tests[i].ipv4_dns1);
+ g_assert_cmpstr (dns[1], ==, ipdpaddr_tests[i].ipv4_dns2);
+ g_object_unref (ipv4);
+ } else
+ g_assert (ipv4 == NULL);
+
+ /* IPv6 */
+ if (ipdpaddr_tests[i].ipv6_addr || ipdpaddr_tests[i].ipv6_dns1) {
+ struct in6_addr a6;
+ g_assert (ipv6);
+
+ if (ipdpaddr_tests[i].ipv6_addr) {
+ g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv6), ==, ipdpaddr_tests[i].ipv6_addr);
+ g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv6), ==, 64);
+
+ g_assert (inet_pton (AF_INET6, mm_bearer_ip_config_get_address (ipv6), &a6));
+ if (IN6_IS_ADDR_LINKLOCAL (&a6))
+ g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_DHCP);
+ else
+ g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_STATIC);
+ } else
+ g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_DHCP);
+
+ dns = mm_bearer_ip_config_get_dns (ipv6);
+ g_assert (dns);
+ dnslen = g_strv_length ((gchar **) dns);
+ g_assert_cmpint (dnslen, ==, 1);
+ g_assert_cmpstr (dns[0], ==, ipdpaddr_tests[i].ipv6_dns1);
+ g_object_unref (ipv6);
+ } else
+ g_assert (ipv6 == NULL);
+ }
+}
+
+/*****************************************************************************/
+/* Test %IPDPCFG responses */
+
+static void
+test_ipdpcfg (void)
+{
+ MM3gppProfile *profile;
+ GList *profiles = NULL;
+ GList *l;
+ GError *error = NULL;
+ gboolean result;
+ gboolean cid_1_validated = FALSE;
+ gboolean cid_2_validated = FALSE;
+ gboolean cid_5_validated = FALSE;
+ const gchar *response =
+ "%IPDPCFG: 1,0,0,,,0\r\n"
+ "%IPDPCFG: 2,0,1,\"aaaa\",\"bbbbb\",0\r\n"
+ "%IPDPCFG: 5,0,2,\"user\",\"pass\",0"; /* last line without CRLF */
+
+ profile = mm_3gpp_profile_new ();
+ mm_3gpp_profile_set_profile_id (profile, 1);
+ mm_3gpp_profile_set_apn (profile, "internet");
+ mm_3gpp_profile_set_ip_type (profile, MM_BEARER_IP_FAMILY_IPV4);
+ profiles = g_list_append (profiles, profile);
+
+ profile = mm_3gpp_profile_new ();
+ mm_3gpp_profile_set_profile_id (profile, 2);
+ mm_3gpp_profile_set_apn (profile, "internet2");
+ mm_3gpp_profile_set_ip_type (profile, MM_BEARER_IP_FAMILY_IPV4V6);
+ profiles = g_list_append (profiles, profile);
+
+ profile = mm_3gpp_profile_new ();
+ mm_3gpp_profile_set_profile_id (profile, 5);
+ mm_3gpp_profile_set_apn (profile, "internet3");
+ mm_3gpp_profile_set_ip_type (profile, MM_BEARER_IP_FAMILY_IPV6);
+ profiles = g_list_append (profiles, profile);
+
+ result = mm_icera_parse_ipdpcfg_query_response (response, profiles, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ for (l = profiles; l; l = g_list_next (l)) {
+ MM3gppProfile *iter = l->data;
+
+ switch (mm_3gpp_profile_get_profile_id (iter)) {
+ case 1:
+ cid_1_validated = TRUE;
+ g_assert_cmpuint (mm_3gpp_profile_get_allowed_auth (iter), ==, MM_BEARER_ALLOWED_AUTH_NONE);
+ g_assert (!mm_3gpp_profile_get_user (iter));
+ g_assert (!mm_3gpp_profile_get_password (iter));
+ break;
+ case 2:
+ cid_2_validated = TRUE;
+ g_assert_cmpuint (mm_3gpp_profile_get_allowed_auth (iter), ==, MM_BEARER_ALLOWED_AUTH_PAP);
+ g_assert_cmpstr (mm_3gpp_profile_get_user (iter), ==, "aaaa");
+ g_assert_cmpstr (mm_3gpp_profile_get_password (iter), ==, "bbbbb");
+ break;
+ case 5:
+ cid_5_validated = TRUE;
+ g_assert_cmpuint (mm_3gpp_profile_get_allowed_auth (iter), ==, MM_BEARER_ALLOWED_AUTH_CHAP);
+ g_assert_cmpstr (mm_3gpp_profile_get_user (iter), ==, "user");
+ g_assert_cmpstr (mm_3gpp_profile_get_password (iter), ==, "pass");
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ }
+ g_assert (cid_1_validated);
+ g_assert (cid_2_validated);
+ g_assert (cid_5_validated);
+
+ g_list_free_full (profiles, (GDestroyNotify)g_object_unref);
+}
+
+/*****************************************************************************/
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/icera/ipdpaddr", test_ipdpaddr);
+ g_test_add_func ("/MM/icera/ipdpcfg", test_ipdpcfg);
+
+ return g_test_run ();
+}
diff --git a/src/plugins/intel/mm-broadband-modem-mbim-intel.c b/src/plugins/intel/mm-broadband-modem-mbim-intel.c
new file mode 100644
index 00000000..f6cf33db
--- /dev/null
+++ b/src/plugins/intel/mm-broadband-modem-mbim-intel.c
@@ -0,0 +1,144 @@
+/* -*- 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) 2021-2022 Intel Corporation
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-broadband-modem-mbim-intel.h"
+#include "mm-iface-modem-location.h"
+#include "mm-shared-xmm.h"
+
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void shared_xmm_init (MMSharedXmm *iface);
+
+static MMIfaceModemLocation *iface_modem_location_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimIntel, mm_broadband_modem_mbim_intel, MM_TYPE_BROADBAND_MODEM_MBIM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_XMM, shared_xmm_init))
+
+/*****************************************************************************/
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ MMPortSerialAt *ports[3];
+ guint i;
+
+ /* Run the shared XMM port setup logic */
+ mm_shared_xmm_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));
+
+ /* GNSS control port may or may not be a primary/secondary port */
+ ports[2] = mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self));
+ if (ports[2] && ((ports[2] == ports[0]) || (ports[2] == ports[1])))
+ ports[2] = NULL;
+
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ g_object_set (ports[i],
+ MM_PORT_SERIAL_SEND_DELAY, (guint64) 0,
+ NULL);
+ }
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemMbimIntel *
+mm_broadband_modem_mbim_intel_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_INTEL,
+ 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,
+ /* 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,
+ MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, TRUE,
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
+ MM_BROADBAND_MODEM_MBIM_QMI_UNSUPPORTED, TRUE,
+#endif
+ NULL);
+}
+
+static void
+mm_broadband_modem_mbim_intel_init (MMBroadbandModemMbimIntel *self)
+{
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = mm_shared_xmm_location_load_capabilities;
+ iface->load_capabilities_finish = mm_shared_xmm_location_load_capabilities_finish;
+ iface->enable_location_gathering = mm_shared_xmm_enable_location_gathering;
+ iface->enable_location_gathering_finish = mm_shared_xmm_enable_location_gathering_finish;
+ iface->disable_location_gathering = mm_shared_xmm_disable_location_gathering;
+ iface->disable_location_gathering_finish = mm_shared_xmm_disable_location_gathering_finish;
+ iface->load_supl_server = mm_shared_xmm_location_load_supl_server;
+ iface->load_supl_server_finish = mm_shared_xmm_location_load_supl_server_finish;
+ iface->set_supl_server = mm_shared_xmm_location_set_supl_server;
+ iface->set_supl_server_finish = mm_shared_xmm_location_set_supl_server_finish;
+}
+
+static MMBroadbandModemClass *
+peek_parent_broadband_modem_class (MMSharedXmm *self)
+{
+ return MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mbim_intel_parent_class);
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedXmm *self)
+{
+ return iface_modem_location_parent;
+}
+
+static void
+shared_xmm_init (MMSharedXmm *iface)
+{
+ iface->peek_parent_broadband_modem_class = peek_parent_broadband_modem_class;
+ iface->peek_parent_location_interface = peek_parent_location_interface;
+}
+
+static void
+mm_broadband_modem_mbim_intel_class_init (MMBroadbandModemMbimIntelClass *klass)
+{
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/intel/mm-broadband-modem-mbim-intel.h b/src/plugins/intel/mm-broadband-modem-mbim-intel.h
new file mode 100644
index 00000000..549f179d
--- /dev/null
+++ b/src/plugins/intel/mm-broadband-modem-mbim-intel.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) 2021-2022 Intel Corporation
+ */
+
+#ifndef MM_BROADBAND_MODEM_MBIM_INTEL_H
+#define MM_BROADBAND_MODEM_MBIM_INTEL_H
+
+#include "mm-broadband-modem-mbim.h"
+
+#define MM_TYPE_BROADBAND_MODEM_MBIM_INTEL (mm_broadband_modem_mbim_intel_get_type ())
+#define MM_BROADBAND_MODEM_MBIM_INTEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_INTEL, MMBroadbandModemMbimIntel))
+#define MM_BROADBAND_MODEM_MBIM_INTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_INTEL, MMBroadbandModemMbimIntelClass))
+#define MM_IS_BROADBAND_MODEM_MBIM_INTEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_INTEL))
+#define MM_IS_BROADBAND_MODEM_MBIM_INTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_INTEL))
+#define MM_BROADBAND_MODEM_MBIM_INTEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_INTEL, MMBroadbandModemMbimIntelClass))
+
+typedef struct _MMBroadbandModemMbimIntel MMBroadbandModemMbimIntel;
+typedef struct _MMBroadbandModemMbimIntelClass MMBroadbandModemMbimIntelClass;
+
+struct _MMBroadbandModemMbimIntel {
+ MMBroadbandModemMbim parent;
+};
+
+struct _MMBroadbandModemMbimIntelClass{
+ MMBroadbandModemMbimClass parent;
+};
+
+GType mm_broadband_modem_mbim_intel_get_type (void);
+
+MMBroadbandModemMbimIntel *mm_broadband_modem_mbim_intel_new (const gchar *device,
+ const gchar **driver,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_MBIM_INTEL_H */
diff --git a/src/plugins/intel/mm-plugin-intel.c b/src/plugins/intel/mm-plugin-intel.c
new file mode 100644
index 00000000..d83edfba
--- /dev/null
+++ b/src/plugins/intel/mm-plugin-intel.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) 2021-2022 Intel Corporation
+ */
+
+#include <stdio.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-intel.h"
+#include "mm-log-object.h"
+#include "mm-broadband-modem.h"
+
+#if defined WITH_MBIM
+#include "mm-broadband-modem-mbim-intel.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginIntel, mm_plugin_intel, 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_MBIM
+ if (mm_port_probe_list_has_mbim_port (probes)) {
+ mm_obj_dbg (self, "MBIM-powered Intel modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_intel_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ mm_obj_dbg (self, "Generic Intel modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "net", "wwan", NULL };
+ static const guint16 vendor_ids[] = { 0x8086, 0 };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_INTEL,
+ MM_PLUGIN_NAME, "Intel",
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_MBIM, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_intel_init (MMPluginIntel *self)
+{
+ /*nothing to be done here, but required for creating intel plugin instance*/
+}
+
+static void
+mm_plugin_intel_class_init (MMPluginIntelClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/intel/mm-plugin-intel.h b/src/plugins/intel/mm-plugin-intel.h
new file mode 100644
index 00000000..3d2880b2
--- /dev/null
+++ b/src/plugins/intel/mm-plugin-intel.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) 2021-2022 Intel Corporation
+ */
+
+#ifndef MM_PLUGIN_INTEL_H
+#define MM_PLUGIN_INTEL_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_INTEL (mm_plugin_intel_get_type ())
+#define MM_PLUGIN_INTEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_INTEL, MMPluginIntel))
+#define MM_PLUGIN_INTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_INTEL, MMPluginIntelClass))
+#define MM_IS_PLUGIN_INTEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_INTEL))
+#define MM_IS_PLUGIN_INTEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_INTEL))
+#define MM_PLUGIN_INTEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_INTEL, MMPluginIntelClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginIntel;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginIntelClass;
+
+GType mm_plugin_intel_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_INTEL_H */
diff --git a/src/plugins/iridium/mm-bearer-iridium.c b/src/plugins/iridium/mm-bearer-iridium.c
new file mode 100644
index 00000000..52e8ada9
--- /dev/null
+++ b/src/plugins/iridium/mm-bearer-iridium.c
@@ -0,0 +1,266 @@
+/* -*- 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) 2012 Ammonit Measurement GmbH
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-bearer-iridium.h"
+#include "mm-base-modem-at.h"
+
+/* Allow up to 200s to get a proper IP connection */
+#define BEARER_IRIDIUM_IP_TIMEOUT_DEFAULT 200
+
+G_DEFINE_TYPE (MMBearerIridium, mm_bearer_iridium, MM_TYPE_BASE_BEARER)
+
+/*****************************************************************************/
+/* Connect */
+
+typedef struct {
+ MMPortSerialAt *primary;
+ GError *saved_error;
+} ConnectContext;
+
+static void
+connect_context_free (ConnectContext *ctx)
+{
+ if (ctx->saved_error)
+ g_error_free (ctx->saved_error);
+ if (ctx->primary)
+ g_object_unref (ctx->primary);
+ g_free (ctx);
+}
+
+static MMBearerConnectResult *
+connect_finish (MMBaseBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+connect_report_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ ConnectContext *ctx;
+ const gchar *result;
+
+ /* If cancelled, complete */
+ if (g_task_return_error_if_cancelled (task)) {
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ /* If we got a proper extended reply, build the new error to be set */
+ result = mm_base_modem_at_command_full_finish (modem, res, NULL);
+ if (result && g_str_has_prefix (result, "+CEER: ") && strlen (result) > 7) {
+ g_task_return_new_error (task,
+ ctx->saved_error->domain,
+ ctx->saved_error->code,
+ "%s", &result[7]);
+ } else {
+ /* Otherwise, take the original error as it was */
+ g_task_return_error (task, ctx->saved_error);
+ ctx->saved_error = NULL;
+ }
+ g_object_unref (task);
+}
+
+static void
+dial_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ ConnectContext *ctx;
+ MMBearerIpConfig *config;
+
+ ctx = g_task_get_task_data (task);
+
+ /* DO NOT check for cancellable here. If we got here without errors, the
+ * bearer is really connected and therefore we need to reflect that in
+ * the state machine. */
+ mm_base_modem_at_command_full_finish (modem, res, &(ctx->saved_error));
+ if (ctx->saved_error) {
+ /* Try to get more information why it failed */
+ mm_base_modem_at_command_full (
+ modem,
+ ctx->primary,
+ "+CEER",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)connect_report_ready,
+ task);
+ return;
+ }
+
+ /* Port is connected; update the state */
+ mm_port_set_connected (MM_PORT (ctx->primary), TRUE);
+
+ /* Build IP config; always PPP based */
+ config = mm_bearer_ip_config_new ();
+ mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_PPP);
+
+ /* Return operation result */
+ g_task_return_pointer (
+ task,
+ mm_bearer_connect_result_new (MM_PORT (ctx->primary), config, NULL),
+ (GDestroyNotify)mm_bearer_connect_result_unref);
+ g_object_unref (task);
+ g_object_unref (config);
+}
+
+static void
+service_type_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ ConnectContext *ctx;
+ GError *error = NULL;
+
+ /* If cancelled, complete */
+ if (g_task_return_error_if_cancelled (task)) {
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ /* Errors setting the service type will be critical */
+ mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* We just use the default number to dial in the Iridium network. Also note
+ * that we won't specify a specific port to use; Iridium modems only expose
+ * one. */
+ mm_base_modem_at_command_full (
+ modem,
+ ctx->primary,
+ "ATDT008816000025",
+ MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)dial_ready,
+ task);
+}
+
+static void
+connect (MMBaseBearer *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ConnectContext *ctx;
+ GTask *task;
+ MMBaseModem *modem = NULL;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ if (mm_bearer_properties_get_multiplex (mm_base_bearer_peek_config (self)) == MM_BEARER_MULTIPLEX_SUPPORT_REQUIRED) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Multiplex support not available");
+ g_object_unref (task);
+ return;
+ }
+
+ g_object_get (self,
+ MM_BASE_BEARER_MODEM, &modem,
+ NULL);
+ g_assert (modem);
+
+ /* Don't bother to get primary and check if connected and all that; we
+ * already do this check when sending the ATDT call */
+
+ /* In this context, we only keep the stuff we'll need later */
+ ctx = g_new0 (ConnectContext, 1);
+ ctx->primary = mm_base_modem_get_port_primary (modem);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) connect_context_free);
+
+ /* Bearer service type set to 9600bps (V.110), which behaves better than the
+ * default 9600bps (V.32). */
+ mm_base_modem_at_command_full (
+ modem,
+ ctx->primary,
+ "+CBST=71,0,1",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)service_type_ready,
+ task);
+
+ g_object_unref (modem);
+}
+
+/*****************************************************************************/
+
+MMBaseBearer *
+mm_bearer_iridium_new (MMBroadbandModemIridium *modem,
+ MMBearerProperties *config)
+{
+ MMBaseBearer *bearer;
+
+ /* The Iridium bearer inherits from MMBaseBearer (so it's not a MMBroadbandBearer)
+ * and that means that the object is not async-initable, so we just use
+ * g_object_get() here */
+ bearer = g_object_new (MM_TYPE_BEARER_IRIDIUM,
+ MM_BASE_BEARER_MODEM, modem,
+ MM_BASE_BEARER_CONFIG, config,
+ "ip-timeout", BEARER_IRIDIUM_IP_TIMEOUT_DEFAULT,
+ NULL);
+
+ /* Only export valid bearers */
+ mm_base_bearer_export (bearer);
+
+ return bearer;
+}
+
+static void
+mm_bearer_iridium_init (MMBearerIridium *self)
+{
+}
+
+static void
+mm_bearer_iridium_class_init (MMBearerIridiumClass *klass)
+{
+ MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);
+
+ /* Virtual methods */
+ base_bearer_class->connect = connect;
+ base_bearer_class->connect_finish = connect_finish;
+ base_bearer_class->load_connection_status = NULL;
+ base_bearer_class->load_connection_status_finish = NULL;
+#if defined WITH_SUSPEND_RESUME
+ base_bearer_class->reload_connection_status = NULL;
+ base_bearer_class->reload_connection_status_finish = NULL;
+#endif
+}
diff --git a/src/plugins/iridium/mm-bearer-iridium.h b/src/plugins/iridium/mm-bearer-iridium.h
new file mode 100644
index 00000000..eba6ac54
--- /dev/null
+++ b/src/plugins/iridium/mm-bearer-iridium.h
@@ -0,0 +1,55 @@
+/* -*- 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:
+ *
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ *
+ * Copyright (C) 2012 Ammonit Measurement GmbH.
+ */
+
+#ifndef MM_BEARER_IRIDIUM_H
+#define MM_BEARER_IRIDIUM_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-base-bearer.h"
+#include "mm-broadband-modem-iridium.h"
+
+#define MM_TYPE_BEARER_IRIDIUM (mm_bearer_iridium_get_type ())
+#define MM_BEARER_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BEARER_IRIDIUM, MMBearerIridium))
+#define MM_BEARER_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BEARER_IRIDIUM, MMBearerIridiumClass))
+#define MM_IS_BEARER_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BEARER_IRIDIUM))
+#define MM_IS_BEARER_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BEARER_IRIDIUM))
+#define MM_BEARER_IRIDIUM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BEARER_IRIDIUM, MMBearerIridiumClass))
+
+typedef struct _MMBearerIridium MMBearerIridium;
+typedef struct _MMBearerIridiumClass MMBearerIridiumClass;
+
+struct _MMBearerIridium {
+ MMBaseBearer parent;
+};
+
+struct _MMBearerIridiumClass {
+ MMBaseBearerClass parent;
+};
+
+GType mm_bearer_iridium_get_type (void);
+
+/* Iridium bearer creation implementation.
+ * NOTE it is *not* a broadband bearer, so not async-initable */
+MMBaseBearer *mm_bearer_iridium_new (MMBroadbandModemIridium *modem,
+ MMBearerProperties *config);
+
+#endif /* MM_BEARER_IRIDIUM_H */
diff --git a/src/plugins/iridium/mm-broadband-modem-iridium.c b/src/plugins/iridium/mm-broadband-modem-iridium.c
new file mode 100644
index 00000000..681d9123
--- /dev/null
+++ b/src/plugins/iridium/mm-broadband-modem-iridium.c
@@ -0,0 +1,433 @@
+/* -*- 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) 2011 - 2012 Ammonit Measurement GmbH
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log-object.h"
+#include "mm-errors-types.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-messaging.h"
+#include "mm-broadband-modem-iridium.h"
+#include "mm-sim-iridium.h"
+#include "mm-bearer-iridium.h"
+#include "mm-modem-helpers.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void iface_modem_messaging_init (MMIfaceModemMessaging *iface);
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemIridium, mm_broadband_modem_iridium, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init))
+
+/*****************************************************************************/
+/* Operator Code loading (3GPP interface) */
+
+static gchar *
+load_operator_code_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_operator_code (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ /* Only "90103" operator code is assumed */
+ g_task_return_pointer (task, g_strdup ("90103"), g_free);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Operator Name loading (3GPP interface) */
+
+static gchar *
+load_operator_name_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_operator_name (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ /* Only "IRIDIUM" operator name is assumed */
+ g_task_return_pointer (task, g_strdup ("IRIDIUM"), g_free);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Enable unsolicited events (SMS indications) (Messaging interface) */
+
+static gboolean
+messaging_enable_unsolicited_events_finish (MMIfaceModemMessaging *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+messaging_enable_unsolicited_events (MMIfaceModemMessaging *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* AT+CNMI=<mode>,[<mt>[,<bm>[,<ds>[,<bfr>]]]]
+ * but <bm> can only be 0,
+ * and <ds> can only be either 0 or 1
+ *
+ * Note: Modem may return +CMS ERROR:322, which indicates Memory Full,
+ * not a big deal
+ */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CNMI=2,1,0,0,1",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Signal quality (Modem interface) */
+
+static guint
+load_signal_quality_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ gint quality = 0;
+ const gchar *result;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!result)
+ return 0;
+
+ /* Skip possible whitespaces after '+CSQF:' and before the response */
+ result = mm_strip_tag (result, "+CSQF:");
+ while (*result == ' ')
+ result++;
+
+ if (sscanf (result, "%d", &quality))
+ /* Normalize the quality. <rssi> is NOT given in dBs,
+ * given as a relative value between 0 and 5 */
+ quality = CLAMP (quality, 0, 5) * 100 / 5;
+ else
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Could not parse signal quality results");
+
+ return quality;
+}
+
+static void
+load_signal_quality (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* The iridium modem may have a huge delay to get signal quality if we pass
+ * AT+CSQ, so we'll default to use AT+CSQF, which is a fast version that
+ * returns right away the last signal quality value retrieved */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CSQF",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Flow control (Modem interface) */
+
+static gboolean
+setup_flow_control_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+setup_flow_control_ready (MMBroadbandModemIridium *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
+ /* Let the error be critical. We DO need RTS/CTS in order to have
+ * proper modem disabling. */
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+setup_flow_control (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Enable RTS/CTS flow control.
+ * Other available values:
+ * AT&K0: Disable flow control
+ * AT&K3: RTS/CTS
+ * AT&K4: XOFF/XON
+ * AT&K6: Both RTS/CTS and XOFF/XON
+ */
+ g_object_set (self, MM_BROADBAND_MODEM_FLOW_CONTROL, MM_FLOW_CONTROL_RTS_CTS, NULL);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "&K3",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)setup_flow_control_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Load supported modes (Modem inteface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GArray *combinations;
+ MMModemModeCombination mode;
+ GTask *task;
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
+
+ /* Report CS only, Iridium connections are circuit-switched */
+ mode.allowed = MM_MODEM_MODE_CS;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_return_pointer (task, combinations, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Create SIM (Modem inteface) */
+
+static MMBaseSim *
+create_sim_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return mm_sim_iridium_new_finish (res, error);
+}
+
+static void
+create_sim (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* New Iridium SIM */
+ mm_sim_iridium_new (MM_BASE_MODEM (self),
+ NULL, /* cancellable */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Create Bearer (Modem interface) */
+
+static MMBaseBearer *
+create_bearer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+create_bearer (MMIfaceModem *self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBaseBearer *bearer;
+ GTask *task;
+
+ mm_obj_dbg (self, "creating Iridium bearer...");
+ bearer = mm_bearer_iridium_new (MM_BROADBAND_MODEM_IRIDIUM (self),
+ properties);
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_return_pointer (task, bearer, g_object_unref);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+
+static const gchar *primary_init_sequence[] = {
+ /* Disable echo */
+ "E0",
+ /* Get word responses */
+ "V1",
+ /* Extended numeric codes */
+ "+CMEE=1",
+ NULL
+};
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ MMPortSerialAt *primary;
+
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_iridium_parent_class)->setup_ports (self);
+
+ /* Set 9600 baudrate by default in the AT port */
+ mm_obj_dbg (self, "baudrate will be set to 9600 bps...");
+ primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ if (!primary)
+ return;
+
+ g_object_set (G_OBJECT (primary),
+ MM_PORT_SERIAL_BAUD, 9600,
+ MM_PORT_SERIAL_AT_INIT_SEQUENCE, primary_init_sequence,
+ NULL);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemIridium *
+mm_broadband_modem_iridium_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_IRIDIUM,
+ 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,
+ /* Iridium bearer supports TTY only */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+ /* Allow only up to 3 consecutive timeouts in the serial port */
+ MM_BASE_MODEM_MAX_TIMEOUTS, 3,
+ /* Only CS network is supported by the Iridium modem */
+ MM_IFACE_MODEM_3GPP_PS_NETWORK_SUPPORTED, FALSE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_iridium_init (MMBroadbandModemIridium *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ /* Create Iridium-specific SIM and bearer*/
+ iface->create_sim = create_sim;
+ iface->create_sim_finish = create_sim_finish;
+ iface->create_bearer = create_bearer;
+ iface->create_bearer_finish = create_bearer_finish;
+
+ /* CSQF-based signal quality */
+ iface->load_signal_quality = load_signal_quality;
+ iface->load_signal_quality_finish = load_signal_quality_finish;
+
+ /* RTS/CTS flow control */
+ iface->setup_flow_control = setup_flow_control;
+ iface->setup_flow_control_finish = setup_flow_control_finish;
+
+ /* No need to power-up/power-down the modem */
+ iface->load_power_state = NULL;
+ iface->load_power_state_finish = NULL;
+ iface->modem_power_up = NULL;
+ iface->modem_power_up_finish = NULL;
+ iface->modem_power_down = NULL;
+ iface->modem_power_down_finish = NULL;
+
+ /* Supported modes cannot be queried */
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ /* Fixed operator code and name to be reported */
+ iface->load_operator_name = load_operator_name;
+ iface->load_operator_name_finish = load_operator_name_finish;
+ iface->load_operator_code = load_operator_code;
+ iface->load_operator_code_finish = load_operator_code_finish;
+
+ /* Don't try to scan networks with AT+COPS=?.
+ * It does work, but it will only reply about the Iridium network
+ * being found (so not very helpful, as that is the only one expected), but
+ * also, it will use a non-standard reply format. Instead of supporting that
+ * specific format used, just fully skip it.
+ * For reference, the result is:
+ * +COPS:(002),"IRIDIUM","IRIDIUM","90103",,(000-001),(000-002)
+ */
+ iface->scan_networks = NULL;
+ iface->scan_networks_finish = NULL;
+}
+
+static void
+iface_modem_messaging_init (MMIfaceModemMessaging *iface)
+{
+ iface->enable_unsolicited_events = messaging_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = messaging_enable_unsolicited_events_finish;
+}
+
+static void
+mm_broadband_modem_iridium_class_init (MMBroadbandModemIridiumClass *klass)
+{
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/iridium/mm-broadband-modem-iridium.h b/src/plugins/iridium/mm-broadband-modem-iridium.h
new file mode 100644
index 00000000..b9a1270b
--- /dev/null
+++ b/src/plugins/iridium/mm-broadband-modem-iridium.h
@@ -0,0 +1,49 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2011 Red Hat, Inc.
+ * Copyright (C) 2011 Google Inc.
+ */
+
+#ifndef MM_BROADBAND_MODEM_IRIDIUM_H
+#define MM_BROADBAND_MODEM_IRIDIUM_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_IRIDIUM (mm_broadband_modem_iridium_get_type ())
+#define MM_BROADBAND_MODEM_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_IRIDIUM, MMBroadbandModemIridium))
+#define MM_BROADBAND_MODEM_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_IRIDIUM, MMBroadbandModemIridiumClass))
+#define MM_IS_BROADBAND_MODEM_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_IRIDIUM))
+#define MM_IS_BROADBAND_MODEM_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_IRIDIUM))
+#define MM_BROADBAND_MODEM_IRIDIUM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_IRIDIUM, MMBroadbandModemIridiumClass))
+
+typedef struct _MMBroadbandModemIridium MMBroadbandModemIridium;
+typedef struct _MMBroadbandModemIridiumClass MMBroadbandModemIridiumClass;
+
+struct _MMBroadbandModemIridium {
+ MMBroadbandModem parent;
+};
+
+struct _MMBroadbandModemIridiumClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_iridium_get_type (void);
+
+MMBroadbandModemIridium *mm_broadband_modem_iridium_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_IRIDIUM_H */
diff --git a/src/plugins/iridium/mm-plugin-iridium.c b/src/plugins/iridium/mm-plugin-iridium.c
new file mode 100644
index 00000000..741847f8
--- /dev/null
+++ b/src/plugins/iridium/mm-plugin-iridium.c
@@ -0,0 +1,89 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2011 - 2012 Ammonit Measurement GmbH
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-iridium.h"
+#include "mm-broadband-modem-iridium.h"
+#include "mm-private-boxed-types.h"
+
+G_DEFINE_TYPE (MMPluginIridium, mm_plugin_iridium, 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)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_iridium_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", NULL };
+ static const guint16 vendor_ids[] = { 0x1edd, 0 };
+ static const gchar *vendor_strings[] = { "iridium", NULL };
+ /* Also support motorola-branded Iridium modems */
+ static const mm_str_pair product_strings[] = {{(gchar *)"motorola", (gchar *)"satellite" },
+ { NULL, NULL }};
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_IRIDIUM,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_STRINGS, vendor_strings,
+ MM_PLUGIN_ALLOWED_PRODUCT_STRINGS, product_strings,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_iridium_init (MMPluginIridium *self)
+{
+}
+
+static void
+mm_plugin_iridium_class_init (MMPluginIridiumClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/iridium/mm-plugin-iridium.h b/src/plugins/iridium/mm-plugin-iridium.h
new file mode 100644
index 00000000..b729ce98
--- /dev/null
+++ b/src/plugins/iridium/mm-plugin-iridium.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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2011 - 2012 Ammonit Measurement GmbH
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#ifndef MM_PLUGIN_IRIDIUM_H
+#define MM_PLUGIN_IRIDIUM_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_IRIDIUM (mm_plugin_iridium_get_type ())
+#define MM_PLUGIN_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_IRIDIUM, MMPluginIridium))
+#define MM_PLUGIN_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_IRIDIUM, MMPluginIridiumClass))
+#define MM_IS_PLUGIN_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_IRIDIUM))
+#define MM_IS_PLUGIN_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_IRIDIUM))
+#define MM_PLUGIN_IRIDIUM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_IRIDIUM, MMPluginIridiumClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginIridium;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginIridiumClass;
+
+GType mm_plugin_iridium_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_IRIDIUM_H */
diff --git a/src/plugins/iridium/mm-sim-iridium.c b/src/plugins/iridium/mm-sim-iridium.c
new file mode 100644
index 00000000..3495039b
--- /dev/null
+++ b/src/plugins/iridium/mm-sim-iridium.c
@@ -0,0 +1,95 @@
+/* -*- 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) 2011 - 2012 Ammonit Measurement GmbH.
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-sim-iridium.h"
+
+G_DEFINE_TYPE (MMSimIridium, mm_sim_iridium, MM_TYPE_BASE_SIM)
+
+/*****************************************************************************/
+
+MMBaseSim *
+mm_sim_iridium_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *source;
+ GObject *sim;
+
+ source = g_async_result_get_source_object (res);
+ sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!sim)
+ return NULL;
+
+ /* Only export valid SIMs */
+ mm_base_sim_export (MM_BASE_SIM (sim));
+
+ return MM_BASE_SIM (sim);
+}
+
+void
+mm_sim_iridium_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (MM_TYPE_SIM_IRIDIUM,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_SIM_MODEM, modem,
+ "active", TRUE, /* by default always active */
+ NULL);
+}
+
+static void
+mm_sim_iridium_init (MMSimIridium *self)
+{
+}
+
+static void
+mm_sim_iridium_class_init (MMSimIridiumClass *klass)
+{
+ MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass);
+
+ /* Skip querying the SIM card info, not supported by Iridium modems */
+ base_sim_class->load_sim_identifier = NULL;
+ base_sim_class->load_sim_identifier_finish = NULL;
+ base_sim_class->load_imsi = NULL;
+ base_sim_class->load_imsi_finish = NULL;
+ base_sim_class->load_operator_identifier = NULL;
+ base_sim_class->load_operator_identifier_finish = NULL;
+ base_sim_class->load_operator_name = NULL;
+ base_sim_class->load_operator_name_finish = NULL;
+
+ /* Skip managing preferred networks, not applicable to Iridium modems */
+ base_sim_class->load_preferred_networks = NULL;
+ base_sim_class->load_preferred_networks_finish = NULL;
+ base_sim_class->set_preferred_networks = NULL;
+ base_sim_class->set_preferred_networks_finish = NULL;
+}
diff --git a/src/plugins/iridium/mm-sim-iridium.h b/src/plugins/iridium/mm-sim-iridium.h
new file mode 100644
index 00000000..2f3e2916
--- /dev/null
+++ b/src/plugins/iridium/mm-sim-iridium.h
@@ -0,0 +1,52 @@
+/* -*- 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) 2011 - 2012 Ammonit Measurement GmbH.
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#ifndef MM_SIM_IRIDIUM_H
+#define MM_SIM_IRIDIUM_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "mm-base-sim.h"
+
+#define MM_TYPE_SIM_IRIDIUM (mm_sim_iridium_get_type ())
+#define MM_SIM_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_IRIDIUM, MMSimIridium))
+#define MM_SIM_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_IRIDIUM, MMSimIridiumClass))
+#define MM_IS_SIM_IRIDIUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_IRIDIUM))
+#define MM_IS_SIM_IRIDIUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_IRIDIUM))
+#define MM_SIM_IRIDIUM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_IRIDIUM, MMSimIridiumClass))
+
+typedef struct _MMSimIridium MMSimIridium;
+typedef struct _MMSimIridiumClass MMSimIridiumClass;
+
+struct _MMSimIridium {
+ MMBaseSim parent;
+};
+
+struct _MMSimIridiumClass {
+ MMBaseSimClass parent;
+};
+
+GType mm_sim_iridium_get_type (void);
+
+void mm_sim_iridium_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseSim *mm_sim_iridium_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SIM_IRIDIUM_H */
diff --git a/src/plugins/linktop/77-mm-linktop-port-types.rules b/src/plugins/linktop/77-mm-linktop-port-types.rules
new file mode 100644
index 00000000..dc2ef0d6
--- /dev/null
+++ b/src/plugins/linktop/77-mm-linktop-port-types.rules
@@ -0,0 +1,16 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_linktop_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="230d", GOTO="mm_linktop_generic"
+GOTO="mm_linktop_end"
+
+LABEL="mm_linktop_generic"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Linktop HSPADataCard
+# ttyACM0 (if #1): Data port
+# ttyACM1 (if #3): Primary AT port
+ATTRS{idVendor}=="230d", ATTRS{idProduct}=="0001", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PPP}="1"
+ATTRS{idVendor}=="230d", ATTRS{idProduct}=="0001", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+LABEL="mm_linktop_end"
diff --git a/src/plugins/linktop/mm-broadband-modem-linktop.c b/src/plugins/linktop/mm-broadband-modem-linktop.c
new file mode 100644
index 00000000..a83682c8
--- /dev/null
+++ b/src/plugins/linktop/mm-broadband-modem-linktop.c
@@ -0,0 +1,269 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "ModemManager.h"
+#include "mm-serial-parsers.h"
+#include "mm-modem-helpers.h"
+#include "mm-iface-modem.h"
+#include "mm-base-modem-at.h"
+#include "mm-broadband-modem-linktop.h"
+#include "mm-modem-helpers-linktop.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+
+static MMIfaceModem *iface_modem_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemLinktop, mm_broadband_modem_linktop, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init))
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_load_supported_modes_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ GArray *all;
+ GArray *combinations;
+ GArray *filtered;
+ MMModemModeCombination mode;
+
+ all = iface_modem_parent->load_supported_modes_finish (self, res, &error);
+ if (!all) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3);
+
+ /* 2G only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 3G only */
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ /* Filter out those unsupported modes */
+ filtered = mm_filter_supported_modes (all, combinations, self);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Run parent's loading */
+ iface_modem_parent->load_supported_modes (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_supported_modes_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Load initial allowed/preferred modes (Modem interface) */
+
+static gboolean
+load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response || !mm_linktop_parse_cfun_query_current_modes (response, allowed, error))
+ return FALSE;
+
+ /* None preferred always */
+ *preferred = MM_MODEM_MODE_NONE;
+
+ return TRUE;
+}
+
+static void
+load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Set allowed modes (Modem interface) */
+
+static gboolean
+set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+allowed_mode_update_ready (MMBroadbandModemLinktop *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error)
+ /* Let the error be critical. */
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *command;
+ gint linktop_mode = -1;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (allowed == MM_MODEM_MODE_2G)
+ linktop_mode = LINKTOP_MODE_2G;
+ else if (allowed == MM_MODEM_MODE_3G)
+ linktop_mode = LINKTOP_MODE_3G;
+ else if ((allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) &&
+ (preferred == MM_MODEM_MODE_NONE))
+ linktop_mode = LINKTOP_MODE_ANY;
+ else if ((allowed == MM_MODEM_MODE_ANY &&
+ preferred == MM_MODEM_MODE_NONE))
+ linktop_mode = LINKTOP_MODE_ANY;
+
+ if (linktop_mode < 0) {
+ gchar *allowed_str;
+ gchar *preferred_str;
+
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Requested mode (allowed: '%s', preferred: '%s') not "
+ "supported by the modem.",
+ allowed_str,
+ preferred_str);
+ g_object_unref (task);
+ g_free (allowed_str);
+ g_free (preferred_str);
+ return;
+ }
+
+ command = g_strdup_printf ("AT+CFUN=%d", linktop_mode);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)allowed_mode_update_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemLinktop *
+mm_broadband_modem_linktop_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_LINKTOP,
+ 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 AT only */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_linktop_init (MMBroadbandModemLinktop *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = load_current_modes;
+ iface->load_current_modes_finish = load_current_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = set_current_modes_finish;
+}
+
+static void
+mm_broadband_modem_linktop_class_init (MMBroadbandModemLinktopClass *klass)
+{
+}
diff --git a/src/plugins/linktop/mm-broadband-modem-linktop.h b/src/plugins/linktop/mm-broadband-modem-linktop.h
new file mode 100644
index 00000000..385a20b8
--- /dev/null
+++ b/src/plugins/linktop/mm-broadband-modem-linktop.h
@@ -0,0 +1,49 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_MODEM_LINKTOP_H
+#define MM_BROADBAND_MODEM_LINKTOP_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_LINKTOP (mm_broadband_modem_linktop_get_type ())
+#define MM_BROADBAND_MODEM_LINKTOP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_LINKTOP, MMBroadbandModemLinktop))
+#define MM_BROADBAND_MODEM_LINKTOP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_LINKTOP, MMBroadbandModemLinktopClass))
+#define MM_IS_BROADBAND_MODEM_LINKTOP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_LINKTOP))
+#define MM_IS_BROADBAND_MODEM_LINKTOP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_LINKTOP))
+#define MM_BROADBAND_MODEM_LINKTOP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_LINKTOP, MMBroadbandModemLinktopClass))
+
+typedef struct _MMBroadbandModemLinktop MMBroadbandModemLinktop;
+typedef struct _MMBroadbandModemLinktopClass MMBroadbandModemLinktopClass;
+
+struct _MMBroadbandModemLinktop {
+ MMBroadbandModem parent;
+};
+
+struct _MMBroadbandModemLinktopClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_linktop_get_type (void);
+
+MMBroadbandModemLinktop *mm_broadband_modem_linktop_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_LINKTOP_H */
diff --git a/src/plugins/linktop/mm-modem-helpers-linktop.c b/src/plugins/linktop/mm-modem-helpers-linktop.c
new file mode 100644
index 00000000..2ca46bb6
--- /dev/null
+++ b/src/plugins/linktop/mm-modem-helpers-linktop.c
@@ -0,0 +1,54 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2016 Red Hat, Inc.
+ * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-linktop.h"
+
+/*****************************************************************************/
+
+gboolean
+mm_linktop_parse_cfun_query_current_modes (const gchar *response,
+ MMModemMode *allowed,
+ GError **error)
+{
+ guint state;
+
+ g_assert (allowed);
+
+ if (!mm_3gpp_parse_cfun_query_response (response, &state, error))
+ return FALSE;
+
+ switch (state) {
+ case LINKTOP_MODE_OFFLINE:
+ case LINKTOP_MODE_LOW_POWER:
+ *allowed = MM_MODEM_MODE_NONE;
+ return TRUE;
+ case LINKTOP_MODE_2G:
+ *allowed = MM_MODEM_MODE_2G;
+ return TRUE;
+ case LINKTOP_MODE_3G:
+ *allowed = MM_MODEM_MODE_3G;
+ return TRUE;
+ case LINKTOP_MODE_ANY:
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ return TRUE;
+ default:
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown linktop +CFUN current mode: %u", state);
+ return FALSE;
+ }
+}
diff --git a/src/plugins/linktop/mm-modem-helpers-linktop.h b/src/plugins/linktop/mm-modem-helpers-linktop.h
new file mode 100644
index 00000000..69fa7ee2
--- /dev/null
+++ b/src/plugins/linktop/mm-modem-helpers-linktop.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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2016 Red Hat, Inc.
+ * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_MODEM_HELPERS_LINKTOP_H
+#define MM_MODEM_HELPERS_LINKTOP_H
+
+#include <glib.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+typedef enum {
+ LINKTOP_MODE_OFFLINE = 0,
+ LINKTOP_MODE_ANY = 1,
+ LINKTOP_MODE_LOW_POWER = 4,
+ LINKTOP_MODE_2G = 5,
+ LINKTOP_MODE_3G = 6,
+} MMLinktopMode;
+
+/* AT+CFUN? response parsers */
+gboolean mm_linktop_parse_cfun_query_current_modes (const gchar *response,
+ MMModemMode *allowed,
+ GError **error);
+
+#endif /* MM_MODEM_HELPERS_LINKTOP_H */
diff --git a/src/plugins/linktop/mm-plugin-linktop.c b/src/plugins/linktop/mm-plugin-linktop.c
new file mode 100644
index 00000000..8276e59f
--- /dev/null
+++ b/src/plugins/linktop/mm-plugin-linktop.c
@@ -0,0 +1,79 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-linktop.h"
+#include "mm-broadband-modem-linktop.h"
+
+G_DEFINE_TYPE (MMPluginLinktop, mm_plugin_linktop, 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)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_linktop_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", NULL };
+ static const guint16 vendor_ids[] = { 0x230d, 0 };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_LINKTOP,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_linktop_init (MMPluginLinktop *self)
+{
+}
+
+static void
+mm_plugin_linktop_class_init (MMPluginLinktopClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/linktop/mm-plugin-linktop.h b/src/plugins/linktop/mm-plugin-linktop.h
new file mode 100644
index 00000000..6c8e5789
--- /dev/null
+++ b/src/plugins/linktop/mm-plugin-linktop.h
@@ -0,0 +1,42 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_PLUGIN_LINKTOP_H
+#define MM_PLUGIN_LINKTOP_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_LINKTOP (mm_plugin_linktop_get_type ())
+#define MM_PLUGIN_LINKTOP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_LINKTOP, MMPluginLinktop))
+#define MM_PLUGIN_LINKTOP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_LINKTOP, MMPluginLinktopClass))
+#define MM_IS_PLUGIN_LINKTOP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_LINKTOP))
+#define MM_IS_PLUGIN_LINKTOP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_LINKTOP))
+#define MM_PLUGIN_LINKTOP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_LINKTOP, MMPluginLinktopClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginLinktop;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginLinktopClass;
+
+GType mm_plugin_linktop_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_LINKTOP_H */
diff --git a/src/plugins/linktop/tests/test-modem-helpers-linktop.c b/src/plugins/linktop/tests/test-modem-helpers-linktop.c
new file mode 100644
index 00000000..07aa8378
--- /dev/null
+++ b/src/plugins/linktop/tests/test-modem-helpers-linktop.c
@@ -0,0 +1,71 @@
+/* -*- 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) 2016 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 "mm-log-test.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-linktop.h"
+
+/*****************************************************************************/
+
+typedef struct {
+ const gchar *str;
+ MMModemMode allowed;
+} CfunQueryCurrentModeTest;
+
+static const CfunQueryCurrentModeTest cfun_query_current_mode_tests[] = {
+ { "+CFUN: 0", MM_MODEM_MODE_NONE },
+ { "+CFUN: 1", MM_MODEM_MODE_2G | MM_MODEM_MODE_3G },
+ { "+CFUN: 4", MM_MODEM_MODE_NONE },
+ { "+CFUN: 5", MM_MODEM_MODE_2G },
+ { "+CFUN: 6", MM_MODEM_MODE_3G },
+};
+
+static void
+test_cfun_query_current_modes (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cfun_query_current_mode_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ MMModemMode allowed = MM_MODEM_MODE_NONE;
+
+ success = mm_linktop_parse_cfun_query_current_modes (cfun_query_current_mode_tests[i].str, &allowed, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+ g_assert_cmpuint (cfun_query_current_mode_tests[i].allowed, ==, allowed);
+ }
+}
+
+/*****************************************************************************/
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/linktop/cfun/query/current-modes", test_cfun_query_current_modes);
+
+ return g_test_run ();
+}
diff --git a/src/plugins/longcheer/77-mm-longcheer-port-types.rules b/src/plugins/longcheer/77-mm-longcheer-port-types.rules
new file mode 100644
index 00000000..e0fbe849
--- /dev/null
+++ b/src/plugins/longcheer/77-mm-longcheer-port-types.rules
@@ -0,0 +1,173 @@
+# do not edit this file, it will be overwritten on update
+
+# Longcheer makes modules that other companies rebrand, like:
+#
+# Alcatel One Touch X020
+# Alcatel One Touch X030
+# MobiData MBD-200HU
+# ST Mobile Connect HSUPA USB Modem
+#
+# Most of these values were scraped from various Longcheer-based Windows
+# driver .inf files. cmmdm.inf lists the actual data (ie PPP) ports, while
+# cmser.inf lists the aux ports that may be either AT-capable or not but
+# cannot be used for PPP.
+
+ACTION!="add|change|move|bind", GOTO="mm_longcheer_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1c9e", GOTO="mm_longcheer_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1bbb", GOTO="mm_tamobile_vendorcheck"
+GOTO="mm_longcheer_port_types_end"
+
+LABEL="mm_longcheer_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="3197", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="3197", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="3197", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6000", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6000", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6000", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6060", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6060", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6060", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+# Alcatel One Touch X020
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6061", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6061", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="6061", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7001", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7001", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7001", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7001", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7002", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7002", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7002", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7002", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7002", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7101", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7101", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7101", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7101", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7102", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7102", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7102", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7102", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="7102", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8000", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8000", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8000", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8000", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8001", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8001", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8001", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8001", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8002", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8002", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8002", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="8002", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+# ChinaBird PL68
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9000", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9000", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9000", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9001", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9002", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9002", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9002", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9002", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9003", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9003", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9003", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9003", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9003", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9004", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9004", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9004", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9005", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9005", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9005", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9010", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9010", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9010", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9010", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9012", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9012", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9012", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9012", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9020", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9020", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9020", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9020", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9022", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9022", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9022", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9022", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+# Zoom products
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9602", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9602", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9602", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9602", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9603", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9603", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9603", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9603", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9604", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9604", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9604", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9604", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9605", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9605", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9605", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9605", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9605", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9606", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9606", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9606", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9606", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9606", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9607", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9607", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9607", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9607", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9607", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1c9e", ATTRS{idProduct}=="9607", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+GOTO="mm_longcheer_port_types_end"
+
+LABEL="mm_tamobile_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Alcatel One Touch X060s
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{ID_MM_LONGCHEER_TAGGED}="1"
+
+GOTO="mm_longcheer_port_types_end"
+
+LABEL="mm_longcheer_port_types_end"
diff --git a/src/plugins/longcheer/mm-broadband-modem-longcheer.c b/src/plugins/longcheer/mm-broadband-modem-longcheer.c
new file mode 100644
index 00000000..0926de2c
--- /dev/null
+++ b/src/plugins/longcheer/mm-broadband-modem-longcheer.c
@@ -0,0 +1,416 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log.h"
+#include "mm-errors-types.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem.h"
+#include "mm-modem-helpers.h"
+#include "mm-broadband-modem-longcheer.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+
+static MMIfaceModem *iface_modem_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemLongcheer, mm_broadband_modem_longcheer, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init))
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_load_supported_modes_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ GArray *all;
+ GArray *combinations;
+ GArray *filtered;
+ MMModemModeCombination mode;
+
+ all = iface_modem_parent->load_supported_modes_finish (self, res, &error);
+ if (!all) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 4);
+
+ /* 2G only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 3G only */
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G, 2G preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G, 3G preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+
+ /* Filter out those unsupported modes */
+ filtered = mm_filter_supported_modes (all, combinations, self);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Run parent's loading */
+ iface_modem_parent->load_supported_modes (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_supported_modes_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Load initial allowed/preferred modes (Modem interface) */
+
+static gboolean
+load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ const gchar *response;
+ const gchar *str;
+ gint mododr = -1;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return FALSE;
+
+ str = mm_strip_tag (response, "+MODODR:");
+ if (!str) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse MODODR response: '%s'",
+ response);
+ return FALSE;
+ }
+
+ mododr = atoi (str);
+ switch (mododr) {
+ case 1:
+ /* UMTS only */
+ *allowed = MM_MODEM_MODE_3G;
+ *preferred = MM_MODEM_MODE_NONE;
+ return TRUE;
+ case 2:
+ /* UMTS preferred */
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ *preferred = MM_MODEM_MODE_3G;
+ return TRUE;
+ case 3:
+ /* GSM only */
+ *allowed = MM_MODEM_MODE_2G;
+ *preferred = MM_MODEM_MODE_NONE;
+ return TRUE;
+ case 4:
+ /* GSM preferred */
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ *preferred = MM_MODEM_MODE_2G;
+ return TRUE;
+ default:
+ break;
+ }
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse unexpected MODODR response: '%s'",
+ response);
+ return FALSE;
+}
+
+static void
+load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+MODODR?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Set allowed modes (Modem interface) */
+
+static gboolean
+set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+allowed_mode_update_ready (MMBroadbandModemLongcheer *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error)
+ /* Let the error be critical. */
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *command;
+ gint mododr = 0;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (allowed == MM_MODEM_MODE_2G)
+ mododr = 3;
+ else if (allowed == MM_MODEM_MODE_3G)
+ mododr = 1;
+ else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) {
+ if (preferred == MM_MODEM_MODE_2G)
+ mododr = 4;
+ else if (preferred == MM_MODEM_MODE_3G)
+ mododr = 2;
+ } else if (allowed == MM_MODEM_MODE_ANY && preferred == MM_MODEM_MODE_NONE)
+ /* Default to 3G preferred */
+ mododr = 2;
+
+ if (mododr == 0) {
+ gchar *allowed_str;
+ gchar *preferred_str;
+
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Requested mode (allowed: '%s', preferred: '%s') not "
+ "supported by the modem.",
+ allowed_str,
+ preferred_str);
+ g_object_unref (task);
+
+ g_free (allowed_str);
+ g_free (preferred_str);
+ return;
+ }
+
+ command = g_strdup_printf ("+MODODR=%d", mododr);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)allowed_mode_update_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Load access technologies (Modem interface) */
+
+static gboolean
+load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ GError **error)
+{
+ const gchar *result;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!result)
+ return FALSE;
+
+ result = mm_strip_tag (result, "+PSRAT:");
+ *access_technologies = mm_string_to_access_tech (result);
+ *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY;
+ return TRUE;
+}
+
+static void
+load_access_technologies (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+PSRAT",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load unlock retries (Modem interface) */
+
+static MMUnlockRetries *
+load_unlock_retries_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_unlock_retries_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ int pin1, puk1, pin2, puk2;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* That's right; no +CPNNUM: prefix, it looks like this:
+ *
+ * AT+CPNNUM
+ * PIN1=3; PUK1=10; PIN2=3; PUK2=10
+ * OK
+ */
+ if (sscanf (response, "PIN1=%d; PUK1=%d; PIN2=%d; PUK2=%d", &pin1, &puk1, &pin2, &puk2) == 4) {
+ MMUnlockRetries *retries;
+ retries = mm_unlock_retries_new ();
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN2, pin2);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK2, puk2);
+ g_task_return_pointer (task, retries, g_object_unref);
+ } else {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Invalid unlock retries response: '%s'",
+ response);
+ }
+ g_object_unref (task);
+}
+
+static void
+load_unlock_retries (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CPNNUM",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)load_unlock_retries_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemLongcheer *
+mm_broadband_modem_longcheer_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_LONGCHEER,
+ 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 AT only */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_longcheer_init (MMBroadbandModemLongcheer *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_access_technologies = load_access_technologies;
+ iface->load_access_technologies_finish = load_access_technologies_finish;
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = load_current_modes;
+ iface->load_current_modes_finish = load_current_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = set_current_modes_finish;
+ iface->load_unlock_retries = load_unlock_retries;
+ iface->load_unlock_retries_finish = load_unlock_retries_finish;
+}
+
+static void
+mm_broadband_modem_longcheer_class_init (MMBroadbandModemLongcheerClass *klass)
+{
+}
diff --git a/src/plugins/longcheer/mm-broadband-modem-longcheer.h b/src/plugins/longcheer/mm-broadband-modem-longcheer.h
new file mode 100644
index 00000000..710abeef
--- /dev/null
+++ b/src/plugins/longcheer/mm-broadband-modem-longcheer.h
@@ -0,0 +1,49 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_MODEM_LONGCHEER_H
+#define MM_BROADBAND_MODEM_LONGCHEER_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_LONGCHEER (mm_broadband_modem_longcheer_get_type ())
+#define MM_BROADBAND_MODEM_LONGCHEER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_LONGCHEER, MMBroadbandModemLongcheer))
+#define MM_BROADBAND_MODEM_LONGCHEER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_LONGCHEER, MMBroadbandModemLongcheerClass))
+#define MM_IS_BROADBAND_MODEM_LONGCHEER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_LONGCHEER))
+#define MM_IS_BROADBAND_MODEM_LONGCHEER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_LONGCHEER))
+#define MM_BROADBAND_MODEM_LONGCHEER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_LONGCHEER, MMBroadbandModemLongcheerClass))
+
+typedef struct _MMBroadbandModemLongcheer MMBroadbandModemLongcheer;
+typedef struct _MMBroadbandModemLongcheerClass MMBroadbandModemLongcheerClass;
+
+struct _MMBroadbandModemLongcheer {
+ MMBroadbandModem parent;
+};
+
+struct _MMBroadbandModemLongcheerClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_longcheer_get_type (void);
+
+MMBroadbandModemLongcheer *mm_broadband_modem_longcheer_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_LONGCHEER_H */
diff --git a/src/plugins/longcheer/mm-plugin-longcheer.c b/src/plugins/longcheer/mm-plugin-longcheer.c
new file mode 100644
index 00000000..31774efb
--- /dev/null
+++ b/src/plugins/longcheer/mm-plugin-longcheer.c
@@ -0,0 +1,243 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-plugin-longcheer.h"
+#include "mm-broadband-modem-longcheer.h"
+
+G_DEFINE_TYPE (MMPluginLongcheer, mm_plugin_longcheer, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+/* Custom init */
+
+typedef struct {
+ MMPortSerialAt *port;
+ guint retries;
+} LongcheerCustomInitContext;
+
+static void
+longcheer_custom_init_context_free (LongcheerCustomInitContext *ctx)
+{
+ g_object_unref (ctx->port);
+ g_slice_free (LongcheerCustomInitContext, ctx);
+}
+
+static gboolean
+longcheer_custom_init_finish (MMPortProbe *probe,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void longcheer_custom_init_step (GTask *task);
+
+static void
+gmr_ready (MMPortSerialAt *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMPortProbe *probe;
+ const gchar *p;
+ const gchar *response;
+
+ probe = g_task_get_source_object (task);
+
+ response = mm_port_serial_at_command_finish (port, res, NULL);
+ if (!response) {
+ mm_obj_dbg (probe, "retrying custom init step...");
+ longcheer_custom_init_step (task);
+ return;
+ }
+
+ /* Note the lack of a ':' on the GMR; the X200 doesn't send one */
+ p = mm_strip_tag (response, "AT+GMR");
+ if (p && *p == 'L') {
+ /* X200 modems have a GMR firmware revision that starts with 'L', and
+ * as far as I can tell X060s devices have a revision starting with 'C'.
+ * So use that to determine if the device is an X200, which this plugin
+ * does not support since it uses a different chipset even though the
+ * X060s and the X200 have the exact same USB VID and PID.
+ */
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "X200 cannot be supported with the Longcheer plugin");
+ } else {
+ mm_obj_dbg (probe, "device is not a X200");
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+longcheer_custom_init_step (GTask *task)
+{
+ MMPortProbe *probe;
+ LongcheerCustomInitContext *ctx;
+ GCancellable *cancellable;
+
+ probe = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+ cancellable = g_task_get_cancellable (task);
+
+ /* If cancelled, end */
+ if (g_cancellable_is_cancelled (cancellable)) {
+ mm_obj_dbg (probe, "no need to keep on running custom init");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ if (ctx->retries == 0) {
+ /* In this case, we need the AT command result to decide whether we can
+ * support this modem or not, so really fail if we didn't get it. */
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't get device revision information");
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->retries--;
+ mm_port_serial_at_command (
+ ctx->port,
+ "AT+GMR",
+ 3,
+ FALSE, /* raw */
+ FALSE, /* allow_cached */
+ cancellable,
+ (GAsyncReadyCallback)gmr_ready,
+ task);
+}
+
+static void
+longcheer_custom_init (MMPortProbe *probe,
+ MMPortSerialAt *port,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMDevice *device;
+ LongcheerCustomInitContext *ctx;
+ GTask *task;
+
+ ctx = g_slice_new (LongcheerCustomInitContext);
+ ctx->port = g_object_ref (port);
+ ctx->retries = 3;
+
+ task = g_task_new (probe, cancellable, callback, user_data);
+ /* Clears the check-cancellable flag of the task as we expect the task to
+ * return TRUE upon cancellation.
+ */
+ g_task_set_check_cancellable (task, FALSE);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)longcheer_custom_init_context_free);
+
+ /* TCT/Alcatel in their infinite wisdom assigned the same USB VID/PID to
+ * the x060s (Longcheer firmware) and the x200 (something else) and thus
+ * we can't tell them apart via udev rules. Worse, they both report the
+ * same +GMM and +GMI, so we're left with just +GMR which is a sketchy way
+ * to tell modems apart. We can't really use Longcheer-specific commands
+ * like AT+MODODR or AT+PSRAT because we're not sure if they work when the
+ * SIM PIN has not been entered yet; many modems have a limited command
+ * parser before the SIM is unlocked.
+ */
+ device = mm_port_probe_peek_device (probe);
+ if (mm_device_get_vendor (device) != 0x1bbb ||
+ mm_device_get_product (device) != 0x0000) {
+ /* If not exactly this vendor/product, just skip */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ longcheer_custom_init_step (task);
+}
+
+/*****************************************************************************/
+
+static MMBaseModem *
+create_modem (MMPlugin *self,
+ const gchar *uid,
+ const gchar **drivers,
+ guint16 vendor,
+ guint16 product,
+ guint16 subsystem_vendor,
+ GList *probes,
+ GError **error)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_longcheer_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", NULL };
+ /* Vendors: Longcheer and TAMobile */
+ static const guint16 vendor_ids[] = { 0x1c9e, 0x1bbb, 0 };
+ /* Some TAMobile devices are different chipsets and should be handled
+ * by other plugins, so only handle LONGCHEER tagged devices here.
+ */
+ static const gchar *udev_tags[] = {
+ "ID_MM_LONGCHEER_TAGGED",
+ NULL
+ };
+ static const MMAsyncMethod custom_init = {
+ .async = G_CALLBACK (longcheer_custom_init),
+ .finish = G_CALLBACK (longcheer_custom_init_finish),
+ };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_LONGCHEER,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_UDEV_TAGS, udev_tags,
+ MM_PLUGIN_CUSTOM_INIT, &custom_init,
+ NULL));
+}
+
+static void
+mm_plugin_longcheer_init (MMPluginLongcheer *self)
+{
+}
+
+static void
+mm_plugin_longcheer_class_init (MMPluginLongcheerClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/longcheer/mm-plugin-longcheer.h b/src/plugins/longcheer/mm-plugin-longcheer.h
new file mode 100644
index 00000000..1a5f2b98
--- /dev/null
+++ b/src/plugins/longcheer/mm-plugin-longcheer.h
@@ -0,0 +1,42 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_PLUGIN_LONGCHEER_H
+#define MM_PLUGIN_LONGCHEER_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_LONGCHEER (mm_plugin_longcheer_get_type ())
+#define MM_PLUGIN_LONGCHEER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_LONGCHEER, MMPluginLongcheer))
+#define MM_PLUGIN_LONGCHEER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_LONGCHEER, MMPluginLongcheerClass))
+#define MM_IS_PLUGIN_LONGCHEER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_LONGCHEER))
+#define MM_IS_PLUGIN_LONGCHEER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_LONGCHEER))
+#define MM_PLUGIN_LONGCHEER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_LONGCHEER, MMPluginLongcheerClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginLongcheer;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginLongcheerClass;
+
+GType mm_plugin_longcheer_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_LONGCHEER_H */
diff --git a/src/plugins/mbm/77-mm-ericsson-mbm.rules b/src/plugins/mbm/77-mm-ericsson-mbm.rules
new file mode 100644
index 00000000..73e08692
--- /dev/null
+++ b/src/plugins/mbm/77-mm-ericsson-mbm.rules
@@ -0,0 +1,174 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_mbm_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="0bdb", GOTO="mm_mbm_ericsson_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="0fce", GOTO="mm_mbm_sony_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="413c", GOTO="mm_mbm_dell_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="03f0", GOTO="mm_mbm_hp_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="0930", GOTO="mm_mbm_toshiba_vendorcheck"
+GOTO="mm_mbm_end"
+
+LABEL="mm_mbm_ericsson_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Ericsson F3507g
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1900", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1900", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1902", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1902", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson F3607gw
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1904", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1904", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1905", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1905", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1906", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1906", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson F3307
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190a", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1909", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson F3307 R2
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1914", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson C3607w
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1049", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson C3607w v2
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190b", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson F5521gw
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190d", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190d", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1911", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1911", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson H5321gw
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1919", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson H5321w
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="191d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson F5321gw
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1917", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson F5321w
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="191b", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson C5621gw
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="191f", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson C5621w
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1921", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson H5321gw
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1926", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1926", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1927", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1927", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson C3304w
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1928", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Ericsson C5621 TFF
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1936", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Lenovo N5321gw
+ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="193e", ENV{ID_MM_ERICSSON_MBM}="1"
+
+GOTO="mm_mbm_end"
+
+LABEL="mm_mbm_sony_vendorcheck"
+
+# Sony-Ericsson MD300
+ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="d0cf", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Sony-Ericsson MD400
+ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="d0e1", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Sony-Ericsson MD400G
+ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="d103", ENV{ID_MM_ERICSSON_MBM}="1"
+
+GOTO="mm_mbm_end"
+
+LABEL="mm_mbm_dell_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Dell 5560
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818e", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818e", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+
+# Dell 5550
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Dell 5530 HSDPA
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8147", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8147", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Dell F3607gw
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8183", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8183", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8184", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8184", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Dell F3307
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818b", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818b", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818c", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818c", ENV{ID_MM_ERICSSON_MBM}="1"
+
+GOTO="mm_mbm_end"
+
+LABEL="mm_mbm_hp_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# HP hs2330 Mobile Broadband Module
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="271d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# HP hs2320 Mobile Broadband Module
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="261d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# HP hs2340 Mobile Broadband Module
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="3a1d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# HP hs2350 Mobile Broadband Module
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="3d1d", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="3d1d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# HP lc2000 Mobile Broadband Module
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="301d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# HP lc2010 Mobile Broadband Module
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="2f1d", ENV{ID_MM_ERICSSON_MBM}="1"
+
+GOTO="mm_mbm_end"
+
+LABEL="mm_mbm_toshiba_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Toshiba
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130b", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130b", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Toshiba F3607gw
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130c", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130c", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1311", ENV{.MM_USBIFNUM}=="09", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1311", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Toshiba F3307
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1315", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1316", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1317", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Toshiba F5521gw
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1313", ENV{ID_MM_ERICSSON_MBM}="1"
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1314", ENV{ID_MM_ERICSSON_MBM}="1"
+
+# Toshiba H5321gw
+ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1319", ENV{ID_MM_ERICSSON_MBM}="1"
+
+GOTO="mm_mbm_end"
+
+LABEL="mm_mbm_end"
diff --git a/src/plugins/mbm/mm-broadband-bearer-mbm.c b/src/plugins/mbm/mm-broadband-bearer-mbm.c
new file mode 100644
index 00000000..c1407ab4
--- /dev/null
+++ b/src/plugins/mbm/mm-broadband-bearer-mbm.c
@@ -0,0 +1,911 @@
+/* -*- 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) 2008 - 2010 Ericsson AB
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ * Copyright (C) 2017 Aleksander Morgado <aleksander@aleksander.es>
+ *
+ * Author: Per Hallsmark <per.hallsmark@ericsson.com>
+ * Bjorn Runaker <bjorn.runaker@ericsson.com>
+ * Torgny Johansson <torgny.johansson@ericsson.com>
+ * Jonas Sjöquist <jonas.sjoquist@ericsson.com>
+ * Dan Williams <dcbw@redhat.com>
+ * Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <arpa/inet.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-base-modem-at.h"
+#include "mm-broadband-bearer-mbm.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-mbm.h"
+#include "mm-daemon-enums-types.h"
+
+G_DEFINE_TYPE (MMBroadbandBearerMbm, mm_broadband_bearer_mbm, MM_TYPE_BROADBAND_BEARER)
+
+struct _MMBroadbandBearerMbmPrivate {
+ GTask *connect_pending;
+ GTask *disconnect_pending;
+};
+
+/*****************************************************************************/
+/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ guint cid;
+ MMPort *data;
+ guint poll_count;
+ guint poll_id;
+ GError *saved_error;
+} Dial3gppContext;
+
+static void
+dial_3gpp_context_free (Dial3gppContext *ctx)
+{
+ g_assert (!ctx->poll_id);
+ g_assert (!ctx->saved_error);
+ g_clear_object (&ctx->data);
+ g_clear_object (&ctx->primary);
+ g_clear_object (&ctx->modem);
+ g_slice_free (Dial3gppContext, ctx);
+}
+
+static MMPort *
+dial_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return MM_PORT (g_task_propagate_pointer (G_TASK (res), error));
+}
+
+static void
+connect_reset_ready (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Dial3gppContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp_finish (self, res, NULL);
+
+ /* When reset is requested, it was either cancelled or an error was stored */
+ if (!g_task_return_error_if_cancelled (task)) {
+ g_assert (ctx->saved_error);
+ g_task_return_error (task, ctx->saved_error);
+ ctx->saved_error = NULL;
+ }
+
+ g_object_unref (task);
+}
+
+static void
+connect_reset (GTask *task)
+{
+ MMBroadbandBearerMbm *self;
+ Dial3gppContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ MM_BROADBAND_BEARER_GET_CLASS (self)->disconnect_3gpp (
+ MM_BROADBAND_BEARER (self),
+ MM_BROADBAND_MODEM (ctx->modem),
+ ctx->primary,
+ NULL,
+ ctx->data,
+ ctx->cid,
+ (GAsyncReadyCallback) connect_reset_ready,
+ task);
+}
+
+static void
+process_pending_connect_attempt (MMBroadbandBearerMbm *self,
+ MMBearerConnectionStatus status)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+
+ /* Recover connection task */
+ task = self->priv->connect_pending;
+ self->priv->connect_pending = NULL;
+ g_assert (task != NULL);
+
+ ctx = g_task_get_task_data (task);
+
+ if (ctx->poll_id) {
+ g_source_remove (ctx->poll_id);
+ ctx->poll_id = 0;
+ }
+
+ /* Received 'CONNECTED' during a connection attempt? */
+ if (status == MM_BEARER_CONNECTION_STATUS_CONNECTED) {
+ /* If we wanted to get cancelled before, do it now. */
+ if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) {
+ connect_reset (task);
+ return;
+ }
+
+ g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref);
+ g_object_unref (task);
+ return;
+ }
+
+ /* If we wanted to get cancelled before and now we couldn't connect,
+ * use the cancelled error and return */
+ if (g_task_return_error_if_cancelled (task)) {
+ g_object_unref (task);
+ return;
+ }
+
+ /* Otherwise, received 'DISCONNECTED' during a connection attempt? */
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Call setup failed");
+ g_object_unref (task);
+}
+
+static gboolean connect_poll_cb (MMBroadbandBearerMbm *self);
+
+static void
+connect_poll_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerMbm *self)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+ const gchar *response;
+ guint state;
+
+ task = g_steal_pointer (&self->priv->connect_pending);
+
+ if (!task) {
+ mm_obj_dbg (self, "connection context was finished already by an unsolicited message");
+ /* Run _finish() to finalize the async call, even if we don't care
+ * the result */
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+ return;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (!response) {
+ ctx->saved_error = error;
+ connect_reset (task);
+ return;
+ }
+
+ if (sscanf (response, "*ENAP: %d", &state) == 1 && state == 1) {
+ /* Success! Connected... */
+ g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Restore pending task and check again in one second */
+ self->priv->connect_pending = task;
+ g_assert (ctx->poll_id == 0);
+ ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc) connect_poll_cb, self);
+}
+
+static gboolean
+connect_poll_cb (MMBroadbandBearerMbm *self)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+
+ task = g_steal_pointer (&self->priv->connect_pending);
+
+ g_assert (task);
+ ctx = g_task_get_task_data (task);
+
+ ctx->poll_id = 0;
+
+ /* Complete if we were cancelled */
+ if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) {
+ connect_reset (task);
+ return G_SOURCE_REMOVE;
+ }
+
+ /* Too many retries... */
+ if (ctx->poll_count > MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT) {
+ g_assert (!ctx->saved_error);
+ ctx->saved_error = g_error_new (MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT,
+ "Connection attempt timed out");
+ connect_reset (task);
+ return G_SOURCE_REMOVE;
+ }
+
+ /* Restore pending task and poll */
+ self->priv->connect_pending = task;
+ ctx->poll_count++;
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ "AT*ENAP?",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)connect_poll_ready,
+ self);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+activate_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerMbm *self)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+
+ /* Try to recover the connection context. If none found, it means the
+ * context was already completed and we have nothing else to do. */
+ task = g_steal_pointer (&self->priv->connect_pending);
+
+ if (!task) {
+ mm_obj_dbg (self, "connection context was finished already by an unsolicited message");
+ /* Run _finish() to finalize the async call, even if we don't care
+ * the result */
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+ goto out;
+ }
+
+ /* From now on, if we get cancelled, we'll need to run the connection
+ * reset ourselves just in case */
+ if (!mm_base_modem_at_command_full_finish (modem, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ goto out;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ /* No unsolicited E2NAP status yet; wait for it and periodically poll
+ * to handle very old F3507g/MD300 firmware that may not send E2NAP. */
+ self->priv->connect_pending = task;
+ ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc)connect_poll_cb, self);
+
+ out:
+ /* Balance refcount with the extra ref we passed to command_full() */
+ g_object_unref (self);
+}
+
+static void
+activate (GTask *task)
+{
+ MMBroadbandBearerMbm *self;
+ Dial3gppContext *ctx;
+ gchar *command;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* The unsolicited response to ENAP may come before the OK does.
+ * We will keep the connection context in the bearer private data so
+ * that it is accessible from the unsolicited message handler. */
+ g_assert (self->priv->connect_pending == NULL);
+ self->priv->connect_pending = task;
+
+ /* Activate the PDP context and start the data session */
+ command = g_strdup_printf ("AT*ENAP=1,%d", ctx->cid);
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ 10,
+ FALSE,
+ FALSE, /* raw */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)activate_ready,
+ g_object_ref (self)); /* we pass the bearer object! */
+ g_free (command);
+}
+
+static void
+authenticate_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_full_finish (modem, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ activate (task);
+}
+
+static void
+authenticate (GTask *task)
+{
+ MMBroadbandBearerMbm *self;
+ Dial3gppContext *ctx;
+ const gchar *user;
+ const gchar *password;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ password = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+
+ /* Both user and password are required; otherwise firmware returns an error */
+ if (user || password) {
+ g_autofree gchar *command = NULL;
+ g_autofree gchar *user_enc = NULL;
+ g_autofree gchar *password_enc = NULL;
+ GError *error = NULL;
+
+ user_enc = mm_modem_charset_str_from_utf8 (user,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (ctx->modem)),
+ FALSE,
+ &error);
+ if (!user_enc) {
+ g_prefix_error (&error, "Couldn't convert user to current charset: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ password_enc = mm_modem_charset_str_from_utf8 (password,
+ mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (ctx->modem)),
+ FALSE,
+ &error);
+ if (!password_enc) {
+ g_prefix_error (&error, "Couldn't convert password to current charset: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ command = g_strdup_printf ("AT*EIAAUW=%d,1,\"%s\",\"%s\"",
+ ctx->cid, user_enc, password_enc);
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback) authenticate_ready,
+ task);
+ return;
+ }
+
+ mm_obj_dbg (self, "authentication not needed");
+ activate (task);
+}
+
+static void
+dial_3gpp (MMBroadbandBearer *_self,
+ MMBaseModem *modem,
+ MMPortSerialAt *primary,
+ guint cid,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandBearerMbm *self = MM_BROADBAND_BEARER_MBM (_self);
+ GTask *task;
+ Dial3gppContext *ctx;
+
+ g_assert (primary != NULL);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ ctx = g_slice_new0 (Dial3gppContext);
+ ctx->modem = g_object_ref (modem);
+ ctx->primary = g_object_ref (primary);
+ ctx->cid = cid;
+ g_task_set_task_data (task, ctx, (GDestroyNotify)dial_3gpp_context_free);
+
+ /* We need a net data port */
+ ctx->data = mm_base_modem_get_best_data_port (modem, MM_PORT_TYPE_NET);
+ if (!ctx->data) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_NOT_FOUND,
+ "No valid data port found to launch connection");
+ g_object_unref (task);
+ return;
+ }
+
+ authenticate (task);
+}
+
+/*****************************************************************************/
+/* 3GPP IP config retrieval (sub-step of the 3GPP Connection sequence) */
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ MMBearerIpFamily family;
+} GetIpConfig3gppContext;
+
+static void
+get_ip_config_context_free (GetIpConfig3gppContext *ctx)
+{
+ g_object_unref (ctx->primary);
+ g_object_unref (ctx->modem);
+ g_free (ctx);
+}
+
+static gboolean
+get_ip_config_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ MMBearerIpConfig **ipv4_config,
+ MMBearerIpConfig **ipv6_config,
+ GError **error)
+{
+ MMBearerConnectResult *configs;
+ MMBearerIpConfig *ipv4, *ipv6;
+
+ configs = g_task_propagate_pointer (G_TASK (res), error);
+ if (!configs)
+ return FALSE;
+
+ ipv4 = mm_bearer_connect_result_peek_ipv4_config (configs);
+ ipv6 = mm_bearer_connect_result_peek_ipv6_config (configs);
+ g_assert (ipv4 || ipv6);
+ if (ipv4_config && ipv4)
+ *ipv4_config = g_object_ref (ipv4);
+ if (ipv6_config && ipv6)
+ *ipv6_config = g_object_ref (ipv6);
+
+ mm_bearer_connect_result_unref (configs);
+ return TRUE;
+}
+
+static void
+ip_config_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GetIpConfig3gppContext *ctx;
+ MMBearerIpConfig *ipv4_config = NULL;
+ MMBearerIpConfig *ipv6_config = NULL;
+ const gchar *response;
+ GError *error = NULL;
+ MMBearerConnectResult *connect_result;
+
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (error) {
+ g_error_free (error);
+
+ /* Fall back to DHCP configuration; early devices don't support *E2IPCFG */
+ if (ctx->family == MM_BEARER_IP_FAMILY_IPV4 || ctx->family == MM_BEARER_IP_FAMILY_IPV4V6) {
+ ipv4_config = mm_bearer_ip_config_new ();
+ mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_DHCP);
+ }
+ if (ctx->family == MM_BEARER_IP_FAMILY_IPV6 || ctx->family == MM_BEARER_IP_FAMILY_IPV4V6) {
+ ipv6_config = mm_bearer_ip_config_new ();
+ mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP);
+ }
+ } else {
+ if (!mm_mbm_parse_e2ipcfg_response (response,
+ &ipv4_config,
+ &ipv6_config,
+ &error)) {
+ g_task_return_error (task, error);
+ goto out;
+ }
+
+ if (!ipv4_config && !ipv6_config) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't get IP config: couldn't parse response '%s'",
+ response);
+ goto out;
+ }
+ }
+
+ connect_result = mm_bearer_connect_result_new (MM_PORT (ctx->primary),
+ ipv4_config,
+ ipv6_config);
+ g_task_return_pointer (task,
+ connect_result,
+ (GDestroyNotify)mm_bearer_connect_result_unref);
+
+out:
+ g_object_unref (task);
+ g_clear_object (&ipv4_config);
+ g_clear_object (&ipv6_config);
+}
+
+static void
+get_ip_config_3gpp (MMBroadbandBearer *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ MMBearerIpFamily ip_family,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GetIpConfig3gppContext *ctx;
+ GTask *task;
+
+ ctx = g_new0 (GetIpConfig3gppContext, 1);
+ ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
+ ctx->primary = g_object_ref (primary);
+ ctx->family = ip_family;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)get_ip_config_context_free);
+
+ mm_base_modem_at_command_full (MM_BASE_MODEM (modem),
+ primary,
+ "*E2IPCFG?",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)ip_config_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* 3GPP disconnect */
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ guint poll_count;
+ guint poll_id;
+} DisconnectContext;
+
+static void
+disconnect_context_free (DisconnectContext *ctx)
+{
+ g_assert (!ctx->poll_id);
+ g_clear_object (&ctx->primary);
+ g_clear_object (&ctx->modem);
+ g_free (ctx);
+}
+
+static gboolean
+disconnect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+process_pending_disconnect_attempt (MMBroadbandBearerMbm *self,
+ MMBearerConnectionStatus status)
+{
+ GTask *task;
+ DisconnectContext *ctx;
+
+ /* Recover disconnection task */
+ task = g_steal_pointer (&self->priv->disconnect_pending);
+ ctx = g_task_get_task_data (task);
+
+ if (ctx->poll_id) {
+ g_source_remove (ctx->poll_id);
+ ctx->poll_id = 0;
+ }
+
+ /* Received 'DISCONNECTED' during a disconnection attempt? */
+ if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) {
+ mm_obj_dbg (self, "connection disconnect indicated by an unsolicited message");
+ g_task_return_boolean (task, TRUE);
+ } else {
+ /* Otherwise, report error */
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Disconnection failed");
+ }
+ g_object_unref (task);
+}
+
+static gboolean disconnect_poll_cb (MMBroadbandBearerMbm *self);
+
+static void
+disconnect_poll_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerMbm *self)
+
+{
+ GTask *task;
+ DisconnectContext *ctx;
+ GError *error = NULL;
+ const gchar *response;
+ guint state;
+
+ task = g_steal_pointer (&self->priv->disconnect_pending);
+
+ if (!task) {
+ mm_obj_dbg (self, "disconnection context was finished already by an unsolicited message");
+ /* Run _finish() to finalize the async call, even if we don't care
+ * the result */
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+ goto out;
+ }
+
+ response = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ goto out;
+ }
+
+ if (sscanf (response, "*ENAP: %d", &state) == 1 && state == 0) {
+ /* Disconnected */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ goto out;
+ }
+
+ /* Restore pending task and check in 1s */
+ self->priv->disconnect_pending = task;
+ ctx = g_task_get_task_data (task);
+ g_assert (ctx->poll_id == 0);
+ ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc) disconnect_poll_cb, self);
+
+ out:
+ /* Balance refcount with the extra ref we passed to command_full() */
+ g_object_unref (self);
+}
+
+static gboolean
+disconnect_poll_cb (MMBroadbandBearerMbm *self)
+{
+ GTask *task;
+ DisconnectContext *ctx;
+
+ task = self->priv->disconnect_pending;
+ self->priv->disconnect_pending = NULL;
+
+ g_assert (task);
+ ctx = g_task_get_task_data (task);
+
+ ctx->poll_id = 0;
+
+ /* Too many retries... */
+ if (ctx->poll_count > MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT) {
+ g_task_return_new_error (task,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT,
+ "Disconnection attempt timed out");
+ g_object_unref (task);
+ return G_SOURCE_REMOVE;
+ }
+
+ /* Restore pending task and poll */
+ self->priv->disconnect_pending = task;
+ ctx->poll_count++;
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ "AT*ENAP?",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback) disconnect_poll_ready,
+ g_object_ref (self)); /* we pass the bearer object! */
+ return G_SOURCE_REMOVE;
+}
+
+static void
+disconnect_enap_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerMbm *self)
+{
+ DisconnectContext *ctx;
+ GTask *task;
+ GError *error = NULL;
+
+ task = g_steal_pointer (&self->priv->disconnect_pending);
+
+ /* Try to recover the disconnection context. If none found, it means the
+ * context was already completed and we have nothing else to do. */
+ if (!task) {
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+ goto out;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ /* Ignore errors for now */
+ mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (error) {
+ mm_obj_dbg (self, "disconnection failed (not fatal): %s", error->message);
+ g_error_free (error);
+ }
+
+ /* No unsolicited E2NAP status yet; wait for it and periodically poll
+ * to handle very old F3507g/MD300 firmware that may not send E2NAP. */
+ self->priv->disconnect_pending = task;
+ ctx->poll_id = g_timeout_add_seconds (1, (GSourceFunc)disconnect_poll_cb, self);
+
+ out:
+ /* Balance refcount with the extra ref we passed to command_full() */
+ g_object_unref (self);
+}
+
+static void
+disconnect_3gpp (MMBroadbandBearer *_self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandBearerMbm *self = MM_BROADBAND_BEARER_MBM (_self);
+ GTask *task;
+ DisconnectContext *ctx;
+
+ g_assert (primary != NULL);
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_new0 (DisconnectContext, 1);
+ ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
+ ctx->primary = g_object_ref (primary);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) disconnect_context_free);
+
+ /* The unsolicited response to ENAP may come before the OK does.
+ * We will keep the disconnection context in the bearer private data so
+ * that it is accessible from the unsolicited message handler. */
+ g_assert (self->priv->disconnect_pending == NULL);
+ self->priv->disconnect_pending = task;
+
+ mm_base_modem_at_command_full (MM_BASE_MODEM (modem),
+ primary,
+ "*ENAP=0",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)disconnect_enap_ready,
+ g_object_ref (self)); /* we pass the bearer object! */
+}
+
+/*****************************************************************************/
+
+static void
+report_connection_status (MMBaseBearer *_self,
+ MMBearerConnectionStatus status,
+ const GError *connection_error)
+{
+ MMBroadbandBearerMbm *self = MM_BROADBAND_BEARER_MBM (_self);
+
+ g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED ||
+ status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED ||
+ status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
+
+ /* Process pending connection attempt */
+ if (self->priv->connect_pending) {
+ process_pending_connect_attempt (self, status);
+ return;
+ }
+
+ /* Process pending disconnection attempt */
+ if (self->priv->disconnect_pending) {
+ process_pending_disconnect_attempt (self, status);
+ return;
+ }
+
+ mm_obj_dbg (self, "received spontaneous E2NAP (%s)",
+ mm_bearer_connection_status_get_string (status));
+
+ /* Received a random 'DISCONNECTED'...*/
+ if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED ||
+ status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED) {
+ /* If no connection/disconnection attempt on-going, make sure we mark ourselves as
+ * disconnected. Make sure we only pass 'DISCONNECTED' to the parent */
+ MM_BASE_BEARER_CLASS (mm_broadband_bearer_mbm_parent_class)->report_connection_status (
+ _self,
+ MM_BEARER_CONNECTION_STATUS_DISCONNECTED,
+ NULL);
+ }
+}
+
+/*****************************************************************************/
+
+MMBaseBearer *
+mm_broadband_bearer_mbm_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *bearer;
+ GObject *source;
+
+ source = g_async_result_get_source_object (res);
+ bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!bearer)
+ return NULL;
+
+ /* Only export valid bearers */
+ mm_base_bearer_export (MM_BASE_BEARER (bearer));
+
+ return MM_BASE_BEARER (bearer);
+}
+
+void
+mm_broadband_bearer_mbm_new (MMBroadbandModemMbm *modem,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (
+ MM_TYPE_BROADBAND_BEARER_MBM,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_BEARER_MODEM, modem,
+ MM_BASE_BEARER_CONFIG, config,
+ NULL);
+}
+
+static void
+mm_broadband_bearer_mbm_init (MMBroadbandBearerMbm *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_BEARER_MBM,
+ MMBroadbandBearerMbmPrivate);
+}
+
+static void
+mm_broadband_bearer_mbm_class_init (MMBroadbandBearerMbmClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);
+ MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandBearerMbmPrivate));
+
+ base_bearer_class->report_connection_status = report_connection_status;
+ base_bearer_class->load_connection_status = NULL;
+ base_bearer_class->load_connection_status_finish = NULL;
+#if defined WITH_SUSPEND_RESUME
+ base_bearer_class->reload_connection_status = NULL;
+ base_bearer_class->reload_connection_status_finish = NULL;
+#endif
+
+ broadband_bearer_class->dial_3gpp = dial_3gpp;
+ broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish;
+ broadband_bearer_class->get_ip_config_3gpp = get_ip_config_3gpp;
+ broadband_bearer_class->get_ip_config_3gpp_finish = get_ip_config_3gpp_finish;
+ broadband_bearer_class->disconnect_3gpp = disconnect_3gpp;
+ broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish;
+}
diff --git a/src/plugins/mbm/mm-broadband-bearer-mbm.h b/src/plugins/mbm/mm-broadband-bearer-mbm.h
new file mode 100644
index 00000000..a05b456c
--- /dev/null
+++ b/src/plugins/mbm/mm-broadband-bearer-mbm.h
@@ -0,0 +1,68 @@
+/* -*- 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) 2008 - 2010 Ericsson AB
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ *
+ * Author: Per Hallsmark <per.hallsmark@ericsson.com>
+ * Bjorn Runaker <bjorn.runaker@ericsson.com>
+ * Torgny Johansson <torgny.johansson@ericsson.com>
+ * Jonas Sjöquist <jonas.sjoquist@ericsson.com>
+ * Dan Williams <dcbw@redhat.com>
+ * Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#ifndef MM_BROADBAND_BEARER_MBM_H
+#define MM_BROADBAND_BEARER_MBM_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-broadband-bearer.h"
+#include "mm-broadband-modem-mbm.h"
+
+#define MM_TYPE_BROADBAND_BEARER_MBM (mm_broadband_bearer_mbm_get_type ())
+#define MM_BROADBAND_BEARER_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_MBM, MMBroadbandBearerMbm))
+#define MM_BROADBAND_BEARER_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_MBM, MMBroadbandBearerMbmClass))
+#define MM_IS_BROADBAND_BEARER_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_MBM))
+#define MM_IS_BROADBAND_BEARER_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_MBM))
+#define MM_BROADBAND_BEARER_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_MBM, MMBroadbandBearerMbmClass))
+
+typedef struct _MMBroadbandBearerMbm MMBroadbandBearerMbm;
+typedef struct _MMBroadbandBearerMbmClass MMBroadbandBearerMbmClass;
+typedef struct _MMBroadbandBearerMbmPrivate MMBroadbandBearerMbmPrivate;
+
+struct _MMBroadbandBearerMbm {
+ MMBroadbandBearer parent;
+ MMBroadbandBearerMbmPrivate *priv;
+};
+
+struct _MMBroadbandBearerMbmClass {
+ MMBroadbandBearerClass parent;
+};
+
+GType mm_broadband_bearer_mbm_get_type (void);
+
+/* Default 3GPP bearer creation implementation */
+void mm_broadband_bearer_mbm_new (MMBroadbandModemMbm *modem,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseBearer *mm_broadband_bearer_mbm_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_BROADBAND_BEARER_MBM_H */
diff --git a/src/plugins/mbm/mm-broadband-modem-mbm.c b/src/plugins/mbm/mm-broadband-modem-mbm.c
new file mode 100644
index 00000000..fbc9830c
--- /dev/null
+++ b/src/plugins/mbm/mm-broadband-modem-mbm.c
@@ -0,0 +1,1583 @@
+/* -*- 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) 2008 - 2010 Ericsson AB
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ *
+ * Author: Per Hallsmark <per.hallsmark@ericsson.com>
+ * Bjorn Runaker <bjorn.runaker@ericsson.com>
+ * Torgny Johansson <torgny.johansson@ericsson.com>
+ * Jonas Sjöquist <jonas.sjoquist@ericsson.com>
+ * Dan Williams <dcbw@redhat.com>
+ * Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log-object.h"
+#include "mm-bearer-list.h"
+#include "mm-errors-types.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-mbm.h"
+#include "mm-broadband-modem-mbm.h"
+#include "mm-broadband-bearer-mbm.h"
+#include "mm-sim-mbm.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-location.h"
+
+/* sets the interval in seconds on how often the card emits the NMEA sentences */
+#define MBM_GPS_NMEA_INTERVAL "5"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+static MMIfaceModemLocation *iface_modem_location_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbm, mm_broadband_modem_mbm, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init))
+
+#define MBM_E2NAP_DISCONNECTED 0
+#define MBM_E2NAP_CONNECTED 1
+#define MBM_E2NAP_CONNECTING 2
+
+struct _MMBroadbandModemMbmPrivate {
+ gboolean have_emrdy;
+
+ GRegex *e2nap_regex;
+ GRegex *e2nap_ext_regex;
+ GRegex *emrdy_regex;
+ GRegex *pacsp_regex;
+ GRegex *estksmenu_regex;
+ GRegex *estksms_regex;
+ GRegex *emwi_regex;
+ GRegex *erinfo_regex;
+
+ MMModemLocationSource enabled_sources;
+
+ guint mbm_mode;
+};
+
+/*****************************************************************************/
+/* Create Bearer (Modem interface) */
+
+static MMBaseBearer *
+modem_create_bearer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+broadband_bearer_mbm_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer = NULL;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_mbm_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+
+ g_object_unref (task);
+}
+
+static void
+modem_create_bearer (MMIfaceModem *self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_obj_dbg (self, "creating MBM bearer...");
+ mm_broadband_bearer_mbm_new (MM_BROADBAND_MODEM_MBM (self),
+ properties,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_mbm_new_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Create SIM (Modem interface) */
+
+static MMBaseSim *
+create_sim_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return mm_sim_mbm_new_finish (res, error);
+}
+
+static void
+create_sim (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* New MBM SIM */
+ mm_sim_mbm_new (MM_BASE_MODEM (self),
+ NULL, /* cancellable */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* After SIM unlock (Modem interface) */
+
+static gboolean
+modem_after_sim_unlock_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static gboolean
+after_sim_unlock_wait_cb (GTask *task)
+{
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+modem_after_sim_unlock (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* wait so sim pin is done */
+ g_timeout_add (500, (GSourceFunc)after_sim_unlock_wait_cb, task);
+}
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *_self,
+ GAsyncResult *res,
+ GError **error)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self);
+ const gchar *response;
+ guint32 mask = 0;
+ GArray *combinations;
+ MMModemModeCombination mode;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return FALSE;
+
+ if (!mm_mbm_parse_cfun_test (response, self, &mask, error))
+ return FALSE;
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3);
+
+ /* 2G only */
+ if (mask & (1 << MBM_NETWORK_MODE_2G)) {
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ /* 3G only */
+ if (mask & (1 << MBM_NETWORK_MODE_3G)) {
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ /* 2G and 3G */
+ if (mask & (1 << MBM_NETWORK_MODE_ANY)) {
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ if (combinations->len == 0) {
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't load any supported mode");
+ g_array_unref (combinations);
+ return NULL;
+ }
+
+ return combinations;
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load initial allowed/preferred modes (Modem interface) */
+
+static gboolean
+load_current_modes_finish (MMIfaceModem *_self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self);
+ const gchar *response;
+ gint mbm_mode = -1;
+
+ g_assert (allowed);
+ g_assert (preferred);
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response || !mm_mbm_parse_cfun_query_current_modes (response, allowed, &mbm_mode, error))
+ return FALSE;
+
+ /* No settings to set preferred */
+ *preferred = MM_MODEM_MODE_NONE;
+
+ if (mbm_mode != -1)
+ self->priv->mbm_mode = mbm_mode;
+
+ return TRUE;
+}
+
+static void
+load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Set allowed modes (Modem interface) */
+
+typedef struct {
+ gint mbm_mode;
+} SetCurrentModesContext;
+
+static gboolean
+set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+allowed_mode_update_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetCurrentModesContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ mm_base_modem_at_command_finish (self, res, &error);
+ if (error)
+ /* Let the error be critical. */
+ g_task_return_error (task, error);
+ else {
+ /* Cache current allowed mode */
+ MM_BROADBAND_MODEM_MBM (self)->priv->mbm_mode = ctx->mbm_mode;
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SetCurrentModesContext *ctx;
+ GTask *task;
+ gchar *command;
+
+ ctx = g_new (SetCurrentModesContext, 1);
+ ctx->mbm_mode = -1;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ if (allowed == MM_MODEM_MODE_2G)
+ ctx->mbm_mode = MBM_NETWORK_MODE_2G;
+ else if (allowed == MM_MODEM_MODE_3G)
+ ctx->mbm_mode = MBM_NETWORK_MODE_3G;
+ else if ((allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G) ||
+ allowed == MM_MODEM_MODE_ANY) &&
+ preferred == MM_MODEM_MODE_NONE)
+ ctx->mbm_mode = MBM_NETWORK_MODE_ANY;
+
+ if (ctx->mbm_mode < 0) {
+ gchar *allowed_str;
+ gchar *preferred_str;
+
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Requested mode (allowed: '%s', preferred: '%s') not "
+ "supported by the modem.",
+ allowed_str,
+ preferred_str);
+ g_object_unref (task);
+ g_free (allowed_str);
+ g_free (preferred_str);
+ return;
+ }
+
+ command = g_strdup_printf ("+CFUN=%d", ctx->mbm_mode);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)allowed_mode_update_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Initializing the modem (during first enabling) */
+
+static gboolean
+enabling_modem_init_finish (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+enabling_init_sequence_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ /* Ignore errors */
+ mm_base_modem_at_sequence_full_finish (self, res, NULL, NULL);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static const MMBaseModemAtCommand enabling_modem_init_sequence[] = {
+ /* Init command */
+ { "&F", 3, FALSE, NULL },
+ /* Ensure disconnected */
+ { "*ENAP=0", 3, FALSE, NULL },
+ { NULL }
+};
+
+static void
+run_enabling_init_sequence (GTask *task)
+{
+ MMBaseModem *self;
+
+ self = g_task_get_source_object (task);
+ mm_base_modem_at_sequence_full (self,
+ mm_base_modem_peek_port_primary (self),
+ enabling_modem_init_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)enabling_init_sequence_ready,
+ task);
+}
+
+static void
+emrdy_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ /* EMRDY unsolicited response might have happened between the command
+ * submission and the response. This was seen once:
+ *
+ * (ttyACM0): --> 'AT*EMRDY?<CR>'
+ * (ttyACM0): <-- 'T*EMRD<CR><LF>*EMRDY: 1<CR><LF>Y?'
+ *
+ * So suppress the warning if the unsolicited handler handled the response
+ * before we get here.
+ */
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ if (g_error_matches (error,
+ MM_SERIAL_ERROR,
+ MM_SERIAL_ERROR_RESPONSE_TIMEOUT))
+ mm_obj_warn (self, "timed out waiting for EMRDY response");
+ else
+ MM_BROADBAND_MODEM_MBM (self)->priv->have_emrdy = TRUE;
+ g_error_free (error);
+ }
+
+ run_enabling_init_sequence (task);
+}
+
+static void
+enabling_modem_init (MMBroadbandModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self);
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Modem is ready?, no need to check EMRDY */
+ if (self->priv->have_emrdy) {
+ run_enabling_init_sequence (task);
+ return;
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "*EMRDY?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)emrdy_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Modem power down (Modem interface) */
+
+static gboolean
+modem_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_power_down (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Use AT+CFUN=4 for power down. It will stop the RF (IMSI detach), and
+ * keeps access to the SIM */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=4",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Powering up the modem (Modem interface) */
+
+static gboolean
+modem_power_up_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ /* By default, errors in the power up command are ignored. */
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL);
+ return TRUE;
+}
+
+static void
+modem_power_up (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self);
+ gchar *command;
+
+ g_assert (self->priv->mbm_mode == MBM_NETWORK_MODE_ANY ||
+ self->priv->mbm_mode == MBM_NETWORK_MODE_2G ||
+ self->priv->mbm_mode == MBM_NETWORK_MODE_3G);
+
+ command = g_strdup_printf ("+CFUN=%u", self->priv->mbm_mode);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ command,
+ 5,
+ FALSE,
+ callback,
+ user_data);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Power state loading (Modem interface) */
+
+static MMModemPowerState
+load_power_state_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *response;
+ MMModemPowerState state;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response || !mm_mbm_parse_cfun_query_power_state (response, &state, error))
+ return MM_MODEM_POWER_STATE_UNKNOWN;
+
+ return state;
+}
+
+static void
+load_power_state (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Reset (Modem interface) */
+
+static gboolean
+reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ /* Ignore errors */
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL);
+ return TRUE;
+}
+
+static void
+reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "*E2RESET",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Factory reset (Modem interface) */
+
+static gboolean
+factory_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ /* Ignore errors */
+ mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, NULL);
+ return TRUE;
+}
+
+static const MMBaseModemAtCommand factory_reset_sequence[] = {
+ /* Init command */
+ { "&F +CMEE=0", 3, FALSE, NULL },
+ { "+COPS=0", 3, FALSE, NULL },
+ { "+CR=0", 3, FALSE, NULL },
+ { "+CRC=0", 3, FALSE, NULL },
+ { "+CREG=0", 3, FALSE, NULL },
+ { "+CMER=0", 3, FALSE, NULL },
+ { "*EPEE=0", 3, FALSE, NULL },
+ { "+CNMI=2, 0, 0, 0, 0", 3, FALSE, NULL },
+ { "+CGREG=0", 3, FALSE, NULL },
+ { "*EIAD=0", 3, FALSE, NULL },
+ { "+CGSMS=3", 3, FALSE, NULL },
+ { "+CSCA=\"\",129", 3, FALSE, NULL },
+ { NULL }
+};
+
+static void
+factory_reset (MMIfaceModem *self,
+ const gchar *code,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_obj_dbg (self, "ignoring user-provided factory reset code: '%s'", code);
+
+ mm_base_modem_at_sequence (MM_BASE_MODEM (self),
+ factory_reset_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load unlock retries (Modem interface) */
+
+static MMUnlockRetries *
+load_unlock_retries_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ MMUnlockRetries *unlock_retries;
+ const gchar *response;
+ gint matched;
+ guint a, b, c ,d;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return NULL;
+
+ matched = sscanf (response, "*EPIN: %d, %d, %d, %d",
+ &a, &b, &c, &d);
+ if (matched != 4) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Could not parse PIN retries results: '%s'",
+ response);
+ return NULL;
+ }
+
+ if (a > 998) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Invalid PIN attempts left: '%u'",
+ a);
+ return NULL;
+ }
+
+ unlock_retries = mm_unlock_retries_new ();
+ mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PIN, a);
+ mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PUK, b);
+ mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PIN2, c);
+ mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PUK2, d);
+ return unlock_retries;
+}
+
+static void
+load_unlock_retries (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "*EPIN?",
+ 10,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (3GPP interface) */
+
+typedef struct {
+ MMBearerConnectionStatus status;
+} BearerListReportStatusForeachContext;
+
+static void
+bearer_list_report_status_foreach (MMBaseBearer *bearer,
+ BearerListReportStatusForeachContext *ctx)
+{
+ mm_base_bearer_report_connection_status (bearer, ctx->status);
+}
+
+static void
+e2nap_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMBroadbandModemMbm *self)
+{
+ MMBearerList *list = NULL;
+ guint state;
+ BearerListReportStatusForeachContext ctx;
+
+ if (!mm_get_uint_from_match_info (info, 1, &state))
+ return;
+
+ ctx.status = MM_BEARER_CONNECTION_STATUS_UNKNOWN;
+
+ switch (state) {
+ case MBM_E2NAP_DISCONNECTED:
+ mm_obj_dbg (self, "disconnected");
+ ctx.status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED;
+ break;
+ case MBM_E2NAP_CONNECTED:
+ mm_obj_dbg (self, "connected");
+ ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTED;
+ break;
+ case MBM_E2NAP_CONNECTING:
+ mm_obj_dbg (self, "connecting");
+ break;
+ default:
+ /* Should not happen */
+ mm_obj_dbg (self, "unhandled E2NAP state %d", state);
+ }
+
+ /* If unknown status, don't try to report anything */
+ if (ctx.status == MM_BEARER_CONNECTION_STATUS_UNKNOWN)
+ return;
+
+ /* If empty bearer list, nothing else to do */
+ g_object_get (self,
+ MM_IFACE_MODEM_BEARER_LIST, &list,
+ NULL);
+ if (!list)
+ return;
+
+ mm_bearer_list_foreach (list,
+ (MMBearerListForeachFunc)bearer_list_report_status_foreach,
+ &ctx);
+ g_object_unref (list);
+}
+
+static void
+erinfo_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMBroadbandModemMbm *self)
+{
+ MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ guint mode;
+
+ if (mm_get_uint_from_match_info (info, 2, &mode)) {
+ switch (mode) {
+ case 1:
+ act = MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
+ break;
+ case 2:
+ act = MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* 3G modes take precedence */
+ if (mm_get_uint_from_match_info (info, 3, &mode)) {
+ switch (mode) {
+ case 1:
+ act = MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ break;
+ case 2:
+ act = MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
+ break;
+ case 3:
+ act = MM_MODEM_ACCESS_TECHNOLOGY_HSPA;
+ break;
+ default:
+ break;
+ }
+ }
+
+ mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
+ act,
+ MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
+}
+
+static void
+set_unsolicited_events_handlers (MMBroadbandModemMbm *self,
+ gboolean enable)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ 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 unsolicited events in given port */
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ /* Access technology related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->erinfo_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)erinfo_received : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ /* Connection related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->e2nap_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)e2nap_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->e2nap_ext_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)e2nap_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+static gboolean
+modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else {
+ /* Our own setup now */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MBM (self), TRUE);
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's setup */
+ iface_modem_3gpp_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_setup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+static void
+parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Our own cleanup first */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MBM (self), FALSE);
+
+ /* And now chain up parent's cleanup */
+ iface_modem_3gpp_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Enabling unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+own_enable_unsolicited_events_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_full_finish (self, res, NULL, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static const MMBaseModemAtCommand unsolicited_enable_sequence[] = {
+ { "*ERINFO=1", 5, FALSE, NULL },
+ { "*E2NAP=1", 5, FALSE, NULL },
+ { NULL }
+};
+
+static void
+parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Our own enable now */
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ unsolicited_enable_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)own_enable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's enable */
+ iface_modem_3gpp_parent->enable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_enable_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Disabling unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+own_disable_unsolicited_events_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_full_finish (self, res, NULL, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Next, chain up parent's disable */
+ iface_modem_3gpp_parent->disable_unsolicited_events (
+ MM_IFACE_MODEM_3GPP (self),
+ (GAsyncReadyCallback)parent_disable_unsolicited_events_ready,
+ task);
+}
+
+static const MMBaseModemAtCommand unsolicited_disable_sequence[] = {
+ { "*ERINFO=0", 5, FALSE, NULL },
+ { "*E2NAP=0", 5, FALSE, NULL },
+ { NULL }
+};
+
+static void
+modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Our own disable first */
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ unsolicited_disable_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)own_disable_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Location capabilities loading (Location interface) */
+
+static MMModemLocationSource
+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
+parent_load_capabilities_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource sources;
+ GError *error = NULL;
+
+ sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* not sure how to check if GPS is supported, just allow it */
+ if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)))
+ sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED);
+
+ /* So we're done, complete */
+ g_task_return_int (task, sources);
+ g_object_unref (task);
+}
+
+static void
+location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's setup */
+ iface_modem_location_parent->load_capabilities (
+ self,
+ (GAsyncReadyCallback)parent_load_capabilities_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Enable/Disable location gathering (Location interface) */
+
+typedef struct {
+ MMModemLocationSource source;
+} LocationGatheringContext;
+
+/******************************/
+/* Disable location gathering */
+
+static gboolean
+disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+gps_disabled_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LocationGatheringContext *ctx;
+ MMPortSerialGps *gps_port;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ mm_base_modem_at_command_full_finish (self, res, &error);
+
+ /* Only use the GPS port in NMEA/RAW setups */
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ /* Even if we get an error here, we try to close the GPS port */
+ gps_port = mm_base_modem_peek_port_gps (self);
+ if (gps_port)
+ mm_port_serial_close (MM_PORT_SERIAL (gps_port));
+ }
+
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+disable_location_gathering (MMIfaceModemLocation *_self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self);
+ gboolean stop_gps = FALSE;
+ LocationGatheringContext *ctx;
+ GTask *task;
+
+ ctx = g_new (LocationGatheringContext, 1);
+ ctx->source = source;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ /* Only stop GPS engine if no GPS-related sources enabled */
+ if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ self->priv->enabled_sources &= ~source;
+
+ if (!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)))
+ stop_gps = TRUE;
+ }
+
+ if (stop_gps) {
+ mm_base_modem_at_command_full (MM_BASE_MODEM (_self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (_self)),
+ "AT*E2GPSCTL=0",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)gps_disabled_ready,
+ task);
+ return;
+ }
+
+ /* For any other location (e.g. 3GPP), or if still some GPS needed, just return */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Enable location gathering (Location interface) */
+
+static gboolean
+enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+gps_enabled_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LocationGatheringContext *ctx;
+ GError *error = NULL;
+ MMPortSerialGps *gps_port;
+
+ if (!mm_base_modem_at_command_full_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ /* Only use the GPS port in NMEA/RAW setups */
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ gps_port = mm_base_modem_peek_port_gps (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 {
+ GByteArray *buf;
+ const gchar *command = "ATE0*E2GPSNPD\r\n";
+
+ /* We need to send an AT command to the GPS data port to
+ * toggle it into this data mode. This is a particularity of
+ * mbm cards where the GPS data port is not hard wired. So
+ * we need to use the MMPortSerial API here.
+ */
+ buf = g_byte_array_new ();
+ g_byte_array_append (buf, (const guint8 *) command, strlen (command));
+ mm_port_serial_command (MM_PORT_SERIAL (gps_port),
+ buf,
+ 3,
+ FALSE, /* never cached */
+ FALSE, /* always queued last */
+ NULL,
+ NULL,
+ NULL);
+ g_byte_array_unref (buf);
+ g_task_return_boolean (task, TRUE);
+ }
+
+ } else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+parent_enable_location_gathering_ready (MMIfaceModemLocation *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self);
+ LocationGatheringContext *ctx;
+ gboolean start_gps = FALSE;
+ GError *error = NULL;
+
+ if (!iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Now our own enabling */
+
+ /* NMEA and RAW are both enabled in the same way */
+ ctx = g_task_get_task_data (task);
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ /* Only start GPS engine if not done already */
+ if (!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)))
+ start_gps = TRUE;
+ self->priv->enabled_sources |= ctx->source;
+ }
+
+ if (start_gps) {
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ "AT*E2GPSCTL=1," MBM_GPS_NMEA_INTERVAL ",0",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)gps_enabled_ready,
+ task);
+ return;
+ }
+
+ /* For any other location (e.g. 3GPP), or if GPS already running just return */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ LocationGatheringContext *ctx;
+ GTask *task;
+
+ ctx = g_new (LocationGatheringContext, 1);
+ ctx->source = source;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ /* Chain up parent's gathering enable */
+ iface_modem_location_parent->enable_location_gathering (self,
+ source,
+ (GAsyncReadyCallback)parent_enable_location_gathering_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+emrdy_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMBroadbandModemMbm *self)
+{
+ self->priv->have_emrdy = TRUE;
+}
+
+static void
+gps_trace_received (MMPortSerialGps *port,
+ const gchar *trace,
+ MMIfaceModemLocation *self)
+{
+ mm_iface_modem_location_gps_update (self, trace);
+}
+
+static void
+setup_ports (MMBroadbandModem *_self)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self);
+ MMPortSerialAt *ports[2];
+ MMPortSerialGps *gps_data_port;
+ guint i;
+
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mbm_parent_class)->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));
+
+ /* Setup unsolicited handlers which should be always on */
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ /* The Ericsson modems always have a free AT command port, so we
+ * don't need to flash the ports when disconnecting to get back to
+ * command mode. F5521gw R2A07 resets port properties like echo when
+ * flashed, leading to confusion. bgo #650740
+ */
+ g_object_set (G_OBJECT (ports[i]),
+ MM_PORT_SERIAL_FLASH_OK, FALSE,
+ NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->emrdy_regex,
+ (MMPortSerialAtUnsolicitedMsgFn)emrdy_received,
+ self,
+ NULL);
+
+ /* Several unsolicited messages to always ignore... */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->pacsp_regex,
+ NULL, NULL, NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->estksmenu_regex,
+ NULL, NULL, NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->estksms_regex,
+ NULL, NULL, NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->emwi_regex,
+ NULL, NULL, NULL);
+ }
+
+ /* Now reset the unsolicited messages we'll handle when enabled */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MBM (self), FALSE);
+
+ /* NMEA GPS monitoring */
+ gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self));
+ if (gps_data_port) {
+ /* make sure GPS is stopped incase it was left enabled */
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ "AT*E2GPSCTL=0",
+ 3, FALSE, FALSE, NULL, NULL, NULL);
+ /* Add handler for the NMEA traces */
+ mm_port_serial_gps_add_trace_handler (gps_data_port,
+ (MMPortSerialGpsTraceFn)gps_trace_received,
+ self, NULL);
+ }
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemMbm *
+mm_broadband_modem_mbm_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_MBM,
+ 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,
+ /* MBM bearer supports NET only */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_mbm_init (MMBroadbandModemMbm *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_MODEM_MBM,
+ MMBroadbandModemMbmPrivate);
+
+ /* Prepare regular expressions to setup */
+ self->priv->e2nap_regex = g_regex_new ("\\r\\n\\*E2NAP: (\\d)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->e2nap_ext_regex = g_regex_new ("\\r\\n\\*E2NAP: (\\d),.*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->emrdy_regex = g_regex_new ("\\r\\n\\*EMRDY: \\d\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->pacsp_regex = g_regex_new ("\\r\\n\\+PACSP(\\d)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->estksmenu_regex = g_regex_new ("\\R\\*ESTKSMENU:.*\\R",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF, G_REGEX_MATCH_NEWLINE_CRLF, NULL);
+ self->priv->estksms_regex = g_regex_new ("\\r\\n\\*ESTKSMS:.*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->emwi_regex = g_regex_new ("\\r\\n\\*EMWI: (\\d),(\\d).*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->erinfo_regex = g_regex_new ("\\r\\n\\*ERINFO:\\s*(\\d),(\\d),(\\d).*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ self->priv->mbm_mode = MBM_NETWORK_MODE_ANY;
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (object);
+
+ g_regex_unref (self->priv->e2nap_regex);
+ g_regex_unref (self->priv->e2nap_ext_regex);
+ g_regex_unref (self->priv->emrdy_regex);
+ g_regex_unref (self->priv->pacsp_regex);
+ g_regex_unref (self->priv->estksmenu_regex);
+ g_regex_unref (self->priv->estksms_regex);
+ g_regex_unref (self->priv->emwi_regex);
+ g_regex_unref (self->priv->erinfo_regex);
+
+ G_OBJECT_CLASS (mm_broadband_modem_mbm_parent_class)->finalize (object);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->create_bearer = modem_create_bearer;
+ iface->create_bearer_finish = modem_create_bearer_finish;
+ iface->create_sim = create_sim;
+ iface->create_sim_finish = create_sim_finish;
+ iface->modem_after_sim_unlock = modem_after_sim_unlock;
+ iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish;
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = load_current_modes;
+ iface->load_current_modes_finish = load_current_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = set_current_modes_finish;
+ iface->reset = reset;
+ iface->reset_finish = reset_finish;
+ iface->factory_reset = factory_reset;
+ iface->factory_reset_finish = factory_reset_finish;
+ iface->load_unlock_retries = load_unlock_retries;
+ iface->load_unlock_retries_finish = load_unlock_retries_finish;
+ iface->load_power_state = load_power_state;
+ iface->load_power_state_finish = load_power_state_finish;
+ iface->modem_power_up = modem_power_up;
+ iface->modem_power_up_finish = modem_power_up_finish;
+ iface->modem_power_down = modem_power_down;
+ iface->modem_power_down_finish = modem_power_down_finish;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish;
+
+ iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = location_load_capabilities;
+ iface->load_capabilities_finish = location_load_capabilities_finish;
+ iface->enable_location_gathering = enable_location_gathering;
+ iface->enable_location_gathering_finish = enable_location_gathering_finish;
+ iface->disable_location_gathering = disable_location_gathering;
+ iface->disable_location_gathering_finish = disable_location_gathering_finish;
+}
+
+static void
+mm_broadband_modem_mbm_class_init (MMBroadbandModemMbmClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemMbmPrivate));
+
+ object_class->finalize = finalize;
+ broadband_modem_class->setup_ports = setup_ports;
+ broadband_modem_class->enabling_modem_init = enabling_modem_init;
+ broadband_modem_class->enabling_modem_init_finish = enabling_modem_init_finish;
+}
diff --git a/src/plugins/mbm/mm-broadband-modem-mbm.h b/src/plugins/mbm/mm-broadband-modem-mbm.h
new file mode 100644
index 00000000..21eeaef5
--- /dev/null
+++ b/src/plugins/mbm/mm-broadband-modem-mbm.h
@@ -0,0 +1,58 @@
+/* -*- 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) 2008 - 2010 Ericsson AB
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ *
+ * Author: Per Hallsmark <per.hallsmark@ericsson.com>
+ * Bjorn Runaker <bjorn.runaker@ericsson.com>
+ * Torgny Johansson <torgny.johansson@ericsson.com>
+ * Jonas Sjöquist <jonas.sjoquist@ericsson.com>
+ * Dan Williams <dcbw@redhat.com>
+ * Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#ifndef MM_BROADBAND_MODEM_MBM_H
+#define MM_BROADBAND_MODEM_MBM_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_MBM (mm_broadband_modem_mbm_get_type ())
+#define MM_BROADBAND_MODEM_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBM, MMBroadbandModemMbm))
+#define MM_BROADBAND_MODEM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBM, MMBroadbandModemMbmClass))
+#define MM_IS_BROADBAND_MODEM_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBM))
+#define MM_IS_BROADBAND_MODEM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBM))
+#define MM_BROADBAND_MODEM_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBM, MMBroadbandModemMbmClass))
+
+typedef struct _MMBroadbandModemMbm MMBroadbandModemMbm;
+typedef struct _MMBroadbandModemMbmClass MMBroadbandModemMbmClass;
+typedef struct _MMBroadbandModemMbmPrivate MMBroadbandModemMbmPrivate;
+
+struct _MMBroadbandModemMbm {
+ MMBroadbandModem parent;
+ MMBroadbandModemMbmPrivate *priv;
+};
+
+struct _MMBroadbandModemMbmClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_mbm_get_type (void);
+
+MMBroadbandModemMbm *mm_broadband_modem_mbm_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_MBM_H */
diff --git a/src/plugins/mbm/mm-modem-helpers-mbm.c b/src/plugins/mbm/mm-modem-helpers-mbm.c
new file mode 100644
index 00000000..846cc4d6
--- /dev/null
+++ b/src/plugins/mbm/mm-modem-helpers-mbm.c
@@ -0,0 +1,337 @@
+/* -*- 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) 2012 Google, Inc.
+ * Copyright (C) 2012 - 2013 Aleksander Morgado <aleksander@gnu.org>
+ * Copyright (C) 2014 Dan Williams <dcbw@redhat.com>
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <netinet/in.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-mbm.h"
+
+/*****************************************************************************/
+/* *E2IPCFG response parser */
+
+static gboolean
+validate_address (int family, const char *addr)
+{
+ struct in6_addr tmp6 = IN6ADDR_ANY_INIT;
+
+ if (inet_pton (family, addr, (void *) &tmp6) != 1)
+{
+g_message ("%s: famil '%s'", __func__, addr);
+ return FALSE;
+}
+ if ((family == AF_INET6) && IN6_IS_ADDR_UNSPECIFIED (&tmp6))
+ return FALSE;
+ return TRUE;
+}
+
+#define E2IPCFG_TAG "*E2IPCFG"
+
+gboolean
+mm_mbm_parse_e2ipcfg_response (const gchar *response,
+ MMBearerIpConfig **out_ip4_config,
+ MMBearerIpConfig **out_ip6_config,
+ GError **error)
+{
+ MMBearerIpConfig **ip_config = NULL;
+ gboolean got_address = FALSE;
+ gboolean got_gw = FALSE;
+ gboolean got_dns = FALSE;
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *match_error = NULL;
+ gchar *dns[3] = { 0 };
+ guint dns_idx = 0;
+ int family = AF_INET;
+ MMBearerIpMethod method = MM_BEARER_IP_METHOD_STATIC;
+
+ g_return_val_if_fail (out_ip4_config, FALSE);
+ g_return_val_if_fail (out_ip6_config, FALSE);
+
+ if (!response || !g_str_has_prefix (response, E2IPCFG_TAG)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing " E2IPCFG_TAG " prefix");
+ return FALSE;
+ }
+
+ response = mm_strip_tag (response, "*E2IPCFG: ");
+
+ if (strchr (response, ':')) {
+ family = AF_INET6;
+ ip_config = out_ip6_config;
+ method = MM_BEARER_IP_METHOD_DHCP;
+ } else if (strchr (response, '.')) {
+ family = AF_INET;
+ ip_config = out_ip4_config;
+ method = MM_BEARER_IP_METHOD_STATIC;
+ } else {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Failed to detect " E2IPCFG_TAG " address family");
+ return FALSE;
+ }
+
+ /* *E2IPCFG: (1,<IP>)(2,<gateway>)(3,<DNS>)(3,<DNS>)
+ *
+ * *E2IPCFG: (1,"46.157.32.246")(2,"46.157.32.243")(3,"193.213.112.4")(3,"130.67.15.198")
+ * *E2IPCFG: (1,"fe80:0000:0000:0000:0000:0000:e537:1801")(3,"2001:4600:0004:0fff:0000:0000:0000:0054")(3,"2001:4600:0004:1fff:0000:0000:0000:0054")
+ * *E2IPCFG: (1,"fe80:0000:0000:0000:0000:0027:b7fe:9401")(3,"fd00:976a:0000:0000:0000:0000:0000:0009")
+ */
+ r = g_regex_new ("\\((\\d),\"([0-9a-fA-F.:]+)\"\\)", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse " E2IPCFG_TAG " results: ");
+ } else {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't match " E2IPCFG_TAG " reply");
+ }
+ return FALSE;
+ }
+
+ *ip_config = mm_bearer_ip_config_new ();
+ mm_bearer_ip_config_set_method (*ip_config, method);
+ while (g_match_info_matches (match_info)) {
+ g_autofree gchar *id = NULL;
+ g_autofree gchar *str = NULL;
+
+ id = g_match_info_fetch (match_info, 1);
+ str = g_match_info_fetch (match_info, 2);
+
+ switch (atoi (id)) {
+ case 1:
+ if (validate_address (family, str)) {
+ mm_bearer_ip_config_set_address (*ip_config, str);
+ mm_bearer_ip_config_set_prefix (*ip_config, (family == AF_INET6) ? 64 : 28);
+ got_address = TRUE;
+ }
+ break;
+ case 2:
+ if ((family == AF_INET) && validate_address (family, str)) {
+ mm_bearer_ip_config_set_gateway (*ip_config, str);
+ got_gw = TRUE;
+ }
+ break;
+ case 3:
+ if (validate_address (family, str)) {
+ dns[dns_idx++] = g_strdup (str);
+ got_dns = TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+ g_match_info_next (match_info, NULL);
+ }
+
+ if (got_dns) {
+ mm_bearer_ip_config_set_dns (*ip_config, (const gchar **) dns);
+ g_free (dns[0]);
+ g_free (dns[1]);
+ }
+
+ if (!got_address || (family == AF_INET && !got_gw)) {
+ g_object_unref (*ip_config);
+ *ip_config = NULL;
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Got incomplete IP configuration from " E2IPCFG_TAG);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+#define CFUN_TAG "+CFUN:"
+
+static void
+add_supported_mode (guint mode,
+ gpointer log_object,
+ guint32 *mask)
+{
+ g_assert (mask);
+ if (mode >= 32)
+ mm_obj_warn (log_object, "ignored unexpected mode in +CFUN match: %d", mode);
+ else
+ *mask |= (1 << mode);
+}
+
+gboolean
+mm_mbm_parse_cfun_test (const gchar *response,
+ gpointer log_object,
+ guint32 *supported_mask,
+ GError **error)
+{
+ gchar **groups;
+ guint32 mask = 0;
+
+ g_assert (supported_mask);
+
+ if (!response || !g_str_has_prefix (response, CFUN_TAG)) {
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Missing " CFUN_TAG " prefix");
+ return FALSE;
+ }
+
+ /*
+ * AT+CFUN=?
+ * +CFUN: (0,1,4-6),(0,1)
+ * OK
+ */
+
+ /* Strip tag from response */
+ response = mm_strip_tag (response, CFUN_TAG);
+
+ /* Split response in (groups) */
+ groups = mm_split_string_groups (response);
+
+ /* First group is the one listing supported modes */
+ if (groups && groups[0]) {
+ gchar **supported_modes;
+
+ supported_modes = g_strsplit_set (groups[0], ", ", -1);
+ if (supported_modes) {
+ guint i;
+
+ for (i = 0; supported_modes[i]; i++) {
+ gchar *separator;
+ guint mode;
+
+ if (!supported_modes[i][0])
+ continue;
+
+ /* Check if this is a range that's being given to us */
+ separator = strchr (supported_modes[i], '-');
+ if (separator) {
+ gchar *first_str;
+ gchar *last_str;
+ guint first;
+ guint last;
+
+ *separator = '\0';
+ first_str = supported_modes[i];
+ last_str = separator + 1;
+
+ if (!mm_get_uint_from_str (first_str, &first))
+ mm_obj_warn (log_object, "couldn't match range start: '%s'", first_str);
+ else if (!mm_get_uint_from_str (last_str, &last))
+ mm_obj_warn (log_object, "couldn't match range stop: '%s'", last_str);
+ else if (first >= last)
+ mm_obj_warn (log_object, "couldn't match range: wrong first '%s' and last '%s' items", first_str, last_str);
+ else {
+ for (mode = first; mode <= last; mode++)
+ add_supported_mode (mode, log_object, &mask);
+ }
+ } else {
+ if (!mm_get_uint_from_str (supported_modes[i], &mode))
+ mm_obj_warn (log_object, "couldn't match mode: '%s'", supported_modes[i]);
+ else
+ add_supported_mode (mode, log_object, &mask);
+ }
+ }
+
+ g_strfreev (supported_modes);
+ }
+ }
+ g_strfreev (groups);
+
+ if (mask)
+ *supported_mask = mask;
+ return !!mask;
+}
+
+/*****************************************************************************/
+/* AT+CFUN? response parsers */
+
+gboolean
+mm_mbm_parse_cfun_query_power_state (const gchar *response,
+ MMModemPowerState *out_state,
+ GError **error)
+{
+ guint state;
+
+ if (!mm_3gpp_parse_cfun_query_response (response, &state, error))
+ return FALSE;
+
+ switch (state) {
+ case MBM_NETWORK_MODE_OFFLINE:
+ *out_state = MM_MODEM_POWER_STATE_OFF;
+ return TRUE;
+ case MBM_NETWORK_MODE_LOW_POWER:
+ *out_state = MM_MODEM_POWER_STATE_LOW;
+ return TRUE;
+ case MBM_NETWORK_MODE_ANY:
+ case MBM_NETWORK_MODE_2G:
+ case MBM_NETWORK_MODE_3G:
+ *out_state = MM_MODEM_POWER_STATE_ON;
+ return TRUE;
+ default:
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown +CFUN pòwer state: '%u'", state);
+ return FALSE;
+ }
+}
+
+gboolean
+mm_mbm_parse_cfun_query_current_modes (const gchar *response,
+ MMModemMode *allowed,
+ gint *mbm_mode,
+ GError **error)
+{
+ guint state;
+
+ g_assert (mbm_mode);
+ g_assert (allowed);
+
+ if (!mm_3gpp_parse_cfun_query_response (response, &state, error))
+ return FALSE;
+
+ switch (state) {
+ case MBM_NETWORK_MODE_OFFLINE:
+ case MBM_NETWORK_MODE_LOW_POWER:
+ /* Do not update mbm_mode */
+ *allowed = MM_MODEM_MODE_NONE;
+ return TRUE;
+ case MBM_NETWORK_MODE_2G:
+ *mbm_mode = MBM_NETWORK_MODE_2G;
+ *allowed = MM_MODEM_MODE_2G;
+ return TRUE;
+ case MBM_NETWORK_MODE_3G:
+ *mbm_mode = MBM_NETWORK_MODE_3G;
+ *allowed = MM_MODEM_MODE_3G;
+ return TRUE;
+ case MBM_NETWORK_MODE_ANY:
+ /* Do not update mbm_mode */
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ return TRUE;
+ default:
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown +CFUN current mode: '%u'", state);
+ return FALSE;
+ }
+}
diff --git a/src/plugins/mbm/mm-modem-helpers-mbm.h b/src/plugins/mbm/mm-modem-helpers-mbm.h
new file mode 100644
index 00000000..3e3bf57a
--- /dev/null
+++ b/src/plugins/mbm/mm-modem-helpers-mbm.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2014 Dan Williams <dcbw@redhat.com>
+ */
+
+#ifndef MM_MODEM_HELPERS_MBM_H
+#define MM_MODEM_HELPERS_MBM_H
+
+#include "glib.h"
+
+/* *E2IPCFG response parser */
+gboolean mm_mbm_parse_e2ipcfg_response (const gchar *response,
+ MMBearerIpConfig **out_ip4_config,
+ MMBearerIpConfig **out_ip6_config,
+ GError **error);
+
+typedef enum {
+ MBM_NETWORK_MODE_OFFLINE = 0,
+ MBM_NETWORK_MODE_ANY = 1,
+ MBM_NETWORK_MODE_LOW_POWER = 4,
+ MBM_NETWORK_MODE_2G = 5,
+ MBM_NETWORK_MODE_3G = 6,
+} MbmNetworkMode;
+
+/* AT+CFUN=? test parser
+ * Returns a bitmask, bit index set for the supported modes reported */
+gboolean mm_mbm_parse_cfun_test (const gchar *response,
+ gpointer log_object,
+ guint32 *supported_mask,
+ GError **error);
+
+/* AT+CFUN? response parsers */
+gboolean mm_mbm_parse_cfun_query_power_state (const gchar *response,
+ MMModemPowerState *out_state,
+ GError **error);
+gboolean mm_mbm_parse_cfun_query_current_modes (const gchar *response,
+ MMModemMode *allowed,
+ gint *mbm_mode,
+ GError **error);
+
+#endif /* MM_MODEM_HELPERS_MBM_H */
diff --git a/src/plugins/mbm/mm-plugin-mbm.c b/src/plugins/mbm/mm-plugin-mbm.c
new file mode 100644
index 00000000..6790d8a8
--- /dev/null
+++ b/src/plugins/mbm/mm-plugin-mbm.c
@@ -0,0 +1,101 @@
+/* -*- 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) 2008 Ericsson AB
+ * Copyright (C) 2012 Lanedo GmbH
+ *
+ * Author: Per Hallsmark <per.hallsmark@ericsson.com>
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-plugin-mbm.h"
+#include "mm-broadband-modem-mbm.h"
+
+#if defined WITH_MBIM
+#include "mm-broadband-modem-mbim.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginMbm, mm_plugin_mbm, 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_MBIM
+ if (mm_port_probe_list_has_mbim_port (probes)) {
+ mm_obj_dbg (self, "MBIM-powered Ericsson modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_mbm_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", NULL };
+ static const gchar *udev_tags[] = {
+ "ID_MM_ERICSSON_MBM",
+ NULL
+ };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_MBM,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_UDEV_TAGS, udev_tags,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_MBIM, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_mbm_init (MMPluginMbm *self)
+{
+}
+
+static void
+mm_plugin_mbm_class_init (MMPluginMbmClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/mbm/mm-plugin-mbm.h b/src/plugins/mbm/mm-plugin-mbm.h
new file mode 100644
index 00000000..ac07d7e1
--- /dev/null
+++ b/src/plugins/mbm/mm-plugin-mbm.h
@@ -0,0 +1,43 @@
+/* -*- 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) 2008 Ericsson AB
+ * Copyright (C) 2012 Lanedo GmbH
+ *
+ * Author: Per Hallsmark <per.hallsmark@ericsson.com>
+ */
+
+#ifndef MM_PLUGIN_MBM_H
+#define MM_PLUGIN_MBM_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_MBM (mm_plugin_mbm_get_type ())
+#define MM_PLUGIN_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_MBM, MMPluginMbm))
+#define MM_PLUGIN_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_MBM, MMPluginMbmClass))
+#define MM_IS_PLUGIN_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_MBM))
+#define MM_IS_PLUGIN_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_MBM))
+#define MM_PLUGIN_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_MBM, MMPluginMbmClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginMbm;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginMbmClass;
+
+GType mm_plugin_mbm_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_MBM_H */
diff --git a/src/plugins/mbm/mm-sim-mbm.c b/src/plugins/mbm/mm-sim-mbm.c
new file mode 100644
index 00000000..d3f73954
--- /dev/null
+++ b/src/plugins/mbm/mm-sim-mbm.c
@@ -0,0 +1,242 @@
+/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-base-modem-at.h"
+#include "mm-sim-mbm.h"
+
+G_DEFINE_TYPE (MMSimMbm, mm_sim_mbm, MM_TYPE_BASE_SIM)
+
+/*****************************************************************************/
+/* SEND PIN/PUK (Generic implementation) */
+
+typedef struct {
+ MMBaseModem *modem;
+ guint retries;
+} SendPinPukContext;
+
+static void
+send_pin_puk_context_free (SendPinPukContext *ctx)
+{
+ g_object_unref (ctx->modem);
+ g_slice_free (SendPinPukContext, ctx);
+}
+
+static gboolean
+common_send_pin_puk_finish (MMBaseSim *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void wait_for_unlocked_status (GTask *task);
+
+static void
+cpin_query_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+
+ const gchar *result;
+
+ result = mm_base_modem_at_command_finish (modem, res, NULL);
+ if (result && strstr (result, "READY")) {
+ /* All done! */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Need to recheck */
+ wait_for_unlocked_status (task);
+}
+
+static gboolean
+cpin_query_cb (GTask *task)
+{
+ SendPinPukContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ mm_base_modem_at_command (ctx->modem,
+ "+CPIN?",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)cpin_query_ready,
+ task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+wait_for_unlocked_status (GTask *task)
+{
+ MMSimMbm *self;
+ SendPinPukContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* Oops... :/ */
+ if (ctx->retries == 0) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "PIN was sent but modem didn't report unlocked");
+ g_object_unref (task);
+ return;
+ }
+
+ /* Check status */
+ ctx->retries--;
+ mm_obj_dbg (self, "scheduling lock state check...");
+ g_timeout_add_seconds (1, (GSourceFunc)cpin_query_cb, task);
+}
+
+static void
+send_pin_puk_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SendPinPukContext *ctx;
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (modem, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* No explicit error sending the PIN/PUK, now check status until we have the
+ * expected lock status */
+ ctx = g_task_get_task_data (task);
+ ctx->retries = 3;
+ wait_for_unlocked_status (task);
+}
+
+static void
+common_send_pin_puk (MMBaseSim *self,
+ const gchar *pin,
+ const gchar *puk,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SendPinPukContext *ctx;
+ GTask *task;
+ gchar *command;
+
+ ctx = g_slice_new (SendPinPukContext);
+ g_object_get (self,
+ MM_BASE_SIM_MODEM, &ctx->modem,
+ NULL);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)send_pin_puk_context_free);
+
+ command = (puk ?
+ g_strdup_printf ("+CPIN=\"%s\",\"%s\"", puk, pin) :
+ g_strdup_printf ("+CPIN=\"%s\"", pin));
+ mm_base_modem_at_command (ctx->modem,
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)send_pin_puk_ready,
+ task);
+ g_free (command);
+}
+
+static void
+send_puk (MMBaseSim *self,
+ const gchar *puk,
+ const gchar *new_pin,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_send_pin_puk (self, new_pin, puk, callback, user_data);
+}
+
+static void
+send_pin (MMBaseSim *self,
+ const gchar *pin,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_send_pin_puk (self, pin, NULL, callback, user_data);
+}
+
+/*****************************************************************************/
+
+MMBaseSim *
+mm_sim_mbm_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *source;
+ GObject *sim;
+
+ source = g_async_result_get_source_object (res);
+ sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!sim)
+ return NULL;
+
+ /* Only export valid SIMs */
+ mm_base_sim_export (MM_BASE_SIM (sim));
+
+ return MM_BASE_SIM (sim);
+}
+
+void
+mm_sim_mbm_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (MM_TYPE_SIM_MBM,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_SIM_MODEM, modem,
+ "active", TRUE, /* by default always active */
+ NULL);
+}
+
+static void
+mm_sim_mbm_init (MMSimMbm *self)
+{
+}
+
+static void
+mm_sim_mbm_class_init (MMSimMbmClass *klass)
+{
+ MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass);
+
+ base_sim_class->send_pin = send_pin;
+ base_sim_class->send_pin_finish = common_send_pin_puk_finish;
+ base_sim_class->send_puk = send_puk;
+ base_sim_class->send_puk_finish = common_send_pin_puk_finish;
+}
diff --git a/src/plugins/mbm/mm-sim-mbm.h b/src/plugins/mbm/mm-sim-mbm.h
new file mode 100644
index 00000000..1d843242
--- /dev/null
+++ b/src/plugins/mbm/mm-sim-mbm.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_SIM_MBM_H
+#define MM_SIM_MBM_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "mm-base-sim.h"
+
+#define MM_TYPE_SIM_MBM (mm_sim_mbm_get_type ())
+#define MM_SIM_MBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_MBM, MMSimMbm))
+#define MM_SIM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_MBM, MMSimMbmClass))
+#define MM_IS_SIM_MBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_MBM))
+#define MM_IS_SIM_MBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_MBM))
+#define MM_SIM_MBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_MBM, MMSimMbmClass))
+
+typedef struct _MMSimMbm MMSimMbm;
+typedef struct _MMSimMbmClass MMSimMbmClass;
+
+struct _MMSimMbm {
+ MMBaseSim parent;
+};
+
+struct _MMSimMbmClass {
+ MMBaseSimClass parent;
+};
+
+GType mm_sim_mbm_get_type (void);
+
+void mm_sim_mbm_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseSim *mm_sim_mbm_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SIM_MBM_H */
diff --git a/src/plugins/mbm/tests/test-modem-helpers-mbm.c b/src/plugins/mbm/tests/test-modem-helpers-mbm.c
new file mode 100644
index 00000000..4169140a
--- /dev/null
+++ b/src/plugins/mbm/tests/test-modem-helpers-mbm.c
@@ -0,0 +1,268 @@
+/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org>
+ * Copyright (C) 2014 Dan Williams <dcbw@redhat.com>
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include <locale.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-test.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-mbm.h"
+
+/*****************************************************************************/
+/* Test *E2IPCFG responses */
+
+typedef struct {
+ const gchar *str;
+
+ /* IPv4 */
+ const gchar *ipv4_addr;
+ const gchar *ipv4_gw;
+ const gchar *ipv4_dns1;
+ const gchar *ipv4_dns2;
+
+ /* IPv6 */
+ const gchar *ipv6_addr;
+ const gchar *ipv6_dns1;
+ const gchar *ipv6_dns2;
+} E2ipcfgTest;
+
+static const E2ipcfgTest tests[] = {
+ { "*E2IPCFG: (1,\"46.157.32.246\")(2,\"46.157.32.243\")(3,\"193.213.112.4\")(3,\"130.67.15.198\")\r\n",
+ "46.157.32.246", "46.157.32.243", "193.213.112.4", "130.67.15.198",
+ NULL, NULL },
+
+ { "*E2IPCFG: (1,\"fe80:0000:0000:0000:0000:0000:e537:1801\")(3,\"2001:4600:0004:0fff:0000:0000:0000:0054\")(3,\"2001:4600:0004:1fff:0000:0000:0000:0054\")\r\n",
+ NULL, NULL, NULL, NULL,
+ "fe80:0000:0000:0000:0000:0000:e537:1801", "2001:4600:0004:0fff:0000:0000:0000:0054", "2001:4600:0004:1fff:0000:0000:0000:0054" },
+
+ { "*E2IPCFG: (1,\"fe80:0000:0000:0000:0000:0027:b7fe:9401\")(3,\"fd00:976a:0000:0000:0000:0000:0000:0009\")\r\n",
+ NULL, NULL, NULL, NULL,
+ "fe80:0000:0000:0000:0000:0027:b7fe:9401", "fd00:976a:0000:0000:0000:0000:0000:0009", NULL },
+
+ { NULL }
+};
+
+static void
+test_e2ipcfg (void)
+{
+ guint i;
+
+ for (i = 0; tests[i].str; i++) {
+ gboolean success;
+ GError *error = NULL;
+ MMBearerIpConfig *ipv4 = NULL;
+ MMBearerIpConfig *ipv6 = NULL;
+ const gchar **dns;
+ guint dnslen;
+
+ success = mm_mbm_parse_e2ipcfg_response (tests[i].str, &ipv4, &ipv6, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+
+ /* IPv4 */
+ if (tests[i].ipv4_addr) {
+ g_assert (ipv4);
+ g_assert_cmpint (mm_bearer_ip_config_get_method (ipv4), ==, MM_BEARER_IP_METHOD_STATIC);
+ g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv4), ==, tests[i].ipv4_addr);
+ g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv4), ==, 28);
+ g_assert_cmpstr (mm_bearer_ip_config_get_gateway (ipv4), ==, tests[i].ipv4_gw);
+
+ dns = mm_bearer_ip_config_get_dns (ipv4);
+ g_assert (dns);
+ dnslen = g_strv_length ((gchar **) dns);
+ if (tests[i].ipv4_dns2 != NULL)
+ g_assert_cmpint (dnslen, ==, 2);
+ else
+ g_assert_cmpint (dnslen, ==, 1);
+ g_assert_cmpstr (dns[0], ==, tests[i].ipv4_dns1);
+ g_assert_cmpstr (dns[1], ==, tests[i].ipv4_dns2);
+ g_object_unref (ipv4);
+ } else
+ g_assert (ipv4 == NULL);
+
+ /* IPv6 */
+ if (tests[i].ipv6_addr) {
+ struct in6_addr a6;
+ g_assert (ipv6);
+
+ g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv6), ==, tests[i].ipv6_addr);
+ g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv6), ==, 64);
+
+ g_assert (inet_pton (AF_INET6, mm_bearer_ip_config_get_address (ipv6), &a6));
+ if (IN6_IS_ADDR_LINKLOCAL (&a6))
+ g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_DHCP);
+ else
+ g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_STATIC);
+
+ dns = mm_bearer_ip_config_get_dns (ipv6);
+ g_assert (dns);
+ dnslen = g_strv_length ((gchar **) dns);
+ if (tests[i].ipv6_dns2 != NULL)
+ g_assert_cmpint (dnslen, ==, 2);
+ else
+ g_assert_cmpint (dnslen, ==, 1);
+ g_assert_cmpstr (dns[0], ==, tests[i].ipv6_dns1);
+ g_assert_cmpstr (dns[1], ==, tests[i].ipv6_dns2);
+ g_object_unref (ipv6);
+ } else
+ g_assert (ipv6 == NULL);
+ }
+}
+
+/*****************************************************************************/
+/* Test +CFUN test responses */
+
+#define MAX_MODES 32
+
+typedef struct {
+ const gchar *str;
+ guint32 expected_mask;
+} CfunTest;
+
+static const CfunTest cfun_tests[] = {
+ {
+ "+CFUN: (0,1,4-6),(1-0)\r\n",
+ ((1 << MBM_NETWORK_MODE_OFFLINE) |
+ (1 << MBM_NETWORK_MODE_ANY) |
+ (1 << MBM_NETWORK_MODE_LOW_POWER) |
+ (1 << MBM_NETWORK_MODE_2G) |
+ (1 << MBM_NETWORK_MODE_3G))
+ },
+ {
+ "+CFUN: (0,1,4-6)\r\n",
+ ((1 << MBM_NETWORK_MODE_OFFLINE) |
+ (1 << MBM_NETWORK_MODE_ANY) |
+ (1 << MBM_NETWORK_MODE_LOW_POWER) |
+ (1 << MBM_NETWORK_MODE_2G) |
+ (1 << MBM_NETWORK_MODE_3G))
+ },
+ {
+ "+CFUN: (0,1,4)\r\n",
+ ((1 << MBM_NETWORK_MODE_OFFLINE) |
+ (1 << MBM_NETWORK_MODE_ANY) |
+ (1 << MBM_NETWORK_MODE_LOW_POWER))
+ },
+ {
+ "+CFUN: (0,1)\r\n",
+ ((1 << MBM_NETWORK_MODE_OFFLINE) |
+ (1 << MBM_NETWORK_MODE_ANY))
+ },
+};
+
+static void
+test_cfun_test (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cfun_tests); i++) {
+ guint32 mask;
+ gboolean success;
+ GError *error = NULL;
+
+ success = mm_mbm_parse_cfun_test (cfun_tests[i].str, NULL, &mask, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+ g_assert_cmpuint (mask, ==, cfun_tests[i].expected_mask);
+ }
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ const gchar *str;
+ MMModemPowerState state;
+} CfunQueryPowerStateTest;
+
+static const CfunQueryPowerStateTest cfun_query_power_state_tests[] = {
+ { "+CFUN: 0", MM_MODEM_POWER_STATE_OFF },
+ { "+CFUN: 1", MM_MODEM_POWER_STATE_ON },
+ { "+CFUN: 4", MM_MODEM_POWER_STATE_LOW },
+ { "+CFUN: 5", MM_MODEM_POWER_STATE_ON },
+ { "+CFUN: 6", MM_MODEM_POWER_STATE_ON },
+};
+
+static void
+test_cfun_query_power_state (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cfun_query_power_state_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ MMModemPowerState state;
+
+ success = mm_mbm_parse_cfun_query_power_state (cfun_query_power_state_tests[i].str, &state, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+ g_assert_cmpuint (cfun_query_power_state_tests[i].state, ==, state);
+ }
+}
+
+typedef struct {
+ const gchar *str;
+ MMModemMode allowed;
+ gint mbm_mode;
+} CfunQueryCurrentModeTest;
+
+static const CfunQueryCurrentModeTest cfun_query_current_mode_tests[] = {
+ { "+CFUN: 0", MM_MODEM_MODE_NONE, -1 },
+ { "+CFUN: 1", MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, -1 },
+ { "+CFUN: 4", MM_MODEM_MODE_NONE, -1 },
+ { "+CFUN: 5", MM_MODEM_MODE_2G, MBM_NETWORK_MODE_2G },
+ { "+CFUN: 6", MM_MODEM_MODE_3G, MBM_NETWORK_MODE_3G },
+};
+
+static void
+test_cfun_query_current_modes (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cfun_query_current_mode_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ MMModemMode allowed = MM_MODEM_MODE_NONE;
+ gint mbm_mode = -1;
+
+ success = mm_mbm_parse_cfun_query_current_modes (cfun_query_current_mode_tests[i].str, &allowed, &mbm_mode, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+ g_assert_cmpuint (cfun_query_current_mode_tests[i].allowed, ==, allowed);
+ g_assert_cmpint (cfun_query_current_mode_tests[i].mbm_mode, ==, mbm_mode);
+ }
+}
+
+/*****************************************************************************/
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/mbm/e2ipcfg", test_e2ipcfg);
+ g_test_add_func ("/MM/mbm/cfun/test", test_cfun_test);
+ g_test_add_func ("/MM/mbm/cfun/query/power-state", test_cfun_query_power_state);
+ g_test_add_func ("/MM/mbm/cfun/query/current-modes", test_cfun_query_current_modes);
+
+ return g_test_run ();
+}
diff --git a/src/plugins/meson.build b/src/plugins/meson.build
new file mode 100644
index 00000000..9e081beb
--- /dev/null
+++ b/src/plugins/meson.build
@@ -0,0 +1,1027 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2021 Iñigo Martinez <inigomartinez@gmail.com>
+
+symbol_map = plugins_dir / 'symbol.map'
+ldflags = cc.get_supported_link_arguments('-Wl,--version-script,@0@'.format(symbol_map))
+
+# common service test support
+plugins_common_test_dep = []
+if enable_tests
+ sources = files(
+ 'tests/test-fixture.c',
+ 'tests/test-helpers.c',
+ 'tests/test-port-context.c',
+ )
+
+ deps = [
+ libhelpers_dep,
+ libmm_test_generated_dep
+ ]
+
+ libmm_test_common = library(
+ 'mm-test-common',
+ sources: sources,
+ include_directories: top_inc,
+ dependencies: deps + [gio_unix_dep],
+ c_args: '-DTEST_SERVICES="@0@"'.format(build_root / 'data/tests'),
+ )
+
+ libmm_test_common_dep = declare_dependency(
+ include_directories: 'tests',
+ dependencies: deps,
+ link_with: libmm_test_common,
+ )
+
+ plugins_common_test_dep += [ libmm_test_common_dep ]
+endif
+
+# plugins
+plugins = {}
+plugins_data = []
+plugins_udev_rules = []
+plugins_test_udev_rules_dir_c_args = []
+plugins_test_keyfile_c_args = []
+
+# never include static libs as deps when building
+# plugins or shared utils modules
+plugins_incs = [
+ top_inc,
+ src_inc,
+ kerneldevice_inc,
+]
+
+plugins_deps = [libmm_glib_dep]
+
+if enable_mbim
+ plugins_deps += mbim_glib_dep
+endif
+
+if enable_qmi
+ plugins_deps += qmi_glib_dep
+endif
+
+# common Fibocom support library (MBIM only)
+if plugins_shared['fibocom']
+ fibocom_inc = include_directories('fibocom')
+
+ c_args = '-DMM_MODULE_NAME="shared-fibocom"'
+
+ sources = files(
+ 'fibocom/mm-shared.c',
+ 'fibocom/mm-shared-fibocom.c',
+ )
+
+ plugins += {'shared-fibocom': {
+ 'plugin': false,
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': c_args},
+ }}
+endif
+
+# Common Foxconn modem support library (MBIM only)
+if plugins_shared['foxconn']
+ foxconn_inc = include_directories('foxconn')
+
+ sources = files(
+ 'foxconn/mm-broadband-modem-mbim-foxconn.c',
+ 'foxconn/mm-shared.c',
+ )
+
+ c_args = [
+ '-DMM_MODULE_NAME="shared-foxconn"',
+ '-DPKGDATADIR="@0@"'.format(mm_pkgdatadir),
+ ]
+
+ plugins += {'shared-foxconn': {
+ 'plugin': false,
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': c_args},
+ }}
+endif
+
+# common icera support
+if plugins_shared['icera']
+ icera_inc = include_directories('icera')
+
+ common_c_args = '-DMM_MODULE_NAME="shared-icera"'
+
+ sources = files(
+ 'icera/mm-broadband-bearer-icera.c',
+ 'icera/mm-broadband-modem-icera.c',
+ 'icera/mm-shared.c',
+ )
+
+ plugins += {'shared-icera': {
+ 'plugin': false,
+ 'helper': {'sources': files('icera/mm-modem-helpers-icera.c'), 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'module': {'sources': sources + daemon_enums_sources, 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'test': {'sources': files('icera/tests/test-modem-helpers-icera.c'), 'include_directories': plugins_incs + [icera_inc], 'dependencies': libhelpers_dep},
+ }}
+endif
+
+# common novatel support
+if plugins_shared['novatel']
+ novatel_inc = include_directories('novatel')
+
+ sources = files(
+ 'novatel/mm-broadband-modem-novatel.c',
+ 'novatel/mm-common-novatel.c',
+ 'novatel/mm-shared.c',
+ )
+
+ plugins += {'shared-novatel': {
+ 'plugin': false,
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="shared-novatel"'},
+ }}
+endif
+
+# common option support
+if plugins_shared['option']
+ sources = files(
+ 'option/mm-broadband-modem-option.c',
+ 'option/mm-shared.c',
+ 'option/mm-shared-option.c',
+ 'option/mm-sim-option.c',
+ )
+
+ plugins += {'shared-option': {
+ 'plugin': false,
+ 'module': {'sources': sources, 'include_directories': plugins_incs},
+ }}
+endif
+
+# common sierra support
+if plugins_shared['sierra']
+ sierra_inc = include_directories('sierra')
+
+ common_c_args = '-DMM_MODULE_NAME="shared-sierra"'
+
+ sources = files(
+ 'sierra/mm-broadband-bearer-sierra.c',
+ 'sierra/mm-broadband-modem-sierra.c',
+ 'sierra/mm-common-sierra.c',
+ 'sierra/mm-shared.c',
+ 'sierra/mm-sim-sierra.c',
+ )
+
+ plugins += {'shared-sierra': {
+ 'plugin': false,
+ 'helper': {'sources': files('sierra/mm-modem-helpers-sierra.c'), 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'test': {'sources': files('sierra/tests/test-modem-helpers-sierra.c'), 'include_directories': sierra_inc, 'dependencies': libhelpers_dep},
+ }}
+endif
+
+# common telit support
+if plugins_shared['telit']
+ telit_inc = include_directories('telit')
+
+ common_c_args = '-DMM_MODULE_NAME="shared-telit"'
+
+ headers = files('telit/mm-modem-helpers-telit.h')
+
+ sources = files(
+ 'telit/mm-broadband-modem-telit.c',
+ 'telit/mm-common-telit.c',
+ 'telit/mm-shared.c',
+ 'telit/mm-shared-telit.c',
+ )
+
+ enums_types = 'mm-telit-enums-types'
+
+ sources += gnome.mkenums(
+ enums_types + '.c',
+ sources: headers,
+ c_template: build_aux_dir / enums_types + '.c.template',
+ fhead: '#include "mm-telit-enums-types.h"',
+ )
+
+ sources += gnome.mkenums(
+ enums_types + '.h',
+ sources: headers,
+ h_template: build_aux_dir / enums_types + '.h.template',
+ fhead: '#include "mm-modem-helpers-telit.h"\n#ifndef __MM_TELIT_ENUMS_TYPES_H__\n#define __MM_TELIT_ENUMS_TYPES_H__\n',
+ ftail: '#endif /* __MM_TELIT_ENUMS_TYPES_H__ */\n',
+ )
+
+ if enable_mbim
+ sources += files('telit/mm-broadband-modem-mbim-telit.c')
+ endif
+
+ plugins += {'shared-telit': {
+ 'plugin': false,
+ 'helper': {'sources': files('telit/mm-modem-helpers-telit.c'), 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'module': {'sources': sources + daemon_enums_sources, 'include_directories': plugins_incs + [telit_inc], 'c_args': common_c_args},
+ 'test': {'sources': files('telit/tests/test-mm-modem-helpers-telit.c'), 'include_directories': telit_inc, 'dependencies': plugins_common_test_dep},
+ }}
+endif
+
+# common xmm support
+if plugins_shared['xmm']
+ xmm_inc = include_directories('xmm')
+
+ common_c_args = '-DMM_MODULE_NAME="shared-xmm"'
+
+ sources = files(
+ 'xmm/mm-broadband-modem-xmm.c',
+ 'xmm/mm-shared.c',
+ 'xmm/mm-shared-xmm.c',
+ )
+
+ if enable_mbim
+ sources += files('xmm/mm-broadband-modem-mbim-xmm.c')
+ endif
+
+ plugins += {'shared-xmm': {
+ 'plugin': false,
+ 'helper': {'sources': files('xmm/mm-modem-helpers-xmm.c'), 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'test': {'sources': files('xmm/tests/test-modem-helpers-xmm.c'), 'include_directories': xmm_inc, 'dependencies': libhelpers_dep},
+ }}
+endif
+
+# plugin: altair lte
+if plugins_options['altair-lte']
+ common_c_args = '-DMM_MODULE_NAME="altair-lte"'
+
+ sources = files(
+ 'altair/mm-broadband-bearer-altair-lte.c',
+ 'altair/mm-broadband-modem-altair-lte.c',
+ 'altair/mm-plugin-altair-lte.c',
+ )
+
+ plugins += {'plugin-altair-lte': {
+ 'plugin': true,
+ 'helper': {'sources': files('altair/mm-modem-helpers-altair-lte.c'), 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'test': {'sources': files('altair/tests/test-modem-helpers-altair-lte.c'), 'include_directories': include_directories('altair'), 'dependencies': libhelpers_dep},
+ }}
+endif
+
+# plugin: anydata
+if plugins_options['anydata']
+ sources = files(
+ 'anydata/mm-broadband-modem-anydata.c',
+ 'anydata/mm-plugin-anydata.c',
+ )
+
+ plugins += {'plugin-anydata': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="anydata"'},
+ }}
+endif
+
+# plugin: broadmobi
+if plugins_options['broadmobi']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_BROADMOBI="@0@"'.format(plugins_dir / 'broadmobi')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ plugins += {'plugin-broadmobi': {
+ 'plugin': true,
+ 'module': {'sources': files('broadmobi/mm-plugin-broadmobi.c'), 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="broadmobi"']},
+ }}
+
+ plugins_udev_rules += files('broadmobi/77-mm-broadmobi-port-types.rules')
+endif
+
+# plugin: cinterion (previously siemens)
+if plugins_options['cinterion']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_CINTERION="@0@"'.format(plugins_dir / 'cinterion')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ common_c_args = test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="cinterion"']
+
+ sources = files(
+ 'cinterion/mm-broadband-bearer-cinterion.c',
+ 'cinterion/mm-broadband-modem-cinterion.c',
+ 'cinterion/mm-plugin-cinterion.c',
+ 'cinterion/mm-shared-cinterion.c',
+ )
+
+ if enable_qmi
+ sources += files('cinterion/mm-broadband-modem-qmi-cinterion.c')
+ endif
+
+ if enable_mbim
+ sources += files('cinterion/mm-broadband-modem-mbim-cinterion.c')
+ endif
+
+ plugins += {'plugin-cinterion': {
+ 'plugin': true,
+ 'helper': {'sources': files('cinterion/mm-modem-helpers-cinterion.c'), 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'module': {'sources': sources + daemon_enums_sources, 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'test': {'sources': files('cinterion/tests/test-modem-helpers-cinterion.c'), 'include_directories': plugins_incs + [include_directories('cinterion')], 'dependencies': libport_dep},
+ }}
+
+ plugins_udev_rules += files('cinterion/77-mm-cinterion-port-types.rules')
+endif
+
+# plugin: dell
+if plugins_options['dell']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_DELL="@0@"'.format(plugins_dir / 'dell')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ incs = plugins_incs + [
+ novatel_inc,
+ sierra_inc,
+ telit_inc,
+ xmm_inc,
+ ]
+
+ if enable_mbim
+ incs += [foxconn_inc]
+ endif
+
+ plugins += {'plugin-dell': {
+ 'plugin': true,
+ 'module': {'sources': files('dell/mm-plugin-dell.c'), 'include_directories': incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="dell"']},
+ }}
+
+ plugins_udev_rules += files('dell/77-mm-dell-port-types.rules')
+endif
+
+# plugin: dlink
+if plugins_options['dlink']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_DLINK="@0@"'.format(plugins_dir / 'dlink')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ plugins += {'plugin-dlink': {
+ 'plugin': true,
+ 'module': {'sources': files('dlink/mm-plugin-dlink.c'), 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="d-link"']},
+ }}
+
+ plugins_udev_rules += files('dlink/77-mm-dlink-port-types.rules')
+endif
+
+# plugin: fibocom
+if plugins_options['fibocom']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_FIBOCOM="@0@"'.format(plugins_dir / 'fibocom')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ incs = plugins_incs + [xmm_inc]
+
+ sources = files(
+ 'fibocom/mm-broadband-bearer-fibocom-ecm.c',
+ 'fibocom/mm-broadband-modem-fibocom.c',
+ 'fibocom/mm-plugin-fibocom.c',
+ )
+ if enable_mbim
+ incs += [fibocom_inc]
+
+ sources += files(
+ 'fibocom/mm-broadband-modem-mbim-xmm-fibocom.c',
+ 'fibocom/mm-broadband-modem-mbim-fibocom.c',
+ )
+ endif
+ plugins += {'plugin-fibocom': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="fibocom"']},
+ }}
+
+ plugins_udev_rules += files('fibocom/77-mm-fibocom-port-types.rules')
+endif
+
+# plugin: foxconn
+if plugins_options['foxconn']
+ foxconn_dir = plugins_dir / 'foxconn'
+
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_FOXCONN="@0@"'.format(foxconn_dir)]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ test_keyfile_c_args = ['-DTESTKEYFILE_FOXCONN_T77W968="@0@"'.format(foxconn_dir / 'mm-foxconn-t77w968-carrier-mapping.conf')]
+ plugins_test_keyfile_c_args += test_keyfile_c_args
+
+ plugins += {'plugin-foxconn': {
+ 'plugin': true,
+ 'module': {'sources': files('foxconn/mm-plugin-foxconn.c'), 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + test_keyfile_c_args + ['-DMM_MODULE_NAME="foxconn"']},
+ }}
+
+ plugins_data += files(
+ 'foxconn/mm-foxconn-t77w968-carrier-mapping.conf',
+ )
+ plugins_udev_rules += files('foxconn/77-mm-foxconn-port-types.rules')
+endif
+
+# plugin: generic
+if plugins_options['generic']
+ plugins += {'plugin-generic': {
+ 'plugin': true,
+ 'module': {'sources': files('generic/mm-plugin-generic.c'), 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="generic"'},
+ 'test': {'sources': files('generic/tests/test-service-generic.c'), 'include_directories': include_directories('generic'), 'dependencies': plugins_common_test_dep, 'c_args': '-DCOMMON_GSM_PORT_CONF="@0@"'.format(plugins_dir / 'tests/gsm-port.conf')},
+ }}
+endif
+
+# plugin: gosuncn
+if plugins_options['gosuncn']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_GOSUNCN="@0@"'.format(plugins_dir / 'gosuncn')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ plugins += {'plugin-gosuncn': {
+ 'plugin': true,
+ 'module': {'sources': files('gosuncn/mm-plugin-gosuncn.c'), 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="gosuncn"']},
+ }}
+
+ plugins_udev_rules += files('gosuncn/77-mm-gosuncn-port-types.rules')
+endif
+
+# plugin: haier
+if plugins_options['haier']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_HAIER="@0@"'.format(plugins_dir / 'haier')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ plugins += {'plugin-haier': {
+ 'plugin': true,
+ 'module': {'sources': files('haier/mm-plugin-haier.c'), 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="haier"']},
+ }}
+
+ plugins_udev_rules += files('haier/77-mm-haier-port-types.rules')
+endif
+
+# plugin: huawei
+if plugins_options['huawei']
+ huawei_inc = include_directories('huawei')
+
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_HUAWEI="@0@"'.format(plugins_dir / 'huawei')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ common_c_args = test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="huawei"']
+
+ headers = files('huawei/mm-modem-helpers-huawei.h')
+
+ sources = files(
+ 'huawei/mm-broadband-bearer-huawei.c',
+ 'huawei/mm-broadband-modem-huawei.c',
+ 'huawei/mm-plugin-huawei.c',
+ 'huawei/mm-sim-huawei.c',
+ )
+
+ enums_types = 'mm-huawei-enums-types'
+
+ enums_sources = []
+ enums_sources += gnome.mkenums(
+ enums_types + '.c',
+ sources: headers,
+ c_template: build_aux_dir / enums_types + '.c.template',
+ fhead: '#include "mm-huawei-enums-types.h"',
+ )
+
+ enums_sources += gnome.mkenums(
+ enums_types + '.h',
+ sources: headers,
+ h_template: build_aux_dir / enums_types + '.h.template',
+ fhead: '#include "mm-modem-helpers-huawei.h"\n#ifndef __MM_HUAWEI_ENUMS_TYPES_H__\n#define __MM_HUAWEI_ENUMS_TYPES_H__\n',
+ ftail: '#endif /* __MM_HUAWEI_ENUMS_TYPES_H__ */\n',
+ )
+
+ plugins += {'plugin-huawei': {
+ 'plugin': true,
+ 'helper': {'sources': files('huawei/mm-modem-helpers-huawei.c') + daemon_enums_sources, 'include_directories': plugins_incs + [huawei_inc], 'c_args': common_c_args},
+ 'module': {'sources': sources + enums_sources + port_enums_sources + daemon_enums_sources, 'include_directories': plugins_incs + [huawei_inc], 'c_args': common_c_args},
+ 'test': {'sources': files('huawei/tests/test-modem-helpers-huawei.c') + enums_sources, 'include_directories': huawei_inc, 'dependencies': libhelpers_dep},
+ }}
+
+ plugins_udev_rules += files('huawei/77-mm-huawei-net-port-types.rules')
+endif
+
+# plugin: intel
+if plugins_options['intel']
+ sources = files(
+ 'intel/mm-plugin-intel.c',
+ )
+
+ if enable_mbim
+ sources += files('intel/mm-broadband-modem-mbim-intel.c')
+ endif
+
+ common_c_args = '-DMM_MODULE_NAME="intel"'
+
+ plugins += {'plugin-intel': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs + [xmm_inc], 'c_args': common_c_args},
+ }}
+endif
+
+# plugin: iridium
+if plugins_options['iridium']
+ sources = files(
+ 'iridium/mm-bearer-iridium.c',
+ 'iridium/mm-broadband-modem-iridium.c',
+ 'iridium/mm-plugin-iridium.c',
+ 'iridium/mm-sim-iridium.c',
+ )
+
+ plugins += {'plugin-iridium': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="iridium"'},
+ }}
+endif
+
+# plugin: linktop
+if plugins_options['linktop']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_LINKTOP="@0@"'.format(plugins_dir / 'linktop')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ common_c_args = test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="linktop"']
+
+ sources = files(
+ 'linktop/mm-plugin-linktop.c',
+ 'linktop/mm-broadband-modem-linktop.c',
+ )
+
+ plugins += {'plugin-linktop': {
+ 'plugin': true,
+ 'helper': {'sources': files('linktop/mm-modem-helpers-linktop.c'), 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'test': {'sources': files('linktop/tests/test-modem-helpers-linktop.c'), 'include_directories': include_directories('linktop'), 'dependencies': libhelpers_dep},
+ }}
+
+ plugins_udev_rules += files('linktop/77-mm-linktop-port-types.rules')
+endif
+
+# plugin: longcheer (and rebranded dongles)
+if plugins_options['longcheer']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_LONGCHEER="@0@"'.format(plugins_dir / 'longcheer')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ sources = files(
+ 'longcheer/mm-broadband-modem-longcheer.c',
+ 'longcheer/mm-plugin-longcheer.c',
+ )
+
+ plugins += {'plugin-longcheer': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="longcheer"']},
+ }}
+
+ plugins_udev_rules += files('longcheer/77-mm-longcheer-port-types.rules')
+endif
+
+# plugin: ericsson mbm
+if plugins_options['mbm']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_MBM="@0@"'.format(plugins_dir / 'mbm')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ common_c_args = test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="ericsson-mbm"']
+
+ sources = files(
+ 'mbm/mm-broadband-bearer-mbm.c',
+ 'mbm/mm-broadband-modem-mbm.c',
+ 'mbm/mm-plugin-mbm.c',
+ 'mbm/mm-sim-mbm.c',
+ )
+
+ plugins += {'plugin-ericsson-mbm': {
+ 'plugin': true,
+ 'helper': {'sources': files('mbm/mm-modem-helpers-mbm.c'), 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'module': {'sources': sources + daemon_enums_sources, 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'test': {'sources': files('mbm/tests/test-modem-helpers-mbm.c'), 'include_directories': plugins_incs + [include_directories('mbm')], 'dependencies': libhelpers_dep},
+ }}
+
+ plugins_udev_rules += files('mbm/77-mm-ericsson-mbm.rules')
+endif
+
+# plugin: motorola
+if plugins_options['motorola']
+ sources = files(
+ 'motorola/mm-broadband-modem-motorola.c',
+ 'motorola/mm-plugin-motorola.c',
+ )
+
+ plugins += {'plugin-motorola': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="motorola"'},
+ }}
+endif
+
+# plugin: mtk
+if plugins_options['mtk']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_MTK="@0@"'.format(plugins_dir / 'mtk')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ sources = files(
+ 'mtk/mm-broadband-modem-mtk.c',
+ 'mtk/mm-plugin-mtk.c',
+ )
+
+ plugins += {'plugin-mtk': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="motorola"']},
+ }}
+
+ plugins_udev_rules += files('mtk/77-mm-mtk-port-types.rules')
+endif
+
+# plugin: nokia
+if plugins_options['nokia']
+ sources = files(
+ 'nokia/mm-broadband-modem-nokia.c',
+ 'nokia/mm-plugin-nokia.c',
+ 'nokia/mm-sim-nokia.c',
+ )
+
+ plugins += {'plugin-nokia': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="nokia"'},
+ }}
+endif
+
+# plugin: nokia (icera)
+if plugins_options['nokia-icera']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_NOKIA_ICERA="@0@"'.format(plugins_dir / 'nokia')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ plugins += {'plugin-nokia-icera': {
+ 'plugin': true,
+ 'module': {'sources': files('nokia/mm-plugin-nokia-icera.c'), 'include_directories': plugins_incs + [icera_inc], 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="nokia-icera"']},
+ }}
+
+ plugins_udev_rules += files('nokia/77-mm-nokia-port-types.rules')
+endif
+
+# plugin: novatel non-lte
+if plugins_options['novatel']
+ plugins += {'plugin-novatel': {
+ 'plugin': true,
+ 'module': {'sources': files('novatel/mm-plugin-novatel.c'), 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="novatel"'},
+ }}
+endif
+
+# plugin: novatel lte
+if plugins_options['novatel-lte']
+ sources = files(
+ 'novatel/mm-plugin-novatel-lte.c',
+ 'novatel/mm-broadband-modem-novatel-lte.c',
+ 'novatel/mm-broadband-bearer-novatel-lte.c',
+ 'novatel/mm-sim-novatel-lte.c',
+ )
+
+ plugins += {'plugin-novatel-lte': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="novatel-lte"'},
+ }}
+endif
+
+# plugin: option
+if plugins_options['option']
+ plugins += {'plugin-option': {
+ 'plugin': true,
+ 'module': {'sources': files('option/mm-plugin-option.c'), 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="option"'},
+ }}
+endif
+
+# plugin: option hso
+if plugins_options['option-hso']
+ sources = files(
+ 'option/mm-plugin-hso.c',
+ 'option/mm-broadband-bearer-hso.c',
+ 'option/mm-broadband-modem-hso.c',
+ )
+
+ plugins += {'plugin-option-hso': {
+ 'plugin': true,
+ 'module': {'sources': sources + daemon_enums_sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="option-hso"'},
+ }}
+endif
+
+# plugin: pantech
+if plugins_options['pantech']
+ sources = files(
+ 'pantech/mm-broadband-modem-pantech.c',
+ 'pantech/mm-plugin-pantech.c',
+ 'pantech/mm-sim-pantech.c',
+ )
+
+ plugins += {'plugin-pantech': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="pantech"'},
+ }}
+endif
+
+# plugin: qcom-soc
+if plugins_options['qcom-soc']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_QCOM_SOC="@0@"'.format(plugins_dir / 'qcom-soc')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ sources = files(
+ 'qcom-soc/mm-broadband-modem-qmi-qcom-soc.c',
+ 'qcom-soc/mm-plugin-qcom-soc.c',
+ )
+
+ plugins += {'plugin-qcom-soc': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="qcom-soc"']},
+ }}
+
+ plugins_udev_rules += files('qcom-soc/77-mm-qcom-soc.rules')
+endif
+
+# plugin: quectel
+if plugins_options['quectel']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_QUECTEL="@0@"'.format(plugins_dir / 'quectel')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ common_c_args = test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="quectel"']
+
+ sources = files(
+ 'quectel/mm-broadband-modem-quectel.c',
+ 'quectel/mm-plugin-quectel.c',
+ 'quectel/mm-shared-quectel.c',
+ )
+
+ if enable_qmi
+ sources += files('quectel/mm-broadband-modem-qmi-quectel.c')
+ endif
+
+ if enable_mbim
+ sources += files('quectel/mm-broadband-modem-mbim-quectel.c')
+ endif
+
+ plugins += {'plugin-quectel': {
+ 'plugin': true,
+ 'helper': {'sources': files('quectel/mm-modem-helpers-quectel.c'), 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'test': {'sources': files('quectel/tests/test-modem-helpers-quectel.c'), 'include_directories': include_directories('quectel'), 'dependencies': libhelpers_dep},
+ }}
+
+ plugins_udev_rules += files('quectel/77-mm-quectel-port-types.rules')
+endif
+
+# plugin: samsung
+if plugins_options['samsung']
+ sources = files(
+ 'samsung/mm-broadband-modem-samsung.c',
+ 'samsung/mm-plugin-samsung.c',
+ )
+
+ plugins += {'plugin-samsung': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs + [icera_inc], 'c_args': '-DMM_MODULE_NAME="samsung"'},
+ }}
+endif
+
+# plugin: sierra (legacy)
+if plugins_options['sierra-legacy']
+ sources = files(
+ 'sierra/mm-broadband-modem-sierra-icera.c',
+ 'sierra/mm-plugin-sierra-legacy.c',
+ )
+
+ plugins += {'plugin-sierra-legacy': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs + [icera_inc], 'c_args': '-DMM_MODULE_NAME="sierra-legacy"'},
+ }}
+endif
+
+# plugin: sierra (new QMI or MBIM modems)
+if plugins_options['sierra']
+ plugins += {'plugin-sierra': {
+ 'plugin': true,
+ 'module': {'sources': files('sierra/mm-plugin-sierra.c'), 'include_directories': plugins_incs + [xmm_inc], 'c_args': '-DMM_MODULE_NAME="sierra"'},
+ }}
+
+ plugins_udev_rules += files('sierra/77-mm-sierra.rules')
+endif
+
+# plugin: simtech
+if plugins_options['simtech']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_SIMTECH="@0@"'.format(plugins_dir / 'simtech')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ common_c_args = test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="simtech"']
+
+ sources = files(
+ 'simtech/mm-broadband-modem-simtech.c',
+ 'simtech/mm-plugin-simtech.c',
+ 'simtech/mm-shared-simtech.c',
+ )
+
+ if enable_qmi
+ sources += files('simtech/mm-broadband-modem-qmi-simtech.c')
+ endif
+
+ plugins += {'plugin-simtech': {
+ 'plugin': true,
+ 'helper': {'sources': files('simtech/mm-modem-helpers-simtech.c'), 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'test': {'sources': files('simtech/tests/test-modem-helpers-simtech.c'), 'include_directories': plugins_incs + [include_directories('simtech')], 'dependencies': libport_dep},
+ }}
+
+ plugins_udev_rules += files('simtech/77-mm-simtech-port-types.rules')
+endif
+
+# plugin: telit
+if plugins_options['telit']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_TELIT="@0@"'.format(plugins_dir / 'telit')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ plugins += {'plugin-telit': {
+ 'plugin': true,
+ 'module': {'sources': files('telit/mm-plugin-telit.c'), 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="telit"']},
+ }}
+
+ plugins_udev_rules += files('telit/77-mm-telit-port-types.rules')
+endif
+
+# plugin: thuraya xt
+if plugins_options['thuraya']
+ common_c_args = ['-DMM_MODULE_NAME="thuraya"']
+
+ sources = files(
+ 'thuraya/mm-broadband-modem-thuraya.c',
+ 'thuraya/mm-plugin-thuraya.c',
+ )
+
+ plugins += {'plugin-thuraya': {
+ 'plugin': true,
+ 'helper': {'sources': files('thuraya/mm-modem-helpers-thuraya.c'), 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'test': {'sources': files('thuraya/tests/test-mm-modem-helpers-thuraya.c'), 'include_directories': include_directories('thuraya'), 'dependencies': libhelpers_dep},
+ }}
+endif
+
+# plugin: tplink
+if plugins_options['tplink']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_TPLINK="@0@"'.format(plugins_dir / 'tplink')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ plugins += {'plugin-tplink': {
+ 'plugin': true,
+ 'module': {'sources': files('tplink/mm-plugin-tplink.c'), 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="tp-link"']},
+ }}
+
+ plugins_udev_rules += files('tplink/77-mm-tplink-port-types.rules')
+endif
+
+# plugin: u-blox
+if plugins_options['ublox']
+ ublox_inc = include_directories('ublox')
+
+ common_c_args = '-DMM_MODULE_NAME="u-blox"'
+
+ headers = files('ublox/mm-modem-helpers-ublox.h')
+
+ sources = files(
+ 'ublox/mm-broadband-bearer-ublox.c',
+ 'ublox/mm-broadband-modem-ublox.c',
+ 'ublox/mm-plugin-ublox.c',
+ 'ublox/mm-sim-ublox.c',
+ )
+
+ enums_types = 'mm-ublox-enums-types'
+
+ sources += gnome.mkenums(
+ enums_types + '.c',
+ sources: headers,
+ c_template: build_aux_dir / enums_types + '.c.template',
+ fhead: '#include "mm-ublox-enums-types.h"',
+ )
+
+ sources += gnome.mkenums(
+ enums_types + '.h',
+ sources: headers,
+ h_template: build_aux_dir / enums_types + '.h.template',
+ fhead: '#include "mm-modem-helpers-ublox.h"\n#ifndef __MM_UBLOX_ENUMS_TYPES_H__\n#define __MM_UBLOX_ENUMS_TYPES_H__\n',
+ ftail: '#endif /* __MM_UBLOX_ENUMS_TYPES_H__ */\n',
+ )
+
+ plugins += {'plugin-ublox': {
+ 'plugin': true,
+ 'helper': {'sources': files('ublox/mm-modem-helpers-ublox.c'), 'include_directories': plugins_incs, 'c_args': common_c_args},
+ 'module': {'sources': sources + daemon_enums_sources, 'include_directories': plugins_incs + [ublox_inc], 'c_args': common_c_args},
+ 'test': {'sources': files('ublox/tests/test-modem-helpers-ublox.c'), 'include_directories': ublox_inc, 'dependencies': plugins_common_test_dep},
+ }}
+
+ plugins_udev_rules += files('ublox/77-mm-ublox-port-types.rules')
+endif
+
+# plugin: via
+if plugins_options['via']
+ sources = files(
+ 'via/mm-broadband-modem-via.c',
+ 'via/mm-plugin-via.c',
+ )
+
+ plugins += {'plugin-via': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="via"'},
+ }}
+endif
+
+# plugin: wavecom (now sierra airlink)
+if plugins_options['wavecom']
+ sources = files(
+ 'wavecom/mm-broadband-modem-wavecom.c',
+ 'wavecom/mm-plugin-wavecom.c',
+ )
+
+ plugins += {'plugin-wavecom': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="wavecom"'},
+ }}
+endif
+
+# plugin: alcatel/TCT/JRD x220D and possibly others
+if plugins_options['x22x']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_X22X="@0@"'.format(plugins_dir / 'x22x')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ sources = files(
+ 'x22x/mm-broadband-modem-x22x.c',
+ 'x22x/mm-plugin-x22x.c',
+ )
+
+ plugins += {'plugin-x22x': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs, 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="x22x"']},
+ }}
+
+ plugins_udev_rules += files('x22x/77-mm-x22x-port-types.rules')
+endif
+
+# plugin: zte
+if plugins_options['zte']
+ test_udev_rules_dir_c_args = ['-DTESTUDEVRULESDIR_ZTE="@0@"'.format(plugins_dir / 'zte')]
+ plugins_test_udev_rules_dir_c_args += test_udev_rules_dir_c_args
+
+ sources = files(
+ 'zte/mm-broadband-modem-zte.c',
+ 'zte/mm-broadband-modem-zte-icera.c',
+ 'zte/mm-common-zte.c',
+ 'zte/mm-plugin-zte.c',
+ )
+
+ plugins += {'plugin-zte': {
+ 'plugin': true,
+ 'module': {'sources': sources, 'include_directories': plugins_incs + [icera_inc], 'c_args': test_udev_rules_dir_c_args + ['-DMM_MODULE_NAME="zte"']},
+ }}
+
+ plugins_udev_rules += files('zte/77-mm-zte-port-types.rules')
+endif
+
+foreach plugin_name, plugin_data: plugins
+ libpluginhelpers = []
+ if plugin_data.has_key('helper')
+ libpluginhelpers = static_library(
+ 'helpers-' + plugin_name,
+ dependencies: plugins_deps,
+ kwargs: plugin_data['helper'],
+ )
+ endif
+
+ module_args = plugin_data['module']
+ if plugin_data['plugin']
+ module_args += {
+ 'link_args': ldflags,
+ 'link_depends': symbol_map,
+ }
+ endif
+
+ shared_module(
+ 'mm-' + plugin_name,
+ dependencies: plugins_deps,
+ link_with: libpluginhelpers,
+ kwargs: module_args,
+ install: true,
+ install_dir: mm_pkglibdir,
+ )
+
+ if enable_tests
+ if plugin_data.has_key('test')
+ test_unit = 'test-' + plugin_name
+
+ exe = executable(
+ test_unit,
+ link_with: libpluginhelpers,
+ kwargs: plugin_data['test'],
+ )
+
+ test(test_unit, exe)
+ endif
+ endif
+endforeach
+
+install_data(
+ plugins_data,
+ install_dir: mm_pkgdatadir,
+)
+
+install_data(
+ plugins_udev_rules,
+ install_dir: udev_rulesdir,
+)
+
+# udev-rules and keyfiles tests
+test_units = {
+ 'udev-rules': {'include_directories': top_inc, 'dependencies': libkerneldevice_dep, 'c_args': plugins_test_udev_rules_dir_c_args},
+ 'keyfiles': {'include_directories': [top_inc, src_inc], 'dependencies': libmm_glib_dep, 'c_args': plugins_test_keyfile_c_args},
+}
+
+foreach name, data: test_units
+ test_name = 'test-' + name
+
+ exe = executable(
+ test_name,
+ sources: 'tests/@0@.c'.format(test_name),
+ kwargs: data,
+ )
+
+ test(test_name, exe)
+endforeach
diff --git a/src/plugins/motorola/mm-broadband-modem-motorola.c b/src/plugins/motorola/mm-broadband-modem-motorola.c
new file mode 100644
index 00000000..8ebd2114
--- /dev/null
+++ b/src/plugins/motorola/mm-broadband-modem-motorola.c
@@ -0,0 +1,92 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2011 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-broadband-modem-motorola.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMotorola, mm_broadband_modem_motorola, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init));
+
+/*****************************************************************************/
+
+MMBroadbandModemMotorola *
+mm_broadband_modem_motorola_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_MOTOROLA,
+ 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,
+ NULL);
+}
+
+static void
+mm_broadband_modem_motorola_init (MMBroadbandModemMotorola *self)
+{
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ /* Loading IMEI not supported */
+ iface->load_imei = NULL;
+ iface->load_imei_finish = NULL;
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ /* Loading IMEI with +CGSN is not supported, just assume we cannot load
+ * equipment ID */
+ iface->load_equipment_identifier = NULL;
+ iface->load_equipment_identifier_finish = NULL;
+
+ /* These devices just don't implement AT+CFUN */
+ iface->load_power_state = NULL;
+ iface->load_power_state_finish = NULL;
+ iface->modem_power_up = NULL;
+ iface->modem_power_up_finish = NULL;
+ iface->modem_power_down = NULL;
+ iface->modem_power_down_finish = NULL;
+}
+
+static void
+mm_broadband_modem_motorola_class_init (MMBroadbandModemMotorolaClass *klass)
+{
+}
diff --git a/src/plugins/motorola/mm-broadband-modem-motorola.h b/src/plugins/motorola/mm-broadband-modem-motorola.h
new file mode 100644
index 00000000..f57e5b3d
--- /dev/null
+++ b/src/plugins/motorola/mm-broadband-modem-motorola.h
@@ -0,0 +1,49 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_MODEM_MOTOROLA_H
+#define MM_BROADBAND_MODEM_MOTOROLA_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_MOTOROLA (mm_broadband_modem_motorola_get_type ())
+#define MM_BROADBAND_MODEM_MOTOROLA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MOTOROLA, MMBroadbandModemMotorola))
+#define MM_BROADBAND_MODEM_MOTOROLA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MOTOROLA, MMBroadbandModemMotorolaClass))
+#define MM_IS_BROADBAND_MODEM_MOTOROLA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MOTOROLA))
+#define MM_IS_BROADBAND_MODEM_MOTOROLA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MOTOROLA))
+#define MM_BROADBAND_MODEM_MOTOROLA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MOTOROLA, MMBroadbandModemMotorolaClass))
+
+typedef struct _MMBroadbandModemMotorola MMBroadbandModemMotorola;
+typedef struct _MMBroadbandModemMotorolaClass MMBroadbandModemMotorolaClass;
+
+struct _MMBroadbandModemMotorola {
+ MMBroadbandModem parent;
+};
+
+struct _MMBroadbandModemMotorolaClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_motorola_get_type (void);
+
+MMBroadbandModemMotorola *mm_broadband_modem_motorola_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_MOTOROLA_H */
diff --git a/src/plugins/motorola/mm-plugin-motorola.c b/src/plugins/motorola/mm-plugin-motorola.c
new file mode 100644
index 00000000..fbe9b2fa
--- /dev/null
+++ b/src/plugins/motorola/mm-plugin-motorola.c
@@ -0,0 +1,84 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-private-boxed-types.h"
+#include "mm-plugin-motorola.h"
+#include "mm-broadband-modem-motorola.h"
+
+G_DEFINE_TYPE (MMPluginMotorola, mm_plugin_motorola, 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)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_motorola_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", NULL };
+ static const mm_uint16_pair product_ids[] = {
+ { 0x22b8, 0x3802 }, /* C330/C350L/C450/EZX GSM Phone */
+ { 0x22b8, 0x4902 }, /* Triplet GSM Phone */
+ { 0, 0 }
+ };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_MOTOROLA,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_PRODUCT_IDS, product_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_motorola_init (MMPluginMotorola *self)
+{
+}
+
+static void
+mm_plugin_motorola_class_init (MMPluginMotorolaClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/motorola/mm-plugin-motorola.h b/src/plugins/motorola/mm-plugin-motorola.h
new file mode 100644
index 00000000..6812ba36
--- /dev/null
+++ b/src/plugins/motorola/mm-plugin-motorola.h
@@ -0,0 +1,42 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_PLUGIN_MOTOROLA_H
+#define MM_PLUGIN_MOTOROLA_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_MOTOROLA (mm_plugin_motorola_get_type ())
+#define MM_PLUGIN_MOTOROLA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_MOTOROLA, MMPluginMotorola))
+#define MM_PLUGIN_MOTOROLA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_MOTOROLA, MMPluginMotorolaClass))
+#define MM_IS_PLUGIN_MOTOROLA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_MOTOROLA))
+#define MM_IS_PLUGIN_MOTOROLA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_MOTOROLA))
+#define MM_PLUGIN_MOTOROLA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_MOTOROLA, MMPluginMotorolaClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginMotorola;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginMotorolaClass;
+
+GType mm_plugin_motorola_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_MOTOROLA_H */
diff --git a/src/plugins/mtk/77-mm-mtk-port-types.rules b/src/plugins/mtk/77-mm-mtk-port-types.rules
new file mode 100644
index 00000000..96939fd4
--- /dev/null
+++ b/src/plugins/mtk/77-mm-mtk-port-types.rules
@@ -0,0 +1,53 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_mtk_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="0e8d", GOTO="mm_mtk_port_types_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="2001", GOTO="mm_dlink_port_types_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="07d1", GOTO="mm_dlink_port_types_vendorcheck"
+GOTO="mm_mtk_port_types_end"
+
+# MediaTek devices ---------------------------
+
+LABEL="mm_mtk_port_types_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a1", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a1", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a1", ENV{ID_MM_MTK_TAGGED}="1"
+
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a2", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a2", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a2", ENV{ID_MM_MTK_TAGGED}="1"
+
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a4", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a4", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a4", ENV{ID_MM_MTK_TAGGED}="1"
+
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a5", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a5", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a5", ENV{ID_MM_MTK_TAGGED}="1"
+
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a7", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a7", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="00a7", ENV{ID_MM_MTK_TAGGED}="1"
+
+GOTO="mm_mtk_port_types_end"
+
+# D-Link devices ---------------------------
+
+LABEL="mm_dlink_port_types_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# D-Link DWM-156 A3
+ATTRS{idVendor}=="07d1", ATTRS{idProduct}=="7e11", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="07d1", ATTRS{idProduct}=="7e11", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="07d1", ATTRS{idProduct}=="7e11", ENV{ID_MM_MTK_TAGGED}="1"
+
+# D-Link DWM-156 A5 (and later?)
+ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7d00", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7d00", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="2001", ATTRS{idProduct}=="7d00", ENV{ID_MM_MTK_TAGGED}="1"
+
+GOTO="mm_mtk_port_types_end"
+
+LABEL="mm_mtk_port_types_end"
diff --git a/src/plugins/mtk/mm-broadband-modem-mtk.c b/src/plugins/mtk/mm-broadband-modem-mtk.c
new file mode 100644
index 00000000..869784f3
--- /dev/null
+++ b/src/plugins/mtk/mm-broadband-modem-mtk.c
@@ -0,0 +1,934 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log-object.h"
+#include "mm-errors-types.h"
+#include "mm-modem-helpers.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-broadband-modem-mtk.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMtk, mm_broadband_modem_mtk, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init));
+
+struct _MMBroadbandModemMtkPrivate {
+ /* Signal quality regex */
+ GRegex *ecsqg_regex;
+ GRegex *ecsqu_regex;
+ GRegex *ecsqeg_regex;
+ GRegex *ecsqeu_regex;
+ GRegex *ecsqel_regex;
+};
+
+/*****************************************************************************/
+/* Unlock retries (Modem interface) */
+
+static MMUnlockRetries *
+load_unlock_retries_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_unlock_retries_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autoptr(GRegex) r = NULL;
+ const gchar *response;
+ GError *error = NULL;
+ GError *match_error = NULL;
+ gint pin1;
+ gint puk1;
+ gint pin2;
+ gint puk2;
+ MMUnlockRetries *retries;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ r = g_regex_new (
+ "\\+EPINC:\\s*([0-9]+),\\s*([0-9]+),\\s*([0-9]+),\\s*([0-9]+)",
+ 0,
+ 0,
+ NULL);
+
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &match_error)){
+ if (match_error)
+ g_task_return_error (task, match_error);
+ else
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Failed to match EPINC response: %s", response);
+ g_task_return_error (task, error);
+ } else if (!mm_get_int_from_match_info (match_info, 1, &pin1) ||
+ !mm_get_int_from_match_info (match_info, 2, &pin2) ||
+ !mm_get_int_from_match_info (match_info, 3, &puk1) ||
+ !mm_get_int_from_match_info (match_info, 4, &puk2)) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse the EPINC response: '%s'",
+ response);
+ } else {
+ retries = mm_unlock_retries_new ();
+
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN2, pin2);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK2, puk2);
+
+ g_task_return_pointer (task, retries, g_object_unref);
+ }
+ g_object_unref (task);
+}
+
+static void
+load_unlock_retries (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+EPINC?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)load_unlock_retries_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+static gboolean
+modem_after_sim_unlock_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static gboolean
+after_sim_unlock_wait_cb (GTask *task)
+{
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+modem_after_sim_unlock (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* For device, 3 second is OK for SIM get ready */
+ g_timeout_add_seconds (3, (GSourceFunc)after_sim_unlock_wait_cb, task);
+}
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static void
+get_supported_modes_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+
+{
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autoptr(GRegex) r = NULL;
+ const gchar *response;
+ GError *error = NULL;
+ MMModemModeCombination mode;
+ GArray *combinations;
+ GError *match_error = NULL;
+ gint device_type;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ r = g_regex_new ("\\+EGMR:\\s*\"MT([0-9]+)",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &match_error)) {
+ if (match_error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Failed to match EGMR response: %s", response);
+ g_object_unref (task);
+ return;
+ }
+
+ if (!mm_get_int_from_match_info (match_info, 1, &device_type)) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Failed to parse the allowed mode response: '%s'",
+ response);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE,
+ FALSE,
+ sizeof (MMModemModeCombination),
+ 8);
+
+ /* 2G only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 3G only */
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G, no prefer*/
+ mode.allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G, 3G prefer*/
+ mode.allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+
+ if (device_type == 6290) {
+ /* 4G only */
+ mode.allowed = MM_MODEM_MODE_4G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 4G, no prefer */
+ mode.allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 3G and 4G, no prefer */
+ mode.allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G, 3G and 4G, no prefer */
+ mode.allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ /*********************************************************************
+ * No need to filter out any unsupported modes for MTK device. For
+ * +GCAP, +WS64 not support completely, generic filter will filter
+ * out 4G modes.
+ */
+ g_task_return_pointer (task, combinations, (GDestroyNotify)g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+EGMR=0,0",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)get_supported_modes_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+/*****************************************************************************/
+/* Load initial allowed/preferred modes (Modem interface) */
+
+static gboolean
+load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autoptr(GRegex) r = NULL;
+ const gchar *response;
+ gint erat_mode = -1;
+ gint erat_pref = -1;
+ GError *match_error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return FALSE;
+
+ r = g_regex_new (
+ "\\+ERAT:\\s*[0-9]+,\\s*[0-9]+,\\s*([0-9]+),\\s*([0-9]+)",
+ 0,
+ 0,
+ error);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &match_error)) {
+ if (match_error)
+ g_propagate_error (error, match_error);
+ else
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse +ERAT response: '%s'",
+ response);
+ return FALSE;
+ }
+
+ if (!mm_get_int_from_match_info (match_info, 1, &erat_mode) ||
+ !mm_get_int_from_match_info (match_info, 2, &erat_pref)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Failed to parse the ERAT response: m=%d p=%d",
+ erat_mode, erat_pref);
+ return FALSE;
+ }
+
+ /* Correctly parsed! */
+ switch (erat_mode) {
+ case 0:
+ *allowed = MM_MODEM_MODE_2G;
+ break;
+ case 1:
+ *allowed = MM_MODEM_MODE_3G;
+ break;
+ case 2:
+ *allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G;
+ break;
+ case 3:
+ *allowed = MM_MODEM_MODE_4G;
+ break;
+ case 4:
+ *allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G;
+ break;
+ case 5:
+ *allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G;
+ break;
+ case 6:
+ *allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G;
+ break;
+ default:
+ mm_obj_dbg (self, "unsupported allowed mode reported in +ERAT: %d", erat_mode);
+ return FALSE;
+ }
+
+ switch (erat_pref) {
+ case 0:
+ *preferred = MM_MODEM_MODE_NONE;
+ break;
+ case 1:
+ *preferred = MM_MODEM_MODE_2G;
+ break;
+ case 2:
+ *preferred = MM_MODEM_MODE_3G;
+ break;
+ case 3:
+ *preferred = MM_MODEM_MODE_4G;
+ break;
+ default:
+ mm_obj_dbg (self, "unsupported preferred mode %d", erat_pref);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+ERAT?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Set allowed modes (Modem interface) */
+
+static gboolean
+set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+allowed_mode_update_ready (MMBroadbandModemMtk *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+
+ if (error)
+ /* Let the error be critical. */
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *command;
+ gint erat_mode = -1;
+ gint erat_pref = -1;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (allowed == MM_MODEM_MODE_2G) {
+ erat_mode = 0;
+ erat_pref = 0;
+ } else if (allowed == MM_MODEM_MODE_3G) {
+ erat_mode = 1;
+ erat_pref = 0;
+ } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) {
+ erat_mode = 2;
+ if (preferred == MM_MODEM_MODE_3G)
+ erat_pref = 2;
+ else if (preferred == MM_MODEM_MODE_NONE)
+ erat_pref = 0;
+ /* 2G prefer not supported */
+ } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G) &&
+ preferred == MM_MODEM_MODE_NONE) {
+ erat_mode = 6;
+ erat_pref = 0;
+ } else if ((allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G)) &&
+ preferred == MM_MODEM_MODE_NONE) {
+ erat_mode = 4;
+ erat_pref = 0;
+ } else if ((allowed == (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G)) &&
+ preferred == MM_MODEM_MODE_NONE) {
+ erat_mode = 5;
+ erat_pref = 0;
+ } else if (allowed == MM_MODEM_MODE_4G) {
+ erat_mode = 3;
+ erat_pref = 0;
+ }
+
+ if (erat_mode < 0 || erat_pref < 0) {
+ gchar *allowed_str;
+ gchar *preferred_str;
+
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Requested mode (allowed: '%s', preferred: '%s') not supported by the modem.",
+ allowed_str,
+ preferred_str);
+ g_object_unref (task);
+ g_free (allowed_str);
+ g_free (preferred_str);
+ return;
+ }
+
+ command = g_strdup_printf ("AT+ERAT=%d,%d", erat_mode, erat_pref);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 30,
+ FALSE,
+ (GAsyncReadyCallback)allowed_mode_update_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (3GPP interface) */
+
+static void
+mtk_80_signal_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemMtk *self)
+{
+ guint quality = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &quality))
+ return;
+
+ if (quality == 99)
+ quality = 0;
+ else
+ quality = MM_CLAMP_HIGH (quality, 31) * 100 / 31;
+
+ mm_obj_dbg (self, "6280 signal quality URC received: %u", quality);
+ mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);
+}
+
+static void
+mtk_90_2g_signal_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemMtk *self)
+{
+ guint quality = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &quality))
+ return;
+
+ if (quality == 99)
+ quality = 0;
+ else
+ quality = MM_CLAMP_HIGH (quality, 63) * 100 / 63;
+
+ mm_obj_dbg (self, "2G signal quality URC received: %u", quality);
+ mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);
+}
+
+static void
+mtk_90_3g_signal_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemMtk *self)
+{
+ guint quality = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &quality))
+ return;
+
+ quality = MM_CLAMP_HIGH (quality, 96) * 100 / 96;
+
+ mm_obj_dbg (self, "3G signal quality URC received: %u", quality);
+ mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);
+}
+
+static void
+mtk_90_4g_signal_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemMtk *self)
+{
+ guint quality = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &quality))
+ return;
+
+ quality = MM_CLAMP_HIGH (quality, 97) * 100 / 97;
+
+ mm_obj_dbg (self, "4G signal quality URC received: %u", quality);
+ mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);
+}
+
+static void
+set_unsolicited_events_handlers (MMBroadbandModemMtk *self,
+ gboolean enable)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ 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;
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->ecsqg_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)mtk_80_signal_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->ecsqu_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)mtk_80_signal_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->ecsqeg_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)mtk_90_2g_signal_changed:NULL,
+ enable ? self : NULL,
+ NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->ecsqeu_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)mtk_90_3g_signal_changed:NULL,
+ enable ? self : NULL,
+ NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->ecsqel_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)mtk_90_4g_signal_changed:NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+static gboolean
+modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else {
+ /* Our own setup now */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MTK (self),
+ TRUE);
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's setup */
+ iface_modem_3gpp_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_setup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+static void
+parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Our own cleanup first */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MTK (self), FALSE);
+
+ /* And now chain up parent's cleanup */
+ iface_modem_3gpp_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+static const MMBaseModemAtCommand unsolicited_enable_sequence[] = {
+ /* enable signal URC */
+ { "+ECSQ=2", 5, FALSE, NULL },
+ { NULL }
+};
+
+static const MMBaseModemAtCommand unsolicited_disable_sequence[] = {
+ /* disable signal URC */
+ { "+ECSQ=0" , 5, FALSE, NULL },
+ { NULL }
+};
+
+static void
+own_enable_unsolicited_events_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_full_finish (self, res, NULL, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ }
+
+ /* Our own enable now */
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ unsolicited_enable_sequence,
+ NULL,NULL,NULL,
+ (GAsyncReadyCallback)own_enable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's enable */
+ iface_modem_3gpp_parent->enable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_enable_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+static gboolean
+modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+own_disable_unsolicited_events_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_full_finish (self, res, NULL, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Next, chain up parent's disable */
+ iface_modem_3gpp_parent->disable_unsolicited_events (
+ MM_IFACE_MODEM_3GPP (self),
+ (GAsyncReadyCallback)parent_disable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Our own disable first */
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ unsolicited_disable_sequence,
+ NULL, NULL, NULL,
+ (GAsyncReadyCallback)own_disable_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+static gboolean
+modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mtk_parent_class)->setup_ports (self);
+
+ /* Now reset the unsolicited messages we'll handle when enabled */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MTK (self), FALSE);
+}
+
+/*****************************************************************************/
+MMBroadbandModemMtk *
+mm_broadband_modem_mtk_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_MTK,
+ 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,
+ NULL);
+}
+
+static void
+mm_broadband_modem_mtk_init (MMBroadbandModemMtk *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
+ MM_TYPE_BROADBAND_MODEM_MTK,
+ MMBroadbandModemMtkPrivate);
+ self->priv->ecsqg_regex = g_regex_new (
+ "\\r\\n\\+ECSQ:\\s*([0-9]*),\\s*[0-9]*,\\s*-[0-9]*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->ecsqu_regex = g_regex_new (
+ "\\r\\n\\+ECSQ:\\s*([0-9]*),\\s*[0-9]*,\\s*-[0-9]*,\\s*-[0-9]*,\\s*-[0-9]*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->ecsqeg_regex = g_regex_new (
+ "\\r\\n\\+ECSQ:\\s*([0-9]*),\\s*[0-9]*,\\s*-[0-9]*,\\s*1,\\s*1,\\s*1,\\s*1,\\s*[0-9]*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->ecsqeu_regex = g_regex_new (
+ "\\r\\n\\+ECSQ:\\s*([0-9]*),\\s*[0-9]*,\\s*1,\\s*-[0-9]*,\\s*-[0-9]*,\\s*1,\\s*1,\\s*[0-9]*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->ecsqel_regex = g_regex_new (
+ "\\r\\n\\+ECSQ:\\s*[0-9]*,\\s*([0-9]*),\\s*1,\\s*1,\\s*1,\\s*-[0-9]*,\\s*-[0-9]*,\\s*[0-9]*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemMtk *self = MM_BROADBAND_MODEM_MTK (object);
+
+ g_regex_unref (self->priv->ecsqg_regex);
+ g_regex_unref (self->priv->ecsqu_regex);
+ g_regex_unref (self->priv->ecsqeg_regex);
+ g_regex_unref (self->priv->ecsqeu_regex);
+ g_regex_unref (self->priv->ecsqel_regex);
+
+ G_OBJECT_CLASS (mm_broadband_modem_mtk_parent_class)->finalize (object);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->modem_after_sim_unlock = modem_after_sim_unlock;
+ iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish;
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = load_current_modes;
+ iface->load_current_modes_finish = load_current_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = set_current_modes_finish;
+ iface->load_unlock_retries = load_unlock_retries;
+ iface->load_unlock_retries_finish = load_unlock_retries_finish;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish;
+}
+
+static void
+mm_broadband_modem_mtk_class_init (MMBroadbandModemMtkClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemMtkPrivate));
+
+ object_class->finalize = finalize;
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/mtk/mm-broadband-modem-mtk.h b/src/plugins/mtk/mm-broadband-modem-mtk.h
new file mode 100644
index 00000000..bbe72cc2
--- /dev/null
+++ b/src/plugins/mtk/mm-broadband-modem-mtk.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2011 Red Hat, Inc.
+ * Copyright (C) 2011 Google Inc.
+ */
+
+#ifndef MM_BROADBAND_MODEM_MTK_H
+#define MM_BROADBAND_MODEM_MTK_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_MTK (mm_broadband_modem_mtk_get_type ())
+#define MM_BROADBAND_MODEM_MTK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MTK, MMBroadbandModemMtk))
+#define MM_BROADBAND_MODEM_MTK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MTK, MMBroadbandModemMtkClass))
+#define MM_IS_BROADBAND_MODEM_MTK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MTK))
+#define MM_IS_BROADBAND_MODEM_MTK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MTK))
+#define MM_BROADBAND_MODEM_MTK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MTK, MMBroadbandModemMtkClass))
+
+typedef struct _MMBroadbandModemMtk MMBroadbandModemMtk;
+typedef struct _MMBroadbandModemMtkClass MMBroadbandModemMtkClass;
+typedef struct _MMBroadbandModemMtkPrivate MMBroadbandModemMtkPrivate;
+
+struct _MMBroadbandModemMtk {
+ MMBroadbandModem parent;
+ MMBroadbandModemMtkPrivate *priv;
+};
+
+struct _MMBroadbandModemMtkClass {
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_mtk_get_type (void);
+
+MMBroadbandModemMtk *mm_broadband_modem_mtk_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_MTK_H */
diff --git a/src/plugins/mtk/mm-plugin-mtk.c b/src/plugins/mtk/mm-plugin-mtk.c
new file mode 100644
index 00000000..957e38a3
--- /dev/null
+++ b/src/plugins/mtk/mm-plugin-mtk.c
@@ -0,0 +1,82 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-mtk.h"
+#include "mm-broadband-modem-mtk.h"
+
+G_DEFINE_TYPE (MMPluginMtk, mm_plugin_mtk, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+
+/* MTK done */
+static MMBaseModem *
+create_modem (MMPlugin *self,
+ const gchar *uid,
+ const gchar **drivers,
+ guint16 vendor,
+ guint16 product,
+ guint16 subsystem_vendor,
+ GList *probes,
+ GError **error)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_mtk_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", NULL };
+ static const gchar *udev_tags[]={
+ "ID_MM_MTK_TAGGED",
+ NULL};
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_MTK,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_UDEV_TAGS, udev_tags,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_mtk_init (MMPluginMtk *self)
+{
+}
+
+static void
+mm_plugin_mtk_class_init (MMPluginMtkClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/mtk/mm-plugin-mtk.h b/src/plugins/mtk/mm-plugin-mtk.h
new file mode 100644
index 00000000..cc7b0d19
--- /dev/null
+++ b/src/plugins/mtk/mm-plugin-mtk.h
@@ -0,0 +1,42 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_PLUGIN_MTK_H
+#define MM_PLUGIN_MTK_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_MTK (mm_plugin_mtk_get_type ())
+#define MM_PLUGIN_MTK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_MTK, MMPluginMtk))
+#define MM_PLUGIN_MTK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_MTK, MMPluginMtkClass))
+#define MM_IS_PLUGIN_MTK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_MTK))
+#define MM_IS_PLUGIN_MTK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_MTK))
+#define MM_PLUGIN_MTK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_MTK, MMPluginMtkClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginMtk;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginMtkClass;
+
+GType mm_plugin_mtk_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_MTK_H */
diff --git a/src/plugins/nokia/77-mm-nokia-port-types.rules b/src/plugins/nokia/77-mm-nokia-port-types.rules
new file mode 100644
index 00000000..daa8bd4a
--- /dev/null
+++ b/src/plugins/nokia/77-mm-nokia-port-types.rules
@@ -0,0 +1,38 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_nokia_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="0421", GOTO="mm_nokia_port_types"
+GOTO="mm_nokia_port_types_end"
+
+LABEL="mm_nokia_port_types"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# For Nokia Internet Sticks (CS-xx) the modem/PPP port appears to always be USB interface 1
+
+ATTRS{idVendor}=="0421", ATTRS{idProduct}=="060D", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+ATTRS{idVendor}=="0421", ATTRS{idProduct}=="0611", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+ATTRS{idVendor}=="0421", ATTRS{idProduct}=="061A", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+ATTRS{idVendor}=="0421", ATTRS{idProduct}=="061B", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+ATTRS{idVendor}=="0421", ATTRS{idProduct}=="061F", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+ATTRS{idVendor}=="0421", ATTRS{idProduct}=="0619", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+ATTRS{idVendor}=="0421", ATTRS{idProduct}=="0620", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+ATTRS{idVendor}=="0421", ATTRS{idProduct}=="0623", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+ATTRS{idVendor}=="0421", ATTRS{idProduct}=="0624", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+ATTRS{idVendor}=="0421", ATTRS{idProduct}=="0625", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+ATTRS{idVendor}=="0421", ATTRS{idProduct}=="062A", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+ATTRS{idVendor}=="0421", ATTRS{idProduct}=="062E", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+ATTRS{idVendor}=="0421", ATTRS{idProduct}=="062F", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+LABEL="mm_nokia_port_types_end"
diff --git a/src/plugins/nokia/mm-broadband-modem-nokia.c b/src/plugins/nokia/mm-broadband-modem-nokia.c
new file mode 100644
index 00000000..fd608868
--- /dev/null
+++ b/src/plugins/nokia/mm-broadband-modem-nokia.c
@@ -0,0 +1,399 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2011 Red Hat, Inc.
+ * Copyright (C) 2011 Google Inc.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-serial-parsers.h"
+#include "mm-errors-types.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-messaging.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-modem-helpers.h"
+#include "mm-base-modem-at.h"
+#include "mm-broadband-modem-nokia.h"
+#include "mm-sim-nokia.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_messaging_init (MMIfaceModemMessaging *iface);
+
+static MMIfaceModem *iface_modem_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemNokia, mm_broadband_modem_nokia, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init));
+
+/*****************************************************************************/
+/* Create SIM (Modem interface) */
+
+static MMBaseSim *
+create_sim_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return mm_sim_nokia_new_finish (res, error);
+}
+
+static void
+create_sim (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* New Nokia SIM */
+ mm_sim_nokia_new (MM_BASE_MODEM (self),
+ NULL, /* cancellable */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load access technologies (Modem interface) */
+
+typedef struct {
+ MMModemAccessTechnology act;
+ guint mask;
+} AccessTechInfo;
+
+static gboolean
+load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ 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 FALSE;
+ }
+
+ *access_technologies = (MMModemAccessTechnology)value;
+ *mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK;
+ return TRUE;
+}
+
+static void
+parent_load_access_technologies_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ guint mask = 0;
+ GError *error = NULL;
+
+ if (!iface_modem_parent->load_access_technologies_finish (self, res, &act, &mask, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_int (task, act);
+
+ g_object_unref (task);
+}
+
+static void
+access_tech_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ const gchar *response, *p;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL);
+ if (!response) {
+ /* Chain up to parent */
+ iface_modem_parent->load_access_technologies (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_access_technologies_ready,
+ task);
+ return;
+ }
+
+ p = mm_strip_tag (response, "*CNTI:");
+ p = strchr (p, ',');
+ if (p)
+ act = mm_string_to_access_tech (p + 1);
+
+ if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN)
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse access technologies result: '%s'",
+ response);
+ else
+ g_task_return_int (task, act);
+
+ g_object_unref (task);
+}
+
+static void
+load_access_technologies (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "*CNTI=0",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)access_tech_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Loading supported charsets (Modem interface) */
+
+static MMModemCharset
+load_supported_charsets_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *response;
+ MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return MM_MODEM_CHARSET_UNKNOWN;
+
+ if (!mm_3gpp_parse_cscs_test_response (response, &charsets)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Failed to parse the supported character sets response");
+ return MM_MODEM_CHARSET_UNKNOWN;
+ }
+
+ return charsets;
+}
+
+static void
+load_supported_charsets (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CSCS=?",
+ 20,
+ TRUE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Initializing the modem (during first enabling) */
+
+typedef struct {
+ guint retries;
+} EnablingModemInitContext;
+
+static gboolean
+enabling_modem_init_finish (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GError **error)
+
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void retry_atz (GTask *task);
+
+static void
+atz_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ EnablingModemInitContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ /* One retry less */
+ ctx->retries--;
+
+ if (!mm_base_modem_at_command_full_finish (self, res, &error)) {
+ /* Consumed all retries... */
+ if (ctx->retries == 0) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Retry... */
+ g_error_free (error);
+ retry_atz (task);
+ return;
+ }
+
+ /* Good! */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+retry_atz (GTask *task)
+{
+ MMBaseModem *self;
+
+ self = g_task_get_source_object (task);
+
+ mm_base_modem_at_command_full (self,
+ mm_base_modem_peek_port_primary (self),
+ "Z",
+ 6,
+ FALSE,
+ FALSE,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)atz_ready,
+ task);
+}
+
+static void
+enabling_modem_init (MMBroadbandModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ EnablingModemInitContext *ctx;
+ GTask *task;
+
+ ctx = g_new (EnablingModemInitContext, 1);
+
+ /* Send the init command twice; some devices (Nokia N900) appear to take a
+ * few commands before responding correctly. Instead of penalizing them for
+ * being stupid the first time by failing to enable the device, just
+ * try again. */
+ ctx->retries = 2;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ retry_atz (task);
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static const gchar *primary_init_sequence[] = {
+ /* When initializing a Nokia port, first enable the echo,
+ * and then disable it, so that we get it properly disabled. */
+ "E1 E0",
+ /* The N900 ignores the E0 when it's on the same line as the E1, so try again */
+ "E0",
+ /* Get word responses */
+ "V1",
+ /* Extended numeric codes */
+ "+CMEE=1",
+ /* Report all call status */
+ "X4",
+ /* Assert DCD when carrier detected */
+ "&C1",
+ NULL
+};
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ MMPortSerialAt *primary;
+
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_nokia_parent_class)->setup_ports (self);
+
+ primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+
+ g_object_set (primary,
+ MM_PORT_SERIAL_AT_INIT_SEQUENCE, primary_init_sequence,
+ NULL);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemNokia *
+mm_broadband_modem_nokia_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_NOKIA,
+ 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,
+ NULL);
+}
+
+static void
+mm_broadband_modem_nokia_init (MMBroadbandModemNokia *self)
+{
+}
+
+static void
+iface_modem_messaging_init (MMIfaceModemMessaging *iface)
+{
+ /* Don't even try to check messaging support */
+ iface->check_support = NULL;
+ iface->check_support_finish = NULL;
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ /* Create Nokia-specific SIM*/
+ iface->create_sim = create_sim;
+ iface->create_sim_finish = create_sim_finish;
+
+ /* Nokia handsets (at least N85) do not support "power on"; they do
+ * support "power off" but you proabably do not want to turn off the
+ * power on your telephone if something went wrong with connecting
+ * process. So, disabling both these operations. The Nokia GSM/UMTS command
+ * reference v1.2 also states that only CFUN=0 (turn off but still charge)
+ * and CFUN=1 (full functionality) are supported, and since the phone has
+ * to be in CFUN=1 before we'll be able to talk to it in the first place,
+ * we shouldn't bother with CFUN at all.
+ */
+ iface->load_power_state = NULL;
+ iface->load_power_state_finish = NULL;
+ iface->modem_power_up = NULL;
+ iface->modem_power_up_finish = NULL;
+ iface->modem_power_down = NULL;
+ iface->modem_power_down_finish = NULL;
+ iface->load_supported_charsets = load_supported_charsets;
+ iface->load_supported_charsets_finish = load_supported_charsets_finish;
+
+ iface->load_access_technologies = load_access_technologies;
+ iface->load_access_technologies_finish = load_access_technologies_finish;
+}
+
+static void
+mm_broadband_modem_nokia_class_init (MMBroadbandModemNokiaClass *klass)
+{
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ broadband_modem_class->setup_ports = setup_ports;
+ broadband_modem_class->enabling_modem_init = enabling_modem_init;
+ broadband_modem_class->enabling_modem_init_finish = enabling_modem_init_finish;
+}
diff --git a/src/plugins/nokia/mm-broadband-modem-nokia.h b/src/plugins/nokia/mm-broadband-modem-nokia.h
new file mode 100644
index 00000000..d00a5bcb
--- /dev/null
+++ b/src/plugins/nokia/mm-broadband-modem-nokia.h
@@ -0,0 +1,49 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2011 Red Hat, Inc.
+ * Copyright (C) 2011 Google Inc.
+ */
+
+#ifndef MM_BROADBAND_MODEM_NOKIA_H
+#define MM_BROADBAND_MODEM_NOKIA_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_NOKIA (mm_broadband_modem_nokia_get_type ())
+#define MM_BROADBAND_MODEM_NOKIA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_NOKIA, MMBroadbandModemNokia))
+#define MM_BROADBAND_MODEM_NOKIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_NOKIA, MMBroadbandModemNokiaClass))
+#define MM_IS_BROADBAND_MODEM_NOKIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_NOKIA))
+#define MM_IS_BROADBAND_MODEM_NOKIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_NOKIA))
+#define MM_BROADBAND_MODEM_NOKIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_NOKIA, MMBroadbandModemNokiaClass))
+
+typedef struct _MMBroadbandModemNokia MMBroadbandModemNokia;
+typedef struct _MMBroadbandModemNokiaClass MMBroadbandModemNokiaClass;
+
+struct _MMBroadbandModemNokia {
+ MMBroadbandModem parent;
+};
+
+struct _MMBroadbandModemNokiaClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_nokia_get_type (void);
+
+MMBroadbandModemNokia *mm_broadband_modem_nokia_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_NOKIA_H */
diff --git a/src/plugins/nokia/mm-plugin-nokia-icera.c b/src/plugins/nokia/mm-plugin-nokia-icera.c
new file mode 100644
index 00000000..78c8fd4c
--- /dev/null
+++ b/src/plugins/nokia/mm-plugin-nokia-icera.c
@@ -0,0 +1,90 @@
+/* -*- 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) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-nokia-icera.h"
+#include "mm-broadband-modem-icera.h"
+
+G_DEFINE_TYPE (MMPluginNokiaIcera, mm_plugin_nokia_icera, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+/* Custom commands for AT probing */
+
+static const MMPortProbeAtCommand custom_at_probe[] = {
+ { "ATE1 E0", 3, mm_port_probe_response_processor_is_at },
+ { "ATE1 E0", 3, mm_port_probe_response_processor_is_at },
+ { "ATE1 E0", 3, mm_port_probe_response_processor_is_at },
+ { NULL }
+};
+
+/*****************************************************************************/
+
+static MMBaseModem *
+create_modem (MMPlugin *self,
+ const gchar *uid,
+ const gchar **drivers,
+ guint16 vendor,
+ guint16 product,
+ guint16 subsystem_vendor,
+ GList *probes,
+ GError **error)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_icera_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", NULL };
+ static const guint16 vendor_ids[] = { 0x0421, 0 };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_NOKIA_ICERA,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_CUSTOM_AT_PROBE, custom_at_probe,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_ICERA, TRUE, /* Only Nokia/Icera modems */
+ NULL));
+}
+
+static void
+mm_plugin_nokia_icera_init (MMPluginNokiaIcera *self)
+{
+}
+
+static void
+mm_plugin_nokia_icera_class_init (MMPluginNokiaIceraClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/nokia/mm-plugin-nokia-icera.h b/src/plugins/nokia/mm-plugin-nokia-icera.h
new file mode 100644
index 00000000..137692fb
--- /dev/null
+++ b/src/plugins/nokia/mm-plugin-nokia-icera.h
@@ -0,0 +1,41 @@
+/* -*- 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) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_PLUGIN_NOKIA_ICERA_H
+#define MM_PLUGIN_NOKIA_ICERA_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_NOKIA_ICERA (mm_plugin_nokia_icera_get_type ())
+#define MM_PLUGIN_NOKIA_ICERA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_NOKIA_ICERA, MMPluginNokiaIcera))
+#define MM_PLUGIN_NOKIA_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_NOKIA_ICERA, MMPluginNokiaIceraClass))
+#define MM_IS_PLUGIN_NOKIA_ICERA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_NOKIA_ICERA))
+#define MM_IS_PLUGIN_NOKIA_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_NOKIA_ICERA))
+#define MM_PLUGIN_NOKIA_ICERA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_NOKIA_ICERA, MMPluginNokiaIceraClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginNokiaIcera;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginNokiaIceraClass;
+
+GType mm_plugin_nokia_icera_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_NOKIA_ICERA_H */
diff --git a/src/plugins/nokia/mm-plugin-nokia.c b/src/plugins/nokia/mm-plugin-nokia.c
new file mode 100644
index 00000000..b2700b70
--- /dev/null
+++ b/src/plugins/nokia/mm-plugin-nokia.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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2011 Red Hat, Inc.
+ * Copyright (C) 2011 - 2012 Google, Inc.
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-nokia.h"
+#include "mm-broadband-modem-nokia.h"
+
+G_DEFINE_TYPE (MMPluginNokia, mm_plugin_nokia, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+/* Custom commands for AT probing */
+
+static const MMPortProbeAtCommand custom_at_probe[] = {
+ { "ATE1 E0", 3, mm_port_probe_response_processor_is_at },
+ { "ATE1 E0", 3, mm_port_probe_response_processor_is_at },
+ { "ATE1 E0", 3, mm_port_probe_response_processor_is_at },
+ { NULL }
+};
+
+/*****************************************************************************/
+
+static MMBaseModem *
+create_modem (MMPlugin *self,
+ const gchar *uid,
+ const gchar **drivers,
+ guint16 vendor,
+ guint16 product,
+ guint16 subsystem_vendor,
+ GList *probes,
+ GError **error)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_nokia_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", NULL };
+ static const guint16 vendor_ids[] = { 0x0421, 0 };
+ static const gchar *vendor_strings[] = { "nokia", NULL };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_NOKIA,
+ 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_CUSTOM_AT_PROBE, custom_at_probe,
+ MM_PLUGIN_ALLOWED_SINGLE_AT, TRUE, /* only 1 AT port expected! */
+ MM_PLUGIN_FORBIDDEN_ICERA, TRUE, /* No Nokia/Icera modems */
+ NULL));
+}
+
+static void
+mm_plugin_nokia_init (MMPluginNokia *self)
+{
+}
+
+static void
+mm_plugin_nokia_class_init (MMPluginNokiaClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/nokia/mm-plugin-nokia.h b/src/plugins/nokia/mm-plugin-nokia.h
new file mode 100644
index 00000000..e2f3589b
--- /dev/null
+++ b/src/plugins/nokia/mm-plugin-nokia.h
@@ -0,0 +1,41 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_PLUGIN_NOKIA_H
+#define MM_PLUGIN_NOKIA_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_NOKIA (mm_plugin_nokia_get_type ())
+#define MM_PLUGIN_NOKIA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_NOKIA, MMPluginNokia))
+#define MM_PLUGIN_NOKIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_NOKIA, MMPluginNokiaClass))
+#define MM_IS_PLUGIN_NOKIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_NOKIA))
+#define MM_IS_PLUGIN_NOKIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_NOKIA))
+#define MM_PLUGIN_NOKIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_NOKIA, MMPluginNokiaClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginNokia;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginNokiaClass;
+
+GType mm_plugin_nokia_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_NOKIA_H */
diff --git a/src/plugins/nokia/mm-sim-nokia.c b/src/plugins/nokia/mm-sim-nokia.c
new file mode 100644
index 00000000..a0d7c81a
--- /dev/null
+++ b/src/plugins/nokia/mm-sim-nokia.c
@@ -0,0 +1,86 @@
+/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-sim-nokia.h"
+
+G_DEFINE_TYPE (MMSimNokia, mm_sim_nokia, MM_TYPE_BASE_SIM)
+
+/*****************************************************************************/
+
+MMBaseSim *
+mm_sim_nokia_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *source;
+ GObject *sim;
+
+ source = g_async_result_get_source_object (res);
+ sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!sim)
+ return NULL;
+
+ /* Only export valid SIMs */
+ mm_base_sim_export (MM_BASE_SIM (sim));
+
+ return MM_BASE_SIM (sim);
+}
+
+void
+mm_sim_nokia_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (MM_TYPE_SIM_NOKIA,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_SIM_MODEM, modem,
+ "active", TRUE, /* by default always active */
+ NULL);
+}
+
+static void
+mm_sim_nokia_init (MMSimNokia *self)
+{
+}
+
+static void
+mm_sim_nokia_class_init (MMSimNokiaClass *klass)
+{
+ MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass);
+
+ /* Skip querying most SIM card info, +CRSM not supported by Nokia modems */
+ base_sim_class->load_sim_identifier = NULL;
+ base_sim_class->load_sim_identifier_finish = NULL;
+ base_sim_class->load_operator_identifier = NULL;
+ base_sim_class->load_operator_identifier_finish = NULL;
+ base_sim_class->load_operator_name = NULL;
+ base_sim_class->load_operator_name_finish = NULL;
+}
diff --git a/src/plugins/nokia/mm-sim-nokia.h b/src/plugins/nokia/mm-sim-nokia.h
new file mode 100644
index 00000000..05d6e2b1
--- /dev/null
+++ b/src/plugins/nokia/mm-sim-nokia.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_SIM_NOKIA_H
+#define MM_SIM_NOKIA_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "mm-base-sim.h"
+
+#define MM_TYPE_SIM_NOKIA (mm_sim_nokia_get_type ())
+#define MM_SIM_NOKIA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_NOKIA, MMSimNokia))
+#define MM_SIM_NOKIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_NOKIA, MMSimNokiaClass))
+#define MM_IS_SIM_NOKIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_NOKIA))
+#define MM_IS_SIM_NOKIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_NOKIA))
+#define MM_SIM_NOKIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_NOKIA, MMSimNokiaClass))
+
+typedef struct _MMSimNokia MMSimNokia;
+typedef struct _MMSimNokiaClass MMSimNokiaClass;
+
+struct _MMSimNokia {
+ MMBaseSim parent;
+};
+
+struct _MMSimNokiaClass {
+ MMBaseSimClass parent;
+};
+
+GType mm_sim_nokia_get_type (void);
+
+void mm_sim_nokia_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseSim *mm_sim_nokia_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SIM_NOKIA_H */
diff --git a/src/plugins/novatel/mm-broadband-bearer-novatel-lte.c b/src/plugins/novatel/mm-broadband-bearer-novatel-lte.c
new file mode 100644
index 00000000..cd3296e2
--- /dev/null
+++ b/src/plugins/novatel/mm-broadband-bearer-novatel-lte.c
@@ -0,0 +1,580 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2011 - 2012 Google, Inc.
+ * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-base-modem-at.h"
+#include "mm-broadband-bearer-novatel-lte.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+
+#define QMISTATUS_TAG "$NWQMISTATUS:"
+
+G_DEFINE_TYPE (MMBroadbandBearerNovatelLte, mm_broadband_bearer_novatel_lte, MM_TYPE_BROADBAND_BEARER)
+
+/*****************************************************************************/
+
+static gchar *
+normalize_qmistatus (const gchar *status)
+{
+ gchar *normalized_status, *iter;
+
+ if (!status)
+ return NULL;
+
+ normalized_status = g_strdup (status);
+ for (iter = normalized_status; *iter; iter++)
+ if (g_ascii_isspace (*iter))
+ *iter = ' ';
+
+ return normalized_status;
+}
+
+static gboolean
+is_qmistatus_connected (const gchar *str)
+{
+ str = mm_strip_tag (str, QMISTATUS_TAG);
+
+ return g_strrstr (str, "QMI State: CONNECTED") || g_strrstr (str, "QMI State: QMI_WDS_PKT_DATA_CONNECTED");
+}
+
+static gboolean
+is_qmistatus_disconnected (const gchar *str)
+{
+ str = mm_strip_tag (str, QMISTATUS_TAG);
+
+ return g_strrstr (str, "QMI State: DISCONNECTED") || g_strrstr (str, "QMI State: QMI_WDS_PKT_DATA_DISCONNECTED");
+}
+
+static gboolean
+is_qmistatus_call_failed (const gchar *str)
+{
+ str = mm_strip_tag (str, QMISTATUS_TAG);
+
+ return (g_strrstr (str, "QMI_RESULT_FAILURE:QMI_ERR_CALL_FAILED") != NULL);
+}
+
+/*****************************************************************************/
+/* Connection status monitoring */
+
+static MMBearerConnectionStatus
+load_connection_status_finish (MMBaseBearer *bearer,
+ 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_BEARER_CONNECTION_STATUS_UNKNOWN;
+ }
+ return (MMBearerConnectionStatus)value;
+}
+
+static void
+poll_connection_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *result;
+ GError *error = NULL;
+
+ result = mm_base_modem_at_command_finish (modem, res, &error);
+ if (!result)
+ g_task_return_error (task, error);
+ else if (is_qmistatus_disconnected (result))
+ g_task_return_int (task, MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
+ else
+ g_task_return_int (task, MM_BEARER_CONNECTION_STATUS_CONNECTED);
+ g_object_unref (task);
+}
+
+static void
+load_connection_status (MMBaseBearer *bearer,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ MMBaseModem *modem = NULL;
+
+ task = g_task_new (bearer, NULL, callback, user_data);
+
+ g_object_get (MM_BASE_BEARER (bearer),
+ MM_BASE_BEARER_MODEM, &modem,
+ NULL);
+
+ mm_base_modem_at_command (
+ modem,
+ "$NWQMISTATUS",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) poll_connection_ready,
+ task);
+
+ g_object_unref (modem);
+}
+
+/*****************************************************************************/
+/* 3GPP Connection sequence */
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ MMPort *data;
+ gint retries;
+} DetailedConnectContext;
+
+static void
+detailed_connect_context_free (DetailedConnectContext *ctx)
+{
+ if (ctx->data)
+ g_object_unref (ctx->data);
+ g_object_unref (ctx->primary);
+ g_object_unref (ctx->modem);
+ g_slice_free (DetailedConnectContext, ctx);
+}
+
+static MMBearerConnectResult *
+connect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static gboolean connect_3gpp_qmistatus (GTask *task);
+
+static void
+connect_3gpp_qmistatus_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerNovatelLte *self;
+ DetailedConnectContext *ctx;
+ const gchar *result;
+ gchar *normalized_result;
+ GError *error = NULL;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ if (g_task_return_error_if_cancelled (task)) {
+ g_object_unref (task);
+ return;
+ }
+
+ result = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (!result) {
+ if (!g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+ mm_obj_dbg (self, "connection status failed: %s; will retry", error->message);
+ g_error_free (error);
+ goto retry;
+ }
+
+ if (is_qmistatus_connected (result)) {
+ MMBearerIpConfig *config;
+
+ mm_obj_dbg (self, "connected");
+ config = mm_bearer_ip_config_new ();
+ mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_DHCP);
+ g_task_return_pointer (
+ task,
+ mm_bearer_connect_result_new (ctx->data, config, NULL),
+ (GDestroyNotify)mm_bearer_connect_result_unref);
+ g_object_unref (task);
+ g_object_unref (config);
+ return;
+ }
+
+ /* Don't retry if the call failed */
+ if (is_qmistatus_call_failed (result)) {
+ mm_obj_dbg (self, "not retrying: call failed");
+ ctx->retries = 0;
+ }
+
+retry:
+ if (ctx->retries > 0) {
+ ctx->retries--;
+ mm_obj_dbg (self, "retrying status check in a second: %d retries left", ctx->retries);
+ g_timeout_add_seconds (1, (GSourceFunc)connect_3gpp_qmistatus, task);
+ return;
+ }
+
+ /* Already exhausted all retries */
+ normalized_result = normalize_qmistatus (result);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "QMI connect failed: %s",
+ normalized_result);
+ g_object_unref (task);
+ g_free (normalized_result);
+}
+
+static gboolean
+connect_3gpp_qmistatus (GTask *task)
+{
+ DetailedConnectContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ mm_base_modem_at_command_full (
+ ctx->modem,
+ ctx->primary,
+ "$NWQMISTATUS",
+ 3, /* timeout */
+ FALSE, /* allow_cached */
+ FALSE, /* is_raw */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)connect_3gpp_qmistatus_ready, /* callback */
+ task); /* user_data */
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+connect_3gpp_qmiconnect_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *result;
+ GError *error = NULL;
+
+ result = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (!result) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /*
+ * The connection takes a bit of time to set up, but there's no
+ * asynchronous notification from the modem when this has
+ * happened. Instead, we need to poll the modem to see if it's
+ * ready.
+ */
+ g_timeout_add_seconds (1, (GSourceFunc)connect_3gpp_qmistatus, task);
+}
+
+static void
+connect_3gpp_authenticate (GTask *task)
+{
+ MMBroadbandBearerNovatelLte *self;
+ DetailedConnectContext *ctx;
+ MMBearerProperties *config;
+ gchar *command, *apn, *user, *password;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ config = mm_base_bearer_peek_config (MM_BASE_BEARER (self));
+ apn = mm_port_serial_at_quote_string (mm_bearer_properties_get_apn (config));
+ user = mm_port_serial_at_quote_string (mm_bearer_properties_get_user (config));
+ password = mm_port_serial_at_quote_string (mm_bearer_properties_get_password (config));
+ command = g_strdup_printf ("$NWQMICONNECT=,,,,,,%s,,,%s,%s",
+ apn, user, password);
+ g_free (apn);
+ g_free (user);
+ g_free (password);
+ mm_base_modem_at_command_full (
+ ctx->modem,
+ ctx->primary,
+ command,
+ 10, /* timeout */
+ FALSE, /* allow_cached */
+ FALSE, /* is_raw */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)connect_3gpp_qmiconnect_ready,
+ task); /* user_data */
+ g_free (command);
+}
+
+static void
+connect_3gpp (MMBroadbandBearer *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ DetailedConnectContext *ctx;
+ GTask *task;
+
+ ctx = g_slice_new0 (DetailedConnectContext);
+ ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
+ ctx->primary = g_object_ref (primary);
+ ctx->retries = MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)detailed_connect_context_free);
+
+ /* Get a 'net' data port */
+ ctx->data = mm_base_modem_get_best_data_port (ctx->modem, MM_PORT_TYPE_NET);
+ if (!ctx->data) {
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_CONNECTED,
+ "Couldn't connect: no available net port available");
+ g_object_unref (task);
+ return;
+ }
+
+ connect_3gpp_authenticate (task);
+}
+
+/*****************************************************************************/
+/* 3GPP Disonnection sequence */
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ MMPort *data;
+ gint retries;
+} DetailedDisconnectContext;
+
+static void
+detailed_disconnect_context_free (DetailedDisconnectContext *ctx)
+{
+ g_object_unref (ctx->data);
+ g_object_unref (ctx->primary);
+ g_object_unref (ctx->modem);
+ g_free (ctx);
+}
+
+static gboolean
+disconnect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static gboolean disconnect_3gpp_qmistatus (GTask *task);
+
+static void
+disconnect_3gpp_status_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerNovatelLte *self;
+ DetailedDisconnectContext *ctx;
+ const gchar *result;
+ GError *error = NULL;
+ gboolean is_connected = FALSE;
+
+ self = g_task_get_source_object (task);
+
+ result = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (result) {
+ mm_obj_dbg (self, "QMI connection status: %s", result);
+ if (is_qmistatus_disconnected (result)) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+ if (is_qmistatus_connected (result))
+ is_connected = TRUE;
+ } else {
+ mm_obj_dbg (self, "QMI connection status failed: %s", error->message);
+ g_error_free (error);
+ result = "Unknown error";
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ if (ctx->retries > 0) {
+ ctx->retries--;
+ mm_obj_dbg (self, "retrying status check in a second: %d retries left", ctx->retries);
+ g_timeout_add_seconds (1, (GSourceFunc)disconnect_3gpp_qmistatus, task);
+ return;
+ }
+
+ /* If $NWQMISTATUS reports a CONNECTED QMI state, returns an error such that
+ * the modem state remains 'connected'. Otherwise, assumes the modem is
+ * disconnected from the network successfully. */
+ if (is_connected) {
+ gchar *normalized_result;
+
+ normalized_result = normalize_qmistatus (result);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "QMI disconnect failed: %s",
+ normalized_result);
+ g_free (normalized_result);
+ } else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static gboolean
+disconnect_3gpp_qmistatus (GTask *task)
+{
+ DetailedDisconnectContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ mm_base_modem_at_command_full (
+ ctx->modem,
+ ctx->primary,
+ "$NWQMISTATUS",
+ 3, /* timeout */
+ FALSE, /* allow_cached */
+ FALSE, /* is_raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)disconnect_3gpp_status_ready,
+ task); /* user_data */
+ return G_SOURCE_REMOVE;
+}
+
+
+static void
+disconnect_3gpp_check_status (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerNovatelLte *self;
+ GError *error = NULL;
+
+ self = g_task_get_source_object (task);
+
+ mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (error) {
+ mm_obj_dbg (self, "disconnection error: %s", error->message);
+ g_error_free (error);
+ }
+
+ disconnect_3gpp_qmistatus (task);
+}
+
+static void
+disconnect_3gpp (MMBroadbandBearer *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ DetailedDisconnectContext *ctx;
+ GTask *task;
+
+ ctx = g_new0 (DetailedDisconnectContext, 1);
+ ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
+ ctx->primary = g_object_ref (primary);
+ ctx->data = g_object_ref (data);
+ ctx->retries = MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)detailed_disconnect_context_free);
+
+ mm_base_modem_at_command_full (
+ ctx->modem,
+ ctx->primary,
+ "$NWQMIDISCONNECT",
+ 10, /* timeout */
+ FALSE, /* allow_cached */
+ FALSE, /* is_raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)disconnect_3gpp_check_status,
+ task); /* user_data */
+}
+
+/*****************************************************************************/
+
+MMBaseBearer *
+mm_broadband_bearer_novatel_lte_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *bearer;
+ GObject *source;
+
+ source = g_async_result_get_source_object (res);
+ bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!bearer)
+ return NULL;
+
+ /* Only export valid bearers */
+ mm_base_bearer_export (MM_BASE_BEARER (bearer));
+
+ return MM_BASE_BEARER (bearer);
+}
+
+void
+mm_broadband_bearer_novatel_lte_new (MMBroadbandModemNovatelLte *modem,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (
+ MM_TYPE_BROADBAND_BEARER_NOVATEL_LTE,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_BEARER_MODEM, modem,
+ MM_BASE_BEARER_CONFIG, config,
+ NULL);
+}
+
+static void
+mm_broadband_bearer_novatel_lte_init (MMBroadbandBearerNovatelLte *self)
+{
+}
+
+static void
+mm_broadband_bearer_novatel_lte_class_init (MMBroadbandBearerNovatelLteClass *klass)
+{
+ MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);
+ MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass);
+
+ base_bearer_class->load_connection_status = load_connection_status;
+ base_bearer_class->load_connection_status_finish = load_connection_status_finish;
+#if defined WITH_SUSPEND_RESUME
+ base_bearer_class->reload_connection_status = load_connection_status;
+ base_bearer_class->reload_connection_status_finish = load_connection_status_finish;
+#endif
+
+ broadband_bearer_class->connect_3gpp = connect_3gpp;
+ broadband_bearer_class->connect_3gpp_finish = connect_3gpp_finish;
+ broadband_bearer_class->disconnect_3gpp = disconnect_3gpp;
+ broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish;
+}
diff --git a/src/plugins/novatel/mm-broadband-bearer-novatel-lte.h b/src/plugins/novatel/mm-broadband-bearer-novatel-lte.h
new file mode 100644
index 00000000..4f503865
--- /dev/null
+++ b/src/plugins/novatel/mm-broadband-bearer-novatel-lte.h
@@ -0,0 +1,60 @@
+/* -*- 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:
+ *
+ * Author: Nathan Williams <njw@google.com>
+ *
+ * Copyright (C) 2012 Google, Inc.
+ */
+
+#ifndef MM_BROADBAND_BEARER_NOVATEL_LTE_H
+#define MM_BROADBAND_BEARER_NOVATEL_LTE_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-broadband-bearer.h"
+#include "mm-broadband-modem-novatel-lte.h"
+
+#define MM_TYPE_BROADBAND_BEARER_NOVATEL_LTE (mm_broadband_bearer_novatel_lte_get_type ())
+#define MM_BROADBAND_BEARER_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_NOVATEL_LTE, MMBroadbandBearerNovatelLte))
+#define MM_BROADBAND_BEARER_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_NOVATEL_LTE, MMBroadbandBearerNovatelLteClass))
+#define MM_IS_BROADBAND_BEARER_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_NOVATEL_LTE))
+#define MM_IS_BROADBAND_BEARER_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_NOVATEL_LTE))
+#define MM_BROADBAND_BEARER_NOVATEL_LTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_NOVATEL_LTE, MMBroadbandBearerNovatelLteClass))
+
+typedef struct _MMBroadbandBearerNovatelLte MMBroadbandBearerNovatelLte;
+typedef struct _MMBroadbandBearerNovatelLteClass MMBroadbandBearerNovatelLteClass;
+
+struct _MMBroadbandBearerNovatelLte {
+ MMBroadbandBearer parent;
+};
+
+struct _MMBroadbandBearerNovatelLteClass {
+ MMBroadbandBearerClass parent;
+};
+
+GType mm_broadband_bearer_novatel_lte_get_type (void);
+
+/* Default 3GPP bearer creation implementation */
+void mm_broadband_bearer_novatel_lte_new (MMBroadbandModemNovatelLte *modem,
+ MMBearerProperties *properties,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseBearer *mm_broadband_bearer_novatel_lte_new_finish (GAsyncResult *res,
+ GError **error);
+
+
+#endif /* MM_BROADBAND_BEARER_NOVATEL_LTE_H */
diff --git a/src/plugins/novatel/mm-broadband-modem-novatel-lte.c b/src/plugins/novatel/mm-broadband-modem-novatel-lte.c
new file mode 100644
index 00000000..19d1c594
--- /dev/null
+++ b/src/plugins/novatel/mm-broadband-modem-novatel-lte.c
@@ -0,0 +1,701 @@
+/* -*- 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) 2012 Google Inc.
+ * Author: Nathan Williams <njw@google.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-base-modem-at.h"
+#include "mm-broadband-bearer-novatel-lte.h"
+#include "mm-broadband-modem-novatel-lte.h"
+#include "mm-sim-novatel-lte.h"
+#include "mm-errors-types.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-messaging.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-serial-parsers.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemNovatelLte, mm_broadband_modem_novatel_lte, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init));
+
+/*****************************************************************************/
+/* Modem power down (Modem interface) */
+
+static gboolean
+modem_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_power_down (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=4",
+ 6,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Create Bearer (Modem interface) */
+
+static MMBaseBearer *
+modem_create_bearer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+broadband_bearer_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer = NULL;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_novatel_lte_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+ g_object_unref (task);
+}
+
+static void
+modem_create_bearer (MMIfaceModem *self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* We just create a MMBroadbandBearer */
+ mm_broadband_bearer_novatel_lte_new (MM_BROADBAND_MODEM_NOVATEL_LTE (self),
+ properties,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_new_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Create SIM (Modem interface) */
+
+static MMBaseSim *
+modem_create_sim_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return mm_sim_novatel_lte_new_finish (res, error);
+}
+
+static void
+modem_create_sim (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* New Novatel LTE SIM */
+ mm_sim_novatel_lte_new (MM_BASE_MODEM (self),
+ NULL, /* cancellable */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* After SIM unlock (Modem interface) */
+
+static gboolean
+modem_after_sim_unlock_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static gboolean
+after_sim_unlock_wait_cb (GTask *task)
+{
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+modem_after_sim_unlock (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* A 3-second wait is necessary for SIM to become ready.
+ * Otherwise, a subsequent AT+CRSM command will likely fail. */
+ g_timeout_add_seconds (3, (GSourceFunc)after_sim_unlock_wait_cb, task);
+}
+
+/*****************************************************************************/
+/* Load own numbers (Modem interface) */
+
+static GStrv
+load_own_numbers_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GVariant *result;
+ GStrv own_numbers;
+
+ result = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error);
+ if (!result)
+ return NULL;
+
+ own_numbers = (GStrv) g_variant_dup_strv (result, NULL);
+ return own_numbers;
+}
+
+static MMBaseModemAtResponseProcessorResult
+response_processor_cnum_ignore_at_errors (MMBaseModem *self,
+ gpointer none,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ GStrv own_numbers;
+
+ *result = NULL;
+ *result_error = NULL;
+
+ if (error) {
+ /* Ignore AT errors (ie, ERROR or CMx ERROR) */
+ if (error->domain != MM_MOBILE_EQUIPMENT_ERROR || last_command) {
+ *result_error = g_error_copy (error);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
+ }
+
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
+ }
+
+ own_numbers = mm_3gpp_parse_cnum_exec_response (response);
+ if (!own_numbers)
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
+
+ *result = g_variant_new_strv ((const gchar *const *) own_numbers, -1);
+ g_strfreev (own_numbers);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
+}
+
+static MMBaseModemAtResponseProcessorResult
+response_processor_nwmdn_ignore_at_errors (MMBaseModem *self,
+ gpointer none,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ g_auto(GStrv) own_numbers = NULL;
+ GPtrArray *array;
+ gchar *mdn;
+
+ *result = NULL;
+ *result_error = NULL;
+
+ if (error) {
+ /* Ignore AT errors (ie, ERROR or CMx ERROR) */
+ if (error->domain != MM_MOBILE_EQUIPMENT_ERROR || last_command) {
+ *result_error = g_error_copy (error);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
+ }
+
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
+ }
+
+ mdn = g_strdup (mm_strip_tag (response, "$NWMDN:"));
+
+ array = g_ptr_array_new ();
+ g_ptr_array_add (array, mdn);
+ g_ptr_array_add (array, NULL);
+ own_numbers = (GStrv) g_ptr_array_free (array, FALSE);
+
+ *result = g_variant_new_strv ((const gchar *const *) own_numbers, -1);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
+}
+
+static const MMBaseModemAtCommand own_numbers_commands[] = {
+ { "+CNUM", 3, TRUE, response_processor_cnum_ignore_at_errors },
+ { "$NWMDN", 3, TRUE, response_processor_nwmdn_ignore_at_errors },
+ { NULL }
+};
+
+static void
+load_own_numbers (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_sequence (
+ MM_BASE_MODEM (self),
+ own_numbers_commands,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load supported bands (Modem interface) */
+
+/*
+ * Mapping from bits set in response of $NWBAND? command to MMModemBand values.
+ * The bit positions and band names on the right come from the response to $NWBAND=?
+ */
+static MMModemBand bandbits[] = {
+ MM_MODEM_BAND_CDMA_BC0, /* "00 CDMA2000 Band Class 0, A-System" */
+ MM_MODEM_BAND_CDMA_BC0, /* "01 CDMA2000 Band Class 0, B-System" */
+ MM_MODEM_BAND_CDMA_BC1, /* "02 CDMA2000 Band Class 1, all blocks" */
+ MM_MODEM_BAND_CDMA_BC2, /* "03 CDMA2000 Band Class 2, place holder" */
+ MM_MODEM_BAND_CDMA_BC3, /* "04 CDMA2000 Band Class 3, A-System" */
+ MM_MODEM_BAND_CDMA_BC4, /* "05 CDMA2000 Band Class 4, all blocks" */
+ MM_MODEM_BAND_CDMA_BC5, /* "06 CDMA2000 Band Class 5, all blocks" */
+ MM_MODEM_BAND_DCS, /* "07 GSM DCS band" */
+ MM_MODEM_BAND_EGSM, /* "08 GSM Extended GSM (E-GSM) band" */
+ MM_MODEM_BAND_UNKNOWN, /* "09 GSM Primary GSM (P-GSM) band" */
+ MM_MODEM_BAND_CDMA_BC6, /* "10 CDMA2000 Band Class 6" */
+ MM_MODEM_BAND_CDMA_BC7, /* "11 CDMA2000 Band Class 7" */
+ MM_MODEM_BAND_CDMA_BC8, /* "12 CDMA2000 Band Class 8" */
+ MM_MODEM_BAND_CDMA_BC9, /* "13 CDMA2000 Band Class 9" */
+ MM_MODEM_BAND_CDMA_BC10, /* "14 CDMA2000 Band Class 10 */
+ MM_MODEM_BAND_CDMA_BC11, /* "15 CDMA2000 Band Class 11 */
+ MM_MODEM_BAND_G450, /* "16 GSM 450 band" */
+ MM_MODEM_BAND_G480, /* "17 GSM 480 band" */
+ MM_MODEM_BAND_G750, /* "18 GSM 750 band" */
+ MM_MODEM_BAND_G850, /* "19 GSM 850 band" */
+ MM_MODEM_BAND_UNKNOWN, /* "20 GSM 900 Railways band" */
+ MM_MODEM_BAND_PCS, /* "21 GSM PCS band" */
+ MM_MODEM_BAND_UTRAN_1, /* "22 WCDMA I IMT 2000 band" */
+ MM_MODEM_BAND_UTRAN_2, /* "23 WCDMA II PCS band" */
+ MM_MODEM_BAND_UTRAN_3, /* "24 WCDMA III 1700 band" */
+ MM_MODEM_BAND_UTRAN_4, /* "25 WCDMA IV 1700 band" */
+ MM_MODEM_BAND_UTRAN_5, /* "26 WCDMA V US850 band" */
+ MM_MODEM_BAND_UTRAN_6, /* "27 WCDMA VI JAPAN 800 band" */
+ MM_MODEM_BAND_UNKNOWN, /* "28 Reserved for BC12/BC14 */
+ MM_MODEM_BAND_UNKNOWN, /* "29 Reserved for BC12/BC14 */
+ MM_MODEM_BAND_UNKNOWN, /* "30 Reserved" */
+ MM_MODEM_BAND_UNKNOWN, /* "31 Reserved" */
+};
+
+static GArray *
+load_supported_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_supported_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ GArray *bands;
+ guint i;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /*
+ * The modem doesn't support telling us what bands are supported;
+ * list everything we know about.
+ */
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 23);
+ for (i = 0 ; i < G_N_ELEMENTS (bandbits) ; i++) {
+ if (bandbits[i] != MM_MODEM_BAND_UNKNOWN)
+ g_array_append_val(bands, bandbits[i]);
+ }
+
+ g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Load current bands (Modem interface) */
+
+static GArray *
+load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_current_bands_done (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GArray *bands;
+ const gchar *response;
+ GError *error = NULL;
+ guint i;
+ guint32 bandval;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /*
+ * Response is "$NWBAND: <hex value>", where the hex value is a
+ * bitmask of supported bands.
+ */
+ bandval = (guint32)strtoul(response + 9, NULL, 16);
+
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 4);
+ for (i = 0 ; i < G_N_ELEMENTS (bandbits) ; i++) {
+ if ((bandval & (1 << i)) && bandbits[i] != MM_MODEM_BAND_UNKNOWN)
+ g_array_append_val(bands, bandbits[i]);
+ }
+
+ g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_current_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "$NWBAND?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)load_current_bands_done,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Load unlock retries (Modem interface) */
+
+static MMUnlockRetries *
+load_unlock_retries_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_unlock_retries_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ gint pin_num, pin_value;
+ int scan_count;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ response = mm_strip_tag (response, "$NWPINR:");
+
+ scan_count = sscanf (response, "PIN%d, %d", &pin_num, &pin_value);
+ if (scan_count != 2 || (pin_num != 1 && pin_num != 2)) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Invalid unlock retries response: '%s'",
+ response);
+ } else {
+ MMUnlockRetries *retries;
+
+ retries = mm_unlock_retries_new ();
+ mm_unlock_retries_set (retries,
+ pin_num == 1 ? MM_MODEM_LOCK_SIM_PIN : MM_MODEM_LOCK_SIM_PIN2,
+ pin_value);
+ g_task_return_pointer (task, retries, g_object_unref);
+ }
+ g_object_unref (task);
+}
+
+static void
+load_unlock_retries (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "$NWPINR?",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)load_unlock_retries_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Load access technologies (Modem interface) */
+
+static gboolean
+load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ 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 FALSE;
+ }
+
+ *access_technologies = (MMModemAccessTechnology) value;
+ *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY;
+ return TRUE;
+}
+
+static void
+load_access_technologies_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ MMModemAccessTechnology act;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ if (strstr (response, "LTE"))
+ act |= MM_MODEM_ACCESS_TECHNOLOGY_LTE;
+ if (strstr (response, "WCDMA"))
+ act |= MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ if (strstr (response, "EV-DO Rev 0"))
+ act |= MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
+ if (strstr (response, "EV-DO Rev A"))
+ act |= MM_MODEM_ACCESS_TECHNOLOGY_EVDOA;
+ if (strstr (response, "CDMA 1X"))
+ act |= MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
+ if (strstr (response, "GSM"))
+ act |= MM_MODEM_ACCESS_TECHNOLOGY_GSM;
+
+ g_task_return_int (task, act);
+ g_object_unref (task);
+}
+
+static void
+load_access_technologies (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "$NWSYSMODE",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)load_access_technologies_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Reset (Modem interface) */
+
+static gboolean
+reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=6",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Scan networks (3GPP interface) */
+
+static GList *
+scan_networks_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_scan_networks_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ GList *scan_result;
+
+ scan_result = iface_modem_3gpp_parent->scan_networks_finish (self, res, &error);
+ if (!scan_result)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task,
+ scan_result,
+ (GDestroyNotify)mm_3gpp_network_info_list_free);
+ g_object_unref (task);
+}
+
+static void
+scan_networks (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ MMModemAccessTechnology access_tech;
+
+ mm_obj_dbg (self, "scanning for networks (Novatel LTE)...");
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* The Novatel LTE modem does not properly support AT+COPS=? in LTE mode.
+ * Thus, do not try to scan networks when the current access technologies
+ * include LTE.
+ */
+ access_tech = mm_iface_modem_get_access_technologies (MM_IFACE_MODEM (self));
+ if (access_tech & MM_MODEM_ACCESS_TECHNOLOGY_LTE) {
+ g_autofree gchar *access_tech_string = NULL;
+
+ access_tech_string = mm_modem_access_technology_build_string_from_mask (access_tech);
+ mm_obj_warn (self, "couldn't scan for networks with access technologies: %s", access_tech_string);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Couldn't scan for networks with access technologies: %s",
+ access_tech_string);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Otherwise, just fallback to the generic scan method */
+ iface_modem_3gpp_parent->scan_networks (self,
+ (GAsyncReadyCallback)parent_scan_networks_ready,
+ task);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemNovatelLte *
+mm_broadband_modem_novatel_lte_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_NOVATEL_LTE,
+ 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,
+ /* Novatel LTE bearer supports NET only */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_novatel_lte_init (MMBroadbandModemNovatelLte *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface->modem_power_down = modem_power_down;
+ iface->modem_power_down_finish = modem_power_down_finish;
+ iface->create_bearer = modem_create_bearer;
+ iface->create_bearer_finish = modem_create_bearer_finish;
+ iface->create_sim = modem_create_sim;
+ iface->create_sim_finish = modem_create_sim_finish;
+ iface->modem_after_sim_unlock = modem_after_sim_unlock;
+ iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish;
+ iface->load_own_numbers = load_own_numbers;
+ iface->load_own_numbers_finish = load_own_numbers_finish;
+ iface->load_supported_bands = load_supported_bands;
+ iface->load_supported_bands_finish = load_supported_bands_finish;
+ iface->load_current_bands = load_current_bands;
+ iface->load_current_bands_finish = load_current_bands_finish;
+ iface->load_unlock_retries = load_unlock_retries;
+ iface->load_unlock_retries_finish = load_unlock_retries_finish;
+ /* No support for setting bands, as it destabilizes the modem. */
+ iface->load_access_technologies = load_access_technologies;
+ iface->load_access_technologies_finish = load_access_technologies_finish;
+ iface->reset = reset;
+ iface->reset_finish = reset_finish;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->scan_networks = scan_networks;
+ iface->scan_networks_finish = scan_networks_finish;
+}
+
+static void
+mm_broadband_modem_novatel_lte_class_init (MMBroadbandModemNovatelLteClass *klass)
+{
+}
diff --git a/src/plugins/novatel/mm-broadband-modem-novatel-lte.h b/src/plugins/novatel/mm-broadband-modem-novatel-lte.h
new file mode 100644
index 00000000..0f64339d
--- /dev/null
+++ b/src/plugins/novatel/mm-broadband-modem-novatel-lte.h
@@ -0,0 +1,50 @@
+/* -*- 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) 2012 Google Inc.
+ * Author: Nathan Williams <njw@google.com>
+ */
+
+#ifndef MM_BROADBAND_MODEM_NOVATEL_LTE_H
+#define MM_BROADBAND_MODEM_NOVATEL_LTE_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_NOVATEL_LTE (mm_broadband_modem_novatel_lte_get_type ())
+#define MM_BROADBAND_MODEM_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_NOVATEL_LTE, MMBroadbandModemNovatelLte))
+#define MM_BROADBAND_MODEM_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_NOVATEL_LTE, MMBroadbandModemNovatelLteClass))
+#define MM_IS_BROADBAND_MODEM_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_NOVATEL_LTE))
+#define MM_IS_BROADBAND_MODEM_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_NOVATEL_LTE))
+#define MM_BROADBAND_MODEM_NOVATEL_LTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_NOVATEL_LTE, MMBroadbandModemNovatelLteClass))
+
+typedef struct _MMBroadbandModemNovatelLte MMBroadbandModemNovatelLte;
+typedef struct _MMBroadbandModemNovatelLteClass MMBroadbandModemNovatelLteClass;
+typedef struct _MMBroadbandModemNovatelLtePrivate MMBroadbandModemNovatelLtePrivate;
+
+struct _MMBroadbandModemNovatelLte {
+ MMBroadbandModem parent;
+ MMBroadbandModemNovatelLtePrivate *priv;
+};
+
+struct _MMBroadbandModemNovatelLteClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_novatel_lte_get_type (void);
+
+MMBroadbandModemNovatelLte *mm_broadband_modem_novatel_lte_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_NOVATEL_LTE_H */
diff --git a/src/plugins/novatel/mm-broadband-modem-novatel.c b/src/plugins/novatel/mm-broadband-modem-novatel.c
new file mode 100644
index 00000000..cd8e5676
--- /dev/null
+++ b/src/plugins/novatel/mm-broadband-modem-novatel.c
@@ -0,0 +1,1609 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-cdma.h"
+#include "mm-iface-modem-time.h"
+#include "mm-iface-modem-messaging.h"
+#include "mm-broadband-modem-novatel.h"
+#include "mm-errors-types.h"
+#include "mm-modem-helpers.h"
+#include "mm-common-helpers.h"
+#include "libqcdm/src/commands.h"
+#include "libqcdm/src/result.h"
+#include "mm-log-object.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_messaging_init (MMIfaceModemMessaging *iface);
+static void iface_modem_cdma_init (MMIfaceModemCdma *iface);
+static void iface_modem_time_init (MMIfaceModemTime *iface);
+
+static MMIfaceModem *iface_modem_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemNovatel, mm_broadband_modem_novatel, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init))
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_load_supported_modes_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ GArray *all;
+ GArray *combinations;
+ GArray *filtered;
+ MMModemModeCombination mode;
+
+ all = iface_modem_parent->load_supported_modes_finish (self, res, &error);
+ if (!all) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5);
+
+ /* 2G only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 3G only */
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G, 2G preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G, 3G preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+
+ /* Filter out those unsupported modes */
+ filtered = mm_filter_supported_modes (all, combinations, self);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Run parent's loading */
+ iface_modem_parent->load_supported_modes (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_supported_modes_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Load initial allowed/preferred modes (Modem interface) */
+
+typedef struct {
+ MMModemMode allowed;
+ MMModemMode preferred;
+} LoadCurrentModesResult;
+
+static gboolean
+load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ LoadCurrentModesResult *result;
+
+ result = g_task_propagate_pointer (G_TASK (res), error);
+ if (!result)
+ return FALSE;
+
+ *allowed = result->allowed;
+ *preferred = result->preferred;
+ g_free (result);
+
+ return TRUE;
+}
+
+static void
+nwrat_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LoadCurrentModesResult *result;
+ GError *error = NULL;
+ const gchar *response;
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ gint a = -1;
+ gint b = -1;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Parse response */
+ r = g_regex_new ("\\$NWRAT:\\s*(\\d),(\\d),(\\d)", G_REGEX_UNGREEDY, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &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 match NWRAT reply: %s",
+ response);
+ g_object_unref (task);
+ return;
+ }
+
+ if (!mm_get_int_from_match_info (match_info, 1, &a) ||
+ !mm_get_int_from_match_info (match_info, 2, &b) ||
+ a < 0 || a > 2 ||
+ b < 1 || b > 2) {
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse mode/tech response '%s': invalid modes reported",
+ response);
+ g_object_unref (task);
+ return;
+ }
+
+ result = g_new0 (LoadCurrentModesResult, 1);
+
+ switch (a) {
+ case 0:
+ result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ result->preferred = MM_MODEM_MODE_NONE;
+ break;
+ case 1:
+ if (b == 1) {
+ result->allowed = MM_MODEM_MODE_2G;
+ result->preferred = MM_MODEM_MODE_NONE;
+ } else /* b == 2 */ {
+ result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ result->preferred = MM_MODEM_MODE_2G;
+ }
+ break;
+ case 2:
+ if (b == 1) {
+ result->allowed = MM_MODEM_MODE_3G;
+ result->preferred = MM_MODEM_MODE_NONE;
+ } else /* b == 2 */ {
+ result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ result->preferred = MM_MODEM_MODE_3G;
+ }
+ break;
+ default:
+ /* We only allow mode 0|1|2 */
+ g_assert_not_reached ();
+ break;
+ }
+
+ g_task_return_pointer (task, result, g_free);
+ g_object_unref (task);
+}
+
+static void
+load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Load allowed modes only in 3GPP modems */
+ if (!mm_iface_modem_is_3gpp (self)) {
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Loading allowed modes not supported in CDMA-only modems");
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "$NWRAT?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)nwrat_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Set allowed modes (Modem interface) */
+
+static gboolean
+set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+allowed_mode_update_ready (MMBroadbandModemNovatel *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error)
+ /* Let the error be critical. */
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *command;
+ gint a = -1;
+ gint b = -1;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Setting allowed modes only in 3GPP modems */
+ if (!mm_iface_modem_is_3gpp (self)) {
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Setting allowed modes not supported in CDMA-only modems");
+ g_object_unref (task);
+ return;
+ }
+
+ if (allowed == MM_MODEM_MODE_2G) {
+ a = 1;
+ b = 1;
+ } else if (allowed == MM_MODEM_MODE_3G) {
+ a = 2;
+ b = 1;
+ } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) {
+ b = 2;
+ if (preferred == MM_MODEM_MODE_NONE)
+ a = 0;
+ else if (preferred == MM_MODEM_MODE_2G)
+ a = 1;
+ else if (preferred == MM_MODEM_MODE_3G)
+ a = 2;
+ } else if (allowed == MM_MODEM_MODE_ANY &&
+ preferred == MM_MODEM_MODE_NONE) {
+ b = 2;
+ a = 0;
+ }
+
+ if (a < 0 || b < 0) {
+ gchar *allowed_str;
+ gchar *preferred_str;
+
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Requested mode (allowed: '%s', preferred: '%s') not "
+ "supported by the modem.",
+ allowed_str,
+ preferred_str);
+ g_object_unref (task);
+ g_free (allowed_str);
+ g_free (preferred_str);
+ return;
+ }
+
+ command = g_strdup_printf ("AT$NWRAT=%d,%d", a, b);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)allowed_mode_update_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+
+static void
+close_and_unref_port (MMPortSerialQcdm *port)
+{
+ mm_port_serial_close (MM_PORT_SERIAL (port));
+ g_object_unref (port);
+}
+
+static gboolean
+get_evdo_version_finish (MMBaseModem *self,
+ GAsyncResult *res,
+ guint *hdr_revision, /* QCDM_HDR_REV_* */
+ GError **error)
+{
+ gssize result;
+
+ result = g_task_propagate_int (G_TASK (res), error);
+ if (result < 0)
+ return FALSE;
+
+ *hdr_revision = (guint8) result;
+ return TRUE;
+}
+
+static void
+nw_snapshot_old_ready (MMPortSerialQcdm *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ QcdmResult *result;
+ GError *error = NULL;
+ GByteArray *response;
+ guint8 hdr_revision = QCDM_HDR_REV_UNKNOWN;
+
+ response = mm_port_serial_qcdm_command_finish (port, res, &error);
+ if (error) {
+ /* Just ignore the error and complete with the input info */
+ g_prefix_error (&error, "Couldn't run QCDM Novatel Modem MSM6500 snapshot: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Parse the response */
+ result = qcdm_cmd_nw_subsys_modem_snapshot_cdma_result ((const gchar *) response->data, response->len, NULL);
+ g_byte_array_unref (response);
+ if (!result) {
+ g_prefix_error (&error, "Failed to get QCDM Novatel Modem MSM6500 snapshot: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Success */
+ qcdm_result_get_u8 (result, QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_HDR_REV, &hdr_revision);
+ qcdm_result_unref (result);
+
+ g_task_return_int (task, (gint) hdr_revision);
+ g_object_unref (task);
+}
+
+static void
+nw_snapshot_new_ready (MMPortSerialQcdm *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemNovatel *self;
+ QcdmResult *result;
+ GByteArray *nwsnap;
+ GError *error = NULL;
+ GByteArray *response;
+ guint8 hdr_revision = QCDM_HDR_REV_UNKNOWN;
+
+ self = g_task_get_source_object (task);
+
+ response = mm_port_serial_qcdm_command_finish (port, res, &error);
+ if (error) {
+ g_prefix_error (&error, "couldn't run QCDM Novatel Modem MSM6800 snapshot: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Parse the response */
+ result = qcdm_cmd_nw_subsys_modem_snapshot_cdma_result ((const gchar *) response->data, response->len, NULL);
+ g_byte_array_unref (response);
+ if (result) {
+ /* Success */
+ qcdm_result_get_u8 (result, QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_HDR_REV, &hdr_revision);
+ qcdm_result_unref (result);
+
+ g_task_return_int (task, (gint) hdr_revision);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "failed to get QCDM Novatel Modem MSM6800 snapshot");
+
+ /* Try for MSM6500 */
+ nwsnap = g_byte_array_sized_new (25);
+ nwsnap->len = qcdm_cmd_nw_subsys_modem_snapshot_cdma_new ((char *) nwsnap->data, 25, QCDM_NW_CHIPSET_6500);
+ g_assert (nwsnap->len);
+ mm_port_serial_qcdm_command (port,
+ nwsnap,
+ 3,
+ NULL,
+ (GAsyncReadyCallback)nw_snapshot_old_ready,
+ task);
+ g_byte_array_unref (nwsnap);
+}
+
+static void
+get_evdo_version (MMBaseModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ GByteArray *nwsnap;
+ GTask *task;
+ MMPortSerialQcdm *port;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ port = mm_base_modem_get_port_qcdm (self);
+ if (!port) {
+ error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "No available QCDM port");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+ g_task_set_task_data (task, port, (GDestroyNotify) close_and_unref_port);
+
+ if (!mm_port_serial_open (MM_PORT_SERIAL (port), &error)) {
+ g_prefix_error (&error, "couldn't open QCDM port: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Try MSM6800 first since newer cards use that */
+ nwsnap = g_byte_array_sized_new (25);
+ nwsnap->len = qcdm_cmd_nw_subsys_modem_snapshot_cdma_new ((char *) nwsnap->data, 25, QCDM_NW_CHIPSET_6800);
+ g_assert (nwsnap->len);
+ mm_port_serial_qcdm_command (port,
+ nwsnap,
+ 3,
+ NULL,
+ (GAsyncReadyCallback)nw_snapshot_new_ready,
+ task);
+ g_byte_array_unref (nwsnap);
+}
+
+/*****************************************************************************/
+/* Load access technologies (Modem interface) */
+
+typedef struct {
+ MMModemAccessTechnology act;
+ guint mask;
+ guint hdr_revision; /* QCDM_HDR_REV_* */
+} AccessTechContext;
+
+static gboolean
+modem_load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ GError **error)
+{
+ AccessTechContext *ctx;
+
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return FALSE;
+
+ /* Update access technology with specific EVDO revision from QCDM if we have them */
+ ctx = g_task_get_task_data (G_TASK (res));
+ if (ctx->act & MM_IFACE_MODEM_CDMA_ALL_EVDO_ACCESS_TECHNOLOGIES_MASK) {
+ if (ctx->hdr_revision == QCDM_HDR_REV_0) {
+ mm_obj_dbg (self, "modem snapshot EVDO revision: 0");
+ ctx->act &= ~MM_IFACE_MODEM_CDMA_ALL_EVDO_ACCESS_TECHNOLOGIES_MASK;
+ ctx->act |= MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
+ } else if (ctx->hdr_revision == QCDM_HDR_REV_A) {
+ mm_obj_dbg (self, "modem snapshot EVDO revision: A");
+ ctx->act &= ~MM_IFACE_MODEM_CDMA_ALL_EVDO_ACCESS_TECHNOLOGIES_MASK;
+ ctx->act |= MM_MODEM_ACCESS_TECHNOLOGY_EVDOA;
+ } else
+ mm_obj_dbg (self, "modem snapshot EVDO revision: %d (unknown)", ctx->hdr_revision);
+ }
+
+ *access_technologies = ctx->act;
+ *mask = ctx->mask;
+ return TRUE;
+}
+
+static void
+cnti_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ AccessTechContext *ctx = g_task_get_task_data (task);
+ GError *error = NULL;
+ const gchar *response;
+ const gchar *p;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ p = mm_strip_tag (response, "$CNTI:");
+ p = strchr (p, ',');
+ if (!p) {
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse $CNTI result '%s'",
+ response);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->act = mm_string_to_access_tech (p);
+ ctx->mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK;
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+evdo_version_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ AccessTechContext *ctx = g_task_get_task_data (task);
+
+ if (!get_evdo_version_finish (self, res, &ctx->hdr_revision, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_load_access_technologies_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ AccessTechContext *ctx = g_task_get_task_data (task);
+
+ if (!iface_modem_parent->load_access_technologies_finish (self,
+ res,
+ &ctx->act,
+ &ctx->mask,
+ &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* No point in checking EVDO revision if EVDO isn't being used */
+ if (!(ctx->act & MM_IFACE_MODEM_CDMA_ALL_EVDO_ACCESS_TECHNOLOGIES_MASK)) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Get the EVDO revision from QCDM */
+ get_evdo_version (MM_BASE_MODEM (self),
+ (GAsyncReadyCallback) evdo_version_ready,
+ task);
+}
+
+static void
+modem_load_access_technologies (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ AccessTechContext *ctx;
+ GTask *task;
+
+ /* Setup context */
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_new0 (AccessTechContext, 1);
+ g_task_set_task_data (task, ctx, g_free);
+
+ /* CDMA-only modems defer to parent for generic access technology
+ * checking, but can determine EVDOr0 vs. EVDOrA through proprietary
+ * QCDM commands.
+ */
+ if (mm_iface_modem_is_cdma_only (self)) {
+ iface_modem_parent->load_access_technologies (
+ self,
+ (GAsyncReadyCallback)parent_load_access_technologies_ready,
+ task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "$CNTI=0",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)cnti_set_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Signal quality loading (Modem interface) */
+
+static guint
+modem_load_signal_quality_finish (MMIfaceModem *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 0;
+ }
+ return (guint)value;
+}
+
+static void
+parent_load_signal_quality_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ guint signal_quality;
+
+ signal_quality = iface_modem_parent->load_signal_quality_finish (self, res, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_int (task, signal_quality);
+ g_object_unref (task);
+}
+
+static gint
+get_one_quality (const gchar *reply,
+ const gchar *tag)
+{
+ gint quality = -1;
+ char *temp, *p;
+ gint dbm;
+ gboolean success = FALSE;
+
+ p = strstr (reply, tag);
+ if (!p)
+ return -1;
+
+ /* Skip the tag */
+ p += strlen (tag);
+
+ /* Skip spaces */
+ while (isspace (*p))
+ p++;
+
+ p = temp = g_strdup (p);
+
+ /* Cut off the string after the dBm */
+ while (isdigit (*p) || (*p == '-'))
+ p++;
+ *p = '\0';
+
+ /* When registered with EVDO, RX0/RX1 are returned by many cards with
+ * negative dBm. When registered only with 1x, some cards return "1x RSSI"
+ * with positive dBm.
+ */
+
+ if (mm_get_int_from_str (temp, &dbm)) {
+ if (*temp == '-') {
+ /* Some cards appear to use RX0/RX1 and output RSSI in negative dBm */
+ if (dbm < 0)
+ success = TRUE;
+ } else if (isdigit (*temp) && (dbm > 0) && (dbm <= 125)) {
+ /* S720 appears to use "1x RSSI" and print RSSI in dBm without '-' */
+ dbm *= -1;
+ success = TRUE;
+ }
+ }
+
+ if (success)
+ quality = MM_RSSI_TO_QUALITY (dbm);
+
+ g_free (temp);
+ return quality;
+}
+
+static void
+nwrssi_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ gint quality;
+
+ response = mm_base_modem_at_command_finish (self, res, NULL);
+ if (!response) {
+ /* Fallback to parent's method */
+ iface_modem_parent->load_signal_quality (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_signal_quality_ready,
+ task);
+ return;
+ }
+
+ /* Parse the signal quality */
+ quality = get_one_quality (response, "RX0=");
+ if (quality < 0)
+ quality = get_one_quality (response, "1x RSSI=");
+ if (quality < 0)
+ quality = get_one_quality (response, "RX1=");
+ if (quality < 0)
+ quality = get_one_quality (response, "HDR RSSI=");
+
+ if (quality >= 0)
+ g_task_return_int (task, quality);
+ else
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse $NWRSSI response: '%s'",
+ response);
+ g_object_unref (task);
+}
+
+static void
+modem_load_signal_quality (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* 3GPP modems can just run parent's signal quality loading */
+ if (mm_iface_modem_is_3gpp (self)) {
+ iface_modem_parent->load_signal_quality (
+ self,
+ (GAsyncReadyCallback)parent_load_signal_quality_ready,
+ task);
+ return;
+ }
+
+ /* CDMA modems need custom signal quality loading */
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "$NWRSSI",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)nwrssi_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Automatic activation (CDMA interface) */
+
+static gboolean
+modem_cdma_activate_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+qcmipgetp_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response)
+ g_task_return_error (task, error);
+ else {
+ mm_obj_dbg (self, "current profile information retrieved: %s", response);
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+activate_cdv_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Let's query the MIP profile */
+ mm_base_modem_at_command (self,
+ "$QCMIPGETP",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)qcmipgetp_ready,
+ task);
+}
+
+static void
+modem_cdma_activate (MMIfaceModemCdma *self,
+ const gchar *carrier_code,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *cmd;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ cmd = g_strdup_printf ("+CDV=%s", carrier_code);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)activate_cdv_ready,
+ task);
+ g_free (cmd);
+}
+
+/*****************************************************************************/
+/* Manual activation (CDMA interface) */
+
+/* Wait up to 2 minutes */
+#define MAX_IOTA_QUERY_RETRIES 24
+#define MAX_IOTA_QUERY_RETRY_TIME 5
+
+typedef enum {
+ CDMA_ACTIVATION_STEP_FIRST,
+ CDMA_ACTIVATION_STEP_REQUEST_ACTIVATION,
+ CDMA_ACTIVATION_STEP_OTA_UPDATE,
+ CDMA_ACTIVATION_STEP_PRL_UPDATE,
+ CDMA_ACTIVATION_STEP_WAIT_UNTIL_FINISHED,
+ CDMA_ACTIVATION_STEP_LAST
+} CdmaActivationStep;
+
+typedef struct {
+ CdmaActivationStep step;
+ MMCdmaManualActivationProperties *properties;
+ guint wait_timeout_id;
+ guint wait_retries;
+} CdmaActivationContext;
+
+static void
+cdma_activation_context_free (CdmaActivationContext *ctx)
+{
+ g_assert (ctx->wait_timeout_id == 0);
+ g_object_unref (ctx->properties);
+ g_slice_free (CdmaActivationContext, ctx);
+}
+
+static gboolean
+modem_cdma_activate_manual_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void cdma_activation_step (GTask *task);
+
+static gboolean
+cdma_activation_step_retry (GTask *task)
+{
+ CdmaActivationContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ ctx->wait_timeout_id = 0;
+ cdma_activation_step (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+iota_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+ CdmaActivationContext *ctx;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ /* Finished? */
+ if (strstr (response, "IOTA Enabled")) {
+ ctx->step++;
+ cdma_activation_step (task);
+ return;
+ }
+
+ /* Too many retries? */
+ if (ctx->wait_retries == MAX_IOTA_QUERY_RETRIES) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Too much time waiting to finish the IOTA activation");
+ g_object_unref (task);
+ return;
+ }
+
+ /* Otherwise, schedule retry in some secs */
+ g_assert (ctx->wait_timeout_id == 0);
+ ctx->wait_retries++;
+ ctx->wait_timeout_id = g_timeout_add_seconds (MAX_IOTA_QUERY_RETRY_TIME,
+ (GSourceFunc)cdma_activation_step_retry,
+ task);
+}
+
+static void
+activation_command_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+ CdmaActivationContext *ctx;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Keep on */
+ ctx = g_task_get_task_data (task);
+ ctx->step++;
+ cdma_activation_step (task);
+}
+
+static void
+cdma_activation_step (GTask *task)
+{
+ MMBroadbandModemNovatel *self;
+ CdmaActivationContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case CDMA_ACTIVATION_STEP_FIRST:
+ mm_obj_dbg (self, "launching manual activation...");
+ ctx->step++;
+ /* fall-through */
+
+ case CDMA_ACTIVATION_STEP_REQUEST_ACTIVATION: {
+ gchar *command;
+
+ mm_obj_msg (self, "activation step [1/5]: setting up activation details");
+ command = g_strdup_printf ("$NWACTIVATION=%s,%s,%s",
+ mm_cdma_manual_activation_properties_get_spc (ctx->properties),
+ mm_cdma_manual_activation_properties_get_mdn (ctx->properties),
+ mm_cdma_manual_activation_properties_get_min (ctx->properties));
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ command,
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)activation_command_ready,
+ task);
+ g_free (command);
+ return;
+ }
+
+ case CDMA_ACTIVATION_STEP_OTA_UPDATE:
+ mm_obj_msg (self, "activation step [2/5]: starting OTA activation");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+IOTA=1",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)activation_command_ready,
+ task);
+ return;
+
+ case CDMA_ACTIVATION_STEP_PRL_UPDATE:
+ mm_obj_msg (self, "activation step [3/5]: starting PRL update");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+IOTA=2",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)activation_command_ready,
+ task);
+ return;
+
+ case CDMA_ACTIVATION_STEP_WAIT_UNTIL_FINISHED:
+ mm_obj_msg (self, "activation step [4/5]: checking activation process status");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+IOTA?",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)iota_query_ready,
+ task);
+ return;
+
+ case CDMA_ACTIVATION_STEP_LAST:
+ mm_obj_msg (self, "activation step [5/5]: activation process finished");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+modem_cdma_activate_manual (MMIfaceModemCdma *self,
+ MMCdmaManualActivationProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ CdmaActivationContext *ctx;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Setup context */
+ ctx = g_slice_new0 (CdmaActivationContext);
+ ctx->properties = g_object_ref (properties);
+ ctx->step = CDMA_ACTIVATION_STEP_FIRST;
+ g_task_set_task_data (task, ctx, (GDestroyNotify) cdma_activation_context_free);
+
+ /* And start it */
+ cdma_activation_step (task);
+}
+
+/*****************************************************************************/
+/* Enable unsolicited events (SMS indications) (Messaging interface) */
+
+static gboolean
+messaging_enable_unsolicited_events_finish (MMIfaceModemMessaging *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+messaging_enable_unsolicited_events (MMIfaceModemMessaging *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Many Qualcomm chipsets don't support mode=2 */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CNMI=1,1,2,1,0",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Detailed registration state (CDMA interface) */
+
+typedef struct {
+ MMModemCdmaRegistrationState cdma1x_state;
+ MMModemCdmaRegistrationState evdo_state;
+} DetailedRegistrationStateResult;
+
+typedef struct {
+ MMPortSerialQcdm *port;
+ gboolean close_port;
+ MMModemCdmaRegistrationState cdma1x_state;
+ MMModemCdmaRegistrationState evdo_state;
+} DetailedRegistrationStateContext;
+
+static void
+detailed_registration_state_context_free (DetailedRegistrationStateContext *ctx)
+{
+ if (ctx->port) {
+ if (ctx->close_port)
+ mm_port_serial_close (MM_PORT_SERIAL (ctx->port));
+ g_object_unref (ctx->port);
+ }
+ g_free (ctx);
+}
+
+static gboolean
+modem_cdma_get_detailed_registration_state_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ MMModemCdmaRegistrationState *detailed_cdma1x_state,
+ MMModemCdmaRegistrationState *detailed_evdo_state,
+ GError **error)
+{
+ GTask *task = G_TASK (res);
+ DetailedRegistrationStateContext *ctx = g_task_get_task_data (task);;
+
+ if (!g_task_propagate_boolean (task, error))
+ return FALSE;
+
+ *detailed_cdma1x_state = ctx->cdma1x_state;
+ *detailed_evdo_state = ctx->evdo_state;
+ return TRUE;
+}
+
+static void
+parse_modem_eri (DetailedRegistrationStateContext *ctx, QcdmResult *result)
+{
+ MMModemCdmaRegistrationState new_state;
+ guint8 indicator_id = 0, icon_id = 0, icon_mode = 0;
+
+ qcdm_result_get_u8 (result, QCDM_CMD_NW_SUBSYS_ERI_ITEM_INDICATOR_ID, &indicator_id);
+ qcdm_result_get_u8 (result, QCDM_CMD_NW_SUBSYS_ERI_ITEM_ICON_ID, &icon_id);
+ qcdm_result_get_u8 (result, QCDM_CMD_NW_SUBSYS_ERI_ITEM_ICON_MODE, &icon_mode);
+
+ /* We use the "Icon ID" (also called the "Icon Index") because if it is 1,
+ * the device is never roaming. Any operator-defined IDs (greater than 2)
+ * may or may not be roaming, but that's operator-defined and we don't
+ * know anything about them.
+ *
+ * Indicator ID:
+ * 0 appears to be "not roaming", contrary to standard ERI values
+ * >= 1 appears to be the actual ERI value, which may or may not be
+ * roaming depending on the operator's custom ERI list
+ *
+ * Icon ID:
+ * 0 = roaming indicator on
+ * 1 = roaming indicator off
+ * 2 = roaming indicator flash
+ *
+ * Icon Mode:
+ * 0 = normal
+ * 1 = flash (only used with Icon ID >= 2)
+ *
+ * Roaming example:
+ * Roam: 160
+ * Indicator ID: 160
+ * Icon ID: 3
+ * Icon Mode: 0
+ * Call Prompt: 1
+ *
+ * Home example:
+ * Roam: 0
+ * Indicator ID: 0
+ * Icon ID: 1
+ * Icon Mode: 0
+ * Call Prompt: 1
+ */
+ if (icon_id == 1)
+ new_state = MM_MODEM_CDMA_REGISTRATION_STATE_HOME;
+ else
+ new_state = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING;
+
+ if (ctx->cdma1x_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
+ ctx->cdma1x_state = new_state;
+ if (ctx->evdo_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
+ ctx->evdo_state = new_state;
+}
+
+static void
+reg_eri_6500_cb (MMPortSerialQcdm *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemNovatel *self;
+ DetailedRegistrationStateContext *ctx;
+ GError *error = NULL;
+ GByteArray *response;
+ QcdmResult *result;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ response = mm_port_serial_qcdm_command_finish (port, res, &error);
+ if (error) {
+ /* Just ignore the error and complete with the input info */
+ mm_obj_dbg (self, "couldn't run QCDM MSM6500 ERI: %s", error->message);
+ g_error_free (error);
+ goto done;
+ }
+
+ result = qcdm_cmd_nw_subsys_eri_result ((const gchar *) response->data, response->len, NULL);
+ g_byte_array_unref (response);
+ if (result) {
+ parse_modem_eri (ctx, result);
+ qcdm_result_unref (result);
+ }
+
+done:
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+reg_eri_6800_cb (MMPortSerialQcdm *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemNovatel *self;
+ DetailedRegistrationStateContext *ctx;
+ GError *error = NULL;
+ GByteArray *response;
+ GByteArray *nweri;
+ QcdmResult *result;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ response = mm_port_serial_qcdm_command_finish (port, res, &error);
+ if (error) {
+ /* Just ignore the error and complete with the input info */
+ mm_obj_dbg (self, "couldn't run QCDM MSM6800 ERI: %s", error->message);
+ g_error_free (error);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Parse the response */
+ result = qcdm_cmd_nw_subsys_eri_result ((const gchar *) response->data, response->len, NULL);
+ g_byte_array_unref (response);
+ if (result) {
+ /* Success */
+ parse_modem_eri (ctx, result);
+ qcdm_result_unref (result);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Try for MSM6500 */
+ nweri = g_byte_array_sized_new (25);
+ nweri->len = qcdm_cmd_nw_subsys_eri_new ((char *) nweri->data, 25, QCDM_NW_CHIPSET_6500);
+ g_assert (nweri->len);
+ mm_port_serial_qcdm_command (port,
+ nweri,
+ 3,
+ NULL,
+ (GAsyncReadyCallback)reg_eri_6500_cb,
+ task);
+ g_byte_array_unref (nweri);
+}
+
+static void
+modem_cdma_get_detailed_registration_state (MMIfaceModemCdma *self,
+ MMModemCdmaRegistrationState cdma1x_state,
+ MMModemCdmaRegistrationState evdo_state,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ DetailedRegistrationStateContext *ctx;
+ GTask *task;
+ GByteArray *nweri;
+ GError *error = NULL;
+
+ /* Setup context */
+ task = g_task_new (self, NULL, callback, user_data);
+ ctx = g_new0 (DetailedRegistrationStateContext, 1);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) detailed_registration_state_context_free);
+
+ ctx->cdma1x_state = cdma1x_state;
+ ctx->evdo_state = evdo_state;
+
+ ctx->port = mm_base_modem_get_port_qcdm (MM_BASE_MODEM (self));
+ if (!ctx->port) {
+ /* Ignore errors and use non-detailed registration state */
+ mm_obj_dbg (self, "no available QCDM port");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ if (!mm_port_serial_open (MM_PORT_SERIAL (ctx->port), &error)) {
+ /* Ignore errors and use non-detailed registration state */
+ mm_obj_dbg (self, "couldn't open QCDM port: %s", error->message);
+ g_error_free (error);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->close_port = TRUE;
+
+ /* Try MSM6800 first since newer cards use that */
+ nweri = g_byte_array_sized_new (25);
+ nweri->len = qcdm_cmd_nw_subsys_eri_new ((char *) nweri->data, 25, QCDM_NW_CHIPSET_6800);
+ g_assert (nweri->len);
+ mm_port_serial_qcdm_command (ctx->port,
+ nweri,
+ 3,
+ NULL,
+ (GAsyncReadyCallback)reg_eri_6800_cb,
+ task);
+ g_byte_array_unref (nweri);
+}
+
+/*****************************************************************************/
+/* Load network time (Time interface) */
+
+static gboolean
+parse_nwltime_reply (const char *response,
+ gchar **out_iso_8601,
+ MMNetworkTimezone **out_tz,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *match_error = NULL;
+ guint year;
+ guint month;
+ guint day;
+ guint hour;
+ guint minute;
+ guint second;
+ g_autofree gchar *result = NULL;
+ gint utc_offset = 0;
+ gboolean success = FALSE;
+
+ /* Sample reply: 2013.3.27.15.47.19.2.-5 */
+ r = g_regex_new ("(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.([\\-\\+\\d]+)$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse $NWLTIME results: ");
+ } else {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't match $NWLTIME reply");
+ }
+ } else {
+ /* Remember that g_match_info_get_match_count() includes match #0 */
+ g_assert (g_match_info_get_match_count (match_info) >= 9);
+
+ if (mm_get_uint_from_match_info (match_info, 1, &year) &&
+ mm_get_uint_from_match_info (match_info, 2, &month) &&
+ mm_get_uint_from_match_info (match_info, 3, &day) &&
+ mm_get_uint_from_match_info (match_info, 4, &hour) &&
+ mm_get_uint_from_match_info (match_info, 5, &minute) &&
+ mm_get_uint_from_match_info (match_info, 6, &second) &&
+ mm_get_int_from_match_info (match_info, 8, &utc_offset)) {
+ result = mm_new_iso8601_time (year, month, day, hour, minute, second,
+ TRUE, utc_offset * 60, error);
+ if (out_tz) {
+ *out_tz = mm_network_timezone_new ();
+ mm_network_timezone_set_offset (*out_tz, utc_offset * 60);
+ }
+
+ success = (result != NULL);
+ } else {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse $NWLTIME reply");
+ }
+ }
+
+ if (out_iso_8601)
+ *out_iso_8601 = g_steal_pointer (&result);
+
+ return success;
+}
+
+static gchar *
+modem_time_load_network_time_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *response;
+ gchar *result = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (response)
+ parse_nwltime_reply (response, &result, NULL, error);
+ return result;
+}
+
+static void
+modem_time_load_network_time (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "$NWLTIME",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load network timezone (Time interface) */
+
+static MMNetworkTimezone *
+modem_time_load_network_timezone_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *response;
+ MMNetworkTimezone *tz = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (response)
+ parse_nwltime_reply (response, NULL, &tz, error);
+ return tz;
+}
+
+static void
+modem_time_load_network_timezone (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "$NWLTIME",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Check support (Time interface) */
+
+static gboolean
+modem_time_check_support_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_time_check_support (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Only CDMA devices support this at the moment */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "$NWLTIME",
+ 3,
+ TRUE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemNovatel *
+mm_broadband_modem_novatel_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_NOVATEL,
+ 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,
+ NULL);
+}
+
+static void
+mm_broadband_modem_novatel_init (MMBroadbandModemNovatel *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = load_current_modes;
+ iface->load_current_modes_finish = load_current_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = set_current_modes_finish;
+ iface->load_access_technologies_finish = modem_load_access_technologies_finish;
+ iface->load_access_technologies = modem_load_access_technologies;
+ iface->load_signal_quality = modem_load_signal_quality;
+ iface->load_signal_quality_finish = modem_load_signal_quality_finish;
+}
+
+static void
+iface_modem_messaging_init (MMIfaceModemMessaging *iface)
+{
+ iface->enable_unsolicited_events = messaging_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = messaging_enable_unsolicited_events_finish;
+}
+
+static void
+iface_modem_cdma_init (MMIfaceModemCdma *iface)
+{
+ iface->get_detailed_registration_state = modem_cdma_get_detailed_registration_state;
+ iface->get_detailed_registration_state_finish = modem_cdma_get_detailed_registration_state_finish;
+ iface->activate = modem_cdma_activate;
+ iface->activate_finish = modem_cdma_activate_finish;
+ iface->activate_manual = modem_cdma_activate_manual;
+ iface->activate_manual_finish = modem_cdma_activate_manual_finish;
+}
+
+static void
+iface_modem_time_init (MMIfaceModemTime *iface)
+{
+ iface->check_support = modem_time_check_support;
+ iface->check_support_finish = modem_time_check_support_finish;
+ iface->load_network_time = modem_time_load_network_time;
+ iface->load_network_time_finish = modem_time_load_network_time_finish;
+ iface->load_network_timezone = modem_time_load_network_timezone;
+ iface->load_network_timezone_finish = modem_time_load_network_timezone_finish;
+}
+
+static void
+mm_broadband_modem_novatel_class_init (MMBroadbandModemNovatelClass *klass)
+{
+}
diff --git a/src/plugins/novatel/mm-broadband-modem-novatel.h b/src/plugins/novatel/mm-broadband-modem-novatel.h
new file mode 100644
index 00000000..36604172
--- /dev/null
+++ b/src/plugins/novatel/mm-broadband-modem-novatel.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_MODEM_NOVATEL_H
+#define MM_BROADBAND_MODEM_NOVATEL_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_NOVATEL (mm_broadband_modem_novatel_get_type ())
+#define MM_BROADBAND_MODEM_NOVATEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_NOVATEL, MMBroadbandModemNovatel))
+#define MM_BROADBAND_MODEM_NOVATEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_NOVATEL, MMBroadbandModemNovatelClass))
+#define MM_IS_BROADBAND_MODEM_NOVATEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_NOVATEL))
+#define MM_IS_BROADBAND_MODEM_NOVATEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_NOVATEL))
+#define MM_BROADBAND_MODEM_NOVATEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_NOVATEL, MMBroadbandModemNovatelClass))
+
+typedef struct _MMBroadbandModemNovatel MMBroadbandModemNovatel;
+typedef struct _MMBroadbandModemNovatelClass MMBroadbandModemNovatelClass;
+typedef struct _MMBroadbandModemNovatelPrivate MMBroadbandModemNovatelPrivate;
+
+struct _MMBroadbandModemNovatel {
+ MMBroadbandModem parent;
+ MMBroadbandModemNovatelPrivate *priv;
+};
+
+struct _MMBroadbandModemNovatelClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_novatel_get_type (void);
+
+MMBroadbandModemNovatel *mm_broadband_modem_novatel_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_NOVATEL_H */
diff --git a/src/plugins/novatel/mm-common-novatel.c b/src/plugins/novatel/mm-common-novatel.c
new file mode 100644
index 00000000..b6b0e272
--- /dev/null
+++ b/src/plugins/novatel/mm-common-novatel.c
@@ -0,0 +1,145 @@
+/* -*- 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) 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-common-novatel.h"
+#include "mm-log-object.h"
+
+/*****************************************************************************/
+/* Custom init */
+
+typedef struct {
+ MMPortSerialAt *port;
+ guint nwdmat_retries;
+ guint wait_time;
+} CustomInitContext;
+
+static void
+custom_init_context_free (CustomInitContext *ctx)
+{
+ g_object_unref (ctx->port);
+ g_slice_free (CustomInitContext, ctx);
+}
+
+gboolean
+mm_common_novatel_custom_init_finish (MMPortProbe *probe,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void custom_init_step (GTask *task);
+
+static void
+nwdmat_ready (MMPortSerialAt *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ MMPortProbe *probe;
+
+ probe = g_task_get_source_object (task);
+
+ mm_port_serial_at_command_finish (port, res, &error);
+ if (error) {
+ if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) {
+ custom_init_step (task);
+ return;
+ }
+
+ mm_obj_dbg (probe, "error flipping secondary ports to AT mode: %s", error->message);
+ }
+
+ /* Finish custom_init */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static gboolean
+custom_init_wait_cb (GTask *task)
+{
+ custom_init_step (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+custom_init_step (GTask *task)
+{
+ CustomInitContext *ctx;
+ MMPortProbe *probe;
+
+ probe = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* If cancelled, end */
+ if (g_task_return_error_if_cancelled (task)) {
+ mm_obj_dbg (probe, "no need to keep on running custom init");
+ g_object_unref (task);
+ return;
+ }
+
+ /* If device has a QMI port, don't run $NWDMAT */
+ if (mm_port_probe_list_has_qmi_port (mm_device_peek_port_probe_list (mm_port_probe_peek_device (probe)))) {
+ mm_obj_dbg (probe, "no need to run custom init: device has QMI port");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ if (ctx->wait_time > 0) {
+ ctx->wait_time--;
+ g_timeout_add_seconds (1, (GSourceFunc)custom_init_wait_cb, task);
+ return;
+ }
+
+ if (ctx->nwdmat_retries > 0) {
+ ctx->nwdmat_retries--;
+ mm_port_serial_at_command (ctx->port,
+ "$NWDMAT=1",
+ 3,
+ FALSE, /* raw */
+ FALSE, /* allow_cached */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)nwdmat_ready,
+ task);
+ return;
+ }
+
+ /* Finish custom_init */
+ mm_obj_dbg (probe, "couldn't flip secondary port to AT: all retries consumed");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_common_novatel_custom_init (MMPortProbe *probe,
+ MMPortSerialAt *port,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ CustomInitContext *ctx;
+ GTask *task;
+
+ ctx = g_slice_new (CustomInitContext);
+ ctx->port = g_object_ref (port);
+ ctx->nwdmat_retries = 3;
+ ctx->wait_time = 2;
+
+ task = g_task_new (probe, cancellable, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)custom_init_context_free);
+
+ custom_init_step (task);
+}
diff --git a/src/plugins/novatel/mm-common-novatel.h b/src/plugins/novatel/mm-common-novatel.h
new file mode 100644
index 00000000..70572fd3
--- /dev/null
+++ b/src/plugins/novatel/mm-common-novatel.h
@@ -0,0 +1,31 @@
+/* -*- 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) 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_COMMON_NOVATEL_H
+#define MM_COMMON_NOVATEL_H
+
+#include "glib.h"
+#include "mm-plugin.h"
+
+void mm_common_novatel_custom_init (MMPortProbe *probe,
+ MMPortSerialAt *port,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_common_novatel_custom_init_finish (MMPortProbe *probe,
+ GAsyncResult *result,
+ GError **error);
+
+#endif /* MM_COMMON_NOVATEL_H */
diff --git a/src/plugins/novatel/mm-plugin-novatel-lte.c b/src/plugins/novatel/mm-plugin-novatel-lte.c
new file mode 100644
index 00000000..025707ae
--- /dev/null
+++ b/src/plugins/novatel/mm-plugin-novatel-lte.c
@@ -0,0 +1,81 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2012 Google Inc.
+ * Author: Nathan Williams <njw@google.com>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#include "mm-plugin-novatel-lte.h"
+#include "mm-private-boxed-types.h"
+#include "mm-broadband-modem-novatel-lte.h"
+
+G_DEFINE_TYPE (MMPluginNovatelLte, mm_plugin_novatel_lte, 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)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_novatel_lte_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", NULL };
+ static const mm_uint16_pair products[] = { { 0x1410, 0x9010 }, /* Novatel E362 */
+ {0, 0} };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_NOVATEL_LTE,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_PRODUCT_IDS, products,
+ MM_PLUGIN_ALLOWED_SINGLE_AT, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_novatel_lte_init (MMPluginNovatelLte *self)
+{
+}
+
+static void
+mm_plugin_novatel_lte_class_init (MMPluginNovatelLteClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/novatel/mm-plugin-novatel-lte.h b/src/plugins/novatel/mm-plugin-novatel-lte.h
new file mode 100644
index 00000000..8f6c8be1
--- /dev/null
+++ b/src/plugins/novatel/mm-plugin-novatel-lte.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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2012 Google Inc.
+ * Author: Nathan Williams <njw@google.com>
+ */
+
+#ifndef MM_PLUGIN_NOVATEL_LTE_H
+#define MM_PLUGIN_NOVATEL_LTE_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_NOVATEL_LTE (mm_plugin_novatel_lte_get_type ())
+#define MM_PLUGIN_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_NOVATEL_LTE, MMPluginNovatelLte))
+#define MM_PLUGIN_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_NOVATEL_LTE, MMPluginNovatelLteClass))
+#define MM_IS_PLUGIN_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_NOVATEL_LTE))
+#define MM_IS_PLUGIN_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_NOVATEL_LTE))
+#define MM_PLUGIN_NOVATEL_LTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_NOVATEL_LTE, MMPluginNovatelLteClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginNovatelLte;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginNovatelLteClass;
+
+GType mm_plugin_novatel_lte_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_NOVATEL_LTE_H */
diff --git a/src/plugins/novatel/mm-plugin-novatel.c b/src/plugins/novatel/mm-plugin-novatel.c
new file mode 100644
index 00000000..c17f6a9a
--- /dev/null
+++ b/src/plugins/novatel/mm-plugin-novatel.c
@@ -0,0 +1,113 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-novatel.h"
+#include "mm-common-novatel.h"
+#include "mm-private-boxed-types.h"
+#include "mm-broadband-modem-novatel.h"
+#include "mm-log-object.h"
+
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginNovatel, mm_plugin_novatel, 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 Novatel modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_novatel_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", NULL };
+ static const guint16 vendors[] = { 0x1410, 0 };
+ static const mm_uint16_pair forbidden_products[] = { { 0x1410, 0x9010 }, /* Novatel E362 */
+ { 0, 0 } };
+ static const MMAsyncMethod custom_init = {
+ .async = G_CALLBACK (mm_common_novatel_custom_init),
+ .finish = G_CALLBACK (mm_common_novatel_custom_init_finish),
+ };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_NOVATEL,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendors,
+ MM_PLUGIN_FORBIDDEN_PRODUCT_IDS, forbidden_products,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_CUSTOM_INIT, &custom_init,
+ MM_PLUGIN_REQUIRED_QCDM, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_novatel_init (MMPluginNovatel *self)
+{
+}
+
+static void
+mm_plugin_novatel_class_init (MMPluginNovatelClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/novatel/mm-plugin-novatel.h b/src/plugins/novatel/mm-plugin-novatel.h
new file mode 100644
index 00000000..b553e278
--- /dev/null
+++ b/src/plugins/novatel/mm-plugin-novatel.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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_PLUGIN_NOVATEL_H
+#define MM_PLUGIN_NOVATEL_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_NOVATEL (mm_plugin_novatel_get_type ())
+#define MM_PLUGIN_NOVATEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_NOVATEL, MMPluginNovatel))
+#define MM_PLUGIN_NOVATEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_NOVATEL, MMPluginNovatelClass))
+#define MM_IS_PLUGIN_NOVATEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_NOVATEL))
+#define MM_IS_PLUGIN_NOVATEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_NOVATEL))
+#define MM_PLUGIN_NOVATEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_NOVATEL, MMPluginNovatelClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginNovatel;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginNovatelClass;
+
+GType mm_plugin_novatel_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_NOVATEL_H */
diff --git a/src/plugins/novatel/mm-shared.c b/src/plugins/novatel/mm-shared.c
new file mode 100644
index 00000000..6bd11e4b
--- /dev/null
+++ b/src/plugins/novatel/mm-shared.c
@@ -0,0 +1,20 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-shared.h"
+
+MM_SHARED_DEFINE_MAJOR_VERSION
+MM_SHARED_DEFINE_MINOR_VERSION
+MM_SHARED_DEFINE_NAME(Novatel)
diff --git a/src/plugins/novatel/mm-sim-novatel-lte.c b/src/plugins/novatel/mm-sim-novatel-lte.c
new file mode 100644
index 00000000..4d71bd80
--- /dev/null
+++ b/src/plugins/novatel/mm-sim-novatel-lte.c
@@ -0,0 +1,234 @@
+/* -*- 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) 2012 Google, Inc.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+#include "mm-modem-helpers.h"
+#include "mm-base-modem-at.h"
+
+#include "mm-sim-novatel-lte.h"
+
+G_DEFINE_TYPE (MMSimNovatelLte, mm_sim_novatel_lte, MM_TYPE_BASE_SIM)
+
+/*****************************************************************************/
+/* IMSI loading */
+
+static gchar *
+load_imsi_finish (MMBaseSim *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+imsi_read_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response, *str;
+ gchar buf[19];
+ gchar imsi[16];
+ gsize len = 0;
+ gint sw1, sw2;
+ gint i;
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ memset (buf, 0, sizeof (buf));
+ str = mm_strip_tag (response, "+CRSM:");
+
+ /* With or without quotes... */
+ if (sscanf (str, "%d,%d,\"%18c\"", &sw1, &sw2, (char *) &buf) != 3 &&
+ sscanf (str, "%d,%d,%18c", &sw1, &sw2, (char *) &buf) != 3) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse the CRSM response: '%s'",
+ response);
+ g_object_unref (task);
+ return;
+ }
+
+ if ((sw1 != 0x90 || sw2 != 0x00) &&
+ (sw1 != 0x91) &&
+ (sw1 != 0x92) &&
+ (sw1 != 0x9f)) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "SIM failed to handle CRSM request (sw1 %d sw2 %d)",
+ sw1, sw2);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Make sure the buffer is only digits or 'F' */
+ for (len = 0; len < sizeof (buf) && buf[len]; len++) {
+ if (isdigit (buf[len]))
+ continue;
+ if (buf[len] == 'F' || buf[len] == 'f') {
+ buf[len] = 'F'; /* canonicalize the F */
+ continue;
+ }
+ if (buf[len] == '\"') {
+ buf[len] = 0;
+ break;
+ }
+
+ /* Invalid character */
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "CRSM IMSI response contained invalid character '%c'",
+ buf[len]);
+ g_object_unref (task);
+ return;
+ }
+
+ /* BCD encoded IMSIs plus the length byte and parity are 18 digits long */
+ if (len != 18) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Invalid +CRSM IMSI response size (was %zd, expected 18)",
+ len);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Skip the length byte (digit 0-1) and parity (digit 3). Swap digits in
+ * the EFimsi response to get the actual IMSI, each group of 2 digits is
+ * reversed in the +CRSM response. i.e.:
+ *
+ * **0*21436587a9cbed -> 0123456789abcde
+ */
+ memset (imsi, 0, sizeof (imsi));
+ imsi[0] = buf[2];
+ for (i = 1; i < 8; i++) {
+ imsi[(i * 2) - 1] = buf[(i * 2) + 3];
+ imsi[i * 2] = buf[(i * 2) + 2];
+ }
+
+ /* Zero out the first F, if any, for IMSIs shorter than 15 digits */
+ for (i = 0; i < 15; i++) {
+ if (imsi[i] == 'F') {
+ imsi[i++] = 0;
+ break;
+ }
+ }
+
+ /* Ensure all 'F's, if any, are at the end */
+ for (; i < 15; i++) {
+ if (imsi[i] != 'F') {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Invalid +CRSM IMSI length (unexpected F)");
+ g_object_unref (task);
+ return;
+ }
+ }
+
+ g_task_return_pointer (task, g_strdup (imsi), g_free);
+ g_object_unref (task);
+}
+
+static void
+load_imsi (MMBaseSim *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBaseModem *modem = NULL;
+
+ g_object_get (self,
+ MM_BASE_SIM_MODEM, &modem,
+ NULL);
+
+ mm_base_modem_at_command (
+ modem,
+ "+CRSM=176,28423,0,0,9",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)imsi_read_ready,
+ g_task_new (self, NULL, callback, user_data));
+ g_object_unref (modem);
+}
+
+/*****************************************************************************/
+
+MMBaseSim *
+mm_sim_novatel_lte_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *source;
+ GObject *sim;
+
+ source = g_async_result_get_source_object (res);
+ sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!sim)
+ return NULL;
+
+ /* Only export valid SIMs */
+ mm_base_sim_export (MM_BASE_SIM (sim));
+
+ return MM_BASE_SIM (sim);
+}
+
+void
+mm_sim_novatel_lte_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (MM_TYPE_SIM_NOVATEL_LTE,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_SIM_MODEM, modem,
+ "active", TRUE, /* by default always active */
+ NULL);
+}
+
+static void
+mm_sim_novatel_lte_init (MMSimNovatelLte *self)
+{
+}
+
+static void
+mm_sim_novatel_lte_class_init (MMSimNovatelLteClass *klass)
+{
+ MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass);
+
+ base_sim_class->load_imsi = load_imsi;
+ base_sim_class->load_imsi_finish = load_imsi_finish;
+}
diff --git a/src/plugins/novatel/mm-sim-novatel-lte.h b/src/plugins/novatel/mm-sim-novatel-lte.h
new file mode 100644
index 00000000..df8f38e6
--- /dev/null
+++ b/src/plugins/novatel/mm-sim-novatel-lte.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2012 Google, Inc.
+ */
+
+#ifndef MM_SIM_NOVATEL_LTE_H
+#define MM_SIM_NOVATEL_LTE_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "mm-base-sim.h"
+
+#define MM_TYPE_SIM_NOVATEL_LTE (mm_sim_novatel_lte_get_type ())
+#define MM_SIM_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_NOVATEL_LTE, MMSimNovatelLte))
+#define MM_SIM_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_NOVATEL_LTE, MMSimNovatelLteClass))
+#define MM_IS_SIM_NOVATEL_LTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_NOVATEL_LTE))
+#define MM_IS_SIM_NOVATEL_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_NOVATEL_LTE))
+#define MM_SIM_NOVATEL_LTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_NOVATEL_LTE, MMSimNovatelLteClass))
+
+typedef struct _MMSimNovatelLte MMSimNovatelLte;
+typedef struct _MMSimNovatelLteClass MMSimNovatelLteClass;
+
+struct _MMSimNovatelLte {
+ MMBaseSim parent;
+};
+
+struct _MMSimNovatelLteClass {
+ MMBaseSimClass parent;
+};
+
+GType mm_sim_novatel_lte_get_type (void);
+
+void mm_sim_novatel_lte_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseSim *mm_sim_novatel_lte_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SIM_NOVATEL_LTE_H */
diff --git a/src/plugins/option/mm-broadband-bearer-hso.c b/src/plugins/option/mm-broadband-bearer-hso.c
new file mode 100644
index 00000000..9199e7b3
--- /dev/null
+++ b/src/plugins/option/mm-broadband-bearer-hso.c
@@ -0,0 +1,818 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <arpa/inet.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-base-modem-at.h"
+#include "mm-broadband-bearer-hso.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-daemon-enums-types.h"
+
+G_DEFINE_TYPE (MMBroadbandBearerHso, mm_broadband_bearer_hso, MM_TYPE_BROADBAND_BEARER);
+
+struct _MMBroadbandBearerHsoPrivate {
+ guint auth_idx;
+
+ GTask *connect_pending;
+ guint connect_pending_id;
+ gulong connect_port_closed_id;
+};
+
+/*****************************************************************************/
+/* 3GPP IP config retrieval (sub-step of the 3GPP Connection sequence) */
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ guint cid;
+} GetIpConfig3gppContext;
+
+static void
+get_ip_config_context_free (GetIpConfig3gppContext *ctx)
+{
+ g_object_unref (ctx->primary);
+ g_object_unref (ctx->modem);
+ g_slice_free (GetIpConfig3gppContext, ctx);
+}
+
+static gboolean
+get_ip_config_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ MMBearerIpConfig **ipv4_config,
+ MMBearerIpConfig **ipv6_config,
+ GError **error)
+{
+ MMBearerIpConfig *ip_config;
+
+ ip_config = g_task_propagate_pointer (G_TASK (res), error);
+ if (!ip_config)
+ return FALSE;
+
+ /* No IPv6 for now */
+ *ipv4_config = ip_config; /* Transfer ownership */
+ *ipv6_config = NULL;
+ return TRUE;
+}
+
+#define OWANDATA_TAG "_OWANDATA: "
+
+static void
+ip_config_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GetIpConfig3gppContext *ctx;
+ MMBearerIpConfig *ip_config = NULL;
+ const gchar *response;
+ GError *error = NULL;
+ gchar **items;
+ gchar *dns[3] = { 0 };
+ guint i;
+ guint dns_i;
+
+ response = mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* TODO: use a regex to parse this */
+
+ /* Check result */
+ if (!g_str_has_prefix (response, OWANDATA_TAG)) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't get IP config: invalid response '%s'",
+ response);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_task_get_task_data (task);
+ response = mm_strip_tag (response, OWANDATA_TAG);
+ items = g_strsplit (response, ", ", 0);
+
+ for (i = 0, dns_i = 0; items[i]; i++) {
+ if (i == 0) { /* CID */
+ guint num;
+
+ if (!mm_get_uint_from_str (items[i], &num) ||
+ num != ctx->cid) {
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unknown CID in OWANDATA response ("
+ "got %d, expected %d)", (guint) num, ctx->cid);
+ break;
+ }
+ } else if (i == 1) { /* IP address */
+ guint32 tmp;
+
+ if (!inet_pton (AF_INET, items[i], &tmp))
+ break;
+
+ ip_config = mm_bearer_ip_config_new ();
+ mm_bearer_ip_config_set_method (ip_config, MM_BEARER_IP_METHOD_STATIC);
+ mm_bearer_ip_config_set_address (ip_config, items[i]);
+ mm_bearer_ip_config_set_prefix (ip_config, 32);
+ } else if (i == 3 || i == 4) { /* DNS entries */
+ guint32 tmp;
+
+ if (!inet_pton (AF_INET, items[i], &tmp)) {
+ g_clear_object (&ip_config);
+ break;
+ }
+
+ dns[dns_i++] = items[i];
+ }
+ }
+
+ if (!ip_config) {
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't get IP config: couldn't parse response '%s'",
+ response);
+ } else {
+ /* If we got DNS entries, set them in the IP config */
+ if (dns[0])
+ mm_bearer_ip_config_set_dns (ip_config, (const gchar **)dns);
+
+ g_task_return_pointer (task, ip_config, g_object_unref);
+ }
+
+ g_object_unref (task);
+ g_strfreev (items);
+}
+
+static void
+get_ip_config_3gpp (MMBroadbandBearer *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ MMBearerIpFamily ip_family,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GetIpConfig3gppContext *ctx;
+ GTask *task;
+ gchar *command;
+
+ ctx = g_slice_new0 (GetIpConfig3gppContext);
+ ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
+ ctx->primary = g_object_ref (primary);
+ ctx->cid = cid;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)get_ip_config_context_free);
+
+ command = g_strdup_printf ("AT_OWANDATA=%d", cid);
+ mm_base_modem_at_command_full (
+ MM_BASE_MODEM (modem),
+ primary,
+ command,
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)ip_config_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ guint cid;
+ MMPort *data;
+ guint auth_idx;
+ GError *saved_error;
+} Dial3gppContext;
+
+static void
+dial_3gpp_context_free (Dial3gppContext *ctx)
+{
+ g_assert (!ctx->saved_error);
+ g_clear_object (&ctx->data);
+ g_clear_object (&ctx->primary);
+ g_clear_object (&ctx->modem);
+ g_slice_free (Dial3gppContext, ctx);
+}
+
+static guint
+dial_3gpp_get_connecting_cid (GTask *task)
+{
+ Dial3gppContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ return ctx->cid;
+}
+
+static MMPort *
+dial_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return MM_PORT (g_task_propagate_pointer (G_TASK (res), error));
+}
+
+static void
+connect_reset_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Dial3gppContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+
+ /* When reset is requested, it was either cancelled or an error was stored */
+ if (!g_task_return_error_if_cancelled (task)) {
+ g_assert (ctx->saved_error);
+ g_task_return_error (task, ctx->saved_error);
+ ctx->saved_error = NULL;
+ }
+
+ g_object_unref (task);
+}
+
+static void
+connect_reset (GTask *task)
+{
+ Dial3gppContext *ctx;
+ gchar *command;
+
+ ctx = g_task_get_task_data (task);
+
+ /* Need to reset the connection attempt */
+ command = g_strdup_printf ("AT_OWANCALL=%d,0,1", ctx->cid);
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)connect_reset_ready,
+ task);
+ g_free (command);
+}
+
+static void
+process_pending_connect_attempt (MMBroadbandBearerHso *self,
+ MMBearerConnectionStatus status)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+
+ /* Recover task and remove both cancellation and timeout (if any)*/
+ g_assert (self->priv->connect_pending);
+ task = self->priv->connect_pending;
+ self->priv->connect_pending = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (self->priv->connect_pending_id) {
+ g_source_remove (self->priv->connect_pending_id);
+ self->priv->connect_pending_id = 0;
+ }
+
+ if (self->priv->connect_port_closed_id) {
+ g_signal_handler_disconnect (ctx->primary, self->priv->connect_port_closed_id);
+ self->priv->connect_port_closed_id = 0;
+ }
+
+ /* Reporting connected */
+ if (status == MM_BEARER_CONNECTION_STATUS_CONNECTED) {
+ /* If we wanted to get cancelled before, do it now. */
+ if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) {
+ connect_reset (task);
+ return;
+ }
+
+ g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Received CONNECTION_FAILED or DISCONNECTED during a connection attempt,
+ * so return a failed error. Note that if the cancellable has been cancelled
+ * already, a cancelled error would be returned instead. */
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Call setup failed");
+ g_object_unref (task);
+}
+
+static gboolean
+connect_timed_out_cb (MMBroadbandBearerHso *self)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+
+ /* Cleanup timeout ID */
+ self->priv->connect_pending_id = 0;
+
+ /* Recover task and own it */
+ task = self->priv->connect_pending;
+ self->priv->connect_pending = NULL;
+ g_assert (task);
+
+ ctx = g_task_get_task_data (task);
+
+ /* Remove closed port watch, if found */
+ if (self->priv->connect_port_closed_id) {
+ g_signal_handler_disconnect (ctx->primary, self->priv->connect_port_closed_id);
+ self->priv->connect_port_closed_id = 0;
+ }
+
+ /* Setup error to return after the reset */
+ g_assert (!ctx->saved_error);
+ ctx->saved_error = g_error_new (MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT,
+ "Connection attempt timed out");
+
+ /* It's probably pointless to try to reset this here, but anyway... */
+ connect_reset (task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+forced_close_cb (MMBroadbandBearerHso *self)
+{
+ /* Just treat the forced close event as any other unsolicited message */
+ mm_base_bearer_report_connection_status (MM_BASE_BEARER (self),
+ MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED);
+}
+
+static void
+activate_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerHso *self)
+{
+ GTask *task;
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+
+ task = g_steal_pointer (&self->priv->connect_pending);
+
+ /* Try to recover the connection task. If none found, it means the
+ * task was already completed and we have nothing else to do.
+ * But note that we won't take owneship of the task yet! */
+ if (!task) {
+ mm_obj_dbg (self, "connection context was finished already by an unsolicited message");
+ /* Run _finish() to finalize the async call, even if we don't care
+ * about the result */
+ mm_base_modem_at_command_full_finish (modem, res, NULL);
+ goto out;
+ }
+
+ /* From now on, if we get cancelled, we'll need to run the connection
+ * reset ourselves just in case */
+
+ /* Errors on the dial command are fatal */
+ if (!mm_base_modem_at_command_full_finish (modem, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ goto out;
+ }
+
+ /* Track the task again */
+ self->priv->connect_pending = task;
+
+ /* We will now setup a timeout and keep the context in the bearer's private.
+ * Reports of modem being connected will arrive via unsolicited messages.
+ * This timeout should be long enough. Actually... ideally should never get
+ * reached. */
+ self->priv->connect_pending_id = g_timeout_add_seconds (MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT,
+ (GSourceFunc)connect_timed_out_cb,
+ self);
+
+ /* If we get the port closed, we treat as a connect error */
+ ctx = g_task_get_task_data (task);
+ self->priv->connect_port_closed_id = g_signal_connect_swapped (ctx->primary,
+ "forced-close",
+ G_CALLBACK (forced_close_cb),
+ self);
+
+ out:
+ /* Balance refcount with the extra ref we passed to command_full() */
+ g_object_unref (self);
+}
+
+static void authenticate (GTask *task);
+
+static void
+authenticate_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerHso *self;
+ Dial3gppContext *ctx;
+ gchar *command;
+
+ /* If cancelled, complete */
+ if (g_task_return_error_if_cancelled (task)) {
+ g_object_unref (task);
+ return;
+ }
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_full_finish (modem, res, NULL)) {
+ /* Try the next auth command */
+ ctx->auth_idx++;
+ authenticate (task);
+ return;
+ }
+
+ /* Store which auth command worked, for next attempts */
+ self->priv->auth_idx = ctx->auth_idx;
+
+ /* The unsolicited response to AT_OWANCALL may come before the OK does.
+ * We will keep the connection context in the bearer private data so
+ * that it is accessible from the unsolicited message handler. Note
+ * also that we do NOT pass the ctx to the GAsyncReadyCallback, as it
+ * may not be valid any more when the callback is called (it may be
+ * already completed in the unsolicited handling) */
+ g_assert (self->priv->connect_pending == NULL);
+ self->priv->connect_pending = task;
+
+ /* Success, activate the PDP context and start the data session */
+ command = g_strdup_printf ("AT_OWANCALL=%d,1,1", ctx->cid);
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback) activate_ready,
+ g_object_ref (self)); /* we pass the bearer object! */
+ g_free (command);
+}
+
+const gchar *auth_commands[] = {
+ "$QCPDPP",
+ /* Icera-based devices (GI0322/Quicksilver, iCON 505) don't implement
+ * $QCPDPP, but instead use _OPDPP with the same arguments.
+ */
+ "_OPDPP",
+ NULL
+};
+
+static void
+authenticate (GTask *task)
+{
+ MMBroadbandBearerHso *self;
+ Dial3gppContext *ctx;
+ gchar *command;
+ const gchar *user;
+ const gchar *password;
+ MMBearerAllowedAuth allowed_auth;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ if (!auth_commands[ctx->auth_idx]) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't run HSO authentication");
+ g_object_unref (task);
+ return;
+ }
+
+ user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ password = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ allowed_auth = mm_bearer_properties_get_allowed_auth (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+
+ /* Both user and password are required; otherwise firmware returns an error */
+ if (!user || !password || allowed_auth == MM_BEARER_ALLOWED_AUTH_NONE) {
+ mm_obj_dbg (self, "not using authentication");
+ command = g_strdup_printf ("%s=%d,0",
+ auth_commands[ctx->auth_idx],
+ ctx->cid);
+ } else {
+ gchar *quoted_user;
+ gchar *quoted_password;
+ guint hso_auth;
+
+ if (allowed_auth == MM_BEARER_ALLOWED_AUTH_UNKNOWN) {
+ mm_obj_dbg (self, "using default (CHAP) authentication method");
+ hso_auth = 2;
+ } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_CHAP) {
+ mm_obj_dbg (self, "using CHAP authentication method");
+ hso_auth = 2;
+ } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_PAP) {
+ mm_obj_dbg (self, "using PAP authentication method");
+ hso_auth = 1;
+ } else {
+ gchar *str;
+
+ str = mm_bearer_allowed_auth_build_string_from_mask (allowed_auth);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot use any of the specified authentication methods (%s)",
+ str);
+ g_object_unref (task);
+ g_free (str);
+ return;
+ }
+
+ quoted_user = mm_port_serial_at_quote_string (user);
+ quoted_password = mm_port_serial_at_quote_string (password);
+ command = g_strdup_printf ("%s=%d,%u,%s,%s",
+ auth_commands[ctx->auth_idx],
+ ctx->cid,
+ hso_auth,
+ quoted_password,
+ quoted_user);
+ g_free (quoted_user);
+ g_free (quoted_password);
+ }
+
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)authenticate_ready,
+ task);
+ g_free (command);
+}
+
+static void
+dial_3gpp (MMBroadbandBearer *_self,
+ MMBaseModem *modem,
+ MMPortSerialAt *primary,
+ guint cid,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandBearerHso *self = MM_BROADBAND_BEARER_HSO (_self);
+ GTask *task;
+ Dial3gppContext *ctx;
+
+ g_assert (primary != NULL);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ ctx = g_slice_new0 (Dial3gppContext);
+ ctx->modem = g_object_ref (modem);
+ ctx->primary = g_object_ref (primary);
+ ctx->cid = cid;
+ g_task_set_task_data (task, ctx, (GDestroyNotify)dial_3gpp_context_free);
+
+ /* Always start with the index that worked last time
+ * (will be 0 the first time)*/
+ ctx->auth_idx = self->priv->auth_idx;
+
+ /* We need a net data port */
+ ctx->data = mm_base_modem_get_best_data_port (modem, MM_PORT_TYPE_NET);
+ if (!ctx->data) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_NOT_FOUND,
+ "No valid data port found to launch connection");
+ g_object_unref (task);
+ return;
+ }
+
+ authenticate (task);
+}
+
+/*****************************************************************************/
+/* 3GPP disconnect */
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+} DisconnectContext;
+
+static void
+disconnect_context_free (DisconnectContext *ctx)
+{
+ g_object_unref (ctx->primary);
+ g_object_unref (ctx->modem);
+ g_free (ctx);
+}
+
+static gboolean
+disconnect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+disconnect_owancall_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerHso *self;
+ GError *error = NULL;
+
+ self = g_task_get_source_object (task);
+
+ /* Ignore errors for now */
+ mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (error) {
+ mm_obj_dbg (self, "disconnection failed (not fatal): %s", error->message);
+ g_error_free (error);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+disconnect_3gpp (MMBroadbandBearer *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ gchar *command;
+ DisconnectContext *ctx;
+ GTask *task;
+
+ g_assert (primary != NULL);
+
+ ctx = g_new0 (DisconnectContext, 1);
+ ctx->modem = MM_BASE_MODEM (g_object_ref (modem));
+ ctx->primary = g_object_ref (primary);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)disconnect_context_free);
+
+ /* Use specific CID */
+ command = g_strdup_printf ("AT_OWANCALL=%d,0,0", cid);
+ mm_base_modem_at_command_full (MM_BASE_MODEM (modem),
+ primary,
+ command,
+ MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)disconnect_owancall_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+
+gint
+mm_broadband_bearer_hso_get_connecting_profile_id (MMBroadbandBearerHso *self)
+{
+ return (self->priv->connect_pending ?
+ (gint)dial_3gpp_get_connecting_cid (self->priv->connect_pending) :
+ MM_3GPP_PROFILE_ID_UNKNOWN);
+}
+
+/*****************************************************************************/
+
+static void
+report_connection_status (MMBaseBearer *_self,
+ MMBearerConnectionStatus status,
+ const GError *connection_error)
+{
+ MMBroadbandBearerHso *self = MM_BROADBAND_BEARER_HSO (_self);
+
+ g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED ||
+ status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED ||
+ status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
+
+ /* Process pending connection attempt */
+ if (self->priv->connect_pending) {
+ process_pending_connect_attempt (self, status);
+ return;
+ }
+
+ mm_obj_dbg (self, "received spontaneous _OWANCALL (%s)",
+ mm_bearer_connection_status_get_string (status));
+
+ if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) {
+ /* If no connection attempt on-going, make sure we mark ourselves as
+ * disconnected */
+ MM_BASE_BEARER_CLASS (mm_broadband_bearer_hso_parent_class)->report_connection_status (_self, status,connection_error);
+ }
+}
+
+/*****************************************************************************/
+
+MMBaseBearer *
+mm_broadband_bearer_hso_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *bearer;
+ GObject *source;
+
+ source = g_async_result_get_source_object (res);
+ bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!bearer)
+ return NULL;
+
+ /* Only export valid bearers */
+ mm_base_bearer_export (MM_BASE_BEARER (bearer));
+
+ return MM_BASE_BEARER (bearer);
+}
+
+void
+mm_broadband_bearer_hso_new (MMBroadbandModemHso *modem,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (
+ MM_TYPE_BROADBAND_BEARER_HSO,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_BEARER_MODEM, modem,
+ MM_BASE_BEARER_CONFIG, config,
+ NULL);
+}
+
+static void
+mm_broadband_bearer_hso_init (MMBroadbandBearerHso *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_BEARER_HSO,
+ MMBroadbandBearerHsoPrivate);
+}
+
+static void
+mm_broadband_bearer_hso_class_init (MMBroadbandBearerHsoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);
+ MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandBearerHsoPrivate));
+
+ base_bearer_class->report_connection_status = report_connection_status;
+ base_bearer_class->load_connection_status = NULL;
+ base_bearer_class->load_connection_status_finish = NULL;
+#if defined WITH_SUSPEND_RESUME
+ base_bearer_class->reload_connection_status = NULL;
+ base_bearer_class->reload_connection_status_finish = NULL;
+#endif
+
+ broadband_bearer_class->dial_3gpp = dial_3gpp;
+ broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish;
+ broadband_bearer_class->get_ip_config_3gpp = get_ip_config_3gpp;
+ broadband_bearer_class->get_ip_config_3gpp_finish = get_ip_config_3gpp_finish;
+ broadband_bearer_class->disconnect_3gpp = disconnect_3gpp;
+ broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish;
+}
diff --git a/src/plugins/option/mm-broadband-bearer-hso.h b/src/plugins/option/mm-broadband-bearer-hso.h
new file mode 100644
index 00000000..def46ac3
--- /dev/null
+++ b/src/plugins/option/mm-broadband-bearer-hso.h
@@ -0,0 +1,61 @@
+/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_BEARER_HSO_H
+#define MM_BROADBAND_BEARER_HSO_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-broadband-bearer.h"
+#include "mm-broadband-modem-hso.h"
+
+#define MM_TYPE_BROADBAND_BEARER_HSO (mm_broadband_bearer_hso_get_type ())
+#define MM_BROADBAND_BEARER_HSO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_HSO, MMBroadbandBearerHso))
+#define MM_BROADBAND_BEARER_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_HSO, MMBroadbandBearerHsoClass))
+#define MM_IS_BROADBAND_BEARER_HSO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_HSO))
+#define MM_IS_BROADBAND_BEARER_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_HSO))
+#define MM_BROADBAND_BEARER_HSO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_HSO, MMBroadbandBearerHsoClass))
+
+typedef struct _MMBroadbandBearerHso MMBroadbandBearerHso;
+typedef struct _MMBroadbandBearerHsoClass MMBroadbandBearerHsoClass;
+typedef struct _MMBroadbandBearerHsoPrivate MMBroadbandBearerHsoPrivate;
+
+struct _MMBroadbandBearerHso {
+ MMBroadbandBearer parent;
+ MMBroadbandBearerHsoPrivate *priv;
+};
+
+struct _MMBroadbandBearerHsoClass {
+ MMBroadbandBearerClass parent;
+};
+
+GType mm_broadband_bearer_hso_get_type (void);
+
+/* Default 3GPP bearer creation implementation */
+void mm_broadband_bearer_hso_new (MMBroadbandModemHso *modem,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseBearer *mm_broadband_bearer_hso_new_finish (GAsyncResult *res,
+ GError **error);
+
+gint mm_broadband_bearer_hso_get_connecting_profile_id (MMBroadbandBearerHso *self);
+
+#endif /* MM_BROADBAND_BEARER_HSO_H */
diff --git a/src/plugins/option/mm-broadband-modem-hso.c b/src/plugins/option/mm-broadband-modem-hso.c
new file mode 100644
index 00000000..a2cc1770
--- /dev/null
+++ b/src/plugins/option/mm-broadband-modem-hso.c
@@ -0,0 +1,788 @@
+/* -*- 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 hso) 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-modem-helpers.h"
+#include "mm-log-object.h"
+#include "mm-errors-types.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-location.h"
+#include "mm-base-modem-at.h"
+#include "mm-broadband-modem-hso.h"
+#include "mm-broadband-bearer-hso.h"
+#include "mm-bearer-list.h"
+#include "mm-shared-option.h"
+
+static void shared_option_init (MMSharedOption *iface);
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+static MMIfaceModemLocation *iface_modem_location_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemHso, mm_broadband_modem_hso, MM_TYPE_BROADBAND_MODEM_OPTION, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_OPTION, shared_option_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init));
+
+struct _MMBroadbandModemHsoPrivate {
+ /* Regex for connected notifications */
+ GRegex *_owancall_regex;
+
+ MMModemLocationSource enabled_sources;
+};
+
+/*****************************************************************************/
+/* Create Bearer (Modem interface) */
+
+static MMBaseBearer *
+modem_create_bearer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+broadband_bearer_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer = NULL;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+
+ g_object_unref (task);
+}
+
+static void
+broadband_bearer_hso_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer = NULL;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_hso_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+
+ g_object_unref (task);
+}
+
+static void
+modem_create_bearer (MMIfaceModem *self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (mm_bearer_properties_get_ip_type (properties) &
+ (MM_BEARER_IP_FAMILY_IPV6 | MM_BEARER_IP_FAMILY_IPV4V6)) {
+ mm_obj_dbg (self, "creating generic bearer (IPv6 requested)...");
+ mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
+ properties,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_new_ready,
+ task);
+ return;
+ }
+
+ mm_obj_dbg (self, "creating HSO bearer...");
+ mm_broadband_bearer_hso_new (MM_BROADBAND_MODEM_HSO (self),
+ properties,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_hso_new_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load unlock retries (Modem interface) */
+
+static MMUnlockRetries *
+load_unlock_retries_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_unlock_retries_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ int pin1, puk1;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ response = mm_strip_tag (response, "_OERCN:");
+ if (sscanf (response, " %d, %d", &pin1, &puk1) == 2) {
+ MMUnlockRetries *retries;
+ retries = mm_unlock_retries_new ();
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1);
+ g_task_return_pointer (task, retries, g_object_unref);
+ } else {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Invalid unlock retries response: '%s'",
+ response);
+ }
+ g_object_unref (task);
+}
+
+static void
+load_unlock_retries (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "_OERCN?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)load_unlock_retries_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (3GPP interface) */
+
+typedef struct {
+ guint cid;
+ MMBearerConnectionStatus status;
+} BearerListReportStatusForeachContext;
+
+static void
+bearer_list_report_status_foreach (MMBaseBearer *bearer,
+ BearerListReportStatusForeachContext *ctx)
+{
+ gint profile_id;
+ gint connecting_profile_id;
+
+ if (!MM_IS_BROADBAND_BEARER_HSO (bearer))
+ return;
+
+ /* The profile ID in the base bearer is set only once the modem is connected */
+ profile_id = mm_base_bearer_get_profile_id (bearer);
+
+ /* The profile ID in the hso bearer is available during the connecting phase */
+ connecting_profile_id = mm_broadband_bearer_hso_get_connecting_profile_id (MM_BROADBAND_BEARER_HSO (bearer));
+
+ if ((profile_id != (gint)ctx->cid) && (connecting_profile_id != (gint)ctx->cid))
+ return;
+
+ mm_base_bearer_report_connection_status (MM_BASE_BEARER (bearer), ctx->status);
+}
+
+static void
+hso_connection_status_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemHso *self)
+{
+ g_autoptr(MMBearerList) list = NULL;
+ BearerListReportStatusForeachContext ctx;
+ guint cid;
+ guint status;
+
+ /* Ensure we got proper parsed values */
+ if (!mm_get_uint_from_match_info (match_info, 1, &cid) ||
+ !mm_get_uint_from_match_info (match_info, 2, &status))
+ return;
+
+ /* Setup context */
+ ctx.cid = cid;
+ ctx.status = MM_BEARER_CONNECTION_STATUS_UNKNOWN;
+
+ switch (status) {
+ case 1:
+ ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTED;
+ break;
+ case 3:
+ ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED;
+ break;
+ case 0:
+ ctx.status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED;
+ break;
+ default:
+ break;
+ }
+
+ /* If unknown status, don't try to report anything */
+ if (ctx.status == MM_BEARER_CONNECTION_STATUS_UNKNOWN)
+ return;
+
+ /* If empty bearer list, nothing else to do */
+ g_object_get (self,
+ MM_IFACE_MODEM_BEARER_LIST, &list,
+ NULL);
+
+ /* Will report status only in the bearer with the specific CID */
+ if (list)
+ mm_bearer_list_foreach (list, (MMBearerListForeachFunc)bearer_list_report_status_foreach, &ctx);
+}
+
+static gboolean
+modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else {
+ /* Our own setup now */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ MM_BROADBAND_MODEM_HSO (self)->priv->_owancall_regex,
+ (MMPortSerialAtUnsolicitedMsgFn)hso_connection_status_changed,
+ self,
+ NULL);
+
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's setup */
+ iface_modem_3gpp_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_setup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+static void
+parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Our own cleanup first */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ MM_BROADBAND_MODEM_HSO (self)->priv->_owancall_regex,
+ NULL, NULL, NULL);
+
+ /* And now chain up parent's cleanup */
+ iface_modem_3gpp_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Location capabilities loading (Location interface) */
+
+static MMModemLocationSource
+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
+parent_load_capabilities_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource sources;
+ GError *error = NULL;
+
+ sources = 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.
+ *
+ * We could issue AT_OIFACE? to list the interfaces currently enabled in the
+ * module, to see if there is a 'GPS' interface enabled. But we'll just go
+ * and see if there is already a 'GPS control' AT port and a raw serial 'GPS'
+ * port grabbed.
+ *
+ * NOTE: A deeper implementation could handle the situation where the GPS
+ * interface is found disabled in AT_OIFACE?. In this case, we could issue
+ * AT_OIFACE="GPS",1 to enable it (and AT_OIFACE="GPS",0 to disable it), but
+ * enabling/disabling GPS involves a complete reboot of the modem, which is
+ * probably not the desired thing here.
+ */
+ if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)) &&
+ mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self)))
+ sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED);
+
+ /* So we're done, complete */
+ g_task_return_int (task, sources);
+ g_object_unref (task);
+}
+
+static void
+location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's setup */
+ iface_modem_location_parent->load_capabilities (
+ self,
+ (GAsyncReadyCallback)parent_load_capabilities_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Enable/Disable location gathering (Location interface) */
+
+typedef struct {
+ MMModemLocationSource source;
+} LocationGatheringContext;
+
+/******************************/
+/* Disable location gathering */
+
+static gboolean
+disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+gps_disabled_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LocationGatheringContext *ctx;
+ MMPortSerialGps *gps_port;
+ GError *error = NULL;
+
+ mm_base_modem_at_command_full_finish (self, res, &error);
+
+ ctx = g_task_get_task_data (task);
+
+ /* Only use the GPS port in NMEA/RAW setups */
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ /* Even if we get an error here, we try to close the GPS port */
+ gps_port = mm_base_modem_peek_port_gps (self);
+ if (gps_port)
+ mm_port_serial_close (MM_PORT_SERIAL (gps_port));
+ }
+
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+disable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemHso *hso = MM_BROADBAND_MODEM_HSO (self);
+ gboolean stop_gps = FALSE;
+ LocationGatheringContext *ctx;
+ GTask *task;
+
+ ctx = g_new (LocationGatheringContext, 1);
+ ctx->source = source;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ /* Only stop GPS engine if no GPS-related sources enabled */
+ if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ hso->priv->enabled_sources &= ~source;
+
+ if (!(hso->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)))
+ stop_gps = TRUE;
+ }
+
+ if (stop_gps) {
+ /* We enable continuous GPS fixes with AT_OGPS=0 */
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self)),
+ "_OGPS=0",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)gps_disabled_ready,
+ task);
+ return;
+ }
+
+ /* For any other location (e.g. 3GPP), or if still some GPS needed, just return */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+/*****************************/
+/* Enable location gathering */
+
+static gboolean
+enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+gps_enabled_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LocationGatheringContext *ctx;
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_full_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ /* Only use the GPS port in NMEA/RAW setups */
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ MMPortSerialGps *gps_port;
+
+ gps_port = mm_base_modem_peek_port_gps (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
+ g_task_return_boolean (task, TRUE);
+ }
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+parent_enable_location_gathering_ready (MMIfaceModemLocation *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemHso *self = MM_BROADBAND_MODEM_HSO (_self);
+ LocationGatheringContext *ctx;
+ gboolean start_gps = FALSE;
+ GError *error = NULL;
+
+ if (!iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Now our own enabling */
+
+ ctx = g_task_get_task_data (task);
+
+ /* NMEA, RAW and UNMANAGED are all enabled in the same way */
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ /* Only start GPS engine if not done already.
+ * NOTE: interface already takes care of making sure that raw/nmea and
+ * unmanaged are not enabled at the same time */
+ if (!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)))
+ start_gps = TRUE;
+ self->priv->enabled_sources |= ctx->source;
+ }
+
+ if (start_gps) {
+ /* We enable continuous GPS fixes with AT_OGPS=2 */
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self)),
+ "_OGPS=2",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)gps_enabled_ready,
+ task);
+ return;
+ }
+
+ /* For any other location (e.g. 3GPP), or if GPS already running just return */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ LocationGatheringContext *ctx;
+ GTask *task;
+
+ ctx = g_new (LocationGatheringContext, 1);
+ ctx->source = source;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ /* Chain up parent's gathering enable */
+ iface_modem_location_parent->enable_location_gathering (
+ self,
+ source,
+ (GAsyncReadyCallback)parent_enable_location_gathering_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+trace_received (MMPortSerialGps *port,
+ const gchar *trace,
+ MMIfaceModemLocation *self)
+{
+ mm_iface_modem_location_gps_update (self, trace);
+}
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ MMPortSerialAt *gps_control_port;
+ MMPortSerialGps *gps_data_port;
+
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_hso_parent_class)->setup_ports (self);
+
+ /* _OWANCALL unsolicited messages are only expected in the primary port. */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ MM_BROADBAND_MODEM_HSO (self)->priv->_owancall_regex,
+ NULL, NULL, NULL);
+
+ g_object_set (mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ MM_PORT_SERIAL_SEND_DELAY, (guint64) 0,
+ /* built-in echo removal conflicts with unsolicited _OWANCALL
+ * messages, which are not <CR><LF> prefixed. */
+ MM_PORT_SERIAL_AT_REMOVE_ECHO, FALSE,
+ NULL);
+
+ gps_control_port = mm_base_modem_peek_port_gps_control (MM_BASE_MODEM (self));
+ gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self));
+ if (gps_control_port && gps_data_port) {
+ /* It may happen that the modem was started with GPS already enabled, or
+ * maybe ModemManager got rebooted and it was left enabled before. We'll make
+ * sure that it is disabled when we initialize the modem */
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ gps_control_port,
+ "_OGPS=0",
+ 3, FALSE, FALSE, NULL, NULL, NULL);
+
+ /* Add handler for the NMEA traces */
+ mm_port_serial_gps_add_trace_handler (gps_data_port,
+ (MMPortSerialGpsTraceFn)trace_received,
+ self,
+ NULL);
+ }
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemHso *
+mm_broadband_modem_hso_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_HSO,
+ 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 (AT) and HSO bearer (NET) supported */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemHso *self = MM_BROADBAND_MODEM_HSO (object);
+
+ g_regex_unref (self->priv->_owancall_regex);
+
+ G_OBJECT_CLASS (mm_broadband_modem_hso_parent_class)->finalize (object);
+}
+
+static void
+mm_broadband_modem_hso_init (MMBroadbandModemHso *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_MODEM_HSO,
+ MMBroadbandModemHsoPrivate);
+
+ self->priv->_owancall_regex = g_regex_new ("_OWANCALL: (\\d),\\s*(\\d)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE;
+}
+
+static void
+shared_option_init (MMSharedOption *iface)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface->create_sim = mm_shared_option_create_sim;
+ iface->create_sim_finish = mm_shared_option_create_sim_finish;
+ iface->create_bearer = modem_create_bearer;
+ iface->create_bearer_finish = modem_create_bearer_finish;
+ iface->load_unlock_retries = load_unlock_retries;
+ iface->load_unlock_retries_finish = load_unlock_retries_finish;
+
+ /* HSO modems don't need the extra 10s wait after powering up */
+ iface->modem_after_power_up = NULL;
+ iface->modem_after_power_up_finish = NULL;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = location_load_capabilities;
+ iface->load_capabilities_finish = location_load_capabilities_finish;
+ iface->enable_location_gathering = enable_location_gathering;
+ iface->enable_location_gathering_finish = enable_location_gathering_finish;
+ iface->disable_location_gathering = disable_location_gathering;
+ iface->disable_location_gathering_finish = disable_location_gathering_finish;
+}
+
+static void
+mm_broadband_modem_hso_class_init (MMBroadbandModemHsoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemHsoPrivate));
+
+ object_class->finalize = finalize;
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/option/mm-broadband-modem-hso.h b/src/plugins/option/mm-broadband-modem-hso.h
new file mode 100644
index 00000000..b918719c
--- /dev/null
+++ b/src/plugins/option/mm-broadband-modem-hso.h
@@ -0,0 +1,51 @@
+/* -*- 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 hso) 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_MODEM_HSO_H
+#define MM_BROADBAND_MODEM_HSO_H
+
+#include "mm-broadband-modem-option.h"
+
+#define MM_TYPE_BROADBAND_MODEM_HSO (mm_broadband_modem_hso_get_type ())
+#define MM_BROADBAND_MODEM_HSO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_HSO, MMBroadbandModemHso))
+#define MM_BROADBAND_MODEM_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_HSO, MMBroadbandModemHsoClass))
+#define MM_IS_BROADBAND_MODEM_HSO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_HSO))
+#define MM_IS_BROADBAND_MODEM_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_HSO))
+#define MM_BROADBAND_MODEM_HSO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_HSO, MMBroadbandModemHsoClass))
+
+typedef struct _MMBroadbandModemHso MMBroadbandModemHso;
+typedef struct _MMBroadbandModemHsoClass MMBroadbandModemHsoClass;
+typedef struct _MMBroadbandModemHsoPrivate MMBroadbandModemHsoPrivate;
+
+struct _MMBroadbandModemHso {
+ MMBroadbandModemOption parent;
+ MMBroadbandModemHsoPrivate *priv;
+};
+
+struct _MMBroadbandModemHsoClass{
+ MMBroadbandModemOptionClass parent;
+};
+
+GType mm_broadband_modem_hso_get_type (void);
+
+MMBroadbandModemHso *mm_broadband_modem_hso_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_HSO_H */
diff --git a/src/plugins/option/mm-broadband-modem-option.c b/src/plugins/option/mm-broadband-modem-option.c
new file mode 100644
index 00000000..dcecd5b0
--- /dev/null
+++ b/src/plugins/option/mm-broadband-modem-option.c
@@ -0,0 +1,1228 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-modem-helpers.h"
+#include "mm-log-object.h"
+#include "mm-errors-types.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-base-modem-at.h"
+#include "mm-broadband-modem-option.h"
+#include "mm-shared-option.h"
+
+static void shared_option_init (MMSharedOption *iface);
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemOption, mm_broadband_modem_option, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_OPTION, shared_option_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init))
+
+struct _MMBroadbandModemOptionPrivate {
+ /* Regex for access-technology related notifications */
+ GRegex *_ossysi_regex;
+ GRegex *_octi_regex;
+ GRegex *_ouwcti_regex;
+
+ /* Regex for signal quality related notifications */
+ GRegex *_osigq_regex;
+
+ /* Regex for other notifications to ignore */
+ GRegex *ignore_regex;
+
+ guint after_power_up_wait_id;
+};
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_load_supported_modes_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ GArray *all;
+ GArray *combinations;
+ GArray *filtered;
+ MMModemModeCombination mode;
+
+ all = iface_modem_parent->load_supported_modes_finish (self, res, &error);
+ if (!all) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5);
+
+ /* 2G only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 3G only */
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G, 2G preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G, 3G preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+
+ /* Filter out those unsupported modes */
+ filtered = mm_filter_supported_modes (all, combinations, self);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Run parent's loading */
+ iface_modem_parent->load_supported_modes (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_supported_modes_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Load initial allowed/preferred modes (Modem interface) */
+
+static gboolean
+load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ const gchar *response;
+ const gchar *str;
+ gint a, b;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return FALSE;
+
+ str = mm_strip_tag (response, "_OPSYS:");
+
+ if (!sscanf (str, "%d,%d", &a, &b)) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse OPSYS response: '%s'",
+ response);
+ return FALSE;
+ }
+
+ switch (a) {
+ case 0:
+ *allowed = MM_MODEM_MODE_2G;
+ *preferred = MM_MODEM_MODE_NONE;
+ return TRUE;
+ case 1:
+ *allowed = MM_MODEM_MODE_3G;
+ *preferred = MM_MODEM_MODE_NONE;
+ return TRUE;
+ case 2:
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ *preferred = MM_MODEM_MODE_2G;
+ return TRUE;
+ case 3:
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ *preferred = MM_MODEM_MODE_3G;
+ return TRUE;
+ case 5: /* any */
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ *preferred = MM_MODEM_MODE_NONE;
+ return TRUE;
+ default:
+ break;
+ }
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse unexpected OPSYS response: '%s'",
+ response);
+ return FALSE;
+}
+
+static void
+load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "_OPSYS?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Set allowed modes (Modem interface) */
+
+static gboolean
+set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+allowed_mode_update_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (self, res, &error);
+ if (error)
+ /* Let the error be critical. */
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *command;
+ gint option_mode = -1;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (allowed == MM_MODEM_MODE_2G)
+ option_mode = 0;
+ else if (allowed == MM_MODEM_MODE_3G)
+ option_mode = 1;
+ else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) {
+ if (preferred == MM_MODEM_MODE_2G)
+ option_mode = 2;
+ else if (preferred == MM_MODEM_MODE_3G)
+ option_mode = 3;
+ else /* none preferred, so AUTO */
+ option_mode = 5;
+ } else if (allowed == MM_MODEM_MODE_ANY && preferred == MM_MODEM_MODE_NONE)
+ option_mode = 5;
+
+ if (option_mode < 0) {
+ gchar *allowed_str;
+ gchar *preferred_str;
+
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Requested mode (allowed: '%s', preferred: '%s') not "
+ "supported by the modem.",
+ allowed_str,
+ preferred_str);
+ g_object_unref (task);
+ g_free (allowed_str);
+ g_free (preferred_str);
+ return;
+ }
+
+ command = g_strdup_printf ("AT_OPSYS=%d,2", option_mode);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)allowed_mode_update_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Load access technologies (Modem interface) */
+
+typedef enum {
+ ACCESS_TECHNOLOGIES_STEP_FIRST,
+ ACCESS_TECHNOLOGIES_STEP_OSSYS,
+ ACCESS_TECHNOLOGIES_STEP_OCTI,
+ ACCESS_TECHNOLOGIES_STEP_OWCTI,
+ ACCESS_TECHNOLOGIES_STEP_LAST
+} AccessTechnologiesStep;
+
+typedef struct {
+ MMModemAccessTechnology access_technology;
+ gboolean check_2g;
+ gboolean check_3g;
+ AccessTechnologiesStep step;
+} AccessTechnologiesContext;
+
+static void load_access_technologies_step (GTask *task);
+
+static gboolean
+load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ 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 FALSE;
+ }
+
+ /* We are reporting ALL 3GPP access technologies here */
+ *access_technologies = (MMModemAccessTechnology) value;
+ *mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK;
+ return TRUE;
+}
+
+static gboolean
+ossys_to_mm (gchar ossys,
+ MMModemAccessTechnology *access_technology)
+{
+ if (ossys == '0') {
+ *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
+ return TRUE;
+ }
+
+ if (ossys == '2') {
+ *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ return TRUE;
+ }
+
+ if (ossys == '3') {
+ *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+parse_ossys_response (const gchar *response,
+ MMModemAccessTechnology *access_technology)
+{
+ MMModemAccessTechnology current = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ const gchar *p;
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+
+ p = mm_strip_tag (response, "_OSSYS:");
+ r = g_regex_new ("(\\d),(\\d)", G_REGEX_UNGREEDY, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match (r, p, 0, &match_info);
+ if (g_match_info_matches (match_info)) {
+ g_autofree gchar *str = NULL;
+
+ str = g_match_info_fetch (match_info, 2);
+ if (str && ossys_to_mm (str[0], &current)) {
+ *access_technology = current;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void
+ossys_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ AccessTechnologiesContext *ctx;
+ const gchar *response;
+
+ ctx = g_task_get_task_data (task);
+
+ /* If for some reason the OSSYS request failed, still try to check
+ * explicit 2G/3G mode with OCTI and OWCTI; maybe we'll get something.
+ */
+ response = mm_base_modem_at_command_finish (self, res, NULL);
+ /* Response is _OSSYS: <n>,<act> so we must skip the <n> */
+ if (response &&
+ parse_ossys_response (response, &ctx->access_technology)) {
+ /* If the OSSYS response indicated a generic access tech type
+ * then only check for more specific access tech of that type.
+ */
+ if (ctx->access_technology == MM_MODEM_ACCESS_TECHNOLOGY_GPRS)
+ ctx->check_3g = FALSE;
+ else if (ctx->access_technology == MM_MODEM_ACCESS_TECHNOLOGY_UMTS)
+ ctx->check_2g = FALSE;
+ }
+
+ /* Go on to next step */
+ ctx->step++;
+ load_access_technologies_step (task);
+}
+
+static gboolean
+octi_to_mm (gchar octi,
+ MMModemAccessTechnology *access_technology)
+{
+ if (octi == '1') {
+ *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_GSM;
+ return TRUE;
+ }
+
+ if (octi == '2') {
+ *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
+ return TRUE;
+ }
+
+ if (octi == '3') {
+ *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+parse_octi_response (const gchar *response,
+ MMModemAccessTechnology *access_technology)
+{
+ MMModemAccessTechnology current = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ const gchar *p;
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+
+ p = mm_strip_tag (response, "_OCTI:");
+ r = g_regex_new ("(\\d),(\\d)", G_REGEX_UNGREEDY, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match (r, p, 0, &match_info);
+ if (g_match_info_matches (match_info)) {
+ g_autofree gchar *str = NULL;
+
+ str = g_match_info_fetch (match_info, 2);
+ if (str && octi_to_mm (str[0], &current)) {
+ *access_technology = current;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void
+octi_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ AccessTechnologiesContext *ctx;
+ MMModemAccessTechnology octi = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ const gchar *response;
+
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (self, res, NULL);
+ if (response &&
+ parse_octi_response (response, &octi)) {
+ /* If current tech is 2G or unknown then use the more specific
+ * OCTI response.
+ */
+ if (ctx->access_technology < MM_MODEM_ACCESS_TECHNOLOGY_UMTS)
+ ctx->access_technology = octi;
+ }
+
+ /* Go on to next step */
+ ctx->step++;
+ load_access_technologies_step (task);
+}
+
+static gboolean
+owcti_to_mm (gchar owcti, MMModemAccessTechnology *access_technology)
+{
+ if (owcti == '1') {
+ *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ return TRUE;
+ }
+
+ if (owcti == '2') {
+ *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
+ return TRUE;
+ }
+
+ if (owcti == '3') {
+ *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_HSUPA;
+ return TRUE;
+ }
+
+ if (owcti == '4') {
+ *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_HSPA;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+parse_owcti_response (const gchar *response,
+ MMModemAccessTechnology *access_technology)
+{
+ response = mm_strip_tag (response, "_OWCTI:");
+ return owcti_to_mm (*response, access_technology);
+}
+
+static void
+owcti_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ AccessTechnologiesContext *ctx;
+ MMModemAccessTechnology owcti = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ const gchar *response;
+
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (self, res, NULL);
+ if (response &&
+ parse_owcti_response (response, &owcti)) {
+ ctx->access_technology = owcti;
+ }
+
+ /* Go on to next step */
+ ctx->step++;
+ load_access_technologies_step (task);
+}
+
+static void
+load_access_technologies_step (GTask *task)
+{
+ MMBroadbandModemOption *self;
+ AccessTechnologiesContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case ACCESS_TECHNOLOGIES_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+
+ case ACCESS_TECHNOLOGIES_STEP_OSSYS:
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "_OSSYS?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)ossys_query_ready,
+ task);
+ break;
+
+ case ACCESS_TECHNOLOGIES_STEP_OCTI:
+ if (ctx->check_2g) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "_OCTI?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)octi_query_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case ACCESS_TECHNOLOGIES_STEP_OWCTI:
+ if (ctx->check_3g) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "_OWCTI?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)owcti_query_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case ACCESS_TECHNOLOGIES_STEP_LAST:
+ /* All done, set result and complete */
+ g_task_return_int (task, ctx->access_technology);
+ g_object_unref (task);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+run_access_technology_loading_sequence (MMIfaceModem *self,
+ AccessTechnologiesStep first,
+ gboolean check_2g,
+ gboolean check_3g,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ AccessTechnologiesContext *ctx;
+ GTask *task;
+
+ ctx = g_new (AccessTechnologiesContext, 1);
+ ctx->step = first;
+ ctx->check_2g = check_2g;
+ ctx->check_3g = check_3g;
+ ctx->access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ load_access_technologies_step (task);
+}
+
+static void
+load_access_technologies (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ run_access_technology_loading_sequence (self,
+ ACCESS_TECHNOLOGIES_STEP_FIRST,
+ TRUE, /* check 2g */
+ TRUE, /* check 3g */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* After power up (Modem interface) */
+
+static gboolean
+modem_after_power_up_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static gboolean
+after_power_up_wait_cb (GTask *task)
+{
+ MMBroadbandModemOption *self;
+
+ self = g_task_get_source_object (task);
+ self->priv->after_power_up_wait_id = 0;
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+modem_after_power_up (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemOption *self = MM_BROADBAND_MODEM_OPTION (_self);
+
+ /* Some Option devices return OK on +CFUN=1 right away but need some time
+ * to finish initialization.
+ */
+ g_warn_if_fail (self->priv->after_power_up_wait_id == 0);
+ self->priv->after_power_up_wait_id =
+ g_timeout_add_seconds (10,
+ (GSourceFunc)after_power_up_wait_cb,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* IMSI loading (3GPP interface) */
+
+static gchar *
+modem_3gpp_load_imei_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ gchar *imei;
+ gchar *comma;
+
+ imei = g_strdup (mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error));
+ if (!imei)
+ return NULL;
+
+ /* IMEI reported by Option modems contain the IMEI plus something else:
+ *
+ * (ttyHS4): --> 'AT+CGSN<CR>'
+ * (ttyHS4): <-- '<CR><LF>357516032005989,TR19A8P11R<CR><LF><CR><LF>OK<CR><LF>'
+ */
+ comma = strchr (imei, ',');
+ if (comma)
+ *comma = '\0';
+
+ return imei;
+}
+
+static void
+modem_3gpp_load_imei (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CGSN",
+ 3,
+ TRUE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (3GPP interface) */
+
+static void
+option_ossys_tech_changed (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMBroadbandModemOption *self)
+{
+ MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ gchar *str;
+
+ str = g_match_info_fetch (info, 1);
+ if (str) {
+ ossys_to_mm (str[0], &act);
+ g_free (str);
+ }
+
+ mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
+ act,
+ MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
+
+ /* _OSSYSI only indicates general 2G/3G mode, so queue up some explicit
+ * access technology requests.
+ */
+ if (act == MM_MODEM_ACCESS_TECHNOLOGY_GPRS)
+ run_access_technology_loading_sequence (MM_IFACE_MODEM (self),
+ ACCESS_TECHNOLOGIES_STEP_OCTI,
+ TRUE, /* check 2g */
+ FALSE, /* check 3g */
+ NULL,
+ NULL);
+ else if (act == MM_MODEM_ACCESS_TECHNOLOGY_UMTS)
+ run_access_technology_loading_sequence (MM_IFACE_MODEM (self),
+ ACCESS_TECHNOLOGIES_STEP_OWCTI,
+ FALSE, /* check 2g */
+ TRUE, /* check 3g */
+ NULL,
+ NULL);
+}
+
+static void
+option_2g_tech_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemOption *self)
+{
+ MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ gchar *str;
+
+ str = g_match_info_fetch (match_info, 1);
+ if (str && octi_to_mm (str[0], &act))
+ mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
+ act,
+ MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
+ g_free (str);
+}
+
+static void
+option_3g_tech_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemOption *self)
+{
+ MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ gchar *str;
+
+ str = g_match_info_fetch (match_info, 1);
+ if (str && owcti_to_mm (str[0], &act))
+ mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
+ act,
+ MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
+ g_free (str);
+}
+
+static void
+option_signal_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemOption *self)
+{
+ gchar *str;
+ guint quality = 0;
+
+ str = g_match_info_fetch (match_info, 1);
+ if (str) {
+ quality = atoi (str);
+ g_free (str);
+ }
+
+ if (quality == 99) {
+ /* 99 means unknown */
+ quality = 0;
+ } else {
+ /* Normalize the quality */
+ quality = MM_CLAMP_HIGH (quality, 31) * 100 / 31;
+ }
+
+ mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);
+}
+
+static void
+set_unsolicited_events_handlers (MMBroadbandModemOption *self,
+ gboolean enable)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ 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 unsolicited events in given port */
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ /* Access technology related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->_ossysi_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)option_ossys_tech_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->_octi_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)option_2g_tech_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->_ouwcti_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)option_3g_tech_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ /* Signal quality related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->_osigq_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)option_signal_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ /* Other unsolicited events to always ignore */
+ if (!enable)
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->ignore_regex,
+ NULL, NULL, NULL);
+ }
+}
+
+static gboolean
+modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else {
+ /* Our own setup now */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_OPTION (self), TRUE);
+ g_task_return_boolean (task, TRUE);
+ }
+
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's setup */
+ iface_modem_3gpp_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_setup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+static void
+parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Our own cleanup first */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_OPTION (self), FALSE);
+
+ /* And now chain up parent's cleanup */
+ iface_modem_3gpp_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Enabling unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+own_enable_unsolicited_events_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_full_finish (self, res, NULL, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static const MMBaseModemAtCommand unsolicited_enable_sequence[] = {
+ { "_OSSYS=1", 3, FALSE, NULL },
+ { "_OCTI=1", 3, FALSE, NULL },
+ { "_OUWCTI=1", 3, FALSE, NULL },
+ { "_OSQI=1", 3, FALSE, NULL },
+ { NULL }
+};
+
+static void
+parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ }
+
+ /* Our own enable now */
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ unsolicited_enable_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)own_enable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's enable */
+ iface_modem_3gpp_parent->enable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_enable_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Disabling unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static const MMBaseModemAtCommand unsolicited_disable_sequence[] = {
+ { "_OSSYS=0", 3, FALSE, NULL },
+ { "_OCTI=0", 3, FALSE, NULL },
+ { "_OUWCTI=0", 3, FALSE, NULL },
+ { "_OSQI=0", 3, FALSE, NULL },
+ { NULL }
+};
+
+static void
+parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+own_disable_unsolicited_events_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_full_finish (self, res, NULL, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Next, chain up parent's disable */
+ iface_modem_3gpp_parent->disable_unsolicited_events (
+ MM_IFACE_MODEM_3GPP (self),
+ (GAsyncReadyCallback)parent_disable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Our own disable first */
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ unsolicited_disable_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)own_disable_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_option_parent_class)->setup_ports (self);
+
+ /* Now reset the unsolicited messages we'll handle when enabled */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_OPTION (self), FALSE);
+}
+
+/*****************************************************************************/
+
+static gboolean
+is_nozomi (const gchar **drivers)
+{
+ if (drivers) {
+ guint i;
+
+ for (i = 0; drivers[i]; i++) {
+ if (g_str_equal (drivers[i], "nozomi"))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+MMBroadbandModemOption *
+mm_broadband_modem_option_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ MMModem3gppFacility ignored;
+
+ /* Ignore PH-SIM facility in 'nozomi' managed modems */
+ ignored = is_nozomi (drivers) ? MM_MODEM_3GPP_FACILITY_PH_SIM : MM_MODEM_3GPP_FACILITY_NONE;
+
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_OPTION,
+ 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_3GPP_IGNORED_FACILITY_LOCKS, ignored,
+ NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemOption *self = MM_BROADBAND_MODEM_OPTION (object);
+
+ g_regex_unref (self->priv->_ossysi_regex);
+ g_regex_unref (self->priv->_octi_regex);
+ g_regex_unref (self->priv->_ouwcti_regex);
+ g_regex_unref (self->priv->_osigq_regex);
+ g_regex_unref (self->priv->ignore_regex);
+
+ G_OBJECT_CLASS (mm_broadband_modem_option_parent_class)->finalize (object);
+}
+
+static void
+mm_broadband_modem_option_init (MMBroadbandModemOption *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_MODEM_OPTION,
+ MMBroadbandModemOptionPrivate);
+ self->priv->after_power_up_wait_id = 0;
+
+ /* Prepare regular expressions to setup */
+ self->priv->_ossysi_regex = g_regex_new ("\\r\\n_OSSYSI:\\s*(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->_octi_regex = g_regex_new ("\\r\\n_OCTI:\\s*(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->_ouwcti_regex = g_regex_new ("\\r\\n_OUWCTI:\\s*(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->_osigq_regex = g_regex_new ("\\r\\n_OSIGQ:\\s*(\\d+),(\\d)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->ignore_regex = g_regex_new ("\\r\\n\\+PACSP0\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+static void
+shared_option_init (MMSharedOption *iface)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->create_sim = mm_shared_option_create_sim;
+ iface->create_sim_finish = mm_shared_option_create_sim_finish;
+ iface->modem_after_power_up = modem_after_power_up;
+ iface->modem_after_power_up_finish = modem_after_power_up_finish;
+ iface->load_access_technologies = load_access_technologies;
+ iface->load_access_technologies_finish = load_access_technologies_finish;
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = load_current_modes;
+ iface->load_current_modes_finish = load_current_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = set_current_modes_finish;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_imei = modem_3gpp_load_imei;
+ iface->load_imei_finish = modem_3gpp_load_imei_finish;
+ iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish;
+}
+
+static void
+mm_broadband_modem_option_class_init (MMBroadbandModemOptionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemOptionPrivate));
+
+ object_class->finalize = finalize;
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/option/mm-broadband-modem-option.h b/src/plugins/option/mm-broadband-modem-option.h
new file mode 100644
index 00000000..faf0595e
--- /dev/null
+++ b/src/plugins/option/mm-broadband-modem-option.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_MODEM_OPTION_H
+#define MM_BROADBAND_MODEM_OPTION_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_OPTION (mm_broadband_modem_option_get_type ())
+#define MM_BROADBAND_MODEM_OPTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_OPTION, MMBroadbandModemOption))
+#define MM_BROADBAND_MODEM_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_OPTION, MMBroadbandModemOptionClass))
+#define MM_IS_BROADBAND_MODEM_OPTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_OPTION))
+#define MM_IS_BROADBAND_MODEM_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_OPTION))
+#define MM_BROADBAND_MODEM_OPTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_OPTION, MMBroadbandModemOptionClass))
+
+typedef struct _MMBroadbandModemOption MMBroadbandModemOption;
+typedef struct _MMBroadbandModemOptionClass MMBroadbandModemOptionClass;
+typedef struct _MMBroadbandModemOptionPrivate MMBroadbandModemOptionPrivate;
+
+struct _MMBroadbandModemOption {
+ MMBroadbandModem parent;
+ MMBroadbandModemOptionPrivate *priv;
+};
+
+struct _MMBroadbandModemOptionClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_option_get_type (void);
+
+MMBroadbandModemOption *mm_broadband_modem_option_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_OPTION_H */
diff --git a/src/plugins/option/mm-plugin-hso.c b/src/plugins/option/mm-plugin-hso.c
new file mode 100644
index 00000000..9a28ca64
--- /dev/null
+++ b/src/plugins/option/mm-plugin-hso.c
@@ -0,0 +1,202 @@
+/* -*- 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 hso) 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-private-boxed-types.h"
+#include "mm-plugin-hso.h"
+#include "mm-broadband-modem-hso.h"
+#include "mm-log-object.h"
+
+G_DEFINE_TYPE (MMPluginHso, mm_plugin_hso, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+/* Custom init */
+
+#define TAG_HSO_AT_CONTROL "hso-at-control"
+#define TAG_HSO_AT_APP "hso-at-app"
+#define TAG_HSO_AT_MODEM "hso-at-modem"
+#define TAG_HSO_AT_GPS_CONTROL "hso-at-gps-control"
+#define TAG_HSO_GPS "hso-gps"
+#define TAG_HSO_DIAG "hso-diag"
+
+static gboolean
+hso_custom_init_finish (MMPortProbe *probe,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+hso_custom_init (MMPortProbe *probe,
+ MMPortSerialAt *port,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMKernelDevice *kernel_port;
+ GTask *task;
+ const gchar *subsys, *sysfs_path;
+
+ subsys = mm_port_probe_get_port_subsys (probe);
+ kernel_port = mm_port_probe_peek_port (probe);
+ sysfs_path = mm_kernel_device_get_sysfs_path (kernel_port);
+
+ if (g_str_equal (subsys, "tty")) {
+ gchar *hsotype_path;
+ gchar *contents = NULL;
+
+ hsotype_path = g_build_filename (sysfs_path, "hsotype", NULL);
+ if (g_file_get_contents (hsotype_path, &contents, NULL, NULL)) {
+ mm_obj_dbg (probe, "HSO port type %s: %s", hsotype_path, contents);
+ if (g_str_has_prefix (contents, "Control")) {
+ g_object_set_data (G_OBJECT (probe), TAG_HSO_AT_CONTROL, GUINT_TO_POINTER (TRUE));
+ mm_port_probe_set_result_at (probe, TRUE);
+ } else if (g_str_has_prefix (contents, "Application")) {
+ g_object_set_data (G_OBJECT (probe), TAG_HSO_AT_APP, GUINT_TO_POINTER (TRUE));
+ mm_port_probe_set_result_at (probe, TRUE);
+ } else if (g_str_has_prefix (contents, "Modem")) {
+ g_object_set_data (G_OBJECT (probe), TAG_HSO_AT_MODEM, GUINT_TO_POINTER (TRUE));
+ mm_port_probe_set_result_at (probe, TRUE);
+ } else if (g_str_has_prefix (contents, "GPS Control")) {
+ g_object_set_data (G_OBJECT (probe), TAG_HSO_AT_GPS_CONTROL, GUINT_TO_POINTER (TRUE));
+ mm_port_probe_set_result_at (probe, TRUE);
+ } else if (g_str_has_prefix (contents, "GPS")) {
+ /* Not an AT port, but the port to grab GPS traces */
+ g_object_set_data (G_OBJECT (probe), TAG_HSO_GPS, GUINT_TO_POINTER (TRUE));
+ mm_port_probe_set_result_at (probe, FALSE);
+ mm_port_probe_set_result_qcdm (probe, FALSE);
+ } else if (g_str_has_prefix (contents, "Diag")) {
+ g_object_set_data (G_OBJECT (probe), TAG_HSO_DIAG, GUINT_TO_POINTER (TRUE));
+ mm_port_probe_set_result_at (probe, FALSE);
+
+ /* Don't automatically tag as QCDM, as the 'hso' driver reports
+ * a DIAG port for some Icera-based modems, which don't have
+ * QCDM ports since they aren't made by Qualcomm.
+ */
+ }
+ g_free (contents);
+ }
+ g_free (hsotype_path);
+ }
+
+ task = g_task_new (probe, NULL, callback, user_data);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+
+static MMBaseModem *
+create_modem (MMPlugin *self,
+ const gchar *uid,
+ const gchar **drivers,
+ guint16 vendor,
+ guint16 product,
+ guint16 subsystem_vendor,
+ GList *probes,
+ GError **error)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_hso_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+static gboolean
+grab_port (MMPlugin *self,
+ MMBaseModem *modem,
+ MMPortProbe *probe,
+ GError **error)
+{
+ const gchar *subsys;
+ MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE;
+ MMPortType port_type;
+
+ subsys = mm_port_probe_get_port_subsys (probe);
+ port_type = mm_port_probe_get_port_type (probe);
+
+ /* Detect AT port types */
+ if (g_str_equal (subsys, "tty")) {
+ if (g_object_get_data (G_OBJECT (probe), TAG_HSO_AT_CONTROL))
+ pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY;
+ else if (g_object_get_data (G_OBJECT (probe), TAG_HSO_AT_APP))
+ pflags = MM_PORT_SERIAL_AT_FLAG_SECONDARY;
+ else if (g_object_get_data (G_OBJECT (probe), TAG_HSO_AT_GPS_CONTROL))
+ pflags = MM_PORT_SERIAL_AT_FLAG_GPS_CONTROL;
+ else if (g_object_get_data (G_OBJECT (probe), TAG_HSO_AT_MODEM))
+ pflags = MM_PORT_SERIAL_AT_FLAG_PPP;
+ else if (g_object_get_data (G_OBJECT (probe), TAG_HSO_GPS)) {
+ /* Not an AT port, but the port to grab GPS traces */
+ g_assert (port_type == MM_PORT_TYPE_UNKNOWN);
+ port_type = MM_PORT_TYPE_GPS;
+ }
+ }
+
+ return mm_base_modem_grab_port (modem,
+ mm_port_probe_peek_port (probe),
+ port_type,
+ pflags,
+ error);
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", NULL };
+ static const gchar *drivers[] = { "hso", NULL };
+ static const MMAsyncMethod custom_init = {
+ .async = G_CALLBACK (hso_custom_init),
+ .finish = G_CALLBACK (hso_custom_init_finish),
+ };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_HSO,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_DRIVERS, drivers,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_QCDM, TRUE,
+ MM_PLUGIN_CUSTOM_INIT, &custom_init,
+ MM_PLUGIN_SEND_DELAY, (guint64) 0,
+ NULL));
+}
+
+static void
+mm_plugin_hso_init (MMPluginHso *self)
+{
+}
+
+static void
+mm_plugin_hso_class_init (MMPluginHsoClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+ plugin_class->grab_port = grab_port;
+}
diff --git a/src/plugins/option/mm-plugin-hso.h b/src/plugins/option/mm-plugin-hso.h
new file mode 100644
index 00000000..5ef13439
--- /dev/null
+++ b/src/plugins/option/mm-plugin-hso.h
@@ -0,0 +1,42 @@
+/* -*- 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 hso) 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_PLUGIN_HSO_H
+#define MM_PLUGIN_HSO_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_HSO (mm_plugin_hso_get_type ())
+#define MM_PLUGIN_HSO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_HSO, MMPluginHso))
+#define MM_PLUGIN_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_HSO, MMPluginHsoClass))
+#define MM_IS_PLUGIN_HSO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_HSO))
+#define MM_IS_PLUGIN_HSO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_HSO))
+#define MM_PLUGIN_HSO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_HSO, MMPluginHsoClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginHso;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginHsoClass;
+
+GType mm_plugin_hso_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_HSO_H */
diff --git a/src/plugins/option/mm-plugin-option.c b/src/plugins/option/mm-plugin-option.c
new file mode 100644
index 00000000..4dcb55a1
--- /dev/null
+++ b/src/plugins/option/mm-plugin-option.c
@@ -0,0 +1,121 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-private-boxed-types.h"
+#include "mm-plugin-option.h"
+#include "mm-broadband-modem-option.h"
+
+G_DEFINE_TYPE (MMPluginOption, mm_plugin_option, 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)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_option_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+static gboolean
+grab_port (MMPlugin *self,
+ MMBaseModem *modem,
+ MMPortProbe *probe,
+ GError **error)
+{
+ MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE;
+ MMKernelDevice *port;
+ gint usbif;
+
+ /* The Option plugin cannot do anything with non-AT ports */
+ if (!mm_port_probe_is_at (probe)) {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Ignoring non-AT port");
+ return FALSE;
+ }
+
+ port = mm_port_probe_peek_port (probe);
+
+ /* Genuine Option NV devices are always supposed to use USB interface 0 as
+ * the modem/data port, per mail with Option engineers. Only this port
+ * will emit responses to dialing commands.
+ */
+ usbif = mm_kernel_device_get_interface_number (port);
+ if (usbif == 0)
+ pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY | MM_PORT_SERIAL_AT_FLAG_PPP;
+
+ return mm_base_modem_grab_port (modem,
+ port,
+ MM_PORT_TYPE_AT, /* we only allow AT ports here */
+ pflags,
+ error);
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", NULL };
+ static const guint16 vendor_ids[] = { 0x0af0, /* Option USB devices */
+ 0x1931, /* Nozomi CardBus devices */
+ 0 };
+ static const gchar *drivers[] = { "option1", "option", "nozomi", NULL };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_OPTION,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_DRIVERS, drivers,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_option_init (MMPluginOption *self)
+{
+}
+
+static void
+mm_plugin_option_class_init (MMPluginOptionClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+ plugin_class->grab_port = grab_port;
+}
diff --git a/src/plugins/option/mm-plugin-option.h b/src/plugins/option/mm-plugin-option.h
new file mode 100644
index 00000000..275fc403
--- /dev/null
+++ b/src/plugins/option/mm-plugin-option.h
@@ -0,0 +1,42 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_PLUGIN_OPTION_H
+#define MM_PLUGIN_OPTION_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_OPTION (mm_plugin_option_get_type ())
+#define MM_PLUGIN_OPTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_OPTION, MMPluginOption))
+#define MM_PLUGIN_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_OPTION, MMPluginOptionClass))
+#define MM_IS_PLUGIN_OPTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_OPTION))
+#define MM_IS_PLUGIN_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_OPTION))
+#define MM_PLUGIN_OPTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_OPTION, MMPluginOptionClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginOption;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginOptionClass;
+
+GType mm_plugin_option_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_OPTION_H */
diff --git a/src/plugins/option/mm-shared-option.c b/src/plugins/option/mm-shared-option.c
new file mode 100644
index 00000000..a06888a1
--- /dev/null
+++ b/src/plugins/option/mm-shared-option.c
@@ -0,0 +1,77 @@
+/* -*- 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) 2021 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdio.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-sim-option.h"
+#include "mm-shared-option.h"
+
+/*****************************************************************************/
+/* Create SIM (Modem inteface) */
+
+MMBaseSim *
+mm_shared_option_create_sim_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return mm_sim_option_new_finish (res, error);
+}
+
+void
+mm_shared_option_create_sim (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_sim_option_new (MM_BASE_MODEM (self),
+ NULL, /* cancellable */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+
+static void
+shared_option_init (gpointer g_iface)
+{
+}
+
+GType
+mm_shared_option_get_type (void)
+{
+ static GType shared_option_type = 0;
+
+ if (!G_UNLIKELY (shared_option_type)) {
+ static const GTypeInfo info = {
+ sizeof (MMSharedOption), /* class_size */
+ shared_option_init, /* base_init */
+ NULL, /* base_finalize */
+ };
+
+ shared_option_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedOption", &info, 0);
+ g_type_interface_add_prerequisite (shared_option_type, MM_TYPE_IFACE_MODEM);
+ }
+
+ return shared_option_type;
+}
diff --git a/src/plugins/option/mm-shared-option.h b/src/plugins/option/mm-shared-option.h
new file mode 100644
index 00000000..0d4baf60
--- /dev/null
+++ b/src/plugins/option/mm-shared-option.h
@@ -0,0 +1,49 @@
+/* -*- 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) 2021 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_SHARED_OPTION_H
+#define MM_SHARED_OPTION_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-location.h"
+
+#define MM_TYPE_SHARED_OPTION (mm_shared_option_get_type ())
+#define MM_SHARED_OPTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_OPTION, MMSharedOption))
+#define MM_IS_SHARED_OPTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_OPTION))
+#define MM_SHARED_OPTION_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_OPTION, MMSharedOption))
+
+typedef struct _MMSharedOption MMSharedOption;
+
+struct _MMSharedOption {
+ GTypeInterface g_iface;
+};
+
+GType mm_shared_option_get_type (void);
+
+void mm_shared_option_create_sim (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseSim *mm_shared_option_create_sim_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SHARED_OPTION_H */
diff --git a/src/plugins/option/mm-shared.c b/src/plugins/option/mm-shared.c
new file mode 100644
index 00000000..3f89d86a
--- /dev/null
+++ b/src/plugins/option/mm-shared.c
@@ -0,0 +1,20 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-shared.h"
+
+MM_SHARED_DEFINE_MAJOR_VERSION
+MM_SHARED_DEFINE_MINOR_VERSION
+MM_SHARED_DEFINE_NAME(Option)
diff --git a/src/plugins/option/mm-sim-option.c b/src/plugins/option/mm-sim-option.c
new file mode 100644
index 00000000..0871c4f2
--- /dev/null
+++ b/src/plugins/option/mm-sim-option.c
@@ -0,0 +1,84 @@
+/* -*- 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) 2021 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-sim-option.h"
+
+G_DEFINE_TYPE (MMSimOption, mm_sim_option, MM_TYPE_BASE_SIM)
+
+/*****************************************************************************/
+
+MMBaseSim *
+mm_sim_option_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *source;
+ GObject *sim;
+
+ source = g_async_result_get_source_object (res);
+ sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!sim)
+ return NULL;
+
+ /* Only export valid SIMs */
+ mm_base_sim_export (MM_BASE_SIM (sim));
+
+ return MM_BASE_SIM (sim);
+}
+
+void
+mm_sim_option_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (MM_TYPE_SIM_OPTION,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_SIM_MODEM, modem,
+ "active", TRUE, /* by default always active */
+ NULL);
+}
+
+static void
+mm_sim_option_init (MMSimOption *self)
+{
+}
+
+static void
+mm_sim_option_class_init (MMSimOptionClass *klass)
+{
+ MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass);
+
+ /* Skip managing preferred networks, not supported by Option modems */
+ base_sim_class->load_preferred_networks = NULL;
+ base_sim_class->load_preferred_networks_finish = NULL;
+ base_sim_class->set_preferred_networks = NULL;
+ base_sim_class->set_preferred_networks_finish = NULL;
+}
diff --git a/src/plugins/option/mm-sim-option.h b/src/plugins/option/mm-sim-option.h
new file mode 100644
index 00000000..c502a397
--- /dev/null
+++ b/src/plugins/option/mm-sim-option.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2021 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_SIM_OPTION_H
+#define MM_SIM_OPTION_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "mm-base-sim.h"
+
+#define MM_TYPE_SIM_OPTION (mm_sim_option_get_type ())
+#define MM_SIM_OPTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_OPTION, MMSimOption))
+#define MM_SIM_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_OPTION, MMSimOptionClass))
+#define MM_IS_SIM_OPTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_OPTION))
+#define MM_IS_SIM_OPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_OPTION))
+#define MM_SIM_OPTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_OPTION, MMSimOptionClass))
+
+typedef struct _MMSimOption MMSimOption;
+typedef struct _MMSimOptionClass MMSimOptionClass;
+
+struct _MMSimOption {
+ MMBaseSim parent;
+};
+
+struct _MMSimOptionClass {
+ MMBaseSimClass parent;
+};
+
+GType mm_sim_option_get_type (void);
+
+void mm_sim_option_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseSim *mm_sim_option_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SIM_OPTION_H */
diff --git a/src/plugins/pantech/mm-broadband-modem-pantech.c b/src/plugins/pantech/mm-broadband-modem-pantech.c
new file mode 100644
index 00000000..4e8b58e0
--- /dev/null
+++ b/src/plugins/pantech/mm-broadband-modem-pantech.c
@@ -0,0 +1,187 @@
+/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-messaging.h"
+#include "mm-errors-types.h"
+#include "mm-broadband-modem-pantech.h"
+#include "mm-sim-pantech.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_messaging_init (MMIfaceModemMessaging *iface);
+
+static MMIfaceModemMessaging *iface_modem_messaging_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemPantech, mm_broadband_modem_pantech, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init))
+
+/*****************************************************************************/
+/* Load supported SMS storages (Messaging interface) */
+
+static void
+skip_sm_sr_storage (GArray *mem)
+{
+ guint i = mem->len;
+
+ if (!mem)
+ return;
+
+ /* Remove SM and SR from the list of supported storages */
+ while (i-- > 0) {
+ if (g_array_index (mem, MMSmsStorage, i) == MM_SMS_STORAGE_SR ||
+ g_array_index (mem, MMSmsStorage, i) == MM_SMS_STORAGE_SM)
+ g_array_remove_index (mem, i);
+ }
+}
+
+static gboolean
+load_supported_storages_finish (MMIfaceModemMessaging *self,
+ GAsyncResult *res,
+ GArray **mem1,
+ GArray **mem2,
+ GArray **mem3,
+ GError **error)
+{
+ if (!iface_modem_messaging_parent->load_supported_storages_finish (self, res, mem1, mem2, mem3, error))
+ return FALSE;
+
+ skip_sm_sr_storage (*mem1);
+ skip_sm_sr_storage (*mem2);
+ skip_sm_sr_storage (*mem3);
+ return TRUE;
+}
+
+static void
+load_supported_storages (MMIfaceModemMessaging *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's loading */
+ iface_modem_messaging_parent->load_supported_storages (self, callback, user_data);
+}
+
+/*****************************************************************************/
+/* Create SIM (Modem interface) */
+
+static MMBaseSim *
+create_sim_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return mm_sim_pantech_new_finish (res, error);
+}
+
+static void
+create_sim (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* New Pantech SIM */
+ mm_sim_pantech_new (MM_BASE_MODEM (self),
+ NULL, /* cancellable */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* After SIM unlock (Modem interface) */
+
+static gboolean
+modem_after_sim_unlock_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static gboolean
+after_sim_unlock_wait_cb (GTask *task)
+{
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+modem_after_sim_unlock (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* wait so sim pin is done */
+ g_timeout_add_seconds (5,
+ (GSourceFunc)after_sim_unlock_wait_cb,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemPantech *
+mm_broadband_modem_pantech_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_PANTECH,
+ 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,
+ NULL);
+}
+
+static void
+mm_broadband_modem_pantech_init (MMBroadbandModemPantech *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ /* Create Pantech-specific SIM */
+ iface->create_sim = create_sim;
+ iface->create_sim_finish = create_sim_finish;
+
+ iface->modem_after_sim_unlock = modem_after_sim_unlock;
+ iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish;
+}
+
+static void
+iface_modem_messaging_init (MMIfaceModemMessaging *iface)
+{
+ iface_modem_messaging_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_supported_storages = load_supported_storages;
+ iface->load_supported_storages_finish = load_supported_storages_finish;
+}
+
+static void
+mm_broadband_modem_pantech_class_init (MMBroadbandModemPantechClass *klass)
+{
+}
diff --git a/src/plugins/pantech/mm-broadband-modem-pantech.h b/src/plugins/pantech/mm-broadband-modem-pantech.h
new file mode 100644
index 00000000..4a0a3a27
--- /dev/null
+++ b/src/plugins/pantech/mm-broadband-modem-pantech.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) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_MODEM_PANTECH_H
+#define MM_BROADBAND_MODEM_PANTECH_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_PANTECH (mm_broadband_modem_pantech_get_type ())
+#define MM_BROADBAND_MODEM_PANTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_PANTECH, MMBroadbandModemPantech))
+#define MM_BROADBAND_MODEM_PANTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_PANTECH, MMBroadbandModemPantechClass))
+#define MM_IS_BROADBAND_MODEM_PANTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_PANTECH))
+#define MM_IS_BROADBAND_MODEM_PANTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_PANTECH))
+#define MM_BROADBAND_MODEM_PANTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_PANTECH, MMBroadbandModemPantechClass))
+
+typedef struct _MMBroadbandModemPantech MMBroadbandModemPantech;
+typedef struct _MMBroadbandModemPantechClass MMBroadbandModemPantechClass;
+
+struct _MMBroadbandModemPantech {
+ MMBroadbandModem parent;
+};
+
+struct _MMBroadbandModemPantechClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_pantech_get_type (void);
+
+MMBroadbandModemPantech *mm_broadband_modem_pantech_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_PANTECH_H */
diff --git a/src/plugins/pantech/mm-plugin-pantech.c b/src/plugins/pantech/mm-plugin-pantech.c
new file mode 100644
index 00000000..4af1955b
--- /dev/null
+++ b/src/plugins/pantech/mm-plugin-pantech.c
@@ -0,0 +1,161 @@
+/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-plugin-pantech.h"
+#include "mm-broadband-modem-pantech.h"
+
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginPantech, mm_plugin_pantech, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+/* Custom commands for AT probing
+ * There's currently no WMC probing plugged in the logic, so We need to detect
+ * WMC ports ourselves somehow. Just assume that the WMC port will reply "ERROR"
+ * to the "ATE0" command.
+ */
+static gboolean
+port_probe_response_processor_is_pantech_at (const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ if (error) {
+ /* Timeout errors are the only ones not fatal;
+ * they will just go on to the next command. */
+ if (g_error_matches (error,
+ MM_SERIAL_ERROR,
+ MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) {
+ return FALSE;
+ }
+
+ /* All other errors indicate NOT an AT port */
+ *result = g_variant_new_boolean (FALSE);
+ return TRUE;
+ }
+
+ /* No error reported, valid AT port! */
+ *result = g_variant_new_boolean (TRUE);
+ return TRUE;
+}
+
+static const MMPortProbeAtCommand custom_at_probe[] = {
+ { "ATE0", 3, port_probe_response_processor_is_pantech_at },
+ { "ATE0", 3, port_probe_response_processor_is_pantech_at },
+ { "ATE0", 3, port_probe_response_processor_is_pantech_at },
+ { NULL }
+};
+
+/*****************************************************************************/
+
+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 Pantech modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_pantech_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+static gboolean
+grab_port (MMPlugin *self,
+ MMBaseModem *modem,
+ MMPortProbe *probe,
+ GError **error)
+{
+ MMPortType ptype;
+ MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE;
+
+ ptype = mm_port_probe_get_port_type (probe);
+
+ /* Always prefer the ttyACM port as PRIMARY AT port */
+ if (ptype == MM_PORT_TYPE_AT &&
+ g_str_has_prefix (mm_port_probe_get_port_name (probe), "ttyACM")) {
+ pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY;
+ }
+
+ return mm_base_modem_grab_port (modem,
+ mm_port_probe_peek_port (probe),
+ ptype,
+ pflags,
+ error);
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL };
+ static const guint16 vendor_ids[] = { 0x106c, 0 };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_PANTECH,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_QCDM, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ MM_PLUGIN_CUSTOM_AT_PROBE, custom_at_probe,
+ NULL));
+}
+
+static void
+mm_plugin_pantech_init (MMPluginPantech *self)
+{
+}
+
+static void
+mm_plugin_pantech_class_init (MMPluginPantechClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+ plugin_class->grab_port = grab_port;
+}
diff --git a/src/plugins/pantech/mm-plugin-pantech.h b/src/plugins/pantech/mm-plugin-pantech.h
new file mode 100644
index 00000000..fdbdd9ea
--- /dev/null
+++ b/src/plugins/pantech/mm-plugin-pantech.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) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_PLUGIN_PANTECH_H
+#define MM_PLUGIN_PANTECH_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_PANTECH (mm_plugin_pantech_get_type ())
+#define MM_PLUGIN_PANTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_PANTECH, MMPluginPantech))
+#define MM_PLUGIN_PANTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_PANTECH, MMPluginPantechClass))
+#define MM_IS_PLUGIN_PANTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_PANTECH))
+#define MM_IS_PLUGIN_PANTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_PANTECH))
+#define MM_PLUGIN_PANTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_PANTECH, MMPluginPantechClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginPantech;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginPantechClass;
+
+GType mm_plugin_pantech_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_PANTECH_H */
diff --git a/src/plugins/pantech/mm-sim-pantech.c b/src/plugins/pantech/mm-sim-pantech.c
new file mode 100644
index 00000000..33414572
--- /dev/null
+++ b/src/plugins/pantech/mm-sim-pantech.c
@@ -0,0 +1,87 @@
+/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-sim-pantech.h"
+
+G_DEFINE_TYPE (MMSimPantech, mm_sim_pantech, MM_TYPE_BASE_SIM)
+
+/*****************************************************************************/
+
+MMBaseSim *
+mm_sim_pantech_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *source;
+ GObject *sim;
+
+ source = g_async_result_get_source_object (res);
+ sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!sim)
+ return NULL;
+
+ /* Only export valid SIMs */
+ mm_base_sim_export (MM_BASE_SIM (sim));
+
+ return MM_BASE_SIM (sim);
+}
+
+void
+mm_sim_pantech_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (MM_TYPE_SIM_PANTECH,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_SIM_MODEM, modem,
+ "active", TRUE, /* by default always active */
+ NULL);
+}
+
+static void
+mm_sim_pantech_init (MMSimPantech *self)
+{
+}
+
+static void
+mm_sim_pantech_class_init (MMSimPantechClass *klass)
+{
+ MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass);
+
+ /* Skip querying most SIM card info, +CRSM just shoots the Pantech modems
+ * (at least the UMW190) in the head */
+ base_sim_class->load_sim_identifier = NULL;
+ base_sim_class->load_sim_identifier_finish = NULL;
+ base_sim_class->load_operator_identifier = NULL;
+ base_sim_class->load_operator_identifier_finish = NULL;
+ base_sim_class->load_operator_name = NULL;
+ base_sim_class->load_operator_name_finish = NULL;
+}
diff --git a/src/plugins/pantech/mm-sim-pantech.h b/src/plugins/pantech/mm-sim-pantech.h
new file mode 100644
index 00000000..8d227645
--- /dev/null
+++ b/src/plugins/pantech/mm-sim-pantech.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_SIM_PANTECH_H
+#define MM_SIM_PANTECH_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "mm-base-sim.h"
+
+#define MM_TYPE_SIM_PANTECH (mm_sim_pantech_get_type ())
+#define MM_SIM_PANTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_PANTECH, MMSimPantech))
+#define MM_SIM_PANTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_PANTECH, MMSimPantechClass))
+#define MM_IS_SIM_PANTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_PANTECH))
+#define MM_IS_SIM_PANTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_PANTECH))
+#define MM_SIM_PANTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_PANTECH, MMSimPantechClass))
+
+typedef struct _MMSimPantech MMSimPantech;
+typedef struct _MMSimPantechClass MMSimPantechClass;
+
+struct _MMSimPantech {
+ MMBaseSim parent;
+};
+
+struct _MMSimPantechClass {
+ MMBaseSimClass parent;
+};
+
+GType mm_sim_pantech_get_type (void);
+
+void mm_sim_pantech_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseSim *mm_sim_pantech_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SIM_PANTECH_H */
diff --git a/src/plugins/qcom-soc/77-mm-qcom-soc.rules b/src/plugins/qcom-soc/77-mm-qcom-soc.rules
new file mode 100644
index 00000000..9719f96f
--- /dev/null
+++ b/src/plugins/qcom-soc/77-mm-qcom-soc.rules
@@ -0,0 +1,40 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_qcom_soc_end"
+
+# Process only known wwan, net and rpmsg ports
+SUBSYSTEM=="net", DRIVERS=="bam-dmux", GOTO="mm_qcom_soc_process"
+SUBSYSTEM=="net", DRIVERS=="ipa", GOTO="mm_qcom_soc_process"
+SUBSYSTEM=="wwan", DRIVERS=="qcom-q6v5-mss", GOTO="mm_qcom_soc_process"
+SUBSYSTEM=="rpmsg", DRIVERS=="qcom-q6v5-mss", GOTO="mm_qcom_soc_process"
+GOTO="mm_qcom_soc_end"
+
+LABEL="mm_qcom_soc_process"
+
+# Flag the port as being part of the SoC
+ENV{ID_MM_QCOM_SOC}="1"
+
+#
+# Add a common physdev UID to all ports in the Qualcomm SoC, so that they
+# are all bound together to the same modem object.
+#
+# The MSM8916, MSM8974, .... Qualcomm SoCs use the combination of RPMSG/WWAN
+# based control ports plus BAM-DMUX based network ports.
+#
+ENV{ID_MM_PHYSDEV_UID}="qcom-soc"
+
+# port type hints for the rpmsgexport-ed ports
+SUBSYSTEM=="rpmsg", ATTR{name}=="DATA*", ATTR{name}=="*_CNTL", ENV{ID_MM_PORT_TYPE_QMI}="1"
+SUBSYSTEM=="rpmsg", ATTR{name}=="DATA*", ATTR{name}!="*_CNTL", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# ignore every other port without explicit hints
+SUBSYSTEM=="rpmsg", ENV{ID_MM_PORT_TYPE_QMI}!="1", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}!="1", ENV{ID_MM_PORT_IGNORE}="1"
+
+# explicitly ignore ports intended for USB tethering (DATA40, DATA40_CNTL)
+SUBSYSTEM=="rpmsg", ATTR{name}=="DATA40*", ENV{ID_MM_PORT_IGNORE}="1"
+KERNEL=="rmnet_usb*", ENV{ID_MM_PORT_IGNORE}="1"
+
+# flag all rpmsg ports under this plugin as candidate
+KERNEL=="rpmsg*", SUBSYSTEM=="rpmsg", ENV{ID_MM_CANDIDATE}="1"
+
+LABEL="mm_qcom_soc_end"
diff --git a/src/plugins/qcom-soc/mm-broadband-modem-qmi-qcom-soc.c b/src/plugins/qcom-soc/mm-broadband-modem-qmi-qcom-soc.c
new file mode 100644
index 00000000..21d62c12
--- /dev/null
+++ b/src/plugins/qcom-soc/mm-broadband-modem-qmi-qcom-soc.c
@@ -0,0 +1,176 @@
+/* -*- 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 <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log.h"
+#include "mm-iface-modem.h"
+#include "mm-broadband-modem-qmi-qcom-soc.h"
+
+G_DEFINE_TYPE (MMBroadbandModemQmiQcomSoc, mm_broadband_modem_qmi_qcom_soc, MM_TYPE_BROADBAND_MODEM_QMI)
+
+/*****************************************************************************/
+
+static const QmiSioPort sio_port_per_port_number[] = {
+ QMI_SIO_PORT_A2_MUX_RMNET0,
+ QMI_SIO_PORT_A2_MUX_RMNET1,
+ QMI_SIO_PORT_A2_MUX_RMNET2,
+ QMI_SIO_PORT_A2_MUX_RMNET3,
+ QMI_SIO_PORT_A2_MUX_RMNET4,
+ QMI_SIO_PORT_A2_MUX_RMNET5,
+ QMI_SIO_PORT_A2_MUX_RMNET6,
+ QMI_SIO_PORT_A2_MUX_RMNET7
+};
+
+static MMPortQmi *
+peek_port_qmi_for_data_bam_dmux (MMBroadbandModemQmi *self,
+ MMPort *data,
+ MMQmiDataEndpoint *out_endpoint,
+ GError **error)
+{
+ MMPortQmi *found = NULL;
+ MMKernelDevice *net_port;
+ gint net_port_number;
+
+ net_port = mm_port_peek_kernel_device (data);
+
+ /* The dev_port notified by the bam-dmux driver indicates which SIO port we should be using */
+ net_port_number = mm_kernel_device_get_attribute_as_int (net_port, "dev_port");
+ if (net_port_number < 0 || net_port_number >= (gint) G_N_ELEMENTS (sio_port_per_port_number)) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_NOT_FOUND,
+ "Couldn't find SIO port number for 'net/%s'",
+ mm_port_get_device (data));
+ return NULL;
+ }
+
+ /* Find one QMI port, we don't care which one */
+ found = mm_broadband_modem_qmi_peek_port_qmi (self);
+
+ if (!found)
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_NOT_FOUND,
+ "Couldn't find any QMI port for 'net/%s'",
+ mm_port_get_device (data));
+ else if (out_endpoint) {
+ /* WDS Bind (Mux) Data Port must be called with the correct endpoint
+ * interface number/SIO port to make multiplexing work with BAM-DMUX */
+ out_endpoint->type = QMI_DATA_ENDPOINT_TYPE_BAM_DMUX;
+ out_endpoint->interface_number = net_port_number;
+ out_endpoint->sio_port = sio_port_per_port_number[net_port_number];
+ }
+
+ return found;
+}
+
+static MMPortQmi *
+peek_port_qmi_for_data_ipa (MMBroadbandModemQmi *self,
+ MMPort *data,
+ MMQmiDataEndpoint *out_endpoint,
+ GError **error)
+{
+ MMPortQmi *found = NULL;
+
+ /* when using IPA, we have a main network interface that will be multiplexed
+ * to create link interfaces. We can assume any of the available QMI ports is
+ * able to manage that. */
+
+ found = mm_broadband_modem_qmi_peek_port_qmi (self);
+
+ if (!found)
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_NOT_FOUND,
+ "Couldn't find any QMI port for 'net/%s'",
+ mm_port_get_device (data));
+ else if (out_endpoint)
+ mm_port_qmi_get_endpoint_info (found, out_endpoint);
+
+ return found;
+}
+
+static MMPortQmi *
+peek_port_qmi_for_data (MMBroadbandModemQmi *self,
+ MMPort *data,
+ MMQmiDataEndpoint *out_endpoint,
+ GError **error)
+{
+ MMKernelDevice *net_port;
+ const gchar *net_port_driver;
+
+ g_assert (MM_IS_BROADBAND_MODEM_QMI (self));
+ g_assert (mm_port_get_subsys (data) == MM_PORT_SUBSYS_NET);
+
+ net_port = mm_port_peek_kernel_device (data);
+ net_port_driver = mm_kernel_device_get_driver (net_port);
+
+ if (g_strcmp0 (net_port_driver, "ipa") == 0)
+ return peek_port_qmi_for_data_ipa (self, data, out_endpoint, error);
+
+ if (g_strcmp0 (net_port_driver, "bam-dmux") == 0)
+ return peek_port_qmi_for_data_bam_dmux (self, data, out_endpoint, error);
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unsupported QMI kernel driver for 'net/%s': %s",
+ mm_port_get_device (data),
+ net_port_driver);
+ return NULL;
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemQmiQcomSoc *
+mm_broadband_modem_qmi_qcom_soc_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_QCOM_SOC,
+ 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,
+ /* 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_qcom_soc_init (MMBroadbandModemQmiQcomSoc *self)
+{
+}
+
+static void
+mm_broadband_modem_qmi_qcom_soc_class_init (MMBroadbandModemQmiQcomSocClass *klass)
+{
+ MMBroadbandModemQmiClass *broadband_modem_qmi_class = MM_BROADBAND_MODEM_QMI_CLASS (klass);
+
+ broadband_modem_qmi_class->peek_port_qmi_for_data = peek_port_qmi_for_data;
+}
diff --git a/src/plugins/qcom-soc/mm-broadband-modem-qmi-qcom-soc.h b/src/plugins/qcom-soc/mm-broadband-modem-qmi-qcom-soc.h
new file mode 100644
index 00000000..92c37beb
--- /dev/null
+++ b/src/plugins/qcom-soc/mm-broadband-modem-qmi-qcom-soc.h
@@ -0,0 +1,49 @@
+/* -*- 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_BROADBAND_MODEM_QMI_QCOM_SOC_H
+#define MM_BROADBAND_MODEM_QMI_QCOM_SOC_H
+
+#include "mm-broadband-modem-qmi.h"
+
+#define MM_TYPE_BROADBAND_MODEM_QMI_QCOM_SOC (mm_broadband_modem_qmi_qcom_soc_get_type ())
+#define MM_BROADBAND_MODEM_QMI_QCOM_SOC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_QMI_QCOM_SOC, MMBroadbandModemQmiQcomSoc))
+#define MM_BROADBAND_MODEM_QMI_QCOM_SOC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_QMI_QCOM_SOC, MMBroadbandModemQmiQcomSocClass))
+#define MM_IS_BROADBAND_MODEM_QMI_QCOM_SOC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_QMI_QCOM_SOC))
+#define MM_IS_BROADBAND_MODEM_QMI_QCOM_SOC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_QMI_QCOM_SOC))
+#define MM_BROADBAND_MODEM_QMI_QCOM_SOC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_QMI_QCOM_SOC, MMBroadbandModemQmiQcomSocClass))
+
+typedef struct _MMBroadbandModemQmiQcomSoc MMBroadbandModemQmiQcomSoc;
+typedef struct _MMBroadbandModemQmiQcomSocClass MMBroadbandModemQmiQcomSocClass;
+typedef struct _MMBroadbandModemQmiQcomSocPrivate MMBroadbandModemQmiQcomSocPrivate;
+
+struct _MMBroadbandModemQmiQcomSoc {
+ MMBroadbandModemQmi parent;
+ MMBroadbandModemQmiQcomSocPrivate *priv;
+};
+
+struct _MMBroadbandModemQmiQcomSocClass{
+ MMBroadbandModemQmiClass parent;
+};
+
+GType mm_broadband_modem_qmi_qcom_soc_get_type (void);
+
+MMBroadbandModemQmiQcomSoc *mm_broadband_modem_qmi_qcom_soc_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_QMI_QCOM_SOC_H */
diff --git a/src/plugins/qcom-soc/mm-plugin-qcom-soc.c b/src/plugins/qcom-soc/mm-plugin-qcom-soc.c
new file mode 100644
index 00000000..ae844dd6
--- /dev/null
+++ b/src/plugins/qcom-soc/mm-plugin-qcom-soc.c
@@ -0,0 +1,98 @@
+/* -*- 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 <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <time.h>
+
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-qcom-soc.h"
+#include "mm-broadband-modem-qmi-qcom-soc.h"
+#include "mm-log-object.h"
+
+G_DEFINE_TYPE (MMPluginQcomSoc, mm_plugin_qcom_soc, 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 (!mm_port_probe_list_has_qmi_port (probes)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unsupported device: at least a QMI port is required");
+ return NULL;
+ }
+
+ mm_obj_dbg (self, "Qualcomm SoC modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_qcom_soc_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "wwan", "rpmsg", "net", "qrtr", NULL };
+ static const gchar *udev_tags[] = {
+ "ID_MM_QCOM_SOC",
+ NULL
+ };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_QCOM_SOC,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ MM_PLUGIN_ALLOWED_UDEV_TAGS, udev_tags,
+ NULL));
+}
+
+static void
+mm_plugin_qcom_soc_init (MMPluginQcomSoc *self)
+{
+}
+
+static void
+mm_plugin_qcom_soc_class_init (MMPluginQcomSocClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/qcom-soc/mm-plugin-qcom-soc.h b/src/plugins/qcom-soc/mm-plugin-qcom-soc.h
new file mode 100644
index 00000000..54da154f
--- /dev/null
+++ b/src/plugins/qcom-soc/mm-plugin-qcom-soc.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) 2020 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_PLUGIN_QCOM_SOC_H
+#define MM_PLUGIN_QCOM_SOC_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_QCOM_SOC (mm_plugin_qcom_soc_get_type ())
+#define MM_PLUGIN_QCOM_SOC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_QCOM_SOC, MMPluginQcomSoc))
+#define MM_PLUGIN_QCOM_SOC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_QCOM_SOC, MMPluginQcomSocClass))
+#define MM_IS_PLUGIN_QCOM_SOC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_QCOM_SOC))
+#define MM_IS_PLUGIN_QCOM_SOC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_QCOM_SOC))
+#define MM_PLUGIN_QCOM_SOC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_QCOM_SOC, MMPluginQcomSocClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginQcomSoc;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginQcomSocClass;
+
+GType mm_plugin_qcom_soc_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_QCOM_SOC_H */
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 ();
+}
diff --git a/src/plugins/samsung/mm-broadband-modem-samsung.c b/src/plugins/samsung/mm-broadband-modem-samsung.c
new file mode 100644
index 00000000..1ffc178f
--- /dev/null
+++ b/src/plugins/samsung/mm-broadband-modem-samsung.c
@@ -0,0 +1,94 @@
+/* -*- 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) 2011 Samsung Electronics, Inc.
+ * Copyright (C) 2012 Google Inc.
+ * Author: Nathan Williams <njw@google.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-base-modem-at.h"
+#include "mm-broadband-modem-samsung.h"
+#include "mm-broadband-bearer-icera.h"
+#include "mm-modem-helpers.h"
+
+G_DEFINE_TYPE (MMBroadbandModemSamsung, mm_broadband_modem_samsung, MM_TYPE_BROADBAND_MODEM_ICERA)
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_samsung_parent_class)->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));
+
+ /* Configure AT ports */
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ g_object_set (ports[i],
+ MM_PORT_SERIAL_SEND_DELAY, (guint64) 0,
+ NULL);
+ }
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemSamsung *
+mm_broadband_modem_samsung_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_SAMSUNG,
+ 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 (AT) and Icera bearer (NET) supported */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+ /* We want to use DHCP always! */
+ MM_BROADBAND_MODEM_ICERA_DEFAULT_IP_METHOD, MM_BEARER_IP_METHOD_DHCP,
+ NULL);
+}
+
+static void
+mm_broadband_modem_samsung_init (MMBroadbandModemSamsung *self)
+{
+}
+
+static void
+mm_broadband_modem_samsung_class_init (MMBroadbandModemSamsungClass *klass)
+{
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/samsung/mm-broadband-modem-samsung.h b/src/plugins/samsung/mm-broadband-modem-samsung.h
new file mode 100644
index 00000000..106f973c
--- /dev/null
+++ b/src/plugins/samsung/mm-broadband-modem-samsung.h
@@ -0,0 +1,49 @@
+/* -*- 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) 2011 Samsung Electronics, Inc.
+ * Copyright (C) 2012 Google Inc.
+ * Author: Nathan Williams <njw@google.com>
+ */
+
+#ifndef MM_BROADBAND_MODEM_SAMSUNG_H
+#define MM_BROADBAND_MODEM_SAMSUNG_H
+
+#include "mm-broadband-modem-icera.h"
+
+#define MM_TYPE_BROADBAND_MODEM_SAMSUNG (mm_broadband_modem_samsung_get_type ())
+#define MM_BROADBAND_MODEM_SAMSUNG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_SAMSUNG, MMBroadbandModemSamsung))
+#define MM_BROADBAND_MODEM_SAMSUNG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_SAMSUNG, MMBroadbandModemSamsungClass))
+#define MM_IS_BROADBAND_MODEM_SAMSUNG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_SAMSUNG))
+#define MM_IS_BROADBAND_MODEM_SAMSUNG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_SAMSUNG))
+#define MM_BROADBAND_MODEM_SAMSUNG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_SAMSUNG, MMBroadbandModemSamsungClass))
+
+typedef struct _MMBroadbandModemSamsung MMBroadbandModemSamsung;
+typedef struct _MMBroadbandModemSamsungClass MMBroadbandModemSamsungClass;
+
+struct _MMBroadbandModemSamsung {
+ MMBroadbandModemIcera parent;
+};
+
+struct _MMBroadbandModemSamsungClass{
+ MMBroadbandModemIceraClass parent;
+};
+
+GType mm_broadband_modem_samsung_get_type (void);
+
+MMBroadbandModemSamsung *mm_broadband_modem_samsung_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_SAMSUNG_H */
diff --git a/src/plugins/samsung/mm-plugin-samsung.c b/src/plugins/samsung/mm-plugin-samsung.c
new file mode 100644
index 00000000..3ce64d73
--- /dev/null
+++ b/src/plugins/samsung/mm-plugin-samsung.c
@@ -0,0 +1,83 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2011 Samsung Electronics, Inc.
+ * Copyright (C) 2012 Google Inc.
+ * Author: Nathan Williams <njw@google.com>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#include "mm-plugin-samsung.h"
+#include "mm-private-boxed-types.h"
+#include "mm-broadband-modem-samsung.h"
+
+G_DEFINE_TYPE (MMPluginSamsung, mm_plugin_samsung, 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)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_samsung_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", NULL };
+ static const mm_uint16_pair products[] = { { 0x04e8, 0x6872 },
+ { 0x04e8, 0x6906 },
+ { 0, 0 } };
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_SAMSUNG,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_PRODUCT_IDS, products,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_SEND_DELAY, (guint64) 0,
+ NULL));
+}
+
+static void
+mm_plugin_samsung_init (MMPluginSamsung *self)
+{
+}
+
+static void
+mm_plugin_samsung_class_init (MMPluginSamsungClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/samsung/mm-plugin-samsung.h b/src/plugins/samsung/mm-plugin-samsung.h
new file mode 100644
index 00000000..85f32028
--- /dev/null
+++ b/src/plugins/samsung/mm-plugin-samsung.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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2011 Samsung Electronics, Inc.
+ * Copyright (C) 2012 Google Inc.
+ * Author: Nathan Williams <njw@google.com>
+ */
+
+#ifndef MM_PLUGIN_SAMSUNG_H
+#define MM_PLUGIN_SAMSUNG_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_SAMSUNG (mm_plugin_samsung_get_type ())
+#define MM_PLUGIN_SAMSUNG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_SAMSUNG, MMPluginSamsung))
+#define MM_PLUGIN_SAMSUNG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_SAMSUNG, MMPluginSamsungClass))
+#define MM_IS_PLUGIN_SAMSUNG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_SAMSUNG))
+#define MM_IS_PLUGIN_SAMSUNG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_SAMSUNG))
+#define MM_PLUGIN_SAMSUNG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_SAMSUNG, MMPluginSamsungClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginSamsung;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginSamsungClass;
+
+GType mm_plugin_samsung_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_SAMSUNG_H */
diff --git a/src/plugins/sierra/77-mm-sierra.rules b/src/plugins/sierra/77-mm-sierra.rules
new file mode 100644
index 00000000..e26e9a40
--- /dev/null
+++ b/src/plugins/sierra/77-mm-sierra.rules
@@ -0,0 +1,40 @@
+
+# do not edit this file, it will be overwritten on update
+ACTION!="add|change|move|bind", GOTO="mm_sierra_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1199", GOTO="mm_sierra_generic"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1519", GOTO="mm_sierra_comneon"
+GOTO="mm_sierra_end"
+
+LABEL="mm_sierra_generic"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Netgear AC341U: enable connection status polling explicitly
+ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9057", ENV{ID_MM_QMI_CONNECTION_STATUS_POLLING_ENABLE}="1"
+
+# EM7345: disable CPOL based features
+ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1"
+
+# MC74XX: Add port hints
+# if 03: primary port
+# if 02: raw NMEA port
+# if 00: diag/qcdm port
+ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9071", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9071", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9071", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+
+# EM7565: Add port hints
+# if 03: primary port
+# if 02: raw NMEA port
+# if 00: diag/qcdm port
+ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9091", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9091", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9091", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+
+GOTO="mm_sierra_end"
+
+LABEL="mm_sierra_comneon"
+
+# GL7600: disable CPOL based features
+ATTRS{idVendor}=="1519", ATTRS{idProduct}=="0443", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1"
+
+LABEL="mm_sierra_end"
diff --git a/src/plugins/sierra/mm-broadband-bearer-sierra.c b/src/plugins/sierra/mm-broadband-bearer-sierra.c
new file mode 100644
index 00000000..5c540389
--- /dev/null
+++ b/src/plugins/sierra/mm-broadband-bearer-sierra.c
@@ -0,0 +1,682 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-base-modem-at.h"
+#include "mm-broadband-bearer-sierra.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-sierra.h"
+
+G_DEFINE_TYPE (MMBroadbandBearerSierra, mm_broadband_bearer_sierra, MM_TYPE_BROADBAND_BEARER);
+
+struct _MMBroadbandBearerSierraPrivate {
+ gboolean is_icera;
+};
+
+enum {
+ PROP_0,
+ PROP_IS_ICERA,
+ PROP_LAST
+};
+
+/*****************************************************************************/
+/* Connection status monitoring */
+
+static MMBearerConnectionStatus
+load_connection_status_finish (MMBaseBearer *bearer,
+ 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_BEARER_CONNECTION_STATUS_UNKNOWN;
+ }
+ return (MMBearerConnectionStatus)value;
+}
+
+static void
+scact_periodic_query_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ GList *pdp_active_list = NULL;
+ GList *l;
+ MMBearerConnectionStatus status = MM_BEARER_CONNECTION_STATUS_UNKNOWN;
+ guint cid;
+
+ cid = GPOINTER_TO_UINT (g_task_get_task_data (task));
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (response)
+ pdp_active_list = mm_sierra_parse_scact_read_response (response, &error);
+
+ if (error) {
+ g_assert (!pdp_active_list);
+ g_prefix_error (&error, "Couldn't check current list of active PDP contexts: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ for (l = pdp_active_list; l; l = g_list_next (l)) {
+ MM3gppPdpContextActive *pdp_active;
+
+ /* We look for the specific CID */
+ pdp_active = (MM3gppPdpContextActive *)(l->data);
+ if (pdp_active->cid == cid) {
+ status = (pdp_active->active ? MM_BEARER_CONNECTION_STATUS_CONNECTED : MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
+ break;
+ }
+ }
+ mm_3gpp_pdp_context_active_list_free (pdp_active_list);
+
+ /* PDP context not found? This shouldn't happen, error out */
+ if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN)
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "PDP context not found in the known contexts list");
+ else
+ g_task_return_int (task, (gssize) status);
+ g_object_unref (task);
+}
+
+static void
+load_connection_status (MMBaseBearer *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ MMBaseModem *modem = NULL;
+ MMPortSerialAt *port;
+ gint profile_id;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ g_object_get (MM_BASE_BEARER (self),
+ MM_BASE_BEARER_MODEM, &modem,
+ NULL);
+
+ /* If CID not defined, error out */
+ profile_id = mm_base_bearer_get_profile_id (self);
+ if (profile_id == MM_3GPP_PROFILE_ID_UNKNOWN) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't load connection status: profile id not defined");
+ g_object_unref (task);
+ goto out;
+ }
+ g_task_set_task_data (task, GUINT_TO_POINTER ((guint)profile_id), NULL);
+
+ /* If no control port available, error out */
+ port = mm_base_modem_peek_best_at_port (modem, NULL);
+ if (!port) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Couldn't load connection status: no control port available");
+ g_object_unref (task);
+ goto out;
+ }
+
+ mm_base_modem_at_command_full (MM_BASE_MODEM (modem),
+ port,
+ "!SCACT?",
+ 3,
+ FALSE, /* allow cached */
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback) scact_periodic_query_ready,
+ task);
+
+out:
+ g_clear_object (&modem);
+}
+
+/*****************************************************************************/
+/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */
+
+typedef enum {
+ DIAL_3GPP_STEP_FIRST,
+ DIAL_3GPP_STEP_PS_ATTACH,
+ DIAL_3GPP_STEP_AUTHENTICATE,
+ DIAL_3GPP_STEP_CONNECT,
+ DIAL_3GPP_STEP_LAST
+} Dial3gppStep;
+
+typedef struct {
+ MMBaseModem *modem;
+ MMPortSerialAt *primary;
+ guint cid;
+ MMPort *data;
+ Dial3gppStep step;
+} Dial3gppContext;
+
+static void
+dial_3gpp_context_free (Dial3gppContext *ctx)
+{
+ if (ctx->data)
+ g_object_unref (ctx->data);
+ g_object_unref (ctx->primary);
+ g_object_unref (ctx->modem);
+ g_slice_free (Dial3gppContext, ctx);
+}
+
+static MMPort *
+dial_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void dial_3gpp_context_step (GTask *task);
+
+static void
+parent_dial_3gpp_ready (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ ctx->data = MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_sierra_parent_class)->dial_3gpp_finish (self, res, &error);
+ if (!ctx->data) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Go on */
+ ctx->step++;
+ dial_3gpp_context_step (task);
+}
+
+static void
+scact_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_full_finish (modem, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Go on */
+ ctx->step++;
+ dial_3gpp_context_step (task);
+}
+
+static void
+authenticate_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_full_finish (modem, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Go on */
+ ctx->step++;
+ dial_3gpp_context_step (task);
+}
+
+static void
+cgatt_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Dial3gppContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_full_finish (modem, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Go on */
+ ctx->step++;
+ dial_3gpp_context_step (task);
+}
+
+static void
+dial_3gpp_context_step (GTask *task)
+{
+ MMBroadbandBearerSierra *self;
+ Dial3gppContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ if (g_task_return_error_if_cancelled (task)) {
+ g_object_unref (task);
+ return;
+ }
+
+ switch (ctx->step) {
+ case DIAL_3GPP_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+
+ case DIAL_3GPP_STEP_PS_ATTACH:
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ "+CGATT=1",
+ 10,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)cgatt_ready,
+ task);
+ return;
+
+ case DIAL_3GPP_STEP_AUTHENTICATE:
+ if (!MM_IS_PORT_SERIAL_AT (ctx->data)) {
+ gchar *command;
+ const gchar *user;
+ const gchar *password;
+ MMBearerAllowedAuth allowed_auth;
+
+ user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ password = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ allowed_auth = mm_bearer_properties_get_allowed_auth (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+
+ if (!user || !password || allowed_auth == MM_BEARER_ALLOWED_AUTH_NONE) {
+ mm_obj_dbg (self, "not using authentication");
+ if (self->priv->is_icera)
+ command = g_strdup_printf ("%%IPDPCFG=%d,0,0,\"\",\"\"", ctx->cid);
+ else
+ command = g_strdup_printf ("$QCPDPP=%d,0", ctx->cid);
+ } else {
+ gchar *quoted_user;
+ gchar *quoted_password;
+ guint sierra_auth;
+
+ if (allowed_auth == MM_BEARER_ALLOWED_AUTH_UNKNOWN) {
+ mm_obj_dbg (self, "using default (CHAP) authentication method");
+ sierra_auth = 2;
+ } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_CHAP) {
+ mm_obj_dbg (self, "using CHAP authentication method");
+ sierra_auth = 2;
+ } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_PAP) {
+ mm_obj_dbg (self, "using PAP authentication method");
+ sierra_auth = 1;
+ } else {
+ gchar *str;
+
+ str = mm_bearer_allowed_auth_build_string_from_mask (allowed_auth);
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot use any of the specified authentication methods (%s)",
+ str);
+ g_free (str);
+ g_object_unref (task);
+ return;
+ }
+
+ quoted_user = mm_port_serial_at_quote_string (user);
+ quoted_password = mm_port_serial_at_quote_string (password);
+ if (self->priv->is_icera) {
+ command = g_strdup_printf ("%%IPDPCFG=%d,0,%u,%s,%s",
+ ctx->cid,
+ sierra_auth,
+ quoted_user,
+ quoted_password);
+ } else {
+ /* Yes, password comes first... */
+ command = g_strdup_printf ("$QCPDPP=%d,%u,%s,%s",
+ ctx->cid,
+ sierra_auth,
+ quoted_password,
+ quoted_user);
+ }
+ g_free (quoted_user);
+ g_free (quoted_password);
+ }
+
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)authenticate_ready,
+ task);
+ g_free (command);
+ return;
+ }
+
+ ctx->step++;
+ /* fall through */
+
+ case DIAL_3GPP_STEP_CONNECT:
+ /* We need a net or AT data port */
+ ctx->data = mm_base_modem_get_best_data_port (ctx->modem, MM_PORT_TYPE_NET);
+ if (ctx->data) {
+ gchar *command;
+
+ command = g_strdup_printf ("!SCACT=1,%d", ctx->cid);
+ mm_base_modem_at_command_full (ctx->modem,
+ ctx->primary,
+ command,
+ MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)scact_ready,
+ task);
+ g_free (command);
+ return;
+ }
+
+ /* Chain up parent's dialling if we don't have a net port */
+ MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_sierra_parent_class)->dial_3gpp (
+ MM_BROADBAND_BEARER (self),
+ ctx->modem,
+ ctx->primary,
+ ctx->cid,
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)parent_dial_3gpp_ready,
+ task);
+ return;
+
+ case DIAL_3GPP_STEP_LAST:
+ g_task_return_pointer (task,
+ g_object_ref (ctx->data),
+ g_object_unref);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+dial_3gpp (MMBroadbandBearer *self,
+ MMBaseModem *modem,
+ MMPortSerialAt *primary,
+ guint cid,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Dial3gppContext *ctx;
+ GTask *task;
+
+ g_assert (primary != NULL);
+
+ ctx = g_slice_new0 (Dial3gppContext);
+ ctx->modem = g_object_ref (modem);
+ ctx->primary = g_object_ref (primary);
+ ctx->cid = cid;
+ ctx->step = DIAL_3GPP_STEP_FIRST;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)dial_3gpp_context_free);
+
+ dial_3gpp_context_step (task);
+}
+
+/*****************************************************************************/
+/* 3GPP disconnect */
+
+static gboolean
+disconnect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_disconnect_3gpp_ready (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_sierra_parent_class)->disconnect_3gpp_finish (self, res, &error)) {
+ mm_obj_dbg (self, "parent disconnection failed (not fatal): %s", error->message);
+ g_error_free (error);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+disconnect_scact_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerSierra *self;
+ GError *error = NULL;
+
+ self = g_task_get_source_object (task);
+
+ /* Ignore errors for now */
+ mm_base_modem_at_command_full_finish (modem, res, &error);
+ if (error) {
+ mm_obj_dbg (self, "disconnection failed (not fatal): %s", error->message);
+ g_error_free (error);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+disconnect_3gpp (MMBroadbandBearer *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ g_assert (primary != NULL);
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (!MM_IS_PORT_SERIAL_AT (data)) {
+ gchar *command;
+
+ /* Use specific CID */
+ command = g_strdup_printf ("!SCACT=0,%u", cid);
+ mm_base_modem_at_command_full (MM_BASE_MODEM (modem),
+ primary,
+ command,
+ MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)disconnect_scact_ready,
+ task);
+ g_free (command);
+ return;
+ }
+
+ /* Chain up parent's disconnection if we don't have a net port */
+ MM_BROADBAND_BEARER_CLASS (mm_broadband_bearer_sierra_parent_class)->disconnect_3gpp (
+ self,
+ modem,
+ primary,
+ secondary,
+ data,
+ cid,
+ (GAsyncReadyCallback)parent_disconnect_3gpp_ready,
+ task);
+}
+
+/*****************************************************************************/
+
+#define MM_BROADBAND_BEARER_SIERRA_IS_ICERA "is-icera"
+
+MMBaseBearer *
+mm_broadband_bearer_sierra_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *bearer;
+ GObject *source;
+
+ source = g_async_result_get_source_object (res);
+ bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!bearer)
+ return NULL;
+
+ /* Only export valid bearers */
+ mm_base_bearer_export (MM_BASE_BEARER (bearer));
+
+ return MM_BASE_BEARER (bearer);
+}
+
+void
+mm_broadband_bearer_sierra_new (MMBroadbandModem *modem,
+ MMBearerProperties *config,
+ gboolean is_icera,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (
+ MM_TYPE_BROADBAND_BEARER_SIERRA,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_BEARER_MODEM, modem,
+ MM_BASE_BEARER_CONFIG, config,
+ MM_BROADBAND_BEARER_SIERRA_IS_ICERA, is_icera,
+ NULL);
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MMBroadbandBearerSierra *self = MM_BROADBAND_BEARER_SIERRA (object);
+
+ switch (prop_id) {
+ case PROP_IS_ICERA:
+ self->priv->is_icera = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MMBroadbandBearerSierra *self = MM_BROADBAND_BEARER_SIERRA (object);
+
+ switch (prop_id) {
+ case PROP_IS_ICERA:
+ g_value_set_boolean (value, self->priv->is_icera);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+mm_broadband_bearer_sierra_init (MMBroadbandBearerSierra *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
+ MM_TYPE_BROADBAND_BEARER_SIERRA,
+ MMBroadbandBearerSierraPrivate);
+}
+
+static void
+mm_broadband_bearer_sierra_class_init (MMBroadbandBearerSierraClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);
+ MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandBearerSierraPrivate));
+
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+
+ base_bearer_class->load_connection_status = load_connection_status;
+ base_bearer_class->load_connection_status_finish = load_connection_status_finish;
+#if defined WITH_SUSPEND_RESUME
+ base_bearer_class->reload_connection_status = load_connection_status;
+ base_bearer_class->reload_connection_status_finish = load_connection_status_finish;
+#endif
+
+ broadband_bearer_class->dial_3gpp = dial_3gpp;
+ broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish;
+ broadband_bearer_class->disconnect_3gpp = disconnect_3gpp;
+ broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish;
+
+ g_object_class_install_property (object_class, PROP_IS_ICERA,
+ g_param_spec_boolean (MM_BROADBAND_BEARER_SIERRA_IS_ICERA,
+ "IsIcera",
+ "Whether the modem uses Icera commands or not.",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
diff --git a/src/plugins/sierra/mm-broadband-bearer-sierra.h b/src/plugins/sierra/mm-broadband-bearer-sierra.h
new file mode 100644
index 00000000..9f75685e
--- /dev/null
+++ b/src/plugins/sierra/mm-broadband-bearer-sierra.h
@@ -0,0 +1,62 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#ifndef MM_BROADBAND_BEARER_SIERRA_H
+#define MM_BROADBAND_BEARER_SIERRA_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-broadband-bearer.h"
+#include "mm-broadband-modem-sierra.h"
+
+#define MM_TYPE_BROADBAND_BEARER_SIERRA (mm_broadband_bearer_sierra_get_type ())
+#define MM_BROADBAND_BEARER_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_SIERRA, MMBroadbandBearerSierra))
+#define MM_BROADBAND_BEARER_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_SIERRA, MMBroadbandBearerSierraClass))
+#define MM_IS_BROADBAND_BEARER_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_SIERRA))
+#define MM_IS_BROADBAND_BEARER_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_SIERRA))
+#define MM_BROADBAND_BEARER_SIERRA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_SIERRA, MMBroadbandBearerSierraClass))
+
+typedef struct _MMBroadbandBearerSierra MMBroadbandBearerSierra;
+typedef struct _MMBroadbandBearerSierraClass MMBroadbandBearerSierraClass;
+typedef struct _MMBroadbandBearerSierraPrivate MMBroadbandBearerSierraPrivate;
+
+struct _MMBroadbandBearerSierra {
+ MMBroadbandBearer parent;
+ MMBroadbandBearerSierraPrivate *priv;
+};
+
+struct _MMBroadbandBearerSierraClass {
+ MMBroadbandBearerClass parent;
+};
+
+GType mm_broadband_bearer_sierra_get_type (void);
+
+/* Default 3GPP bearer creation implementation */
+void mm_broadband_bearer_sierra_new (MMBroadbandModem *modem,
+ MMBearerProperties *config,
+ gboolean is_icera,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseBearer *mm_broadband_bearer_sierra_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_BROADBAND_BEARER_SIERRA_H */
diff --git a/src/plugins/sierra/mm-broadband-modem-sierra-icera.c b/src/plugins/sierra/mm-broadband-modem-sierra-icera.c
new file mode 100644
index 00000000..c7dfcf81
--- /dev/null
+++ b/src/plugins/sierra/mm-broadband-modem-sierra-icera.c
@@ -0,0 +1,145 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-broadband-modem-sierra-icera.h"
+#include "mm-iface-modem.h"
+#include "mm-modem-helpers.h"
+#include "mm-log-object.h"
+#include "mm-common-sierra.h"
+#include "mm-broadband-bearer-sierra.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemSierraIcera, mm_broadband_modem_sierra_icera, MM_TYPE_BROADBAND_MODEM_ICERA, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init))
+
+/*****************************************************************************/
+/* Create Bearer (Modem interface) */
+
+static MMBaseBearer *
+modem_create_bearer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+broadband_bearer_sierra_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer = NULL;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_sierra_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+
+ g_object_unref (task);
+}
+
+static void
+modem_create_bearer (MMIfaceModem *self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ mm_obj_dbg (self, "creating sierra bearer...");
+ mm_broadband_bearer_sierra_new (MM_BROADBAND_MODEM (self),
+ properties,
+ TRUE, /* is_icera */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_sierra_new_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_sierra_icera_parent_class)->setup_ports (self);
+
+ mm_common_sierra_setup_ports (self);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemSierraIcera *
+mm_broadband_modem_sierra_icera_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_SIERRA_ICERA,
+ 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,
+ /* Sierra bearer supports both NET and TTY */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_sierra_icera_init (MMBroadbandModemSierraIcera *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ mm_common_sierra_peek_parent_interfaces (iface);
+
+ iface->load_power_state = mm_common_sierra_load_power_state;
+ iface->load_power_state_finish = mm_common_sierra_load_power_state_finish;
+ iface->modem_power_up = mm_common_sierra_modem_power_up;
+ iface->modem_power_up_finish = mm_common_sierra_modem_power_up_finish;
+ iface->create_sim = mm_common_sierra_create_sim;
+ iface->create_sim_finish = mm_common_sierra_create_sim_finish;
+ iface->create_bearer = modem_create_bearer;
+ iface->create_bearer_finish = modem_create_bearer_finish;
+}
+
+static void
+mm_broadband_modem_sierra_icera_class_init (MMBroadbandModemSierraIceraClass *klass)
+{
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/sierra/mm-broadband-modem-sierra-icera.h b/src/plugins/sierra/mm-broadband-modem-sierra-icera.h
new file mode 100644
index 00000000..97b07d19
--- /dev/null
+++ b/src/plugins/sierra/mm-broadband-modem-sierra-icera.h
@@ -0,0 +1,49 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#ifndef MM_BROADBAND_MODEM_SIERRA_ICERA_H
+#define MM_BROADBAND_MODEM_SIERRA_ICERA_H
+
+#include "mm-broadband-modem-icera.h"
+
+#define MM_TYPE_BROADBAND_MODEM_SIERRA_ICERA (mm_broadband_modem_sierra_icera_get_type ())
+#define MM_BROADBAND_MODEM_SIERRA_ICERA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_SIERRA_ICERA, MMBroadbandModemSierraIcera))
+#define MM_BROADBAND_MODEM_SIERRA_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_SIERRA_ICERA, MMBroadbandModemSierraIceraClass))
+#define MM_IS_BROADBAND_MODEM_SIERRA_ICERA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_SIERRA_ICERA))
+#define MM_IS_BROADBAND_MODEM_SIERRA_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_SIERRA_ICERA))
+#define MM_BROADBAND_MODEM_SIERRA_ICERA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_SIERRA_ICERA, MMBroadbandModemSierraIceraClass))
+
+typedef struct _MMBroadbandModemSierraIcera MMBroadbandModemSierraIcera;
+typedef struct _MMBroadbandModemSierraIceraClass MMBroadbandModemSierraIceraClass;
+
+struct _MMBroadbandModemSierraIcera {
+ MMBroadbandModemIcera parent;
+};
+
+struct _MMBroadbandModemSierraIceraClass {
+ MMBroadbandModemIceraClass parent;
+};
+
+GType mm_broadband_modem_sierra_icera_get_type (void);
+
+MMBroadbandModemSierraIcera *mm_broadband_modem_sierra_icera_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_SIERRA_ICERA_H */
diff --git a/src/plugins/sierra/mm-broadband-modem-sierra.c b/src/plugins/sierra/mm-broadband-modem-sierra.c
new file mode 100644
index 00000000..83611f6d
--- /dev/null
+++ b/src/plugins/sierra/mm-broadband-modem-sierra.c
@@ -0,0 +1,1937 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-broadband-modem-sierra.h"
+#include "mm-base-modem-at.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-common-helpers.h"
+#include "mm-errors-types.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-cdma.h"
+#include "mm-iface-modem-time.h"
+#include "mm-common-sierra.h"
+#include "mm-broadband-bearer-sierra.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_cdma_init (MMIfaceModemCdma *iface);
+static void iface_modem_time_init (MMIfaceModemTime *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModemCdma *iface_modem_cdma_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemSierra, mm_broadband_modem_sierra, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init));
+
+typedef enum {
+ TIME_METHOD_UNKNOWN = 0,
+ TIME_METHOD_TIME = 1,
+ TIME_METHOD_SYSTIME = 2,
+} TimeMethod;
+
+struct _MMBroadbandModemSierraPrivate {
+ TimeMethod time_method;
+};
+
+/*****************************************************************************/
+/* Load unlock retries (Modem interface) */
+
+static MMUnlockRetries *
+load_unlock_retries_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ MMUnlockRetries *unlock_retries;
+ const gchar *response;
+ gint matched;
+ guint a, b, c ,d;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return NULL;
+
+ matched = sscanf (response, "+CPINC: %d,%d,%d,%d",
+ &a, &b, &c, &d);
+ if (matched != 4) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Could not parse PIN retries results: '%s'",
+ response);
+ return NULL;
+ }
+
+ if (a > 998) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Invalid PIN attempts left: '%u'",
+ a);
+ return NULL;
+ }
+
+ unlock_retries = mm_unlock_retries_new ();
+ mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PIN, a);
+ mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PIN2, b);
+ mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PUK, c);
+ mm_unlock_retries_set (unlock_retries, MM_MODEM_LOCK_SIM_PUK2, d);
+ return unlock_retries;
+}
+
+static void
+load_unlock_retries (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CPINC?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Generic AT!STATUS parsing */
+
+typedef enum {
+ SYS_MODE_UNKNOWN,
+ SYS_MODE_NO_SERVICE,
+ SYS_MODE_CDMA_1X,
+ SYS_MODE_EVDO_REV0,
+ SYS_MODE_EVDO_REVA
+} SysMode;
+
+#define MODEM_REG_TAG "Modem has registered"
+#define GENERIC_ROAM_TAG "Roaming:"
+#define ROAM_1X_TAG "1xRoam:"
+#define ROAM_EVDO_TAG "HDRRoam:"
+#define SYS_MODE_TAG "Sys Mode:"
+#define SYS_MODE_NO_SERVICE_TAG "NO SRV"
+#define SYS_MODE_EVDO_TAG "HDR"
+#define SYS_MODE_1X_TAG "1x"
+#define SYS_MODE_CDMA_TAG "CDMA"
+#define EVDO_REV_TAG "HDR Revision:"
+#define SID_TAG "SID:"
+
+static gboolean
+get_roam_value (const gchar *reply,
+ const gchar *tag,
+ gboolean is_eri,
+ gboolean *out_roaming)
+{
+ gchar *p;
+ gboolean success;
+ guint32 ind = 0;
+
+ p = strstr (reply, tag);
+ if (!p)
+ return FALSE;
+
+ p += strlen (tag);
+ while (*p && isspace (*p))
+ p++;
+
+ /* Use generic ERI parsing if it's an ERI */
+ if (is_eri) {
+ success = mm_cdma_parse_eri (p, out_roaming, &ind, NULL);
+ if (success) {
+ /* Sierra redefines ERI 0, 1, and 2 */
+ if (ind == 0)
+ *out_roaming = FALSE; /* home */
+ else if (ind == 1 || ind == 2)
+ *out_roaming = TRUE; /* roaming */
+ }
+ return success;
+ }
+
+ /* If it's not an ERI, roaming is just true/false */
+ if (*p == '1') {
+ *out_roaming = TRUE;
+ return TRUE;
+ } else if (*p == '0') {
+ *out_roaming = FALSE;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+sys_mode_has_service (SysMode mode)
+{
+ return ( mode == SYS_MODE_CDMA_1X
+ || mode == SYS_MODE_EVDO_REV0
+ || mode == SYS_MODE_EVDO_REVA);
+}
+
+static gboolean
+sys_mode_is_evdo (SysMode mode)
+{
+ return (mode == SYS_MODE_EVDO_REV0 || mode == SYS_MODE_EVDO_REVA);
+}
+
+static gboolean
+parse_status (const char *response,
+ MMModemCdmaRegistrationState *out_cdma1x_state,
+ MMModemCdmaRegistrationState *out_evdo_state,
+ MMModemAccessTechnology *out_act)
+{
+ gchar **lines;
+ gchar **iter;
+ gboolean registered = FALSE;
+ gboolean have_sid = FALSE;
+ SysMode evdo_mode = SYS_MODE_UNKNOWN;
+ SysMode sys_mode = SYS_MODE_UNKNOWN;
+ gboolean evdo_roam = FALSE, cdma1x_roam = FALSE;
+
+ lines = g_strsplit_set (response, "\n\r", 0);
+ if (!lines)
+ return FALSE;
+
+ /* Sierra CDMA parts have two general formats depending on whether they
+ * support EVDO or not. EVDO parts report both 1x and EVDO roaming status
+ * while of course 1x parts only report 1x status. Some modems also do not
+ * report the Roaming information (MP 555 GPS).
+ *
+ * AT!STATUS responses:
+ *
+ * Unregistered MC5725:
+ * -----------------------
+ * Current band: PCS CDMA
+ * Current channel: 350
+ * SID: 0 NID: 0 1xRoam: 0 HDRRoam: 0
+ * Temp: 33 State: 100 Sys Mode: NO SRV
+ * Pilot NOT acquired
+ * Modem has NOT registered
+ *
+ * Registered MC5725:
+ * -----------------------
+ * Current band: Cellular Sleep
+ * Current channel: 775
+ * SID: 30 NID: 2 1xRoam: 0 HDRRoam: 0
+ * Temp: 29 State: 200 Sys Mode: HDR
+ * Pilot acquired
+ * Modem has registered
+ * HDR Revision: A
+ *
+ * Unregistered AC580:
+ * -----------------------
+ * Current band: PCS CDMA
+ * Current channel: 350
+ * SID: 0 NID: 0 Roaming: 0
+ * Temp: 39 State: 100 Scan Mode: 0
+ * Pilot NOT acquired
+ * Modem has NOT registered
+ *
+ * Registered AC580:
+ * -----------------------
+ * Current band: Cellular Sleep
+ * Current channel: 548
+ * SID: 26 NID: 1 Roaming: 1
+ * Temp: 39 State: 200 Scan Mode: 0
+ * Pilot Acquired
+ * Modem has registered
+ */
+
+ /* We have to handle the two formats slightly differently; for newer formats
+ * with "Sys Mode", we consider the modem registered if the Sys Mode is not
+ * "NO SRV". The explicit registration status is just icing on the cake.
+ * For older formats (no "Sys Mode") we treat the modem as registered if
+ * the SID is non-zero.
+ */
+
+ for (iter = lines; iter && *iter; iter++) {
+ gboolean bool_val = FALSE;
+ char *p;
+
+ if (!strncmp (*iter, MODEM_REG_TAG, strlen (MODEM_REG_TAG))) {
+ registered = TRUE;
+ continue;
+ }
+
+ /* Roaming */
+ get_roam_value (*iter, ROAM_1X_TAG, TRUE, &cdma1x_roam);
+ get_roam_value (*iter, ROAM_EVDO_TAG, TRUE, &evdo_roam);
+ if (get_roam_value (*iter, GENERIC_ROAM_TAG, FALSE, &bool_val))
+ cdma1x_roam = evdo_roam = bool_val;
+
+ /* Current system mode */
+ p = strstr (*iter, SYS_MODE_TAG);
+ if (p) {
+ p += strlen (SYS_MODE_TAG);
+ while (*p && isspace (*p))
+ p++;
+ if (!strncmp (p, SYS_MODE_NO_SERVICE_TAG, strlen (SYS_MODE_NO_SERVICE_TAG)))
+ sys_mode = SYS_MODE_NO_SERVICE;
+ else if (!strncmp (p, SYS_MODE_EVDO_TAG, strlen (SYS_MODE_EVDO_TAG)))
+ sys_mode = SYS_MODE_EVDO_REV0;
+ else if ( !strncmp (p, SYS_MODE_1X_TAG, strlen (SYS_MODE_1X_TAG))
+ || !strncmp (p, SYS_MODE_CDMA_TAG, strlen (SYS_MODE_CDMA_TAG)))
+ sys_mode = SYS_MODE_CDMA_1X;
+ }
+
+ /* Current EVDO revision if system mode is EVDO */
+ p = strstr (*iter, EVDO_REV_TAG);
+ if (p) {
+ p += strlen (EVDO_REV_TAG);
+ while (*p && isspace (*p))
+ p++;
+ if (*p == 'A')
+ evdo_mode = SYS_MODE_EVDO_REVA;
+ else if (*p == '0')
+ evdo_mode = SYS_MODE_EVDO_REV0;
+ }
+
+ /* SID */
+ p = strstr (*iter, SID_TAG);
+ if (p) {
+ p += strlen (SID_TAG);
+ while (*p && isspace (*p))
+ p++;
+ if (isdigit (*p) && (*p != '0'))
+ have_sid = TRUE;
+ }
+ }
+
+ /* Update current system mode */
+ if (sys_mode_is_evdo (sys_mode)) {
+ /* Prefer the explicit EVDO mode from EVDO_REV_TAG */
+ if (evdo_mode != SYS_MODE_UNKNOWN)
+ sys_mode = evdo_mode;
+ }
+
+ /* If the modem didn't report explicit registration with "Modem has
+ * registered" then get registration status by looking at either system
+ * mode or (for older devices that don't report that) just the SID.
+ */
+ if (!registered) {
+ if (sys_mode != SYS_MODE_UNKNOWN)
+ registered = sys_mode_has_service (sys_mode);
+ else
+ registered = have_sid;
+ }
+
+ if (registered) {
+ *out_cdma1x_state = (cdma1x_roam ?
+ MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING :
+ MM_MODEM_CDMA_REGISTRATION_STATE_HOME);
+
+ if (sys_mode_is_evdo (sys_mode)) {
+ *out_evdo_state = (evdo_roam ?
+ MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING :
+ MM_MODEM_CDMA_REGISTRATION_STATE_HOME);
+ } else {
+ *out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+ }
+ } else {
+ /* Not registered */
+ *out_cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+ *out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+ }
+
+ if (out_act) {
+ *out_act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ if (registered) {
+ if (sys_mode == SYS_MODE_CDMA_1X)
+ *out_act = MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
+ else if (sys_mode == SYS_MODE_EVDO_REV0)
+ *out_act = MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
+ else if (sys_mode == SYS_MODE_EVDO_REVA)
+ *out_act = MM_MODEM_ACCESS_TECHNOLOGY_EVDOA;
+ }
+ }
+
+ g_strfreev (lines);
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Load access technologies (Modem interface) */
+
+typedef struct {
+ MMModemAccessTechnology act;
+ guint mask;
+} AccessTechInfo;
+
+static AccessTechInfo *
+access_tech_info_new (MMModemAccessTechnology act,
+ guint mask)
+{
+ AccessTechInfo *info;
+
+ info = g_new (AccessTechInfo, 1);
+ info->act = act;
+ info->mask = mask;
+
+ return info;
+}
+
+static gboolean
+load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ GError **error)
+{
+ AccessTechInfo *info;
+
+ info = g_task_propagate_pointer (G_TASK (res), error);
+ if (!info)
+ return FALSE;
+
+ *access_technologies = info->act;
+ *mask = info->mask;
+ g_free (info);
+ return TRUE;
+}
+
+static void
+access_tech_3gpp_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response)
+ g_task_return_error (task, error);
+ else {
+ MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ const gchar *p;
+
+ p = mm_strip_tag (response, "*CNTI:");
+ p = strchr (p, ',');
+ if (p)
+ act = mm_string_to_access_tech (p + 1);
+
+ if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN)
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse access technologies result: '%s'",
+ response);
+ else
+ g_task_return_pointer (
+ task,
+ access_tech_info_new (act, MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK),
+ g_free);
+ }
+
+ g_object_unref (task);
+}
+
+static void
+access_tech_cdma_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response)
+ g_task_return_error (task, error);
+ else {
+ MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ MMModemCdmaRegistrationState cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+ MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+
+ if (!parse_status (response, &cdma1x_state, &evdo_state, &act))
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse access technologies result: '%s'",
+ response);
+ else
+ g_task_return_pointer (
+ task,
+ access_tech_info_new (act, MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK),
+ g_free);
+ }
+
+ g_object_unref (task);
+}
+
+static void
+load_access_technologies (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (mm_iface_modem_is_3gpp (self)) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "*CNTI=0",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)access_tech_3gpp_ready,
+ task);
+ return;
+ }
+
+ if (mm_iface_modem_is_cdma (self)) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "!STATUS",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)access_tech_cdma_ready,
+ task);
+ return;
+ }
+
+ g_assert_not_reached ();
+}
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_load_supported_modes_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ GArray *all;
+ GArray *combinations;
+ GArray *filtered;
+ MMModemModeCombination mode;
+
+ all = iface_modem_parent->load_supported_modes_finish (self, res, &error);
+ if (!all) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* CDMA-only modems don't support changing modes, default to parent's */
+ if (!mm_iface_modem_is_3gpp (self)) {
+ g_task_return_pointer (task, all, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Build list of combinations for 3GPP devices */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5);
+
+ /* 2G only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 3G only */
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ /* Non-LTE devices allow 2G/3G preferred modes */
+ if (!mm_iface_modem_is_3gpp_lte (self)) {
+ /* 2G and 3G, 2G preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G, 3G preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+ } else {
+ /* 4G only */
+ mode.allowed = MM_MODEM_MODE_4G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G, 3G and 4G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ /* Filter out those unsupported modes */
+ filtered = mm_filter_supported_modes (all, combinations, self);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Run parent's loading */
+ iface_modem_parent->load_supported_modes (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_supported_modes_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Load initial allowed/preferred modes (Modem interface) */
+
+typedef struct {
+ MMModemMode allowed;
+ MMModemMode preferred;
+} LoadCurrentModesResult;
+
+static gboolean
+load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ g_autofree LoadCurrentModesResult *result = NULL;
+
+ result = g_task_propagate_pointer (G_TASK (res), error);
+ if (!result)
+ return FALSE;
+
+ *allowed = result->allowed;
+ *preferred = result->preferred;
+ return TRUE;
+}
+
+static void
+selrat_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autofree LoadCurrentModesResult *result = NULL;
+ const gchar *response;
+ GError *error = NULL;
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+
+ response = mm_base_modem_at_command_full_finish (self, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ result = g_new0 (LoadCurrentModesResult, 1);
+
+ /* Example response: !SELRAT: 03, UMTS 3G Preferred */
+ r = g_regex_new ("!SELRAT:\\s*(\\d+).*$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &error)) {
+ guint mode;
+
+ if (mm_get_uint_from_match_info (match_info, 1, &mode) && mode <= 7) {
+ switch (mode) {
+ case 0:
+ result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ result->preferred = MM_MODEM_MODE_NONE;
+ if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self)))
+ result->allowed |= MM_MODEM_MODE_4G;
+ result->preferred = MM_MODEM_MODE_NONE;
+ break;
+ case 1:
+ result->allowed = MM_MODEM_MODE_3G;
+ result->preferred = MM_MODEM_MODE_NONE;
+ break;
+ case 2:
+ result->allowed = MM_MODEM_MODE_2G;
+ result->preferred = MM_MODEM_MODE_NONE;
+ break;
+ case 3:
+ /* in Sierra LTE devices, mode 3 is automatic, including LTE, no preference */
+ if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self))) {
+ result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ result->preferred = MM_MODEM_MODE_NONE;
+ } else {
+ result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ result->preferred = MM_MODEM_MODE_3G;
+ }
+ break;
+ case 4:
+ /* in Sierra LTE devices, mode 4 is automatic, including LTE, no preference */
+ if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self))) {
+ result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ result->preferred = MM_MODEM_MODE_NONE;
+ } else {
+ result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ result->preferred = MM_MODEM_MODE_2G;
+ }
+ break;
+ case 5:
+ result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ result->preferred = MM_MODEM_MODE_NONE;
+ break;
+ case 6:
+ result->allowed = MM_MODEM_MODE_4G;
+ result->preferred = MM_MODEM_MODE_NONE;
+ break;
+ case 7:
+ result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ result->preferred = MM_MODEM_MODE_NONE;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ } else
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse the allowed mode response: '%s'",
+ response);
+ } else if (!error)
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Could not parse allowed mode response: Response didn't match: '%s'",
+ response);
+
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, g_steal_pointer (&result), g_free);
+ g_object_unref (task);
+}
+
+static void
+load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ MMPortSerialAt *primary;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (!mm_iface_modem_is_3gpp (self)) {
+ /* Cannot do this in CDMA modems */
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot load allowed modes in CDMA modems");
+ g_object_unref (task);
+ return;
+ }
+
+ /* Sierra secondary ports don't have full AT command interpreters */
+ primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ if (!primary || mm_port_get_connected (MM_PORT (primary))) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_CONNECTED,
+ "Cannot load allowed modes while connected");
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ primary,
+ "!SELRAT?",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)selrat_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Set current modes (Modem interface) */
+
+static gboolean
+set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+selrat_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_full_finish (self, res, &error))
+ /* Let the error be critical. */
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ MMPortSerialAt *primary;
+ gint idx = -1;
+ gchar *command;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (!mm_iface_modem_is_3gpp (self)) {
+ /* Cannot do this in CDMA modems */
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot set allowed modes in CDMA modems");
+ g_object_unref (task);
+ return;
+ }
+
+ /* Sierra secondary ports don't have full AT command interpreters */
+ primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ if (!primary || mm_port_get_connected (MM_PORT (primary))) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_CONNECTED,
+ "Cannot set allowed modes while connected");
+ g_object_unref (task);
+ return;
+ }
+
+ if (allowed == MM_MODEM_MODE_3G)
+ idx = 1;
+ else if (allowed == MM_MODEM_MODE_2G)
+ idx = 2;
+ else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) {
+ /* in Sierra LTE devices, modes 3 and 4 are automatic, including LTE, no preference */
+ if (mm_iface_modem_is_3gpp_lte (self)) {
+ if (preferred == MM_MODEM_MODE_NONE)
+ idx = 5; /* GSM and UMTS Only */
+ }
+ else if (preferred == MM_MODEM_MODE_3G)
+ idx = 3;
+ else if (preferred == MM_MODEM_MODE_2G)
+ idx = 4;
+ else if (preferred == MM_MODEM_MODE_NONE)
+ idx = 0;
+ } else if (allowed == MM_MODEM_MODE_4G)
+ idx = 6;
+ else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G) &&
+ preferred == MM_MODEM_MODE_NONE)
+ idx = 7;
+ else if (allowed == MM_MODEM_MODE_ANY && preferred == MM_MODEM_MODE_NONE)
+ idx = 0;
+
+ if (idx < 0) {
+ gchar *allowed_str;
+ gchar *preferred_str;
+
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Requested mode (allowed: '%s', preferred: '%s') not "
+ "supported by the modem.",
+ allowed_str,
+ preferred_str);
+ g_object_unref (task);
+
+ g_free (allowed_str);
+ g_free (preferred_str);
+ return;
+ }
+
+ command = g_strdup_printf ("!SELRAT=%d", idx);
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ primary,
+ command,
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)selrat_set_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* After SIM unlock (Modem interface) */
+
+static gboolean
+modem_after_sim_unlock_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static gboolean
+after_sim_unlock_wait_cb (GTask *task)
+{
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+modem_after_sim_unlock (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ guint timeout = 8;
+ const gchar **drivers;
+ guint i;
+
+ /* A short wait is necessary for SIM to become ready, otherwise some older
+ * cards (AC881) crash if asked to connect immediately after sending the
+ * PIN. Assume sierra_net driven devices are better and don't need as long
+ * a delay.
+ */
+ drivers = mm_base_modem_get_drivers (MM_BASE_MODEM (self));
+ for (i = 0; drivers[i]; i++) {
+ if (g_str_equal (drivers[i], "sierra_net"))
+ timeout = 3;
+ }
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ g_timeout_add_seconds (timeout, (GSourceFunc)after_sim_unlock_wait_cb, task);
+}
+
+/*****************************************************************************/
+/* Load own numbers (Modem interface) */
+
+static GStrv
+modem_load_own_numbers_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_load_own_numbers_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ GStrv numbers;
+
+ numbers = iface_modem_parent->load_own_numbers_finish (self, res, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, numbers, (GDestroyNotify)g_strfreev);
+
+ g_object_unref (task);
+}
+
+#define MDN_TAG "MDN: "
+
+static void
+own_numbers_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response, *p;
+ const gchar *numbers[2] = { NULL, NULL };
+ gchar mdn[15];
+ guint i;
+
+ response = mm_base_modem_at_command_finish (self, res, NULL);
+ if (!response)
+ goto fallback;
+
+ p = strstr (response, MDN_TAG);
+ if (!p)
+ goto fallback;
+
+ response = p + strlen (MDN_TAG);
+ while (isspace (*response))
+ response++;
+
+ for (i = 0; i < (sizeof (mdn) - 1) && isdigit (response[i]); i++)
+ mdn[i] = response[i];
+ mdn[i] = '\0';
+ numbers[0] = &mdn[0];
+
+ /* MDNs are 10 digits in length */
+ if (i != 10) {
+ mm_obj_warn (self, "failed to parse MDN: expected 10 digits, got %d", i);
+ goto fallback;
+ }
+
+ g_task_return_pointer (task,
+ g_strdupv ((gchar **) numbers),
+ (GDestroyNotify)g_strfreev);
+ g_object_unref (task);
+ return;
+
+fallback:
+ /* Fall back to parent method */
+ iface_modem_parent->load_own_numbers (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_own_numbers_ready,
+ task);
+}
+
+static void
+modem_load_own_numbers (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* 3GPP modems can just run parent's own number loading */
+ if (mm_iface_modem_is_3gpp (self)) {
+ iface_modem_parent->load_own_numbers (
+ self,
+ (GAsyncReadyCallback)parent_load_own_numbers_ready,
+ task);
+ return;
+ }
+
+ /* CDMA modems try AT~NAMVAL?0 first, then fall back to parent for
+ * loading own number from NV memory with QCDM.
+ */
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "~NAMVAL?0",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)own_numbers_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Create Bearer (Modem interface) */
+
+static MMBaseBearer *
+modem_create_bearer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+broadband_bearer_sierra_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseBearer *bearer = NULL;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_sierra_new_finish (res, &error);
+ if (!bearer)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bearer, g_object_unref);
+
+ g_object_unref (task);
+}
+
+static void
+modem_create_bearer (MMIfaceModem *self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ mm_obj_dbg (self, "creating Sierra bearer...");
+ mm_broadband_bearer_sierra_new (MM_BROADBAND_MODEM (self),
+ properties,
+ FALSE, /* is_icera */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_sierra_new_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Reset (Modem interface) */
+
+static gboolean
+modem_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "!RESET",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Modem power down (Modem interface) */
+
+static gboolean
+modem_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+modem_power_down_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ /* Ignore errors for now; we're not sure if all Sierra CDMA devices support
+ * at!pcstate or 3GPP devices support +CFUN=4.
+ */
+ mm_base_modem_at_command_finish (self, res, NULL);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_power_down (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* For CDMA modems, run !pcstate */
+ if (mm_iface_modem_is_cdma_only (self)) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "!pcstate=0",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)modem_power_down_ready,
+ task);
+ return;
+ }
+
+ /* For GSM modems, run AT+CFUN=4 (power save) */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=4",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)modem_power_down_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup registration checks (CDMA interface) */
+
+typedef struct {
+ gboolean skip_qcdm_call_manager_step;
+ gboolean skip_qcdm_hdr_step;
+ gboolean skip_at_cdma_service_status_step;
+ gboolean skip_at_cdma1x_serving_system_step;
+ gboolean skip_detailed_registration_state;
+} SetupRegistrationChecksResults;
+
+static gboolean
+setup_registration_checks_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ gboolean *skip_qcdm_call_manager_step,
+ gboolean *skip_qcdm_hdr_step,
+ gboolean *skip_at_cdma_service_status_step,
+ gboolean *skip_at_cdma1x_serving_system_step,
+ gboolean *skip_detailed_registration_state,
+ GError **error)
+{
+ SetupRegistrationChecksResults *results;
+
+ results = g_task_propagate_pointer (G_TASK (res), error);
+ if (!results)
+ return FALSE;
+
+ *skip_qcdm_call_manager_step = results->skip_qcdm_call_manager_step;
+ *skip_qcdm_hdr_step = results->skip_qcdm_hdr_step;
+ *skip_at_cdma_service_status_step = results->skip_at_cdma_service_status_step;
+ *skip_at_cdma1x_serving_system_step = results->skip_at_cdma1x_serving_system_step;
+ *skip_detailed_registration_state = results->skip_detailed_registration_state;
+ g_free (results);
+ return TRUE;
+}
+
+static void
+parent_setup_registration_checks_ready (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ SetupRegistrationChecksResults *results;
+
+ results = g_new0 (SetupRegistrationChecksResults, 1);
+ if (!iface_modem_cdma_parent->setup_registration_checks_finish (self,
+ res,
+ &results->skip_qcdm_call_manager_step,
+ &results->skip_qcdm_hdr_step,
+ &results->skip_at_cdma_service_status_step,
+ &results->skip_at_cdma1x_serving_system_step,
+ &results->skip_detailed_registration_state,
+ &error)) {
+ g_task_return_error (task, error);
+ g_free (results);
+ } else {
+ /* Skip +CSS */
+ results->skip_at_cdma1x_serving_system_step = TRUE;
+ /* Skip +CAD */
+ results->skip_at_cdma_service_status_step = TRUE;
+
+ /* Force to always use the detailed registration checks, as we have
+ * !STATUS for that */
+ results->skip_detailed_registration_state = FALSE;
+
+ g_task_return_pointer (task, results, g_free);
+ }
+ g_object_unref (task);
+}
+
+static void
+setup_registration_checks (MMIfaceModemCdma *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Run parent's checks first */
+ iface_modem_cdma_parent->setup_registration_checks (self,
+ (GAsyncReadyCallback)parent_setup_registration_checks_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Detailed registration state (CDMA interface) */
+
+typedef struct {
+ MMModemCdmaRegistrationState detailed_cdma1x_state;
+ MMModemCdmaRegistrationState detailed_evdo_state;
+} DetailedRegistrationStateResults;
+
+static gboolean
+get_detailed_registration_state_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ MMModemCdmaRegistrationState *detailed_cdma1x_state,
+ MMModemCdmaRegistrationState *detailed_evdo_state,
+ GError **error)
+{
+ DetailedRegistrationStateResults *results;
+
+ results = g_task_propagate_pointer (G_TASK (res), error);
+ if (!results)
+ return FALSE;
+
+ *detailed_cdma1x_state = results->detailed_cdma1x_state;
+ *detailed_evdo_state = results->detailed_evdo_state;
+ g_free (results);
+ return TRUE;
+}
+
+static void
+status_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ DetailedRegistrationStateResults *results;
+ const gchar *response;
+
+ results = g_task_get_task_data (task);
+
+ /* If error, leave superclass' reg state alone if AT!STATUS isn't supported. */
+ response = mm_base_modem_at_command_finish (self, res, NULL);
+ if (response)
+ parse_status (response,
+ &(results->detailed_cdma1x_state),
+ &(results->detailed_evdo_state),
+ NULL);
+
+ g_task_return_pointer (task, g_memdup (results, sizeof (*results)), g_free);
+ g_object_unref (task);
+}
+
+static void
+get_detailed_registration_state (MMIfaceModemCdma *self,
+ MMModemCdmaRegistrationState cdma1x_state,
+ MMModemCdmaRegistrationState evdo_state,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ DetailedRegistrationStateResults *results;
+ GTask *task;
+
+ results = g_new0 (DetailedRegistrationStateResults, 1);
+ results->detailed_cdma1x_state = cdma1x_state;
+ results->detailed_evdo_state = evdo_state;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, results, g_free);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "!STATUS",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)status_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Automatic activation (CDMA interface) */
+
+typedef enum {
+ CDMA_AUTOMATIC_ACTIVATION_STEP_FIRST,
+ CDMA_AUTOMATIC_ACTIVATION_STEP_UNLOCK,
+ CDMA_AUTOMATIC_ACTIVATION_STEP_CDV,
+ CDMA_AUTOMATIC_ACTIVATION_STEP_CHECK,
+ CDMA_AUTOMATIC_ACTIVATION_STEP_LAST
+} CdmaAutomaticActivationStep;
+
+typedef struct {
+ CdmaAutomaticActivationStep step;
+ gchar *carrier_code;
+} CdmaAutomaticActivationContext;
+
+static void
+cdma_automatic_activation_context_free (CdmaAutomaticActivationContext *ctx)
+{
+ g_free (ctx->carrier_code);
+ g_slice_free (CdmaAutomaticActivationContext, ctx);
+}
+
+static gboolean
+modem_cdma_activate_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void cdma_automatic_activation_step (GTask *task);
+
+static void
+automatic_activation_command_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+ CdmaAutomaticActivationContext *ctx;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Keep on */
+ ctx = g_task_get_task_data (task);
+ ctx->step++;
+ cdma_automatic_activation_step (task);
+}
+
+static void
+cdma_automatic_activation_step (GTask *task)
+{
+ MMBroadbandModemSierra *self;
+ CdmaAutomaticActivationContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case CDMA_AUTOMATIC_ACTIVATION_STEP_FIRST:
+ ctx->step++;
+ /* fall-through */
+
+ case CDMA_AUTOMATIC_ACTIVATION_STEP_UNLOCK:
+ mm_obj_msg (self, "activation step [1/4]: unlocking device");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "~NAMLCK=000000",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)automatic_activation_command_ready,
+ task);
+ return;
+
+ case CDMA_AUTOMATIC_ACTIVATION_STEP_CDV: {
+ gchar *command;
+
+ mm_obj_msg (self, "activation step [2/4]: requesting OTASP");
+ command = g_strdup_printf ("+CDV%s", ctx->carrier_code);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ command,
+ 120,
+ FALSE,
+ (GAsyncReadyCallback)automatic_activation_command_ready,
+ task);
+ g_free (command);
+ return;
+ }
+
+ case CDMA_AUTOMATIC_ACTIVATION_STEP_CHECK:
+ mm_obj_msg (self, "activation step [3/4]: checking activation info");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "~NAMVAL?0",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)automatic_activation_command_ready,
+ task);
+ return;
+
+ case CDMA_AUTOMATIC_ACTIVATION_STEP_LAST:
+ mm_obj_msg (self, "activation step [4/4]: activation process finished");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+modem_cdma_activate (MMIfaceModemCdma *self,
+ const gchar *carrier_code,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ CdmaAutomaticActivationContext *ctx;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Setup context */
+ ctx = g_slice_new0 (CdmaAutomaticActivationContext);
+ ctx->carrier_code = g_strdup (carrier_code);
+ ctx->step = CDMA_AUTOMATIC_ACTIVATION_STEP_FIRST;
+ g_task_set_task_data (task, ctx, (GDestroyNotify)cdma_automatic_activation_context_free);
+
+ /* And start it */
+ cdma_automatic_activation_step (task);
+}
+
+/*****************************************************************************/
+/* Manual activation (CDMA interface) */
+
+typedef enum {
+ CDMA_MANUAL_ACTIVATION_STEP_FIRST,
+ CDMA_MANUAL_ACTIVATION_STEP_SPC,
+ CDMA_MANUAL_ACTIVATION_STEP_MDN_MIN,
+ CDMA_MANUAL_ACTIVATION_STEP_OTASP,
+ CDMA_MANUAL_ACTIVATION_STEP_CHECK,
+ CDMA_MANUAL_ACTIVATION_STEP_LAST
+} CdmaManualActivationStep;
+
+typedef struct {
+ CdmaManualActivationStep step;
+ MMCdmaManualActivationProperties *properties;
+} CdmaManualActivationContext;
+
+static gboolean
+modem_cdma_activate_manual_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void cdma_manual_activation_step (GTask *task);
+
+static void
+manual_activation_command_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+ CdmaManualActivationContext *ctx;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Keep on */
+ ctx = g_task_get_task_data (task);
+ ctx->step++;
+ cdma_manual_activation_step (task);
+}
+
+static void
+cdma_manual_activation_step (GTask *task)
+{
+ MMBroadbandModemSierra *self;
+ CdmaManualActivationContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case CDMA_MANUAL_ACTIVATION_STEP_FIRST:
+ ctx->step++;
+ /* fall-through */
+
+ case CDMA_MANUAL_ACTIVATION_STEP_SPC: {
+ gchar *command;
+
+ mm_obj_msg (self, "activation step [1/5]: unlocking device");
+ command = g_strdup_printf ("~NAMLCK=%s",
+ mm_cdma_manual_activation_properties_get_spc (ctx->properties));
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ command,
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)manual_activation_command_ready,
+ task);
+ g_free (command);
+ return;
+ }
+
+ case CDMA_MANUAL_ACTIVATION_STEP_MDN_MIN: {
+ gchar *command;
+
+ mm_obj_msg (self, "activation step [2/5]: setting MDN/MIN/SID");
+ command = g_strdup_printf ("~NAMVAL=0,%s,%s,%" G_GUINT16_FORMAT ",65535",
+ mm_cdma_manual_activation_properties_get_mdn (ctx->properties),
+ mm_cdma_manual_activation_properties_get_min (ctx->properties),
+ mm_cdma_manual_activation_properties_get_sid (ctx->properties));
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ command,
+ 120,
+ FALSE,
+ (GAsyncReadyCallback)manual_activation_command_ready,
+ task);
+ g_free (command);
+ return;
+ }
+
+ case CDMA_MANUAL_ACTIVATION_STEP_OTASP:
+ mm_obj_msg (self, "activation step [3/5]: requesting OTASP");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "!IOTASTART",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)manual_activation_command_ready,
+ task);
+ return;
+
+ case CDMA_MANUAL_ACTIVATION_STEP_CHECK:
+ mm_obj_msg (self, "activation step [4/5]: checking activation info");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "~NAMVAL?0",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)manual_activation_command_ready,
+ task);
+ return;
+
+ case CDMA_MANUAL_ACTIVATION_STEP_LAST:
+ mm_obj_msg (self, "activation step [5/5]: activation process finished");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+modem_cdma_activate_manual (MMIfaceModemCdma *self,
+ MMCdmaManualActivationProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ CdmaManualActivationContext *ctx;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Setup context */
+ ctx = g_slice_new0 (CdmaManualActivationContext);
+ ctx->properties = g_object_ref (properties);
+ ctx->step = CDMA_MANUAL_ACTIVATION_STEP_FIRST;
+ g_task_set_task_data (task, ctx, (GDestroyNotify)cdma_automatic_activation_context_free);
+
+ /* And start it */
+ cdma_manual_activation_step (task);
+}
+
+/*****************************************************************************/
+/* Load network time (Time interface) */
+
+static gchar *
+parse_time (const gchar *response,
+ const gchar *regex,
+ const gchar *tag,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *match_error = NULL;
+ guint year;
+ guint month;
+ guint day;
+ guint hour;
+ guint minute;
+ guint second;
+ gchar *result = NULL;
+
+ r = g_regex_new (regex, 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse %s results: ", tag);
+ } else {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't match %s reply", tag);
+ }
+ } else {
+ if (mm_get_uint_from_match_info (match_info, 1, &year) &&
+ mm_get_uint_from_match_info (match_info, 2, &month) &&
+ mm_get_uint_from_match_info (match_info, 3, &day) &&
+ mm_get_uint_from_match_info (match_info, 4, &hour) &&
+ mm_get_uint_from_match_info (match_info, 5, &minute) &&
+ mm_get_uint_from_match_info (match_info, 6, &second)) {
+ result = mm_new_iso8601_time (year, month, day, hour, minute, second, FALSE, 0, error);
+ } else {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse %s reply", tag);
+ }
+ }
+
+ return result;
+}
+
+
+static gchar *
+parse_3gpp_time (const gchar *response, GError **error)
+{
+ /* Returns both local time and UTC time, but we have no good way to
+ * determine the timezone from all of that, so just report local time.
+ */
+ return parse_time (response,
+ "\\s*!TIME:\\s+"
+ "(\\d+)/(\\d+)/(\\d+)\\s+"
+ "(\\d+):(\\d+):(\\d+)\\s*\\(local\\)\\s+"
+ "(\\d+)/(\\d+)/(\\d+)\\s+"
+ "(\\d+):(\\d+):(\\d+)\\s*\\(UTC\\)\\s*",
+ "!TIME",
+ error);
+}
+
+static gchar *
+parse_cdma_time (const gchar *response, GError **error)
+{
+ /* YYYYMMDDWHHMMSS */
+ return parse_time (response,
+ "\\s*(\\d{4})(\\d{2})(\\d{2})\\d(\\d{2})(\\d{2})(\\d{2})\\s*",
+ "!SYSTIME",
+ error);
+}
+
+static gchar *
+modem_time_load_network_time_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *response = NULL;
+ char *iso8601 = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (response) {
+ if (strstr (response, "!TIME:"))
+ iso8601 = parse_3gpp_time (response, error);
+ else
+ iso8601 = parse_cdma_time (response, error);
+ }
+ return iso8601;
+}
+
+static void
+modem_time_load_network_time (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ const char *command;
+
+ switch (MM_BROADBAND_MODEM_SIERRA (self)->priv->time_method) {
+ case TIME_METHOD_TIME:
+ command = "!TIME?";
+ break;
+ case TIME_METHOD_SYSTIME:
+ command = "!SYSTIME?";
+ break;
+ case TIME_METHOD_UNKNOWN:
+ default:
+ g_assert_not_reached ();
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Check support (Time interface) */
+
+enum {
+ TIME_SUPPORTED = 1,
+ SYSTIME_SUPPORTED = 2,
+};
+
+static gboolean
+modem_time_check_support_finish (MMIfaceModemTime *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+modem_time_check_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ GVariant *result;
+ gboolean supported = FALSE;
+
+ result = mm_base_modem_at_sequence_finish (self, res, NULL, &error);
+ if (!error && result) {
+ MMBroadbandModemSierra *sierra = MM_BROADBAND_MODEM_SIERRA (self);
+
+ sierra->priv->time_method = g_variant_get_uint32 (result);
+ if (sierra->priv->time_method != TIME_METHOD_UNKNOWN)
+ supported = TRUE;
+ }
+ g_clear_error (&error);
+
+ g_task_return_boolean (task, supported);
+ g_object_unref (task);
+}
+
+static MMBaseModemAtResponseProcessorResult
+parse_time_reply (MMBaseModem *self,
+ gpointer none,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ *result_error = NULL;
+
+ /* If error, try next command */
+ if (!error) {
+ if (strstr (command, "!TIME"))
+ *result = g_variant_new_uint32 (TIME_METHOD_TIME);
+ else if (strstr (command, "!SYSTIME"))
+ *result = g_variant_new_uint32 (TIME_METHOD_SYSTIME);
+ }
+
+ /* Stop sequence if we get a result, but not on errors */
+ return (*result ?
+ MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS :
+ MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE);
+}
+
+static const MMBaseModemAtCommand time_check_sequence[] = {
+ { "!TIME?", 3, FALSE, parse_time_reply }, /* 3GPP */
+ { "!SYSTIME?", 3, FALSE, parse_time_reply }, /* CDMA */
+ { NULL }
+};
+
+static void
+modem_time_check_support (MMIfaceModemTime *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_sequence (
+ MM_BASE_MODEM (self),
+ time_check_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ (GAsyncReadyCallback)modem_time_check_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_sierra_parent_class)->setup_ports (self);
+
+ mm_common_sierra_setup_ports (self);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemSierra *
+mm_broadband_modem_sierra_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_SIERRA,
+ 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,
+ /* Sierra bearer supports both NET and TTY */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_sierra_init (MMBroadbandModemSierra *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
+ MM_TYPE_BROADBAND_MODEM_SIERRA,
+ MMBroadbandModemSierraPrivate);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ mm_common_sierra_peek_parent_interfaces (iface);
+
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = load_current_modes;
+ iface->load_current_modes_finish = load_current_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = set_current_modes_finish;
+ iface->load_access_technologies = load_access_technologies;
+ iface->load_access_technologies_finish = load_access_technologies_finish;
+ iface->load_own_numbers = modem_load_own_numbers;
+ iface->load_own_numbers_finish = modem_load_own_numbers_finish;
+ iface->reset = modem_reset;
+ iface->reset_finish = modem_reset_finish;
+ iface->load_power_state = mm_common_sierra_load_power_state;
+ iface->load_power_state_finish = mm_common_sierra_load_power_state_finish;
+ iface->modem_power_up = mm_common_sierra_modem_power_up;
+ iface->modem_power_up_finish = mm_common_sierra_modem_power_up_finish;
+ iface->modem_power_down = modem_power_down;
+ iface->modem_power_down_finish = modem_power_down_finish;
+ iface->create_sim = mm_common_sierra_create_sim;
+ iface->create_sim_finish = mm_common_sierra_create_sim_finish;
+ iface->load_unlock_retries = load_unlock_retries;
+ iface->load_unlock_retries_finish = load_unlock_retries_finish;
+ iface->modem_after_sim_unlock = modem_after_sim_unlock;
+ iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish;
+ iface->create_bearer = modem_create_bearer;
+ iface->create_bearer_finish = modem_create_bearer_finish;
+}
+
+static void
+iface_modem_cdma_init (MMIfaceModemCdma *iface)
+{
+ iface_modem_cdma_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_registration_checks = setup_registration_checks;
+ iface->setup_registration_checks_finish = setup_registration_checks_finish;
+ iface->get_detailed_registration_state = get_detailed_registration_state;
+ iface->get_detailed_registration_state_finish = get_detailed_registration_state_finish;
+ iface->activate = modem_cdma_activate;
+ iface->activate_finish = modem_cdma_activate_finish;
+ iface->activate_manual = modem_cdma_activate_manual;
+ iface->activate_manual_finish = modem_cdma_activate_manual_finish;
+}
+
+static void
+iface_modem_time_init (MMIfaceModemTime *iface)
+{
+ iface->check_support = modem_time_check_support;
+ iface->check_support_finish = modem_time_check_support_finish;
+ iface->load_network_time = modem_time_load_network_time;
+ iface->load_network_time_finish = modem_time_load_network_time_finish;
+}
+
+static void
+mm_broadband_modem_sierra_class_init (MMBroadbandModemSierraClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemSierraPrivate));
+
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/sierra/mm-broadband-modem-sierra.h b/src/plugins/sierra/mm-broadband-modem-sierra.h
new file mode 100644
index 00000000..9804b184
--- /dev/null
+++ b/src/plugins/sierra/mm-broadband-modem-sierra.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#ifndef MM_BROADBAND_MODEM_SIERRA_H
+#define MM_BROADBAND_MODEM_SIERRA_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_SIERRA (mm_broadband_modem_sierra_get_type ())
+#define MM_BROADBAND_MODEM_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_SIERRA, MMBroadbandModemSierra))
+#define MM_BROADBAND_MODEM_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_SIERRA, MMBroadbandModemSierraClass))
+#define MM_IS_BROADBAND_MODEM_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_SIERRA))
+#define MM_IS_BROADBAND_MODEM_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_SIERRA))
+#define MM_BROADBAND_MODEM_SIERRA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_SIERRA, MMBroadbandModemSierraClass))
+
+typedef struct _MMBroadbandModemSierra MMBroadbandModemSierra;
+typedef struct _MMBroadbandModemSierraClass MMBroadbandModemSierraClass;
+typedef struct _MMBroadbandModemSierraPrivate MMBroadbandModemSierraPrivate;
+
+struct _MMBroadbandModemSierra {
+ MMBroadbandModem parent;
+ MMBroadbandModemSierraPrivate *priv;
+};
+
+struct _MMBroadbandModemSierraClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_sierra_get_type (void);
+
+MMBroadbandModemSierra *mm_broadband_modem_sierra_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_SIERRA_H */
diff --git a/src/plugins/sierra/mm-common-sierra.c b/src/plugins/sierra/mm-common-sierra.c
new file mode 100644
index 00000000..72cbc34f
--- /dev/null
+++ b/src/plugins/sierra/mm-common-sierra.c
@@ -0,0 +1,516 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "mm-common-sierra.h"
+#include "mm-base-modem-at.h"
+#include "mm-log.h"
+#include "mm-modem-helpers.h"
+#include "mm-sim-sierra.h"
+
+static MMIfaceModem *iface_modem_parent;
+
+/*****************************************************************************/
+/* Custom init and port type hints */
+
+#define TAG_SIERRA_APP_PORT "sierra-app-port"
+#define TAG_SIERRA_APP1_PPP_OK "sierra-app1-ppp-ok"
+
+gboolean
+mm_common_sierra_grab_port (MMPlugin *self,
+ MMBaseModem *modem,
+ MMPortProbe *probe,
+ GError **error)
+{
+ MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE;
+ MMPortType ptype;
+
+ ptype = mm_port_probe_get_port_type (probe);
+
+ /* Is it a GSM secondary port? */
+ if (g_object_get_data (G_OBJECT (probe), TAG_SIERRA_APP_PORT)) {
+ if (g_object_get_data (G_OBJECT (probe), TAG_SIERRA_APP1_PPP_OK))
+ pflags = MM_PORT_SERIAL_AT_FLAG_PPP;
+ else
+ pflags = MM_PORT_SERIAL_AT_FLAG_SECONDARY;
+ } else if (ptype == MM_PORT_TYPE_AT)
+ pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY;
+
+ return mm_base_modem_grab_port (modem,
+ mm_port_probe_peek_port (probe),
+ ptype,
+ pflags,
+ error);
+}
+
+gboolean
+mm_common_sierra_port_probe_list_is_icera (GList *probes)
+{
+ GList *l;
+
+ for (l = probes; l; l = g_list_next (l)) {
+ /* Only assume the Icera probing check is valid IF the port is not
+ * secondary. This will skip the stupid ports which reply OK to every
+ * AT command, even the one we use to check for Icera support */
+ if (mm_port_probe_is_icera (MM_PORT_PROBE (l->data)) &&
+ !g_object_get_data (G_OBJECT (l->data), TAG_SIERRA_APP_PORT))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+typedef struct {
+ MMPortSerialAt *port;
+ guint retries;
+} SierraCustomInitContext;
+
+static void
+sierra_custom_init_context_free (SierraCustomInitContext *ctx)
+{
+ g_object_unref (ctx->port);
+ g_slice_free (SierraCustomInitContext, ctx);
+}
+
+gboolean
+mm_common_sierra_custom_init_finish (MMPortProbe *probe,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void sierra_custom_init_step (GTask *task);
+
+static void
+gcap_ready (MMPortSerialAt *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMPortProbe *probe;
+ SierraCustomInitContext *ctx;
+ const gchar *response;
+ GError *error = NULL;
+
+ probe = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ response = mm_port_serial_at_command_finish (port, res, &error);
+ if (error) {
+ /* If consumed all tries and the last error was a timeout, assume the
+ * port is not AT */
+ if (ctx->retries == 0 &&
+ g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) {
+ mm_port_probe_set_result_at (probe, FALSE);
+ }
+ /* If reported a hard parse error, this port is definitely not an AT
+ * port, skip trying. */
+ else if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_PARSE_FAILED)) {
+ mm_port_probe_set_result_at (probe, FALSE);
+ ctx->retries = 0;
+ }
+ /* Some Icera-based devices (eg, USB305) have an AT-style port that
+ * replies to everything with ERROR, so tag as unsupported; sometimes
+ * the real AT ports do this too, so let a retry tag the port as
+ * supported if it responds correctly later. */
+ else if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN)) {
+ mm_port_probe_set_result_at (probe, FALSE);
+ }
+
+ /* Just retry... */
+ sierra_custom_init_step (task);
+ goto out;
+ }
+
+ /* A valid reply to ATI tells us this is an AT port already */
+ mm_port_probe_set_result_at (probe, TRUE);
+
+ /* Sierra APPx ports have limited AT command parsers that just reply with
+ * "OK" to most commands. These can sometimes be used for PPP while the
+ * main port is used for status and control, but older modems tend to crash
+ * or fail PPP. So we allowlist modems that are known to allow PPP on the
+ * secondary APP ports.
+ */
+ if (strstr (response, "APP1")) {
+ g_object_set_data (G_OBJECT (probe), TAG_SIERRA_APP_PORT, GUINT_TO_POINTER (TRUE));
+
+ /* PPP-on-APP1-port allowlist */
+ if (strstr (response, "C885") ||
+ strstr (response, "USB 306") ||
+ strstr (response, "MC8790"))
+ g_object_set_data (G_OBJECT (probe), TAG_SIERRA_APP1_PPP_OK, GUINT_TO_POINTER (TRUE));
+
+ /* For debugging: let users figure out if their device supports PPP
+ * on the APP1 port or not.
+ */
+ if (getenv ("MM_SIERRA_APP1_PPP_OK")) {
+ mm_obj_dbg (probe, "APP1 PPP OK '%s'", response);
+ g_object_set_data (G_OBJECT (probe), TAG_SIERRA_APP1_PPP_OK, GUINT_TO_POINTER (TRUE));
+ }
+ } else if (strstr (response, "APP2") ||
+ strstr (response, "APP3") ||
+ strstr (response, "APP4")) {
+ /* Additional APP ports don't support most AT commands, so they cannot
+ * be used as the primary port.
+ */
+ g_object_set_data (G_OBJECT (probe), TAG_SIERRA_APP_PORT, GUINT_TO_POINTER (TRUE));
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+
+out:
+ if (error)
+ g_error_free (error);
+}
+
+static void
+sierra_custom_init_step (GTask *task)
+{
+ MMPortProbe *probe;
+ SierraCustomInitContext *ctx;
+ GCancellable *cancellable;
+
+ probe = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+ cancellable = g_task_get_cancellable (task);
+
+ /* If cancelled, end */
+ if (g_cancellable_is_cancelled (cancellable)) {
+ mm_obj_dbg (probe, "no need to keep on running custom init");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ if (ctx->retries == 0) {
+ mm_obj_dbg (probe, "couldn't get port type hints");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->retries--;
+ mm_port_serial_at_command (
+ ctx->port,
+ "ATI",
+ 3,
+ FALSE, /* raw */
+ FALSE, /* allow_cached */
+ cancellable,
+ (GAsyncReadyCallback)gcap_ready,
+ task);
+}
+
+void
+mm_common_sierra_custom_init (MMPortProbe *probe,
+ MMPortSerialAt *port,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SierraCustomInitContext *ctx;
+ GTask *task;
+
+ ctx = g_slice_new (SierraCustomInitContext);
+ ctx->port = g_object_ref (port);
+ ctx->retries = 3;
+
+ task = g_task_new (probe, cancellable, callback, user_data);
+ g_task_set_check_cancellable (task, FALSE);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)sierra_custom_init_context_free);
+
+ sierra_custom_init_step (task);
+}
+
+/*****************************************************************************/
+/* Modem power up (Modem interface) */
+
+gboolean
+mm_common_sierra_modem_power_up_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static gboolean
+sierra_power_up_wait_cb (GTask *task)
+{
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+cfun_enable_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ guint i;
+ const gchar **drivers;
+ gboolean is_new_sierra = FALSE;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Many Sierra devices return OK immediately in response to CFUN=1 but
+ * need some time to finish powering up, otherwise subsequent commands
+ * may return failure or even crash the modem. Give more time for older
+ * devices like the AC860 and C885, which aren't driven by the 'sierra_net'
+ * driver. Assume any DirectIP (ie, sierra_net) device is new enough
+ * to allow a lower timeout.
+ */
+ drivers = mm_base_modem_get_drivers (MM_BASE_MODEM (self));
+ for (i = 0; drivers[i]; i++) {
+ if (g_str_equal (drivers[i], "sierra_net")) {
+ is_new_sierra = TRUE;
+ break;
+ }
+ }
+
+ /* The modem object will be valid in the callback as 'task' keeps a
+ * reference to it. */
+ g_timeout_add_seconds (is_new_sierra ? 5 : 10, (GSourceFunc)sierra_power_up_wait_cb, task);
+}
+
+static void
+pcstate_enable_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ /* Ignore errors for now; we're not sure if all Sierra CDMA devices support
+ * at!pcstate.
+ */
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_common_sierra_modem_power_up (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* For CDMA modems, run !pcstate */
+ if (mm_iface_modem_is_cdma_only (self)) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "!pcstate=1",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)pcstate_enable_ready,
+ task);
+ return;
+ }
+
+ mm_obj_warn (self, "not in full functionality status, power-up command is needed");
+ mm_obj_warn (self, "device may be rebooted");
+
+ /* Try to go to full functionality mode without rebooting the system.
+ * Works well if we previously switched off the power with CFUN=4
+ */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=1,0", /* ",0" requests no reset */
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)cfun_enable_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Power state loading (Modem interface) */
+
+MMModemPowerState
+mm_common_sierra_load_power_state_finish (MMIfaceModem *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_POWER_STATE_UNKNOWN;
+ }
+ return (MMModemPowerState)value;
+}
+
+static void
+parent_load_power_state_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ MMModemPowerState state;
+
+ state = iface_modem_parent->load_power_state_finish (self, res, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_int (task, state);
+
+ g_object_unref (task);
+}
+
+static void
+pcstate_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *result;
+ guint state;
+ GError *error = NULL;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!result) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Parse power state reply */
+ result = mm_strip_tag (result, "!PCSTATE:");
+ if (!mm_get_uint_from_str (result, &state)) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse !PCSTATE response '%s'",
+ result);
+ } else {
+ switch (state) {
+ case 0:
+ g_task_return_int (task, MM_MODEM_POWER_STATE_LOW);
+ break;
+ case 1:
+ g_task_return_int (task, MM_MODEM_POWER_STATE_ON);
+ break;
+ default:
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unhandled power state: '%u'",
+ state);
+ break;
+ }
+ }
+
+ g_object_unref (task);
+}
+
+void
+mm_common_sierra_load_power_state (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Check power state with AT!PCSTATE? */
+ if (mm_iface_modem_is_cdma_only (self)) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "!pcstate?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)pcstate_query_ready,
+ task);
+ return;
+ }
+
+ /* Otherwise run parent's */
+ iface_modem_parent->load_power_state (self,
+ (GAsyncReadyCallback)parent_load_power_state_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Create SIM (Modem interface) */
+
+MMBaseSim *
+mm_common_sierra_create_sim_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return mm_sim_sierra_new_finish (res, error);
+}
+
+void
+mm_common_sierra_create_sim (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* New Sierra SIM */
+ mm_sim_sierra_new (MM_BASE_MODEM (self),
+ NULL, /* cancellable */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Setup ports */
+
+void
+mm_common_sierra_setup_ports (MMBroadbandModem *self)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+ g_autoptr(GRegex) pacsp_regex = NULL;
+
+ pacsp_regex = g_regex_new ("\\r\\n\\+PACSP.*\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ if (i == 1) {
+ /* Built-in echo removal conflicts with the APP1 port's limited AT
+ * parser, which doesn't always prefix responses with <CR><LF>.
+ */
+ g_object_set (ports[i],
+ MM_PORT_SERIAL_AT_REMOVE_ECHO, FALSE,
+ NULL);
+ }
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ pacsp_regex,
+ NULL, NULL, NULL);
+ }
+}
+
+/*****************************************************************************/
+
+void
+mm_common_sierra_peek_parent_interfaces (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+}
diff --git a/src/plugins/sierra/mm-common-sierra.h b/src/plugins/sierra/mm-common-sierra.h
new file mode 100644
index 00000000..22471c0f
--- /dev/null
+++ b/src/plugins/sierra/mm-common-sierra.h
@@ -0,0 +1,67 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#ifndef MM_COMMON_SIERRA_H
+#define MM_COMMON_SIERRA_H
+
+#include "mm-plugin.h"
+#include "mm-broadband-modem.h"
+#include "mm-iface-modem.h"
+#include "mm-base-sim.h"
+
+gboolean mm_common_sierra_grab_port (MMPlugin *self,
+ MMBaseModem *modem,
+ MMPortProbe *probe,
+ GError **error);
+
+gboolean mm_common_sierra_port_probe_list_is_icera (GList *probes);
+
+void mm_common_sierra_custom_init (MMPortProbe *probe,
+ MMPortSerialAt *port,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_common_sierra_custom_init_finish (MMPortProbe *probe,
+ GAsyncResult *result,
+ GError **error);
+
+void mm_common_sierra_load_power_state (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMModemPowerState mm_common_sierra_load_power_state_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_common_sierra_modem_power_up (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_common_sierra_modem_power_up_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_common_sierra_create_sim (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseSim *mm_common_sierra_create_sim_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_common_sierra_setup_ports (MMBroadbandModem *self);
+
+void mm_common_sierra_peek_parent_interfaces (MMIfaceModem *iface);
+
+#endif /* MM_COMMON_SIERRA_H */
diff --git a/src/plugins/sierra/mm-modem-helpers-sierra.c b/src/plugins/sierra/mm-modem-helpers-sierra.c
new file mode 100644
index 00000000..821be199
--- /dev/null
+++ b/src/plugins/sierra/mm-modem-helpers-sierra.c
@@ -0,0 +1,76 @@
+/* -*- 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>
+ */
+
+#include <glib.h>
+#include <string.h>
+
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-sierra.h"
+
+GList *
+mm_sierra_parse_scact_read_response (const gchar *reply,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ GList *list = NULL;
+
+ if (!reply || !reply[0])
+ /* Nothing configured, all done */
+ return NULL;
+
+ r = g_regex_new ("!SCACT:\\s*(\\d+),(\\d+)",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, &inner_error);
+ g_assert (r);
+
+ g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, &inner_error);
+ while (!inner_error && g_match_info_matches (match_info)) {
+ MM3gppPdpContextActive *pdp_active;
+ guint cid = 0;
+ guint aux = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &cid)) {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse CID from reply: '%s'",
+ reply);
+ break;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 2, &aux) || (aux != 0 && aux != 1)) {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse context status from reply: '%s'",
+ reply);
+ break;
+ }
+
+ pdp_active = g_slice_new0 (MM3gppPdpContextActive);
+ pdp_active->cid = cid;
+ pdp_active->active = (gboolean) aux;
+ list = g_list_prepend (list, pdp_active);
+
+ g_match_info_next (match_info, &inner_error);
+ }
+
+ if (inner_error) {
+ mm_3gpp_pdp_context_active_list_free (list);
+ g_propagate_error (error, inner_error);
+ g_prefix_error (error, "Couldn't properly parse list of active/inactive PDP contexts. ");
+ return NULL;
+ }
+
+ return g_list_sort (list, (GCompareFunc) mm_3gpp_pdp_context_active_cmp);
+}
diff --git a/src/plugins/sierra/mm-modem-helpers-sierra.h b/src/plugins/sierra/mm-modem-helpers-sierra.h
new file mode 100644
index 00000000..f5c6cd2c
--- /dev/null
+++ b/src/plugins/sierra/mm-modem-helpers-sierra.h
@@ -0,0 +1,26 @@
+/* -*- 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_MODEM_HELPERS_SIERRA_H
+#define MM_MODEM_HELPERS_SIERRA_H
+
+#include <glib.h>
+#include <ModemManager.h>
+
+/* MM3gppPdpContextActive list */
+GList *mm_sierra_parse_scact_read_response (const gchar *reply,
+ GError **error);
+
+#endif /* MM_MODEM_HELPERS_SIERRA_H */
diff --git a/src/plugins/sierra/mm-plugin-sierra-legacy.c b/src/plugins/sierra/mm-plugin-sierra-legacy.c
new file mode 100644
index 00000000..521b8ad1
--- /dev/null
+++ b/src/plugins/sierra/mm-plugin-sierra-legacy.c
@@ -0,0 +1,99 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ * Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <stdlib.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-sierra-legacy.h"
+#include "mm-common-sierra.h"
+#include "mm-broadband-modem-sierra.h"
+#include "mm-broadband-modem-sierra-icera.h"
+
+G_DEFINE_TYPE (MMPluginSierraLegacy, mm_plugin_sierra_legacy, 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 (mm_common_sierra_port_probe_list_is_icera (probes))
+ return MM_BASE_MODEM (mm_broadband_modem_sierra_icera_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+
+ return MM_BASE_MODEM (mm_broadband_modem_sierra_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", NULL };
+ static const gchar *drivers[] = { "sierra", "sierra_net", NULL };
+ static const gchar *forbidden_drivers[] = { "qmi_wwan", "cdc_mbim", NULL };
+ static const MMAsyncMethod custom_init = {
+ .async = G_CALLBACK (mm_common_sierra_custom_init),
+ .finish = G_CALLBACK (mm_common_sierra_custom_init_finish),
+ };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_SIERRA_LEGACY,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_DRIVERS, drivers,
+ MM_PLUGIN_FORBIDDEN_DRIVERS, forbidden_drivers,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_CUSTOM_INIT, &custom_init,
+ MM_PLUGIN_ICERA_PROBE, TRUE,
+ MM_PLUGIN_REMOVE_ECHO, FALSE,
+ NULL));
+}
+
+static void
+mm_plugin_sierra_legacy_init (MMPluginSierraLegacy *self)
+{
+}
+
+static void
+mm_plugin_sierra_legacy_class_init (MMPluginSierraLegacyClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+ plugin_class->grab_port = mm_common_sierra_grab_port;
+}
diff --git a/src/plugins/sierra/mm-plugin-sierra-legacy.h b/src/plugins/sierra/mm-plugin-sierra-legacy.h
new file mode 100644
index 00000000..787118d6
--- /dev/null
+++ b/src/plugins/sierra/mm-plugin-sierra-legacy.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) 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_PLUGIN_SIERRA_LEGACY_H
+#define MM_PLUGIN_SIERRA_LEGACY_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_SIERRA_LEGACY (mm_plugin_sierra_legacy_get_type ())
+#define MM_PLUGIN_SIERRA_LEGACY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_SIERRA_LEGACY, MMPluginSierraLegacy))
+#define MM_PLUGIN_SIERRA_LEGACY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_SIERRA_LEGACY, MMPluginSierraLegacyClass))
+#define MM_IS_PLUGIN_SIERRA_LEGACY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_SIERRA_LEGACY))
+#define MM_IS_PLUGIN_SIERRA_LEGACY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_SIERRA_LEGACY))
+#define MM_PLUGIN_SIERRA_LEGACY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_SIERRA_LEGACY, MMPluginSierraLegacyClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginSierraLegacy;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginSierraLegacyClass;
+
+GType mm_plugin_sierra_legacy_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_SIERRA_LEGACY_H */
diff --git a/src/plugins/sierra/mm-plugin-sierra.c b/src/plugins/sierra/mm-plugin-sierra.c
new file mode 100644
index 00000000..e4b4b676
--- /dev/null
+++ b/src/plugins/sierra/mm-plugin-sierra.c
@@ -0,0 +1,137 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ * Copyright (C) 2015 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-sierra.h"
+#include "mm-broadband-modem.h"
+#include "mm-broadband-modem-xmm.h"
+
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi.h"
+#endif
+
+#if defined WITH_MBIM
+#include "mm-broadband-modem-mbim.h"
+#include "mm-broadband-modem-mbim-xmm.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginSierra, mm_plugin_sierra, 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 Sierra modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+#if defined WITH_MBIM
+ if (mm_port_probe_list_has_mbim_port (probes)) {
+ if (mm_port_probe_list_is_xmm (probes)) {
+ mm_obj_dbg (self, "MBIM-powered XMM-based Sierra modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_xmm_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+ mm_obj_dbg (self, "MBIM-powered Sierra modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ if (mm_port_probe_list_is_xmm (probes)) {
+ mm_obj_dbg (self, "XMM-based Sierra modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_xmm_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+
+ /* Fallback to default modem in the worst case */
+ return MM_BASE_MODEM (mm_broadband_modem_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", NULL };
+ static const guint16 vendor_ids[] = { 0x1199, 0 };
+ static const gchar *drivers[] = { "qmi_wwan", "cdc_mbim", NULL };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_SIERRA,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_DRIVERS, drivers,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_REQUIRED_QCDM, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ MM_PLUGIN_ALLOWED_MBIM, TRUE,
+ MM_PLUGIN_XMM_PROBE, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_sierra_init (MMPluginSierra *self)
+{
+}
+
+static void
+mm_plugin_sierra_class_init (MMPluginSierraClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/sierra/mm-plugin-sierra.h b/src/plugins/sierra/mm-plugin-sierra.h
new file mode 100644
index 00000000..59b6e6b9
--- /dev/null
+++ b/src/plugins/sierra/mm-plugin-sierra.h
@@ -0,0 +1,42 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#ifndef MM_PLUGIN_SIERRA_H
+#define MM_PLUGIN_SIERRA_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_SIERRA (mm_plugin_sierra_get_type ())
+#define MM_PLUGIN_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_SIERRA, MMPluginSierra))
+#define MM_PLUGIN_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_SIERRA, MMPluginSierraClass))
+#define MM_IS_PLUGIN_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_SIERRA))
+#define MM_IS_PLUGIN_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_SIERRA))
+#define MM_PLUGIN_SIERRA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_SIERRA, MMPluginSierraClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginSierra;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginSierraClass;
+
+GType mm_plugin_sierra_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_SIERRA_H */
diff --git a/src/plugins/sierra/mm-shared.c b/src/plugins/sierra/mm-shared.c
new file mode 100644
index 00000000..665aceca
--- /dev/null
+++ b/src/plugins/sierra/mm-shared.c
@@ -0,0 +1,20 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-shared.h"
+
+MM_SHARED_DEFINE_MAJOR_VERSION
+MM_SHARED_DEFINE_MINOR_VERSION
+MM_SHARED_DEFINE_NAME(Sierra)
diff --git a/src/plugins/sierra/mm-sim-sierra.c b/src/plugins/sierra/mm-sim-sierra.c
new file mode 100644
index 00000000..2f3caa48
--- /dev/null
+++ b/src/plugins/sierra/mm-sim-sierra.c
@@ -0,0 +1,158 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+#include "mm-modem-helpers.h"
+#include "mm-base-modem-at.h"
+
+#include "mm-sim-sierra.h"
+
+G_DEFINE_TYPE (MMSimSierra, mm_sim_sierra, MM_TYPE_BASE_SIM)
+
+/*****************************************************************************/
+/* SIM identifier loading */
+
+static gchar *
+load_sim_identifier_finish (MMBaseSim *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+iccid_read_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+ const gchar *p;
+ char *parsed;
+ GError *local = NULL;
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ p = mm_strip_tag (response, "!ICCID:");
+ if (!p) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse !ICCID response: '%s'",
+ response);
+ g_object_unref (task);
+ return;
+ }
+
+ parsed = mm_3gpp_parse_iccid (p, &local);
+ if (parsed)
+ g_task_return_pointer (task, parsed, g_free);
+ else
+ g_task_return_error (task, local);
+
+ g_object_unref (task);
+}
+
+static void
+load_sim_identifier (MMBaseSim *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBaseModem *modem = NULL;
+ GTask *task;
+
+ g_object_get (self,
+ MM_BASE_SIM_MODEM, &modem,
+ NULL);
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ mm_base_modem_at_command (
+ modem,
+ "!ICCID?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)iccid_read_ready,
+ task);
+ g_object_unref (modem);
+}
+
+/*****************************************************************************/
+
+MMBaseSim *
+mm_sim_sierra_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *source;
+ GObject *sim;
+
+ source = g_async_result_get_source_object (res);
+ sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!sim)
+ return NULL;
+
+ /* Only export valid SIMs */
+ mm_base_sim_export (MM_BASE_SIM (sim));
+
+ return MM_BASE_SIM (sim);
+}
+
+void
+mm_sim_sierra_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (MM_TYPE_SIM_SIERRA,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_SIM_MODEM, modem,
+ "active", TRUE, /* by default always active */
+ NULL);
+}
+
+static void
+mm_sim_sierra_init (MMSimSierra *self)
+{
+}
+
+static void
+mm_sim_sierra_class_init (MMSimSierraClass *klass)
+{
+ MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass);
+
+ base_sim_class->load_sim_identifier = load_sim_identifier;
+ base_sim_class->load_sim_identifier_finish = load_sim_identifier_finish;
+}
diff --git a/src/plugins/sierra/mm-sim-sierra.h b/src/plugins/sierra/mm-sim-sierra.h
new file mode 100644
index 00000000..470d4e95
--- /dev/null
+++ b/src/plugins/sierra/mm-sim-sierra.h
@@ -0,0 +1,53 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#ifndef MM_SIM_SIERRA_H
+#define MM_SIM_SIERRA_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "mm-base-sim.h"
+
+#define MM_TYPE_SIM_SIERRA (mm_sim_sierra_get_type ())
+#define MM_SIM_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_SIERRA, MMSimSierra))
+#define MM_SIM_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_SIERRA, MMSimSierraClass))
+#define MM_IS_SIM_SIERRA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_SIERRA))
+#define MM_IS_SIM_SIERRA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_SIERRA))
+#define MM_SIM_SIERRA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_SIERRA, MMSimSierraClass))
+
+typedef struct _MMSimSierra MMSimSierra;
+typedef struct _MMSimSierraClass MMSimSierraClass;
+
+struct _MMSimSierra {
+ MMBaseSim parent;
+};
+
+struct _MMSimSierraClass {
+ MMBaseSimClass parent;
+};
+
+GType mm_sim_sierra_get_type (void);
+
+void mm_sim_sierra_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseSim *mm_sim_sierra_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SIM_SIERRA_H */
diff --git a/src/plugins/sierra/tests/test-modem-helpers-sierra.c b/src/plugins/sierra/tests/test-modem-helpers-sierra.c
new file mode 100644
index 00000000..b0c66496
--- /dev/null
+++ b/src/plugins/sierra/tests/test-modem-helpers-sierra.c
@@ -0,0 +1,130 @@
+/* -*- 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>
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include <locale.h>
+#include <arpa/inet.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-test.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-sierra.h"
+
+/*****************************************************************************/
+/* Test !SCACT? responses */
+
+static void
+test_scact_read_results (const gchar *desc,
+ const gchar *reply,
+ MM3gppPdpContextActive *expected_results,
+ guint32 expected_results_len)
+{
+ GList *l;
+ GError *error = NULL;
+ GList *results;
+
+ g_debug ("\nTesting %s !SCACT response...\n", desc);
+
+ results = mm_sierra_parse_scact_read_response (reply, &error);
+ g_assert_no_error (error);
+ if (expected_results_len) {
+ g_assert (results);
+ g_assert_cmpuint (g_list_length (results), ==, expected_results_len);
+ }
+
+ for (l = results; l; l = g_list_next (l)) {
+ MM3gppPdpContextActive *pdp = l->data;
+ gboolean found = FALSE;
+ guint i;
+
+ for (i = 0; !found && i < expected_results_len; i++) {
+ MM3gppPdpContextActive *expected;
+
+ expected = &expected_results[i];
+ if (pdp->cid == expected->cid) {
+ found = TRUE;
+ g_assert_cmpuint (pdp->active, ==, expected->active);
+ }
+ }
+
+ g_assert (found == TRUE);
+ }
+
+ mm_3gpp_pdp_context_active_list_free (results);
+}
+
+static void
+test_scact_read_response_none (void)
+{
+ test_scact_read_results ("none", "", NULL, 0);
+}
+
+static void
+test_scact_read_response_single_inactive (void)
+{
+ const gchar *reply = "!SCACT: 1,0\r\n";
+ static MM3gppPdpContextActive expected[] = {
+ { 1, FALSE },
+ };
+
+ test_scact_read_results ("single inactive", reply, &expected[0], G_N_ELEMENTS (expected));
+}
+
+static void
+test_scact_read_response_single_active (void)
+{
+ const gchar *reply = "!SCACT: 1,1\r\n";
+ static MM3gppPdpContextActive expected[] = {
+ { 1, TRUE },
+ };
+
+ test_scact_read_results ("single active", reply, &expected[0], G_N_ELEMENTS (expected));
+}
+
+static void
+test_scact_read_response_multiple (void)
+{
+ const gchar *reply =
+ "!SCACT: 1,0\r\n"
+ "!SCACT: 4,1\r\n"
+ "!SCACT: 5,0\r\n";
+ static MM3gppPdpContextActive expected[] = {
+ { 1, FALSE },
+ { 4, TRUE },
+ { 5, FALSE },
+ };
+
+ test_scact_read_results ("multiple", reply, &expected[0], G_N_ELEMENTS (expected));
+}
+
+/*****************************************************************************/
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/sierra/scact/read/none", test_scact_read_response_none);
+ g_test_add_func ("/MM/sierra/scact/read/single-inactive", test_scact_read_response_single_inactive);
+ g_test_add_func ("/MM/sierra/scact/read/single-active", test_scact_read_response_single_active);
+ g_test_add_func ("/MM/sierra/scact/read/multiple", test_scact_read_response_multiple);
+
+ return g_test_run ();
+}
diff --git a/src/plugins/simtech/77-mm-simtech-port-types.rules b/src/plugins/simtech/77-mm-simtech-port-types.rules
new file mode 100644
index 00000000..e51a60dd
--- /dev/null
+++ b/src/plugins/simtech/77-mm-simtech-port-types.rules
@@ -0,0 +1,59 @@
+# do not edit this file, it will be overwritten on update
+
+# Simtech makes modules that other companies rebrand, like:
+#
+# A-LINK 3GU
+# SCT UM300
+#
+# Most of these values were scraped from various SimTech-based Windows
+# driver .inf files. *mdm.inf lists the main command ports, while
+# *ser.inf lists the aux ports that may be used for PPP.
+
+
+ACTION!="add|change|move|bind", GOTO="mm_simtech_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1e0e", GOTO="mm_simtech_port_types"
+GOTO="mm_simtech_port_types_end"
+
+LABEL="mm_simtech_port_types"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# A-LINK 3GU
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="cefe", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="cefe", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="cefe", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# Prolink PH-300
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9100", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9100", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# SCT UM300
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9200", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9200", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# SIM7000, SIM7100, SIM7600...
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AUDIO}="1"
+
+# SIM7070, SIM7080, SIM7090 (default layout)...
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9205", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9205", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9205", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9205", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PPP}="1"
+# Disable CPOL based features
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9205", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1"
+
+
+# SIM7070, SIM7080, SIM7090 (secondary layout)...
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PPP}="1"
+# Disable CPOL based features
+ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9206", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1"
+
+LABEL="mm_simtech_port_types_end"
diff --git a/src/plugins/simtech/mm-broadband-modem-qmi-simtech.c b/src/plugins/simtech/mm-broadband-modem-qmi-simtech.c
new file mode 100644
index 00000000..93ff8576
--- /dev/null
+++ b/src/plugins/simtech/mm-broadband-modem-qmi-simtech.c
@@ -0,0 +1,127 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-errors-types.h"
+#include "mm-iface-modem-location.h"
+#include "mm-iface-modem-voice.h"
+#include "mm-broadband-modem-qmi-simtech.h"
+#include "mm-shared-simtech.h"
+
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void iface_modem_voice_init (MMIfaceModemVoice *iface);
+static void shared_simtech_init (MMSharedSimtech *iface);
+
+static MMIfaceModemLocation *iface_modem_location_parent;
+static MMIfaceModemVoice *iface_modem_voice_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQmiSimtech, mm_broadband_modem_qmi_simtech, MM_TYPE_BROADBAND_MODEM_QMI, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_SIMTECH, shared_simtech_init))
+
+/*****************************************************************************/
+
+MMBroadbandModemQmiSimtech *
+mm_broadband_modem_qmi_simtech_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_SIMTECH,
+ 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,
+ /* QMI modem supports NET only */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE,
+ MM_BROADBAND_MODEM_INDICATORS_DISABLED, TRUE,
+ MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_qmi_simtech_init (MMBroadbandModemQmiSimtech *self)
+{
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = mm_shared_simtech_location_load_capabilities;
+ iface->load_capabilities_finish = mm_shared_simtech_location_load_capabilities_finish;
+ iface->enable_location_gathering = mm_shared_simtech_enable_location_gathering;
+ iface->enable_location_gathering_finish = mm_shared_simtech_enable_location_gathering_finish;
+ iface->disable_location_gathering = mm_shared_simtech_disable_location_gathering;
+ iface->disable_location_gathering_finish = mm_shared_simtech_disable_location_gathering_finish;
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedSimtech *self)
+{
+ return iface_modem_location_parent;
+}
+
+static void
+iface_modem_voice_init (MMIfaceModemVoice *iface)
+{
+ iface_modem_voice_parent = g_type_interface_peek_parent (iface);
+
+ iface->check_support = mm_shared_simtech_voice_check_support;
+ iface->check_support_finish = mm_shared_simtech_voice_check_support_finish;
+ iface->enable_unsolicited_events = mm_shared_simtech_voice_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = mm_shared_simtech_voice_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = mm_shared_simtech_voice_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = mm_shared_simtech_voice_disable_unsolicited_events_finish;
+ iface->setup_unsolicited_events = mm_shared_simtech_voice_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_simtech_voice_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_simtech_voice_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_simtech_voice_cleanup_unsolicited_events_finish;
+ iface->setup_in_call_audio_channel = mm_shared_simtech_voice_setup_in_call_audio_channel;
+ iface->setup_in_call_audio_channel_finish = mm_shared_simtech_voice_setup_in_call_audio_channel_finish;
+ iface->cleanup_in_call_audio_channel = mm_shared_simtech_voice_cleanup_in_call_audio_channel;
+ iface->cleanup_in_call_audio_channel_finish = mm_shared_simtech_voice_cleanup_in_call_audio_channel_finish;
+}
+
+static MMIfaceModemVoice *
+peek_parent_voice_interface (MMSharedSimtech *self)
+{
+ return iface_modem_voice_parent;
+}
+
+static void
+shared_simtech_init (MMSharedSimtech *iface)
+{
+ iface->peek_parent_location_interface = peek_parent_location_interface;
+ iface->peek_parent_voice_interface = peek_parent_voice_interface;
+}
+
+static void
+mm_broadband_modem_qmi_simtech_class_init (MMBroadbandModemQmiSimtechClass *klass)
+{
+}
diff --git a/src/plugins/simtech/mm-broadband-modem-qmi-simtech.h b/src/plugins/simtech/mm-broadband-modem-qmi-simtech.h
new file mode 100644
index 00000000..2f5b819b
--- /dev/null
+++ b/src/plugins/simtech/mm-broadband-modem-qmi-simtech.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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_BROADBAND_MODEM_QMI_SIMTECH_QMI_H
+#define MM_BROADBAND_MODEM_QMI_SIMTECH_QMI_H
+
+#include "mm-broadband-modem-qmi.h"
+
+#define MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH (mm_broadband_modem_qmi_simtech_get_type ())
+#define MM_BROADBAND_MODEM_QMI_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH, MMBroadbandModemQmiSimtech))
+#define MM_BROADBAND_MODEM_QMI_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH, MMBroadbandModemQmiSimtechClass))
+#define MM_IS_BROADBAND_MODEM_QMI_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH))
+#define MM_IS_BROADBAND_MODEM_QMI_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH))
+#define MM_BROADBAND_MODEM_QMI_SIMTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_QMI_SIMTECH, MMBroadbandModemQmiSimtechClass))
+
+typedef struct _MMBroadbandModemQmiSimtech MMBroadbandModemQmiSimtech;
+typedef struct _MMBroadbandModemQmiSimtechClass MMBroadbandModemQmiSimtechClass;
+
+struct _MMBroadbandModemQmiSimtech {
+ MMBroadbandModemQmi parent;
+};
+
+struct _MMBroadbandModemQmiSimtechClass{
+ MMBroadbandModemQmiClass parent;
+};
+
+GType mm_broadband_modem_qmi_simtech_get_type (void);
+
+MMBroadbandModemQmiSimtech *mm_broadband_modem_qmi_simtech_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_QMI_SIMTECH_H */
diff --git a/src/plugins/simtech/mm-broadband-modem-simtech.c b/src/plugins/simtech/mm-broadband-modem-simtech.c
new file mode 100644
index 00000000..2ca0c6ae
--- /dev/null
+++ b/src/plugins/simtech/mm-broadband-modem-simtech.c
@@ -0,0 +1,1351 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "ModemManager.h"
+#include "mm-modem-helpers.h"
+#include "mm-log-object.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-location.h"
+#include "mm-iface-modem-voice.h"
+#include "mm-shared-simtech.h"
+#include "mm-broadband-modem-simtech.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void iface_modem_voice_init (MMIfaceModemVoice *iface);
+static void shared_simtech_init (MMSharedSimtech *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+static MMIfaceModemLocation *iface_modem_location_parent;
+static MMIfaceModemVoice *iface_modem_voice_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemSimtech, mm_broadband_modem_simtech, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_SIMTECH, shared_simtech_init))
+
+typedef enum {
+ FEATURE_SUPPORT_UNKNOWN,
+ FEATURE_NOT_SUPPORTED,
+ FEATURE_SUPPORTED
+} FeatureSupport;
+
+struct _MMBroadbandModemSimtechPrivate {
+ FeatureSupport cnsmod_support;
+ FeatureSupport autocsq_support;
+ GRegex *cnsmod_regex;
+ GRegex *csq_regex;
+};
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (3GPP interface) */
+
+static MMModemAccessTechnology
+simtech_act_to_mm_act (guint nsmod)
+{
+ static const MMModemAccessTechnology simtech_act_to_mm_act_map[] = {
+ [0] = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN,
+ [1] = MM_MODEM_ACCESS_TECHNOLOGY_GSM,
+ [2] = MM_MODEM_ACCESS_TECHNOLOGY_GPRS,
+ [3] = MM_MODEM_ACCESS_TECHNOLOGY_EDGE,
+ [4] = MM_MODEM_ACCESS_TECHNOLOGY_UMTS,
+ [5] = MM_MODEM_ACCESS_TECHNOLOGY_HSDPA,
+ [6] = MM_MODEM_ACCESS_TECHNOLOGY_HSUPA,
+ [7] = MM_MODEM_ACCESS_TECHNOLOGY_HSPA,
+ [8] = MM_MODEM_ACCESS_TECHNOLOGY_LTE,
+ };
+
+ return (nsmod < G_N_ELEMENTS (simtech_act_to_mm_act_map) ? simtech_act_to_mm_act_map[nsmod] : MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN);
+}
+
+static void
+simtech_tech_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemSimtech *self)
+{
+ guint simtech_act = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &simtech_act))
+ return;
+
+ mm_iface_modem_update_access_technologies (
+ MM_IFACE_MODEM (self),
+ simtech_act_to_mm_act (simtech_act),
+ MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
+}
+
+static void
+simtech_signal_changed (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemSimtech *self)
+{
+ guint quality = 0;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &quality))
+ return;
+
+ if (quality != 99)
+ quality = MM_CLAMP_HIGH (quality, 31) * 100 / 31;
+ else
+ quality = 0;
+
+ mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);
+}
+
+static void
+set_unsolicited_events_handlers (MMBroadbandModemSimtech *self,
+ gboolean enable)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ 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 unsolicited events in given port */
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ /* Access technology related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->cnsmod_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)simtech_tech_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ /* Signal quality related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->csq_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)simtech_signal_changed : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+static gboolean
+modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else {
+ /* Our own setup now */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_SIMTECH (self), TRUE);
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's setup */
+ iface_modem_3gpp_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_setup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+static void
+parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Our own cleanup first */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_SIMTECH (self), FALSE);
+
+ /* And now chain up parent's cleanup */
+ iface_modem_3gpp_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Enable unsolicited events (3GPP interface) */
+
+typedef enum {
+ ENABLE_UNSOLICITED_EVENTS_STEP_FIRST,
+ ENABLE_UNSOLICITED_EVENTS_STEP_PARENT,
+ ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_CNSMOD,
+ ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_CNSMOD,
+ ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_AUTOCSQ,
+ ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_AUTOCSQ,
+ ENABLE_UNSOLICITED_EVENTS_STEP_LAST,
+} EnableUnsolicitedEventsStep;
+
+typedef struct {
+ EnableUnsolicitedEventsStep step;
+} EnableUnsolicitedEventsContext;
+
+static gboolean
+modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void enable_unsolicited_events_context_step (GTask *task);
+
+static void
+autocsq_set_enabled_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ EnableUnsolicitedEventsContext *ctx;
+ GError *error = NULL;
+ gboolean csq_urcs_enabled = FALSE;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ mm_obj_dbg (self, "couldn't enable automatic signal quality reporting: %s", error->message);
+ g_error_free (error);
+ } else
+ csq_urcs_enabled = TRUE;
+
+ /* Disable access technology polling if we can use the +CSQ URCs */
+ g_object_set (self,
+ MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, csq_urcs_enabled,
+ NULL);
+
+ /* go to next step */
+ ctx->step++;
+ enable_unsolicited_events_context_step (task);
+}
+
+static void
+autocsq_test_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemSimtech *self;
+ EnableUnsolicitedEventsContext *ctx;
+
+ self = MM_BROADBAND_MODEM_SIMTECH (_self);
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (_self, res, NULL))
+ self->priv->autocsq_support = FEATURE_NOT_SUPPORTED;
+ else
+ self->priv->autocsq_support = FEATURE_SUPPORTED;
+
+ /* go to next step */
+ ctx->step++;
+ enable_unsolicited_events_context_step (task);
+}
+
+static void
+cnsmod_set_enabled_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ EnableUnsolicitedEventsContext *ctx;
+ GError *error = NULL;
+ gboolean cnsmod_urcs_enabled = FALSE;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ mm_obj_dbg (self, "couldn't enable automatic access technology reporting: %s", error->message);
+ g_error_free (error);
+ } else
+ cnsmod_urcs_enabled = TRUE;
+
+ /* Disable access technology polling if we can use the +CNSMOD URCs */
+ g_object_set (self,
+ MM_IFACE_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED, cnsmod_urcs_enabled,
+ NULL);
+
+ /* go to next step */
+ ctx->step++;
+ enable_unsolicited_events_context_step (task);
+}
+
+static void
+cnsmod_test_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemSimtech *self;
+ EnableUnsolicitedEventsContext *ctx;
+
+ self = MM_BROADBAND_MODEM_SIMTECH (_self);
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (_self, res, NULL))
+ self->priv->cnsmod_support = FEATURE_NOT_SUPPORTED;
+ else
+ self->priv->cnsmod_support = FEATURE_SUPPORTED;
+
+ /* go to next step */
+ ctx->step++;
+ enable_unsolicited_events_context_step (task);
+}
+
+static void
+parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ EnableUnsolicitedEventsContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* go to next step */
+ ctx->step++;
+ enable_unsolicited_events_context_step (task);
+}
+
+static void
+enable_unsolicited_events_context_step (GTask *task)
+{
+ MMBroadbandModemSimtech *self;
+ EnableUnsolicitedEventsContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case ENABLE_UNSOLICITED_EVENTS_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+
+ case ENABLE_UNSOLICITED_EVENTS_STEP_PARENT:
+ iface_modem_3gpp_parent->enable_unsolicited_events (
+ MM_IFACE_MODEM_3GPP (self),
+ (GAsyncReadyCallback)parent_enable_unsolicited_events_ready,
+ task);
+ return;
+
+ case ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_CNSMOD:
+ if (self->priv->cnsmod_support == FEATURE_SUPPORT_UNKNOWN) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CNSMOD=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)cnsmod_test_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_CNSMOD:
+ if (self->priv->cnsmod_support == FEATURE_SUPPORTED) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ /* Autoreport +CNSMOD when it changes */
+ "+CNSMOD=1",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)cnsmod_set_enabled_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case ENABLE_UNSOLICITED_EVENTS_STEP_CHECK_SUPPORT_AUTOCSQ:
+ if (self->priv->autocsq_support == FEATURE_SUPPORT_UNKNOWN) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+AUTOCSQ=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)autocsq_test_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case ENABLE_UNSOLICITED_EVENTS_STEP_ENABLE_AUTOCSQ:
+ if (self->priv->autocsq_support == FEATURE_SUPPORTED) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ /* Autoreport+ CSQ (first arg), and only report when it changes (second arg) */
+ "+AUTOCSQ=1,1",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)autocsq_set_enabled_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case ENABLE_UNSOLICITED_EVENTS_STEP_LAST:
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ EnableUnsolicitedEventsContext *ctx;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_new (EnableUnsolicitedEventsContext, 1);
+ ctx->step = ENABLE_UNSOLICITED_EVENTS_STEP_FIRST;
+ g_task_set_task_data (task, ctx, g_free);
+
+ enable_unsolicited_events_context_step (task);
+}
+
+/*****************************************************************************/
+/* Disable unsolicited events (3GPP interface) */
+
+typedef enum {
+ DISABLE_UNSOLICITED_EVENTS_STEP_FIRST,
+ DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_AUTOCSQ,
+ DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_CNSMOD,
+ DISABLE_UNSOLICITED_EVENTS_STEP_PARENT,
+ DISABLE_UNSOLICITED_EVENTS_STEP_LAST,
+} DisableUnsolicitedEventsStep;
+
+typedef struct {
+ DisableUnsolicitedEventsStep step;
+} DisableUnsolicitedEventsContext;
+
+static gboolean
+modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void disable_unsolicited_events_context_step (GTask *task);
+
+static void
+parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ DisableUnsolicitedEventsContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* go to next step */
+ ctx->step++;
+ disable_unsolicited_events_context_step (task);
+}
+
+static void
+cnsmod_set_disabled_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ DisableUnsolicitedEventsContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ mm_obj_dbg (self, "couldn't disable automatic access technology reporting: %s", error->message);
+ g_error_free (error);
+ }
+
+ /* go to next step */
+ ctx->step++;
+ disable_unsolicited_events_context_step (task);
+}
+
+static void
+autocsq_set_disabled_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ DisableUnsolicitedEventsContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ mm_obj_dbg (self, "couldn't disable automatic signal quality reporting: %s", error->message);
+ g_error_free (error);
+ }
+
+ /* go to next step */
+ ctx->step++;
+ disable_unsolicited_events_context_step (task);
+}
+
+static void
+disable_unsolicited_events_context_step (GTask *task)
+{
+ MMBroadbandModemSimtech *self;
+ DisableUnsolicitedEventsContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case DISABLE_UNSOLICITED_EVENTS_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+
+ case DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_AUTOCSQ:
+ if (self->priv->autocsq_support == FEATURE_SUPPORTED) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+AUTOCSQ=0",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)autocsq_set_disabled_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case DISABLE_UNSOLICITED_EVENTS_STEP_DISABLE_CNSMOD:
+ if (self->priv->cnsmod_support == FEATURE_SUPPORTED) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CNSMOD=0",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)cnsmod_set_disabled_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case DISABLE_UNSOLICITED_EVENTS_STEP_PARENT:
+ iface_modem_3gpp_parent->disable_unsolicited_events (
+ MM_IFACE_MODEM_3GPP (self),
+ (GAsyncReadyCallback)parent_disable_unsolicited_events_ready,
+ task);
+ return;
+
+ case DISABLE_UNSOLICITED_EVENTS_STEP_LAST:
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ DisableUnsolicitedEventsContext *ctx;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_new (DisableUnsolicitedEventsContext, 1);
+ ctx->step = DISABLE_UNSOLICITED_EVENTS_STEP_FIRST;
+ g_task_set_task_data (task, ctx, g_free);
+
+ disable_unsolicited_events_context_step (task);
+}
+
+/*****************************************************************************/
+/* Load access technologies (Modem interface) */
+
+static gboolean
+load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ gssize act;
+
+ act = g_task_propagate_int (G_TASK (res), &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ *access_technologies = (MMModemAccessTechnology) act;
+ *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY;
+ return TRUE;
+}
+
+static void
+cnsmod_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response, *p;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ p = mm_strip_tag (response, "+CNSMOD:");
+ if (p)
+ p = strchr (p, ',');
+
+ if (!p || !isdigit (*(p + 1)))
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse the +CNSMOD response: '%s'",
+ response);
+ else
+ g_task_return_int (task, simtech_act_to_mm_act (atoi (p + 1)));
+ g_object_unref (task);
+}
+
+static void
+load_access_technologies (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemSimtech *self;
+ GTask *task;
+
+ self = MM_BROADBAND_MODEM_SIMTECH (_self);
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Launch query only for 3GPP modems */
+ if (!mm_iface_modem_is_3gpp (_self)) {
+ g_task_return_int (task, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN);
+ g_object_unref (task);
+ return;
+ }
+
+ g_assert (self->priv->cnsmod_support != FEATURE_SUPPORT_UNKNOWN);
+ if (self->priv->cnsmod_support == FEATURE_NOT_SUPPORTED) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Loading access technologies with +CNSMOD is not supported");
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "AT+CNSMOD?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)cnsmod_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load signal quality (Modem interface) */
+
+static guint
+load_signal_quality_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ gssize value;
+
+ value = g_task_propagate_int (G_TASK (res), error);
+ return value < 0 ? 0 : value;
+}
+
+static void
+csq_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response, *p;
+ GError *error = NULL;
+ gint quality;
+ gint ber;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Given that we may have enabled AUTOCSQ support, it is totally possible
+ * to get an empty string at this point, because the +CSQ reply may have
+ * been processed as an URC already. If we ever see this, we should not return
+ * an error, because that would reset the reported signal quality to 0 :/
+ * So, in this case, return the last cached signal quality value. */
+ if (!response[0]) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_IN_PROGRESS,
+ "already refreshed via URCs");
+ g_object_unref (task);
+ return;
+ }
+
+ p = mm_strip_tag (response, "+CSQ:");
+ if (sscanf (p, "%d, %d", &quality, &ber)) {
+ if (quality != 99)
+ quality = CLAMP (quality, 0, 31) * 100 / 31;
+ else
+ quality = 0;
+ g_task_return_int (task, quality);
+ g_object_unref (task);
+ return;
+ }
+
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Could not parse signal quality results");
+ g_object_unref (task);
+}
+
+static void
+load_signal_quality (MMIfaceModem *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),
+ "+CSQ",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)csq_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_load_supported_modes_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ GArray *all;
+ GArray *combinations;
+ GArray *filtered;
+ MMModemModeCombination mode;
+
+ all = iface_modem_parent->load_supported_modes_finish (self, res, &error);
+ if (!all) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5);
+ /* 2G only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 3G only */
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G, 2G preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G, 3G preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+
+ /* Filter out those unsupported modes */
+ filtered = mm_filter_supported_modes (all, combinations, self);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Run parent's loading */
+ iface_modem_parent->load_supported_modes (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_supported_modes_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Load initial allowed/preferred modes (Modem interface) */
+
+typedef struct {
+ MMModemMode allowed;
+ MMModemMode preferred;
+} LoadCurrentModesResult;
+
+typedef struct {
+ gint acqord;
+ gint modepref;
+} LoadCurrentModesContext;
+
+static gboolean
+load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ LoadCurrentModesResult *result;
+
+ result = g_task_propagate_pointer (G_TASK (res), error);
+ if (!result)
+ return FALSE;
+
+ *allowed = result->allowed;
+ *preferred = result->preferred;
+ g_free (result);
+ return TRUE;
+}
+
+static void
+cnmp_query_ready (MMBroadbandModemSimtech *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LoadCurrentModesContext *ctx;
+ LoadCurrentModesResult *result;
+ const gchar *response, *p;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ p = mm_strip_tag (response, "+CNMP:");
+ if (!p) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse the mode preference response: '%s'",
+ response);
+ g_object_unref (task);
+ return;
+ }
+
+ result = g_new (LoadCurrentModesResult, 1);
+ result->allowed = MM_MODEM_MODE_NONE;
+ result->preferred = MM_MODEM_MODE_NONE;
+
+ ctx->modepref = atoi (p);
+ switch (ctx->modepref) {
+ case 2:
+ /* Automatic */
+ switch (ctx->acqord) {
+ case 0:
+ result->allowed = MM_MODEM_MODE_ANY;
+ result->preferred = MM_MODEM_MODE_NONE;
+ break;
+ case 1:
+ result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ result->preferred = MM_MODEM_MODE_2G;
+ break;
+ case 2:
+ result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ result->preferred = MM_MODEM_MODE_3G;
+ break;
+ default:
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unknown acquisition order preference: '%d'",
+ ctx->acqord);
+ g_object_unref (task);
+ g_free (result);
+ return;
+ }
+ break;
+
+ case 13:
+ /* GSM only */
+ result->allowed = MM_MODEM_MODE_2G;
+ result->preferred = MM_MODEM_MODE_NONE;
+ break;
+
+ case 14:
+ /* WCDMA only */
+ result->allowed = MM_MODEM_MODE_3G;
+ result->preferred = MM_MODEM_MODE_NONE;
+ break;
+
+ default:
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unknown mode preference: '%d'",
+ ctx->modepref);
+ g_object_unref (task);
+ g_free (result);
+ return;
+ }
+
+ g_task_return_pointer (task, result, g_free);
+ g_object_unref (task);
+}
+
+static void
+cnaop_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LoadCurrentModesContext *ctx;
+ const gchar *response, *p;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_task_get_task_data (task);
+
+ p = mm_strip_tag (response, "+CNAOP:");
+ if (p)
+ ctx->acqord = atoi (p);
+
+ if (ctx->acqord < 0 || ctx->acqord > 2) {
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse the acquisition order response: '%s'",
+ response);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CNMP?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)cnmp_query_ready,
+ task);
+}
+
+static void
+load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ LoadCurrentModesContext *ctx;
+
+ ctx = g_new (LoadCurrentModesContext, 1);
+ ctx->acqord = -1;
+ ctx->modepref = -1;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CNAOP?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)cnaop_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Set allowed modes (Modem interface) */
+
+typedef struct {
+ guint nmp; /* mode preference */
+ guint naop; /* acquisition order */
+} SetCurrentModesContext;
+
+static gboolean
+set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+cnaop_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+cnmp_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetCurrentModesContext *ctx;
+ GError *error = NULL;
+ gchar *command;
+
+ ctx = g_task_get_task_data (task);
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ /* Let the error be critical. */
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ command = g_strdup_printf ("+CNAOP=%u", ctx->naop);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)cnaop_set_ready,
+ task);
+ g_free (command);
+}
+
+static void
+set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ SetCurrentModesContext *ctx;
+ gchar *command;
+
+ /* Defaults: automatic search */
+ ctx = g_new (SetCurrentModesContext, 1);
+ ctx->nmp = 2;
+ ctx->naop = 0;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ if (allowed == MM_MODEM_MODE_ANY && preferred == MM_MODEM_MODE_NONE) {
+ /* defaults nmp and naop */
+ } else if (allowed == MM_MODEM_MODE_2G) {
+ ctx->nmp = 13;
+ ctx->naop = 0;
+ } else if (allowed == MM_MODEM_MODE_3G) {
+ ctx->nmp = 14;
+ ctx->naop = 0;
+ } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) {
+ /* default nmp */
+ if (preferred == MM_MODEM_MODE_2G)
+ ctx->naop = 3;
+ else if (preferred == MM_MODEM_MODE_3G)
+ ctx->naop = 2;
+ else
+ /* default naop */
+ ctx->naop = 0;
+ } else {
+ gchar *allowed_str;
+ gchar *preferred_str;
+
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Requested mode (allowed: '%s', preferred: '%s') not "
+ "supported by the modem.",
+ allowed_str,
+ preferred_str);
+ g_object_unref (task);
+ g_free (allowed_str);
+ g_free (preferred_str);
+ return;
+ }
+
+ command = g_strdup_printf ("+CNMP=%u", ctx->nmp);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)cnmp_set_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_simtech_parent_class)->setup_ports (self);
+
+ /* Now reset the unsolicited messages we'll handle when enabled */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_SIMTECH (self), FALSE);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemSimtech *
+mm_broadband_modem_simtech_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_SIMTECH,
+ 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_BROADBAND_MODEM_INDICATORS_DISABLED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_simtech_init (MMBroadbandModemSimtech *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_MODEM_SIMTECH,
+ MMBroadbandModemSimtechPrivate);
+
+ self->priv->cnsmod_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->autocsq_support = FEATURE_SUPPORT_UNKNOWN;
+
+ self->priv->cnsmod_regex = g_regex_new ("\\r\\n\\+CNSMOD:\\s*(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->csq_regex = g_regex_new ("\\r\\n\\+CSQ:\\s*(\\d+),(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemSimtech *self = MM_BROADBAND_MODEM_SIMTECH (object);
+
+ g_regex_unref (self->priv->cnsmod_regex);
+ g_regex_unref (self->priv->csq_regex);
+
+ G_OBJECT_CLASS (mm_broadband_modem_simtech_parent_class)->finalize (object);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_signal_quality = load_signal_quality;
+ iface->load_signal_quality_finish = load_signal_quality_finish;
+ iface->load_access_technologies = load_access_technologies;
+ iface->load_access_technologies_finish = load_access_technologies_finish;
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = load_current_modes;
+ iface->load_current_modes_finish = load_current_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = set_current_modes_finish;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+
+ iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish;
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = mm_shared_simtech_location_load_capabilities;
+ iface->load_capabilities_finish = mm_shared_simtech_location_load_capabilities_finish;
+ iface->enable_location_gathering = mm_shared_simtech_enable_location_gathering;
+ iface->enable_location_gathering_finish = mm_shared_simtech_enable_location_gathering_finish;
+ iface->disable_location_gathering = mm_shared_simtech_disable_location_gathering;
+ iface->disable_location_gathering_finish = mm_shared_simtech_disable_location_gathering_finish;
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedSimtech *self)
+{
+ return iface_modem_location_parent;
+}
+
+static void
+iface_modem_voice_init (MMIfaceModemVoice *iface)
+{
+ iface_modem_voice_parent = g_type_interface_peek_parent (iface);
+
+ iface->check_support = mm_shared_simtech_voice_check_support;
+ iface->check_support_finish = mm_shared_simtech_voice_check_support_finish;
+ iface->enable_unsolicited_events = mm_shared_simtech_voice_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = mm_shared_simtech_voice_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = mm_shared_simtech_voice_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = mm_shared_simtech_voice_disable_unsolicited_events_finish;
+ iface->setup_unsolicited_events = mm_shared_simtech_voice_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = mm_shared_simtech_voice_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = mm_shared_simtech_voice_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = mm_shared_simtech_voice_cleanup_unsolicited_events_finish;
+ iface->setup_in_call_audio_channel = mm_shared_simtech_voice_setup_in_call_audio_channel;
+ iface->setup_in_call_audio_channel_finish = mm_shared_simtech_voice_setup_in_call_audio_channel_finish;
+ iface->cleanup_in_call_audio_channel = mm_shared_simtech_voice_cleanup_in_call_audio_channel;
+ iface->cleanup_in_call_audio_channel_finish = mm_shared_simtech_voice_cleanup_in_call_audio_channel_finish;
+
+}
+
+static MMIfaceModemVoice *
+peek_parent_voice_interface (MMSharedSimtech *self)
+{
+ return iface_modem_voice_parent;
+}
+
+static void
+shared_simtech_init (MMSharedSimtech *iface)
+{
+ iface->peek_parent_location_interface = peek_parent_location_interface;
+ iface->peek_parent_voice_interface = peek_parent_voice_interface;
+}
+
+static void
+mm_broadband_modem_simtech_class_init (MMBroadbandModemSimtechClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemSimtechPrivate));
+
+ object_class->finalize = finalize;
+
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/simtech/mm-broadband-modem-simtech.h b/src/plugins/simtech/mm-broadband-modem-simtech.h
new file mode 100644
index 00000000..a2b57fea
--- /dev/null
+++ b/src/plugins/simtech/mm-broadband-modem-simtech.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_MODEM_SIMTECH_H
+#define MM_BROADBAND_MODEM_SIMTECH_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_SIMTECH (mm_broadband_modem_simtech_get_type ())
+#define MM_BROADBAND_MODEM_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_SIMTECH, MMBroadbandModemSimtech))
+#define MM_BROADBAND_MODEM_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_SIMTECH, MMBroadbandModemSimtechClass))
+#define MM_IS_BROADBAND_MODEM_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_SIMTECH))
+#define MM_IS_BROADBAND_MODEM_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_SIMTECH))
+#define MM_BROADBAND_MODEM_SIMTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_SIMTECH, MMBroadbandModemSimtechClass))
+
+typedef struct _MMBroadbandModemSimtech MMBroadbandModemSimtech;
+typedef struct _MMBroadbandModemSimtechClass MMBroadbandModemSimtechClass;
+typedef struct _MMBroadbandModemSimtechPrivate MMBroadbandModemSimtechPrivate;
+
+struct _MMBroadbandModemSimtech {
+ MMBroadbandModem parent;
+ MMBroadbandModemSimtechPrivate *priv;
+};
+
+struct _MMBroadbandModemSimtechClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_simtech_get_type (void);
+
+MMBroadbandModemSimtech *mm_broadband_modem_simtech_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_SIMTECH_H */
diff --git a/src/plugins/simtech/mm-modem-helpers-simtech.c b/src/plugins/simtech/mm-modem-helpers-simtech.c
new file mode 100644
index 00000000..0403c145
--- /dev/null
+++ b/src/plugins/simtech/mm-modem-helpers-simtech.c
@@ -0,0 +1,209 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "ModemManager.h"
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+#include "mm-errors-types.h"
+#include "mm-modem-helpers-simtech.h"
+#include "mm-modem-helpers.h"
+
+
+/*****************************************************************************/
+/* +CLCC test parser
+ *
+ * Example (SIM7600E):
+ * AT+CLCC=?
+ * +CLCC: (0-1)
+ */
+
+gboolean
+mm_simtech_parse_clcc_test (const gchar *response,
+ gboolean *clcc_urcs_supported,
+ GError **error)
+{
+ g_assert (response);
+
+ response = mm_strip_tag (response, "+CLCC:");
+
+ /* 3GPP specifies that the output of AT+CLCC=? should be just OK, so support
+ * that */
+ if (!response[0]) {
+ *clcc_urcs_supported = FALSE;
+ return TRUE;
+ }
+
+ /* As per 3GPP TS 27.007, the AT+CLCC command doesn't expect any argument,
+ * as it only is designed to report the current call list, nothing else.
+ * In the case of the Simtech plugin, though, we are going to support +CLCC
+ * URCs that can be enabled/disabled via AT+CLCC=1/0. We therefore need to
+ * detect whether this URC management is possible or not, for now with a
+ * simple check looking for the specific "(0-1)" string.
+ */
+ if (!strncmp (response, "(0-1)", 5)) {
+ *clcc_urcs_supported = TRUE;
+ return TRUE;
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "unexpected +CLCC test response: '%s'", response);
+ return FALSE;
+}
+
+/*****************************************************************************/
+
+GRegex *
+mm_simtech_get_clcc_urc_regex (void)
+{
+ return g_regex_new ("\\r\\n(\\+CLCC: .*\\r\\n)+",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+gboolean
+mm_simtech_parse_clcc_list (const gchar *str,
+ gpointer log_object,
+ GList **out_list,
+ GError **error)
+{
+ /* Parse the URC contents as a plain +CLCC response, but make sure to skip first
+ * EOL in the string because the plain +CLCC response would never have that.
+ */
+ return mm_3gpp_parse_clcc_response (mm_strip_tag (str, "\r\n"), log_object, out_list, error);
+}
+
+void
+mm_simtech_call_info_list_free (GList *call_info_list)
+{
+ mm_3gpp_call_info_list_free (call_info_list);
+}
+
+/*****************************************************************************/
+
+/*
+ * <CR><LF>VOICE CALL: BEGIN<CR><LF>
+ * <CR><LF>VOICE CALL: END: 000041<CR><LF>
+ */
+GRegex *
+mm_simtech_get_voice_call_urc_regex (void)
+{
+ return g_regex_new ("\\r\\nVOICE CALL:\\s*([A-Z]+)(?::\\s*(\\d+))?\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+gboolean
+mm_simtech_parse_voice_call_urc (GMatchInfo *match_info,
+ gboolean *start_or_stop,
+ guint *duration,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ gchar *str;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (!str) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't read voice call URC action");
+ goto out;
+ }
+
+ if (g_strcmp0 (str, "BEGIN") == 0) {
+ *start_or_stop = TRUE;
+ *duration = 0;
+ goto out;
+ }
+
+ if (g_strcmp0 (str, "END") == 0) {
+ *start_or_stop = FALSE;
+ if (!mm_get_uint_from_match_info (match_info, 2, duration))
+ *duration = 0;
+ goto out;
+ }
+
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown voice call URC action: %s", str);
+
+out:
+ g_free (str);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+/*
+ * <CR><LF>MISSED_CALL: 11:01AM 07712345678<CR><LF>
+ */
+GRegex *
+mm_simtech_get_missed_call_urc_regex (void)
+{
+ return g_regex_new ("\\r\\nMISSED_CALL:\\s*(.+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+gboolean
+mm_simtech_parse_missed_call_urc (GMatchInfo *match_info,
+ gchar **details,
+ GError **error)
+{
+ gchar *str;
+
+ str = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (!str) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't read missed call URC details");
+ return FALSE;
+ }
+
+ *details = str;
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+/*
+ * Using TWO <CR> instead of one...
+ * <CR><CR><LF>+CRING: VOICE<CR><CR><LF>
+ */
+GRegex *
+mm_simtech_get_cring_urc_regex (void)
+{
+ return g_regex_new ("(?:\\r)+\\n\\+CRING:\\s*(\\S+)(?:\\r)+\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+/*****************************************************************************/
+
+/*
+ * <CR><CR><LF>+RXDTMF: 8<CR><CR><LF>
+ * <CR><CR><LF>+RXDTMF: *<CR><CR><LF>
+ * <CR><CR><LF>+RXDTMF: 7<CR><CR><LF>
+ *
+ * Note! using TWO <CR> instead of one...
+ */
+GRegex *
+mm_simtech_get_rxdtmf_urc_regex (void)
+{
+ return g_regex_new ("(?:\\r)+\\n\\+RXDTMF:\\s*([0-9A-D\\*\\#])(?:\\r)+\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
diff --git a/src/plugins/simtech/mm-modem-helpers-simtech.h b/src/plugins/simtech/mm-modem-helpers-simtech.h
new file mode 100644
index 00000000..1949d2e7
--- /dev/null
+++ b/src/plugins/simtech/mm-modem-helpers-simtech.h
@@ -0,0 +1,67 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_MODEM_HELPERS_SIMTECH_H
+#define MM_MODEM_HELPERS_SIMTECH_H
+
+#include <glib.h>
+
+#include <ModemManager.h>
+#include <mm-base-bearer.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+/*****************************************************************************/
+/* +CLCC URC helpers */
+
+gboolean mm_simtech_parse_clcc_test (const gchar *response,
+ gboolean *clcc_urcs_supported,
+ GError **error);
+
+GRegex *mm_simtech_get_clcc_urc_regex (void);
+gboolean mm_simtech_parse_clcc_list (const gchar *str,
+ gpointer log_object,
+ GList **out_list,
+ GError **error);
+void mm_simtech_call_info_list_free (GList *call_info_list);
+
+/*****************************************************************************/
+/* VOICE CALL URC helpers */
+
+GRegex *mm_simtech_get_voice_call_urc_regex (void);
+gboolean mm_simtech_parse_voice_call_urc (GMatchInfo *match_info,
+ gboolean *start_or_stop,
+ guint *duration,
+ GError **error);
+
+/*****************************************************************************/
+/* MISSED_CALL URC helpers */
+
+GRegex *mm_simtech_get_missed_call_urc_regex (void);
+gboolean mm_simtech_parse_missed_call_urc (GMatchInfo *match_info,
+ gchar **details,
+ GError **error);
+
+/*****************************************************************************/
+/* Non-standard CRING URC helpers */
+
+GRegex *mm_simtech_get_cring_urc_regex (void);
+
+/*****************************************************************************/
+/* +RXDTMF URC helpers */
+
+GRegex *mm_simtech_get_rxdtmf_urc_regex (void);
+
+#endif /* MM_MODEM_HELPERS_SIMTECH_H */
diff --git a/src/plugins/simtech/mm-plugin-simtech.c b/src/plugins/simtech/mm-plugin-simtech.c
new file mode 100644
index 00000000..9b4f377e
--- /dev/null
+++ b/src/plugins/simtech/mm-plugin-simtech.c
@@ -0,0 +1,98 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-plugin-simtech.h"
+#include "mm-broadband-modem-simtech.h"
+
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi-simtech.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginSimtech, mm_plugin_simtech, 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 SimTech modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_simtech_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_simtech_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", NULL };
+ static const guint16 vendor_ids[] = { 0x1e0e, /* A-Link (for now) */
+ 0 };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_SIMTECH,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_QCDM, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_simtech_init (MMPluginSimtech *self)
+{
+}
+
+static void
+mm_plugin_simtech_class_init (MMPluginSimtechClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/simtech/mm-plugin-simtech.h b/src/plugins/simtech/mm-plugin-simtech.h
new file mode 100644
index 00000000..eab8630c
--- /dev/null
+++ b/src/plugins/simtech/mm-plugin-simtech.h
@@ -0,0 +1,42 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_PLUGIN_SIMTECH_H
+#define MM_PLUGIN_SIMTECH_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_SIMTECH (mm_plugin_simtech_get_type ())
+#define MM_PLUGIN_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_SIMTECH, MMPluginSimtech))
+#define MM_PLUGIN_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_SIMTECH, MMPluginSimtechClass))
+#define MM_IS_PLUGIN_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_SIMTECH))
+#define MM_IS_PLUGIN_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_SIMTECH))
+#define MM_PLUGIN_SIMTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_SIMTECH, MMPluginSimtechClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginSimtech;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginSimtechClass;
+
+GType mm_plugin_simtech_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_SIMTECH_H */
diff --git a/src/plugins/simtech/mm-shared-simtech.c b/src/plugins/simtech/mm-shared-simtech.c
new file mode 100644
index 00000000..99c2346e
--- /dev/null
+++ b/src/plugins/simtech/mm-shared-simtech.c
@@ -0,0 +1,1261 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#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-voice.h"
+#include "mm-iface-modem-location.h"
+#include "mm-base-modem.h"
+#include "mm-base-modem-at.h"
+#include "mm-shared-simtech.h"
+#include "mm-modem-helpers-simtech.h"
+
+/*****************************************************************************/
+/* Private data context */
+
+#define PRIVATE_TAG "shared-simtech-private-tag"
+static GQuark private_quark;
+
+typedef enum {
+ FEATURE_SUPPORT_UNKNOWN,
+ FEATURE_NOT_SUPPORTED,
+ FEATURE_SUPPORTED,
+} FeatureSupport;
+
+typedef struct {
+ /* location */
+ MMIfaceModemLocation *iface_modem_location_parent;
+ MMModemLocationSource supported_sources;
+ MMModemLocationSource enabled_sources;
+ FeatureSupport cgps_support;
+ /* voice */
+ MMIfaceModemVoice *iface_modem_voice_parent;
+ FeatureSupport cpcmreg_support;
+ FeatureSupport clcc_urc_support;
+ GRegex *clcc_urc_regex;
+ GRegex *voice_call_regex;
+ GRegex *missed_call_regex;
+ GRegex *cring_regex;
+ GRegex *rxdtmf_regex;
+} Private;
+
+static void
+private_free (Private *ctx)
+{
+ g_regex_unref (ctx->rxdtmf_regex);
+ g_regex_unref (ctx->cring_regex);
+ g_regex_unref (ctx->missed_call_regex);
+ g_regex_unref (ctx->voice_call_regex);
+ g_regex_unref (ctx->clcc_urc_regex);
+ g_slice_free (Private, ctx);
+}
+
+static Private *
+get_private (MMSharedSimtech *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->supported_sources = MM_MODEM_LOCATION_SOURCE_NONE;
+ priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE;
+ priv->cgps_support = FEATURE_SUPPORT_UNKNOWN;
+ priv->cpcmreg_support = FEATURE_SUPPORT_UNKNOWN;
+ priv->clcc_urc_support = FEATURE_SUPPORT_UNKNOWN;
+ priv->clcc_urc_regex = mm_simtech_get_clcc_urc_regex ();
+ priv->voice_call_regex = mm_simtech_get_voice_call_urc_regex ();
+ priv->missed_call_regex = mm_simtech_get_missed_call_urc_regex ();
+ priv->cring_regex = mm_simtech_get_cring_urc_regex ();
+ priv->rxdtmf_regex = mm_simtech_get_rxdtmf_urc_regex ();
+
+ /* Setup parent class' MMIfaceModemLocation and MMIfaceModemVoice */
+
+ g_assert (MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_location_interface);
+ priv->iface_modem_location_parent = MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_location_interface (self);
+
+ g_assert (MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_voice_interface);
+ priv->iface_modem_voice_parent = MM_SHARED_SIMTECH_GET_INTERFACE (self)->peek_parent_voice_interface (self);
+
+ g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
+ }
+
+ return priv;
+}
+
+/*****************************************************************************/
+/* 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_simtech_location_load_capabilities_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ gssize aux;
+
+ aux = 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) aux;
+}
+
+static void probe_gps_features (GTask *task);
+
+static void
+cgps_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+
+ if (!mm_base_modem_at_command_finish (self, res, NULL))
+ priv->cgps_support = FEATURE_NOT_SUPPORTED;
+ else
+ priv->cgps_support = FEATURE_SUPPORTED;
+
+ probe_gps_features (task);
+}
+
+static void
+probe_gps_features (GTask *task)
+{
+ MMSharedSimtech *self;
+ MMModemLocationSource sources;
+ Private *priv;
+
+ self = MM_SHARED_SIMTECH (g_task_get_source_object (task));
+ priv = get_private (self);
+
+ /* Need to check if CGPS supported... */
+ if (priv->cgps_support == FEATURE_SUPPORT_UNKNOWN) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self), "+CGPS=?", 3, TRUE, (GAsyncReadyCallback) cgps_test_ready, task);
+ return;
+ }
+
+ /* All GPS features probed */
+
+ /* Recover parent sources */
+ sources = GPOINTER_TO_UINT (g_task_get_task_data (task));
+
+ if (priv->cgps_support == FEATURE_SUPPORTED) {
+ mm_obj_dbg (self, "GPS commands supported: GPS capabilities enabled");
+
+ /* We only flag as supported by this implementation those sources not already
+ * supported by the parent implementation */
+ if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_NMEA))
+ priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA;
+ if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_RAW))
+ priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW;
+ if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))
+ priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED;
+
+ sources |= priv->supported_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);
+ } else
+ mm_obj_dbg (self, "no GPS command supported: no GPS capabilities");
+
+ g_task_return_int (task, (gssize) sources);
+ g_object_unref (task);
+}
+
+static void
+parent_load_capabilities_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource sources;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_SIMTECH (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;
+ }
+
+ /* Cache sources supported by the parent */
+ g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL);
+
+ /* Probe all GPS features */
+ probe_gps_features (task);
+}
+
+void
+mm_shared_simtech_location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+ task = g_task_new (self, NULL, callback, user_data);
+
+ g_assert (priv->iface_modem_location_parent);
+ g_assert (priv->iface_modem_location_parent->load_capabilities);
+ g_assert (priv->iface_modem_location_parent->load_capabilities_finish);
+
+ priv->iface_modem_location_parent->load_capabilities (self,
+ (GAsyncReadyCallback)parent_load_capabilities_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Disable location gathering (Location interface) */
+
+gboolean
+mm_shared_simtech_disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+disable_cgps_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource source;
+ Private *priv;
+ GError *error = NULL;
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+
+ mm_base_modem_at_command_finish (self, res, &error);
+
+ /* Only use the GPS port in NMEA/RAW setups */
+ source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task));
+ if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ MMPortSerialGps *gps_port;
+
+ /* Even if we get an error here, we try to close the 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));
+ }
+
+ if (error)
+ g_task_return_error (task, error);
+ else {
+ priv->enabled_sources &= ~source;
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+parent_disable_location_gathering_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+
+ g_assert (priv->iface_modem_location_parent);
+ 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_simtech_disable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMModemLocationSource enabled_sources;
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+ g_assert (priv->iface_modem_location_parent);
+
+ /* Only consider request if it applies to one of the sources we are
+ * supporting, otherwise run parent disable */
+ if (!(priv->supported_sources & source)) {
+ /* If disabling implemented by the parent, run it. */
+ 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)parent_disable_location_gathering_ready,
+ task);
+ return;
+ }
+ /* Otherwise, we're done */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* We only expect GPS sources here */
+ g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED));
+
+ /* Flag as disabled to see how many others we would have left enabled */
+ enabled_sources = priv->enabled_sources;
+ enabled_sources &= ~source;
+
+ /* If there are still GPS-related sources enabled, do nothing else */
+ if (enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ priv->enabled_sources &= ~source;
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Stop GPS engine if all GPS-related sources are disabled */
+ g_assert (priv->cgps_support == FEATURE_SUPPORTED);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CGPS=0",
+ 10,
+ FALSE,
+ (GAsyncReadyCallback) disable_cgps_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Enable location gathering (Location interface) */
+
+gboolean
+mm_shared_simtech_enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+enable_cgps_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource source;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Only use the GPS port in NMEA/RAW setups */
+ source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task));
+ 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");
+ g_object_unref (task);
+ return;
+ }
+ }
+
+ 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_SIMTECH (self));
+
+ g_assert (priv->iface_modem_location_parent);
+ 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_simtech_enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
+
+ priv = get_private (MM_SHARED_SIMTECH (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);
+
+ /* Only consider request if it applies to one of the sources we are
+ * supporting, otherwise run parent enable */
+ if (!(priv->supported_sources & source)) {
+ priv->iface_modem_location_parent->enable_location_gathering (self,
+ source,
+ (GAsyncReadyCallback)parent_enable_location_gathering_ready,
+ task);
+ return;
+ }
+
+ /* We only expect GPS sources here */
+ g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED));
+
+ /* If GPS already started, store new flag and we're done */
+ if (priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ priv->enabled_sources |= source;
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ g_assert (priv->cgps_support == FEATURE_SUPPORTED);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CGPS=1,1",
+ 10,
+ FALSE,
+ (GAsyncReadyCallback) enable_cgps_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Common enable/disable voice unsolicited events */
+
+typedef struct {
+ gboolean enable;
+ MMPortSerialAt *primary;
+ MMPortSerialAt *secondary;
+ gchar *clcc_command;
+ gboolean clcc_primary_done;
+ gboolean clcc_secondary_done;
+} VoiceUnsolicitedEventsContext;
+
+static void
+voice_unsolicited_events_context_free (VoiceUnsolicitedEventsContext *ctx)
+{
+ g_clear_object (&ctx->secondary);
+ g_clear_object (&ctx->primary);
+ g_free (ctx->clcc_command);
+ g_slice_free (VoiceUnsolicitedEventsContext, ctx);
+}
+
+static gboolean
+common_voice_enable_disable_unsolicited_events_finish (MMSharedSimtech *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void run_voice_enable_disable_unsolicited_events (GTask *task);
+
+static void
+clcc_command_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ VoiceUnsolicitedEventsContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ mm_obj_dbg (self, "couldn't %s +CLCC reporting: '%s'",
+ ctx->enable ? "enable" : "disable",
+ error->message);
+ g_error_free (error);
+ }
+
+ /* Continue on next port */
+ run_voice_enable_disable_unsolicited_events (task);
+}
+
+static void
+run_voice_enable_disable_unsolicited_events (GTask *task)
+{
+ MMSharedSimtech *self;
+ Private *priv;
+ VoiceUnsolicitedEventsContext *ctx;
+ MMPortSerialAt *port = NULL;
+
+ self = MM_SHARED_SIMTECH (g_task_get_source_object (task));
+ priv = get_private (self);
+ ctx = g_task_get_task_data (task);
+
+ /* If +CLCC URCs not supported, we're done */
+ if (priv->clcc_urc_support == FEATURE_NOT_SUPPORTED) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ if (!ctx->clcc_primary_done && ctx->primary) {
+ mm_obj_dbg (self, "%s +CLCC extended list of current calls reporting in primary port...",
+ ctx->enable ? "enabling" : "disabling");
+ ctx->clcc_primary_done = TRUE;
+ port = ctx->primary;
+ } else if (!ctx->clcc_secondary_done && ctx->secondary) {
+ mm_obj_dbg (self, "%s +CLCC extended list of current calls reporting in secondary port...",
+ ctx->enable ? "enabling" : "disabling");
+ ctx->clcc_secondary_done = TRUE;
+ port = ctx->secondary;
+ }
+
+ if (port) {
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ port,
+ ctx->clcc_command,
+ 3,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback)clcc_command_ready,
+ task);
+ return;
+ }
+
+ /* Fully done now */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+common_voice_enable_disable_unsolicited_events (MMSharedSimtech *self,
+ gboolean enable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ VoiceUnsolicitedEventsContext *ctx;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_slice_new0 (VoiceUnsolicitedEventsContext);
+ ctx->enable = enable;
+ if (enable)
+ ctx->clcc_command = g_strdup ("+CLCC=1");
+ else
+ ctx->clcc_command = g_strdup ("+CLCC=0");
+ ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+ ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
+ g_task_set_task_data (task, ctx, (GDestroyNotify) voice_unsolicited_events_context_free);
+
+ run_voice_enable_disable_unsolicited_events (task);
+}
+
+/*****************************************************************************/
+/* Disable unsolicited events (Voice interface) */
+
+gboolean
+mm_shared_simtech_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+
+ if (!priv->iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error)) {
+ mm_obj_warn (self, "couldn't disable parent voice unsolicited events: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+voice_disable_unsolicited_events_ready (MMSharedSimtech *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+ GError *error = NULL;
+
+ if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) {
+ mm_obj_warn (self, "couldn't disable Simtech-specific voice unsolicited events: %s", error->message);
+ g_error_free (error);
+ }
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events);
+ g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events_finish);
+
+ /* Chain up parent's disable */
+ priv->iface_modem_voice_parent->disable_unsolicited_events (
+ MM_IFACE_MODEM_VOICE (self),
+ (GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready,
+ task);
+}
+
+void
+mm_shared_simtech_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* our own disabling first */
+ common_voice_enable_disable_unsolicited_events (MM_SHARED_SIMTECH (self),
+ FALSE,
+ (GAsyncReadyCallback) voice_disable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Enable unsolicited events (Voice interface) */
+
+gboolean
+mm_shared_simtech_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+voice_enable_unsolicited_events_ready (MMSharedSimtech *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) {
+ mm_obj_warn (self, "couldn't enable Simtech-specific voice unsolicited events: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+
+ if (!priv->iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error)) {
+ mm_obj_warn (self, "couldn't enable parent voice unsolicited events: %s", error->message);
+ g_error_free (error);
+ }
+
+ /* our own enabling next */
+ common_voice_enable_disable_unsolicited_events (MM_SHARED_SIMTECH (self),
+ TRUE,
+ (GAsyncReadyCallback) voice_enable_unsolicited_events_ready,
+ task);
+}
+
+void
+mm_shared_simtech_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events);
+ g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events_finish);
+
+ /* chain up parent's enable first */
+ priv->iface_modem_voice_parent->enable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Common setup/cleanup voice unsolicited events */
+
+static void
+clcc_urc_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMSharedSimtech *self)
+{
+ gchar *full;
+ GError *error = NULL;
+ GList *call_info_list = NULL;
+
+ full = g_match_info_fetch (match_info, 0);
+
+ if (!mm_simtech_parse_clcc_list (full, self, &call_info_list, &error)) {
+ mm_obj_warn (self, "couldn't parse +CLCC list in URC: %s", error->message);
+ g_error_free (error);
+ } else
+ mm_iface_modem_voice_report_all_calls (MM_IFACE_MODEM_VOICE (self), call_info_list);
+
+ mm_simtech_call_info_list_free (call_info_list);
+ g_free (full);
+}
+
+static void
+missed_call_urc_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMSharedSimtech *self)
+{
+ GError *error = NULL;
+ gchar *details = NULL;
+
+ if (!mm_simtech_parse_missed_call_urc (match_info, &details, &error)) {
+ mm_obj_warn (self, "couldn't parse missed call URC: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ mm_obj_dbg (self, "missed call reported: %s", details);
+ g_free (details);
+}
+
+static void
+voice_call_urc_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMSharedSimtech *self)
+{
+ GError *error = NULL;
+ gboolean start_or_stop = FALSE; /* start = TRUE, stop = FALSE */
+ guint duration = 0;
+
+ if (!mm_simtech_parse_voice_call_urc (match_info, &start_or_stop, &duration, &error)) {
+ mm_obj_warn (self, "couldn't parse voice call URC: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ if (start_or_stop) {
+ mm_obj_dbg (self, "voice call started");
+ return;
+ }
+
+ if (duration) {
+ mm_obj_dbg (self, "voice call finished (duration: %us)", duration);
+ return;
+ }
+
+ mm_obj_dbg (self, "voice call finished");
+}
+
+static void
+cring_urc_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMSharedSimtech *self)
+{
+ MMCallInfo call_info;
+ g_autofree gchar *str = NULL;
+
+ /* We could have "VOICE" or "DATA". Now consider only "VOICE" */
+ str = mm_get_string_unquoted_from_match_info (info, 1);
+ mm_obj_dbg (self, "ringing (%s)", str);
+
+ call_info.index = 0;
+ call_info.direction = MM_CALL_DIRECTION_INCOMING;
+ call_info.state = MM_CALL_STATE_RINGING_IN;
+ call_info.number = NULL;
+
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
+}
+
+static void
+rxdtmf_urc_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMSharedSimtech *self)
+{
+ g_autofree gchar *dtmf = NULL;
+
+ dtmf = g_match_info_fetch (match_info, 1);
+ mm_obj_dbg (self, "received DTMF: %s", dtmf);
+ /* call index unknown */
+ mm_iface_modem_voice_received_dtmf (MM_IFACE_MODEM_VOICE (self), 0, dtmf);
+}
+
+static void
+common_voice_setup_cleanup_unsolicited_events (MMSharedSimtech *self,
+ gboolean enable)
+{
+ Private *priv;
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ priv = get_private (MM_SHARED_SIMTECH (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));
+
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ if (priv->clcc_urc_support == FEATURE_SUPPORTED)
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ priv->clcc_urc_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)clcc_urc_received : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ priv->voice_call_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)voice_call_urc_received : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ priv->missed_call_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)missed_call_urc_received : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ priv->cring_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)cring_urc_received : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ priv->rxdtmf_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)rxdtmf_urc_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+/*****************************************************************************/
+/* Cleanup unsolicited events (Voice interface) */
+
+gboolean
+mm_shared_simtech_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+
+ if (!priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error)) {
+ mm_obj_warn (self, "couldn't cleanup parent voice unsolicited events: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_simtech_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events);
+ g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish);
+
+ /* our own cleanup first */
+ common_voice_setup_cleanup_unsolicited_events (MM_SHARED_SIMTECH (self), FALSE);
+
+ /* Chain up parent's cleanup */
+ priv->iface_modem_voice_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup unsolicited events (Voice interface) */
+
+gboolean
+mm_shared_simtech_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+
+ if (!priv->iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) {
+ mm_obj_warn (self, "couldn't setup parent voice unsolicited events: %s", error->message);
+ g_error_free (error);
+ }
+
+ /* our own setup next */
+ common_voice_setup_cleanup_unsolicited_events (MM_SHARED_SIMTECH (self), TRUE);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_simtech_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events);
+ g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events_finish);
+
+ /* chain up parent's setup first */
+ priv->iface_modem_voice_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* In-call audio channel setup/cleanup */
+
+gboolean
+mm_shared_simtech_voice_setup_in_call_audio_channel_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ MMPort **audio_port, /* optional */
+ MMCallAudioFormat **audio_format, /* optional */
+ GError **error)
+{
+ Private *priv;
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return FALSE;
+
+ if (audio_format)
+ *audio_format = NULL;
+
+ if (audio_port) {
+ if (priv->cpcmreg_support == FEATURE_SUPPORTED)
+ *audio_port = MM_PORT (mm_base_modem_get_port_audio (MM_BASE_MODEM (self)));
+ else
+ *audio_port = NULL;
+ }
+
+ return TRUE;
+}
+
+gboolean
+mm_shared_simtech_voice_cleanup_in_call_audio_channel_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+cpcmreg_set_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
+common_setup_cleanup_in_call_audio_channel (MMSharedSimtech *self,
+ gboolean setup,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Do nothing if CPCMREG isn't supported */
+ if (priv->cpcmreg_support != FEATURE_SUPPORTED) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ setup ? "+CPCMREG=1" : "+CPCMREG=0",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) cpcmreg_set_ready,
+ task);
+}
+
+void
+mm_shared_simtech_voice_setup_in_call_audio_channel (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_setup_cleanup_in_call_audio_channel (MM_SHARED_SIMTECH (self), TRUE, callback, user_data);
+}
+
+void
+mm_shared_simtech_voice_cleanup_in_call_audio_channel (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_setup_cleanup_in_call_audio_channel (MM_SHARED_SIMTECH (self), FALSE, callback, user_data);
+}
+
+/*****************************************************************************/
+/* Check if Voice supported (Voice interface) */
+
+gboolean
+mm_shared_simtech_voice_check_support_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+cpcmreg_format_check_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+
+ priv->cpcmreg_support = (mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL) ?
+ FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED);
+ mm_obj_dbg (self, "modem %s USB audio control", (priv->cpcmreg_support == FEATURE_SUPPORTED) ? "supports" : "doesn't support");
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+clcc_format_check_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+ GError *error = NULL;
+ const gchar *response;
+ gboolean clcc_urc_supported = FALSE;
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL);
+ if (response && !mm_simtech_parse_clcc_test (response, &clcc_urc_supported, &error)) {
+ mm_obj_dbg (self, "failed checking CLCC URC support: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ priv->clcc_urc_support = (clcc_urc_supported ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED);
+ mm_obj_dbg (self, "modem %s +CLCC URCs", (priv->clcc_urc_support == FEATURE_SUPPORTED) ? "supports" : "doesn't support");
+
+ /* If +CLCC URC supported we won't need polling in the parent */
+ g_object_set (self,
+ MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED, (priv->clcc_urc_support == FEATURE_SUPPORTED),
+ NULL);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CPCMREG=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback) cpcmreg_format_check_ready,
+ task);
+}
+
+static void
+parent_voice_check_support_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ Private *priv;
+ GError *error = NULL;
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+ if (!priv->iface_modem_voice_parent->check_support_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* voice is supported, check if +CLCC URCs are available */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CLCC=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback) clcc_format_check_ready,
+ task);
+}
+
+void
+mm_shared_simtech_voice_check_support (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_SIMTECH (self));
+ g_assert (priv->iface_modem_voice_parent);
+ g_assert (priv->iface_modem_voice_parent->check_support);
+ g_assert (priv->iface_modem_voice_parent->check_support_finish);
+
+ /* chain up parent's setup first */
+ priv->iface_modem_voice_parent->check_support (
+ self,
+ (GAsyncReadyCallback)parent_voice_check_support_ready,
+ task);
+}
+
+/*****************************************************************************/
+
+static void
+shared_simtech_init (gpointer g_iface)
+{
+}
+
+GType
+mm_shared_simtech_get_type (void)
+{
+ static GType shared_simtech_type = 0;
+
+ if (!G_UNLIKELY (shared_simtech_type)) {
+ static const GTypeInfo info = {
+ sizeof (MMSharedSimtech), /* class_size */
+ shared_simtech_init, /* base_init */
+ NULL, /* base_finalize */
+ };
+
+ shared_simtech_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedSimtech", &info, 0);
+ g_type_interface_add_prerequisite (shared_simtech_type, MM_TYPE_IFACE_MODEM_LOCATION);
+ }
+
+ return shared_simtech_type;
+}
diff --git a/src/plugins/simtech/mm-shared-simtech.h b/src/plugins/simtech/mm-shared-simtech.h
new file mode 100644
index 00000000..37a221ca
--- /dev/null
+++ b/src/plugins/simtech/mm-shared-simtech.h
@@ -0,0 +1,129 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_SHARED_SIMTECH_H
+#define MM_SHARED_SIMTECH_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-location.h"
+#include "mm-iface-modem-voice.h"
+
+#define MM_TYPE_SHARED_SIMTECH (mm_shared_simtech_get_type ())
+#define MM_SHARED_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_SIMTECH, MMSharedSimtech))
+#define MM_IS_SHARED_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_SIMTECH))
+#define MM_SHARED_SIMTECH_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_SIMTECH, MMSharedSimtech))
+
+typedef struct _MMSharedSimtech MMSharedSimtech;
+
+struct _MMSharedSimtech {
+ GTypeInterface g_iface;
+
+ /* Peek location interface of the parent class of the object */
+ MMIfaceModemLocation * (* peek_parent_location_interface) (MMSharedSimtech *self);
+
+ /* Peek voice interface of the parent class of the object */
+ MMIfaceModemVoice * (* peek_parent_voice_interface) (MMSharedSimtech *self);
+};
+
+GType mm_shared_simtech_get_type (void);
+
+/*****************************************************************************/
+/* Location interface */
+
+void mm_shared_simtech_location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMModemLocationSource mm_shared_simtech_location_load_capabilities_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_simtech_enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_simtech_enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_simtech_disable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_simtech_disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+
+
+/*****************************************************************************/
+/* Voice interface */
+
+void mm_shared_simtech_voice_check_support (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_simtech_voice_check_support_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_simtech_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_simtech_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_simtech_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_simtech_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_simtech_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_simtech_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_simtech_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_simtech_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_simtech_voice_setup_in_call_audio_channel (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_simtech_voice_setup_in_call_audio_channel_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ MMPort **audio_port, /* optional */
+ MMCallAudioFormat **audio_format, /* optional */
+ GError **error);
+void mm_shared_simtech_voice_cleanup_in_call_audio_channel (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_simtech_voice_cleanup_in_call_audio_channel_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SHARED_SIMTECH_H */
diff --git a/src/plugins/simtech/tests/test-modem-helpers-simtech.c b/src/plugins/simtech/tests/test-modem-helpers-simtech.c
new file mode 100644
index 00000000..ba6532cc
--- /dev/null
+++ b/src/plugins/simtech/tests/test-modem-helpers-simtech.c
@@ -0,0 +1,324 @@
+/* -*- 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) 2019 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 "mm-log-test.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-simtech.h"
+
+/*****************************************************************************/
+/* Test +CLCC URCs */
+
+static void
+common_test_clcc_urc (const gchar *urc,
+ const MMCallInfo *expected_call_info_list,
+ guint expected_call_info_list_size)
+{
+ g_autoptr(GRegex) clcc_regex = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autofree gchar *str = NULL;
+ GError *error = NULL;
+ GList *call_info_list = NULL;
+ GList *l;
+ gboolean result;
+
+ clcc_regex = mm_simtech_get_clcc_urc_regex ();
+
+ /* Same matching logic as done in MMSerialPortAt when processing URCs! */
+ result = g_regex_match_full (clcc_regex, urc, -1, 0, 0, &match_info, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ /* read full matched content */
+ str = g_match_info_fetch (match_info, 0);
+ g_assert (str);
+
+ result = mm_simtech_parse_clcc_list (str, NULL, &call_info_list, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ g_debug ("found %u calls", g_list_length (call_info_list));
+
+ if (expected_call_info_list) {
+ g_assert (call_info_list);
+ g_assert_cmpuint (g_list_length (call_info_list), ==, expected_call_info_list_size);
+ } else
+ g_assert (!call_info_list);
+
+ for (l = call_info_list; l; l = g_list_next (l)) {
+ const MMCallInfo *call_info = (const MMCallInfo *)(l->data);
+ gboolean found = FALSE;
+ guint i;
+
+ g_debug ("call at index %u: direction %s, state %s, number %s",
+ call_info->index,
+ mm_call_direction_get_string (call_info->direction),
+ mm_call_state_get_string (call_info->state),
+ call_info->number ? call_info->number : "n/a");
+
+ for (i = 0; !found && i < expected_call_info_list_size; i++)
+ found = ((call_info->index == expected_call_info_list[i].index) &&
+ (call_info->direction == expected_call_info_list[i].direction) &&
+ (call_info->state == expected_call_info_list[i].state) &&
+ (g_strcmp0 (call_info->number, expected_call_info_list[i].number) == 0));
+
+ g_assert (found);
+ }
+
+ mm_simtech_call_info_list_free (call_info_list);
+}
+
+static void
+test_clcc_urc_single (void)
+{
+ static const MMCallInfo expected_call_info_list[] = {
+ { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "123456789" }
+ };
+
+ const gchar *urc =
+ "\r\n+CLCC: 1,1,0,0,0,\"123456789\",161"
+ "\r\n";
+
+ common_test_clcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
+}
+
+static void
+test_clcc_urc_multiple (void)
+{
+ static const MMCallInfo expected_call_info_list[] = {
+ { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, NULL },
+ { 2, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "123456789" },
+ { 3, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "987654321" },
+ };
+
+ const gchar *urc =
+ "\r\n+CLCC: 1,1,0,0,0" /* number unknown */
+ "\r\n+CLCC: 2,1,0,0,0,\"123456789\",161"
+ "\r\n+CLCC: 3,1,0,0,0,\"987654321\",161,\"Alice\""
+ "\r\n";
+
+ common_test_clcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
+}
+
+static void
+test_clcc_urc_complex (void)
+{
+ static const MMCallInfo expected_call_info_list[] = {
+ { 1, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_ACTIVE, (gchar *) "123456789" },
+ { 2, MM_CALL_DIRECTION_INCOMING, MM_CALL_STATE_WAITING, (gchar *) "987654321" },
+ };
+
+ const gchar *urc =
+ "\r\n^CIEV: 1,0" /* some different URC before our match */
+ "\r\n+CLCC: 1,1,0,0,0,\"123456789\",161"
+ "\r\n+CLCC: 2,1,5,0,0,\"987654321\",161"
+ "\r\n^CIEV: 1,0" /* some different URC after our match */
+ "\r\n";
+
+ common_test_clcc_urc (urc, expected_call_info_list, G_N_ELEMENTS (expected_call_info_list));
+}
+
+/*****************************************************************************/
+
+static void
+common_test_voice_call_urc (const gchar *urc,
+ gboolean expected_start_or_stop,
+ guint expected_duration)
+{
+ g_autoptr(GRegex) voice_call_regex = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *error = NULL;
+ gboolean start_or_stop = FALSE; /* start = TRUE, stop = FALSE */
+ guint duration = 0;
+ gboolean result;
+
+ voice_call_regex = mm_simtech_get_voice_call_urc_regex ();
+
+ /* Same matching logic as done in MMSerialPortAt when processing URCs! */
+ result = g_regex_match_full (voice_call_regex, urc, -1, 0, 0, &match_info, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ result = mm_simtech_parse_voice_call_urc (match_info, &start_or_stop, &duration, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ g_assert_cmpuint (expected_start_or_stop, ==, start_or_stop);
+ g_assert_cmpuint (expected_duration, ==, duration);
+}
+
+static void
+test_voice_call_begin_urc (void)
+{
+ common_test_voice_call_urc ("\r\nVOICE CALL: BEGIN\r\n", TRUE, 0);
+}
+
+static void
+test_voice_call_end_urc (void)
+{
+ common_test_voice_call_urc ("\r\nVOICE CALL: END\r\n", FALSE, 0);
+}
+
+static void
+test_voice_call_end_duration_urc (void)
+{
+ common_test_voice_call_urc ("\r\nVOICE CALL: END: 000041\r\n", FALSE, 41);
+}
+
+/*****************************************************************************/
+
+static void
+common_test_missed_call_urc (const gchar *urc,
+ const gchar *expected_details)
+{
+ g_autoptr(GRegex) missed_call_regex = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autofree gchar *details = NULL;
+ GError *error = NULL;
+ gboolean result;
+
+ missed_call_regex = mm_simtech_get_missed_call_urc_regex ();
+
+ /* Same matching logic as done in MMSerialPortAt when processing URCs! */
+ result = g_regex_match_full (missed_call_regex, urc, -1, 0, 0, &match_info, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ result = mm_simtech_parse_missed_call_urc (match_info, &details, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ g_assert_cmpstr (expected_details, ==, details);
+}
+
+static void
+test_missed_call_urc (void)
+{
+ common_test_missed_call_urc ("\r\nMISSED_CALL: 11:01AM 07712345678\r\n", "11:01AM 07712345678");
+}
+
+/*****************************************************************************/
+
+static void
+common_test_cring_urc (const gchar *urc,
+ const gchar *expected_type)
+{
+ g_autoptr(GRegex) cring_regex = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autofree gchar *type = NULL;
+ GError *error = NULL;
+ gboolean result;
+
+ cring_regex = mm_simtech_get_cring_urc_regex ();
+
+ /* Same matching logic as done in MMSerialPortAt when processing URCs! */
+ result = g_regex_match_full (cring_regex, urc, -1, 0, 0, &match_info, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ type = g_match_info_fetch (match_info, 1);
+ g_assert (type);
+
+ g_assert_cmpstr (type, ==, expected_type);
+}
+
+static void
+test_cring_urc_two_crs (void)
+{
+ common_test_cring_urc ("\r\r\n+CRING: VOICE\r\r\n", "VOICE");
+}
+
+static void
+test_cring_urc_one_cr (void)
+{
+ common_test_cring_urc ("\r\n+CRING: VOICE\r\n", "VOICE");
+}
+
+/*****************************************************************************/
+
+static void
+common_test_rxdtmf_urc (const gchar *urc,
+ const gchar *expected_str)
+{
+ g_autoptr(GRegex) rxdtmf_regex = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autofree gchar *type = NULL;
+ GError *error = NULL;
+ gboolean result;
+
+ rxdtmf_regex = mm_simtech_get_rxdtmf_urc_regex ();
+
+ /* Same matching logic as done in MMSerialPortAt when processing URCs! */
+ result = g_regex_match_full (rxdtmf_regex, urc, -1, 0, 0, &match_info, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ type = g_match_info_fetch (match_info, 1);
+ g_assert (type);
+
+ g_assert_cmpstr (type, ==, expected_str);
+}
+
+static void
+test_rxdtmf_urc_two_crs (void)
+{
+ common_test_rxdtmf_urc ("\r\r\n+RXDTMF: 8\r\r\n", "8");
+ common_test_rxdtmf_urc ("\r\r\n+RXDTMF: *\r\r\n", "*");
+ common_test_rxdtmf_urc ("\r\r\n+RXDTMF: #\r\r\n", "#");
+ common_test_rxdtmf_urc ("\r\r\n+RXDTMF: A\r\r\n", "A");
+}
+
+static void
+test_rxdtmf_urc_one_cr (void)
+{
+ common_test_rxdtmf_urc ("\r\n+RXDTMF: 8\r\n", "8");
+ common_test_rxdtmf_urc ("\r\n+RXDTMF: *\r\n", "*");
+ common_test_rxdtmf_urc ("\r\n+RXDTMF: #\r\n", "#");
+ common_test_rxdtmf_urc ("\r\n+RXDTMF: A\r\n", "A");
+}
+
+/*****************************************************************************/
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/simtech/clcc/urc/single", test_clcc_urc_single);
+ g_test_add_func ("/MM/simtech/clcc/urc/multiple", test_clcc_urc_multiple);
+ g_test_add_func ("/MM/simtech/clcc/urc/complex", test_clcc_urc_complex);
+
+ g_test_add_func ("/MM/simtech/voicecall/urc/begin", test_voice_call_begin_urc);
+ g_test_add_func ("/MM/simtech/voicecall/urc/end", test_voice_call_end_urc);
+ g_test_add_func ("/MM/simtech/voicecall/urc/end-duration", test_voice_call_end_duration_urc);
+
+ g_test_add_func ("/MM/simtech/missedcall/urc", test_missed_call_urc);
+
+ g_test_add_func ("/MM/simtech/cring/urc/two-crs", test_cring_urc_two_crs);
+ g_test_add_func ("/MM/simtech/cring/urc/one-cr", test_cring_urc_one_cr);
+
+ g_test_add_func ("/MM/simtech/rxdtmf/urc/two-crs", test_rxdtmf_urc_two_crs);
+ g_test_add_func ("/MM/simtech/rxdtmf/urc/one-cr", test_rxdtmf_urc_one_cr);
+
+ return g_test_run ();
+}
diff --git a/src/plugins/symbol.map b/src/plugins/symbol.map
new file mode 100644
index 00000000..b2c9f9cf
--- /dev/null
+++ b/src/plugins/symbol.map
@@ -0,0 +1,8 @@
+{
+global:
+ mm_plugin_major_version*;
+ mm_plugin_minor_version*;
+ mm_plugin_create*;
+local:
+ *;
+};
diff --git a/src/plugins/telit/77-mm-telit-port-types.rules b/src/plugins/telit/77-mm-telit-port-types.rules
new file mode 100644
index 00000000..b9439ffc
--- /dev/null
+++ b/src/plugins/telit/77-mm-telit-port-types.rules
@@ -0,0 +1,146 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_telit_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1bc7", GOTO="mm_telit_generic"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="8087", GOTO="mm_telit_intel"
+GOTO="mm_telit_end"
+
+LABEL="mm_telit_generic"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# UC864-E, UC864-E-AUTO, UC864-K, UC864-WD, UC864-WDU
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1003", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1003", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# UC864-G
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1004", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1004", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1004", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# CC864-DUAL
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1005", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1005", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1005", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# CC864-SINGLE, CC864-KPS
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1006", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1006", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# DE910-DUAL
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+# CE910-DUAL
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1011", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+# LE910C1-EUX
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1031", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1031", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1031", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# LE910C1-EUX (ECM composition)
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1033", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1033", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1033", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# LE922, LM9x0
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+
+# LE922, LM9x0 (MBIM composition)
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{.MM_USBIFNUM}=="07", ENV{ID_MM_PORT_IGNORE}="1"
+
+# FN980
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+
+# FN980 (MBIM composition)
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{.MM_USBIFNUM}=="07", ENV{ID_MM_PORT_IGNORE}="1"
+
+# LN920
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+
+# LN920 (MBIM composition)
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{.MM_USBIFNUM}=="07", ENV{ID_MM_PORT_IGNORE}="1"
+
+# LE910C1 with default usb cfg
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1201", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1201", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1201", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1201", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1201", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+
+# LE910C1 (MBIM)
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1204", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1204", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1204", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1204", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1204", ENV{.MM_USBIFNUM}=="07", ENV{ID_MM_PORT_IGNORE}="1"
+
+# ME910C1
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1101", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1101", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1101", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# MEx10G1
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="110a", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_TYPE_QCDM}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="110a", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="110a", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# LE910S1 (RNDIS)
+# The following port is ignored since it's a diagnostic port for collecting proprietary modem traces (not QCDM)
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7010", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7010", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7010", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+# LE910S1 (ECM)
+# The following port is ignored since it's a diagnostic port for collecting proprietary modem traces (not QCDM)
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7011", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7011", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="7011", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+# LM940/960 initial port delay
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{ID_MM_TELIT_PORT_DELAY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1041", ENV{ID_MM_TELIT_PORT_DELAY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1042", ENV{ID_MM_TELIT_PORT_DELAY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1043", ENV{ID_MM_TELIT_PORT_DELAY}="1"
+
+# FN980 initial port delay
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1050", ENV{ID_MM_TELIT_PORT_DELAY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1051", ENV{ID_MM_TELIT_PORT_DELAY}="1"
+
+# LN920 initial port delay
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1060", ENV{ID_MM_TELIT_PORT_DELAY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1061", ENV{ID_MM_TELIT_PORT_DELAY}="1"
+
+GOTO="mm_telit_end"
+
+LABEL="mm_telit_intel"
+
+# Telit LN930, generic Intel vid:pid in MBIM mode
+ATTRS{idVendor}=="8087", ATTRS{idProduct}=="0911", ENV{ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED}="1"
+
+LABEL="mm_telit_end"
diff --git a/src/plugins/telit/mm-broadband-modem-mbim-telit.c b/src/plugins/telit/mm-broadband-modem-mbim-telit.c
new file mode 100644
index 00000000..8437c841
--- /dev/null
+++ b/src/plugins/telit/mm-broadband-modem-mbim-telit.c
@@ -0,0 +1,242 @@
+/* -*- 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) 2019 Daniele Palmas <dnlplm@gmail.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-iface-modem.h"
+#include "mm-base-modem-at.h"
+#include "mm-broadband-modem-mbim-telit.h"
+#include "mm-modem-helpers-telit.h"
+#include "mm-shared-telit.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void shared_telit_init (MMSharedTelit *iface);
+
+static MMIfaceModem *iface_modem_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimTelit, mm_broadband_modem_mbim_telit, MM_TYPE_BROADBAND_MODEM_MBIM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_TELIT, shared_telit_init))
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return (GArray *) g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_supported_modes_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemModeCombination modes_combination;
+ MMModemMode modes_mask = MM_MODEM_MODE_NONE;
+ const gchar *response;
+ GArray *modes;
+ GArray *all;
+ GArray *combinations;
+ GArray *filtered;
+ GError *error = NULL;
+ MMSharedTelit *shared = MM_SHARED_TELIT (self);
+ guint i;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ g_prefix_error (&error, "generic query of supported 3GPP networks with WS46=? failed: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ modes = mm_3gpp_parse_ws46_test_response (response, self, &error);
+ if (!modes) {
+ g_prefix_error (&error, "parsing WS46=? response failed: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ for (i = 0; i < modes->len; i++) {
+ MMModemMode mode;
+ g_autofree gchar *str = NULL;
+
+ mode = g_array_index (modes, MMModemMode, i);
+
+ modes_mask |= mode;
+
+ str = mm_modem_mode_build_string_from_mask (mode);
+ mm_obj_dbg (self, "device allows (3GPP) mode combination: %s", str);
+ }
+
+ g_array_unref (modes);
+
+ /* Build a mask with all supported modes */
+ all = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
+ modes_combination.allowed = modes_mask;
+ modes_combination.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (all, modes_combination);
+
+ /* Filter out those unsupported modes */
+ combinations = mm_telit_build_modes_list();
+ filtered = mm_filter_supported_modes (all, combinations, self);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ mm_shared_telit_store_supported_modes (shared, filtered);
+ g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_supported_modes (MMIfaceModem *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),
+ "+WS46=?",
+ 3,
+ TRUE, /* allow caching, it's a test command */
+ (GAsyncReadyCallback) load_supported_modes_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load revision (Modem interface) */
+
+static gchar *
+load_revision_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_load_revision_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ gchar *revision = NULL;
+
+ revision = iface_modem_parent->load_revision_finish (self, res, &error);
+ if (!revision) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+ mm_shared_telit_store_revision (MM_SHARED_TELIT (self), revision);
+ g_task_return_pointer (task, revision, g_free);
+ g_object_unref (task);
+}
+
+static void
+load_revision (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Run parent's loading */
+ /* Telit's custom revision loading (in telit/mm-shared) is AT-only and the
+ * MBIM modem might not have an AT port available, so we call the parent's
+ * load_revision and store the revision taken from the firmware info capabilities. */
+ iface_modem_parent->load_revision (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_revision_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemMbimTelit *
+mm_broadband_modem_mbim_telit_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id,
+ guint16 subsystem_vendor_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM_TELIT,
+ 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,
+ MM_BASE_MODEM_SUBSYSTEM_VENDOR_ID, subsystem_vendor_id,
+ /* 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_telit_init (MMBroadbandModemMbimTelit *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->set_current_bands = mm_shared_telit_modem_set_current_bands;
+ iface->set_current_bands_finish = mm_shared_telit_modem_set_current_bands_finish;
+ iface->load_current_bands = mm_shared_telit_modem_load_current_bands;
+ iface->load_current_bands_finish = mm_shared_telit_modem_load_current_bands_finish;
+ iface->load_supported_bands = mm_shared_telit_modem_load_supported_bands;
+ iface->load_supported_bands_finish = mm_shared_telit_modem_load_supported_bands_finish;
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = mm_shared_telit_load_current_modes;
+ iface->load_current_modes_finish = mm_shared_telit_load_current_modes_finish;
+ iface->set_current_modes = mm_shared_telit_set_current_modes;
+ iface->set_current_modes_finish = mm_shared_telit_set_current_modes_finish;
+ iface->load_revision_finish = load_revision_finish;
+ iface->load_revision = load_revision;
+}
+
+static MMIfaceModem *
+peek_parent_modem_interface (MMSharedTelit *self)
+{
+ return iface_modem_parent;
+}
+
+static void
+shared_telit_init (MMSharedTelit *iface)
+{
+ iface->peek_parent_modem_interface = peek_parent_modem_interface;
+}
+
+static void
+mm_broadband_modem_mbim_telit_class_init (MMBroadbandModemMbimTelitClass *klass)
+{
+}
diff --git a/src/plugins/telit/mm-broadband-modem-mbim-telit.h b/src/plugins/telit/mm-broadband-modem-mbim-telit.h
new file mode 100644
index 00000000..50c21e20
--- /dev/null
+++ b/src/plugins/telit/mm-broadband-modem-mbim-telit.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) 2019 Daniele Palmas <dnlplm@gmail.com>
+ */
+
+#ifndef MM_BROADBAND_MODEM_MBIM_TELIT_H
+#define MM_BROADBAND_MODEM_MBIM_TELIT_H
+
+#include "mm-broadband-modem-mbim.h"
+
+#define MM_TYPE_BROADBAND_MODEM_MBIM_TELIT (mm_broadband_modem_mbim_telit_get_type ())
+#define MM_BROADBAND_MODEM_MBIM_TELIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_TELIT, MMBroadbandModemMbimTelit))
+#define MM_BROADBAND_MODEM_MBIM_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_TELIT, MMBroadbandModemMbimTelitClass))
+#define MM_IS_BROADBAND_MODEM_MBIM_TELIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_TELIT))
+#define MM_IS_BROADBAND_MODEM_MBIM_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_TELIT))
+#define MM_BROADBAND_MODEM_MBIM_TELIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_TELIT, MMBroadbandModemMbimTelitClass))
+
+typedef struct _MMBroadbandModemMbimTelit MMBroadbandModemMbimTelit;
+typedef struct _MMBroadbandModemMbimTelitClass MMBroadbandModemMbimTelitClass;
+
+struct _MMBroadbandModemMbimTelit {
+ MMBroadbandModemMbim parent;
+};
+
+struct _MMBroadbandModemMbimTelitClass{
+ MMBroadbandModemMbimClass parent;
+};
+
+GType mm_broadband_modem_mbim_telit_get_type (void);
+
+MMBroadbandModemMbimTelit *mm_broadband_modem_mbim_telit_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id,
+ guint16 subsystem_vendor_id);
+
+#endif /* MM_BROADBAND_MODEM_TELIT_H */
diff --git a/src/plugins/telit/mm-broadband-modem-telit.c b/src/plugins/telit/mm-broadband-modem-telit.c
new file mode 100644
index 00000000..1683d38a
--- /dev/null
+++ b/src/plugins/telit/mm-broadband-modem-telit.c
@@ -0,0 +1,1562 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log-object.h"
+#include "mm-errors-types.h"
+#include "mm-modem-helpers.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-location.h"
+#include "mm-broadband-modem-telit.h"
+#include "mm-modem-helpers-telit.h"
+#include "mm-telit-enums-types.h"
+#include "mm-shared-telit.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void shared_telit_init (MMSharedTelit *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+static MMIfaceModemLocation *iface_modem_location_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemTelit, mm_broadband_modem_telit, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_TELIT, shared_telit_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init));
+
+#define CSIM_UNLOCK_MAX_TIMEOUT 3
+
+typedef enum {
+ FEATURE_SUPPORT_UNKNOWN,
+ FEATURE_NOT_SUPPORTED,
+ FEATURE_SUPPORTED
+} FeatureSupport;
+
+struct _MMBroadbandModemTelitPrivate {
+ FeatureSupport csim_lock_support;
+ MMTelitQssStatus qss_status;
+ MMTelitCsimLockState csim_lock_state;
+ GTask *csim_lock_task;
+ guint csim_lock_timeout_id;
+ gboolean parse_qss;
+ MMModemLocationSource enabled_sources;
+};
+
+typedef struct {
+ MMModemLocationSource source;
+ guint gps_enable_step;
+} LocationGatheringContext;
+
+/*
+ * AT$GPSNMUN
+ * enable: 0 NMEA stream disabled (default)
+ * 1 NMEA stream enabled in the form $GPSNMUN: <nmea sentence><CR>
+ * 2 NMEA stream enabled in the form <nmea sentence><CR>
+ * 3 dedicated NMEA stream
+ * GGA: 0 disable (default), 1 enable
+ * GLL: 0 disable (default), 1 enable
+ * GSA: 0 disable (default), 1 enable
+ * GSV: 0 disable (default), 1 enable
+ * RMC: 0 disable (default), 1 enable
+ * VTG: 0 disable (default), 1 enable
+ */
+static const gchar *gps_enable[] = {
+ "$GPSP=1",
+ "$GPSNMUN=2,1,1,1,1,1,1"
+};
+
+static gboolean
+disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+gps_disabled_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LocationGatheringContext *ctx;
+ MMPortSerialGps *gps_port;
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (self, res, &error);
+ ctx = g_task_get_task_data (task);
+ /* Only use the GPS port in NMEA/RAW setups */
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ /* Even if we get an error here, we try to close the GPS port */
+ gps_port = mm_base_modem_peek_port_gps (self);
+ if (gps_port)
+ mm_port_serial_close (MM_PORT_SERIAL (gps_port));
+ }
+
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+disable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemTelit *telit = MM_BROADBAND_MODEM_TELIT (self);
+ gboolean stop_gps = FALSE;
+ LocationGatheringContext *ctx;
+ GTask *task;
+
+ ctx = g_new (LocationGatheringContext, 1);
+ ctx->source = source;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ /* Only stop GPS engine if no GPS-related sources enabled */
+ if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ telit->priv->enabled_sources &= ~source;
+
+ if (!(telit->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)))
+ stop_gps = TRUE;
+ }
+
+ if (stop_gps) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "$GPSP=0",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)gps_disabled_ready,
+ task);
+ return;
+ }
+ /* For any other location (e.g. 3GPP), or if still some GPS needed, just return */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+gps_enabled_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LocationGatheringContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ g_prefix_error (&error, "couldn't power up GNSS controller: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+ /* After Receiver was powered up we still have to enable unsolicited NMEA events */
+ if (ctx->gps_enable_step < G_N_ELEMENTS (gps_enable)) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ gps_enable[ctx->gps_enable_step++],
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)gps_enabled_ready,
+ task);
+ return;
+ }
+
+ mm_obj_dbg (self, "GNSS controller is powered up");
+
+ /* Only use the GPS port in NMEA/RAW setups */
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ MMPortSerialGps *gps_port;
+
+ gps_port = mm_base_modem_peek_port_gps (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
+ g_task_return_boolean (task, TRUE);
+ } else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+parent_enable_location_gathering_ready (MMIfaceModemLocation *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemTelit *self = MM_BROADBAND_MODEM_TELIT (_self);
+ LocationGatheringContext *ctx;
+ gboolean start_gps = FALSE;
+ GError *error = NULL;
+
+ if (!iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+ /* Now our own enabling */
+ ctx = g_task_get_task_data (task);
+
+ /* NMEA, RAW and UNMANAGED are all enabled in the same way */
+ if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) {
+ /* Only start GPS engine if not done already */
+ if (!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)))
+ start_gps = TRUE;
+ self->priv->enabled_sources |= ctx->source;
+ }
+
+ if (start_gps && ctx->gps_enable_step < G_N_ELEMENTS (gps_enable)) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ gps_enable[ctx->gps_enable_step++],
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)gps_enabled_ready,
+ task);
+ return;
+ }
+ /* For any other location (e.g. 3GPP), or if GPS already running just return */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ LocationGatheringContext *ctx;
+ GTask *task;
+
+ ctx = g_new (LocationGatheringContext, 1);
+ ctx->source = source;
+ ctx->gps_enable_step = 0;
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ /* Chain up parent's gathering enable */
+ iface_modem_location_parent->enable_location_gathering (
+ self,
+ source,
+ (GAsyncReadyCallback)parent_enable_location_gathering_ready,
+ task);
+}
+
+static void
+trace_received (MMPortSerialGps *port,
+ const gchar *trace,
+ MMIfaceModemLocation *self)
+{
+ mm_iface_modem_location_gps_update (self, trace);
+}
+
+static gboolean
+enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ MMPortSerialGps *gps_data_port;
+
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_telit_parent_class)->setup_ports (self);
+
+ gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self));
+ if (gps_data_port) {
+ /* It may happen that the modem was started with GPS already enabled,
+ * in this case GPSP AT command returns always error. Disable it for consistency
+ */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "$GPSP=0", 3, FALSE, FALSE, NULL);
+
+ /* Add handler for the NMEA traces */
+ mm_port_serial_gps_add_trace_handler (gps_data_port,
+ (MMPortSerialGpsTraceFn)trace_received,
+ self,
+ NULL);
+ }
+}
+
+static MMModemLocationSource
+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
+gpsp_test_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ MMModemLocationSource sources;
+
+ sources = GPOINTER_TO_UINT (g_task_get_task_data (task));
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ mm_obj_dbg (self, "GPS controller not supported: %s", error->message);
+ g_clear_error (&error);
+ } else if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)))
+ sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED);
+
+ g_task_return_int (task, sources);
+ g_object_unref (task);
+}
+
+static void
+parent_load_capabilities_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource sources;
+ GError *error = NULL;
+
+ sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+ g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "$GPSP=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)gpsp_test_ready,
+ task);
+}
+
+static void
+location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's setup */
+ iface_modem_location_parent->load_capabilities (
+ self,
+ (GAsyncReadyCallback)parent_load_capabilities_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Setup SIM hot swap (Modem interface) */
+
+typedef enum {
+ QSS_SETUP_STEP_FIRST,
+ QSS_SETUP_STEP_QUERY,
+ QSS_SETUP_STEP_ENABLE_PRIMARY_PORT,
+ QSS_SETUP_STEP_ENABLE_SECONDARY_PORT,
+ QSS_SETUP_STEP_LAST
+} QssSetupStep;
+
+typedef struct {
+ QssSetupStep step;
+ MMPortSerialAt *primary;
+ MMPortSerialAt *secondary;
+ GError *primary_error;
+ GError *secondary_error;
+} QssSetupContext;
+
+static void qss_setup_step (GTask *task);
+static void pending_csim_unlock_complete (MMBroadbandModemTelit *self);
+
+static void
+telit_qss_unsolicited_handler (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemTelit *self)
+{
+ MMTelitQssStatus cur_qss_status;
+ MMTelitQssStatus prev_qss_status;
+
+ if (!mm_get_int_from_match_info (match_info, 1, (gint*)&cur_qss_status))
+ return;
+
+ prev_qss_status = self->priv->qss_status;
+ self->priv->qss_status = cur_qss_status;
+
+ if (self->priv->csim_lock_state >= CSIM_LOCK_STATE_LOCK_REQUESTED) {
+
+ if (prev_qss_status > QSS_STATUS_SIM_REMOVED && cur_qss_status == QSS_STATUS_SIM_REMOVED) {
+ mm_obj_dbg (self, "QSS handler: #QSS=0 after +CSIM=1: CSIM locked!");
+ self->priv->csim_lock_state = CSIM_LOCK_STATE_LOCKED;
+ }
+
+ if (prev_qss_status == QSS_STATUS_SIM_REMOVED && cur_qss_status != QSS_STATUS_SIM_REMOVED) {
+ mm_obj_dbg (self, "QSS handler: #QSS>=1 after +CSIM=0: CSIM unlocked!");
+ self->priv->csim_lock_state = CSIM_LOCK_STATE_UNLOCKED;
+
+ if (self->priv->csim_lock_timeout_id) {
+ g_source_remove (self->priv->csim_lock_timeout_id);
+ self->priv->csim_lock_timeout_id = 0;
+ }
+
+ pending_csim_unlock_complete (self);
+ }
+
+ return;
+ }
+
+ if (cur_qss_status != prev_qss_status)
+ mm_obj_dbg (self, "QSS handler: status changed %s -> %s",
+ mm_telit_qss_status_get_string (prev_qss_status),
+ mm_telit_qss_status_get_string (cur_qss_status));
+
+ if (self->priv->parse_qss == FALSE) {
+ mm_obj_dbg (self, "QSS handler: message ignored");
+ return;
+ }
+
+ if ((prev_qss_status == QSS_STATUS_SIM_REMOVED && cur_qss_status != QSS_STATUS_SIM_REMOVED) ||
+ (prev_qss_status > QSS_STATUS_SIM_REMOVED && cur_qss_status == QSS_STATUS_SIM_REMOVED)) {
+ mm_obj_msg (self, "QSS handler: SIM swap detected");
+ mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self));
+ }
+}
+
+static void
+qss_setup_context_free (QssSetupContext *ctx)
+{
+ g_clear_object (&(ctx->primary));
+ g_clear_object (&(ctx->secondary));
+ g_clear_error (&(ctx->primary_error));
+ g_clear_error (&(ctx->secondary_error));
+ g_slice_free (QssSetupContext, ctx);
+}
+
+static gboolean
+modem_setup_sim_hot_swap_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+telit_qss_enable_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ QssSetupContext *ctx;
+ MMPortSerialAt *port;
+ GError **error;
+ g_autoptr(GRegex) pattern = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (ctx->step == QSS_SETUP_STEP_ENABLE_PRIMARY_PORT) {
+ port = ctx->primary;
+ error = &ctx->primary_error;
+ } else if (ctx->step == QSS_SETUP_STEP_ENABLE_SECONDARY_PORT) {
+ port = ctx->secondary;
+ error = &ctx->secondary_error;
+ } else
+ g_assert_not_reached ();
+
+ if (!mm_base_modem_at_command_full_finish (self, res, error)) {
+ mm_obj_warn (self, "QSS: error enabling unsolicited on port %s: %s", mm_port_get_device (MM_PORT (port)), (*error)->message);
+ goto next_step;
+ }
+
+ pattern = g_regex_new ("#QSS:\\s*([0-3])\\r\\n", G_REGEX_RAW, 0, NULL);
+ g_assert (pattern);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ port,
+ pattern,
+ (MMPortSerialAtUnsolicitedMsgFn)telit_qss_unsolicited_handler,
+ self,
+ NULL);
+
+next_step:
+ ctx->step++;
+ qss_setup_step (task);
+}
+
+static void
+telit_qss_query_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemTelit *self;
+ GError *error = NULL;
+ const gchar *response;
+ MMTelitQssStatus qss_status;
+ QssSetupContext *ctx;
+
+ self = MM_BROADBAND_MODEM_TELIT (_self);
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (error) {
+ mm_obj_warn (self, "could not get \"#QSS?\" reply: %s", error->message);
+ g_error_free (error);
+ goto next_step;
+ }
+
+ qss_status = mm_telit_parse_qss_query (response, &error);
+ if (error) {
+ mm_obj_warn (self, "QSS query parse error: %s", error->message);
+ g_error_free (error);
+ goto next_step;
+ }
+
+ mm_obj_dbg (self, "QSS: current status is '%s'", mm_telit_qss_status_get_string (qss_status));
+ self->priv->qss_status = qss_status;
+
+next_step:
+ ctx->step++;
+ qss_setup_step (task);
+}
+
+static void
+telit_qss_support_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ QssSetupContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ mm_obj_dbg (self, "#QSS command unsupported: '%s'", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->step++;
+ qss_setup_step (task);
+}
+
+static void
+qss_setup_step (GTask *task)
+{
+ QssSetupContext *ctx;
+ MMBroadbandModemTelit *self;
+
+ self = MM_BROADBAND_MODEM_TELIT (g_task_get_source_object (task));
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case QSS_SETUP_STEP_FIRST:
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "#QSS=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback) telit_qss_support_ready,
+ task);
+ return;
+ case QSS_SETUP_STEP_QUERY:
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "#QSS?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) telit_qss_query_ready,
+ task);
+ return;
+ case QSS_SETUP_STEP_ENABLE_PRIMARY_PORT:
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ ctx->primary,
+ "#QSS=1",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback) telit_qss_enable_ready,
+ task);
+ return;
+ case QSS_SETUP_STEP_ENABLE_SECONDARY_PORT:
+ if (ctx->secondary) {
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ ctx->secondary,
+ "#QSS=1",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback) telit_qss_enable_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+ case QSS_SETUP_STEP_LAST:
+ /* If all enabling actions failed (either both, or only primary if
+ * there is no secondary), then we return an error */
+ if (ctx->primary_error && (ctx->secondary_error || !ctx->secondary)) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "QSS: couldn't enable unsolicited");
+ } else {
+ g_autoptr(GError) error = NULL;
+
+ 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);
+
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+modem_setup_sim_hot_swap (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ QssSetupContext *ctx;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_slice_new0 (QssSetupContext);
+ ctx->step = QSS_SETUP_STEP_FIRST;
+ ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+ ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
+
+ g_task_set_task_data (task, ctx, (GDestroyNotify) qss_setup_context_free);
+ qss_setup_step (task);
+}
+
+/*****************************************************************************/
+/* SIM hot swap cleanup (Modem interface) */
+
+static void
+modem_cleanup_sim_hot_swap (MMIfaceModem *self)
+{
+ mm_broadband_modem_sim_hot_swap_ports_context_reset (MM_BROADBAND_MODEM (self));
+}
+
+/*****************************************************************************/
+/* Load unlock retries (Modem interface)
+ *
+ * NOTE: the logic must make sure that LOAD_UNLOCK_RETRIES_STEP_UNLOCK is always
+ * run if LOAD_UNLOCK_RETRIES_STEP_LOCK has been run. Currently, the logic just
+ * runs all intermediate steps ignoring errors (i.e. not completing the
+ * operation if something fails), so the LOAD_UNLOCK_RETRIES_STEP_UNLOCK is
+ * always run.
+ */
+
+#define CSIM_LOCK_STR "+CSIM=1"
+#define CSIM_UNLOCK_STR "+CSIM=0"
+#define CSIM_QUERY_TIMEOUT 3
+
+typedef enum {
+ LOAD_UNLOCK_RETRIES_STEP_FIRST,
+ LOAD_UNLOCK_RETRIES_STEP_LOCK,
+ LOAD_UNLOCK_RETRIES_STEP_PARENT,
+ LOAD_UNLOCK_RETRIES_STEP_UNLOCK,
+ LOAD_UNLOCK_RETRIES_STEP_LAST
+} LoadUnlockRetriesStep;
+
+typedef struct {
+ MMUnlockRetries *retries;
+ LoadUnlockRetriesStep step;
+} LoadUnlockRetriesContext;
+
+static void load_unlock_retries_step (GTask *task);
+
+static void
+load_unlock_retries_context_free (LoadUnlockRetriesContext *ctx)
+{
+ if (ctx->retries)
+ g_object_unref (ctx->retries);
+ g_slice_free (LoadUnlockRetriesContext, ctx);
+}
+
+static MMUnlockRetries *
+modem_load_unlock_retries_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return (MMUnlockRetries *) g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+csim_unlock_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ MMBroadbandModemTelit *self;
+ LoadUnlockRetriesContext *ctx;
+
+ self = MM_BROADBAND_MODEM_TELIT (_self);
+ ctx = g_task_get_task_data (task);
+
+ /* Ignore errors */
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response) {
+ if (g_error_matches (error,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED)) {
+ self->priv->csim_lock_support = FEATURE_NOT_SUPPORTED;
+ }
+ mm_obj_warn (self, "couldn't unlock SIM card: %s", error->message);
+ g_error_free (error);
+ }
+
+ if (self->priv->csim_lock_support != FEATURE_NOT_SUPPORTED)
+ self->priv->csim_lock_support = FEATURE_SUPPORTED;
+
+ ctx->step++;
+ load_unlock_retries_step (task);
+}
+
+static void
+parent_load_unlock_retries_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LoadUnlockRetriesContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!(ctx->retries = iface_modem_parent->load_unlock_retries_finish (self, res, &error))) {
+ mm_obj_warn (self, "couldn't load unlock retries with generic logic: %s", error->message);
+ g_error_free (error);
+ }
+
+ ctx->step++;
+ load_unlock_retries_step (task);
+}
+
+static void
+csim_lock_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ MMBroadbandModemTelit *self;
+ LoadUnlockRetriesContext *ctx;
+
+ self = MM_BROADBAND_MODEM_TELIT (_self);
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response) {
+ if (g_error_matches (error,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED) ||
+ g_error_matches (error,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN)) {
+ self->priv->csim_lock_support = FEATURE_NOT_SUPPORTED;
+ mm_obj_warn (self, "couldn't lock SIM card: %s; continuing without CSIM lock", error->message);
+ g_error_free (error);
+ } else {
+ g_prefix_error (&error, "Couldn't lock SIM card: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+ } else {
+ self->priv->csim_lock_state = CSIM_LOCK_STATE_LOCK_REQUESTED;
+ }
+
+ if (self->priv->csim_lock_support != FEATURE_NOT_SUPPORTED) {
+ self->priv->csim_lock_support = FEATURE_SUPPORTED;
+ }
+
+ ctx->step++;
+ load_unlock_retries_step (task);
+}
+
+static void
+handle_csim_locking (GTask *task,
+ gboolean is_lock)
+{
+ MMBroadbandModemTelit *self;
+ LoadUnlockRetriesContext *ctx;
+
+ self = MM_BROADBAND_MODEM_TELIT (g_task_get_source_object (task));
+ ctx = g_task_get_task_data (task);
+
+ switch (self->priv->csim_lock_support) {
+ case FEATURE_SUPPORT_UNKNOWN:
+ case FEATURE_SUPPORTED:
+ if (is_lock) {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ CSIM_LOCK_STR,
+ CSIM_QUERY_TIMEOUT,
+ FALSE,
+ (GAsyncReadyCallback) csim_lock_ready,
+ task);
+ } else {
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ CSIM_UNLOCK_STR,
+ CSIM_QUERY_TIMEOUT,
+ FALSE,
+ (GAsyncReadyCallback) csim_unlock_ready,
+ task);
+ }
+ break;
+ case FEATURE_NOT_SUPPORTED:
+ mm_obj_dbg (self, "CSIM lock not supported by this modem; skipping %s command",
+ is_lock ? "lock" : "unlock");
+ ctx->step++;
+ load_unlock_retries_step (task);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+pending_csim_unlock_complete (MMBroadbandModemTelit *self)
+{
+ LoadUnlockRetriesContext *ctx;
+
+ ctx = g_task_get_task_data (self->priv->csim_lock_task);
+
+ if (!ctx->retries) {
+ g_task_return_new_error (self->priv->csim_lock_task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Could not get any of the SIM unlock retries values");
+ } else {
+ g_task_return_pointer (self->priv->csim_lock_task, g_object_ref (ctx->retries), g_object_unref);
+ }
+
+ g_clear_object (&self->priv->csim_lock_task);
+}
+
+static gboolean
+csim_unlock_periodic_check (MMBroadbandModemTelit *self)
+{
+ if (self->priv->csim_lock_state != CSIM_LOCK_STATE_UNLOCKED)
+ mm_obj_warn (self, "CSIM is still locked after %d seconds; trying to continue anyway", CSIM_UNLOCK_MAX_TIMEOUT);
+
+ self->priv->csim_lock_timeout_id = 0;
+ pending_csim_unlock_complete (self);
+ g_object_unref (self);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+load_unlock_retries_step (GTask *task)
+{
+ MMBroadbandModemTelit *self;
+ LoadUnlockRetriesContext *ctx;
+
+ self = MM_BROADBAND_MODEM_TELIT (g_task_get_source_object (task));
+ ctx = g_task_get_task_data (task);
+ switch (ctx->step) {
+ case LOAD_UNLOCK_RETRIES_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+ case LOAD_UNLOCK_RETRIES_STEP_LOCK:
+ handle_csim_locking (task, TRUE);
+ break;
+ case LOAD_UNLOCK_RETRIES_STEP_PARENT:
+ iface_modem_parent->load_unlock_retries (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_unlock_retries_ready,
+ task);
+ break;
+ case LOAD_UNLOCK_RETRIES_STEP_UNLOCK:
+ handle_csim_locking (task, FALSE);
+ break;
+ case LOAD_UNLOCK_RETRIES_STEP_LAST:
+ self->priv->csim_lock_task = task;
+ if (self->priv->csim_lock_state == CSIM_LOCK_STATE_LOCKED) {
+ mm_obj_dbg (self, "CSIM is locked, waiting for #QSS=1");
+ self->priv->csim_lock_timeout_id = g_timeout_add_seconds (CSIM_UNLOCK_MAX_TIMEOUT,
+ (GSourceFunc) csim_unlock_periodic_check,
+ g_object_ref(self));
+ } else {
+ self->priv->csim_lock_state = CSIM_LOCK_STATE_UNLOCKED;
+ pending_csim_unlock_complete (self);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+modem_load_unlock_retries (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ LoadUnlockRetriesContext *ctx;
+
+ g_assert (iface_modem_parent->load_unlock_retries);
+ g_assert (iface_modem_parent->load_unlock_retries_finish);
+
+ ctx = g_slice_new0 (LoadUnlockRetriesContext);
+ ctx->step = LOAD_UNLOCK_RETRIES_STEP_FIRST;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)load_unlock_retries_context_free);
+
+ load_unlock_retries_step (task);
+}
+
+/*****************************************************************************/
+/* Modem after power up (Modem interface) */
+
+static gboolean
+modem_after_power_up_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+modem_after_power_up (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ MMBroadbandModemTelit *modem = MM_BROADBAND_MODEM_TELIT (self);
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ mm_obj_dbg (self, "stop ignoring #QSS");
+ modem->priv->parse_qss = TRUE;
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Modem power down (Modem interface) */
+
+static gboolean
+modem_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+telit_modem_power_down_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (mm_base_modem_at_command_finish (self, res, &error)) {
+ mm_obj_dbg (self, "sgnore #QSS unsolicited during power down/low");
+ MM_BROADBAND_MODEM_TELIT (self)->priv->parse_qss = FALSE;
+ }
+
+ if (error) {
+ mm_obj_warn (self, "failed modem power down: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_power_down (MMIfaceModem *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),
+ "+CFUN=4",
+ 20,
+ FALSE,
+ (GAsyncReadyCallback) telit_modem_power_down_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Reset (Modem interface) */
+
+static gboolean
+modem_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT#REBOOT",
+ 8,
+ FALSE,
+ callback,
+ user_data);
+}
+/*****************************************************************************/
+/* Load access technologies (Modem interface) */
+
+static gboolean
+load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ GError **error)
+{
+ GVariant *result;
+
+ result = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error);
+ if (!result) {
+ if (error)
+ g_assert (*error);
+ return FALSE;
+ }
+
+ *access_technologies = (MMModemAccessTechnology) g_variant_get_uint32 (result);
+ *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY;
+ return TRUE;
+}
+
+static MMBaseModemAtResponseProcessorResult
+response_processor_cops_ignore_at_errors (MMBaseModem *self,
+ gpointer none,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autoptr(GRegex) r = NULL;
+ guint actval = 0;
+ guint mode = 0;
+ guint vid;
+ guint pid;
+
+ *result = NULL;
+ *result_error = NULL;
+
+ if (error) {
+ /* Ignore AT errors (ie, ERROR or CMx ERROR) */
+ if (error->domain != MM_MOBILE_EQUIPMENT_ERROR || last_command) {
+ *result_error = g_error_copy (error);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
+ }
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
+ }
+
+ vid = mm_base_modem_get_vendor_id (self);
+ pid = mm_base_modem_get_product_id (self);
+
+ if (!(vid == 0x1bc7 && (pid == 0x110a || pid == 0x110b))) {
+ /* AcT for non-LPWA modems would be checked by other command */
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
+ }
+
+ r = g_regex_new ("\\+COPS:\\s*(\\d+),(\\d+),([^,]*)(?:,(\\d+))?(?:\\r\\n)?",
+ 0,
+ 0,
+ NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match (r, response, 0, &match_info)) {
+ g_set_error (result_error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Can't match +COPS? response: '%s'",
+ response);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
+ }
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &mode)) {
+ g_set_error (result_error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse mode in +COPS? response: '%s'",
+ response);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
+ }
+
+ if (mode == 2) {
+ g_set_error (result_error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Modem deregistered from the network: aborting AcT query");
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
+ }
+
+ if (!mm_get_uint_from_match_info (match_info, 4, &actval)) {
+ g_set_error (result_error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse act in +COPS? response: '%s'",
+ response);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
+ }
+
+ switch (actval) {
+ case 0:
+ *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_GSM);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
+ case 8:
+ *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_LTE_CAT_M);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
+ case 9:
+ *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_LTE_NB_IOT);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
+ default:
+ break;
+ }
+
+ g_set_error (result_error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to map act in +COPS? response: '%s'",
+ response);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
+}
+
+static MMBaseModemAtResponseProcessorResult
+response_processor_psnt_ignore_at_errors (MMBaseModem *self,
+ gpointer none,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ const gchar *psnt;
+ const gchar *mode;
+
+ *result = NULL;
+ *result_error = NULL;
+
+ if (error) {
+ /* Ignore AT errors (ie, ERROR or CMx ERROR) */
+ if (error->domain != MM_MOBILE_EQUIPMENT_ERROR || last_command) {
+ *result_error = g_error_copy (error);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
+ }
+
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
+ }
+
+ psnt = mm_strip_tag (response, "#PSNT:");
+ mode = strchr (psnt, ',');
+ if (mode) {
+ switch (atoi (++mode)) {
+ case 0:
+ *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_GPRS);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
+ case 1:
+ *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_EDGE);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
+ case 2:
+ *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_UMTS);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
+ case 3:
+ *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_HSDPA);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
+ case 4:
+ if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self)))
+ *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_LTE);
+ else
+ *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
+ case 5:
+ if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self))) {
+ *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
+ }
+ /* Fall-through since #PSNT: 5 is not supported in other than lte modems */
+ default:
+ break;
+ }
+ }
+
+ g_set_error (result_error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse #PSNT response: '%s'",
+ response);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
+}
+
+static MMBaseModemAtResponseProcessorResult
+response_processor_service_ignore_at_errors (MMBaseModem *self,
+ gpointer none,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ const gchar *service;
+
+ *result = NULL;
+ *result_error = NULL;
+
+ if (error) {
+ /* Ignore AT errors (ie, ERROR or CMx ERROR) */
+ if (error->domain != MM_MOBILE_EQUIPMENT_ERROR || last_command) {
+ *result_error = g_error_copy (error);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
+ }
+
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
+ }
+
+ service = mm_strip_tag (response, "+SERVICE:");
+ if (service) {
+ switch (atoi (service)) {
+ case 1:
+ *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_1XRTT);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
+ case 2:
+ *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_EVDO0);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
+ case 3:
+ *result = g_variant_new_uint32 (MM_MODEM_ACCESS_TECHNOLOGY_EVDOA);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
+ default:
+ break;
+ }
+ }
+
+ g_set_error (result_error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse +SERVICE response: '%s'",
+ response);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
+}
+
+static const MMBaseModemAtCommand access_tech_commands[] = {
+ { "+COPS?", 3, FALSE, response_processor_cops_ignore_at_errors },
+ { "#PSNT?", 3, FALSE, response_processor_psnt_ignore_at_errors },
+ { "+SERVICE?", 3, FALSE, response_processor_service_ignore_at_errors },
+ { NULL }
+};
+
+static void
+load_access_technologies (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_sequence (
+ MM_BASE_MODEM (self),
+ access_tech_commands,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return (GArray *) g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_load_supported_modes_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ GArray *all;
+ GArray *combinations;
+ GArray *filtered;
+ MMSharedTelit *shared = MM_SHARED_TELIT (self);
+
+ all = iface_modem_parent->load_supported_modes_finish (self, res, &error);
+ if (!all) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* CDMA-only modems don't support changing modes, default to parent's */
+ if (!mm_iface_modem_is_3gpp (self)) {
+ g_task_return_pointer (task, all, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Filter out those unsupported modes */
+ combinations = mm_telit_build_modes_list();
+ filtered = mm_filter_supported_modes (all, combinations, self);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ mm_shared_telit_store_supported_modes (shared, filtered);
+ g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Run parent's loading */
+ iface_modem_parent->load_supported_modes (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_supported_modes_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Enabling unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+cind_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ mm_obj_warn (self, "couldn't enable custom +CIND settings: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) {
+ mm_obj_warn (self, "couldn't enable parent 3GPP unsolicited events: %s", error->message);
+ g_error_free (error);
+ }
+
+ /* Our own enable now */
+ mm_base_modem_at_command_full (
+ MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)),
+ /* Enable +CIEV only for: signal, service, roam */
+ "AT+CIND=0,1,1,0,0,0,1,0,0",
+ 5,
+ FALSE,
+ FALSE,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)cind_set_ready,
+ task);
+}
+
+static void
+modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's enable */
+ iface_modem_3gpp_parent->enable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_enable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemTelit *
+mm_broadband_modem_telit_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_TELIT,
+ 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 AT 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_telit_init (MMBroadbandModemTelit *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_MODEM_TELIT,
+ MMBroadbandModemTelitPrivate);
+
+ self->priv->csim_lock_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->csim_lock_state = CSIM_LOCK_STATE_UNKNOWN;
+ self->priv->qss_status = QSS_STATUS_UNKNOWN;
+ self->priv->parse_qss = TRUE;
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->set_current_bands = mm_shared_telit_modem_set_current_bands;
+ iface->set_current_bands_finish = mm_shared_telit_modem_set_current_bands_finish;
+ iface->load_current_bands = mm_shared_telit_modem_load_current_bands;
+ iface->load_current_bands_finish = mm_shared_telit_modem_load_current_bands_finish;
+ iface->load_revision = mm_shared_telit_modem_load_revision;
+ iface->load_revision_finish = mm_shared_telit_modem_load_revision_finish;
+ iface->load_supported_bands = mm_shared_telit_modem_load_supported_bands;
+ iface->load_supported_bands_finish = mm_shared_telit_modem_load_supported_bands_finish;
+ iface->load_unlock_retries_finish = modem_load_unlock_retries_finish;
+ iface->load_unlock_retries = modem_load_unlock_retries;
+ iface->reset = modem_reset;
+ iface->reset_finish = modem_reset_finish;
+ iface->modem_after_power_up = modem_after_power_up;
+ iface->modem_after_power_up_finish = modem_after_power_up_finish;
+ iface->modem_power_down = modem_power_down;
+ iface->modem_power_down_finish = modem_power_down_finish;
+ iface->load_access_technologies = load_access_technologies;
+ iface->load_access_technologies_finish = load_access_technologies_finish;
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = mm_shared_telit_load_current_modes;
+ iface->load_current_modes_finish = mm_shared_telit_load_current_modes_finish;
+ iface->set_current_modes = mm_shared_telit_set_current_modes;
+ iface->set_current_modes_finish = mm_shared_telit_set_current_modes_finish;
+ iface->setup_sim_hot_swap = modem_setup_sim_hot_swap;
+ iface->setup_sim_hot_swap_finish = modem_setup_sim_hot_swap_finish;
+ iface->cleanup_sim_hot_swap = modem_cleanup_sim_hot_swap;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish;
+}
+
+static void
+shared_telit_init (MMSharedTelit *iface)
+{
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = location_load_capabilities;
+ iface->load_capabilities_finish = location_load_capabilities_finish;
+ iface->enable_location_gathering = enable_location_gathering;
+ iface->enable_location_gathering_finish = enable_location_gathering_finish;
+ iface->disable_location_gathering = disable_location_gathering;
+ iface->disable_location_gathering_finish = disable_location_gathering_finish;
+}
+
+static void
+mm_broadband_modem_telit_class_init (MMBroadbandModemTelitClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemTelitPrivate));
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/telit/mm-broadband-modem-telit.h b/src/plugins/telit/mm-broadband-modem-telit.h
new file mode 100644
index 00000000..f68465e7
--- /dev/null
+++ b/src/plugins/telit/mm-broadband-modem-telit.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2013 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_MODEM_TELIT_H
+#define MM_BROADBAND_MODEM_TELIT_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_TELIT (mm_broadband_modem_telit_get_type ())
+#define MM_BROADBAND_MODEM_TELIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_TELIT, MMBroadbandModemTelit))
+#define MM_BROADBAND_MODEM_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_TELIT, MMBroadbandModemTelitClass))
+#define MM_IS_BROADBAND_MODEM_TELIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_TELIT))
+#define MM_IS_BROADBAND_MODEM_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_TELIT))
+#define MM_BROADBAND_MODEM_TELIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_TELIT, MMBroadbandModemTelitClass))
+
+typedef struct _MMBroadbandModemTelit MMBroadbandModemTelit;
+typedef struct _MMBroadbandModemTelitClass MMBroadbandModemTelitClass;
+typedef struct _MMBroadbandModemTelitPrivate MMBroadbandModemTelitPrivate;
+
+struct _MMBroadbandModemTelit {
+ MMBroadbandModem parent;
+ MMBroadbandModemTelitPrivate *priv;
+};
+
+struct _MMBroadbandModemTelitClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_telit_get_type (void);
+
+MMBroadbandModemTelit *mm_broadband_modem_telit_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_TELIT_H */
diff --git a/src/plugins/telit/mm-common-telit.c b/src/plugins/telit/mm-common-telit.c
new file mode 100644
index 00000000..911c605b
--- /dev/null
+++ b/src/plugins/telit/mm-common-telit.c
@@ -0,0 +1,373 @@
+/* -*- 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) 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <string.h>
+
+#include "mm-common-telit.h"
+#include "mm-log-object.h"
+#include "mm-serial-parsers.h"
+
+/*****************************************************************************/
+
+#define TAG_GETPORTCFG_SUPPORTED "getportcfg-supported"
+
+#define TAG_TELIT_MODEM_PORT "ID_MM_TELIT_PORT_TYPE_MODEM"
+#define TAG_TELIT_AUX_PORT "ID_MM_TELIT_PORT_TYPE_AUX"
+#define TAG_TELIT_NMEA_PORT "ID_MM_TELIT_PORT_TYPE_NMEA"
+
+#define TELIT_GE910_FAMILY_PID 0x0022
+
+/* The following number of retries of the port responsiveness
+ * check allows having up to 30 seconds of wait, that should
+ * be fine for most of the modems */
+#define TELIT_PORT_CHECK_RETRIES 6
+
+gboolean
+telit_grab_port (MMPlugin *self,
+ MMBaseModem *modem,
+ MMPortProbe *probe,
+ GError **error)
+{
+ MMKernelDevice *port;
+ MMDevice *device;
+ MMPortType ptype;
+ MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE;
+ const gchar *subsys;
+
+ port = mm_port_probe_peek_port (probe);
+ ptype = mm_port_probe_get_port_type (probe);
+ device = mm_port_probe_peek_device (probe);
+ subsys = mm_port_probe_get_port_subsys (probe);
+
+ /* Just skip custom port identification for subsys different than tty */
+ if (!g_str_equal (subsys, "tty"))
+ goto out;
+
+ /* AT#PORTCFG (if supported) can be used for identifying the port layout */
+ if (g_object_get_data (G_OBJECT (device), TAG_GETPORTCFG_SUPPORTED) != NULL) {
+ guint usbif;
+
+ usbif = (guint) mm_kernel_device_get_interface_number (port);
+ if (usbif == GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT))) {
+ mm_obj_dbg (self, "AT port '%s/%s' flagged as primary",
+ mm_port_probe_get_port_subsys (probe),
+ mm_port_probe_get_port_name (probe));
+ pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY;
+ } else if (usbif == GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (device), TAG_TELIT_AUX_PORT))) {
+ mm_obj_dbg (self, "AT port '%s/%s' flagged as secondary",
+ mm_port_probe_get_port_subsys (probe),
+ mm_port_probe_get_port_name (probe));
+ pflags = MM_PORT_SERIAL_AT_FLAG_SECONDARY;
+ } else if (usbif == GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (device), TAG_TELIT_NMEA_PORT))) {
+ mm_obj_dbg (self, "port '%s/%s' flagged as NMEA",
+ mm_port_probe_get_port_subsys (probe),
+ mm_port_probe_get_port_name (probe));
+ ptype = MM_PORT_TYPE_GPS;
+ } else
+ ptype = MM_PORT_TYPE_IGNORED;
+ }
+
+out:
+ return mm_base_modem_grab_port (modem,
+ port,
+ ptype,
+ pflags,
+ error);
+}
+
+/*****************************************************************************/
+/* Custom init */
+
+typedef struct {
+ MMPortSerialAt *port;
+ gboolean getportcfg_done;
+ guint getportcfg_retries;
+ guint port_responsive_retries;
+} TelitCustomInitContext;
+
+gboolean
+telit_custom_init_finish (MMPortProbe *probe,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void telit_custom_init_step (GTask *task);
+
+static gboolean
+cache_port_mode (MMPortProbe *probe,
+ MMDevice *device,
+ const gchar *reply)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GRegexCompileFlags flags = G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW;
+ GError *error = NULL;
+ gboolean ret = FALSE;
+ guint portcfg_current;
+
+ /* #PORTCFG: <requested>,<active> */
+ r = g_regex_new ("#PORTCFG:\\s*(\\d+),(\\d+)", flags, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, &error))
+ goto out;
+
+ if (!mm_get_uint_from_match_info (match_info, 2, &portcfg_current)) {
+ mm_obj_dbg (probe, "unrecognized #PORTCFG <active> value");
+ goto out;
+ }
+
+ /* Reference for port configurations:
+ * HE910/UE910/UL865 Families Ports Arrangements User Guide
+ * GE910 Family Ports Arrangements User Guide
+ */
+ switch (portcfg_current) {
+ case 0:
+ case 1:
+ case 4:
+ case 5:
+ case 7:
+ case 9:
+ case 10:
+ case 11:
+ g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, GUINT_TO_POINTER (0x00));
+ if (mm_device_get_product (device) == TELIT_GE910_FAMILY_PID)
+ g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x02));
+ else
+ g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x06));
+ break;
+ case 2:
+ case 3:
+ case 6:
+ g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, GUINT_TO_POINTER (0x00));
+ break;
+ case 8:
+ case 12:
+ g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, GUINT_TO_POINTER (0x00));
+ if (mm_device_get_product (device) == TELIT_GE910_FAMILY_PID) {
+ g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x02));
+ g_object_set_data (G_OBJECT (device), TAG_TELIT_NMEA_PORT, GUINT_TO_POINTER (0x04));
+ } else {
+ g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, GUINT_TO_POINTER (0x06));
+ g_object_set_data (G_OBJECT (device), TAG_TELIT_NMEA_PORT, GUINT_TO_POINTER (0x0a));
+ }
+ break;
+ default:
+ /* portcfg value not supported */
+ goto out;
+ }
+ ret = TRUE;
+
+out:
+ if (error) {
+ mm_obj_dbg (probe, "error while matching #PORTCFG: %s", error->message);
+ g_error_free (error);
+ }
+ return ret;
+}
+
+static void
+getportcfg_ready (MMPortSerialAt *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ MMPortProbe *probe;
+ TelitCustomInitContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ probe = g_task_get_source_object (task);
+
+ response = mm_port_serial_at_command_finish (port, res, &error);
+ if (error) {
+ mm_obj_dbg (probe, "couldn't get telit port mode: '%s'", error->message);
+
+ /* If ERROR or COMMAND NOT SUPPORT occur then do not retry the
+ * command.
+ */
+ if (g_error_matches (error,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN))
+ ctx->getportcfg_done = TRUE;
+ } else {
+ MMDevice *device;
+
+ device = mm_port_probe_peek_device (probe);
+
+ /* Results are cached in the parent device object */
+ if (g_object_get_data (G_OBJECT (device), TAG_GETPORTCFG_SUPPORTED) == NULL) {
+ mm_obj_dbg (probe, "retrieving telit port mode layout");
+ if (cache_port_mode (probe, device, response)) {
+ g_object_set_data (G_OBJECT (device), TAG_GETPORTCFG_SUPPORTED, GUINT_TO_POINTER (TRUE));
+ ctx->getportcfg_done = TRUE;
+ }
+ }
+
+ /* Port answered to #PORTCFG, so mark it as being AT already */
+ mm_port_probe_set_result_at (probe, TRUE);
+ }
+
+ if (error)
+ g_error_free (error);
+
+ telit_custom_init_step (task);
+}
+
+static void
+telit_custom_init_context_free (TelitCustomInitContext *ctx)
+{
+ g_object_unref (ctx->port);
+ g_slice_free (TelitCustomInitContext, ctx);
+}
+
+static void
+telit_custom_init_step (GTask *task)
+{
+ MMKernelDevice *port;
+ MMPortProbe *probe;
+ TelitCustomInitContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ probe = g_task_get_source_object (task);
+
+ /* If cancelled, end */
+ if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) {
+ mm_obj_dbg (probe, "no need to keep on running custom init");
+ goto out;
+ }
+
+ /* Try to get a port configuration from the modem: usb interface 00
+ * is always linked to an AT port
+ */
+ port = mm_port_probe_peek_port (probe);
+ if (!ctx->getportcfg_done && mm_kernel_device_get_interface_number (port) == 0) {
+ if (ctx->getportcfg_retries == 0)
+ goto out;
+ ctx->getportcfg_retries--;
+
+ mm_port_serial_at_command (
+ ctx->port,
+ "AT#PORTCFG?",
+ 2,
+ FALSE, /* raw */
+ FALSE, /* allow_cached */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)getportcfg_ready,
+ task);
+ return;
+ }
+
+out:
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void at_ready (MMPortSerialAt *port,
+ GAsyncResult *res,
+ GTask *task);
+
+static void
+wait_for_ready (GTask *task)
+{
+ TelitCustomInitContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ if (ctx->port_responsive_retries == 0) {
+ telit_custom_init_step (task);
+ return;
+ }
+ ctx->port_responsive_retries--;
+
+ mm_port_serial_at_command (
+ ctx->port,
+ "AT",
+ 5,
+ FALSE, /* raw */
+ FALSE, /* allow_cached */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)at_ready,
+ task);
+}
+
+static void
+at_ready (MMPortSerialAt *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMPortProbe *probe;
+ g_autoptr(GError) error = NULL;
+
+ probe = g_task_get_source_object (task);
+
+ mm_port_serial_at_command_finish (port, res, &error);
+ if (error) {
+ /* On a timeout or send error, wait */
+ if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT) ||
+ g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED)) {
+ wait_for_ready (task);
+ return;
+ }
+ /* On an unknown error, make it fatal */
+ if (!mm_serial_parser_v1_is_known_error (error)) {
+ mm_obj_warn (probe, "custom port initialization logic failed: %s", error->message);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+ }
+
+ /* When successful mark the port as AT and continue checking #PORTCFG */
+ mm_obj_dbg (probe, "port is AT");
+ mm_port_probe_set_result_at (probe, TRUE);
+ telit_custom_init_step (task);
+}
+
+void
+telit_custom_init (MMPortProbe *probe,
+ MMPortSerialAt *port,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TelitCustomInitContext *ctx;
+ GTask *task;
+ gboolean wait_needed;
+
+ ctx = g_slice_new (TelitCustomInitContext);
+ ctx->port = g_object_ref (port);
+ ctx->getportcfg_done = FALSE;
+ ctx->getportcfg_retries = 3;
+ ctx->port_responsive_retries = TELIT_PORT_CHECK_RETRIES;
+ task = g_task_new (probe, cancellable, callback, user_data);
+ g_task_set_check_cancellable (task, FALSE);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)telit_custom_init_context_free);
+
+ /* Some Telit modems require an initial delay for the ports to be responsive
+ * If no explicit tag is present, the modem does not need this step
+ * and can directly look for #PORTCFG support */
+ wait_needed = mm_kernel_device_get_global_property_as_boolean (mm_port_probe_peek_port (probe),
+ "ID_MM_TELIT_PORT_DELAY");
+ if (wait_needed) {
+ mm_obj_dbg (probe, "Start polling for port responsiveness");
+ wait_for_ready (task);
+ return;
+ }
+
+ telit_custom_init_step (task);
+}
diff --git a/src/plugins/telit/mm-common-telit.h b/src/plugins/telit/mm-common-telit.h
new file mode 100644
index 00000000..0c5dbe7a
--- /dev/null
+++ b/src/plugins/telit/mm-common-telit.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) 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_COMMON_TELIT_H
+#define MM_COMMON_TELIT_H
+
+#include "glib.h"
+#include "mm-plugin.h"
+
+gboolean
+telit_custom_init_finish (MMPortProbe *probe,
+ GAsyncResult *result,
+ GError **error);
+
+void
+telit_custom_init (MMPortProbe *probe,
+ MMPortSerialAt *port,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean
+telit_grab_port (MMPlugin *self,
+ MMBaseModem *modem,
+ MMPortProbe *probe,
+ GError **error);
+
+#endif /* MM_COMMON_TELIT_H */
diff --git a/src/plugins/telit/mm-modem-helpers-telit.c b/src/plugins/telit/mm-modem-helpers-telit.c
new file mode 100644
index 00000000..c0df8093
--- /dev/null
+++ b/src/plugins/telit/mm-modem-helpers-telit.c
@@ -0,0 +1,967 @@
+/* -*- 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) 2015-2019 Telit.
+ * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MMCLI
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-telit.h"
+
+/*****************************************************************************/
+/* AT#BND 2G values */
+
+#define MM_MODEM_BAND_TELIT_2G_FIRST MM_MODEM_BAND_EGSM
+#define MM_MODEM_BAND_TELIT_2G_LAST MM_MODEM_BAND_G850
+
+#define B2G_FLAG(band) (1 << (band - MM_MODEM_BAND_TELIT_2G_FIRST))
+
+/* Index of the array is the telit 2G band value [0-5]
+ * The bitmask value here is built from the 2G MMModemBand values right away. */
+static const guint32 telit_2g_to_mm_band_mask[] = {
+ [0] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_DCS),
+ [1] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_PCS),
+ [2] = B2G_FLAG (MM_MODEM_BAND_DCS) + B2G_FLAG (MM_MODEM_BAND_G850),
+ [3] = B2G_FLAG (MM_MODEM_BAND_PCS) + B2G_FLAG (MM_MODEM_BAND_G850),
+ [4] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_DCS) + B2G_FLAG (MM_MODEM_BAND_PCS),
+ [5] = B2G_FLAG (MM_MODEM_BAND_EGSM) + B2G_FLAG (MM_MODEM_BAND_DCS) + B2G_FLAG (MM_MODEM_BAND_PCS) + B2G_FLAG (MM_MODEM_BAND_G850),
+};
+
+/*****************************************************************************/
+/* AT#BND 3G values */
+
+/* NOTE: UTRAN_1 to UTRAN_9 enum values are NOT IN ORDER!
+ * E.g. numerically UTRAN_7 is after UTRAN_9...
+ *
+ * This array allows us to iterate them in a way which is a bit
+ * more friendly.
+ */
+static const guint band_utran_index[] = {
+ [MM_MODEM_BAND_UTRAN_1] = 1,
+ [MM_MODEM_BAND_UTRAN_2] = 2,
+ [MM_MODEM_BAND_UTRAN_3] = 3,
+ [MM_MODEM_BAND_UTRAN_4] = 4,
+ [MM_MODEM_BAND_UTRAN_5] = 5,
+ [MM_MODEM_BAND_UTRAN_6] = 6,
+ [MM_MODEM_BAND_UTRAN_7] = 7,
+ [MM_MODEM_BAND_UTRAN_8] = 8,
+ [MM_MODEM_BAND_UTRAN_9] = 9,
+ [MM_MODEM_BAND_UTRAN_10] = 10,
+ [MM_MODEM_BAND_UTRAN_11] = 11,
+ [MM_MODEM_BAND_UTRAN_12] = 12,
+ [MM_MODEM_BAND_UTRAN_13] = 13,
+ [MM_MODEM_BAND_UTRAN_14] = 14,
+ [MM_MODEM_BAND_UTRAN_19] = 19,
+ [MM_MODEM_BAND_UTRAN_20] = 20,
+ [MM_MODEM_BAND_UTRAN_21] = 21,
+ [MM_MODEM_BAND_UTRAN_22] = 22,
+ [MM_MODEM_BAND_UTRAN_25] = 25,
+ [MM_MODEM_BAND_UTRAN_26] = 26,
+ [MM_MODEM_BAND_UTRAN_32] = 32,
+};
+
+#define MM_MODEM_BAND_TELIT_3G_FIRST MM_MODEM_BAND_UTRAN_1
+#define MM_MODEM_BAND_TELIT_3G_LAST MM_MODEM_BAND_UTRAN_19
+
+#define B3G_NUM(band) band_utran_index[band]
+#define B3G_FLAG(band) ((B3G_NUM (band) > 0) ? (1LL << (B3G_NUM (band) - B3G_NUM (MM_MODEM_BAND_TELIT_3G_FIRST))) : 0)
+
+/* Index of the arrays is the telit 3G band value.
+ * The bitmask value here is built from the 3G MMModemBand values right away.
+ *
+ * We have 2 different sets of bands that are different for values >=12, because
+ * the LM940/960 models support a different set of 3G bands.
+ */
+
+#define TELIT_3G_TO_MM_BAND_MASK_DEFAULT_N_ELEMENTS 27
+static guint64 telit_3g_to_mm_band_mask_default[TELIT_3G_TO_MM_BAND_MASK_DEFAULT_N_ELEMENTS];
+
+#define TELIT_3G_TO_MM_BAND_MASK_ALTERNATE_N_ELEMENTS 20
+static guint64 telit_3g_to_mm_band_mask_alternate[TELIT_3G_TO_MM_BAND_MASK_ALTERNATE_N_ELEMENTS];
+
+static void
+initialize_telit_3g_to_mm_band_masks (void)
+{
+ static gboolean initialized = FALSE;
+
+ /* We need to initialize the arrays in runtime because gcc < 8 doesn't like initializing
+ * with operations that are using the band_utran_index constant array elements */
+
+ if (initialized)
+ return;
+
+ telit_3g_to_mm_band_mask_default[0] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1);
+ telit_3g_to_mm_band_mask_default[1] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2);
+ telit_3g_to_mm_band_mask_default[2] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_default[3] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_default[4] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_default[5] = B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_default[6] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_default[7] = B3G_FLAG (MM_MODEM_BAND_UTRAN_4);
+ telit_3g_to_mm_band_mask_default[8] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_default[9] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_default[10] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_default[11] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_default[12] = B3G_FLAG (MM_MODEM_BAND_UTRAN_6);
+ telit_3g_to_mm_band_mask_default[13] = B3G_FLAG (MM_MODEM_BAND_UTRAN_3);
+ telit_3g_to_mm_band_mask_default[14] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6);
+ telit_3g_to_mm_band_mask_default[15] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_default[16] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_default[17] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_6);
+ telit_3g_to_mm_band_mask_default[18] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_default[19] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6);
+ telit_3g_to_mm_band_mask_default[20] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6);
+ telit_3g_to_mm_band_mask_default[21] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6);
+ telit_3g_to_mm_band_mask_default[22] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_default[23] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3);
+ telit_3g_to_mm_band_mask_default[24] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_default[25] = B3G_FLAG (MM_MODEM_BAND_UTRAN_19);
+ telit_3g_to_mm_band_mask_default[26] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_19);
+
+ /* Initialize alternate 3G band set */
+ telit_3g_to_mm_band_mask_alternate[0] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1);
+ telit_3g_to_mm_band_mask_alternate[1] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2);
+ telit_3g_to_mm_band_mask_alternate[2] = B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[3] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[4] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[5] = B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_alternate[6] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_alternate[7] = B3G_FLAG (MM_MODEM_BAND_UTRAN_4);
+ telit_3g_to_mm_band_mask_alternate[8] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[9] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[10] = B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[11] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_alternate[12] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_alternate[13] = B3G_FLAG (MM_MODEM_BAND_UTRAN_3);
+ telit_3g_to_mm_band_mask_alternate[14] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[15] = B3G_FLAG (MM_MODEM_BAND_UTRAN_3) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5);
+ telit_3g_to_mm_band_mask_alternate[16] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_3) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_4) + B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_alternate[17] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8);
+ telit_3g_to_mm_band_mask_alternate[18] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) + B3G_FLAG (MM_MODEM_BAND_UTRAN_9) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_19);
+ telit_3g_to_mm_band_mask_alternate[19] = B3G_FLAG (MM_MODEM_BAND_UTRAN_1) + B3G_FLAG (MM_MODEM_BAND_UTRAN_2) + B3G_FLAG (MM_MODEM_BAND_UTRAN_4) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_5) + B3G_FLAG (MM_MODEM_BAND_UTRAN_6) + B3G_FLAG (MM_MODEM_BAND_UTRAN_8) +
+ B3G_FLAG (MM_MODEM_BAND_UTRAN_9) + B3G_FLAG (MM_MODEM_BAND_UTRAN_19);
+}
+
+/*****************************************************************************/
+/* AT#BND 4G values
+ *
+ * The Telit-specific value for 4G bands is a bitmask of the band values, given
+ * in hexadecimal or decimal format.
+ */
+
+#define MM_MODEM_BAND_TELIT_4G_FIRST MM_MODEM_BAND_EUTRAN_1
+#define MM_MODEM_BAND_TELIT_4G_LAST MM_MODEM_BAND_EUTRAN_64
+
+#define B4G_FLAG(band) (((guint64) 1) << (band - MM_MODEM_BAND_TELIT_4G_FIRST))
+
+#define MM_MODEM_BAND_TELIT_EXT_4G_FIRST MM_MODEM_BAND_EUTRAN_65
+#define MM_MODEM_BAND_TELIT_EXT_4G_LAST MM_MODEM_BAND_EUTRAN_85
+
+#define B4G_FLAG_EXT(band) (((guint64) 1) << (band - MM_MODEM_BAND_TELIT_EXT_4G_FIRST))
+
+/*****************************************************************************/
+/* Set current bands helpers */
+
+gchar *
+mm_telit_build_bnd_request (GArray *bands_array,
+ MMTelitBNDParseConfig *config,
+ GError **error)
+{
+ guint32 mask2g = 0;
+ guint64 mask3g = 0;
+ guint64 mask4g = 0;
+ guint64 mask4gext = 0;
+ guint i;
+ gint flag2g = -1;
+ gint64 flag3g = -1;
+ gint64 flag4g = -1;
+ gchar *cmd;
+ gboolean modem_is_2g = config->modem_is_2g;
+ gboolean modem_is_3g = config->modem_is_3g;
+ gboolean modem_is_4g = config->modem_is_4g;
+
+ for (i = 0; i < bands_array->len; i++) {
+ MMModemBand band;
+
+ band = g_array_index (bands_array, MMModemBand, i);
+
+ /* Convert 2G bands into a bitmask, to match against telit_2g_to_mm_band_mask. */
+ if (modem_is_2g && mm_common_band_is_gsm (band) &&
+ (band >= MM_MODEM_BAND_TELIT_2G_FIRST) && (band <= MM_MODEM_BAND_TELIT_2G_LAST))
+ mask2g += B2G_FLAG (band);
+
+ /* Convert 3G bands into a bitmask, to match against telit_3g_to_mm_band_mask. We use
+ * a 64-bit explicit bitmask so that all values fit correctly. */
+ if (modem_is_3g && mm_common_band_is_utran (band) &&
+ (B3G_NUM (band) >= B3G_NUM (MM_MODEM_BAND_TELIT_3G_FIRST)) && (B3G_NUM (band) <= B3G_NUM (MM_MODEM_BAND_TELIT_3G_LAST)))
+ mask3g += B3G_FLAG (band);
+
+ /* Convert 4G bands into a bitmask. We use a 64bit explicit bitmask so that
+ * all values fit correctly. */
+ if (modem_is_4g && mm_common_band_is_eutran (band)) {
+ if (band >= MM_MODEM_BAND_TELIT_4G_FIRST && band <= MM_MODEM_BAND_TELIT_4G_LAST)
+ mask4g += B4G_FLAG (band);
+ else if (band >= MM_MODEM_BAND_TELIT_EXT_4G_FIRST && band <= MM_MODEM_BAND_TELIT_EXT_4G_LAST)
+ mask4gext += B4G_FLAG_EXT (band);
+ }
+ }
+
+ /* Get 2G-specific telit value */
+ if (mask2g) {
+ for (i = 0; i < G_N_ELEMENTS (telit_2g_to_mm_band_mask); i++) {
+ if (mask2g == telit_2g_to_mm_band_mask[i]) {
+ flag2g = i;
+ break;
+ }
+ }
+ if (flag2g == -1) {
+ gchar *bands_str;
+
+ bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)(bands_array->data), bands_array->len);
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't find matching 2G bands Telit value for band combination: '%s'", bands_str);
+ g_free (bands_str);
+ return NULL;
+ }
+ }
+
+ /* Get 3G-specific telit value */
+ if (mask3g) {
+ const guint64 *telit_3g_to_mm_band_mask;
+ guint telit_3g_to_mm_band_mask_n_elements;
+
+ initialize_telit_3g_to_mm_band_masks ();
+
+ /* Select correct 3G band mask */
+ if (config->modem_alternate_3g_bands) {
+ telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_alternate;
+ telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_alternate);
+ } else {
+ telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_default;
+ telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_default);
+ }
+ for (i = 0; i < telit_3g_to_mm_band_mask_n_elements; i++) {
+ if (mask3g == telit_3g_to_mm_band_mask[i]) {
+ flag3g = i;
+ break;
+ }
+ }
+ if (flag3g == -1) {
+ gchar *bands_str;
+
+ bands_str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)(bands_array->data), bands_array->len);
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't find matching 3G bands Telit value for band combination: '%s'", bands_str);
+ g_free (bands_str);
+ return NULL;
+ }
+ }
+
+ /* Get 4G-specific telit band bitmask */
+ flag4g = (mask4g != 0) ? ((gint64)mask4g) : -1;
+
+ /* If the modem supports a given access tech, we must always give band settings
+ * for the specific tech */
+ if (modem_is_2g && flag2g == -1) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
+ "None or invalid 2G bands combination in the provided list");
+ return NULL;
+ }
+ if (modem_is_3g && flag3g == -1) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
+ "None or invalid 3G bands combination in the provided list");
+ return NULL;
+ }
+ if (modem_is_4g && mask4g == 0 && mask4gext == 0) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
+ "None or invalid 4G bands combination in the provided list");
+ return NULL;
+ }
+
+ if (modem_is_2g && !modem_is_3g && !modem_is_4g)
+ cmd = g_strdup_printf ("#BND=%d", flag2g);
+ else if (!modem_is_2g && modem_is_3g && !modem_is_4g)
+ cmd = g_strdup_printf ("#BND=0,%" G_GINT64_FORMAT, flag3g);
+ else if (modem_is_2g && modem_is_3g && !modem_is_4g)
+ cmd = g_strdup_printf ("#BND=%d,%" G_GINT64_FORMAT, flag2g, flag3g);
+ else if (!modem_is_2g && !modem_is_3g && modem_is_4g) {
+ if (!config->modem_ext_4g_bands)
+ cmd = g_strdup_printf ("#BND=0,0,%" G_GINT64_FORMAT, flag4g);
+ else
+ cmd = g_strdup_printf ("#BND=0,0,%" G_GINT64_MODIFIER "x" ",%" G_GINT64_MODIFIER "x", mask4g, mask4gext);
+ } else if (!modem_is_2g && modem_is_3g && modem_is_4g) {
+ if (!config->modem_ext_4g_bands)
+ cmd = g_strdup_printf ("#BND=0,%" G_GINT64_FORMAT ",%" G_GINT64_FORMAT, flag3g, flag4g);
+ else
+ cmd = g_strdup_printf ("#BND=0,%" G_GINT64_FORMAT ",%" G_GINT64_MODIFIER "x" ",%" G_GINT64_MODIFIER "x", flag3g, mask4g, mask4gext);
+ } else if (modem_is_2g && !modem_is_3g && modem_is_4g) {
+ if (!config->modem_ext_4g_bands)
+ cmd = g_strdup_printf ("#BND=%d,0,%" G_GINT64_FORMAT, flag2g, flag4g);
+ else
+ cmd = g_strdup_printf ("#BND=%d,0,%" G_GINT64_MODIFIER "x" ",%" G_GINT64_MODIFIER "x", flag2g, mask4g, mask4gext);
+ } else if (modem_is_2g && modem_is_3g && modem_is_4g) {
+ if (!config->modem_ext_4g_bands)
+ cmd = g_strdup_printf ("#BND=%d,%" G_GINT64_FORMAT ",%" G_GINT64_FORMAT, flag2g, flag3g, flag4g);
+ else
+ cmd = g_strdup_printf ("#BND=%d,%" G_GINT64_FORMAT ",%" G_GINT64_MODIFIER "x" ",%" G_GINT64_MODIFIER "x", flag2g, flag3g, mask4g, mask4gext);
+ } else
+ g_assert_not_reached ();
+
+ return cmd;
+}
+
+/*****************************************************************************/
+/* #BND response parser
+ *
+ * AT#BND=?
+ * #BND: <2G band flags range>,<3G band flags range>[, <4G band flags range>]
+ *
+ * where "band flags" is a list of numbers defining the supported bands.
+ * Note that the one Telit band flag may represent more than one MM band.
+ *
+ * e.g.
+ *
+ * #BND: (0-2),(3,4)
+ *
+ * (0,2) = 2G band flag 0 is EGSM + DCS
+ * = 2G band flag 1 is EGSM + PCS
+ * = 2G band flag 2 is DCS + G850
+ * (3,4) = 3G band flag 3 is U2100 + U1900 + U850
+ * = 3G band flag 4 is U1900 + U850
+ *
+ * Modems that supports 4G bands, return a range value(X-Y) where
+ * X: represent the lower supported band, such as X = 2^(B-1), being B = B1, B2,..., B32
+ * Y: is a 32 bit number resulting from a mask of all the supported bands:
+ * 1 - B1
+ * 2 - B2
+ * 4 - B3
+ * 8 - B4
+ * ...
+ * i - B(2exp(i-1))
+ * ...
+ * 2147483648 - B32
+ *
+ * e.g.
+ * (2-4106)
+ * 2 = 2^1 --> lower supported band B2
+ * 4106 = 2^1 + 2^3 + 2^12 --> the supported bands are B2, B4, B13
+ *
+ *
+ * AT#BND?
+ * #BND: <2G band flags>,<3G band flags>[, <4G band flags>]
+ *
+ * where "band flags" is a number defining the current bands.
+ * Note that the one Telit band flag may represent more than one MM band.
+ *
+ * e.g.
+ *
+ * #BND: 0,4
+ *
+ * 0 = 2G band flag 0 is EGSM + DCS
+ * 4 = 3G band flag 4 is U1900 + U850
+ *
+ * ----------------
+ *
+ * For modems such as LN920 the #BND configuration/response for LTE bands is different
+ * from what is explained above:
+ *
+ * AT#BND=<GSM_band>[,<UMTS_band>[,<LTE_band>[,<LTE_band_ext>]]]
+ *
+ * <LTE_band>: hex: Indicates the LTE supported bands expressed as the sum of Band number (1+2+8 ...) calculated as shown in the table (mask of 64 bits):
+ *
+ * Band number(Hex) Band i
+ * 0 disable
+ * 1 B1
+ * 2 B2
+ * 4 B3
+ * 8 B4
+ * ...
+ * ...
+ * 80000000 B32
+ * ...
+ * ...
+ * 800000000000 B48
+ *
+ * It can take value, 0 - 87A03B0F38DF: range of the sum of Band number (1+2+4 ...)
+ *
+ * <LTE_band_ext>: hex: Indicates the LTE supported bands from B65 expressed as the sum of Band number (1+2+8 ...) calculated as shown in the table (mask of 64 bits):
+ *
+ * Band number(Hex) Band i
+ * 0 disable
+ * 2 B66
+ * 40 B71
+ *
+ * It can take value, 0 - 42: range of the sum of Band number (2+40)
+ *
+ * Note: LTE_band and LTE_band_ext cannot be 0 at the same time
+ *
+ * Example output:
+ *
+ * AT#BND=?
+ * #BND: (0),(0-11,17,18),(87A03B0F38DF),(42)
+ *
+ * AT#BND?
+ * #BND: 0,18,87A03B0F38DF,42
+ *
+ */
+
+static gboolean
+telit_get_2g_mm_bands (GMatchInfo *match_info,
+ gpointer log_object,
+ GArray **bands,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ GArray *values = NULL;
+ gchar *match_str = NULL;
+ guint i;
+
+ match_str = g_match_info_fetch_named (match_info, "Bands2G");
+ if (!match_str || match_str[0] == '\0') {
+ g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Could not find 2G band values from response");
+ goto out;
+ }
+
+ values = mm_parse_uint_list (match_str, &inner_error);
+ if (!values)
+ goto out;
+
+ for (i = 0; i < values->len; i++) {
+ guint value;
+
+ value = g_array_index (values, guint, i);
+ if (value < G_N_ELEMENTS (telit_2g_to_mm_band_mask)) {
+ guint j;
+
+ for (j = MM_MODEM_BAND_TELIT_2G_FIRST; j <= MM_MODEM_BAND_TELIT_2G_LAST; j++) {
+ if ((telit_2g_to_mm_band_mask[value] & B2G_FLAG (j)) && !mm_common_bands_garray_lookup (*bands, j))
+ *bands = g_array_append_val (*bands, j);
+ }
+ } else
+ mm_obj_dbg (log_object, "unhandled telit 2G band value configuration: %u", value);
+ }
+
+out:
+ g_free (match_str);
+ g_clear_pointer (&values, g_array_unref);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+telit_get_3g_mm_bands (GMatchInfo *match_info,
+ gpointer log_object,
+ gboolean modem_alternate_3g_bands,
+ GArray **bands,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ GArray *values = NULL;
+ gchar *match_str = NULL;
+ guint i;
+ const guint64 *telit_3g_to_mm_band_mask;
+ guint telit_3g_to_mm_band_mask_n_elements;
+
+ initialize_telit_3g_to_mm_band_masks ();
+
+ /* Select correct 3G band mask */
+ if (modem_alternate_3g_bands) {
+ telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_alternate;
+ telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_alternate);
+ } else {
+ telit_3g_to_mm_band_mask = telit_3g_to_mm_band_mask_default;
+ telit_3g_to_mm_band_mask_n_elements = G_N_ELEMENTS (telit_3g_to_mm_band_mask_default);
+ }
+
+ match_str = g_match_info_fetch_named (match_info, "Bands3G");
+ if (!match_str || match_str[0] == '\0') {
+ g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Could not find 3G band values from response");
+ goto out;
+ }
+
+ values = mm_parse_uint_list (match_str, &inner_error);
+ if (!values)
+ goto out;
+
+ for (i = 0; i < values->len; i++) {
+ guint value;
+
+ value = g_array_index (values, guint, i);
+
+ if (value < telit_3g_to_mm_band_mask_n_elements) {
+ guint j;
+
+ for (j = 0; j < G_N_ELEMENTS (band_utran_index); j++) {
+ /* ignore non-3G bands */
+ if (band_utran_index[j] == 0)
+ continue;
+
+ if ((telit_3g_to_mm_band_mask[value] & B3G_FLAG (j)) && !mm_common_bands_garray_lookup (*bands, j))
+ *bands = g_array_append_val (*bands, j);
+ }
+ } else
+ mm_obj_dbg (log_object, "unhandled telit 3G band value configuration: %u", value);
+ }
+
+out:
+ g_free (match_str);
+ g_clear_pointer (&values, g_array_unref);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+telit_get_4g_mm_bands (GMatchInfo *match_info,
+ GArray **bands,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ MMModemBand band;
+ gchar *match_str = NULL;
+ guint64 value;
+ gchar **tokens = NULL;
+ gboolean hex_format = FALSE;
+ gboolean ok;
+
+ match_str = g_match_info_fetch_named (match_info, "Bands4GDec");
+ if (!match_str) {
+ match_str = g_match_info_fetch_named (match_info, "Bands4GHex");
+ hex_format = match_str != NULL;
+ }
+
+ if (!match_str || match_str[0] == '\0') {
+ g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Could not find 4G band flags from response");
+ goto out;
+ }
+
+ /* splitting will never return NULL as string is not empty */
+ tokens = g_strsplit (match_str, "-", -1);
+
+ /* If this is a range, get upper threshold, which contains the total supported mask */
+ ok = hex_format?
+ mm_get_u64_from_hex_str (tokens[1] ? tokens[1] : tokens[0], &value):
+ mm_get_u64_from_str (tokens[1] ? tokens[1] : tokens[0], &value);
+ if (!ok) {
+ g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Could not parse 4G band mask from string: '%s'", match_str);
+ goto out;
+ }
+
+ for (band = MM_MODEM_BAND_TELIT_4G_FIRST; band <= MM_MODEM_BAND_TELIT_4G_LAST; band++) {
+ if ((value & B4G_FLAG (band)) && !mm_common_bands_garray_lookup (*bands, band))
+ g_array_append_val (*bands, band);
+ }
+
+out:
+ g_strfreev (tokens);
+ g_free (match_str);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+telit_get_ext_4g_mm_bands (GMatchInfo *match_info,
+ GArray **bands,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ MMModemBand band;
+ gchar *match_str = NULL;
+ gchar *match_str_ext = NULL;
+ guint64 value;
+
+ match_str = g_match_info_fetch_named (match_info, "Bands4GHex");
+ if (!match_str || match_str[0] == '\0') {
+ g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Could not find 4G band hex mask flag from response");
+ goto out;
+ }
+
+ if (!mm_get_u64_from_hex_str (match_str, &value)) {
+ g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Could not parse 4G band hex mask from string: '%s'", match_str);
+ goto out;
+ }
+
+ for (band = MM_MODEM_BAND_TELIT_4G_FIRST; band <= MM_MODEM_BAND_TELIT_4G_LAST; band++) {
+ if ((value & B4G_FLAG (band)) && !mm_common_bands_garray_lookup (*bands, band))
+ g_array_append_val (*bands, band);
+ }
+
+ /* extended bands */
+ match_str_ext = g_match_info_fetch_named (match_info, "Bands4GExt");
+ if (match_str_ext) {
+
+ if (!mm_get_u64_from_hex_str (match_str_ext, &value)) {
+ g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Could not parse 4G ext band mask from string: '%s'", match_str_ext);
+ goto out;
+ }
+
+ for (band = MM_MODEM_BAND_TELIT_EXT_4G_FIRST; band <= MM_MODEM_BAND_TELIT_EXT_4G_LAST; band++) {
+ if ((value & B4G_FLAG_EXT (band)) && !mm_common_bands_garray_lookup (*bands, band))
+ g_array_append_val (*bands, band);
+ }
+ }
+
+out:
+ g_free (match_str);
+ g_free (match_str_ext);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+typedef enum {
+ LOAD_BANDS_TYPE_SUPPORTED,
+ LOAD_BANDS_TYPE_CURRENT,
+} LoadBandsType;
+
+/* Regex tokens for #BND parsing */
+#define MM_SUPPORTED_BANDS_2G "\\s*\\((?P<Bands2G>[0-9\\-,]*)\\)"
+#define MM_SUPPORTED_BANDS_3G "(,\\s*\\((?P<Bands3G>[0-9\\-,]*)\\))?"
+#define MM_SUPPORTED_BANDS_4G_HEX "(,\\s*\\((?P<Bands4GHex>[0-9A-F\\-,]*)\\))?"
+#define MM_SUPPORTED_BANDS_4G_DEC "(,\\s*\\((?P<Bands4GDec>[0-9\\-,]*)\\))?"
+#define MM_SUPPORTED_BANDS_4G_EXT "(,\\s*\\((?P<Bands4GHex>[0-9A-F]+)\\))?(,\\s*\\((?P<Bands4GExt>[0-9A-F]+)\\))?"
+#define MM_CURRENT_BANDS_2G "\\s*(?P<Bands2G>\\d+)"
+#define MM_CURRENT_BANDS_3G "(,\\s*(?P<Bands3G>\\d+))?"
+#define MM_CURRENT_BANDS_4G_HEX "(,\\s*(?P<Bands4GHex>[0-9A-F]+))?"
+#define MM_CURRENT_BANDS_4G_DEC "(,\\s*(?P<Bands4GDec>\\d+))?"
+#define MM_CURRENT_BANDS_4G_EXT "(,\\s*(?P<Bands4GHex>[0-9A-F]+))?(,\\s*(?P<Bands4GExt>[0-9A-F]+))?"
+
+static GArray *
+common_parse_bnd_response (const gchar *response,
+ MMTelitBNDParseConfig *config,
+ LoadBandsType load_type,
+ gpointer log_object,
+ GError **error)
+{
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autoptr(GRegex) r = NULL;
+ GError *inner_error = NULL;
+ GArray *bands = NULL;
+ const gchar *load_bands_regex = NULL;
+
+ static const gchar *load_bands_regex_4g_hex[] = {
+ [LOAD_BANDS_TYPE_SUPPORTED] = "#BND:"MM_SUPPORTED_BANDS_2G MM_SUPPORTED_BANDS_3G MM_SUPPORTED_BANDS_4G_HEX,
+ [LOAD_BANDS_TYPE_CURRENT] = "#BND:"MM_CURRENT_BANDS_2G MM_CURRENT_BANDS_3G MM_CURRENT_BANDS_4G_HEX,
+
+ };
+ static const gchar *load_bands_regex_4g_dec[] = {
+ [LOAD_BANDS_TYPE_SUPPORTED] = "#BND:"MM_SUPPORTED_BANDS_2G MM_SUPPORTED_BANDS_3G MM_SUPPORTED_BANDS_4G_DEC,
+ [LOAD_BANDS_TYPE_CURRENT] = "#BND:"MM_CURRENT_BANDS_2G MM_CURRENT_BANDS_3G MM_CURRENT_BANDS_4G_DEC,
+ };
+ static const gchar *load_bands_regex_4g_ext[] = {
+ [LOAD_BANDS_TYPE_SUPPORTED] = "#BND:"MM_SUPPORTED_BANDS_2G MM_SUPPORTED_BANDS_3G MM_SUPPORTED_BANDS_4G_EXT,
+ [LOAD_BANDS_TYPE_CURRENT] = "#BND:"MM_CURRENT_BANDS_2G MM_CURRENT_BANDS_3G MM_CURRENT_BANDS_4G_EXT,
+ };
+
+ if (config->modem_ext_4g_bands)
+ load_bands_regex = load_bands_regex_4g_ext[load_type];
+ else if (config->modem_has_hex_format_4g_bands)
+ load_bands_regex = load_bands_regex_4g_hex[load_type];
+ else
+ load_bands_regex = load_bands_regex_4g_dec[load_type];
+
+ r = g_regex_new (load_bands_regex, G_REGEX_RAW, 0, NULL);
+ g_assert (r);
+
+ if (!g_regex_match (r, response, 0, &match_info)) {
+ g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Could not parse response '%s'", response);
+ goto out;
+ }
+
+ if (!g_match_info_matches (match_info)) {
+ g_set_error (&inner_error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Could not find matches in response '%s'", response);
+ goto out;
+ }
+
+ bands = g_array_new (TRUE, TRUE, sizeof (MMModemBand));
+
+ if (config->modem_is_2g && !telit_get_2g_mm_bands (match_info, log_object, &bands, &inner_error))
+ goto out;
+
+ if (config->modem_is_3g && !telit_get_3g_mm_bands (match_info, log_object, config->modem_alternate_3g_bands, &bands, &inner_error))
+ goto out;
+
+ if (config->modem_is_4g) {
+ gboolean ok;
+
+ ok = config->modem_ext_4g_bands?
+ telit_get_ext_4g_mm_bands (match_info, &bands, &inner_error) :
+ telit_get_4g_mm_bands (match_info, &bands, &inner_error);
+ if (!ok)
+ goto out;
+ }
+out:
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ g_clear_pointer (&bands, g_array_unref);
+ return NULL;
+ }
+
+ return bands;
+}
+
+GArray *
+mm_telit_parse_bnd_query_response (const gchar *response,
+ MMTelitBNDParseConfig *config,
+ gpointer log_object,
+ GError **error)
+{
+ return common_parse_bnd_response (response,
+ config,
+ LOAD_BANDS_TYPE_CURRENT,
+ log_object,
+ error);
+}
+
+GArray *
+mm_telit_parse_bnd_test_response (const gchar *response,
+ MMTelitBNDParseConfig *config,
+ gpointer log_object,
+ GError **error)
+{
+ return common_parse_bnd_response (response,
+ config,
+ LOAD_BANDS_TYPE_SUPPORTED,
+ log_object,
+ error);
+}
+
+/*****************************************************************************/
+/* #QSS? response parser */
+
+MMTelitQssStatus
+mm_telit_parse_qss_query (const gchar *response,
+ GError **error)
+{
+ gint qss_status;
+ gint qss_mode;
+
+ qss_status = QSS_STATUS_UNKNOWN;
+ if (sscanf (response, "#QSS: %d,%d", &qss_mode, &qss_status) != 2) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Could not parse \"#QSS?\" response: %s", response);
+ return QSS_STATUS_UNKNOWN;
+ }
+
+ switch (qss_status) {
+ case QSS_STATUS_SIM_REMOVED:
+ case QSS_STATUS_SIM_INSERTED:
+ case QSS_STATUS_SIM_INSERTED_AND_UNLOCKED:
+ case QSS_STATUS_SIM_INSERTED_AND_READY:
+ return (MMTelitQssStatus) qss_status;
+ default:
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown QSS status value given: %d", qss_status);
+ return QSS_STATUS_UNKNOWN;
+ }
+}
+
+/*****************************************************************************/
+/* Supported modes list helper */
+
+GArray *
+mm_telit_build_modes_list (void)
+{
+ GArray *combinations;
+ MMModemModeCombination mode;
+
+ /* Build list of combinations for 3GPP devices */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 7);
+
+ /* 2G only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 3G only */
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 4G only */
+ mode.allowed = MM_MODEM_MODE_4G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 4G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 3G and 4G */
+ mode.allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G, 3G and 4G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ return combinations;
+}
+
+/*****************************************************************************/
+/* Software Package version response parser */
+
+gchar *
+mm_telit_parse_swpkgv_response (const gchar *response)
+{
+ gchar *version = NULL;
+ g_autofree gchar *base_version = NULL;
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ guint matches;
+
+ /* We are interested only in the first line of the response */
+ r = g_regex_new ("(?P<Base>\\d{2}.\\d{2}.*)",
+ G_REGEX_RAW | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF,
+ G_REGEX_MATCH_NEWLINE_CR,
+ NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, NULL)) {
+ return NULL;
+ }
+
+ matches = g_match_info_get_match_count (match_info);
+ if (matches < 2 || matches > 4) {
+ return NULL;
+ }
+
+ base_version = g_match_info_fetch_named (match_info, "Base");
+ if (base_version)
+ version = g_strdup (base_version);
+
+ return version;
+}
+
+/*****************************************************************************/
+/* MM Telit Model from revision string */
+
+MMTelitModel
+mm_telit_model_from_revision (const gchar *revision)
+{
+ guint i;
+ static const struct {
+ const gchar *revision_prefix;
+ MMTelitModel model;
+ } revision_to_model_map [] = {
+ {"24.01", MM_TELIT_MODEL_LM940},
+ {"25.", MM_TELIT_MODEL_LE910C1},
+ {"32.", MM_TELIT_MODEL_LM960},
+ {"38.", MM_TELIT_MODEL_FN980},
+ {"40.", MM_TELIT_MODEL_LN920},
+ {"45.00", MM_TELIT_MODEL_FN990},
+ };
+
+ if (!revision)
+ return MM_TELIT_MODEL_DEFAULT;
+
+ for (i = 0; i < G_N_ELEMENTS (revision_to_model_map); ++i) {
+ if (g_str_has_prefix (revision, revision_to_model_map[i].revision_prefix))
+ return revision_to_model_map[i].model;
+ }
+
+ return MM_TELIT_MODEL_DEFAULT;
+}
+
+static MMTelitSwRevCmp lm9x0_software_revision_cmp (const gchar *revision_a,
+ const gchar *revision_b)
+{
+ /* LM940 and LM960 share the same software revision format
+ * WW.XY.ABC[-ZZZZ], where WW is the chipset code and C the major version.
+ * If WW is the same, the other values X, Y, A and B are also the same, so
+ * we can limit the comparison to C only. ZZZZ is the minor version (it
+ * includes if version is beta, test, or alpha), but at this stage we are
+ * not interested in compare it. */
+ guint chipset_a, chipset_b;
+ guint major_a, major_b;
+ guint x, y, a, b;
+
+ g_return_val_if_fail (
+ sscanf (revision_a, "%2u.%1u%1u.%1u%1u%1u", &chipset_a, &x, &y, &a, &b, &major_a) == 6,
+ MM_TELIT_SW_REV_CMP_INVALID);
+ g_return_val_if_fail (
+ sscanf (revision_b, "%2u.%1u%1u.%1u%1u%1u", &chipset_b, &x, &y, &a, &b, &major_b) == 6,
+ MM_TELIT_SW_REV_CMP_INVALID);
+
+ if (chipset_a != chipset_b)
+ return MM_TELIT_SW_REV_CMP_INVALID;
+ if (major_a > major_b)
+ return MM_TELIT_SW_REV_CMP_NEWER;
+ if (major_a < major_b)
+ return MM_TELIT_SW_REV_CMP_OLDER;
+ return MM_TELIT_SW_REV_CMP_EQUAL;
+}
+
+MMTelitSwRevCmp mm_telit_software_revision_cmp (const gchar *revision_a,
+ const gchar *revision_b)
+{
+ MMTelitModel model_a;
+ MMTelitModel model_b;
+
+ model_a = mm_telit_model_from_revision (revision_a);
+ model_b = mm_telit_model_from_revision (revision_b);
+
+ if ((model_a == MM_TELIT_MODEL_LM940 || model_a == MM_TELIT_MODEL_LM960) &&
+ (model_b == MM_TELIT_MODEL_LM940 || model_b == MM_TELIT_MODEL_LM960)) {
+ return lm9x0_software_revision_cmp (revision_a, revision_b);
+ }
+
+ return MM_TELIT_SW_REV_CMP_UNSUPPORTED;
+}
diff --git a/src/plugins/telit/mm-modem-helpers-telit.h b/src/plugins/telit/mm-modem-helpers-telit.h
new file mode 100644
index 00000000..38449769
--- /dev/null
+++ b/src/plugins/telit/mm-modem-helpers-telit.h
@@ -0,0 +1,90 @@
+/* -*- 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) 2015-2019 Telit.
+ * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+#ifndef MM_MODEM_HELPERS_TELIT_H
+#define MM_MODEM_HELPERS_TELIT_H
+
+#include <glib.h>
+#include "ModemManager.h"
+
+typedef enum {
+ MM_TELIT_MODEL_DEFAULT,
+ MM_TELIT_MODEL_FN980,
+ MM_TELIT_MODEL_LE910C1,
+ MM_TELIT_MODEL_LM940,
+ MM_TELIT_MODEL_LM960,
+ MM_TELIT_MODEL_LN920,
+ MM_TELIT_MODEL_FN990,
+} MMTelitModel;
+
+typedef struct {
+ gboolean modem_is_2g;
+ gboolean modem_is_3g;
+ gboolean modem_is_4g;
+ gboolean modem_alternate_3g_bands;
+ gboolean modem_has_hex_format_4g_bands;
+ gboolean modem_ext_4g_bands;
+} MMTelitBNDParseConfig;
+
+typedef enum {
+ MM_TELIT_SW_REV_CMP_INVALID,
+ MM_TELIT_SW_REV_CMP_UNSUPPORTED,
+ MM_TELIT_SW_REV_CMP_OLDER,
+ MM_TELIT_SW_REV_CMP_EQUAL,
+ MM_TELIT_SW_REV_CMP_NEWER,
+} MMTelitSwRevCmp;
+
+/* #BND response parsers and request builder */
+GArray *mm_telit_parse_bnd_query_response (const gchar *response,
+ MMTelitBNDParseConfig *config,
+ gpointer log_object,
+ GError **error);
+GArray *mm_telit_parse_bnd_test_response (const gchar *response,
+ MMTelitBNDParseConfig *config,
+ gpointer log_object,
+ GError **error);
+gchar *mm_telit_build_bnd_request (GArray *bands_array,
+ MMTelitBNDParseConfig *config,
+ GError **error);
+
+/* #QSS? response parser */
+typedef enum { /*< underscore_name=mm_telit_qss_status >*/
+ QSS_STATUS_UNKNOWN = -1,
+ QSS_STATUS_SIM_REMOVED,
+ QSS_STATUS_SIM_INSERTED,
+ QSS_STATUS_SIM_INSERTED_AND_UNLOCKED,
+ QSS_STATUS_SIM_INSERTED_AND_READY,
+} MMTelitQssStatus;
+
+MMTelitQssStatus mm_telit_parse_qss_query (const gchar *response, GError **error);
+
+/* CSIM lock state */
+typedef enum { /*< underscore_name=mm_telit_csim_lock_state >*/
+ CSIM_LOCK_STATE_UNKNOWN,
+ CSIM_LOCK_STATE_UNLOCKED,
+ CSIM_LOCK_STATE_LOCK_REQUESTED,
+ CSIM_LOCK_STATE_LOCKED,
+} MMTelitCsimLockState;
+
+GArray *mm_telit_build_modes_list (void);
+
+gchar *mm_telit_parse_swpkgv_response (const gchar *response);
+
+MMTelitModel mm_telit_model_from_revision (const gchar *revision);
+
+MMTelitSwRevCmp mm_telit_software_revision_cmp (const gchar *reference,
+ const gchar *revision);
+
+#endif /* MM_MODEM_HELPERS_TELIT_H */
diff --git a/src/plugins/telit/mm-plugin-telit.c b/src/plugins/telit/mm-plugin-telit.c
new file mode 100644
index 00000000..d19966d6
--- /dev/null
+++ b/src/plugins/telit/mm-plugin-telit.c
@@ -0,0 +1,132 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2013 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-plugin-telit.h"
+#include "mm-common-telit.h"
+#include "mm-broadband-modem-telit.h"
+
+
+#if defined WITH_QMI
+# include "mm-broadband-modem-qmi.h"
+#endif
+
+#if defined WITH_MBIM
+# include "mm-broadband-modem-mbim-telit.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginTelit, mm_plugin_telit, 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 Telit modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_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 Telit modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_telit_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product,
+ subsystem_vendor));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_telit_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 };
+ /* Vendors: Telit */
+ static const guint16 vendor_ids[] = { 0x1bc7, 0 };
+ static const mm_uint16_pair subsystem_vendor_ids[] = {
+ { 0x17cb, 0x1c5d }, /* FN990 */
+ { 0, 0 }
+ };
+ static const gchar *vendor_strings[] = { "telit", NULL };
+ /* Custom init for port identification */
+ static const MMAsyncMethod custom_init = {
+ .async = G_CALLBACK (telit_custom_init),
+ .finish = G_CALLBACK (telit_custom_init_finish),
+ };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_TELIT,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_SUBSYSTEM_VENDOR_IDS, subsystem_vendor_ids,
+ MM_PLUGIN_ALLOWED_VENDOR_STRINGS, vendor_strings,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ MM_PLUGIN_ALLOWED_MBIM, TRUE,
+ MM_PLUGIN_ALLOWED_QCDM, TRUE,
+ MM_PLUGIN_CUSTOM_INIT, &custom_init,
+ NULL));
+}
+
+static void
+mm_plugin_telit_init (MMPluginTelit *self)
+{
+}
+
+static void
+mm_plugin_telit_class_init (MMPluginTelitClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+ plugin_class->grab_port = telit_grab_port;
+}
diff --git a/src/plugins/telit/mm-plugin-telit.h b/src/plugins/telit/mm-plugin-telit.h
new file mode 100644
index 00000000..0c61fbb8
--- /dev/null
+++ b/src/plugins/telit/mm-plugin-telit.h
@@ -0,0 +1,42 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2013 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_PLUGIN_TELIT_H
+#define MM_PLUGIN_TELIT_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_TELIT (mm_plugin_telit_get_type ())
+#define MM_PLUGIN_TELIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_TELIT, MMPluginTelit))
+#define MM_PLUGIN_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_TELIT, MMPluginTelitClass))
+#define MM_IS_PLUGIN_TELIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_TELIT))
+#define MM_IS_PLUGIN_TELIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_TELIT))
+#define MM_PLUGIN_TELIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_TELIT, MMPluginTelitClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginTelit;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginTelitClass;
+
+GType mm_plugin_telit_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_TELIT_H */
diff --git a/src/plugins/telit/mm-shared-telit.c b/src/plugins/telit/mm-shared-telit.c
new file mode 100644
index 00000000..09c122bb
--- /dev/null
+++ b/src/plugins/telit/mm-shared-telit.c
@@ -0,0 +1,795 @@
+/* -*- 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) 2019 Daniele Palmas <dnlplm@gmail.com>
+ */
+
+#include <config.h>
+
+#include <stdio.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-location.h"
+#include "mm-base-modem.h"
+#include "mm-base-modem-at.h"
+#include "mm-modem-helpers-telit.h"
+#include "mm-shared-telit.h"
+
+/*****************************************************************************/
+/* Private data context */
+
+#define TELIT_LM940_EXT_LTE_BND_SW_REVISION "24.01.516"
+
+#define PRIVATE_TAG "shared-telit-private-tag"
+static GQuark private_quark;
+
+typedef struct {
+ MMIfaceModem *iface_modem_parent;
+ gboolean alternate_3g_bands;
+ gboolean ext_4g_bands;
+ GArray *supported_bands;
+ GArray *supported_modes;
+ gchar *software_package_version;
+} Private;
+
+static void
+private_free (Private *priv)
+{
+ if (priv->supported_bands)
+ g_array_unref (priv->supported_bands);
+ if (priv->supported_modes)
+ g_array_unref (priv->supported_modes);
+ g_free (priv->software_package_version);
+ g_slice_free (Private, priv);
+}
+
+static gboolean
+has_alternate_3g_bands (const gchar *revision)
+{
+ MMTelitModel model;
+
+ model = mm_telit_model_from_revision (revision);
+ return (model == MM_TELIT_MODEL_FN980 ||
+ model == MM_TELIT_MODEL_FN990 ||
+ model == MM_TELIT_MODEL_LM940 ||
+ model == MM_TELIT_MODEL_LM960 ||
+ model == MM_TELIT_MODEL_LN920);
+}
+
+static gboolean
+is_bnd_4g_format_hex (const gchar *revision)
+{
+ MMTelitModel model;
+
+ model = mm_telit_model_from_revision (revision);
+
+ return (model == MM_TELIT_MODEL_FN980 ||
+ model == MM_TELIT_MODEL_LE910C1 ||
+ model == MM_TELIT_MODEL_LM940 ||
+ model == MM_TELIT_MODEL_LM960 ||
+ model == MM_TELIT_MODEL_LN920);
+}
+
+static gboolean
+has_extended_4g_bands (const gchar *revision)
+{
+ MMTelitModel model;
+
+ model = mm_telit_model_from_revision (revision);
+ if (model == MM_TELIT_MODEL_LM940)
+ return mm_telit_software_revision_cmp (revision, TELIT_LM940_EXT_LTE_BND_SW_REVISION) >= MM_TELIT_SW_REV_CMP_EQUAL;
+
+ return (model == MM_TELIT_MODEL_FN980 ||
+ model == MM_TELIT_MODEL_FN990 ||
+ model == MM_TELIT_MODEL_LM960 ||
+ model == MM_TELIT_MODEL_LN920);
+}
+
+static Private *
+get_private (MMSharedTelit *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);
+
+ if (MM_SHARED_TELIT_GET_INTERFACE (self)->peek_parent_modem_interface)
+ priv->iface_modem_parent = MM_SHARED_TELIT_GET_INTERFACE (self)->peek_parent_modem_interface (self);
+
+ g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
+ }
+
+ return priv;
+}
+
+void
+mm_shared_telit_store_supported_modes (MMSharedTelit *self,
+ GArray *modes)
+{
+ Private *priv;
+
+ priv = get_private (MM_SHARED_TELIT (self));
+ priv->supported_modes = g_array_ref (modes);
+}
+
+void
+mm_shared_telit_store_revision (MMSharedTelit *self,
+ const gchar *revision)
+{
+ Private *priv;
+
+ priv = get_private (MM_SHARED_TELIT (self));
+ g_clear_pointer (&priv->software_package_version, g_free);
+ priv->software_package_version = g_strdup (revision);
+ priv->alternate_3g_bands = has_alternate_3g_bands (revision);
+ priv->ext_4g_bands = has_extended_4g_bands (revision);
+}
+
+void
+mm_shared_telit_get_bnd_parse_config (MMIfaceModem *self, MMTelitBNDParseConfig *config)
+{
+ Private *priv;
+
+ priv = get_private (MM_SHARED_TELIT (self));
+
+ config->modem_is_2g = mm_iface_modem_is_2g (self);
+ config->modem_is_3g = mm_iface_modem_is_3g (self);
+ config->modem_is_4g = mm_iface_modem_is_4g (self);
+ config->modem_alternate_3g_bands = priv->alternate_3g_bands;
+ config->modem_has_hex_format_4g_bands = is_bnd_4g_format_hex (priv->software_package_version);
+ config->modem_ext_4g_bands = priv->ext_4g_bands;
+}
+
+/*****************************************************************************/
+/* Load current mode (Modem interface) */
+
+gboolean
+mm_shared_telit_load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ const gchar *response;
+ const gchar *str;
+ gint a;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return FALSE;
+
+ str = mm_strip_tag (response, "+WS46: ");
+
+ if (!sscanf (str, "%d", &a)) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse +WS46 response: '%s'",
+ response);
+ return FALSE;
+ }
+
+ *preferred = MM_MODEM_MODE_NONE;
+ switch (a) {
+ case 12:
+ *allowed = MM_MODEM_MODE_2G;
+ return TRUE;
+ case 22:
+ *allowed = MM_MODEM_MODE_3G;
+ return TRUE;
+ case 25:
+ if (mm_iface_modem_is_3gpp_lte (self))
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ else
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ return TRUE;
+ case 28:
+ *allowed = MM_MODEM_MODE_4G;
+ return TRUE;
+ case 29:
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ return TRUE;
+ case 30:
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G);
+ return TRUE;
+ case 31:
+ *allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ return TRUE;
+ default:
+ break;
+ }
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse unexpected +WS46 response: '%s'",
+ response);
+ return FALSE;
+}
+
+void
+mm_shared_telit_load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+WS46?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load supported bands (Modem interface) */
+
+GArray *
+mm_shared_telit_modem_load_supported_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_supported_bands_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_TELIT (self));
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response)
+ g_task_return_error (task, error);
+ else {
+ GArray *bands;
+ MMTelitBNDParseConfig config;
+
+ mm_shared_telit_get_bnd_parse_config (MM_IFACE_MODEM (self), &config);
+
+ bands = mm_telit_parse_bnd_test_response (response, &config, self, &error);
+ if (!bands)
+ g_task_return_error (task, error);
+ else {
+ /* Store supported bands to be able to build ANY when setting */
+ priv->supported_bands = g_array_ref (bands);
+ if (priv->ext_4g_bands)
+ mm_obj_dbg (self, "telit modem using extended 4G band setup");
+ g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
+ }
+ }
+
+ g_object_unref (task);
+}
+
+static void
+load_supported_bands_at (MMIfaceModem *self,
+ GTask *task)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "#BND=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback) load_supported_bands_ready,
+ task);
+}
+
+static void
+parent_load_supported_bands_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GArray *bands;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_TELIT (self));
+
+ bands = priv->iface_modem_parent->load_supported_bands_finish (MM_IFACE_MODEM (self), res, &error);
+ if (bands) {
+ g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
+ g_object_unref (task);
+ } else {
+ mm_obj_dbg (self, "parent load supported bands failure, falling back to AT commands");
+ load_supported_bands_at (self, task);
+ g_clear_error (&error);
+ }
+}
+
+void
+mm_shared_telit_modem_load_supported_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Private *priv;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ priv = get_private (MM_SHARED_TELIT (self));
+
+ if (priv->iface_modem_parent &&
+ priv->iface_modem_parent->load_supported_bands &&
+ priv->iface_modem_parent->load_supported_bands_finish) {
+ priv->iface_modem_parent->load_supported_bands (self,
+ (GAsyncReadyCallback) parent_load_supported_bands_ready,
+ task);
+ } else
+ load_supported_bands_at (self, task);
+}
+
+/*****************************************************************************/
+/* Load current bands (Modem interface) */
+
+GArray *
+mm_shared_telit_modem_load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_current_bands_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response)
+ g_task_return_error (task, error);
+ else {
+ GArray *bands;
+ MMTelitBNDParseConfig config;
+
+ mm_shared_telit_get_bnd_parse_config (MM_IFACE_MODEM (self), &config);
+
+ bands = mm_telit_parse_bnd_query_response (response, &config, self, &error);
+ if (!bands)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
+ }
+
+ g_object_unref (task);
+}
+
+static void
+load_current_bands_at (MMIfaceModem *self,
+ GTask *task)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "#BND?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) load_current_bands_ready,
+ task);
+}
+
+static void
+parent_load_current_bands_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GArray *bands;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_TELIT (self));
+
+ bands = priv->iface_modem_parent->load_current_bands_finish (MM_IFACE_MODEM (self), res, &error);
+ if (bands) {
+ g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref);
+ g_object_unref (task);
+ } else {
+ mm_obj_dbg (self, "parent load current bands failure, falling back to AT commands");
+ load_current_bands_at (self, task);
+ g_clear_error (&error);
+ }
+}
+
+void
+mm_shared_telit_modem_load_current_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Private *priv;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ priv = get_private (MM_SHARED_TELIT (self));
+
+ if (priv->iface_modem_parent &&
+ priv->iface_modem_parent->load_current_bands &&
+ priv->iface_modem_parent->load_current_bands_finish) {
+ priv->iface_modem_parent->load_current_bands (self,
+ (GAsyncReadyCallback) parent_load_current_bands_ready,
+ task);
+ } else
+ load_current_bands_at (self, task);
+}
+
+/*****************************************************************************/
+/* Set current bands (Modem interface) */
+
+gboolean
+mm_shared_telit_modem_set_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+set_current_bands_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (self, res, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+set_current_bands_at (MMIfaceModem *self,
+ GTask *task)
+{
+ GError *error = NULL;
+ gchar *cmd;
+ GArray *bands_array;
+ MMTelitBNDParseConfig config;
+
+ bands_array = g_task_get_task_data (task);
+ g_assert (bands_array);
+
+ if (bands_array->len == 1 && g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
+ Private *priv;
+
+ priv = get_private (MM_SHARED_TELIT (self));
+ if (!priv->supported_bands) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't build ANY band settings: unknown supported bands");
+ g_object_unref (task);
+ return;
+ }
+ bands_array = priv->supported_bands;
+ }
+
+ mm_shared_telit_get_bnd_parse_config (self, &config);
+ cmd = mm_telit_build_bnd_request (bands_array, &config, &error);
+ if (!cmd) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 20,
+ FALSE,
+ (GAsyncReadyCallback)set_current_bands_ready,
+ task);
+ g_free (cmd);
+}
+
+static void
+parent_set_current_bands_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_TELIT (self));
+
+ if (priv->iface_modem_parent->set_current_bands_finish (MM_IFACE_MODEM (self), res, &error)) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ } else {
+ g_clear_error (&error);
+ set_current_bands_at (self, task);
+ }
+}
+
+void
+mm_shared_telit_modem_set_current_bands (MMIfaceModem *self,
+ GArray *bands_array,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_TELIT (self));
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, g_array_ref (bands_array), (GDestroyNotify)g_array_unref);
+
+ if (priv->iface_modem_parent &&
+ priv->iface_modem_parent->set_current_bands &&
+ priv->iface_modem_parent->set_current_bands_finish) {
+ priv->iface_modem_parent->set_current_bands (self,
+ bands_array,
+ (GAsyncReadyCallback) parent_set_current_bands_ready,
+ task);
+ } else
+ set_current_bands_at (self, task);
+}
+
+/*****************************************************************************/
+/* Set current modes (Modem interface) */
+
+gboolean
+mm_shared_telit_set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+ws46_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (self, res, &error);
+ if (error)
+ /* Let the error be critical. */
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_telit_set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *command;
+ Private *priv;
+ gint ws46_mode = -1;
+
+ priv = get_private (MM_SHARED_TELIT (self));
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (allowed == MM_MODEM_MODE_ANY && priv->supported_modes) {
+ guint i;
+
+ allowed = MM_MODEM_MODE_NONE;
+ /* Process list of modes to gather supported ones */
+ for (i = 0; i < priv->supported_modes->len; i++) {
+ if (g_array_index (priv->supported_modes, MMModemMode, i) & MM_MODEM_MODE_2G)
+ allowed |= MM_MODEM_MODE_2G;
+ if (g_array_index (priv->supported_modes, MMModemMode, i) & MM_MODEM_MODE_3G)
+ allowed |= MM_MODEM_MODE_3G;
+ if (g_array_index (priv->supported_modes, MMModemMode, i) & MM_MODEM_MODE_4G)
+ allowed |= MM_MODEM_MODE_4G;
+ }
+ }
+
+ if (allowed == MM_MODEM_MODE_2G)
+ ws46_mode = 12;
+ else if (allowed == MM_MODEM_MODE_3G)
+ ws46_mode = 22;
+ else if (allowed == MM_MODEM_MODE_4G)
+ ws46_mode = 28;
+ else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) {
+ if (mm_iface_modem_is_3gpp_lte (self))
+ ws46_mode = 29;
+ else
+ ws46_mode = 25;
+ } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G))
+ ws46_mode = 30;
+ else if (allowed == (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G))
+ ws46_mode = 31;
+ else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G))
+ ws46_mode = 25;
+
+ /* Telit modems do not support preferred mode selection */
+ if ((ws46_mode < 0) || (preferred != MM_MODEM_MODE_NONE)) {
+ gchar *allowed_str;
+ gchar *preferred_str;
+
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Requested mode (allowed: '%s', preferred: '%s') not "
+ "supported by the modem.",
+ allowed_str,
+ preferred_str);
+ g_free (allowed_str);
+ g_free (preferred_str);
+
+ g_object_unref (task);
+ return;
+ }
+
+ command = g_strdup_printf ("AT+WS46=%d", ws46_mode);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback) ws46_set_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Revision loading */
+
+gchar *
+mm_shared_telit_modem_load_revision_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_revision_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error;
+ GVariant *result;
+
+ result = mm_base_modem_at_sequence_finish (self, res, NULL, &error);
+ if (!result) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ } else {
+ gchar *revision = NULL;
+
+ revision = g_variant_dup_string (result, NULL);
+ mm_shared_telit_store_revision (MM_SHARED_TELIT (self), revision);
+ g_task_return_pointer (task, revision, g_free);
+ g_object_unref (task);
+ }
+}
+
+/*
+ * parse AT#SWPKGV command
+ * Execution command returns the software package version without #SWPKGV: command echo.
+ * The response is as follows:
+ *
+ * AT#SWPKGV
+ * <Telit Software Package Version>-<Production Parameters Version>
+ * <Modem FW Version> (Usually the same value returned by AT+GMR)
+ * <Production Parameters Version>
+ * <Application FW Version>
+ */
+static MMBaseModemAtResponseProcessorResult
+software_package_version_ready (MMBaseModem *self,
+ gpointer none,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ gchar *version = NULL;
+
+ if (error) {
+ *result = NULL;
+
+ /* Ignore AT errors (ie, ERROR or CMx ERROR) */
+ if (error->domain != MM_MOBILE_EQUIPMENT_ERROR || last_command) {
+ *result_error = g_error_copy (error);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
+ }
+
+ *result_error = NULL;
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
+ }
+
+ version = mm_telit_parse_swpkgv_response (response);
+ if (!version)
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
+
+ *result = g_variant_new_take_string (version);
+ return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
+}
+
+static const MMBaseModemAtCommand revisions[] = {
+ { "#SWPKGV", 3, TRUE, software_package_version_ready },
+ { "+CGMR", 3, TRUE, mm_base_modem_response_processor_string_ignore_at_errors },
+ { "+GMR", 3, TRUE, mm_base_modem_response_processor_string_ignore_at_errors },
+ { NULL }
+};
+
+void
+mm_shared_telit_modem_load_revision (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Private *priv;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ priv = get_private (MM_SHARED_TELIT (self));
+
+ mm_obj_dbg (self, "loading revision...");
+ if (priv->software_package_version) {
+ g_task_return_pointer (task,
+ g_strdup (priv->software_package_version),
+ g_free);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_sequence (
+ MM_BASE_MODEM (self),
+ revisions,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ (GAsyncReadyCallback) load_revision_ready,
+ task);
+}
+
+/*****************************************************************************/
+
+static void
+shared_telit_init (gpointer g_iface)
+{
+}
+
+GType
+mm_shared_telit_get_type (void)
+{
+ static GType shared_telit_type = 0;
+
+ if (!G_UNLIKELY (shared_telit_type)) {
+ static const GTypeInfo info = {
+ sizeof (MMSharedTelit), /* class_size */
+ shared_telit_init, /* base_init */
+ NULL, /* base_finalize */
+ };
+
+ shared_telit_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedTelit", &info, 0);
+ g_type_interface_add_prerequisite (shared_telit_type, MM_TYPE_IFACE_MODEM);
+ }
+
+ return shared_telit_type;
+}
+
diff --git a/src/plugins/telit/mm-shared-telit.h b/src/plugins/telit/mm-shared-telit.h
new file mode 100644
index 00000000..bf093ea5
--- /dev/null
+++ b/src/plugins/telit/mm-shared-telit.h
@@ -0,0 +1,107 @@
+/* -*- 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) 2019 Daniele Palmas <dnlplm@gmail.com>
+ */
+
+#ifndef MM_SHARED_TELIT_H
+#define MM_SHARED_TELIT_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-location.h"
+#include "mm-modem-helpers-telit.h"
+
+#define MM_TYPE_SHARED_TELIT (mm_shared_telit_get_type ())
+#define MM_SHARED_TELIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_TELIT, MMSharedTelit))
+#define MM_IS_SHARED_TELIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_TELIT))
+#define MM_SHARED_TELIT_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_TELIT, MMSharedTelit))
+
+typedef struct _MMSharedTelit MMSharedTelit;
+
+struct _MMSharedTelit {
+ GTypeInterface g_iface;
+
+ /* Peek modem interface of the parent class of the object */
+ MMIfaceModem * (* peek_parent_modem_interface) (MMSharedTelit *self);
+};
+
+GType mm_shared_telit_get_type (void);
+
+void mm_shared_telit_store_supported_modes (MMSharedTelit *self,
+ GArray *modes);
+
+gboolean mm_shared_telit_load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error);
+
+void mm_shared_telit_load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean mm_shared_telit_set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_telit_set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+void mm_shared_telit_modem_load_supported_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GArray * mm_shared_telit_modem_load_supported_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_telit_modem_load_current_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GArray * mm_shared_telit_modem_load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+gboolean mm_shared_telit_modem_set_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_telit_modem_set_current_bands (MMIfaceModem *self,
+ GArray *bands_array,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+void mm_shared_telit_modem_load_revision (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gchar * mm_shared_telit_modem_load_revision_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_telit_store_revision (MMSharedTelit *self,
+ const gchar *revision);
+
+void mm_shared_telit_get_bnd_parse_config (MMIfaceModem *self,
+ MMTelitBNDParseConfig *config);
+#endif /* MM_SHARED_TELIT_H */
diff --git a/src/plugins/telit/mm-shared.c b/src/plugins/telit/mm-shared.c
new file mode 100644
index 00000000..ad2843dd
--- /dev/null
+++ b/src/plugins/telit/mm-shared.c
@@ -0,0 +1,20 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-shared.h"
+
+MM_SHARED_DEFINE_MAJOR_VERSION
+MM_SHARED_DEFINE_MINOR_VERSION
+MM_SHARED_DEFINE_NAME(Telit)
diff --git a/src/plugins/telit/tests/test-mm-modem-helpers-telit.c b/src/plugins/telit/tests/test-mm-modem-helpers-telit.c
new file mode 100644
index 00000000..e14ba6ba
--- /dev/null
+++ b/src/plugins/telit/tests/test-mm-modem-helpers-telit.c
@@ -0,0 +1,695 @@
+/* -*- 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) 2015-2019 Telit
+ * Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ *
+ */
+#include <stdio.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <locale.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-test.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-telit.h"
+
+#include "test-helpers.h"
+
+/******************************************************************************/
+
+#define MAX_BANDS_LIST_LEN 17
+
+typedef struct {
+ const gchar *response;
+ MMTelitBNDParseConfig config;
+ guint mm_bands_len;
+ MMModemBand mm_bands [MAX_BANDS_LIST_LEN];
+} BndResponseTest;
+
+static BndResponseTest supported_band_mapping_tests [] = {
+ {
+ "#BND: (0-3)", {TRUE, FALSE, FALSE, FALSE, FALSE, FALSE}, 4,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_G850 }
+ },
+ {
+ "#BND: (0-3),(0,2,5,6)", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 7,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8 }
+ },
+ {
+ "#BND: (0,3),(0,2,5,6)", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 7,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8 }
+ },
+ {
+ "#BND: (0,2),(0,2,5,6)", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 6,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8 }
+ },
+ {
+ "#BND: (0,2),(0-4,5,6)", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 7,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8 }
+ },
+ {
+ "#BND: (0-3),(0,2,5,6),(1-1)", {TRUE, TRUE, TRUE, FALSE, FALSE, FALSE}, 8,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_EUTRAN_1 }
+ },
+ {
+ "#BND: (0),(0),(1-3)", {TRUE, TRUE, TRUE, FALSE, FALSE, FALSE}, 5,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_EUTRAN_1,
+ MM_MODEM_BAND_EUTRAN_2 }
+ },
+ {
+ "#BND: (0),(0),(1-3)", {FALSE, FALSE, TRUE, FALSE, FALSE, FALSE}, 2,
+ { MM_MODEM_BAND_EUTRAN_1,
+ MM_MODEM_BAND_EUTRAN_2 }
+ },
+ /* 3G alternate band settings: default */
+ {
+ "#BND: (0),(0,2,5,6,12,25)", {FALSE, TRUE, FALSE, FALSE, FALSE, FALSE}, 5,
+ { MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_UTRAN_6,
+ MM_MODEM_BAND_UTRAN_19 }
+ },
+ /* 3G alternate band settings: alternate */
+ {
+ "#BND: (0),(0,2,5,6,12,13)", {FALSE, TRUE, FALSE, TRUE, FALSE, FALSE}, 4,
+ { MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_3,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8 }
+ },
+ /* ME910 (2G+4G device)
+ * 168695967: 0xA0E189F: 0000 1010 0000 1110 0001 1000 1001 1111
+ */
+ {
+ "#BND: (0-5),(0),(1-168695967)", {TRUE, FALSE, TRUE, FALSE, FALSE, FALSE}, 17,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_EUTRAN_1,
+ MM_MODEM_BAND_EUTRAN_2,
+ MM_MODEM_BAND_EUTRAN_3,
+ MM_MODEM_BAND_EUTRAN_4,
+ MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_8,
+ MM_MODEM_BAND_EUTRAN_12,
+ MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_18,
+ MM_MODEM_BAND_EUTRAN_19,
+ MM_MODEM_BAND_EUTRAN_20,
+ MM_MODEM_BAND_EUTRAN_26,
+ MM_MODEM_BAND_EUTRAN_28 }
+ },
+ /* 4G ext band settings: devices such as LN920 */
+ {
+ "#BND: (0),(0),(1003100185A),(42)", {FALSE, TRUE, TRUE, FALSE, TRUE, TRUE}, 13,
+ { MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_EUTRAN_2,
+ MM_MODEM_BAND_EUTRAN_4,
+ MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7,
+ MM_MODEM_BAND_EUTRAN_12,
+ MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_25,
+ MM_MODEM_BAND_EUTRAN_29,
+ MM_MODEM_BAND_EUTRAN_30,
+ MM_MODEM_BAND_EUTRAN_41,
+ MM_MODEM_BAND_EUTRAN_66,
+ MM_MODEM_BAND_EUTRAN_71 }
+ },
+ /* 4G band in hex format: devices such as LE910C1-EUX */
+ {
+ "#BND: (0),(0,5,6,13,15,23),(80800C5)", {TRUE, TRUE, TRUE, FALSE, TRUE, FALSE}, 11,
+ {
+ MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_3,
+ MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_EUTRAN_1,
+ MM_MODEM_BAND_EUTRAN_3,
+ MM_MODEM_BAND_EUTRAN_7,
+ MM_MODEM_BAND_EUTRAN_8,
+ MM_MODEM_BAND_EUTRAN_20,
+ MM_MODEM_BAND_EUTRAN_28 }
+ }
+};
+
+static void
+test_parse_supported_bands_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (supported_band_mapping_tests); i++) {
+ GError *error = NULL;
+ GArray *bands = NULL;
+
+ bands = mm_telit_parse_bnd_test_response (supported_band_mapping_tests[i].response,
+ &supported_band_mapping_tests[i].config,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (bands);
+
+ mm_test_helpers_compare_bands (bands,
+ supported_band_mapping_tests[i].mm_bands,
+ supported_band_mapping_tests[i].mm_bands_len);
+ g_array_unref (bands);
+ }
+}
+
+static BndResponseTest current_band_mapping_tests [] = {
+ {
+ "#BND: 0", {TRUE, FALSE, FALSE, FALSE, FALSE, FALSE}, 2,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS }
+ },
+ {
+ "#BND: 0,5", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 3,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_UTRAN_8 }
+ },
+ {
+ "#BND: 1,3", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 5,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_5 }
+ },
+ {
+ "#BND: 2,7", {TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, 3,
+ { MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_UTRAN_4 }
+ },
+ {
+ "#BND: 3,0,1", {TRUE, TRUE, TRUE, FALSE, FALSE, FALSE}, 4,
+ { MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_EUTRAN_1 }
+ },
+ {
+ "#BND: 0,0,3", {TRUE, FALSE, TRUE, FALSE, FALSE, FALSE}, 4,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_EUTRAN_1,
+ MM_MODEM_BAND_EUTRAN_2 }
+ },
+ {
+ "#BND: 0,0,3", {FALSE, FALSE, TRUE, FALSE, FALSE, FALSE}, 2,
+ { MM_MODEM_BAND_EUTRAN_1,
+ MM_MODEM_BAND_EUTRAN_2 }
+ },
+ /* 3G alternate band settings: default */
+ {
+ "#BND: 0,12", {FALSE, TRUE, FALSE, FALSE, FALSE, FALSE}, 1,
+ { MM_MODEM_BAND_UTRAN_6 }
+ },
+ /* 3G alternate band settings: alternate */
+ {
+ "#BND: 0,12", {FALSE, TRUE, FALSE, TRUE, FALSE, FALSE}, 4,
+ { MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_UTRAN_3,
+ MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_8 }
+ },
+ /* ME910 (2G+4G device)
+ * 168695967: 0xA0E189F: 0000 1010 0000 1110 0001 1000 1001 1111
+ */
+ {
+ "#BND: 5,0,168695967", {TRUE, FALSE, TRUE, FALSE, FALSE, FALSE}, 17,
+ { MM_MODEM_BAND_EGSM,
+ MM_MODEM_BAND_DCS,
+ MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_EUTRAN_1,
+ MM_MODEM_BAND_EUTRAN_2,
+ MM_MODEM_BAND_EUTRAN_3,
+ MM_MODEM_BAND_EUTRAN_4,
+ MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_8,
+ MM_MODEM_BAND_EUTRAN_12,
+ MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_18,
+ MM_MODEM_BAND_EUTRAN_19,
+ MM_MODEM_BAND_EUTRAN_20,
+ MM_MODEM_BAND_EUTRAN_26,
+ MM_MODEM_BAND_EUTRAN_28 }
+ },
+ /* 4G ext band settings: devices such as LN920 */
+ {
+ "#BND: 0,0,1003100185A,42", {FALSE, TRUE, TRUE, FALSE, FALSE, TRUE}, 13,
+ { MM_MODEM_BAND_UTRAN_1,
+ MM_MODEM_BAND_EUTRAN_2,
+ MM_MODEM_BAND_EUTRAN_4,
+ MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7,
+ MM_MODEM_BAND_EUTRAN_12,
+ MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_25,
+ MM_MODEM_BAND_EUTRAN_29,
+ MM_MODEM_BAND_EUTRAN_30,
+ MM_MODEM_BAND_EUTRAN_41,
+ MM_MODEM_BAND_EUTRAN_66,
+ MM_MODEM_BAND_EUTRAN_71 }
+ }
+};
+
+static void
+test_parse_current_bands_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (current_band_mapping_tests); i++) {
+ GError *error = NULL;
+ GArray *bands = NULL;
+
+ bands = mm_telit_parse_bnd_query_response (current_band_mapping_tests[i].response,
+ &current_band_mapping_tests[i].config,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (bands);
+
+ mm_test_helpers_compare_bands (bands,
+ current_band_mapping_tests[i].mm_bands,
+ current_band_mapping_tests[i].mm_bands_len);
+ g_array_unref (bands);
+ }
+}
+
+/******************************************************************************/
+
+static void
+test_common_bnd_cmd (const gchar *expected_cmd,
+ gboolean modem_is_2g,
+ gboolean modem_is_3g,
+ gboolean modem_is_4g,
+ gboolean modem_alternate_3g_bands,
+ gboolean modem_ext_4g_bands,
+ GArray *bands_array)
+{
+ gchar *cmd;
+ GError *error = NULL;
+ MMTelitBNDParseConfig config = {
+ .modem_is_2g = modem_is_2g,
+ .modem_is_3g = modem_is_3g,
+ .modem_is_4g = modem_is_4g,
+ .modem_alternate_3g_bands = modem_alternate_3g_bands,
+ .modem_ext_4g_bands = modem_ext_4g_bands
+ };
+
+ cmd = mm_telit_build_bnd_request (bands_array, &config, &error);
+ g_assert_no_error (error);
+ g_assert_cmpstr (cmd, ==, expected_cmd);
+ g_free (cmd);
+}
+
+#define test_common_bnd_cmd_2g(EXPECTED_CMD, BANDS_ARRAY) test_common_bnd_cmd (EXPECTED_CMD, TRUE, FALSE, FALSE, FALSE, FALSE, BANDS_ARRAY)
+#define test_common_bnd_cmd_3g(EXPECTED_CMD, ALTERNATE, BANDS_ARRAY) test_common_bnd_cmd (EXPECTED_CMD, FALSE, TRUE, FALSE, ALTERNATE, FALSE, BANDS_ARRAY)
+#define test_common_bnd_cmd_4g(EXPECTED_CMD, EXTENDED, BANDS_ARRAY) test_common_bnd_cmd (EXPECTED_CMD, FALSE, FALSE, TRUE, FALSE, EXTENDED, BANDS_ARRAY)
+
+static void
+test_common_bnd_cmd_error (gboolean modem_is_2g,
+ gboolean modem_is_3g,
+ gboolean modem_is_4g,
+ GArray *bands_array,
+ MMCoreError expected_error)
+{
+ gchar *cmd;
+ GError *error = NULL;
+ MMTelitBNDParseConfig config = {
+ .modem_is_2g = modem_is_2g,
+ .modem_is_3g = modem_is_3g,
+ .modem_is_4g = modem_is_4g,
+ .modem_alternate_3g_bands = FALSE,
+ .modem_ext_4g_bands = FALSE,
+ };
+ cmd = mm_telit_build_bnd_request (bands_array, &config, &error);
+ g_assert_error (error, MM_CORE_ERROR, (gint)expected_error);
+ g_assert (!cmd);
+}
+
+#define test_common_bnd_cmd_2g_invalid(BANDS_ARRAY) test_common_bnd_cmd_error (TRUE, FALSE, FALSE, BANDS_ARRAY, MM_CORE_ERROR_FAILED)
+#define test_common_bnd_cmd_3g_invalid(BANDS_ARRAY) test_common_bnd_cmd_error (FALSE, TRUE, FALSE, BANDS_ARRAY, MM_CORE_ERROR_FAILED)
+#define test_common_bnd_cmd_4g_invalid(BANDS_ARRAY) test_common_bnd_cmd_error (FALSE, FALSE, TRUE, BANDS_ARRAY, MM_CORE_ERROR_FAILED)
+#define test_common_bnd_cmd_2g_not_found(BANDS_ARRAY) test_common_bnd_cmd_error (TRUE, FALSE, FALSE, BANDS_ARRAY, MM_CORE_ERROR_NOT_FOUND)
+#define test_common_bnd_cmd_3g_not_found(BANDS_ARRAY) test_common_bnd_cmd_error (FALSE, TRUE, FALSE, BANDS_ARRAY, MM_CORE_ERROR_NOT_FOUND)
+#define test_common_bnd_cmd_4g_not_found(BANDS_ARRAY) test_common_bnd_cmd_error (FALSE, FALSE, TRUE, BANDS_ARRAY, MM_CORE_ERROR_NOT_FOUND)
+
+static void
+test_telit_get_2g_bnd_flag (void)
+{
+ GArray *bands_array;
+ MMModemBand egsm = MM_MODEM_BAND_EGSM;
+ MMModemBand dcs = MM_MODEM_BAND_DCS;
+ MMModemBand pcs = MM_MODEM_BAND_PCS;
+ MMModemBand g850 = MM_MODEM_BAND_G850;
+ MMModemBand u2100 = MM_MODEM_BAND_UTRAN_1;
+ MMModemBand eutran_i = MM_MODEM_BAND_EUTRAN_1;
+
+ /* Test Flag 0 */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
+ g_array_append_val (bands_array, egsm);
+ g_array_append_val (bands_array, dcs);
+ test_common_bnd_cmd_2g ("#BND=0", bands_array);
+ g_array_unref (bands_array);
+
+ /* Test flag 1 */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
+ g_array_append_val (bands_array, egsm);
+ g_array_append_val (bands_array, pcs);
+ test_common_bnd_cmd_2g ("#BND=1", bands_array);
+ g_array_unref (bands_array);
+
+ /* Test flag 2 */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
+ g_array_append_val (bands_array, g850);
+ g_array_append_val (bands_array, dcs);
+ test_common_bnd_cmd_2g ("#BND=2", bands_array);
+ g_array_unref (bands_array);
+
+ /* Test flag 3 */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
+ g_array_append_val (bands_array, g850);
+ g_array_append_val (bands_array, pcs);
+ test_common_bnd_cmd_2g ("#BND=3", bands_array);
+ g_array_unref (bands_array);
+
+ /* Test invalid band array */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
+ g_array_append_val (bands_array, g850);
+ g_array_append_val (bands_array, egsm);
+ test_common_bnd_cmd_2g_invalid (bands_array);
+ g_array_unref (bands_array);
+
+ /* Test unmatched band array */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
+ g_array_append_val (bands_array, u2100);
+ g_array_append_val (bands_array, eutran_i);
+ test_common_bnd_cmd_2g_not_found (bands_array);
+ g_array_unref (bands_array);
+}
+
+static void
+test_telit_get_3g_bnd_flag (void)
+{
+ GArray *bands_array;
+ MMModemBand u2100 = MM_MODEM_BAND_UTRAN_1;
+ MMModemBand u1900 = MM_MODEM_BAND_UTRAN_2;
+ MMModemBand u1800 = MM_MODEM_BAND_UTRAN_3;
+ MMModemBand u850 = MM_MODEM_BAND_UTRAN_5;
+ MMModemBand u800 = MM_MODEM_BAND_UTRAN_6;
+ MMModemBand u900 = MM_MODEM_BAND_UTRAN_8;
+ MMModemBand u17iv = MM_MODEM_BAND_UTRAN_4;
+ MMModemBand u17ix = MM_MODEM_BAND_UTRAN_9;
+ MMModemBand egsm = MM_MODEM_BAND_EGSM;
+ MMModemBand eutran_i = MM_MODEM_BAND_EUTRAN_1;
+
+ /* Test flag 0 */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
+ g_array_append_val (bands_array, u2100);
+ test_common_bnd_cmd_3g ("#BND=0,0", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,0", TRUE, bands_array);
+ g_array_unref (bands_array);
+
+ /* Test flag 1 */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
+ g_array_append_val (bands_array, u1900);
+ test_common_bnd_cmd_3g ("#BND=0,1", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,1", TRUE, bands_array);
+ g_array_unref (bands_array);
+
+ /* Test flag 2 */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
+ g_array_append_val (bands_array, u850);
+ test_common_bnd_cmd_3g ("#BND=0,2", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,2", TRUE, bands_array);
+ g_array_unref (bands_array);
+
+ /* Test flag 3 */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 3);
+ g_array_append_val (bands_array, u2100);
+ g_array_append_val (bands_array, u1900);
+ g_array_append_val (bands_array, u850);
+ test_common_bnd_cmd_3g ("#BND=0,3", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,3", TRUE, bands_array);
+ g_array_unref (bands_array);
+
+ /* Test flag 4 */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
+ g_array_append_val (bands_array, u1900);
+ g_array_append_val (bands_array, u850);
+ test_common_bnd_cmd_3g ("#BND=0,4", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,4", TRUE, bands_array);
+ g_array_unref (bands_array);
+
+ /* Test flag 5 */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
+ g_array_append_val (bands_array, u900);
+ test_common_bnd_cmd_3g ("#BND=0,5", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,5", TRUE, bands_array);
+ g_array_unref (bands_array);
+
+ /* Test flag 6 */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
+ g_array_append_val (bands_array, u2100);
+ g_array_append_val (bands_array, u900);
+ test_common_bnd_cmd_3g ("#BND=0,6", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,6", TRUE, bands_array);
+ g_array_unref (bands_array);
+
+ /* Test flag 7 */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
+ g_array_append_val (bands_array, u17iv);
+ test_common_bnd_cmd_3g ("#BND=0,7", FALSE, bands_array);
+ test_common_bnd_cmd_3g ("#BND=0,7", TRUE, bands_array);
+ g_array_unref (bands_array);
+
+ /* Test flag 12 in default */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
+ g_array_append_val (bands_array, u800);
+ test_common_bnd_cmd_3g ("#BND=0,12", FALSE, bands_array);
+ g_array_unref (bands_array);
+
+ /* Test flag 12 in alternate */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 4);
+ g_array_append_val (bands_array, u2100);
+ g_array_append_val (bands_array, u1800);
+ g_array_append_val (bands_array, u850);
+ g_array_append_val (bands_array, u900);
+ test_common_bnd_cmd_3g ("#BND=0,12", TRUE, bands_array);
+ g_array_unref (bands_array);
+
+ /* Test invalid band array */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
+ g_array_append_val (bands_array, u17ix);
+ test_common_bnd_cmd_3g_invalid (bands_array);
+ g_array_unref (bands_array);
+
+ /* Test unmatched band array */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
+ g_array_append_val (bands_array, egsm);
+ g_array_append_val (bands_array, eutran_i);
+ test_common_bnd_cmd_3g_not_found (bands_array);
+ g_array_unref (bands_array);
+}
+
+static void
+test_telit_get_4g_bnd_flag (void)
+{
+ GArray *bands_array;
+ MMModemBand eutran_i = MM_MODEM_BAND_EUTRAN_1;
+ MMModemBand eutran_ii = MM_MODEM_BAND_EUTRAN_2;
+ MMModemBand u2100 = MM_MODEM_BAND_UTRAN_1;
+ MMModemBand egsm = MM_MODEM_BAND_EGSM;
+
+ /* Test flag 1 */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
+ g_array_append_val (bands_array, eutran_i);
+ test_common_bnd_cmd_4g ("#BND=0,0,1", FALSE, bands_array);
+ g_array_unref (bands_array);
+
+ /* Test flag 3 */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
+ g_array_append_val (bands_array, eutran_i);
+ g_array_append_val (bands_array, eutran_ii);
+ test_common_bnd_cmd_4g ("#BND=0,0,3", FALSE, bands_array);
+ g_array_unref (bands_array);
+
+ /* Test unmatched band array */
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 2);
+ g_array_append_val (bands_array, egsm);
+ g_array_append_val (bands_array, u2100);
+ test_common_bnd_cmd_4g_not_found (bands_array);
+ g_array_unref (bands_array);
+}
+
+/******************************************************************************/
+
+typedef struct {
+ const char* response;
+ MMTelitQssStatus expected_qss;
+ const char *error_message;
+} QssParseTest;
+
+static QssParseTest qss_parse_tests [] = {
+ {"#QSS: 0,0", QSS_STATUS_SIM_REMOVED, NULL},
+ {"#QSS: 1,0", QSS_STATUS_SIM_REMOVED, NULL},
+ {"#QSS: 0,1", QSS_STATUS_SIM_INSERTED, NULL},
+ {"#QSS: 0,2", QSS_STATUS_SIM_INSERTED_AND_UNLOCKED, NULL},
+ {"#QSS: 0,3", QSS_STATUS_SIM_INSERTED_AND_READY, NULL},
+ {"#QSS:0,3", QSS_STATUS_SIM_INSERTED_AND_READY, NULL},
+ {"#QSS: 0, 3", QSS_STATUS_SIM_INSERTED_AND_READY, NULL},
+ {"#QSS: 0", QSS_STATUS_UNKNOWN, "Could not parse \"#QSS?\" response: #QSS: 0"},
+ {"QSS:0,1", QSS_STATUS_UNKNOWN, "Could not parse \"#QSS?\" response: QSS:0,1"},
+ {"#QSS: 0,5", QSS_STATUS_UNKNOWN, "Unknown QSS status value given: 5"},
+};
+
+static void
+test_telit_parse_qss_query (void)
+{
+ MMTelitQssStatus actual_qss_status;
+ GError *error = NULL;
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (qss_parse_tests); i++) {
+ actual_qss_status = mm_telit_parse_qss_query (qss_parse_tests[i].response, &error);
+
+ g_assert_cmpint (actual_qss_status, ==, qss_parse_tests[i].expected_qss);
+ if (qss_parse_tests[i].error_message) {
+ g_assert_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED);
+ g_assert_cmpstr (error->message, ==, qss_parse_tests[i].error_message);
+ g_clear_error (&error);
+ }
+ }
+}
+
+static void
+test_telit_parse_swpkgv_response (void)
+{
+ static struct {
+ const gchar *response;
+ const gchar *expected;
+ } tt [] = {
+ {"\r\n12.34.567\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "12.34.567"},
+ {"\r\n13.35.568-A123\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "13.35.568-A123"},
+ {"\r\n14.36.569-B124\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "14.36.569-B124"},
+ {"\r\n15.37.570-T125\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "15.37.570-T125"},
+ {"\r\n16.38.571-P0F.224700\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "16.38.571-P0F.224700"},
+ /* real example from LE910C1-EUX */
+ {"\r\n25.30.224-B001-P0F.224700\r\nM0F.223004-B001\r\nP0F.224700\r\nA0F.223004-B001\r\n\r\nOK\r\n", "25.30.224-B001-P0F.224700"},
+ };
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (tt); i++) {
+ gchar *actual = NULL;
+
+ actual = mm_telit_parse_swpkgv_response(tt[i].response);
+
+ g_assert_cmpstr (tt[i].expected, ==, actual);
+ g_free (actual);
+ }
+}
+
+static void
+test_telit_compare_software_revision_string (void)
+{
+ struct {
+ const char *revision_a;
+ const char *revision_b;
+ MMTelitSwRevCmp expected;
+ } tt [] = {
+ {"24.01.514", "24.01.514", MM_TELIT_SW_REV_CMP_EQUAL},
+ {"24.01.514", "24.01.513", MM_TELIT_SW_REV_CMP_NEWER},
+ {"24.01.513", "24.01.514", MM_TELIT_SW_REV_CMP_OLDER},
+ {"32.00.013", "24.01.514", MM_TELIT_SW_REV_CMP_INVALID},
+ {"32.00.014", "32.00.014", MM_TELIT_SW_REV_CMP_EQUAL},
+ {"32.00.014", "32.00.013", MM_TELIT_SW_REV_CMP_NEWER},
+ {"32.00.013", "32.00.014", MM_TELIT_SW_REV_CMP_OLDER},
+ {"38.00.000", "38.00.000", MM_TELIT_SW_REV_CMP_UNSUPPORTED},
+ /* LM9x0 Minor version (e.g. beta, test, alpha) value is currently
+ * ignored because not required by any implemented feature. */
+ {"24.01.516-B123", "24.01.516-B134", MM_TELIT_SW_REV_CMP_EQUAL},
+ };
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (tt); i++) {
+ g_assert_cmpint (tt[i].expected,
+ ==,
+ mm_telit_software_revision_cmp (tt[i].revision_a, tt[i].revision_b));
+ }
+}
+
+/******************************************************************************/
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/telit/bands/supported/parse_bands_response", test_parse_supported_bands_response);
+ g_test_add_func ("/MM/telit/bands/current/parse_bands_response", test_parse_current_bands_response);
+ g_test_add_func ("/MM/telit/bands/current/set_bands/2g", test_telit_get_2g_bnd_flag);
+ g_test_add_func ("/MM/telit/bands/current/set_bands/3g", test_telit_get_3g_bnd_flag);
+ g_test_add_func ("/MM/telit/bands/current/set_bands/4g", test_telit_get_4g_bnd_flag);
+ g_test_add_func ("/MM/telit/qss/query", test_telit_parse_qss_query);
+ g_test_add_func ("/MM/telit/swpkv/parse_response", test_telit_parse_swpkgv_response);
+ g_test_add_func ("/MM/telit/revision/compare", test_telit_compare_software_revision_string);
+ return g_test_run ();
+}
diff --git a/src/plugins/tests/gsm-port.conf b/src/plugins/tests/gsm-port.conf
new file mode 100644
index 00000000..ae157834
--- /dev/null
+++ b/src/plugins/tests/gsm-port.conf
@@ -0,0 +1,46 @@
+
+
+AT \r\nOK\r\n
+ATE0 \r\nOK\r\n
+ATV1 \r\nOK\r\n
+AT+CMEE=1 \r\nOK\r\n
+ATX4 \r\nOK\r\n
+AT&C1 \r\nOK\r\n
+AT+IFC=1,1 \r\nOK\r\n
+AT+GCAP \r\n+GCAP: +CGSM +DS +ES\r\n\r\nOK\r\n
+ATI \r\nManufacturer: Some vendor\r\nModel: Some model\r\nRevision: Some revision\r\nIMEI: 001100110011002<CR><LF>+GCAP: +CGSM,+DS,+ES\r\n\r\nOK\r\n
+AT+WS46=? \r\n+WS46: (12,22)\r\n\r\nOK\r\n
+AT+CGMI \r\nSome vendor\r\n\r\nOK\r\n
+AT+CGMM \r\nSome model\r\n\r\nOK\r\n
+AT+CGMR \r\nSome revision\r\n\r\nOK\r\n
+AT+CGSN \r\n123456789012345\r\n\r\nOK\r\n
+AT+CGDCONT=? \r\n+CGDCONT: (1-11),"IP",,,(0-2),(0-3)\r\n+CGDCONT: (1-11),"IPV6",,,(0-2),(0-3)\r\n+CGDCONT: (1-11),"IPV4V6",,,(0-2),(0-3)\r\n+CGDCONT: (1-11),"PPP",,,(0-2),(0-3)\r\n\r\nOK\r\n
+AT+CIMI \r\n998899889988997\r\n\r\nOK\r\n
+AT+CLCK=? \r\n+CLCK: ("SC","AO","OI","OX","AI","IR","AB","AG","AC","PS","FD")\r\n\r\nOK\r\n
+AT+CLCK="SC",2 \r\n+CLCK: 1\r\n\r\nOK\r\n
+AT+CLCK="FD",2 \r\n+CLCK: 1\r\n\r\nOK\r\n
+AT+CLCK="PS",2 \r\n+CLCK: 1\r\n\r\nOK\r\n
+AT+CFUN? \r\n+CFUN: 1\r\n\r\nOK\r\n
+AT+CSCS=? \r\n+CSCS: ("IRA","UCS2","GSM")\r\n\r\nOK\r\n
+AT+CSCS="UCS2" \r\nOK\r\n
+AT+CSCS? \r\n+CSCS: "UCS2"\r\n\r\nOK\r\n
+AT+CREG=2 \r\nOK\r\n
+AT+CGREG=2 \r\nOK\r\n
+AT+CREG=0 \r\nOK\r\n
+AT+CGREG=0 \r\nOK\r\n
+AT+CREG? \r\n+CREG: 2,1,"1234","001122BB"\r\n\r\nOK\r\n
+AT+CGREG? \r\n+CGREG: 2,1,"31C5","0083F7CD"\r\n\r\nOK\r\n
+AT+COPS=3,2;+COPS? \r\n+COPS: 0,2,"21401",2\r\n\r\nOK\r\n
+AT+COPS=3,0;+COPS? \r\n+COPS: 0,0,"vodafone ES"\r\n\r\nOK\r\n
+AT+CMGF=? \r\n+CMGF: (0,1)\r\n\r\nOK\r\n
+AT+CMGF=0 \r\nOK\r\n
+AT+CSQ \r\n+CSQ: 17,99\r\n\r\nOK\r\n
+
+# By default, no PIN required
+AT+CPIN? \r\n+CPIN: READY\r\n\r\nOK\r\n
+
+# By default, no messaging support
+AT+CNMI=? \r\nERROR\r\n
+
+# By default, no USSD support
+AT+CUSD=? \r\nERROR\r\n
diff --git a/src/plugins/tests/test-fixture.c b/src/plugins/tests/test-fixture.c
new file mode 100644
index 00000000..29eb8d55
--- /dev/null
+++ b/src/plugins/tests/test-fixture.c
@@ -0,0 +1,163 @@
+/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include "test-fixture.h"
+
+void
+test_fixture_setup (TestFixture *fixture)
+{
+ GError *error = NULL;
+ GVariant *result;
+
+ /* Create the global dbus-daemon for this test suite */
+ fixture->dbus = g_test_dbus_new (G_TEST_DBUS_NONE);
+
+ /* Add the private directory with our in-tree service files,
+ * TEST_SERVICES is defined by the build system to point
+ * to the right directory. */
+ g_test_dbus_add_service_dir (fixture->dbus, TEST_SERVICES);
+
+ /* Start the private DBus daemon */
+ g_test_dbus_up (fixture->dbus);
+
+ /* Create DBus connection */
+ fixture->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (fixture->connection == NULL)
+ g_error ("Error getting connection to test bus: %s", error->message);
+
+ /* Ping to autostart MM; wait up to 30s */
+ result = g_dbus_connection_call_sync (fixture->connection,
+ "org.freedesktop.ModemManager1",
+ "/org/freedesktop/ModemManager1",
+ "org.freedesktop.DBus.Peer",
+ "Ping",
+ NULL, /* inputs */
+ NULL, /* outputs */
+ G_DBUS_CALL_FLAGS_NONE,
+ 30000, /* timeout, ms */
+ NULL, /* cancellable */
+ &error);
+ if (!result)
+ g_error ("Error starting ModemManager in test bus: %s", error->message);
+ g_variant_unref (result);
+
+ /* Create the proxy that we're going to test */
+ fixture->test = mm_gdbus_test_proxy_new_sync (fixture->connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ "org.freedesktop.ModemManager1",
+ "/org/freedesktop/ModemManager1",
+ NULL, /* cancellable */
+ &error);
+ if (fixture->test == NULL)
+ g_error ("Error getting ModemManager test proxy: %s", error->message);
+}
+
+void
+test_fixture_teardown (TestFixture *fixture)
+{
+ g_object_unref (fixture->connection);
+
+ /* Tear down the proxy */
+ if (fixture->test)
+ g_object_unref (fixture->test);
+
+ /* Stop the private D-Bus daemon; stopping the bus will stop MM as well */
+ g_test_dbus_down (fixture->dbus);
+ g_object_unref (fixture->dbus);
+}
+
+void
+test_fixture_set_profile (TestFixture *fixture,
+ const gchar *profile_name,
+ const gchar *plugin,
+ const gchar *const *ports)
+{
+ GError *error = NULL;
+
+ /* Set the test profile */
+ g_assert (fixture->test != NULL);
+ if (!mm_gdbus_test_call_set_profile_sync (fixture->test,
+ profile_name,
+ plugin,
+ ports,
+ NULL, /* cancellable */
+ &error))
+ g_error ("Error setting test profile: %s", error->message);
+}
+
+static MMObject *
+common_get_modem (TestFixture *fixture,
+ gboolean modem_expected)
+{
+ MMObject *found = NULL;
+ guint wait_time = 0;
+
+ /* Find new modem object */
+ while (TRUE) {
+ GError *error = NULL;
+ MMManager *manager;
+ GList *modems;
+ guint n_modems;
+ gboolean ready = FALSE;
+
+ /* Create manager on each loop, so that we don't require on an external
+ * global main context processing to receive the DBus property updates.
+ */
+ g_assert (fixture->connection != NULL);
+ manager = mm_manager_new_sync (fixture->connection,
+ G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
+ NULL, /* cancellable */
+ &error);
+ if (!manager)
+ g_error ("Couldn't create manager: %s", error->message);
+
+ modems = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (manager));
+ n_modems = g_list_length (modems);
+ g_assert_cmpuint (n_modems, <=, 1);
+
+ if ((guint)modem_expected == n_modems) {
+ if (modems) {
+ found = MM_OBJECT (g_object_ref (modems->data));
+ g_message ("Found modem at '%s'", mm_object_get_path (found));
+ }
+ ready = TRUE;
+ }
+
+ g_list_free_full (modems, g_object_unref);
+ g_object_unref (manager);
+
+ if (ready)
+ break;
+
+ /* Blocking wait */
+ g_assert_cmpuint (wait_time, <=, 20);
+ wait_time++;
+ sleep (1);
+ }
+
+ return found;
+}
+
+MMObject *
+test_fixture_get_modem (TestFixture *fixture)
+{
+ return common_get_modem (fixture, TRUE);
+}
+
+void
+test_fixture_no_modem (TestFixture *fixture)
+{
+ common_get_modem (fixture, FALSE);
+}
diff --git a/src/plugins/tests/test-fixture.h b/src/plugins/tests/test-fixture.h
new file mode 100644
index 00000000..b6c24379
--- /dev/null
+++ b/src/plugins/tests/test-fixture.h
@@ -0,0 +1,54 @@
+/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef TEST_FIXTURE_H
+#define TEST_FIXTURE_H
+
+#include <gio/gio.h>
+#include <libmm-glib.h>
+#include "mm-gdbus-test.h"
+
+/*****************************************************************************/
+/* Test fixture setup */
+
+/* The fixture contains a GTestDBus object and
+ * a proxy to the service we're going to be testing.
+ */
+typedef struct {
+ GTestDBus *dbus;
+ MmGdbusTest *test;
+ GDBusConnection *connection;
+} TestFixture;
+
+void test_fixture_setup (TestFixture *fixture);
+void test_fixture_teardown (TestFixture *fixture);
+
+typedef void (*TCFunc) (TestFixture *, gconstpointer);
+#define TEST_ADD(path,method) \
+ g_test_add (path, \
+ TestFixture, \
+ NULL, \
+ (TCFunc)test_fixture_setup, \
+ (TCFunc)method, \
+ (TCFunc)test_fixture_teardown)
+
+void test_fixture_set_profile (TestFixture *fixture,
+ const gchar *profile_name,
+ const gchar *plugin,
+ const gchar *const *ports);
+MMObject *test_fixture_get_modem (TestFixture *fixture);
+void test_fixture_no_modem (TestFixture *fixture);
+
+#endif /* TEST_FIXTURE_H */
diff --git a/src/plugins/tests/test-helpers.c b/src/plugins/tests/test-helpers.c
new file mode 100644
index 00000000..8148d908
--- /dev/null
+++ b/src/plugins/tests/test-helpers.c
@@ -0,0 +1,52 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log.h"
+#include "mm-modem-helpers.h"
+
+#include "test-helpers.h"
+
+void
+mm_test_helpers_compare_bands (GArray *bands,
+ const MMModemBand *expected_bands,
+ guint n_expected_bands)
+{
+ gchar *bands_str;
+ GArray *expected_bands_array;
+ gchar *expected_bands_str;
+
+ if (!expected_bands || !n_expected_bands) {
+ g_assert (!bands);
+ return;
+ }
+
+ g_assert (bands);
+ mm_common_bands_garray_sort (bands);
+ bands_str = mm_common_build_bands_string ((MMModemBand *)(gpointer)(bands->data), bands->len);
+
+ expected_bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), n_expected_bands);
+ g_array_append_vals (expected_bands_array, expected_bands, n_expected_bands);
+ mm_common_bands_garray_sort (expected_bands_array);
+ expected_bands_str = mm_common_build_bands_string ((MMModemBand *)(gpointer)(expected_bands_array->data), expected_bands_array->len);
+ g_array_unref (expected_bands_array);
+
+ g_assert_cmpstr (bands_str, ==, expected_bands_str);
+ g_free (bands_str);
+ g_free (expected_bands_str);
+}
diff --git a/src/plugins/tests/test-helpers.h b/src/plugins/tests/test-helpers.h
new file mode 100644
index 00000000..8362754c
--- /dev/null
+++ b/src/plugins/tests/test-helpers.h
@@ -0,0 +1,26 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef TEST_HELPERS_H
+#define TEST_HELPERS_H
+
+#include <glib.h>
+#include <libmm-glib.h>
+
+void mm_test_helpers_compare_bands (GArray *bands,
+ const MMModemBand *expected_bands,
+ guint n_expected_bands);
+
+#endif /* TEST_HELPERS_H */
diff --git a/src/plugins/tests/test-keyfiles.c b/src/plugins/tests/test-keyfiles.c
new file mode 100644
index 00000000..f92df9c3
--- /dev/null
+++ b/src/plugins/tests/test-keyfiles.c
@@ -0,0 +1,79 @@
+/* -*- 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>
+ */
+#include <config.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <string.h>
+#include <stdio.h>
+#include <locale.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-test.h"
+
+/************************************************************/
+
+static void
+common_test (const gchar *keyfile_path)
+{
+ GKeyFile *keyfile;
+ GError *error = NULL;
+ gboolean ret;
+
+ if (!keyfile_path)
+ return;
+
+ keyfile = g_key_file_new ();
+ ret = g_key_file_load_from_file (keyfile, keyfile_path, G_KEY_FILE_NONE, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+ g_key_file_unref (keyfile);
+}
+
+/* Placeholder test to avoid compiler warning about common_test() being unused
+ * when none of the plugins enabled in build have custom key files. */
+static void
+test_placeholder (void)
+{
+ common_test (NULL);
+}
+
+/************************************************************/
+
+#if defined ENABLE_PLUGIN_FOXCONN
+static void
+test_foxconn_t77w968 (void)
+{
+ common_test (TESTKEYFILE_FOXCONN_T77W968);
+}
+#endif
+
+/************************************************************/
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+ g_test_add_func ("/MM/test-keyfiles/placeholder", test_placeholder);
+
+#if defined ENABLE_PLUGIN_FOXCONN
+ g_test_add_func ("/MM/test-keyfiles/foxconn/t77w968", test_foxconn_t77w968);
+#endif
+
+ return g_test_run ();
+}
diff --git a/src/plugins/tests/test-port-context.c b/src/plugins/tests/test-port-context.c
new file mode 100644
index 00000000..e96cff7b
--- /dev/null
+++ b/src/plugins/tests/test-port-context.c
@@ -0,0 +1,422 @@
+/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <gio/gio.h>
+#include <gio/gunixsocketaddress.h>
+#include <string.h>
+
+#include "test-port-context.h"
+
+#define BUFFER_SIZE 1024
+
+struct _TestPortContext {
+ gchar *name;
+ GThread *thread;
+ gboolean ready;
+ GCond ready_cond;
+ GMutex ready_mutex;
+ GMainLoop *loop;
+ GMainContext *context;
+ GSocket *socket;
+ GSocketService *socket_service;
+ GList *clients;
+ GHashTable *commands;
+};
+
+/*****************************************************************************/
+
+void
+test_port_context_set_command (TestPortContext *self,
+ const gchar *command,
+ const gchar *response)
+{
+ if (G_UNLIKELY (!self->commands))
+ self->commands = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ g_hash_table_replace (self->commands, g_strdup (command), g_strcompress (response));
+}
+
+void
+test_port_context_load_commands (TestPortContext *self,
+ const gchar *file)
+{
+ GError *error = NULL;
+ gchar *contents;
+ gchar *current;
+
+ if (!g_file_get_contents (file, &contents, NULL, &error))
+ g_error ("Couldn't load commands file '%s': %s",
+ g_filename_display_name (file),
+ error->message);
+
+ current = contents;
+ while (current) {
+ gchar *next;
+
+ next = strchr (current, '\n');
+ if (next) {
+ *next = '\0';
+ next++;
+ }
+
+ g_strstrip (current);
+ if (current[0] != '\0' && current[0] != '#') {
+ gchar *response;
+
+ response = current;
+ while (*response != ' ')
+ response++;
+ g_assert (*response == ' ');
+ *response = '\0';
+ response++;
+ while (*response == ' ')
+ response++;
+ g_assert (*response != '\0');
+
+ test_port_context_set_command (self, current, response);
+ }
+ current = next;
+ }
+
+ g_free (contents);
+}
+
+static const gchar *
+process_next_command (TestPortContext *ctx,
+ GByteArray *buffer)
+{
+ gsize i = 0;
+ gchar *command;
+ const gchar *response;
+ static const gchar *error_response = "\r\nERROR\r\n";
+
+ /* Find command end */
+ while (i < buffer->len && buffer->data[i] != '\r' && buffer->data[i] != '\n')
+ i++;
+ if (i == buffer->len)
+ /* no command */
+ return NULL;
+
+ while (i < buffer->len && (buffer->data[i] == '\r' || buffer->data[i] == '\n'))
+ buffer->data[i++] = '\0';
+
+ /* Setup command and lookup response */
+ command = g_strndup ((gchar *)buffer->data, i);
+ response = g_hash_table_lookup (ctx->commands, command);
+ g_free (command);
+
+ /* Remove command from buffer */
+ g_byte_array_remove_range (buffer, 0, i);
+
+ return response ? response : error_response;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ TestPortContext *ctx;
+ GSocketConnection *connection;
+ GSource *connection_readable_source;
+ GByteArray *buffer;
+} Client;
+
+static void
+client_free (Client *client)
+{
+ g_source_destroy (client->connection_readable_source);
+ g_source_unref (client->connection_readable_source);
+ g_output_stream_close (g_io_stream_get_output_stream (G_IO_STREAM (client->connection)), NULL, NULL);
+ if (client->buffer)
+ g_byte_array_unref (client->buffer);
+ g_object_unref (client->connection);
+ g_slice_free (Client, client);
+}
+
+static void
+connection_close (Client *client)
+{
+ client->ctx->clients = g_list_remove (client->ctx->clients, client);
+ client_free (client);
+}
+
+static void
+client_parse_request (Client *client)
+{
+ const gchar *response;
+
+ do {
+ response = process_next_command (client->ctx, client->buffer);
+ if (response) {
+ GError *error = NULL;
+
+ if (!g_output_stream_write_all (g_io_stream_get_output_stream (G_IO_STREAM (client->connection)),
+ response,
+ strlen (response),
+ NULL, /* bytes_written */
+ NULL, /* cancellable */
+ &error)) {
+ g_warning ("Cannot send response to client: %s", error->message);
+ g_error_free (error);
+ }
+ }
+
+ } while (response);
+}
+
+static gboolean
+connection_readable_cb (GSocket *socket,
+ GIOCondition condition,
+ Client *client)
+{
+ guint8 buffer[BUFFER_SIZE];
+ GError *error = NULL;
+ gssize r;
+
+ if (condition & G_IO_HUP || condition & G_IO_ERR) {
+ g_debug ("client connection closed");
+ connection_close (client);
+ return FALSE;
+ }
+
+ if (!(condition & G_IO_IN || condition & G_IO_PRI))
+ return TRUE;
+
+ r = g_input_stream_read (g_io_stream_get_input_stream (G_IO_STREAM (client->connection)),
+ buffer,
+ BUFFER_SIZE,
+ NULL,
+ &error);
+
+ if (r < 0) {
+ g_warning ("Error reading from istream: %s", error ? error->message : "unknown");
+ if (error)
+ g_error_free (error);
+ /* Close the device */
+ connection_close (client);
+ return FALSE;
+ }
+
+ if (r == 0)
+ return TRUE;
+
+ /* else, r > 0 */
+ if (!G_UNLIKELY (client->buffer))
+ client->buffer = g_byte_array_sized_new (r);
+ g_byte_array_append (client->buffer, buffer, r);
+
+ /* Try to parse input messages */
+ client_parse_request (client);
+
+ return TRUE;
+}
+
+static Client *
+client_new (TestPortContext *self,
+ GSocketConnection *connection)
+{
+ Client *client;
+
+ client = g_slice_new0 (Client);
+ client->ctx = self;
+ client->connection = g_object_ref (connection);
+ client->connection_readable_source = g_socket_create_source (g_socket_connection_get_socket (client->connection),
+ G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP,
+ NULL);
+ g_source_set_callback (client->connection_readable_source,
+ (GSourceFunc)connection_readable_cb,
+ client,
+ NULL);
+ g_source_attach (client->connection_readable_source, self->context);
+
+ return client;
+}
+
+/* /\*****************************************************************************\/ */
+
+static void
+incoming_cb (GSocketService *service,
+ GSocketConnection *connection,
+ GObject *unused,
+ TestPortContext *self)
+{
+ Client *client;
+
+ client = client_new (self, connection);
+ self->clients = g_list_append (self->clients, client);
+}
+
+static void
+create_socket_service (TestPortContext *self)
+{
+ GError *error = NULL;
+ GSocketService *service;
+ GSocketAddress *address;
+ GSocket *socket;
+
+ g_assert (self->socket_service == NULL);
+
+ /* Create socket */
+ socket = g_socket_new (G_SOCKET_FAMILY_UNIX,
+ G_SOCKET_TYPE_STREAM,
+ G_SOCKET_PROTOCOL_DEFAULT,
+ &error);
+ if (!socket)
+ g_error ("Cannot create socket: %s", error->message);
+
+ /* Bind to address */
+ address = (g_unix_socket_address_new_with_type (
+ self->name,
+ -1,
+ (g_str_has_prefix (self->name, "abstract:") ?
+ G_UNIX_SOCKET_ADDRESS_ABSTRACT :
+ G_UNIX_SOCKET_ADDRESS_PATH)));
+ if (!g_socket_bind (socket, address, TRUE, &error))
+ g_error ("Cannot bind socket: %s", error->message);
+ g_object_unref (address);
+
+ /* Listen */
+ if (!g_socket_listen (socket, &error))
+ g_error ("Cannot listen in socket: %s", error->message);
+
+ /* Create socket service */
+ service = g_socket_service_new ();
+ g_signal_connect (service, "incoming", G_CALLBACK (incoming_cb), self);
+ if (!g_socket_listener_add_socket (G_SOCKET_LISTENER (service),
+ socket,
+ NULL, /* don't pass an object, will take a reference */
+ &error))
+ g_error ("Cannot add listener to socket: %s", error->message);
+
+ /* Start it */
+ g_socket_service_start (service);
+
+ /* And store both the service and the socket.
+ * Since GLib 2.42 the socket may not be explicitly closed when the
+ * listener is diposed, so we'll do it ourselves. */
+ self->socket_service = service;
+ self->socket = socket;
+
+ /* Signal that the thread is ready */
+ g_mutex_lock (&self->ready_mutex);
+ self->ready = TRUE;
+ g_cond_signal (&self->ready_cond);
+ g_mutex_unlock (&self->ready_mutex);
+}
+
+/*****************************************************************************/
+
+static gboolean
+cancel_loop_cb (TestPortContext *self)
+{
+ g_main_loop_quit (self->loop);
+ return FALSE;
+}
+
+void
+test_port_context_stop (TestPortContext *self)
+{
+ g_assert (self->thread != NULL);
+ g_assert (self->loop != NULL);
+ g_assert (self->context != NULL);
+
+ /* Cancel main loop of the port context thread, by scheduling an idle task
+ * in the thread-owned main context */
+ g_main_context_invoke (self->context, (GSourceFunc) cancel_loop_cb, self);
+
+ g_thread_join (self->thread);
+ self->thread = NULL;
+}
+
+static gpointer
+port_context_thread_func (TestPortContext *self)
+{
+ g_assert (self->loop == NULL);
+ g_assert (self->context == NULL);
+
+ /* Define main context and loop for the thread */
+ self->context = g_main_context_new ();
+ self->loop = g_main_loop_new (self->context, FALSE);
+ g_main_context_push_thread_default (self->context);
+
+ /* Once the thread default context is setup, launch service */
+ create_socket_service (self);
+
+ g_main_loop_run (self->loop);
+
+ g_main_loop_unref (self->loop);
+ self->loop = NULL;
+ g_main_context_unref (self->context);
+ self->context = NULL;
+ return NULL;
+}
+
+void
+test_port_context_start (TestPortContext *self)
+{
+ g_assert (self->thread == NULL);
+ self->thread = g_thread_new (self->name,
+ (GThreadFunc)port_context_thread_func,
+ self);
+
+ /* Now wait until the thread has finished its initialization and is
+ * ready to serve connections */
+ g_mutex_lock (&self->ready_mutex);
+ while (!self->ready)
+ g_cond_wait (&self->ready_cond, &self->ready_mutex);
+ g_mutex_unlock (&self->ready_mutex);
+}
+
+/*****************************************************************************/
+
+void
+test_port_context_free (TestPortContext *self)
+{
+ g_assert (self->thread == NULL);
+ g_assert (self->loop == NULL);
+
+ g_cond_clear (&self->ready_cond);
+ g_mutex_clear (&self->ready_mutex);
+
+ if (self->commands)
+ g_hash_table_unref (self->commands);
+ g_list_free_full (self->clients, (GDestroyNotify)client_free);
+ if (self->socket) {
+ GError *error = NULL;
+
+ if (!g_socket_close (self->socket, &error)) {
+ g_debug ("Couldn't close socket: %s", error->message);
+ g_error_free (error);
+ }
+ g_object_unref (self->socket);
+ }
+ if (self->socket_service) {
+ if (g_socket_service_is_active (self->socket_service))
+ g_socket_service_stop (self->socket_service);
+ g_object_unref (self->socket_service);
+ }
+ g_free (self->name);
+ g_slice_free (TestPortContext, self);
+}
+
+TestPortContext *
+test_port_context_new (const gchar *name)
+{
+ TestPortContext *self;
+
+ self = g_slice_new0 (TestPortContext);
+ self->name = g_strdup (name);
+ g_cond_init (&self->ready_cond);
+ g_mutex_init (&self->ready_mutex);
+ return self;
+}
diff --git a/src/plugins/tests/test-port-context.h b/src/plugins/tests/test-port-context.h
new file mode 100644
index 00000000..9aaf1077
--- /dev/null
+++ b/src/plugins/tests/test-port-context.h
@@ -0,0 +1,35 @@
+/* -*- 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) 2013 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef TEST_PORT_CONTEXT_H
+#define TEST_PORT_CONTEXT_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+typedef struct _TestPortContext TestPortContext;
+
+TestPortContext *test_port_context_new (const gchar *name);
+void test_port_context_start (TestPortContext *self);
+void test_port_context_stop (TestPortContext *self);
+void test_port_context_free (TestPortContext *self);
+
+void test_port_context_set_command (TestPortContext *self,
+ const gchar *command,
+ const gchar *response);
+void test_port_context_load_commands (TestPortContext *self,
+ const gchar *commands_file);
+
+#endif /* TEST_PORT_CONTEXT_H */
diff --git a/src/plugins/tests/test-udev-rules.c b/src/plugins/tests/test-udev-rules.c
new file mode 100644
index 00000000..fe11c783
--- /dev/null
+++ b/src/plugins/tests/test-udev-rules.c
@@ -0,0 +1,257 @@
+/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <string.h>
+#include <stdio.h>
+#include <locale.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-kernel-device-generic-rules.h"
+#include "mm-log-test.h"
+
+/************************************************************/
+
+static void
+common_test (const gchar *plugindir)
+{
+ GArray *rules;
+ GError *error = NULL;
+
+ if (!plugindir)
+ return;
+
+ rules = mm_kernel_device_generic_rules_load (plugindir, &error);
+ g_assert_no_error (error);
+ g_assert (rules);
+ g_assert (rules->len > 0);
+
+ g_array_unref (rules);
+}
+
+/* Placeholder test to avoid compiler warning about common_test() being unused
+ * when none of the plugins enabled in build have custom udev rules. */
+static void
+test_placeholder (void)
+{
+ common_test (NULL);
+}
+
+/************************************************************/
+
+#if defined ENABLE_PLUGIN_HUAWEI
+static void
+test_huawei (void)
+{
+ common_test (TESTUDEVRULESDIR_HUAWEI);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_MBM
+static void
+test_mbm (void)
+{
+ common_test (TESTUDEVRULESDIR_MBM);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_NOKIA_ICERA
+static void
+test_nokia_icera (void)
+{
+ common_test (TESTUDEVRULESDIR_NOKIA_ICERA);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_ZTE
+static void
+test_zte (void)
+{
+ common_test (TESTUDEVRULESDIR_ZTE);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_LONGCHEER
+static void
+test_longcheer (void)
+{
+ common_test (TESTUDEVRULESDIR_LONGCHEER);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_SIMTECH
+static void
+test_simtech (void)
+{
+ common_test (TESTUDEVRULESDIR_SIMTECH);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_X22X
+static void
+test_x22x (void)
+{
+ common_test (TESTUDEVRULESDIR_X22X);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_CINTERION
+static void
+test_cinterion (void)
+{
+ common_test (TESTUDEVRULESDIR_CINTERION);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_DELL
+static void
+test_dell (void)
+{
+ common_test (TESTUDEVRULESDIR_DELL);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_TELIT
+static void
+test_telit (void)
+{
+ common_test (TESTUDEVRULESDIR_TELIT);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_MTK
+static void
+test_mtk (void)
+{
+ common_test (TESTUDEVRULESDIR_MTK);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_HAIER
+static void
+test_haier (void)
+{
+ common_test (TESTUDEVRULESDIR_HAIER);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_FIBOCOM
+static void
+test_fibocom (void)
+{
+ common_test (TESTUDEVRULESDIR_FIBOCOM);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_QUECTEL
+static void
+test_quectel (void)
+{
+ common_test (TESTUDEVRULESDIR_QUECTEL);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_GOSUNCN
+static void
+test_gosuncn (void)
+{
+ common_test (TESTUDEVRULESDIR_GOSUNCN);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_QCOM_SOC && defined WITH_QMI
+static void
+test_qcom_soc (void)
+{
+ common_test (TESTUDEVRULESDIR_QCOM_SOC);
+}
+#endif
+
+#if defined ENABLE_PLUGIN_LINKTOP
+static void
+test_linktop (void)
+{
+ common_test (TESTUDEVRULESDIR_LINKTOP);
+}
+#endif
+
+/************************************************************/
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+ g_test_add_func ("/MM/test-udev-rules/placeholder", test_placeholder);
+
+#if defined ENABLE_PLUGIN_HUAWEI
+ g_test_add_func ("/MM/test-udev-rules/huawei", test_huawei);
+#endif
+#if defined ENABLE_PLUGIN_MBM
+ g_test_add_func ("/MM/test-udev-rules/mbm", test_mbm);
+#endif
+#if defined ENABLE_PLUGIN_NOKIA_ICERA
+ g_test_add_func ("/MM/test-udev-rules/nokia-icera", test_nokia_icera);
+#endif
+#if defined ENABLE_PLUGIN_ZTE
+ g_test_add_func ("/MM/test-udev-rules/zte", test_zte);
+#endif
+#if defined ENABLE_PLUGIN_LONGCHEER
+ g_test_add_func ("/MM/test-udev-rules/longcheer", test_longcheer);
+#endif
+#if defined ENABLE_PLUGIN_SIMTECH
+ g_test_add_func ("/MM/test-udev-rules/simtech", test_simtech);
+#endif
+#if defined ENABLE_PLUGIN_X22X
+ g_test_add_func ("/MM/test-udev-rules/x22x", test_x22x);
+#endif
+#if defined ENABLE_PLUGIN_CINTERION
+ g_test_add_func ("/MM/test-udev-rules/cinterion", test_cinterion);
+#endif
+#if defined ENABLE_PLUGIN_DELL
+ g_test_add_func ("/MM/test-udev-rules/dell", test_dell);
+#endif
+#if defined ENABLE_PLUGIN_TELIT
+ g_test_add_func ("/MM/test-udev-rules/telit", test_telit);
+#endif
+#if defined ENABLE_PLUGIN_MTK
+ g_test_add_func ("/MM/test-udev-rules/mtk", test_mtk);
+#endif
+#if defined ENABLE_PLUGIN_HAIER
+ g_test_add_func ("/MM/test-udev-rules/haier", test_haier);
+#endif
+#if defined ENABLE_PLUGIN_FIBOCOM
+ g_test_add_func ("/MM/test-udev-rules/fibocom", test_fibocom);
+#endif
+#if defined ENABLE_PLUGIN_QUECTEL
+ g_test_add_func ("/MM/test-udev-rules/quectel", test_quectel);
+#endif
+#if defined ENABLE_PLUGIN_GOSUNCN
+ g_test_add_func ("/MM/test-udev-rules/gosuncn", test_gosuncn);
+#endif
+#if defined ENABLE_PLUGIN_QCOM_SOC && defined WITH_QMI
+ g_test_add_func ("/MM/test-udev-rules/qcom-soc", test_qcom_soc);
+#endif
+#if defined ENABLE_PLUGIN_LINKTOP
+ g_test_add_func ("/MM/test-udev-rules/linktop", test_linktop);
+#endif
+
+ return g_test_run ();
+}
diff --git a/src/plugins/thuraya/mm-broadband-modem-thuraya.c b/src/plugins/thuraya/mm-broadband-modem-thuraya.c
new file mode 100644
index 00000000..92c8a8a2
--- /dev/null
+++ b/src/plugins/thuraya/mm-broadband-modem-thuraya.c
@@ -0,0 +1,284 @@
+/* -*- 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) 2011 - 2012 Ammonit Measurement GmbH
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ * Copyright (C) 2016 Thomas Sailer <t.sailer@alumni.ethz.ch>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-errors-types.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-messaging.h"
+#include "mm-broadband-modem-thuraya.h"
+#include "mm-broadband-bearer.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-thuraya.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void iface_modem_messaging_init (MMIfaceModemMessaging *iface);
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemThuraya, mm_broadband_modem_thuraya, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init) );
+
+/*****************************************************************************/
+/* Operator Code and Name loading (3GPP interface) */
+
+static gchar *
+load_operator_code_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_operator_code (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_return_pointer (task, g_strdup ("90106"), g_free);
+ g_object_unref (task);
+}
+
+static gchar *
+load_operator_name_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_operator_name (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_return_pointer (task, g_strdup ("THURAYA"), g_free);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Load supported modes (Modem inteface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ GArray *combinations;
+ MMModemModeCombination mode;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
+
+ /* Report any, Thuraya connections are packet-switched */
+ mode.allowed = MM_MODEM_MODE_ANY;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ g_task_return_pointer (task, combinations, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Load supported SMS storages (Messaging interface) */
+
+typedef struct {
+ GArray *mem1;
+ GArray *mem2;
+ GArray *mem3;
+} SupportedStoragesResult;
+
+static void
+supported_storages_result_free (SupportedStoragesResult *result)
+{
+ if (result->mem1)
+ g_array_unref (result->mem1);
+ if (result->mem2)
+ g_array_unref (result->mem2);
+ if (result->mem3)
+ g_array_unref (result->mem3);
+ g_free (result);
+}
+
+static gboolean
+modem_messaging_load_supported_storages_finish (MMIfaceModemMessaging *self,
+ GAsyncResult *res,
+ GArray **mem1,
+ GArray **mem2,
+ GArray **mem3,
+ GError **error)
+{
+ SupportedStoragesResult *result;
+
+ result = g_task_propagate_pointer (G_TASK (res), error);
+ if (!result)
+ return FALSE;
+
+ *mem1 = g_array_ref (result->mem1);
+ *mem2 = g_array_ref (result->mem2);
+ *mem3 = g_array_ref (result->mem3);
+ supported_storages_result_free (result);
+ return TRUE;
+}
+
+static void
+cpms_format_check_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ SupportedStoragesResult *result;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ result = g_new0 (SupportedStoragesResult, 1);
+
+ /* Parse reply */
+ if (!mm_thuraya_3gpp_parse_cpms_test_response (response,
+ &result->mem1,
+ &result->mem2,
+ &result->mem3,
+ &error)) {
+ supported_storages_result_free (result);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ g_task_return_pointer (task, result, (GDestroyNotify) supported_storages_result_free);
+ g_object_unref (task);
+}
+
+static void
+modem_messaging_load_supported_storages (MMIfaceModemMessaging *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Check support storages */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CPMS=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)cpms_format_check_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemThuraya *
+mm_broadband_modem_thuraya_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_THURAYA,
+ 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,
+ NULL);
+}
+
+static void
+mm_broadband_modem_thuraya_init (MMBroadbandModemThuraya *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ /* No need to power-up/power-down the modem */
+ iface->load_power_state = NULL;
+ iface->load_power_state_finish = NULL;
+ iface->modem_power_up = NULL;
+ iface->modem_power_up_finish = NULL;
+ iface->modem_power_down = NULL;
+ iface->modem_power_down_finish = NULL;
+
+ /* Supported modes cannot be queried */
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ /* Fixed operator code and name to be reported */
+ iface->load_operator_name = load_operator_name;
+ iface->load_operator_name_finish = load_operator_name_finish;
+ iface->load_operator_code = load_operator_code;
+ iface->load_operator_code_finish = load_operator_code_finish;
+
+ /* Don't try to scan networks with AT+COPS=?.
+ * The Thuraya XT does not seem to properly support AT+COPS=?.
+ * When issuing this command, it seems to get sufficiently confused
+ * to drop the signal. Furthermore, it is useless anyway as there is only
+ * one network supported, Thuraya.
+ */
+ iface->scan_networks = NULL;
+ iface->scan_networks_finish = NULL;
+}
+
+static void
+iface_modem_messaging_init (MMIfaceModemMessaging *iface)
+{
+ iface->load_supported_storages = modem_messaging_load_supported_storages;
+ iface->load_supported_storages_finish = modem_messaging_load_supported_storages_finish;
+}
+
+static void
+mm_broadband_modem_thuraya_class_init (MMBroadbandModemThurayaClass *klass)
+{
+}
diff --git a/src/plugins/thuraya/mm-broadband-modem-thuraya.h b/src/plugins/thuraya/mm-broadband-modem-thuraya.h
new file mode 100644
index 00000000..42df9b9c
--- /dev/null
+++ b/src/plugins/thuraya/mm-broadband-modem-thuraya.h
@@ -0,0 +1,50 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2011 Red Hat, Inc.
+ * Copyright (C) 2011 Google Inc.
+ * Copyright (C) 2016 Thomas Sailer <t.sailer@alumni.ethz.ch>
+ */
+
+#ifndef MM_BROADBAND_MODEM_THURAYA_H
+#define MM_BROADBAND_MODEM_THURAYA_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_THURAYA (mm_broadband_modem_thuraya_get_type ())
+#define MM_BROADBAND_MODEM_THURAYA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_THURAYA, MMBroadbandModemThuraya))
+#define MM_BROADBAND_MODEM_THURAYA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_THURAYA, MMBroadbandModemThurayaClass))
+#define MM_IS_BROADBAND_MODEM_THURAYA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_THURAYA))
+#define MM_IS_BROADBAND_MODEM_THURAYA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_THURAYA))
+#define MM_BROADBAND_MODEM_THURAYA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_THURAYA, MMBroadbandModemThurayaClass))
+
+typedef struct _MMBroadbandModemThuraya MMBroadbandModemThuraya;
+typedef struct _MMBroadbandModemThurayaClass MMBroadbandModemThurayaClass;
+
+struct _MMBroadbandModemThuraya {
+ MMBroadbandModem parent;
+};
+
+struct _MMBroadbandModemThurayaClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_thuraya_get_type (void);
+
+MMBroadbandModemThuraya *mm_broadband_modem_thuraya_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_THURAYA_H */
diff --git a/src/plugins/thuraya/mm-modem-helpers-thuraya.c b/src/plugins/thuraya/mm-modem-helpers-thuraya.c
new file mode 100644
index 00000000..0c713a18
--- /dev/null
+++ b/src/plugins/thuraya/mm-modem-helpers-thuraya.c
@@ -0,0 +1,148 @@
+/* -*- 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) 2016 Thomas Sailer <t.sailer@alumni.ethz.ch>
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MMCLI
+#include <libmm-glib.h>
+
+#include "mm-log.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-thuraya.h"
+
+/*************************************************************************/
+
+static MMSmsStorage
+storage_from_str (const gchar *str)
+{
+ if (g_str_equal (str, "SM"))
+ return MM_SMS_STORAGE_SM;
+ if (g_str_equal (str, "ME"))
+ return MM_SMS_STORAGE_ME;
+ if (g_str_equal (str, "MT"))
+ return MM_SMS_STORAGE_MT;
+ if (g_str_equal (str, "SR"))
+ return MM_SMS_STORAGE_SR;
+ if (g_str_equal (str, "BM"))
+ return MM_SMS_STORAGE_BM;
+ if (g_str_equal (str, "TA"))
+ return MM_SMS_STORAGE_TA;
+ return MM_SMS_STORAGE_UNKNOWN;
+}
+
+gboolean
+mm_thuraya_3gpp_parse_cpms_test_response (const gchar *reply,
+ GArray **mem1,
+ GArray **mem2,
+ GArray **mem3,
+ GError **error)
+{
+#define N_EXPECTED_GROUPS 3
+
+ gchar **splitp;
+ const gchar *splita[N_EXPECTED_GROUPS];
+ guint i;
+ g_auto(GStrv) split = NULL;
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GArray) tmp1 = NULL;
+ g_autoptr(GArray) tmp2 = NULL;
+ g_autoptr(GArray) tmp3 = NULL;
+
+ g_assert (mem1 != NULL);
+ g_assert (mem2 != NULL);
+ g_assert (mem3 != NULL);
+
+ split = g_strsplit (mm_strip_tag (reply, "+CPMS:"), " ", -1);
+ if (!split) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't split +CPMS response");
+ return FALSE;
+ }
+
+ /* remove empty strings, and count non-empty strings */
+ i = 0;
+ for (splitp = split; *splitp; ++splitp) {
+ if (!**splitp)
+ continue;
+ if (i < N_EXPECTED_GROUPS)
+ splita[i] = *splitp;
+ ++i;
+ }
+
+ if (i != N_EXPECTED_GROUPS) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse +CPMS response: invalid number of groups (%u != %u)",
+ i, N_EXPECTED_GROUPS);
+ return FALSE;
+ }
+
+ r = g_regex_new ("\\s*\"([^,\\)]+)\"\\s*", 0, 0, NULL);
+ g_assert (r);
+
+ for (i = 0; i < N_EXPECTED_GROUPS; i++) {
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GArray *array;
+
+ /* We always return a valid array, even if it may be empty */
+ array = g_array_new (FALSE, FALSE, sizeof (MMSmsStorage));
+
+ /* Got a range group to match */
+ if (g_regex_match (r, splita[i], 0, &match_info)) {
+ while (g_match_info_matches (match_info)) {
+ g_autofree gchar *str = NULL;
+
+ str = g_match_info_fetch (match_info, 1);
+ if (str) {
+ MMSmsStorage storage;
+
+ storage = storage_from_str (str);
+ g_array_append_val (array, storage);
+ }
+
+ g_match_info_next (match_info, NULL);
+ }
+ }
+
+ if (!tmp1)
+ tmp1 = array;
+ else if (!tmp2)
+ tmp2 = array;
+ else if (!tmp3)
+ tmp3 = array;
+ else
+ g_assert_not_reached ();
+ }
+
+ /* Only return TRUE if all sets have been parsed correctly
+ * (even if the arrays may be empty) */
+ if (tmp1 && tmp2 && tmp3) {
+ *mem1 = g_steal_pointer (&tmp1);
+ *mem2 = g_steal_pointer (&tmp2);
+ *mem3 = g_steal_pointer (&tmp3);
+ return TRUE;
+ }
+
+ /* Otherwise, cleanup and return FALSE */
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse +CPMS response: not all groups detected (mem1 %s, mem2 %s, mem3 %s)",
+ tmp1 ? "yes" : "no",
+ tmp2 ? "yes" : "no",
+ tmp3 ? "yes" : "no");
+ return FALSE;
+}
diff --git a/src/plugins/thuraya/mm-modem-helpers-thuraya.h b/src/plugins/thuraya/mm-modem-helpers-thuraya.h
new file mode 100644
index 00000000..33bb079f
--- /dev/null
+++ b/src/plugins/thuraya/mm-modem-helpers-thuraya.h
@@ -0,0 +1,28 @@
+/* -*- 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) 2016 Thomas Sailer <t.sailer@alumni.ethz.ch>
+ *
+ */
+#ifndef MM_MODEM_HELPERS_THURAYA_H
+#define MM_MODEM_HELPERS_THURAYA_H
+
+#include <glib.h>
+
+/* AT+CPMS=? (Preferred SMS storage) response parser */
+gboolean mm_thuraya_3gpp_parse_cpms_test_response (const gchar *reply,
+ GArray **mem1,
+ GArray **mem2,
+ GArray **mem3,
+ GError **error);
+
+#endif /* MM_MODEM_HELPERS_THURAYA_H */
diff --git a/src/plugins/thuraya/mm-plugin-thuraya.c b/src/plugins/thuraya/mm-plugin-thuraya.c
new file mode 100644
index 00000000..5097e24e
--- /dev/null
+++ b/src/plugins/thuraya/mm-plugin-thuraya.c
@@ -0,0 +1,85 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2011 - 2012 Ammonit Measurement GmbH
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ * Copyright (C) 2016 Thomas Sailer <t.sailer@alumni.ethz.ch>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-thuraya.h"
+#include "mm-broadband-modem.h"
+#include "mm-broadband-modem-thuraya.h"
+#include "mm-private-boxed-types.h"
+
+G_DEFINE_TYPE (MMPluginThuraya, mm_plugin_thuraya, 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)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_thuraya_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", NULL };
+ static const guint16 vendor_ids[] = { 0x1a26, 0 };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_THURAYA,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_thuraya_init (MMPluginThuraya *self)
+{
+}
+
+static void
+mm_plugin_thuraya_class_init (MMPluginThurayaClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/thuraya/mm-plugin-thuraya.h b/src/plugins/thuraya/mm-plugin-thuraya.h
new file mode 100644
index 00000000..fb86090d
--- /dev/null
+++ b/src/plugins/thuraya/mm-plugin-thuraya.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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2011 - 2012 Ammonit Measurement GmbH
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ * Copyright (C) 2016 Thomas Sailer <t.sailer@alumni.ethz.ch>
+ */
+
+#ifndef MM_PLUGIN_THURAYA_H
+#define MM_PLUGIN_THURAYA_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_THURAYA (mm_plugin_thuraya_get_type ())
+#define MM_PLUGIN_THURAYA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_THURAYA, MMPluginThuraya))
+#define MM_PLUGIN_THURAYA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_THURAYA, MMPluginThurayaClass))
+#define MM_IS_PLUGIN_THURAYA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_THURAYA))
+#define MM_IS_PLUGIN_THURAYA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_THURAYA))
+#define MM_PLUGIN_THURAYA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_THURAYA, MMPluginThurayaClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginThuraya;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginThurayaClass;
+
+GType mm_plugin_thuraya_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_THURAYA_H */
diff --git a/src/plugins/thuraya/tests/test-mm-modem-helpers-thuraya.c b/src/plugins/thuraya/tests/test-mm-modem-helpers-thuraya.c
new file mode 100644
index 00000000..bdc073d0
--- /dev/null
+++ b/src/plugins/thuraya/tests/test-mm-modem-helpers-thuraya.c
@@ -0,0 +1,106 @@
+/* -*- 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) 2016 Thomas Sailer <t.sailer@alumni.ethz.ch>
+ *
+ */
+#include <stdio.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <locale.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-thuraya.h"
+#include "mm-log-test.h"
+
+/*****************************************************************************/
+/* Test CPMS response */
+
+static gboolean
+is_storage_supported (GArray *supported,
+ MMSmsStorage storage)
+{
+ guint i;
+
+ for (i = 0; i < supported->len; i++) {
+ if (storage == g_array_index (supported, MMSmsStorage, i))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+test_cpms_response_thuraya (void *f, gpointer d)
+{
+ /*
+ * First: ("ME","MT") 2-item group
+ * Second: "ME" 1 item
+ * Third: ("SM") 1-item group
+ */
+ const gchar *reply = "+CPMS: \"MT\",\"SM\",\"BM\",\"ME\",\"SR\", \"MT\",\"SM\",\"BM\",\"ME\",\"SR\", \"MT\",\"SM\",\"BM\",\"ME\",\"SR\" ";
+ GArray *mem1 = NULL;
+ GArray *mem2 = NULL;
+ GArray *mem3 = NULL;
+ GError *error = NULL;
+
+ g_debug ("Testing thuraya +CPMS=? response...");
+
+ g_assert (mm_thuraya_3gpp_parse_cpms_test_response (reply, &mem1, &mem2, &mem3, &error));
+ g_assert_no_error (error);
+ g_assert_cmpuint (mem1->len, ==, 5);
+ g_assert (is_storage_supported (mem1, MM_SMS_STORAGE_MT));
+ g_assert (is_storage_supported (mem1, MM_SMS_STORAGE_SM));
+ g_assert (is_storage_supported (mem1, MM_SMS_STORAGE_BM));
+ g_assert (is_storage_supported (mem1, MM_SMS_STORAGE_ME));
+ g_assert (is_storage_supported (mem1, MM_SMS_STORAGE_SR));
+ g_assert_cmpuint (mem2->len, ==, 5);
+ g_assert (is_storage_supported (mem2, MM_SMS_STORAGE_MT));
+ g_assert (is_storage_supported (mem2, MM_SMS_STORAGE_SM));
+ g_assert (is_storage_supported (mem2, MM_SMS_STORAGE_BM));
+ g_assert (is_storage_supported (mem2, MM_SMS_STORAGE_ME));
+ g_assert (is_storage_supported (mem2, MM_SMS_STORAGE_SR));
+ g_assert_cmpuint (mem3->len, ==, 5);
+ g_assert (is_storage_supported (mem3, MM_SMS_STORAGE_MT));
+ g_assert (is_storage_supported (mem3, MM_SMS_STORAGE_SM));
+ g_assert (is_storage_supported (mem3, MM_SMS_STORAGE_BM));
+ g_assert (is_storage_supported (mem3, MM_SMS_STORAGE_ME));
+ g_assert (is_storage_supported (mem3, MM_SMS_STORAGE_SR));
+
+ g_array_unref (mem1);
+ g_array_unref (mem2);
+ g_array_unref (mem3);
+}
+
+/*****************************************************************************/
+
+#define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (GTestFixtureFunc) t, NULL)
+
+int main (int argc, char **argv)
+{
+ GTestSuite *suite;
+ gint result;
+
+ g_test_init (&argc, &argv, NULL);
+
+ suite = g_test_get_root ();
+
+ g_test_suite_add (suite, TESTCASE (test_cpms_response_thuraya, NULL));
+
+ result = g_test_run ();
+
+ return result;
+}
diff --git a/src/plugins/tplink/77-mm-tplink-port-types.rules b/src/plugins/tplink/77-mm-tplink-port-types.rules
new file mode 100644
index 00000000..f5c8894e
--- /dev/null
+++ b/src/plugins/tplink/77-mm-tplink-port-types.rules
@@ -0,0 +1,15 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_tplink_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="2357", GOTO="mm_tplink_port_types"
+GOTO="mm_tplink_port_types_end"
+
+LABEL="mm_tplink_port_types"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# D-Link DWM-222
+ATTRS{idVendor}=="2357", ATTRS{idProduct}=="9000", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="2357", ATTRS{idProduct}=="9000", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="2357", ATTRS{idProduct}=="9000", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+
+LABEL="mm_tplink_port_types_end"
diff --git a/src/plugins/tplink/mm-plugin-tplink.c b/src/plugins/tplink/mm-plugin-tplink.c
new file mode 100644
index 00000000..1c03fef0
--- /dev/null
+++ b/src/plugins/tplink/mm-plugin-tplink.c
@@ -0,0 +1,94 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-plugin-tplink.h"
+#include "mm-broadband-modem.h"
+
+#if defined WITH_QMI
+# include "mm-broadband-modem-qmi.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginTplink, mm_plugin_tplink, 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 TP-Link modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_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", NULL };
+ static const guint16 vendor_ids[] = { 0x2357, 0 };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_TPLINK,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_tplink_init (MMPluginTplink *self)
+{
+}
+
+static void
+mm_plugin_tplink_class_init (MMPluginTplinkClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/tplink/mm-plugin-tplink.h b/src/plugins/tplink/mm-plugin-tplink.h
new file mode 100644
index 00000000..16dc5f5b
--- /dev/null
+++ b/src/plugins/tplink/mm-plugin-tplink.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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_PLUGIN_TPLINK_H
+#define MM_PLUGIN_TPLINK_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_TPLINK (mm_plugin_tplink_get_type ())
+#define MM_PLUGIN_TPLINK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_TPLINK, MMPluginTplink))
+#define MM_PLUGIN_TPLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_TPLINK, MMPluginTplinkClass))
+#define MM_IS_PLUGIN_TPLINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_TPLINK))
+#define MM_IS_PLUGIN_TPLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_TPLINK))
+#define MM_PLUGIN_TPLINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_TPLINK, MMPluginTplinkClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginTplink;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginTplinkClass;
+
+GType mm_plugin_tplink_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_TPLINK_H */
diff --git a/src/plugins/ublox/77-mm-ublox-port-types.rules b/src/plugins/ublox/77-mm-ublox-port-types.rules
new file mode 100644
index 00000000..c2a1ac99
--- /dev/null
+++ b/src/plugins/ublox/77-mm-ublox-port-types.rules
@@ -0,0 +1,78 @@
+# do not edit this file, it will be overwritten on update
+ACTION!="add|change|move|bind", GOTO="mm_ublox_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1546", GOTO="mm_ublox_port_types"
+GOTO="mm_ublox_port_types_end"
+
+LABEL="mm_ublox_port_types"
+
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Fully ignore u-blox GPS devices
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a5", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a6", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a7", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a8", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a9", ENV{ID_MM_DEVICE_IGNORE}="1"
+
+# Toby-L4 port types
+# ttyACM0 (if #2): secondary (ignore)
+# ttyACM1 (if #4): debug port (ignore)
+# ttyACM2 (if #6): primary
+# Wait up to 20s for the +READY URC
+# ttyACM3 (if #8): AT port for FOTA (ignore)
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_UBLOX_PORT_READY_DELAY}="20"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="08", ENV{ID_MM_PORT_IGNORE}="1"
+
+# TOBY-L200
+# Wait up to 20s before probing AT ports
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1141", ENV{ID_MM_UBLOX_PORT_READY_DELAY}="20"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1143", ENV{ID_MM_UBLOX_PORT_READY_DELAY}="20"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1146", ENV{ID_MM_UBLOX_PORT_READY_DELAY}="20"
+
+# TOBY-R2 port types
+# ttyACM0 (if #0): primary
+# ttyACM1 (if #2): secondary
+# ttyACM2 (if #4): tertiary
+# ttyACM3 (if #6): GNSS Tunneling (ignore)
+# ttyACM4 (if #8): SIM Access Profile (ignore)
+# ttyACM5 (if #10): Primary Log for diagnostics (ignore)
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1107", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1107", ENV{.MM_USBIFNUM}=="08", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1107", ENV{.MM_USBIFNUM}=="0a", ENV{ID_MM_PORT_IGNORE}="1"
+
+# LARA-R2 port types
+# ttyACM0 (if #0): primary
+# ttyACM1 (if #2): secondary
+# ttyACM2 (if #4): tertiary
+# ttyACM3 (if #6): GNSS Tunneling (ignore)
+# ttyACM4 (if #8): SIM Access Profile (ignore)
+# ttyACM5 (if #10): Primary Log for diagnostics (ignore)
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="110a", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="110a", ENV{.MM_USBIFNUM}=="08", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="110a", ENV{.MM_USBIFNUM}=="0a", ENV{ID_MM_PORT_IGNORE}="1"
+
+# LISA-U2 / SARA-U2 port types
+# ttyACM0 (if #0): primary
+# ttyACM1 (if #2): secondary
+# ttyACM2 (if #4): tertiary
+# ttyACM3 (if #6): GNSS Tunneling (ignore)
+# ttyACM4 (if #8): Primary Log for diagnostics (ignore)
+# ttyACM5 (if #10): Secondary Log for diagnostics (ignore)
+# ttyACM6 (if #12): SAP (SIM Access Profile) (ignore)
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1102", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1102", ENV{.MM_USBIFNUM}=="08", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1102", ENV{.MM_USBIFNUM}=="0a", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1102", ENV{.MM_USBIFNUM}=="0c", ENV{ID_MM_PORT_IGNORE}="1"
+
+# LISA-U2 / SARA-U2 (alternative configuration) port types
+# ttyACM0 (if #0): primary
+# ttyACM1 (if #2): GNSS Tunneling (ignore)
+# ttyACM2 (if #4): Primary Log for diagnostics (ignore)
+# ttyACM3 (if #6): SAP (SIM Access Profile) (ignore)
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1104", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1104", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="1104", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+
+LABEL="mm_ublox_port_types_end"
diff --git a/src/plugins/ublox/README b/src/plugins/ublox/README
new file mode 100644
index 00000000..573be2ce
--- /dev/null
+++ b/src/plugins/ublox/README
@@ -0,0 +1,162 @@
+
+The 'ublox' plugin is originally targeted for the u-blox TOBY-L2 series,
+although it may be used with other kind of devices likely without many issues.
+
+=====================================
+ USB profiles and networking modes
+=====================================
+
+The TOBY-L2 devices may work in multiple different USB profiles:
+
+ * AT+UUSBCONF=0: fairly back-compatible profile, where only cdc-acm TTYs are
+ exposed. ModemManager will default to PPP for the connection setup when in
+ this profile.
+
+ * AT+UUSBCONF=2: ECM profile, where multiple cdc-acm TTYs are exposed along
+ with a ECM network interface.
+
+ * AT+UUSBCONF=3: RNDIS profile, where one cdc-acm TTY and a RNDIS network
+ interface are exposed. This is the default factory-programmed value.
+
+When a profile with a network interface (ECM or RNDIS) is in use, the device may
+work in multiple networking modes:
+
+ * AT+UBMCONF=1: Router mode, with a built-in DHCP server running behind the
+ network interface. The network interface will be assigned an IP address from
+ a subnet managed by the device itself. This is the default factory-programmed
+ value. E.g.:
+
+ $ ip addr
+ 9: usb0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000
+ link/ether 02:07:01:15:00:0b brd ff:ff:ff:ff:ff:ff
+ inet 192.168.1.100/24 brd 192.168.1.255 scope global dynamic usb0
+ valid_lft 43009sec preferred_lft 43009sec
+
+ $ ip route
+ default via 192.168.1.1 dev usb0 proto static metric 700
+ 192.168.1.0/24 dev usb0 proto kernel scope link src 192.168.1.100 metric 700
+
+ * AT+UBMCONF=2: Bridge mode, where static IP addressing and routing must be
+ performed once connected. The network interface will be assigned the same IP
+ address provided by the network operator. The plugin uses 'AT+UIPADDR=N' the
+ default gateway IP settings and 'AT++CGCONTRDP=N' to retrieve the interface
+ IP settings and DNS setup.
+
+ $ ip addr
+ 11: usb0: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000
+ link/ether 02:07:01:15:00:0b brd ff:ff:ff:ff:ff:ff
+ inet 47.59.109.26/32 brd 47.59.109.26 scope global usb0
+ valid_lft forever preferred_lft forever
+
+ $ ip route
+ default via 47.59.109.229 dev usb0 proto static metric 700
+ 47.59.109.26 dev usb0 proto kernel scope link src 47.59.109.26 metric 700
+ 47.59.109.229 dev usb0 proto static scope link metric 700
+
+The 'ublox' plugin in ModemManager works with any of the previous combinations
+seamlessly. It is assumed that the device doesn't change either USB profile or
+networking mode once it has been detected and processed by ModemManager.
+
+NOTE: If manually selecting different USB profiles or networking modes, remember
+to reset the module before assuming the new settings have been applied. E.g.,
+using plain mmcli commands:
+ $ sudo mmcli -m 0 --command="AT+UUSBCONF=3"
+ $ sudo mmcli -m 0 --reset
+
+=================================
+ Connection setup
+=================================
+
+The plugin allows to connect to specific APNs in the usual way (i.e. by creating
+a PDP context for the specific APN), and then activating the PDP context with
+'AT+CGACT=[CID]'.
+
+Authentication settings of the APN (user, password, authentication type) are
+also supported via the 'AT+UAUTHREQ' command.
+
+The plugin doesn't currently support reporting as auto-connected the default LTE
+bearer.
+
+========================================
+ Connection monitoring and statistics
+========================================
+
+The status of the connection of the specific PDP context is monitored
+periodically using 'AT+CGACT?', in order to detect network-originated
+disconnections. This implementation is given in the Generic broadband bearer
+implementation, and is not ublox-specific.
+
+If the device supports it, connection TX/RX statistics will also be periodically
+loaded using the AT+UGCNTRD command. Note, though, that the TOBY-L2 doesn't seem
+to support this information via control commands.
+
+===========================================
+ Supported and current mode combinations
+===========================================
+
+The full list of supported mode combinations is loaded using 'AT+URAT=?', and
+then filtered by device product name to remove technologies not supported in
+several devices. E.g. the standard TOBY-L2 list of supported mode combinations
+will include all 2G, 3G and 4G, but if the device is a L201, 2G support will be
+removed from the list.
+
+The current mode combination in use is loaded using 'AT+URAT?', and the setting
+may be changed using the 'AT+URAT=X' request.
+
+In order to be able to update this setting, the device will be put in low-power
+mode ('AT+CFUN=4'), then the setting update will be run, and finally the device
+will recover the previous functionality mode it was in (e.g. 'AT+CFUN=1' if it
+was in full functionality mode).
+
+===============================
+ Supported and current bands
+===============================
+
+The full list of supported bands is hardcoded based on the supported modes of the
+device. There is no runtime loading of which are the supported bands because the
+'AT+UBANDSEL=?' command gives different results depending on the current access
+technology (i.e. there is no single full list of supported bands reported).
+
+The current list of bands is loaded via the 'AT+UBANDSEL?' command, and the
+setting may be changed using the 'AT+UBANDSEL=X' request.
+
+In order to be able to update this setting, the device will be put in low-power
+mode ('AT+CFUN=4'), then the setting update will be run, and finally the device
+will recover the previous functionality mode it was in (e.g. 'AT+CFUN=1' if it
+was in full functionality mode).
+
+======================
+ Functionality mode
+======================
+
+The plugin implements a custom 'AT+CFUN?' response parser because it provides
+multiple modes that may be treated as 'low-power' by ModemManager (e.g. mode
+'0' is minimum functionality, mode '4' is airplane mode and mode '19' is
+minimum functionality with SIM deactivated).
+
+The plugin implements power-on ('AT+CFUN=1'), power-down ('AT+CFUN=4'), reset
+('AT+CFUN=16') and power-off ('AT+CPWROFF'). As usual, a reset will trigger a
+power cycle of the device, and the power-off will render the modem unusable
+until it's power cycled externally.
+
+====================================
+ Network registration and quality
+====================================
+
+The LTE specific 'AT+CEREG' registration checks will be enabled by default if
+the module supports LTE. Additionally, a custom 'CREG/CGREG/CEREG' state number
+parser is provided to support u-blox specific reported states (e.g. state '6'
+to indicate 'sms only registration' on the '+CREG' indications for the CS
+network when on LTE).
+
+The plugin implements the extended Signal interface, providing RSSI, RSCP, Ec/Io
+and RSRQ measurements obtained with the 'AT+CESQ' command.
+
+==================
+ PIN management
+==================
+
+A custom method to load the PIN/PUK remaining attempts is implemented based on
+the 'AT+UPINCNT' command.
+
+Have fun!
diff --git a/src/plugins/ublox/mm-broadband-bearer-ublox.c b/src/plugins/ublox/mm-broadband-bearer-ublox.c
new file mode 100644
index 00000000..27db9b11
--- /dev/null
+++ b/src/plugins/ublox/mm-broadband-bearer-ublox.c
@@ -0,0 +1,1035 @@
+/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <arpa/inet.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-broadband-bearer-ublox.h"
+#include "mm-base-modem-at.h"
+#include "mm-log-object.h"
+#include "mm-ublox-enums-types.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-ublox.h"
+
+G_DEFINE_TYPE (MMBroadbandBearerUblox, mm_broadband_bearer_ublox, MM_TYPE_BROADBAND_BEARER)
+
+enum {
+ PROP_0,
+ PROP_USB_PROFILE,
+ PROP_NETWORKING_MODE,
+ PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+struct _MMBroadbandBearerUbloxPrivate {
+ MMUbloxUsbProfile profile;
+ MMUbloxNetworkingMode mode;
+ MMUbloxBearerAllowedAuth allowed_auths;
+ FeatureSupport statistics;
+ FeatureSupport cedata;
+};
+
+/*****************************************************************************/
+/* Common connection context and task */
+
+typedef struct {
+ MMBroadbandModem *modem;
+ MMPortSerialAt *primary;
+ MMPort *data;
+ guint cid;
+ gboolean auth_required;
+ MMBearerIpConfig *ip_config; /* For IPv4 settings */
+} CommonConnectContext;
+
+static void
+common_connect_context_free (CommonConnectContext *ctx)
+{
+ if (ctx->ip_config)
+ g_object_unref (ctx->ip_config);
+ if (ctx->data)
+ g_object_unref (ctx->data);
+ g_object_unref (ctx->modem);
+ g_object_unref (ctx->primary);
+ g_slice_free (CommonConnectContext, ctx);
+}
+
+static GTask *
+common_connect_task_new (MMBroadbandBearerUblox *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ guint cid,
+ MMPort *data,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ CommonConnectContext *ctx;
+ GTask *task;
+
+ ctx = g_slice_new0 (CommonConnectContext);
+ ctx->modem = g_object_ref (modem);
+ ctx->primary = g_object_ref (primary);
+ ctx->cid = cid;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) common_connect_context_free);
+
+ /* We need a net data port */
+ if (data)
+ ctx->data = g_object_ref (data);
+ else {
+ ctx->data = mm_base_modem_get_best_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET);
+ if (!ctx->data) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_NOT_FOUND,
+ "No valid data port found to launch connection");
+ g_object_unref (task);
+ return NULL;
+ }
+ }
+
+ return task;
+}
+
+/*****************************************************************************/
+/* 3GPP IP config (sub-step of the 3GPP Connection sequence) */
+
+static gboolean
+get_ip_config_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ MMBearerIpConfig **ipv4_config,
+ MMBearerIpConfig **ipv6_config,
+ GError **error)
+{
+ MMBearerConnectResult *configs;
+ MMBearerIpConfig *ipv4;
+
+ configs = g_task_propagate_pointer (G_TASK (res), error);
+ if (!configs)
+ return FALSE;
+
+ /* Just IPv4 for now */
+ ipv4 = mm_bearer_connect_result_peek_ipv4_config (configs);
+ g_assert (ipv4);
+ if (ipv4_config)
+ *ipv4_config = g_object_ref (ipv4);
+ if (ipv6_config)
+ *ipv6_config = NULL;
+ mm_bearer_connect_result_unref (configs);
+ return TRUE;
+}
+
+static void
+complete_get_ip_config_3gpp (GTask *task)
+{
+ CommonConnectContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ g_assert (mm_bearer_ip_config_get_method (ctx->ip_config) != MM_BEARER_IP_METHOD_UNKNOWN);
+ g_task_return_pointer (task,
+ mm_bearer_connect_result_new (ctx->data, ctx->ip_config, NULL),
+ (GDestroyNotify) mm_bearer_connect_result_unref);
+ g_object_unref (task);
+}
+
+static void
+cgcontrdp_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerUblox *self;
+ const gchar *response;
+ GError *error = NULL;
+ CommonConnectContext *ctx;
+ gchar *local_address = NULL;
+ gchar *subnet = NULL;
+ gchar *dns_addresses[3] = { NULL, NULL, NULL };
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (!response || !mm_3gpp_parse_cgcontrdp_response (response,
+ NULL, /* cid */
+ NULL, /* bearer id */
+ NULL, /* apn */
+ &local_address,
+ &subnet,
+ NULL, /* gateway_address */
+ &dns_addresses[0],
+ &dns_addresses[1],
+ &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "IPv4 address retrieved: %s", local_address);
+ mm_bearer_ip_config_set_address (ctx->ip_config, local_address);
+ mm_obj_dbg (self, "IPv4 subnet retrieved: %s", subnet);
+ mm_bearer_ip_config_set_prefix (ctx->ip_config, mm_netmask_to_cidr (subnet));
+ if (dns_addresses[0])
+ mm_obj_dbg (self, "primary DNS retrieved: %s", dns_addresses[0]);
+ if (dns_addresses[1])
+ mm_obj_dbg (self, "secondary DNS retrieved: %s", dns_addresses[1]);
+ mm_bearer_ip_config_set_dns (ctx->ip_config, (const gchar **) dns_addresses);
+
+ g_free (local_address);
+ g_free (subnet);
+ g_free (dns_addresses[0]);
+ g_free (dns_addresses[1]);
+
+ mm_obj_dbg (self, "finished IP settings retrieval for PDP context #%u...", ctx->cid);
+
+ complete_get_ip_config_3gpp (task);
+}
+
+static void
+uipaddr_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerUblox *self;
+ const gchar *response;
+ gchar *cmd;
+ GError *error = NULL;
+ CommonConnectContext *ctx;
+ gchar *gw_ipv4_address = NULL;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (!response || !mm_ublox_parse_uipaddr_response (response,
+ NULL, /* cid */
+ NULL, /* if_name */
+ &gw_ipv4_address,
+ NULL, /* ipv4_subnet */
+ NULL, /* ipv6_global_address */
+ NULL, /* ipv6_link_local_address */
+ &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "IPv4 gateway address retrieved: %s", gw_ipv4_address);
+ mm_bearer_ip_config_set_gateway (ctx->ip_config, gw_ipv4_address);
+ g_free (gw_ipv4_address);
+
+ cmd = g_strdup_printf ("+CGCONTRDP=%u", ctx->cid);
+ mm_obj_dbg (self, "gathering IP and DNS information for PDP context #%u...", ctx->cid);
+ mm_base_modem_at_command (MM_BASE_MODEM (modem),
+ cmd,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback) cgcontrdp_ready,
+ task);
+ g_free (cmd);
+}
+
+static void
+get_ip_config_3gpp (MMBroadbandBearer *_self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ MMBearerIpFamily ip_family,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandBearerUblox *self = MM_BROADBAND_BEARER_UBLOX (_self);
+ GTask *task;
+ CommonConnectContext *ctx;
+
+ if (!(task = common_connect_task_new (MM_BROADBAND_BEARER_UBLOX (self),
+ MM_BROADBAND_MODEM (modem),
+ primary,
+ cid,
+ data,
+ NULL,
+ callback,
+ user_data)))
+ return;
+
+ ctx = g_task_get_task_data (task);
+ ctx->ip_config = mm_bearer_ip_config_new ();
+
+ /* If we're in BRIDGE mode, we need to ask for static IP addressing details:
+ * - AT+UIPADDR=[CID] will give us the default gateway address.
+ * - +CGCONTRDP?[CID] will give us the IP address, subnet and DNS addresses.
+ */
+ if (self->priv->mode == MM_UBLOX_NETWORKING_MODE_BRIDGE) {
+ gchar *cmd;
+
+ mm_bearer_ip_config_set_method (ctx->ip_config, MM_BEARER_IP_METHOD_STATIC);
+
+ cmd = g_strdup_printf ("+UIPADDR=%u", cid);
+ mm_obj_dbg (self, "gathering gateway information for PDP context #%u...", cid);
+ mm_base_modem_at_command (MM_BASE_MODEM (modem),
+ cmd,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback) uipaddr_ready,
+ task);
+ g_free (cmd);
+ return;
+ }
+
+ /* If we're in ROUTER networking mode, we just need to request DHCP on the
+ * network interface. Early return with that result. */
+ if (self->priv->mode == MM_UBLOX_NETWORKING_MODE_ROUTER) {
+ mm_bearer_ip_config_set_method (ctx->ip_config, MM_BEARER_IP_METHOD_DHCP);
+ complete_get_ip_config_3gpp (task);
+ return;
+ }
+
+ g_assert_not_reached ();
+}
+
+/*****************************************************************************/
+/* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */
+
+static MMPort *
+dial_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return MM_PORT (g_task_propagate_pointer (G_TASK (res), error));
+}
+
+static void
+cedata_activate_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ MMBroadbandBearerUblox *self)
+{
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (!response) {
+ mm_obj_warn (self, "ECM data connection attempt failed: %s", error->message);
+ mm_base_bearer_report_connection_status (MM_BASE_BEARER (self),
+ MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
+ g_error_free (error);
+ }
+ /* we received a full bearer object reference */
+ g_object_unref (self);
+}
+
+static void
+cgact_activate_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ CommonConnectContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (!response)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref);
+ g_object_unref (task);
+}
+
+static void
+activate_3gpp (GTask *task)
+{
+ MMBroadbandBearerUblox *self;
+ CommonConnectContext *ctx;
+ g_autofree gchar *cmd = NULL;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* SARA-U2xx and LISA-U20x only expose one CDC-ECM interface. Hence,
+ * the fixed 0 as the interface index here. When we see modems with
+ * multiple interfaces, this needs to be revisited. */
+ if (self->priv->profile == MM_UBLOX_USB_PROFILE_ECM && self->priv->cedata == FEATURE_SUPPORTED) {
+ cmd = g_strdup_printf ("+UCEDATA=%u,0", ctx->cid);
+ mm_obj_dbg (self, "establishing ECM data connection for PDP context #%u...", ctx->cid);
+ mm_base_modem_at_command (MM_BASE_MODEM (ctx->modem),
+ cmd,
+ MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT,
+ FALSE,
+ (GAsyncReadyCallback) cedata_activate_ready,
+ g_object_ref (self));
+
+ /* We'll mark the task done here since the modem expects the DHCP
+ discover packet while +UCEDATA runs. If the command fails, we'll
+ mark the bearer disconnected later in the callback. */
+ g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref);
+ g_object_unref (task);
+ return;
+ }
+
+ cmd = g_strdup_printf ("+CGACT=1,%u", ctx->cid);
+ mm_obj_dbg (self, "activating PDP context #%u...", ctx->cid);
+ mm_base_modem_at_command (MM_BASE_MODEM (ctx->modem),
+ cmd,
+ MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT,
+ FALSE,
+ (GAsyncReadyCallback) cgact_activate_ready,
+ task);
+}
+
+static void
+test_cedata_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerUblox *self;
+ const gchar *response;
+
+ self = g_task_get_source_object (task);
+
+ response = mm_base_modem_at_command_finish (modem, res, NULL);
+ if (response)
+ self->priv->cedata = FEATURE_SUPPORTED;
+ else
+ self->priv->cedata = FEATURE_UNSUPPORTED;
+ mm_obj_dbg (self, "+UCEDATA command%s available",
+ (self->priv->cedata == FEATURE_SUPPORTED) ? "" : " not");
+
+ activate_3gpp (task);
+}
+
+static void
+test_cedata (GTask *task)
+{
+ MMBroadbandBearerUblox *self;
+ CommonConnectContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* We don't need to test for +UCEDATA if we're not using CDC-ECM or if we
+ have tested before. Instead, we jump right to the activation. */
+ if (self->priv->profile != MM_UBLOX_USB_PROFILE_ECM || self->priv->cedata != FEATURE_SUPPORT_UNKNOWN) {
+ activate_3gpp (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "checking availability of +UCEDATA command...");
+ mm_base_modem_at_command (MM_BASE_MODEM (ctx->modem),
+ "+UCEDATA=?",
+ 3,
+ TRUE, /* allow_cached */
+ (GAsyncReadyCallback) test_cedata_ready,
+ task);
+}
+
+static void
+uauthreq_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (!response) {
+ CommonConnectContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ /* If authentication required and the +UAUTHREQ failed, abort */
+ if (ctx->auth_required) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+ /* Otherwise, ignore */
+ g_error_free (error);
+ }
+
+ test_cedata (task);
+}
+
+static void
+authenticate_3gpp (GTask *task)
+{
+ MMBroadbandBearerUblox *self;
+ CommonConnectContext *ctx;
+ g_autofree gchar *cmd = NULL;
+ MMBearerAllowedAuth allowed_auth;
+ gint ublox_auth = -1;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ allowed_auth = mm_bearer_properties_get_allowed_auth (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+
+ if (!ctx->auth_required) {
+ mm_obj_dbg (self, "not using authentication");
+ ublox_auth = 0;
+ goto out;
+ }
+
+ if (allowed_auth == MM_BEARER_ALLOWED_AUTH_UNKNOWN || allowed_auth == (MM_BEARER_ALLOWED_AUTH_PAP | MM_BEARER_ALLOWED_AUTH_CHAP)) {
+ mm_obj_dbg (self, "using automatic authentication method");
+ if (self->priv->allowed_auths & MM_UBLOX_BEARER_ALLOWED_AUTH_AUTO)
+ ublox_auth = 3;
+ else if (self->priv->allowed_auths & MM_UBLOX_BEARER_ALLOWED_AUTH_CHAP)
+ ublox_auth = 2;
+ else if (self->priv->allowed_auths & MM_UBLOX_BEARER_ALLOWED_AUTH_PAP)
+ ublox_auth = 1;
+ else if (self->priv->allowed_auths & MM_UBLOX_BEARER_ALLOWED_AUTH_NONE)
+ ublox_auth = 0;
+ } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_PAP) {
+ mm_obj_dbg (self, "using PAP authentication method");
+ ublox_auth = 1;
+ } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_CHAP) {
+ mm_obj_dbg (self, "using CHAP authentication method");
+ ublox_auth = 2;
+ }
+
+out:
+
+ if (ublox_auth < 0) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_bearer_allowed_auth_build_string_from_mask (allowed_auth);
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot use any of the specified authentication methods (%s)", str);
+ g_object_unref (task);
+ return;
+ }
+
+ if (ublox_auth > 0) {
+ const gchar *user;
+ const gchar *password;
+ g_autofree gchar *quoted_user = NULL;
+ g_autofree gchar *quoted_password = NULL;
+
+ user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ password = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+
+ quoted_user = mm_port_serial_at_quote_string (user);
+ quoted_password = mm_port_serial_at_quote_string (password);
+
+ cmd = g_strdup_printf ("+UAUTHREQ=%u,%u,%s,%s",
+ ctx->cid,
+ ublox_auth,
+ quoted_user,
+ quoted_password);
+ } else
+ cmd = g_strdup_printf ("+UAUTHREQ=%u,0,\"\",\"\"", ctx->cid);
+
+ mm_obj_dbg (self, "setting up authentication preferences in PDP context #%u...", ctx->cid);
+ mm_base_modem_at_command (MM_BASE_MODEM (ctx->modem),
+ cmd,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback) uauthreq_ready,
+ task);
+}
+
+static void
+uauthreq_test_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerUblox *self;
+ const gchar *response;
+ GError *error = NULL;
+
+ self = g_task_get_source_object (task);
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (!response)
+ goto out;
+
+ self->priv->allowed_auths = mm_ublox_parse_uauthreq_test (response, self, &error);
+out:
+ if (error) {
+ CommonConnectContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ /* If authentication required and the +UAUTHREQ test failed, abort */
+ if (ctx->auth_required) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+ /* Otherwise, ignore and jump to test_cedata directly as no auth setup
+ * is needed */
+ g_error_free (error);
+ test_cedata (task);
+ return;
+ }
+
+ authenticate_3gpp (task);
+}
+
+static void
+check_supported_authentication_methods (GTask *task)
+{
+ MMBroadbandBearerUblox *self;
+ CommonConnectContext *ctx;
+ const gchar *user;
+ const gchar *password;
+ MMBearerAllowedAuth allowed_auth;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ password = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+ allowed_auth = mm_bearer_properties_get_allowed_auth (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
+
+ /* Flag whether authentication is required. If it isn't, we won't fail
+ * connection attempt if the +UAUTHREQ command fails */
+ ctx->auth_required = (user && password && allowed_auth != MM_BEARER_ALLOWED_AUTH_NONE);
+
+ /* If we already cached the support, not do it again */
+ if (self->priv->allowed_auths != MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN) {
+ authenticate_3gpp (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "checking supported authentication methods...");
+ mm_base_modem_at_command (MM_BASE_MODEM (ctx->modem),
+ "+UAUTHREQ=?",
+ 10,
+ TRUE, /* allow cached */
+ (GAsyncReadyCallback) uauthreq_test_ready,
+ task);
+}
+
+static void
+dial_3gpp (MMBroadbandBearer *self,
+ MMBaseModem *modem,
+ MMPortSerialAt *primary,
+ guint cid,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ if (!(task = common_connect_task_new (MM_BROADBAND_BEARER_UBLOX (self),
+ MM_BROADBAND_MODEM (modem),
+ primary,
+ cid,
+ NULL, /* data, unused */
+ cancellable,
+ callback,
+ user_data)))
+ return;
+
+ check_supported_authentication_methods (task);
+}
+
+/*****************************************************************************/
+/* 3GPP disconnection */
+
+static gboolean
+disconnect_3gpp_finish (MMBroadbandBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+cgact_deactivate_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerUblox *self;
+ const gchar *response;
+ GError *error = NULL;
+
+ self = g_task_get_source_object (task);
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (!response) {
+ /* TOBY-L4 and TOBY-L2 L2 don't allow to disconnect the last LTE bearer
+ * as that would imply de-registration from the LTE network, so we just
+ * assume that it's disconnected from the user point of view.
+ *
+ * TOBY-L4 reports this as a generic unknown Packet Domain Error, which
+ * is a bit unfortunate:
+ * AT+CGACT=0,1
+ * +CME ERROR: 148
+ *
+ * TOBY-L2 reports this as "LAST PDN disconnection not allowed" but using
+ * the legacy numeric value before 3GPP Rel 11 (i.e. 151 instead of 171).
+ * AT+CGACT=0,1
+ * +CME ERROR: 151
+ */
+ if (!g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_GPRS_UNKNOWN) &&
+ !g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_LAST_PDN_DISCONNECTION_NOT_ALLOWED) &&
+ !g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_LAST_PDN_DISCONNECTION_NOT_ALLOWED_LEGACY) &&
+ !g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "ignored error when disconnecting last LTE bearer: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+disconnect_3gpp (MMBroadbandBearer *self,
+ MMBroadbandModem *modem,
+ MMPortSerialAt *primary,
+ MMPortSerialAt *secondary,
+ MMPort *data,
+ guint cid,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ g_autofree gchar *cmd = NULL;
+
+ if (!(task = common_connect_task_new (MM_BROADBAND_BEARER_UBLOX (self),
+ MM_BROADBAND_MODEM (modem),
+ primary,
+ cid,
+ data,
+ NULL,
+ callback,
+ user_data)))
+ return;
+
+ cmd = g_strdup_printf ("+CGACT=0,%u", cid);
+ mm_obj_dbg (self, "deactivating PDP context #%u...", cid);
+ mm_base_modem_at_command (MM_BASE_MODEM (modem),
+ cmd,
+ MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT,
+ FALSE,
+ (GAsyncReadyCallback) cgact_deactivate_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Reload statistics */
+
+typedef struct {
+ guint64 bytes_rx;
+ guint64 bytes_tx;
+} StatsResult;
+
+static gboolean
+reload_stats_finish (MMBaseBearer *self,
+ guint64 *bytes_rx,
+ guint64 *bytes_tx,
+ GAsyncResult *res,
+ GError **error)
+{
+ StatsResult *result;
+
+ result = g_task_propagate_pointer (G_TASK (res), error);
+ if (!result)
+ return FALSE;
+
+ if (bytes_rx)
+ *bytes_rx = result->bytes_rx;
+ if (bytes_tx)
+ *bytes_tx = result->bytes_tx;
+ g_free (result);
+ return TRUE;
+}
+
+static void
+ugcntrd_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerUblox *self;
+ const gchar *response;
+ GError *error = NULL;
+ guint64 tx_bytes = 0;
+ guint64 rx_bytes = 0;
+ gint cid;
+
+ self = MM_BROADBAND_BEARER_UBLOX (g_task_get_source_object (task));
+
+ cid = mm_base_bearer_get_profile_id (MM_BASE_BEARER (self));
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (response) {
+ if (cid == MM_3GPP_PROFILE_ID_UNKNOWN)
+ error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown profile id");
+ else
+ mm_ublox_parse_ugcntrd_response_for_cid (response,
+ cid,
+ &tx_bytes, &rx_bytes,
+ NULL, NULL,
+ &error);
+ }
+
+ if (error) {
+ g_prefix_error (&error, "Couldn't load PDP context %u statistics: ", cid);
+ g_task_return_error (task, error);
+ } else {
+ StatsResult *result;
+
+ result = g_new (StatsResult, 1);
+ result->bytes_rx = rx_bytes;
+ result->bytes_tx = tx_bytes;
+ g_task_return_pointer (task, result, g_free);
+ }
+ g_object_unref (task);
+}
+
+static void
+run_reload_stats (MMBroadbandBearerUblox *self,
+ GTask *task)
+{
+ /* Unsupported? */
+ if (self->priv->statistics == FEATURE_UNSUPPORTED) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Loading statistics isn't supported by this device");
+ g_object_unref (task);
+ return;
+ }
+
+ /* Supported */
+ if (self->priv->statistics == FEATURE_SUPPORTED) {
+ MMBaseModem *modem = NULL;
+
+ g_object_get (MM_BASE_BEARER (self),
+ MM_BASE_BEARER_MODEM, &modem,
+ NULL);
+ mm_base_modem_at_command (MM_BASE_MODEM (modem),
+ "+UGCNTRD",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) ugcntrd_ready,
+ task);
+ g_object_unref (modem);
+ return;
+ }
+
+ g_assert_not_reached ();
+}
+
+static void
+ugcntrd_test_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandBearerUblox *self;
+
+ self = MM_BROADBAND_BEARER_UBLOX (g_task_get_source_object (task));
+
+ if (!mm_base_modem_at_command_finish (modem, res, NULL))
+ self->priv->statistics = FEATURE_UNSUPPORTED;
+ else
+ self->priv->statistics = FEATURE_SUPPORTED;
+
+ run_reload_stats (self, task);
+}
+
+static void
+reload_stats (MMBaseBearer *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (MM_BROADBAND_BEARER_UBLOX (self)->priv->statistics == FEATURE_SUPPORT_UNKNOWN) {
+ MMBaseModem *modem = NULL;
+
+ g_object_get (MM_BASE_BEARER (self),
+ MM_BASE_BEARER_MODEM, &modem,
+ NULL);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (modem),
+ "+UGCNTRD=?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) ugcntrd_test_ready,
+ task);
+ g_object_unref (modem);
+ return;
+ }
+
+ run_reload_stats (MM_BROADBAND_BEARER_UBLOX (self), task);
+}
+
+/*****************************************************************************/
+
+MMBaseBearer *
+mm_broadband_bearer_ublox_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *source;
+ GObject *bearer;
+
+ source = g_async_result_get_source_object (res);
+ bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!bearer)
+ return NULL;
+
+ /* Only export valid bearers */
+ mm_base_bearer_export (MM_BASE_BEARER (bearer));
+
+ return MM_BASE_BEARER (bearer);
+}
+
+void
+mm_broadband_bearer_ublox_new (MMBroadbandModem *modem,
+ MMUbloxUsbProfile profile,
+ MMUbloxNetworkingMode mode,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_assert (mode == MM_UBLOX_NETWORKING_MODE_ROUTER || mode == MM_UBLOX_NETWORKING_MODE_BRIDGE);
+
+ g_async_initable_new_async (
+ MM_TYPE_BROADBAND_BEARER_UBLOX,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_BEARER_MODEM, modem,
+ MM_BASE_BEARER_CONFIG, config,
+ MM_BROADBAND_BEARER_UBLOX_USB_PROFILE, profile,
+ MM_BROADBAND_BEARER_UBLOX_NETWORKING_MODE, mode,
+ NULL);
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MMBroadbandBearerUblox *self = MM_BROADBAND_BEARER_UBLOX (object);
+
+ switch (prop_id) {
+ case PROP_USB_PROFILE:
+ self->priv->profile = g_value_get_enum (value);
+ break;
+ case PROP_NETWORKING_MODE:
+ self->priv->mode = g_value_get_enum (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MMBroadbandBearerUblox *self = MM_BROADBAND_BEARER_UBLOX (object);
+
+ switch (prop_id) {
+ case PROP_USB_PROFILE:
+ g_value_set_enum (value, self->priv->profile);
+ break;
+ case PROP_NETWORKING_MODE:
+ g_value_set_enum (value, self->priv->mode);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+mm_broadband_bearer_ublox_init (MMBroadbandBearerUblox *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_BEARER_UBLOX,
+ MMBroadbandBearerUbloxPrivate);
+
+ /* Defaults */
+ self->priv->profile = MM_UBLOX_USB_PROFILE_UNKNOWN;
+ self->priv->mode = MM_UBLOX_NETWORKING_MODE_UNKNOWN;
+ self->priv->allowed_auths = MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN;
+ self->priv->statistics = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->cedata = FEATURE_SUPPORT_UNKNOWN;
+}
+
+static void
+mm_broadband_bearer_ublox_class_init (MMBroadbandBearerUbloxClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);
+ MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandBearerUbloxPrivate));
+
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+
+ /* Note: the ublox plugin uses the generic AT+CGACT? based check to monitor
+ * the connection status (i.e. default load_connection_status()) */
+ base_bearer_class->reload_stats = reload_stats;
+ base_bearer_class->reload_stats_finish = reload_stats_finish;
+
+ broadband_bearer_class->disconnect_3gpp = disconnect_3gpp;
+ broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish;
+ broadband_bearer_class->dial_3gpp = dial_3gpp;
+ broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish;
+ broadband_bearer_class->get_ip_config_3gpp = get_ip_config_3gpp;
+ broadband_bearer_class->get_ip_config_3gpp_finish = get_ip_config_3gpp_finish;
+
+ properties[PROP_USB_PROFILE] =
+ g_param_spec_enum (MM_BROADBAND_BEARER_UBLOX_USB_PROFILE,
+ "USB profile",
+ "USB profile in use",
+ MM_TYPE_UBLOX_USB_PROFILE,
+ MM_UBLOX_USB_PROFILE_UNKNOWN,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_USB_PROFILE, properties[PROP_USB_PROFILE]);
+
+ properties[PROP_NETWORKING_MODE] =
+ g_param_spec_enum (MM_BROADBAND_BEARER_UBLOX_NETWORKING_MODE,
+ "Networking mode",
+ "Networking mode in use",
+ MM_TYPE_UBLOX_NETWORKING_MODE,
+ MM_UBLOX_NETWORKING_MODE_UNKNOWN,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_NETWORKING_MODE, properties[PROP_NETWORKING_MODE]);
+}
diff --git a/src/plugins/ublox/mm-broadband-bearer-ublox.h b/src/plugins/ublox/mm-broadband-bearer-ublox.h
new file mode 100644
index 00000000..36c26894
--- /dev/null
+++ b/src/plugins/ublox/mm-broadband-bearer-ublox.h
@@ -0,0 +1,63 @@
+/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_BROADBAND_BEARER_UBLOX_H
+#define MM_BROADBAND_BEARER_UBLOX_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-broadband-bearer.h"
+#include "mm-modem-helpers-ublox.h"
+
+#define MM_TYPE_BROADBAND_BEARER_UBLOX (mm_broadband_bearer_ublox_get_type ())
+#define MM_BROADBAND_BEARER_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_UBLOX, MMBroadbandBearerUblox))
+#define MM_BROADBAND_BEARER_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_UBLOX, MMBroadbandBearerUbloxClass))
+#define MM_IS_BROADBAND_BEARER_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_UBLOX))
+#define MM_IS_BROADBAND_BEARER_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_UBLOX))
+#define MM_BROADBAND_BEARER_UBLOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_UBLOX, MMBroadbandBearerUbloxClass))
+
+#define MM_BROADBAND_BEARER_UBLOX_USB_PROFILE "broadband-bearer-ublox-usb-profile"
+#define MM_BROADBAND_BEARER_UBLOX_NETWORKING_MODE "broadband-bearer-ublox-networking-mode"
+
+typedef struct _MMBroadbandBearerUblox MMBroadbandBearerUblox;
+typedef struct _MMBroadbandBearerUbloxClass MMBroadbandBearerUbloxClass;
+typedef struct _MMBroadbandBearerUbloxPrivate MMBroadbandBearerUbloxPrivate;
+
+struct _MMBroadbandBearerUblox {
+ MMBroadbandBearer parent;
+ MMBroadbandBearerUbloxPrivate *priv;
+};
+
+struct _MMBroadbandBearerUbloxClass {
+ MMBroadbandBearerClass parent;
+};
+
+GType mm_broadband_bearer_ublox_get_type (void);
+
+void mm_broadband_bearer_ublox_new (MMBroadbandModem *modem,
+ MMUbloxUsbProfile profile,
+ MMUbloxNetworkingMode mode,
+ MMBearerProperties *config,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseBearer *mm_broadband_bearer_ublox_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_BROADBAND_BEARER_UBLOX_H */
diff --git a/src/plugins/ublox/mm-broadband-modem-ublox.c b/src/plugins/ublox/mm-broadband-modem-ublox.c
new file mode 100644
index 00000000..12be08c3
--- /dev/null
+++ b/src/plugins/ublox/mm-broadband-modem-ublox.c
@@ -0,0 +1,2093 @@
+/* -*- 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) 2016-2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log-object.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-voice.h"
+#include "mm-base-modem-at.h"
+#include "mm-broadband-bearer.h"
+#include "mm-broadband-modem-ublox.h"
+#include "mm-broadband-bearer-ublox.h"
+#include "mm-sim-ublox.h"
+#include "mm-modem-helpers-ublox.h"
+#include "mm-ublox-enums-types.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_voice_init (MMIfaceModemVoice *iface);
+
+static MMIfaceModemVoice *iface_modem_voice_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemUblox, mm_broadband_modem_ublox, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init))
+
+
+struct _MMBroadbandModemUbloxPrivate {
+ /* USB profile in use */
+ MMUbloxUsbProfile profile;
+ gboolean profile_checked;
+ /* Networking mode in use */
+ MMUbloxNetworkingMode mode;
+ gboolean mode_checked;
+
+ /* Flag to specify whether a power operation is ongoing */
+ gboolean power_operation_ongoing;
+
+ /* Mode combination to apply if "any" requested */
+ MMModemMode any_allowed;
+
+ /* AT command configuration */
+ UbloxSupportConfig support_config;
+
+ /* Voice +UCALLSTAT support */
+ GRegex *ucallstat_regex;
+
+ FeatureSupport udtmfd_support;
+ GRegex *udtmfd_regex;
+
+ /* Regex to ignore */
+ GRegex *pbready_regex;
+};
+
+/*****************************************************************************/
+/* Per-model configuration loading */
+
+static void
+preload_support_config (MMBroadbandModemUblox *self)
+{
+ const gchar *model;
+ GError *error = NULL;
+
+ /* Make sure we load only once */
+ if (self->priv->support_config.loaded)
+ return;
+
+ model = mm_iface_modem_get_model (MM_IFACE_MODEM (self));
+
+ if (!mm_ublox_get_support_config (model, &self->priv->support_config, &error)) {
+ mm_obj_warn (self, "loading support configuration failed: %s", error->message);
+ g_error_free (error);
+
+ /* default to NOT SUPPORTED if unknown model */
+ self->priv->support_config.method = SETTINGS_UPDATE_METHOD_UNKNOWN;
+ self->priv->support_config.uact = FEATURE_UNSUPPORTED;
+ self->priv->support_config.ubandsel = FEATURE_UNSUPPORTED;
+ } else
+ mm_obj_dbg (self, "support configuration found for '%s'", model);
+
+ switch (self->priv->support_config.method) {
+ case SETTINGS_UPDATE_METHOD_CFUN:
+ mm_obj_dbg (self, " band update requires low-power mode");
+ break;
+ case SETTINGS_UPDATE_METHOD_COPS:
+ mm_obj_dbg (self, " band update requires explicit unregistration");
+ break;
+ case SETTINGS_UPDATE_METHOD_UNKNOWN:
+ /* not an error, this just means we don't need anything special */
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ switch (self->priv->support_config.uact) {
+ case FEATURE_SUPPORTED:
+ mm_obj_dbg (self, " UACT based band configuration supported");
+ break;
+ case FEATURE_UNSUPPORTED:
+ mm_obj_dbg (self, " UACT based band configuration unsupported");
+ break;
+ case FEATURE_SUPPORT_UNKNOWN:
+ default:
+ g_assert_not_reached();
+ }
+
+ switch (self->priv->support_config.ubandsel) {
+ case FEATURE_SUPPORTED:
+ mm_obj_dbg (self, " UBANDSEL based band configuration supported");
+ break;
+ case FEATURE_UNSUPPORTED:
+ mm_obj_dbg (self, " UBANDSEL based band configuration unsupported");
+ break;
+ case FEATURE_SUPPORT_UNKNOWN:
+ default:
+ g_assert_not_reached();
+ }
+}
+
+/*****************************************************************************/
+
+static gboolean
+acquire_power_operation (MMBroadbandModemUblox *self,
+ GError **error)
+{
+ if (self->priv->power_operation_ongoing) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_RETRY,
+ "An operation which requires power updates is currently in progress");
+ return FALSE;
+ }
+ self->priv->power_operation_ongoing = TRUE;
+ return TRUE;
+}
+
+static void
+release_power_operation (MMBroadbandModemUblox *self)
+{
+ g_assert (self->priv->power_operation_ongoing);
+ self->priv->power_operation_ongoing = FALSE;
+}
+
+/*****************************************************************************/
+/* Load supported bands (Modem interface) */
+
+static GArray *
+load_supported_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return (GArray *) g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_supported_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ GError *error = NULL;
+ GArray *bands = NULL;
+ const gchar *model;
+
+ model = mm_iface_modem_get_model (self);
+ task = g_task_new (self, NULL, callback, user_data);
+
+ bands = mm_ublox_get_supported_bands (model, self, &error);
+ if (!bands)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, bands, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Load current bands (Modem interface) */
+
+static GArray *
+load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return (GArray *) g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+uact_load_current_bands_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+ GArray *out;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ out = mm_ublox_parse_uact_response (response, &error);
+ if (!out) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ g_task_return_pointer (task, out, (GDestroyNotify)g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+ubandsel_load_current_bands_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ const gchar *response;
+ const gchar *model;
+ GArray *out;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ model = mm_iface_modem_get_model (MM_IFACE_MODEM (self));
+ out = mm_ublox_parse_ubandsel_response (response, model, self, &error);
+ if (!out) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ g_task_return_pointer (task, out, (GDestroyNotify)g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_current_bands (MMIfaceModem *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self);
+ GTask *task;
+
+ preload_support_config (self);
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (self->priv->support_config.ubandsel == FEATURE_SUPPORTED) {
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+UBANDSEL?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)ubandsel_load_current_bands_ready,
+ task);
+ return;
+ }
+
+ if (self->priv->support_config.uact == FEATURE_SUPPORTED) {
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+UACT?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)uact_load_current_bands_ready,
+ task);
+ return;
+ }
+
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "loading current bands is unsupported");
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Set allowed modes/bands (Modem interface) */
+
+typedef enum {
+ SET_CURRENT_MODES_BANDS_STEP_FIRST,
+ SET_CURRENT_MODES_BANDS_STEP_ACQUIRE,
+ SET_CURRENT_MODES_BANDS_STEP_CURRENT_POWER,
+ SET_CURRENT_MODES_BANDS_STEP_BEFORE_COMMAND,
+ SET_CURRENT_MODES_BANDS_STEP_COMMAND,
+ SET_CURRENT_MODES_BANDS_STEP_AFTER_COMMAND,
+ SET_CURRENT_MODES_BANDS_STEP_RELEASE,
+ SET_CURRENT_MODES_BANDS_STEP_LAST,
+} SetCurrentModesBandsStep;
+
+typedef struct {
+ SetCurrentModesBandsStep step;
+ gchar *command;
+ MMModemPowerState initial_state;
+ GError *saved_error;
+} SetCurrentModesBandsContext;
+
+static void
+set_current_modes_bands_context_free (SetCurrentModesBandsContext *ctx)
+{
+ g_assert (!ctx->saved_error);
+ g_free (ctx->command);
+ g_slice_free (SetCurrentModesBandsContext, ctx);
+}
+
+static void
+set_current_modes_bands_context_new (GTask *task,
+ gchar *command)
+{
+ SetCurrentModesBandsContext *ctx;
+
+ ctx = g_slice_new0 (SetCurrentModesBandsContext);
+ ctx->command = command;
+ ctx->initial_state = MM_MODEM_POWER_STATE_UNKNOWN;
+ ctx->step = SET_CURRENT_MODES_BANDS_STEP_FIRST;
+ g_task_set_task_data (task, ctx, (GDestroyNotify) set_current_modes_bands_context_free);
+}
+
+static gboolean
+common_set_current_modes_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void set_current_modes_bands_step (GTask *task);
+
+static void
+set_current_modes_bands_reregister_in_network_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetCurrentModesBandsContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ /* propagate the error if none already set */
+ mm_iface_modem_3gpp_reregister_in_network_finish (self, res, ctx->saved_error ? NULL : &ctx->saved_error);
+
+ /* Go to next step (release power operation) regardless of the result */
+ ctx->step++;
+ set_current_modes_bands_step (task);
+}
+
+static void
+set_current_modes_bands_after_command_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetCurrentModesBandsContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ /* propagate the error if none already set */
+ mm_base_modem_at_command_finish (self, res, ctx->saved_error ? NULL : &ctx->saved_error);
+
+ /* Go to next step (release power operation) regardless of the result */
+ ctx->step++;
+ set_current_modes_bands_step (task);
+}
+
+static void
+set_current_modes_bands_command_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetCurrentModesBandsContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &ctx->saved_error))
+ ctx->step = SET_CURRENT_MODES_BANDS_STEP_RELEASE;
+ else
+ ctx->step++;
+
+ set_current_modes_bands_step (task);
+}
+
+static void
+set_current_modes_bands_before_command_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetCurrentModesBandsContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_finish (self, res, &ctx->saved_error))
+ ctx->step = SET_CURRENT_MODES_BANDS_STEP_RELEASE;
+ else
+ ctx->step++;
+
+ set_current_modes_bands_step (task);
+}
+
+static void
+set_current_modes_bands_current_power_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self);
+ SetCurrentModesBandsContext *ctx;
+ const gchar *response;
+
+ ctx = g_task_get_task_data (task);
+
+ g_assert (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_CFUN);
+
+ response = mm_base_modem_at_command_finish (_self, res, &ctx->saved_error);
+ if (!response || !mm_ublox_parse_cfun_response (response, &ctx->initial_state, &ctx->saved_error))
+ ctx->step = SET_CURRENT_MODES_BANDS_STEP_RELEASE;
+ else
+ ctx->step++;
+
+ set_current_modes_bands_step (task);
+}
+
+static void
+set_current_modes_bands_step (GTask *task)
+{
+ MMBroadbandModemUblox *self;
+ SetCurrentModesBandsContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case SET_CURRENT_MODES_BANDS_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+
+ case SET_CURRENT_MODES_BANDS_STEP_ACQUIRE:
+ mm_obj_dbg (self, "acquiring power operation...");
+ if (!acquire_power_operation (self, &ctx->saved_error)) {
+ ctx->step = SET_CURRENT_MODES_BANDS_STEP_LAST;
+ set_current_modes_bands_step (task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case SET_CURRENT_MODES_BANDS_STEP_CURRENT_POWER:
+ /* If using CFUN, we check whether we're already in low-power mode.
+ * And if we are, we just skip triggering low-power mode ourselves.
+ */
+ if (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_CFUN) {
+ mm_obj_dbg (self, "checking current power operation...");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) set_current_modes_bands_current_power_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case SET_CURRENT_MODES_BANDS_STEP_BEFORE_COMMAND:
+ /* If COPS required around the set command, run it unconditionally */
+ if (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_COPS) {
+ mm_obj_dbg (self, "deregistering from the network for configuration change...");
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+COPS=2",
+ 10,
+ FALSE,
+ (GAsyncReadyCallback) set_current_modes_bands_before_command_ready,
+ task);
+ return;
+ }
+ /* If CFUN required, check initial state before triggering low-power mode ourselves */
+ else if (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_CFUN) {
+ /* Do nothing if already in low-power mode */
+ if (ctx->initial_state != MM_MODEM_POWER_STATE_LOW) {
+ mm_obj_dbg (self, "powering down for configuration change...");
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CFUN=4",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) set_current_modes_bands_before_command_ready,
+ task);
+ return;
+ }
+ }
+
+ ctx->step++;
+ /* fall through */
+
+ case SET_CURRENT_MODES_BANDS_STEP_COMMAND:
+ mm_obj_dbg (self, "updating configuration...");
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ ctx->command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) set_current_modes_bands_command_ready,
+ task);
+ return;
+
+ case SET_CURRENT_MODES_BANDS_STEP_AFTER_COMMAND:
+ /* If COPS required around the set command, run it unconditionally */
+ if (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_COPS) {
+ mm_iface_modem_3gpp_reregister_in_network (MM_IFACE_MODEM_3GPP (self),
+ (GAsyncReadyCallback) set_current_modes_bands_reregister_in_network_ready,
+ task);
+ return;
+ }
+ /* If CFUN required, see if we need to recover power */
+ else if (self->priv->support_config.method == SETTINGS_UPDATE_METHOD_CFUN) {
+ /* If we were in low-power mode before the change, do nothing, otherwise,
+ * full power mode back */
+ if (ctx->initial_state != MM_MODEM_POWER_STATE_LOW) {
+ mm_obj_dbg (self, "recovering power state after configuration change...");
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CFUN=1",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) set_current_modes_bands_after_command_ready,
+ task);
+ return;
+ }
+ }
+ ctx->step++;
+ /* fall through */
+
+ case SET_CURRENT_MODES_BANDS_STEP_RELEASE:
+ mm_obj_dbg (self, "releasing power operation...");
+ release_power_operation (self);
+ ctx->step++;
+ /* fall through */
+
+ case SET_CURRENT_MODES_BANDS_STEP_LAST:
+ if (ctx->saved_error) {
+ g_task_return_error (task, ctx->saved_error);
+ ctx->saved_error = NULL;
+ } else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *command;
+ GError *error = NULL;
+
+ preload_support_config (MM_BROADBAND_MODEM_UBLOX (self));
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Handle ANY */
+ if (allowed == MM_MODEM_MODE_ANY)
+ allowed = MM_BROADBAND_MODEM_UBLOX (self)->priv->any_allowed;
+
+ /* Build command */
+ command = mm_ublox_build_urat_set_command (allowed, preferred, &error);
+ if (!command) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ set_current_modes_bands_context_new (task, command);
+ set_current_modes_bands_step (task);
+}
+
+static void
+set_current_bands (MMIfaceModem *_self,
+ GArray *bands_array,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self);
+ GTask *task;
+ GError *error = NULL;
+ gchar *command = NULL;
+ const gchar *model;
+
+ preload_support_config (self);
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ model = mm_iface_modem_get_model (_self);
+
+ /* Build command */
+ if (self->priv->support_config.uact == FEATURE_SUPPORTED)
+ command = mm_ublox_build_uact_set_command (bands_array, &error);
+ else if (self->priv->support_config.ubandsel == FEATURE_SUPPORTED)
+ command = mm_ublox_build_ubandsel_set_command (bands_array, model, &error);
+
+ if (!command) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ set_current_modes_bands_context_new (task, command);
+ set_current_modes_bands_step (task);
+}
+
+/*****************************************************************************/
+/* Load current modes (Modem interface) */
+
+static gboolean
+load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return FALSE;
+
+ return mm_ublox_parse_urat_read_response (response, self, allowed, preferred, error);
+}
+
+static void
+load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+URAT?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *response;
+ GArray *combinations;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return FALSE;
+
+ if (!(combinations = mm_ublox_parse_urat_test_response (response, self, error)))
+ return FALSE;
+
+ if (!(combinations = mm_ublox_filter_supported_modes (mm_iface_modem_get_model (self), combinations, self, error)))
+ return FALSE;
+
+ /* Decide and store which combination to apply when ANY requested */
+ MM_BROADBAND_MODEM_UBLOX (self)->priv->any_allowed = mm_ublox_get_modem_mode_any (combinations);
+
+ /* If 4G supported, explicitly use +CEREG */
+ if (MM_BROADBAND_MODEM_UBLOX (self)->priv->any_allowed & MM_MODEM_MODE_4G)
+ g_object_set (self, MM_IFACE_MODEM_3GPP_EPS_NETWORK_SUPPORTED, TRUE, NULL);
+
+ return combinations;
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+URAT=?",
+ 3,
+ TRUE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Power state loading (Modem interface) */
+
+static MMModemPowerState
+load_power_state_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ MMModemPowerState state = MM_MODEM_POWER_STATE_UNKNOWN;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (response)
+ mm_ublox_parse_cfun_response (response, &state, error);
+ return state;
+}
+
+static void
+load_power_state (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Modem power up/down/off (Modem interface) */
+
+static gboolean
+common_modem_power_operation_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+power_operation_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ release_power_operation (MM_BROADBAND_MODEM_UBLOX (self));
+
+ 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
+common_modem_power_operation (MMBroadbandModemUblox *self,
+ const gchar *command,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ GError *error = NULL;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Fail if there is already an ongoing power management operation */
+ if (!acquire_power_operation (self, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Use AT+CFUN=4 for power down, puts device in airplane mode */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ command,
+ 30,
+ FALSE,
+ (GAsyncReadyCallback) power_operation_ready,
+ task);
+}
+
+static void
+modem_reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_modem_power_operation (MM_BROADBAND_MODEM_UBLOX (self), "+CFUN=16", callback, user_data);
+}
+
+static void
+modem_power_off (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_modem_power_operation (MM_BROADBAND_MODEM_UBLOX (self), "+CPWROFF", callback, user_data);
+}
+
+static void
+modem_power_down (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_modem_power_operation (MM_BROADBAND_MODEM_UBLOX (self), "+CFUN=4", callback, user_data);
+}
+
+static void
+modem_power_up (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_modem_power_operation (MM_BROADBAND_MODEM_UBLOX (self), "+CFUN=1", callback, user_data);
+}
+
+/*****************************************************************************/
+/* Load unlock retries (Modem interface) */
+
+static MMUnlockRetries *
+load_unlock_retries_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *response;
+ MMUnlockRetries *retries;
+ guint pin_attempts = 0;
+ guint pin2_attempts = 0;
+ guint puk_attempts = 0;
+ guint puk2_attempts = 0;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response || !mm_ublox_parse_upincnt_response (response,
+ &pin_attempts, &pin2_attempts,
+ &puk_attempts, &puk2_attempts,
+ error))
+ return NULL;
+
+ retries = mm_unlock_retries_new ();
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin_attempts);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk_attempts);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN2, pin2_attempts);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK2, puk2_attempts);
+
+ return retries;
+}
+
+static void
+load_unlock_retries (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+UPINCNT",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Common enable/disable voice unsolicited events */
+
+typedef enum {
+ VOICE_UNSOLICITED_EVENTS_STEP_FIRST,
+ VOICE_UNSOLICITED_EVENTS_STEP_UCALLSTAT_PRIMARY,
+ VOICE_UNSOLICITED_EVENTS_STEP_UCALLSTAT_SECONDARY,
+ VOICE_UNSOLICITED_EVENTS_STEP_UDTMFD_PRIMARY,
+ VOICE_UNSOLICITED_EVENTS_STEP_UDTMFD_SECONDARY,
+ VOICE_UNSOLICITED_EVENTS_STEP_LAST,
+} VoiceUnsolicitedEventsStep;
+
+typedef struct {
+ gboolean enable;
+ VoiceUnsolicitedEventsStep step;
+ MMPortSerialAt *primary;
+ MMPortSerialAt *secondary;
+ gchar *ucallstat_command;
+ gchar *udtmfd_command;
+} VoiceUnsolicitedEventsContext;
+
+static void
+voice_unsolicited_events_context_free (VoiceUnsolicitedEventsContext *ctx)
+{
+ g_clear_object (&ctx->secondary);
+ g_clear_object (&ctx->primary);
+ g_free (ctx->ucallstat_command);
+ g_free (ctx->udtmfd_command);
+ g_slice_free (VoiceUnsolicitedEventsContext, ctx);
+}
+
+static gboolean
+common_voice_enable_disable_unsolicited_events_finish (MMBroadbandModemUblox *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void voice_unsolicited_events_context_step (GTask *task);
+
+static void
+udtmfd_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ VoiceUnsolicitedEventsContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_full_finish (self, res, &error)) {
+ mm_obj_dbg (self, "couldn't %s +UUDTMFD reporting: '%s'",
+ ctx->enable ? "enable" : "disable",
+ error->message);
+ g_error_free (error);
+ }
+
+ ctx->step++;
+ voice_unsolicited_events_context_step (task);
+}
+
+static void
+ucallstat_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ VoiceUnsolicitedEventsContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_base_modem_at_command_full_finish (self, res, &error)) {
+ mm_obj_dbg (self, "couldn't %s +UCALLSTAT reporting: '%s'",
+ ctx->enable ? "enable" : "disable",
+ error->message);
+ g_error_free (error);
+ }
+
+ ctx->step++;
+ voice_unsolicited_events_context_step (task);
+}
+
+static void
+voice_unsolicited_events_context_step (GTask *task)
+{
+ MMBroadbandModemUblox *self;
+ VoiceUnsolicitedEventsContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case VOICE_UNSOLICITED_EVENTS_STEP_FIRST:
+ ctx->step++;
+ /* fall-through */
+
+ case VOICE_UNSOLICITED_EVENTS_STEP_UCALLSTAT_PRIMARY:
+ if (ctx->primary) {
+ mm_obj_dbg (self, "%s extended call status reporting in primary port...",
+ ctx->enable ? "enabling" : "disabling");
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ ctx->primary,
+ ctx->ucallstat_command,
+ 3,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback)ucallstat_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall-through */
+
+ case VOICE_UNSOLICITED_EVENTS_STEP_UCALLSTAT_SECONDARY:
+ if (ctx->secondary) {
+ mm_obj_dbg (self, "%s extended call status reporting in secondary port...",
+ ctx->enable ? "enabling" : "disabling");
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ ctx->secondary,
+ ctx->ucallstat_command,
+ 3,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback)ucallstat_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall-through */
+
+ case VOICE_UNSOLICITED_EVENTS_STEP_UDTMFD_PRIMARY:
+ if ((self->priv->udtmfd_support == FEATURE_SUPPORTED) && (ctx->primary)) {
+ mm_obj_dbg (self, "%s DTMF detection and reporting in primary port...",
+ ctx->enable ? "enabling" : "disabling");
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ ctx->primary,
+ ctx->udtmfd_command,
+ 3,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback)udtmfd_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall-through */
+
+ case VOICE_UNSOLICITED_EVENTS_STEP_UDTMFD_SECONDARY:
+ if ((self->priv->udtmfd_support == FEATURE_SUPPORTED) && (ctx->secondary)) {
+ mm_obj_dbg (self, "%s DTMF detection and reporting in secondary port...",
+ ctx->enable ? "enabling" : "disabling");
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ ctx->secondary,
+ ctx->udtmfd_command,
+ 3,
+ FALSE,
+ FALSE,
+ NULL,
+ (GAsyncReadyCallback)udtmfd_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall-through */
+
+ case VOICE_UNSOLICITED_EVENTS_STEP_LAST:
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+common_voice_enable_disable_unsolicited_events (MMBroadbandModemUblox *self,
+ gboolean enable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ VoiceUnsolicitedEventsContext *ctx;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_slice_new0 (VoiceUnsolicitedEventsContext);
+ ctx->step = VOICE_UNSOLICITED_EVENTS_STEP_FIRST;
+ ctx->enable = enable;
+ if (enable) {
+ ctx->ucallstat_command = g_strdup ("+UCALLSTAT=1");
+ ctx->udtmfd_command = g_strdup ("+UDTMFD=1,2");
+ } else {
+ ctx->ucallstat_command = g_strdup ("+UCALLSTAT=0");
+ ctx->udtmfd_command = g_strdup ("+UDTMFD=0");
+ }
+ ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+ ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
+ g_task_set_task_data (task, ctx, (GDestroyNotify) voice_unsolicited_events_context_free);
+
+ voice_unsolicited_events_context_step (task);
+}
+
+/*****************************************************************************/
+/* SIM hot swap setup (Modem interface) */
+
+typedef enum {
+ CIEV_SIM_STATUS_UNKNOWN = -1,
+ CIEV_SIM_STATUS_REMOVED,
+ CIEV_SIM_STATUS_INSERTED,
+} MMUbloxSimInsertStatus;
+
+static gboolean
+modem_setup_sim_hot_swap_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+ublox_ciev_unsolicited_handler (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemUblox *self)
+{
+ gint sim_insert_status = CIEV_SIM_STATUS_UNKNOWN;
+
+ if (!mm_get_int_from_match_info (match_info, 1, &sim_insert_status)) {
+ mm_obj_dbg (self, "CIEV: unable to parse sim insert indication");
+ return;
+ }
+
+ mm_obj_msg (self, "CIEV: sim hot swap detected '%d'", sim_insert_status);
+ if (sim_insert_status == CIEV_SIM_STATUS_INSERTED ||
+ sim_insert_status == CIEV_SIM_STATUS_REMOVED) {
+ mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self));
+ } else {
+ mm_obj_warn (self, "(%s) CIEV: unable to determine sim insert status: %d",
+ mm_port_get_device (MM_PORT (port)),
+ sim_insert_status);
+ }
+}
+
+static void
+ublox_setup_ciev_handler (MMIfaceModem *self,
+ guint simind_idx)
+{
+ g_autoptr(GRegex) pattern = NULL;
+ g_autofree gchar *ciev_regex = NULL;
+ MMPortSerialAt *primary_port;
+ MMPortSerialAt *secondary_port;
+
+ primary_port = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ mm_obj_dbg (self, "setting up simind 'CIEV: %d' events handler", simind_idx);
+ ciev_regex = g_strdup_printf ("\\r\\n\\+CIEV: %d,([0-1]{1})\\r\\n", simind_idx);
+ pattern = g_regex_new (ciev_regex,
+ G_REGEX_RAW | G_REGEX_OPTIMIZE,
+ 0, NULL);
+ g_assert (pattern);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ primary_port,
+ pattern,
+ (MMPortSerialAtUnsolicitedMsgFn) ublox_ciev_unsolicited_handler,
+ self,
+ NULL);
+
+ secondary_port = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
+ if (secondary_port)
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ secondary_port,
+ pattern,
+ (MMPortSerialAtUnsolicitedMsgFn) ublox_ciev_unsolicited_handler,
+ self,
+ NULL);
+}
+
+static void
+process_cind_verbosity_response (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+
+ mm_base_modem_at_command_finish (self, res, &error);
+
+ if (error) {
+ mm_obj_warn (self, "CIND: verbose mode is not configured: %s", error->message);
+ g_task_return_error (task, g_steal_pointer (&error));
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "CIND unsolicited response codes processing verbosity configured successfully");
+
+ 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);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+typedef struct {
+ gchar *desc;
+ guint idx;
+ gint min;
+ gint max;
+} CindResponse;
+
+static void
+cind_simind_format_check_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GHashTable *indicators = NULL;
+ GError *error = NULL;
+ const gchar *result;
+ CindResponse *r;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error ||
+ !(indicators = mm_3gpp_parse_cind_test_response (result, &error))) {
+ mm_obj_dbg (self, "+CIND check failed: %s", error->message);
+ g_prefix_error (&error, "CIND check failed: ");
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ r = g_hash_table_lookup (indicators, "simind");
+ if (r) {
+ mm_obj_dbg (self, "simind CIEV indications are supported, indication order number: %d", r->idx);
+ ublox_setup_ciev_handler (MM_IFACE_MODEM (self), r->idx);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CMER=1,0,0,1,0",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) process_cind_verbosity_response,
+ task);
+ } else {
+ mm_obj_dbg (self, "simind CIEV indications are not supported");
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "simind CIEV indications are not supported");
+ g_object_unref (task);
+ }
+ g_hash_table_destroy (indicators);
+}
+
+static void
+modem_setup_sim_hot_swap (MMIfaceModem *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),
+ "+CIND=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback) cind_simind_format_check_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* SIM hot swap cleanup (Modem interface) */
+
+static void
+modem_cleanup_sim_hot_swap (MMIfaceModem *self)
+{
+ mm_broadband_modem_sim_hot_swap_ports_context_reset (MM_BROADBAND_MODEM (self));
+}
+
+/*****************************************************************************/
+/* Enabling unsolicited events (Voice interface) */
+
+static gboolean
+modem_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+voice_enable_unsolicited_events_ready (MMBroadbandModemUblox *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) {
+ mm_obj_warn (self, "Couldn't enable u-blox-specific voice unsolicited events: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ common_voice_enable_disable_unsolicited_events (MM_BROADBAND_MODEM_UBLOX (self),
+ TRUE,
+ (GAsyncReadyCallback) voice_enable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's enable */
+ iface_modem_voice_parent->enable_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Disabling unsolicited events (Voice interface) */
+
+static gboolean
+modem_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+voice_disable_unsolicited_events_ready (MMBroadbandModemUblox *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) {
+ mm_obj_warn (self, "Couldn't disable u-blox-specific voice unsolicited events: %s", error->message);
+ g_error_free (error);
+ }
+
+ iface_modem_voice_parent->disable_unsolicited_events (
+ MM_IFACE_MODEM_VOICE (self),
+ (GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready,
+ task);
+}
+
+static void
+modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ common_voice_enable_disable_unsolicited_events (MM_BROADBAND_MODEM_UBLOX (self),
+ FALSE,
+ (GAsyncReadyCallback) voice_disable_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Common setup/cleanup voice unsolicited events */
+
+static void
+ucallstat_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemUblox *self)
+{
+ static const MMCallState ublox_call_state[] = {
+ [0] = MM_CALL_STATE_ACTIVE,
+ [1] = MM_CALL_STATE_HELD,
+ [2] = MM_CALL_STATE_DIALING, /* Dialing (MOC) */
+ [3] = MM_CALL_STATE_RINGING_OUT, /* Alerting (MOC) */
+ [4] = MM_CALL_STATE_RINGING_IN, /* Incoming (MTC) */
+ [5] = MM_CALL_STATE_WAITING, /* Waiting (MTC) */
+ [6] = MM_CALL_STATE_TERMINATED,
+ [7] = MM_CALL_STATE_ACTIVE, /* Treated same way as ACTIVE */
+ };
+
+ MMCallInfo call_info = { 0 };
+ guint aux;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &aux)) {
+ mm_obj_warn (self, "couldn't parse call index from +UCALLSTAT");
+ return;
+ }
+ call_info.index = aux;
+
+ if (!mm_get_uint_from_match_info (match_info, 2, &aux) ||
+ (aux >= G_N_ELEMENTS (ublox_call_state))) {
+ mm_obj_warn (self, "couldn't parse call state from +UCALLSTAT");
+ return;
+ }
+ call_info.state = ublox_call_state[aux];
+
+ /* guess direction for some of the states */
+ switch (call_info.state) {
+ case MM_CALL_STATE_DIALING:
+ case MM_CALL_STATE_RINGING_OUT:
+ call_info.direction = MM_CALL_DIRECTION_OUTGOING;
+ break;
+ case MM_CALL_STATE_RINGING_IN:
+ case MM_CALL_STATE_WAITING:
+ call_info.direction = MM_CALL_DIRECTION_INCOMING;
+ break;
+ case MM_CALL_STATE_UNKNOWN:
+ case MM_CALL_STATE_ACTIVE:
+ case MM_CALL_STATE_HELD:
+ case MM_CALL_STATE_TERMINATED:
+ default:
+ call_info.direction = MM_CALL_DIRECTION_UNKNOWN;
+ break;
+ }
+
+ mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
+}
+
+static void
+udtmfd_received (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemUblox *self)
+{
+ g_autofree gchar *dtmf = NULL;
+
+ dtmf = g_match_info_fetch (match_info, 1);
+ mm_obj_dbg (self, "received DTMF: %s", dtmf);
+ /* call index unknown */
+ mm_iface_modem_voice_received_dtmf (MM_IFACE_MODEM_VOICE (self), 0, dtmf);
+}
+
+static void
+common_voice_setup_cleanup_unsolicited_events (MMBroadbandModemUblox *self,
+ gboolean enable)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ if (G_UNLIKELY (!self->priv->ucallstat_regex))
+ self->priv->ucallstat_regex = g_regex_new ("\\r\\n\\+UCALLSTAT:\\s*(\\d+),(\\d+)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ if (G_UNLIKELY (!self->priv->udtmfd_regex))
+ self->priv->udtmfd_regex = g_regex_new ("\\r\\n\\+UUDTMFD:\\s*([0-9A-D\\*\\#])\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ self->priv->ucallstat_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)ucallstat_received : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
+ self->priv->udtmfd_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)udtmfd_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+/*****************************************************************************/
+/* Cleanup unsolicited events (Voice interface) */
+
+static gboolean
+modem_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error)) {
+ mm_obj_warn (self, "Couldn't cleanup parent voice unsolicited events: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* our own cleanup first */
+ common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_UBLOX (self), FALSE);
+
+ /* Chain up parent's cleanup */
+ iface_modem_voice_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup unsolicited events (Voice interface) */
+
+static gboolean
+modem_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) {
+ mm_obj_warn (self, "Couldn't setup parent voice unsolicited events: %s", error->message);
+ g_error_free (error);
+ }
+
+ /* our own setup next */
+ common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_UBLOX (self), TRUE);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* chain up parent's setup first */
+ iface_modem_voice_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Create call (Voice interface) */
+
+static MMBaseCall *
+create_call (MMIfaceModemVoice *self,
+ MMCallDirection direction,
+ const gchar *number)
+{
+ return mm_base_call_new (MM_BASE_MODEM (self),
+ direction,
+ number,
+ TRUE, /* skip_incoming_timeout */
+ TRUE, /* supports_dialing_to_ringing */
+ TRUE); /* supports_ringing_to_active */
+}
+
+/*****************************************************************************/
+/* Check if Voice supported (Voice interface) */
+
+static gboolean
+modem_voice_check_support_finish (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+udtmfd_test_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self);
+
+ self->priv->udtmfd_support = (!!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL) ?
+ FEATURE_SUPPORTED : FEATURE_UNSUPPORTED);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_voice_check_support_ready (MMIfaceModemVoice *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_voice_parent->check_support_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* voice is supported, check if +UDTMFD is available */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+UDTMFD=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback) udtmfd_test_ready,
+ task);
+}
+
+static void
+modem_voice_check_support (MMIfaceModemVoice *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* chain up parent's setup first */
+ iface_modem_voice_parent->check_support (
+ self,
+ (GAsyncReadyCallback)parent_voice_check_support_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Create Bearer (Modem interface) */
+
+typedef enum {
+ CREATE_BEARER_STEP_FIRST,
+ CREATE_BEARER_STEP_CHECK_PROFILE,
+ CREATE_BEARER_STEP_CHECK_MODE,
+ CREATE_BEARER_STEP_CREATE_BEARER,
+ CREATE_BEARER_STEP_LAST,
+} CreateBearerStep;
+
+typedef struct {
+ CreateBearerStep step;
+ MMBearerProperties *properties;
+ MMBaseBearer *bearer;
+ gboolean has_net;
+} CreateBearerContext;
+
+static void
+create_bearer_context_free (CreateBearerContext *ctx)
+{
+ g_clear_object (&ctx->bearer);
+ g_object_unref (ctx->properties);
+ g_slice_free (CreateBearerContext, ctx);
+}
+
+static MMBaseBearer *
+modem_create_bearer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return MM_BASE_BEARER (g_task_propagate_pointer (G_TASK (res), error));
+}
+
+static void create_bearer_step (GTask *task);
+
+static void
+broadband_bearer_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemUblox *self;
+ CreateBearerContext *ctx;
+ GError *error = NULL;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ g_assert (!ctx->bearer);
+ ctx->bearer = mm_broadband_bearer_new_finish (res, &error);
+ if (!ctx->bearer) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "new generic broadband bearer created at DBus path '%s'", mm_base_bearer_get_path (ctx->bearer));
+ ctx->step++;
+ create_bearer_step (task);
+}
+
+static void
+broadband_bearer_ublox_new_ready (GObject *source,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemUblox *self;
+ CreateBearerContext *ctx;
+ GError *error = NULL;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ g_assert (!ctx->bearer);
+ ctx->bearer = mm_broadband_bearer_ublox_new_finish (res, &error);
+ if (!ctx->bearer) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "new u-blox broadband bearer created at DBus path '%s'", mm_base_bearer_get_path (ctx->bearer));
+ ctx->step++;
+ create_bearer_step (task);
+}
+
+static void
+mode_check_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self);
+ const gchar *response;
+ GError *error = NULL;
+ CreateBearerContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response) {
+ mm_obj_dbg (self, "couldn't load current networking mode: %s", error->message);
+ g_error_free (error);
+ } else if (!mm_ublox_parse_ubmconf_response (response, &self->priv->mode, &error)) {
+ mm_obj_dbg (self, "couldn't parse current networking mode response '%s': %s", response, error->message);
+ g_error_free (error);
+ } else {
+ g_assert (self->priv->mode != MM_UBLOX_NETWORKING_MODE_UNKNOWN);
+ mm_obj_dbg (self, "networking mode loaded: %s", mm_ublox_networking_mode_get_string (self->priv->mode));
+ }
+
+ /* If checking networking mode isn't supported, we'll fallback to
+ * assume the device is in router mode, which is the mode asking for
+ * less connection setup rules from our side (just request DHCP).
+ */
+ if (self->priv->mode == MM_UBLOX_NETWORKING_MODE_UNKNOWN && ctx->has_net) {
+ mm_obj_dbg (self, "fallback to default networking mode: router");
+ self->priv->mode = MM_UBLOX_NETWORKING_MODE_ROUTER;
+ }
+
+ self->priv->mode_checked = TRUE;
+
+ ctx->step++;
+ create_bearer_step (task);
+}
+
+static void
+profile_check_ready (MMBaseModem *_self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self);
+ const gchar *response;
+ GError *error = NULL;
+ CreateBearerContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_finish (_self, res, &error);
+ if (!response) {
+ mm_obj_dbg (self, "couldn't load current usb profile: %s", error->message);
+ g_error_free (error);
+ } else if (!mm_ublox_parse_uusbconf_response (response, &self->priv->profile, &error)) {
+ mm_obj_dbg (self, "couldn't parse current usb profile response '%s': %s", response, error->message);
+ g_error_free (error);
+ } else {
+ g_assert (self->priv->profile != MM_UBLOX_USB_PROFILE_UNKNOWN);
+ mm_obj_dbg (self, "usb profile loaded: %s", mm_ublox_usb_profile_get_string (self->priv->profile));
+ }
+
+ /* Assume the operation has been performed, even if it may have failed */
+ self->priv->profile_checked = TRUE;
+
+ ctx->step++;
+ create_bearer_step (task);
+}
+
+static void
+create_bearer_step (GTask *task)
+{
+ MMBroadbandModemUblox *self;
+ CreateBearerContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case CREATE_BEARER_STEP_FIRST:
+ ctx->step++;
+ /* fall through */
+
+ case CREATE_BEARER_STEP_CHECK_PROFILE:
+ if (!self->priv->profile_checked) {
+ mm_obj_dbg (self, "checking current USB profile...");
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+UUSBCONF?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) profile_check_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case CREATE_BEARER_STEP_CHECK_MODE:
+ if (!self->priv->mode_checked) {
+ mm_obj_dbg (self, "checking current networking mode...");
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+UBMCONF?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) mode_check_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
+ case CREATE_BEARER_STEP_CREATE_BEARER:
+ /* If we have a net interface, we'll create a u-blox bearer, unless for
+ * any reason we have the back-compatible profile selected. */
+ if ((self->priv->profile != MM_UBLOX_USB_PROFILE_BACK_COMPATIBLE) && ctx->has_net) {
+ /* whenever there is a net port, we should have loaded a valid networking mode */
+ g_assert (self->priv->mode != MM_UBLOX_NETWORKING_MODE_UNKNOWN);
+ mm_obj_dbg (self, "creating u-blox broadband bearer (%s profile, %s mode)...",
+ mm_ublox_usb_profile_get_string (self->priv->profile),
+ mm_ublox_networking_mode_get_string (self->priv->mode));
+ mm_broadband_bearer_ublox_new (
+ MM_BROADBAND_MODEM (self),
+ self->priv->profile,
+ self->priv->mode,
+ ctx->properties,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback) broadband_bearer_ublox_new_ready,
+ task);
+ return;
+ }
+
+ /* If usb profile is back-compatible already, or if there is no NET port
+ * available, create default generic bearer */
+ mm_obj_dbg (self, "creating generic broadband bearer...");
+ mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
+ ctx->properties,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback) broadband_bearer_new_ready,
+ task);
+ return;
+
+ case CREATE_BEARER_STEP_LAST:
+ g_assert (ctx->bearer);
+ g_task_return_pointer (task, g_object_ref (ctx->bearer), g_object_unref);
+ g_object_unref (task);
+ return;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_assert_not_reached ();
+}
+
+static void
+modem_create_bearer (MMIfaceModem *self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ CreateBearerContext *ctx;
+ GTask *task;
+
+ ctx = g_slice_new0 (CreateBearerContext);
+ ctx->step = CREATE_BEARER_STEP_FIRST;
+ ctx->properties = g_object_ref (properties);
+
+ /* Flag whether this modem has exposed a network interface */
+ ctx->has_net = !!mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) create_bearer_context_free);
+ create_bearer_step (task);
+}
+
+/*****************************************************************************/
+/* Create SIM (Modem interface) */
+
+static MMBaseSim *
+modem_create_sim_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return mm_sim_ublox_new_finish (res, error);
+}
+
+static void
+modem_create_sim (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_sim_ublox_new (MM_BASE_MODEM (self),
+ NULL, /* cancellable */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+setup_ports (MMBroadbandModem *_self)
+{
+ MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (_self);
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_ublox_parent_class)->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));
+
+ /* Configure AT ports */
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ g_object_set (ports[i],
+ MM_PORT_SERIAL_SEND_DELAY, (guint64) 0,
+ NULL);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->pbready_regex,
+ NULL, NULL, NULL);
+ }
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemUblox *
+mm_broadband_modem_ublox_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_UBLOX,
+ 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,
+ MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE,
+ /* Generic bearer (TTY) and u-blox bearer (NET) supported */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_ublox_init (MMBroadbandModemUblox *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_MODEM_UBLOX,
+ MMBroadbandModemUbloxPrivate);
+ self->priv->profile = MM_UBLOX_USB_PROFILE_UNKNOWN;
+ self->priv->mode = MM_UBLOX_NETWORKING_MODE_UNKNOWN;
+ self->priv->any_allowed = MM_MODEM_MODE_NONE;
+ self->priv->support_config.loaded = FALSE;
+ self->priv->support_config.method = SETTINGS_UPDATE_METHOD_UNKNOWN;
+ self->priv->support_config.uact = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->support_config.ubandsel = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->udtmfd_support = FEATURE_SUPPORT_UNKNOWN;
+ self->priv->pbready_regex = g_regex_new ("\\r\\n\\+PBREADY\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface->create_sim = modem_create_sim;
+ iface->create_sim_finish = modem_create_sim_finish;
+ iface->create_bearer = modem_create_bearer;
+ iface->create_bearer_finish = modem_create_bearer_finish;
+ iface->load_unlock_retries = load_unlock_retries;
+ iface->load_unlock_retries_finish = load_unlock_retries_finish;
+ iface->load_power_state = load_power_state;
+ iface->load_power_state_finish = load_power_state_finish;
+ iface->modem_power_up = modem_power_up;
+ iface->modem_power_up_finish = common_modem_power_operation_finish;
+ iface->modem_power_down = modem_power_down;
+ iface->modem_power_down_finish = common_modem_power_operation_finish;
+ iface->modem_power_off = modem_power_off;
+ iface->modem_power_off_finish = common_modem_power_operation_finish;
+ iface->reset = modem_reset;
+ iface->reset_finish = common_modem_power_operation_finish;
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = load_current_modes;
+ iface->load_current_modes_finish = load_current_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = common_set_current_modes_bands_finish;
+ iface->load_supported_bands = load_supported_bands;
+ iface->load_supported_bands_finish = load_supported_bands_finish;
+ iface->load_current_bands = load_current_bands;
+ iface->load_current_bands_finish = load_current_bands_finish;
+ iface->set_current_bands = set_current_bands;
+ iface->set_current_bands_finish = common_set_current_modes_bands_finish;
+ iface->setup_sim_hot_swap = modem_setup_sim_hot_swap;
+ iface->setup_sim_hot_swap_finish = modem_setup_sim_hot_swap_finish;
+ iface->cleanup_sim_hot_swap = modem_cleanup_sim_hot_swap;
+}
+
+static void
+iface_modem_voice_init (MMIfaceModemVoice *iface)
+{
+ iface_modem_voice_parent = g_type_interface_peek_parent (iface);
+
+ iface->check_support = modem_voice_check_support;
+ iface->check_support_finish = modem_voice_check_support_finish;
+ iface->setup_unsolicited_events = modem_voice_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_voice_setup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_voice_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_voice_cleanup_unsolicited_events_finish;
+ iface->enable_unsolicited_events = modem_voice_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = modem_voice_enable_unsolicited_events_finish;
+ iface->disable_unsolicited_events = modem_voice_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = modem_voice_disable_unsolicited_events_finish;
+
+ iface->create_call = create_call;
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemUblox *self = MM_BROADBAND_MODEM_UBLOX (object);
+
+ g_regex_unref (self->priv->pbready_regex);
+
+ if (self->priv->ucallstat_regex)
+ g_regex_unref (self->priv->ucallstat_regex);
+ if (self->priv->udtmfd_regex)
+ g_regex_unref (self->priv->udtmfd_regex);
+
+ G_OBJECT_CLASS (mm_broadband_modem_ublox_parent_class)->finalize (object);
+}
+
+static void
+mm_broadband_modem_ublox_class_init (MMBroadbandModemUbloxClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemUbloxPrivate));
+
+ object_class->finalize = finalize;
+
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/ublox/mm-broadband-modem-ublox.h b/src/plugins/ublox/mm-broadband-modem-ublox.h
new file mode 100644
index 00000000..f1c6bbcf
--- /dev/null
+++ b/src/plugins/ublox/mm-broadband-modem-ublox.h
@@ -0,0 +1,49 @@
+/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_BROADBAND_MODEM_UBLOX_H
+#define MM_BROADBAND_MODEM_UBLOX_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_UBLOX (mm_broadband_modem_ublox_get_type ())
+#define MM_BROADBAND_MODEM_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_UBLOX, MMBroadbandModemUblox))
+#define MM_BROADBAND_MODEM_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_UBLOX, MMBroadbandModemUbloxClass))
+#define MM_IS_BROADBAND_MODEM_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_UBLOX))
+#define MM_IS_BROADBAND_MODEM_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_UBLOX))
+#define MM_BROADBAND_MODEM_UBLOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_UBLOX, MMBroadbandModemUbloxClass))
+
+typedef struct _MMBroadbandModemUblox MMBroadbandModemUblox;
+typedef struct _MMBroadbandModemUbloxClass MMBroadbandModemUbloxClass;
+typedef struct _MMBroadbandModemUbloxPrivate MMBroadbandModemUbloxPrivate;
+
+struct _MMBroadbandModemUblox {
+ MMBroadbandModem parent;
+ MMBroadbandModemUbloxPrivate *priv;
+};
+
+struct _MMBroadbandModemUbloxClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_ublox_get_type (void);
+
+MMBroadbandModemUblox *mm_broadband_modem_ublox_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_UBLOX_H */
diff --git a/src/plugins/ublox/mm-modem-helpers-ublox.c b/src/plugins/ublox/mm-modem-helpers-ublox.c
new file mode 100644
index 00000000..84a7cf27
--- /dev/null
+++ b/src/plugins/ublox/mm-modem-helpers-ublox.c
@@ -0,0 +1,2014 @@
+/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <glib.h>
+#include <string.h>
+
+#include "mm-log.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-ublox.h"
+
+/*****************************************************************************/
+/* +UPINCNT response parser */
+
+gboolean
+mm_ublox_parse_upincnt_response (const gchar *response,
+ guint *out_pin_attempts,
+ guint *out_pin2_attempts,
+ guint *out_puk_attempts,
+ guint *out_puk2_attempts,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ guint pin_attempts = 0;
+ guint pin2_attempts = 0;
+ guint puk_attempts = 0;
+ guint puk2_attempts = 0;
+ gboolean success = TRUE;
+
+ g_assert (out_pin_attempts);
+ g_assert (out_pin2_attempts);
+ g_assert (out_puk_attempts);
+ g_assert (out_puk2_attempts);
+
+ /* Response may be e.g.:
+ * +UPINCNT: 3,3,10,10
+ */
+ r = g_regex_new ("\\+UPINCNT: (\\d+),(\\d+),(\\d+),(\\d+)(?:\\r\\n)?", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ if (!mm_get_uint_from_match_info (match_info, 1, &pin_attempts)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Couldn't parse PIN attempts");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 2, &pin2_attempts)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Couldn't parse PIN2 attempts");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 3, &puk_attempts)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Couldn't parse PUK attempts");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 4, &puk2_attempts)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Couldn't parse PUK2 attempts");
+ goto out;
+ }
+ success = TRUE;
+ }
+
+out:
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (!success) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse +UPINCNT response: '%s'", response);
+ return FALSE;
+ }
+
+ *out_pin_attempts = pin_attempts;
+ *out_pin2_attempts = pin2_attempts;
+ *out_puk_attempts = puk_attempts;
+ *out_puk2_attempts = puk2_attempts;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* UUSBCONF? response parser */
+
+gboolean
+mm_ublox_parse_uusbconf_response (const gchar *response,
+ MMUbloxUsbProfile *out_profile,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ MMUbloxUsbProfile profile = MM_UBLOX_USB_PROFILE_UNKNOWN;
+
+ g_assert (out_profile != NULL);
+
+ /* Response may be e.g.:
+ * +UUSBCONF: 3,"RNDIS",,"0x1146"
+ * +UUSBCONF: 2,"ECM",,"0x1143"
+ * +UUSBCONF: 0,"",,"0x1141"
+ *
+ * Note: we don't rely on the PID; assuming future new modules will
+ * have a different PID but they may keep the profile names.
+ */
+ r = g_regex_new ("\\+UUSBCONF: (\\d+),([^,]*),([^,]*),([^,]*)(?:\\r\\n)?", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ g_autofree gchar *profile_name = NULL;
+
+ profile_name = mm_get_string_unquoted_from_match_info (match_info, 2);
+ if (profile_name && profile_name[0]) {
+ if (g_str_equal (profile_name, "RNDIS"))
+ profile = MM_UBLOX_USB_PROFILE_RNDIS;
+ else if (g_str_equal (profile_name, "ECM"))
+ profile = MM_UBLOX_USB_PROFILE_ECM;
+ else
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Unknown USB profile: '%s'", profile_name);
+ } else
+ profile = MM_UBLOX_USB_PROFILE_BACK_COMPATIBLE;
+ }
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (profile == MM_UBLOX_USB_PROFILE_UNKNOWN) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse profile response");
+ return FALSE;
+ }
+
+ *out_profile = profile;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* UBMCONF? response parser */
+
+gboolean
+mm_ublox_parse_ubmconf_response (const gchar *response,
+ MMUbloxNetworkingMode *out_mode,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ MMUbloxNetworkingMode mode = MM_UBLOX_NETWORKING_MODE_UNKNOWN;
+
+ g_assert (out_mode != NULL);
+
+ /* Response may be e.g.:
+ * +UBMCONF: 1
+ * +UBMCONF: 2
+ */
+ r = g_regex_new ("\\+UBMCONF: (\\d+)(?:\\r\\n)?", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ guint mode_id = 0;
+
+ if (mm_get_uint_from_match_info (match_info, 1, &mode_id)) {
+ switch (mode_id) {
+ case 1:
+ mode = MM_UBLOX_NETWORKING_MODE_ROUTER;
+ break;
+ case 2:
+ mode = MM_UBLOX_NETWORKING_MODE_BRIDGE;
+ break;
+ default:
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Unknown mode id: '%u'", mode_id);
+ break;
+ }
+ }
+ }
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (mode == MM_UBLOX_NETWORKING_MODE_UNKNOWN) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse networking mode response");
+ return FALSE;
+ }
+
+ *out_mode = mode;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* UIPADDR=N response parser */
+
+gboolean
+mm_ublox_parse_uipaddr_response (const gchar *response,
+ guint *out_cid,
+ gchar **out_if_name,
+ gchar **out_ipv4_address,
+ gchar **out_ipv4_subnet,
+ gchar **out_ipv6_global_address,
+ gchar **out_ipv6_link_local_address,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ guint cid = 0;
+ g_autofree gchar *if_name = NULL;
+ g_autofree gchar *ipv4_address = NULL;
+ g_autofree gchar *ipv4_subnet = NULL;
+ g_autofree gchar *ipv6_global_address = NULL;
+ g_autofree gchar *ipv6_link_local_address = NULL;
+
+ /* Response may be e.g.:
+ * +UIPADDR: 1,"ccinet0","5.168.120.13","255.255.255.0","",""
+ * +UIPADDR: 2,"ccinet1","","","2001::2:200:FF:FE00:0/64","FE80::200:FF:FE00:0/64"
+ * +UIPADDR: 3,"ccinet2","5.10.100.2","255.255.255.0","2001::1:200:FF:FE00:0/64","FE80::200:FF:FE00:0/64"
+ *
+ * We assume only ONE line is returned; because we request +UIPADDR with a specific N CID.
+ */
+ r = g_regex_new ("\\+UIPADDR: (\\d+),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*)(?:\\r\\n)?", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (!g_match_info_matches (match_info)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Couldn't match +UIPADDR response");
+ return FALSE;
+ }
+
+ if (out_cid && !mm_get_uint_from_match_info (match_info, 1, &cid)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing cid");
+ return FALSE;
+ }
+
+ if (out_if_name && !(if_name = mm_get_string_unquoted_from_match_info (match_info, 2))) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing interface name");
+ return FALSE;
+ }
+
+ /* Remaining strings are optional */
+ ipv4_address = mm_get_string_unquoted_from_match_info (match_info, 3);
+ ipv4_subnet = mm_get_string_unquoted_from_match_info (match_info, 4);
+ ipv6_global_address = mm_get_string_unquoted_from_match_info (match_info, 5);
+ ipv6_link_local_address = mm_get_string_unquoted_from_match_info (match_info, 6);
+
+ if (out_cid)
+ *out_cid = cid;
+ if (out_if_name)
+ *out_if_name = g_steal_pointer (&if_name);
+ if (out_ipv4_address)
+ *out_ipv4_address = g_steal_pointer (&ipv4_address);
+ if (out_ipv4_subnet)
+ *out_ipv4_subnet = g_steal_pointer (&ipv4_subnet);
+ if (out_ipv6_global_address)
+ *out_ipv6_global_address = g_steal_pointer (&ipv6_global_address);
+ if (out_ipv6_link_local_address)
+ *out_ipv6_link_local_address = g_steal_pointer (&ipv6_link_local_address);
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* CFUN? response parser */
+
+gboolean
+mm_ublox_parse_cfun_response (const gchar *response,
+ MMModemPowerState *out_state,
+ GError **error)
+{
+ guint state;
+
+ if (!mm_3gpp_parse_cfun_query_response (response, &state, error))
+ return FALSE;
+
+ switch (state) {
+ case 1:
+ *out_state = MM_MODEM_POWER_STATE_ON;
+ return TRUE;
+ case 0:
+ /* minimum functionality */
+ case 4:
+ /* airplane mode */
+ case 19:
+ /* minimum functionality with SIM deactivated */
+ *out_state = MM_MODEM_POWER_STATE_LOW;
+ return TRUE;
+ default:
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown +CFUN state: %u", state);
+ return FALSE;
+ }
+}
+
+/*****************************************************************************/
+/* URAT=? response parser */
+
+/* Index of the array is the ublox-specific value */
+static const MMModemMode ublox_combinations[] = {
+ ( MM_MODEM_MODE_2G ),
+ ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G ),
+ ( MM_MODEM_MODE_3G ),
+ ( MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_2G | MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_4G ),
+};
+
+GArray *
+mm_ublox_parse_urat_test_response (const gchar *response,
+ gpointer log_object,
+ GError **error)
+{
+ GArray *combinations = NULL;
+ GArray *selected = NULL;
+ GArray *preferred = NULL;
+ gchar **split;
+ guint split_len;
+ GError *inner_error = NULL;
+ guint i;
+
+ /*
+ * E.g.:
+ * AT+URAT=?
+ * +URAT: (0-6),(0,2,3)
+ */
+ response = mm_strip_tag (response, "+URAT:");
+ split = mm_split_string_groups (response);
+ split_len = g_strv_length (split);
+ if (split_len > 2 || split_len < 1) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unexpected number of groups in +URAT=? response: %u", g_strv_length (split));
+ goto out;
+ }
+
+ /* The selected list must have values */
+ selected = mm_parse_uint_list (split[0], &inner_error);
+ if (inner_error)
+ goto out;
+
+ if (!selected) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No selected RAT values given in +URAT=? response");
+ goto out;
+ }
+
+ /* For our purposes, the preferred list may be empty */
+ preferred = mm_parse_uint_list (split[1], &inner_error);
+ if (inner_error)
+ goto out;
+
+ /* Build array of combinations */
+ combinations = g_array_new (FALSE, FALSE, sizeof (MMModemModeCombination));
+
+ for (i = 0; i < selected->len; i++) {
+ guint selected_value;
+ MMModemModeCombination combination;
+ guint j;
+
+ selected_value = g_array_index (selected, guint, i);
+ if (selected_value >= G_N_ELEMENTS (ublox_combinations)) {
+ mm_obj_warn (log_object, "unexpected AcT value: %u", selected_value);
+ continue;
+ }
+
+ /* Combination without any preferred */
+ combination.allowed = ublox_combinations[selected_value];
+ combination.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, combination);
+
+ if (mm_count_bits_set (combination.allowed) == 1)
+ continue;
+
+ if (!preferred)
+ continue;
+
+ for (j = 0; j < preferred->len; j++) {
+ guint preferred_value;
+
+ preferred_value = g_array_index (preferred, guint, j);
+ if (preferred_value >= G_N_ELEMENTS (ublox_combinations)) {
+ mm_obj_warn (log_object, "unexpected AcT preferred value: %u", preferred_value);
+ continue;
+ }
+ combination.preferred = ublox_combinations[preferred_value];
+ if (mm_count_bits_set (combination.preferred) != 1) {
+ mm_obj_warn (log_object, "AcT preferred value should be a single AcT: %u", preferred_value);
+ continue;
+ }
+ if (!(combination.allowed & combination.preferred))
+ continue;
+ g_array_append_val (combinations, combination);
+ }
+ }
+
+ if (combinations->len == 0) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No combinations built from +URAT=? response");
+ goto out;
+ }
+
+out:
+ g_strfreev (split);
+ if (selected)
+ g_array_unref (selected);
+ if (preferred)
+ g_array_unref (preferred);
+
+ if (inner_error) {
+ if (combinations)
+ g_array_unref (combinations);
+ g_propagate_error (error, inner_error);
+ return NULL;
+ }
+
+ return combinations;
+}
+
+typedef struct {
+ const gchar *model;
+ SettingsUpdateMethod method;
+ FeatureSupport uact;
+ FeatureSupport ubandsel;
+ MMModemMode mode;
+ MMModemBand bands_2g[4];
+ MMModemBand bands_3g[6];
+ MMModemBand bands_4g[12];
+} BandConfiguration;
+
+static const BandConfiguration band_configuration[] = {
+ {
+ .model = "SARA-G300",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G,
+ .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS }
+ },
+ {
+ .model = "SARA-G310",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }
+ },
+ {
+ .model = "SARA-G340",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G,
+ .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS }
+ },
+ {
+ .model = "SARA-G350",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }
+ },
+ {
+ .model = "SARA-G450",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }
+ },
+ {
+ .model = "LISA-U200",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 }
+ },
+ {
+ .model = "LISA-U201",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 }
+ },
+ {
+ .model = "LISA-U230",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 }
+ },
+ {
+ .model = "LISA-U260",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 }
+ },
+ {
+ .model = "LISA-U270",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 }
+ },
+ {
+ .model = "SARA-U201",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 }
+ },
+ {
+ .model = "SARA-U260",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 }
+ },
+ {
+ .model = "SARA-U270",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 }
+ },
+ {
+ .model = "SARA-U280",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 }
+ },
+ {
+ .model = "MPCI-L201",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_17 }
+ },
+ {
+ .model = "MPCI-L200",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_4,
+ MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_17 }
+ },
+ {
+ .model = "MPCI-L210",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20 }
+ },
+ {
+ .model = "MPCI-L220",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_19 }
+ },
+ {
+ .model = "MPCI-L280",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_28 }
+ },
+ {
+ .model = "TOBY-L200",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_4,
+ MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_17 }
+ },
+ {
+ .model = "TOBY-L201",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_17 }
+ },
+ {
+ .model = "TOBY-L210",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20 }
+ },
+ {
+ .model = "TOBY-L220",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_19 }
+ },
+ {
+ .model = "TOBY-L280",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_28 }
+ },
+ {
+ .model = "TOBY-L4006",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_SUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_2 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_29 }
+ },
+ {
+ .model = "TOBY-L4106",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_SUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7,
+ MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_38 }
+ },
+ {
+ .model = "TOBY-L4206",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_SUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_9,
+ MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_28 }
+ },
+ {
+ .model = "TOBY-L4906",
+ .method = SETTINGS_UPDATE_METHOD_CFUN,
+ .uact = FEATURE_SUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_39,
+ MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41 }
+ },
+ {
+ .model = "TOBY-R200",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2,
+ MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_12 }
+ },
+ {
+ .model = "TOBY-R202",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_12 }
+ },
+ {
+ .model = "LARA-R202",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_12 }
+ },
+ {
+ .model = "LARA-R203",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_12 }
+ },
+ {
+ .model = "LARA-R204",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_13 }
+ },
+ {
+ .model = "LARA-R211",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_20 }
+ },
+ {
+ .model = "LARA-R280",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .bands_3g = { MM_MODEM_BAND_UTRAN_1 },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_28 }
+ },
+ {
+ .model = "LARA-R3121",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_20 }
+ },
+ {
+ .model = "SARA-N200",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_8 }
+ },
+ {
+ .model = "SARA-N201",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_5 }
+ },
+ {
+ .model = "SARA-N210",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_20 }
+ },
+ {
+ .model = "SARA-N211",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20 }
+ },
+ {
+ .model = "SARA-N280",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_SUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_28 }
+ },
+ {
+ .model = "SARA-R410M-52B",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13 }
+ },
+ {
+ .model = "SARA-R410M-02B",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3,
+ MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_8,
+ MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_19,
+ MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_39 }
+ },
+ {
+ .model = "SARA-R412M-02B",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G,
+ .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS },
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3,
+ MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_8,
+ MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_19,
+ MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_39 }
+ },
+ {
+ .model = "SARA-N410-02B",
+ .method = SETTINGS_UPDATE_METHOD_COPS,
+ .uact = FEATURE_UNSUPPORTED,
+ .ubandsel = FEATURE_UNSUPPORTED,
+ .mode = MM_MODEM_MODE_4G,
+ .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3,
+ MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_8,
+ MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_19,
+ MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_28 }
+ },
+};
+
+gboolean
+mm_ublox_get_support_config (const gchar *model,
+ UbloxSupportConfig *config,
+ GError **error)
+{
+ guint i;
+
+ if (!model) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Support configuration unknown for unknown model");
+ return FALSE;
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) {
+ /* NOTE: matching by prefix! */
+ if (g_str_has_prefix (model, band_configuration[i].model)) {
+ config->loaded = TRUE;
+ config->method = band_configuration[i].method;
+ config->uact = band_configuration[i].uact;
+ config->ubandsel = band_configuration[i].ubandsel;
+ return TRUE;
+ }
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No support configuration found for modem: %s", model);
+ return FALSE;
+}
+
+/*****************************************************************************/
+/* Supported modes loading */
+
+static MMModemMode
+supported_modes_per_model (const gchar *model)
+{
+ MMModemMode mode;
+ guint i;
+
+ mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G;
+
+ if (model) {
+ for (i = 0; i < G_N_ELEMENTS (band_configuration); i++)
+ if (g_str_has_prefix (model, band_configuration[i].model)) {
+ mode = band_configuration[i].mode;
+ return mode;;
+ }
+ }
+
+ return mode;
+}
+
+GArray *
+mm_ublox_filter_supported_modes (const gchar *model,
+ GArray *combinations,
+ gpointer logger,
+ GError **error)
+{
+ MMModemModeCombination mode;
+ GArray *all;
+ GArray *filtered;
+
+ /* Model not specified? */
+ if (!model)
+ return combinations;
+
+ /* AT+URAT=? lies; we need an extra per-device filtering, thanks u-blox.
+ * Don't know all PIDs for all devices, so model string based filtering... */
+
+ mode.allowed = supported_modes_per_model (model);
+ mode.preferred = MM_MODEM_MODE_NONE;
+
+ /* Nothing filtered? */
+ if (mode.allowed == supported_modes_per_model (NULL))
+ return combinations;
+
+ all = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
+ g_array_append_val (all, mode);
+ filtered = mm_filter_supported_modes (all, combinations, logger);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ /* Error if nothing left */
+ if (filtered->len == 0) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No valid mode combinations built after filtering (model %s)", model);
+ g_array_unref (filtered);
+ return NULL;
+ }
+
+ return filtered;
+}
+
+/*****************************************************************************/
+/* Supported bands loading */
+
+GArray *
+mm_ublox_get_supported_bands (const gchar *model,
+ gpointer log_object,
+ GError **error)
+{
+ MMModemMode mode;
+ GArray *bands;
+ guint i, j;
+
+ mode = supported_modes_per_model (model);
+ bands = g_array_new (FALSE, FALSE, sizeof (MMModemBand));
+
+ for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) {
+ if (g_str_has_prefix (model, band_configuration[i].model)) {
+ mm_obj_dbg (log_object, "known supported bands found for model: %s", band_configuration[i].model);
+ break;
+ }
+ }
+
+ if (i == G_N_ELEMENTS (band_configuration)) {
+ mm_obj_warn (log_object, "unknown model name given when looking for supported bands: %s", model);
+ return NULL;
+ }
+
+ mode = band_configuration[i].mode;
+
+ if (mode & MM_MODEM_MODE_2G) {
+ for (j = 0; j < G_N_ELEMENTS (band_configuration[i].bands_2g) && band_configuration[i].bands_2g[j]; j++) {
+ bands = g_array_append_val (bands, band_configuration[i].bands_2g[j]);
+ }
+ }
+
+ if (mode & MM_MODEM_MODE_3G) {
+ for (j = 0; j < G_N_ELEMENTS (band_configuration[i].bands_3g) && band_configuration[i].bands_3g[j]; j++) {
+ bands = g_array_append_val (bands, band_configuration[i].bands_3g[j]);
+ }
+ }
+
+ if (mode & MM_MODEM_MODE_4G) {
+ for (j = 0; j < G_N_ELEMENTS (band_configuration[i].bands_4g) && band_configuration[i].bands_4g[j]; j++) {
+ bands = g_array_append_val (bands, band_configuration[i].bands_4g[j]);
+ }
+ }
+
+ if (bands->len == 0) {
+ g_array_unref (bands);
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No valid supported bands loaded");
+ return NULL;
+ }
+
+ return bands;
+}
+
+typedef struct {
+ guint num;
+ MMModemBand band[4];
+} NumToBand;
+
+/* 2G GSM Band Frequencies */
+static const NumToBand num_bands_2g [] = {
+ { .num = 850, .band = { MM_MODEM_BAND_G850 } },
+ { .num = 900, .band = { MM_MODEM_BAND_EGSM } },
+ { .num = 1900, .band = { MM_MODEM_BAND_PCS } },
+ { .num = 1800, .band = { MM_MODEM_BAND_DCS } },
+};
+
+/* 3G UMTS Band Frequencies */
+static const NumToBand num_bands_3g [] = {
+ { .num = 800, .band = { MM_MODEM_BAND_UTRAN_6 } },
+ { .num = 850, .band = { MM_MODEM_BAND_UTRAN_5 } },
+ { .num = 900, .band = { MM_MODEM_BAND_UTRAN_8 } },
+ { .num = 1700, .band = { MM_MODEM_BAND_UTRAN_4 } },
+ { .num = 1900, .band = { MM_MODEM_BAND_UTRAN_2 } },
+ { .num = 2100, .band = { MM_MODEM_BAND_UTRAN_1 } },
+};
+
+/* 4G LTE Band Frequencies */
+static const NumToBand num_bands_4g [] = {
+ { .num = 700, .band = { MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_17 } },
+ { .num = 800, .band = { MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_27 } },
+ { .num = 850, .band = { MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_26 } },
+ { .num = 900, .band = { MM_MODEM_BAND_EUTRAN_8 } },
+ { .num = 1700, .band = { MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_10 } },
+ { .num = 1800, .band = { MM_MODEM_BAND_EUTRAN_3 } },
+ { .num = 1900, .band = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_39 } },
+ { .num = 2100, .band = { MM_MODEM_BAND_EUTRAN_1 } },
+ { .num = 2300, .band = { MM_MODEM_BAND_EUTRAN_40 } },
+ { .num = 2500, .band = { MM_MODEM_BAND_EUTRAN_41 } },
+ { .num = 2600, .band = { MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_38 } },
+};
+/*****************************************************************************/
+/* +UBANDSEL? response parser */
+
+static MMModemBand
+num_to_band_2g (guint num)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (num_bands_2g); i++) {
+ if (num == num_bands_2g[i].num)
+ return num_bands_2g[i].band[0];
+ }
+ return MM_MODEM_BAND_UNKNOWN;
+}
+
+static MMModemBand
+num_to_band_3g (guint num)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (num_bands_3g); i++) {
+ if (num == num_bands_3g[i].num)
+ return num_bands_3g[i].band[0];
+ }
+ return MM_MODEM_BAND_UNKNOWN;
+}
+
+static guint
+band_to_num (MMModemBand band)
+{
+ guint i, j;
+
+ /* Search 2G list */
+ for (i = 0; i < G_N_ELEMENTS (num_bands_2g); i++) {
+ for (j = 0; j < G_N_ELEMENTS (num_bands_2g[i].band) && num_bands_2g[i].band[j]; j++) {
+ if (band == num_bands_2g[i].band[j])
+ return num_bands_2g[i].num;
+ }
+ }
+
+ /* Search 3G list */
+ for (i = 0; i < G_N_ELEMENTS (num_bands_3g); i++) {
+ for (j = 0; j < G_N_ELEMENTS (num_bands_3g[i].band) && num_bands_3g[i].band[j]; j++) {
+ if (band == num_bands_3g[i].band[j])
+ return num_bands_3g[i].num;
+ }
+ }
+
+ /* Search 4G list */
+ for (i = 0; i < G_N_ELEMENTS (num_bands_4g); i++) {
+ for (j = 0; j < G_N_ELEMENTS (num_bands_4g[i].band) && num_bands_4g[i].band[j]; j++) {
+ if (band == num_bands_4g[i].band[j])
+ return num_bands_4g[i].num;
+ }
+ }
+
+ /* Should never happen */
+ return 0;
+}
+
+static void
+append_bands (GArray *bands,
+ guint ubandsel_value,
+ MMModemMode mode,
+ const gchar *model,
+ gpointer log_object)
+{
+ guint i, j, k, x;
+ MMModemBand band;
+
+ /* Find Modem Model Index in band_configuration */
+ for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) {
+ if (g_str_has_prefix (model, band_configuration[i].model)) {
+ mm_obj_dbg (log_object, "known bands found for model: %s", band_configuration[i].model);
+ break;
+ }
+ }
+
+ if (i == G_N_ELEMENTS (band_configuration)) {
+ mm_obj_warn (log_object, "unknown model name given when looking for bands: %s", model);
+ return;
+ }
+
+ if (mode & MM_MODEM_MODE_2G) {
+ band = num_to_band_2g (ubandsel_value);
+ if (band != MM_MODEM_BAND_UNKNOWN) {
+ for (x = 0; x < G_N_ELEMENTS (band_configuration[i].bands_2g); x++) {
+ if (band_configuration[i].bands_2g[x] == band) {
+ g_array_append_val (bands, band);
+ break;
+ }
+ }
+ }
+ }
+
+ if (mode & MM_MODEM_MODE_3G) {
+ band = num_to_band_3g (ubandsel_value);
+ if (band != MM_MODEM_BAND_UNKNOWN) {
+ for (x = 0; x < G_N_ELEMENTS (band_configuration[i].bands_3g); x++) {
+ if (band_configuration[i].bands_3g[x] == band) {
+ g_array_append_val (bands, band);
+ break;
+ }
+ }
+ }
+ }
+
+ /* Note: The weird code segment below is to separate out specific LTE bands since
+ * UBANDSEL? reports back the frequency of the band and not the band itself.
+ */
+
+ if (mode & MM_MODEM_MODE_4G) {
+ for (j = 0; j < G_N_ELEMENTS (num_bands_4g); j++) {
+ if (ubandsel_value == num_bands_4g[j].num) {
+ for (k = 0; k < G_N_ELEMENTS (num_bands_4g[j].band); k++) {
+ band = num_bands_4g[j].band[k];
+ if (band != MM_MODEM_BAND_UNKNOWN) {
+ for (x = 0; x < G_N_ELEMENTS (band_configuration[i].bands_4g); x++) {
+ if (band_configuration[i].bands_4g[x] == band) {
+ g_array_append_val (bands, band);
+ break;
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+GArray *
+mm_ublox_parse_ubandsel_response (const gchar *response,
+ const gchar *model,
+ gpointer log_object,
+ GError **error)
+{
+ GArray *array_values = NULL;
+ GArray *array = NULL;
+ gchar *dupstr = NULL;
+ GError *inner_error = NULL;
+ guint i;
+ MMModemMode mode;
+
+ if (!g_str_has_prefix (response, "+UBANDSEL")) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse +UBANDSEL response: '%s'", response);
+ goto out;
+ }
+
+ /* Response may be e.g.:
+ * +UBANDSEL: 850,900,1800,1900
+ */
+ dupstr = g_strchomp (g_strdup (mm_strip_tag (response, "+UBANDSEL:")));
+
+ array_values = mm_parse_uint_list (dupstr, &inner_error);
+ if (!array_values)
+ goto out;
+
+ /* Convert list of ubandsel numbers to MMModemBand values */
+ mode = supported_modes_per_model (model);
+ array = g_array_new (FALSE, FALSE, sizeof (MMModemBand));
+ for (i = 0; i < array_values->len; i++)
+ append_bands (array, g_array_index (array_values, guint, i), mode, model, log_object);
+
+ if (!array->len) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No known band selection values matched in +UBANDSEL response: '%s'", response);
+ goto out;
+ }
+
+out:
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ g_clear_pointer (&array, g_array_unref);
+ }
+ g_clear_pointer (&array_values, g_array_unref);
+ g_free (dupstr);
+ return array;
+}
+
+/*****************************************************************************/
+/* UBANDSEL=X command builder */
+
+static gint
+ubandsel_num_cmp (const guint *a, const guint *b)
+{
+ return (*a - *b);
+}
+
+gchar *
+mm_ublox_build_ubandsel_set_command (GArray *bands,
+ const gchar *model,
+ GError **error)
+{
+ GString *command = NULL;
+ GArray *ubandsel_nums;
+ guint num;
+ guint i, j, k;
+
+ if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY)
+ return g_strdup ("+UBANDSEL=0");
+
+ for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) {
+ if (g_str_has_prefix (model, band_configuration[i].model))
+ break;
+ }
+
+ if (i == G_N_ELEMENTS (band_configuration)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown modem model %s", model);
+ return NULL;
+ }
+
+ ubandsel_nums = g_array_sized_new (FALSE, FALSE, sizeof (guint), bands->len);
+
+ for (j = 0; j < bands->len; j++) {
+ MMModemBand band;
+ gboolean found = FALSE;
+
+ band = g_array_index (bands, MMModemBand, j);
+
+ /* Check to see if band is supported by the model */
+ for (k = 0; !found && k < G_N_ELEMENTS (band_configuration[i].bands_2g) && band_configuration[i].bands_2g[k]; k++) {
+ if (band == band_configuration[i].bands_2g[k])
+ found = TRUE;
+ }
+
+ for (k = 0; !found && k < G_N_ELEMENTS (band_configuration[i].bands_3g) && band_configuration[i].bands_3g[k]; k++) {
+ if (band == band_configuration[i].bands_3g[k])
+ found = TRUE;
+ }
+
+ for (k = 0; !found && k < G_N_ELEMENTS (band_configuration[i].bands_4g) && band_configuration[i].bands_4g[k]; k++) {
+ if (band == band_configuration[i].bands_4g[k])
+ found = TRUE;
+ }
+
+ if (found) {
+ num = band_to_num (band);
+ g_assert (num != 0);
+ g_array_append_val (ubandsel_nums, num);
+ }
+ }
+
+ if (ubandsel_nums->len == 0) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Given band combination is unsupported");
+ g_array_unref (ubandsel_nums);
+ return NULL;
+ }
+
+ if (ubandsel_nums->len > 1)
+ g_array_sort (ubandsel_nums, (GCompareFunc) ubandsel_num_cmp);
+
+ /* Build command */
+ command = g_string_new ("+UBANDSEL=");
+ for (i = 0; i < ubandsel_nums->len; i++)
+ g_string_append_printf (command, "%s%u", i == 0 ? "" : ",", g_array_index (ubandsel_nums, guint, i));
+
+ return g_string_free (command, FALSE);
+}
+
+/*****************************************************************************/
+/* Get mode to apply when ANY */
+
+MMModemMode
+mm_ublox_get_modem_mode_any (const GArray *combinations)
+{
+ guint i;
+ MMModemMode any = MM_MODEM_MODE_NONE;
+ guint any_bits_set = 0;
+
+ for (i = 0; i < combinations->len; i++) {
+ MMModemModeCombination *combination;
+ guint bits_set;
+
+ combination = &g_array_index (combinations, MMModemModeCombination, i);
+ if (combination->preferred != MM_MODEM_MODE_NONE)
+ continue;
+ bits_set = mm_count_bits_set (combination->allowed);
+ if (bits_set > any_bits_set) {
+ any_bits_set = bits_set;
+ any = combination->allowed;
+ }
+ }
+
+ /* If combinations were processed via mm_ublox_parse_urat_test_response(),
+ * we're sure that there will be at least one combination with preferred
+ * 'none', so there must be some valid combination as result */
+ g_assert (any != MM_MODEM_MODE_NONE);
+ return any;
+}
+
+/*****************************************************************************/
+/* UACT common config */
+
+typedef struct {
+ guint num;
+ MMModemBand band;
+} UactBandConfig;
+
+static const UactBandConfig uact_band_config[] = {
+ /* GSM bands */
+ { .num = 900, .band = MM_MODEM_BAND_EGSM },
+ { .num = 1800, .band = MM_MODEM_BAND_DCS },
+ { .num = 1900, .band = MM_MODEM_BAND_PCS },
+ { .num = 850, .band = MM_MODEM_BAND_G850 },
+ { .num = 450, .band = MM_MODEM_BAND_G450 },
+ { .num = 480, .band = MM_MODEM_BAND_G480 },
+ { .num = 750, .band = MM_MODEM_BAND_G750 },
+ { .num = 380, .band = MM_MODEM_BAND_G380 },
+ { .num = 410, .band = MM_MODEM_BAND_G410 },
+ { .num = 710, .band = MM_MODEM_BAND_G710 },
+ { .num = 810, .band = MM_MODEM_BAND_G810 },
+ /* UMTS bands */
+ { .num = 1, .band = MM_MODEM_BAND_UTRAN_1 },
+ { .num = 2, .band = MM_MODEM_BAND_UTRAN_2 },
+ { .num = 3, .band = MM_MODEM_BAND_UTRAN_3 },
+ { .num = 4, .band = MM_MODEM_BAND_UTRAN_4 },
+ { .num = 5, .band = MM_MODEM_BAND_UTRAN_5 },
+ { .num = 6, .band = MM_MODEM_BAND_UTRAN_6 },
+ { .num = 7, .band = MM_MODEM_BAND_UTRAN_7 },
+ { .num = 8, .band = MM_MODEM_BAND_UTRAN_8 },
+ { .num = 9, .band = MM_MODEM_BAND_UTRAN_9 },
+ { .num = 10, .band = MM_MODEM_BAND_UTRAN_10 },
+ { .num = 11, .band = MM_MODEM_BAND_UTRAN_11 },
+ { .num = 12, .band = MM_MODEM_BAND_UTRAN_12 },
+ { .num = 13, .band = MM_MODEM_BAND_UTRAN_13 },
+ { .num = 14, .band = MM_MODEM_BAND_UTRAN_14 },
+ { .num = 19, .band = MM_MODEM_BAND_UTRAN_19 },
+ { .num = 20, .band = MM_MODEM_BAND_UTRAN_20 },
+ { .num = 21, .band = MM_MODEM_BAND_UTRAN_21 },
+ { .num = 22, .band = MM_MODEM_BAND_UTRAN_22 },
+ { .num = 25, .band = MM_MODEM_BAND_UTRAN_25 },
+ /* LTE bands */
+ { .num = 101, .band = MM_MODEM_BAND_EUTRAN_1 },
+ { .num = 102, .band = MM_MODEM_BAND_EUTRAN_2 },
+ { .num = 103, .band = MM_MODEM_BAND_EUTRAN_3 },
+ { .num = 104, .band = MM_MODEM_BAND_EUTRAN_4 },
+ { .num = 105, .band = MM_MODEM_BAND_EUTRAN_5 },
+ { .num = 106, .band = MM_MODEM_BAND_EUTRAN_6 },
+ { .num = 107, .band = MM_MODEM_BAND_EUTRAN_7 },
+ { .num = 108, .band = MM_MODEM_BAND_EUTRAN_8 },
+ { .num = 109, .band = MM_MODEM_BAND_EUTRAN_9 },
+ { .num = 110, .band = MM_MODEM_BAND_EUTRAN_10 },
+ { .num = 111, .band = MM_MODEM_BAND_EUTRAN_11 },
+ { .num = 112, .band = MM_MODEM_BAND_EUTRAN_12 },
+ { .num = 113, .band = MM_MODEM_BAND_EUTRAN_13 },
+ { .num = 114, .band = MM_MODEM_BAND_EUTRAN_14 },
+ { .num = 117, .band = MM_MODEM_BAND_EUTRAN_17 },
+ { .num = 118, .band = MM_MODEM_BAND_EUTRAN_18 },
+ { .num = 119, .band = MM_MODEM_BAND_EUTRAN_19 },
+ { .num = 120, .band = MM_MODEM_BAND_EUTRAN_20 },
+ { .num = 121, .band = MM_MODEM_BAND_EUTRAN_21 },
+ { .num = 122, .band = MM_MODEM_BAND_EUTRAN_22 },
+ { .num = 123, .band = MM_MODEM_BAND_EUTRAN_23 },
+ { .num = 124, .band = MM_MODEM_BAND_EUTRAN_24 },
+ { .num = 125, .band = MM_MODEM_BAND_EUTRAN_25 },
+ { .num = 126, .band = MM_MODEM_BAND_EUTRAN_26 },
+ { .num = 127, .band = MM_MODEM_BAND_EUTRAN_27 },
+ { .num = 128, .band = MM_MODEM_BAND_EUTRAN_28 },
+ { .num = 129, .band = MM_MODEM_BAND_EUTRAN_29 },
+ { .num = 130, .band = MM_MODEM_BAND_EUTRAN_30 },
+ { .num = 131, .band = MM_MODEM_BAND_EUTRAN_31 },
+ { .num = 132, .band = MM_MODEM_BAND_EUTRAN_32 },
+ { .num = 133, .band = MM_MODEM_BAND_EUTRAN_33 },
+ { .num = 134, .band = MM_MODEM_BAND_EUTRAN_34 },
+ { .num = 135, .band = MM_MODEM_BAND_EUTRAN_35 },
+ { .num = 136, .band = MM_MODEM_BAND_EUTRAN_36 },
+ { .num = 137, .band = MM_MODEM_BAND_EUTRAN_37 },
+ { .num = 138, .band = MM_MODEM_BAND_EUTRAN_38 },
+ { .num = 139, .band = MM_MODEM_BAND_EUTRAN_39 },
+ { .num = 140, .band = MM_MODEM_BAND_EUTRAN_40 },
+ { .num = 141, .band = MM_MODEM_BAND_EUTRAN_41 },
+ { .num = 142, .band = MM_MODEM_BAND_EUTRAN_42 },
+ { .num = 143, .band = MM_MODEM_BAND_EUTRAN_43 },
+ { .num = 144, .band = MM_MODEM_BAND_EUTRAN_44 },
+ { .num = 145, .band = MM_MODEM_BAND_EUTRAN_45 },
+ { .num = 146, .band = MM_MODEM_BAND_EUTRAN_46 },
+ { .num = 147, .band = MM_MODEM_BAND_EUTRAN_47 },
+ { .num = 148, .band = MM_MODEM_BAND_EUTRAN_48 },
+};
+
+static MMModemBand
+uact_num_to_band (guint num)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (uact_band_config); i++) {
+ if (num == uact_band_config[i].num)
+ return uact_band_config[i].band;
+ }
+ return MM_MODEM_BAND_UNKNOWN;
+}
+
+static guint
+uact_band_to_num (MMModemBand band)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (uact_band_config); i++) {
+ if (band == uact_band_config[i].band)
+ return uact_band_config[i].num;
+ }
+ return 0;
+}
+
+/*****************************************************************************/
+/* UACT? response parser */
+
+static GArray *
+uact_num_array_to_band_array (GArray *nums)
+{
+ GArray *bands = NULL;
+ guint i;
+
+ if (!nums)
+ return NULL;
+
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), nums->len);
+ for (i = 0; i < nums->len; i++) {
+ MMModemBand band;
+
+ band = uact_num_to_band (g_array_index (nums, guint, i));
+ g_array_append_val (bands, band);
+ }
+
+ return bands;
+}
+
+GArray *
+mm_ublox_parse_uact_response (const gchar *response,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ GArray *nums = NULL;
+ GArray *bands = NULL;
+
+ /*
+ * AT+UACT?
+ * +UACT: ,,,900,1800,1,8,101,103,107,108,120,138
+ */
+ r = g_regex_new ("\\+UACT: ([^,]*),([^,]*),([^,]*),(.*)(?:\\r\\n)?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ g_autofree gchar *bandstr = NULL;
+
+ bandstr = mm_get_string_unquoted_from_match_info (match_info, 4);
+ nums = mm_parse_uint_list (bandstr, &inner_error);
+ }
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return NULL;
+ }
+
+ /* Convert to MMModemBand values */
+ if (nums) {
+ bands = uact_num_array_to_band_array (nums);
+ g_array_unref (nums);
+ }
+
+ if (!bands)
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No known band selection values matched in +UACT response: '%s'", response);
+
+ return bands;
+}
+
+/*****************************************************************************/
+/* UACT=? response parser */
+
+static GArray *
+parse_bands_from_string (const gchar *str,
+ const gchar *group,
+ gpointer log_object)
+{
+ GArray *bands = NULL;
+ GError *inner_error = NULL;
+ GArray *nums;
+
+ nums = mm_parse_uint_list (str, &inner_error);
+ if (nums) {
+ gchar *tmpstr;
+
+ bands = uact_num_array_to_band_array (nums);
+ tmpstr = mm_common_build_bands_string ((MMModemBand *)(gpointer)(bands->data), bands->len);
+ mm_obj_dbg (log_object, "modem reports support for %s bands: %s", group, tmpstr);
+ g_free (tmpstr);
+
+ g_array_unref (nums);
+ } else if (inner_error) {
+ mm_obj_warn (log_object, "couldn't parse list of supported %s bands: %s", group, inner_error->message);
+ g_clear_error (&inner_error);
+ }
+
+ return bands;
+}
+
+gboolean
+mm_ublox_parse_uact_test (const gchar *response,
+ gpointer log_object,
+ GArray **bands2g_out,
+ GArray **bands3g_out,
+ GArray **bands4g_out,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_auto(GStrv) split = NULL;
+ GError *inner_error = NULL;
+ const gchar *bands2g_str = NULL;
+ const gchar *bands3g_str = NULL;
+ const gchar *bands4g_str = NULL;
+ GArray *bands2g = NULL;
+ GArray *bands3g = NULL;
+ GArray *bands4g = NULL;
+
+ g_assert (bands2g_out && bands3g_out && bands4g_out);
+
+ /*
+ * AT+UACT=?
+ * +UACT: ,,,(900,1800),(1,8),(101,103,107,108,120),(138)
+ */
+ r = g_regex_new ("\\+UACT: ([^,]*),([^,]*),([^,]*),(.*)(?:\\r\\n)?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (inner_error)
+ goto out;
+
+ if (g_match_info_matches (match_info)) {
+ g_autofree gchar *aux = NULL;
+ guint n_groups;
+
+ aux = mm_get_string_unquoted_from_match_info (match_info, 4);
+ split = mm_split_string_groups (aux);
+ n_groups = g_strv_length (split);
+ if (n_groups >= 1)
+ bands2g_str = split[0];
+ if (n_groups >= 2)
+ bands3g_str = split[1];
+ if (n_groups >= 3)
+ bands4g_str = split[2];
+ }
+
+ if (!bands2g_str && !bands3g_str && !bands4g_str) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "frequency groups not found: %s", response);
+ goto out;
+ }
+
+ bands2g = parse_bands_from_string (bands2g_str, "2G", log_object);
+ bands3g = parse_bands_from_string (bands3g_str, "3G", log_object);
+ bands4g = parse_bands_from_string (bands4g_str, "4G", log_object);
+
+ if (!bands2g->len && !bands3g->len && !bands4g->len) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "no supported frequencies reported: %s", response);
+ goto out;
+ }
+
+ /* success */
+
+out:
+ if (inner_error) {
+ if (bands2g)
+ g_array_unref (bands2g);
+ if (bands3g)
+ g_array_unref (bands3g);
+ if (bands4g)
+ g_array_unref (bands4g);
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ *bands2g_out = bands2g;
+ *bands3g_out = bands3g;
+ *bands4g_out = bands4g;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* UACT=X command builder */
+
+gchar *
+mm_ublox_build_uact_set_command (GArray *bands,
+ GError **error)
+{
+ GString *command;
+
+ /* Build command */
+ command = g_string_new ("+UACT=,,,");
+
+ if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY)
+ g_string_append (command, "0");
+ else {
+ guint i;
+
+ for (i = 0; i < bands->len; i++) {
+ MMModemBand band;
+ guint num;
+
+ band = g_array_index (bands, MMModemBand, i);
+ num = uact_band_to_num (band);
+ if (!num) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Band unsupported by this plugin: %s", mm_modem_band_get_string (band));
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+
+ g_string_append_printf (command, "%s%u", i == 0 ? "" : ",", num);
+ }
+ }
+
+ return g_string_free (command, FALSE);
+}
+
+/*****************************************************************************/
+/* URAT? response parser */
+
+gboolean
+mm_ublox_parse_urat_read_response (const gchar *response,
+ gpointer log_object,
+ MMModemMode *out_allowed,
+ MMModemMode *out_preferred,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ MMModemMode allowed = MM_MODEM_MODE_NONE;
+ MMModemMode preferred = MM_MODEM_MODE_NONE;
+ g_autofree gchar *allowed_str = NULL;
+ g_autofree gchar *preferred_str = NULL;
+
+ g_assert (out_allowed != NULL && out_preferred != NULL);
+
+ /* Response may be e.g.:
+ * +URAT: 1,2
+ * +URAT: 1
+ */
+ r = g_regex_new ("\\+URAT: (\\d+)(?:,(\\d+))?(?:\\r\\n)?", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ guint value = 0;
+
+ /* Selected item is mandatory */
+ if (!mm_get_uint_from_match_info (match_info, 1, &value)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't read AcT selected value");
+ goto out;
+ }
+ if (value >= G_N_ELEMENTS (ublox_combinations)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unexpected AcT selected value: %u", value);
+ goto out;
+ }
+ allowed = ublox_combinations[value];
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ mm_obj_dbg (log_object, "current allowed modes retrieved: %s", allowed_str);
+
+ /* Preferred item is optional */
+ if (mm_get_uint_from_match_info (match_info, 2, &value)) {
+ if (value >= G_N_ELEMENTS (ublox_combinations)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unexpected AcT preferred value: %u", value);
+ goto out;
+ }
+ preferred = ublox_combinations[value];
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ mm_obj_dbg (log_object, "current preferred modes retrieved: %s", preferred_str);
+ if (mm_count_bits_set (preferred) != 1) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "AcT preferred value should be a single AcT: %s", preferred_str);
+ goto out;
+ }
+ if (!(allowed & preferred)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "AcT preferred value (%s) not a subset of the allowed value (%s)",
+ preferred_str, allowed_str);
+ goto out;
+ }
+ }
+ }
+
+out:
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (allowed == MM_MODEM_MODE_NONE) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse +URAT response: %s", response);
+ return FALSE;
+ }
+
+ *out_allowed = allowed;
+ *out_preferred = preferred;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* URAT=X command builder */
+
+static gboolean
+append_rat_value (GString *str,
+ MMModemMode mode,
+ GError **error)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (ublox_combinations); i++) {
+ if (ublox_combinations[i] == mode) {
+ g_string_append_printf (str, "%u", i);
+ return TRUE;
+ }
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No AcT value matches requested mode");
+ return FALSE;
+}
+
+gchar *
+mm_ublox_build_urat_set_command (MMModemMode allowed,
+ MMModemMode preferred,
+ GError **error)
+{
+ GString *command;
+
+ command = g_string_new ("+URAT=");
+ if (!append_rat_value (command, allowed, error)) {
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+
+ if (preferred != MM_MODEM_MODE_NONE) {
+ g_string_append (command, ",");
+ if (!append_rat_value (command, preferred, error)) {
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+ }
+
+ return g_string_free (command, FALSE);
+}
+
+/*****************************************************************************/
+/* +UAUTHREQ=? test parser */
+
+MMUbloxBearerAllowedAuth
+mm_ublox_parse_uauthreq_test (const char *response,
+ gpointer log_object,
+ GError **error)
+{
+ MMUbloxBearerAllowedAuth mask = MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN;
+ GError *inner_error = NULL;
+ GArray *allowed_auths = NULL;
+ gchar **split;
+ guint split_len;
+
+ /*
+ * Response may be like:
+ * AT+UAUTHREQ=?
+ * +UAUTHREQ: (1-4),(0-2),,
+ */
+ response = mm_strip_tag (response, "+UAUTHREQ:");
+ split = mm_split_string_groups (response);
+ split_len = g_strv_length (split);
+ if (split_len < 2) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unexpected number of groups in +UAUTHREQ=? response: %u", g_strv_length (split));
+ goto out;
+ }
+
+ allowed_auths = mm_parse_uint_list (split[1], &inner_error);
+ if (inner_error)
+ goto out;
+
+ if (allowed_auths) {
+ guint i;
+
+ for (i = 0; i < allowed_auths->len; i++) {
+ guint val;
+
+ val = g_array_index (allowed_auths, guint, i);
+ switch (val) {
+ case 0:
+ mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_NONE;
+ break;
+ case 1:
+ mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_PAP;
+ break;
+ case 2:
+ mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_CHAP;
+ break;
+ case 3:
+ mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_AUTO;
+ break;
+ default:
+ mm_obj_warn (log_object, "unexpected +UAUTHREQ value: %u", val);
+ break;
+ }
+ }
+ }
+
+ if (!mask) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No supported authentication methods in +UAUTHREQ=? response");
+ goto out;
+ }
+
+out:
+ g_strfreev (split);
+
+ if (allowed_auths)
+ g_array_unref (allowed_auths);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN;
+ }
+
+ return mask;
+}
+
+/*****************************************************************************/
+/* +UGCNTRD response parser */
+
+gboolean
+mm_ublox_parse_ugcntrd_response_for_cid (const gchar *response,
+ guint in_cid,
+ guint64 *out_session_tx_bytes,
+ guint64 *out_session_rx_bytes,
+ guint64 *out_total_tx_bytes,
+ guint64 *out_total_rx_bytes,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ guint64 session_tx_bytes = 0;
+ guint64 session_rx_bytes = 0;
+ guint64 total_tx_bytes = 0;
+ guint64 total_rx_bytes = 0;
+ gboolean matched = FALSE;
+
+ /* Response may be e.g.:
+ * +UGCNTRD: 31,2704,1819,2724,1839
+ * We assume only ONE line is returned.
+ */
+ r = g_regex_new ("\\+UGCNTRD:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL);
+ g_assert (r != NULL);
+
+ /* Report invalid CID given */
+ if (!in_cid) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Invalid CID given");
+ goto out;
+ }
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ while (!inner_error && g_match_info_matches (match_info)) {
+ guint cid = 0;
+
+ /* Matched CID? */
+ if (!mm_get_uint_from_match_info (match_info, 1, &cid) || cid != in_cid) {
+ g_match_info_next (match_info, &inner_error);
+ continue;
+ }
+
+ if (out_session_tx_bytes && !mm_get_u64_from_match_info (match_info, 2, &session_tx_bytes)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing session TX bytes");
+ goto out;
+ }
+
+ if (out_session_rx_bytes && !mm_get_u64_from_match_info (match_info, 3, &session_rx_bytes)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing session RX bytes");
+ goto out;
+ }
+
+ if (out_total_tx_bytes && !mm_get_u64_from_match_info (match_info, 4, &total_tx_bytes)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing total TX bytes");
+ goto out;
+ }
+
+ if (out_total_rx_bytes && !mm_get_u64_from_match_info (match_info, 5, &total_rx_bytes)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing total RX bytes");
+ goto out;
+ }
+
+ matched = TRUE;
+ break;
+ }
+
+ if (!matched) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "No statistics found for CID %u", in_cid);
+ goto out;
+ }
+
+out:
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (out_session_tx_bytes)
+ *out_session_tx_bytes = session_tx_bytes;
+ if (out_session_rx_bytes)
+ *out_session_rx_bytes = session_rx_bytes;
+ if (out_total_tx_bytes)
+ *out_total_tx_bytes = total_tx_bytes;
+ if (out_total_rx_bytes)
+ *out_total_rx_bytes = total_rx_bytes;
+ return TRUE;
+}
diff --git a/src/plugins/ublox/mm-modem-helpers-ublox.h b/src/plugins/ublox/mm-modem-helpers-ublox.h
new file mode 100644
index 00000000..06bba003
--- /dev/null
+++ b/src/plugins/ublox/mm-modem-helpers-ublox.h
@@ -0,0 +1,213 @@
+/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_MODEM_HELPERS_UBLOX_H
+#define MM_MODEM_HELPERS_UBLOX_H
+
+#include <glib.h>
+#include <ModemManager.h>
+
+/*****************************************************************************/
+/* AT Commands Support */
+
+typedef enum {
+ FEATURE_SUPPORT_UNKNOWN,
+ FEATURE_SUPPORTED,
+ FEATURE_UNSUPPORTED,
+} FeatureSupport;
+
+typedef enum {
+ SETTINGS_UPDATE_METHOD_UNKNOWN,
+ SETTINGS_UPDATE_METHOD_CFUN,
+ SETTINGS_UPDATE_METHOD_COPS,
+} SettingsUpdateMethod;
+
+typedef struct UbloxSupportConfig {
+ gboolean loaded;
+ SettingsUpdateMethod method;
+ FeatureSupport uact;
+ FeatureSupport ubandsel;
+} UbloxSupportConfig;
+
+/*****************************************************************************/
+/* +UPINCNT response parser */
+
+gboolean mm_ublox_parse_upincnt_response (const gchar *response,
+ guint *out_pin_attempts,
+ guint *out_pin2_attempts,
+ guint *out_puk_attempts,
+ guint *out_puk2_attempts,
+ GError **error);
+
+/*****************************************************************************/
+/* UUSBCONF? response parser */
+
+typedef enum { /*< underscore_name=mm_ublox_usb_profile >*/
+ MM_UBLOX_USB_PROFILE_UNKNOWN,
+ MM_UBLOX_USB_PROFILE_RNDIS,
+ MM_UBLOX_USB_PROFILE_ECM,
+ MM_UBLOX_USB_PROFILE_BACK_COMPATIBLE,
+} MMUbloxUsbProfile;
+
+gboolean mm_ublox_parse_uusbconf_response (const gchar *response,
+ MMUbloxUsbProfile *out_profile,
+ GError **error);
+
+/*****************************************************************************/
+/* UBMCONF? response parser */
+
+typedef enum { /*< underscore_name=mm_ublox_networking_mode >*/
+ MM_UBLOX_NETWORKING_MODE_UNKNOWN,
+ MM_UBLOX_NETWORKING_MODE_ROUTER,
+ MM_UBLOX_NETWORKING_MODE_BRIDGE,
+} MMUbloxNetworkingMode;
+
+gboolean mm_ublox_parse_ubmconf_response (const gchar *response,
+ MMUbloxNetworkingMode *out_mode,
+ GError **error);
+
+/*****************************************************************************/
+/* UIPADDR=N response parser */
+
+gboolean mm_ublox_parse_uipaddr_response (const gchar *response,
+ guint *out_cid,
+ gchar **out_if_name,
+ gchar **out_ipv4_address,
+ gchar **out_ipv4_subnet,
+ gchar **out_ipv6_global_address,
+ gchar **out_ipv6_link_local_address,
+ GError **error);
+
+/*****************************************************************************/
+/* CFUN? response parser */
+
+gboolean mm_ublox_parse_cfun_response (const gchar *response,
+ MMModemPowerState *out_state,
+ GError **error);
+
+/*****************************************************************************/
+/* URAT=? response parser */
+
+GArray *mm_ublox_parse_urat_test_response (const gchar *response,
+ gpointer log_object,
+ GError **error);
+
+/*****************************************************************************/
+/* Model-based config support loading */
+
+gboolean mm_ublox_get_support_config (const gchar *model,
+ UbloxSupportConfig *config,
+ GError **error);
+
+/*****************************************************************************/
+/* Model-based supported modes filtering */
+
+GArray *mm_ublox_filter_supported_modes (const gchar *model,
+ GArray *combinations,
+ gpointer logger,
+ GError **error);
+
+/*****************************************************************************/
+/* Model-based supported bands loading */
+
+GArray *mm_ublox_get_supported_bands (const gchar *model,
+ gpointer log_object,
+ GError **error);
+
+/*****************************************************************************/
+/* UBANDSEL? response parser */
+
+GArray *mm_ublox_parse_ubandsel_response (const gchar *response,
+ const gchar *model,
+ gpointer log_object,
+ GError **error);
+
+/*****************************************************************************/
+/* UBANDSEL=X command builder */
+
+gchar *mm_ublox_build_ubandsel_set_command (GArray *bands,
+ const gchar *model,
+ GError **error);
+
+/*****************************************************************************/
+/* UACT? response parser */
+
+GArray *mm_ublox_parse_uact_response (const gchar *response,
+ GError **error);
+
+/*****************************************************************************/
+/* UACT=? test parser */
+
+gboolean mm_ublox_parse_uact_test (const gchar *response,
+ gpointer log_object,
+ GArray **bands_2g,
+ GArray **bands_3g,
+ GArray **bands_4g,
+ GError **error);
+
+/*****************************************************************************/
+/* UACT=X command builder */
+
+gchar *mm_ublox_build_uact_set_command (GArray *bands,
+ GError **error);
+
+/*****************************************************************************/
+/* Get mode to apply when ANY */
+
+MMModemMode mm_ublox_get_modem_mode_any (const GArray *combinations);
+
+/*****************************************************************************/
+/* URAT? response parser */
+
+gboolean mm_ublox_parse_urat_read_response (const gchar *response,
+ gpointer log_object,
+ MMModemMode *out_allowed,
+ MMModemMode *out_preferred,
+ GError **error);
+
+/*****************************************************************************/
+/* URAT=X command builder */
+
+gchar *mm_ublox_build_urat_set_command (MMModemMode allowed,
+ MMModemMode preferred,
+ GError **error);
+
+/*****************************************************************************/
+/* +UAUTHREQ=? test parser */
+
+typedef enum { /*< underscore_name=mm_ublox_bearer_allowed_auth >*/
+ MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN = 0,
+ MM_UBLOX_BEARER_ALLOWED_AUTH_NONE = 1 << 0,
+ MM_UBLOX_BEARER_ALLOWED_AUTH_PAP = 1 << 1,
+ MM_UBLOX_BEARER_ALLOWED_AUTH_CHAP = 1 << 2,
+ MM_UBLOX_BEARER_ALLOWED_AUTH_AUTO = 1 << 3,
+} MMUbloxBearerAllowedAuth;
+
+MMUbloxBearerAllowedAuth mm_ublox_parse_uauthreq_test (const char *response,
+ gpointer log_object,
+ GError **error);
+
+/*****************************************************************************/
+/* +UGCNTRD response parser */
+
+gboolean mm_ublox_parse_ugcntrd_response_for_cid (const gchar *response,
+ guint in_cid,
+ guint64 *session_tx_bytes,
+ guint64 *session_rx_bytes,
+ guint64 *total_tx_bytes,
+ guint64 *total_rx_bytes,
+ GError **error);
+
+#endif /* MM_MODEM_HELPERS_UBLOX_H */
diff --git a/src/plugins/ublox/mm-plugin-ublox.c b/src/plugins/ublox/mm-plugin-ublox.c
new file mode 100644
index 00000000..db6d94d3
--- /dev/null
+++ b/src/plugins/ublox/mm-plugin-ublox.c
@@ -0,0 +1,265 @@
+/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-serial-parsers.h"
+#include "mm-broadband-modem-ublox.h"
+#include "mm-plugin-ublox.h"
+
+G_DEFINE_TYPE (MMPluginUblox, mm_plugin_ublox, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+
+static MMBaseModem *
+create_modem (MMPlugin *self,
+ const gchar *sysfs_path,
+ const gchar **drivers,
+ guint16 vendor,
+ guint16 product,
+ guint16 subsystem_vendor,
+ GList *probes,
+ GError **error)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_ublox_new (sysfs_path,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+/* Custom init context */
+
+typedef struct {
+ MMPortSerialAt *port;
+ GRegex *ready_regex;
+ guint timeout_id;
+ gint wait_timeout_secs;
+} CustomInitContext;
+
+static void
+custom_init_context_free (CustomInitContext *ctx)
+{
+ g_assert (!ctx->timeout_id);
+ g_regex_unref (ctx->ready_regex);
+ g_object_unref (ctx->port);
+ g_slice_free (CustomInitContext, ctx);
+}
+
+static gboolean
+ublox_custom_init_finish (MMPortProbe *probe,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static gboolean
+ready_timeout (GTask *task)
+{
+ CustomInitContext *ctx;
+ MMPortProbe *probe;
+
+ ctx = g_task_get_task_data (task);
+ probe = g_task_get_source_object (task);
+
+ ctx->timeout_id = 0;
+
+ mm_port_serial_at_add_unsolicited_msg_handler (ctx->port, ctx->ready_regex,
+ NULL, NULL, NULL);
+
+ mm_obj_dbg (probe, "timed out waiting for READY unsolicited message");
+
+ /* not an error really, we didn't probe anything yet, that's all */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+ready_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ GTask *task)
+{
+ CustomInitContext *ctx;
+ MMPortProbe *probe;
+
+ ctx = g_task_get_task_data (task);
+ probe = g_task_get_source_object (task);
+
+ g_source_remove (ctx->timeout_id);
+ ctx->timeout_id = 0;
+
+ mm_obj_dbg (probe, "received READY: port is AT");
+
+ /* Flag as an AT port right away */
+ mm_port_probe_set_result_at (probe, TRUE);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+wait_for_ready (GTask *task)
+{
+ CustomInitContext *ctx;
+ MMPortProbe *probe;
+
+ ctx = g_task_get_task_data (task);
+ probe = g_task_get_source_object (task);
+
+ mm_obj_dbg (probe, "waiting for READY unsolicited message...");
+
+ /* Configure a regex on the TTY, so that we stop the custom init
+ * as soon as +READY URC is received */
+ mm_port_serial_at_add_unsolicited_msg_handler (ctx->port,
+ ctx->ready_regex,
+ (MMPortSerialAtUnsolicitedMsgFn) ready_received,
+ task,
+ NULL);
+
+ mm_obj_dbg (probe, "waiting %d seconds for init timeout", ctx->wait_timeout_secs);
+
+ /* Otherwise, let the custom init timeout in some seconds. */
+ ctx->timeout_id = g_timeout_add_seconds (ctx->wait_timeout_secs, (GSourceFunc) ready_timeout, task);
+}
+
+static void
+quick_at_ready (MMPortSerialAt *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMPortProbe *probe;
+ g_autoptr(GError) error = NULL;
+
+ probe = g_task_get_source_object (task);
+
+ mm_port_serial_at_command_finish (port, res, &error);
+ if (error) {
+ /* On a timeout error, wait for READY URC */
+ if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) {
+ wait_for_ready (task);
+ return;
+ }
+ /* On an unknown error, make it fatal */
+ if (!mm_serial_parser_v1_is_known_error (error)) {
+ mm_obj_warn (probe, "custom port initialization logic failed: %s", error->message);
+ goto out;
+ }
+ }
+
+ mm_obj_dbg (probe, "port is AT");
+ mm_port_probe_set_result_at (probe, TRUE);
+
+out:
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+ublox_custom_init (MMPortProbe *probe,
+ MMPortSerialAt *port,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ CustomInitContext *ctx;
+ gint wait_timeout_secs;
+
+ task = g_task_new (probe, cancellable, callback, user_data);
+
+ /* If no explicit READY_DELAY configured, we don't need a custom init procedure */
+ wait_timeout_secs = mm_kernel_device_get_property_as_int (mm_port_probe_peek_port (probe), "ID_MM_UBLOX_PORT_READY_DELAY");
+ if (wait_timeout_secs <= 0) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_slice_new0 (CustomInitContext);
+ ctx->wait_timeout_secs = wait_timeout_secs;
+ ctx->port = g_object_ref (port);
+ ctx->ready_regex = g_regex_new ("\\r\\n\\+AT:\\s*READY\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) custom_init_context_free);
+
+ /* If the device hasn't been plugged in right away, we assume it was already
+ * running for some time. We validate the assumption with a quick AT probe,
+ * and if it times out, we run the explicit READY wait from scratch (e.g.
+ * to cope with the case where MM starts after the TTY has been exposed but
+ * where the device was also just reseted) */
+ if (!mm_device_get_hotplugged (mm_port_probe_peek_device (probe))) {
+ mm_port_serial_at_command (ctx->port,
+ "AT",
+ 1,
+ FALSE, /* raw */
+ FALSE, /* allow_cached */
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)quick_at_ready,
+ task);
+ return;
+ }
+
+ /* Device hotplugged and has a defined ready delay, wait for READY URC */
+ wait_for_ready (task);
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", NULL };
+ static const guint16 vendor_ids[] = { 0x1546, 0 };
+ static const gchar *vendor_strings[] = { "u-blox", NULL };
+ static const MMAsyncMethod custom_init = {
+ .async = G_CALLBACK (ublox_custom_init),
+ .finish = G_CALLBACK (ublox_custom_init_finish),
+ };
+
+ return MM_PLUGIN (g_object_new (MM_TYPE_PLUGIN_UBLOX,
+ 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_SEND_DELAY, (guint64) 0,
+ MM_PLUGIN_CUSTOM_INIT, &custom_init,
+ NULL));
+}
+
+static void
+mm_plugin_ublox_init (MMPluginUblox *self)
+{
+}
+
+static void
+mm_plugin_ublox_class_init (MMPluginUbloxClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/ublox/mm-plugin-ublox.h b/src/plugins/ublox/mm-plugin-ublox.h
new file mode 100644
index 00000000..adfc6247
--- /dev/null
+++ b/src/plugins/ublox/mm-plugin-ublox.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) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_PLUGIN_UBLOX_H
+#define MM_PLUGIN_UBLOX_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_UBLOX (mm_plugin_ublox_get_type ())
+#define MM_PLUGIN_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_UBLOX, MMPluginUblox))
+#define MM_PLUGIN_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_UBLOX, MMPluginUbloxClass))
+#define MM_IS_PLUGIN_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_UBLOX))
+#define MM_IS_PLUGIN_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_UBLOX))
+#define MM_PLUGIN_UBLOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_UBLOX, MMPluginUbloxClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginUblox;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginUbloxClass;
+
+GType mm_plugin_ublox_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_UBLOX_H */
diff --git a/src/plugins/ublox/mm-sim-ublox.c b/src/plugins/ublox/mm-sim-ublox.c
new file mode 100644
index 00000000..5850767e
--- /dev/null
+++ b/src/plugins/ublox/mm-sim-ublox.c
@@ -0,0 +1,163 @@
+/* -*- 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>
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+#include "mm-log.h"
+#include "mm-modem-helpers.h"
+#include "mm-base-modem-at.h"
+
+#include "mm-sim-ublox.h"
+
+G_DEFINE_TYPE (MMSimUblox, mm_sim_ublox, MM_TYPE_BASE_SIM)
+
+/*****************************************************************************/
+/* SIM identifier loading */
+
+static gchar *
+load_sim_identifier_finish (MMBaseSim *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_load_sim_identifier_ready (MMSimUblox *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ gchar *simid;
+
+ simid = MM_BASE_SIM_CLASS (mm_sim_ublox_parent_class)->load_sim_identifier_finish (MM_BASE_SIM (self), res, &error);
+ if (simid)
+ g_task_return_pointer (task, simid, g_free);
+ else
+ g_task_return_error (task, error);
+ g_object_unref (task);
+}
+
+static void
+ccid_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBaseSim *self;
+ const gchar *response;
+ gchar *parsed;
+
+ response = mm_base_modem_at_command_finish (modem, res, NULL);
+ if (!response)
+ goto error;
+
+ response = mm_strip_tag (response, "+CCID:");
+ if (!response)
+ goto error;
+
+ parsed = mm_3gpp_parse_iccid (response, NULL);
+ if (parsed) {
+ g_task_return_pointer (task, parsed, g_free);
+ g_object_unref (task);
+ return;
+ }
+
+error:
+ /* Chain up to parent method to for devices that don't support +CCID properly */
+ self = g_task_get_source_object (task);
+ MM_BASE_SIM_CLASS (mm_sim_ublox_parent_class)->load_sim_identifier (self,
+ (GAsyncReadyCallback) parent_load_sim_identifier_ready,
+ task);
+}
+
+static void
+load_sim_identifier (MMBaseSim *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBaseModem *modem = NULL;
+
+ g_object_get (self,
+ MM_BASE_SIM_MODEM, &modem,
+ NULL);
+ mm_base_modem_at_command (
+ modem,
+ "+CCID",
+ 5,
+ FALSE,
+ (GAsyncReadyCallback)ccid_ready,
+ g_task_new (self, NULL, callback, user_data));
+ g_object_unref (modem);
+}
+
+/*****************************************************************************/
+
+MMBaseSim *
+mm_sim_ublox_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *source;
+ GObject *sim;
+
+ source = g_async_result_get_source_object (res);
+ sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+ g_object_unref (source);
+
+ if (!sim)
+ return NULL;
+
+ /* Only export valid SIMs */
+ mm_base_sim_export (MM_BASE_SIM (sim));
+
+ return MM_BASE_SIM (sim);
+}
+
+void
+mm_sim_ublox_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (MM_TYPE_SIM_UBLOX,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ MM_BASE_SIM_MODEM, modem,
+ "active", TRUE, /* by default always active */
+ NULL);
+}
+
+static void
+mm_sim_ublox_init (MMSimUblox *self)
+{
+}
+
+static void
+mm_sim_ublox_class_init (MMSimUbloxClass *klass)
+{
+ MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass);
+
+ base_sim_class->load_sim_identifier = load_sim_identifier;
+ base_sim_class->load_sim_identifier_finish = load_sim_identifier_finish;
+}
diff --git a/src/plugins/ublox/mm-sim-ublox.h b/src/plugins/ublox/mm-sim-ublox.h
new file mode 100644
index 00000000..31e2c98b
--- /dev/null
+++ b/src/plugins/ublox/mm-sim-ublox.h
@@ -0,0 +1,51 @@
+/* -*- 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_SIM_UBLOX_H
+#define MM_SIM_UBLOX_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "mm-base-sim.h"
+
+#define MM_TYPE_SIM_UBLOX (mm_sim_ublox_get_type ())
+#define MM_SIM_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SIM_UBLOX, MMSimUblox))
+#define MM_SIM_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SIM_UBLOX, MMSimUbloxClass))
+#define MM_IS_SIM_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SIM_UBLOX))
+#define MM_IS_SIM_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SIM_UBLOX))
+#define MM_SIM_UBLOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SIM_UBLOX, MMSimUbloxClass))
+
+typedef struct _MMSimUblox MMSimUblox;
+typedef struct _MMSimUbloxClass MMSimUbloxClass;
+
+struct _MMSimUblox {
+ MMBaseSim parent;
+};
+
+struct _MMSimUbloxClass {
+ MMBaseSimClass parent;
+};
+
+GType mm_sim_ublox_get_type (void);
+
+void mm_sim_ublox_new (MMBaseModem *modem,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMBaseSim *mm_sim_ublox_new_finish (GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SIM_UBLOX_H */
diff --git a/src/plugins/ublox/tests/test-modem-helpers-ublox.c b/src/plugins/ublox/tests/test-modem-helpers-ublox.c
new file mode 100644
index 00000000..2d662877
--- /dev/null
+++ b/src/plugins/ublox/tests/test-modem-helpers-ublox.c
@@ -0,0 +1,1026 @@
+/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include <locale.h>
+#include <arpa/inet.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-test.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-ublox.h"
+
+#include "test-helpers.h"
+
+/*****************************************************************************/
+/* Test +UPINCNT responses */
+
+typedef struct {
+ const gchar *str;
+ guint pin_attempts;
+ guint pin2_attempts;
+ guint puk_attempts;
+ guint puk2_attempts;
+} UpinCntResponseTest;
+
+static const UpinCntResponseTest upincnt_response_tests[] = {
+ { .str = "+UPINCNT: 3,3,10,10\r\n",
+ .pin_attempts = 3,
+ .pin2_attempts = 3,
+ .puk_attempts = 10,
+ .puk2_attempts = 10
+ },
+ { .str = "+UPINCNT: 0,3,5,5\r\n",
+ .pin_attempts = 0,
+ .pin2_attempts = 3,
+ .puk_attempts = 5,
+ .puk2_attempts = 5
+ },
+ { .str = "+UPINCNT: 0,0,0,0\r\n",
+ .pin_attempts = 0,
+ .pin2_attempts = 0,
+ .puk_attempts = 0,
+ .puk2_attempts = 0
+ },
+};
+
+static void
+test_upincnt_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (upincnt_response_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ guint pin_attempts = G_MAXUINT;
+ guint pin2_attempts = G_MAXUINT;
+ guint puk_attempts = G_MAXUINT;
+ guint puk2_attempts = G_MAXUINT;
+
+ success = mm_ublox_parse_upincnt_response (upincnt_response_tests[i].str,
+ &pin_attempts, &pin2_attempts,
+ &puk_attempts, &puk2_attempts,
+ &error);
+ g_assert_no_error (error);
+ g_assert (success);
+ g_assert_cmpuint (upincnt_response_tests[i].pin_attempts, ==, pin_attempts);
+ g_assert_cmpuint (upincnt_response_tests[i].pin2_attempts, ==, pin2_attempts);
+ g_assert_cmpuint (upincnt_response_tests[i].puk_attempts, ==, puk_attempts);
+ g_assert_cmpuint (upincnt_response_tests[i].puk2_attempts, ==, puk2_attempts);
+ }
+}
+
+/*****************************************************************************/
+/* Test UUSBCONF? responses */
+
+typedef struct {
+ const gchar *str;
+ MMUbloxUsbProfile profile;
+} UusbconfResponseTest;
+
+static const UusbconfResponseTest uusbconf_response_tests[] = {
+ {
+ .str = "+UUSBCONF: 3,\"RNDIS\",,\"0x1146\"\r\n",
+ .profile = MM_UBLOX_USB_PROFILE_RNDIS
+ },
+ {
+ .str = "+UUSBCONF: 2,\"ECM\",,\"0x1143\"\r\n",
+ .profile = MM_UBLOX_USB_PROFILE_ECM
+ },
+ {
+ .str = "+UUSBCONF: 0,\"\",,\"0x1141\"\r\n",
+ .profile = MM_UBLOX_USB_PROFILE_BACK_COMPATIBLE
+ },
+};
+
+static void
+test_uusbconf_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (uusbconf_response_tests); i++) {
+ MMUbloxUsbProfile profile = MM_UBLOX_USB_PROFILE_UNKNOWN;
+ GError *error = NULL;
+ gboolean success;
+
+ success = mm_ublox_parse_uusbconf_response (uusbconf_response_tests[i].str, &profile, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+ g_assert_cmpuint (uusbconf_response_tests[i].profile, ==, profile);
+ }
+}
+
+/*****************************************************************************/
+/* Test UBMCONF? responses */
+
+typedef struct {
+ const gchar *str;
+ MMUbloxNetworkingMode mode;
+} UbmconfResponseTest;
+
+static const UbmconfResponseTest ubmconf_response_tests[] = {
+ {
+ .str = "+UBMCONF: 1\r\n",
+ .mode = MM_UBLOX_NETWORKING_MODE_ROUTER
+ },
+ {
+ .str = "+UBMCONF: 2\r\n",
+ .mode = MM_UBLOX_NETWORKING_MODE_BRIDGE
+ },
+};
+
+static void
+test_ubmconf_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (ubmconf_response_tests); i++) {
+ MMUbloxNetworkingMode mode = MM_UBLOX_NETWORKING_MODE_UNKNOWN;
+ GError *error = NULL;
+ gboolean success;
+
+ success = mm_ublox_parse_ubmconf_response (ubmconf_response_tests[i].str, &mode, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+ g_assert_cmpuint (ubmconf_response_tests[i].mode, ==, mode);
+ }
+}
+
+/*****************************************************************************/
+/* Test UIPADDR=N responses */
+
+typedef struct {
+ const gchar *str;
+ guint cid;
+ const gchar *if_name;
+ const gchar *ipv4_address;
+ const gchar *ipv4_subnet;
+ const gchar *ipv6_global_address;
+ const gchar *ipv6_link_local_address;
+} UipaddrResponseTest;
+
+static const UipaddrResponseTest uipaddr_response_tests[] = {
+ {
+ .str = "+UIPADDR: 1,\"ccinet0\",\"5.168.120.13\",\"255.255.255.0\",\"\",\"\"",
+ .cid = 1,
+ .if_name = "ccinet0",
+ .ipv4_address = "5.168.120.13",
+ .ipv4_subnet = "255.255.255.0",
+ },
+ {
+ .str = "+UIPADDR: 2,\"ccinet1\",\"\",\"\",\"2001::1:200:FF:FE00:0/64\",\"FE80::200:FF:FE00:0/64\"",
+ .cid = 2,
+ .if_name = "ccinet1",
+ .ipv6_global_address = "2001::1:200:FF:FE00:0/64",
+ .ipv6_link_local_address = "FE80::200:FF:FE00:0/64",
+ },
+ {
+ .str = "+UIPADDR: 3,\"ccinet2\",\"5.10.100.2\",\"255.255.255.0\",\"2001::1:200:FF:FE00:0/64\",\"FE80::200:FF:FE00:0/64\"",
+ .cid = 3,
+ .if_name = "ccinet2",
+ .ipv4_address = "5.10.100.2",
+ .ipv4_subnet = "255.255.255.0",
+ .ipv6_global_address = "2001::1:200:FF:FE00:0/64",
+ .ipv6_link_local_address = "FE80::200:FF:FE00:0/64",
+ },
+};
+
+static void
+test_uipaddr_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (uipaddr_response_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ guint cid = G_MAXUINT;
+ gchar *if_name = NULL;
+ gchar *ipv4_address = NULL;
+ gchar *ipv4_subnet = NULL;
+ gchar *ipv6_global_address = NULL;
+ gchar *ipv6_link_local_address = NULL;
+
+ success = mm_ublox_parse_uipaddr_response (uipaddr_response_tests[i].str,
+ &cid,
+ &if_name,
+ &ipv4_address,
+ &ipv4_subnet,
+ &ipv6_global_address,
+ &ipv6_link_local_address,
+ &error);
+ g_assert_no_error (error);
+ g_assert (success);
+ g_assert_cmpuint (uipaddr_response_tests[i].cid, ==, cid);
+ g_assert_cmpstr (uipaddr_response_tests[i].if_name, ==, if_name);
+ g_assert_cmpstr (uipaddr_response_tests[i].ipv4_address, ==, ipv4_address);
+ g_assert_cmpstr (uipaddr_response_tests[i].ipv4_subnet, ==, ipv4_subnet);
+ g_assert_cmpstr (uipaddr_response_tests[i].ipv6_global_address, ==, ipv6_global_address);
+ g_assert_cmpstr (uipaddr_response_tests[i].ipv6_link_local_address, ==, ipv6_link_local_address);
+
+ g_free (if_name);
+ g_free (ipv4_address);
+ g_free (ipv4_subnet);
+ g_free (ipv6_global_address);
+ g_free (ipv6_link_local_address);
+ }
+}
+
+/*****************************************************************************/
+/* Test CFUN? response */
+
+typedef struct {
+ const gchar *str;
+ MMModemPowerState state;
+} CfunQueryTest;
+
+static const CfunQueryTest cfun_query_tests[] = {
+ { "+CFUN: 1", MM_MODEM_POWER_STATE_ON },
+ { "+CFUN: 1,0", MM_MODEM_POWER_STATE_ON },
+ { "+CFUN: 0", MM_MODEM_POWER_STATE_LOW },
+ { "+CFUN: 0,0", MM_MODEM_POWER_STATE_LOW },
+ { "+CFUN: 4", MM_MODEM_POWER_STATE_LOW },
+ { "+CFUN: 4,0", MM_MODEM_POWER_STATE_LOW },
+ { "+CFUN: 19", MM_MODEM_POWER_STATE_LOW },
+ { "+CFUN: 19,0", MM_MODEM_POWER_STATE_LOW },
+};
+
+static void
+test_cfun_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (cfun_query_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ MMModemPowerState state = MM_MODEM_POWER_STATE_UNKNOWN;
+
+ success = mm_ublox_parse_cfun_response (cfun_query_tests[i].str, &state, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+ g_assert_cmpuint (cfun_query_tests[i].state, ==, state);
+ }
+}
+
+/*****************************************************************************/
+/* Test URAT=? responses and model based filtering */
+
+static void
+compare_combinations (const gchar *response,
+ const gchar *model,
+ const MMModemModeCombination *expected_combinations,
+ guint n_expected_combinations)
+{
+ GArray *combinations;
+ GError *error = NULL;
+ guint i;
+
+ combinations = mm_ublox_parse_urat_test_response (response, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (combinations);
+
+ combinations = mm_ublox_filter_supported_modes (model, combinations, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (combinations);
+
+ g_assert_cmpuint (combinations->len, ==, n_expected_combinations);
+
+ for (i = 0; i < combinations->len; i++) {
+ MMModemModeCombination combination;
+ guint j;
+ gboolean found = FALSE;
+
+ combination = g_array_index (combinations, MMModemModeCombination, i);
+ for (j = 0; !found && j < n_expected_combinations; j++)
+ found = (combination.allowed == expected_combinations[j].allowed &&
+ combination.preferred == expected_combinations[j].preferred);
+ g_assert (found);
+ }
+
+ g_array_unref (combinations);
+}
+
+static void
+test_urat_test_response_2g (void)
+{
+ static const MMModemModeCombination expected_combinations[] = {
+ { MM_MODEM_MODE_2G, MM_MODEM_MODE_NONE }
+ };
+
+ compare_combinations ("+URAT: 0", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations));
+ compare_combinations ("+URAT: 0,0", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations));
+ compare_combinations ("+URAT: (0)", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations));
+ compare_combinations ("+URAT: (0),(0)", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations));
+}
+
+static void
+test_urat_test_response_2g3g (void)
+{
+ static const MMModemModeCombination expected_combinations[] = {
+ { MM_MODEM_MODE_2G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_2G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_3G },
+ };
+
+ compare_combinations ("+URAT: (0,1,2),(0,2)", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations));
+ compare_combinations ("+URAT: (0-2),(0,2)", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations));
+}
+
+static void
+test_urat_test_response_2g3g4g (void)
+{
+ static const MMModemModeCombination expected_combinations[] = {
+ { MM_MODEM_MODE_2G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_2G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_3G },
+
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, MM_MODEM_MODE_2G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G },
+
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_3G },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G },
+
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_2G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_3G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G },
+ };
+
+ compare_combinations ("+URAT: (0,1,2,3,4,5,6),(0,2,3)", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations));
+ compare_combinations ("+URAT: (0-6),(0,2,3)", NULL, expected_combinations, G_N_ELEMENTS (expected_combinations));
+}
+
+static void
+test_mode_filtering_toby_l201 (void)
+{
+ static const MMModemModeCombination expected_combinations[] = {
+ { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_3G },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G },
+ };
+
+ compare_combinations ("+URAT: (0-6),(0,2,3)", "TOBY-L201", expected_combinations, G_N_ELEMENTS (expected_combinations));
+}
+
+static void
+test_mode_filtering_lisa_u200 (void)
+{
+ static const MMModemModeCombination expected_combinations[] = {
+ { MM_MODEM_MODE_2G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE },
+
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_2G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_3G },
+ };
+
+ compare_combinations ("+URAT: (0-6),(0,2,3)", "LISA-U200", expected_combinations, G_N_ELEMENTS (expected_combinations));
+}
+
+static void
+test_mode_filtering_sara_u280 (void)
+{
+ static const MMModemModeCombination expected_combinations[] = {
+ { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE },
+ };
+
+ compare_combinations ("+URAT: (0-6),(0,2,3)", "SARA-U280", expected_combinations, G_N_ELEMENTS (expected_combinations));
+}
+
+/*****************************************************************************/
+/* URAT? response parser and URAT=X command builder */
+
+typedef struct {
+ const gchar *command;
+ const gchar *response;
+ MMModemMode allowed;
+ MMModemMode preferred;
+} UratTest;
+
+static const UratTest urat_tests[] = {
+ {
+ .command = "+URAT=1,2",
+ .response = "+URAT: 1,2\r\n",
+ .allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G),
+ .preferred = MM_MODEM_MODE_3G,
+ },
+ {
+ .command = "+URAT=4,0",
+ .response = "+URAT: 4,0\r\n",
+ .allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G),
+ .preferred = MM_MODEM_MODE_2G,
+ },
+ {
+ .command = "+URAT=0",
+ .response = "+URAT: 0\r\n",
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE,
+ },
+ {
+ .command = "+URAT=6",
+ .response = "+URAT: 6\r\n",
+ .allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G),
+ .preferred = MM_MODEM_MODE_NONE,
+ },
+};
+
+static void
+test_urat_read_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (urat_tests); i++) {
+ MMModemMode allowed = MM_MODEM_MODE_NONE;
+ MMModemMode preferred = MM_MODEM_MODE_NONE;
+ GError *error = NULL;
+ gboolean success;
+
+ success = mm_ublox_parse_urat_read_response (urat_tests[i].response, NULL,
+ &allowed, &preferred, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+ g_assert_cmpuint (urat_tests[i].allowed, ==, allowed);
+ g_assert_cmpuint (urat_tests[i].preferred, ==, preferred);
+ }
+}
+
+static void
+test_urat_write_command (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (urat_tests); i++) {
+ gchar *command;
+ GError *error = NULL;
+
+ command = mm_ublox_build_urat_set_command (urat_tests[i].allowed, urat_tests[i].preferred, &error);
+ g_assert_no_error (error);
+ g_assert_cmpstr (command, ==, urat_tests[i].command);
+ g_free (command);
+ }
+}
+
+/*****************************************************************************/
+/* Test +UBANDSEL? response parser */
+
+static void
+common_validate_ubandsel_response (const gchar *str,
+ const MMModemBand *expected_bands,
+ const gchar *model,
+ guint n_expected_bands)
+{
+ GError *error = NULL;
+ GArray *bands;
+
+ bands = mm_ublox_parse_ubandsel_response (str, model, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (bands);
+
+ mm_test_helpers_compare_bands (bands, expected_bands, n_expected_bands);
+ g_array_unref (bands);
+}
+
+static void
+test_ubandsel_response_four (void)
+{
+ const MMModemBand expected_bands[] = {
+ /* 700 */ MM_MODEM_BAND_EUTRAN_4,
+ /* 1700 */ MM_MODEM_BAND_EUTRAN_13
+ };
+
+ common_validate_ubandsel_response ("+UBANDSEL: 700,1700\r\n", expected_bands, "LARA-R204", G_N_ELEMENTS (expected_bands));
+}
+
+static void
+test_ubandsel_response_three (void)
+{
+ const MMModemBand expected_bands[] = {
+ /* 800 */ MM_MODEM_BAND_UTRAN_6,
+ /* 850 */ MM_MODEM_BAND_G850, MM_MODEM_BAND_UTRAN_5,
+ /* 900 */ MM_MODEM_BAND_EGSM, MM_MODEM_BAND_UTRAN_8,
+ /* 1900 */ MM_MODEM_BAND_PCS, MM_MODEM_BAND_UTRAN_2,
+ /* 2100 */ MM_MODEM_BAND_UTRAN_1
+ };
+
+ common_validate_ubandsel_response ("+UBANDSEL: 800,850,900,1900,2100\r\n", expected_bands, "SARA-U201", G_N_ELEMENTS (expected_bands));
+}
+
+static void
+test_ubandsel_response_two (void)
+{
+ const MMModemBand expected_bands[] = {
+ /* 850 */ MM_MODEM_BAND_G850,
+ /* 900 */ MM_MODEM_BAND_EGSM,
+ /* 1800 */ MM_MODEM_BAND_DCS,
+ /* 1900 */ MM_MODEM_BAND_PCS
+ };
+
+ common_validate_ubandsel_response ("+UBANDSEL: 850,900,1800,1900\r\n", expected_bands, "SARA-G310", G_N_ELEMENTS (expected_bands));
+}
+
+static void
+test_ubandsel_response_one (void)
+{
+ const MMModemBand expected_bands[] = {
+ /* 850 */ MM_MODEM_BAND_G850, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_EUTRAN_5,
+ /* 1700 */ MM_MODEM_BAND_EUTRAN_4,
+ /* 1900 */ MM_MODEM_BAND_PCS, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_EUTRAN_2
+ };
+
+ common_validate_ubandsel_response ("+UBANDSEL: 850,1700,1900\r\n", expected_bands, "TOBY-R200", G_N_ELEMENTS (expected_bands));
+}
+
+/*****************************************************************************/
+/* +UBANDSEL=x command builder */
+
+static void
+common_validate_ubandsel_request (const MMModemBand *bands,
+ guint n_bands,
+ const gchar *model,
+ const gchar *expected_request)
+{
+ GError *error = NULL;
+ GArray *bands_array;
+ gchar *request;
+
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), n_bands);
+ g_array_append_vals (bands_array, bands, n_bands);
+
+ request = mm_ublox_build_ubandsel_set_command (bands_array, model, &error);
+ g_assert_no_error (error);
+ g_assert (request);
+
+ g_assert_cmpstr (request, ==, expected_request);
+
+ g_array_unref (bands_array);
+ g_free (request);
+}
+
+static void
+test_ubandsel_request_any (void)
+{
+ const MMModemBand bands[] = {
+ MM_MODEM_BAND_ANY
+ };
+
+ common_validate_ubandsel_request (bands, G_N_ELEMENTS (bands), "TOBY-R200", "+UBANDSEL=0");
+}
+
+static void
+test_ubandsel_request_2g (void)
+{
+ const MMModemBand bands[] = {
+ MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS
+ };
+
+ common_validate_ubandsel_request (bands, G_N_ELEMENTS (bands), "SARA-G310", "+UBANDSEL=850,900,1800,1900");
+}
+
+static void
+test_ubandsel_request_1800 (void)
+{
+ const MMModemBand bands[] = {
+ MM_MODEM_BAND_DCS, MM_MODEM_BAND_UTRAN_3, MM_MODEM_BAND_EUTRAN_3
+ };
+
+ common_validate_ubandsel_request (bands, G_N_ELEMENTS (bands), "TOBY-R200", "+UBANDSEL=1800");
+}
+
+/*****************************************************************************/
+/* Test +UACT? response parser */
+
+static void
+common_validate_uact_response (const gchar *str,
+ const MMModemBand *expected_bands,
+ guint n_expected_bands)
+{
+ GError *error = NULL;
+ GArray *bands;
+
+ bands = mm_ublox_parse_uact_response (str, &error);
+
+ if (n_expected_bands > 0) {
+ g_assert (bands);
+ g_assert_no_error (error);
+ mm_test_helpers_compare_bands (bands, expected_bands, n_expected_bands);
+ g_array_unref (bands);
+ } else {
+ g_assert (!bands);
+ g_assert (error);
+ g_error_free (error);
+ }
+}
+
+static void
+test_uact_response_empty_list (void)
+{
+ common_validate_uact_response ("", NULL, 0);
+ common_validate_uact_response ("+UACT: ,,,\r\n", NULL, 0);
+}
+
+static void
+test_uact_response_2g (void)
+{
+ const MMModemBand expected_bands[] = {
+ MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS,
+ };
+
+ common_validate_uact_response ("+UACT: ,,,900,1800,1900,850\r\n",
+ expected_bands, G_N_ELEMENTS (expected_bands));
+}
+
+static void
+test_uact_response_2g3g (void)
+{
+ const MMModemBand expected_bands[] = {
+ MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_3, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_7, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_9,
+ };
+
+ common_validate_uact_response ("+UACT: ,,,900,1800,1900,850,1,2,3,4,5,6,7,8,9\r\n",
+ expected_bands, G_N_ELEMENTS (expected_bands));
+}
+
+static void
+test_uact_response_2g3g4g (void)
+{
+ const MMModemBand expected_bands[] = {
+ MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS,
+ MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_3, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5,
+ MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_7, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_9,
+ MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_6, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_9,
+ };
+
+ common_validate_uact_response ("+UACT: ,,,900,1800,1900,850,1,2,3,4,5,6,7,8,9,101,102,103,104,105,106,107,108,109\r\n",
+ expected_bands, G_N_ELEMENTS (expected_bands));
+}
+
+/*****************************************************************************/
+/* Test +UACT=? test parser */
+
+static void
+common_validate_uact_test (const gchar *str,
+ const MMModemBand *expected_bands_2g,
+ guint n_expected_bands_2g,
+ const MMModemBand *expected_bands_3g,
+ guint n_expected_bands_3g,
+ const MMModemBand *expected_bands_4g,
+ guint n_expected_bands_4g)
+{
+ GError *error = NULL;
+ gboolean result;
+ GArray *bands_2g = NULL;
+ GArray *bands_3g = NULL;
+ GArray *bands_4g = NULL;
+
+ result = mm_ublox_parse_uact_test (str, NULL, &bands_2g, &bands_3g, &bands_4g, &error);
+ g_assert_no_error (error);
+ g_assert (result);
+
+ mm_test_helpers_compare_bands (bands_2g, expected_bands_2g, n_expected_bands_2g);
+ if (bands_2g)
+ g_array_unref (bands_2g);
+ mm_test_helpers_compare_bands (bands_3g, expected_bands_3g, n_expected_bands_3g);
+ if (bands_3g)
+ g_array_unref (bands_3g);
+ mm_test_helpers_compare_bands (bands_4g, expected_bands_4g, n_expected_bands_4g);
+ if (bands_4g)
+ g_array_unref (bands_4g);
+}
+
+static void
+test_uact_test_2g (void)
+{
+ const MMModemBand expected_bands_2g[] = {
+ MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS
+ };
+
+ common_validate_uact_test ("+UACT: ,,,(900,1800)\r\n",
+ expected_bands_2g, G_N_ELEMENTS (expected_bands_2g),
+ NULL, 0,
+ NULL, 0);
+}
+
+static void
+test_uact_test_2g3g (void)
+{
+ const MMModemBand expected_bands_2g[] = {
+ MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS
+ };
+ const MMModemBand expected_bands_3g[] = {
+ MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_8
+ };
+
+ common_validate_uact_test ("+UACT: ,,,(900,1800),(1,8)\r\n",
+ expected_bands_2g, G_N_ELEMENTS (expected_bands_2g),
+ expected_bands_3g, G_N_ELEMENTS (expected_bands_3g),
+ NULL, 0);
+}
+
+static void
+test_uact_test_2g3g4g (void)
+{
+ const MMModemBand expected_bands_2g[] = {
+ MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS
+ };
+ const MMModemBand expected_bands_3g[] = {
+ MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_8
+ };
+ const MMModemBand expected_bands_4g[] = {
+ MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20
+ };
+
+ common_validate_uact_test ("+UACT: ,,,(900,1800),(1,8),(101,103,107,108,120)\r\n",
+ expected_bands_2g, G_N_ELEMENTS (expected_bands_2g),
+ expected_bands_3g, G_N_ELEMENTS (expected_bands_3g),
+ expected_bands_4g, G_N_ELEMENTS (expected_bands_4g));
+}
+
+static void
+test_uact_test_2g3g4g_2 (void)
+{
+ const MMModemBand expected_bands_2g[] = {
+ MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS
+ };
+ const MMModemBand expected_bands_3g[] = {
+ MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_8
+ };
+ const MMModemBand expected_bands_4g[] = {
+ MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20
+ };
+
+ common_validate_uact_test ("+UACT: ,,,(900,1800),(1,8),(101,103,107,108,120),(138)\r\n",
+ expected_bands_2g, G_N_ELEMENTS (expected_bands_2g),
+ expected_bands_3g, G_N_ELEMENTS (expected_bands_3g),
+ expected_bands_4g, G_N_ELEMENTS (expected_bands_4g));
+}
+
+/*****************************************************************************/
+/* +UACT=x command builder */
+
+static void
+common_validate_uact_request (const MMModemBand *bands,
+ guint n_bands,
+ const gchar *expected_request)
+{
+ GError *error = NULL;
+ GArray *bands_array;
+ gchar *request;
+
+ bands_array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), n_bands);
+ g_array_append_vals (bands_array, bands, n_bands);
+
+ request = mm_ublox_build_uact_set_command (bands_array, &error);
+ g_assert_no_error (error);
+ g_assert (request);
+
+ g_assert_cmpstr (request, ==, expected_request);
+
+ g_array_unref (bands_array);
+ g_free (request);
+}
+
+static void
+test_uact_request_any (void)
+{
+ const MMModemBand bands[] = {
+ MM_MODEM_BAND_ANY
+ };
+
+ common_validate_uact_request (bands, G_N_ELEMENTS (bands), "+UACT=,,,0");
+}
+
+static void
+test_uact_request_2g (void)
+{
+ const MMModemBand bands[] = {
+ MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS,
+ };
+
+ common_validate_uact_request (bands, G_N_ELEMENTS (bands), "+UACT=,,,900,1800");
+}
+
+static void
+test_uact_request_3g (void)
+{
+ const MMModemBand bands[] = {
+ MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_8,
+ };
+
+ common_validate_uact_request (bands, G_N_ELEMENTS (bands), "+UACT=,,,1,8");
+}
+
+static void
+test_uact_request_4g (void)
+{
+ const MMModemBand bands[] = {
+ MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20
+ };
+
+ common_validate_uact_request (bands, G_N_ELEMENTS (bands), "+UACT=,,,101,103,107,108,120");
+}
+
+/*****************************************************************************/
+/* Test +UAUTHREQ=? responses */
+
+static void
+common_validate_uauthreq_test (const gchar *str,
+ MMUbloxBearerAllowedAuth expected_allowed_auths)
+{
+ GError *error = NULL;
+ MMUbloxBearerAllowedAuth allowed_auths;
+
+ allowed_auths = mm_ublox_parse_uauthreq_test (str, NULL, &error);
+ g_assert_no_error (error);
+ g_assert_cmpuint (allowed_auths, ==, expected_allowed_auths);
+}
+
+static void
+test_uauthreq_tobyl4 (void)
+{
+ common_validate_uauthreq_test ("+UAUTHREQ: (1-4),(0-2),,",
+ (MM_UBLOX_BEARER_ALLOWED_AUTH_NONE |
+ MM_UBLOX_BEARER_ALLOWED_AUTH_PAP |
+ MM_UBLOX_BEARER_ALLOWED_AUTH_CHAP));
+}
+
+static void
+test_uauthreq_with_auto (void)
+{
+ common_validate_uauthreq_test ("+UAUTHREQ: (1-4),(0-3),,",
+ (MM_UBLOX_BEARER_ALLOWED_AUTH_NONE |
+ MM_UBLOX_BEARER_ALLOWED_AUTH_PAP |
+ MM_UBLOX_BEARER_ALLOWED_AUTH_CHAP |
+ MM_UBLOX_BEARER_ALLOWED_AUTH_AUTO));
+}
+
+static void
+test_uauthreq_less_fields (void)
+{
+ common_validate_uauthreq_test ("+UAUTHREQ: (1-4),(0-2)",
+ (MM_UBLOX_BEARER_ALLOWED_AUTH_NONE |
+ MM_UBLOX_BEARER_ALLOWED_AUTH_PAP |
+ MM_UBLOX_BEARER_ALLOWED_AUTH_CHAP));
+}
+
+/*****************************************************************************/
+/* Test +UGCNTRD responses */
+
+typedef struct {
+ const gchar *str;
+ guint cid;
+ guint64 session_tx_bytes;
+ guint64 session_rx_bytes;
+ guint64 total_tx_bytes;
+ guint64 total_rx_bytes;
+} UgcntrdResponseTest;
+
+static const UgcntrdResponseTest ugcntrd_response_tests[] = {
+ {
+ .str = "+UGCNTRD: 1, 100, 0, 100, 0",
+ .cid = 1,
+ .session_tx_bytes = 100,
+ .session_rx_bytes = 0,
+ .total_tx_bytes = 100,
+ .total_rx_bytes = 0
+ },
+ {
+ .str = "+UGCNTRD: 31,2704,1819,2724,1839",
+ .cid = 31,
+ .session_tx_bytes = 2704,
+ .session_rx_bytes = 1819,
+ .total_tx_bytes = 2724,
+ .total_rx_bytes = 1839
+ },
+ {
+ .str = "+UGCNTRD: 1, 100, 0, 100, 0\r\n"
+ "+UGCNTRD: 31,2704,1819,2724,1839\r\n",
+ .cid = 1,
+ .session_tx_bytes = 100,
+ .session_rx_bytes = 0,
+ .total_tx_bytes = 100,
+ .total_rx_bytes = 0
+ },
+ {
+ .str = "+UGCNTRD: 1, 100, 0, 100, 0\r\n"
+ "+UGCNTRD: 31,2704,1819,2724,1839\r\n",
+ .cid = 31,
+ .session_tx_bytes = 2704,
+ .session_rx_bytes = 1819,
+ .total_tx_bytes = 2724,
+ .total_rx_bytes = 1839
+ },
+ {
+ .str = "+UGCNTRD: 2,1397316870,113728263578,1397316870,113728263578\r\n",
+ .cid = 2,
+ .session_tx_bytes = 1397316870ULL,
+ .session_rx_bytes = 113728263578ULL,
+ .total_tx_bytes = 1397316870ULL,
+ .total_rx_bytes = 113728263578ULL
+ }
+};
+
+static void
+test_ugcntrd_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (ugcntrd_response_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ guint64 session_tx_bytes = 0;
+ guint64 session_rx_bytes = 0;
+ guint64 total_tx_bytes = 0;
+ guint64 total_rx_bytes = 0;
+
+ success = mm_ublox_parse_ugcntrd_response_for_cid (ugcntrd_response_tests[i].str,
+ ugcntrd_response_tests[i].cid,
+ &session_tx_bytes,
+ &session_rx_bytes,
+ &total_tx_bytes,
+ &total_rx_bytes,
+ &error);
+ g_assert_no_error (error);
+ g_assert (success);
+ g_assert_cmpuint (ugcntrd_response_tests[i].session_tx_bytes, ==, session_tx_bytes);
+ g_assert_cmpuint (ugcntrd_response_tests[i].session_rx_bytes, ==, session_rx_bytes);
+ g_assert_cmpuint (ugcntrd_response_tests[i].total_tx_bytes, ==, total_tx_bytes);
+ g_assert_cmpuint (ugcntrd_response_tests[i].total_rx_bytes, ==, total_rx_bytes);
+ }
+}
+
+/*****************************************************************************/
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/ublox/upincnt/response", test_upincnt_response);
+ g_test_add_func ("/MM/ublox/uusbconf/response", test_uusbconf_response);
+ g_test_add_func ("/MM/ublox/ubmconf/response", test_ubmconf_response);
+ g_test_add_func ("/MM/ublox/uipaddr/response", test_uipaddr_response);
+ g_test_add_func ("/MM/ublox/cfun/response", test_cfun_response);
+ g_test_add_func ("/MM/ublox/urat/test/response/2g", test_urat_test_response_2g);
+ g_test_add_func ("/MM/ublox/urat/test/response/2g3g", test_urat_test_response_2g3g);
+ g_test_add_func ("/MM/ublox/urat/test/response/2g3g4g", test_urat_test_response_2g3g4g);
+ g_test_add_func ("/MM/ublox/urat/test/response/toby-l201", test_mode_filtering_toby_l201);
+ g_test_add_func ("/MM/ublox/urat/test/response/lisa-u200", test_mode_filtering_lisa_u200);
+ g_test_add_func ("/MM/ublox/urat/test/response/sara-u280", test_mode_filtering_sara_u280);
+ g_test_add_func ("/MM/ublox/urat/read/response", test_urat_read_response);
+ g_test_add_func ("/MM/ublox/urat/write/command", test_urat_write_command);
+ g_test_add_func ("/MM/ublox/ubandsel/response/one", test_ubandsel_response_one);
+ g_test_add_func ("/MM/ublox/ubandsel/response/two", test_ubandsel_response_two);
+ g_test_add_func ("/MM/ublox/ubandsel/response/three", test_ubandsel_response_three);
+ g_test_add_func ("/MM/ublox/ubandsel/response/four", test_ubandsel_response_four);
+ g_test_add_func ("/MM/ublox/ubandsel/request/any", test_ubandsel_request_any);
+ g_test_add_func ("/MM/ublox/ubandsel/request/2g", test_ubandsel_request_2g);
+ g_test_add_func ("/MM/ublox/ubandsel/request/1800", test_ubandsel_request_1800);
+ g_test_add_func ("/MM/ublox/uact/response/empty-list", test_uact_response_empty_list);
+ g_test_add_func ("/MM/ublox/uact/response/2g", test_uact_response_2g);
+ g_test_add_func ("/MM/ublox/uact/response/2g3g", test_uact_response_2g3g);
+ g_test_add_func ("/MM/ublox/uact/response/2g3g4g", test_uact_response_2g3g4g);
+ g_test_add_func ("/MM/ublox/uact/test/2g", test_uact_test_2g);
+ g_test_add_func ("/MM/ublox/uact/test/2g3g", test_uact_test_2g3g);
+ g_test_add_func ("/MM/ublox/uact/test/2g3g4g", test_uact_test_2g3g4g);
+ g_test_add_func ("/MM/ublox/uact/test/2g3g4g/2", test_uact_test_2g3g4g_2);
+ g_test_add_func ("/MM/ublox/uact/request/any", test_uact_request_any);
+ g_test_add_func ("/MM/ublox/uact/request/2g", test_uact_request_2g);
+ g_test_add_func ("/MM/ublox/uact/request/3g", test_uact_request_3g);
+ g_test_add_func ("/MM/ublox/uact/request/4g", test_uact_request_4g);
+ g_test_add_func ("/MM/ublox/uauthreq/test/tobyl4", test_uauthreq_tobyl4);
+ g_test_add_func ("/MM/ublox/uauthreq/test/with-auto", test_uauthreq_with_auto);
+ g_test_add_func ("/MM/ublox/uauthreq/test/less-fields", test_uauthreq_less_fields);
+ g_test_add_func ("/MM/ublox/ugcntrd/response", test_ugcntrd_response);
+
+ return g_test_run ();
+}
diff --git a/src/plugins/via/mm-broadband-modem-via.c b/src/plugins/via/mm-broadband-modem-via.c
new file mode 100644
index 00000000..896db8cd
--- /dev/null
+++ b/src/plugins/via/mm-broadband-modem-via.c
@@ -0,0 +1,543 @@
+/* -*- 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) 2012 Red Hat, Inc.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-errors-types.h"
+#include "mm-base-modem-at.h"
+#include "mm-broadband-modem-via.h"
+#include "mm-iface-modem-cdma.h"
+#include "mm-iface-modem.h"
+
+static void iface_modem_cdma_init (MMIfaceModemCdma *iface);
+
+static MMIfaceModemCdma *iface_modem_cdma_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemVia, mm_broadband_modem_via, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_init))
+
+struct _MMBroadbandModemViaPrivate {
+ /* Regex for signal quality related notifications */
+ GRegex *hrssilvl_regex; /* EVDO signal strength */
+
+ /* Regex for other notifications to ignore */
+ GRegex *mode_regex; /* Access technology change */
+ GRegex *dosession_regex; /* EVDO data dormancy */
+ GRegex *simst_regex;
+ GRegex *vpon_regex;
+ GRegex *creg_regex;
+ GRegex *vrom_regex; /* Roaming indicator (reportedly unreliable) */
+ GRegex *vser_regex;
+ GRegex *ciev_regex;
+ GRegex *vpup_regex;
+};
+
+/*****************************************************************************/
+/* Setup registration checks (CDMA interface) */
+
+typedef struct {
+ gboolean skip_qcdm_call_manager_step;
+ gboolean skip_qcdm_hdr_step;
+ gboolean skip_at_cdma_service_status_step;
+ gboolean skip_at_cdma1x_serving_system_step;
+ gboolean skip_detailed_registration_state;
+} SetupRegistrationChecksResults;
+
+static gboolean
+setup_registration_checks_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ gboolean *skip_qcdm_call_manager_step,
+ gboolean *skip_qcdm_hdr_step,
+ gboolean *skip_at_cdma_service_status_step,
+ gboolean *skip_at_cdma1x_serving_system_step,
+ gboolean *skip_detailed_registration_state,
+ GError **error)
+{
+ SetupRegistrationChecksResults *results;
+
+ results = g_task_propagate_pointer (G_TASK (res), error);
+ if (!results)
+ return FALSE;
+
+ *skip_qcdm_call_manager_step = results->skip_qcdm_call_manager_step;
+ *skip_qcdm_hdr_step = results->skip_qcdm_hdr_step;
+ *skip_at_cdma_service_status_step = results->skip_at_cdma_service_status_step;
+ *skip_at_cdma1x_serving_system_step = results->skip_at_cdma1x_serving_system_step;
+ *skip_detailed_registration_state = results->skip_detailed_registration_state;
+
+ g_free (results);
+
+ return TRUE;
+}
+
+static void
+parent_setup_registration_checks_ready (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ SetupRegistrationChecksResults *results;
+
+ results = g_new0 (SetupRegistrationChecksResults, 1);
+
+ if (!iface_modem_cdma_parent->setup_registration_checks_finish (self,
+ res,
+ &results->skip_qcdm_call_manager_step,
+ &results->skip_qcdm_hdr_step,
+ &results->skip_at_cdma_service_status_step,
+ &results->skip_at_cdma1x_serving_system_step,
+ &results->skip_detailed_registration_state,
+ &error)) {
+ g_free (results);
+ g_task_return_error (task, error);
+ } else {
+ /* Skip +CSS */
+ results->skip_at_cdma1x_serving_system_step = TRUE;
+ /* Skip +CAD */
+ results->skip_at_cdma_service_status_step = TRUE;
+ /* Force to always use the detailed registration checks, as we have
+ * ^SYSINFO for that */
+ results->skip_detailed_registration_state = FALSE;
+ g_task_return_pointer (task, results, g_free);
+ }
+ g_object_unref (task);
+}
+
+static void
+setup_registration_checks (MMIfaceModemCdma *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Run parent's checks first */
+ iface_modem_cdma_parent->setup_registration_checks (
+ self,
+ (GAsyncReadyCallback)parent_setup_registration_checks_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Detailed registration state (CDMA interface) */
+
+typedef struct {
+ MMModemCdmaRegistrationState detailed_cdma1x_state;
+ MMModemCdmaRegistrationState detailed_evdo_state;
+} DetailedRegistrationStateResults;
+
+static gboolean
+get_detailed_registration_state_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ MMModemCdmaRegistrationState *detailed_cdma1x_state,
+ MMModemCdmaRegistrationState *detailed_evdo_state,
+ GError **error)
+{
+ g_autofree DetailedRegistrationStateResults *results = NULL;
+
+ results = g_task_propagate_pointer (G_TASK (res), error);
+ if (!results)
+ return FALSE;
+
+ *detailed_cdma1x_state = results->detailed_cdma1x_state;
+ *detailed_evdo_state = results->detailed_evdo_state;
+ return TRUE;
+}
+
+static void
+sysinfo_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+
+{
+ DetailedRegistrationStateResults *ctx;
+ g_autofree DetailedRegistrationStateResults *results = NULL;
+ const gchar *response;
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ MMModemCdmaRegistrationState reg_state;
+ guint val = 0;
+
+ ctx = g_task_get_task_data (task);
+
+ /* Set input detailed states as fallback */
+ results = g_memdup (ctx, sizeof (*ctx));
+
+ /* If error, leave superclass' reg state alone if AT^SYSINFO isn't supported. */
+ response = mm_base_modem_at_command_finish (self, res, NULL);
+ if (!response)
+ goto out;
+
+ response = mm_strip_tag (response, "^SYSINFO:");
+
+ /* Format is "<srv_status>,<srv_domain>,<roam_status>,<sys_mode>,<sim_state>" */
+ r = g_regex_new ("\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ g_assert (r != NULL);
+
+ /* Try to parse the results */
+ g_regex_match (r, response, 0, &match_info);
+ if (g_match_info_get_match_count (match_info) < 6) {
+ mm_obj_warn (self, "failed to parse ^SYSINFO response: '%s'", response);
+ goto out;
+ }
+
+ /* At this point the generic code already knows we've been registered */
+ reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
+
+ if (mm_get_uint_from_match_info (match_info, 1, &val)) {
+ if (val == 2) {
+ /* Service available, check roaming state */
+ val = 0;
+ if (mm_get_uint_from_match_info (match_info, 3, &val)) {
+ if (val == 0)
+ reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_HOME;
+ else if (val == 1)
+ reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING;
+ }
+ }
+ }
+
+ /* Check service type */
+ val = 0;
+ if (mm_get_uint_from_match_info (match_info, 4, &val)) {
+ if (val == 2) /* CDMA */
+ results->detailed_cdma1x_state = reg_state;
+ else if (val == 4) /* HDR */
+ results->detailed_evdo_state = reg_state;
+ else if (val == 8) { /* Hybrid */
+ results->detailed_cdma1x_state = reg_state;
+ results->detailed_evdo_state = reg_state;
+ }
+ } else {
+ /* Say we're registered to something even though sysmode parsing failed */
+ mm_obj_dbg (self, "SYSMODE parsing failed: assuming registered at least in CDMA1x");
+ results->detailed_cdma1x_state = reg_state;
+ }
+
+out:
+ g_task_return_pointer (task, g_steal_pointer (&results), g_free);
+ g_object_unref (task);
+}
+
+static void
+get_detailed_registration_state (MMIfaceModemCdma *self,
+ MMModemCdmaRegistrationState cdma1x_state,
+ MMModemCdmaRegistrationState evdo_state,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ DetailedRegistrationStateResults *ctx;
+
+ /* Setup context */
+ ctx = g_new0 (DetailedRegistrationStateResults, 1);
+ ctx->detailed_cdma1x_state = cdma1x_state;
+ ctx->detailed_evdo_state = evdo_state;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "^SYSINFO",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)sysinfo_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (CDMA interface) */
+
+static void
+handle_evdo_quality_change (MMPortSerialAt *port,
+ GMatchInfo *match_info,
+ MMBroadbandModemVia *self)
+{
+ guint quality = 0;
+
+ if (mm_get_uint_from_match_info (match_info, 1, &quality)) {
+ quality = MM_CLAMP_HIGH (quality, 100);
+ mm_obj_dbg (self, "EVDO signal quality: %u", quality);
+ mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);
+ }
+}
+
+static void
+set_unsolicited_events_handlers (MMBroadbandModemVia *self,
+ gboolean enable)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ 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 unsolicited events in given port */
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ /* Signal quality related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->hrssilvl_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)handle_evdo_quality_change : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+}
+
+static gboolean
+modem_cdma_setup_cleanup_unsolicited_events_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_cdma_setup_unsolicited_events_ready (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_cdma_parent->setup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else {
+ /* Our own setup now */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_VIA (self), TRUE);
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_cdma_setup_unsolicited_events (MMIfaceModemCdma *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Chain up parent's setup */
+ iface_modem_cdma_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_cdma_setup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+static void
+parent_cdma_cleanup_unsolicited_events_ready (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_cdma_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+modem_cdma_cleanup_unsolicited_events (MMIfaceModemCdma *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Our own cleanup first */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_VIA (self), FALSE);
+
+ /* And now chain up parent's cleanup */
+ iface_modem_cdma_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_cdma_cleanup_unsolicited_events_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+set_ignored_unsolicited_events_handlers (MMBroadbandModemVia *self)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ 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 unsolicited events in given port */
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->mode_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->dosession_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->simst_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->vpon_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->creg_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->vrom_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->vser_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->ciev_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->vpup_regex,
+ NULL, NULL, NULL);
+ }
+}
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_via_parent_class)->setup_ports (self);
+
+ /* Unsolicited messages to always ignore */
+ set_ignored_unsolicited_events_handlers (MM_BROADBAND_MODEM_VIA (self));
+
+ /* Now reset the unsolicited messages we'll handle when enabled */
+ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_VIA (self), FALSE);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemVia *
+mm_broadband_modem_via_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_VIA,
+ 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,
+ NULL);
+}
+
+static void
+mm_broadband_modem_via_init (MMBroadbandModemVia *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_MODEM_VIA,
+ MMBroadbandModemViaPrivate);
+
+ /* Prepare regular expressions to setup */
+ self->priv->hrssilvl_regex = g_regex_new ("\\r\\n\\^HRSSILVL:(.*)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->mode_regex = g_regex_new ("\\r\\n\\^MODE:(.*)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->dosession_regex = g_regex_new ("\\r\\n\\+DOSESSION:(.*)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->simst_regex = g_regex_new ("\\r\\n\\^SIMST:(.*)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->simst_regex = g_regex_new ("\\r\\n\\+VPON:(.*)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->creg_regex = g_regex_new ("\\r\\n\\+CREG:(.*)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->vrom_regex = g_regex_new ("\\r\\n\\+VROM:(.*)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->vser_regex = g_regex_new ("\\r\\n\\+VSER:(.*)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->ciev_regex = g_regex_new ("\\r\\n\\+CIEV:(.*)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->vpup_regex = g_regex_new ("\\r\\n\\+VPUP:(.*)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemVia *self = MM_BROADBAND_MODEM_VIA (object);
+
+ g_regex_unref (self->priv->hrssilvl_regex);
+ g_regex_unref (self->priv->mode_regex);
+ g_regex_unref (self->priv->dosession_regex);
+ g_regex_unref (self->priv->simst_regex);
+ g_regex_unref (self->priv->simst_regex);
+ g_regex_unref (self->priv->creg_regex);
+ g_regex_unref (self->priv->vrom_regex);
+ g_regex_unref (self->priv->vser_regex);
+ g_regex_unref (self->priv->ciev_regex);
+ g_regex_unref (self->priv->vpup_regex);
+
+ G_OBJECT_CLASS (mm_broadband_modem_via_parent_class)->finalize (object);
+}
+
+static void
+iface_modem_cdma_init (MMIfaceModemCdma *iface)
+{
+ iface_modem_cdma_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = modem_cdma_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_cdma_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish;
+ iface->setup_registration_checks = setup_registration_checks;
+ iface->setup_registration_checks_finish = setup_registration_checks_finish;
+ iface->get_detailed_registration_state = get_detailed_registration_state;
+ iface->get_detailed_registration_state_finish = get_detailed_registration_state_finish;
+}
+
+static void
+mm_broadband_modem_via_class_init (MMBroadbandModemViaClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemViaPrivate));
+
+ object_class->finalize = finalize;
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/via/mm-broadband-modem-via.h b/src/plugins/via/mm-broadband-modem-via.h
new file mode 100644
index 00000000..2a31117f
--- /dev/null
+++ b/src/plugins/via/mm-broadband-modem-via.h
@@ -0,0 +1,49 @@
+/* -*- 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) 2012 Red Hat, Inc.
+ */
+
+#ifndef MM_BROADBAND_MODEM_VIA_H
+#define MM_BROADBAND_MODEM_VIA_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_VIA (mm_broadband_modem_via_get_type ())
+#define MM_BROADBAND_MODEM_VIA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_VIA, MMBroadbandModemVia))
+#define MM_BROADBAND_MODEM_VIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_VIA, MMBroadbandModemViaClass))
+#define MM_IS_BROADBAND_MODEM_VIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_VIA))
+#define MM_IS_BROADBAND_MODEM_VIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_VIA))
+#define MM_BROADBAND_MODEM_VIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_VIA, MMBroadbandModemViaClass))
+
+typedef struct _MMBroadbandModemVia MMBroadbandModemVia;
+typedef struct _MMBroadbandModemViaClass MMBroadbandModemViaClass;
+typedef struct _MMBroadbandModemViaPrivate MMBroadbandModemViaPrivate;
+
+struct _MMBroadbandModemVia {
+ MMBroadbandModem parent;
+ MMBroadbandModemViaPrivate *priv;
+};
+
+struct _MMBroadbandModemViaClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_via_get_type (void);
+
+MMBroadbandModemVia *mm_broadband_modem_via_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_VIA_H */
diff --git a/src/plugins/via/mm-plugin-via.c b/src/plugins/via/mm-plugin-via.c
new file mode 100644
index 00000000..b1939092
--- /dev/null
+++ b/src/plugins/via/mm-plugin-via.c
@@ -0,0 +1,84 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-broadband-modem-via.h"
+#include "mm-plugin-via.h"
+
+G_DEFINE_TYPE (MMPluginVia, mm_plugin_via, 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)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_via_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", NULL };
+ static const mm_str_pair product_strings[] = { { (gchar *) "via", (gchar *) "cbp7" },
+ { (gchar *) "fusion", (gchar *) "2770p" },
+ { NULL, NULL } };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_VIA,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_PRODUCT_STRINGS, product_strings,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_REQUIRED_QCDM, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_via_init (MMPluginVia *self)
+{
+}
+
+static void
+mm_plugin_via_class_init (MMPluginViaClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/via/mm-plugin-via.h b/src/plugins/via/mm-plugin-via.h
new file mode 100644
index 00000000..68d8c5f6
--- /dev/null
+++ b/src/plugins/via/mm-plugin-via.h
@@ -0,0 +1,46 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ */
+
+#ifndef MM_PLUGIN_VIA_H
+#define MM_PLUGIN_VIA_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_VIA (mm_plugin_via_get_type ())
+#define MM_PLUGIN_VIA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_VIA, MMPluginVia))
+#define MM_PLUGIN_VIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_VIA, MMPluginViaClass))
+#define MM_IS_PLUGIN_VIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_VIA))
+#define MM_IS_PLUGIN_VIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_VIA))
+#define MM_PLUGIN_VIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_VIA, MMPluginViaClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginVia;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginViaClass;
+
+GType mm_plugin_via_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_VIA_H */
diff --git a/src/plugins/wavecom/mm-broadband-modem-wavecom.c b/src/plugins/wavecom/mm-broadband-modem-wavecom.c
new file mode 100644
index 00000000..521e72de
--- /dev/null
+++ b/src/plugins/wavecom/mm-broadband-modem-wavecom.c
@@ -0,0 +1,1320 @@
+/* -*- 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) 2011 Ammonit Measurement GmbH
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "ModemManager.h"
+#include "mm-log-object.h"
+#include "mm-serial-parsers.h"
+#include "mm-modem-helpers.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-base-modem-at.h"
+#include "mm-broadband-modem-wavecom.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemWavecom, mm_broadband_modem_wavecom, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init))
+
+#define WAVECOM_MS_CLASS_CC_IDSTR "\"CC\""
+#define WAVECOM_MS_CLASS_CG_IDSTR "\"CG\""
+#define WAVECOM_MS_CLASS_B_IDSTR "\"B\""
+#define WAVECOM_MS_CLASS_A_IDSTR "\"A\""
+
+/* Setup relationship between 2G bands in the modem (identified by a
+ * single digit in ASCII) and the bitmask in ModemManager. */
+typedef struct {
+ gchar wavecom_band;
+ guint n_mm_bands;
+ MMModemBand mm_bands[4];
+} WavecomBand2G;
+static const WavecomBand2G bands_2g[] = {
+ { '0', 1, { MM_MODEM_BAND_G850, 0, 0, 0 }},
+ { '1', 1, { MM_MODEM_BAND_EGSM, 0, 0, 0 }},
+ { '2', 1, { MM_MODEM_BAND_DCS, 0, 0, 0 }},
+ { '3', 1, { MM_MODEM_BAND_PCS, 0, 0, 0 }},
+ { '4', 2, { MM_MODEM_BAND_G850, MM_MODEM_BAND_PCS, 0, 0 }},
+ { '5', 2, { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, 0, 0 }},
+ { '6', 2, { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_PCS, 0, 0 }},
+ { '7', 4, { MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS, MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM }}
+};
+
+/* Setup relationship between the 3G band bitmask in the modem and the bitmask
+ * in ModemManager. */
+typedef struct {
+ guint32 wavecom_band_flag;
+ MMModemBand mm_band;
+} WavecomBand3G;
+static const WavecomBand3G bands_3g[] = {
+ { (1 << 0), MM_MODEM_BAND_UTRAN_1 },
+ { (1 << 1), MM_MODEM_BAND_UTRAN_2 },
+ { (1 << 2), MM_MODEM_BAND_UTRAN_3 },
+ { (1 << 3), MM_MODEM_BAND_UTRAN_4 },
+ { (1 << 4), MM_MODEM_BAND_UTRAN_5 },
+ { (1 << 5), MM_MODEM_BAND_UTRAN_6 },
+ { (1 << 6), MM_MODEM_BAND_UTRAN_7 },
+ { (1 << 7), MM_MODEM_BAND_UTRAN_8 },
+ { (1 << 8), MM_MODEM_BAND_UTRAN_9 }
+};
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+supported_ms_classes_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GArray *all;
+ GArray *combinations;
+ GArray *filtered;
+ const gchar *response;
+ GError *error = NULL;
+ MMModemModeCombination mode;
+ MMModemMode mode_all;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ response = mm_strip_tag (response, "+CGCLASS:");
+ mode_all = MM_MODEM_MODE_NONE;
+ if (strstr (response, WAVECOM_MS_CLASS_A_IDSTR))
+ mode_all |= MM_MODEM_MODE_3G;
+ if (strstr (response, WAVECOM_MS_CLASS_B_IDSTR))
+ mode_all |= (MM_MODEM_MODE_2G | MM_MODEM_MODE_CS);
+ if (strstr (response, WAVECOM_MS_CLASS_CG_IDSTR))
+ mode_all |= MM_MODEM_MODE_2G;
+ if (strstr (response, WAVECOM_MS_CLASS_CC_IDSTR))
+ mode_all |= MM_MODEM_MODE_CS;
+
+ /* If none received, error */
+ if (mode_all == MM_MODEM_MODE_NONE) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't get supported mobile station classes: '%s'",
+ response);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Build ALL mask */
+ all = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
+ mode.allowed = mode_all;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (all, mode);
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 7);
+ /* CS only */
+ mode.allowed = MM_MODEM_MODE_CS;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* CS and 2G */
+ mode.allowed = (MM_MODEM_MODE_CS | MM_MODEM_MODE_2G);
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ /* 3G only */
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G, 2G preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G, 3G preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+
+ /* Filter out those unsupported modes */
+ filtered = mm_filter_supported_modes (all, combinations, self);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_supported_modes (MMIfaceModem *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),
+ "+CGCLASS=?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)supported_ms_classes_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load initial allowed/preferred modes (Modem interface) */
+
+typedef struct {
+ MMModemMode allowed;
+ MMModemMode preferred;
+} LoadCurrentModesResult;
+
+static gboolean
+load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ g_autofree LoadCurrentModesResult *result = NULL;
+
+ result = g_task_propagate_pointer (G_TASK (res), error);
+ if (!result)
+ return FALSE;
+
+ *allowed = result->allowed;
+ *preferred = result->preferred;
+ return TRUE;
+}
+
+static void
+wwsm_read_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autofree LoadCurrentModesResult *result = NULL;
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ result = g_new0 (LoadCurrentModesResult, 1);
+ result->allowed = MM_MODEM_MODE_NONE;
+ result->preferred = MM_MODEM_MODE_NONE;
+
+ /* Possible responses:
+ * +WWSM: 0 (2G only)
+ * +WWSM: 1 (3G only)
+ * +WWSM: 2,0 (Any)
+ * +WWSM: 2,1 (2G preferred)
+ * +WWSM: 2,2 (3G preferred)
+ */
+ r = g_regex_new ("\\r\\n\\+WWSM: ([0-2])(,([0-2]))?.*$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (g_regex_match (r, response, 0, &match_info)) {
+ guint allowed = 0;
+
+ if (mm_get_uint_from_match_info (match_info, 1, &allowed)) {
+ switch (allowed) {
+ case 0:
+ result->allowed = MM_MODEM_MODE_2G;
+ result->preferred = MM_MODEM_MODE_NONE;
+ break;
+ case 1:
+ result->allowed = MM_MODEM_MODE_3G;
+ result->preferred = MM_MODEM_MODE_NONE;
+ break;
+ case 2: {
+ guint preferred = 0;
+
+ result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+
+ /* 3, to avoid the comma */
+ if (mm_get_uint_from_match_info (match_info, 3, &preferred)) {
+ switch (preferred) {
+ case 0:
+ result->preferred = MM_MODEM_MODE_NONE;
+ break;
+ case 1:
+ result->preferred = MM_MODEM_MODE_2G;
+ break;
+ case 2:
+ result->preferred = MM_MODEM_MODE_3G;
+ break;
+ default:
+ g_warn_if_reached ();
+ break;
+ }
+ }
+ break;
+ }
+ default:
+ g_warn_if_reached ();
+ break;
+ }
+ }
+ }
+
+ if (result->allowed == MM_MODEM_MODE_NONE)
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unknown wireless data service reply: '%s'",
+ response);
+ else
+ g_task_return_pointer (task, g_steal_pointer (&result), g_free);
+ g_object_unref (task);
+}
+
+static void
+current_ms_class_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autofree LoadCurrentModesResult *result = NULL;
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ response = mm_strip_tag (response, "+CGCLASS:");
+
+ if (strncmp (response,
+ WAVECOM_MS_CLASS_A_IDSTR,
+ strlen (WAVECOM_MS_CLASS_A_IDSTR)) == 0) {
+ mm_obj_dbg (self, "configured as a Class A mobile station");
+ /* For 3G devices, query WWSM status */
+ mm_base_modem_at_command (self,
+ "+WWSM?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)wwsm_read_ready,
+ task);
+ return;
+ }
+
+ result = g_new0 (LoadCurrentModesResult, 1);
+ result->allowed = MM_MODEM_MODE_NONE;
+ result->preferred = MM_MODEM_MODE_NONE;
+
+ if (strncmp (response,
+ WAVECOM_MS_CLASS_B_IDSTR,
+ strlen (WAVECOM_MS_CLASS_B_IDSTR)) == 0) {
+ mm_obj_dbg (self, "configured as a Class B mobile station");
+ result->allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_CS);
+ result->preferred = MM_MODEM_MODE_2G;
+ } else if (strncmp (response,
+ WAVECOM_MS_CLASS_CG_IDSTR,
+ strlen (WAVECOM_MS_CLASS_CG_IDSTR)) == 0) {
+ mm_obj_dbg (self, "configured as a Class CG mobile station");
+ result->allowed = MM_MODEM_MODE_2G;
+ result->preferred = MM_MODEM_MODE_NONE;
+ } else if (strncmp (response,
+ WAVECOM_MS_CLASS_CC_IDSTR,
+ strlen (WAVECOM_MS_CLASS_CC_IDSTR)) == 0) {
+ mm_obj_dbg (self, "configured as a Class CC mobile station");
+ result->allowed = MM_MODEM_MODE_CS;
+ result->preferred = MM_MODEM_MODE_NONE;
+ }
+
+ if (result->allowed == MM_MODEM_MODE_NONE)
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unknown mobile station class: '%s'",
+ response);
+ else
+ g_task_return_pointer (task, g_steal_pointer (&result), g_free);
+ g_object_unref (task);
+}
+
+static void
+load_current_modes (MMIfaceModem *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),
+ "+CGCLASS?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)current_ms_class_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Set allowed modes (Modem interface) */
+
+typedef struct {
+ gchar *cgclass_command;
+ gchar *wwsm_command;
+} SetCurrentModesContext;
+
+static void
+set_current_modes_context_free (SetCurrentModesContext *ctx)
+{
+ g_free (ctx->cgclass_command);
+ g_free (ctx->wwsm_command);
+ g_free (ctx);
+}
+
+static gboolean
+set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+wwsm_update_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (self, res, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+cgclass_update_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetCurrentModesContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ if (!ctx->wwsm_command) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ ctx->wwsm_command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)wwsm_update_ready,
+ task);
+}
+
+static void
+set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ SetCurrentModesContext *ctx;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ ctx = g_new0 (SetCurrentModesContext, 1);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) set_current_modes_context_free);
+
+ /* Handle ANY/NONE */
+ if (allowed == MM_MODEM_MODE_ANY && preferred == MM_MODEM_MODE_NONE) {
+ if (mm_iface_modem_is_3g (self)) {
+ allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ preferred = MM_MODEM_MODE_NONE;
+ } else {
+ allowed = (MM_MODEM_MODE_CS | MM_MODEM_MODE_2G);
+ preferred = MM_MODEM_MODE_2G;
+ }
+ }
+
+ if (allowed == MM_MODEM_MODE_CS)
+ ctx->cgclass_command = g_strdup ("+CGCLASS=" WAVECOM_MS_CLASS_CC_IDSTR);
+ else if (allowed == MM_MODEM_MODE_2G)
+ ctx->cgclass_command = g_strdup ("+CGCLASS=" WAVECOM_MS_CLASS_CG_IDSTR);
+ else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_CS) &&
+ preferred == MM_MODEM_MODE_2G)
+ ctx->cgclass_command = g_strdup ("+CGCLASS=" WAVECOM_MS_CLASS_B_IDSTR);
+ else if (allowed & MM_MODEM_MODE_3G) {
+ if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) {
+ if (preferred == MM_MODEM_MODE_2G)
+ ctx->wwsm_command = g_strdup ("+WWSM=2,1");
+ else if (preferred == MM_MODEM_MODE_3G)
+ ctx->wwsm_command = g_strdup ("+WWSM=2,2");
+ else if (preferred == MM_MODEM_MODE_NONE)
+ ctx->wwsm_command = g_strdup ("+WWSM=2,0");
+ } else if (allowed == MM_MODEM_MODE_3G)
+ ctx->wwsm_command = g_strdup ("+WWSM=1");
+
+ if (ctx->wwsm_command)
+ ctx->cgclass_command = g_strdup ("+CGCLASS=" WAVECOM_MS_CLASS_A_IDSTR);
+ }
+
+ if (!ctx->cgclass_command) {
+ g_autofree gchar *allowed_str = NULL;
+ g_autofree gchar *preferred_str = NULL;
+
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Requested mode (allowed: '%s', preferred: '%s') not "
+ "supported by the modem.",
+ allowed_str, preferred_str);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ ctx->cgclass_command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)cgclass_update_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Load supported bands (Modem interface) */
+
+static GArray *
+load_supported_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_supported_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ GArray *bands;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* We do assume that we already know if the modem is 2G-only, 3G-only or
+ * 2G+3G. This is checked quite before trying to load supported bands. */
+
+#define _g_array_insert_enum(array,index,type,val) do { \
+ type aux = (type)val; \
+ g_array_insert_val (array, index, aux); \
+ } while (0)
+
+ /* Add 3G-specific bands */
+ if (mm_iface_modem_is_3g (self)) {
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 10);
+ _g_array_insert_enum (bands, 0, MMModemBand, MM_MODEM_BAND_UTRAN_1);
+ _g_array_insert_enum (bands, 1, MMModemBand, MM_MODEM_BAND_UTRAN_2);
+ _g_array_insert_enum (bands, 2, MMModemBand, MM_MODEM_BAND_UTRAN_3);
+ _g_array_insert_enum (bands, 3, MMModemBand, MM_MODEM_BAND_UTRAN_4);
+ _g_array_insert_enum (bands, 4, MMModemBand, MM_MODEM_BAND_UTRAN_5);
+ _g_array_insert_enum (bands, 5, MMModemBand, MM_MODEM_BAND_UTRAN_6);
+ _g_array_insert_enum (bands, 6, MMModemBand, MM_MODEM_BAND_UTRAN_7);
+ _g_array_insert_enum (bands, 7, MMModemBand, MM_MODEM_BAND_UTRAN_8);
+ _g_array_insert_enum (bands, 8, MMModemBand, MM_MODEM_BAND_UTRAN_9);
+ }
+ /* Add 2G-specific bands */
+ else {
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 4);
+ _g_array_insert_enum (bands, 0, MMModemBand, MM_MODEM_BAND_EGSM);
+ _g_array_insert_enum (bands, 1, MMModemBand, MM_MODEM_BAND_DCS);
+ _g_array_insert_enum (bands, 2, MMModemBand, MM_MODEM_BAND_PCS);
+ _g_array_insert_enum (bands, 3, MMModemBand, MM_MODEM_BAND_G850);
+ }
+
+ g_task_return_pointer (task, bands, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Load current bands (Modem interface) */
+
+static GArray *
+load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+get_2g_band_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ const gchar *p;
+ GError *error = NULL;
+ GArray *bands_array = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ p = mm_strip_tag (response, "+WMBS:");
+ if (p) {
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (bands_2g); i++) {
+ if (bands_2g[i].wavecom_band == *p) {
+ guint j;
+
+ if (G_UNLIKELY (!bands_array))
+ bands_array = g_array_new (FALSE, FALSE, sizeof (MMModemBand));
+
+ for (j = 0; j < bands_2g[i].n_mm_bands; j++)
+ g_array_append_val (bands_array, bands_2g[i].mm_bands[j]);
+ }
+ }
+ }
+
+ if (!bands_array)
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse current bands reply: '%s'",
+ response);
+ else
+ g_task_return_pointer (task, bands_array, (GDestroyNotify)g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+get_3g_band_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ const gchar *p;
+ GError *error = NULL;
+ GArray *bands_array = NULL;
+ guint32 wavecom_band;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Example reply:
+ * AT+WUBS? -->
+ * <-- +WUBS: "3",1
+ * <-- OK
+ * The "3" meaning here Band I and II are selected.
+ */
+
+ p = mm_strip_tag (response, "+WUBS:");
+ if (*p == '"')
+ p++;
+
+ wavecom_band = atoi (p);
+ if (wavecom_band > 0) {
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (bands_3g); i++) {
+ if (bands_3g[i].wavecom_band_flag & wavecom_band) {
+ if (G_UNLIKELY (!bands_array))
+ bands_array = g_array_new (FALSE, FALSE, sizeof (MMModemBand));
+ g_array_append_val (bands_array, bands_3g[i].mm_band);
+ }
+ }
+ }
+
+ if (!bands_array)
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse current bands reply: '%s'",
+ response);
+ else
+ g_task_return_pointer (task, bands_array, (GDestroyNotify)g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_current_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (mm_iface_modem_is_3g (self))
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT+WUBS?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)get_3g_band_ready,
+ task);
+ else
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "AT+WMBS?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)get_2g_band_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Set current_bands (Modem interface) */
+
+static gboolean
+set_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+wmbs_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+set_bands_3g (GTask *task,
+ GArray *bands_array)
+{
+ MMBroadbandModemWavecom *self;
+ guint wavecom_band = 0;
+ guint i;
+ g_autoptr(GArray) bands_array_final = NULL;
+ g_autofree gchar *cmd = NULL;
+
+ self = g_task_get_source_object (task);
+
+ /* The special case of ANY should be treated separately. */
+ if (bands_array->len == 1 &&
+ g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
+ /* We build an array with all bands to set; so that we use the same
+ * logic to build the cinterion_band, and so that we can log the list of
+ * bands being set properly */
+ bands_array_final = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), G_N_ELEMENTS (bands_3g));
+ for (i = 0; i < G_N_ELEMENTS (bands_3g); i++)
+ g_array_append_val (bands_array_final, bands_3g[i].mm_band);
+ } else
+ bands_array_final = g_array_ref (bands_array);
+
+ for (i = 0; i < G_N_ELEMENTS (bands_3g); i++) {
+ guint j;
+
+ for (j = 0; j < bands_array_final->len; j++) {
+ if (g_array_index (bands_array_final, MMModemBand, j) == bands_3g[i].mm_band) {
+ wavecom_band |= bands_3g[i].wavecom_band_flag;
+ break;
+ }
+ }
+ }
+
+ if (wavecom_band == 0) {
+ g_autofree gchar *bands_string = NULL;
+
+ bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands_array_final->data, bands_array_final->len);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "The given band combination is not supported: '%s'",
+ bands_string);
+ g_object_unref (task);
+ return;
+ }
+
+ cmd = g_strdup_printf ("+WMBS=\"%u\",1", wavecom_band);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)wmbs_set_ready,
+ task);
+}
+
+static void
+set_bands_2g (GTask *task,
+ GArray *bands_array)
+{
+ MMBroadbandModemWavecom *self;
+ gchar wavecom_band = '\0';
+ guint i;
+ g_autofree gchar *cmd = NULL;
+ g_autoptr(GArray) bands_array_final = NULL;
+
+ self = g_task_get_source_object (task);
+
+ /* If the iface properly checked the given list against the supported bands,
+ * it's not possible to get an array longer than 4 here. */
+ g_assert (bands_array->len <= 4);
+
+ /* The special case of ANY should be treated separately. */
+ if (bands_array->len == 1 &&
+ g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
+ const WavecomBand2G *all;
+
+ /* All bands is the last element in our 2G bands array */
+ all = &bands_2g[G_N_ELEMENTS (bands_2g) - 1];
+
+ /* We build an array with all bands to set; so that we use the same
+ * logic to build the cinterion_band, and so that we can log the list of
+ * bands being set properly */
+ bands_array_final = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 4);
+ g_array_append_vals (bands_array_final, all->mm_bands, all->n_mm_bands);
+ } else
+ bands_array_final = g_array_ref (bands_array);
+
+ for (i = 0; wavecom_band == '\0' && i < G_N_ELEMENTS (bands_2g); i++) {
+ g_autoptr(GArray) supported_combination = NULL;
+
+ supported_combination = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), bands_2g[i].n_mm_bands);
+ g_array_append_vals (supported_combination, bands_2g[i].mm_bands, bands_2g[i].n_mm_bands);
+
+ /* Check if the given array is exactly one of the supported combinations */
+ if (mm_common_bands_garray_cmp (bands_array_final, supported_combination)) {
+ wavecom_band = bands_2g[i].wavecom_band;
+ break;
+ }
+ }
+
+ if (wavecom_band == '\0') {
+ g_autofree gchar *bands_string = NULL;
+
+ bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands_array_final->data, bands_array_final->len);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "The given band combination is not supported: '%s'",
+ bands_string);
+ g_object_unref (task);
+ return;
+ }
+
+ cmd = g_strdup_printf ("+WMBS=%c,1", wavecom_band);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)wmbs_set_ready,
+ task);
+}
+
+static void
+set_current_bands (MMIfaceModem *self,
+ GArray *bands_array,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ /* The bands that we get here are previously validated by the interface, and
+ * that means that ALL the bands given here were also given in the list of
+ * supported bands. BUT BUT, that doesn't mean that the exact list of bands
+ * will end up being valid, as not all combinations are possible. E.g,
+ * Wavecom modems supporting only 2G have specific combinations allowed.
+ */
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (mm_iface_modem_is_3g (self))
+ set_bands_3g (task, bands_array);
+ else
+ set_bands_2g (task, bands_array);
+}
+
+/*****************************************************************************/
+/* Load access technologies (Modem interface) */
+
+static gboolean
+load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ GError **error)
+{
+ MMModemAccessTechnology act;
+ const gchar *p;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return FALSE;
+
+ act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ p = mm_strip_tag (response, "+WGPRSIND:");
+ if (p) {
+ switch (*p) {
+ case '1':
+ /* GPRS only */
+ act = MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
+ break;
+ case '2':
+ /* EGPRS/EDGE supported */
+ act = MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
+ break;
+ case '3':
+ /* 3G R99 supported */
+ act = MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ break;
+ case '4':
+ /* HSDPA supported */
+ act = MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
+ break;
+ case '5':
+ /* HSUPA supported */
+ act = MM_MODEM_ACCESS_TECHNOLOGY_HSUPA;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse access technologies result: '%s'",
+ response);
+ return FALSE;
+ }
+
+ /* We are reporting ALL 3GPP access technologies here */
+ *access_technologies = act;
+ *mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK;
+ return TRUE;
+}
+
+static void
+load_access_technologies (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+WGPRS=9,2",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Register in network (3GPP interface) */
+
+static gboolean
+register_in_network_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_registration_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->register_in_network_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+run_parent_registration (GTask *task)
+{
+ MMBroadbandModemWavecom *self;
+ const gchar *operator_id;
+
+ self = g_task_get_source_object (task);
+ operator_id = g_task_get_task_data (task);
+
+ iface_modem_3gpp_parent->register_in_network (
+ MM_IFACE_MODEM_3GPP (self),
+ operator_id,
+ g_task_get_cancellable (task),
+ (GAsyncReadyCallback)parent_registration_ready,
+ task);
+}
+
+static gboolean
+parse_network_registration_mode (const gchar *reply,
+ guint *mode)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+
+ g_assert (mode != NULL);
+
+ if (!reply)
+ return FALSE;
+
+ r = g_regex_new ("\\+COPS:\\s*(\\d)", G_REGEX_UNGREEDY, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match (r, reply, 0, &match_info);
+
+ return (g_match_info_matches (match_info) && mm_get_uint_from_match_info (match_info, 1, mode));
+}
+
+static void
+cops_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ guint mode;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ if (!parse_network_registration_mode (response, &mode)) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse current network registration mode: '%s'",
+ response);
+ g_object_unref (task);
+ return;
+ }
+
+ /* If the modem is not configured for automatic registration, run parent */
+ if (mode != 0) {
+ run_parent_registration (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "device is already in automatic registration mode, not requesting it again");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+register_in_network (MMIfaceModem3gpp *self,
+ const gchar *operator_id,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ /* Store operator id as task data */
+ g_task_set_task_data (task, g_strdup (operator_id), g_free);
+
+ /* If requesting automatic registration, we first need to query what the
+ * current mode is. We must NOT send +COPS=0 if it already is in 0 mode,
+ * or the device will get stuck. */
+ if (operator_id == NULL || operator_id[0] == '\0') {
+ /* Check which is the current operator selection status */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+COPS?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)cops_ready,
+ task);
+ return;
+ }
+
+ /* Otherwise, run parent's implementation right away */
+ run_parent_registration (task);
+}
+
+/*****************************************************************************/
+/* After SIM unlock (Modem interface) */
+
+static gboolean
+modem_after_sim_unlock_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static gboolean
+after_sim_unlock_wait_cb (GTask *task)
+{
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+modem_after_sim_unlock (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ /* A short wait is necessary for SIM to become ready, otherwise reloading
+ * facility lock states may fail with a +CME ERROR: 515 error.
+ */
+ task = g_task_new (self, NULL, callback, user_data);
+ g_timeout_add_seconds (5, (GSourceFunc)after_sim_unlock_wait_cb, task);
+}
+
+/*****************************************************************************/
+/* Modem power up (Modem interface) */
+
+static gboolean
+modem_power_up_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_power_up (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_obj_warn (self, "not in full functionality status, power-up command is needed");
+ mm_obj_warn (self, "the device maybe rebooted");
+
+ /* Try to go to full functionality mode without rebooting the system.
+ * Works well if we previously switched off the power with CFUN=4
+ */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=1,0",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Modem power down (Modem interface) */
+
+static gboolean
+modem_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_power_down (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Use AT+CFUN=4 for power down. It will stop the RF (IMSI detach), and
+ * keeps access to the SIM */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=4",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Modem power down (Modem interface) */
+
+static gboolean
+modem_power_off_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_power_off (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CPOF=1",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ gpointer parser;
+ MMPortSerialAt *primary;
+ g_autoptr(GRegex) regex = NULL;
+
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_wavecom_parent_class)->setup_ports (self);
+
+ /* Set 9600 baudrate by default in the AT port */
+ mm_obj_dbg (self, "baudrate will be set to 9600 bps...");
+ primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ if (!primary)
+ return;
+
+ /* AT+CPIN? replies will never have an OK appended */
+ parser = mm_serial_parser_v1_new ();
+ regex = g_regex_new ("\\r\\n\\+CPIN: .*\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE,
+ 0, NULL);
+ mm_serial_parser_v1_set_custom_regex (parser, regex, NULL);
+
+ mm_port_serial_at_set_response_parser (MM_PORT_SERIAL_AT (primary),
+ mm_serial_parser_v1_parse,
+ parser,
+ mm_serial_parser_v1_destroy);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemWavecom *
+mm_broadband_modem_wavecom_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_WAVECOM,
+ 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,
+ NULL);
+}
+
+static void
+mm_broadband_modem_wavecom_init (MMBroadbandModemWavecom *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = load_current_modes;
+ iface->load_current_modes_finish = load_current_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = set_current_modes_finish;
+ iface->load_supported_bands = load_supported_bands;
+ iface->load_supported_bands_finish = load_supported_bands_finish;
+ iface->load_current_bands = load_current_bands;
+ iface->load_current_bands_finish = load_current_bands_finish;
+ iface->set_current_bands = set_current_bands;
+ iface->set_current_bands_finish = set_current_bands_finish;
+ iface->load_access_technologies = load_access_technologies;
+ iface->load_access_technologies_finish = load_access_technologies_finish;
+ iface->modem_after_sim_unlock = modem_after_sim_unlock;
+ iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish;
+ iface->modem_power_up = modem_power_up;
+ iface->modem_power_up_finish = modem_power_up_finish;
+ iface->modem_power_down = modem_power_down;
+ iface->modem_power_down_finish = modem_power_down_finish;
+ iface->modem_power_off = modem_power_off;
+ iface->modem_power_off_finish = modem_power_off_finish;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->register_in_network = register_in_network;
+ iface->register_in_network_finish = register_in_network_finish;
+}
+
+static void
+mm_broadband_modem_wavecom_class_init (MMBroadbandModemWavecomClass *klass)
+{
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/wavecom/mm-broadband-modem-wavecom.h b/src/plugins/wavecom/mm-broadband-modem-wavecom.h
new file mode 100644
index 00000000..89bdbde8
--- /dev/null
+++ b/src/plugins/wavecom/mm-broadband-modem-wavecom.h
@@ -0,0 +1,50 @@
+/* -*- 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) 2011 Ammonit Measurement GmbH
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ *
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#ifndef MM_BROADBAND_MODEM_WAVECOM_H
+#define MM_BROADBAND_MODEM_WAVECOM_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_WAVECOM (mm_broadband_modem_wavecom_get_type ())
+#define MM_BROADBAND_MODEM_WAVECOM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_WAVECOM, MMBroadbandModemWavecom))
+#define MM_BROADBAND_MODEM_WAVECOM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_WAVECOM, MMBroadbandModemWavecomClass))
+#define MM_IS_BROADBAND_MODEM_WAVECOM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_WAVECOM))
+#define MM_IS_BROADBAND_MODEM_WAVECOM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_WAVECOM))
+#define MM_BROADBAND_MODEM_WAVECOM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_WAVECOM, MMBroadbandModemWavecomClass))
+
+typedef struct _MMBroadbandModemWavecom MMBroadbandModemWavecom;
+typedef struct _MMBroadbandModemWavecomClass MMBroadbandModemWavecomClass;
+
+struct _MMBroadbandModemWavecom {
+ MMBroadbandModem parent;
+};
+
+struct _MMBroadbandModemWavecomClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_wavecom_get_type (void);
+
+MMBroadbandModemWavecom *mm_broadband_modem_wavecom_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_WAVECOM_H */
diff --git a/src/plugins/wavecom/mm-plugin-wavecom.c b/src/plugins/wavecom/mm-plugin-wavecom.c
new file mode 100644
index 00000000..8e3f9d2c
--- /dev/null
+++ b/src/plugins/wavecom/mm-plugin-wavecom.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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2011 Ammonit Measurement GmbH
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ *
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-wavecom.h"
+#include "mm-broadband-modem-wavecom.h"
+
+G_DEFINE_TYPE (MMPluginWavecom, mm_plugin_wavecom, 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)
+{
+ return MM_BASE_MODEM (mm_broadband_modem_wavecom_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", NULL };
+ static const guint16 vendor_ids[] = { 0x114f, 0 };
+ static const gchar *forbidden_drivers[] = { "qcserial", NULL };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_WAVECOM,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_FORBIDDEN_DRIVERS, forbidden_drivers,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_wavecom_init (MMPluginWavecom *self)
+{
+}
+
+static void
+mm_plugin_wavecom_class_init (MMPluginWavecomClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/wavecom/mm-plugin-wavecom.h b/src/plugins/wavecom/mm-plugin-wavecom.h
new file mode 100644
index 00000000..c1d76309
--- /dev/null
+++ b/src/plugins/wavecom/mm-plugin-wavecom.h
@@ -0,0 +1,49 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2011 Ammonit Measurement GmbH
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ *
+ * Author: Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#ifndef MM_PLUGIN_WAVECOM_H
+#define MM_PLUGIN_WAVECOM_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_WAVECOM (mm_plugin_wavecom_get_type ())
+#define MM_PLUGIN_WAVECOM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_WAVECOM, MMPluginWavecom))
+#define MM_PLUGIN_WAVECOM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_WAVECOM, MMPluginWavecomClass))
+#define MM_IS_PLUGIN_WAVECOM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_WAVECOM))
+#define MM_IS_PLUGIN_WAVECOM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_WAVECOM))
+#define MM_PLUGIN_WAVECOM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_WAVECOM, MMPluginWavecomClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginWavecom;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginWavecomClass;
+
+GType mm_plugin_wavecom_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_WAVECOM_H */
diff --git a/src/plugins/x22x/77-mm-x22x-port-types.rules b/src/plugins/x22x/77-mm-x22x-port-types.rules
new file mode 100644
index 00000000..40e7677a
--- /dev/null
+++ b/src/plugins/x22x/77-mm-x22x-port-types.rules
@@ -0,0 +1,68 @@
+# do not edit this file, it will be overwritten on update
+
+# Alcatel One Touch X220D
+# Alcatel One Touch X200
+#
+# These values were scraped from the X220D's Windows .inf files. jrdmdm.inf
+# lists the actual command and data (ie PPP) ports, while jrdser.inf lists the
+# aux ports that may be either AT-capable or not but cannot be used for PPP.
+
+
+ACTION!="add|change|move|bind", GOTO="mm_x22x_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1bbb", GOTO="mm_x22x_generic_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="0b3c", GOTO="mm_x22x_olivetti_vendorcheck"
+GOTO="mm_x22x_port_types_end"
+
+# Generic JRD devices ---------------------------
+
+LABEL="mm_x22x_generic_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Alcatel X200
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0000", ENV{ID_MM_X22X_TAGGED}="1"
+
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0017", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0017", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0017", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0017", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0017", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="0017", ENV{ID_MM_X22X_TAGGED}="1"
+
+# Archos G9
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="00B7", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="00B7", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="00B7", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_X22X_PORT_TYPE_NMEA}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="00B7", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_X22X_PORT_TYPE_VOICE}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="00B7", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="00B7", ENV{ID_MM_X22X_TAGGED}="1"
+
+# Alcaltel X602D
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="022c", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="022c", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="022c", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="022c", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bbb", ATTRS{idProduct}=="022c", ENV{ID_MM_X22X_TAGGED}="1"
+
+GOTO="mm_x22x_port_types_end"
+
+# Olivetti devices ---------------------------
+
+LABEL="mm_x22x_olivetti_vendorcheck"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+# Olicard 200
+ATTRS{idVendor}=="0b3c", ATTRS{idProduct}=="c005", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="0b3c", ATTRS{idProduct}=="c005", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="0b3c", ATTRS{idProduct}=="c005", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="0b3c", ATTRS{idProduct}=="c005", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="0b3c", ATTRS{idProduct}=="c005", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="0b3c", ATTRS{idProduct}=="c005", ENV{.MM_USBIFNUM}=="06", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="0b3c", ATTRS{idProduct}=="c005", ENV{ID_MM_X22X_TAGGED}="1"
+
+GOTO="mm_x22x_port_types_end"
+
+LABEL="mm_x22x_port_types_end"
diff --git a/src/plugins/x22x/mm-broadband-modem-x22x.c b/src/plugins/x22x/mm-broadband-modem-x22x.c
new file mode 100644
index 00000000..1ce32f57
--- /dev/null
+++ b/src/plugins/x22x/mm-broadband-modem-x22x.c
@@ -0,0 +1,428 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log.h"
+#include "mm-errors-types.h"
+#include "mm-modem-helpers.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem.h"
+#include "mm-broadband-modem-x22x.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+
+static MMIfaceModem *iface_modem_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemX22x, mm_broadband_modem_x22x, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init))
+
+struct _MMBroadbandModemX22xPrivate {
+ GRegex *mode_regex;
+ GRegex *sysinfo_regex;
+ GRegex *specc_regex;
+ GRegex *sperror_regex;
+};
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_load_supported_modes_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ GArray *all;
+ GArray *combinations;
+ GArray *filtered;
+ MMModemModeCombination mode;
+
+ all = iface_modem_parent->load_supported_modes_finish (self, res, &error);
+ if (!all) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+ /* Build list of combinations for 3GPP devices */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3);
+
+ /* 2G only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 3G only */
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ /* Filter out those unsupported modes */
+ filtered = mm_filter_supported_modes (all, combinations, self);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Run parent's loading */
+ iface_modem_parent->load_supported_modes (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_supported_modes_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Load initial allowed/preferred modes (Modem interface) */
+
+static gboolean
+load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ const gchar *response;
+ gchar *str;
+ gint mode = -1;
+ GError *match_error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return FALSE;
+
+ r = g_regex_new ("\\+SYSSEL:\\s*(\\d+),(\\d+),(\\d+),(\\d+)", G_REGEX_UNGREEDY, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &match_error)) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ } else {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't match +SYSSEL reply: %s", response);
+ }
+ return FALSE;
+ }
+
+ str = g_match_info_fetch (match_info, 3);
+ mode = atoi (str);
+ g_free (str);
+
+ switch (mode) {
+ case 0:
+ *allowed = MM_MODEM_MODE_ANY;
+ *preferred = MM_MODEM_MODE_NONE;
+ return TRUE;
+ case 1:
+ *allowed = MM_MODEM_MODE_2G;
+ *preferred = MM_MODEM_MODE_NONE;
+ return TRUE;
+ case 2:
+ *allowed = MM_MODEM_MODE_3G;
+ *preferred = MM_MODEM_MODE_NONE;
+ return TRUE;
+ default:
+ break;
+ }
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse mode/tech response: Unexpected mode '%d'", mode);
+ return FALSE;
+}
+
+static void
+load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+SYSSEL?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Set allowed modes (Modem interface) */
+
+static gboolean
+set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+allowed_mode_update_ready (MMBroadbandModemX22x *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error)
+ /* Let the error be critical. */
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static void
+set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *command;
+ gint syssel = -1;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (allowed == MM_MODEM_MODE_2G)
+ syssel = 1;
+ else if (allowed == MM_MODEM_MODE_3G)
+ syssel = 2;
+ else if (allowed == MM_MODEM_MODE_ANY &&
+ preferred == MM_MODEM_MODE_NONE)
+ syssel = 0;
+ else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G) &&
+ preferred == MM_MODEM_MODE_NONE)
+ syssel = 0;
+ else {
+ gchar *allowed_str;
+ gchar *preferred_str;
+
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Requested mode (allowed: '%s', preferred: '%s') not "
+ "supported by the modem.",
+ allowed_str,
+ preferred_str);
+ g_object_unref (task);
+ g_free (allowed_str);
+ g_free (preferred_str);
+ return;
+ }
+
+ command = g_strdup_printf ("+SYSSEL=,%d,0", syssel);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)allowed_mode_update_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Load access technologies (Modem interface) */
+
+static gboolean
+load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ GError **error)
+{
+ const gchar *result;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!result)
+ return FALSE;
+
+ result = mm_strip_tag (result, "+SSND:");
+ *access_technologies = mm_string_to_access_tech (result);
+ *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY;
+ return TRUE;
+}
+
+static void
+load_access_technologies (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+SSND?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+set_ignored_unsolicited_events_handlers (MMBroadbandModemX22x *self)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ 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;
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->mode_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->sysinfo_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->specc_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ self->priv->sperror_regex,
+ NULL, NULL, NULL);
+ }
+}
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_x22x_parent_class)->setup_ports (self);
+
+ /* Unsolicited messages to always ignore */
+ set_ignored_unsolicited_events_handlers (MM_BROADBAND_MODEM_X22X (self));
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemX22x *
+mm_broadband_modem_x22x_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_X22X,
+ 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,
+ NULL);
+}
+
+static void
+mm_broadband_modem_x22x_init (MMBroadbandModemX22x *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_BROADBAND_MODEM_X22X,
+ MMBroadbandModemX22xPrivate);
+
+ self->priv->mode_regex = g_regex_new ("\\r\\n\\^MODE:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->sysinfo_regex = g_regex_new ("\\r\\n\\^SYSINFO:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->specc_regex = g_regex_new ("\\r\\n\\+SPECC\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ self->priv->sperror_regex = g_regex_new ("\\r\\n\\+SPERROR:.+\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemX22x *self = MM_BROADBAND_MODEM_X22X (object);
+
+ g_regex_unref (self->priv->mode_regex);
+ g_regex_unref (self->priv->sysinfo_regex);
+ g_regex_unref (self->priv->specc_regex);
+ g_regex_unref (self->priv->sperror_regex);
+ G_OBJECT_CLASS (mm_broadband_modem_x22x_parent_class)->finalize (object);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_access_technologies = load_access_technologies;
+ iface->load_access_technologies_finish = load_access_technologies_finish;
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = load_current_modes;
+ iface->load_current_modes_finish = load_current_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = set_current_modes_finish;
+}
+
+static void
+mm_broadband_modem_x22x_class_init (MMBroadbandModemX22xClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemX22xPrivate));
+
+ object_class->finalize = finalize;
+
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/x22x/mm-broadband-modem-x22x.h b/src/plugins/x22x/mm-broadband-modem-x22x.h
new file mode 100644
index 00000000..74c2b48a
--- /dev/null
+++ b/src/plugins/x22x/mm-broadband-modem-x22x.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_MODEM_X22X_H
+#define MM_BROADBAND_MODEM_X22X_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_X22X (mm_broadband_modem_x22x_get_type ())
+#define MM_BROADBAND_MODEM_X22X(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_X22X, MMBroadbandModemX22x))
+#define MM_BROADBAND_MODEM_X22X_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_X22X, MMBroadbandModemX22xClass))
+#define MM_IS_BROADBAND_MODEM_X22X(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_X22X))
+#define MM_IS_BROADBAND_MODEM_X22X_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_X22X))
+#define MM_BROADBAND_MODEM_X22X_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_X22X, MMBroadbandModemX22xClass))
+
+typedef struct _MMBroadbandModemX22x MMBroadbandModemX22x;
+typedef struct _MMBroadbandModemX22xClass MMBroadbandModemX22xClass;
+typedef struct _MMBroadbandModemX22xPrivate MMBroadbandModemX22xPrivate;
+
+struct _MMBroadbandModemX22x {
+ MMBroadbandModem parent;
+ MMBroadbandModemX22xPrivate *priv;
+};
+
+struct _MMBroadbandModemX22xClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_x22x_get_type (void);
+
+MMBroadbandModemX22x *mm_broadband_modem_x22x_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_X22X_H */
diff --git a/src/plugins/x22x/mm-plugin-x22x.c b/src/plugins/x22x/mm-plugin-x22x.c
new file mode 100644
index 00000000..7b49cff8
--- /dev/null
+++ b/src/plugins/x22x/mm-plugin-x22x.c
@@ -0,0 +1,255 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-modem-helpers.h"
+#include "mm-plugin-x22x.h"
+#include "mm-broadband-modem-x22x.h"
+
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginX22x, mm_plugin_x22x, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+/* Custom init */
+
+typedef struct {
+ MMPortSerialAt *port;
+ guint retries;
+} X22xCustomInitContext;
+
+static void
+x22x_custom_init_context_free (X22xCustomInitContext *ctx)
+{
+ g_object_unref (ctx->port);
+ g_slice_free (X22xCustomInitContext, ctx);
+}
+
+static gboolean
+x22x_custom_init_finish (MMPortProbe *probe,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void x22x_custom_init_step (GTask *task);
+
+static void
+gmr_ready (MMPortSerialAt *port,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMPortProbe *probe;
+ const gchar *p;
+ const gchar *response;
+ GError *error = NULL;
+
+ probe = g_task_get_source_object (task);
+
+ response = mm_port_serial_at_command_finish (port, res, &error);
+ if (error) {
+ g_error_free (error);
+ /* Just retry... */
+ x22x_custom_init_step (task);
+ return;
+ }
+
+ /* Note the lack of a ':' on the GMR; the X200 doesn't send one */
+ p = mm_strip_tag (response, "AT+GMR");
+ if (p && *p != 'L') {
+ /* X200 modems have a GMR firmware revision that starts with 'L', and
+ * as far as I can tell X060s devices have a revision starting with 'C'.
+ * So use that to determine if the device is an X200, which this plugin
+ * does supports.
+ */
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Not supported with the X22X plugin");
+ } else {
+ mm_obj_dbg (probe, "(X22X) device is supported by this plugin");
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+x22x_custom_init_step (GTask *task)
+{
+ MMPortProbe *probe;
+ X22xCustomInitContext *ctx;
+ GCancellable *cancellable;
+
+ probe = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+ cancellable = g_task_get_cancellable (task);
+
+ /* If cancelled, end */
+ if (g_cancellable_is_cancelled (cancellable)) {
+ mm_obj_dbg (probe, "(X22X) no need to keep on running custom init");
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ if (ctx->retries == 0) {
+ /* In this case, we need the AT command result to decide whether we can
+ * support this modem or not, so really fail if we didn't get it. */
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't get device revision information");
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->retries--;
+ mm_port_serial_at_command (
+ ctx->port,
+ "AT+GMR",
+ 3,
+ FALSE, /* raw */
+ FALSE, /* allow_cached */
+ cancellable,
+ (GAsyncReadyCallback)gmr_ready,
+ task);
+}
+
+static void
+x22x_custom_init (MMPortProbe *probe,
+ MMPortSerialAt *port,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMDevice *device;
+ X22xCustomInitContext *ctx;
+ GTask *task;
+
+ ctx = g_slice_new (X22xCustomInitContext);
+ ctx->port = g_object_ref (port);
+ ctx->retries = 3;
+
+ task = g_task_new (probe, cancellable, callback, user_data);
+ g_task_set_check_cancellable (task, FALSE);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)x22x_custom_init_context_free);
+
+ /* TCT/Alcatel in their infinite wisdom assigned the same USB VID/PID to
+ * the x060s (Longcheer firmware) and the x200 (X22X, this plugin) and thus
+ * we can't tell them apart via udev rules. Worse, they both report the
+ * same +GMM and +GMI, so we're left with just +GMR which is a sketchy way
+ * to tell modems apart. We can't really use X22X-specific commands
+ * like AT+SSND because we're not sure if they work when the SIM PIN has not
+ * been entered yet; many modems have a limited command parser before the
+ * SIM is unlocked.
+ */
+ device = mm_port_probe_peek_device (probe);
+ if (mm_device_get_vendor (device) != 0x1bbb ||
+ mm_device_get_product (device) != 0x0000) {
+ /* If not exactly this vendor/product, just skip */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ x22x_custom_init_step (task);
+}
+
+/*****************************************************************************/
+
+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 X22X modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ return MM_BASE_MODEM (mm_broadband_modem_x22x_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", NULL };
+ /* Vendors: TAMobile and Olivetti */
+ static const guint16 vendor_ids[] = { 0x1bbb, 0x0b3c, 0 };
+ /* Only handle X22X tagged devices here. */
+ static const gchar *udev_tags[] = {
+ "ID_MM_X22X_TAGGED",
+ NULL
+ };
+ static const MMAsyncMethod custom_init = {
+ .async = G_CALLBACK (x22x_custom_init),
+ .finish = G_CALLBACK (x22x_custom_init_finish),
+ };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_X22X,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ MM_PLUGIN_ALLOWED_UDEV_TAGS, udev_tags,
+ MM_PLUGIN_CUSTOM_INIT, &custom_init,
+ NULL));
+}
+
+static void
+mm_plugin_x22x_init (MMPluginX22x *self)
+{
+}
+
+static void
+mm_plugin_x22x_class_init (MMPluginX22xClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+}
diff --git a/src/plugins/x22x/mm-plugin-x22x.h b/src/plugins/x22x/mm-plugin-x22x.h
new file mode 100644
index 00000000..bea588c6
--- /dev/null
+++ b/src/plugins/x22x/mm-plugin-x22x.h
@@ -0,0 +1,42 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_PLUGIN_X22X_H
+#define MM_PLUGIN_X22X_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_X22X (mm_plugin_x22x_get_type ())
+#define MM_PLUGIN_X22X(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_X22X, MMPluginX22x))
+#define MM_PLUGIN_X22X_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_X22X, MMPluginX22xClass))
+#define MM_IS_PLUGIN_X22X(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_X22X))
+#define MM_IS_PLUGIN_X22X_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_X22X))
+#define MM_PLUGIN_X22X_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_X22X, MMPluginX22xClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginX22x;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginX22xClass;
+
+GType mm_plugin_x22x_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_X22X_H */
diff --git a/src/plugins/xmm/mm-broadband-modem-mbim-xmm.c b/src/plugins/xmm/mm-broadband-modem-mbim-xmm.c
new file mode 100644
index 00000000..287c67f7
--- /dev/null
+++ b/src/plugins/xmm/mm-broadband-modem-mbim-xmm.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 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-location.h"
+#include "mm-broadband-modem-mbim-xmm.h"
+#include "mm-shared-xmm.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void shared_xmm_init (MMSharedXmm *iface);
+
+static MMIfaceModemLocation *iface_modem_location_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimXmm, mm_broadband_modem_mbim_xmm, MM_TYPE_BROADBAND_MODEM_MBIM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_XMM, shared_xmm_init))
+
+/*****************************************************************************/
+
+MMBroadbandModemMbimXmm *
+mm_broadband_modem_mbim_xmm_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_XMM,
+ 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,
+ /* 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,
+ MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, TRUE,
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
+ MM_BROADBAND_MODEM_MBIM_QMI_UNSUPPORTED, TRUE,
+#endif
+ NULL);
+}
+
+static void
+mm_broadband_modem_mbim_xmm_init (MMBroadbandModemMbimXmm *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface->load_supported_modes = mm_shared_xmm_load_supported_modes;
+ iface->load_supported_modes_finish = mm_shared_xmm_load_supported_modes_finish;
+ iface->load_current_modes = mm_shared_xmm_load_current_modes;
+ iface->load_current_modes_finish = mm_shared_xmm_load_current_modes_finish;
+ iface->set_current_modes = mm_shared_xmm_set_current_modes;
+ iface->set_current_modes_finish = mm_shared_xmm_set_current_modes_finish;
+
+ iface->load_supported_bands = mm_shared_xmm_load_supported_bands;
+ iface->load_supported_bands_finish = mm_shared_xmm_load_supported_bands_finish;
+ iface->load_current_bands = mm_shared_xmm_load_current_bands;
+ iface->load_current_bands_finish = mm_shared_xmm_load_current_bands_finish;
+ iface->set_current_bands = mm_shared_xmm_set_current_bands;
+ iface->set_current_bands_finish = mm_shared_xmm_set_current_bands_finish;
+
+ /* power up/down already managed via MBIM */
+ iface->modem_power_off = mm_shared_xmm_power_off;
+ iface->modem_power_off_finish = mm_shared_xmm_power_off_finish;
+ iface->reset = mm_shared_xmm_reset;
+ iface->reset_finish = mm_shared_xmm_reset_finish;
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = mm_shared_xmm_location_load_capabilities;
+ iface->load_capabilities_finish = mm_shared_xmm_location_load_capabilities_finish;
+ iface->enable_location_gathering = mm_shared_xmm_enable_location_gathering;
+ iface->enable_location_gathering_finish = mm_shared_xmm_enable_location_gathering_finish;
+ iface->disable_location_gathering = mm_shared_xmm_disable_location_gathering;
+ iface->disable_location_gathering_finish = mm_shared_xmm_disable_location_gathering_finish;
+ iface->load_supl_server = mm_shared_xmm_location_load_supl_server;
+ iface->load_supl_server_finish = mm_shared_xmm_location_load_supl_server_finish;
+ iface->set_supl_server = mm_shared_xmm_location_set_supl_server;
+ iface->set_supl_server_finish = mm_shared_xmm_location_set_supl_server_finish;
+}
+
+static MMBroadbandModemClass *
+peek_parent_broadband_modem_class (MMSharedXmm *self)
+{
+ return MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mbim_xmm_parent_class);
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedXmm *self)
+{
+ return iface_modem_location_parent;
+}
+
+static void
+shared_xmm_init (MMSharedXmm *iface)
+{
+ iface->peek_parent_broadband_modem_class = peek_parent_broadband_modem_class;
+ iface->peek_parent_location_interface = peek_parent_location_interface;
+}
+
+static void
+mm_broadband_modem_mbim_xmm_class_init (MMBroadbandModemMbimXmmClass *klass)
+{
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ broadband_modem_class->setup_ports = mm_shared_xmm_setup_ports;
+}
diff --git a/src/plugins/xmm/mm-broadband-modem-mbim-xmm.h b/src/plugins/xmm/mm-broadband-modem-mbim-xmm.h
new file mode 100644
index 00000000..88e87cb7
--- /dev/null
+++ b/src/plugins/xmm/mm-broadband-modem-mbim-xmm.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_MBIM_XMM_H
+#define MM_BROADBAND_MODEM_MBIM_XMM_H
+
+#include "mm-broadband-modem-mbim.h"
+
+#define MM_TYPE_BROADBAND_MODEM_MBIM_XMM (mm_broadband_modem_mbim_xmm_get_type ())
+#define MM_BROADBAND_MODEM_MBIM_XMM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM, MMBroadbandModemMbimXmm))
+#define MM_BROADBAND_MODEM_MBIM_XMM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_XMM, MMBroadbandModemMbimXmmClass))
+#define MM_IS_BROADBAND_MODEM_MBIM_XMM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM))
+#define MM_IS_BROADBAND_MODEM_MBIM_XMM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_MBIM_XMM))
+#define MM_BROADBAND_MODEM_MBIM_XMM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM, MMBroadbandModemMbimXmmClass))
+
+typedef struct _MMBroadbandModemMbimXmm MMBroadbandModemMbimXmm;
+typedef struct _MMBroadbandModemMbimXmmClass MMBroadbandModemMbimXmmClass;
+
+struct _MMBroadbandModemMbimXmm {
+ MMBroadbandModemMbim parent;
+};
+
+struct _MMBroadbandModemMbimXmmClass{
+ MMBroadbandModemMbimClass parent;
+};
+
+GType mm_broadband_modem_mbim_xmm_get_type (void);
+
+MMBroadbandModemMbimXmm *mm_broadband_modem_mbim_xmm_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_XMM_H */
diff --git a/src/plugins/xmm/mm-broadband-modem-xmm.c b/src/plugins/xmm/mm-broadband-modem-xmm.c
new file mode 100644
index 00000000..7698ec66
--- /dev/null
+++ b/src/plugins/xmm/mm-broadband-modem-xmm.c
@@ -0,0 +1,151 @@
+/* -*- 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>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-location.h"
+#include "mm-broadband-modem-xmm.h"
+#include "mm-shared-xmm.h"
+
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void shared_xmm_init (MMSharedXmm *iface);
+static void iface_modem_signal_init (MMIfaceModemSignal *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+
+static MMIfaceModemLocation *iface_modem_location_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemXmm, mm_broadband_modem_xmm, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_XMM, shared_xmm_init))
+
+/*****************************************************************************/
+
+MMBroadbandModemXmm *
+mm_broadband_modem_xmm_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_XMM,
+ 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,
+ NULL);
+}
+
+static void
+mm_broadband_modem_xmm_init (MMBroadbandModemXmm *self)
+{
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface->load_supported_modes = mm_shared_xmm_load_supported_modes;
+ iface->load_supported_modes_finish = mm_shared_xmm_load_supported_modes_finish;
+ iface->load_current_modes = mm_shared_xmm_load_current_modes;
+ iface->load_current_modes_finish = mm_shared_xmm_load_current_modes_finish;
+ iface->set_current_modes = mm_shared_xmm_set_current_modes;
+ iface->set_current_modes_finish = mm_shared_xmm_set_current_modes_finish;
+
+ iface->load_supported_bands = mm_shared_xmm_load_supported_bands;
+ iface->load_supported_bands_finish = mm_shared_xmm_load_supported_bands_finish;
+ iface->load_current_bands = mm_shared_xmm_load_current_bands;
+ iface->load_current_bands_finish = mm_shared_xmm_load_current_bands_finish;
+ iface->set_current_bands = mm_shared_xmm_set_current_bands;
+ iface->set_current_bands_finish = mm_shared_xmm_set_current_bands_finish;
+
+ iface->load_power_state = mm_shared_xmm_load_power_state;
+ iface->load_power_state_finish = mm_shared_xmm_load_power_state_finish;
+ iface->modem_power_up = mm_shared_xmm_power_up;
+ iface->modem_power_up_finish = mm_shared_xmm_power_up_finish;
+ iface->modem_power_down = mm_shared_xmm_power_down;
+ iface->modem_power_down_finish = mm_shared_xmm_power_down_finish;
+ iface->modem_power_off = mm_shared_xmm_power_off;
+ iface->modem_power_off_finish = mm_shared_xmm_power_off_finish;
+ iface->reset = mm_shared_xmm_reset;
+ iface->reset_finish = mm_shared_xmm_reset_finish;
+}
+
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = mm_shared_xmm_location_load_capabilities;
+ iface->load_capabilities_finish = mm_shared_xmm_location_load_capabilities_finish;
+ iface->enable_location_gathering = mm_shared_xmm_enable_location_gathering;
+ iface->enable_location_gathering_finish = mm_shared_xmm_enable_location_gathering_finish;
+ iface->disable_location_gathering = mm_shared_xmm_disable_location_gathering;
+ iface->disable_location_gathering_finish = mm_shared_xmm_disable_location_gathering_finish;
+ iface->load_supl_server = mm_shared_xmm_location_load_supl_server;
+ iface->load_supl_server_finish = mm_shared_xmm_location_load_supl_server_finish;
+ iface->set_supl_server = mm_shared_xmm_location_set_supl_server;
+ iface->set_supl_server_finish = mm_shared_xmm_location_set_supl_server_finish;
+}
+
+static MMBroadbandModemClass *
+peek_parent_broadband_modem_class (MMSharedXmm *self)
+{
+ return MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_xmm_parent_class);
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedXmm *self)
+{
+ return iface_modem_location_parent;
+}
+
+static void
+iface_modem_signal_init (MMIfaceModemSignal *iface)
+{
+ iface->check_support = mm_shared_xmm_signal_check_support;
+ iface->check_support_finish = mm_shared_xmm_signal_check_support_finish;
+ iface->load_values = mm_shared_xmm_signal_load_values;
+ iface->load_values_finish = mm_shared_xmm_signal_load_values_finish;
+}
+
+static void
+shared_xmm_init (MMSharedXmm *iface)
+{
+ iface->peek_parent_broadband_modem_class = peek_parent_broadband_modem_class;
+ iface->peek_parent_location_interface = peek_parent_location_interface;
+}
+
+static void
+mm_broadband_modem_xmm_class_init (MMBroadbandModemXmmClass *klass)
+{
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ broadband_modem_class->setup_ports = mm_shared_xmm_setup_ports;
+}
diff --git a/src/plugins/xmm/mm-broadband-modem-xmm.h b/src/plugins/xmm/mm-broadband-modem-xmm.h
new file mode 100644
index 00000000..f63a4bfc
--- /dev/null
+++ b/src/plugins/xmm/mm-broadband-modem-xmm.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_XMM_H
+#define MM_BROADBAND_MODEM_XMM_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_XMM (mm_broadband_modem_xmm_get_type ())
+#define MM_BROADBAND_MODEM_XMM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_XMM, MMBroadbandModemXmm))
+#define MM_BROADBAND_MODEM_XMM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_XMM, MMBroadbandModemXmmClass))
+#define MM_IS_BROADBAND_MODEM_XMM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_XMM))
+#define MM_IS_BROADBAND_MODEM_XMM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_XMM))
+#define MM_BROADBAND_MODEM_XMM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_XMM, MMBroadbandModemXmmClass))
+
+typedef struct _MMBroadbandModemXmm MMBroadbandModemXmm;
+typedef struct _MMBroadbandModemXmmClass MMBroadbandModemXmmClass;
+
+struct _MMBroadbandModemXmm {
+ MMBroadbandModem parent;
+};
+
+struct _MMBroadbandModemXmmClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_xmm_get_type (void);
+
+MMBroadbandModemXmm *mm_broadband_modem_xmm_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_XMM_H */
diff --git a/src/plugins/xmm/mm-modem-helpers-xmm.c b/src/plugins/xmm/mm-modem-helpers-xmm.c
new file mode 100644
index 00000000..70e02a8f
--- /dev/null
+++ b/src/plugins/xmm/mm-modem-helpers-xmm.c
@@ -0,0 +1,1003 @@
+/* -*- 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>
+ */
+
+#include <string.h>
+
+#include "mm-log.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-xmm.h"
+#include "mm-signal.h"
+
+/*****************************************************************************/
+/* XACT common config */
+
+typedef struct {
+ guint num;
+ MMModemBand band;
+} XactBandConfig;
+
+static const XactBandConfig xact_band_config[] = {
+ /* GSM bands */
+ { .num = 900, .band = MM_MODEM_BAND_EGSM },
+ { .num = 1800, .band = MM_MODEM_BAND_DCS },
+ { .num = 1900, .band = MM_MODEM_BAND_PCS },
+ { .num = 850, .band = MM_MODEM_BAND_G850 },
+ { .num = 450, .band = MM_MODEM_BAND_G450 },
+ { .num = 480, .band = MM_MODEM_BAND_G480 },
+ { .num = 750, .band = MM_MODEM_BAND_G750 },
+ { .num = 380, .band = MM_MODEM_BAND_G380 },
+ { .num = 410, .band = MM_MODEM_BAND_G410 },
+ { .num = 710, .band = MM_MODEM_BAND_G710 },
+ { .num = 810, .band = MM_MODEM_BAND_G810 },
+ /* UMTS bands */
+ { .num = 1, .band = MM_MODEM_BAND_UTRAN_1 },
+ { .num = 2, .band = MM_MODEM_BAND_UTRAN_2 },
+ { .num = 3, .band = MM_MODEM_BAND_UTRAN_3 },
+ { .num = 4, .band = MM_MODEM_BAND_UTRAN_4 },
+ { .num = 5, .band = MM_MODEM_BAND_UTRAN_5 },
+ { .num = 6, .band = MM_MODEM_BAND_UTRAN_6 },
+ { .num = 7, .band = MM_MODEM_BAND_UTRAN_7 },
+ { .num = 8, .band = MM_MODEM_BAND_UTRAN_8 },
+ { .num = 9, .band = MM_MODEM_BAND_UTRAN_9 },
+ { .num = 10, .band = MM_MODEM_BAND_UTRAN_10 },
+ { .num = 11, .band = MM_MODEM_BAND_UTRAN_11 },
+ { .num = 12, .band = MM_MODEM_BAND_UTRAN_12 },
+ { .num = 13, .band = MM_MODEM_BAND_UTRAN_13 },
+ { .num = 14, .band = MM_MODEM_BAND_UTRAN_14 },
+ { .num = 19, .band = MM_MODEM_BAND_UTRAN_19 },
+ { .num = 20, .band = MM_MODEM_BAND_UTRAN_20 },
+ { .num = 21, .band = MM_MODEM_BAND_UTRAN_21 },
+ { .num = 22, .band = MM_MODEM_BAND_UTRAN_22 },
+ { .num = 25, .band = MM_MODEM_BAND_UTRAN_25 },
+ /* LTE bands */
+ { .num = 101, .band = MM_MODEM_BAND_EUTRAN_1 },
+ { .num = 102, .band = MM_MODEM_BAND_EUTRAN_2 },
+ { .num = 103, .band = MM_MODEM_BAND_EUTRAN_3 },
+ { .num = 104, .band = MM_MODEM_BAND_EUTRAN_4 },
+ { .num = 105, .band = MM_MODEM_BAND_EUTRAN_5 },
+ { .num = 106, .band = MM_MODEM_BAND_EUTRAN_6 },
+ { .num = 107, .band = MM_MODEM_BAND_EUTRAN_7 },
+ { .num = 108, .band = MM_MODEM_BAND_EUTRAN_8 },
+ { .num = 109, .band = MM_MODEM_BAND_EUTRAN_9 },
+ { .num = 110, .band = MM_MODEM_BAND_EUTRAN_10 },
+ { .num = 111, .band = MM_MODEM_BAND_EUTRAN_11 },
+ { .num = 112, .band = MM_MODEM_BAND_EUTRAN_12 },
+ { .num = 113, .band = MM_MODEM_BAND_EUTRAN_13 },
+ { .num = 114, .band = MM_MODEM_BAND_EUTRAN_14 },
+ { .num = 117, .band = MM_MODEM_BAND_EUTRAN_17 },
+ { .num = 118, .band = MM_MODEM_BAND_EUTRAN_18 },
+ { .num = 119, .band = MM_MODEM_BAND_EUTRAN_19 },
+ { .num = 120, .band = MM_MODEM_BAND_EUTRAN_20 },
+ { .num = 121, .band = MM_MODEM_BAND_EUTRAN_21 },
+ { .num = 122, .band = MM_MODEM_BAND_EUTRAN_22 },
+ { .num = 123, .band = MM_MODEM_BAND_EUTRAN_23 },
+ { .num = 124, .band = MM_MODEM_BAND_EUTRAN_24 },
+ { .num = 125, .band = MM_MODEM_BAND_EUTRAN_25 },
+ { .num = 126, .band = MM_MODEM_BAND_EUTRAN_26 },
+ { .num = 127, .band = MM_MODEM_BAND_EUTRAN_27 },
+ { .num = 128, .band = MM_MODEM_BAND_EUTRAN_28 },
+ { .num = 129, .band = MM_MODEM_BAND_EUTRAN_29 },
+ { .num = 130, .band = MM_MODEM_BAND_EUTRAN_30 },
+ { .num = 131, .band = MM_MODEM_BAND_EUTRAN_31 },
+ { .num = 132, .band = MM_MODEM_BAND_EUTRAN_32 },
+ { .num = 133, .band = MM_MODEM_BAND_EUTRAN_33 },
+ { .num = 134, .band = MM_MODEM_BAND_EUTRAN_34 },
+ { .num = 135, .band = MM_MODEM_BAND_EUTRAN_35 },
+ { .num = 136, .band = MM_MODEM_BAND_EUTRAN_36 },
+ { .num = 137, .band = MM_MODEM_BAND_EUTRAN_37 },
+ { .num = 138, .band = MM_MODEM_BAND_EUTRAN_38 },
+ { .num = 139, .band = MM_MODEM_BAND_EUTRAN_39 },
+ { .num = 140, .band = MM_MODEM_BAND_EUTRAN_40 },
+ { .num = 141, .band = MM_MODEM_BAND_EUTRAN_41 },
+ { .num = 142, .band = MM_MODEM_BAND_EUTRAN_42 },
+ { .num = 143, .band = MM_MODEM_BAND_EUTRAN_43 },
+ { .num = 144, .band = MM_MODEM_BAND_EUTRAN_44 },
+ { .num = 145, .band = MM_MODEM_BAND_EUTRAN_45 },
+ { .num = 146, .band = MM_MODEM_BAND_EUTRAN_46 },
+ { .num = 147, .band = MM_MODEM_BAND_EUTRAN_47 },
+ { .num = 148, .band = MM_MODEM_BAND_EUTRAN_48 },
+ { .num = 149, .band = MM_MODEM_BAND_EUTRAN_49 },
+ { .num = 150, .band = MM_MODEM_BAND_EUTRAN_50 },
+ { .num = 151, .band = MM_MODEM_BAND_EUTRAN_51 },
+ { .num = 152, .band = MM_MODEM_BAND_EUTRAN_52 },
+ { .num = 153, .band = MM_MODEM_BAND_EUTRAN_53 },
+ { .num = 154, .band = MM_MODEM_BAND_EUTRAN_54 },
+ { .num = 155, .band = MM_MODEM_BAND_EUTRAN_55 },
+ { .num = 156, .band = MM_MODEM_BAND_EUTRAN_56 },
+ { .num = 157, .band = MM_MODEM_BAND_EUTRAN_57 },
+ { .num = 158, .band = MM_MODEM_BAND_EUTRAN_58 },
+ { .num = 159, .band = MM_MODEM_BAND_EUTRAN_59 },
+ { .num = 160, .band = MM_MODEM_BAND_EUTRAN_60 },
+ { .num = 161, .band = MM_MODEM_BAND_EUTRAN_61 },
+ { .num = 162, .band = MM_MODEM_BAND_EUTRAN_62 },
+ { .num = 163, .band = MM_MODEM_BAND_EUTRAN_63 },
+ { .num = 164, .band = MM_MODEM_BAND_EUTRAN_64 },
+ { .num = 165, .band = MM_MODEM_BAND_EUTRAN_65 },
+ { .num = 166, .band = MM_MODEM_BAND_EUTRAN_66 },
+};
+
+#define XACT_NUM_IS_BAND_2G(num) (num > 300)
+#define XACT_NUM_IS_BAND_3G(num) (num < 100)
+#define XACT_NUM_IS_BAND_4G(num) (num > 100 && num < 300)
+
+static MMModemBand
+xact_num_to_band (guint num)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (xact_band_config); i++) {
+ if (num == xact_band_config[i].num)
+ return xact_band_config[i].band;
+ }
+ return MM_MODEM_BAND_UNKNOWN;
+}
+
+static guint
+xact_band_to_num (MMModemBand band)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (xact_band_config); i++) {
+ if (band == xact_band_config[i].band)
+ return xact_band_config[i].num;
+ }
+ return 0;
+}
+
+/*****************************************************************************/
+/* XACT=? response parser */
+
+/* Index of the array is the XMM-specific value */
+static const MMModemMode xmm_modes[] = {
+ ( MM_MODEM_MODE_2G ),
+ ( MM_MODEM_MODE_3G ),
+ ( MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G ),
+ ( MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_2G | MM_MODEM_MODE_4G ),
+ ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ),
+};
+
+gboolean
+mm_xmm_parse_xact_test_response (const gchar *response,
+ gpointer log_object,
+ GArray **modes_out,
+ GArray **bands_out,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ GArray *modes = NULL;
+ GArray *all_modes = NULL;
+ GArray *filtered = NULL;
+ GArray *supported = NULL;
+ GArray *preferred = NULL;
+ GArray *bands = NULL;
+ gchar **split = NULL;
+ guint i;
+
+ MMModemModeCombination all = {
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE
+ };
+
+ g_assert (modes_out && bands_out);
+
+ /*
+ * AT+XACT=?
+ * +XACT: (0-6),(0-2),0,1,2,4,5,8,101,102,103,104,105,107,108,111,...
+ */
+ response = mm_strip_tag (response, "+XACT:");
+ split = mm_split_string_groups (response);
+
+ if (g_strv_length (split) < 3) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing fields");
+ goto out;
+ }
+
+ /* First group is list of supported modes */
+ supported = mm_parse_uint_list (split[0], &inner_error);
+ if (inner_error)
+ goto out;
+ if (!supported) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing modes");
+ goto out;
+ }
+
+ /* Second group is list of possible preferred modes.
+ * For our purposes, the preferred list may be empty */
+ preferred = mm_parse_uint_list (split[1], &inner_error);
+ if (inner_error)
+ goto out;
+
+ /* Build array of modes */
+ modes = g_array_new (FALSE, FALSE, sizeof (MMModemModeCombination));
+
+ for (i = 0; i < supported->len; i++) {
+ guint supported_value;
+ MMModemModeCombination combination;
+ guint j;
+
+ supported_value = g_array_index (supported, guint, i);
+
+ if (supported_value >= G_N_ELEMENTS (xmm_modes)) {
+ mm_obj_warn (log_object, "unexpected AcT supported value: %u", supported_value);
+ continue;
+ }
+
+ /* Combination without any preferred */
+ combination.allowed = xmm_modes[supported_value];
+ combination.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (modes, combination);
+
+ if (mm_count_bits_set (combination.allowed) == 1)
+ continue;
+
+ if (!preferred)
+ continue;
+
+ for (j = 0; j < preferred->len; j++) {
+ guint preferred_value;
+
+ preferred_value = g_array_index (preferred, guint, j);
+ if (preferred_value >= G_N_ELEMENTS (xmm_modes)) {
+ mm_obj_warn (log_object, "unexpected AcT preferred value: %u", preferred_value);
+ continue;
+ }
+ combination.preferred = xmm_modes[preferred_value];
+ if (mm_count_bits_set (combination.preferred) != 1) {
+ mm_obj_warn (log_object, "AcT preferred value should be a single AcT: %u", preferred_value);
+ continue;
+ }
+ if (!(combination.allowed & combination.preferred))
+ continue;
+ g_array_append_val (modes, combination);
+ }
+ }
+
+ if (modes->len == 0) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No modes list built from +XACT=? response");
+ goto out;
+ }
+
+ /* Build array of bands */
+ bands = g_array_new (FALSE, FALSE, sizeof (MMModemBand));
+
+ /*
+ * The next element at index 2 may be '0'. We will just treat that field as
+ * any other band field as '0' isn't a supported band, we'll just ignore it.
+ */
+ for (i = 2; split[i]; i++) {
+ MMModemBand band;
+ guint num;
+
+ if (!mm_get_uint_from_str (split[i], &num)) {
+ mm_obj_warn (log_object, "unexpected band value: %s", split[i]);
+ continue;
+ }
+
+ if (num == 0)
+ continue;
+
+ band = xact_num_to_band (num);
+ if (band == MM_MODEM_BAND_UNKNOWN) {
+ mm_obj_warn (log_object, "unsupported band value: %s", split[i]);
+ continue;
+ }
+
+ g_array_append_val (bands, band);
+
+ if (XACT_NUM_IS_BAND_2G (num))
+ all.allowed |= MM_MODEM_MODE_2G;
+ if (XACT_NUM_IS_BAND_3G (num))
+ all.allowed |= MM_MODEM_MODE_3G;
+ if (XACT_NUM_IS_BAND_4G (num))
+ all.allowed |= MM_MODEM_MODE_4G;
+ }
+
+ if (bands->len == 0) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No bands list built from +XACT=? response");
+ goto out;
+ }
+
+ /* AT+XACT lies about the supported modes, e.g. it may report 2G supported
+ * for 3G+4G only devices. So, filter out unsupported modes based on the
+ * supported bands */
+ all_modes = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
+ g_array_append_val (all_modes, all);
+
+ filtered = mm_filter_supported_modes (all_modes, modes, log_object);
+ if (!filtered || filtered->len == 0) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Empty supported mode list after frequency band filtering");
+ goto out;
+ }
+
+ /* success */
+
+out:
+ if (modes)
+ g_array_unref (modes);
+ if (all_modes)
+ g_array_unref (all_modes);
+ if (supported)
+ g_array_unref (supported);
+ if (preferred)
+ g_array_unref (preferred);
+ g_strfreev (split);
+
+ if (inner_error) {
+ if (filtered)
+ g_array_unref (filtered);
+ if (bands)
+ g_array_unref (bands);
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ g_assert (filtered);
+ *modes_out = filtered;
+ g_assert (bands);
+ *bands_out = bands;
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* AT+XACT? response parser */
+
+gboolean
+mm_xmm_parse_xact_query_response (const gchar *response,
+ MMModemModeCombination *mode_out,
+ GArray **bands_out,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ GArray *bands = NULL;
+ guint i;
+
+ MMModemModeCombination mode = {
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE,
+ };
+
+ /* At least one */
+ g_assert (mode_out || bands_out);
+
+ /*
+ * AT+XACT?
+ * +XACT: 4,1,2,1,2,4,5,8,101,102,103,104,105,107,108,111,...
+ *
+ * Note: the first 3 fields corresponde to allowed and preferred modes. Only the
+ * first one of those 3 first fields is mandatory, the other two may be empty.
+ */
+ r = g_regex_new ("\\+XACT: (\\d+),([^,]*),([^,]*),(.*)(?:\\r\\n)?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ if (mode_out) {
+ guint xmm_mode;
+
+ /* Number at index 1 */
+ mm_get_uint_from_match_info (match_info, 1, &xmm_mode);
+ if (xmm_mode >= G_N_ELEMENTS (xmm_modes)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unsupported XACT AcT value: %u", xmm_mode);
+ goto out;
+ }
+ mode.allowed = xmm_modes[xmm_mode];
+
+ /* Number at index 2 */
+ if (mm_count_bits_set (mode.allowed) > 1 && mm_get_uint_from_match_info (match_info, 2, &xmm_mode)) {
+ if (xmm_mode >= G_N_ELEMENTS (xmm_modes)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unsupported XACT preferred AcT value: %u", xmm_mode);
+ goto out;
+ }
+ mode.preferred = xmm_modes[xmm_mode];
+ }
+
+ /* Number at index 3: ignored */
+ }
+
+ if (bands_out) {
+ gchar *bandstr;
+ GArray *nums;
+
+ /* Bands start at index 4 */
+ bandstr = mm_get_string_unquoted_from_match_info (match_info, 4);
+ nums = mm_parse_uint_list (bandstr, &inner_error);
+ g_free (bandstr);
+
+ if (inner_error)
+ goto out;
+ if (!nums) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Invalid XACT? response");
+ goto out;
+ }
+
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), nums->len);
+ for (i = 0; i < nums->len; i++) {
+ MMModemBand band;
+
+ band = xact_num_to_band (g_array_index (nums, guint, i));
+ if (band != MM_MODEM_BAND_UNKNOWN)
+ g_array_append_val (bands, band);
+ }
+ g_array_unref (nums);
+
+ if (bands->len == 0) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing current band list");
+ goto out;
+ }
+ }
+ }
+
+ /* success */
+
+out:
+ if (inner_error) {
+ if (bands)
+ g_array_unref (bands);
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (mode_out) {
+ g_assert (mode.allowed != MM_MODEM_MODE_NONE);
+ mode_out->allowed = mode.allowed;
+ mode_out->preferred = mode.preferred;
+ }
+
+ if (bands_out) {
+ g_assert (bands);
+ *bands_out = bands;
+ }
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* AT+XACT=X command builder */
+
+static gboolean
+append_rat_value (GString *str,
+ MMModemMode mode,
+ GError **error)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (xmm_modes); i++) {
+ if (xmm_modes[i] == mode) {
+ g_string_append_printf (str, "%u", i);
+ return TRUE;
+ }
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No AcT value matches requested mode");
+ return FALSE;
+}
+
+gchar *
+mm_xmm_build_xact_set_command (const MMModemModeCombination *mode,
+ const GArray *bands,
+ GError **error)
+{
+ GString *command;
+
+ /* At least one required */
+ g_assert (mode || bands);
+
+ /* Build command */
+ command = g_string_new ("+XACT=");
+
+ /* Mode is optional. If not given, we set all fields as empty */
+ if (mode) {
+ /* Allowed mask */
+ if (!append_rat_value (command, mode->allowed, error)) {
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+
+ /* Preferred */
+ if (mode->preferred != MM_MODEM_MODE_NONE) {
+ g_string_append (command, ",");
+ if (!append_rat_value (command, mode->preferred, error)) {
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+ /* We never set <PreferredAct2> because that is anyway not part of
+ * ModemManager's API. In modems with triple GSM/UMTS/LTE mode, the
+ * <PreferredAct2> is always the highest of the remaining ones. E.g.
+ * if "2G+3G+4G allowed with 2G preferred", the second preferred one
+ * would be 4G, not 3G. */
+ g_string_append (command, ",");
+ } else
+ g_string_append (command, ",,");
+ } else
+ g_string_append (command, ",,");
+
+ if (bands) {
+ g_string_append (command, ",");
+ /* Automatic band selection */
+ if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY)
+ g_string_append (command, "0");
+ else {
+ guint i;
+
+ for (i = 0; i < bands->len; i++) {
+ MMModemBand band;
+ guint num;
+
+ band = g_array_index (bands, MMModemBand, i);
+ num = xact_band_to_num (band);
+ if (!num) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Band unsupported by this plugin: %s", mm_modem_band_get_string (band));
+ g_string_free (command, TRUE);
+ return NULL;
+ }
+
+ g_string_append_printf (command, "%s%u", i == 0 ? "" : ",", num);
+ }
+ }
+ }
+
+ return g_string_free (command, FALSE);
+}
+
+/*****************************************************************************/
+/* Get mode to apply when ANY */
+
+MMModemMode
+mm_xmm_get_modem_mode_any (const GArray *combinations)
+{
+ guint i;
+ MMModemMode any = MM_MODEM_MODE_NONE;
+ guint any_bits_set = 0;
+
+ for (i = 0; i < combinations->len; i++) {
+ MMModemModeCombination *combination;
+ guint bits_set;
+
+ combination = &g_array_index (combinations, MMModemModeCombination, i);
+ if (combination->preferred != MM_MODEM_MODE_NONE)
+ continue;
+ bits_set = mm_count_bits_set (combination->allowed);
+ if (bits_set > any_bits_set) {
+ any_bits_set = bits_set;
+ any = combination->allowed;
+ }
+ }
+
+ /* If combinations were processed via mm_xmm_parse_uact_test_response(),
+ * we're sure that there will be at least one combination with preferred
+ * 'none', so there must be some valid combination as result */
+ g_assert (any != MM_MODEM_MODE_NONE);
+ return any;
+}
+
+/*****************************************************************************/
+/* +XCESQ? response parser */
+
+gboolean
+mm_xmm_parse_xcesq_query_response (const gchar *response,
+ guint *out_rxlev,
+ guint *out_ber,
+ guint *out_rscp,
+ guint *out_ecn0,
+ guint *out_rsrq,
+ guint *out_rsrp,
+ gint *out_rssnr,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ guint rxlev = 99;
+ guint ber = 99;
+ guint rscp = 255;
+ guint ecn0 = 255;
+ guint rsrq = 255;
+ guint rsrp = 255;
+ gint rssnr = 255;
+ gboolean success = FALSE;
+
+ g_assert (out_rxlev);
+ g_assert (out_ber);
+ g_assert (out_rscp);
+ g_assert (out_ecn0);
+ g_assert (out_rsrq);
+ g_assert (out_rsrp);
+ g_assert (out_rssnr);
+
+ /* Response may be e.g.:
+ * +XCESQ: 0,99,99,255,255,24,51,18
+ * +XCESQ: 0,99,99,46,31,255,255,255
+ * +XCESQ: 0,99,99,255,255,17,45,-2
+ */
+ r = g_regex_new ("\\+XCESQ: (\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(-?\\d+)(?:\\r\\n)?", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ /* Ignore "n" value */
+ if (!mm_get_uint_from_match_info (match_info, 2, &rxlev)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RXLEV");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 3, &ber)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read BER");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 4, &rscp)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSCP");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 5, &ecn0)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read Ec/N0");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 6, &rsrq)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRQ");
+ goto out;
+ }
+ if (!mm_get_uint_from_match_info (match_info, 7, &rsrp)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRP");
+ goto out;
+ }
+ if (!mm_get_int_from_match_info (match_info, 8, &rssnr)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSSNR");
+ goto out;
+ }
+ success = TRUE;
+ }
+
+out:
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (!success) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse +XCESQ response: %s", response);
+ return FALSE;
+ }
+
+ *out_rxlev = rxlev;
+ *out_ber = ber;
+ *out_rscp = rscp;
+ *out_ecn0 = ecn0;
+ *out_rsrq = rsrq;
+ *out_rsrp = rsrp;
+ *out_rssnr = rssnr;
+ return TRUE;
+}
+
+static gboolean
+rssnr_level_to_rssnr (gint rssnr_level,
+ gpointer log_object,
+ gdouble *out_rssnr)
+{
+ if (rssnr_level <= 100 &&
+ rssnr_level >= -100) {
+ *out_rssnr = rssnr_level / 2.0;
+ return TRUE;
+ }
+
+ if (rssnr_level != 255)
+ mm_obj_warn (log_object, "unexpected RSSNR level: %u", rssnr_level);
+ return FALSE;
+}
+
+/*****************************************************************************/
+/* Get extended signal information */
+
+gboolean
+mm_xmm_xcesq_response_to_signal_info (const gchar *response,
+ gpointer log_object,
+ MMSignal **out_gsm,
+ MMSignal **out_umts,
+ MMSignal **out_lte,
+ GError **error)
+{
+ guint rxlev = 0;
+ guint ber = 0;
+ guint rscp_level = 0;
+ guint ecn0_level = 0;
+ guint rsrq_level = 0;
+ guint rsrp_level = 0;
+ gint rssnr_level = 0;
+ gdouble rssi = MM_SIGNAL_UNKNOWN;
+ gdouble rscp = MM_SIGNAL_UNKNOWN;
+ gdouble ecio = MM_SIGNAL_UNKNOWN;
+ gdouble rsrq = MM_SIGNAL_UNKNOWN;
+ gdouble rsrp = MM_SIGNAL_UNKNOWN;
+ gdouble rssnr = MM_SIGNAL_UNKNOWN;
+ MMSignal *gsm = NULL;
+ MMSignal *umts = NULL;
+ MMSignal *lte = NULL;
+
+ if (!mm_xmm_parse_xcesq_query_response (response,
+ &rxlev, &ber,
+ &rscp_level, &ecn0_level,
+ &rsrq_level, &rsrp_level,
+ &rssnr_level, error))
+ return FALSE;
+
+ /* GERAN RSSI */
+ if (mm_3gpp_rxlev_to_rssi (rxlev, log_object, &rssi)) {
+ gsm = mm_signal_new ();
+ mm_signal_set_rssi (gsm, rssi);
+ }
+
+ /* ignore BER */
+
+ /* UMTS RSCP */
+ if (mm_3gpp_rscp_level_to_rscp (rscp_level, log_object, &rscp)) {
+ umts = mm_signal_new ();
+ mm_signal_set_rscp (umts, rscp);
+ }
+
+ /* UMTS EcIo (assumed EcN0) */
+ if (mm_3gpp_ecn0_level_to_ecio (ecn0_level, log_object, &ecio)) {
+ if (!umts)
+ umts = mm_signal_new ();
+ mm_signal_set_ecio (umts, ecio);
+ }
+
+ /* Calculate RSSI if we have ecio and rscp */
+ if (umts && ecio != -G_MAXDOUBLE && rscp != -G_MAXDOUBLE) {
+ mm_signal_set_rssi (umts, rscp - ecio);
+ }
+
+ /* LTE RSRQ */
+ if (mm_3gpp_rsrq_level_to_rsrq (rsrq_level, log_object, &rsrq)) {
+ lte = mm_signal_new ();
+ mm_signal_set_rsrq (lte, rsrq);
+ }
+
+ /* LTE RSRP */
+ if (mm_3gpp_rsrp_level_to_rsrp (rsrp_level, log_object, &rsrp)) {
+ if (!lte)
+ lte = mm_signal_new ();
+ mm_signal_set_rsrp (lte, rsrp);
+ }
+
+ /* LTE RSSNR */
+ if (rssnr_level_to_rssnr (rssnr_level, log_object, &rssnr)) {
+ if (!lte)
+ lte = mm_signal_new ();
+ mm_signal_set_snr (lte, rssnr);
+ }
+
+ if (!gsm && !umts && !lte) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't build detailed signal info");
+ return FALSE;
+ }
+
+ if (out_gsm)
+ *out_gsm = gsm;
+ if (out_umts)
+ *out_umts = umts;
+ if (out_lte)
+ *out_lte = lte;
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* AT+XLCSLSR=? response parser */
+
+static gboolean
+number_group_contains_value (const gchar *group,
+ const gchar *group_name,
+ guint value,
+ GError **error)
+{
+ GArray *aux;
+ guint i;
+ gboolean found;
+
+ aux = mm_parse_uint_list (group, NULL);
+ if (!aux) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Unsupported +XLCSLSR format: invalid %s field format", group_name);
+ return FALSE;
+ }
+
+ found = FALSE;
+ for (i = 0; i < aux->len; i++) {
+ guint value_i;
+
+ value_i = g_array_index (aux, guint, i);
+ if (value == value_i) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ g_array_unref (aux);
+ return found;
+}
+
+gboolean
+mm_xmm_parse_xlcslsr_test_response (const gchar *response,
+ gboolean *transport_protocol_invalid_supported,
+ gboolean *transport_protocol_supl_supported,
+ gboolean *standalone_position_mode_supported,
+ gboolean *ms_assisted_based_position_mode_supported,
+ gboolean *loc_response_type_nmea_supported,
+ gboolean *gnss_type_gps_glonass_supported,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gchar **groups = NULL;
+ GError *inner_error = NULL;
+
+ /*
+ * AT+XLCSLSR=?
+ * +XLCSLSR:(0-2),(0-3), ,(0,1), ,(0,1),(0 -7200),(0-255),(0-1),(0-2),(1-256),(0,1)
+ * transport_protocol: 2 (invalid) or 1 (supl)
+ * pos_mode: 3 (standalone) or 2 (ms assisted/based)
+ * client_id: <empty>
+ * client_id_type: <empty>
+ * mlc_number: <empty>
+ * mlc_number_type: <empty>
+ * interval: 1 (seconds)
+ * service_type_id: <empty>
+ * pseudonym_indicator: <empty>
+ * loc_response_type: 1 (NMEA strings)
+ * nmea_mask: 118 (01110110: GGA,GSA,GSV,RMC,VTG)
+ * gnss_type: 0 (GPS or GLONASS)
+ */
+ response = mm_strip_tag (response, "+XLCSLSR:");
+ groups = mm_split_string_groups (response);
+
+ /* We expect 12 groups */
+ if (g_strv_length (groups) < 12) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Unsupported +XLCSLSR format: expected 12 fields");
+ goto out;
+ }
+
+ if (transport_protocol_invalid_supported) {
+ *transport_protocol_invalid_supported = number_group_contains_value (groups[0],
+ "transport protocol",
+ 2, /* invalid */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ if (transport_protocol_supl_supported) {
+ *transport_protocol_supl_supported = number_group_contains_value (groups[0],
+ "transport protocol",
+ 1, /* supl */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ if (standalone_position_mode_supported) {
+ *standalone_position_mode_supported = number_group_contains_value (groups[1],
+ "position mode",
+ 3, /* standalone */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ if (ms_assisted_based_position_mode_supported) {
+ *ms_assisted_based_position_mode_supported = number_group_contains_value (groups[1],
+ "position mode",
+ 2, /* ms assisted/based */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ if (loc_response_type_nmea_supported) {
+ *loc_response_type_nmea_supported = number_group_contains_value (groups[9],
+ "location response type",
+ 1, /* NMEA */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ if (gnss_type_gps_glonass_supported) {
+ *gnss_type_gps_glonass_supported = number_group_contains_value (groups[11],
+ "gnss type",
+ 0, /* GPS/GLONASS */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ ret = TRUE;
+
+ out:
+ g_strfreev (groups);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ return ret;
+}
+
+/*****************************************************************************/
+/* AT+XLCSSLP? response parser */
+
+gboolean
+mm_xmm_parse_xlcsslp_query_response (const gchar *response,
+ gchar **supl_address,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ gchar *address = NULL;
+ guint port = 0;
+
+ /*
+ * E.g.:
+ * +XLCSSLP:1,"www.spirent-lcs.com",7275
+ */
+
+ r = g_regex_new ("\\+XLCSSLP:\\s*(\\d+),([^,]*),(\\d+)(?:\\r\\n)?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ guint type;
+
+ /* We only support types 0 (IPv4) and 1 (FQDN) */
+ mm_get_uint_from_match_info (match_info, 1, &type);
+ if (type != 0 && type != 1) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Unsupported SUPL server address type (%u) in response: %s", type, response);
+ goto out;
+ }
+
+ address = mm_get_string_unquoted_from_match_info (match_info, 2);
+ mm_get_uint_from_match_info (match_info, 3, &port);
+ if (!port) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Invalid SUPL address port number in response: %s", response);
+ goto out;
+ }
+ }
+
+out:
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (supl_address)
+ *supl_address = g_strdup_printf ("%s:%u", address, port);
+ g_free (address);
+
+ return TRUE;
+}
diff --git a/src/plugins/xmm/mm-modem-helpers-xmm.h b/src/plugins/xmm/mm-modem-helpers-xmm.h
new file mode 100644
index 00000000..a18f0667
--- /dev/null
+++ b/src/plugins/xmm/mm-modem-helpers-xmm.h
@@ -0,0 +1,75 @@
+/* -*- 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_MODEM_HELPERS_XMM_H
+#define MM_MODEM_HELPERS_XMM_H
+
+#include <glib.h>
+#include <ModemManager.h>
+
+/* AT+XACT=? response parser */
+gboolean mm_xmm_parse_xact_test_response (const gchar *response,
+ gpointer logger,
+ GArray **modes_out,
+ GArray **bands_out,
+ GError **error);
+
+/* AT+XACT? response parser */
+gboolean mm_xmm_parse_xact_query_response (const gchar *response,
+ MMModemModeCombination *mode_out,
+ GArray **bands_out,
+ GError **error);
+
+/* AT+XACT=X command builder */
+gchar *mm_xmm_build_xact_set_command (const MMModemModeCombination *mode,
+ const GArray *bands,
+ GError **error);
+
+/* Mode to apply when ANY */
+MMModemMode mm_xmm_get_modem_mode_any (const GArray *combinations);
+
+gboolean mm_xmm_parse_xcesq_query_response (const gchar *response,
+ guint *out_rxlev,
+ guint *out_ber,
+ guint *out_rscp,
+ guint *out_ecn0,
+ guint *out_rsrq,
+ guint *out_rsrp,
+ gint *out_rssnr,
+ GError **error);
+
+gboolean mm_xmm_xcesq_response_to_signal_info (const gchar *response,
+ gpointer log_object,
+ MMSignal **out_gsm,
+ MMSignal **out_umts,
+ MMSignal **out_lte,
+ GError **error);
+
+/* AT+XLCSLSR=? response parser */
+gboolean mm_xmm_parse_xlcslsr_test_response (const gchar *response,
+ gboolean *transport_protocol_invalid_supported,
+ gboolean *transport_protocol_supl_supported,
+ gboolean *standalone_position_mode_supported,
+ gboolean *ms_assisted_based_position_mode_supported,
+ gboolean *loc_response_type_nmea_supported,
+ gboolean *gnss_type_gps_glonass_supported,
+ GError **error);
+
+/* AT+XLCSSLP? response parser */
+gboolean mm_xmm_parse_xlcsslp_query_response (const gchar *response,
+ gchar **supl_address,
+ GError **error);
+
+#endif /* MM_MODEM_HELPERS_XMM_H */
diff --git a/src/plugins/xmm/mm-shared-xmm.c b/src/plugins/xmm/mm-shared-xmm.c
new file mode 100644
index 00000000..90f8867a
--- /dev/null
+++ b/src/plugins/xmm/mm-shared-xmm.c
@@ -0,0 +1,1710 @@
+/* -*- 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>
+ */
+
+#include <config.h>
+#include <arpa/inet.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-signal.h"
+#include "mm-iface-modem-location.h"
+#include "mm-base-modem.h"
+#include "mm-base-modem-at.h"
+#include "mm-shared-xmm.h"
+#include "mm-modem-helpers-xmm.h"
+
+/*****************************************************************************/
+/* Private data context */
+
+#define PRIVATE_TAG "shared-xmm-private-tag"
+static GQuark private_quark;
+
+typedef enum {
+ GPS_ENGINE_STATE_OFF,
+ GPS_ENGINE_STATE_STANDALONE,
+ GPS_ENGINE_STATE_AGPS_MSA,
+ GPS_ENGINE_STATE_AGPS_MSB,
+} GpsEngineState;
+
+typedef struct {
+ /* Broadband modem class support */
+ MMBroadbandModemClass *broadband_modem_class_parent;
+
+ /* Modem interface support */
+ GArray *supported_modes;
+ GArray *supported_bands;
+ MMModemMode allowed_modes;
+
+ /* Location interface support */
+ MMIfaceModemLocation *iface_modem_location_parent;
+ MMModemLocationSource supported_sources;
+ MMModemLocationSource enabled_sources;
+ GpsEngineState gps_engine_state;
+ MMPortSerialAt *gps_port;
+ GRegex *xlsrstop_regex;
+ GRegex *nmea_regex;
+
+ /* Asynchronous GPS engine stop task completion */
+ GTask *pending_gps_engine_stop_task;
+} Private;
+
+static void
+private_free (Private *priv)
+{
+ g_assert (!priv->pending_gps_engine_stop_task);
+ g_clear_object (&priv->gps_port);
+ if (priv->supported_modes)
+ g_array_unref (priv->supported_modes);
+ if (priv->supported_bands)
+ g_array_unref (priv->supported_bands);
+ g_regex_unref (priv->xlsrstop_regex);
+ g_regex_unref (priv->nmea_regex);
+ g_slice_free (Private, priv);
+}
+
+static Private *
+get_private (MMSharedXmm *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->gps_engine_state = GPS_ENGINE_STATE_OFF;
+
+ /* Setup regex for URCs */
+ priv->xlsrstop_regex = g_regex_new ("\\r\\n\\+XLSRSTOP:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ priv->nmea_regex = g_regex_new ("(?:\\r\\n)?(?:\\r\\n)?(\\$G.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ /* Setup parent class' MMBroadbandModemClass */
+ g_assert (MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_broadband_modem_class);
+ priv->broadband_modem_class_parent = MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_broadband_modem_class (self);
+
+ /* Setup parent class' MMIfaceModemLocation */
+ g_assert (MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_location_interface);
+ priv->iface_modem_location_parent = MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_location_interface (self);
+
+ g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
+ }
+
+ return priv;
+}
+
+/*****************************************************************************/
+/* Supported modes/bands (Modem interface) */
+
+GArray *
+mm_shared_xmm_load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ Private *priv;
+
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return NULL;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ g_assert (priv->supported_modes);
+ return g_array_ref (priv->supported_modes);
+}
+
+GArray *
+mm_shared_xmm_load_supported_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ Private *priv;
+
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return NULL;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ g_assert (priv->supported_bands);
+ return g_array_ref (priv->supported_bands);
+}
+
+static void
+xact_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response ||
+ !mm_xmm_parse_xact_test_response (response,
+ self,
+ &priv->supported_modes,
+ &priv->supported_bands,
+ &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+common_load_supported_modes_bands (GTask *task)
+{
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (g_task_get_source_object (task)),
+ "+XACT=?",
+ 3,
+ TRUE, /* allow caching */
+ (GAsyncReadyCallback)xact_test_ready,
+ task);
+}
+
+void
+mm_shared_xmm_load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Private *priv;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ priv = get_private (MM_SHARED_XMM (self));
+
+ if (!priv->supported_modes) {
+ common_load_supported_modes_bands (task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_xmm_load_supported_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Private *priv;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ priv = get_private (MM_SHARED_XMM (self));
+
+ if (!priv->supported_bands) {
+ common_load_supported_modes_bands (task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Current modes (Modem interface) */
+
+gboolean
+mm_shared_xmm_load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ MMModemModeCombination *result;
+
+ result = g_task_propagate_pointer (G_TASK (res), error);
+ if (!result)
+ return FALSE;
+
+ *allowed = result->allowed;
+ *preferred = result->preferred;
+ g_free (result);
+ return TRUE;
+}
+
+static void
+xact_query_modes_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ Private *priv;
+ MMModemModeCombination *result;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ result = g_new0 (MMModemModeCombination, 1);
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response || !mm_xmm_parse_xact_query_response (response, result, NULL, &error)) {
+ priv->allowed_modes = MM_MODEM_MODE_NONE;
+ g_free (result);
+ g_task_return_error (task, error);
+ } else {
+ priv->allowed_modes = result->allowed;
+ g_task_return_pointer (task, result, g_free);
+ }
+ g_object_unref (task);
+}
+
+void
+mm_shared_xmm_load_current_modes (MMIfaceModem *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),
+ "+XACT?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)xact_query_modes_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Current bands (Modem interface) */
+
+GArray *
+mm_shared_xmm_load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return (GArray *) g_task_propagate_pointer (G_TASK (res), error);
+}
+
+
+static void
+xact_query_bands_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ GArray *result = NULL;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response ||
+ !mm_xmm_parse_xact_query_response (response, NULL, &result, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, result, (GDestroyNotify)g_array_unref);
+ g_object_unref (task);
+}
+
+void
+mm_shared_xmm_load_current_bands (MMIfaceModem *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),
+ "+XACT?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)xact_query_bands_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Set current modes (Modem interface) */
+
+gboolean
+mm_shared_xmm_set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+xact_set_modes_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);
+}
+
+void
+mm_shared_xmm_set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ MMModemModeCombination mode;
+ gchar *command;
+ GError *error = NULL;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (allowed != MM_MODEM_MODE_ANY) {
+ mode.allowed = allowed;
+ mode.preferred = preferred;
+ } else {
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ mode.allowed = mm_xmm_get_modem_mode_any (priv->supported_modes);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ }
+
+ command = mm_xmm_build_xact_set_command (&mode, NULL, &error);
+ if (!command) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)xact_set_modes_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Set current bands (Modem interface) */
+
+gboolean
+mm_shared_xmm_set_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+xact_set_bands_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 gchar *
+validate_and_build_command_set_current_bands (MMSharedXmm *self,
+ const GArray *bands_array,
+ const GArray *supported_modes,
+ MMModemMode allowed_modes,
+ GError **error)
+{
+ gboolean band_2g_found = FALSE;
+ gboolean band_3g_found = FALSE;
+ gboolean band_4g_found = FALSE;
+ GArray *unapplied_bands;
+ GError *inner_error = NULL;
+ guint i;
+
+ /* ANY applies only to the currently selected modes */
+ if (bands_array->len == 1 && g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
+ MMModemModeCombination mode;
+ MMModemMode unapplied;
+
+ /* If we are enabling automatic band selection to a mode combination that does not include
+ * all supported modes, warn about it because automatic band selection wouldn't be executed
+ * for the non-selected modes.
+ *
+ * This is a known limitation of the modem firmware.
+ */
+ unapplied = mm_xmm_get_modem_mode_any (supported_modes) & ~(allowed_modes);
+ if (unapplied != MM_MODEM_MODE_NONE) {
+ g_autofree gchar *str = NULL;
+
+ str = mm_modem_mode_build_string_from_mask (unapplied);
+ mm_obj_warn (self, "automatic band selection not applied to non-current modes %s", str);
+ }
+
+ /* Nothing else to validate, go build the command right away */
+
+ /* We must create the set command with an explicit set of allowed modes.
+ * We pass NONE as preferred, but that WON'T change the currently selected preferred mode,
+ * it will be ignored when the command is processed as an empty field will be given */
+ mode.allowed = allowed_modes;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ return mm_xmm_build_xact_set_command (&mode, bands_array, error);
+ }
+
+ unapplied_bands = g_array_new (FALSE, FALSE, sizeof (MMModemBand));
+ for (i = 0; i < bands_array->len; i++) {
+ MMModemBand band;
+
+ band = g_array_index (bands_array, MMModemBand, i);
+ if (mm_common_band_is_eutran (band)) {
+ band_4g_found = TRUE;
+ if (!(allowed_modes & MM_MODEM_MODE_4G))
+ g_array_append_val (unapplied_bands, band);
+ }
+ if (mm_common_band_is_utran (band)) {
+ band_3g_found = TRUE;
+ if (!(allowed_modes & MM_MODEM_MODE_3G))
+ g_array_append_val (unapplied_bands, band);
+ }
+ if (mm_common_band_is_gsm (band)) {
+ band_2g_found = TRUE;
+ if (!(allowed_modes & MM_MODEM_MODE_2G))
+ g_array_append_val (unapplied_bands, band);
+ }
+ }
+
+ /* If 2G selected, there must be at least one 2G band */
+ if ((allowed_modes & MM_MODEM_MODE_2G) && !band_2g_found) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "At least one GSM band is required when 2G mode is allowed");
+ goto out;
+ }
+
+ /* If 3G selected, there must be at least one 3G band */
+ if ((allowed_modes & MM_MODEM_MODE_3G) && !band_3g_found) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "At least one UTRAN band is required when 3G mode is allowed");
+ goto out;
+ }
+
+ /* If 4G selected, there must be at least one 4G band */
+ if ((allowed_modes & MM_MODEM_MODE_4G) && !band_4g_found) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "At least one E-UTRAN band is required when 4G mode is allowed");
+ goto out;
+ }
+
+ /* Don't try to modify bands for modes that are not enabled */
+ if (unapplied_bands->len > 0) {
+ gchar *str;
+
+ str = mm_common_build_bands_string ((const MMModemBand *)(gconstpointer)unapplied_bands->data, unapplied_bands->len);
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "Cannot update bands for modes not currently allowed: %s", str);
+ g_free (str);
+ goto out;
+ }
+
+out:
+ if (unapplied_bands)
+ g_array_unref (unapplied_bands);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return NULL;
+ }
+
+ return mm_xmm_build_xact_set_command (NULL, bands_array, error);
+}
+
+void
+mm_shared_xmm_set_current_bands (MMIfaceModem *self,
+ GArray *bands_array,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *command = NULL;
+ GError *error = NULL;
+ Private *priv;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Setting bands requires additional validation rules based on the
+ * currently selected list of allowed modes */
+ priv = get_private (MM_SHARED_XMM (self));
+ if (priv->allowed_modes == MM_MODEM_MODE_NONE) {
+ error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Cannot set bands if allowed modes are unknown");
+ goto out;
+ }
+
+ command = validate_and_build_command_set_current_bands (MM_SHARED_XMM (self),
+ bands_array,
+ priv->supported_modes,
+ priv->allowed_modes,
+ &error);
+
+out:
+ if (!command) {
+ g_assert (error);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)xact_set_bands_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Power state loading (Modem interface) */
+
+MMModemPowerState
+mm_shared_xmm_load_power_state_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ guint state;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return MM_MODEM_POWER_STATE_UNKNOWN;
+
+ if (!mm_3gpp_parse_cfun_query_response (response, &state, error))
+ return MM_MODEM_POWER_STATE_UNKNOWN;
+
+ switch (state) {
+ case 1:
+ return MM_MODEM_POWER_STATE_ON;
+ case 4:
+ return MM_MODEM_POWER_STATE_LOW;
+ default:
+ break;
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unknown +CFUN state: %u", state);
+ return MM_MODEM_POWER_STATE_UNKNOWN;
+}
+
+void
+mm_shared_xmm_load_power_state (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Modem power up/down/off (Modem interface) */
+
+static gboolean
+common_modem_power_operation_finish (MMSharedXmm *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+power_operation_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
+common_modem_power_operation (MMSharedXmm *self,
+ const gchar *command,
+ 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),
+ command,
+ 30,
+ FALSE,
+ (GAsyncReadyCallback) power_operation_ready,
+ task);
+}
+
+gboolean
+mm_shared_xmm_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error);
+}
+
+void
+mm_shared_xmm_reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_modem_power_operation (MM_SHARED_XMM (self), "+CFUN=16", callback, user_data);
+}
+
+gboolean
+mm_shared_xmm_power_off_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error);
+}
+
+void
+mm_shared_xmm_power_off (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_modem_power_operation (MM_SHARED_XMM (self), "+CPWROFF", callback, user_data);
+}
+
+gboolean
+mm_shared_xmm_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error);
+}
+
+void
+mm_shared_xmm_power_down (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_modem_power_operation (MM_SHARED_XMM (self), "+CFUN=4", callback, user_data);
+}
+
+gboolean
+mm_shared_xmm_power_up_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error);
+}
+
+void
+mm_shared_xmm_power_up (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_modem_power_operation (MM_SHARED_XMM (self), "+CFUN=1", callback, user_data);
+}
+
+/*****************************************************************************/
+/* Check support (Signal interface) */
+
+gboolean
+mm_shared_xmm_signal_check_support_finish (MMIfaceModemSignal *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+void
+mm_shared_xmm_signal_check_support (MMIfaceModemSignal *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+XCESQ=?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load extended signal information (Signal interface) */
+
+gboolean
+mm_shared_xmm_signal_load_values_finish (MMIfaceModemSignal *self,
+ GAsyncResult *res,
+ MMSignal **cdma,
+ MMSignal **evdo,
+ MMSignal **gsm,
+ MMSignal **umts,
+ MMSignal **lte,
+ MMSignal **nr5g,
+ GError **error)
+{
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response || !mm_xmm_xcesq_response_to_signal_info (response, self, gsm, umts, lte, error))
+ return FALSE;
+
+ if (cdma)
+ *cdma = NULL;
+ if (evdo)
+ *evdo = NULL;
+ if (nr5g)
+ *nr5g = NULL;
+
+ return TRUE;
+}
+
+void
+mm_shared_xmm_signal_load_values (MMIfaceModemSignal *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+XCESQ?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Get GPS control port (Location interface)
+ *
+ * This port is an AT port that will also be used for NMEA data.
+ */
+
+static MMPortSerialAt *
+shared_xmm_get_gps_control_port (MMSharedXmm *self,
+ GError **error)
+{
+ MMPortSerialAt *gps_port = NULL;
+
+ gps_port = mm_base_modem_get_port_gps_control (MM_BASE_MODEM (self));
+ if (!gps_port) {
+ gps_port = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
+ if (!gps_port)
+ gps_port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+ }
+
+ if (!gps_port)
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No valid port found to control GPS");
+ return gps_port;
+}
+
+/*****************************************************************************/
+/* Load capabilities (Location interface) */
+
+MMModemLocationSource
+mm_shared_xmm_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
+xlcslsr_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource sources;
+ const gchar *response;
+ GError *error = NULL;
+ Private *priv;
+ gboolean transport_protocol_invalid_supported;
+ gboolean transport_protocol_supl_supported;
+ gboolean standalone_position_mode_supported;
+ gboolean ms_assisted_based_position_mode_supported;
+ gboolean loc_response_type_nmea_supported;
+ gboolean gnss_type_gps_glonass_supported;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ /* Recover parent sources */
+ sources = GPOINTER_TO_UINT (g_task_get_task_data (task));
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response ||
+ !mm_xmm_parse_xlcslsr_test_response (response,
+ &transport_protocol_invalid_supported,
+ &transport_protocol_supl_supported,
+ &standalone_position_mode_supported,
+ &ms_assisted_based_position_mode_supported,
+ &loc_response_type_nmea_supported,
+ &gnss_type_gps_glonass_supported,
+ &error)) {
+ mm_obj_dbg (self, "XLCSLSR based GPS control unsupported: %s", error->message);
+ g_clear_error (&error);
+ } else if (!transport_protocol_invalid_supported ||
+ !standalone_position_mode_supported ||
+ !loc_response_type_nmea_supported ||
+ !gnss_type_gps_glonass_supported) {
+ mm_obj_dbg (self, "XLCSLSR based GPS control unsupported: protocol invalid %s, standalone %s, nmea %s, gps/glonass %s",
+ transport_protocol_invalid_supported ? "supported" : "unsupported",
+ standalone_position_mode_supported ? "supported" : "unsupported",
+ loc_response_type_nmea_supported ? "supported" : "unsupported",
+ gnss_type_gps_glonass_supported ? "supported" : "unsupported");
+ } else {
+ mm_obj_dbg (self, "XLCSLSR based GPS control supported");
+ priv->supported_sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW);
+
+ if (transport_protocol_supl_supported && ms_assisted_based_position_mode_supported) {
+ mm_obj_dbg (self, "XLCSLSR based A-GPS control supported");
+ priv->supported_sources |= (MM_MODEM_LOCATION_SOURCE_AGPS_MSA | MM_MODEM_LOCATION_SOURCE_AGPS_MSB);
+ } else {
+ mm_obj_dbg (self, "XLCSLSR based A-GPS control unsupported: protocol supl %s, ms assisted/based %s",
+ transport_protocol_supl_supported ? "supported" : "unsupported",
+ ms_assisted_based_position_mode_supported ? "supported" : "unsupported");
+ }
+
+ sources |= priv->supported_sources;
+ }
+
+ g_task_return_int (task, sources);
+ g_object_unref (task);
+}
+
+static void
+run_xlcslsr_test (GTask *task)
+{
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (g_task_get_source_object (task)),
+ "+XLCSLSR=?",
+ 3,
+ TRUE, /* allow caching */
+ (GAsyncReadyCallback)xlcslsr_test_ready,
+ task);
+}
+
+static void
+parent_load_capabilities_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource sources;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (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;
+ }
+
+ /* If parent already supports GPS sources, we won't do anything else */
+ if (sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ mm_obj_dbg (self, "no need to run XLCSLSR based location gathering");
+ g_task_return_int (task, sources);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Cache sources supported by the parent */
+ g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL);
+ run_xlcslsr_test (task);
+}
+
+void
+mm_shared_xmm_location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ task = g_task_new (self, NULL, callback, user_data);
+
+ g_assert (priv->iface_modem_location_parent);
+
+ if (!priv->iface_modem_location_parent->load_capabilities ||
+ !priv->iface_modem_location_parent->load_capabilities_finish) {
+ /* no parent capabilities */
+ g_task_set_task_data (task, GUINT_TO_POINTER (MM_MODEM_LOCATION_SOURCE_NONE), NULL);
+ run_xlcslsr_test (task);
+ return;
+ }
+
+ priv->iface_modem_location_parent->load_capabilities (self,
+ (GAsyncReadyCallback)parent_load_capabilities_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* NMEA trace processing */
+
+static void
+nmea_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMSharedXmm *self)
+{
+ gchar *trace;
+
+ trace = g_match_info_fetch (info, 1);
+ mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), trace);
+ g_free (trace);
+}
+
+/*****************************************************************************/
+/* GPS engine state selection */
+
+#define GPS_ENGINE_STOP_TIMEOUT_SECS 10
+
+typedef struct {
+ GpsEngineState state;
+ guint engine_stop_timeout_id;
+} GpsEngineSelectContext;
+
+static void
+gps_engine_select_context_free (GpsEngineSelectContext *ctx)
+{
+ g_assert (!ctx->engine_stop_timeout_id);
+ g_slice_free (GpsEngineSelectContext, ctx);
+}
+
+static gboolean
+gps_engine_state_select_finish (MMSharedXmm *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+xlcslsr_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GpsEngineSelectContext *ctx;
+ const gchar *response;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ ctx = g_task_get_task_data (task);
+
+ response = mm_base_modem_at_command_full_finish (self, res, &error);
+ if (!response) {
+ g_clear_object (&priv->gps_port);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "GPS engine started");
+
+ g_assert (priv->gps_port);
+ mm_port_serial_at_add_unsolicited_msg_handler (priv->gps_port,
+ priv->nmea_regex,
+ (MMPortSerialAtUnsolicitedMsgFn)nmea_received,
+ self,
+ NULL);
+ priv->gps_engine_state = ctx->state;
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+gps_engine_start (GTask *task)
+{
+ GpsEngineSelectContext *ctx;
+ MMSharedXmm *self;
+ Private *priv;
+ GError *error = NULL;
+ guint transport_protocol = 0;
+ guint pos_mode = 0;
+ gchar *cmd;
+
+ self = g_task_get_source_object (task);
+ priv = get_private (self);
+ ctx = g_task_get_task_data (task);
+
+ g_assert (!priv->gps_port);
+ priv->gps_port = shared_xmm_get_gps_control_port (self, &error);
+ if (!priv->gps_port) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ switch (ctx->state) {
+ case GPS_ENGINE_STATE_STANDALONE:
+ transport_protocol = 2;
+ pos_mode = 3;
+ break;
+ case GPS_ENGINE_STATE_AGPS_MSB:
+ transport_protocol = 1;
+ pos_mode = 1;
+ break;
+ case GPS_ENGINE_STATE_AGPS_MSA:
+ transport_protocol = 1;
+ pos_mode = 2;
+ break;
+ case GPS_ENGINE_STATE_OFF:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ mm_obj_dbg (self, "starting GPS engine...");
+
+ /*
+ * AT+XLCSLSR
+ * transport_protocol: 2 (invalid) or 1 (supl)
+ * pos_mode: 3 (standalone), 1 (msb) or 2 (msa)
+ * client_id: <empty>
+ * client_id_type: <empty>
+ * mlc_number: <empty>
+ * mlc_number_type: <empty>
+ * interval: 1 (seconds)
+ * service_type_id: <empty>
+ * pseudonym_indicator: <empty>
+ * loc_response_type: 1 (NMEA strings)
+ * nmea_mask: 118 (01110110: GGA,GSA,GSV,RMC,VTG)
+ * gnss_type: 0 (GPS or GLONASS)
+ */
+ g_assert (priv->gps_port);
+ cmd = g_strdup_printf ("AT+XLCSLSR=%u,%u,,,,,1,,,1,118,0", transport_protocol, pos_mode);
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ priv->gps_port,
+ cmd,
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)xlcslsr_ready,
+ task);
+ g_free (cmd);
+}
+
+static GTask *
+recover_pending_gps_engine_stop_task (Private *priv)
+{
+ GTask *task;
+ GpsEngineSelectContext *ctx;
+
+ /* We're taking over full ownership of the GTask at this point. */
+ if (!priv->pending_gps_engine_stop_task)
+ return NULL;
+ task = g_steal_pointer (&priv->pending_gps_engine_stop_task);
+ ctx = g_task_get_task_data (task);
+
+ /* remove timeout */
+ if (ctx->engine_stop_timeout_id) {
+ g_source_remove (ctx->engine_stop_timeout_id);
+ ctx->engine_stop_timeout_id = 0;
+ }
+
+ /* disable urc handling */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ priv->gps_port,
+ priv->xlsrstop_regex,
+ NULL, NULL, NULL);
+
+ return task;
+}
+
+static void
+gps_engine_stopped (GTask *task)
+{
+ MMSharedXmm *self;
+ GpsEngineSelectContext *ctx;
+ Private *priv;
+
+ self = g_task_get_source_object (task);
+ priv = get_private (self);
+ ctx = g_task_get_task_data (task);
+
+ g_assert (priv->gps_port);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ priv->gps_port,
+ priv->nmea_regex,
+ NULL, NULL, NULL);
+ g_clear_object (&priv->gps_port);
+ priv->gps_engine_state = GPS_ENGINE_STATE_OFF;
+
+ /* If already reached requested state, we're done */
+ if (ctx->state == priv->gps_engine_state) {
+ /* If we had an error when requesting this specific state, report it */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Otherwise, start with new state */
+ gps_engine_start (task);
+}
+
+static gboolean
+xlsrstop_urc_timeout (MMSharedXmm *self)
+{
+ GTask *task;
+ Private *priv;
+
+ priv = get_private (self);
+
+ task = recover_pending_gps_engine_stop_task (priv);
+ g_assert (task);
+
+ mm_obj_dbg (self, "timed out waiting for full GPS engine stop report, assuming stopped...");
+ gps_engine_stopped (task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+xlsrstop_urc_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMSharedXmm *self)
+{
+ GTask *task;
+ Private *priv;
+
+ priv = get_private (self);
+
+ task = recover_pending_gps_engine_stop_task (priv);
+ g_assert (task);
+
+ mm_obj_dbg (self, "GPS engine fully stopped");
+ gps_engine_stopped (task);
+}
+
+static void
+xlsrstop_ready (MMBaseModem *self,
+ GAsyncResult *res)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (!mm_base_modem_at_command_full_finish (self, res, &error)) {
+ Private *priv;
+ GTask *task;
+
+ mm_obj_dbg (self, "GPS engine stop request failed: %s", error->message);
+
+ priv = get_private (MM_SHARED_XMM (self));
+ task = recover_pending_gps_engine_stop_task (priv);
+ if (task) {
+ g_task_return_error (task, g_steal_pointer (&error));
+ g_object_unref (task);
+ }
+ return;
+ }
+
+ mm_obj_dbg (self, "GPS engine stop request accepted");
+}
+
+static void
+gps_engine_stop (GTask *task)
+{
+ MMSharedXmm *self;
+ Private *priv;
+ GpsEngineSelectContext *ctx;
+
+ self = g_task_get_source_object (task);
+ priv = get_private (self);
+ ctx = g_task_get_task_data (task);
+
+ g_assert (priv->gps_port);
+
+ /* After a +XLSRSTOP command the modem will reply OK to report that the stop
+ * request has been received, but at this point the engine may still be on.
+ * We must wait for the additional +XLSRSTOP URC to tell us that the engine
+ * is really off before going on.
+ *
+ * We do this by setting up a temporary regex match for the URC during this
+ * operation, and also by configuring a timeout so that we don't wait forever
+ * for the URC.
+ *
+ * The initial +XLSRSTOP response will be ignored unless an error is being
+ * reported.
+ *
+ * The operation task is kept in private info because it will be shared by all
+ * the possible paths that may complete it (response, URC, timeout).
+ */
+ if (priv->pending_gps_engine_stop_task) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
+ "An engine stop task is already ongoing");
+ g_object_unref (task);
+ return;
+ }
+ priv->pending_gps_engine_stop_task = task;
+
+ mm_obj_dbg (self, "launching GPS engine stop operation...");
+
+ ctx->engine_stop_timeout_id = g_timeout_add_seconds (GPS_ENGINE_STOP_TIMEOUT_SECS,
+ (GSourceFunc) xlsrstop_urc_timeout,
+ self);
+
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ priv->gps_port,
+ priv->xlsrstop_regex,
+ (MMPortSerialAtUnsolicitedMsgFn)xlsrstop_urc_received,
+ self,
+ NULL);
+
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ priv->gps_port,
+ "+XLSRSTOP",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)xlsrstop_ready,
+ NULL);
+}
+
+static void
+gps_engine_state_select (MMSharedXmm *self,
+ GpsEngineState state,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GpsEngineSelectContext *ctx;
+ GTask *task;
+ Private *priv;
+
+ priv = get_private (self);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ ctx = g_slice_new0 (GpsEngineSelectContext);
+ ctx->state = state;
+ g_task_set_task_data (task, ctx, (GDestroyNotify)gps_engine_select_context_free);
+
+ /* If already in the requested state, we're done */
+ if (state == priv->gps_engine_state) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* If states are different we always STOP first */
+ if (priv->gps_engine_state != GPS_ENGINE_STATE_OFF) {
+ gps_engine_stop (task);
+ return;
+ }
+
+ /* If GPS already stopped, go on to START right away */
+ g_assert (state != GPS_ENGINE_STATE_OFF);
+ gps_engine_start (task);
+}
+
+static GpsEngineState
+gps_engine_state_get_expected (MMModemLocationSource sources)
+{
+ /* If at lease one of GPS nmea/raw sources enabled, engine started */
+ if (sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ /* If MSA A-GPS is enabled, MSA mode */
+ if (sources & MM_MODEM_LOCATION_SOURCE_AGPS_MSA)
+ return GPS_ENGINE_STATE_AGPS_MSA;
+ /* If MSB A-GPS is enabled, MSB mode */
+ if (sources & MM_MODEM_LOCATION_SOURCE_AGPS_MSB)
+ return GPS_ENGINE_STATE_AGPS_MSB;
+ /* Otherwise, STANDALONE */
+ return GPS_ENGINE_STATE_STANDALONE;
+ }
+ /* If no GPS nmea/raw sources enabled, engine stopped */
+ return GPS_ENGINE_STATE_OFF;
+}
+
+/*****************************************************************************/
+/* Disable location gathering (Location interface) */
+
+gboolean
+mm_shared_xmm_disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+disable_gps_engine_state_select_ready (MMSharedXmm *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource source;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ if (!gps_engine_state_select_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ source = GPOINTER_TO_UINT (g_task_get_task_data (task));
+ priv->enabled_sources &= ~source;
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_disable_location_gathering_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ g_assert (priv->iface_modem_location_parent);
+ 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_xmm_disable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
+
+ priv = get_private (MM_SHARED_XMM (self));
+ g_assert (priv->iface_modem_location_parent);
+
+ /* Only consider request if it applies to one of the sources we are
+ * supporting, otherwise run parent disable */
+ if (!(priv->supported_sources & source)) {
+ /* If disabling implemented by the parent, run it. */
+ 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)parent_disable_location_gathering_ready,
+ task);
+ return;
+ }
+ /* Otherwise, we're done */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* We only expect GPS sources here */
+ g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSA |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSB));
+
+ /* Update engine based on the expected sources */
+ gps_engine_state_select (MM_SHARED_XMM (self),
+ gps_engine_state_get_expected (priv->enabled_sources & ~source),
+ (GAsyncReadyCallback) disable_gps_engine_state_select_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Enable location gathering (Location interface) */
+
+gboolean
+mm_shared_xmm_enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+enable_gps_engine_state_select_ready (MMSharedXmm *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource source;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ if (!gps_engine_state_select_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ source = GPOINTER_TO_UINT (g_task_get_task_data (task));
+ 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_XMM (self));
+
+ g_assert (priv->iface_modem_location_parent);
+ 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_xmm_enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
+
+ priv = get_private (MM_SHARED_XMM (self));
+ g_assert (priv->iface_modem_location_parent);
+
+ /* Only consider request if it applies to one of the sources we are
+ * supporting, otherwise run parent enable */
+ if (priv->iface_modem_location_parent->enable_location_gathering &&
+ priv->iface_modem_location_parent->enable_location_gathering_finish &&
+ !(priv->supported_sources & source)) {
+ priv->iface_modem_location_parent->enable_location_gathering (self,
+ source,
+ (GAsyncReadyCallback)parent_enable_location_gathering_ready,
+ task);
+ return;
+ }
+
+ /* We only expect GPS sources here */
+ g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
+ MM_MODEM_LOCATION_SOURCE_GPS_RAW |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSA |
+ MM_MODEM_LOCATION_SOURCE_AGPS_MSB));
+
+ /* Update engine based on the expected sources */
+ gps_engine_state_select (MM_SHARED_XMM (self),
+ gps_engine_state_get_expected (priv->enabled_sources | source),
+ (GAsyncReadyCallback) enable_gps_engine_state_select_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Location: Load SUPL server */
+
+gchar *
+mm_shared_xmm_location_load_supl_server_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+xlcsslp_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ gchar *supl_address;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response || !mm_xmm_parse_xlcsslp_query_response (response, &supl_address, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, supl_address, g_free);
+ g_object_unref (task);
+}
+
+void
+mm_shared_xmm_location_load_supl_server (MMIfaceModemLocation *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),
+ "+XLCSSLP?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)xlcsslp_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+
+gboolean
+mm_shared_xmm_location_set_supl_server_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+xlcsslp_set_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);
+}
+
+void
+mm_shared_xmm_location_set_supl_server (MMIfaceModemLocation *self,
+ const gchar *supl,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *cmd = NULL;
+ gchar *fqdn = NULL;
+ guint32 ip;
+ guint16 port;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ mm_parse_supl_address (supl, &fqdn, &ip, &port, NULL);
+ g_assert (port);
+ if (fqdn)
+ cmd = g_strdup_printf ("+XLCSSLP=1,%s,%u", fqdn, port);
+ else if (ip) {
+ struct in_addr a = { .s_addr = ip };
+ gchar buf[INET_ADDRSTRLEN + 1] = { 0 };
+
+ /* we got 'ip' from inet_pton(), so this next step should always succeed */
+ g_assert (inet_ntop (AF_INET, &a, buf, sizeof (buf) - 1));
+ cmd = g_strdup_printf ("+XLCSSLP=0,%s,%u", buf, port);
+ } else
+ g_assert_not_reached ();
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)xlcsslp_set_ready,
+ task);
+ g_free (cmd);
+ g_free (fqdn);
+}
+
+/*****************************************************************************/
+
+void
+mm_shared_xmm_setup_ports (MMBroadbandModem *self)
+{
+ Private *priv;
+ g_autoptr(MMPortSerialAt) gps_port = NULL;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ g_assert (priv->broadband_modem_class_parent);
+ g_assert (priv->broadband_modem_class_parent->setup_ports);
+
+ /* Parent setup first always */
+ priv->broadband_modem_class_parent->setup_ports (self);
+
+ /* Then, setup the GPS port */
+ gps_port = shared_xmm_get_gps_control_port (MM_SHARED_XMM (self), NULL);
+ if (gps_port) {
+ /* After running AT+XLSRSTOP we may get an unsolicited response
+ * reporting its status, we just ignore it. */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ gps_port,
+ priv->xlsrstop_regex,
+ NULL, NULL, NULL);
+
+ /* make sure GPS is stopped in case it was left enabled */
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ gps_port,
+ "+XLSRSTOP",
+ 3, FALSE, FALSE, NULL, NULL, NULL);
+ }
+}
+
+/*****************************************************************************/
+
+static void
+shared_xmm_init (gpointer g_iface)
+{
+}
+
+GType
+mm_shared_xmm_get_type (void)
+{
+ static GType shared_xmm_type = 0;
+
+ if (!G_UNLIKELY (shared_xmm_type)) {
+ static const GTypeInfo info = {
+ sizeof (MMSharedXmm), /* class_size */
+ shared_xmm_init, /* base_init */
+ NULL, /* base_finalize */
+ };
+
+ shared_xmm_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedXmm", &info, 0);
+ g_type_interface_add_prerequisite (shared_xmm_type, MM_TYPE_IFACE_MODEM);
+ g_type_interface_add_prerequisite (shared_xmm_type, MM_TYPE_IFACE_MODEM_LOCATION);
+ }
+
+ return shared_xmm_type;
+}
diff --git a/src/plugins/xmm/mm-shared-xmm.h b/src/plugins/xmm/mm-shared-xmm.h
new file mode 100644
index 00000000..a1f51639
--- /dev/null
+++ b/src/plugins/xmm/mm-shared-xmm.h
@@ -0,0 +1,184 @@
+/* -*- 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_SHARED_XMM_H
+#define MM_SHARED_XMM_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-signal.h"
+#include "mm-iface-modem-location.h"
+
+#define MM_TYPE_SHARED_XMM (mm_shared_xmm_get_type ())
+#define MM_SHARED_XMM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_XMM, MMSharedXmm))
+#define MM_IS_SHARED_XMM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SHARED_XMM))
+#define MM_SHARED_XMM_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_SHARED_XMM, MMSharedXmm))
+
+typedef struct _MMSharedXmm MMSharedXmm;
+
+struct _MMSharedXmm {
+ GTypeInterface g_iface;
+
+ /* Peek broadband modem class of the parent class of the object */
+ MMBroadbandModemClass * (* peek_parent_broadband_modem_class) (MMSharedXmm *self);
+
+ /* Peek location interface of the parent class of the object */
+ MMIfaceModemLocation * (* peek_parent_location_interface) (MMSharedXmm *self);
+};
+
+GType mm_shared_xmm_get_type (void);
+
+/* Shared XMM device setup */
+
+void mm_shared_xmm_setup_ports (MMBroadbandModem *self);
+
+/* Shared XMM device management support */
+
+void mm_shared_xmm_load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GArray *mm_shared_xmm_load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error);
+void mm_shared_xmm_set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_xmm_load_supported_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GArray *mm_shared_xmm_load_supported_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_load_current_bands (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GArray *mm_shared_xmm_load_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_set_current_bands (MMIfaceModem *self,
+ GArray *bands_array,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_set_current_bands_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_xmm_load_power_state (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMModemPowerState mm_shared_xmm_load_power_state_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_power_up (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_power_up_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_power_down (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_power_off (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_power_off_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+
+gboolean mm_shared_xmm_signal_check_support_finish (MMIfaceModemSignal *self,
+ GAsyncResult *res,
+ GError **error);
+
+void mm_shared_xmm_signal_check_support (MMIfaceModemSignal *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_signal_load_values_finish (MMIfaceModemSignal *self,
+ GAsyncResult *res,
+ MMSignal **cdma,
+ MMSignal **evdo,
+ MMSignal **gsm,
+ MMSignal **umts,
+ MMSignal **lte,
+ MMSignal **nr5g,
+ GError **error);
+void mm_shared_xmm_signal_load_values (MMIfaceModemSignal *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+void mm_shared_xmm_location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMModemLocationSource mm_shared_xmm_location_load_capabilities_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_disable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_location_load_supl_server (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gchar *mm_shared_xmm_location_load_supl_server_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_location_set_supl_server (MMIfaceModemLocation *self,
+ const gchar *supl,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_location_set_supl_server_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+
+#endif /* MM_SHARED_XMM_H */
diff --git a/src/plugins/xmm/mm-shared.c b/src/plugins/xmm/mm-shared.c
new file mode 100644
index 00000000..203f0fbb
--- /dev/null
+++ b/src/plugins/xmm/mm-shared.c
@@ -0,0 +1,20 @@
+/* -*- 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) 2019 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-shared.h"
+
+MM_SHARED_DEFINE_MAJOR_VERSION
+MM_SHARED_DEFINE_MINOR_VERSION
+MM_SHARED_DEFINE_NAME(Xmm)
diff --git a/src/plugins/xmm/tests/test-modem-helpers-xmm.c b/src/plugins/xmm/tests/test-modem-helpers-xmm.c
new file mode 100644
index 00000000..e40ffcab
--- /dev/null
+++ b/src/plugins/xmm/tests/test-modem-helpers-xmm.c
@@ -0,0 +1,780 @@
+/* -*- 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>
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include <locale.h>
+#include <math.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-test.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-xmm.h"
+
+#define g_assert_cmpfloat_tolerance(val1, val2, tolerance) \
+ g_assert_cmpfloat (fabs (val1 - val2), <, tolerance)
+
+/*****************************************************************************/
+/* Test XACT=? responses */
+
+static void
+validate_xact_test_response (const gchar *response,
+ const MMModemModeCombination *expected_modes,
+ guint n_expected_modes,
+ const MMModemBand *expected_bands,
+ guint n_expected_bands)
+{
+ GError *error = NULL;
+ GArray *modes = NULL;
+ GArray *bands = NULL;
+ gboolean ret;
+ guint i;
+
+ ret = mm_xmm_parse_xact_test_response (response, NULL, &modes, &bands, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ g_assert_cmpuint (modes->len, ==, n_expected_modes);
+ for (i = 0; i < modes->len; i++) {
+ MMModemModeCombination mode;
+ guint j;
+ gboolean found = FALSE;
+
+ mode = g_array_index (modes, MMModemModeCombination, i);
+ for (j = 0; !found && j < n_expected_modes; j++)
+ found = (mode.allowed == expected_modes[j].allowed && mode.preferred == expected_modes[j].preferred);
+ g_assert (found);
+ }
+ g_array_unref (modes);
+
+ g_assert_cmpuint (bands->len, ==, n_expected_bands);
+ for (i = 0; i < bands->len; i++) {
+ MMModemBand band;
+ guint j;
+ gboolean found = FALSE;
+
+ band = g_array_index (bands, MMModemBand, i);
+ for (j = 0; !found && j < n_expected_bands; j++)
+ found = (band == expected_bands[j]);
+ g_assert (found);
+ }
+ g_array_unref (bands);
+}
+
+static void
+test_xact_test_4g_only (void)
+{
+ const gchar *response =
+ "+XACT: "
+ "(0-6),(0-2),0,"
+ "101,102,103,104,105,107,108,111,112,113,117,118,119,120,121,126,128,129,130,138,139,140,141,166";
+
+ static const MMModemModeCombination expected_modes[] = {
+ { MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ };
+
+ static const MMModemBand expected_bands[] = {
+ MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_11, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_17, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_21,
+ MM_MODEM_BAND_EUTRAN_26, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_29, MM_MODEM_BAND_EUTRAN_30, MM_MODEM_BAND_EUTRAN_38,
+ MM_MODEM_BAND_EUTRAN_39, MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41, MM_MODEM_BAND_EUTRAN_66
+ };
+
+ /* NOTE: 2G and 3G modes are reported in XACT but no 2G or 3G frequencies supported */
+ validate_xact_test_response (response,
+ expected_modes, G_N_ELEMENTS (expected_modes),
+ expected_bands, G_N_ELEMENTS (expected_bands));
+}
+
+static void
+test_xact_test_3g_4g (void)
+{
+ const gchar *response =
+ "+XACT: "
+ "(0-6),(0-2),0,"
+ "1,2,4,5,8,"
+ "101,102,103,104,105,107,108,111,112,113,117,118,119,120,121,126,128,129,130,138,139,140,141,166";
+
+ static const MMModemModeCombination expected_modes[] = {
+ { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_3G },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G },
+ };
+
+ static const MMModemBand expected_bands[] = {
+ MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_11, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_17, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_21,
+ MM_MODEM_BAND_EUTRAN_26, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_29, MM_MODEM_BAND_EUTRAN_30, MM_MODEM_BAND_EUTRAN_38,
+ MM_MODEM_BAND_EUTRAN_39, MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41, MM_MODEM_BAND_EUTRAN_66
+ };
+
+ /* NOTE: 2G modes are reported in XACT but no 2G frequencies supported */
+ validate_xact_test_response (response,
+ expected_modes, G_N_ELEMENTS (expected_modes),
+ expected_bands, G_N_ELEMENTS (expected_bands));
+}
+
+static void
+test_xact_test_2g_3g_4g (void)
+{
+ const gchar *response =
+ "+XACT: "
+ "(0-6),(0-2),0,"
+ "900,1800,1900,850,"
+ "1,2,4,5,8,"
+ "101,102,103,104,105,107,108,111,112,113,117,118,119,120,121,126,128,129,130,138,139,140,141,166";
+
+ static const MMModemModeCombination expected_modes[] = {
+ { MM_MODEM_MODE_2G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_2G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, MM_MODEM_MODE_3G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, MM_MODEM_MODE_2G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_3G },
+ { MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_2G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_3G },
+ { MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, MM_MODEM_MODE_4G },
+ };
+
+ static const MMModemBand expected_bands[] = {
+ MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS, MM_MODEM_BAND_G850,
+ MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_11, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_17, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_21,
+ MM_MODEM_BAND_EUTRAN_26, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_29, MM_MODEM_BAND_EUTRAN_30, MM_MODEM_BAND_EUTRAN_38,
+ MM_MODEM_BAND_EUTRAN_39, MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41, MM_MODEM_BAND_EUTRAN_66
+ };
+
+ validate_xact_test_response (response,
+ expected_modes, G_N_ELEMENTS (expected_modes),
+ expected_bands, G_N_ELEMENTS (expected_bands));
+}
+
+/*****************************************************************************/
+/* Test XACT? responses */
+
+static void
+validate_xact_query_response (const gchar *response,
+ const MMModemModeCombination *expected_mode,
+ const MMModemBand *expected_bands,
+ guint n_expected_bands)
+{
+ GError *error = NULL;
+ GArray *bands = NULL;
+ gboolean ret;
+ guint i;
+
+ MMModemModeCombination mode = {
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE,
+ };
+
+ ret = mm_xmm_parse_xact_query_response (response, &mode, &bands, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ g_assert_cmpuint (mode.allowed, ==, expected_mode->allowed);
+ g_assert_cmpuint (mode.preferred, ==, expected_mode->preferred);
+
+ g_assert_cmpuint (bands->len, ==, n_expected_bands);
+ for (i = 0; i < bands->len; i++) {
+ MMModemBand band;
+ guint j;
+ gboolean found = FALSE;
+
+ band = g_array_index (bands, MMModemBand, i);
+ for (j = 0; !found && j < n_expected_bands; j++)
+ found = (band == expected_bands[j]);
+ g_assert (found);
+ }
+ g_array_unref (bands);
+}
+
+static void
+test_xact_query_3g_only (void)
+{
+ const gchar *response =
+ "+XACT: "
+ "1,1,,"
+ "1,2,4,5,8,"
+ "101,102,103,104,105,107,108,111,112,113,117,118,119,120,121,126,128,129,130,138,139,140,141,166";
+
+ static const MMModemModeCombination expected_mode = {
+ .allowed = MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_NONE
+ };
+
+ static const MMModemBand expected_bands[] = {
+ MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_11, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_17, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_21,
+ MM_MODEM_BAND_EUTRAN_26, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_29, MM_MODEM_BAND_EUTRAN_30, MM_MODEM_BAND_EUTRAN_38,
+ MM_MODEM_BAND_EUTRAN_39, MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41, MM_MODEM_BAND_EUTRAN_66
+ };
+
+ validate_xact_query_response (response,
+ &expected_mode,
+ expected_bands, G_N_ELEMENTS (expected_bands));
+}
+
+static void
+test_xact_query_3g_4g (void)
+{
+ const gchar *response =
+ "+XACT: "
+ "4,1,2,"
+ "1,2,4,5,8,"
+ "101,102,103,104,105,107,108,111,112,113,117,118,119,120,121,126,128,129,130,138,139,140,141,166";
+
+ static const MMModemModeCombination expected_mode = {
+ .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_3G
+ };
+
+ static const MMModemBand expected_bands[] = {
+ MM_MODEM_BAND_UTRAN_1, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8,
+ MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5,
+ MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_11, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13,
+ MM_MODEM_BAND_EUTRAN_17, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_21,
+ MM_MODEM_BAND_EUTRAN_26, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_29, MM_MODEM_BAND_EUTRAN_30, MM_MODEM_BAND_EUTRAN_38,
+ MM_MODEM_BAND_EUTRAN_39, MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41, MM_MODEM_BAND_EUTRAN_66
+ };
+
+ validate_xact_query_response (response,
+ &expected_mode,
+ expected_bands, G_N_ELEMENTS (expected_bands));
+}
+
+/*****************************************************************************/
+
+#define XACT_SET_TEST_MAX_BANDS 6
+
+typedef struct {
+ MMModemMode allowed;
+ MMModemMode preferred;
+ MMModemBand bands[XACT_SET_TEST_MAX_BANDS];
+ const gchar *expected_command;
+} XactSetTest;
+
+static const XactSetTest set_tests[] = {
+ {
+ /* 2G-only, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=0,,"
+ },
+ {
+ /* 3G-only, no explicit bands */
+ .allowed = MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=1,,"
+ },
+ {
+ /* 4G-only, no explicit bands */
+ .allowed = MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=2,,"
+ },
+ {
+ /* 2G+3G, none preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=3,,"
+ },
+ {
+ /* 2G+3G, 2G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_2G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=3,0,"
+ },
+ {
+ /* 2G+3G, 3G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+ .preferred = MM_MODEM_MODE_3G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=3,1,"
+ },
+ {
+ /* 3G+4G, none preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=4,,"
+ },
+ {
+ /* 3G+4G, 3G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_3G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=4,1,"
+ },
+ {
+ /* 3G+4G, 4G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_4G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=4,2,"
+ },
+ {
+ /* 2G+4G, none preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=5,,"
+ },
+ {
+ /* 2G+4G, 2G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_2G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=5,0,"
+ },
+ {
+ /* 2G+4G, 4G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_4G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=5,2,"
+ },
+ {
+ /* 2G+3G+4G, none preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=6,,"
+ },
+ {
+ /* 2G+3G+4G, 2G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_2G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=6,0,"
+ },
+ {
+ /* 2G+3G+4G, 3G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_3G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=6,1,"
+ },
+ {
+ /* 2G+3G+4G, 4G preferred, no explicit bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_4G,
+ .bands = { [0] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=6,2,"
+ },
+ {
+ /* 2G bands, no explicit modes */
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_EGSM,
+ [1] = MM_MODEM_BAND_DCS,
+ [2] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=,,,900,1800"
+ },
+ {
+ /* 3G bands, no explicit modes */
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_UTRAN_1,
+ [1] = MM_MODEM_BAND_UTRAN_2,
+ [2] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=,,,1,2"
+ },
+ {
+ /* 4G bands, no explicit modes */
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_EUTRAN_1,
+ [1] = MM_MODEM_BAND_EUTRAN_2,
+ [2] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=,,,101,102"
+ },
+ {
+ /* 2G, 3G and 4G bands, no explicit modes */
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_EGSM,
+ [1] = MM_MODEM_BAND_DCS,
+ [2] = MM_MODEM_BAND_UTRAN_1,
+ [3] = MM_MODEM_BAND_UTRAN_2,
+ [4] = MM_MODEM_BAND_EUTRAN_1,
+ [5] = MM_MODEM_BAND_EUTRAN_2 },
+ .expected_command = "+XACT=,,,900,1800,1,2,101,102"
+ },
+ {
+ /* Auto bands, no explicit modes */
+ .allowed = MM_MODEM_MODE_NONE,
+ .preferred = MM_MODEM_MODE_NONE,
+ .bands = { [0] = MM_MODEM_BAND_ANY,
+ [1] = MM_MODEM_BAND_UNKNOWN },
+ .expected_command = "+XACT=,,,0"
+ },
+
+ {
+ /* 2G+3G+4G with 4G preferred, and 2G+3G+4G bands */
+ .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+ .preferred = MM_MODEM_MODE_4G,
+ .bands = { [0] = MM_MODEM_BAND_EGSM,
+ [1] = MM_MODEM_BAND_DCS,
+ [2] = MM_MODEM_BAND_UTRAN_1,
+ [3] = MM_MODEM_BAND_UTRAN_2,
+ [4] = MM_MODEM_BAND_EUTRAN_1,
+ [5] = MM_MODEM_BAND_EUTRAN_2 },
+ .expected_command = "+XACT=6,2,,900,1800,1,2,101,102"
+ },
+};
+
+static void
+validate_xact_set_command (const MMModemMode allowed,
+ const MMModemMode preferred,
+ const MMModemBand *bands,
+ guint n_bands,
+ const gchar *expected_command)
+{
+ gchar *command;
+ MMModemModeCombination mode;
+ GArray *bandsarray = NULL;
+ GError *error = NULL;
+
+ if (n_bands)
+ bandsarray = g_array_append_vals (g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), n_bands), bands, n_bands);
+
+ mode.allowed = allowed;
+ mode.preferred = preferred;
+
+ command = mm_xmm_build_xact_set_command ((mode.allowed != MM_MODEM_MODE_NONE) ? &mode : NULL, bandsarray, &error);
+ g_assert_no_error (error);
+ g_assert (command);
+
+ g_assert_cmpstr (command, == , expected_command);
+
+ g_free (command);
+ if (bandsarray)
+ g_array_unref (bandsarray);
+}
+
+static void
+test_xact_set (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (set_tests); i++) {
+ guint n_bands = 0;
+ guint j;
+
+ for (j = 0; j < XACT_SET_TEST_MAX_BANDS; j++) {
+ if (set_tests[i].bands[j] != MM_MODEM_BAND_UNKNOWN)
+ n_bands++;
+ }
+
+ validate_xact_set_command (set_tests[i].allowed,
+ set_tests[i].preferred,
+ set_tests[i].bands,
+ n_bands,
+ set_tests[i].expected_command);
+ }
+}
+
+/*****************************************************************************/
+/* Test +XCESQ responses */
+
+typedef struct {
+ const gchar *str;
+
+ gboolean gsm_info;
+ guint rxlev;
+ gdouble rssi;
+ guint ber;
+
+ gboolean umts_info;
+ guint rscp_level;
+ gdouble rscp;
+ guint ecn0_level;
+ gdouble ecio;
+
+ gboolean lte_info;
+ guint rsrq_level;
+ gdouble rsrq;
+ guint rsrp_level;
+ gdouble rsrp;
+ gint rssnr_level;
+ gdouble rssnr;
+} XCesqResponseTest;
+
+static const XCesqResponseTest xcesq_response_tests[] = {
+ {
+ .str = "+XCESQ: 0,99,99,255,255,19,46,32",
+ .gsm_info = FALSE, .rxlev = 99, .ber = 99,
+ .umts_info = FALSE, .rscp_level = 255, .ecn0_level = 255,
+ .lte_info = TRUE, .rsrq_level = 19, .rsrq = -10.5, .rsrp_level = 46, .rsrp = -95.0, .rssnr_level = 32, .rssnr = 16.0
+ },
+ {
+ .str = "+XCESQ: 0,99,99,255,255,19,46,-32",
+ .gsm_info = FALSE, .rxlev = 99, .ber = 99,
+ .umts_info = FALSE, .rscp_level = 255, .ecn0_level = 255,
+ .lte_info = TRUE, .rsrq_level = 19, .rsrq = -10.5, .rsrp_level = 46, .rsrp = -95.0, .rssnr_level = -32, .rssnr = -16.0
+ },
+ {
+ .str = "+XCESQ: 0,99,99,255,255,16,47,28",
+ .gsm_info = FALSE, .rxlev = 99, .ber = 99,
+ .umts_info = FALSE, .rscp_level = 255, .ecn0_level = 255,
+ .lte_info = TRUE, .rsrq_level = 16, .rsrq = -12.0, .rsrp_level = 47, .rsrp = -94.0, .rssnr_level = 28, .rssnr = 14.0
+ },
+ {
+ .str = "+XCESQ: 0,99,99,41,29,255,255,255",
+ .gsm_info = FALSE, .rxlev = 99, .ber = 99,
+ .umts_info = TRUE, .rscp_level = 41, .rscp = -80.0, .ecn0_level = 29, .ecio = -10.0,
+ .lte_info = FALSE, .rsrq_level = 255, .rsrp_level = 255, .rssnr_level = 255
+ },
+ {
+ .str = "+XCESQ: 0,10,6,255,255,255,255,255",
+ .gsm_info = TRUE, .rxlev = 10, .rssi = -101.0, .ber = 6,
+ .umts_info = FALSE, .rscp_level = 255, .ecn0_level = 255,
+ .lte_info = FALSE, .rsrq_level = 255, .rsrp_level = 255, .rssnr_level = 255
+ }
+};
+
+static void
+test_xcesq_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (xcesq_response_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ guint rxlev = G_MAXUINT;
+ guint ber = G_MAXUINT;
+ guint rscp = G_MAXUINT;
+ guint ecn0 = G_MAXUINT;
+ guint rsrq = G_MAXUINT;
+ guint rsrp = G_MAXUINT;
+ gint rssnr = G_MAXUINT;
+
+ success = mm_xmm_parse_xcesq_query_response (xcesq_response_tests[i].str,
+ &rxlev, &ber,
+ &rscp, &ecn0,
+ &rsrq, &rsrp,
+ &rssnr, &error);
+ g_assert_no_error (error);
+ g_assert (success);
+
+ g_assert_cmpuint (xcesq_response_tests[i].rxlev, ==, rxlev);
+ g_assert_cmpuint (xcesq_response_tests[i].ber, ==, ber);
+ g_assert_cmpuint (xcesq_response_tests[i].rscp_level, ==, rscp);
+ g_assert_cmpuint (xcesq_response_tests[i].ecn0_level, ==, ecn0);
+ g_assert_cmpuint (xcesq_response_tests[i].rsrq_level, ==, rsrq);
+ g_assert_cmpuint (xcesq_response_tests[i].rsrp_level, ==, rsrp);
+ g_assert_cmpuint (xcesq_response_tests[i].rssnr_level, ==, rssnr);
+ }
+}
+
+static void
+test_xcesq_response_to_signal (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (xcesq_response_tests); i++) {
+ GError *error = NULL;
+ gboolean success;
+ MMSignal *gsm = NULL;
+ MMSignal *umts = NULL;
+ MMSignal *lte = NULL;
+
+ success = mm_xmm_xcesq_response_to_signal_info (xcesq_response_tests[i].str,
+ NULL,
+ &gsm, &umts, &lte,
+ &error);
+ g_assert_no_error (error);
+ g_assert (success);
+
+ if (xcesq_response_tests[i].gsm_info) {
+ g_assert (gsm);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rssi (gsm), xcesq_response_tests[i].rssi, 0.1);
+ g_object_unref (gsm);
+ } else
+ g_assert (!gsm);
+
+ if (xcesq_response_tests[i].umts_info) {
+ g_assert (umts);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rscp (umts), xcesq_response_tests[i].rscp, 0.1);
+ g_assert_cmpfloat_tolerance (mm_signal_get_ecio (umts), xcesq_response_tests[i].ecio, 0.1);
+ g_object_unref (umts);
+ } else
+ g_assert (!umts);
+
+ if (xcesq_response_tests[i].lte_info) {
+ g_assert (lte);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rsrq (lte), xcesq_response_tests[i].rsrq, 0.1);
+ g_assert_cmpfloat_tolerance (mm_signal_get_rsrp (lte), xcesq_response_tests[i].rsrp, 0.1);
+ g_assert_cmpfloat_tolerance (mm_signal_get_snr (lte), xcesq_response_tests[i].rssnr, 0.1);
+ g_object_unref (lte);
+ } else
+ g_assert (!lte);
+ }
+}
+
+/*****************************************************************************/
+/* AT+XLCSLSR=? response parser */
+
+typedef struct {
+ const gchar *response;
+ gboolean expected_transport_protocol_invalid_supported;
+ gboolean expected_transport_protocol_supl_supported;
+ gboolean expected_standalone_position_mode_supported;
+ gboolean expected_ms_assisted_based_position_mode_supported;
+ gboolean expected_loc_response_type_nmea_supported;
+ gboolean expected_gnss_type_gps_glonass_supported;
+} XlcslsrTest;
+
+static XlcslsrTest xlcslsr_tests[] = {
+ {
+ "+XLCSLSR:(0-2),(0-3), ,(0-1), ,(0-1),(0-7200),(0-255),(0-1),(0-2),(1-256),(0-1)",
+ TRUE, TRUE, TRUE, TRUE, TRUE, TRUE,
+ },
+ {
+ "+XLCSLSR:(0,1,2),(0,1,2,3), ,(0,1), ,(0,1),(0-7200),(0-255),(0,1),(0,1,2),(1-256),(0,1)",
+ TRUE, TRUE, TRUE, TRUE, TRUE, TRUE,
+ },
+ {
+ "+XLCSLSR:(0-1),(0-2), ,(0,1), ,(0,1),(0 -7200),(0-255),(0-1),(0),(1-256),(1)",
+ FALSE, TRUE, FALSE, TRUE, FALSE, FALSE
+ },
+};
+
+static void
+test_xlcslsr_test (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (xlcslsr_tests); i++) {
+ GError *error = NULL;
+ gboolean ret;
+ gboolean transport_protocol_invalid_supported;
+ gboolean transport_protocol_supl_supported;
+ gboolean standalone_position_mode_supported;
+ gboolean ms_assisted_based_position_mode_supported;
+ gboolean loc_response_type_nmea_supported;
+ gboolean gnss_type_gps_glonass_supported;
+
+ ret = mm_xmm_parse_xlcslsr_test_response (xlcslsr_tests[i].response,
+ &transport_protocol_invalid_supported,
+ &transport_protocol_supl_supported,
+ &standalone_position_mode_supported,
+ &ms_assisted_based_position_mode_supported,
+ &loc_response_type_nmea_supported,
+ &gnss_type_gps_glonass_supported,
+ &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ g_assert (transport_protocol_invalid_supported == xlcslsr_tests[i].expected_transport_protocol_invalid_supported);
+ g_assert (transport_protocol_supl_supported == xlcslsr_tests[i].expected_transport_protocol_supl_supported);
+ g_assert (standalone_position_mode_supported == xlcslsr_tests[i].expected_standalone_position_mode_supported);
+ g_assert (ms_assisted_based_position_mode_supported == xlcslsr_tests[i].expected_ms_assisted_based_position_mode_supported);
+ g_assert (loc_response_type_nmea_supported == xlcslsr_tests[i].expected_loc_response_type_nmea_supported);
+ g_assert (gnss_type_gps_glonass_supported == xlcslsr_tests[i].expected_gnss_type_gps_glonass_supported);
+ }
+}
+
+/*****************************************************************************/
+/* AT+XLCSSLP? response parser */
+
+typedef struct {
+ const gchar *response;
+ const gchar *expected;
+} XlcsslpQuery;
+
+static XlcsslpQuery xlcsslp_queries[] = {
+ {
+ "+XLCSSLP:1,\"www.spirent-lcs.com\",7275",
+ "www.spirent-lcs.com:7275"
+ },
+ {
+ "+XLCSSLP:0,\"123.123.123.123\",7275",
+ "123.123.123.123:7275"
+ },
+};
+
+static void
+test_xlcsslp_queries (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (xlcsslp_queries); i++) {
+ GError *error = NULL;
+ gchar *supl_server = NULL;
+ gboolean ret;
+
+ ret = mm_xmm_parse_xlcsslp_query_response (xlcsslp_queries[i].response,
+ &supl_server,
+ &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ g_assert_cmpstr (supl_server, ==, xlcsslp_queries[i].expected);
+ g_free (supl_server);
+ }
+}
+
+/*****************************************************************************/
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/xmm/xact/test/4g-only", test_xact_test_4g_only);
+ g_test_add_func ("/MM/xmm/xact/test/3g-4g", test_xact_test_3g_4g);
+ g_test_add_func ("/MM/xmm/xact/test/2g-3g-4g", test_xact_test_2g_3g_4g);
+
+ g_test_add_func ("/MM/xmm/xact/query/3g-only", test_xact_query_3g_only);
+ g_test_add_func ("/MM/xmm/xact/query/3g-4g", test_xact_query_3g_4g);
+
+ g_test_add_func ("/MM/xmm/xact/set", test_xact_set);
+
+ g_test_add_func ("/MM/xmm/xcesq/query_response", test_xcesq_response);
+ g_test_add_func ("/MM/xmm/xcesq/query_response_to_signal", test_xcesq_response_to_signal);
+
+ g_test_add_func ("/MM/xmm/xlcslsr/test", test_xlcslsr_test);
+
+ g_test_add_func ("/MM/xmm/xlcsslp/query", test_xlcsslp_queries);
+
+ return g_test_run ();
+}
diff --git a/src/plugins/zte/77-mm-zte-port-types.rules b/src/plugins/zte/77-mm-zte-port-types.rules
new file mode 100644
index 00000000..46a83aca
--- /dev/null
+++ b/src/plugins/zte/77-mm-zte-port-types.rules
@@ -0,0 +1,204 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add|change|move|bind", GOTO="mm_zte_port_types_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="19d2", GOTO="mm_zte_port_types"
+GOTO="mm_zte_port_types_end"
+
+LABEL="mm_zte_port_types"
+SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0001", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0001", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0002", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0002", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0003", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0003", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0004", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0004", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0005", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0005", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0006", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0006", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0007", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0007", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0008", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0008", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0009", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0009", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="000A", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="000A", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0012", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0012", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0015", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0015", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0016", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0016", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0017", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0017", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0018", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0018", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0019", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0019", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0021", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0021", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0024", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0024", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0025", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0025", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0030", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0030", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0031", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0031", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0033", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0033", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0037", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0037", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0039", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0039", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0042", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0042", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0043", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0043", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0048", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0048", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0049", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0049", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0052", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0052", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0054", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0054", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0055", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0055", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0057", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0057", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0058", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0058", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0061", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0063", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0063", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0064", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0064", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0066", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0066", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0078", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0078", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0082", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0082", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0091", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0091", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0104", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0104", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0106", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0106", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0108", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0108", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0113", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0113", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0117", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0117", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0118", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0118", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0121", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0121", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0122", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0122", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0123", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0123", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0124", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0124", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0125", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0125", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0126", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0126", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0128", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0128", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0156", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0156", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1007", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1007", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1008", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1008", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1010", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1254", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1254", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1268", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1268", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1515", ENV{.MM_USBIFNUM}=="00", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1515", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="2002", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="2002", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="2003", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="2003", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# Icera-based devices that use DHCP, not AT%IPDPADDR
+ATTRS{product}=="K3805-z", ENV{ID_MM_ZTE_ICERA_DHCP}="1"
+
+# MF60 exposes QMI, but it is unusable, fallback to AT+PPP
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1402", SUBSYSTEM=="usb", KERNEL=="cdc-wdm*", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1402", SUBSYSTEM=="usbmisc", KERNEL=="cdc-wdm*", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1402", SUBSYSTEM=="net", ENV{ID_MM_PORT_IGNORE}="1"
+
+LABEL="mm_zte_port_types_end"
diff --git a/src/plugins/zte/mm-broadband-modem-zte-icera.c b/src/plugins/zte/mm-broadband-modem-zte-icera.c
new file mode 100644
index 00000000..66aea942
--- /dev/null
+++ b/src/plugins/zte/mm-broadband-modem-zte-icera.c
@@ -0,0 +1,204 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-base-modem-at.h"
+#include "mm-common-zte.h"
+#include "mm-broadband-modem-zte-icera.h"
+#include "mm-modem-helpers.h"
+
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemZteIcera, mm_broadband_modem_zte_icera, MM_TYPE_BROADBAND_MODEM_ICERA, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init));
+
+struct _MMBroadbandModemZteIceraPrivate {
+ /* Unsolicited messaging setup */
+ MMCommonZteUnsolicitedSetup *unsolicited_setup;
+};
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else {
+ /* Our own setup now */
+ mm_common_zte_set_unsolicited_events_handlers (MM_BROADBAND_MODEM (self),
+ MM_BROADBAND_MODEM_ZTE_ICERA (self)->priv->unsolicited_setup,
+ TRUE);
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's setup */
+ iface_modem_3gpp_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_setup_unsolicited_events_ready,
+ task);
+}
+
+static void
+parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Our own cleanup first */
+ mm_common_zte_set_unsolicited_events_handlers (MM_BROADBAND_MODEM (self),
+ MM_BROADBAND_MODEM_ZTE_ICERA (self)->priv->unsolicited_setup,
+ FALSE);
+
+ /* And now chain up parent's cleanup */
+ iface_modem_3gpp_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_zte_icera_parent_class)->setup_ports (self);
+
+ /* Now reset the unsolicited messages we'll handle when enabled */
+ mm_common_zte_set_unsolicited_events_handlers (MM_BROADBAND_MODEM (self),
+ MM_BROADBAND_MODEM_ZTE_ICERA (self)->priv->unsolicited_setup,
+ FALSE);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemZteIcera *
+mm_broadband_modem_zte_icera_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_ZTE_ICERA,
+ 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 (AT) and Icera bearer (NET) supported */
+ MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+ MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_zte_icera_init (MMBroadbandModemZteIcera *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
+ MM_TYPE_BROADBAND_MODEM_ZTE_ICERA,
+ MMBroadbandModemZteIceraPrivate);
+ self->priv->unsolicited_setup = mm_common_zte_unsolicited_setup_new ();
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemZteIcera *self = MM_BROADBAND_MODEM_ZTE_ICERA (object);
+
+ mm_common_zte_unsolicited_setup_free (self->priv->unsolicited_setup);
+
+ G_OBJECT_CLASS (mm_broadband_modem_zte_icera_parent_class)->finalize (object);
+}
+
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+}
+
+static void
+mm_broadband_modem_zte_icera_class_init (MMBroadbandModemZteIceraClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemZteIceraPrivate));
+
+ object_class->finalize = finalize;
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/zte/mm-broadband-modem-zte-icera.h b/src/plugins/zte/mm-broadband-modem-zte-icera.h
new file mode 100644
index 00000000..fb319046
--- /dev/null
+++ b/src/plugins/zte/mm-broadband-modem-zte-icera.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_MODEM_ZTE_ICERA_H
+#define MM_BROADBAND_MODEM_ZTE_ICERA_H
+
+#include "mm-broadband-modem-icera.h"
+
+#define MM_TYPE_BROADBAND_MODEM_ZTE_ICERA (mm_broadband_modem_zte_icera_get_type ())
+#define MM_BROADBAND_MODEM_ZTE_ICERA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_ZTE_ICERA, MMBroadbandModemZteIcera))
+#define MM_BROADBAND_MODEM_ZTE_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_ZTE_ICERA, MMBroadbandModemZteIceraClass))
+#define MM_IS_BROADBAND_MODEM_ZTE_ICERA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_ZTE_ICERA))
+#define MM_IS_BROADBAND_MODEM_ZTE_ICERA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_ZTE_ICERA))
+#define MM_BROADBAND_MODEM_ZTE_ICERA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_ZTE_ICERA, MMBroadbandModemZteIceraClass))
+
+typedef struct _MMBroadbandModemZteIcera MMBroadbandModemZteIcera;
+typedef struct _MMBroadbandModemZteIceraClass MMBroadbandModemZteIceraClass;
+typedef struct _MMBroadbandModemZteIceraPrivate MMBroadbandModemZteIceraPrivate;
+
+struct _MMBroadbandModemZteIcera {
+ MMBroadbandModemIcera parent;
+ MMBroadbandModemZteIceraPrivate *priv;
+};
+
+struct _MMBroadbandModemZteIceraClass{
+ MMBroadbandModemIceraClass parent;
+};
+
+GType mm_broadband_modem_zte_icera_get_type (void);
+
+MMBroadbandModemZteIcera *mm_broadband_modem_zte_icera_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_ZTE_ICERA_H */
diff --git a/src/plugins/zte/mm-broadband-modem-zte.c b/src/plugins/zte/mm-broadband-modem-zte.c
new file mode 100644
index 00000000..35283531
--- /dev/null
+++ b/src/plugins/zte/mm-broadband-modem-zte.c
@@ -0,0 +1,763 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-errors-types.h"
+#include "mm-modem-helpers.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-common-zte.h"
+#include "mm-broadband-modem-zte.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+
+static MMIfaceModem *iface_modem_parent;
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemZte, mm_broadband_modem_zte, MM_TYPE_BROADBAND_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init));
+
+struct _MMBroadbandModemZtePrivate {
+ /* Unsolicited messaging setup */
+ MMCommonZteUnsolicitedSetup *unsolicited_setup;
+};
+
+/*****************************************************************************/
+/* Unlock retries (Modem interface) */
+
+static MMUnlockRetries *
+load_unlock_retries_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+load_unlock_retries_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ gint pin1, puk1;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ response = mm_strip_tag (response, "+ZPINPUK:");
+ if (sscanf (response, "%d,%d", &pin1, &puk1) == 2) {
+ MMUnlockRetries *retries;
+
+ retries = mm_unlock_retries_new ();
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1);
+ mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1);
+ g_task_return_pointer (task, retries, g_object_unref);
+ } else {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Invalid unlock retries response: '%s'",
+ response);
+ }
+ g_object_unref (task);
+}
+
+static void
+load_unlock_retries (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+ZPINPUK=?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)load_unlock_retries_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* After SIM unlock (Modem interface) */
+
+typedef struct {
+ guint retries;
+} ModemAfterSimUnlockContext;
+
+static gboolean
+modem_after_sim_unlock_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void modem_after_sim_unlock_context_step (GTask *task);
+
+static gboolean
+cpms_timeout_cb (GTask *task)
+{
+ ModemAfterSimUnlockContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ ctx->retries--;
+ modem_after_sim_unlock_context_step (task);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+cpms_try_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (self, res, &error) &&
+ g_error_matches (error,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_SIM_BUSY)) {
+ /* Retry in 2 seconds */
+ g_timeout_add_seconds (2, (GSourceFunc)cpms_timeout_cb, task);
+ g_error_free (error);
+ return;
+ }
+
+ if (error)
+ g_error_free (error);
+
+ /* Well, we're done */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_after_sim_unlock_context_step (GTask *task)
+{
+ MMBroadbandModemZte *self;
+ ModemAfterSimUnlockContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ if (ctx->retries == 0) {
+ /* Well... just return without error */
+ g_task_return_new_error (
+ task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Consumed all attempts to wait for SIM not being busy");
+ g_object_unref (task);
+ return;
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CPMS?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)cpms_try_ready,
+ task);
+}
+
+static gboolean
+after_sim_unlock_wait_cb (GTask *task)
+{
+ /* Attempt to disable floods of "+ZUSIMR:2" unsolicited responses that
+ * eventually fill up the device's buffers and make it crash. Normally
+ * done during probing, but if the device has a PIN enabled it won't
+ * accept the +CPMS? during the probe and we have to do it here.
+ */
+ modem_after_sim_unlock_context_step (task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+modem_after_sim_unlock (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ModemAfterSimUnlockContext *ctx;
+ GTask *task;
+
+ ctx = g_new (ModemAfterSimUnlockContext, 1);
+ ctx->retries = 3;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, g_free);
+
+ g_timeout_add_seconds (1, (GSourceFunc)after_sim_unlock_wait_cb, task);
+}
+
+/*****************************************************************************/
+/* Modem power down (Modem interface) */
+
+static gboolean
+modem_power_down_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_power_down (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Use AT+CFUN=4 for power down. It will stop the RF (IMSI detach), and
+ * keeps access to the SIM */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=4",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load supported modes (Modem interface) */
+
+static GArray *
+load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+parent_load_supported_modes_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ GArray *all;
+ GArray *combinations;
+ GArray *filtered;
+ MMModemModeCombination mode;
+
+ all = iface_modem_parent->load_supported_modes_finish (self, res, &error);
+ if (!all) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Build list of combinations */
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5);
+
+ /* 2G only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 3G only */
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ if (!mm_iface_modem_is_3gpp_lte (self)) {
+ /* 2G and 3G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G, 2G preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ /* 2G and 3G, 3G preferred */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+ } else {
+ /* 4G only */
+ mode.allowed = MM_MODEM_MODE_4G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ /* 2G, 3G and 4G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ /* Filter out those unsupported modes */
+ filtered = mm_filter_supported_modes (all, combinations, self);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Run parent's loading */
+ iface_modem_parent->load_supported_modes (
+ MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)parent_load_supported_modes_ready,
+ g_task_new (self, NULL, callback, user_data));
+}
+
+/*****************************************************************************/
+/* Load initial allowed/preferred modes (Modem interface) */
+
+static gboolean
+load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ const gchar *response;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_autoptr(GRegex) r = NULL;
+ gint cm_mode = -1;
+ gint pref_acq = -1;
+ GError *match_error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return FALSE;
+
+ r = g_regex_new ("\\+ZSNT:\\s*(\\d),(\\d),(\\d)", G_REGEX_UNGREEDY, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &match_error)) {
+ if (match_error)
+ g_propagate_error (error, match_error);
+ else
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse +ZSNT response: '%s'",
+ response);
+ return FALSE;
+ }
+
+ if (!mm_get_int_from_match_info (match_info, 1, &cm_mode) ||
+ cm_mode < 0 || (cm_mode > 2 && cm_mode != 6) ||
+ !mm_get_int_from_match_info (match_info, 3, &pref_acq) ||
+ pref_acq < 0 || pref_acq > 2) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse the allowed mode response: '%s'",
+ response);
+ return FALSE;
+ }
+
+ /* Correctly parsed! */
+ if (cm_mode == 0) {
+ /* Both 2G, 3G and LTE allowed. For LTE modems, no 2G/3G preference supported. */
+ if (pref_acq == 0 || mm_iface_modem_is_3gpp_lte (self)) {
+ /* Any allowed */
+ *allowed = MM_MODEM_MODE_ANY;
+ *preferred = MM_MODEM_MODE_NONE;
+ } else if (pref_acq == 1) {
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ *preferred = MM_MODEM_MODE_2G;
+ } else if (pref_acq == 2) {
+ *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ *preferred = MM_MODEM_MODE_3G;
+ } else
+ g_assert_not_reached ();
+ } else if (cm_mode == 1) {
+ /* GSM only */
+ *allowed = MM_MODEM_MODE_2G;
+ *preferred = MM_MODEM_MODE_NONE;
+ } else if (cm_mode == 2) {
+ /* WCDMA only */
+ *allowed = MM_MODEM_MODE_3G;
+ *preferred = MM_MODEM_MODE_NONE;
+ } else if (cm_mode == 6) {
+ /* LTE only */
+ *allowed = MM_MODEM_MODE_4G;
+ *preferred = MM_MODEM_MODE_NONE;
+ } else
+ g_assert_not_reached ();
+
+ return TRUE;
+}
+
+static void
+load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+ZSNT?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Set allowed modes (Modem interface) */
+
+static gboolean
+set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+allowed_mode_update_ready (MMBroadbandModemZte *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+
+ if (error)
+ /* Let the error be critical. */
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gchar *command;
+ gint cm_mode = -1;
+ gint pref_acq = -1;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ if (allowed == MM_MODEM_MODE_2G) {
+ cm_mode = 1;
+ pref_acq = 0;
+ } else if (allowed == MM_MODEM_MODE_3G) {
+ cm_mode = 2;
+ pref_acq = 0;
+ } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)
+ && !mm_iface_modem_is_3gpp_lte (self)) { /* LTE models do not support 2G|3G mode */
+ cm_mode = 0;
+ if (preferred == MM_MODEM_MODE_2G)
+ pref_acq = 1;
+ else if (preferred == MM_MODEM_MODE_3G)
+ pref_acq = 2;
+ else /* none preferred, so AUTO */
+ pref_acq = 0;
+ } else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G) &&
+ preferred == MM_MODEM_MODE_NONE) {
+ cm_mode = 0;
+ pref_acq = 0;
+ } else if (allowed == MM_MODEM_MODE_ANY &&
+ preferred == MM_MODEM_MODE_NONE) {
+ cm_mode = 0;
+ pref_acq = 0;
+ } else if (allowed == MM_MODEM_MODE_4G) {
+ cm_mode = 6;
+ pref_acq = 0;
+ }
+
+ if (cm_mode < 0 || pref_acq < 0) {
+ gchar *allowed_str;
+ gchar *preferred_str;
+
+ allowed_str = mm_modem_mode_build_string_from_mask (allowed);
+ preferred_str = mm_modem_mode_build_string_from_mask (preferred);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Requested mode (allowed: '%s', preferred: '%s') not "
+ "supported by the modem.",
+ allowed_str,
+ preferred_str);
+ g_object_unref (task);
+
+ g_free (allowed_str);
+ g_free (preferred_str);
+ return;
+ }
+
+ command = g_strdup_printf ("AT+ZSNT=%d,0,%d", cm_mode, pref_acq);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ command,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)allowed_mode_update_ready,
+ task);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Load access technology (Modem interface) */
+
+static gboolean
+load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ GError **error)
+{
+ const gchar *response;
+
+ /* CDMA-only devices run parent access technology checks */
+ if (mm_iface_modem_is_cdma_only (self)) {
+ return iface_modem_parent->load_access_technologies_finish (self,
+ res,
+ access_technologies,
+ mask,
+ error);
+ }
+
+ /* Otherwise process and handle +ZPAS response from 3GPP devices */
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!response)
+ return FALSE;
+
+ /* Sample response from an MF626:
+ * +ZPAS: "GPRS/EDGE","CS_ONLY"
+ */
+ response = mm_strip_tag (response, "+ZPAS:");
+ *access_technologies = mm_string_to_access_tech (response);
+ *mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK;
+ return TRUE;
+}
+
+static void
+load_access_technologies (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+
+ /* CDMA modems don't support ZPAS and thus run parent's access technology
+ * loading. */
+ if (mm_iface_modem_is_cdma_only (self)) {
+ iface_modem_parent->load_access_technologies (self, callback, user_data);
+ return;
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+ZPAS?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else {
+ /* Our own setup now */
+ mm_common_zte_set_unsolicited_events_handlers (MM_BROADBAND_MODEM (self),
+ MM_BROADBAND_MODEM_ZTE (self)->priv->unsolicited_setup,
+ TRUE);
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Chain up parent's setup */
+ iface_modem_3gpp_parent->setup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_setup_unsolicited_events_ready,
+ task);
+}
+
+static void
+parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Our own cleanup first */
+ mm_common_zte_set_unsolicited_events_handlers (MM_BROADBAND_MODEM (self),
+ MM_BROADBAND_MODEM_ZTE (self)->priv->unsolicited_setup,
+ FALSE);
+
+ /* And now chain up parent's cleanup */
+ iface_modem_3gpp_parent->cleanup_unsolicited_events (
+ self,
+ (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Setup ports (Broadband modem class) */
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ /* Call parent's setup ports first always */
+ MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_zte_parent_class)->setup_ports (self);
+
+ /* Now reset the unsolicited messages we'll handle when enabled */
+ mm_common_zte_set_unsolicited_events_handlers (MM_BROADBAND_MODEM (self),
+ MM_BROADBAND_MODEM_ZTE (self)->priv->unsolicited_setup,
+ FALSE);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemZte *
+mm_broadband_modem_zte_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM_ZTE,
+ 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_BROADBAND_MODEM_INDICATORS_DISABLED, TRUE,
+ NULL);
+}
+
+static void
+mm_broadband_modem_zte_init (MMBroadbandModemZte *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
+ MM_TYPE_BROADBAND_MODEM_ZTE,
+ MMBroadbandModemZtePrivate);
+ self->priv->unsolicited_setup = mm_common_zte_unsolicited_setup_new ();
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModemZte *self = MM_BROADBAND_MODEM_ZTE (object);
+
+ mm_common_zte_unsolicited_setup_free (self->priv->unsolicited_setup);
+
+ G_OBJECT_CLASS (mm_broadband_modem_zte_parent_class)->finalize (object);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ iface_modem_parent = g_type_interface_peek_parent (iface);
+
+ iface->modem_after_sim_unlock = modem_after_sim_unlock;
+ iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish;
+ iface->modem_power_down = modem_power_down;
+ iface->modem_power_down_finish = modem_power_down_finish;
+ iface->load_access_technologies = load_access_technologies;
+ iface->load_access_technologies_finish = load_access_technologies_finish;
+ iface->load_supported_modes = load_supported_modes;
+ iface->load_supported_modes_finish = load_supported_modes_finish;
+ iface->load_current_modes = load_current_modes;
+ iface->load_current_modes_finish = load_current_modes_finish;
+ iface->set_current_modes = set_current_modes;
+ iface->set_current_modes_finish = set_current_modes_finish;
+ iface->load_unlock_retries = load_unlock_retries;
+ iface->load_unlock_retries_finish = load_unlock_retries_finish;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+ iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+}
+
+static void
+mm_broadband_modem_zte_class_init (MMBroadbandModemZteClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemZtePrivate));
+
+ object_class->finalize = finalize;
+ broadband_modem_class->setup_ports = setup_ports;
+}
diff --git a/src/plugins/zte/mm-broadband-modem-zte.h b/src/plugins/zte/mm-broadband-modem-zte.h
new file mode 100644
index 00000000..a61eead4
--- /dev/null
+++ b/src/plugins/zte/mm-broadband-modem-zte.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_BROADBAND_MODEM_ZTE_H
+#define MM_BROADBAND_MODEM_ZTE_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_ZTE (mm_broadband_modem_zte_get_type ())
+#define MM_BROADBAND_MODEM_ZTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_ZTE, MMBroadbandModemZte))
+#define MM_BROADBAND_MODEM_ZTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_MODEM_ZTE, MMBroadbandModemZteClass))
+#define MM_IS_BROADBAND_MODEM_ZTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_ZTE))
+#define MM_IS_BROADBAND_MODEM_ZTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_MODEM_ZTE))
+#define MM_BROADBAND_MODEM_ZTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_MODEM_ZTE, MMBroadbandModemZteClass))
+
+typedef struct _MMBroadbandModemZte MMBroadbandModemZte;
+typedef struct _MMBroadbandModemZteClass MMBroadbandModemZteClass;
+typedef struct _MMBroadbandModemZtePrivate MMBroadbandModemZtePrivate;
+
+struct _MMBroadbandModemZte {
+ MMBroadbandModem parent;
+ MMBroadbandModemZtePrivate *priv;
+};
+
+struct _MMBroadbandModemZteClass{
+ MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_zte_get_type (void);
+
+MMBroadbandModemZte *mm_broadband_modem_zte_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id);
+
+#endif /* MM_BROADBAND_MODEM_ZTE_H */
diff --git a/src/plugins/zte/mm-common-zte.c b/src/plugins/zte/mm-common-zte.c
new file mode 100644
index 00000000..5c992c22
--- /dev/null
+++ b/src/plugins/zte/mm-common-zte.c
@@ -0,0 +1,141 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include "ModemManager.h"
+#include "mm-modem-helpers.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-common-zte.h"
+
+struct _MMCommonZteUnsolicitedSetup {
+ /* Regex for access-technology related notifications */
+ GRegex *zpasr_regex;
+
+ /* Requests to always ignore */
+ GRegex *zusimr_regex; /* SMS related */
+ GRegex *zdonr_regex; /* Unsolicited operator display */
+ GRegex *zpstm_regex; /* SIM request to Build Main Menu */
+ GRegex *zend_regex; /* SIM request to Rebuild Main Menu */
+};
+
+MMCommonZteUnsolicitedSetup *
+mm_common_zte_unsolicited_setup_new (void)
+{
+ MMCommonZteUnsolicitedSetup *setup;
+
+ setup = g_new (MMCommonZteUnsolicitedSetup, 1);
+
+ /* Prepare regular expressions to setup */
+
+ setup->zusimr_regex = g_regex_new ("\\r\\n\\+ZUSIMR:(.*)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ g_assert (setup->zusimr_regex != NULL);
+
+ setup->zdonr_regex = g_regex_new ("\\r\\n\\+ZDONR: (.*)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ g_assert (setup->zdonr_regex != NULL);
+
+ setup->zpasr_regex = g_regex_new ("\\r\\n\\+ZPASR:\\s*(.*)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ g_assert (setup->zpasr_regex != NULL);
+
+ setup->zpstm_regex = g_regex_new ("\\r\\n\\+ZPSTM: (.*)\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ g_assert (setup->zpstm_regex != NULL);
+
+ setup->zend_regex = g_regex_new ("\\r\\n\\+ZEND\\r\\n",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ g_assert (setup->zend_regex != NULL);
+
+ return setup;
+}
+
+void
+mm_common_zte_unsolicited_setup_free (MMCommonZteUnsolicitedSetup *setup)
+{
+ g_regex_unref (setup->zusimr_regex);
+ g_regex_unref (setup->zdonr_regex);
+ g_regex_unref (setup->zpasr_regex);
+ g_regex_unref (setup->zpstm_regex);
+ g_regex_unref (setup->zend_regex);
+ g_free (setup);
+}
+
+static void
+zpasr_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMBroadbandModem *self)
+{
+ MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ gchar *str;
+
+ str = g_match_info_fetch (info, 1);
+ if (str) {
+ act = mm_string_to_access_tech (str);
+ g_free (str);
+ }
+
+ mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
+ act,
+ MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
+}
+
+void
+mm_common_zte_set_unsolicited_events_handlers (MMBroadbandModem *self,
+ MMCommonZteUnsolicitedSetup *setup,
+ gboolean enable)
+{
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ 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 unsolicited events in given port */
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ /* Access technology related */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ setup->zpasr_regex,
+ enable ? (MMPortSerialAtUnsolicitedMsgFn)zpasr_received : NULL,
+ enable ? self : NULL,
+ NULL);
+
+ /* Other unsolicited events to always ignore */
+ if (!enable) {
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ setup->zusimr_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ setup->zdonr_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ setup->zpstm_regex,
+ NULL, NULL, NULL);
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ setup->zend_regex,
+ NULL, NULL, NULL);
+ }
+ }
+}
diff --git a/src/plugins/zte/mm-common-zte.h b/src/plugins/zte/mm-common-zte.h
new file mode 100644
index 00000000..66ee6eeb
--- /dev/null
+++ b/src/plugins/zte/mm-common-zte.h
@@ -0,0 +1,31 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_COMMON_ZTE_H
+#define MM_COMMON_ZTE_H
+
+#include "mm-broadband-modem.h"
+
+typedef struct _MMCommonZteUnsolicitedSetup MMCommonZteUnsolicitedSetup;
+MMCommonZteUnsolicitedSetup *mm_common_zte_unsolicited_setup_new (void);
+void mm_common_zte_unsolicited_setup_free (MMCommonZteUnsolicitedSetup *setup);
+
+void mm_common_zte_set_unsolicited_events_handlers (MMBroadbandModem *self,
+ MMCommonZteUnsolicitedSetup *setup,
+ gboolean enable);
+
+#endif /* MM_COMMON_ZTE_H */
diff --git a/src/plugins/zte/mm-plugin-zte.c b/src/plugins/zte/mm-plugin-zte.c
new file mode 100644
index 00000000..a39386e6
--- /dev/null
+++ b/src/plugins/zte/mm-plugin-zte.c
@@ -0,0 +1,177 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-plugin-zte.h"
+#include "mm-broadband-modem-zte.h"
+#include "mm-broadband-modem-zte-icera.h"
+
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi.h"
+#endif
+
+#if defined WITH_MBIM
+#include "mm-broadband-modem-mbim.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginZte, mm_plugin_zte, MM_TYPE_PLUGIN)
+
+MM_PLUGIN_DEFINE_MAJOR_VERSION
+MM_PLUGIN_DEFINE_MINOR_VERSION
+
+/*****************************************************************************/
+/* Custom commands for AT probing */
+
+/* Many ZTE devices will flood the port with "Message waiting" indications
+ * and eventually fill up the serial buffer and crash. We need to turn off
+ * that indicator. See NetworkManager commits
+ * 1235f71b20c92cded4abd976ccc5010649aae1a0 and
+ * f38ad328acfdc6ce29dd1380602c546b064161ae for more details.
+ *
+ * We use this command also for checking AT support in the port.
+ */
+static const MMPortProbeAtCommand custom_at_probe[] = {
+ { "ATE0+CPMS?", 3, mm_port_probe_response_processor_is_at },
+ { "ATE0+CPMS?", 3, mm_port_probe_response_processor_is_at },
+ { "ATE0+CPMS?", 3, mm_port_probe_response_processor_is_at },
+ { NULL }
+};
+
+/*****************************************************************************/
+
+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 ZTE modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_qmi_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 ZTE modem found...");
+ return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+ }
+#endif
+
+ if (mm_port_probe_list_is_icera (probes))
+ return MM_BASE_MODEM (mm_broadband_modem_zte_icera_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+
+ return MM_BASE_MODEM (mm_broadband_modem_zte_new (uid,
+ drivers,
+ mm_plugin_get_name (self),
+ vendor,
+ product));
+}
+
+static gboolean
+grab_port (MMPlugin *self,
+ MMBaseModem *modem,
+ MMPortProbe *probe,
+ GError **error)
+{
+ MMKernelDevice *port;
+ MMPortType ptype;
+
+ port = mm_port_probe_peek_port (probe);
+
+ /* Ignore net ports on non-Icera non-QMI modems */
+ ptype = mm_port_probe_get_port_type (probe);
+ if (ptype == MM_PORT_TYPE_NET && MM_IS_BROADBAND_MODEM_ZTE (modem)) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Ignoring net port in ZTE modem");
+ return FALSE;
+ }
+
+ if (mm_kernel_device_get_global_property_as_boolean (port, "ID_MM_ZTE_ICERA_DHCP")) {
+ mm_obj_dbg (self, "icera-based modem will use DHCP");
+ g_object_set (modem,
+ MM_BROADBAND_MODEM_ICERA_DEFAULT_IP_METHOD, MM_BEARER_IP_METHOD_DHCP,
+ NULL);
+ }
+
+ return mm_base_modem_grab_port (modem,
+ port,
+ ptype,
+ MM_PORT_SERIAL_AT_FLAG_NONE,
+ error);
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+ static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL };
+ static const guint16 vendor_ids[] = { 0x19d2, 0 };
+
+ return MM_PLUGIN (
+ g_object_new (MM_TYPE_PLUGIN_ZTE,
+ MM_PLUGIN_NAME, MM_MODULE_NAME,
+ MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+ MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
+ MM_PLUGIN_CUSTOM_AT_PROBE, custom_at_probe,
+ MM_PLUGIN_ALLOWED_AT, TRUE,
+ MM_PLUGIN_REQUIRED_QCDM, TRUE,
+ MM_PLUGIN_ALLOWED_QMI, TRUE,
+ MM_PLUGIN_ALLOWED_MBIM, TRUE,
+ MM_PLUGIN_ICERA_PROBE, TRUE,
+ NULL));
+}
+
+static void
+mm_plugin_zte_init (MMPluginZte *self)
+{
+}
+
+static void
+mm_plugin_zte_class_init (MMPluginZteClass *klass)
+{
+ MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+ plugin_class->create_modem = create_modem;
+ plugin_class->grab_port = grab_port;
+}
diff --git a/src/plugins/zte/mm-plugin-zte.h b/src/plugins/zte/mm-plugin-zte.h
new file mode 100644
index 00000000..353ce86e
--- /dev/null
+++ b/src/plugins/zte/mm-plugin-zte.h
@@ -0,0 +1,42 @@
+/* -*- 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) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#ifndef MM_PLUGIN_ZTE_H
+#define MM_PLUGIN_ZTE_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_ZTE (mm_plugin_zte_get_type ())
+#define MM_PLUGIN_ZTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_ZTE, MMPluginZte))
+#define MM_PLUGIN_ZTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_ZTE, MMPluginZteClass))
+#define MM_IS_PLUGIN_ZTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_ZTE))
+#define MM_IS_PLUGIN_ZTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_ZTE))
+#define MM_PLUGIN_ZTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_ZTE, MMPluginZteClass))
+
+typedef struct {
+ MMPlugin parent;
+} MMPluginZte;
+
+typedef struct {
+ MMPluginClass parent;
+} MMPluginZteClass;
+
+GType mm_plugin_zte_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_ZTE_H */