Re: [PATCH 03/13] PM: Implement wakelock api.

Arve Hjønnevåg
2009-02-11T02:52:43Z

On Sat, Feb 7, 2009 at 2:27 PM, Rafael J. Wysocki <rjw@sisk.pl> wrote:
>> +{
>> + struct timespec ts;
>> + struct timespec kt;
>> + struct timespec tomono;
>> + struct timespec delta;
>> + unsigned long seq;
>> + long timeout;
>> +
>> + if (!(lock->flags & WAKE_LOCK_AUTO_EXPIRE))
>> + return 0;
>> + do {
>> + seq = read_seqbegin(&xtime_lock);
>> + timeout = lock->expires - jiffies;
>
> Can it overflow?

Yes, but I have not fixed this since you have to hold a wakelock for
several months to hit it. I could fix it may expiring wakelocks at
least every MAX_JIFFY_OFFSET or by moving to jiffies_64.

> Anyway, you are using struct timespec in computations, so why don't you
> store it in the lock structure?

I only use struct timespec for the stats.

>> +
>> +static int print_lock_stat(char *buf, struct wake_lock *lock)
>> +{
>> + int lock_count = lock->stat.count;
>> + int expire_count = lock->stat.expire_count;
>> + ktime_t active_time = ktime_set(0, 0);
>> + ktime_t total_time = lock->stat.total_time;
>> + ktime_t max_time = lock->stat.max_time;
>> + ktime_t prevent_suspend_time = lock->stat.prevent_suspend_time;
>> + if (lock->flags & WAKE_LOCK_ACTIVE) {
>> + ktime_t now, add_time;
>> + int expired = get_expired_time(lock, &now);
>> + if (!expired)
>> + now = ktime_get();
>> + add_time = ktime_sub(now, lock->stat.last_time);
>> + lock_count++;
>> + if (!expired)
>> + active_time = add_time;
>> + else
>> + expire_count++;
>> + total_time = ktime_add(total_time, add_time);
>> + if (lock->flags & WAKE_LOCK_PREVENTING_SUSPEND)
>> + prevent_suspend_time = ktime_add(prevent_suspend_time,
>> + ktime_sub(now, last_sleep_time_update));
>> + if (add_time.tv64 > max_time.tv64)
>> + max_time = add_time;
>> + }
>> +
>> + return sprintf(buf, "\"%s\"\t%d\t%d\t%d\t%lld\t%lld\t%lld\t%lld\t"
>> + "%lld\n", lock->name, lock_count, expire_count,
>> + lock->stat.wakeup_count, ktime_to_ns(active_time),
>> + ktime_to_ns(total_time),
>> + ktime_to_ns(prevent_suspend_time), ktime_to_ns(max_time),
>> + ktime_to_ns(lock->stat.last_time));
>> +}
>> +
>
> Why did you decide to put that in /proc ?

That's where most other stats are reported.

>> +static long has_wake_lock_locked(int type)
>
> I'd change the name, it suggests something different from what the function
> does.

It does what the name suggest, and more. The code that just cares if
there are lock held or not is easier to read this way.

>> +{
>> + struct wake_lock *lock, *n;
>> + long max_timeout = 0;
>> +
>> + BUG_ON(type >= WAKE_LOCK_TYPE_COUNT);
>> + list_for_each_entry_safe(lock, n, &active_wake_locks[type], link) {
>> + if (lock->flags & WAKE_LOCK_AUTO_EXPIRE) {
>> + long timeout = lock->expires - jiffies;
>
> I think time_after() is for things like this.

time_after does not return the timeout, which I need here. I changed
another instance though.

>> +static void suspend(struct work_struct *work)
>> +{
>> + int ret;
>> + int entry_event_num;
>> +
>> + if (has_wake_lock(WAKE_LOCK_SUSPEND)) {
>> + if (debug_mask & DEBUG_SUSPEND)
>> + pr_info("suspend: abort suspend\n");
>> + return;
>> + }
>> +
>> + entry_event_num = current_event_num;
>> + sys_sync();
>
> pm_suspend() will do it, you don't need to.

Removed.

>
>> + if (debug_mask & DEBUG_SUSPEND)
>> + pr_info("suspend: enter suspend\n");
>
> Shouldn't that check if someone has taken a wakelock in the meantime?

It checks at the top of the function. With sys_sync gone, another
check would not be useful.

>> + ret = pm_suspend(requested_suspend_state);

>> +static int power_suspend_late(struct platform_device *pdev, pm_message_t state)
>> +{
>> + int ret = has_wake_lock(WAKE_LOCK_SUSPEND) ? -EAGAIN : 0;
>> +#ifdef CONFIG_WAKELOCK_STAT
>> + wait_for_wakeup = 1;
>> +#endif
>> + if (debug_mask & DEBUG_SUSPEND)
>> + pr_info("power_suspend_late return %d\n", ret);
>> + return ret;
>> +}
>> +
>> +static struct platform_driver power_driver = {
>> + .driver.name = "power",
>> + .suspend_late = power_suspend_late,
>> +};
>> +static struct platform_device power_device = {
>> + .name = "power",
>> +};
>
> What's this and what's it for?

It makes sure suspend fails if a wakelock is locked while suspending.
Is there a simpler way to register a suspend_late handler?

>> +void wake_lock_init(struct wake_lock *lock, int type, const char *name)
>> +{
>> + unsigned long irqflags = 0;
>> +
>> + if (name)
>> + lock->name = name;
>
> Hm. I'd rather reserve memory for the name and copy it from the address
> provided by the caller.

I'd rather not. With the current interface, wake_lock_init can never fail.

>> + BUG_ON(!lock->name);
>
> Isn't it a bit too drastic? Perhaps make it return a value so that the caller
> can check if it has the lock?

I think a BUG_ON is appropriate. Calling wake_lock_init with an
invalid argument is a driver bug.

>> +static void wake_lock_internal(
>
> What about __wake_lock() ?

OK.

>
>> + struct wake_lock *lock, long timeout, int has_timeout)
>
> What's the point of the last argument? Wouldn't timeout == 0 be sufficient?

No, timeout == 0 means expire immediately.

> Also, does it make sense to pass negative timeout to it?

A negative timeout is fine, as long as it does not underflow.

>
>> +{
>> + int type;
>> + unsigned long irqflags;
>> + long expire_in;
>> +
>> + spin_lock_irqsave(&list_lock, irqflags);
>> + type = lock->flags & WAKE_LOCK_TYPE_MASK;
>> + BUG_ON(type >= WAKE_LOCK_TYPE_COUNT);
>> + BUG_ON(!(lock->flags & WAKE_LOCK_INITIALIZED));
>
> I don't like these BUG_ON()s. Please do something less drastic instead.
>
>> +#ifdef CONFIG_WAKELOCK_STAT
>> + if (type == WAKE_LOCK_SUSPEND && wait_for_wakeup) {
>> + if (debug_mask & DEBUG_WAKEUP)
>> + pr_info("wakeup wake lock: %s\n", lock->name);
>> + wait_for_wakeup = 0;
>> + lock->stat.wakeup_count++;
>> + }
>> + if ((lock->flags & WAKE_LOCK_AUTO_EXPIRE) &&
>> + (long)(lock->expires - jiffies) <= 0) {
>> + wake_unlock_stat_locked(lock, 0);
>> + lock->stat.last_time = ktime_get();
>> + }
>> +#endif
>> + if (!(lock->flags & WAKE_LOCK_ACTIVE)) {
>> + lock->flags |= WAKE_LOCK_ACTIVE;
>> +#ifdef CONFIG_WAKELOCK_STAT
>> + lock->stat.last_time = ktime_get();
>> +#endif
>> + }
>> + list_del(&lock->link);
>> + if (has_timeout) {
>> + if (debug_mask & DEBUG_WAKE_LOCK)
>> + pr_info("wake_lock: %s, type %d, timeout %ld.%03lu\n",
>> + lock->name, type, timeout / HZ,
>> + (timeout % HZ) * MSEC_PER_SEC / HZ);
>> + lock->expires = jiffies + timeout;
>> + lock->flags |= WAKE_LOCK_AUTO_EXPIRE;
>> + list_add_tail(&lock->link, &active_wake_locks[type]);
>> + } else {
>> + if (debug_mask & DEBUG_WAKE_LOCK)
>> + pr_info("wake_lock: %s, type %d\n", lock->name, type);
>> + lock->expires = LONG_MAX;
>> + lock->flags &= ~WAKE_LOCK_AUTO_EXPIRE;
>> + list_add(&lock->link, &active_wake_locks[type]);
>> + }
>
> Why not to use list_add_tail() in both cases?

If you add a wakelock with no timeout, there is no reason for
has_wake_lock_locked to look at more than one wakelock.

>
>> + if (type == WAKE_LOCK_SUSPEND) {
>> + current_event_num++;
>> +#ifdef CONFIG_WAKELOCK_STAT
>> + if (lock == &main_wake_lock)
>> + update_sleep_wait_stats_locked(1);
>> + else if (!wake_lock_active(&main_wake_lock))
>> + update_sleep_wait_stats_locked(0);
>> +#endif
>> + if (has_timeout)
>> + expire_in = has_wake_lock_locked(type);
>> + else
>> + expire_in = -1;
>
> expire_i = has_timeout ? has_wake_lock_locked(type) : -1;
>
> (there is something like this above too).
>
>> + if (expire_in > 0) {
>> + if (debug_mask & DEBUG_EXPIRE)
>> + pr_info("wake_lock: %s, start expire timer, "
>> + "%ld\n", lock->name, expire_in);
>> + mod_timer(&expire_timer, jiffies + expire_in);
>
> What exactly are you trying to achieve here?

Unlock wakelocks with timeouts when doing so can allow the system to
go to sleep.

>
>> + } else {
>> + if (del_timer(&expire_timer))
>> + if (debug_mask & DEBUG_EXPIRE)
>> + pr_info("wake_lock: %s, stop expire "
>> + "timer\n", lock->name);
>> + if (expire_in == 0)
>> + queue_work(suspend_work_queue, &suspend_work);
>
> This appears to only happen if timeout = 0 is passed to this function while
> has_timeout == true. Is it correct?

Unless you are on a system where jiffies can change while this
function is running, then yes.

>> + ret = platform_device_register(&power_device);
>> + if (ret) {
>> + pr_err("wakelocks_init: platform_device_register failed\n");
>> + goto err_platform_device_register;
>> + }
>> + ret = platform_driver_register(&power_driver);
>> + if (ret) {
>> + pr_err("wakelocks_init: platform_driver_register failed\n");
>> + goto err_platform_driver_register;
>> + }
>
> Is this really a platform thing?
>
> What exactly is the benefit of having the 'power_device' and 'power_driver'
> registered?.

I need both for suspend_late to be called.

> Also, I'd really prefer to see the patch without the CONFIG_WAKELOCK_STAT
> thing included, first. It would have been much easier to read.

I moved more of the stats code to the CONFIG_WAKELOCK_STAT section
which should help.

--
Arve Hjønnevåg