// SPDX-License-Identifier: GPL-2.0

/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2012, 2015, Intel Corporation.
 */

/* This file is part of Lustre, http://www.lustre.org/
 *
 * Author: liang@whamcloud.com
 */

#define DEBUG_SUBSYSTEM S_LNET

#include <lnet/lib-lnet.h>

/** destroy cpu-partition lock, see libcfs_private.h for more detail */
void
cfs_percpt_lock_free(struct cfs_percpt_lock *pcl)
{
	LASSERT(pcl->pcl_locks != NULL);
	LASSERT(!pcl->pcl_locked);

	cfs_percpt_free(pcl->pcl_locks);
	LIBCFS_FREE(pcl, sizeof(*pcl));
}
EXPORT_SYMBOL(cfs_percpt_lock_free);

/**
 * cfs_percpt_lock_create() - create cpu-partition lock
 * @cptab: CPU Partitioning Table
 * @keys: lock class key for lockdep
 *
 * cpu-partition lock is designed for large-scale SMP system, so we need to
 * reduce cacheline conflict as possible as we can, that's the
 * reason we always allocate cacheline-aligned memory block.
 *
 * reate cpu-partition lock, see libcfs_private.h for more detail.
 *
 * Return Pointer to struct cfs_percpt_lock or %NULL on error
 */
struct cfs_percpt_lock *
cfs_percpt_lock_create(struct cfs_cpt_table *cptab,
		       struct lock_class_key *keys)
{
	struct cfs_percpt_lock	*pcl;
	spinlock_t		*lock;
	int			i;

	/* NB: cptab can be NULL, pcl will be for HW CPUs on that case */
	LIBCFS_ALLOC(pcl, sizeof(*pcl));
	if (pcl == NULL)
		return NULL;

	pcl->pcl_cptab = cptab;
	pcl->pcl_locks = cfs_percpt_alloc(cptab, sizeof(*lock));
	if (pcl->pcl_locks == NULL) {
		LIBCFS_FREE(pcl, sizeof(*pcl));
		return NULL;
	}

	if (keys == NULL) {
		CWARN("Cannot setup class key for percpt lock, you may see recursive locking warnings which are actually fake.\n");
	}

	cfs_percpt_for_each(lock, i, pcl->pcl_locks) {
		spin_lock_init(lock);
		if (keys != NULL)
			lockdep_set_class(lock, &keys[i]);
	}

	return pcl;
}
EXPORT_SYMBOL(cfs_percpt_lock_create);

/**
 * cfs_percpt_lock() - lock a CPU partition
 * @pcl: Per-CPT lock structure
 * @index: Which CPT partition to lock
 *         @index != CFS_PERCPT_LOCK_EX
 *           hold private lock indexed by @index
 *
 *         @index == CFS_PERCPT_LOCK_EX
 *           exclusively lock @pcl and nobody can take private lock
 */
void
cfs_percpt_lock(struct cfs_percpt_lock *pcl, int index)
__acquires(pcl->pcl_locks)
{
	int	ncpt = cfs_cpt_number(pcl->pcl_cptab);
	int	i;

	LASSERT(index >= CFS_PERCPT_LOCK_EX && index < ncpt);

	if (ncpt == 1) {
		index = 0;
	} else { /* serialize with exclusive lock */
		while (pcl->pcl_locked)
			cpu_relax();
	}

	if (likely(index != CFS_PERCPT_LOCK_EX)) {
		spin_lock(pcl->pcl_locks[index]);
		return;
	}

	/* exclusive lock request */
	for (i = 0; i < ncpt; i++) {
		spin_lock(pcl->pcl_locks[i]);
		if (i == 0) {
			LASSERT(!pcl->pcl_locked);
			/* nobody should take private lock after this
			 * so I wouldn't starve for too long time
			 */
			pcl->pcl_locked = 1;
		}
	}
}
EXPORT_SYMBOL(cfs_percpt_lock);

/** unlock a CPU partition */
void
cfs_percpt_unlock(struct cfs_percpt_lock *pcl, int index)
__releases(pcl->pcl_locks)
{
	int	ncpt = cfs_cpt_number(pcl->pcl_cptab);
	int	i;

	index = ncpt == 1 ? 0 : index;

	if (likely(index != CFS_PERCPT_LOCK_EX)) {
		spin_unlock(pcl->pcl_locks[index]);
		return;
	}

	for (i = ncpt - 1; i >= 0; i--) {
		if (i == 0) {
			LASSERT(pcl->pcl_locked);
			pcl->pcl_locked = 0;
		}
		spin_unlock(pcl->pcl_locks[i]);
	}
}
EXPORT_SYMBOL(cfs_percpt_unlock);
