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_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 
449 cleanup:
450  return;
451 }
452 
453 static 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 
496 EXIT:
497  /* clean up */
498  if( dev )
499  udev_device_unref(dev);
500 
501  return score;
502 }
503 
504 gboolean 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 
616 EXIT:
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 
634 void 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 }
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