/****************************************************************************
 *  License : All rights reserved for TES Electronic Solutions GmbH
 *            See included /docs/license.txt for details
 *  Project : D/AVE HD
 *  Purpose : Kernel callgate for user mode (threaded)
 ****************************************************************************
 * Version Control Information :
 *  $Revision: 11006 $
 *  $Date: 2016-11-07 17:32:44 +0100 (Mo, 07. Nov 2016) $
 *  $LastChangedBy: michael.golczewski $
 ****************************************************************************
 * Change History (autogenerated):
 ****************************************************************************/

#include "davehd_kernel_driver.h"
#include "davehd_kernel_bridge.h"
#include "davehd_kernel_schedule.h"
#include "davehd_kernel_client.h"
#include "r_typedefs.h"


#include "r_os_api.h"

/* QUEUE_SIZE must be power of two */
#define QUEUE_SIZE 64u

/* QUEUE_MASK is bit mask and depends on QUEUE_SIZE */
#define QUEUE_MASK (QUEUE_SIZE - 1u)  

/* Number of clients that can use bridge concurrently */
#define DHD_MAX_CONCURRENT_CLIENTS 64



/* message queue to store pending messages */

typedef struct dhd_message_s {
  dhd_gpu_call_entry_t function;        /* function to call */  
  dhd_gpu_call_data_t *parameters;      /* function parameters */
  R_OS_Sem_t          *response;        /* semaphore to signal when response is available */
} dhd_message_t;

typedef struct queue_s {
  dhd_message_t *elements[QUEUE_SIZE];  /* array holding currently waiting messages */
  dhd_uint8_t first;                    /* index to first element in queue */
  dhd_uint8_t last;                     /* index to element following the last element in queue */ 
  dhd_uint8_t count;                    /* number of elements currently waiting in queue */  
} queue_t;

static queue_t loc_queue;

/* one set of heap data is neccessary for dhd_gpu_trigger() */
static dhd_message_t loc_msg;
static dhd_gpu_call_data_t loc_call_data;

static R_OS_ThreadMutex_t loc_queue_check_mutex;        /* mutex to lock concurrent check of queue full state between multiple clients */
static R_OS_ThreadMutex_t loc_queue_change_mutex;       /* mutex to lock concurrent modification of queue between client and server */
static R_OS_Sem_t         loc_queue_message_sem;        /* event on new message in queue */
static R_OS_Sem_t         loc_queue_full_sem;           /* allows to wait in case the queue is full */

/* Array to store client semaphores */
static R_OS_Sem_t loc_client_sem[DHD_MAX_CONCURRENT_CLIENTS];

/* Array to store information about which semaphores are in use. 0 - free, 1 - in use */
static dhd_int8_t loc_client_active[DHD_MAX_CONCURRENT_CLIENTS] = {0};



/*----------------------------------------------------------------------------------------------------------
 * Push messages onto the message queue
 */
static void loc_queue_push(dhd_message_t* element);
static void loc_queue_push(dhd_message_t* element) {
  dhd_bool_t queue_full = DHD_FALSE;

  /* lock out other clients*/
  R_OS_ThreadMutexLock(&loc_queue_check_mutex);  

  /* lock out server (kernel)*/
  R_OS_ThreadMutexLock(&loc_queue_change_mutex);

  /* put message into queue*/
  loc_queue.elements[loc_queue.last] = element;
  ++loc_queue.last;
  loc_queue.last = (QUEUE_MASK & loc_queue.last);
  ++loc_queue.count;
  
  /* check if queue is full now -> need to check this before unlocking!*/
  if (loc_queue.count == QUEUE_SIZE) {
    queue_full = DHD_TRUE;  
  }  
  
  /* unlock now, so server may read from queue again*/
  R_OS_ThreadMutexUnlock(&loc_queue_change_mutex);
    
  /* notify the server about the message*/
  R_OS_SemPost(&loc_queue_message_sem);

  /* wait for signal from server when queue is full, before allowing other clients*/
  /* -> lock out clients when queue is full*/
  if (queue_full == DHD_TRUE) {
    R_OS_SemWait(&loc_queue_full_sem);  
  }
  
  R_OS_ThreadMutexUnlock(&loc_queue_check_mutex);
}


/*----------------------------------------------------------------------------------------------------------
 * Pop messages from the message queue
 */
static dhd_message_t* loc_queue_pop(void);
static dhd_message_t* loc_queue_pop(void) {
  dhd_message_t* result;
  dhd_bool_t queue_full = DHD_FALSE;

  /* wait for a new message*/
  R_OS_SemWait(&loc_queue_message_sem);
  
  /* lock out clients while we modify the queue*/
  R_OS_ThreadMutexLock(&loc_queue_change_mutex);
  
  /* note when we are now removing a message from a full queue*/
  if (loc_queue.count == QUEUE_SIZE) {
    queue_full = DHD_TRUE;
  }
  
  /* remove the message*/
  result = loc_queue.elements[loc_queue.first];
  ++loc_queue.first;
  loc_queue.first = (QUEUE_MASK & loc_queue.first);
  --loc_queue.count;
  
  R_OS_ThreadMutexUnlock(&loc_queue_change_mutex); 

  if (queue_full == DHD_TRUE) {
    /* we have just read one element from a previously full queue -> one client will be waiting, so release it*/
    R_OS_SemPost(&loc_queue_full_sem);
  }
 
  return result;
}


/*----------------------------------------------------------------------------------------------------------
 */
void dhd_gpu_bridge_wakeup(dhd_bridge_wakeup_token a_token) {
  R_OS_Sem_t* response = (R_OS_Sem_t*) a_token;
  R_OS_SemPost(response);
}


/*----------------------------------------------------------------------------------------------------------
 * Dispatches commands from clients
 */
void dhd_gpu_dispatch_loop(void) {
  dhd_gpu_call_entry_t function;
  dhd_gpu_call_data_t *parameters;
  dhd_uint32_t result;  

  dhd_message_t *message = loc_queue_pop();
  
  /* decode call details and dispatch the call*/
  function   = message->function;
  parameters = message->parameters;
  result = dhd_gpu_dispatch( function, parameters );

  /* schedule caller for later wakeup if required (only used when m_gpu_config->m_force_synchronous is set)*/
  if ((function == E_DHD_CALL_WAIT_FOR_JOB) && 
      (  result == E_DHD_ERROR_SCHEDULE_FOR_WAIT)) {
    /* parameter block has to be correct when client is added for waiting as he IRQ can resume a job as soon as it is in the */
    /* wait list. For this to work the first entry in parameter.m_wait_for_job is unused. */
    parameters->m_response = E_DHD_OK;      

    /* by not setting the response event we make sure the calling side will block*/
    /* we use the response event handle as wakeup token*/
    dhd_gpu_bridge_add_waiting(parameters->m_wait_for_job.m_client, parameters->m_wait_for_job.m_id, (dhd_bridge_wakeup_token)message->response );          
  } else if ((R_OS_Sem_t*) 0 != message->response) {
    /* call has been processed. store return value and signal completion*/
    parameters->m_response = result;
    R_OS_SemPost(message->response);
  }
}



/*----------------------------------------------------------------------------------------------------------
 * Perform a call to kernel mode. Calls into the kernel mode will block (and potentially send the caller
 * to sleep) until the request has been processed.
 *
 * Parameters:
 *  a_bridge - client side bridge handle (as returned by dhd_gpu_bridge_init_client).
 *  a_function - identifier for the function to be called (see dhd_gpu_call_entry_t).
 *  a_parameters - pointer to a dhd_gpu_call_data_t with the section relevant for a_function filled out.
 *
 * Returns:
 *  depends on function that was called
 *
 * See Also: <link !!Bridge, Kernel Mode Interface>
 */
dhd_uint32_t dhd_gpu_call(dhd_handle_t a_bridge, dhd_gpu_call_entry_t a_function, dhd_gpu_call_data_t *a_parameters) {
  dhd_uint32_t result = 0;
  if (R_NULL == a_bridge) {
    /* no valid bridge handle - local kernel call can dispatch directly */
    if ( a_function == E_DHD_CALL_INIT ) {
      dhd_gpu_preprocess( a_parameters );
    }
    result = dhd_gpu_dispatch( a_function, a_parameters );
  } else {
    dhd_gpu_call_data_t parameters = *a_parameters;

    /* storage of message is on stack of client -> safe since we wait below until the server side has 
       processed the message before we leave this scope */
    dhd_message_t message;

    /* prepare message */
    message.function   = a_function;
    message.parameters = &parameters;
    message.response   = (R_OS_Sem_t*)a_bridge;

    /* push message onto message queue */
    loc_queue_push(&message);

    /* wait for semaphore, which is posted after message has been processed */
    R_OS_SemWait(message.response);

    result = parameters.m_response;
  }
  return result;
}


/*----------------------------------------------------------------------------------------------------------
 * Create kernel thread which dispatches commands of its client(s).
 * Implementation for non-threaded KMD is empty.
 *
 * Parameters:
 * See Also: dhd_gpu_dispatch_loop
 */
void dhd_gpu_bridge_init_server(void) {
  R_OS_ThreadMutexInit(&loc_queue_check_mutex, 0);
  R_OS_ThreadMutexInit(&loc_queue_change_mutex, 0);
  R_OS_SemInit(&loc_queue_message_sem, 0, 0);
  R_OS_SemInit(&loc_queue_full_sem, 0, 0);  

  /* generic part of bridge_init_server */
  dhd_gpu_bridge_init_server_internal();
}


/*----------------------------------------------------------------------------------------------------------
 * Shut down kernel side of bridge. Counterpart for dhd_gpu_bridge_init_server.
 *
 * See Also: dhd_gpu_bridge_init_server
 */
void dhd_gpu_bridge_shutdown_server(void) {
  R_OS_ThreadMutexDestroy(&loc_queue_check_mutex);
  R_OS_ThreadMutexDestroy(&loc_queue_change_mutex);  
  R_OS_SemDestroy(&loc_queue_message_sem);
  R_OS_SemDestroy(&loc_queue_full_sem);  

  /* wake up all remaining clients */
  dhd_gpu_bridge_wakeup_all();
}


/*----------------------------------------------------------------------------------------------------------
 * Initialize client side of bridge. Each user mode client has to call this function in order to obtain
 * the bridge handle required to perform gpu calls.
 *
 * Returns:
 *  bridge handle
 *
 * See Also: dhd_gpu_bridge_shutdown_client
 */
dhd_handle_t dhd_gpu_bridge_init_client(void) {
  dhd_handle_t result = R_NULL;
  dhd_int16_t i;

  /* search for first available semaphore */
  for (i=0; (i<DHD_MAX_CONCURRENT_CLIENTS) && (0 != loc_client_active[i]); ++i);

  /* if semaphore available, initialize and assign to return value */
  if (i<DHD_MAX_CONCURRENT_CLIENTS) {
    loc_client_active[i] = 1;
    R_OS_SemInit(&loc_client_sem[i], 0, 0);
    result = (dhd_handle_t) &loc_client_sem[i];
  }
  return result;
}


/*----------------------------------------------------------------------------------------------------------
 * Shut down client side of bridge. After all communication is finished a client must close it's side of the
 * bridge using this function
 *
 * Parameters:
 *  a_bridge - client side bridge handle (as returned by dhd_gpu_bridge_init_client).
 *
 * Returns:
 *  E_DHD_OK if the client was successfully shut down
 *  E_DHD_ERROR_FATAL if the given client bridge could not be found
 * See Also: dhd_gpu_bridge_init_client 
 */
dhd_uint32_t dhd_gpu_bridge_shutdown_client(dhd_handle_t a_bridge) {
  if (0 != a_bridge) {
    dhd_int16_t i;

    /* search for array index corresponding to handle */
    for (i=0; (i<DHD_MAX_CONCURRENT_WAITS) && (&loc_client_sem[i] != (R_OS_Sem_t*)a_bridge) ; ++i) {
    }

    if (i<DHD_MAX_CONCURRENT_WAITS) {
      /* if index is found destroy semaphore and delete use flag */      
      R_OS_SemDestroy(&loc_client_sem[i]);
      loc_client_active[i] = 0;      
    } else {
      /* this is a true error: we should know a client bridge which is to be shut down */
      return E_DHD_ERROR_FATAL;
    }
  }
  return E_DHD_OK;
}

/*----------------------------------------------------------------------------------------------------------
 * Asynchronously add a GPU call, discarding  the response.
 * This is designed to be only called from interrupt context.
 * Note: This platform's implementation does not allow a gpu_trigger to happen while the last one is
 * still running!
 */
void dhd_gpu_trigger(dhd_handle_t a_bridge, dhd_gpu_call_entry_t a_function, dhd_gpu_call_data_t *a_parameters) {
  loc_msg.function = a_function;
  loc_call_data = *a_parameters;
  loc_msg.parameters = &loc_call_data;
  loc_msg.response = (R_OS_Sem_t*) 0;
  loc_queue_push(&loc_msg);
}
