blob: e217acef8f4c1a9bbee088ce7ace37d49385ff5e [file] [log] [blame] [edit]
// SPDX-License-Identifier: GPL-2.0
/*
* Wakelock for the runtime to explicitly claim it's going to use the EdgeTPU
* device.
*
* Copyright (C) 2021 Google, Inc.
*/
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include "edgetpu-config.h"
#include "edgetpu-internal.h"
#include "edgetpu-wakelock.h"
static const char *const event_name[] = {
#define X(name, _) #name
EDGETPU_WAKELOCK_EVENTS
#undef X
};
/*
* Loops through the events and warns if any event has a non-zero counter.
* Returns true if at least one non-zero counter is found.
*
* Caller holds @wakelock->lock.
*/
static bool wakelock_warn_non_zero_event(struct edgetpu_wakelock *wakelock)
{
int i;
bool ret = false;
for (i = 0; i < EDGETPU_WAKELOCK_EVENT_END; i++) {
if (wakelock->event_count[i]) {
ret = true;
etdev_warn(wakelock->etdev,
"%s has non-zero counter=%d", event_name[i],
wakelock->event_count[i]);
}
}
return ret;
}
struct edgetpu_wakelock *edgetpu_wakelock_alloc(struct edgetpu_dev *etdev)
{
#ifndef EDGETPU_HAS_WAKELOCK
return EDGETPU_NO_WAKELOCK;
#else /* !EDGETPU_HAS_WAKELOCK */
struct edgetpu_wakelock *wakelock =
kzalloc(sizeof(*wakelock), GFP_KERNEL);
if (!wakelock)
return NULL;
wakelock->etdev = etdev;
mutex_init(&wakelock->lock);
/* Initialize client wakelock state to "released" */
wakelock->req_count = 0;
return wakelock;
#endif /* EDGETPU_HAS_WAKELOCK */
}
void edgetpu_wakelock_free(struct edgetpu_wakelock *wakelock)
{
if (IS_ERR_OR_NULL(wakelock))
return;
kfree(wakelock);
}
bool edgetpu_wakelock_inc_event_locked(struct edgetpu_wakelock *wakelock,
enum edgetpu_wakelock_event evt)
{
bool ret = true;
if (NO_WAKELOCK(wakelock))
return true;
if (!wakelock->req_count) {
ret = false;
etdev_warn(
wakelock->etdev,
"invalid increase event %d when wakelock is released",
evt);
} else {
++wakelock->event_count[evt];
/* integer overflow.. */
if (unlikely(wakelock->event_count[evt] == 0)) {
--wakelock->event_count[evt];
ret = false;
etdev_warn_once(wakelock->etdev,
"int overflow on increasing event %d",
evt);
}
}
return ret;
}
bool edgetpu_wakelock_inc_event(struct edgetpu_wakelock *wakelock,
enum edgetpu_wakelock_event evt)
{
bool ret;
if (NO_WAKELOCK(wakelock))
return true;
mutex_lock(&wakelock->lock);
ret = edgetpu_wakelock_inc_event_locked(wakelock, evt);
mutex_unlock(&wakelock->lock);
return ret;
}
bool edgetpu_wakelock_dec_event_locked(struct edgetpu_wakelock *wakelock,
enum edgetpu_wakelock_event evt)
{
bool ret = true;
if (NO_WAKELOCK(wakelock))
return true;
if (!wakelock->event_count[evt]) {
ret = false;
etdev_warn(wakelock->etdev, "event %d unbalanced decreasing",
evt);
} else {
--wakelock->event_count[evt];
}
return ret;
}
bool edgetpu_wakelock_dec_event(struct edgetpu_wakelock *wakelock,
enum edgetpu_wakelock_event evt)
{
bool ret;
if (NO_WAKELOCK(wakelock))
return true;
mutex_lock(&wakelock->lock);
ret = edgetpu_wakelock_dec_event_locked(wakelock, evt);
mutex_unlock(&wakelock->lock);
return ret;
}
uint edgetpu_wakelock_lock(struct edgetpu_wakelock *wakelock)
{
if (NO_WAKELOCK(wakelock))
return 1;
mutex_lock(&wakelock->lock);
return wakelock->req_count;
}
void edgetpu_wakelock_unlock(struct edgetpu_wakelock *wakelock)
{
if (!NO_WAKELOCK(wakelock))
mutex_unlock(&wakelock->lock);
}
int edgetpu_wakelock_acquire(struct edgetpu_wakelock *wakelock)
{
int ret;
if (NO_WAKELOCK(wakelock))
return 1;
ret = wakelock->req_count++;
/* integer overflow */
if (unlikely(ret < 0)) {
wakelock->req_count--;
return -EOVERFLOW;
}
return ret;
}
int edgetpu_wakelock_release(struct edgetpu_wakelock *wakelock)
{
if (NO_WAKELOCK(wakelock))
return 1;
if (!wakelock->req_count) {
etdev_warn(wakelock->etdev, "invalid wakelock release");
return -EINVAL;
}
/* only need to check events when this is the last reference */
if (wakelock->req_count == 1 &&
wakelock_warn_non_zero_event(wakelock)) {
etdev_warn(
wakelock->etdev,
"detected non-zero events, refusing wakelock release");
return -EAGAIN;
}
return --wakelock->req_count;
}