cinterion: add support for mode setting using SXRAT

The previously used COPS command to set to LTE-only mode did not work
for an ELS81 modem.
Now ^SXRAT is used to switch modes instead of COPS, if SXRAT is
supported by the modem.
This commit is contained in:
Christian Taedcke
2022-05-18 15:45:57 +02:00
committed by Aleksander Morgado
parent fcd393a6c2
commit a25b45f795
4 changed files with 601 additions and 15 deletions

View File

@@ -88,6 +88,10 @@ struct _MMBroadbandModemCinterionPrivate {
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 */
@@ -100,6 +104,10 @@ struct _MMBroadbandModemCinterionPrivate {
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;
@@ -1797,15 +1805,186 @@ parent_load_supported_modes_ready (MMIfaceModem *self,
}
static void
load_supported_modes (MMIfaceModem *self,
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,
g_task_new (self, NULL, callback, user_data));
task);
}
/*****************************************************************************/
@@ -1849,20 +2028,15 @@ allowed_access_technology_update_ready (MMBroadbandModemCinterion *self,
}
static void
set_current_modes (MMIfaceModem *_self,
MMModemMode allowed,
MMModemMode preferred,
GAsyncReadyCallback callback,
gpointer user_data)
cops_set_current_modes (MMBroadbandModemCinterion *self,
MMModemMode allowed,
MMModemMode preferred,
GTask *task)
{
MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self);
gchar *command;
GTask *task;
gchar *command;
g_assert (preferred == MM_MODEM_MODE_NONE);
task = g_task_new (self, NULL, callback, user_data);
/* 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
@@ -1874,11 +2048,11 @@ set_current_modes (MMIfaceModem *_self,
* which is based on the quality of the connection.
*/
if (mm_iface_modem_is_4g (_self) && allowed == MM_MODEM_MODE_4G)
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 (_self) && allowed == MM_MODEM_MODE_3G)
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 (_self) && allowed == MM_MODEM_MODE_2G)
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
@@ -1902,6 +2076,60 @@ set_current_modes (MMIfaceModem *_self,
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) */
@@ -2917,6 +3145,7 @@ mm_broadband_modem_cinterion_init (MMBroadbandModemCinterion *self)
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);
@@ -2924,6 +3153,8 @@ mm_broadband_modem_cinterion_init (MMBroadbandModemCinterion *self)
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
@@ -2943,6 +3174,10 @@ finalize (GObject *object)
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);

View File

@@ -637,6 +637,94 @@ out:
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 */
@@ -1651,3 +1739,66 @@ mm_cinterion_build_auth_string (gpointer log_object,
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);
}

View File

@@ -83,6 +83,15 @@ gboolean mm_cinterion_parse_cnmi_test (const gchar *response,
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 */
@@ -192,4 +201,11 @@ gchar *mm_cinterion_build_auth_string (gpointer log_object,
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 */

View File

@@ -1743,6 +1743,187 @@ test_sgauth_response (void)
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)
@@ -1778,6 +1959,9 @@ int main (int argc, char **argv)
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 ();
}