ref: 2c9eeaf3cfd204ee49d676e8716445bb563249cd
dir: /io_timer.c/
/*
* io_timer.c - PIT and RTC timer portions (REFACTORED)
*
* This file contains ONLY the timer-related I/O functions from io.c,
* updated to use the new timer architecture.
*
* CHANGES from original:
* - Removed timer_set()/timer_cancel() calls for PIT/RTC
* - CPU0 derives PIT/RTC deadlines from shared state in timer.c
* - Added timer_kick() call to nudge CPU0 when state changes
* - Cleaner separation: this file only manages device state,
* timer.c handles all scheduling
*/
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <tos.h>
#include "dat.h"
#include "fns.h"
extern uvlong vmtime_ns(void);
extern void timer_kick(void);
extern int debug;
extern int state;
/* ============================================================
* PIT (8254 Programmable Interval Timer)
* ============================================================ */
extern PitShared *pitshared;
enum { PIT_PERIOD_NS = 838 }; /* ~1.193182 MHz */
static void
pitout_locked(int n, int v)
{
switch(v){
case IRQLLOHI: case 1: pitshared->ch[n].output = 1; break;
case 0: pitshared->ch[n].output = 0; break;
case IRQLTOGGLE: pitshared->ch[n].output ^= 1; break;
}
}
/*
* pitadvance - Check and fire expired PIT timers
*
* Called by CPU0 only from timers_advance().
* No timer_set() calls - CPU0's timer_rearm_locked() reads
* shared state directly to compute next deadline.
*/
void
pitadvance(void)
{
int i;
int nc;
PITChannel *p;
vlong nt, t;
int irq_needed = 0;
int irq_val = 0;
if(pitshared == nil)
return;
lock(&pitshared->lock);
for(i = 0; i < 3; i++){
p = &pitshared->ch[i];
nt = vmtime_ns();
t = nt - p->lastnsec;
p->lastnsec = nt;
switch(p->mode){
case 0: /* Interrupt on terminal count */
if(p->state != 0){
nc = t / PIT_PERIOD_NS;
if(p->count <= nc){
pitout_locked(i, 1);
if(i == 0){
irq_needed = 1;
irq_val = 1;
}
}
p->count -= nc;
p->lastnsec -= t % PIT_PERIOD_NS;
}
break;
case 2: /* Rate generator */
if(p->state != 0){
nc = t / PIT_PERIOD_NS;
while(p->count <= nc && p->reload > 0){
nc -= p->count;
p->count = p->reload;
pitout_locked(i, IRQLLOHI);
if(i == 0){
irq_needed = 1;
irq_val = IRQLLOHI;
}
}
p->count -= nc;
p->lastnsec -= t % PIT_PERIOD_NS;
}
break;
case 3: /* Square wave */
if(p->state != 0){
nc = t / PIT_PERIOD_NS;
while(p->count <= nc){
nc -= p->count;
p->count = p->reload;
pitout_locked(i, IRQLTOGGLE);
if(i == 0 && p->output){
irq_needed = 1;
irq_val = 1;
}
}
p->count -= nc;
p->lastnsec -= t % PIT_PERIOD_NS;
}
break;
}
}
unlock(&pitshared->lock);
/* Fire IRQ outside lock */
if(irq_needed)
ioapic_irqline(0, irq_val);
/* No timer_set() needed - CPU0's timer_rearm_locked() will
* compute next deadline from p->lastnsec + p->count * PIT_PERIOD_NS */
}
static void
pitsetreload(int n, u16int v)
{
PITChannel *p = &pitshared->ch[n];
p->reload = v != 0 ? v : 0x10000;
p->count = p->reload;
p->state = 1;
p->lastnsec = vmtime_ns();
if(p->mode == 0)
pitout_locked(n, 0);
else if(p->mode == 2 || p->mode == 3)
pitout_locked(n, 1);
/* No timer_set() - just kick CPU0 to pick up new state */
}
u32int
pitio(int isin, u16int port, u32int val, int sz, void *)
{
PITChannel *p;
int n;
u32int ret = 0;
int state_changed = 0;
if(sz != 1 && port != 0x61)
vmerror("pit: access size %d != 1", sz);
val = (u8int)val;
n = port & 3;
lock(&pitshared->lock);
switch(isin << 16 | port){
case 0x40: case 0x41: case 0x42:
p = &pitshared->ch[n];
switch(p->access){
case 1:
p->count = val;
pitsetreload(n, p->count);
state_changed = 1;
break;
case 2:
p->count = val << 8;
pitsetreload(n, p->count);
state_changed = 1;
break;
case 3:
if(p->writestate == READLO){
p->count = p->count & 0xff00 | val;
p->writestate = READHI;
}else{
p->count = p->count & 0xff | val << 8;
p->writestate = READLO;
pitsetreload(n, p->count);
state_changed = 1;
}
break;
}
break;
case 0x43:
n = val >> 6;
if(n == 3){
unlock(&pitshared->lock);
return 0; /* Read-back - not implemented */
}
p = &pitshared->ch[n];
if((val & 0x30) == 0){
p->latch = p->count;
p->readstate = READLATLO;
}else{
p->access = val >> 4 & 3;
p->mode = val >> 1 & 7;
p->bcd = val & 1;
p->writestate = READLO;
p->state = 0;
}
break;
case 0x61:
pitshared->port61 = val;
break;
case 0x10040: case 0x10041: case 0x10042:
p = &pitshared->ch[n];
switch(p->readstate){
case READLO:
ret = p->count;
if(p->access == 3)
p->readstate = READHI;
break;
case READHI:
ret = p->count >> 8;
if(p->access == 3)
p->readstate = READLO;
break;
case READLATLO:
ret = p->latch;
p->readstate = READLATHI;
break;
case READLATHI:
ret = p->latch >> 8;
p->readstate = READLO;
break;
}
break;
case 0x10043:
ret = 0;
break;
case 0x10061:
ret = pitshared->port61 & 0x0f | pitshared->ch[2].output << 5;
break;
default:
unlock(&pitshared->lock);
return iowhine(isin, port, val, sz, "pit");
}
unlock(&pitshared->lock);
/* Kick CPU0 to recompute deadlines if state changed */
if(state_changed)
timer_kick();
return ret;
}
void
pit_init(void)
{
extern void *vioalloc(ulong);
pitshared = vioalloc(sizeof(PitShared));
memset(pitshared, 0, sizeof(PitShared));
pitshared->ch[0].state = 1;
dprint("PIT: initialized (shared)\n");
}
/* ============================================================
* RTC (Real-Time Clock)
* ============================================================ */
extern RtcShared *rtcshared;
static uchar
bcd(uchar m, uchar c)
{
return (m & 1) != 0 ? c : c / 10 << 4 | c % 10;
}
/*
* rtcadvance - Check and fire expired RTC periodic interrupt
*
* Called by CPU0 only from timers_advance().
* No timer_set() calls - CPU0's timer_rearm_locked() reads
* rtcshared->rtcnext directly to compute next deadline.
*/
void
rtcadvance(void)
{
vlong t;
int irqval;
if(rtcshared == nil)
return;
lock(&rtcshared->lock);
if(rtcshared->rtcnext != -1){
t = vmtime_ns();
if(t >= rtcshared->rtcnext){
rtcshared->cmos[0xc] |= 0x40;
rtcshared->rtcnext = -1;
/* No timer_cancel() needed */
}
/* No timer_set() needed - CPU0 reads rtcnext directly */
}
irqval = (rtcshared->cmos[0xc] & rtcshared->cmos[0xb] & 0x70) != 0;
unlock(&rtcshared->lock);
ioapic_irqline(8, irqval);
}
/* Called with rtcshared->lock held */
static void
rtcadvance_locked(void)
{
vlong t;
if(rtcshared->rtcnext != -1){
t = vmtime_ns();
if(t >= rtcshared->rtcnext){
rtcshared->cmos[0xc] |= 0x40;
rtcshared->rtcnext = -1;
}
}
}
static void
rtcset(void)
{
vlong t, b;
int d;
/* Caller must hold rtcshared->lock */
rtcadvance_locked();
if((rtcshared->cmos[0xa] >> 4) > 2 ||
(rtcshared->cmos[0xa] & 15) == 0 ||
(rtcshared->cmos[0xc] & 0x40) != 0){
rtcshared->rtcnext = -1;
return;
}
switch(rtcshared->cmos[0xa]){
case 0x21: d = 12; break;
case 0x22: d = 13; break;
default: d = 4 + (rtcshared->cmos[0xa] & 0xf);
}
b = (1000000000ULL << d) / 1048576;
t = vmtime_ns();
rtcshared->rtcnext = t + b - t % b;
/* No timer_set() - CPU0 reads rtcnext directly */
}
u32int
rtcio(int isin, u16int port, u32int val, int sz, void *)
{
static u8int addr;
uintptr basemem, extmem;
int i, s;
Tm *tm;
u32int ret = 0;
int state_changed = 0;
if(sz != 1) vmerror("rtc: access size %d != 1", sz);
val = (u8int)val;
lock(&rtcshared->lock);
if(!rtcshared->cmosinit){
basemem = gavail(gptr(0, 0)) >> 10;
if(basemem > 640) basemem = 640;
extmem = gavail(gptr(1<<20, 0)) >> 10;
if(extmem >= 65535) extmem = 65535;
rtcshared->cmos[0x15] = basemem;
rtcshared->cmos[0x16] = basemem >> 8;
rtcshared->cmos[0x17] = extmem;
rtcshared->cmos[0x18] = extmem >> 8;
s = 0;
for(i = 0x10; i < 0x2e; i++)
s += rtcshared->cmos[i];
rtcshared->cmos[0x2e] = s >> 8;
rtcshared->cmos[0x2f] = s;
rtcshared->cmosinit = 1;
}
switch(isin << 16 | port){
case 0x70:
addr = val;
break;
case 0x71:
switch(addr){
case 0xa:
rtcshared->cmos[addr] = val & 0x7f;
rtcset();
state_changed = 1;
break;
case 0xb:
rtcshared->cmos[addr] = val | 2;
rtcadvance_locked();
state_changed = 1;
break;
case 0xc: case 0xd:
goto no;
default:
if(addr < nelem(rtcshared->cmos))
rtcshared->cmos[addr] = val;
else no:
vmerror("rtc: write to unknown address %#x (val=%#x)", addr, val);
break;
}
break;
case 0x10070:
ret = addr;
break;
case 0x10071:
tm = gmtime(time(nil));
switch(addr){
case 0x00: ret = bcd(rtcshared->cmos[0xb], tm->sec); break;
case 0x02: ret = bcd(rtcshared->cmos[0xb], tm->min); break;
case 0x04: ret = bcd(rtcshared->cmos[0xb], tm->hour); break;
case 0x06: ret = bcd(rtcshared->cmos[0xb], tm->wday + 1); break;
case 0x07: ret = bcd(rtcshared->cmos[0xb], tm->mday); break;
case 0x08: ret = bcd(rtcshared->cmos[0xb], tm->mon + 1); break;
case 0x09: ret = bcd(rtcshared->cmos[0xb], tm->year % 100); break;
case 0x0c:
i = rtcshared->cmos[0xc] | ((rtcshared->cmos[0xc] & rtcshared->cmos[0xb] & 0x70) != 0) << 7;
rtcshared->cmos[0xc] = 0;
rtcset();
state_changed = 1;
ret = i;
break;
case 0x32: ret = bcd(rtcshared->cmos[0xb], tm->year / 100 + 19); break;
default:
if(addr < nelem(rtcshared->cmos))
ret = rtcshared->cmos[addr];
else
vmerror("rtc: read from unknown address %#x", addr);
break;
}
break;
default:
unlock(&rtcshared->lock);
return iowhine(isin, port, val, sz, "rtc");
}
unlock(&rtcshared->lock);
/* Kick CPU0 to recompute deadlines if state changed */
if(state_changed)
timer_kick();
return ret;
}
void
rtc_init(void)
{
extern void *vioalloc(ulong);
rtcshared = vioalloc(sizeof(RtcShared));
memset(rtcshared, 0, sizeof(RtcShared));
/* Initial CMOS values */
rtcshared->cmos[1] = 0xff;
rtcshared->cmos[3] = 0xff;
rtcshared->cmos[5] = 0xff;
rtcshared->cmos[0xa] = 0x26;
rtcshared->cmos[0xb] = 1<<1;
rtcshared->cmos[0xd] = 1<<7; /* cmos valid */
rtcshared->cmos[0xf] = 0x56; /* cmos tests pass */
rtcshared->cmos[0x11] = 0x80; /* mouse enabled */
rtcshared->cmos[0x14] = 0x2e; /* cga 80-column */
rtcshared->cmos[0x2d] = 0x1c; /* caches + fpu enabled */
rtcshared->rtcnext = -1;
rtcshared->cmosinit = 0;
dprint("RTC: initialized (shared)\n");
}