blob: 75f6140ca0ccd4183cdd51a9263b4e0ee9a59311 [file] [log] [blame]
/* Copyright (c) 2008-2009, Code Aurora Forum. 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <mach/remote_spinlock.h>
#include "smd_private.h"
#define SMEM_SPINLOCK_COUNT 8
#define SMEM_SPINLOCK_ARRAY_SIZE (SMEM_SPINLOCK_COUNT * sizeof(uint32_t))
struct raw_remote_spinlock {
union {
volatile u32 lock;
struct {
volatile u8 self_lock;
volatile u8 other_lock;
volatile u8 next_yield;
u8 pad;
} dek;
};
};
static inline void __raw_remote_ex_spin_lock(struct raw_remote_spinlock *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" (1)
: "cc");
smp_mb();
}
static inline void __raw_remote_ex_spin_unlock(struct raw_remote_spinlock *lock)
{
smp_mb();
asm volatile (
" str %1, [%0]\n"
:
: "r" (&lock->lock), "r" (0)
: "cc");
}
static inline void __raw_remote_swp_spin_lock(struct raw_remote_spinlock *lock)
{
unsigned long tmp;
asm volatile (
"1: swp %0, %2, [%1]\n"
" teq %0, #0\n"
" bne 1b"
: "=&r" (tmp)
: "r" (&lock->lock), "r" (1)
: "cc");
smp_mb();
}
static inline void __raw_remote_swp_spin_unlock(struct raw_remote_spinlock *lock)
{
smp_mb();
asm volatile (
" str %1, [%0]"
:
: "r" (&lock->lock), "r" (0)
: "cc");
}
#define DEK_LOCK_REQUEST 1
#define DEK_LOCK_YIELD (!DEK_LOCK_REQUEST)
#define DEK_YIELD_TURN_SELF 0
static void __raw_remote_dek_spin_lock(struct raw_remote_spinlock *lock)
{
lock->dek.self_lock = DEK_LOCK_REQUEST;
while (lock->dek.other_lock) {
if (lock->dek.next_yield == DEK_YIELD_TURN_SELF)
lock->dek.self_lock = DEK_LOCK_YIELD;
while (lock->dek.other_lock)
;
lock->dek.self_lock = DEK_LOCK_REQUEST;
}
lock->dek.next_yield = DEK_YIELD_TURN_SELF;
smp_mb();
}
static void __raw_remote_dek_spin_unlock(struct raw_remote_spinlock *lock)
{
smp_mb();
lock->dek.self_lock = DEK_LOCK_YIELD;
}
#if defined(CONFIG_MSM_REMOTE_SPINLOCK_DEKKERS)
/* Use Dekker's algorithm when LDREX/STREX and SWP are unavailable for
* shared memory */
#define _raw_remote_spin_lock(lock) __raw_remote_dek_spin_lock(lock)
#define _raw_remote_spin_unlock(lock) __raw_remote_dek_spin_unlock(lock)
#elif defined(CONFIG_MSM_REMOTE_SPINLOCK_SWP)
/* Use SWP-based locks when LDREX/STREX are unavailable for shared memory. */
#define _raw_remote_spin_lock(lock) __raw_remote_swp_spin_lock(lock)
#define _raw_remote_spin_unlock(lock) __raw_remote_swp_spin_unlock(lock)
#else
/* Use LDREX/STREX for shared memory locking, when available */
#define _raw_remote_spin_lock(lock) __raw_remote_ex_spin_lock(lock)
#define _raw_remote_spin_unlock(lock) __raw_remote_ex_spin_unlock(lock)
#endif
void _remote_spin_lock(remote_spinlock_t *lock)
{
_raw_remote_spin_lock(lock->remote);
}
EXPORT_SYMBOL(_remote_spin_lock);
void _remote_spin_unlock(remote_spinlock_t *lock)
{
_raw_remote_spin_unlock(lock->remote);
}
EXPORT_SYMBOL(_remote_spin_unlock);
static int remote_spin_lock_smem_init(remote_spinlock_t *lock, int id)
{
void *start;
if (id >= SMEM_SPINLOCK_COUNT)
return -EINVAL;
start = smem_alloc(SMEM_SPINLOCK_ARRAY, SMEM_SPINLOCK_ARRAY_SIZE);
if (start == NULL)
return -ENXIO;
lock->remote =
(struct raw_remote_spinlock *)(start + id * sizeof(uint32_t));
return 0;
}
#define DAL_CHUNK_NAME_LENGTH 12
struct dal_chunk_header {
uint32_t size;
char name[DAL_CHUNK_NAME_LENGTH];
uint32_t lock;
uint32_t reserved;
uint32_t type;
uint32_t version;
};
static int remote_spin_lock_dal_init(remote_spinlock_t *lock, const char *name)
{
unsigned long start;
unsigned long end;
unsigned size;
struct dal_chunk_header *cur_hdr;
if (!name)
return -EINVAL;
start = (unsigned long)smem_item(SMEM_DAL_AREA, &size);
if (!start)
return -ENXIO;
end = start + size;
/* Find first chunk header */
cur_hdr = (struct dal_chunk_header *)ALIGN(start, 4096);
lock->remote = NULL;
while (((unsigned long)(cur_hdr + 1) <= end) && (cur_hdr->size != 0)) {
if (!strncmp(cur_hdr->name, name, DAL_CHUNK_NAME_LENGTH)) {
lock->remote =
(struct raw_remote_spinlock *)&cur_hdr->lock;
return 0;
}
cur_hdr = (void *)cur_hdr + cur_hdr->size;
}
pr_err("%s: DAL remote spin lock '%s' not found.\n", __func__, name);
return -EINVAL;
}
int _remote_spin_lock_init(remote_spinlock_t *lock, const char *name)
{
BUG_ON(name == NULL);
/* remote spinlocks can be one of two formats:
* D:<dal chunk name>
* S:<single digit smem lock id>
*/
if (!strncmp(name, "D:", 2)) {
return remote_spin_lock_dal_init(lock, &name[2]);
} else if (!strncmp(name, "S:", 2)) {
BUG_ON(name[3] != '\0');
return remote_spin_lock_smem_init(lock, (uint8_t)(name[2]-'0'));
}
return -EINVAL;
}
EXPORT_SYMBOL(_remote_spin_lock_init);