| /* |
| * Copyright (C) 2010-2014, 2016 ARM Limited. All rights reserved. |
| * |
| * This program is free software and is provided to you under the terms of the GNU General Public License version 2 |
| * as published by the Free Software Foundation, and any use by you of this program is subject to the terms of such GNU licence. |
| * |
| * A copy of the licence is included with the program, and can also be obtained from Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| */ |
| |
| /** |
| * @file mali_osk_notification.c |
| * Implementation of the OS abstraction layer for the kernel device driver |
| */ |
| |
| #include "mali_osk.h" |
| #include "mali_kernel_common.h" |
| |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| |
| /** |
| * Declaration of the notification queue object type |
| * Contains a linked list of notification pending delivery to user space. |
| * It also contains a wait queue of exclusive waiters blocked in the ioctl |
| * When a new notification is posted a single thread is resumed. |
| */ |
| struct _mali_osk_notification_queue_t_struct { |
| spinlock_t mutex; /**< Mutex protecting the list */ |
| wait_queue_head_t receive_queue; /**< Threads waiting for new entries to the queue */ |
| struct list_head head; /**< List of notifications waiting to be picked up */ |
| }; |
| |
| typedef struct _mali_osk_notification_wrapper_t_struct { |
| struct list_head list; /**< Internal linked list variable */ |
| _mali_osk_notification_t data; /**< Notification data */ |
| } _mali_osk_notification_wrapper_t; |
| |
| _mali_osk_notification_queue_t *_mali_osk_notification_queue_init(void) |
| { |
| _mali_osk_notification_queue_t *result; |
| |
| result = (_mali_osk_notification_queue_t *)kmalloc(sizeof(_mali_osk_notification_queue_t), GFP_KERNEL); |
| if (NULL == result) return NULL; |
| |
| spin_lock_init(&result->mutex); |
| init_waitqueue_head(&result->receive_queue); |
| INIT_LIST_HEAD(&result->head); |
| |
| return result; |
| } |
| |
| _mali_osk_notification_t *_mali_osk_notification_create(u32 type, u32 size) |
| { |
| /* OPT Recycling of notification objects */ |
| _mali_osk_notification_wrapper_t *notification; |
| |
| notification = (_mali_osk_notification_wrapper_t *)kmalloc(sizeof(_mali_osk_notification_wrapper_t) + size, |
| GFP_KERNEL | __GFP_HIGH | __GFP_REPEAT); |
| if (NULL == notification) { |
| MALI_DEBUG_PRINT(1, ("Failed to create a notification object\n")); |
| return NULL; |
| } |
| |
| /* Init the list */ |
| INIT_LIST_HEAD(¬ification->list); |
| |
| if (0 != size) { |
| notification->data.result_buffer = ((u8 *)notification) + sizeof(_mali_osk_notification_wrapper_t); |
| } else { |
| notification->data.result_buffer = NULL; |
| } |
| |
| /* set up the non-allocating fields */ |
| notification->data.notification_type = type; |
| notification->data.result_buffer_size = size; |
| |
| /* all ok */ |
| return &(notification->data); |
| } |
| |
| void _mali_osk_notification_delete(_mali_osk_notification_t *object) |
| { |
| _mali_osk_notification_wrapper_t *notification; |
| MALI_DEBUG_ASSERT_POINTER(object); |
| |
| notification = container_of(object, _mali_osk_notification_wrapper_t, data); |
| |
| /* Free the container */ |
| kfree(notification); |
| } |
| |
| void _mali_osk_notification_queue_term(_mali_osk_notification_queue_t *queue) |
| { |
| _mali_osk_notification_t *result; |
| MALI_DEBUG_ASSERT_POINTER(queue); |
| |
| while (_MALI_OSK_ERR_OK == _mali_osk_notification_queue_dequeue(queue, &result)) { |
| _mali_osk_notification_delete(result); |
| } |
| |
| /* not much to do, just free the memory */ |
| kfree(queue); |
| } |
| void _mali_osk_notification_queue_send(_mali_osk_notification_queue_t *queue, _mali_osk_notification_t *object) |
| { |
| #if defined(MALI_UPPER_HALF_SCHEDULING) |
| unsigned long irq_flags; |
| #endif |
| |
| _mali_osk_notification_wrapper_t *notification; |
| MALI_DEBUG_ASSERT_POINTER(queue); |
| MALI_DEBUG_ASSERT_POINTER(object); |
| |
| notification = container_of(object, _mali_osk_notification_wrapper_t, data); |
| |
| #if defined(MALI_UPPER_HALF_SCHEDULING) |
| spin_lock_irqsave(&queue->mutex, irq_flags); |
| #else |
| spin_lock(&queue->mutex); |
| #endif |
| |
| list_add_tail(¬ification->list, &queue->head); |
| |
| #if defined(MALI_UPPER_HALF_SCHEDULING) |
| spin_unlock_irqrestore(&queue->mutex, irq_flags); |
| #else |
| spin_unlock(&queue->mutex); |
| #endif |
| |
| /* and wake up one possible exclusive waiter */ |
| wake_up(&queue->receive_queue); |
| } |
| |
| _mali_osk_errcode_t _mali_osk_notification_queue_dequeue(_mali_osk_notification_queue_t *queue, _mali_osk_notification_t **result) |
| { |
| #if defined(MALI_UPPER_HALF_SCHEDULING) |
| unsigned long irq_flags; |
| #endif |
| |
| _mali_osk_errcode_t ret = _MALI_OSK_ERR_ITEM_NOT_FOUND; |
| _mali_osk_notification_wrapper_t *wrapper_object; |
| |
| #if defined(MALI_UPPER_HALF_SCHEDULING) |
| spin_lock_irqsave(&queue->mutex, irq_flags); |
| #else |
| spin_lock(&queue->mutex); |
| #endif |
| |
| if (!list_empty(&queue->head)) { |
| wrapper_object = list_entry(queue->head.next, _mali_osk_notification_wrapper_t, list); |
| *result = &(wrapper_object->data); |
| list_del_init(&wrapper_object->list); |
| ret = _MALI_OSK_ERR_OK; |
| } |
| |
| #if defined(MALI_UPPER_HALF_SCHEDULING) |
| spin_unlock_irqrestore(&queue->mutex, irq_flags); |
| #else |
| spin_unlock(&queue->mutex); |
| #endif |
| |
| return ret; |
| } |
| |
| _mali_osk_errcode_t _mali_osk_notification_queue_receive(_mali_osk_notification_queue_t *queue, _mali_osk_notification_t **result) |
| { |
| /* check input */ |
| MALI_DEBUG_ASSERT_POINTER(queue); |
| MALI_DEBUG_ASSERT_POINTER(result); |
| |
| /* default result */ |
| *result = NULL; |
| |
| if (wait_event_interruptible(queue->receive_queue, |
| _MALI_OSK_ERR_OK == _mali_osk_notification_queue_dequeue(queue, result))) { |
| return _MALI_OSK_ERR_RESTARTSYSCALL; |
| } |
| |
| return _MALI_OSK_ERR_OK; /* all ok */ |
| } |