Goto sanos source index

//
// pit.c
//
// Programmable Interval Timer functions (PIT i8253)
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
//    notice, this list of conditions and the following disclaimer.  
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.  
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
// SUCH DAMAGE.
// 

#include <os/krnl.h>

#define TMR_CTRL        0x43    // I/O for control
#define TMR_CNT0        0x40    // I/O for counter 0
#define TMR_CNT1        0x41    // I/O for counter 1
#define TMR_CNT2        0x42    // I/O for counter 2

#define TMR_SC0         0       // Select channel 0
#define TMR_SC1         0x40    // Select channel 1
#define TMR_SC2         0x80    // Select channel 2

#define TMR_LOW         0x10    // RW low byte only
#define TMR_HIGH        0x20    // RW high byte only
#define TMR_BOTH        0x30    // RW both bytes

#define TMR_MD0         0       // Mode 0
#define TMR_MD1         0x2     // Mode 1
#define TMR_MD2         0x4     // Mode 2
#define TMR_MD3         0x6     // Mode 3
#define TMR_MD4         0x8     // Mode 4
#define TMR_MD5         0xA     // Mode 5

#define TMR_BCD         1       // BCD mode

#define TMR_LATCH       0       // Latch command

#define TMR_READ        0xF0    // Read command
#define TMR_CNT         0x20    // CNT bit  (Active low, subtract it)
#define TMR_STAT        0x10    // Status bit  (Active low, subtract it)

#define TMR_CH0         0x00    // Channel 0 bit
#define TMR_CH1         0x40    // Channel 1 bit
#define TMR_CH2         0x80    // Channel 2 bit

#define PIT_CLOCK       1193180
#define TIMER_FREQ      100

#define USECS_PER_TICK  (1000000 / TIMER_FREQ)
#define MSECS_PER_TICK  (1000 / TIMER_FREQ)

#define LOADTAB_SIZE        TIMER_FREQ

#define LOADTYPE_IDLE       0
#define LOADTYPE_USER       1
#define LOADTYPE_KERNEL     2
#define LOADTYPE_DPC        3

volatile unsigned int ticks = 0;
volatile unsigned int clocks = 0;
struct timeval systemclock = { 0, 0 };
time_t upsince;
unsigned long cycles_per_tick;
unsigned long loops_per_tick;

unsigned char loadtab[LOADTAB_SIZE];
unsigned char *loadptr;
unsigned char *loadend;

struct interrupt timerintr;
struct dpc timerdpc;

void timer_dpc(void *arg) {
  run_timer_list();
}

int timer_handler(struct context *ctxt, void *arg) {
  struct thread *t;

  // Update timer clock
  clocks += CLOCKS_PER_TICK;

  // Update tick counter
  ticks++;

  // Update system clock
  systemclock.tv_usec += USECS_PER_TICK;
  while (systemclock.tv_usec >= 1000000)  {
    systemclock.tv_sec++;
    systemclock.tv_usec -= 1000000;
  }

  // Update thread times and load average
  t = self();
  if (in_dpc) {
    dpc_time += CLOCKS_PER_TICK;
    *loadptr = LOADTYPE_DPC;
  } else {
    if (USERSPACE(ctxt->eip)) {
      t->tms.tms_utime += CLOCKS_PER_TICK;
      *loadptr = LOADTYPE_USER;
    } else {
      t->tms.tms_stime += CLOCKS_PER_TICK;
      if (t->base_priority == PRIORITY_SYSIDLE) {
        *loadptr = LOADTYPE_IDLE;
      } else {
        *loadptr = LOADTYPE_KERNEL;
      }
    }
  }

  if (++loadptr == loadend) loadptr = loadtab;

  // Adjust thread quantum
  t->quantum -= QUANTUM_UNITS_PER_TICK;
  if (t->quantum <= 0) preempt = 1;

  // Queue timer DPC
  queue_irq_dpc(&timerdpc, timer_dpc, NULL);

  eoi(IRQ_TMR);
  return 0;
}

unsigned char read_cmos_reg(int reg) {
  unsigned char val;

  outp(0x70, reg);
  val = inp(0x71) & 0xFF;

  return val;
}

void write_cmos_reg(int reg, unsigned char val) {
  outp(0x70, reg);
  outp(0x71, val);
}

static int read_bcd_cmos_reg(int reg) {
  int val;

  val = read_cmos_reg(reg);
  return (val >> 4) * 10 + (val & 0x0F);
}

static void write_bcd_cmos_reg(int reg, unsigned char val) {
  write_cmos_reg(reg, (unsigned char) (((val / 10) << 4) + (val % 10)));
}

static void get_cmos_time(struct tm *tm) {
  tm->tm_year = read_bcd_cmos_reg(0x09) + (read_bcd_cmos_reg(0x32) * 100) - 1900;
  tm->tm_mon = read_bcd_cmos_reg(0x08) - 1;
  tm->tm_mday = read_bcd_cmos_reg(0x07);
  tm->tm_hour = read_bcd_cmos_reg(0x04);
  tm->tm_min = read_bcd_cmos_reg(0x02);
  tm->tm_sec = read_bcd_cmos_reg(0x00);

  tm->tm_wday = 0;
  tm->tm_yday = 0;
  tm->tm_isdst = 0;
}

static void set_cmos_time(struct tm *tm) {
  write_bcd_cmos_reg(0x09, (unsigned char) (tm->tm_year % 100));
  write_bcd_cmos_reg(0x32, (unsigned char) ((tm->tm_year + 1900) / 100));
  write_bcd_cmos_reg(0x08, (unsigned char) (tm->tm_mon + 1));
  write_bcd_cmos_reg(0x07, (unsigned char) (tm->tm_mday));
  write_bcd_cmos_reg(0x04, (unsigned char) (tm->tm_hour));
  write_bcd_cmos_reg(0x02, (unsigned char) (tm->tm_min));
  write_bcd_cmos_reg(0x00, (unsigned char) (tm->tm_sec));
}

static void tsc_delay(unsigned long cycles) {
  long end, now;

  end = (unsigned long) rdtsc() + cycles;
  do  {
    __asm { nop };
    now = (unsigned long) rdtsc();
  } while (end - now > 0);
}

static void timed_delay(unsigned long loops) {
  __asm {
    delay_loop:
      dec loops
      jns delay_loop; 
  }
}

void calibrate_delay() {
  static int cpu_speeds[] =  {
    16, 20, 25, 33, 40, 50, 60, 66, 75, 80, 90, 
    100, 110, 120, 133, 150, 166, 180, 188,
    200, 233, 250, 266,
    300, 333, 350, 366, 
    400, 433, 450, 466, 
    500, 533, 550, 566, 
    600, 633, 650, 667, 
    700, 733, 750, 766, 
    800, 833, 850, 866,
    900, 933, 950, 966,
    1000, 1130, 1200, 1260
  };

  unsigned long mhz;
  unsigned long t, bit;
  int precision;

  if (cpu.features & CPU_FEATURE_TSC) {
    unsigned long start;
    unsigned long end;

    t = ticks;
    while (t == ticks);
    start = (unsigned long) rdtsc();

    t = ticks;
    while (t == ticks);
    end = (unsigned long) rdtsc();

    cycles_per_tick = end - start;
  } else {
    // Determine magnitude of loops_per_tick
    loops_per_tick = 1 << 12;
    while (loops_per_tick <<= 1) {
      t = ticks;
      while (t == ticks);
    
      t = ticks;
      timed_delay(loops_per_tick);
      if (t != ticks) break;
    }

    // Do a binary approximation of cycles_per_tick
    precision = 8;
    loops_per_tick >>= 1;
    bit = loops_per_tick;
    while (precision-- && (bit >>= 1)) {
      loops_per_tick |= bit;
      t = ticks;
      while (t == ticks);
      t = ticks;
      timed_delay(loops_per_tick);
      if (ticks != t) loops_per_tick &= ~bit; // Longer than 1 tick
    }

    // Each loop takes 10 cycles
    cycles_per_tick = 10 * loops_per_tick;
  }

  mhz = cycles_per_tick * TIMER_FREQ / 1000000;

  if (mhz > 1275) {
    mhz = ((mhz + 25) / 50) * 50;
  } else if (mhz > 14) {
    int *speed = cpu_speeds;
    int i;
    unsigned long bestmhz = 0;

    for (i = 0; i < sizeof(cpu_speeds) / sizeof(int); i++) {
      int curdiff = mhz - bestmhz;
      int newdiff = mhz - *speed;

      if (curdiff < 0) curdiff = -curdiff;
      if (newdiff < 0) newdiff = -newdiff;

      if (newdiff < curdiff) bestmhz = *speed;
      speed++;
    }

    mhz = bestmhz;
  }

  kprintf(KERN_INFO "speed: %d cycles/tick, %d MHz processor\n", cycles_per_tick, mhz);
  cpu.mhz = mhz;
}

static int uptime_proc(struct proc_file *pf, void *arg) {
  int days, hours, mins, secs;
  int uptime = get_time() - upsince;

  days = uptime / (24 * 60 * 60);
  uptime -= days * (24 * 60 * 60);
  hours = uptime / (60 * 60);
  uptime -= hours * (60 * 60);
  mins = uptime / 60;
  secs = uptime % 60;

  pprintf(pf, "%d day%s %d hour%s %d minute%s %d second%s\n",
    days, days == 1 ? "" : "s",
    hours, hours == 1 ? "" : "s",
    mins, mins == 1 ? "" : "s",
    secs, secs == 1 ? "" : "s");

  return 0;
}

static int loadavg_proc(struct proc_file *pf, void *arg) {
  int loadavg[4];
  unsigned char *p;

  memset(loadavg, 0, sizeof loadavg);
  p = loadtab;
  while (p < loadend) loadavg[*p++]++;

  pprintf(pf, "Load kernel: %d%% user: %d%% dpc: %d%% idle: %d%%\n",
    loadavg[LOADTYPE_KERNEL],
    loadavg[LOADTYPE_USER],
    loadavg[LOADTYPE_DPC],
    loadavg[LOADTYPE_IDLE]);

  return 0;
}

void init_pit() {
  struct tm tm;

  unsigned int cnt = PIT_CLOCK / TIMER_FREQ;
  outp(TMR_CTRL, TMR_CH0 + TMR_BOTH + TMR_MD3);
  outp(TMR_CNT0, (unsigned char) (cnt & 0xFF));
  outp(TMR_CNT0, (unsigned char) (cnt >> 8));

  loadptr = loadtab;
  loadend = loadtab + LOADTAB_SIZE;

  get_cmos_time(&tm);
  systemclock.tv_sec = mktime(&tm);
  upsince = systemclock.tv_sec;

  init_dpc(&timerdpc);
  timerdpc.flags |= DPC_NORAND; // Timer tick is a bad source for randomness
  register_interrupt(&timerintr, INTR_TMR, timer_handler, NULL);
  enable_irq(IRQ_TMR);

  register_proc_inode("uptime", uptime_proc, NULL);
  register_proc_inode("loadavg", loadavg_proc, NULL);
}

void udelay(unsigned long us) {
  if (cpu.features & CPU_FEATURE_TSC) {
    tsc_delay(us * (cycles_per_tick / (1000000 / TIMER_FREQ)));
  } else {
    timed_delay(us * (loops_per_tick / (1000000 / TIMER_FREQ)));
  }
}

unsigned int get_ticks() {
  return ticks;
}

time_t get_time() {
  return systemclock.tv_sec;
}

time_t time(time_t *time) {
  if (time) {
    *time = get_time();
    return *time;
  } else {
    return get_time();
  }
}

void set_time(struct timeval *tv) {
  struct tm tm;

  upsince += (tv->tv_sec - systemclock.tv_sec);
  systemclock.tv_usec = tv->tv_usec;
  systemclock.tv_sec = tv->tv_sec;
  gmtime_r(&tv->tv_sec, &tm);
  set_cmos_time(&tm);
}

int load_sysinfo(struct loadinfo *info) {
  int loadavg[4];
  unsigned char *p;

  memset(loadavg, 0, sizeof loadavg);
  p = loadtab;
  while (p < loadend) loadavg[*p++]++;

  info->uptime = get_time() - upsince;
  info->load_user = loadavg[LOADTYPE_USER];
  info->load_system = loadavg[LOADTYPE_KERNEL];
  info->load_intr = loadavg[LOADTYPE_DPC];
  info->load_idle = loadavg[LOADTYPE_IDLE];

  return 0;
}