linux/drivers/hwspinlock/hwspinlock_core.c

// SPDX-License-Identifier: GPL-2.0
/*
 * Hardware spinlock framework
 *
 * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com
 *
 * Contact: Ohad Ben-Cohen <[email protected]>
 */

#define pr_fmt(fmt)

#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/err.h>
#include <linux/jiffies.h>
#include <linux/radix-tree.h>
#include <linux/hwspinlock.h>
#include <linux/pm_runtime.h>
#include <linux/mutex.h>
#include <linux/of.h>

#include "hwspinlock_internal.h"

/* retry delay used in atomic context */
#define HWSPINLOCK_RETRY_DELAY_US

/* radix tree tags */
#define HWSPINLOCK_UNUSED

/*
 * A radix tree is used to maintain the available hwspinlock instances.
 * The tree associates hwspinlock pointers with their integer key id,
 * and provides easy-to-use API which makes the hwspinlock core code simple
 * and easy to read.
 *
 * Radix trees are quick on lookups, and reasonably efficient in terms of
 * storage, especially with high density usages such as this framework
 * requires (a continuous range of integer keys, beginning with zero, is
 * used as the ID's of the hwspinlock instances).
 *
 * The radix tree API supports tagging items in the tree, which this
 * framework uses to mark unused hwspinlock instances (see the
 * HWSPINLOCK_UNUSED tag above). As a result, the process of querying the
 * tree, looking for an unused hwspinlock instance, is now reduced to a
 * single radix tree API call.
 */
static RADIX_TREE(hwspinlock_tree, GFP_KERNEL);

/*
 * Synchronization of access to the tree is achieved using this mutex,
 * as the radix-tree API requires that users provide all synchronisation.
 * A mutex is needed because we're using non-atomic radix tree allocations.
 */
static DEFINE_MUTEX(hwspinlock_tree_lock);


/**
 * __hwspin_trylock() - attempt to lock a specific hwspinlock
 * @hwlock: an hwspinlock which we want to trylock
 * @mode: controls whether local interrupts are disabled or not
 * @flags: a pointer where the caller's interrupt state will be saved at (if
 *         requested)
 *
 * This function attempts to lock an hwspinlock, and will immediately
 * fail if the hwspinlock is already taken.
 *
 * Caution: If the mode is HWLOCK_RAW, that means user must protect the routine
 * of getting hardware lock with mutex or spinlock. Since in some scenarios,
 * user need some time-consuming or sleepable operations under the hardware
 * lock, they need one sleepable lock (like mutex) to protect the operations.
 *
 * If the mode is neither HWLOCK_IN_ATOMIC nor HWLOCK_RAW, upon a successful
 * return from this function, preemption (and possibly interrupts) is disabled,
 * so the caller must not sleep, and is advised to release the hwspinlock as
 * soon as possible. This is required in order to minimize remote cores polling
 * on the hardware interconnect.
 *
 * The user decides whether local interrupts are disabled or not, and if yes,
 * whether he wants their previous state to be saved. It is up to the user
 * to choose the appropriate @mode of operation, exactly the same way users
 * should decide between spin_trylock, spin_trylock_irq and
 * spin_trylock_irqsave.
 *
 * Returns: %0 if we successfully locked the hwspinlock or -EBUSY if
 * the hwspinlock was already taken.
 *
 * This function will never sleep.
 */
int __hwspin_trylock(struct hwspinlock *hwlock, int mode, unsigned long *flags)
{}
EXPORT_SYMBOL_GPL();

/**
 * __hwspin_lock_timeout() - lock an hwspinlock with timeout limit
 * @hwlock: the hwspinlock to be locked
 * @to: timeout value in msecs
 * @mode: mode which controls whether local interrupts are disabled or not
 * @flags: a pointer to where the caller's interrupt state will be saved at (if
 *         requested)
 *
 * This function locks the given @hwlock. If the @hwlock
 * is already taken, the function will busy loop waiting for it to
 * be released, but give up after @timeout msecs have elapsed.
 *
 * Caution: If the mode is HWLOCK_RAW, that means user must protect the routine
 * of getting hardware lock with mutex or spinlock. Since in some scenarios,
 * user need some time-consuming or sleepable operations under the hardware
 * lock, they need one sleepable lock (like mutex) to protect the operations.
 *
 * If the mode is HWLOCK_IN_ATOMIC (called from an atomic context) the timeout
 * is handled with busy-waiting delays, hence shall not exceed few msecs.
 *
 * If the mode is neither HWLOCK_IN_ATOMIC nor HWLOCK_RAW, upon a successful
 * return from this function, preemption (and possibly interrupts) is disabled,
 * so the caller must not sleep, and is advised to release the hwspinlock as
 * soon as possible. This is required in order to minimize remote cores polling
 * on the hardware interconnect.
 *
 * The user decides whether local interrupts are disabled or not, and if yes,
 * whether he wants their previous state to be saved. It is up to the user
 * to choose the appropriate @mode of operation, exactly the same way users
 * should decide between spin_lock, spin_lock_irq and spin_lock_irqsave.
 *
 * Returns: %0 when the @hwlock was successfully taken, and an appropriate
 * error code otherwise (most notably -ETIMEDOUT if the @hwlock is still
 * busy after @timeout msecs).
 *
 * The function will never sleep.
 */
int __hwspin_lock_timeout(struct hwspinlock *hwlock, unsigned int to,
					int mode, unsigned long *flags)
{}
EXPORT_SYMBOL_GPL();

/**
 * __hwspin_unlock() - unlock a specific hwspinlock
 * @hwlock: a previously-acquired hwspinlock which we want to unlock
 * @mode: controls whether local interrupts needs to be restored or not
 * @flags: previous caller's interrupt state to restore (if requested)
 *
 * This function will unlock a specific hwspinlock, enable preemption and
 * (possibly) enable interrupts or restore their previous state.
 * @hwlock must be already locked before calling this function: it is a bug
 * to call unlock on a @hwlock that is already unlocked.
 *
 * The user decides whether local interrupts should be enabled or not, and
 * if yes, whether he wants their previous state to be restored. It is up
 * to the user to choose the appropriate @mode of operation, exactly the
 * same way users decide between spin_unlock, spin_unlock_irq and
 * spin_unlock_irqrestore.
 *
 * The function will never sleep.
 */
void __hwspin_unlock(struct hwspinlock *hwlock, int mode, unsigned long *flags)
{}
EXPORT_SYMBOL_GPL();

/**
 * hwspin_lock_bust() - bust a specific hwspinlock
 * @hwlock: a previously-acquired hwspinlock which we want to bust
 * @id: identifier of the remote lock holder, if applicable
 *
 * This function will bust a hwspinlock that was previously acquired as
 * long as the current owner of the lock matches the id given by the caller.
 *
 * Context: Process context.
 *
 * Returns: 0 on success, or -EINVAL if the hwspinlock does not exist, or
 * the bust operation fails, and -EOPNOTSUPP if the bust operation is not
 * defined for the hwspinlock.
 */
int hwspin_lock_bust(struct hwspinlock *hwlock, unsigned int id)
{}
EXPORT_SYMBOL_GPL();

/**
 * of_hwspin_lock_simple_xlate - translate hwlock_spec to return a lock id
 * @hwlock_spec: hwlock specifier as found in the device tree
 *
 * This is a simple translation function, suitable for hwspinlock platform
 * drivers that only has a lock specifier length of 1.
 *
 * Returns: a relative index of the lock within a specified bank on success,
 * or -EINVAL on invalid specifier cell count.
 */
static inline int
of_hwspin_lock_simple_xlate(const struct of_phandle_args *hwlock_spec)
{}

/**
 * of_hwspin_lock_get_id() - get lock id for an OF phandle-based specific lock
 * @np: device node from which to request the specific hwlock
 * @index: index of the hwlock in the list of values
 *
 * This function provides a means for DT users of the hwspinlock module to
 * get the global lock id of a specific hwspinlock using the phandle of the
 * hwspinlock device, so that it can be requested using the normal
 * hwspin_lock_request_specific() API.
 *
 * Returns: the global lock id number on success, -EPROBE_DEFER if the
 * hwspinlock device is not yet registered, -EINVAL on invalid args
 * specifier value or an appropriate error as returned from the OF parsing
 * of the DT client node.
 */
int of_hwspin_lock_get_id(struct device_node *np, int index)
{}
EXPORT_SYMBOL_GPL();

/**
 * of_hwspin_lock_get_id_byname() - get lock id for an specified hwlock name
 * @np: device node from which to request the specific hwlock
 * @name: hwlock name
 *
 * This function provides a means for DT users of the hwspinlock module to
 * get the global lock id of a specific hwspinlock using the specified name of
 * the hwspinlock device, so that it can be requested using the normal
 * hwspin_lock_request_specific() API.
 *
 * Returns: the global lock id number on success, -EPROBE_DEFER if the
 * hwspinlock device is not yet registered, -EINVAL on invalid args
 * specifier value or an appropriate error as returned from the OF parsing
 * of the DT client node.
 */
int of_hwspin_lock_get_id_byname(struct device_node *np, const char *name)
{}
EXPORT_SYMBOL_GPL();

static int hwspin_lock_register_single(struct hwspinlock *hwlock, int id)
{}

static struct hwspinlock *hwspin_lock_unregister_single(unsigned int id)
{}

/**
 * hwspin_lock_register() - register a new hw spinlock device
 * @bank: the hwspinlock device, which usually provides numerous hw locks
 * @dev: the backing device
 * @ops: hwspinlock handlers for this device
 * @base_id: id of the first hardware spinlock in this bank
 * @num_locks: number of hwspinlocks provided by this device
 *
 * This function should be called from the underlying platform-specific
 * implementation, to register a new hwspinlock device instance.
 *
 * Should be called from a process context (might sleep)
 *
 * Returns: %0 on success, or an appropriate error code on failure
 */
int hwspin_lock_register(struct hwspinlock_device *bank, struct device *dev,
		const struct hwspinlock_ops *ops, int base_id, int num_locks)
{}
EXPORT_SYMBOL_GPL();

/**
 * hwspin_lock_unregister() - unregister an hw spinlock device
 * @bank: the hwspinlock device, which usually provides numerous hw locks
 *
 * This function should be called from the underlying platform-specific
 * implementation, to unregister an existing (and unused) hwspinlock.
 *
 * Should be called from a process context (might sleep)
 *
 * Returns: %0 on success, or an appropriate error code on failure
 */
int hwspin_lock_unregister(struct hwspinlock_device *bank)
{}
EXPORT_SYMBOL_GPL();

static void devm_hwspin_lock_unreg(struct device *dev, void *res)
{}

static int devm_hwspin_lock_device_match(struct device *dev, void *res,
					 void *data)
{}

/**
 * devm_hwspin_lock_unregister() - unregister an hw spinlock device for
 *				   a managed device
 * @dev: the backing device
 * @bank: the hwspinlock device, which usually provides numerous hw locks
 *
 * This function should be called from the underlying platform-specific
 * implementation, to unregister an existing (and unused) hwspinlock.
 *
 * Should be called from a process context (might sleep)
 *
 * Returns: %0 on success, or an appropriate error code on failure
 */
int devm_hwspin_lock_unregister(struct device *dev,
				struct hwspinlock_device *bank)
{}
EXPORT_SYMBOL_GPL();

/**
 * devm_hwspin_lock_register() - register a new hw spinlock device for
 *				 a managed device
 * @dev: the backing device
 * @bank: the hwspinlock device, which usually provides numerous hw locks
 * @ops: hwspinlock handlers for this device
 * @base_id: id of the first hardware spinlock in this bank
 * @num_locks: number of hwspinlocks provided by this device
 *
 * This function should be called from the underlying platform-specific
 * implementation, to register a new hwspinlock device instance.
 *
 * Should be called from a process context (might sleep)
 *
 * Returns: %0 on success, or an appropriate error code on failure
 */
int devm_hwspin_lock_register(struct device *dev,
			      struct hwspinlock_device *bank,
			      const struct hwspinlock_ops *ops,
			      int base_id, int num_locks)
{}
EXPORT_SYMBOL_GPL();

/**
 * __hwspin_lock_request() - tag an hwspinlock as used and power it up
 * @hwlock: the target hwspinlock
 *
 * This is an internal function that prepares an hwspinlock instance
 * before it is given to the user. The function assumes that
 * hwspinlock_tree_lock is taken.
 *
 * Returns: %0 or positive to indicate success, and a negative value to
 * indicate an error (with the appropriate error code)
 */
static int __hwspin_lock_request(struct hwspinlock *hwlock)
{}

/**
 * hwspin_lock_get_id() - retrieve id number of a given hwspinlock
 * @hwlock: a valid hwspinlock instance
 *
 * Returns: the id number of a given @hwlock, or -EINVAL if @hwlock is invalid.
 */
int hwspin_lock_get_id(struct hwspinlock *hwlock)
{}
EXPORT_SYMBOL_GPL();

/**
 * hwspin_lock_request() - request an hwspinlock
 *
 * This function should be called by users of the hwspinlock device,
 * in order to dynamically assign them an unused hwspinlock.
 * Usually the user of this lock will then have to communicate the lock's id
 * to the remote core before it can be used for synchronization (to get the
 * id of a given hwlock, use hwspin_lock_get_id()).
 *
 * Should be called from a process context (might sleep)
 *
 * Returns: the address of the assigned hwspinlock, or %NULL on error
 */
struct hwspinlock *hwspin_lock_request(void)
{}
EXPORT_SYMBOL_GPL();

/**
 * hwspin_lock_request_specific() - request for a specific hwspinlock
 * @id: index of the specific hwspinlock that is requested
 *
 * This function should be called by users of the hwspinlock module,
 * in order to assign them a specific hwspinlock.
 * Usually early board code will be calling this function in order to
 * reserve specific hwspinlock ids for predefined purposes.
 *
 * Should be called from a process context (might sleep)
 *
 * Returns: the address of the assigned hwspinlock, or %NULL on error
 */
struct hwspinlock *hwspin_lock_request_specific(unsigned int id)
{}
EXPORT_SYMBOL_GPL();

/**
 * hwspin_lock_free() - free a specific hwspinlock
 * @hwlock: the specific hwspinlock to free
 *
 * This function mark @hwlock as free again.
 * Should only be called with an @hwlock that was retrieved from
 * an earlier call to hwspin_lock_request{_specific}.
 *
 * Should be called from a process context (might sleep)
 *
 * Returns: %0 on success, or an appropriate error code on failure
 */
int hwspin_lock_free(struct hwspinlock *hwlock)
{}
EXPORT_SYMBOL_GPL();

static int devm_hwspin_lock_match(struct device *dev, void *res, void *data)
{}

static void devm_hwspin_lock_release(struct device *dev, void *res)
{}

/**
 * devm_hwspin_lock_free() - free a specific hwspinlock for a managed device
 * @dev: the device to free the specific hwspinlock
 * @hwlock: the specific hwspinlock to free
 *
 * This function mark @hwlock as free again.
 * Should only be called with an @hwlock that was retrieved from
 * an earlier call to hwspin_lock_request{_specific}.
 *
 * Should be called from a process context (might sleep)
 *
 * Returns: %0 on success, or an appropriate error code on failure
 */
int devm_hwspin_lock_free(struct device *dev, struct hwspinlock *hwlock)
{}
EXPORT_SYMBOL_GPL();

/**
 * devm_hwspin_lock_request() - request an hwspinlock for a managed device
 * @dev: the device to request an hwspinlock
 *
 * This function should be called by users of the hwspinlock device,
 * in order to dynamically assign them an unused hwspinlock.
 * Usually the user of this lock will then have to communicate the lock's id
 * to the remote core before it can be used for synchronization (to get the
 * id of a given hwlock, use hwspin_lock_get_id()).
 *
 * Should be called from a process context (might sleep)
 *
 * Returns: the address of the assigned hwspinlock, or %NULL on error
 */
struct hwspinlock *devm_hwspin_lock_request(struct device *dev)
{}
EXPORT_SYMBOL_GPL();

/**
 * devm_hwspin_lock_request_specific() - request for a specific hwspinlock for
 *					 a managed device
 * @dev: the device to request the specific hwspinlock
 * @id: index of the specific hwspinlock that is requested
 *
 * This function should be called by users of the hwspinlock module,
 * in order to assign them a specific hwspinlock.
 * Usually early board code will be calling this function in order to
 * reserve specific hwspinlock ids for predefined purposes.
 *
 * Should be called from a process context (might sleep)
 *
 * Returns: the address of the assigned hwspinlock, or %NULL on error
 */
struct hwspinlock *devm_hwspin_lock_request_specific(struct device *dev,
						     unsigned int id)
{}
EXPORT_SYMBOL_GPL();

MODULE_DESCRIPTION();
MODULE_AUTHOR();