/* Copyright (c) 2008-2009, 2011-2015 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 #include #include #include #include #include #include #include #include /** * 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"); 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) { smp_mb(); return 1; } return 0; } static void __raw_remote_ex_spin_unlock(raw_remote_spinlock_t *lock) { int lock_owner; 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 = kzalloc(sizeof(int) * lock_count, 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); 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); 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); 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); smp_mb(); } while (readl_relaxed(lock) != tid); } static void __raw_remote_sfpb_spin_unlock_rlock(raw_remote_spinlock_t *lock) { writel_relaxed(0, lock); 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); 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; 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 -------------------------------------------------- */