/** * @file op_rtc.c * Setup and handling of RTC interrupts * * @remark Copyright 2002 OProfile authors * @remark Read the file COPYING * * @author Bob Montgomery * @author Philippe Elie * @author John Levon */ #include <linux/ioport.h> #include <linux/mc146818rtc.h> #include <asm/ptrace.h> #include "oprofile.h" #include "op_arch.h" #include "op_util.h" #define RTC_IO_PORTS 2 /* not in 2.2 */ #ifndef RTC_IRQ #define RTC_IRQ 8 #endif /* ---------------- RTC handler ------------------ */ static void do_rtc_interrupt(int irq, void * dev_id, struct pt_regs * regs) { uint cpu = op_cpu_id(); unsigned char intr_flags; unsigned long flags; int usermode = user_mode(regs); if ((sysctl.ctr[0].kernel && usermode) || (sysctl.ctr[0].user && !usermode)) return; lock_rtc(flags); /* read and ack the interrupt */ intr_flags = CMOS_READ(RTC_INTR_FLAGS); /* Is this my type of interrupt? */ if (intr_flags & RTC_PF) { op_do_profile(cpu, instruction_pointer(regs), IRQ_ENABLED(regs), 0); } unlock_rtc(flags); return; } static int rtc_setup(void) { unsigned char tmp_control; unsigned long flags; unsigned char tmp_freq_select; unsigned long target; unsigned int exp, freq; lock_rtc(flags); /* disable periodic interrupts */ tmp_control = CMOS_READ(RTC_CONTROL); tmp_control &= ~RTC_PIE; CMOS_WRITE(tmp_control, RTC_CONTROL); CMOS_READ(RTC_INTR_FLAGS); /* Set the frequency for periodic interrupts by finding the * closest power of two within the allowed range. */ target = sysctl.ctr[0].count; exp = 0; while (target > (1 << exp) + ((1 << exp) >> 1)) exp++; freq = 16 - exp; tmp_freq_select = CMOS_READ(RTC_FREQ_SELECT); tmp_freq_select = (tmp_freq_select & 0xf0) | freq; CMOS_WRITE(tmp_freq_select, RTC_FREQ_SELECT); /* Update /proc with the actual frequency. */ sysctl_parms.ctr[0].count = sysctl.ctr[0].count = 1 << exp; unlock_rtc(flags); return 0; } static void rtc_start(void) { unsigned char tmp_control; unsigned long flags; lock_rtc(flags); /* Enable periodic interrupts */ tmp_control = CMOS_READ(RTC_CONTROL); tmp_control |= RTC_PIE; CMOS_WRITE(tmp_control, RTC_CONTROL); /* read the flags register to start interrupts */ CMOS_READ(RTC_INTR_FLAGS); unlock_rtc(flags); } static void rtc_stop(void) { unsigned char tmp_control; unsigned long flags; lock_rtc(flags); /* disable periodic interrupts */ tmp_control = CMOS_READ(RTC_CONTROL); tmp_control &= ~RTC_PIE; CMOS_WRITE(tmp_control, RTC_CONTROL); CMOS_READ(RTC_INTR_FLAGS); unlock_rtc(flags); } static void rtc_start_cpu(uint cpu) { rtc_start(); } static void rtc_stop_cpu(uint cpu) { rtc_stop(); } static int rtc_check_params(void) { int target = sysctl.ctr[0].count; if (check_range(target, OP_MIN_RTC_COUNT, OP_MAX_RTC_COUNT, "RTC value %d is out of range (%d-%d)\n")) return -EINVAL; return 0; } static int rtc_init(void) { /* request_region returns 0 on **failure** */ if (!request_region_check(RTC_PORT(0), RTC_IO_PORTS, "oprofile")) { printk(KERN_ERR "oprofile: can't get RTC I/O Ports\n"); return -EBUSY; } /* request_irq returns 0 on **success** */ if (request_irq(RTC_IRQ, do_rtc_interrupt, SA_INTERRUPT, "oprofile", NULL)) { printk(KERN_ERR "oprofile: IRQ%d busy \n", RTC_IRQ); release_region(RTC_PORT(0), RTC_IO_PORTS); return -EBUSY; } return 0; } static void rtc_deinit(void) { free_irq(RTC_IRQ, NULL); release_region(RTC_PORT(0), RTC_IO_PORTS); } static int rtc_add_sysctls(ctl_table * next) { *next = ((ctl_table) { 1, "rtc_value", &sysctl_parms.ctr[0].count, sizeof(int), 0600, NULL, lproc_dointvec, NULL, }); return 0; } static void rtc_remove_sysctls(ctl_table * next) { /* nothing to do */ } struct op_int_operations op_rtc_ops = { init: rtc_init, deinit: rtc_deinit, add_sysctls: rtc_add_sysctls, remove_sysctls: rtc_remove_sysctls, check_params: rtc_check_params, setup: rtc_setup, start: rtc_start, stop: rtc_stop, start_cpu: rtc_start_cpu, stop_cpu: rtc_stop_cpu, };