| /** |
| * @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, |
| }; |