/
usb_moded-udev.c
485 lines (410 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
@file usb_moded-udev.c
Copyright (C) 2011 Nokia Corporation. All rights reserved.
@author: Philippe De Swert <philippe.de-swert@nokia.com>
This program is free software; you can redistribute it and/or
modify it under the terms of the Lesser 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 Lesser 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
*/
24
25
#include <stdio.h>
#include <stdlib.h>
26
#include <stdbool.h>
27
28
29
30
31
32
33
#include <locale.h>
#include <unistd.h>
#include <poll.h>
#include <libudev.h>
34
35
36
#include <glib.h>
#include "usb_moded-log.h"
37
#include "usb_moded-config.h"
38
39
#include "usb_moded-hw-ab.h"
#include "usb_moded.h"
40
#include "usb_moded-modes.h"
41
42
/* global variables */
43
44
45
46
static struct udev *udev;
static struct udev_monitor *mon;
static GIOChannel *iochannel;
static guint watch_id;
47
static char *dev_name = 0;
48
static int cleanup = 0;
49
/* track cable and charger connects disconnects */
50
51
static int cable = 0, charger = 0;
static guint cable_connection_timeout_id = 0;
52
53
/** Bookkeeping data for power supply locating heuristics */
54
typedef struct power_device {
55
/** Device path used by udev */
56
const char *syspath;
57
/** Likelyhood of being power supply */
58
59
60
int score;
} power_device;
61
/* static function definitions */
62
63
static gboolean monitor_udev(GIOChannel *iochannel G_GNUC_UNUSED, GIOCondition cond,
gpointer data G_GNUC_UNUSED);
64
65
66
67
68
69
static void udev_parse(struct udev_device *dev, bool initial);
static void setup_cable_connection(void);
static void setup_charger_connection(void);
static void cancel_cable_connection_timeout(void);
static void schedule_cable_connection_timeout(void);
static gboolean cable_connection_timeout_cb(gpointer data);
70
71
72
static void notify_issue (gpointer data)
{
73
74
75
/* we do not want to restart when we try to clean up */
if(cleanup)
return;
76
77
78
79
80
81
log_debug("USB connection watch destroyed, restarting it\n!");
/* restart trigger */
hwal_cleanup();
hwal_init();
}
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
static int check_device_is_usb_power_supply(const char *syspath)
{
struct udev *udev;
struct udev_device *dev = 0;
const char *udev_name;
int score = 0;
udev = udev_new();
dev = udev_device_new_from_syspath(udev, syspath);
if(!dev)
return 0;
udev_name = udev_device_get_sysname(dev);
/* try to assign a weighed score */
/* check it is no battery */
if(strstr(udev_name, "battery") || strstr(udev_name, "BAT"))
return 0;
/* if it contains usb in the name it very likely is good */
if(strstr(udev_name, "usb"))
score = score + 10;
/* often charger is also mentioned in the name */
if(strstr(udev_name, "charger"))
score = score + 5;
/* present property is used to detect activity, however online is better */
if(udev_device_get_property_value(dev, "POWER_SUPPLY_PRESENT"))
score = score + 5;
if(udev_device_get_property_value(dev, "POWER_SUPPLY_ONLINE"))
score = score + 10;
/* type is used to detect if it is a cable or dedicated charger.
Bonus points if it is there. */
if(udev_device_get_property_value(dev, "POWER_SUPPLY_TYPE"))
score = score + 10;
/* clean up */
udev_device_unref(dev);
udev_unref(udev);
return(score);
}
124
gboolean hwal_init(void)
125
{
126
char *udev_path = NULL, *udev_subsystem = NULL;
127
struct udev_device *dev;
128
129
130
struct udev_enumerate *list;
struct udev_list_entry *list_entry, *first_entry;
struct power_device power_dev;
131
int ret = 0;
132
133
cleanup = 0;
134
135
136
137
138
139
/* Create the udev object */
udev = udev_new();
if (!udev)
{
log_err("Can't create udev\n");
140
return FALSE;
141
}
142
143
144
udev_path = find_udev_path();
if(udev_path)
145
{
146
dev = udev_device_new_from_syspath(udev, udev_path);
147
g_free(udev_path);
148
}
149
150
else
dev = udev_device_new_from_syspath(udev, "/sys/class/power_supply/usb");
151
152
if (!dev)
{
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
log_debug("Trying to guess $power_supply device.\n");
power_dev.score = 0;
power_dev.syspath = 0;
list = udev_enumerate_new(udev);
udev_enumerate_add_match_subsystem(list, "power_supply");
udev_enumerate_scan_devices(list);
first_entry = udev_enumerate_get_list_entry(list);
udev_list_entry_foreach(list_entry, first_entry)
{
udev_path = (char *)udev_list_entry_get_name(list_entry);
ret = check_device_is_usb_power_supply(udev_path);
if(ret)
{
if(ret > power_dev.score)
{
power_dev.score = ret;
power_dev.syspath = udev_path;
}
}
}
/* check if we found anything with some kind of score */
if(power_dev.score > 0)
{
dev = udev_device_new_from_syspath(udev, power_dev.syspath);
}
if(!dev)
{
log_err("Unable to find $power_supply device.");
/* communicate failure, mainloop will exit and call appropriate clean-up */
return FALSE;
}
186
}
187
188
189
dev_name = strdup(udev_device_get_sysname(dev));
log_debug("device name = %s\n", dev_name);
190
191
192
mon = udev_monitor_new_from_netlink (udev, "udev");
if (!mon)
{
193
log_err("Unable to monitor the netlink\n");
194
195
/* communicate failure, mainloop will exit and call appropriate clean-up */
return FALSE;
196
}
197
198
199
200
udev_subsystem = find_udev_subsystem();
if(udev_subsystem)
{
ret = udev_monitor_filter_add_match_subsystem_devtype(mon, udev_subsystem, NULL);
201
g_free(udev_subsystem);
202
203
204
}
else
ret = udev_monitor_filter_add_match_subsystem_devtype(mon, "power_supply", NULL);
205
if(ret != 0)
206
207
208
209
{
log_err("Udev match failed.\n");
return FALSE;
}
210
ret = udev_monitor_enable_receiving (mon);
211
if(ret != 0)
212
213
214
215
{
log_err("Failed to enable monitor recieving.\n");
return FALSE;
}
216
217
/* check if we are already connected */
218
udev_parse(dev, true);
219
220
iochannel = g_io_channel_unix_new(udev_monitor_get_fd(mon));
221
watch_id = g_io_add_watch_full(iochannel, 0, G_IO_IN, monitor_udev, NULL,notify_issue);
222
223
/* everything went well */
224
udev_device_unref(dev);
225
return TRUE;
226
}
227
228
229
static gboolean monitor_udev(GIOChannel *iochannel G_GNUC_UNUSED, GIOCondition cond,
gpointer data G_GNUC_UNUSED)
230
{
231
232
struct udev_device *dev;
233
234
235
236
237
gboolean continue_watching = TRUE;
/* No code paths are allowed to bypass the release_wakelock() call below */
acquire_wakelock(USB_MODED_WAKELOCK_PROCESS_INPUT);
238
if(cond & G_IO_IN)
239
{
240
/* This normally blocks but G_IO_IN indicates that we can read */
241
dev = udev_monitor_receive_device (mon);
242
243
244
245
246
247
if (!dev)
{
/* if we get something else something bad happened stop watching to avoid busylooping */
continue_watching = FALSE;
}
else
248
{
249
/* check if it is the actual device we want to check */
250
if(!strcmp(dev_name, udev_device_get_sysname(dev)))
251
{
252
253
254
255
if(!strcmp(udev_device_get_action(dev), "change"))
{
udev_parse(dev, false);
}
256
}
257
258
udev_device_unref(dev);
259
}
260
}
261
262
263
264
265
266
267
268
269
if(cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
{
/* Unhandled errors turn io watch to virtual busyloop too */
continue_watching = FALSE;
}
release_wakelock(USB_MODED_WAKELOCK_PROCESS_INPUT);
270
if (!continue_watching && watch_id )
271
{
272
watch_id = 0;
273
274
275
276
log_crit("udev io watch disabled");
}
return continue_watching;
277
278
}
279
void hwal_cleanup(void)
280
{
281
282
283
284
cleanup = 1;
log_debug("HWhal cleanup\n");
285
286
287
288
289
290
291
292
293
294
if(watch_id != 0)
{
g_source_remove(watch_id);
watch_id = 0;
}
if(iochannel != NULL)
{
g_io_channel_unref(iochannel);
iochannel = NULL;
}
295
cancel_cable_connection_timeout();
296
free(dev_name);
297
udev_monitor_unref(mon);
298
299
300
udev_unref(udev);
}
301
static void setup_cable_connection(void)
302
{
303
304
305
306
307
308
309
310
311
312
313
314
315
316
cancel_cable_connection_timeout();
log_debug("UDEV:USB pc cable connected\n");
cable = 1;
charger = 0;
set_usb_connected(TRUE);
}
static void setup_charger_connection(void)
{
cancel_cable_connection_timeout();
log_debug("UDEV:USB dedicated charger connected\n");
317
318
319
320
321
322
323
324
325
326
327
328
329
330
if (cable) {
/* The connection was initially reported incorrectly
* as pc cable, then later on declared as charger.
*
* Clear "connected" boolean flag so that the
* set_charger_connected() call below acts as if charger
* were detected already on connect: mode gets declared
* as dedicated charger (and the mode selection dialog
* shown by ui gets closed).
*/
set_usb_connection_state(FALSE);
}
331
charger = 1;
332
333
334
335
336
337
338
339
340
341
342
cable = 0;
set_charger_connected(TRUE);
}
static gboolean cable_connection_timeout_cb(gpointer data)
{
log_debug("connect delay: timeout");
cable_connection_timeout_id = 0;
setup_cable_connection();
343
344
345
return FALSE;
}
346
static void cancel_cable_connection_timeout(void)
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
if (cable_connection_timeout_id) {
log_debug("connect delay: cancel");
g_source_remove(cable_connection_timeout_id);
cable_connection_timeout_id = 0;
}
}
static void schedule_cable_connection_timeout(void)
{
/* Ignore If already connected */
if (get_usb_connection_state())
return;
if (!cable_connection_timeout_id && cable_connection_delay > 0) {
/* Dedicated charger might be initially misdetected as
* pc cable. Delay a bit befor accepting the state. */
log_debug("connect delay: started (%d ms)",
cable_connection_delay);
cable_connection_timeout_id =
g_timeout_add(cable_connection_delay,
cable_connection_timeout_cb,
NULL);
}
else {
/* If more udev events indicating cable connection
* are received while waiting, accept immediately. */
setup_cable_connection();
}
}
static void udev_parse(struct udev_device *dev, bool initial)
{
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
/* udev properties we are interested in */
const char *power_supply_present = 0;
const char *power_supply_online = 0;
const char *power_supply_type = 0;
/* Assume there is no usb connection until proven otherwise */
bool connected = false;
/* Unless debug logging has been request via command line,
* suppress warnings about potential property issues and/or
* fallback strategies applied (to avoid spamming due to the
* code below seeing the same property values over and over
* again also in stable states).
*/
bool warnings = log_p(LOG_DEBUG);
397
398
399
400
401
/*
* Check for present first as some drivers use online for when charging
* is enabled
*/
402
403
404
405
power_supply_present = udev_device_get_property_value(dev, "POWER_SUPPLY_PRESENT");
if (!power_supply_present) {
power_supply_present =
power_supply_online = udev_device_get_property_value(dev, "POWER_SUPPLY_ONLINE");
406
}
407
408
if (power_supply_present && !strcmp(power_supply_present, "1"))
409
410
411
412
413
414
415
416
417
418
419
connected = true;
/* Transition period = Connection status derived from udev
* events disagrees with usb-moded side bookkeeping. */
if (connected != get_usb_connection_state()) {
/* Enable udev property diagnostic logging */
warnings = true;
/* Block suspend briefly */
delay_suspend();
}
420
/* disconnect */
421
if (!connected) {
422
423
424
if (warnings && !power_supply_present)
log_err("No usable power supply indicator\n");
425
426
427
log_debug("DISCONNECTED");
cancel_cable_connection_timeout();
428
429
430
if (charger) {
log_debug("UDEV:USB dedicated charger disconnected\n");
431
set_charger_connected(FALSE);
432
433
434
435
436
437
438
439
440
441
}
if (cable) {
log_debug("UDEV:USB cable disconnected\n");
set_usb_connected(FALSE);
}
cable = 0;
charger = 0;
}
442
else {
443
444
if (warnings && power_supply_online)
log_warning("Using online property\n");
445
446
power_supply_type = udev_device_get_property_value(dev, "POWER_SUPPLY_TYPE");
447
448
449
450
451
/*
* Power supply type might not exist also :(
* Send connected event but this will not be able
* to discriminate between charger/cable.
*/
452
453
454
455
if (!power_supply_type) {
if( warnings )
log_warning("Fallback since cable detection might not be accurate. "
"Will connect on any voltage on charger.\n");
456
457
458
schedule_cable_connection_timeout();
goto cleanup;
}
459
460
log_debug("CONNECTED - POWER_SUPPLY_TYPE = %s", power_supply_type);
461
462
463
if (!strcmp(power_supply_type, "USB") ||
!strcmp(power_supply_type, "USB_CDP")) {
464
465
466
467
if( initial )
setup_cable_connection();
else
schedule_cable_connection_timeout();
468
}
469
470
471
else if (!strcmp(power_supply_type, "USB_DCP") ||
!strcmp(power_supply_type, "USB_HVDCP") ||
!strcmp(power_supply_type, "USB_HVDCP_3")) {
472
setup_charger_connection();
473
}
474
else if( !strcmp(power_supply_type, "Unknown")) {
475
476
477
// nop
}
else {
478
479
if (warnings)
log_warning("unhandled power supply type: %s", power_supply_type);
480
481
}
}
482
483
484
cleanup:
return;
485
}