/*
 * Copyright(c) 2019 Intel Corporation
 * SPDX-License-Identifier: BSD-3-Clause-Clear
 */

#include "utils_async_lock.h"

struct ocf_async_lock_waiter {
	struct list_head list;
	ocf_async_lock_t lock;
	bool write_lock;
	ocf_async_lock_end_t cmpl;
};

void _ocf_async_lock_collect_waiters(ocf_async_lock_t lock,
		struct list_head *waiters)
{
	ocf_async_lock_waiter_t iter, temp;

	list_for_each_entry_safe(iter, temp, &lock->waiters, list) {
		if (!iter->write_lock) {
			list_move_tail(&iter->list, waiters);
			lock->rd++;
		} else {
			if (!lock->rd) {
				list_move_tail(&iter->list, waiters);
				lock->wr = 1;
			}
			break;
		}
	}
}

void _ocf_async_lock_run_waiters(struct ocf_async_lock *lock,
		struct list_head *waiters, int status)
{
	ocf_async_lock_waiter_t iter, temp;

	/* TODO: Should we run waiters asynchronously? */

	list_for_each_entry_safe(iter, temp, waiters, list) {
		list_del(&iter->list);
		iter->cmpl(iter, status);
		env_vfree(iter);
	}
}

int ocf_async_lock_init(struct ocf_async_lock *lock, uint32_t waiter_priv_size)
{
	int err = 0;

	err = env_spinlock_init(&lock->waiters_lock);
	if (err)
		return err;

	INIT_LIST_HEAD(&lock->waiters);
	lock->rd = 0;
	lock->wr = 0;
	lock->waiter_priv_size = waiter_priv_size;

	return 0;
}

void ocf_async_lock_deinit(struct ocf_async_lock *lock)
{
	struct list_head waiters;
	ocf_async_lock_waiter_t iter, temp;

	INIT_LIST_HEAD(&waiters);

	env_spinlock_lock(&lock->waiters_lock);
	list_for_each_entry_safe(iter, temp, &lock->waiters, list)
		list_move_tail(&iter->list, &waiters);
	env_spinlock_unlock(&lock->waiters_lock);

	env_spinlock_destroy(&lock->waiters_lock);

	_ocf_async_lock_run_waiters(lock, &waiters, -OCF_ERR_NO_LOCK);
}

ocf_async_lock_waiter_t ocf_async_lock_new_waiter(ocf_async_lock_t lock,
		ocf_async_lock_end_t cmpl)
{
	ocf_async_lock_waiter_t waiter;

	waiter = env_vmalloc(sizeof(*waiter) + lock->waiter_priv_size);
	if (!waiter)
		return NULL;

	waiter->lock = lock;
	waiter->cmpl = cmpl;

	return waiter;
}

ocf_async_lock_t ocf_async_lock_waiter_get_lock(ocf_async_lock_waiter_t waiter)
{
	return waiter->lock;
}

void *ocf_async_lock_waiter_get_priv(ocf_async_lock_waiter_t waiter)
{
	return (void *)waiter + sizeof(*waiter);
}

static int _ocf_async_trylock(struct ocf_async_lock *lock)
{
	if (lock->wr || lock->rd)
		return -OCF_ERR_NO_LOCK;

	lock->wr = 1;
	return 0;
}

void ocf_async_lock(ocf_async_lock_waiter_t waiter)
{
	ocf_async_lock_t lock = waiter->lock;
	int result;

	env_spinlock_lock(&lock->waiters_lock);

	result = _ocf_async_trylock(lock);
	if (!result) {
		env_spinlock_unlock(&lock->waiters_lock);
		waiter->cmpl(waiter, 0);
		env_vfree(waiter);
		return;
	}

	waiter->write_lock = true;
	list_add_tail(&waiter->list, &lock->waiters);

	env_spinlock_unlock(&lock->waiters_lock);
}

int ocf_async_trylock(struct ocf_async_lock *lock)
{
	int result;

	env_spinlock_lock(&lock->waiters_lock);
	result = _ocf_async_trylock(lock);
	env_spinlock_unlock(&lock->waiters_lock);

	return result;
}

void ocf_async_unlock(struct ocf_async_lock *lock)
{
	struct list_head waiters;

	INIT_LIST_HEAD(&waiters);

	env_spinlock_lock(&lock->waiters_lock);

	ENV_BUG_ON(lock->rd);
	ENV_BUG_ON(!lock->wr);

	lock->wr = 0;

	_ocf_async_lock_collect_waiters(lock, &waiters);

	env_spinlock_unlock(&lock->waiters_lock);

	_ocf_async_lock_run_waiters(lock, &waiters, 0);
}

static int _ocf_async_read_trylock(struct ocf_async_lock *lock)
{
	if (lock->wr || !list_empty(&lock->waiters))
		return -OCF_ERR_NO_LOCK;

	lock->rd++;
	return 0;
}

void ocf_async_read_lock(ocf_async_lock_waiter_t waiter)
{
	ocf_async_lock_t lock = waiter->lock;
	int result;

	env_spinlock_lock(&lock->waiters_lock);

	result = _ocf_async_read_trylock(lock);
	if (!result) {
		env_spinlock_unlock(&lock->waiters_lock);
		waiter->cmpl(waiter, 0);
		env_vfree(waiter);
		return;
	}

	waiter->write_lock = false;
	list_add_tail(&waiter->list, &lock->waiters);

	env_spinlock_unlock(&lock->waiters_lock);
}

int ocf_async_read_trylock(struct ocf_async_lock *lock)
{
	int result;

	env_spinlock_lock(&lock->waiters_lock);
	result = _ocf_async_read_trylock(lock);
	env_spinlock_unlock(&lock->waiters_lock);

	return result;
}

void ocf_async_read_unlock(struct ocf_async_lock *lock)
{
	struct list_head waiters;

	INIT_LIST_HEAD(&waiters);

	env_spinlock_lock(&lock->waiters_lock);

	ENV_BUG_ON(!lock->rd);
	ENV_BUG_ON(lock->wr);

	if (--lock->rd) {
		env_spinlock_unlock(&lock->waiters_lock);
		return;
	}

	_ocf_async_lock_collect_waiters(lock, &waiters);

	env_spinlock_unlock(&lock->waiters_lock);

	_ocf_async_lock_run_waiters(lock, &waiters, 0);
}

bool ocf_async_is_locked(struct ocf_async_lock *lock)
{
	bool locked;

	env_spinlock_lock(&lock->waiters_lock);
	locked = lock->rd || lock->wr;
	env_spinlock_unlock(&lock->waiters_lock);

	return locked;
}
