#include "sifive/devices/plic.h"
#include "plic_driver.h"
#include "platform.h"
#include "encoding.h"
#include <string.h>

void volatile_memzero(uint8_t * base, unsigned int size)
{
  volatile uint8_t * ptr;
  for (ptr = base; ptr < (base + size); ptr++){
    *ptr = 0;
  }
}

void PLIC_init (
                plic_instance_t * this_plic,
                uintptr_t         base_addr,
                uint32_t num_sources,
                uint32_t num_priorities
                )
{
  
  this_plic->base_addr = base_addr;
  this_plic->num_sources = num_sources;
  this_plic->num_priorities = num_priorities;
  
  // Disable all interrupts (don't assume that these registers are reset).
  unsigned long hart_id = read_csr(mhartid);
  volatile_memzero((uint8_t*) (this_plic->base_addr +
                               PLIC_ENABLE_OFFSET +
                               (hart_id << PLIC_ENABLE_SHIFT_PER_TARGET)),
                   (num_sources + 8) / 8);
  
  // Set all priorities to 0 (equal priority -- don't assume that these are reset).
  volatile_memzero ((uint8_t *)(this_plic->base_addr +
                                PLIC_PRIORITY_OFFSET),
                    (num_sources + 1) << PLIC_PRIORITY_SHIFT_PER_SOURCE);

  // Set the threshold to 0.
  volatile plic_threshold* threshold = (plic_threshold*)
    (this_plic->base_addr +
     PLIC_THRESHOLD_OFFSET +
     (hart_id << PLIC_THRESHOLD_SHIFT_PER_TARGET));

  *threshold = 0;
  
}

void PLIC_set_threshold (plic_instance_t * this_plic,
			 plic_threshold threshold){

  unsigned long hart_id = read_csr(mhartid);  
  volatile plic_threshold* threshold_ptr = (plic_threshold*) (this_plic->base_addr +
                                                              PLIC_THRESHOLD_OFFSET +
                                                              (hart_id << PLIC_THRESHOLD_SHIFT_PER_TARGET));

  *threshold_ptr = threshold;

}
  

void PLIC_enable_interrupt (plic_instance_t * this_plic, plic_source source){

  unsigned long hart_id = read_csr(mhartid);
  volatile uint8_t * current_ptr = (volatile uint8_t *)(this_plic->base_addr +
                                                        PLIC_ENABLE_OFFSET +
                                                        (hart_id << PLIC_ENABLE_SHIFT_PER_TARGET) +
                                                        (source >> 3));
  uint8_t current = *current_ptr;
  current = current | ( 1 << (source & 0x7));
  *current_ptr = current;

}

void PLIC_disable_interrupt (plic_instance_t * this_plic, plic_source source){
  
  unsigned long hart_id = read_csr(mhartid);
  volatile uint8_t * current_ptr = (volatile uint8_t *) (this_plic->base_addr +
                                                         PLIC_ENABLE_OFFSET +
                                                         (hart_id << PLIC_ENABLE_SHIFT_PER_TARGET) +
                                                         (source >> 3));
  uint8_t current = *current_ptr;
  current = current & ~(( 1 << (source & 0x7)));
  *current_ptr = current;
  
}

void PLIC_set_priority (plic_instance_t * this_plic, plic_source source, plic_priority priority){

  if (this_plic->num_priorities > 0) {
    volatile plic_priority * priority_ptr = (volatile plic_priority *)
      (this_plic->base_addr +
       PLIC_PRIORITY_OFFSET +
       (source << PLIC_PRIORITY_SHIFT_PER_SOURCE));
    *priority_ptr = priority;
  }
}

plic_source PLIC_claim_interrupt(plic_instance_t * this_plic){
  
  unsigned long hart_id = read_csr(mhartid);

  volatile plic_source * claim_addr = (volatile plic_source * )
    (this_plic->base_addr +
     PLIC_CLAIM_OFFSET +
     (hart_id << PLIC_CLAIM_SHIFT_PER_TARGET));

  return  *claim_addr;
  
}

void PLIC_complete_interrupt(plic_instance_t * this_plic, plic_source source){
  
  unsigned long hart_id = read_csr(mhartid);
  volatile plic_source * claim_addr = (volatile plic_source *) (this_plic->base_addr +
                                                                PLIC_CLAIM_OFFSET +
                                                                (hart_id << PLIC_CLAIM_SHIFT_PER_TARGET));
  *claim_addr = source;
  
}