blob: f9af7fcb05f069fce754d3b901968fd15f45e35e [file] [log] [blame]
/*
* Copyright (C) 2010-2013 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(&notification->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(&notification->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 */
}