123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602 |
- /* 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 -------------------------------------------------- */
|