diff options
author | Ashutosh Patel <ashutoshp@nvidia.com> | 2013-01-30 12:08:13 +0530 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2013-09-14 12:51:36 -0700 |
commit | 81054cdbcde969876f60ce6add1d68dcf9fa9c1e (patch) | |
tree | 9c1ffe827a79dd68e7b5677b672c44d1641f0167 /drivers/char | |
parent | c36bc7d5995c98087fa37c8893ee77f8b23f2424 (diff) |
chardev: Access control framework for Tegra gmi bus.
-provide exclusive access to gmi bus.
-provide priority based access.
-support Asynchronous & synchronous requests.
bug 1047323
Change-Id: I0a630a9120e5a2abd3b0ff6b1e3250005b1136e1
Signed-off-by: Nitin Sehgal <nsehgal@nvidia.com>
Signed-off-by: Ashutosh Patel <ashutoshp@nvidia.com>
Reviewed-on: http://git-master/r/189918
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
Diffstat (limited to 'drivers/char')
-rw-r--r-- | drivers/char/Kconfig | 8 | ||||
-rw-r--r-- | drivers/char/Makefile | 1 | ||||
-rw-r--r-- | drivers/char/tegra_gmi_access.c | 274 |
3 files changed, 283 insertions, 0 deletions
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 896601e27869..4392d9143b5c 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -629,6 +629,14 @@ config TEGRA_EFS help Enable tegra efs helper driver for Nvidia's board. +config TEGRA_GMI_ACCESS_CONTROL + bool "Provides access control functionality for multiple devices connected on tegra GMI bus." + depends on ARCH_TEGRA + ---help--- + This driver provides access control and priority support for + tegra GMI bus. Nvidia Embedded entertainment boards support multiple + devices connected on the same bus selectable using CS. + config TEGRA_GMI_CHAR bool "Character-device interface for tegra GMI bus" depends on ARCH_TEGRA diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 31b7e9df83d8..8b0415dd3870 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -65,3 +65,4 @@ js-rtc-y = rtc.o obj-$(CONFIG_TILE_SROM) += tile-srom.o obj-$(CONFIG_TEGRA_EFS) += tegra-efshlp.o obj-$(CONFIG_TEGRA_GMI_CHAR) += tegra_gmi_char.o +obj-$(CONFIG_TEGRA_GMI_ACCESS_CONTROL) += tegra_gmi_access.o diff --git a/drivers/char/tegra_gmi_access.c b/drivers/char/tegra_gmi_access.c new file mode 100644 index 000000000000..084b75df12e5 --- /dev/null +++ b/drivers/char/tegra_gmi_access.c @@ -0,0 +1,274 @@ +/* + * drivers/char/tegra_gmi_access.c + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + * + */ + +#include <linux/wait.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/semaphore.h> +#include <linux/rwlock_types.h> +#include <linux/io.h> +#include <linux/tegra_gmi_access.h> +#include <linux/proc_fs.h> +#include <linux/module.h> +#include <mach/iomap.h> +#include <linux/fs.h> +#include <linux/kthread.h> +#include <linux/module.h> +/*Data Structures*/ +#define GMI_MAX_PRIO 4 +#define GMI_MAX_DEVICES 6 +#define GMI_HANDLE_SIGNATURE 0xABABABAB + +static int gmi_init(void); + +struct gmi_device { + u32 signature; + char *dev_name; + u32 priority; + u32 dev_index; +}; + +/* Async Request Queue Access API's */ +struct gmi_async_req_queue_head_t { + spinlock_t lock; + struct list_head async_req_list; +}; + +struct gmi_async_req { + struct list_head async_req_list; + void *priv_data; + gasync_callbackp cb; +}; + +struct gmi_driver { + struct gmi_device *gdev_list[GMI_MAX_DEVICES]; + wait_queue_head_t waitq[GMI_MAX_PRIO]; + /* single queue and is the highest prio*/ + struct gmi_async_req_queue_head_t asyncq; + /* Thread to service async pending requests */ + struct task_struct *athread; + struct semaphore athread_lock; + u32 cur_index; + struct semaphore gbus_lock; + struct semaphore qprot; +}; + +struct gmi_driver *gdrv; + +static void enqueue_request(struct gmi_async_req_queue_head_t *q, + struct gmi_async_req *new) +{ + unsigned long flags; + spin_lock_irqsave(&q->lock, flags); + list_add_tail(&new->async_req_list, &q->async_req_list); + spin_unlock_irqrestore(&q->lock, flags); +} + +static struct gmi_async_req *dequeue_request( + struct gmi_async_req_queue_head_t *q) +{ + struct gmi_async_req *gasync_req = NULL; + unsigned long flags; + spin_lock_irqsave(&q->lock, flags); + if (!list_empty(&q->async_req_list)) { + gasync_req = list_first_entry(&q->async_req_list, + struct gmi_async_req, async_req_list); + list_del(&gasync_req->async_req_list); + } + spin_unlock_irqrestore(&q->lock, flags); + + return gasync_req; +} + +static int request_pending(struct gmi_async_req_queue_head_t *q) +{ + + unsigned long flags; + int ret; + spin_lock_irqsave(&q->lock, flags); + ret = list_empty(&q->async_req_list); + spin_unlock_irqrestore(&q->lock, flags); + return !ret; +} + + +static int async_handler_thread(void *data) +{ + while (1) { + struct gmi_async_req *areq; + + /* Blocked on lock, sleep */ + if (down_interruptible(&gdrv->athread_lock)) + continue; + /* When we reach here bus lock is already taken */ + BUG_ON(!down_trylock(&gdrv->gbus_lock)); + areq = dequeue_request(&gdrv->asyncq); + if (!areq) { + release_gmi_access(); + continue; + } + areq->cb(areq->priv_data); + kfree(areq); + release_gmi_access(); + } + /* unreachable */ + return 0; +} + +int enqueue_gmi_async_request(u32 gdev_handle, gasync_callbackp cb, void *pdata) +{ + struct gmi_async_req *areq; + + if ((*(u32 *)gdev_handle) != GMI_HANDLE_SIGNATURE) { + printk(KERN_ERR"\n Invalid Handle "); + return -1; + } + + if (cb == NULL) + return -1; + if (!gdrv) + BUG(); + + areq = kmalloc(sizeof(struct gmi_async_req), GFP_ATOMIC); + areq->cb = cb; + areq->priv_data = pdata; + INIT_LIST_HEAD(&areq->async_req_list); + enqueue_request(&gdrv->asyncq, areq); + + /* Unblock the thread bus lock granted */ + if (!down_trylock(&gdrv->gbus_lock)) + up(&gdrv->athread_lock); + + return 0; +} + +int request_gmi_access(u32 gdev_handle) +{ + struct gmi_device *gdev = (struct gmi_device *)gdev_handle; + + if ((*(u32 *)gdev_handle) != GMI_HANDLE_SIGNATURE) { + printk("\n Invalid Handle "); + return -1; + } + if (!gdrv || (gdev->priority >= GMI_MAX_PRIO) || + (gdev->dev_index >= GMI_MAX_DEVICES)) + BUG(); + + down(&gdrv->qprot); + + if (down_trylock(&gdrv->gbus_lock)) { + /* Bus already in use so block this request on the waitQueue */ + DEFINE_WAIT(wait_entry); + prepare_to_wait_exclusive(&gdrv->waitq[gdev->priority], + &wait_entry, TASK_UNINTERRUPTIBLE); + up(&gdrv->qprot); + + schedule(); + + finish_wait(&gdrv->waitq[gdev->priority], &wait_entry); + + /* When we reach here we would have the bus lock acquired */ + BUG_ON(!down_trylock(&gdrv->gbus_lock)); + return 0; + } else{ + up(&gdrv->qprot); + return 0; /* Got hold of the bus*/ + } +} +EXPORT_SYMBOL(request_gmi_access); + +void release_gmi_access(void) +{ + int i; + /* Check if any async request is pending serve it + and unblock the thread*/ + if (request_pending(&gdrv->asyncq)) { + up(&gdrv->athread_lock); + return; + } + down(&gdrv->qprot); + /* Wakeup one task the highest priority available */ + for (i = 0; i < GMI_MAX_PRIO; i++) { + if (waitqueue_active(&gdrv->waitq[i])) { + up(&gdrv->qprot); + wake_up(&gdrv->waitq[i]); + return; + } + } + + /*No task Waiting release the bus_lock */ + up(&gdrv->gbus_lock); + up(&gdrv->qprot); +} +EXPORT_SYMBOL(release_gmi_access); + +/* More than one device can register at same priority */ +u32 register_gmi_device(const char *devName, u32 priority) +{ + struct gmi_device *gdev = NULL; + + if (!gdrv) + gmi_init(); + + down(&gdrv->qprot); + + if (!gdrv || (priority >= GMI_MAX_PRIO) || + (gdrv->cur_index >= GMI_MAX_DEVICES)) { + up(&gdrv->qprot); + return (u32)NULL; + } + + gdev = kzalloc(sizeof(struct gmi_device), GFP_KERNEL); + + gdev->signature = GMI_HANDLE_SIGNATURE; + gdev->priority = priority; + /* Init devName :pending*/ + gdev->dev_index = gdrv->cur_index; + gdrv->gdev_list[gdrv->cur_index++] = gdev; + + up(&gdrv->qprot); + return (u32)gdev; +} +EXPORT_SYMBOL(register_gmi_device); + +static int gmi_init(void) +{ + int i; + gdrv = kzalloc(sizeof(*gdrv), GFP_KERNEL); + sema_init(&gdrv->gbus_lock, 1); + sema_init(&gdrv->athread_lock, 1); + down(&gdrv->athread_lock); + sema_init(&gdrv->qprot, 1); + spin_lock_init(&(gdrv->asyncq.lock)); + + INIT_LIST_HEAD(&gdrv->asyncq.async_req_list); + for (i = 0; i < GMI_MAX_PRIO; i++) + init_waitqueue_head(&gdrv->waitq[i]); + + gdrv->athread = kthread_create(async_handler_thread, 0 , "Athread"); + wake_up_process(gdrv->athread); + + return 0; +} + +MODULE_AUTHOR("Nitin Sehgal <nsehgal@nvidia.com>"); +MODULE_DESCRIPTION("Tegra GMI bus access control api's"); +MODULE_LICENSE("GPL"); |