1433d6423SLionel Sambuc 2433d6423SLionel Sambuc /* i386-specific clock functions. */ 3433d6423SLionel Sambuc 4433d6423SLionel Sambuc #include <machine/ports.h> 5433d6423SLionel Sambuc #include <minix/portio.h> 6433d6423SLionel Sambuc 7433d6423SLionel Sambuc #include "kernel/kernel.h" 8433d6423SLionel Sambuc 9433d6423SLionel Sambuc #include "kernel/clock.h" 10433d6423SLionel Sambuc #include "kernel/interrupt.h" 11433d6423SLionel Sambuc #include <minix/u64.h> 12433d6423SLionel Sambuc #include "glo.h" 13433d6423SLionel Sambuc #include "kernel/profile.h" 14433d6423SLionel Sambuc 15433d6423SLionel Sambuc 16433d6423SLionel Sambuc #ifdef USE_APIC 17433d6423SLionel Sambuc #include "apic.h" 18433d6423SLionel Sambuc #endif 19433d6423SLionel Sambuc 20433d6423SLionel Sambuc #include "kernel/spinlock.h" 21433d6423SLionel Sambuc 22433d6423SLionel Sambuc #ifdef CONFIG_SMP 23433d6423SLionel Sambuc #include "kernel/smp.h" 24433d6423SLionel Sambuc #endif 25433d6423SLionel Sambuc 26433d6423SLionel Sambuc #define CLOCK_ACK_BIT 0x80 /* PS/2 clock interrupt acknowledge bit */ 27433d6423SLionel Sambuc 28433d6423SLionel Sambuc /* Clock parameters. */ 29433d6423SLionel Sambuc #define COUNTER_FREQ (2*TIMER_FREQ) /* counter frequency using square wave */ 30433d6423SLionel Sambuc #define LATCH_COUNT 0x00 /* cc00xxxx, c = channel, x = any */ 31433d6423SLionel Sambuc #define SQUARE_WAVE 0x36 /* ccaammmb, a = access, m = mode, b = BCD */ 32433d6423SLionel Sambuc /* 11x11, 11 = LSB then MSB, x11 = sq wave */ 33433d6423SLionel Sambuc #define TIMER_FREQ 1193182 /* clock frequency for timer in PC and AT */ 34433d6423SLionel Sambuc #define TIMER_COUNT(freq) (TIMER_FREQ/(freq)) /* initial value for counter*/ 35433d6423SLionel Sambuc 36433d6423SLionel Sambuc static irq_hook_t pic_timer_hook; /* interrupt handler hook */ 37433d6423SLionel Sambuc 38433d6423SLionel Sambuc static unsigned probe_ticks; 39433d6423SLionel Sambuc static u64_t tsc0, tsc1; 40433d6423SLionel Sambuc #define PROBE_TICKS (system_hz / 10) 41433d6423SLionel Sambuc 42433d6423SLionel Sambuc static unsigned tsc_per_ms[CONFIG_MAX_CPUS]; 43433d6423SLionel Sambuc 44433d6423SLionel Sambuc /*===========================================================================* 45433d6423SLionel Sambuc * init_8235A_timer * 46433d6423SLionel Sambuc *===========================================================================*/ 47433d6423SLionel Sambuc int init_8253A_timer(const unsigned freq) 48433d6423SLionel Sambuc { 49433d6423SLionel Sambuc /* Initialize channel 0 of the 8253A timer to, e.g., 60 Hz, 50433d6423SLionel Sambuc * and register the CLOCK task's interrupt handler to be run 51433d6423SLionel Sambuc * on every clock tick. 52433d6423SLionel Sambuc */ 53433d6423SLionel Sambuc outb(TIMER_MODE, SQUARE_WAVE); /* run continuously */ 54433d6423SLionel Sambuc outb(TIMER0, (TIMER_COUNT(freq) & 0xff)); /* timer low byte */ 55433d6423SLionel Sambuc outb(TIMER0, TIMER_COUNT(freq) >> 8); /* timer high byte */ 56433d6423SLionel Sambuc 57433d6423SLionel Sambuc return OK; 58433d6423SLionel Sambuc } 59433d6423SLionel Sambuc 60433d6423SLionel Sambuc /*===========================================================================* 61433d6423SLionel Sambuc * stop_8235A_timer * 62433d6423SLionel Sambuc *===========================================================================*/ 63433d6423SLionel Sambuc void stop_8253A_timer(void) 64433d6423SLionel Sambuc { 65433d6423SLionel Sambuc /* Reset the clock to the BIOS rate. (For rebooting.) */ 66433d6423SLionel Sambuc outb(TIMER_MODE, 0x36); 67433d6423SLionel Sambuc outb(TIMER0, 0); 68433d6423SLionel Sambuc outb(TIMER0, 0); 69433d6423SLionel Sambuc } 70433d6423SLionel Sambuc 71433d6423SLionel Sambuc void arch_timer_int_handler(void) 72433d6423SLionel Sambuc { 73433d6423SLionel Sambuc } 74433d6423SLionel Sambuc 75433d6423SLionel Sambuc static int calib_cpu_handler(irq_hook_t * UNUSED(hook)) 76433d6423SLionel Sambuc { 77433d6423SLionel Sambuc u64_t tsc; 78433d6423SLionel Sambuc 79433d6423SLionel Sambuc probe_ticks++; 80433d6423SLionel Sambuc read_tsc_64(&tsc); 81433d6423SLionel Sambuc 82433d6423SLionel Sambuc 83433d6423SLionel Sambuc if (probe_ticks == 1) { 84433d6423SLionel Sambuc tsc0 = tsc; 85433d6423SLionel Sambuc } 86433d6423SLionel Sambuc else if (probe_ticks == PROBE_TICKS) { 87433d6423SLionel Sambuc tsc1 = tsc; 88433d6423SLionel Sambuc } 89433d6423SLionel Sambuc 90433d6423SLionel Sambuc /* just in case we are in an SMP single cpu fallback mode */ 91433d6423SLionel Sambuc BKL_UNLOCK(); 92433d6423SLionel Sambuc return 1; 93433d6423SLionel Sambuc } 94433d6423SLionel Sambuc 95433d6423SLionel Sambuc static void estimate_cpu_freq(void) 96433d6423SLionel Sambuc { 97433d6423SLionel Sambuc u64_t tsc_delta; 98433d6423SLionel Sambuc u64_t cpu_freq; 99433d6423SLionel Sambuc 100433d6423SLionel Sambuc irq_hook_t calib_cpu; 101433d6423SLionel Sambuc 102433d6423SLionel Sambuc /* set the probe, we use the legacy timer, IRQ 0 */ 103433d6423SLionel Sambuc put_irq_handler(&calib_cpu, CLOCK_IRQ, calib_cpu_handler); 104433d6423SLionel Sambuc 105433d6423SLionel Sambuc /* just in case we are in an SMP single cpu fallback mode */ 106433d6423SLionel Sambuc BKL_UNLOCK(); 107433d6423SLionel Sambuc /* set the PIC timer to get some time */ 108433d6423SLionel Sambuc intr_enable(); 109433d6423SLionel Sambuc 110433d6423SLionel Sambuc /* loop for some time to get a sample */ 111433d6423SLionel Sambuc while(probe_ticks < PROBE_TICKS) { 112433d6423SLionel Sambuc intr_enable(); 113433d6423SLionel Sambuc } 114433d6423SLionel Sambuc 115433d6423SLionel Sambuc intr_disable(); 116433d6423SLionel Sambuc /* just in case we are in an SMP single cpu fallback mode */ 117433d6423SLionel Sambuc BKL_LOCK(); 118433d6423SLionel Sambuc 119433d6423SLionel Sambuc /* remove the probe */ 120433d6423SLionel Sambuc rm_irq_handler(&calib_cpu); 121433d6423SLionel Sambuc 122433d6423SLionel Sambuc tsc_delta = tsc1 - tsc0; 123433d6423SLionel Sambuc 124433d6423SLionel Sambuc cpu_freq = (tsc_delta / (PROBE_TICKS - 1)) * system_hz; 125433d6423SLionel Sambuc cpu_set_freq(cpuid, cpu_freq); 126433d6423SLionel Sambuc cpu_info[cpuid].freq = (unsigned long)(cpu_freq / 1000000); 127433d6423SLionel Sambuc BOOT_VERBOSE(cpu_print_freq(cpuid)); 128433d6423SLionel Sambuc } 129433d6423SLionel Sambuc 130433d6423SLionel Sambuc int init_local_timer(unsigned freq) 131433d6423SLionel Sambuc { 132433d6423SLionel Sambuc #ifdef USE_APIC 133433d6423SLionel Sambuc /* if we know the address, lapic is enabled and we should use it */ 134433d6423SLionel Sambuc if (lapic_addr) { 135433d6423SLionel Sambuc unsigned cpu = cpuid; 136433d6423SLionel Sambuc tsc_per_ms[cpu] = (unsigned long)(cpu_get_freq(cpu) / 1000); 137433d6423SLionel Sambuc lapic_set_timer_one_shot(1000000 / system_hz); 138433d6423SLionel Sambuc } else { 139*da9af514SLionel Sambuc DEBUGBASIC(("Initiating legacy i8253 timer\n")); 140433d6423SLionel Sambuc #else 141433d6423SLionel Sambuc { 142433d6423SLionel Sambuc #endif 143433d6423SLionel Sambuc init_8253A_timer(freq); 144433d6423SLionel Sambuc estimate_cpu_freq(); 145433d6423SLionel Sambuc /* always only 1 cpu in the system */ 146433d6423SLionel Sambuc tsc_per_ms[0] = (unsigned long)(cpu_get_freq(0) / 1000); 147433d6423SLionel Sambuc } 148433d6423SLionel Sambuc 149433d6423SLionel Sambuc return 0; 150433d6423SLionel Sambuc } 151433d6423SLionel Sambuc 152433d6423SLionel Sambuc void stop_local_timer(void) 153433d6423SLionel Sambuc { 154433d6423SLionel Sambuc #ifdef USE_APIC 155433d6423SLionel Sambuc if (lapic_addr) { 156433d6423SLionel Sambuc lapic_stop_timer(); 157433d6423SLionel Sambuc apic_eoi(); 158433d6423SLionel Sambuc } else 159433d6423SLionel Sambuc #endif 160433d6423SLionel Sambuc { 161433d6423SLionel Sambuc stop_8253A_timer(); 162433d6423SLionel Sambuc } 163433d6423SLionel Sambuc } 164433d6423SLionel Sambuc 165433d6423SLionel Sambuc void restart_local_timer(void) 166433d6423SLionel Sambuc { 167433d6423SLionel Sambuc #ifdef USE_APIC 168433d6423SLionel Sambuc if (lapic_addr) { 169433d6423SLionel Sambuc lapic_restart_timer(); 170433d6423SLionel Sambuc } 171433d6423SLionel Sambuc #endif 172433d6423SLionel Sambuc } 173433d6423SLionel Sambuc 174433d6423SLionel Sambuc int register_local_timer_handler(const irq_handler_t handler) 175433d6423SLionel Sambuc { 176433d6423SLionel Sambuc #ifdef USE_APIC 177433d6423SLionel Sambuc if (lapic_addr) { 178433d6423SLionel Sambuc /* Using APIC, it is configured in apic_idt_init() */ 179433d6423SLionel Sambuc BOOT_VERBOSE(printf("Using LAPIC timer as tick source\n")); 180433d6423SLionel Sambuc } else 181433d6423SLionel Sambuc #endif 182433d6423SLionel Sambuc { 183433d6423SLionel Sambuc /* Using PIC, Initialize the CLOCK's interrupt hook. */ 184433d6423SLionel Sambuc pic_timer_hook.proc_nr_e = NONE; 185433d6423SLionel Sambuc pic_timer_hook.irq = CLOCK_IRQ; 186433d6423SLionel Sambuc 187433d6423SLionel Sambuc put_irq_handler(&pic_timer_hook, CLOCK_IRQ, handler); 188433d6423SLionel Sambuc } 189433d6423SLionel Sambuc 190433d6423SLionel Sambuc return 0; 191433d6423SLionel Sambuc } 192433d6423SLionel Sambuc 193433d6423SLionel Sambuc void cycles_accounting_init(void) 194433d6423SLionel Sambuc { 195433d6423SLionel Sambuc #ifdef CONFIG_SMP 196433d6423SLionel Sambuc unsigned cpu = cpuid; 197433d6423SLionel Sambuc #endif 198433d6423SLionel Sambuc 199433d6423SLionel Sambuc read_tsc_64(get_cpu_var_ptr(cpu, tsc_ctr_switch)); 200433d6423SLionel Sambuc 201433d6423SLionel Sambuc get_cpu_var(cpu, cpu_last_tsc) = 0; 202433d6423SLionel Sambuc get_cpu_var(cpu, cpu_last_idle) = 0; 203433d6423SLionel Sambuc } 204433d6423SLionel Sambuc 205433d6423SLionel Sambuc void context_stop(struct proc * p) 206433d6423SLionel Sambuc { 207433d6423SLionel Sambuc u64_t tsc, tsc_delta; 208433d6423SLionel Sambuc u64_t * __tsc_ctr_switch = get_cpulocal_var_ptr(tsc_ctr_switch); 209433d6423SLionel Sambuc #ifdef CONFIG_SMP 210433d6423SLionel Sambuc unsigned cpu = cpuid; 211433d6423SLionel Sambuc int must_bkl_unlock = 0; 212433d6423SLionel Sambuc 213433d6423SLionel Sambuc /* 214433d6423SLionel Sambuc * This function is called only if we switch from kernel to user or idle 215433d6423SLionel Sambuc * or back. Therefore this is a perfect location to place the big kernel 216433d6423SLionel Sambuc * lock which will hopefully disappear soon. 217433d6423SLionel Sambuc * 218433d6423SLionel Sambuc * If we stop accounting for KERNEL we must unlock the BKL. If account 219433d6423SLionel Sambuc * for IDLE we must not hold the lock 220433d6423SLionel Sambuc */ 221433d6423SLionel Sambuc if (p == proc_addr(KERNEL)) { 222433d6423SLionel Sambuc u64_t tmp; 223433d6423SLionel Sambuc 224433d6423SLionel Sambuc read_tsc_64(&tsc); 225433d6423SLionel Sambuc tmp = tsc - *__tsc_ctr_switch; 226433d6423SLionel Sambuc kernel_ticks[cpu] = kernel_ticks[cpu] + tmp; 227433d6423SLionel Sambuc p->p_cycles = p->p_cycles + tmp; 228433d6423SLionel Sambuc must_bkl_unlock = 1; 229433d6423SLionel Sambuc } else { 230433d6423SLionel Sambuc u64_t bkl_tsc; 231433d6423SLionel Sambuc atomic_t succ; 232433d6423SLionel Sambuc 233433d6423SLionel Sambuc read_tsc_64(&bkl_tsc); 234433d6423SLionel Sambuc /* this only gives a good estimate */ 235433d6423SLionel Sambuc succ = big_kernel_lock.val; 236433d6423SLionel Sambuc 237433d6423SLionel Sambuc BKL_LOCK(); 238433d6423SLionel Sambuc 239433d6423SLionel Sambuc read_tsc_64(&tsc); 240433d6423SLionel Sambuc 241433d6423SLionel Sambuc bkl_ticks[cpu] = bkl_ticks[cpu] + tsc - bkl_tsc; 242433d6423SLionel Sambuc bkl_tries[cpu]++; 243433d6423SLionel Sambuc bkl_succ[cpu] += !(!(succ == 0)); 244433d6423SLionel Sambuc 245433d6423SLionel Sambuc p->p_cycles = p->p_cycles + tsc - *__tsc_ctr_switch; 246433d6423SLionel Sambuc 247433d6423SLionel Sambuc #ifdef CONFIG_SMP 248433d6423SLionel Sambuc /* 249433d6423SLionel Sambuc * Since at the time we got a scheduling IPI we might have been 250433d6423SLionel Sambuc * waiting for BKL already, we may miss it due to a similar IPI to 251433d6423SLionel Sambuc * the cpu which is already waiting for us to handle its. This 252433d6423SLionel Sambuc * results in a live-lock of these two cpus. 253433d6423SLionel Sambuc * 254433d6423SLionel Sambuc * Therefore we always check if there is one pending and if so, 255433d6423SLionel Sambuc * we handle it straight away so the other cpu can continue and 256433d6423SLionel Sambuc * we do not deadlock. 257433d6423SLionel Sambuc */ 258433d6423SLionel Sambuc smp_sched_handler(); 259433d6423SLionel Sambuc #endif 260433d6423SLionel Sambuc } 261433d6423SLionel Sambuc #else 262433d6423SLionel Sambuc read_tsc_64(&tsc); 263433d6423SLionel Sambuc p->p_cycles = p->p_cycles + tsc - *__tsc_ctr_switch; 264433d6423SLionel Sambuc #endif 265433d6423SLionel Sambuc 266433d6423SLionel Sambuc tsc_delta = tsc - *__tsc_ctr_switch; 267433d6423SLionel Sambuc 268433d6423SLionel Sambuc if (kbill_ipc) { 269433d6423SLionel Sambuc kbill_ipc->p_kipc_cycles = 270433d6423SLionel Sambuc kbill_ipc->p_kipc_cycles + tsc_delta; 271433d6423SLionel Sambuc kbill_ipc = NULL; 272433d6423SLionel Sambuc } 273433d6423SLionel Sambuc 274433d6423SLionel Sambuc if (kbill_kcall) { 275433d6423SLionel Sambuc kbill_kcall->p_kcall_cycles = 276433d6423SLionel Sambuc kbill_kcall->p_kcall_cycles + tsc_delta; 277433d6423SLionel Sambuc kbill_kcall = NULL; 278433d6423SLionel Sambuc } 279433d6423SLionel Sambuc 280433d6423SLionel Sambuc /* 281433d6423SLionel Sambuc * deduct the just consumed cpu cycles from the cpu time left for this 282433d6423SLionel Sambuc * process during its current quantum. Skip IDLE and other pseudo kernel 283433d6423SLionel Sambuc * tasks 284433d6423SLionel Sambuc */ 285433d6423SLionel Sambuc if (p->p_endpoint >= 0) { 286433d6423SLionel Sambuc #if DEBUG_RACE 287433d6423SLionel Sambuc p->p_cpu_time_left = 0; 288433d6423SLionel Sambuc #else 289433d6423SLionel Sambuc /* if (tsc_delta < p->p_cpu_time_left) in 64bit */ 290433d6423SLionel Sambuc if (ex64hi(tsc_delta) < ex64hi(p->p_cpu_time_left) || 291433d6423SLionel Sambuc (ex64hi(tsc_delta) == ex64hi(p->p_cpu_time_left) && 292433d6423SLionel Sambuc ex64lo(tsc_delta) < ex64lo(p->p_cpu_time_left))) 293433d6423SLionel Sambuc p->p_cpu_time_left = p->p_cpu_time_left - tsc_delta; 294433d6423SLionel Sambuc else { 295433d6423SLionel Sambuc p->p_cpu_time_left = 0; 296433d6423SLionel Sambuc } 297433d6423SLionel Sambuc #endif 298433d6423SLionel Sambuc } 299433d6423SLionel Sambuc 300433d6423SLionel Sambuc *__tsc_ctr_switch = tsc; 301433d6423SLionel Sambuc 302433d6423SLionel Sambuc #ifdef CONFIG_SMP 303433d6423SLionel Sambuc if(must_bkl_unlock) { 304433d6423SLionel Sambuc BKL_UNLOCK(); 305433d6423SLionel Sambuc } 306433d6423SLionel Sambuc #endif 307433d6423SLionel Sambuc } 308433d6423SLionel Sambuc 309433d6423SLionel Sambuc void context_stop_idle(void) 310433d6423SLionel Sambuc { 311433d6423SLionel Sambuc int is_idle; 312433d6423SLionel Sambuc #ifdef CONFIG_SMP 313433d6423SLionel Sambuc unsigned cpu = cpuid; 314433d6423SLionel Sambuc #endif 315433d6423SLionel Sambuc 316433d6423SLionel Sambuc is_idle = get_cpu_var(cpu, cpu_is_idle); 317433d6423SLionel Sambuc get_cpu_var(cpu, cpu_is_idle) = 0; 318433d6423SLionel Sambuc 319433d6423SLionel Sambuc context_stop(get_cpulocal_var_ptr(idle_proc)); 320433d6423SLionel Sambuc 321433d6423SLionel Sambuc if (is_idle) 322433d6423SLionel Sambuc restart_local_timer(); 323433d6423SLionel Sambuc #if SPROFILE 324433d6423SLionel Sambuc if (sprofiling) 325433d6423SLionel Sambuc get_cpulocal_var(idle_interrupted) = 1; 326433d6423SLionel Sambuc #endif 327433d6423SLionel Sambuc } 328433d6423SLionel Sambuc 329433d6423SLionel Sambuc u64_t ms_2_cpu_time(unsigned ms) 330433d6423SLionel Sambuc { 331433d6423SLionel Sambuc return (u64_t)tsc_per_ms[cpuid] * ms; 332433d6423SLionel Sambuc } 333433d6423SLionel Sambuc 334433d6423SLionel Sambuc unsigned cpu_time_2_ms(u64_t cpu_time) 335433d6423SLionel Sambuc { 336433d6423SLionel Sambuc return (unsigned long)(cpu_time / tsc_per_ms[cpuid]); 337433d6423SLionel Sambuc } 338433d6423SLionel Sambuc 339433d6423SLionel Sambuc short cpu_load(void) 340433d6423SLionel Sambuc { 341433d6423SLionel Sambuc u64_t current_tsc, *current_idle; 342433d6423SLionel Sambuc u64_t tsc_delta, idle_delta, busy; 343433d6423SLionel Sambuc struct proc *idle; 344433d6423SLionel Sambuc short load; 345433d6423SLionel Sambuc #ifdef CONFIG_SMP 346433d6423SLionel Sambuc unsigned cpu = cpuid; 347433d6423SLionel Sambuc #endif 348433d6423SLionel Sambuc 349433d6423SLionel Sambuc u64_t *last_tsc, *last_idle; 350433d6423SLionel Sambuc 351433d6423SLionel Sambuc last_tsc = get_cpu_var_ptr(cpu, cpu_last_tsc); 352433d6423SLionel Sambuc last_idle = get_cpu_var_ptr(cpu, cpu_last_idle); 353433d6423SLionel Sambuc 354433d6423SLionel Sambuc idle = get_cpu_var_ptr(cpu, idle_proc);; 355433d6423SLionel Sambuc read_tsc_64(¤t_tsc); 356433d6423SLionel Sambuc current_idle = &idle->p_cycles; /* ptr to idle proc */ 357433d6423SLionel Sambuc 358433d6423SLionel Sambuc /* calculate load since last cpu_load invocation */ 359433d6423SLionel Sambuc if (*last_tsc) { 360433d6423SLionel Sambuc tsc_delta = current_tsc - *last_tsc; 361433d6423SLionel Sambuc idle_delta = *current_idle - *last_idle; 362433d6423SLionel Sambuc 363433d6423SLionel Sambuc busy = tsc_delta - idle_delta; 364433d6423SLionel Sambuc busy = busy * 100; 365433d6423SLionel Sambuc load = ex64lo(busy / tsc_delta); 366433d6423SLionel Sambuc 367433d6423SLionel Sambuc if (load > 100) 368433d6423SLionel Sambuc load = 100; 369433d6423SLionel Sambuc } else 370433d6423SLionel Sambuc load = 0; 371433d6423SLionel Sambuc 372433d6423SLionel Sambuc *last_tsc = current_tsc; 373433d6423SLionel Sambuc *last_idle = *current_idle; 374433d6423SLionel Sambuc return load; 375433d6423SLionel Sambuc } 376433d6423SLionel Sambuc 377433d6423SLionel Sambuc void busy_delay_ms(int ms) 378433d6423SLionel Sambuc { 379433d6423SLionel Sambuc u64_t cycles = ms_2_cpu_time(ms), tsc0, tsc, tsc1; 380433d6423SLionel Sambuc read_tsc_64(&tsc0); 381433d6423SLionel Sambuc tsc1 = tsc0 + cycles; 382433d6423SLionel Sambuc do { read_tsc_64(&tsc); } while(tsc < tsc1); 383433d6423SLionel Sambuc return; 384433d6423SLionel Sambuc } 385433d6423SLionel Sambuc 386