shithub: vmxsmp

ref: 2c9eeaf3cfd204ee49d676e8716445bb563249cd
dir: /hpet.c/

View raw version
/*
 * 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");
}