Hi, We are introducing a kernel driver for hardware spinlock, called hwspinlock. It is designed to interface with the OMAP4 hardware spinlock module. This driver supports: - Reserved spinlocks for internal use - Dynamic allocation of unreserved locks - Lock, unlock, and trylock functions, with or without disabling irqs/preempt - Registered as a platform device driver The device initialization will set aside some spinlocks as reserved for special-purpose use. All other locks can be used by anyone. This is configurable using a Kconfig option. The device initialization file is: arch/arm/mach-omap2/hwspinlocks.c The driver takes in data passed in device initialization. The function hwspinlock_probe() initializes the array of spinlock structures, each containing a spinlock register address provided by the device initialization. The device driver file is: arch/arm/plat-omap/hwspinlock.c Here's an API summary: int hwspinlock_lock(struct hwspinlock *); Attempt to lock a hardware spinlock. If it is busy, the function will keep trying until it succeeds. This is a blocking function. int hwspinlock_trylock(struct hwspinlock *); Attempt to lock a hardware spinlock. If it is busy, the function will return BUSY. If it succeeds in locking, the function will return ACQUIRED. This is a non-blocking function int hwspinlock_unlock(struct hwspinlock *); Unlock a hardware spinlock. int hwspinlock_lock_irqsave(struct hwspinlock *, unsigned long *); Same as hwspinlock_lock, but disables interrupts and preemption int hwspinlock_trylock_irqsave(struct hwspinlock *, unsigned long *); Same as hwspinlock_trylock, but disables interrupts and preemption int hwspinlock_unlock_irqrestore(struct hwspinlock *, unsigned long); Same as hwspinlock_unlock, but restores interrupts and preemption struct hwspinlock *hwspinlock_request(void); Provides for "dynamic allocation" of an unreserved hardware spinlock. If no more locks are available, returns NULL. struct hwspinlock *hwspinlock_request_specific(unsigned int); Provides for "static allocation" of a reserved hardware spinlock. This allows the system to use a specific reserved lock, identified by an ID. If the ID is invalid or if the desired lock is already allocated, this will return NULL. int hwspinlock_free(struct hwspinlock *); Frees an allocated hardware spinlock (either reserved or unreserved). Please see the below patch contents, or the attached patch, and provide feedback. Signed-off-by: Simon Que <sque@xxxxxx> Cc: Hari Kanigeri <h-kanigeri2@xxxxxx> ===================================================================== diff --git a/arch/arm/mach-omap2/Kconfig b/arch/arm/mach-omap2/Kconfig index 9f73d79..a13c188 100644 --- a/arch/arm/mach-omap2/Kconfig +++ b/arch/arm/mach-omap2/Kconfig @@ -182,3 +182,13 @@ config OMAP3_SDRC_AC_TIMING wish to say no. Selecting yes without understanding what is going on could result in system crashes; +config OMAP_HWSPINLOCK_NUM_RESERVED + int "Number of hardware spinlocks reserved for system use" + depends on ARCH_OMAP + default 8 + range 0 32 + help + Choose a number of hardware spinlocks to reserve for internal use. + The rest will be unreserved and availble for general use. Make + that the number of reserved locks does not exceed the total number + available locks. diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile index 6725b3a..14af19a 100644 --- a/arch/arm/mach-omap2/Makefile +++ b/arch/arm/mach-omap2/Makefile @@ -170,3 +170,5 @@ obj-y += $(nand-m) $(nand-y) smc91x-$(CONFIG_SMC91X) := gpmc-smc91x.o obj-y += $(smc91x-m) $(smc91x-y) + +obj-y += hwspinlocks.o \ No newline at end of file diff --git a/arch/arm/mach-omap2/hwspinlocks.c b/arch/arm/mach-omap2/hwspinlocks.c new file mode 100644 index 0000000..de813a0 --- /dev/null +++ b/arch/arm/mach-omap2/hwspinlocks.c @@ -0,0 +1,126 @@ +/* + * OMAP hardware spinlock driver + * + * Copyright (C) 2010 Texas Instruments. All rights reserved. + * + * Contact: Simon Que <sque@xxxxxx> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/slab.h> + +#include <plat/hwspinlock.h> + +/* Base address of HW spinlock module */ +#define HWSPINLOCK_BASE (L4_44XX_BASE + 0xF6000) +#define HWSPINLOCK_REGADDR(reg) \ + OMAP2_L4_IO_ADDRESS(HWSPINLOCK_BASE + (reg)) + +/* Spinlock register offsets */ +#define HWSPINLOCK_REVISION 0x0000 +#define HWSPINLOCK_SYSCONFIG 0x0010 +#define HWSPINLOCK_SYSSTATUS 0x0014 +#define HWSPINLOCK_LOCK_BASE 0x0800 + +/* Spinlock register addresses */ +#define HWSPINLOCK_REVISION_REG \ + HWSPINLOCK_REGADDR(HWSPINLOCK_REVISION) +#define HWSPINLOCK_SYSCONFIG_REG \ + HWSPINLOCK_REGADDR(HWSPINLOCK_SYSCONFIG) +#define HWSPINLOCK_SYSSTATUS_REG \ + HWSPINLOCK_REGADDR(HWSPINLOCK_SYSSTATUS) +#define HWSPINLOCK_LOCK_REG(i) \ + HWSPINLOCK_REGADDR(HWSPINLOCK_LOCK_BASE + 0x4 * (i)) + +/* Spinlock count code */ +#define HWSPINLOCK_32_REGS 1 +#define HWSPINLOCK_64_REGS 2 +#define HWSPINLOCK_128_REGS 4 +#define HWSPINLOCK_256_REGS 8 +#define HWSPINLOCK_NUMLOCKS_OFFSET 24 + + +/* Initialization function */ +int __init hwspinlocks_init(void) +{ + int i; + int retval = 0; + + struct platform_device *pdev; + struct hwspinlock_plat_info *pdata; + void __iomem *base; + int num_locks; + bool is_reserved; + + /* Determine number of locks */ + switch (__raw_readl(HWSPINLOCK_SYSSTATUS_REG) >> + HWSPINLOCK_NUMLOCKS_OFFSET) { + case HWSPINLOCK_32_REGS: + num_locks = 32; + break; + case HWSPINLOCK_64_REGS: + num_locks = 64; + break; + case HWSPINLOCK_128_REGS: + num_locks = 128; + break; + case HWSPINLOCK_256_REGS: + num_locks = 256; + break; + default: + return -EINVAL; /* Invalid spinlock count code */ + } + + /* Device drivers */ + for (i = 0; i < num_locks; i++) { + pdev = platform_device_alloc("hwspinlock", i); + + base = HWSPINLOCK_LOCK_REG(i); /* Get register address */ + + /* Some locks are reserved for system use */ + if (i < CONFIG_OMAP_HWSPINLOCK_NUM_RESERVED) + is_reserved = true; + else + is_reserved = false; + + /* Pass data to device initialization */ + pdata = kzalloc(sizeof(struct hwspinlock_plat_info), + GFP_KERNEL); + pdata->num_locks = num_locks; + pdata->io_base = base; + pdata->is_reserved = is_reserved; + retval = platform_device_add_data(pdev, pdata, sizeof(*pdata)); + if (retval) + goto device_add_fail; + + retval = platform_device_add(pdev); + if (retval) + goto device_add_fail; + continue; +device_add_fail: + platform_device_put(pdev); + } + + return retval; +} +module_init(hwspinlocks_init); + diff --git a/arch/arm/plat-omap/Makefile b/arch/arm/plat-omap/Makefile index a37abf5..fb98ff9 100644 --- a/arch/arm/plat-omap/Makefile +++ b/arch/arm/plat-omap/Makefile @@ -32,4 +32,5 @@ obj-y += $(i2c-omap-m) $(i2c-omap-y) obj-$(CONFIG_OMAP_MBOX_FWK) += mailbox.o obj-$(CONFIG_OMAP_REMOTE_PROC) += remoteproc.o -obj-$(CONFIG_OMAP_PM_NOOP) += omap-pm-noop.o \ No newline at end of file +obj-$(CONFIG_OMAP_PM_NOOP) += omap-pm-noop.o +obj-y += hwspinlock.o \ No newline at end of file diff --git a/arch/arm/plat-omap/hwspinlock.c b/arch/arm/plat-omap/hwspinlock.c new file mode 100644 index 0000000..327a524 --- /dev/null +++ b/arch/arm/plat-omap/hwspinlock.c @@ -0,0 +1,331 @@ +/* + * OMAP hardware spinlock driver + * + * Copyright (C) 2010 Texas Instruments. All rights reserved. + * + * Contact: Simon Que <sque@xxxxxx> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#include <plat/hwspinlock.h> + +/* for managing a hardware spinlock module */ +struct hwspinlock_state { + bool is_init; /* For first-time initialization */ + int num_locks; /* Total number of locks in system */ + spinlock_t local_lock; /* Local protection */ +}; + +/* Points to the hardware spinlock module */ +static struct hwspinlock_state hwspinlock_state; +static struct hwspinlock_state *hwspinlock_module = &hwspinlock_state; + +/* Spinlock object */ +struct hwspinlock { + void __iomem *io_base; + bool is_reserved; + bool is_allocated; + struct platform_device *pdev; +}; + +/* Array of spinlocks */ +static struct hwspinlock *hwspinlocks; + +/* API functions */ + +/* Busy loop to acquire a spinlock */ +int hwspinlock_lock(struct hwspinlock *handle) +{ + int retval; + + if (WARN_ON(handle == NULL)) + return -EINVAL; + + if (WARN_ON(in_atomic() || in_irq())) + return -EPERM; + + /* Attempt to acquire the lock by reading from it */ + do { + retval = __raw_readl(handle->io_base); + } while (retval == HWSPINLOCK_BUSY); + + return 0; +} +EXPORT_SYMBOL(hwspinlock_lock); + +/* Attempt to acquire a spinlock once */ +int hwspinlock_trylock(struct hwspinlock *handle) +{ + int retval = 0; + + if (WARN_ON(handle == NULL)) + return -EINVAL; + + if (WARN_ON(in_atomic() || in_irq())) + return -EPERM; + + /* Attempt to acquire the lock by reading from it */ + retval = __raw_readl(handle->io_base); + + return retval; +} +EXPORT_SYMBOL(hwspinlock_trylock); + +/* Release a spinlock */ +int hwspinlock_unlock(struct hwspinlock *handle) +{ + if (WARN_ON(handle == NULL)) + return -EINVAL; + + /* Release it by writing 0 to it */ + __raw_writel(0, handle->io_base); + + return 0; +} +EXPORT_SYMBOL(hwspinlock_unlock); + +/* Busy loop to acquire a spinlock, disabling interrupts/preemption */ +int hwspinlock_lock_irqsave(struct hwspinlock *handle, unsigned long *flags) +{ + int retval; + unsigned long temp_flags; + + if (WARN_ON(handle == NULL)) + return -EINVAL; + + if (WARN_ON(in_atomic() || in_irq())) + return -EPERM; + + if (WARN_ON(flags == NULL)) + return -EINVAL; + + /* Attempt to acquire the lock by reading from it */ + do { + preempt_disable(); /* Disable preemption */ + local_irq_save(temp_flags); /* Disable interrupts */ + + retval = __raw_readl(handle->io_base); + + /* Restore interrupts and preemption if not successful */ + if (retval == HWSPINLOCK_BUSY) { + local_irq_restore(temp_flags); + preempt_enable(); + } + } while (retval == HWSPINLOCK_BUSY); + + *flags = temp_flags; /* Return IRQ state */ + + return 0; +} +EXPORT_SYMBOL(hwspinlock_lock_irqsave); + +/* Attempt to acquire a spinlock once, disabling interrupts/preemption */ +int hwspinlock_trylock_irqsave(struct hwspinlock *handle, unsigned long *flags) +{ + int retval = 0; + unsigned long temp_flags; + + if (WARN_ON(handle == NULL)) + return -EINVAL; + + if (WARN_ON(in_atomic() || in_irq())) + return -EPERM; + + if (WARN_ON(flags == NULL)) + return -EINVAL; + + preempt_disable(); /* Disable preemption */ + local_irq_save(temp_flags); /* Disable interrupts */ + + /* Attempt to acquire the lock by reading from it */ + retval = __raw_readl(handle->io_base); + + /* Restore interrupts and preemption if not successful */ + if (retval == HWSPINLOCK_BUSY) { + local_irq_restore(temp_flags); + preempt_enable(); + } else + *flags = temp_flags; /* Return IRQ state */ + + return retval; +} +EXPORT_SYMBOL(hwspinlock_trylock_irqsave); + +/* Unlock a spinlock that was locked with irq/preempt disabled */ +int hwspinlock_unlock_irqrestore(struct hwspinlock *handle, unsigned long + flags) +{ + if (WARN_ON(handle == NULL)) + return -EINVAL; + + /* Release it by writing 0 to it */ + __raw_writel(0, handle->io_base); + + /* Restore interrupts and preemption */ + local_irq_restore(flags); + preempt_enable(); + + return 0; +} +EXPORT_SYMBOL(hwspinlock_unlock_irqrestore); + +/* Request an unclaimed spinlock */ +struct hwspinlock *hwspinlock_request(void) +{ + int i; + bool found = false; + struct hwspinlock *handle = NULL; + unsigned long flags; + + spin_lock_irqsave(&hwspinlock_module->local_lock, flags); + /* Search for an unclaimed, unreserved lock */ + for (i = 0; i < hwspinlock_module->num_locks && !found; i++) { + if (!hwspinlocks[i].is_allocated && + !hwspinlocks[i].is_reserved) { + found = true; + handle = &hwspinlocks[i]; + } + } + spin_unlock_irqrestore(&hwspinlock_module->local_lock, flags); + + /* Return error if no more locks available */ + if (!found) + return NULL; + + handle->is_allocated = true; + + return handle; +} +EXPORT_SYMBOL(hwspinlock_request); + +/* Request an unclaimed spinlock by ID */ +struct hwspinlock *hwspinlock_request_specific(unsigned int id) +{ + struct hwspinlock *handle = NULL; + unsigned long flags; + + spin_lock_irqsave(&hwspinlock_module->local_lock, flags); + + if (WARN_ON(!hwspinlocks[id].is_reserved)) + goto exit; + + if (WARN_ON(hwspinlocks[id].is_allocated)) + goto exit; + + handle = &hwspinlocks[id]; + handle->is_allocated = true; + +exit: + spin_unlock_irqrestore(&hwspinlock_module->local_lock, flags); + return handle; +} +EXPORT_SYMBOL(hwspinlock_request_specific); + +/* Release a claimed spinlock */ +int hwspinlock_free(struct hwspinlock *handle) +{ + if (WARN_ON(handle == NULL)) + return -EINVAL; + + if (WARN_ON(!handle->is_allocated)) + return -ENOMEM; + + handle->is_allocated = false; + + return 0; +} +EXPORT_SYMBOL(hwspinlock_free); + +/* Probe function */ +static int __devinit hwspinlock_probe(struct platform_device *pdev) +{ + struct hwspinlock_plat_info *pdata = pdev->dev.platform_data; + int id = pdev->id; + + /* Set up the spinlock count and array */ + if (!hwspinlock_module->is_init) { + hwspinlock_module->num_locks = pdata->num_locks; + + /* Allocate spinlock device objects */ + hwspinlocks = kmalloc(sizeof(struct hwspinlock) * + hwspinlock_module->num_locks, GFP_KERNEL); + if (WARN_ON(hwspinlocks == NULL)) + return -ENOMEM; + + /* Initialize local lock */ + spin_lock_init(&hwspinlock_module->local_lock); + + /* Only do initialization once */ + hwspinlock_module->is_init = true; + } + + hwspinlocks[id].pdev = pdev; + + hwspinlocks[id].is_reserved = pdata->is_reserved; + hwspinlocks[id].is_allocated = false; + hwspinlocks[id].io_base = pdata->io_base; + + return 0; +} + +static struct platform_driver hwspinlock_driver = { + .probe = hwspinlock_probe, + .driver = { + .name = "hwspinlock", + }, +}; + +/* Initialization function */ +static int __init hwspinlock_init(void) +{ + int retval = 0; + + /* Register spinlock driver */ + retval = platform_driver_register(&hwspinlock_driver); + + /* Make sure the it was properly initialized */ + if (WARN_ON(!hwspinlock_module->is_init)) + return -EACCES; + + return retval; +} + +/* Cleanup function */ +static void __exit hwspinlock_exit(void) +{ + platform_driver_unregister(&hwspinlock_driver); + + /* Free spinlock device objects */ + if (hwspinlock_module->is_init) + kfree(hwspinlocks); +} + +module_init(hwspinlock_init); +module_exit(hwspinlock_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Hardware spinlock driver"); +MODULE_AUTHOR("Simon Que"); +MODULE_AUTHOR("Hari Kanigeri"); diff --git a/arch/arm/plat-omap/include/plat/hwspinlock.h b/arch/arm/plat-omap/include/plat/hwspinlock.h new file mode 100644 index 0000000..1cdf7a8 --- /dev/null +++ b/arch/arm/plat-omap/include/plat/hwspinlock.h @@ -0,0 +1,35 @@ +/* hwspinlock.h */ + +#ifndef HWSPINLOCK_H +#define HWSPINLOCK_H + +#include <linux/platform_device.h> +#include <plat/omap44xx.h> + +/* Read values from the spinlock register */ +#define HWSPINLOCK_ACQUIRED 0 +#define HWSPINLOCK_BUSY 1 + +/* Device data */ +struct hwspinlock_plat_info { + int num_locks; /* Number of locks (initialization) */ + void __iomem *io_base; /* Address of spinlock register */ + bool is_reserved; /* Reserved for system use? */ +}; + +struct hwspinlock; + +int hwspinlock_lock(struct hwspinlock *handle); +int hwspinlock_trylock(struct hwspinlock *handle); +int hwspinlock_unlock(struct hwspinlock *handle); + +int hwspinlock_lock_irqsave(struct hwspinlock *handle, unsigned long *flags); +int hwspinlock_trylock_irqsave(struct hwspinlock *handle, unsigned long *flags); +int hwspinlock_unlock_irqrestore(struct hwspinlock *handle, unsigned long + flags); + +struct hwspinlock *hwspinlock_request(void); +struct hwspinlock *hwspinlock_request_specific(unsigned int id); +int hwspinlock_free(struct hwspinlock *hwspinlock_ptr); + +#endif /* HWSPINLOCK_H */ -- 1.7.0
Attachment:
0001-omap-hwspinlock-Added-hwspinlock-driver.patch
Description: 0001-omap-hwspinlock-Added-hwspinlock-driver.patch