Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[udev] Suspend proof event reading and processing. Fixes JB#34440
Depending on kernel side usb drivers and charging logic, it is possible
that device suspends immediately after reporting usb connect / disconnect
while display is off and autosuspend enabled. That can delay the actions
that should be made on usb connection state changes.

Acquire a wakelock for the duration of udev event input processing.

Hold a wakelock for a while when usb connection state changes occur.

Signed-off-by: Simo Piiroinen <simo.piiroinen@jollamobile.com>
  • Loading branch information
spiiroin committed Apr 26, 2016
1 parent 2fa5781 commit c982d3b
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 15 deletions.
55 changes: 40 additions & 15 deletions src/usb_moded-udev.c
Expand Up @@ -227,32 +227,49 @@ static gboolean monitor_udev(GIOChannel *iochannel G_GNUC_UNUSED, GIOCondition c
{
struct udev_device *dev;

gboolean continue_watching = TRUE;

/* No code paths are allowed to bypass the release_wakelock() call below */
acquire_wakelock(USB_MODED_WAKELOCK_PROCESS_INPUT);

if(cond & G_IO_IN)
{
/* This normally blocks but G_IO_IN indicates that we can read */
dev = udev_monitor_receive_device (mon);
if (dev)
if (!dev)
{
/* if we get something else something bad happened stop watching to avoid busylooping */
continue_watching = FALSE;
}
else
{
/* check if it is the actual device we want to check */
if(strcmp(dev_name, udev_device_get_sysname(dev)))
if(!strcmp(dev_name, udev_device_get_sysname(dev)))
{
udev_device_unref(dev);
return TRUE;
}

if(!strcmp(udev_device_get_action(dev), "change"))
{
udev_parse(dev, false);
if(!strcmp(udev_device_get_action(dev), "change"))
{
udev_parse(dev, false);
}
}

udev_device_unref(dev);
}
/* if we get something else something bad happened stop watching to avoid busylooping */
else
exit(1);
}

/* keep watching */
return TRUE;

if(cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
{
/* Unhandled errors turn io watch to virtual busyloop too */
continue_watching = FALSE;
}

release_wakelock(USB_MODED_WAKELOCK_PROCESS_INPUT);

if (!continue_watching)
{
log_crit("udev io watch disabled");
}

return continue_watching;
}

void hwal_cleanup(void)
Expand Down Expand Up @@ -367,6 +384,10 @@ static void udev_parse(struct udev_device *dev, bool initial)
if (strcmp(tmp, "1")) {
log_debug("DISCONNECTED");

/* Block suspend briefly on connection state change */
if (get_usb_connection_state())
delay_suspend();

cancel_cable_connection_timeout();

if (charger) {
Expand All @@ -383,6 +404,10 @@ static void udev_parse(struct udev_device *dev, bool initial)
charger = 0;
}
else {
/* Block suspend briefly on connection state change */
if (!get_usb_connection_state())
delay_suspend();

tmp = udev_device_get_property_value(dev, "POWER_SUPPLY_TYPE");
/*
* Power supply type might not exist also :(
Expand Down
130 changes: 130 additions & 0 deletions src/usb_moded.c
Expand Up @@ -922,6 +922,135 @@ static gboolean sigpipe_init(void)
return success;
}

/** Write string to already existing sysfs file
*
* Note: Attempts to write to nonexisting files are silently ignored.
*
* @param path Where to write
* @param text What to write
*/
static void write_to_sysfs_file(const char *path, const char *text)
{
int fd = -1;

if (!path || !text)
goto EXIT;

if ((fd = open(path, O_WRONLY)) == -1) {
if (errno != ENOENT) {
log_warning("%s: open for writing failed: %s",
path, strerror(errno));
}
goto EXIT;
}

if (write(fd, text, strlen(text)) == -1) {
log_warning("%s: write failed : %s", path, strerror(errno));
goto EXIT;
}
EXIT:
if (fd != -1)
close(fd);
}

/** Acquire wakelock via sysfs
*
* Wakelock must be released via release_wakelock().
*
* Automatically terminating wakelock is used, so that we
* do not block suspend indefinately in case usb_moded
* gets stuck or crashes.
*
* Note: The name should be unique within the system.
*
* @param wakelock_name Wake lock to be acquired
*/
void acquire_wakelock(const char *wakelock_name)
{
char buff[256];
snprintf(buff, sizeof buff, "%s %lld",
wakelock_name,
USB_MODED_SUSPEND_DELAY_MAXIMUM_MS * 1000000LL);
write_to_sysfs_file("/sys/power/wake_lock", buff);

log_debug("acquire_wakelock %s", wakelock_name);
}

/** Release wakelock via sysfs
*
* @param wakelock_name Wake lock to be released
*/
void release_wakelock(const char *wakelock_name)
{
log_debug("release_wakelock %s", wakelock_name);

write_to_sysfs_file("/sys/power/wake_unlock", wakelock_name);
}

/** Flag for: USB_MODED_WAKELOCK_STATE_CHANGE has been acquired */
static bool blocking_suspend = false;

/** Timer for releasing USB_MODED_WAKELOCK_STATE_CHANGE */
static guint allow_suspend_id = 0;

/** Release wakelock acquired via delay_suspend()
*
* Meant to be called on usb-moded exit so that wakelocks
* are not left behind.
*/
void allow_suspend(void)
{
if( allow_suspend_id ) {
g_source_remove(allow_suspend_id),
allow_suspend_id = 0;
}

if( blocking_suspend ) {
blocking_suspend = false;
release_wakelock(USB_MODED_WAKELOCK_STATE_CHANGE);
}
}

/** Timer callback for releasing wakelock acquired via delay_suspend()
*
* @param aptr callback argument (not used)
*/
static gboolean allow_suspend_cb(gpointer aptr)
{
(void)aptr;

allow_suspend_id = 0;

allow_suspend();

return FALSE;
}

/** Block suspend briefly
*
* Meant to be called in situations usb activity might have woken
* up the device (cable connect while display is off), or could
* allow device to suspend (cable disconnect while display is off).
*
* Allows usb moded some time to finish asynchronous activity and
* other processes to receive and process state changes broadcast
* by usb-moded.
*/
void delay_suspend(void)
{
/* Use of automatically terminating wakelocks also means we need
* to renew the wakelock when extending the suspend delay. */
acquire_wakelock(USB_MODED_WAKELOCK_STATE_CHANGE);

blocking_suspend = true;

if( allow_suspend_id )
g_source_remove(allow_suspend_id);

allow_suspend_id = g_timeout_add(USB_MODED_SUSPEND_DELAY_DEFAULT_MS,
allow_suspend_cb, 0);
}

int main(int argc, char* argv[])
{
int result = EXIT_FAILURE;
Expand Down Expand Up @@ -1068,5 +1197,6 @@ int main(int argc, char* argv[])
EXIT:
handle_exit();

allow_suspend();
return result;
}
18 changes: 18 additions & 0 deletions src/usb_moded.h
Expand Up @@ -71,6 +71,24 @@ void set_charger_connected(gboolean state);
gchar *get_mode_list(void);
int valid_mode(const char *mode);

/** Name of the wakelock usb_moded uses for temporary suspend delay */
#define USB_MODED_WAKELOCK_STATE_CHANGE "usb_moded_state"

/** Name of the wakelock usb_moded uses for input processing */
#define USB_MODED_WAKELOCK_PROCESS_INPUT "usb_moded_input"

/** How long usb_moded will delay suspend by default [ms] */
#define USB_MODED_SUSPEND_DELAY_DEFAULT_MS 5000

/** How long usb_moded is allowed to block suspend [ms] */
#define USB_MODED_SUSPEND_DELAY_MAXIMUM_MS \
(USB_MODED_SUSPEND_DELAY_DEFAULT_MS * 2)

void acquire_wakelock(const char *wakelock_name);
void release_wakelock(const char *wakelock_name);

void allow_suspend(void);
void delay_suspend(void);

extern int cable_connection_delay;

Expand Down

0 comments on commit c982d3b

Please sign in to comment.