summaryrefslogtreecommitdiffstats
path: root/plugins/stemgr.c
blob: 0a3e4d2d781a44fa6c7eec0636808c282d76bc13 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
/*
 *
 *  oFono - Open Source Telephony
 *
 *  Copyright (C) 2011  ST-Ericsson AB.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <errno.h>
#include <string.h>
#include <net/if.h>

#include <glib.h>
#include <gdbus.h>

#define OFONO_API_SUBJECT_TO_CHANGE
#include <ofono/plugin.h>
#include <ofono/log.h>
#include <ofono/modem.h>
#include <ofono/dbus.h>

/*
 * ST-Ericsson's Modem Init Daemon is used for controlling the modem power
 * cycles and provides a dbus API for modem state and properties.
 */
#define MGR_SERVICE		"com.stericsson.modeminit"
#define MGR_INTERFACE		MGR_SERVICE ".Manager"
#define MGR_GET_MODEMS		"GetModems"
#define GET_MODEMS_TIMEOUT	5000

#define MGR_MODEM_INTERFACE	MGR_SERVICE ".Modem"
#define PROPERTY_CHANGED	"PropertyChanged"

enum ste_state {
	STE_STATE_OFF,
	STE_STATE_READY,
	STE_STATE_RESET
};

enum ste_operation {
	STE_OP_STARTING,
	STE_OP_READY,
	STE_OP_RESTART,
	STE_OP_OFF
};

struct ste_modem {
	char *path;
	struct ofono_modem *modem;
	enum ste_state state;
	char *serial;
	char *interface;
};

static GHashTable *modem_list;
static guint modem_daemon_watch;
static guint property_changed_watch;
static DBusConnection *connection;

static void state_change(struct ste_modem *stemodem, enum ste_operation op)
{
	switch (stemodem->state) {
	case STE_STATE_OFF:
		/*
		 * The STE Modem is in state OFF and we're waiting for
		 * the Modem Init Daemon to signal that modem is ready
		 * in order to create and register the modem.
		 */
		switch (op) {
		case STE_OP_READY:
			stemodem->modem = ofono_modem_create(stemodem->serial,
								"ste");
			if (stemodem->modem == NULL) {
				ofono_error("Could not create modem %s, %s",
						stemodem->path,
						stemodem->serial);
				return;
			}

			DBG("register modem %s, %s", stemodem->path,
				stemodem->serial);

			if (stemodem->interface != NULL)
				ofono_modem_set_string(stemodem->modem,
							"Interface",
							stemodem->interface);

			ofono_modem_register(stemodem->modem);
			stemodem->state = STE_STATE_READY;
			break;
		case STE_OP_STARTING:
		case STE_OP_RESTART:
		case STE_OP_OFF:
			break;
		}
		break;
	case STE_STATE_READY:
		/*
		 * The STE Modem is ready and the modem has been created
		 * and registered in oFono. In this state two things can
		 * happen: Modem restarts or is turned off. Turning off
		 * the modem is an exceptional situation e.g. high-temperature,
		 * low battery or upgrade. In this scenario we remove the
		 * STE modem from oFono.
		 */
		switch (op) {
		case STE_OP_READY:
			break;
		case STE_OP_STARTING:
		case STE_OP_RESTART:
			DBG("reset ongoing %s", stemodem->path);
			/* Note: Consider to power off modem here? */
			stemodem->state = STE_STATE_RESET;
			break;
		case STE_OP_OFF:
			DBG("STE modem unregistering %s", stemodem->path);
			ofono_modem_remove(stemodem->modem);
			stemodem->modem = NULL;
			stemodem->state = STE_STATE_OFF;
			break;
		}
		break;
	case STE_STATE_RESET:
		/*
		 * The STE Modem is resetting.In this state two things can
		 * happen: Modem restarts succeeds, or modem is turned off.
		 */
		switch (op) {
		case STE_OP_STARTING:
		case STE_OP_RESTART:
			break;
		case STE_OP_READY:
			DBG("STE modem reset complete %s", stemodem->path);
			if (ofono_modem_get_powered(stemodem->modem))
				ofono_modem_reset(stemodem->modem);
			stemodem->state = STE_STATE_READY;
			break;
		case STE_OP_OFF:
			DBG("STE modem unregistering %s", stemodem->path);
			ofono_modem_remove(stemodem->modem);
			stemodem->modem = NULL;
			stemodem->state = STE_STATE_OFF;
			break;
		}
		break;
	}
}

static void update_property(struct ste_modem *stemodem, const char *prop,
				DBusMessageIter *iter, enum ste_operation *op,
				gboolean *op_valid)
{
	const char *value;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
		return;

	dbus_message_iter_get_basic(iter, &value);

	if (g_strcmp0(prop, "State") == 0) {
		*op_valid = TRUE;
		if (g_strcmp0(value, "booting") == 0)
			*op = STE_OP_STARTING;
		else if (g_strcmp0(value, "upgrading") == 0)
			*op = STE_OP_OFF;
		else if (g_strcmp0(value, "ready") == 0)
			*op = STE_OP_READY;
		else if (g_strcmp0(value, "off") == 0)
			*op = STE_OP_OFF;
		else if (g_strcmp0(value, "dumping") == 0)
			*op = STE_OP_RESTART;
		else
			*op_valid = FALSE;
	} else if (g_strcmp0(prop, "Interface") == 0) {
		g_free(stemodem->interface);
		stemodem->interface = g_strdup(value);
	} else if (g_strcmp0(prop, "Serial") == 0) {
		g_free(stemodem->serial);
		stemodem->serial = g_strdup(value);
	}
}

static void update_modem_properties(const char *path, DBusMessageIter *iter)
{
	enum ste_operation operation;
	gboolean operation_valid;
	struct ste_modem *stemodem = g_hash_table_lookup(modem_list, path);

	if (stemodem == NULL) {
		stemodem = g_try_new0(struct ste_modem, 1);
		if (stemodem == NULL)
			return;

		stemodem->path = g_strdup(path);
		stemodem->state = STE_STATE_OFF;
		g_hash_table_insert(modem_list, stemodem->path, stemodem);
	}

	while (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_DICT_ENTRY) {
		DBusMessageIter entry, value;
		const char *key;

		dbus_message_iter_recurse(iter, &entry);
		dbus_message_iter_get_basic(&entry, &key);

		dbus_message_iter_next(&entry);
		dbus_message_iter_recurse(&entry, &value);

		update_property(stemodem, key, &value, &operation,
					&operation_valid);

		dbus_message_iter_next(iter);
	}

	if (operation_valid)
		state_change(stemodem, operation);
}

static void get_modems_reply(DBusPendingCall *call, void *user_data)
{
	DBusMessageIter iter, list;
	DBusError err;
	DBusMessage *reply = dbus_pending_call_steal_reply(call);

	dbus_error_init(&err);

	if (dbus_set_error_from_message(&err, reply)) {
		ofono_error("%s: %s\n", err.name, err.message);
		dbus_error_free(&err);
		goto done;
	}

	if (!dbus_message_has_signature(reply, "a(oa{sv})"))
		goto done;

	if (!dbus_message_iter_init(reply, &iter))
		goto done;

	dbus_message_iter_recurse(&iter, &list);

	while (dbus_message_iter_get_arg_type(&list) == DBUS_TYPE_STRUCT) {
		DBusMessageIter entry, dict;
		const char *path;

		dbus_message_iter_recurse(&list, &entry);
		dbus_message_iter_get_basic(&entry, &path);
		dbus_message_iter_next(&entry);
		dbus_message_iter_recurse(&entry, &dict);

		update_modem_properties(path, &dict);

		dbus_message_iter_next(&list);
	}

done:
	dbus_message_unref(reply);
}

static void get_modems(void)
{
	DBusMessage *message;
	DBusPendingCall *call;

	message = dbus_message_new_method_call(MGR_SERVICE, "/",
					MGR_INTERFACE, MGR_GET_MODEMS);
	if (message == NULL) {
		ofono_error("Unable to allocate new D-Bus message");
		goto error;
	}

	dbus_message_set_auto_start(message, FALSE);

	if (!dbus_connection_send_with_reply(connection, message, &call,
						GET_MODEMS_TIMEOUT)) {
		ofono_error("Sending D-Bus message failed");
		goto error;
	}

	if (call == NULL) {
		DBG("D-Bus connection not available");
		goto error;
	}

	dbus_pending_call_set_notify(call, get_modems_reply, NULL, NULL);
	dbus_pending_call_unref(call);

error:
	dbus_message_unref(message);
}

static gboolean property_changed(DBusConnection *conn,
					DBusMessage *message, void *user_data)
{
	DBusMessageIter iter;
	struct ste_modem *stemodem;
	const char *key;
	enum ste_operation operation;
	gboolean operation_valid;

	stemodem = g_hash_table_lookup(modem_list,
					dbus_message_get_path(message));

	if (stemodem == NULL)
		return TRUE;


	if (!dbus_message_iter_init(message, &iter))
		return TRUE;

	dbus_message_iter_get_basic(&iter, &key);
	dbus_message_iter_next(&iter);

	update_property(stemodem, key, &iter, &operation, &operation_valid);

	if (operation_valid)
		state_change(stemodem, operation);

	return TRUE;
}

static void mgr_connect(DBusConnection *conn, void *user_data)
{
	property_changed_watch = g_dbus_add_signal_watch(conn,
						MGR_SERVICE, NULL,
						MGR_MODEM_INTERFACE,
						PROPERTY_CHANGED,
						property_changed,
						NULL, NULL);
	get_modems();
}

static void mgr_disconnect(DBusConnection *conn, void *user_data)
{
	g_hash_table_remove_all(modem_list);
	g_dbus_remove_watch(conn, property_changed_watch);
	property_changed_watch = 0;
}

static void destroy_stemodem(gpointer data)
{
	struct ste_modem *stemodem = data;

	ofono_modem_remove(stemodem->modem);

	g_free(stemodem->interface);
	g_free(stemodem->path);
	g_free(stemodem->serial);
	g_free(stemodem);
}

static int stemgr_init(void)
{
	connection = ofono_dbus_get_connection();

	modem_list = g_hash_table_new_full(g_str_hash, g_str_equal,
						NULL, destroy_stemodem);
	modem_daemon_watch = g_dbus_add_service_watch(connection, MGR_SERVICE,
				mgr_connect, mgr_disconnect, NULL, NULL);
	return 0;
}

static void stemgr_exit(void)
{
	g_hash_table_destroy(modem_list);
	g_dbus_remove_watch(connection, modem_daemon_watch);

	if (property_changed_watch > 0)
		g_dbus_remove_watch(connection, property_changed_watch);

}

OFONO_PLUGIN_DEFINE(stemgr, "ST-Ericsson Modem Init Daemon detection", VERSION,
			OFONO_PLUGIN_PRIORITY_DEFAULT, stemgr_init, stemgr_exit)