text: /*
   * Copyright (c) 2006-2016 Frans Kaashoek, Robert Morris, Russ Cox,
   *                         Massachusetts Institute of Technology
   * Copyright (c) 2016 Simon Schmidt
   * 
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   * 
   * The above copyright notice and this permission notice shall be included in all
   * copies or substantial portions of the Software.
   * 
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   * SOFTWARE.
   */
  #include <x86/x86.h>
  #include <x86/traps.h>
  
  // Local APIC registers, divided by 4 for use as u_int32_t[] indices.
  #define ID      (0x0020/4)   // ID
  #define VER     (0x0030/4)   // Version
  #define TPR     (0x0080/4)   // Task Priority
  #define EOI     (0x00B0/4)   // EOI
  #define SVR     (0x00F0/4)   // Spurious Interrupt Vector
    #define ENABLE     0x00000100   // Unit Enable
  #define ESR     (0x0280/4)   // Error Status
  #define ICRLO   (0x0300/4)   // Interrupt Command
    #define INIT       0x00000500   // INIT/RESET
    #define STARTUP    0x00000600   // Startup IPI
    #define DELIVS     0x00001000   // Delivery status
    #define ASSERT     0x00004000   // Assert interrupt (vs deassert)
    #define DEASSERT   0x00000000
    #define LEVEL      0x00008000   // Level triggered
    #define BCAST      0x00080000   // Send to all APICs, including self.
    #define BUSY       0x00001000
    #define FIXED      0x00000000
  #define ICRHI   (0x0310/4)   // Interrupt Command [63:32]
  #define TIMER   (0x0320/4)   // Local Vector Table 0 (TIMER)
    #define X1         0x0000000B   // divide counts by 1
    #define PERIODIC   0x00020000   // Periodic
  #define PCINT   (0x0340/4)   // Performance Counter LVT
  #define LINT0   (0x0350/4)   // Local Vector Table 1 (LINT0)
  #define LINT1   (0x0360/4)   // Local Vector Table 2 (LINT1)
  #define ERROR   (0x0370/4)   // Local Vector Table 3 (ERROR)
    #define MASKED     0x00010000   // Interrupt masked
  #define TICR    (0x0380/4)   // Timer Initial Count
  #define TCCR    (0x0390/4)   // Timer Current Count
  #define TDCR    (0x03E0/4)   // Timer Divide Configuration
  
  volatile u_int32_t*     __i686_local_apic;
  #define lapic __i686_local_apic
  
  static void
  lapicw(int index, int value)
  {
  	lapic[index] = value;
  	lapic[ID];  // wait for write to finish, by reading
  }
  
  void __i686_lapicinit()
  {
  	if(!lapic)
  		return;
  
  	// Enable local APIC; set spurious interrupt vector.
  	lapicw(SVR, ENABLE | (T_IRQ0 + IRQ_SPURIOUS));
  
  	// The timer repeatedly counts down at bus frequency
  	// from lapic[TICR] and then issues an interrupt.
  	// If xv6 cared more about precise timekeeping,
  	// TICR would be calibrated using an external time source.
  	lapicw(TDCR, X1);
  	lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER));
  	lapicw(TICR, 10000000);
  
  	// Disable logical interrupt lines.
  	lapicw(LINT0, MASKED);
  	lapicw(LINT1, MASKED);
  
  	// Disable performance counter overflow interrupts
  	// on machines that provide that interrupt entry.
  	if(((lapic[VER]>>16) & 0xFF) >= 4)
  		lapicw(PCINT, MASKED);
  
  	// Map error interrupt to IRQ_ERROR.
  	lapicw(ERROR, T_IRQ0 + IRQ_ERROR);
  
  	// Clear error status register (requires back-to-back writes).
  	lapicw(ESR, 0);
  	lapicw(ESR, 0);
  
  	// Ack any outstanding interrupts.
  	lapicw(EOI, 0);
  
  	// Send an Init Level De-Assert to synchronise arbitration ID's.
  	lapicw(ICRHI, 0);
  	lapicw(ICRLO, BCAST | INIT | LEVEL);
  	while(lapic[ICRLO] & DELIVS)
  		;
  
  	// Enable interrupts on the APIC (but not on the processor).
  	lapicw(TPR, 0);
  }
  
  
  #if 0
  int
  cpunum(void)
  {
    int apicid, i;
    
    // Cannot call cpu when interrupts are enabled:
    // result not guaranteed to last long enough to be used!
    // Would prefer to panic but even printing is chancy here:
    // almost everything, including cprintf and panic, calls cpu,
    // often indirectly through acquire and release.
    if(readeflags()&FL_IF){
      static int n;
      if(n++ == 0)
        cprintf("cpu called from %x with interrupts enabled\n",
          __builtin_return_address(0));
    }
  
    if (!lapic)
      return 0;
  
    apicid = lapic[ID] >> 24;
    for (i = 0; i < ncpu; ++i) {
      if (cpus[i].apicid == apicid)
        return i;
    }
    panic("unknown apicid\n");
  }
  #endif
  
  // Acknowledge interrupt.
  void __i686_lapiceoi() {
  	if(lapic)
  		lapicw(EOI, 0);
  }
  
  #if 0
  // Spin for a given number of microseconds.
  // On real hardware would want to tune this dynamically.
  void
  microdelay(int us)
  {
  	(void)us;
  }
  #endif
  
  #define CMOS_PORT    0x70
  #define CMOS_RETURN  0x71
  
  #if 0
  // Start additional processor running entry code at addr.
  // See Appendix B of MultiProcessor Specification.
  void
  lapicstartap(u_int8_t apicid, u_int32_t addr)
  {
    int i;
    u_int16_t *wrv;
  
    // "The BSP must initialize CMOS shutdown code to 0AH
    // and the warm reset vector (DWORD based at 40:67) to point at
    // the AP startup code prior to the [universal startup algorithm]."
    outb(CMOS_PORT, 0xF);  // offset 0xF is shutdown code
    outb(CMOS_PORT+1, 0x0A);
    wrv = (u_int16_t*)P2V((0x40<<4 | 0x67));  // Warm reset vector
    wrv[0] = 0;
    wrv[1] = addr >> 4;
  
    // "Universal startup algorithm."
    // Send INIT (level-triggered) interrupt to reset other CPU.
    lapicw(ICRHI, apicid<<24);
    lapicw(ICRLO, INIT | LEVEL | ASSERT);
    microdelay(200);
    lapicw(ICRLO, INIT | LEVEL);
    microdelay(100);    // should be 10ms, but too slow in Bochs!
  
    // Send startup IPI (twice!) to enter code.
    // Regular hardware is supposed to only accept a STARTUP
    // when it is in the halted state due to an INIT.  So the second
    // should be ignored, but it is part of the official Intel algorithm.
    // Bochs complains about the second one.  Too bad for Bochs.
    for(i = 0; i < 2; i++){
      lapicw(ICRHI, apicid<<24);
      lapicw(ICRLO, STARTUP | (addr>>12));
      microdelay(200);
    }
  }
  #endif
  
  #define CMOS_STATA   0x0a
  #define CMOS_STATB   0x0b
  #define CMOS_UIP    (1 << 7)        // RTC update in progress
  
  #define SECS    0x00
  #define MINS    0x02
  #define HOURS   0x04
  #define DAY     0x07
  #define MONTH   0x08
  #define YEAR    0x09
  #if 0
  static u_int32_t cmos_read(u_int32_t reg)
  {
    outb(CMOS_PORT,  reg);
    microdelay(200);
  
    return inb(CMOS_RETURN);
  }
  #endif
  
  #if 0
  static void fill_rtcdate(struct rtcdate *r)
  {
    r->second = cmos_read(SECS);
    r->minute = cmos_read(MINS);
    r->hour   = cmos_read(HOURS);
    r->day    = cmos_read(DAY);
    r->month  = cmos_read(MONTH);
    r->year   = cmos_read(YEAR);
  }
  
  // qemu seems to use 24-hour GWT and the values are BCD encoded
  void cmostime(struct rtcdate *r)
  {
    struct rtcdate t1, t2;
    int sb, bcd;
  
    sb = cmos_read(CMOS_STATB);
  
    bcd = (sb & (1 << 2)) == 0;
  
    // make sure CMOS doesn't modify time while we read it
    for(;;) {
      fill_rtcdate(&t1);
      if(cmos_read(CMOS_STATA) & CMOS_UIP)
          continue;
      fill_rtcdate(&t2);
      if(memcmp(&t1, &t2, sizeof(t1)) == 0)
        break;
    }
  
    // convert
    if(bcd) {
  #define    CONV(x)     (t1.x = ((t1.x >> 4) * 10) + (t1.x & 0xf))
      CONV(second);
      CONV(minute);
      CONV(hour  );
      CONV(day   );
      CONV(month );
      CONV(year  );
  #undef     CONV
    }
  
    *r = t1;
    r->year += 2000;
  }
  #endif
  