uqmi: add explicit check for message type when expecting a response
[oweals/uqmi.git] / commands-nas.c
1 /*
2  * uqmi -- tiny QMI support implementation
3  *
4  * Copyright (C) 2014-2015 Felix Fietkau <nbd@openwrt.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301 USA.
20  */
21
22 #include "qmi-message.h"
23
24 static struct qmi_nas_set_system_selection_preference_request sel_req;
25 static struct   {
26         bool mcc_is_set;
27         bool mnc_is_set;
28 } plmn_code_flag;
29
30 #define cmd_nas_do_set_system_selection_cb no_cb
31 static enum qmi_cmd_result
32 cmd_nas_do_set_system_selection_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
33 {
34         qmi_set_nas_set_system_selection_preference_request(msg, &sel_req);
35         return QMI_CMD_REQUEST;
36 }
37
38 static enum qmi_cmd_result
39 do_sel_network(void)
40 {
41         static bool use_sel_req = false;
42
43         if (!use_sel_req) {
44                 use_sel_req = true;
45                 uqmi_add_command(NULL, __UQMI_COMMAND_nas_do_set_system_selection);
46         }
47
48         return QMI_CMD_DONE;
49 }
50
51 #define cmd_nas_set_network_modes_cb no_cb
52 static enum qmi_cmd_result
53 cmd_nas_set_network_modes_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
54 {
55         static const struct {
56                 const char *name;
57                 QmiNasRatModePreference val;
58         } modes[] = {
59                 { "cdma", QMI_NAS_RAT_MODE_PREFERENCE_CDMA_1X | QMI_NAS_RAT_MODE_PREFERENCE_CDMA_1XEVDO },
60                 { "td-scdma", QMI_NAS_RAT_MODE_PREFERENCE_TD_SCDMA },
61                 { "gsm", QMI_NAS_RAT_MODE_PREFERENCE_GSM },
62                 { "umts", QMI_NAS_RAT_MODE_PREFERENCE_UMTS },
63                 { "lte", QMI_NAS_RAT_MODE_PREFERENCE_LTE },
64         };
65         QmiNasRatModePreference val = 0;
66         char *word;
67         int i;
68
69         for (word = strtok(arg, ",");
70              word;
71              word = strtok(NULL, ",")) {
72                 bool found = false;
73
74                 for (i = 0; i < ARRAY_SIZE(modes); i++) {
75                         if (strcmp(word, modes[i].name) != 0 &&
76                                 strcmp(word, "all") != 0)
77                                 continue;
78
79                         val |= modes[i].val;
80                         found = true;
81                 }
82
83                 if (!found) {
84                         uqmi_add_error("Invalid network mode");
85                         return QMI_CMD_EXIT;
86                 }
87         }
88
89         qmi_set(&sel_req, mode_preference, val);
90         return do_sel_network();
91 }
92
93 #define cmd_nas_set_network_preference_cb no_cb
94 static enum qmi_cmd_result
95 cmd_nas_set_network_preference_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
96 {
97         QmiNasGsmWcdmaAcquisitionOrderPreference pref = QMI_NAS_GSM_WCDMA_ACQUISITION_ORDER_PREFERENCE_AUTOMATIC;
98
99         if (!strcmp(arg, "gsm"))
100                 pref = QMI_NAS_GSM_WCDMA_ACQUISITION_ORDER_PREFERENCE_GSM;
101         else if (!strcmp(arg, "wcdma"))
102                 pref = QMI_NAS_GSM_WCDMA_ACQUISITION_ORDER_PREFERENCE_WCDMA;
103
104         qmi_set(&sel_req, gsm_wcdma_acquisition_order_preference, pref);
105         return do_sel_network();
106 }
107
108 #define cmd_nas_set_roaming_cb no_cb
109 static enum qmi_cmd_result
110 cmd_nas_set_roaming_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
111 {
112         QmiNasRoamingPreference pref;
113
114         if (!strcmp(arg, "any"))
115                 pref = QMI_NAS_ROAMING_PREFERENCE_ANY;
116         else if (!strcmp(arg, "only"))
117                 pref = QMI_NAS_ROAMING_PREFERENCE_NOT_OFF;
118         else if (!strcmp(arg, "off"))
119                 pref = QMI_NAS_ROAMING_PREFERENCE_OFF;
120         else
121                 return uqmi_add_error("Invalid argument");
122
123         qmi_set(&sel_req, roaming_preference, pref);
124         return do_sel_network();
125 }
126
127 #define cmd_nas_set_mcc_cb no_cb
128 static enum qmi_cmd_result
129 cmd_nas_set_mcc_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
130 {
131         char *err;
132         int value = strtoul(arg, &err, 10);
133         if (err && *err) {
134                 uqmi_add_error("Invalid MCC value");
135                 return QMI_CMD_EXIT;
136         }
137
138         sel_req.data.network_selection_preference.mcc = value;
139         plmn_code_flag.mcc_is_set = true;
140         return QMI_CMD_DONE;
141 }
142
143 #define cmd_nas_set_mnc_cb no_cb
144 static enum qmi_cmd_result
145 cmd_nas_set_mnc_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
146 {
147         char *err;
148         int value = strtoul(arg, &err, 10);
149         if (err && *err) {
150                 uqmi_add_error("Invalid MNC value");
151                 return QMI_CMD_EXIT;
152         }
153
154         sel_req.data.network_selection_preference.mnc = value;
155         plmn_code_flag.mnc_is_set = true;
156         return QMI_CMD_DONE;
157 }
158
159 #define cmd_nas_set_plmn_cb no_cb
160 static enum qmi_cmd_result
161 cmd_nas_set_plmn_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
162 {
163         sel_req.set.network_selection_preference = 1;
164         sel_req.data.network_selection_preference.mode = QMI_NAS_NETWORK_SELECTION_PREFERENCE_AUTOMATIC;
165
166         if (!plmn_code_flag.mcc_is_set && plmn_code_flag.mnc_is_set) {
167                 uqmi_add_error("No MCC value");
168                 return QMI_CMD_EXIT;
169         }
170
171         if (plmn_code_flag.mcc_is_set && sel_req.data.network_selection_preference.mcc) {
172                 if (!plmn_code_flag.mnc_is_set) {
173                         uqmi_add_error("No MNC value");
174                         return QMI_CMD_EXIT;
175                 } else {
176                         sel_req.data.network_selection_preference.mode = QMI_NAS_NETWORK_SELECTION_PREFERENCE_MANUAL;
177                 }
178         }
179
180         return do_sel_network();
181 }
182
183 #define cmd_nas_initiate_network_register_cb no_cb
184 static enum qmi_cmd_result
185 cmd_nas_initiate_network_register_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
186 {
187         static struct qmi_nas_initiate_network_register_request register_req = {
188                 QMI_INIT(action, QMI_NAS_NETWORK_REGISTER_TYPE_AUTOMATIC)
189         };
190
191         qmi_set_nas_initiate_network_register_request(msg, &register_req);
192         return QMI_CMD_REQUEST;
193 }
194
195 static void
196 cmd_nas_get_signal_info_cb(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg)
197 {
198         struct qmi_nas_get_signal_info_response res;
199         void *c;
200
201         qmi_parse_nas_get_signal_info_response(msg, &res);
202
203         c = blobmsg_open_table(&status, NULL);
204         if (res.set.cdma_signal_strength) {
205                 blobmsg_add_string(&status, "type", "cdma");
206                 blobmsg_add_u32(&status, "rssi", (int32_t) res.data.cdma_signal_strength.rssi);
207                 blobmsg_add_u32(&status, "ecio", (int32_t) res.data.cdma_signal_strength.ecio);
208         }
209
210         if (res.set.hdr_signal_strength) {
211                 blobmsg_add_string(&status, "type", "hdr");
212                 blobmsg_add_u32(&status, "rssi", (int32_t) res.data.hdr_signal_strength.rssi);
213                 blobmsg_add_u32(&status, "ecio", (int32_t) res.data.hdr_signal_strength.ecio);
214                 blobmsg_add_u32(&status, "io", res.data.hdr_signal_strength.io);
215         }
216
217         if (res.set.gsm_signal_strength) {
218                 blobmsg_add_string(&status, "type", "gsm");
219                 blobmsg_add_u32(&status, "signal", (int32_t) res.data.gsm_signal_strength);
220         }
221
222         if (res.set.wcdma_signal_strength) {
223                 blobmsg_add_string(&status, "type", "wcdma");
224                 blobmsg_add_u32(&status, "rssi", (int32_t) res.data.wcdma_signal_strength.rssi);
225                 blobmsg_add_u32(&status, "ecio", (int32_t) res.data.wcdma_signal_strength.ecio);
226         }
227
228         if (res.set.lte_signal_strength) {
229                 blobmsg_add_string(&status, "type", "lte");
230                 blobmsg_add_u32(&status, "rssi", (int32_t) res.data.lte_signal_strength.rssi);
231                 blobmsg_add_u32(&status, "rsrq", (int32_t) res.data.lte_signal_strength.rsrq);
232                 blobmsg_add_u32(&status, "rsrp", (int32_t) res.data.lte_signal_strength.rsrp);
233                 blobmsg_add_u32(&status, "snr", (int32_t) res.data.lte_signal_strength.snr);
234         }
235
236         if (res.set.tdma_signal_strength) {
237                 blobmsg_add_string(&status, "type", "tdma");
238                 blobmsg_add_u32(&status, "signal", (int32_t) res.data.tdma_signal_strength);
239         }
240
241         blobmsg_close_table(&status, c);
242 }
243
244 static enum qmi_cmd_result
245 cmd_nas_get_signal_info_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
246 {
247         qmi_set_nas_get_signal_info_request(msg);
248         return QMI_CMD_REQUEST;
249 }
250
251 static void
252 cmd_nas_get_serving_system_cb(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg)
253 {
254         struct qmi_nas_get_serving_system_response res;
255         static const char *reg_states[] = {
256                 [QMI_NAS_REGISTRATION_STATE_NOT_REGISTERED] = "not_registered",
257                 [QMI_NAS_REGISTRATION_STATE_REGISTERED] = "registered",
258                 [QMI_NAS_REGISTRATION_STATE_NOT_REGISTERED_SEARCHING] = "searching",
259                 [QMI_NAS_REGISTRATION_STATE_REGISTRATION_DENIED] = "registering_denied",
260                 [QMI_NAS_REGISTRATION_STATE_UNKNOWN] = "unknown",
261         };
262         void *c;
263
264         qmi_parse_nas_get_serving_system_response(msg, &res);
265
266         c = blobmsg_open_table(&status, NULL);
267         if (res.set.serving_system) {
268                 int state = res.data.serving_system.registration_state;
269
270                 if (state > QMI_NAS_REGISTRATION_STATE_UNKNOWN)
271                         state = QMI_NAS_REGISTRATION_STATE_UNKNOWN;
272
273                 blobmsg_add_string(&status, "registration", reg_states[state]);
274         }
275         if (res.set.current_plmn) {
276                 blobmsg_add_u32(&status, "plmn_mcc", res.data.current_plmn.mcc);
277                 blobmsg_add_u32(&status, "plmn_mnc", res.data.current_plmn.mnc);
278                 if (res.data.current_plmn.description)
279                         blobmsg_add_string(&status, "plmn_description", res.data.current_plmn.description);
280         }
281
282         if (res.set.roaming_indicator)
283                 blobmsg_add_u8(&status, "roaming", !res.data.roaming_indicator);
284
285         blobmsg_close_table(&status, c);
286 }
287
288 static enum qmi_cmd_result
289 cmd_nas_get_serving_system_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
290 {
291         qmi_set_nas_get_serving_system_request(msg);
292         return QMI_CMD_REQUEST;
293 }
294
295 static void
296 cmd_nas_network_scan_cb(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg)
297 {
298         static struct qmi_nas_network_scan_response res;
299         const char *network_status[] = {
300                 "current_serving",
301                 "available",
302                 "home",
303                 "roaming",
304                 "forbidden",
305                 "not_forbidden",
306                 "preferred",
307                 "not_preferred",
308         };
309         const char *radio[] = {
310                 [QMI_NAS_RADIO_INTERFACE_NONE] = "none",
311                 [QMI_NAS_RADIO_INTERFACE_CDMA_1X] = "cdma-1x",
312                 [QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO] = "cdma-1x_evdo",
313                 [QMI_NAS_RADIO_INTERFACE_AMPS] = "amps",
314                 [QMI_NAS_RADIO_INTERFACE_GSM] = "gsm",
315                 [QMI_NAS_RADIO_INTERFACE_UMTS] = "umts",
316                 [QMI_NAS_RADIO_INTERFACE_LTE] = "lte",
317                 [QMI_NAS_RADIO_INTERFACE_TD_SCDMA] = "td-scdma",
318         };
319         void *t, *c, *info, *stat;
320         int i, j;
321
322         qmi_parse_nas_network_scan_response(msg, &res);
323
324         t = blobmsg_open_table(&status, NULL);
325
326         c = blobmsg_open_array(&status, "network_info");
327         for (i = 0; i < res.data.network_information_n; i++) {
328                 info = blobmsg_open_table(&status, NULL);
329                 blobmsg_add_u32(&status, "mcc", res.data.network_information[i].mcc);
330                 blobmsg_add_u32(&status, "mnc", res.data.network_information[i].mnc);
331                 if (res.data.network_information[i].description)
332                         blobmsg_add_string(&status, "description", res.data.network_information[i].description);
333                 stat = blobmsg_open_array(&status, "status");
334                 for (j = 0; j < ARRAY_SIZE(network_status); j++) {
335                         if (!(res.data.network_information[i].network_status & (1 << j)))
336                                 continue;
337
338                         blobmsg_add_string(&status, NULL, network_status[j]);
339                 }
340                 blobmsg_close_array(&status, stat);
341                 blobmsg_close_table(&status, info);
342         }
343         blobmsg_close_array(&status, c);
344
345         c = blobmsg_open_array(&status, "radio_access_technology");
346         for (i = 0; i < res.data.radio_access_technology_n; i++) {
347                 const char *r = "unknown";
348                 int r_i = res.data.radio_access_technology[i].radio_interface;
349
350                 info = blobmsg_open_table(&status, NULL);
351                 blobmsg_add_u32(&status, "mcc", res.data.radio_access_technology[i].mcc);
352                 blobmsg_add_u32(&status, "mnc", res.data.radio_access_technology[i].mnc);
353                 if (r_i >= 0 && r_i < ARRAY_SIZE(radio))
354                         r = radio[r_i];
355
356                 blobmsg_add_string(&status, "radio", r);
357                 blobmsg_close_table(&status, info);
358         }
359         blobmsg_close_array(&status, c);
360
361         blobmsg_close_table(&status, t);
362 }
363
364 static enum qmi_cmd_result
365 cmd_nas_network_scan_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
366 {
367         struct qmi_nas_network_scan_request sreq = {
368                 QMI_INIT(network_type,
369                      QMI_NAS_NETWORK_SCAN_TYPE_GSM |
370                      QMI_NAS_NETWORK_SCAN_TYPE_UMTS |
371                      QMI_NAS_NETWORK_SCAN_TYPE_LTE |
372                      QMI_NAS_NETWORK_SCAN_TYPE_TD_SCDMA),
373         };
374
375         qmi_set_nas_network_scan_request(msg, &sreq);
376         return QMI_CMD_REQUEST;
377 }