diff options
Diffstat (limited to 'drivers/hwspinlock/hwspinlock_core.c')
| -rw-r--r-- | drivers/hwspinlock/hwspinlock_core.c | 509 |
1 files changed, 400 insertions, 109 deletions
diff --git a/drivers/hwspinlock/hwspinlock_core.c b/drivers/hwspinlock/hwspinlock_core.c index 461a0d739d75..cc8e952a6772 100644 --- a/drivers/hwspinlock/hwspinlock_core.c +++ b/drivers/hwspinlock/hwspinlock_core.c @@ -1,22 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Hardware spinlock framework * * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com * * Contact: Ohad Ben-Cohen <ohad@wizery.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #define pr_fmt(fmt) "%s: " fmt, __func__ +#include <linux/delay.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/spinlock.h> @@ -27,9 +20,13 @@ #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 100 + /* radix tree tags */ #define HWSPINLOCK_UNUSED (0) /* tags an hwspinlock as unused */ @@ -70,10 +67,16 @@ static DEFINE_MUTEX(hwspinlock_tree_lock); * This function attempts to lock an hwspinlock, and will immediately * fail if the hwspinlock is already taken. * - * 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. + * 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 @@ -81,16 +84,17 @@ static DEFINE_MUTEX(hwspinlock_tree_lock); * should decide between spin_trylock, spin_trylock_irq and * spin_trylock_irqsave. * - * Returns 0 if we successfully locked the hwspinlock or -EBUSY if + * 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) { int ret; - BUG_ON(!hwlock); - BUG_ON(!flags && mode == HWLOCK_IRQSTATE); + if (WARN_ON(!hwlock || (!flags && mode == HWLOCK_IRQSTATE))) + return -EINVAL; /* * This spin_lock{_irq, _irqsave} serves three purposes: @@ -105,12 +109,21 @@ int __hwspin_trylock(struct hwspinlock *hwlock, int mode, unsigned long *flags) * problems with hwspinlock usage (e.g. scheduler checks like * 'scheduling while atomic' etc.) */ - if (mode == HWLOCK_IRQSTATE) + switch (mode) { + case HWLOCK_IRQSTATE: ret = spin_trylock_irqsave(&hwlock->lock, *flags); - else if (mode == HWLOCK_IRQ) + break; + case HWLOCK_IRQ: ret = spin_trylock_irq(&hwlock->lock); - else + break; + case HWLOCK_RAW: + case HWLOCK_IN_ATOMIC: + ret = 1; + break; + default: ret = spin_trylock(&hwlock->lock); + break; + } /* is lock already taken by another context on the local cpu ? */ if (!ret) @@ -121,12 +134,21 @@ int __hwspin_trylock(struct hwspinlock *hwlock, int mode, unsigned long *flags) /* if hwlock is already taken, undo spin_trylock_* and exit */ if (!ret) { - if (mode == HWLOCK_IRQSTATE) + switch (mode) { + case HWLOCK_IRQSTATE: spin_unlock_irqrestore(&hwlock->lock, *flags); - else if (mode == HWLOCK_IRQ) + break; + case HWLOCK_IRQ: spin_unlock_irq(&hwlock->lock); - else + break; + case HWLOCK_RAW: + case HWLOCK_IN_ATOMIC: + /* Nothing to do */ + break; + default: spin_unlock(&hwlock->lock); + break; + } return -EBUSY; } @@ -150,7 +172,7 @@ EXPORT_SYMBOL_GPL(__hwspin_trylock); /** * __hwspin_lock_timeout() - lock an hwspinlock with timeout limit * @hwlock: the hwspinlock to be locked - * @timeout: timeout value in msecs + * @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) @@ -159,26 +181,36 @@ EXPORT_SYMBOL_GPL(__hwspin_trylock); * is already taken, the function will busy loop waiting for it to * be released, but give up after @timeout msecs have elapsed. * - * Upon a successful return from this function, preemption is disabled - * (and possibly local interrupts, too), 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. + * 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 + * 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. + * busy after @timeout msecs). + * + * The function will never sleep. */ int __hwspin_lock_timeout(struct hwspinlock *hwlock, unsigned int to, int mode, unsigned long *flags) { int ret; - unsigned long expire; + unsigned long expire, atomic_delay = 0; expire = msecs_to_jiffies(to) + jiffies; @@ -192,8 +224,15 @@ int __hwspin_lock_timeout(struct hwspinlock *hwlock, unsigned int to, * The lock is already taken, let's check if the user wants * us to try again */ - if (time_is_before_eq_jiffies(expire)) - return -ETIMEDOUT; + if (mode == HWLOCK_IN_ATOMIC) { + udelay(HWSPINLOCK_RETRY_DELAY_US); + atomic_delay += HWSPINLOCK_RETRY_DELAY_US; + if (atomic_delay > to * 1000) + return -ETIMEDOUT; + } else { + if (time_is_before_eq_jiffies(expire)) + return -ETIMEDOUT; + } /* * Allow platform-specific relax handlers to prevent @@ -228,8 +267,8 @@ EXPORT_SYMBOL_GPL(__hwspin_lock_timeout); */ void __hwspin_unlock(struct hwspinlock *hwlock, int mode, unsigned long *flags) { - BUG_ON(!hwlock); - BUG_ON(!flags && mode == HWLOCK_IRQSTATE); + if (WARN_ON(!hwlock || (!flags && mode == HWLOCK_IRQSTATE))) + return; /* * We must make sure that memory operations (both reads and writes), @@ -248,15 +287,169 @@ void __hwspin_unlock(struct hwspinlock *hwlock, int mode, unsigned long *flags) hwlock->bank->ops->unlock(hwlock); /* Undo the spin_trylock{_irq, _irqsave} called while locking */ - if (mode == HWLOCK_IRQSTATE) + switch (mode) { + case HWLOCK_IRQSTATE: spin_unlock_irqrestore(&hwlock->lock, *flags); - else if (mode == HWLOCK_IRQ) + break; + case HWLOCK_IRQ: spin_unlock_irq(&hwlock->lock); - else + break; + case HWLOCK_RAW: + case HWLOCK_IN_ATOMIC: + /* Nothing to do */ + break; + default: spin_unlock(&hwlock->lock); + break; + } } EXPORT_SYMBOL_GPL(__hwspin_unlock); +/** + * 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) +{ + if (WARN_ON(!hwlock)) + return -EINVAL; + + if (!hwlock->bank->ops->bust) { + pr_err("bust operation not defined\n"); + return -EOPNOTSUPP; + } + + return hwlock->bank->ops->bust(hwlock, id); +} +EXPORT_SYMBOL_GPL(hwspin_lock_bust); + +/** + * 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) +{ + if (WARN_ON(hwlock_spec->args_count != 1)) + return -EINVAL; + + return hwlock_spec->args[0]; +} + +/** + * 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) +{ + struct of_phandle_args args; + struct hwspinlock *hwlock; + struct radix_tree_iter iter; + void **slot; + int id; + int ret; + + ret = of_parse_phandle_with_args(np, "hwlocks", "#hwlock-cells", index, + &args); + if (ret) + return ret; + + if (!of_device_is_available(args.np)) { + ret = -ENOENT; + goto out; + } + + /* Find the hwspinlock device: we need its base_id */ + ret = -EPROBE_DEFER; + rcu_read_lock(); + radix_tree_for_each_slot(slot, &hwspinlock_tree, &iter, 0) { + hwlock = radix_tree_deref_slot(slot); + if (unlikely(!hwlock)) + continue; + if (radix_tree_deref_retry(hwlock)) { + slot = radix_tree_iter_retry(&iter); + continue; + } + + if (device_match_of_node(hwlock->bank->dev, args.np)) { + ret = 0; + break; + } + } + rcu_read_unlock(); + if (ret < 0) + goto out; + + id = of_hwspin_lock_simple_xlate(&args); + if (id < 0 || id >= hwlock->bank->num_locks) { + ret = -EINVAL; + goto out; + } + id += hwlock->bank->base_id; + +out: + of_node_put(args.np); + return ret ? ret : id; +} +EXPORT_SYMBOL_GPL(of_hwspin_lock_get_id); + +/** + * 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) +{ + int index; + + if (!name) + return -EINVAL; + + index = of_property_match_string(np, "hwlock-names", name); + if (index < 0) + return index; + + return of_hwspin_lock_get_id(np, index); +} +EXPORT_SYMBOL_GPL(of_hwspin_lock_get_id_byname); + static int hwspin_lock_register_single(struct hwspinlock *hwlock, int id) { struct hwspinlock *tmp; @@ -320,7 +513,7 @@ out: * * Should be called from a process context (might sleep) * - * Returns 0 on success, or an appropriate error code on failure + * 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) @@ -368,7 +561,7 @@ EXPORT_SYMBOL_GPL(hwspin_lock_register); * * Should be called from a process context (might sleep) * - * Returns 0 on success, or an appropriate error code on failure + * Returns: %0 on success, or an appropriate error code on failure */ int hwspin_lock_unregister(struct hwspinlock_device *bank) { @@ -390,14 +583,97 @@ int hwspin_lock_unregister(struct hwspinlock_device *bank) } EXPORT_SYMBOL_GPL(hwspin_lock_unregister); +static void devm_hwspin_lock_unreg(struct device *dev, void *res) +{ + hwspin_lock_unregister(*(struct hwspinlock_device **)res); +} + +static int devm_hwspin_lock_device_match(struct device *dev, void *res, + void *data) +{ + struct hwspinlock_device **bank = res; + + if (WARN_ON(!bank || !*bank)) + return 0; + + return *bank == 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) +{ + int ret; + + ret = devres_release(dev, devm_hwspin_lock_unreg, + devm_hwspin_lock_device_match, bank); + WARN_ON(ret); + + return ret; +} +EXPORT_SYMBOL_GPL(devm_hwspin_lock_unregister); + +/** + * 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) +{ + struct hwspinlock_device **ptr; + int ret; + + ptr = devres_alloc(devm_hwspin_lock_unreg, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = hwspin_lock_register(bank, dev, ops, base_id, num_locks); + if (!ret) { + *ptr = bank; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return ret; +} +EXPORT_SYMBOL_GPL(devm_hwspin_lock_register); + /** * __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 + * 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) @@ -414,13 +690,15 @@ static int __hwspin_lock_request(struct hwspinlock *hwlock) /* notify PM core that power is now needed */ ret = pm_runtime_get_sync(dev); - if (ret < 0) { + if (ret < 0 && ret != -EACCES) { dev_err(dev, "%s: can't power on device\n", __func__); pm_runtime_put_noidle(dev); module_put(dev->driver->owner); return ret; } + ret = 0; + /* mark hwspinlock as used, should not fail */ tmp = radix_tree_tag_clear(&hwspinlock_tree, hwlock_to_id(hwlock), HWSPINLOCK_UNUSED); @@ -432,66 +710,6 @@ 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) -{ - if (!hwlock) { - pr_err("invalid hwlock\n"); - return -EINVAL; - } - - return hwlock_to_id(hwlock); -} -EXPORT_SYMBOL_GPL(hwspin_lock_get_id); - -/** - * 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) -{ - struct hwspinlock *hwlock; - int ret; - - mutex_lock(&hwspinlock_tree_lock); - - /* look for an unused lock */ - ret = radix_tree_gang_lookup_tag(&hwspinlock_tree, (void **)&hwlock, - 0, 1, HWSPINLOCK_UNUSED); - if (ret == 0) { - pr_warn("a free hwspinlock is not available\n"); - hwlock = NULL; - goto out; - } - - /* sanity check that should never fail */ - WARN_ON(ret > 1); - - /* mark as used and power up */ - ret = __hwspin_lock_request(hwlock); - if (ret < 0) - hwlock = NULL; - -out: - mutex_unlock(&hwspinlock_tree_lock); - return hwlock; -} -EXPORT_SYMBOL_GPL(hwspin_lock_request); - -/** * hwspin_lock_request_specific() - request for a specific hwspinlock * @id: index of the specific hwspinlock that is requested * @@ -502,7 +720,7 @@ EXPORT_SYMBOL_GPL(hwspin_lock_request); * * Should be called from a process context (might sleep) * - * Returns the address of the assigned hwspinlock, or NULL on error + * Returns: the address of the assigned hwspinlock, or %NULL on error */ struct hwspinlock *hwspin_lock_request_specific(unsigned int id) { @@ -546,11 +764,11 @@ EXPORT_SYMBOL_GPL(hwspin_lock_request_specific); * * This function mark @hwlock as free again. * Should only be called with an @hwlock that was retrieved from - * an earlier call to omap_hwspin_lock_request{_specific}. + * 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 + * Returns: %0 on success, or an appropriate error code on failure */ int hwspin_lock_free(struct hwspinlock *hwlock) { @@ -577,9 +795,7 @@ int hwspin_lock_free(struct hwspinlock *hwlock) } /* notify the underlying device that power is not needed */ - ret = pm_runtime_put(dev); - if (ret < 0) - goto out; + pm_runtime_put(dev); /* mark this hwspinlock as available */ tmp = radix_tree_tag_set(&hwspinlock_tree, hwlock_to_id(hwlock), @@ -596,6 +812,81 @@ out: } EXPORT_SYMBOL_GPL(hwspin_lock_free); -MODULE_LICENSE("GPL v2"); +static int devm_hwspin_lock_match(struct device *dev, void *res, void *data) +{ + struct hwspinlock **hwlock = res; + + if (WARN_ON(!hwlock || !*hwlock)) + return 0; + + return *hwlock == data; +} + +static void devm_hwspin_lock_release(struct device *dev, void *res) +{ + hwspin_lock_free(*(struct hwspinlock **)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) +{ + int ret; + + ret = devres_release(dev, devm_hwspin_lock_release, + devm_hwspin_lock_match, hwlock); + WARN_ON(ret); + + return ret; +} +EXPORT_SYMBOL_GPL(devm_hwspin_lock_free); + +/** + * 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) +{ + struct hwspinlock **ptr, *hwlock; + + ptr = devres_alloc(devm_hwspin_lock_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return NULL; + + hwlock = hwspin_lock_request_specific(id); + if (hwlock) { + *ptr = hwlock; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return hwlock; +} +EXPORT_SYMBOL_GPL(devm_hwspin_lock_request_specific); + MODULE_DESCRIPTION("Hardware spinlock interface"); MODULE_AUTHOR("Ohad Ben-Cohen <ohad@wizery.com>"); |
