usb_moded 0.86.0+mer58
usb_moded-udev.c
Go to the documentation of this file.
1
30
31#include "usb_moded-udev.h"
32
33#include "usb_moded.h"
35#include "usb_moded-control.h"
37#include "usb_moded-log.h"
38
39#include <libudev.h>
40
41/* ========================================================================= *
42 * Prototypes
43 * ========================================================================= */
44
45/* ------------------------------------------------------------------------- *
46 * UMUDEV
47 * ------------------------------------------------------------------------- */
48
49static gboolean umudev_cable_state_timer_cb (gpointer aptr);
50static void umudev_cable_state_stop_timer (void);
51static void umudev_cable_state_start_timer(gint delay);
52static bool umudev_cable_state_connected (void);
53static cable_state_t umudev_cable_state_get (void);
54static void umudev_cable_state_set (cable_state_t state);
55static void umudev_cable_state_changed (void);
56static void umudev_cable_state_from_udev (cable_state_t curr);
57static void umudev_io_error_cb (gpointer data);
58static gboolean umudev_io_input_cb (GIOChannel *iochannel, GIOCondition cond, gpointer data);
59static void umudev_parse_properties (struct udev_device *dev, bool initial);
60static int umudev_score_as_power_supply (const char *syspath);
61gboolean umudev_init (void);
62void umudev_quit (void);
63
64/* ========================================================================= *
65 * Data
66 * ========================================================================= */
67
68/* global variables */
69static struct udev *umudev_object = 0;
70static struct udev_monitor *umudev_monitor = 0;
71static gchar *umudev_sysname = 0;
72static guint umudev_watch_id = 0;
73static bool umudev_in_cleanup = false;
74
76static cable_state_t umudev_cable_state_current = CABLE_STATE_UNKNOWN;
77
79static cable_state_t umudev_cable_state_active = CABLE_STATE_UNKNOWN;
80
82static cable_state_t umudev_cable_state_previous = CABLE_STATE_UNKNOWN;
83
85static guint umudev_cable_state_timer_id = 0;
86static gint umudev_cable_state_timer_delay = -1;
87
88/* ========================================================================= *
89 * cable state
90 * ========================================================================= */
91
92static gboolean umudev_cable_state_timer_cb(gpointer aptr)
93{
94 LOG_REGISTER_CONTEXT;
95
96 (void)aptr;
97 umudev_cable_state_timer_id = 0;
98 umudev_cable_state_timer_delay = -1;
99
100 log_debug("trigger delayed transfer to: %s",
101 cable_state_repr(umudev_cable_state_current));
102 umudev_cable_state_set(umudev_cable_state_current);
103 return FALSE;
104}
105
106static void umudev_cable_state_stop_timer(void)
107{
108 LOG_REGISTER_CONTEXT;
109
110 if( umudev_cable_state_timer_id ) {
111 log_debug("cancel delayed transfer to: %s",
112 cable_state_repr(umudev_cable_state_current));
113 g_source_remove(umudev_cable_state_timer_id),
114 umudev_cable_state_timer_id = 0;
115 umudev_cable_state_timer_delay = -1;
116 }
117}
118
119static void umudev_cable_state_start_timer(gint delay)
120{
121 LOG_REGISTER_CONTEXT;
122
123 if( umudev_cable_state_timer_delay != delay ) {
124 umudev_cable_state_stop_timer();
125 }
126
127 if( !umudev_cable_state_timer_id ) {
128 log_debug("schedule delayed transfer to: %s",
129 cable_state_repr(umudev_cable_state_current));
130 umudev_cable_state_timer_id =
131 g_timeout_add(delay,
132 umudev_cable_state_timer_cb, 0);
133 umudev_cable_state_timer_delay = delay;
134 }
135}
136
137static bool
138umudev_cable_state_connected(void)
139{
140 LOG_REGISTER_CONTEXT;
141
142 bool connected = false;
143 switch( umudev_cable_state_get() ) {
144 default:
145 break;
146 case CABLE_STATE_CHARGER_CONNECTED:
147 case CABLE_STATE_PC_CONNECTED:
148 connected = true;
149 break;
150 }
151 return connected;
152}
153
154static cable_state_t umudev_cable_state_get(void)
155{
156 LOG_REGISTER_CONTEXT;
157
158 return umudev_cable_state_active;
159}
160
161static void umudev_cable_state_set(cable_state_t state)
162{
163 LOG_REGISTER_CONTEXT;
164
165 umudev_cable_state_stop_timer();
166
167 if( umudev_cable_state_active == state )
168 goto EXIT;
169
170 umudev_cable_state_previous = umudev_cable_state_active;
171 umudev_cable_state_active = state;
172
173 log_debug("cable_state: %s -> %s",
174 cable_state_repr(umudev_cable_state_previous),
175 cable_state_repr(umudev_cable_state_active));
176
177 umudev_cable_state_changed();
178
179EXIT:
180 return;
181}
182
183static void umudev_cable_state_changed(void)
184{
185 LOG_REGISTER_CONTEXT;
186
187 /* The rest of usb-moded separates charger
188 * and pc connection states... make single
189 * state tracking compatible with that. */
190
191 /* First handle pc/charger disconnect based
192 * on previous state.
193 */
194 switch( umudev_cable_state_previous ) {
195 default:
196 case CABLE_STATE_DISCONNECTED:
197 /* dontcare */
198 break;
199 case CABLE_STATE_CHARGER_CONNECTED:
200 umdbus_send_event_signal(CHARGER_DISCONNECTED);
201 break;
202 case CABLE_STATE_PC_CONNECTED:
203 umdbus_send_event_signal(USB_DISCONNECTED);
204 break;
205 }
206
207 /* Then handle pc/charger connect based
208 * on current state.
209 */
210
211 switch( umudev_cable_state_active ) {
212 default:
213 case CABLE_STATE_DISCONNECTED:
214 /* dontcare */
215 break;
216 case CABLE_STATE_CHARGER_CONNECTED:
217 umdbus_send_event_signal(CHARGER_CONNECTED);
218 break;
219 case CABLE_STATE_PC_CONNECTED:
221 break;
222 }
223
224 /* Then act on usb mode */
225 control_set_cable_state(umudev_cable_state_active);
226}
227
228static void umudev_cable_state_from_udev(cable_state_t curr)
229{
230 LOG_REGISTER_CONTEXT;
231
232 cable_state_t prev = umudev_cable_state_current;
233 umudev_cable_state_current = curr;
234
235 if( prev == curr )
236 goto EXIT;
237
238 log_debug("reported cable state: %s -> %s",
239 cable_state_repr(prev),
240 cable_state_repr(curr));
241
242 /* Because mode transitions are handled synchronously and can thus
243 * block the usb-moded mainloop, we might end up receiving a bursts
244 * of stale udev events after returning from mode switch - including
245 * multiple cable connect / disconnect events due to user replugging
246 * the cable in frustration of things taking too long.
247 */
248
249 if( curr == CABLE_STATE_DISCONNECTED ) {
250 /* If we see any disconnect events, those must be acted on
251 * immediately to get the 1st disconnect handled.
252 */
253 umudev_cable_state_set(curr);
254 }
255 else {
256 /* All other transitions are handled with at least 100 ms delay.
257 * This should compress multiple stale disconnect + connect
258 * pairs into single action.
259 */
260 gint delay = 100;
261
262 if( curr == CABLE_STATE_PC_CONNECTED && prev != CABLE_STATE_UNKNOWN ) {
265 }
266
267 umudev_cable_state_start_timer(delay);
268 }
269
270EXIT:
271 return;
272}
273
274/* ========================================================================= *
275 * legacy code
276 * ========================================================================= */
277
278static void umudev_io_error_cb(gpointer data)
279{
280 LOG_REGISTER_CONTEXT;
281
282 (void)data;
283
284 /* we do not want to restart when we try to clean up */
285 if( !umudev_in_cleanup ) {
286 log_debug("USB connection watch destroyed, restarting it\n!");
287 /* restart trigger */
288 umudev_quit();
289 umudev_init();
290 }
291}
292
293static gboolean umudev_io_input_cb(GIOChannel *iochannel, GIOCondition cond, gpointer data)
294{
295 LOG_REGISTER_CONTEXT;
296
297 (void)iochannel;
298 (void)data;
299
300 gboolean continue_watching = TRUE;
301
302 /* No code paths are allowed to bypass the common_release_wakelock() call below */
304
305 if( cond & G_IO_IN )
306 {
307 /* This normally blocks but G_IO_IN indicates that we can read */
308 struct udev_device *dev = udev_monitor_receive_device(umudev_monitor);
309 if( !dev )
310 {
311 /* if we get something else something bad happened stop watching to avoid busylooping */
312 continue_watching = FALSE;
313 }
314 else
315 {
316 /* check if it is the actual device we want to check */
317 if( !strcmp(umudev_sysname, udev_device_get_sysname(dev)) )
318 {
319 if( !strcmp(udev_device_get_action(dev), "change") )
320 {
321 umudev_parse_properties(dev, false);
322 }
323 }
324
325 udev_device_unref(dev);
326 }
327 }
328
329 if( cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL) )
330 {
331 /* Unhandled errors turn io watch to virtual busyloop too */
332 continue_watching = FALSE;
333 }
334
335 if( !continue_watching && umudev_watch_id )
336 {
337 umudev_watch_id = 0;
338 log_crit("udev io watch disabled");
339 }
340
342
343 return continue_watching;
344}
345
346static void umudev_parse_properties(struct udev_device *dev, bool initial)
347{
348 LOG_REGISTER_CONTEXT;
349
350 (void)initial;
351
352 /* udev properties we are interested in */
353 const char *power_supply_present = 0;
354 const char *power_supply_online = 0;
355 const char *power_supply_type = 0;
356
357 /* Assume there is no usb connection until proven otherwise */
358 bool connected = false;
359
360 /* Unless debug logging has been request via command line,
361 * suppress warnings about potential property issues and/or
362 * fallback strategies applied (to avoid spamming due to the
363 * code below seeing the same property values over and over
364 * again also in stable states).
365 */
366 bool warnings = log_p(LOG_DEBUG);
367
368 /*
369 * Check for present first as some drivers use online for when charging
370 * is enabled
371 */
372 power_supply_present = udev_device_get_property_value(dev, "POWER_SUPPLY_PRESENT");
373 if( !power_supply_present ) {
374 power_supply_present =
375 power_supply_online = udev_device_get_property_value(dev, "POWER_SUPPLY_ONLINE");
376 }
377
378 if( power_supply_present && !strcmp(power_supply_present, "1") )
379 connected = true;
380
381 /* Transition period = Connection status derived from udev
382 * events disagrees with usb-moded side bookkeeping. */
383 if( connected != control_get_connection_state() ) {
384 /* Enable udev property diagnostic logging */
385 warnings = true;
386 /* Block suspend briefly */
388 }
389
390 if( !connected ) {
391 /* Handle: Disconnected */
392
393 if( warnings && !power_supply_present )
394 log_err("No usable power supply indicator\n");
395 umudev_cable_state_from_udev(CABLE_STATE_DISCONNECTED);
396 }
397 else {
398 if( warnings && power_supply_online )
399 log_warning("Using online property\n");
400
401 /* At least h4113 i.e. "Xperia XA2 - Dual SIM" seem to have
402 * POWER_SUPPLY_REAL_TYPE udev property with information
403 * that usb-moded expects to be in POWER_SUPPLY_TYPE prop.
404 */
405 power_supply_type = udev_device_get_property_value(dev, "POWER_SUPPLY_REAL_TYPE");
406 if( !power_supply_type )
407 power_supply_type = udev_device_get_property_value(dev, "POWER_SUPPLY_TYPE");
408 /*
409 * Power supply type might not exist also :(
410 * Send connected event but this will not be able
411 * to discriminate between charger/cable.
412 */
413 if( !power_supply_type ) {
414 if( warnings )
415 log_warning("Fallback since cable detection might not be accurate. "
416 "Will connect on any voltage on charger.\n");
417 umudev_cable_state_from_udev(CABLE_STATE_PC_CONNECTED);
418 goto cleanup;
419 }
420
421 log_debug("CONNECTED - POWER_SUPPLY_TYPE = %s", power_supply_type);
422
423 if( !strcmp(power_supply_type, "USB") ||
424 !strcmp(power_supply_type, "USB_CDP") ) {
425 umudev_cable_state_from_udev(CABLE_STATE_PC_CONNECTED);
426 }
427 else if( !strcmp(power_supply_type, "USB_DCP") ||
428 !strcmp(power_supply_type, "USB_HVDCP") ||
429 !strcmp(power_supply_type, "USB_HVDCP_3") ) {
430 umudev_cable_state_from_udev(CABLE_STATE_CHARGER_CONNECTED);
431 }
432 else if( !strcmp(power_supply_type, "USB_FLOAT")) {
433 if( !umudev_cable_state_connected() )
434 log_warning("connection type detection failed, assuming charger");
435 umudev_cable_state_from_udev(CABLE_STATE_CHARGER_CONNECTED);
436 }
437 else if( !strcmp(power_supply_type, "Unknown")) {
438 // nop
439 log_warning("unknown connection type reported, assuming disconnected");
440 umudev_cable_state_from_udev(CABLE_STATE_DISCONNECTED);
441 }
442 else {
443 if( warnings )
444 log_warning("unhandled power supply type: %s", power_supply_type);
445 umudev_cable_state_from_udev(CABLE_STATE_DISCONNECTED);
446 }
447 }
448
449cleanup:
450 return;
451}
452
453static int umudev_score_as_power_supply(const char *syspath)
454{
455 LOG_REGISTER_CONTEXT;
456
457 int score = 0;
458 struct udev_device *dev = 0;
459 const char *sysname = 0;
460
461 if( !umudev_object )
462 goto EXIT;
463
464 if( !(dev = udev_device_new_from_syspath(umudev_object, syspath)) )
465 goto EXIT;
466
467 if( !(sysname = udev_device_get_sysname(dev)) )
468 goto EXIT;
469
470 /* try to assign a weighed score */
471
472 /* check that it is not a battery */
473 if(strstr(sysname, "battery") || strstr(sysname, "BAT"))
474 goto EXIT;
475
476 /* if it contains usb in the name it very likely is good */
477 if(strstr(sysname, "usb"))
478 score = score + 10;
479
480 /* often charger is also mentioned in the name */
481 if(strstr(sysname, "charger"))
482 score = score + 5;
483
484 /* present property is used to detect activity, however online is better */
485 if(udev_device_get_property_value(dev, "POWER_SUPPLY_PRESENT"))
486 score = score + 5;
487
488 if(udev_device_get_property_value(dev, "POWER_SUPPLY_ONLINE"))
489 score = score + 10;
490
491 /* type is used to detect if it is a cable or dedicated charger.
492 * Bonus points if it is there. */
493 if(udev_device_get_property_value(dev, "POWER_SUPPLY_TYPE"))
494 score = score + 10;
495
496EXIT:
497 /* clean up */
498 if( dev )
499 udev_device_unref(dev);
500
501 return score;
502}
503
504gboolean umudev_init(void)
505{
506 LOG_REGISTER_CONTEXT;
507
508 gboolean success = FALSE;
509
510 char *configured_device = NULL;
511 char *configured_subsystem = NULL;
512 struct udev_device *dev = 0;
513 GIOChannel *iochannel = 0;
514
515 int ret = 0;
516
517 /* Clear in-cleanup in case of restart */
518 umudev_in_cleanup = false;
519
520 /* Create the udev object */
521 if( !(umudev_object = udev_new()) ) {
522 log_err("Can't create umudev_object\n");
523 goto EXIT;
524 }
525
526 if( !(configured_device = config_find_udev_path()) )
527 configured_device = g_strdup("/sys/class/power_supply/usb");
528
529 if( !(configured_subsystem = config_find_udev_subsystem()) )
530 configured_subsystem = g_strdup("power_supply");
531
532 /* Try with configured / default device */
533 dev = udev_device_new_from_syspath(umudev_object, configured_device);
534
535 /* If needed, try heuristics */
536 if( !dev ) {
537 log_debug("Trying to guess $power_supply device.\n");
538
539 int current_score = 0;
540 gchar *current_name = 0;
541
542 struct udev_enumerate *list;
543 struct udev_list_entry *list_entry;
544 struct udev_list_entry *first_entry;
545
546 list = udev_enumerate_new(umudev_object);
547 udev_enumerate_add_match_subsystem(list, "power_supply");
548 udev_enumerate_scan_devices(list);
549 first_entry = udev_enumerate_get_list_entry(list);
550 udev_list_entry_foreach(list_entry, first_entry) {
551 const char *name = udev_list_entry_get_name(list_entry);
552 int score = umudev_score_as_power_supply(name);
553 if( current_score < score ) {
554 g_free(current_name);
555 current_name = g_strdup(name);
556 current_score = score;
557 }
558 }
559 /* check if we found anything with some kind of score */
560 if(current_score > 0) {
561 dev = udev_device_new_from_syspath(umudev_object, current_name);
562 }
563 g_free(current_name);
564 }
565
566 /* Give up if no power supply device was found */
567 if( !dev ) {
568 log_err("Unable to find $power_supply device.");
569 /* communicate failure, mainloop will exit and call appropriate clean-up */
570 goto EXIT;
571 }
572
573 /* Cache device name */
574 umudev_sysname = g_strdup(udev_device_get_sysname(dev));
575 log_debug("device name = %s\n", umudev_sysname);
576
577 /* Start monitoring for changes */
578 umudev_monitor = udev_monitor_new_from_netlink(umudev_object, "udev");
579 if( !umudev_monitor )
580 {
581 log_err("Unable to monitor the netlink\n");
582 /* communicate failure, mainloop will exit and call appropriate clean-up */
583 goto EXIT;
584 }
585
586 ret = udev_monitor_filter_add_match_subsystem_devtype(umudev_monitor,
587 configured_subsystem,
588 NULL);
589 if(ret != 0)
590 {
591 log_err("Udev match failed.\n");
592 goto EXIT;
593 }
594
595 ret = udev_monitor_enable_receiving(umudev_monitor);
596 if(ret != 0)
597 {
598 log_err("Failed to enable monitor recieving.\n");
599 goto EXIT;
600 }
601
602 iochannel = g_io_channel_unix_new(udev_monitor_get_fd(umudev_monitor));
603 if( !iochannel )
604 goto EXIT;
605
606 umudev_watch_id = g_io_add_watch_full(iochannel, 0, G_IO_IN, umudev_io_input_cb, NULL, umudev_io_error_cb);
607 if( !umudev_watch_id )
608 goto EXIT;
609
610 /* everything went well */
611 success = TRUE;
612
613 /* check initial status */
614 umudev_parse_properties(dev, true);
615
616EXIT:
617 /* Cleanup local resources */
618 if( iochannel )
619 g_io_channel_unref(iochannel);
620
621 if( dev )
622 udev_device_unref(dev);
623
624 g_free(configured_subsystem);
625 g_free(configured_device);
626
627 /* All or nothing */
628 if( !success )
629 umudev_quit();
630
631 return success;
632}
633
634void umudev_quit(void)
635{
636 LOG_REGISTER_CONTEXT;
637
638 umudev_in_cleanup = true;
639
640 log_debug("HWhal cleanup\n");
641
642 if( umudev_watch_id )
643 {
644 g_source_remove(umudev_watch_id),
645 umudev_watch_id = 0;
646 }
647
648 if( umudev_monitor ) {
649 udev_monitor_unref(umudev_monitor),
650 umudev_monitor = 0;
651 }
652
653 if( umudev_object ) {
654 udev_unref(umudev_object),
655 umudev_object =0 ;
656 }
657
658 g_free(umudev_sysname),
659 umudev_sysname = 0;
660
661 umudev_cable_state_stop_timer();
662}
void common_release_wakelock(const char *wakelock_name)
void common_acquire_wakelock(const char *wakelock_name)
void control_set_cable_state(cable_state_t cable_state)
bool control_get_connection_state(void)
void umdbus_send_event_signal(const char *state_ind)
#define USB_CONNECTED
bool log_p(int lev)
void usbmoded_delay_suspend(void)
Definition usb_moded.c:506
int usbmoded_get_cable_connection_delay(void)
Definition usb_moded.c:442
#define USB_MODED_WAKELOCK_PROCESS_INPUT
Definition usb_moded.h:50