usb_moded 0.86.0+mer64
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 * Constants
43 * ========================================================================= */
44
45// Properties present in battery devices
46#define PROP_CAPACITY "POWER_SUPPLY_CAPACITY"
47// Properties present in charger devices
48#define PROP_ONLINE "POWER_SUPPLY_ONLINE"
49#define PROP_TYPE "POWER_SUPPLY_TYPE"
50#define PROP_REAL_TYPE "POWER_SUPPLY_REAL_TYPE"
51// Properties common to both battery and charger devices
52#define PROP_STATUS "POWER_SUPPLY_STATUS"
53#define PROP_PRESENT "POWER_SUPPLY_PRESENT"
54
55/* ========================================================================= *
56 * Prototypes
57 * ========================================================================= */
58
59/* ------------------------------------------------------------------------- *
60 * UTILITY
61 * ------------------------------------------------------------------------- */
62
63static const char *umudev_pretty_string(const char *str);
64static bool umudev_white_p (int ch);
65static bool umudev_black_p (int ch);
66static char *umudev_strip (char *str);
67static char *umudev_extract_token(char **ppos);
68static gchar *umudev_read_textfile(const char *dirpath, const char *filename);
69static gchar *umudev_get_config (const char *key);
70static struct udev_device **umudev_get_devices (const char *subsystem);
71static void umudev_free_devices (struct udev_device **devices);
72
73/* ------------------------------------------------------------------------- *
74 * UMUDEV_CHARGER
75 * ------------------------------------------------------------------------- */
76
77static bool umudev_charger_set_online (const char *online);
78static bool umudev_charger_set_type (const char *type);
79static void umudev_charger_update_from (struct udev_device *dev);
80static int umudev_charger_get_score (struct udev_device *dev);
81static void umudev_charger_find_device (void);
82static void umudev_charger_schedule_poll(void);
83static void umudev_charger_cancel_poll (void);
84static gboolean umudev_charger_poll_cb (gpointer aptr);
85static void umudev_charger_poll_now (void);
86
87/* ------------------------------------------------------------------------- *
88 * UMUDEV_EXTCON
89 * ------------------------------------------------------------------------- */
90
91static gchar *umudev_extcon_parse_state(const char *rawstate);
92static void umudev_extcon_set_state (const char *rawstate);
93static void umudev_extcon_read_from (const char *syspath);
94static void umudev_extcon_update_from(struct udev_device *dev);
95static void umudev_extcon_find_device(void);
96
97/* ------------------------------------------------------------------------- *
98 * UMUDEV_ANDROID
99 * ------------------------------------------------------------------------- */
100
101static gchar *umudev_android_parse_state(const char *rawstate);
102static void umudev_android_set_state (const char *rawstate);
103static void umudev_android_read_from (const char *syspath);
104static void umudev_android_update_from(struct udev_device *dev);
105static void umudev_android_find_device(void);
106
107/* ------------------------------------------------------------------------- *
108 * UMUDEV_CABLE_STATE
109 * ------------------------------------------------------------------------- */
110
111static gboolean umudev_cable_state_timer_cb (gpointer aptr);
112static void umudev_cable_state_stop_timer (void);
113static void umudev_cable_state_start_timer(gint delay);
114static bool umudev_cable_state_connected (void);
115static cable_state_t umudev_cable_state_get (void);
116static void umudev_cable_state_set (cable_state_t state);
117static void umudev_cable_state_changed (void);
118static void umudev_cable_state_from_udev (cable_state_t curr);
119
120/* ------------------------------------------------------------------------- *
121 * UMUDEV
122 * ------------------------------------------------------------------------- */
123
124static void umudev_io_error_cb (gpointer data);
125static gboolean umudev_io_input_cb (GIOChannel *iochannel, GIOCondition cond, gpointer data);
126static void umudev_evaluate_state(void);
127gboolean umudev_init (void);
128void umudev_quit (void);
129
130/* ========================================================================= *
131 * Data
132 * ========================================================================= */
133
134/* global variables */
135static struct udev *umudev_object = 0;
136static struct udev_monitor *umudev_monitor = 0;
137
138/* Device monitoring: power_supply subsystem */
139static gchar *umudev_charger_syspath = 0;
140static gchar *umudev_charger_subsystem = 0;
141static gchar *umudev_charger_online = 0;
142static gchar *umudev_charger_type = 0;
143
144/* Delayed charger property refresh / state re-evaluation
145 *
146 * This is done when extcon / android_usb changes are seen.
147 * The delay needs to be long enough to cover transient android_usb
148 * DISCONNECTED states related to mode configuration changes, so
149 * that they are not interpreted as physical cable disconnects.
150 */
151static guint umudev_charger_poll_id = 0;
152static gint umudev_charger_poll_delay = 1000;
153
154/* Device monitoring: extcon subsystem */
155static gchar *umudev_extcon_syspath = 0;
156static gchar *umudev_extcon_subsystem = 0;
157static gchar *umudev_extcon_state = NULL;
158
159/* Device monitoring: android_usb subsystem */
160static gchar *umudev_android_syspath = 0;
161static gchar *umudev_android_subsystem = 0;
162static gchar *umudev_android_state = NULL;
163
164static guint umudev_watch_id = 0;
165static bool umudev_in_cleanup = false;
166
168static cable_state_t umudev_cable_state_current = CABLE_STATE_UNKNOWN;
169
171static cable_state_t umudev_cable_state_active = CABLE_STATE_UNKNOWN;
172
174static cable_state_t umudev_cable_state_previous = CABLE_STATE_UNKNOWN;
175
177static guint umudev_cable_state_timer_id = 0;
178static gint umudev_cable_state_timer_delay = -1;
179
180/* ========================================================================= *
181 * UTILITY
182 * ========================================================================= */
183
184static const char *umudev_pretty_string(const char *str)
185{
186 return !str ? "<null>" : !*str ? "<empty>" : str;
187}
188
189static bool umudev_white_p(int ch)
190{
191 return (ch > 0) && (ch <= 32);
192}
193
194static bool umudev_black_p(int ch)
195{
196 return (ch < 0) || (ch > 32);
197}
198
199static char *umudev_strip(char *str)
200{
201 if( str ) {
202 char *dst = str;
203 char *src = str;
204 while( umudev_white_p(*src) )
205 ++src;
206 for( ;; ) {
207 while( umudev_black_p(*src) )
208 *dst++ = *src++;
209 while( umudev_white_p(*src) )
210 ++src;
211 if( !*src )
212 break;
213 *dst++ = ' ';
214 }
215 *dst = 0;
216 }
217 return str;
218}
219
220static char *umudev_extract_token(char **ppos)
221{
222 char *beg = *ppos;
223 while( umudev_white_p(*beg) )
224 ++beg;
225 char *end = beg;
226 while( umudev_black_p(*end) )
227 ++end;
228 if( *end )
229 *end++ = 0;
230 *ppos = end;
231 return beg;
232}
233
234static gchar *umudev_read_textfile(const char *dirpath, const char *filename)
235{
236 gchar *data = NULL;
237 if( dirpath && filename ) {
238 gchar *path = g_strdup_printf("%s/%s", dirpath, filename);
239 if( !g_file_get_contents(path, &data, NULL, NULL) )
240 log_warning("%s: could not read file", path);
241 g_free(path);
242 }
243 return data;
244}
245
246static gchar *umudev_get_config(const char *key)
247{
248 gchar *val = config_get_conf_string(UDEV_ENTRY, key);
249 if( val ) {
250 if( !*val || !strcmp(val, "none") || !strcmp(val, "null") )
251 g_free(val), val = NULL;
252 }
253 return val;
254}
255
256static struct udev_device **umudev_get_devices(const char *subsystem)
257{
258 struct udev_device **devices = NULL;
259
260 struct udev_enumerate *list;
261 if( (list = udev_enumerate_new(umudev_object)) ) {
262 udev_enumerate_add_match_subsystem(list, subsystem);
263 udev_enumerate_scan_devices(list);
264 struct udev_list_entry *iter;
265 size_t count = 0;
266 udev_list_entry_foreach(iter, udev_enumerate_get_list_entry(list)) {
267 ++count;
268 }
269 devices = g_malloc_n(count + 1, sizeof *devices);
270 count = 0;
271 udev_list_entry_foreach(iter, udev_enumerate_get_list_entry(list)) {
272 const char *syspath = udev_list_entry_get_name(iter);
273 struct udev_device *dev =
274 udev_device_new_from_syspath(umudev_object, syspath);
275 if( dev )
276 devices[count++] = dev;
277 }
278 devices[count] = NULL;
279 }
280 return devices;
281}
282
283static void umudev_free_devices(struct udev_device **devices)
284{
285 if( devices ) {
286 for( size_t i = 0; devices[i]; ++i )
287 udev_device_unref(devices[i]);
288 g_free(devices);
289 }
290}
291
292/* ========================================================================= *
293 * UMUDEV_CHARGER
294 * ========================================================================= */
295
296static bool umudev_charger_set_online(const char *online)
297{
298 bool changed = false;
299 if( g_strcmp0(umudev_charger_online, online) ) {
300 log_debug("umudev_charger_online: %s -> %s",
301 umudev_pretty_string(umudev_charger_online),
302 umudev_pretty_string(online));
303 g_free(umudev_charger_online),
304 umudev_charger_online = g_strdup(online);
305 changed = true;
306 }
307 return changed;
308}
309
310static bool umudev_charger_set_type(const char *type)
311{
312 bool changed = false;
313 if( g_strcmp0(umudev_charger_type, type) ) {
314 log_debug("umudev_charger_type: %s -> %s",
315 umudev_pretty_string(umudev_charger_type),
316 umudev_pretty_string(type));
317 g_free(umudev_charger_type),
318 umudev_charger_type = g_strdup(type);
319 changed = true;
320 }
321 return changed;
322}
323
324static void umudev_charger_update_from(struct udev_device *dev)
325{
326 LOG_REGISTER_CONTEXT;
327
328 /* udev properties we are interested in */
329 const char *power_supply_online = 0;
330 const char *power_supply_type = 0;
331
332 /*
333 * Check for present first as some drivers use online for when charging
334 * is enabled
335 */
336 power_supply_online = udev_device_get_property_value(dev, PROP_PRESENT);
337 if( !power_supply_online )
338 power_supply_online = udev_device_get_property_value(dev, PROP_ONLINE);
339
340 /* At least h4113 i.e. "Xperia XA2 - Dual SIM" seem to have
341 * POWER_SUPPLY_REAL_TYPE udev property with information
342 * that usb-moded expects to be in POWER_SUPPLY_TYPE prop.
343 */
344 power_supply_type = udev_device_get_property_value(dev, PROP_REAL_TYPE);
345 if( !power_supply_type )
346 power_supply_type = udev_device_get_property_value(dev, PROP_TYPE);
347
348 umudev_charger_set_online(power_supply_online);
349 umudev_charger_set_type(power_supply_type);
350
351 umudev_evaluate_state();
352}
353
354static int umudev_charger_get_score(struct udev_device *dev)
355{
356 LOG_REGISTER_CONTEXT;
357
358 int score = 0;
359 const char *sysname = NULL;
360
361 if( !dev )
362 goto EXIT;
363
364 if( !(sysname = udev_device_get_sysname(dev)) )
365 goto EXIT;
366
367 /* check that it is not a battery */
368 if( udev_device_get_property_value(dev, PROP_CAPACITY) )
369 goto EXIT;
370
371 /* check that it can be a charger */
372 const char *online = udev_device_get_property_value(dev, PROP_ONLINE);
373 const char *present = udev_device_get_property_value(dev, PROP_PRESENT);
374 if( !online && !present )
375 goto EXIT;
376
377 /* try to assign a weighed score */
378
379 /* if it contains usb in the name it very likely is good */
380 if( strstr(sysname, "usb") )
381 score += 10;
382
383 /* often charger is also mentioned in the name */
384 if( strstr(sysname, "charger") )
385 score += 5;
386
387 /* present property is used to detect activity, however online is better */
388 if( online )
389 score += 10;
390
391 if( present )
392 score += 5;
393
394 /* while usb-moded does not use status, it should be present */
395 if( udev_device_get_property_value(dev, PROP_STATUS) )
396 score += 5;
397
398 /* type is used to detect if it is a cable or dedicated charger.
399 * Bonus points if it is there. */
400 if( udev_device_get_property_value(dev, PROP_TYPE) ||
401 udev_device_get_property_value(dev, PROP_REAL_TYPE) )
402 score += 10;
403EXIT:
404
405 log_debug("score: %2d; for: %s", score, umudev_pretty_string(sysname));
406
407 return score;
408}
409
410static void umudev_charger_find_device(void)
411{
412 LOG_REGISTER_CONTEXT;
413
414 gchar *tracking = NULL;
415 gchar *syspath = NULL;
416 gchar *subsystem = NULL;
417 struct udev_device *dev = NULL;
418
419 if( !(tracking = umudev_get_config(UDEV_CHARGER_TRACKING_KEY)) )
420 tracking = g_strdup(UDEV_CHARGER_TRACKING_FALLBACK);
421
422 log_debug("tracking=%s", umudev_pretty_string(tracking));
423
424 if( !g_strcmp0(tracking, "0") )
425 goto EXIT;
426
427 if( !(syspath = umudev_get_config(UDEV_CHARGER_PATH_KEY)) )
428 syspath = g_strdup(UDEV_CHARGER_PATH_FALLBACK);
429
430 if( syspath ) {
431 if( !(dev = udev_device_new_from_syspath(umudev_object, syspath)) )
432 log_warning("Unable to find $charger device '%s'", syspath);
433 else
434 subsystem = g_strdup(udev_device_get_subsystem(dev));
435 }
436
437 if( !subsystem ) {
438 if( !(subsystem = umudev_get_config(UDEV_CHARGER_SUBSYSTEM_KEY)) )
439 subsystem = g_strdup(UDEV_CHARGER_SUBSYSTEM_FALLBACK);
440 }
441
442 if( !subsystem ) {
443 log_warning("Unable to determine $charger subsystem.");
444 goto EXIT;
445 }
446
447 if( udev_monitor_filter_add_match_subsystem_devtype(umudev_monitor,
448 subsystem,
449 NULL) != 0 ) {
450 log_err("Unable to add $charger match");
451 goto EXIT;
452 }
453
454 umudev_charger_subsystem = g_strdup(subsystem);
455
456 if( !dev ) {
457 /* Explicit device was not named or found -> probe all */
458 log_debug("Trying to guess $charger device.\n");
459 struct udev_device **devices = umudev_get_devices(subsystem);
460 if( devices ) {
461 int best_score = 0;
462 int best_index = -1;
463 for( int i = 0; devices[i]; ++i ) {
464 int score = umudev_charger_get_score(devices[i]);
465 if( best_score < score ) {
466 best_score = score;
467 best_index = i;
468 }
469 }
470 if( best_index != -1 )
471 dev = udev_device_ref(devices[best_index]);
472 umudev_free_devices(devices);
473 }
474 }
475
476 if( dev )
477 umudev_charger_syspath = g_strdup(udev_device_get_syspath(dev));
478
479EXIT:
480 log_debug("charger device: subsystem=%s syspath=%s",
481 umudev_pretty_string(umudev_charger_subsystem),
482 umudev_pretty_string(umudev_charger_syspath));
483
484 if( dev )
485 udev_device_unref(dev);
486 g_free(subsystem);
487 g_free(syspath);
488 g_free(tracking);
489}
490
491static void umudev_charger_schedule_poll(void)
492{
493 LOG_REGISTER_CONTEXT;
494
495 if( !umudev_charger_poll_id ) {
496 umudev_charger_poll_id = g_timeout_add(umudev_charger_poll_delay,
497 umudev_charger_poll_cb,
498 NULL);
499 }
500}
501
502static void umudev_charger_cancel_poll(void)
503{
504 LOG_REGISTER_CONTEXT;
505
506 if( umudev_charger_poll_id ) {
507 g_source_remove(umudev_charger_poll_id),
508 umudev_charger_poll_id = 0;
509 }
510}
511
512static gboolean umudev_charger_poll_cb(gpointer aptr)
513{
514 LOG_REGISTER_CONTEXT;
515
516 umudev_charger_poll_id = 0;
517 umudev_charger_poll_now();
518 return G_SOURCE_REMOVE;
519}
520
521static void umudev_charger_poll_now(void)
522{
523 LOG_REGISTER_CONTEXT;
524
525 umudev_charger_cancel_poll();
526
527 struct udev_device *dev = NULL;
528
529 if( umudev_charger_syspath )
530 dev = udev_device_new_from_syspath(umudev_object,
531 umudev_charger_syspath);
532
533 if( dev ) {
534 umudev_charger_update_from(dev);
535 udev_device_unref(dev);
536 }
537 else {
538 umudev_evaluate_state();
539 }
540}
541
542/* ========================================================================= *
543 * UMUDEV_EXTCON
544 * ========================================================================= */
545
546static gchar *umudev_extcon_parse_state(const char *rawstate)
547{
548 /* We only need the "USB=N" part */
549 gchar *state = NULL;
550 gchar *tmp = g_strdup(rawstate);
551 char *pos = tmp;
552 while( pos && *pos ) {
553 char *tok = umudev_extract_token(&pos);
554 if( !strncmp(tok, "USB=", 4) ) {
555 state = g_strdup(tok);
556 break;
557 }
558 }
559 g_free(tmp);
560 return state;
561}
562
563static void umudev_extcon_set_state(const char *rawstate)
564{
565 LOG_REGISTER_CONTEXT;
566
567 gchar *state = umudev_extcon_parse_state(rawstate);
568 if( g_strcmp0(umudev_extcon_state, state) ) {
569 log_debug("umudev_extcon_state: %s -> %s",
570 umudev_pretty_string(umudev_extcon_state),
571 umudev_pretty_string(state));
572 g_free(umudev_extcon_state),
573 umudev_extcon_state = state,
574 state = NULL;
575 umudev_charger_schedule_poll();
576 }
577 g_free(state);
578}
579
580static void umudev_extcon_read_from(const char *syspath)
581{
582 /* Note: cached state is intentionally left as-it-is if reported
583 * state file does not exist / does not contain "USB=X" entry.
584 */
585 LOG_REGISTER_CONTEXT;
586 gchar *rawstate = umudev_read_textfile(syspath, "state");
587 if( rawstate )
588 umudev_extcon_set_state(rawstate);
589 g_free(rawstate);
590}
591
592static void umudev_extcon_update_from(struct udev_device *dev)
593{
594 const char *state = udev_device_get_property_value(dev, "STATE");
595 if( state )
596 umudev_extcon_set_state(state);
597}
598
599static void umudev_extcon_find_device(void)
600{
601 LOG_REGISTER_CONTEXT;
602
603 gchar *tracking = NULL;
604 gchar *syspath = NULL;
605 gchar *subsystem = NULL;
606 struct udev_device *dev = NULL;
607
608 if( !(tracking = umudev_get_config(UDEV_EXTCON_TRACKING_KEY)) )
609 tracking = g_strdup(UDEV_EXTCON_TRACKING_FALLBACK);
610
611 log_debug("tracking=%s", umudev_pretty_string(tracking));
612
613 if( !g_strcmp0(tracking, "0") )
614 goto EXIT;
615
616 if( !(syspath = umudev_get_config(UDEV_EXTCON_PATH_KEY)) )
617 syspath = g_strdup(UDEV_EXTCON_PATH_FALLBACK);
618
619 if( syspath ) {
620 if( !(dev = udev_device_new_from_syspath(umudev_object, syspath)) )
621 log_warning("Unable to find $extcon device '%s'", syspath);
622 else
623 subsystem = g_strdup(udev_device_get_subsystem(dev));
624 }
625
626 if( !subsystem ) {
627 if( !(subsystem = umudev_get_config(UDEV_EXTCON_SUBSYSTEM_KEY)) )
628 subsystem = g_strdup(UDEV_EXTCON_SUBSYSTEM_FALLBACK);
629 }
630
631 if( !subsystem ) {
632 log_warning("Unable to determine $extcon subsystem.");
633 goto EXIT;
634 }
635
636 if( udev_monitor_filter_add_match_subsystem_devtype(umudev_monitor,
637 subsystem,
638 NULL) != 0 ) {
639 log_err("Unable to add $extcon match");
640 goto EXIT;
641 }
642
643 umudev_extcon_subsystem = g_strdup(subsystem);
644
645 if( dev ) {
646 /* Explicit device was named and found -> probe it */
647 umudev_extcon_syspath = g_strdup(udev_device_get_syspath(dev));
648 umudev_extcon_read_from(umudev_extcon_syspath);
649 }
650 else {
651 /* Explicit device was not named or found -> probe all */
652 struct udev_device **devices = umudev_get_devices(subsystem);
653 if( devices ) {
654 for( size_t i = 0; devices[i]; ++i )
655 umudev_extcon_read_from(udev_device_get_syspath(devices[i]));
656 umudev_free_devices(devices);
657 }
658 }
659
660EXIT:
661 log_debug("extcon device: subsystem=%s syspath=%s",
662 umudev_pretty_string(umudev_extcon_subsystem),
663 umudev_pretty_string(umudev_extcon_syspath));
664
665 if( dev )
666 udev_device_unref(dev);
667 g_free(subsystem);
668 g_free(syspath);
669 g_free(tracking);
670}
671
672/* ========================================================================= *
673 * UMUDEV_ANDROID
674 * ========================================================================= */
675
676static gchar *umudev_android_parse_state(const char *rawstate)
677{
678 /* 'state' file ends with newline, udev properties do not */
679 return umudev_strip(g_strdup(rawstate));
680}
681
682static void umudev_android_set_state(const char *rawstate)
683{
684 LOG_REGISTER_CONTEXT;
685
686 gchar *state = umudev_android_parse_state(rawstate);
687 if( g_strcmp0(umudev_android_state, state) ) {
688 log_debug("umudev_android_state: %s -> %s",
689 umudev_pretty_string(umudev_android_state),
690 umudev_pretty_string(state));
691 g_free(umudev_android_state),
692 umudev_android_state = state,
693 state = NULL;
694 umudev_charger_schedule_poll();
695 }
696 g_free(state);
697}
698
699static void umudev_android_read_from(const char *syspath)
700{
701 /* Note: cached state is intentionally left as-it-is if
702 * state file does not exist / is not readable.
703 */
704 LOG_REGISTER_CONTEXT;
705
706 gchar *rawstate = umudev_read_textfile(syspath, "state");
707 if( rawstate )
708 umudev_android_set_state(rawstate);
709 g_free(rawstate);
710}
711
712static void umudev_android_update_from(struct udev_device *dev)
713{
714 const char *state = udev_device_get_property_value(dev, "USB_STATE");
715 if( state )
716 umudev_android_set_state(state);
717}
718
719static void umudev_android_find_device(void)
720{
721 LOG_REGISTER_CONTEXT;
722
723 gchar *tracking = NULL;
724 gchar *syspath = NULL;
725 gchar *subsystem = NULL;
726 struct udev_device *dev = NULL;
727
728 if( !(tracking = umudev_get_config(UDEV_ANDROID_TRACKING_KEY)) )
729 tracking = g_strdup(UDEV_ANDROID_TRACKING_FALLBACK);
730
731 log_debug("tracking=%s", umudev_pretty_string(tracking));
732
733 if( !g_strcmp0(tracking, "0") )
734 goto EXIT;
735
736 if( !(syspath = umudev_get_config(UDEV_ANDROID_PATH_KEY)) )
737 syspath = g_strdup(UDEV_ANDROID_PATH_FALLBACK);
738
739 if( syspath ) {
740 if( !(dev = udev_device_new_from_syspath(umudev_object, syspath)) )
741 log_warning("Unable to find $android device '%s'", syspath);
742 else
743 subsystem = g_strdup(udev_device_get_subsystem(dev));
744 }
745
746 if( !subsystem ) {
747 if( !(subsystem = umudev_get_config(UDEV_ANDROID_SUBSYSTEM_KEY)) )
748 subsystem = g_strdup(UDEV_ANDROID_SUBSYSTEM_FALLBACK);
749 }
750
751 if( !subsystem ) {
752 log_warning("Unable to determine $android subsystem.");
753 goto EXIT;
754 }
755
756 if( udev_monitor_filter_add_match_subsystem_devtype(umudev_monitor,
757 subsystem,
758 NULL) != 0 ) {
759 log_err("Unable to add $android match");
760 goto EXIT;
761 }
762
763 umudev_android_subsystem = g_strdup(subsystem);
764
765 if( dev ) {
766 /* Explicit device was named and found -> probe it */
767 umudev_android_syspath = g_strdup(udev_device_get_syspath(dev));
768 umudev_android_read_from(umudev_android_syspath);
769 }
770 else {
771 /* Explicit device was not named or found -> probe all */
772 struct udev_device **devices = umudev_get_devices(subsystem);
773 if( devices ) {
774 for( size_t i = 0; devices[i]; ++i )
775 umudev_android_read_from(udev_device_get_syspath(devices[i]));
776 umudev_free_devices(devices);
777 }
778 }
779
780EXIT:
781 log_debug("android device: subsystem=%s syspath=%s",
782 umudev_pretty_string(umudev_android_subsystem),
783 umudev_pretty_string(umudev_android_syspath));
784
785 if( dev )
786 udev_device_unref(dev);
787 g_free(subsystem);
788 g_free(syspath);
789 g_free(tracking);
790}
791
792/* ========================================================================= *
793 * UMUDEV_CABLE_STATE
794 * ========================================================================= */
795
796static gboolean umudev_cable_state_timer_cb(gpointer aptr)
797{
798 LOG_REGISTER_CONTEXT;
799
800 (void)aptr;
801 umudev_cable_state_timer_id = 0;
802 umudev_cable_state_timer_delay = -1;
803
804 log_debug("trigger delayed transfer to: %s",
805 cable_state_repr(umudev_cable_state_current));
806 umudev_cable_state_set(umudev_cable_state_current);
807 return FALSE;
808}
809
810static void umudev_cable_state_stop_timer(void)
811{
812 LOG_REGISTER_CONTEXT;
813
814 if( umudev_cable_state_timer_id ) {
815 log_debug("cancel delayed transfer to: %s",
816 cable_state_repr(umudev_cable_state_current));
817 g_source_remove(umudev_cable_state_timer_id),
818 umudev_cable_state_timer_id = 0;
819 umudev_cable_state_timer_delay = -1;
820 }
821}
822
823static void umudev_cable_state_start_timer(gint delay)
824{
825 LOG_REGISTER_CONTEXT;
826
827 if( umudev_cable_state_timer_delay != delay ) {
828 umudev_cable_state_stop_timer();
829 }
830
831 if( !umudev_cable_state_timer_id ) {
832 log_debug("schedule delayed transfer to: %s",
833 cable_state_repr(umudev_cable_state_current));
834 umudev_cable_state_timer_id =
835 g_timeout_add(delay,
836 umudev_cable_state_timer_cb, 0);
837 umudev_cable_state_timer_delay = delay;
838 }
839}
840
841static bool
842umudev_cable_state_connected(void)
843{
844 LOG_REGISTER_CONTEXT;
845
846 bool connected = false;
847 switch( umudev_cable_state_get() ) {
848 default:
849 break;
850 case CABLE_STATE_CHARGER_CONNECTED:
851 case CABLE_STATE_PC_CONNECTED:
852 connected = true;
853 break;
854 }
855 return connected;
856}
857
858static cable_state_t umudev_cable_state_get(void)
859{
860 LOG_REGISTER_CONTEXT;
861
862 return umudev_cable_state_active;
863}
864
865static void umudev_cable_state_set(cable_state_t state)
866{
867 LOG_REGISTER_CONTEXT;
868
869 umudev_cable_state_stop_timer();
870
871 if( umudev_cable_state_active == state )
872 goto EXIT;
873
874 umudev_cable_state_previous = umudev_cable_state_active;
875 umudev_cable_state_active = state;
876
877 log_debug("cable_state: %s -> %s",
878 cable_state_repr(umudev_cable_state_previous),
879 cable_state_repr(umudev_cable_state_active));
880
881 umudev_cable_state_changed();
882
883EXIT:
884 return;
885}
886
887static void umudev_cable_state_changed(void)
888{
889 LOG_REGISTER_CONTEXT;
890
891 /* The rest of usb-moded separates charger
892 * and pc connection states... make single
893 * state tracking compatible with that. */
894
895 /* First handle pc/charger disconnect based
896 * on previous state.
897 */
898 switch( umudev_cable_state_previous ) {
899 default:
900 case CABLE_STATE_DISCONNECTED:
901 /* dontcare */
902 break;
903 case CABLE_STATE_CHARGER_CONNECTED:
904 umdbus_send_event_signal(CHARGER_DISCONNECTED);
905 break;
906 case CABLE_STATE_PC_CONNECTED:
907 umdbus_send_event_signal(USB_DISCONNECTED);
908 break;
909 }
910
911 /* Then handle pc/charger connect based
912 * on current state.
913 */
914
915 switch( umudev_cable_state_active ) {
916 default:
917 case CABLE_STATE_DISCONNECTED:
918 /* dontcare */
919 break;
920 case CABLE_STATE_CHARGER_CONNECTED:
921 umdbus_send_event_signal(CHARGER_CONNECTED);
922 break;
923 case CABLE_STATE_PC_CONNECTED:
925 break;
926 }
927
928 /* Then act on usb mode */
929 control_set_cable_state(umudev_cable_state_active);
930}
931
932static void umudev_cable_state_from_udev(cable_state_t curr)
933{
934 LOG_REGISTER_CONTEXT;
935
936 cable_state_t prev = umudev_cable_state_current;
937 umudev_cable_state_current = curr;
938
939 if( prev == curr )
940 goto EXIT;
941
942 log_debug("reported cable state: %s -> %s",
943 cable_state_repr(prev),
944 cable_state_repr(curr));
945
946 /* Because mode transitions are handled synchronously and can thus
947 * block the usb-moded mainloop, we might end up receiving a bursts
948 * of stale udev events after returning from mode switch - including
949 * multiple cable connect / disconnect events due to user replugging
950 * the cable in frustration of things taking too long.
951 */
952
953 if( curr == CABLE_STATE_DISCONNECTED ) {
954 /* If we see any disconnect events, those must be acted on
955 * immediately to get the 1st disconnect handled.
956 */
957 umudev_cable_state_set(curr);
958 }
959 else {
960 /* All other transitions are handled with at least 100 ms delay.
961 * This should compress multiple stale disconnect + connect
962 * pairs into single action.
963 */
964 gint delay = 100;
965
966 if( curr == CABLE_STATE_PC_CONNECTED && prev != CABLE_STATE_UNKNOWN ) {
969 }
970
971 umudev_cable_state_start_timer(delay);
972 }
973
974EXIT:
975 return;
976}
977
978/* ========================================================================= *
979 * UMUDEV
980 * ========================================================================= */
981
982static void umudev_io_error_cb(gpointer data)
983{
984 LOG_REGISTER_CONTEXT;
985
986 (void)data;
987
988 /* we do not want to restart when we try to clean up */
989 if( !umudev_in_cleanup ) {
990 log_debug("USB connection watch destroyed, restarting it\n!");
991 /* restart trigger */
992 umudev_quit();
993 umudev_init();
994 }
995}
996
997static gboolean umudev_io_input_cb(GIOChannel *iochannel, GIOCondition cond, gpointer data)
998{
999 LOG_REGISTER_CONTEXT;
1000
1001 (void)iochannel;
1002 (void)data;
1003
1004 gboolean continue_watching = TRUE;
1005
1006 /* No code paths are allowed to bypass the common_release_wakelock() call below */
1008
1009 if( cond & G_IO_IN )
1010 {
1011 /* This normally blocks but G_IO_IN indicates that we can read */
1012 struct udev_device *dev = udev_monitor_receive_device(umudev_monitor);
1013 if( !dev ) {
1014 /* if we get something else something bad happened stop watching to avoid busylooping */
1015 continue_watching = FALSE;
1016 }
1017 else {
1018 const char *syspath = udev_device_get_syspath(dev);
1019 const char *subsystem = udev_device_get_subsystem(dev);
1020 const char *action = udev_device_get_action(dev);
1021 log_debug("action=%s subsystem=%s syspath=%s",
1022 umudev_pretty_string(action),
1023 umudev_pretty_string(subsystem),
1024 umudev_pretty_string(syspath));
1025
1026 if( g_strcmp0(action, "change") ) {
1027 // ignore add/remove events
1028 }
1029 else if( umudev_android_subsystem && !g_strcmp0(umudev_android_subsystem, subsystem) ) {
1030 if( !umudev_android_syspath || !g_strcmp0(umudev_android_syspath, syspath) )
1031 umudev_android_update_from(dev);
1032 }
1033 else if( umudev_extcon_subsystem && !g_strcmp0(umudev_extcon_subsystem, subsystem) ) {
1034 if( !umudev_extcon_syspath || !g_strcmp0(umudev_extcon_syspath, syspath) )
1035 umudev_extcon_update_from(dev);
1036 }
1037 else if( umudev_charger_subsystem && !g_strcmp0(umudev_charger_subsystem, subsystem) ) {
1038 if( !umudev_charger_syspath || !g_strcmp0(umudev_charger_syspath, syspath) ) {
1039 umudev_charger_cancel_poll();
1040 umudev_charger_update_from(dev);
1041 }
1042 }
1043 udev_device_unref(dev);
1044 }
1045 }
1046
1047 if( cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL) ) {
1048 /* Unhandled errors turn io watch to virtual busyloop too */
1049 continue_watching = FALSE;
1050 }
1051
1052 if( !continue_watching && umudev_watch_id ) {
1053 umudev_watch_id = 0;
1054 log_crit("udev io watch disabled");
1055 }
1056
1058
1059 return continue_watching;
1060}
1061
1062static void umudev_evaluate_state(void)
1063{
1064 /* Start from cached charger properties */
1065 const char *charger_online = umudev_charger_online;
1066 const char *charger_type = umudev_charger_type;
1067
1068 /* Apply heuristics
1069 *
1070 * If charger online info is not available or reliable, extcon
1071 * USB=N can be used as a substitute.
1072 *
1073 * In some devices USB=1 means that PC is connected, while in
1074 * others it could be a charger too...
1075 *
1076 * Tracking gadget enumeration / configuration state in android_usb
1077 * can be used to tell apart pc vs charger in those
1078 * devices where USB=1 could be either one.
1079 *
1080 * Caveat: transient android_usb disconnects do occur also during
1081 * gadget configuration changes i.e. due to actions of
1082 * usb-moded itself -> processing of extcon/android_usb
1083 * changes is delayed in the hope that short lasting
1084 * transient states do not get evaluated.
1085 */
1086
1087 const char *override_online = NULL;
1088 const char *override_type = NULL;
1089
1090 if( umudev_extcon_state ) {
1091 if( !strcmp(umudev_extcon_state, "USB=1") ) {
1092 override_online = "1";
1093 override_type = "USB";
1094 }
1095 else if( !strcmp(umudev_extcon_state, "USB=0") ) {
1096 override_type = "USB_DCP";
1097 }
1098 }
1099
1100 if( umudev_android_state ) {
1101 if( !strcmp(umudev_android_state, "DISCONNECTED") ) {
1102 override_type = "USB_DCP";
1103 }
1104 else {
1105 override_type = "USB";
1106 override_online = "1";
1107 }
1108 }
1109
1110 if( override_type && g_strcmp0(override_type, charger_type) ) {
1111 log_debug("override charger_type: %s -> %s",
1112 umudev_pretty_string(charger_type),
1113 umudev_pretty_string(override_type));
1114 charger_type = override_type;
1115 }
1116
1117 if( override_online && g_strcmp0(override_online, charger_online) ) {
1118 log_debug("override charger_online: %s -> %s",
1119 umudev_pretty_string(charger_online),
1120 umudev_pretty_string(override_online));
1121 charger_online = override_online;
1122 }
1123
1124 /* Evaluate */
1125
1126 log_debug("evaluate online=%s type=%s extcon=%s android=%s",
1127 umudev_pretty_string(charger_online),
1128 umudev_pretty_string(charger_type),
1129 umudev_pretty_string(umudev_extcon_state),
1130 umudev_pretty_string(umudev_android_state));
1131
1132 bool connected = !g_strcmp0(charger_online, "1");
1133
1134 /* Unless debug logging has been request via command line,
1135 * suppress warnings about potential property issues and/or
1136 * fallback strategies applied (to avoid spamming due to the
1137 * code below seeing the same property values over and over
1138 * again also in stable states).
1139 */
1140 bool warnings = log_p(LOG_DEBUG);
1141
1142 /* Transition period = Connection status derived from udev
1143 * events disagrees with usb-moded side bookkeeping. */
1144 if( connected != control_get_connection_state() ) {
1145 /* Enable udev property diagnostic logging */
1146 warnings = true;
1147 /* Block suspend briefly */
1149 }
1150
1151 if( !connected ) {
1152 /* Handle: Disconnected */
1153
1154 if( warnings && !charger_online )
1155 log_err("No usable power supply indicator\n");
1156 umudev_cable_state_from_udev(CABLE_STATE_DISCONNECTED);
1157 }
1158 else {
1159 /*
1160 * Power supply type might not exist also :(
1161 * Send connected event but this will not be able
1162 * to discriminate between charger/cable.
1163 */
1164 if( !charger_type ) {
1165 if( warnings )
1166 log_warning("Fallback since cable detection might not be accurate. "
1167 "Will connect on any voltage on charger.\n");
1168 umudev_cable_state_from_udev(CABLE_STATE_PC_CONNECTED);
1169 }
1170 else if( !strcmp(charger_type, "USB") ||
1171 !strcmp(charger_type, "USB_CDP") ) {
1172 umudev_cable_state_from_udev(CABLE_STATE_PC_CONNECTED);
1173 }
1174 else if( !strcmp(charger_type, "USB_DCP") ||
1175 !strcmp(charger_type, "USB_HVDCP") ||
1176 !strcmp(charger_type, "USB_HVDCP_3") ) {
1177 umudev_cable_state_from_udev(CABLE_STATE_CHARGER_CONNECTED);
1178 }
1179 else if( !strcmp(charger_type, "USB_PD") ) {
1180 /* Looks like it is impossible to tell apart PD connections to
1181 * pc and chargers based on stable state property values.
1182 *
1183 * However, it seems that PD capable power banks and chargers
1184 * are 1st reported as chargers, then a switch to PD type occurs
1185 * i.e. we can expect to see sequences like:
1186 * Unknown -> USB_DCP -> USB_PD
1187 *
1188 * Whereas e.g. a laptop connection is expected to report only
1189 * non-charger types, or directly:
1190 * Unknown -> USB_PD
1191 *
1192 * -> Differentiation should be possible by retaining the "this
1193 * is a charger" info obtained from transient USB_DCP/similar
1194 * states.
1195 */
1196 if( umudev_cable_state_current != CABLE_STATE_CHARGER_CONNECTED )
1197 umudev_cable_state_from_udev(CABLE_STATE_PC_CONNECTED);
1198 }
1199 else if( !strcmp(charger_type, "USB_FLOAT") ) {
1200 if( !umudev_cable_state_connected() )
1201 log_warning("connection type detection failed, assuming charger");
1202 umudev_cable_state_from_udev(CABLE_STATE_CHARGER_CONNECTED);
1203 }
1204 else if( !strcmp(charger_type, "Unknown") ) {
1205 log_warning("connection type 'Unknown' reported, assuming disconnected");
1206 umudev_cable_state_from_udev(CABLE_STATE_DISCONNECTED);
1207 }
1208 else {
1209 if( warnings )
1210 log_warning("unhandled power supply type: %s", charger_type);
1211 umudev_cable_state_from_udev(CABLE_STATE_DISCONNECTED);
1212 }
1213 }
1214}
1215
1216gboolean umudev_init(void)
1217{
1218 LOG_REGISTER_CONTEXT;
1219
1220 gboolean success = FALSE;
1221
1222 GIOChannel *iochannel = 0;
1223
1224 /* Clear in-cleanup in case of restart */
1225 umudev_in_cleanup = false;
1226
1227 /* Create the udev object */
1228 if( !(umudev_object = udev_new()) ) {
1229 log_err("Can't create umudev_object\n");
1230 goto EXIT;
1231 }
1232
1233 /* Start monitoring for changes */
1234 umudev_monitor = udev_monitor_new_from_netlink(umudev_object, "udev");
1235 if( !umudev_monitor )
1236 {
1237 log_err("Unable to monitor the netlink\n");
1238 /* communicate failure, mainloop will exit and call appropriate clean-up */
1239 goto EXIT;
1240 }
1241
1242 /* Locate relevant devices */
1243 umudev_charger_find_device();
1244 umudev_extcon_find_device();
1245 umudev_android_find_device();
1246
1247 if( !umudev_charger_syspath ) {
1248 if( !umudev_extcon_subsystem && !umudev_android_subsystem ) {
1249 log_warning("no charger device found, bailing out");
1250 goto EXIT;
1251 }
1252 log_debug("no charger device found, using alternative sources");
1253 }
1254
1255 if( udev_monitor_enable_receiving(umudev_monitor) != 0 ) {
1256 log_err("Failed to enable monitor recieving.\n");
1257 goto EXIT;
1258 }
1259
1260 iochannel = g_io_channel_unix_new(udev_monitor_get_fd(umudev_monitor));
1261 if( !iochannel )
1262 goto EXIT;
1263
1264 umudev_watch_id = g_io_add_watch_full(iochannel, 0, G_IO_IN,
1265 umudev_io_input_cb, NULL,
1266 umudev_io_error_cb);
1267 if( !umudev_watch_id )
1268 goto EXIT;
1269
1270 /* everything went well */
1271 success = TRUE;
1272
1273 /* check initial status */
1274 umudev_charger_poll_now();
1275
1276EXIT:
1277 /* Cleanup local resources */
1278 if( iochannel )
1279 g_io_channel_unref(iochannel);
1280
1281 /* All or nothing */
1282 if( !success )
1283 umudev_quit();
1284
1285 return success;
1286}
1287
1288void umudev_quit(void)
1289{
1290 LOG_REGISTER_CONTEXT;
1291
1292 umudev_in_cleanup = true;
1293
1294 log_debug("HWhal cleanup\n");
1295
1296 if( umudev_watch_id )
1297 {
1298 g_source_remove(umudev_watch_id),
1299 umudev_watch_id = 0;
1300 }
1301
1302 if( umudev_monitor ) {
1303 udev_monitor_unref(umudev_monitor),
1304 umudev_monitor = 0;
1305 }
1306
1307 if( umudev_object ) {
1308 udev_unref(umudev_object),
1309 umudev_object =0 ;
1310 }
1311
1312 g_free(umudev_charger_syspath),
1313 umudev_charger_syspath = 0;
1314 g_free(umudev_charger_subsystem),
1315 umudev_charger_subsystem = 0;
1316
1317 g_free(umudev_extcon_syspath),
1318 umudev_extcon_syspath = 0;
1319 g_free(umudev_extcon_subsystem),
1320 umudev_extcon_subsystem = 0;
1321
1322 g_free(umudev_android_syspath),
1323 umudev_android_syspath = 0;
1324 g_free(umudev_android_subsystem),
1325 umudev_android_subsystem = 0;
1326
1327 umudev_cable_state_stop_timer();
1328
1329 umudev_extcon_set_state(NULL);
1330 umudev_android_set_state(NULL);
1331 umudev_charger_set_online(NULL);
1332 umudev_charger_set_type(NULL);
1333 umudev_charger_cancel_poll();
1334}
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:542
int usbmoded_get_cable_connection_delay(void)
Definition usb_moded.c:478
#define USB_MODED_WAKELOCK_PROCESS_INPUT
Definition usb_moded.h:50