| /* Copyright (c) 2008-2009, 2011-2016 The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only 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. |
| * |
| */ |
| |
| #include <linux/err.h> |
| #include <linux/kernel.h> |
| #include <linux/string.h> |
| #include <linux/delay.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/msm_remote_spinlock.h> |
| #include <linux/slab.h> |
| |
| #include <soc/qcom/smem.h> |
| |
| /** |
| * The local processor (APPS) is PID 0, but because 0 is reserved for an empty |
| * lock, the value PID + 1 is used as the APSS token when writing to the lock. |
| */ |
| #define SPINLOCK_TOKEN_APPS 1 |
| |
| static int is_hw_lock_type; |
| static DEFINE_MUTEX(ops_init_lock); |
| |
| struct spinlock_ops { |
| void (*lock)(raw_remote_spinlock_t *lock); |
| void (*unlock)(raw_remote_spinlock_t *lock); |
| int (*trylock)(raw_remote_spinlock_t *lock); |
| int (*release)(raw_remote_spinlock_t *lock, uint32_t pid); |
| int (*owner)(raw_remote_spinlock_t *lock); |
| void (*lock_rlock_id)(raw_remote_spinlock_t *lock, uint32_t tid); |
| void (*unlock_rlock)(raw_remote_spinlock_t *lock); |
| int (*get_hw_spinlocks_element)(raw_remote_spinlock_t *lock); |
| }; |
| |
| static struct spinlock_ops current_ops; |
| |
| static int remote_spinlock_init_address(int id, _remote_spinlock_t *lock); |
| |
| /* ldrex implementation ----------------------------------------------------- */ |
| static char *ldrex_compatible_string = "qcom,ipc-spinlock-ldrex"; |
| |
| #ifdef CONFIG_ARM |
| static void __raw_remote_ex_spin_lock(raw_remote_spinlock_t *lock) |
| { |
| unsigned long tmp; |
| |
| __asm__ __volatile__( |
| "1: ldrex %0, [%1]\n" |
| " teq %0, #0\n" |
| " strexeq %0, %2, [%1]\n" |
| " teqeq %0, #0\n" |
| " bne 1b" |
| : "=&r" (tmp) |
| : "r" (&lock->lock), "r" (SPINLOCK_TOKEN_APPS) |
| : "cc"); |
| |
| /* |
| * Ensure the ordering of read/write operations to ensure the |
| * proper ownership of the lock during the lock/unlock operations |
| */ |
| smp_mb(); |
| } |
| |
| static int __raw_remote_ex_spin_trylock(raw_remote_spinlock_t *lock) |
| { |
| unsigned long tmp; |
| |
| __asm__ __volatile__( |
| " ldrex %0, [%1]\n" |
| " teq %0, #0\n" |
| " strexeq %0, %2, [%1]\n" |
| : "=&r" (tmp) |
| : "r" (&lock->lock), "r" (SPINLOCK_TOKEN_APPS) |
| : "cc"); |
| |
| if (tmp == 0) { |
| /* |
| * Ensure the ordering of read/write operations to ensure the |
| * proper ownership of the lock during the lock/unlock |
| * operations |
| */ |
| smp_mb(); |
| return 1; |
| } |
| return 0; |
| } |
| |
| static void __raw_remote_ex_spin_unlock(raw_remote_spinlock_t *lock) |
| { |
| int lock_owner; |
| |
| /* |
| * Ensure the ordering of read/write operations to ensure the |
| * proper ownership of the lock during the lock/unlock operations |
| */ |
| smp_mb(); |
| lock_owner = readl_relaxed(&lock->lock); |
| if (lock_owner != SPINLOCK_TOKEN_APPS) { |
| pr_err("%s: spinlock not owned by Apps (actual owner is %d)\n", |
| __func__, lock_owner); |
| } |
| |
| __asm__ __volatile__( |
| " str %1, [%0]\n" |
| : |
| : "r" (&lock->lock), "r" (0) |
| : "cc"); |
| } |
| #else |
| static void __raw_remote_ex_spin_lock(raw_remote_spinlock_t *lock) |
| { |
| } |
| |
| static int __raw_remote_ex_spin_trylock(raw_remote_spinlock_t *lock) |
| { |
| return 0; |
| } |
| |
| static void __raw_remote_ex_spin_unlock(raw_remote_spinlock_t *lock) |
| { |
| } |
| #endif /* CONFIG_ARM */ |
| /* end ldrex implementation ------------------------------------------------- */ |
| |
| /* sfpb implementation ------------------------------------------------------ */ |
| static uint32_t lock_count; |
| static phys_addr_t reg_base; |
| static uint32_t reg_size; |
| static uint32_t lock_offset; /* offset into the hardware block before lock 0 */ |
| static uint32_t lock_size; |
| |
| static void *hw_mutex_reg_base; |
| static DEFINE_MUTEX(hw_map_init_lock); |
| static int *hw_spinlocks; |
| |
| static char *sfpb_compatible_string = "qcom,ipc-spinlock-sfpb"; |
| |
| static int init_hw_mutex(struct device_node *node) |
| { |
| struct resource r; |
| int rc; |
| |
| rc = of_address_to_resource(node, 0, &r); |
| if (rc) |
| BUG(); |
| |
| rc = of_property_read_u32(node, "qcom,num-locks", &lock_count); |
| if (rc) |
| BUG(); |
| |
| reg_base = r.start; |
| reg_size = (uint32_t)(resource_size(&r)); |
| lock_offset = 0; |
| lock_size = reg_size / lock_count; |
| |
| return 0; |
| } |
| |
| static void find_and_init_hw_mutex(void) |
| { |
| struct device_node *node; |
| |
| node = of_find_compatible_node(NULL, NULL, sfpb_compatible_string); |
| BUG_ON(node == NULL); |
| init_hw_mutex(node); |
| hw_mutex_reg_base = ioremap(reg_base, reg_size); |
| BUG_ON(hw_mutex_reg_base == NULL); |
| hw_spinlocks = kcalloc(lock_count, sizeof(int), GFP_KERNEL); |
| BUG_ON(hw_spinlocks == NULL); |
| } |
| |
| static int remote_spinlock_init_address_hw(int id, _remote_spinlock_t *lock) |
| { |
| /* |
| * Optimistic locking. Init only needs to be done once by the first |
| * caller. After that, serializing inits between different callers |
| * is unnecessary. The second check after the lock ensures init |
| * wasn't previously completed by someone else before the lock could |
| * be grabbed. |
| */ |
| if (!hw_mutex_reg_base) { |
| mutex_lock(&hw_map_init_lock); |
| if (!hw_mutex_reg_base) |
| find_and_init_hw_mutex(); |
| mutex_unlock(&hw_map_init_lock); |
| } |
| |
| if (id >= lock_count) |
| return -EINVAL; |
| |
| *lock = hw_mutex_reg_base + lock_offset + id * lock_size; |
| return 0; |
| } |
| |
| static unsigned int remote_spinlock_get_lock_id(raw_remote_spinlock_t *lock) |
| { |
| unsigned int id; |
| |
| BUG_ON((uintptr_t)lock < (uintptr_t)hw_mutex_reg_base); |
| BUG_ON(((uintptr_t)lock - (uintptr_t)hw_mutex_reg_base) < lock_offset); |
| |
| id = (unsigned int)((uintptr_t)lock - (uintptr_t)hw_mutex_reg_base - |
| lock_offset) / lock_size; |
| BUG_ON(id >= lock_count); |
| return id; |
| } |
| |
| static void __raw_remote_sfpb_spin_lock(raw_remote_spinlock_t *lock) |
| { |
| int owner; |
| unsigned int id = remote_spinlock_get_lock_id(lock); |
| |
| /* |
| * Wait for other local processor task to release spinlock if it |
| * already has the remote spinlock locked. This can only happen in |
| * test cases since the local spinlock will prevent this when using the |
| * public APIs. |
| */ |
| while (readl_relaxed(lock) == SPINLOCK_TOKEN_APPS) |
| ; |
| |
| /* acquire remote spinlock */ |
| do { |
| writel_relaxed(SPINLOCK_TOKEN_APPS, lock); |
| /* |
| * Ensure the ordering of read/write operations to ensure the |
| * proper ownership of the lock during the lock/unlock |
| * operations |
| */ |
| smp_mb(); |
| owner = readl_relaxed(lock); |
| hw_spinlocks[id] = owner; |
| } while (owner != SPINLOCK_TOKEN_APPS); |
| } |
| |
| static int __raw_remote_sfpb_spin_trylock(raw_remote_spinlock_t *lock) |
| { |
| int owner; |
| unsigned int id = remote_spinlock_get_lock_id(lock); |
| /* |
| * If the local processor owns the spinlock, return failure. This can |
| * only happen in test cases since the local spinlock will prevent this |
| * when using the public APIs. |
| */ |
| if (readl_relaxed(lock) == SPINLOCK_TOKEN_APPS) |
| return 0; |
| |
| writel_relaxed(SPINLOCK_TOKEN_APPS, lock); |
| /* |
| * Ensure the ordering of read/write operations to ensure the |
| * proper ownership of the lock during the lock/unlock operations |
| */ |
| smp_mb(); |
| owner = readl_relaxed(lock); |
| hw_spinlocks[id] = owner; |
| return owner == SPINLOCK_TOKEN_APPS; |
| } |
| |
| static void __raw_remote_sfpb_spin_unlock(raw_remote_spinlock_t *lock) |
| { |
| int lock_owner; |
| |
| lock_owner = readl_relaxed(lock); |
| if (lock_owner != SPINLOCK_TOKEN_APPS) { |
| pr_err("%s: spinlock not owned by Apps (actual owner is %d)\n", |
| __func__, lock_owner); |
| } |
| |
| writel_relaxed(0, lock); |
| /* |
| * Ensure the ordering of read/write operations to ensure the |
| * proper ownership of the lock during the lock/unlock operations |
| */ |
| smp_mb(); |
| } |
| |
| static void __raw_remote_sfpb_spin_lock_rlock_id(raw_remote_spinlock_t *lock, |
| uint32_t tid) |
| { |
| if (unlikely(!tid)) { |
| pr_err("%s: unsupported rlock tid=0\n", __func__); |
| BUG(); |
| } |
| |
| do { |
| writel_relaxed(tid, lock); |
| /* |
| * Ensure the ordering of read/write operations to ensure the |
| * proper ownership of the lock during the lock/unlock |
| * operations |
| */ |
| smp_mb(); |
| } while (readl_relaxed(lock) != tid); |
| } |
| |
| static void __raw_remote_sfpb_spin_unlock_rlock(raw_remote_spinlock_t *lock) |
| { |
| writel_relaxed(0, lock); |
| /* |
| * Ensure the ordering of read/write operations to ensure the |
| * proper ownership of the lock during the lock/unlock operations |
| */ |
| smp_mb(); |
| } |
| |
| static int __raw_remote_sfpb_get_hw_spinlocks_element( |
| raw_remote_spinlock_t *lock) |
| { |
| return hw_spinlocks[remote_spinlock_get_lock_id(lock)]; |
| } |
| |
| /* end sfpb implementation -------------------------------------------------- */ |
| |
| /* common spinlock API ------------------------------------------------------ */ |
| /** |
| * Release spinlock if it is owned by @pid. |
| * |
| * This is only to be used for situations where the processor owning |
| * the spinlock has crashed and the spinlock must be released. |
| * |
| * @lock: lock structure |
| * @pid: processor ID of processor to release |
| */ |
| static int __raw_remote_gen_spin_release(raw_remote_spinlock_t *lock, |
| uint32_t pid) |
| { |
| int ret = 1; |
| |
| /* |
| * Since 0 is reserved for an empty lock and the PIDs start at 0, the |
| * value PID + 1 is written to the lock. |
| */ |
| if (readl_relaxed(&lock->lock) == (pid + 1)) { |
| writel_relaxed(0, &lock->lock); |
| /* |
| * Ensure the ordering of read/write operations to ensure the |
| * proper ownership of the lock during the lock/unlock |
| * operations |
| */ |
| wmb(); |
| ret = 0; |
| } |
| return ret; |
| } |
| |
| /** |
| * Return owner of the spinlock. |
| * |
| * @lock: pointer to lock structure |
| * @returns: >= 0 owned PID; < 0 for error case |
| * |
| * Used for testing. PID's are assumed to be 31 bits or less. |
| */ |
| static int __raw_remote_gen_spin_owner(raw_remote_spinlock_t *lock) |
| { |
| int owner; |
| |
| /* |
| * Ensure the ordering of read/write operations to ensure the |
| * proper ownership of the lock during the lock/unlock operations |
| */ |
| rmb(); |
| |
| owner = readl_relaxed(&lock->lock); |
| if (owner) |
| return owner - 1; |
| else |
| return -ENODEV; |
| } |
| |
| |
| static int dt_node_is_valid(const struct device_node *node) |
| { |
| const char *status; |
| int statlen; |
| |
| status = of_get_property(node, "status", &statlen); |
| if (status == NULL) |
| return 1; |
| |
| if (statlen > 0) { |
| if (!strcmp(status, "okay") || !strcmp(status, "ok")) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static void initialize_ops(void) |
| { |
| struct device_node *node; |
| |
| /* |
| * of_find_compatible_node() returns a valid pointer even if |
| * the status property is "disabled", so the validity needs |
| * to be checked |
| */ |
| node = of_find_compatible_node(NULL, NULL, sfpb_compatible_string); |
| if (node && dt_node_is_valid(node)) { |
| current_ops.lock = __raw_remote_sfpb_spin_lock; |
| current_ops.unlock = __raw_remote_sfpb_spin_unlock; |
| current_ops.trylock = __raw_remote_sfpb_spin_trylock; |
| current_ops.release = __raw_remote_gen_spin_release; |
| current_ops.owner = __raw_remote_gen_spin_owner; |
| current_ops.lock_rlock_id = |
| __raw_remote_sfpb_spin_lock_rlock_id; |
| current_ops.unlock_rlock = __raw_remote_sfpb_spin_unlock_rlock; |
| current_ops.get_hw_spinlocks_element = |
| __raw_remote_sfpb_get_hw_spinlocks_element; |
| is_hw_lock_type = 1; |
| return; |
| } |
| |
| node = of_find_compatible_node(NULL, NULL, ldrex_compatible_string); |
| if (node && dt_node_is_valid(node)) { |
| current_ops.lock = __raw_remote_ex_spin_lock; |
| current_ops.unlock = __raw_remote_ex_spin_unlock; |
| current_ops.trylock = __raw_remote_ex_spin_trylock; |
| current_ops.release = __raw_remote_gen_spin_release; |
| current_ops.owner = __raw_remote_gen_spin_owner; |
| is_hw_lock_type = 0; |
| return; |
| } |
| |
| current_ops.lock = __raw_remote_ex_spin_lock; |
| current_ops.unlock = __raw_remote_ex_spin_unlock; |
| current_ops.trylock = __raw_remote_ex_spin_trylock; |
| current_ops.release = __raw_remote_gen_spin_release; |
| current_ops.owner = __raw_remote_gen_spin_owner; |
| is_hw_lock_type = 0; |
| pr_warn("Falling back to LDREX remote spinlock implementation"); |
| } |
| |
| /** |
| * Release all spinlocks owned by @pid. |
| * |
| * This is only to be used for situations where the processor owning |
| * spinlocks has crashed and the spinlocks must be released. |
| * |
| * @pid - processor ID of processor to release |
| */ |
| static void remote_spin_release_all_locks(uint32_t pid, int count) |
| { |
| int n; |
| _remote_spinlock_t lock; |
| |
| if (pid >= REMOTE_SPINLOCK_NUM_PID) { |
| pr_err("%s: Unsupported PID %d\n", __func__, pid); |
| return; |
| } |
| |
| for (n = 0; n < count; ++n) { |
| if (remote_spinlock_init_address(n, &lock) == 0) |
| _remote_spin_release(&lock, pid); |
| } |
| } |
| |
| void _remote_spin_release_all(uint32_t pid) |
| { |
| remote_spin_release_all_locks(pid, lock_count); |
| } |
| |
| #define SMEM_SPINLOCK_COUNT 8 |
| #define SMEM_SPINLOCK_ARRAY_SIZE (SMEM_SPINLOCK_COUNT * sizeof(uint32_t)) |
| |
| static int remote_spinlock_init_address_smem(int id, _remote_spinlock_t *lock) |
| { |
| _remote_spinlock_t spinlock_start; |
| |
| if (id >= SMEM_SPINLOCK_COUNT) |
| return -EINVAL; |
| |
| spinlock_start = smem_find(SMEM_SPINLOCK_ARRAY, |
| SMEM_SPINLOCK_ARRAY_SIZE, |
| 0, |
| SMEM_ANY_HOST_FLAG); |
| if (spinlock_start == NULL) |
| return -ENXIO; |
| |
| *lock = spinlock_start + id; |
| |
| lock_count = SMEM_SPINLOCK_COUNT; |
| |
| return 0; |
| } |
| |
| static int remote_spinlock_init_address(int id, _remote_spinlock_t *lock) |
| { |
| if (is_hw_lock_type) |
| return remote_spinlock_init_address_hw(id, lock); |
| else |
| return remote_spinlock_init_address_smem(id, lock); |
| } |
| |
| int _remote_spin_lock_init(remote_spinlock_id_t id, _remote_spinlock_t *lock) |
| { |
| BUG_ON(id == NULL); |
| |
| /* |
| * Optimistic locking. Init only needs to be done once by the first |
| * caller. After that, serializing inits between different callers |
| * is unnecessary. The second check after the lock ensures init |
| * wasn't previously completed by someone else before the lock could |
| * be grabbed. |
| */ |
| if (!current_ops.lock) { |
| mutex_lock(&ops_init_lock); |
| if (!current_ops.lock) |
| initialize_ops(); |
| mutex_unlock(&ops_init_lock); |
| } |
| |
| if (id[0] == 'S' && id[1] == ':') { |
| /* Single-digit lock ID follows "S:" */ |
| BUG_ON(id[3] != '\0'); |
| |
| return remote_spinlock_init_address((((uint8_t)id[2])-'0'), |
| lock); |
| } else { |
| return -EINVAL; |
| } |
| } |
| |
| /* |
| * lock comes in as a pointer to a pointer to the lock location, so it must |
| * be dereferenced and casted to the right type for the actual lock |
| * implementation functions |
| */ |
| void _remote_spin_lock(_remote_spinlock_t *lock) |
| { |
| if (unlikely(!current_ops.lock)) |
| BUG(); |
| current_ops.lock((raw_remote_spinlock_t *)(*lock)); |
| } |
| EXPORT_SYMBOL(_remote_spin_lock); |
| |
| void _remote_spin_unlock(_remote_spinlock_t *lock) |
| { |
| if (unlikely(!current_ops.unlock)) |
| BUG(); |
| current_ops.unlock((raw_remote_spinlock_t *)(*lock)); |
| } |
| EXPORT_SYMBOL(_remote_spin_unlock); |
| |
| int _remote_spin_trylock(_remote_spinlock_t *lock) |
| { |
| if (unlikely(!current_ops.trylock)) |
| BUG(); |
| return current_ops.trylock((raw_remote_spinlock_t *)(*lock)); |
| } |
| EXPORT_SYMBOL(_remote_spin_trylock); |
| |
| int _remote_spin_release(_remote_spinlock_t *lock, uint32_t pid) |
| { |
| if (unlikely(!current_ops.release)) |
| BUG(); |
| return current_ops.release((raw_remote_spinlock_t *)(*lock), pid); |
| } |
| EXPORT_SYMBOL(_remote_spin_release); |
| |
| int _remote_spin_owner(_remote_spinlock_t *lock) |
| { |
| if (unlikely(!current_ops.owner)) |
| BUG(); |
| return current_ops.owner((raw_remote_spinlock_t *)(*lock)); |
| } |
| EXPORT_SYMBOL(_remote_spin_owner); |
| |
| void _remote_spin_lock_rlock_id(_remote_spinlock_t *lock, uint32_t tid) |
| { |
| if (unlikely(!current_ops.lock_rlock_id)) |
| BUG(); |
| current_ops.lock_rlock_id((raw_remote_spinlock_t *)(*lock), tid); |
| } |
| EXPORT_SYMBOL(_remote_spin_lock_rlock_id); |
| |
| void _remote_spin_unlock_rlock(_remote_spinlock_t *lock) |
| { |
| if (unlikely(!current_ops.unlock_rlock)) |
| BUG(); |
| current_ops.unlock_rlock((raw_remote_spinlock_t *)(*lock)); |
| } |
| EXPORT_SYMBOL(_remote_spin_unlock_rlock); |
| |
| int _remote_spin_get_hw_spinlocks_element(_remote_spinlock_t *lock) |
| { |
| return current_ops.get_hw_spinlocks_element( |
| (raw_remote_spinlock_t *)(*lock)); |
| } |
| EXPORT_SYMBOL(_remote_spin_get_hw_spinlocks_element); |
| |
| /* end common spinlock API -------------------------------------------------- */ |