usb_moded  0.86.0+mer57
usb_moded-udev.c
Go to the documentation of this file.
1 
31 #include "usb_moded-udev.h"
32 
33 #include "usb_moded.h"
35 #include "usb_moded-control.h"
36 #include "usb_moded-dbus-private.h"
37 #include "usb_moded-log.h"
38 
39 #include <libudev.h>
40 
41 /* ========================================================================= *
42  * Prototypes
43  * ========================================================================= */
44 
45 /* ------------------------------------------------------------------------- *
46  * UMUDEV
47  * ------------------------------------------------------------------------- */
48 
49 static gboolean umudev_cable_state_timer_cb (gpointer aptr);
50 static void umudev_cable_state_stop_timer (void);
51 static void umudev_cable_state_start_timer(gint delay);
52 static bool umudev_cable_state_connected (void);
53 static cable_state_t umudev_cable_state_get (void);
54 static void umudev_cable_state_set (cable_state_t state);
55 static void umudev_cable_state_changed (void);
56 static void umudev_cable_state_from_udev (cable_state_t curr);
57 static void umudev_io_error_cb (gpointer data);
58 static gboolean umudev_io_input_cb (GIOChannel *iochannel, GIOCondition cond, gpointer data);
59 static void umudev_parse_properties (struct udev_device *dev, bool initial);
60 static int umudev_score_as_power_supply (const char *syspath);
61 gboolean umudev_init (void);
62 void umudev_quit (void);
63 
64 /* ========================================================================= *
65  * Data
66  * ========================================================================= */
67 
68 /* global variables */
69 static struct udev *umudev_object = 0;
70 static struct udev_monitor *umudev_monitor = 0;
71 static gchar *umudev_sysname = 0;
72 static guint umudev_watch_id = 0;
73 static bool umudev_in_cleanup = false;
74 
76 static cable_state_t umudev_cable_state_current = CABLE_STATE_UNKNOWN;
77 
79 static cable_state_t umudev_cable_state_active = CABLE_STATE_UNKNOWN;
80 
82 static cable_state_t umudev_cable_state_previous = CABLE_STATE_UNKNOWN;
83 
85 static guint umudev_cable_state_timer_id = 0;
86 static gint umudev_cable_state_timer_delay = -1;
87 
88 /* ========================================================================= *
89  * cable state
90  * ========================================================================= */
91 
92 static 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 
106 static 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 
119 static 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 
137 static bool
138 umudev_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 
154 static cable_state_t umudev_cable_state_get(void)
155 {
156  LOG_REGISTER_CONTEXT;
157 
158  return umudev_cable_state_active;
159 }
160 
161 static 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 
179 EXIT:
180  return;
181 }
182 
183 static 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 
228 static 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 
270 EXIT:
271  return;
272 }
273 
274 /* ========================================================================= *
275  * legacy code
276  * ========================================================================= */
277 
278 static 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 
293 static 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 
346 static 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_PD") ) {
433  /* Looks like it is impossible to tell apart PD connections to
434  * pc and chargers based on stable state property values.
435  *
436  * However, it seems that PD capable power banks and chargers
437  * are 1st reported as chargers, then a switch to PD type occurs
438  * i.e. we can expect to see sequences like:
439  * Unknown -> USB_DCP -> USB_PD
440  *
441  * Whereas e.g. a laptop connection is expected to report only
442  * non-charger types, or directly:
443  * Unknown -> USB_PD
444  *
445  * -> Differentiation should be possible by retaining the "this
446  * is a charger" info obtained from transient USB_DCP/similar
447  * states.
448  */
449  if( umudev_cable_state_current != CABLE_STATE_CHARGER_CONNECTED )
450  umudev_cable_state_from_udev(CABLE_STATE_PC_CONNECTED);
451  }
452  else if( !strcmp(power_supply_type, "USB_FLOAT")) {
453  if( !umudev_cable_state_connected() )
454  log_warning("connection type detection failed, assuming charger");
455  umudev_cable_state_from_udev(CABLE_STATE_CHARGER_CONNECTED);
456  }
457  else if( !strcmp(power_supply_type, "Unknown")) {
458  // nop
459  log_warning("unknown connection type reported, assuming disconnected");
460  umudev_cable_state_from_udev(CABLE_STATE_DISCONNECTED);
461  }
462  else {
463  if( warnings )
464  log_warning("unhandled power supply type: %s", power_supply_type);
465  umudev_cable_state_from_udev(CABLE_STATE_DISCONNECTED);
466  }
467  }
468 
469 cleanup:
470  return;
471 }
472 
473 static int umudev_score_as_power_supply(const char *syspath)
474 {
475  LOG_REGISTER_CONTEXT;
476 
477  int score = 0;
478  struct udev_device *dev = 0;
479  const char *sysname = 0;
480 
481  if( !umudev_object )
482  goto EXIT;
483 
484  if( !(dev = udev_device_new_from_syspath(umudev_object, syspath)) )
485  goto EXIT;
486 
487  if( !(sysname = udev_device_get_sysname(dev)) )
488  goto EXIT;
489 
490  /* try to assign a weighed score */
491 
492  /* check that it is not a battery */
493  if(strstr(sysname, "battery") || strstr(sysname, "BAT"))
494  goto EXIT;
495 
496  /* if it contains usb in the name it very likely is good */
497  if(strstr(sysname, "usb"))
498  score = score + 10;
499 
500  /* often charger is also mentioned in the name */
501  if(strstr(sysname, "charger"))
502  score = score + 5;
503 
504  /* present property is used to detect activity, however online is better */
505  if(udev_device_get_property_value(dev, "POWER_SUPPLY_PRESENT"))
506  score = score + 5;
507 
508  if(udev_device_get_property_value(dev, "POWER_SUPPLY_ONLINE"))
509  score = score + 10;
510 
511  /* type is used to detect if it is a cable or dedicated charger.
512  * Bonus points if it is there. */
513  if(udev_device_get_property_value(dev, "POWER_SUPPLY_TYPE"))
514  score = score + 10;
515 
516 EXIT:
517  /* clean up */
518  if( dev )
519  udev_device_unref(dev);
520 
521  return score;
522 }
523 
524 gboolean umudev_init(void)
525 {
526  LOG_REGISTER_CONTEXT;
527 
528  gboolean success = FALSE;
529 
530  char *configured_device = NULL;
531  char *configured_subsystem = NULL;
532  struct udev_device *dev = 0;
533  GIOChannel *iochannel = 0;
534 
535  int ret = 0;
536 
537  /* Clear in-cleanup in case of restart */
538  umudev_in_cleanup = false;
539 
540  /* Create the udev object */
541  if( !(umudev_object = udev_new()) ) {
542  log_err("Can't create umudev_object\n");
543  goto EXIT;
544  }
545 
546  if( !(configured_device = config_find_udev_path()) )
547  configured_device = g_strdup("/sys/class/power_supply/usb");
548 
549  if( !(configured_subsystem = config_find_udev_subsystem()) )
550  configured_subsystem = g_strdup("power_supply");
551 
552  /* Try with configured / default device */
553  dev = udev_device_new_from_syspath(umudev_object, configured_device);
554 
555  /* If needed, try heuristics */
556  if( !dev ) {
557  log_debug("Trying to guess $power_supply device.\n");
558 
559  int current_score = 0;
560  gchar *current_name = 0;
561 
562  struct udev_enumerate *list;
563  struct udev_list_entry *list_entry;
564  struct udev_list_entry *first_entry;
565 
566  list = udev_enumerate_new(umudev_object);
567  udev_enumerate_add_match_subsystem(list, "power_supply");
568  udev_enumerate_scan_devices(list);
569  first_entry = udev_enumerate_get_list_entry(list);
570  udev_list_entry_foreach(list_entry, first_entry) {
571  const char *name = udev_list_entry_get_name(list_entry);
572  int score = umudev_score_as_power_supply(name);
573  if( current_score < score ) {
574  g_free(current_name);
575  current_name = g_strdup(name);
576  current_score = score;
577  }
578  }
579  /* check if we found anything with some kind of score */
580  if(current_score > 0) {
581  dev = udev_device_new_from_syspath(umudev_object, current_name);
582  }
583  g_free(current_name);
584  }
585 
586  /* Give up if no power supply device was found */
587  if( !dev ) {
588  log_err("Unable to find $power_supply device.");
589  /* communicate failure, mainloop will exit and call appropriate clean-up */
590  goto EXIT;
591  }
592 
593  /* Cache device name */
594  umudev_sysname = g_strdup(udev_device_get_sysname(dev));
595  log_debug("device name = %s\n", umudev_sysname);
596 
597  /* Start monitoring for changes */
598  umudev_monitor = udev_monitor_new_from_netlink(umudev_object, "udev");
599  if( !umudev_monitor )
600  {
601  log_err("Unable to monitor the netlink\n");
602  /* communicate failure, mainloop will exit and call appropriate clean-up */
603  goto EXIT;
604  }
605 
606  ret = udev_monitor_filter_add_match_subsystem_devtype(umudev_monitor,
607  configured_subsystem,
608  NULL);
609  if(ret != 0)
610  {
611  log_err("Udev match failed.\n");
612  goto EXIT;
613  }
614 
615  ret = udev_monitor_enable_receiving(umudev_monitor);
616  if(ret != 0)
617  {
618  log_err("Failed to enable monitor recieving.\n");
619  goto EXIT;
620  }
621 
622  iochannel = g_io_channel_unix_new(udev_monitor_get_fd(umudev_monitor));
623  if( !iochannel )
624  goto EXIT;
625 
626  umudev_watch_id = g_io_add_watch_full(iochannel, 0, G_IO_IN, umudev_io_input_cb, NULL, umudev_io_error_cb);
627  if( !umudev_watch_id )
628  goto EXIT;
629 
630  /* everything went well */
631  success = TRUE;
632 
633  /* check initial status */
634  umudev_parse_properties(dev, true);
635 
636 EXIT:
637  /* Cleanup local resources */
638  if( iochannel )
639  g_io_channel_unref(iochannel);
640 
641  if( dev )
642  udev_device_unref(dev);
643 
644  g_free(configured_subsystem);
645  g_free(configured_device);
646 
647  /* All or nothing */
648  if( !success )
649  umudev_quit();
650 
651  return success;
652 }
653 
654 void umudev_quit(void)
655 {
656  LOG_REGISTER_CONTEXT;
657 
658  umudev_in_cleanup = true;
659 
660  log_debug("HWhal cleanup\n");
661 
662  if( umudev_watch_id )
663  {
664  g_source_remove(umudev_watch_id),
665  umudev_watch_id = 0;
666  }
667 
668  if( umudev_monitor ) {
669  udev_monitor_unref(umudev_monitor),
670  umudev_monitor = 0;
671  }
672 
673  if( umudev_object ) {
674  udev_unref(umudev_object),
675  umudev_object =0 ;
676  }
677 
678  g_free(umudev_sysname),
679  umudev_sysname = 0;
680 
681  umudev_cable_state_stop_timer();
682 }
USB_CONNECTED
#define USB_CONNECTED
Definition: usb_moded-dbus.h:89
usb_moded-dbus-private.h
usbmoded_get_cable_connection_delay
int usbmoded_get_cable_connection_delay(void)
Definition: usb_moded.c:442
control_set_cable_state
void control_set_cable_state(cable_state_t cable_state)
Definition: usb_moded-control.c:822
USB_MODED_WAKELOCK_PROCESS_INPUT
#define USB_MODED_WAKELOCK_PROCESS_INPUT
Definition: usb_moded.h:50
usb_moded-config-private.h
control_get_connection_state
bool control_get_connection_state(void)
Definition: usb_moded-control.c:864
usb_moded.h
umdbus_send_event_signal
void umdbus_send_event_signal(const char *state_ind)
Definition: usb_moded-dbus.c:1802
usb_moded-control.h
usbmoded_delay_suspend
void usbmoded_delay_suspend(void)
Definition: usb_moded.c:506
common_acquire_wakelock
void common_acquire_wakelock(const char *wakelock_name)
Definition: usb_moded-common.c:323
usb_moded-udev.h
usb_moded-log.h
log_p
bool log_p(int lev)
Definition: usb_moded-log.c:347
common_release_wakelock
void common_release_wakelock(const char *wakelock_name)
Definition: usb_moded-common.c:342