drivers: hwspinlock: add framework
[pandora-kernel.git] / Documentation / hwspinlock.txt
diff --git a/Documentation/hwspinlock.txt b/Documentation/hwspinlock.txt
new file mode 100644 (file)
index 0000000..7dcd1a4
--- /dev/null
@@ -0,0 +1,293 @@
+Hardware Spinlock Framework
+
+1. Introduction
+
+Hardware spinlock modules provide hardware assistance for synchronization
+and mutual exclusion between heterogeneous processors and those not operating
+under a single, shared operating system.
+
+For example, OMAP4 has dual Cortex-A9, dual Cortex-M3 and a C64x+ DSP,
+each of which is running a different Operating System (the master, A9,
+is usually running Linux and the slave processors, the M3 and the DSP,
+are running some flavor of RTOS).
+
+A generic hwspinlock framework allows platform-independent drivers to use
+the hwspinlock device in order to access data structures that are shared
+between remote processors, that otherwise have no alternative mechanism
+to accomplish synchronization and mutual exclusion operations.
+
+This is necessary, for example, for Inter-processor communications:
+on OMAP4, cpu-intensive multimedia tasks are offloaded by the host to the
+remote M3 and/or C64x+ slave processors (by an IPC subsystem called Syslink).
+
+To achieve fast message-based communications, a minimal kernel support
+is needed to deliver messages arriving from a remote processor to the
+appropriate user process.
+
+This communication is based on simple data structures that is shared between
+the remote processors, and access to it is synchronized using the hwspinlock
+module (remote processor directly places new messages in this shared data
+structure).
+
+A common hwspinlock interface makes it possible to have generic, platform-
+independent, drivers.
+
+2. User API
+
+  struct hwspinlock *hwspin_lock_request(void);
+   - dynamically assign an hwspinlock and return its address, or NULL
+     in case an unused hwspinlock isn't available. Users of this
+     API will usually want to communicate the lock's id to the remote core
+     before it can be used to achieve synchronization.
+     Can be called from an atomic context (this function will not sleep) but
+     not from within interrupt context.
+
+  struct hwspinlock *hwspin_lock_request_specific(unsigned int id);
+   - assign a specific hwspinlock id and return its address, or NULL
+     if that hwspinlock is already in use. Usually board code will
+     be calling this function in order to reserve specific hwspinlock
+     ids for predefined purposes.
+     Can be called from an atomic context (this function will not sleep) but
+     not from within interrupt context.
+
+  int hwspin_lock_free(struct hwspinlock *hwlock);
+   - free a previously-assigned hwspinlock; returns 0 on success, or an
+     appropriate error code on failure (e.g. -EINVAL if the hwspinlock
+     is already free).
+     Can be called from an atomic context (this function will not sleep) but
+     not from within interrupt context.
+
+  int hwspin_lock_timeout(struct hwspinlock *hwlock, unsigned int timeout);
+   - lock a previously-assigned hwspinlock with a timeout limit (specified in
+     msecs). If the hwspinlock is already taken, the function will busy loop
+     waiting for it to be released, but give up when the timeout elapses.
+     Upon a successful return from this function, preemption is disabled so
+     the caller must not sleep, and is advised to release the hwspinlock as
+     soon as possible, in order to minimize remote cores polling on the
+     hardware interconnect.
+     Returns 0 when successful and an appropriate error code otherwise (most
+     notably -ETIMEDOUT if the hwspinlock is still busy after timeout msecs).
+     The function will never sleep.
+
+  int hwspin_lock_timeout_irq(struct hwspinlock *hwlock, unsigned int timeout);
+   - lock a previously-assigned hwspinlock with a timeout limit (specified in
+     msecs). If the hwspinlock is already taken, the function will busy loop
+     waiting for it to be released, but give up when the timeout elapses.
+     Upon a successful return from this function, preemption and the local
+     interrupts are disabled, so the caller must not sleep, and is advised to
+     release the hwspinlock as soon as possible.
+     Returns 0 when successful and an appropriate error code otherwise (most
+     notably -ETIMEDOUT if the hwspinlock is still busy after timeout msecs).
+     The function will never sleep.
+
+  int hwspin_lock_timeout_irqsave(struct hwspinlock *hwlock, unsigned int to,
+                                                       unsigned long *flags);
+   - lock a previously-assigned hwspinlock with a timeout limit (specified in
+     msecs). If the hwspinlock is already taken, the function will busy loop
+     waiting for it to be released, but give up when the timeout elapses.
+     Upon a successful return from this function, preemption is disabled,
+     local interrupts are disabled and their previous state is saved at the
+     given flags placeholder. The caller must not sleep, and is advised to
+     release the hwspinlock as soon as possible.
+     Returns 0 when successful and an appropriate error code otherwise (most
+     notably -ETIMEDOUT if the hwspinlock is still busy after timeout msecs).
+     The function will never sleep.
+
+  int hwspin_trylock(struct hwspinlock *hwlock);
+   - attempt to lock a previously-assigned hwspinlock, but immediately fail if
+     it is already taken.
+     Upon a successful return from this function, preemption is disabled so
+     caller must not sleep, and is advised to release the hwspinlock as soon as
+     possible, in order to minimize remote cores polling on the hardware
+     interconnect.
+     Returns 0 on success and an appropriate error code otherwise (most
+     notably -EBUSY if the hwspinlock was already taken).
+     The function will never sleep.
+
+  int hwspin_trylock_irq(struct hwspinlock *hwlock);
+   - attempt to lock a previously-assigned hwspinlock, but immediately fail if
+     it is already taken.
+     Upon a successful return from this function, preemption and the local
+     interrupts are disabled so caller must not sleep, and is advised to
+     release the hwspinlock as soon as possible.
+     Returns 0 on success and an appropriate error code otherwise (most
+     notably -EBUSY if the hwspinlock was already taken).
+     The function will never sleep.
+
+  int hwspin_trylock_irqsave(struct hwspinlock *hwlock, unsigned long *flags);
+   - attempt to lock a previously-assigned hwspinlock, but immediately fail if
+     it is already taken.
+     Upon a successful return from this function, preemption is disabled,
+     the local interrupts are disabled and their previous state is saved
+     at the given flags placeholder. The caller must not sleep, and is advised
+     to release the hwspinlock as soon as possible.
+     Returns 0 on success and an appropriate error code otherwise (most
+     notably -EBUSY if the hwspinlock was already taken).
+     The function will never sleep.
+
+  void hwspin_unlock(struct hwspinlock *hwlock);
+   - unlock a previously-locked hwspinlock. Always succeed, and can be called
+     from any context (the function never sleeps). Note: code should _never_
+     unlock an hwspinlock which is already unlocked (there is no protection
+     against this).
+
+  void hwspin_unlock_irq(struct hwspinlock *hwlock);
+   - unlock a previously-locked hwspinlock and enable local interrupts.
+     The caller should _never_ unlock an hwspinlock which is already unlocked.
+     Doing so is considered a bug (there is no protection against this).
+     Upon a successful return from this function, preemption and local
+     interrupts are enabled. This function will never sleep.
+
+  void
+  hwspin_unlock_irqrestore(struct hwspinlock *hwlock, unsigned long *flags);
+   - unlock a previously-locked hwspinlock.
+     The caller should _never_ unlock an hwspinlock which is already unlocked.
+     Doing so is considered a bug (there is no protection against this).
+     Upon a successful return from this function, preemption is reenabled,
+     and the state of the local interrupts is restored to the state saved at
+     the given flags. This function will never sleep.
+
+  int hwspin_lock_get_id(struct hwspinlock *hwlock);
+   - retrieve id number of a given hwspinlock. This is needed when an
+     hwspinlock is dynamically assigned: before it can be used to achieve
+     mutual exclusion with a remote cpu, the id number should be communicated
+     to the remote task with which we want to synchronize.
+     Returns the hwspinlock id number, or -EINVAL if hwlock is null.
+
+3. Typical usage
+
+#include <linux/hwspinlock.h>
+#include <linux/err.h>
+
+int hwspinlock_example1(void)
+{
+       struct hwspinlock *hwlock;
+       int ret;
+
+       /* dynamically assign a hwspinlock */
+       hwlock = hwspin_lock_request();
+       if (!hwlock)
+               ...
+
+       id = hwspin_lock_get_id(hwlock);
+       /* probably need to communicate id to a remote processor now */
+
+       /* take the lock, spin for 1 sec if it's already taken */
+       ret = hwspin_lock_timeout(hwlock, 1000);
+       if (ret)
+               ...
+
+       /*
+        * we took the lock, do our thing now, but do NOT sleep
+        */
+
+       /* release the lock */
+       hwspin_unlock(hwlock);
+
+       /* free the lock */
+       ret = hwspin_lock_free(hwlock);
+       if (ret)
+               ...
+
+       return ret;
+}
+
+int hwspinlock_example2(void)
+{
+       struct hwspinlock *hwlock;
+       int ret;
+
+       /*
+        * assign a specific hwspinlock id - this should be called early
+        * by board init code.
+        */
+       hwlock = hwspin_lock_request_specific(PREDEFINED_LOCK_ID);
+       if (!hwlock)
+               ...
+
+       /* try to take it, but don't spin on it */
+       ret = hwspin_trylock(hwlock);
+       if (!ret) {
+               pr_info("lock is already taken\n");
+               return -EBUSY;
+       }
+
+       /*
+        * we took the lock, do our thing now, but do NOT sleep
+        */
+
+       /* release the lock */
+       hwspin_unlock(hwlock);
+
+       /* free the lock */
+       ret = hwspin_lock_free(hwlock);
+       if (ret)
+               ...
+
+       return ret;
+}
+
+
+4. API for implementors
+
+  int hwspin_lock_register(struct hwspinlock *hwlock);
+   - to be called from the underlying platform-specific implementation, in
+     order to register a new hwspinlock instance. Can be called from an atomic
+     context (this function will not sleep) but not from within interrupt
+     context. Returns 0 on success, or appropriate error code on failure.
+
+  struct hwspinlock *hwspin_lock_unregister(unsigned int id);
+   - to be called from the underlying vendor-specific implementation, in order
+     to unregister an existing (and unused) hwspinlock instance.
+     Can be called from an atomic context (will not sleep) but not from
+     within interrupt context.
+     Returns the address of hwspinlock on success, or NULL on error (e.g.
+     if the hwspinlock is sill in use).
+
+5. struct hwspinlock
+
+This struct represents an hwspinlock instance. It is registered by the
+underlying hwspinlock implementation using the hwspin_lock_register() API.
+
+/**
+ * struct hwspinlock - vendor-specific hwspinlock implementation
+ *
+ * @dev: underlying device, will be used with runtime PM api
+ * @ops: vendor-specific hwspinlock handlers
+ * @id: a global, unique, system-wide, index of the lock.
+ * @lock: initialized and used by hwspinlock core
+ * @owner: underlying implementation module, used to maintain module ref count
+ */
+struct hwspinlock {
+       struct device *dev;
+       const struct hwspinlock_ops *ops;
+       int id;
+       spinlock_t lock;
+       struct module *owner;
+};
+
+The underlying implementation is responsible to assign the dev, ops, id and
+owner members. The lock member, OTOH, is initialized and used by the hwspinlock
+core.
+
+6. Implementation callbacks
+
+There are three possible callbacks defined in 'struct hwspinlock_ops':
+
+struct hwspinlock_ops {
+       int (*trylock)(struct hwspinlock *lock);
+       void (*unlock)(struct hwspinlock *lock);
+       void (*relax)(struct hwspinlock *lock);
+};
+
+The first two callbacks are mandatory:
+
+The ->trylock() callback should make a single attempt to take the lock, and
+return 0 on failure and 1 on success. This callback may _not_ sleep.
+
+The ->unlock() callback releases the lock. It always succeed, and it, too,
+may _not_ sleep.
+
+The ->relax() callback is optional. It is called by hwspinlock core while
+spinning on a lock, and can be used by the underlying implementation to force
+a delay between two successive invocations of ->trylock(). It may _not_ sleep.