First figure out the source that is being used as clock
# cat /sys/devices/system/clocksource/clocksource0/available_clocksource arch_sys_counter # cat /sys/devices/system/clocksource/clocksource0/current_clocksource arch_sys_counter # cat /proc/interrupts CPU0 CPU1 1: 0 0 GICv3 29 Edge arch_timer 2: 1117 93 GICv3 30 Edge arch_timer 5: 4558 0 GICv3 44 Level serial_tx 6: 515 0 GICv3 45 Level serial_rx 8: 0 0 GICv3 192 Level xhci-hcd:usb1 9: 0 0 GICv3 194 Level a0020000.sata 10: 0 0 GICv3 196 Level mvri-irq IPI0: 278 554 Rescheduling interrupts IPI1: 4 4 Function call interrupts IPI2: 0 0 CPU stop interrupts IPI3: 0 0 Timer broadcast interrupts IPI4: 0 0 IRQ work interruptsIt is the ARMv-8 arch counter, which is in the IP from ARM and every ARMv8 system should have one. Let's check if there is anything else related to clock from kernel message
# dmesg | grep clock [ 0.000000] clocksource arch_sys_counter: mask: 0xffffffffffffff max_cycles: 0x49cd42e20, max_idle_ns: 440795202120 ns [ 0.000003] sched_clock: 56 bits at 20MHz, resolution 50ns, wraps every 43980 46511100ns [ 0.005003] Registered arch_counter_get_cntvct+0x0/0x10 as sched_clock source [ 0.306657] clocksource jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max _idle_ns: 95563022313750000 ns [ 0.510202] Switched to clocksource arch_sys_counter
It said 56bits counter at 20MHz. Where was this value come from? Let's dig a little bit more. Grep "arch_sys_counter" and follow the calling stack finally I found the following code detect the clock frequency
static void arch_timer_detect_rate(void __iomem *cntbase, struct device_node *np) { /* Who has more than one independent system counter? */ if (arch_timer_rate) return; /* * Try to determine the frequency from the device tree or CNTFRQ, * if ACPI is enabled, get the frequency from CNTFRQ ONLY. */ if (!acpi_disabled || of_property_read_u32(np, "clock-frequency", &arch_timer_rate)) { if (cntbase) arch_timer_rate = readl_relaxed(cntbase + CNTFRQ); else arch_timer_rate = arch_timer_get_cntfrq(); } printk(KERN_INFO "arch_timer_rate is %08x , cntbase is %p. \n", arch_timer_rate, cntbase); /* Check the timer frequency. */ if (arch_timer_rate == 0) pr_warn("Architected timer frequency not available\n"); }
The code above first try to detect the clock frequency from device tree (in which I did not setup the timer entry), then try to get it from arch_timer_get_cntfrq().
vim arch/arm64/include/asm/arch_timer.h static inline u32 arch_timer_get_cntfrq(void) { u32 val; asm volatile("mrs %0, cntfrq_el0" : "=r" (val)); return val; }
The clock frequency is in co-processor cntfrq_el0. Let's grep if the value of arch_timer_rate was printed out
# dmesg | grep arch_timer_rate
[ 0.000000] arch_timer_rate is 01312d00 , cntbase is (null).
Note 0x01312d00 is 20M. Is this value a default value on reset or was it preset by a piece of code? I grep "cntfrq_el0" in my kernel source codes and found nothing, and I tried to grep in u-boot's code to find this
vim u-boot/arch/arm/cpu/armv8/nap/psci.S .global _armadalp_cpu_entry _armadalp_cpu_entry: bl enable_affinity isb /* * Could be EL3/EL2/EL1, Initial State: * Little Endian, MMU Disabled, i/dCache Disabled */ adr x0, vectors switch_el x1, 3f, 2f, 1f 3: msr vbar_el3, x0 mrs x0, scr_el3 orr x0, x0, #0xf /* SCR_EL3.NS|IRQ|FIQ|EA */ msr scr_el3, x0 msr cptr_el3, xzr /* Enable FP/SIMD */ ldr x0, =COUNTER_FREQUENCY msr cntfrq_el0, x0 /* Initialize CNTFRQ */ b 0f 2: msr vbar_el2, x0 mov x0, #0x33ff msr cptr_el2, x0 /* Enable FP/SIMD */ b 0f 1: msr vbar_el1, x0 mov x0, #3 << 20 msr cpacr_el1, x0 /* Enable FP/SIMD */ 0:
Register "cntfrq_el0" was initialized as COUNTER_FREQUENCY which was defined as 20M in a header file. This is the value we used on another chip and I ported the u-boot based on that chip but I never got chance to modify this value. I talked with chip designer and figured out it shoud be 12.5M on our system. Issue fixed by update the constant marco COUNTER_FREQUENCY.
No comments:
Post a Comment