/*
    Copyright 2007 (c) by S. Ryffel,
    Swiss Federal Institute of Technology,
    Computer Engineering and Networks Laboratory.

    TOPSY -- A Teachable Operating System.
             Implementation of a tiny and simple
             micro kernel for teaching purposes.

    For further information, please visit http://www.tik.ee.ethz.ch/~topsy

    This software is provided under the terms of the GNU General Public 
    Licence. A full copy of the GNU GPL is provided in the file COPYING 
    found in the development root of Topsy.
*/
/*

    File:                  $Source$
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision$
    Last Date of Change:   $Date$  by: $Author$

*/

#include "MemoryLayout.h"
#include "Debug.h"
#include "TMHalApic.h"
#include "TMHalApicAsm.h"
#include "TMClock.h"

#include "Assert.h"
#include "Exception.h"

#include "SupportAsm.h"

address_t apic_base = (address_t)APIC_BASE;  // Base Address of the APIC-Page
address_t ioapic_base = (address_t)IOAPIC_BASE;  // Base Address of the APIC-Page
cpu_type cpu = BeforeIntel486;
int bsp_apic_id;          // ID of the local APIC
volatile unsigned long fsb_speed;        // Speed of the Front Side Bus in Hz



/****************************************************************************************/
/*
 * Local APIC
 */
/****************************************************************************************/

/* Return the current Speed of the Front Side Bus in Hz
 */
static int intel_get_fsb_speed()
{
  if (cpu < Pentium4) 
  {
    return 100*1000*1000; // 100MHz
  } 
  else 
  {
    /* Find right MSR:
     *  P6 FAMILY PROCESSORS: 0x2A, bits 19..18
     *    System Bus Frequency. (R)
     *      00 = 66MHz
     *      10 = 100Mhz
     *      01 = 133MHz
     *      11 = Reserved
     *  PENTIUM M PROCESSOR: 0x2A, bit 18
     *    System Bus Frequency. (R/O)
     *      0 = 100 MHz
     *      1 = Reserved
     *      Always 0 on the Pentium M processor.
     *  INTEL® CORE™ SOLO AND INTEL® CORE™ DUO PROCESSORS: 
     *    0x2A, bit 18: System Bus Frequency. (R/O)
     *      0 = 100 MHz
     *      1 = Reserved
     *      Always 0 on the Pentium M processor.
     *    0xCD, bits 2..0
     *      Scaleable Bus Speed. (RO)
     *        This field indicates the scaleable bus clock speed:
     *          101B: 100 MHz (FSB 400)
     *          001B: 133 MHz (FSB 533)
     *          011B: 167 MHz (FSB 667)
     *        133.33 MHz should be utilized if performing
     *          calculation with System Bus Speed when
     *          encoding is 101B.
     *        166.67 MHz should be utilized if performing
     *          calculation with System Bus Speed when
     *          encoding is 001B.
     *  INTEL® CORE™ 2 PROCESSOR FAMILY:
     *      0xCD, bits 2..0
     *      Scaleable Bus Speed. (RO)
     *        This field indicates the scaleable bus clock speed:
     *          101B: 100 MHz (FSB 400)
     *          001B: 133 MHz (FSB 533)
     *          011B: 167 MHz (FSB 667)
     *          010B: 200 MHz (FSB 800)
     *          000B: 267 MHz (FSB 1067)
     *          100B: 333 MHz (FSB 1333)
     */
     return 100*1000*1000; // 100MHz

  }
  
}

// checks for an error in ESR
static int apic_check_esr(void) 
{ 
  unsigned long esr = apic_read(APIC_ESR_REG);

  if (!APIC_IS_ERROR(esr)) 
  {
    return 1;
  }
  else
  {
    printf("APIC: error in ESR=%p\n", esr);
    return 0;
  }
}


/* Gets some Information about the Processor
 */
static void get_cpu_infos()
{
  unsigned short ext_family_id, family_id, ext_model_id, model_id;
  int version_info;

  unsigned short cpu_brand, cpu_family, cpu_model;
  char* cpu_brand_string[48], *p, *q;

  // XXX implement Function for other manufacturers than Intel
  if (!__cpu_by_intel())
    PANIC("Only Intel CPUs are supported at this time.");

  // detect minimal CPU type with max cpuid
  switch(__max_cpuid())
  {
  case 0x1:
    cpu = Pentium;
    break;
  case 0x2:
    cpu = PentiumPro;
    break;
  case 0x3:
    cpu = PentiumIII;
    break;
  case 0x5:
    cpu = Pentium4HT;
    break;
  case 0x6:
    cpu = PentiumD;
    break;
  case 0xA:
    cpu = IntelCoreDuo;
    break;
  default:
    cpu = BeforeIntel486;
  }

  // Brand String Method
  //   introduced with the Pentium 4
  if ( __intel_get_brand_string(cpu_brand_string) )
  {
    if (cpu < Pentium4) // update cpu
      cpu = Pentium4;
    
    // brand string worked...
    // Brand String is right aligned => left align it
    p = q = &cpu_brand_string[0];
	  while ( *p == ' ' )
	    p++;
  	if ( p != q ) 
    {
	    while ( *p )
		    *q++ = *p++;
  	  while ( q <= &cpu_brand_string[48] )
	  	  *q++ = '\0';	/* Zero-pad the rest */
	  }
    printf("Detected GenuineIntel CPU: %s\n", cpu_brand_string);
    // XXX use cpu_brand_string...
  }
  else
  {
    // brand string failed => try brand index
    //  brand index was introduced with later-version Intel486, but 
    //  newer Processors do not brand index anymore
    if (!__intel_get_brand_index(&cpu_brand, &version_info, &bsp_apic_id ))
      PANIC("Error detecting cpu type.");

    // Fields of version_info
    //  ext_family: bits 27...20
    //  family    : bits 11...8
    //  ext_model : bits 19...16
    //  model     : bits  7...4
    ext_family_id = (unsigned short)(( version_info & 0xFF00000 ) >> 20 );
    family_id     = (unsigned short)(( version_info & 0x0000F00 ) >>  8 );
    ext_model_id  = (unsigned short)(( version_info & 0x00F0000 ) >> 16 );
    model_id      = (unsigned short)(( version_info & 0x00000F0 ) >>  4 );

    // determine CPU Family
    if (family_id != 0xF)
      cpu_family = family_id;
    else
      cpu_family = ext_family_id + family_id;
    // determine CPU Model
    if (family_id == 0x6 || family_id == 0xF)
      cpu_model = (ext_model_id << 4 ) + model_id;
    else
      cpu_model = model_id;
   
    printf("Detected GenuineIntel CPU: family=0x%x model=0x%x bsp_apic_id=0x%x\n", cpu_family, cpu_model, bsp_apic_id);
    // XXX update cpu
  }

  fsb_speed = 10000; // set it initially to 10KHz
  // fsb_speed = intel_get_fsb_speed();

}

// connect the interrupt lines
static void enable_local_apic(void)
{
  
  
}

/* disconnect the interrupt lines
 */
void clear_local_apic(void)
{
  unsigned int reg;

  // Masks error out
  reg = apic_read(APIC_LVT_ERROR_REG);
  apic_write(APIC_LVT_ERROR_REG, reg | APIC_LVT_MASKED);
  
  // Masks LINT0 (ExtInts)
  reg = apic_read(APIC_LVT_LINT0_REG);
  apic_write(APIC_LVT_LINT0_REG, reg | APIC_LVT_MASKED);

  // Masks LINT1 (NMI)
  reg = apic_read(APIC_LVT_LINT1_REG);
  apic_write(APIC_LVT_LINT1_REG, reg | APIC_LVT_MASKED);

  // Masks Timer
  reg = apic_read(APIC_LVT_TIMER_REG);
  apic_write(APIC_LVT_TIMER_REG, reg | APIC_LVT_MASKED);

  // Thermal Sensors
  reg = apic_read(APIC_LVT_THERM_REG);
  apic_write(APIC_LVT_THERM_REG, reg | APIC_LVT_MASKED);

  // Performance Monitor 
  reg = apic_read(APIC_LVT_PERF_REG);
  apic_write(APIC_LVT_PERF_REG, reg | APIC_LVT_MASKED);

  //  Error
  reg = apic_read(APIC_LVT_ERROR_REG);
  apic_write(APIC_LVT_ERROR_REG, reg | APIC_LVT_MASKED);

}

/* Enable the local APIC
 */
int enable_apic(void)
{
  unsigned long reg;

  // Enable APIC in MSR
  if (__enable_apic())
  {
    
    clear_local_apic();

    // Enable APIC in Spurious Interrupt Vector
    reg = apic_read(APIC_SPURIOUS_IV_REG);  
    apic_write(APIC_SPURIOUS_IV_REG, 
      reg | APIC_SPIV_APIC_ENABLED);

    // Enable normal external Interrupts 
    // reg = apic_read(APIC_LINT0_REG);  
    apic_write(APIC_LVT_LINT0_REG, APIC_LVT_MODE_FIXED );

    // Enable normal NMI interrupts
    // reg = apic_read(APIC_LINT1_REG);  
    apic_write(APIC_LVT_LINT1_REG, APIC_LVT_MODE_FIXED);

    // Set Task Priority Register
    apic_write(APIC_TPR_REG, APIC_TP);

    // Set the Spurious Vector to APIC_IR_SPIV
    reg = apic_read(APIC_SPURIOUS_IV_REG);
    apic_write(APIC_SPURIOUS_IV_REG,
      (reg & ~0xFF) | APIC_IR_SPIV);
    // the ISR is set in tmInstallErrorHandlers()
    
    // Enable and Set the Error Vector
    reg = apic_read(APIC_LVT_ERROR_REG);
    reg &= ~APIC_LVT_MASKED;              // unmask error
    reg = (reg & ~0xFF) | APIC_IR_ERRIV;  // set vector
    apic_write(APIC_LVT_ERROR_REG, reg);
    
    return 1;
  }
  else
  {
    return 0;
  }

}

/* Check for and Initialize the Local APIC
 */
void ApicInit()
{
  int is_bsr, i, j, intvec;
  unsigned long long value;

  // check for APIC
  if (!__is_apic_present())
      PANIC("No APIC found! Recompile without APIC.");
 
  // enable APIC
  if (!enable_apic())
    PANIC("Failed to enable the APIC");

  // get APIC Base Address
  __get_apic_base(&apic_base, &is_bsr);
  ioapic_base = apic_base + (IOAPIC_BASE - APIC_BASE);
  printf("APIC:    base=%p, is_bsr=%i, ioapic=%p\n", apic_base, is_bsr, ioapic_base);

  // Get some information about the processor
  get_cpu_infos();

  /* Send and EOI for every unserved interrupt
   *  pending interrupts are marked with an 1 in one of the ISR Registers.
   */
  for (i = APIC_ISR_NR - 1; i >= 0; i--) 
  {
		value = apic_read(APIC_ISR_REG + i*0x10);
		for (j = 31; j >= 0; j--) {
			if (value & (1<<j))
				__endOf_APIC_IRQ();
		} 
	}

  // apic_print_timer_status();
}


/** APIC read and write functions ********************************************************/

inline void apic_write(address_t addr, unsigned long v)
{
  *((volatile unsigned long *)(addr)) = v;
}
 
 
inline unsigned long apic_read(address_t addr)
{
  return *((volatile unsigned long *)(addr));
}

inline void apic_write_lvt(address_t addr, int v)
{
  *((volatile int *)(addr)) = v;
}


/** APIC local Timer *********************************************************************/

void apic_print_timer_status(void)
{
  int i, j;
  unsigned long value;

  printf("Local APIC Timer status: lvt=%p  fsb %iKHz \n                 icount=%i ccount=%i\n                 spurious vector=%p\n", 
    apic_read(APIC_LVT_TIMER_REG),
    fsb_speed>>10,
    apic_read(APIC_TIMER_ICOUNT_REG),
    apic_read(APIC_TIMER_CCOUNT_REG),
    apic_read(APIC_SPURIOUS_IV_REG)
    );
/*
  printf("                 ISRs: ");
  for (i = APIC_ISR_NR - 1; i >= 0; i--) {
		value = apic_read(APIC_ISR_REG + i*0x10);
	  printf("%p, ", value);
  }

  printf("\n                 IRRs: ");
  for (i = APIC_IRR_NR - 1; i >= 0; i--) {
		value = apic_read(APIC_IRR_REG + i*0x10);
	  printf("%p, ", value);
  }
  printf("\n");
*/
}


inline void disable_APIC_timer(void)
{
  unsigned long v;
	v = apic_read(APIC_LVT_TIMER_REG);
	apic_write(APIC_LVT_TIMER_REG , v | APIC_LVT_MASKED);
}

inline void enable_APIC_timer(void)
{
  unsigned long v;
  v = apic_read(APIC_LVT_TIMER_REG);
	apic_write(APIC_LVT_TIMER_REG , v & ~APIC_LVT_MASKED);

}


// setup the LVT Registers of the Timer
inline void set_timer_lvtr(unsigned long initial_count) 
{
  unsigned int lvt_timer = 0;
  unsigned int divider;
  
  lvt_timer |= APIC_LVT_TIMER_PERIODIC;  // Periodic
  lvt_timer |= T_IR_00;                  // Timer Interrupt Vektor

  // setup divider
  divider = apic_read(APIC_TIMER_DIV_REG);
  divider = (divider & ~(0xF)) | APIC_TIMER_DIV_BY64;    // divide by 64
  
  // write entry in Local Vector Table
  apic_write_lvt(APIC_LVT_TIMER_REG, lvt_timer);

  // write Timer Register
  apic_write(APIC_TIMER_DIV_REG, divider);
  apic_write(APIC_TIMER_ICOUNT_REG, initial_count/APIC_TIMER_DIV);

}

#define   GET_RTC_SECONDS()   (unsigned char)(__readRTC(0x00))
/* calibrate local APIC Timer
 *  returns the Front Side Bus Speed in HZ
 *  XXX is there an other solution to this problem?
 */
static int calibrate_timer(void)
{
  unsigned char s, e;  
  unsigned int t1, t2, result;
  
  // Start APIC Timer with BIG initial count
  set_timer_lvtr(0xFFFFFFFF);

  // align to second
  s = GET_RTC_SECONDS();
  do 
    e = GET_RTC_SECONDS();
  while (e==s);

  // read current Count
  t1 = apic_read(APIC_TIMER_CCOUNT_REG);

  // wait a second
  s = GET_RTC_SECONDS();
  do 
    e = GET_RTC_SECONDS();
  while (e==s);

  // read currenct Count
  t2 = apic_read(APIC_TIMER_CCOUNT_REG);

  // we waited a second...
  result = (t1-t2)*APIC_TIMER_DIV;    // multiply by divider to get FSB

  // disable timer, 
  //  we do not really want that interrupt now ;)
  disable_APIC_timer();

  return result;
}

/* Set local APIC Timer
 */
Error setClockValue( ClockId cId,
                     int period,      // time in ms
                     ClockMode cMode)
{
  unsigned long initial_count, divider;

  // there is only one local apic clock
  ASSERT(cId == CLOCK0);

  initial_count = (unsigned long)((fsb_speed / 1000) * period);

  // printf("setClockValue: fsb_speed=%iMHz period=%ims => icount=%i\n", fsb_speed/(1024*1024), period, initial_count);
  
  // start Timer
  set_timer_lvtr(initial_count);

  return TM_OK;

}

/* When a clock interrupt is generated, a reset is performed. If not done,
 * the interrupt is generated at once again and again ...
 */
void tmResetClockInterrupt(ClockId cId)
{
  /* i386: IRQ is generated. Handled by the  __general_IRQHandler the
           appropriate PIC is resetted.
   */
}

/*
 * Called by TimerInit() int TMClock.c
 */
void ApicTimerInit()
{

  // calibrate clock
  fsb_speed = calibrate_timer();

  printf("Calibrated local APIC Timer, FSB speed is %iKHz\n", fsb_speed>>10);

}

/** APIC Interrupt Service Routines ******************************************************/

/* Called by _TimerISR() in TMClock.c
 */
void ApicTimerISR()
{
  // apic_print_timer_status();
}


/* Determine the APIC-error type
 *  isr is installed in tmInstallErrorHandlers()
 */
void ApicErrorISR(void* arg)
{
  // update ESR 
  apic_write(APIC_ESR_REG, 0);

  // read Errro Status Register
  unsigned long esr = apic_read(APIC_ESR_REG);
  
  if ( esr & APIC_ESR_SEND_CHKSUM )
    WARNING("APIC ERROR: Send Checksum Error");

  if ( esr & APIC_ESR_RECV_CHKSUM )
    WARNING("APIC ERROR: Receive Checksum Error");

  if ( esr & APIC_ESR_SEND_ACK )
    WARNING("APIC ERROR: Send Accept Error");

  if ( esr & APIC_ESR_RECV_ACK )
    WARNING("APIC ERROR: Receive Accept Error");

  if ( esr & APIC_ESR_SEND_ILLVEC )
    WARNING("APIC ERROR: Send Illegal Vector");

  if ( esr & APIC_ESR_RECV_ILLVEC )
    WARNING("APIC ERROR: Receive Illegal Vector");

  if ( esr & APIC_ESR_ILLREG )
    WARNING("APIC ERROR: Illegal Reg. Address");

  // clear ESR
  apic_write(APIC_ESR_REG, 0);

  PANIC("APIC ERROR");

}


/* Spurious Interrupt Handler
 *  nothing to do...
 *  this interrupt handler is installed only if APIC_SPIV_HANDLING_ISR is defined
 */
void ApicSpivISR(void* arg)
{
  Thread* thread = __get_thread_from_esp();
  printf("Spurious Interrupt Vector in %s at eip=%p\n", thread->name, thread->contextPtr->sf->tf_eip);
  return;
}


/****************************************************************************************/
/*
 * IO APIC
 */
/****************************************************************************************/

/* Initialize the IO APIC
 *  connects it
 */
void IOApicInit()
{
  unsigned int intvec;
  unsigned long long value;

  clear_local_apic();

  /* Connect the Interrupts to the APIC
   *  -> Symmetric I/O Mode
   */
  __outb(0x70, 0x22); // select IMCR (Interrupt Mode Configuration Register)
  __outb(0x01, 0x23); // route INTR and NMI through APIC

  /*
   * Reset each entry of the redirection table
   *  0x0-MAXNBOFINTERRUPTS : map on same interrupt, route through APIC with IOAPIC_MODE_FIXED
   *  0xxx-0xFE             : mask out others
   */
  for (intvec=0x00; intvec < IOAPIC_MAXRED; intvec++)
  {
    if (intvec<MAXNBOFINTERRUPTS)
    {
      // map on same interrupt as ExtInt
      value = 0;
      IOAPIC_SET_DESTINATION(value, bsp_apic_id); // Destination is BSP
      IOAPIC_SET_VECTOR(value, intvec+T_IR_00);      // map on vector
      IOAPIC_SET_MODE(value, IOAPIC_MODE_FIXED);  // delivery mode: 8259A-compatible
      value |= IOAPIC_TRIG_LEVEL;                 // trigger mode is level sensitive
      value |= IOAPIC_MASKED;                     // mask interrupt
    }
    else
    {
      // Mask Interrupt out
      value = IOAPIC_MASKED;
    }
    // write table entry
    ioapic_write_redtlb(ioapic_base, intvec, value);
  }
  
  printf("IO-APIC: symmetric I/O mode, redirect IRQs 0x00..0x%2x to BSP IRQs 0x%2x...0x%2x\n", MAXNBOFINTERRUPTS-1, T_IR_00, T_IR_00+MAXNBOFINTERRUPTS-1);

  // for (intvec=0x00; intvec < IOAPIC_MAXRED; intvec++)
  //  printf("ioapic: vec 0x%x value 0x%x\n", intvec, ioapic_read_redtlb(ioapic_base, intvec));
}


/* writes the value into the redirection table to the position intvec
 *   pos i   = lower  32 bits
 *   pos i+1 = higher 32 bits
 */
inline void ioapic_write_redtlb(address_t ioapic_base, int intvec, unsigned long long value)
{
  unsigned long l,r;
  int i = 2*(intvec); // Offset in Table

  r = (unsigned long)(value & (unsigned long long)0xFFFFFFFF);  // lower 32 bits
  l = value >> 32;        // upper 32 bits
  
  // printf("vec=%i  l=0x%x  r=0x%x\n",intvec,  l, r);

  // write lower bits
  IOAPIC_REGSEL(IOAPIC_REDTLB + i);
  IOAPIC_WRITEWIN(r);

  // write upper bits
  IOAPIC_REGSEL(IOAPIC_REDTLB + i+1);
  IOAPIC_WRITEWIN(l);


}

inline unsigned long long ioapic_read_redtlb(address_t ioapic_base, int intvec)
{
  unsigned long long value;
  int i = 2*(intvec); // Offset in Table

  // read upper bits
  IOAPIC_REGSEL(IOAPIC_REDTLB + i+1);
  value = IOAPIC_READWIN;

  value = value << 32;

  // read lower bits
  IOAPIC_REGSEL(IOAPIC_REDTLB + i);
  value += IOAPIC_READWIN;

  return value;
}

inline void ioapic_enable_IRQ(int irq)
{
  unsigned long long value;

  // read original value
  IOAPIC_REGSEL(IOAPIC_REDTLB + (irq-T_IR_00)*2);
  value = IOAPIC_READWIN;

  // write without masked bit
  IOAPIC_WRITEWIN(value & ~IOAPIC_MASKED);

}

inline void ioapic_mask_IRQ(int irq)
{
  unsigned long long value;

  // read original value
  IOAPIC_REGSEL(IOAPIC_REDTLB + (irq-T_IR_00)*2);
  value = IOAPIC_READWIN;

  // write without masked bit
  IOAPIC_WRITEWIN(value | IOAPIC_MASKED);

}

