ref: 2c9eeaf3cfd204ee49d676e8716445bb563249cd
dir: /hpet.c/
/*
* hpet.c - HPET with timer interrupt support (REFACTORED)
*
* CHANGES from original:
* - Removed timer_set()/timer_cancel() calls for HPET timers
* - CPU0 derives HPET deadlines from shared state in timer.c
* - Added timer_kick() call to nudge CPU0 when state changes
* - Cleaner separation: this file only manages HPET state,
* timer.c handles all scheduling
*/
#include <u.h>
#include <libc.h>
#include <tos.h>
#include <thread.h>
#include "dat.h"
#include "fns.h"
#define HPET_FREQ_HZ 14318180ULL /* 14.318180 MHz - standard */
#define HPET_PERIOD_FS 69841279ULL /* 1e15 / 14318180 = 69841279 femtoseconds (~69.84ns) */
#define HPET_CAP 0x000
#define HPET_CFG 0x010
#define HPET_STATUS 0x020
#define HPET_COUNTER 0x0F0
#define HPET_T0_CFG 0x100
#define HPET_T0_CMP 0x108
#define HPET_T1_CFG 0x120
#define HPET_T1_CMP 0x128
#define HPET_T2_CFG 0x140
#define HPET_T2_CMP 0x148
/* Timer config bits */
#define TN_INT_ENB (1 << 2) /* Interrupt enable */
#define TN_TYPE (1 << 3) /* 0=one-shot, 1=periodic */
#define TN_PER_CAP (1 << 4) /* Periodic capable */
#define TN_SIZE_CAP (1 << 5) /* 64-bit capable */
#define TN_VAL_SET (1 << 6) /* Set periodic accumulator */
#define TN_32MODE (1 << 8) /* Force 32-bit mode */
#define TN_INT_ROUTE(x) (((x) >> 9) & 0x1f) /* IRQ routing */
/* Capabilities: 64-bit, periodic capable, 3 timers, legacy replacement capable */
#define HPET_CAP_VALUE ((HPET_PERIOD_FS << 32) | \
(0x8086ULL << 16) | \
(1ULL << 15) | \
(1ULL << 13) | \
(2ULL << 8) | \
0x01ULL)
/* Shared HPET state */
Hpet *hpet;
extern int debug;
extern int state;
extern void ioapic_irqline_smp(int, int);
/* timer_kick() nudges CPU0 to recompute deadlines */
extern void timer_kick(void);
/*
* Fixed-point conversion factors (pre-computed):
*/
#define NS_TO_HPET_MULT 61496551ULL
#define NS_TO_HPET_SHIFT 32
#define HPET_TO_NS_MULT 1171873536ULL
#define HPET_TO_NS_SHIFT 24
static u64int
ns_to_hpet(uvlong ns)
{
return (ns * NS_TO_HPET_MULT) >> NS_TO_HPET_SHIFT;
}
static uvlong
hpet_to_ns(u64int hpet_ticks)
{
return (hpet_ticks * HPET_TO_NS_MULT) >> HPET_TO_NS_SHIFT;
}
static u64int
hpet_counter_unlocked(void)
{
uvlong now_ns, elapsed_ns;
/* Use real host time - always advances even when VM halted */
now_ns = nanosec();
elapsed_ns = now_ns - hpet->time_at_enable;
return hpet->counter_offset + ns_to_hpet(elapsed_ns);
}
/*
* Get current HPET counter value (with locking).
*/
static u64int
hpet_counter(void)
{
u64int val;
lock(&hpet->lock);
val = hpet_counter_unlocked();
unlock(&hpet->lock);
return val;
}
/*
* hpet_advance - Check and fire expired HPET 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
hpet_advance(void)
{
u64int counter;
int i;
int should_fire[3] = {0, 0, 0};
int irqs[3];
int is_periodic[3];
if(hpet == nil || !hpet->enabled)
return;
lock(&hpet->lock);
counter = hpet_counter_unlocked();
for(i = 0; i < 3; i++){
HpetTimer *t = &hpet->timer[i];
if(!t->active)
continue;
if(!(t->cfg & TN_INT_ENB))
continue;
/* Check if counter >= comparator */
if(counter >= t->cmp){
/* Set status bit */
hpet->status |= (1 << i);
/* Determine IRQ */
if(hpet->cfg & 2){ /* Legacy replacement mode */
irqs[i] = (i == 0) ? 0 : 8; /* Timer 0 replaces PIT (IRQ 0) */
} else {
irqs[i] = TN_INT_ROUTE(t->cfg);
if(irqs[i] > 23) irqs[i] = 0;
}
should_fire[i] = 1;
is_periodic[i] = (t->cfg & TN_TYPE) != 0;
/* Handle periodic vs one-shot */
if(is_periodic[i]){
/* Catch up all missed periods at once */
while(t->cmp <= counter && t->period > 0)
t->cmp += t->period;
} else {
/* One-shot: deactivate */
t->active = 0;
}
}
}
unlock(&hpet->lock);
/* Fire interrupts outside lock */
for(i = 0; i < 3; i++){
if(should_fire[i]){
dprint("HPET: timer %d fired! irq=%d periodic=%d\n",
i, irqs[i], is_periodic[i]);
ioapic_irqline_smp(irqs[i], 1);
}
}
}
u64int
hpet_read(u64int addr, int size)
{
u32int offset = addr & 0xFFF;
u32int reg = offset & ~7;
u64int val;
// lock(&hpet->lock);
switch(reg){
case HPET_CAP: val = HPET_CAP_VALUE; break;
case HPET_CFG: val = hpet->cfg; break;
case HPET_STATUS: val = hpet->status; break;
case HPET_COUNTER: val = hpet_counter_unlocked(); break;
case HPET_T0_CFG: val = hpet->timer[0].cfg; break;
case HPET_T0_CMP: val = hpet->timer[0].cmp; break;
case HPET_T1_CFG: val = hpet->timer[1].cfg; break;
case HPET_T1_CMP: val = hpet->timer[1].cmp; break;
case HPET_T2_CFG: val = hpet->timer[2].cfg; break;
case HPET_T2_CMP: val = hpet->timer[2].cmp; break;
default: val = 0; break;
}
// unlock(&hpet->lock);
if(offset & 4)
val >>= 32;
if(size == 4)
val = (u32int)val;
return val;
}
void
hpet_write(u64int addr, u32int val, int size)
{
u32int offset = addr & 0xFFF;
int timer;
int state_changed = 0; /* Track if we need to kick CPU0 */
dprint("HPET write: offset=%#x val=%#x size=%d\n", offset, val, size);
lock(&hpet->lock);
switch(offset){
case HPET_CAP:
case HPET_CAP + 4:
/* Read-only */
break;
case HPET_CFG:
{
int was_enabled = hpet->enabled;
int new_enabled = (val & 1) != 0;
if(was_enabled && !new_enabled){
/* Stopping: save current counter value */
hpet->counter_offset = hpet_counter_unlocked();
dprint("HPET: DISABLED counter=%#llx\n", hpet->counter_offset);
}
if(size == 4)
hpet->cfg = (hpet->cfg & 0xFFFFFFFF00000000ULL) | val;
else
hpet->cfg = val;
hpet->enabled = new_enabled;
if(!was_enabled && new_enabled){
/* Starting: record real host time */
hpet->time_at_enable = nanosec();
dprint("HPET: ENABLED time=%llud counter_offset=%#llx legacy=%d\n",
hpet->time_at_enable, hpet->counter_offset, (hpet->cfg >> 1) & 1);
}
state_changed = 1;
}
break;
case HPET_CFG + 4:
hpet->cfg = (hpet->cfg & 0xFFFFFFFF) | ((u64int)val << 32);
break;
case HPET_STATUS:
/* Write 1 to clear status bits */
hpet->status &= ~(u64int)val;
/* Clear the interrupt when status is cleared */
for(timer = 0; timer < 3; timer++){
if(val & (1 << timer)){
int irq;
if(hpet->cfg & 2)
irq = (timer == 0) ? 0 : 8;
else
irq = TN_INT_ROUTE(hpet->timer[timer].cfg);
// unlock(&hpet->lock);
ioapic_irqline_smp(irq, 0);
// ioapic_set_irq(irq, 0);
// lock(&hpet->lock);
}
}
break;
case HPET_COUNTER:
if(!hpet->enabled){
if(size == 4)
hpet->counter_offset = (hpet->counter_offset & 0xFFFFFFFF00000000ULL) | val;
else
hpet->counter_offset = val;
dprint("HPET: counter set to %#llx\n", hpet->counter_offset);
}
break;
case HPET_COUNTER + 4:
if(!hpet->enabled){
hpet->counter_offset = (hpet->counter_offset & 0xFFFFFFFF) | ((u64int)val << 32);
dprint("HPET: counter set to %#llx\n", hpet->counter_offset);
}
break;
/* Timer 0 */
case HPET_T0_CFG:
hpet->timer[0].cfg = (size == 4) ?
(hpet->timer[0].cfg & 0xFFFFFFFF00000000ULL) | val : val;
dprint("HPET: T0 cfg=%#llx int_enb=%d periodic=%d\n",
hpet->timer[0].cfg,
(hpet->timer[0].cfg >> 2) & 1,
(hpet->timer[0].cfg >> 3) & 1);
state_changed = 1;
break;
case HPET_T0_CFG + 4:
hpet->timer[0].cfg = (hpet->timer[0].cfg & 0xFFFFFFFF) | ((u64int)val << 32);
break;
case HPET_T0_CMP:
if(size == 4)
hpet->timer[0].cmp = (hpet->timer[0].cmp & 0xFFFFFFFF00000000ULL) | val;
else
hpet->timer[0].cmp = val;
/* If VAL_SET bit is set in periodic mode, this also sets the period */
if((hpet->timer[0].cfg & TN_TYPE) && (hpet->timer[0].cfg & TN_VAL_SET)){
hpet->timer[0].period = hpet->timer[0].cmp;
dprint("HPET: T0 period set to %#llx\n", hpet->timer[0].period);
}
hpet->timer[0].active = 1;
dprint("HPET: T0 cmp=%#llx counter=%#llx active=1 int_enb=%d\n",
hpet->timer[0].cmp, hpet_counter_unlocked(),
(hpet->timer[0].cfg & TN_INT_ENB) ? 1 : 0);
state_changed = 1;
break;
case HPET_T0_CMP + 4:
hpet->timer[0].cmp = (hpet->timer[0].cmp & 0xFFFFFFFF) | ((u64int)val << 32);
hpet->timer[0].active = 1;
state_changed = 1;
break;
/* Timer 1 */
case HPET_T1_CFG:
hpet->timer[1].cfg = (size == 4) ?
(hpet->timer[1].cfg & 0xFFFFFFFF00000000ULL) | val : val;
state_changed = 1;
break;
case HPET_T1_CFG + 4:
hpet->timer[1].cfg = (hpet->timer[1].cfg & 0xFFFFFFFF) | ((u64int)val << 32);
break;
case HPET_T1_CMP:
if(size == 4)
hpet->timer[1].cmp = (hpet->timer[1].cmp & 0xFFFFFFFF00000000ULL) | val;
else
hpet->timer[1].cmp = val;
hpet->timer[1].active = 1;
state_changed = 1;
break;
case HPET_T1_CMP + 4:
hpet->timer[1].cmp = (hpet->timer[1].cmp & 0xFFFFFFFF) | ((u64int)val << 32);
hpet->timer[1].active = 1;
state_changed = 1;
break;
/* Timer 2 */
case HPET_T2_CFG:
hpet->timer[2].cfg = (size == 4) ?
(hpet->timer[2].cfg & 0xFFFFFFFF00000000ULL) | val : val;
state_changed = 1;
break;
case HPET_T2_CFG + 4:
hpet->timer[2].cfg = (hpet->timer[2].cfg & 0xFFFFFFFF) | ((u64int)val << 32);
break;
case HPET_T2_CMP:
if(size == 4)
hpet->timer[2].cmp = (hpet->timer[2].cmp & 0xFFFFFFFF00000000ULL) | val;
else
hpet->timer[2].cmp = val;
hpet->timer[2].active = 1;
state_changed = 1;
break;
case HPET_T2_CMP + 4:
hpet->timer[2].cmp = (hpet->timer[2].cmp & 0xFFFFFFFF) | ((u64int)val << 32);
hpet->timer[2].active = 1;
state_changed = 1;
break;
}
unlock(&hpet->lock);
/* Kick CPU0 to recompute deadlines if state changed */
if(state_changed)
timer_kick();
}
void
hpet_init(void)
{
extern void *vioalloc(ulong);
hpet = vioalloc(sizeof(Hpet));
memset(hpet, 0, sizeof(Hpet));
hpet->timer[0].cfg = TN_SIZE_CAP | TN_PER_CAP;
hpet->timer[1].cfg = 0;
hpet->timer[2].cfg = 0;
hpet->cfg = 0;
hpet->enabled = 0;
hpet->counter_offset = 0;
hpet->time_at_enable = 0;
dprint("HPET: init (shared via vioalloc)\n");
}