| From foo@baz Wed Dec 6 18:04:41 CET 2017 |
| From: Ard Biesheuvel <ard.biesheuvel@linaro.org> |
| Date: Mon, 16 Oct 2017 16:28:38 +0100 |
| Subject: clocksource/drivers/arm_arch_timer: Validate CNTFRQ after enabling frame |
| |
| From: Ard Biesheuvel <ard.biesheuvel@linaro.org> |
| |
| |
| [ Upstream commit 21492e1333a0d07af6968667f128e19088cf5ead ] |
| |
| The ACPI GTDT code validates the CNTFRQ field of each MMIO timer |
| frame against the CNTFRQ system register of the current CPU, to |
| ensure that they are equal, which is mandated by the architecture. |
| |
| However, reading the CNTFRQ field of a frame is not possible until |
| the RFRQ bit in the frame's CNTACRn register is set, and doing so |
| before that willl produce the following error: |
| |
| arch_timer: [Firmware Bug]: CNTFRQ mismatch: frame @ 0x00000000e0be0000: (0x00000000), CPU: (0x0ee6b280) |
| arch_timer: Disabling MMIO timers due to CNTFRQ mismatch |
| arch_timer: Failed to initialize memory-mapped timer. |
| |
| The reason is that the CNTFRQ field is RES0 if access is not enabled. |
| |
| So move the validation of CNTFRQ into the loop that iterates over the |
| timers to find the best frame, but defer it until after we have selected |
| the best frame, which should also have enabled the RFRQ bit. |
| |
| Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org> |
| Signed-off-by: Mark Rutland <mark.rutland@arm.com> |
| Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org> |
| Signed-off-by: Sasha Levin <alexander.levin@verizon.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| drivers/clocksource/arm_arch_timer.c | 38 +++++++++++++++++++---------------- |
| 1 file changed, 21 insertions(+), 17 deletions(-) |
| |
| --- a/drivers/clocksource/arm_arch_timer.c |
| +++ b/drivers/clocksource/arm_arch_timer.c |
| @@ -1268,10 +1268,6 @@ arch_timer_mem_find_best_frame(struct ar |
| |
| iounmap(cntctlbase); |
| |
| - if (!best_frame) |
| - pr_err("Unable to find a suitable frame in timer @ %pa\n", |
| - &timer_mem->cntctlbase); |
| - |
| return best_frame; |
| } |
| |
| @@ -1372,6 +1368,8 @@ static int __init arch_timer_mem_of_init |
| |
| frame = arch_timer_mem_find_best_frame(timer_mem); |
| if (!frame) { |
| + pr_err("Unable to find a suitable frame in timer @ %pa\n", |
| + &timer_mem->cntctlbase); |
| ret = -EINVAL; |
| goto out; |
| } |
| @@ -1420,7 +1418,7 @@ arch_timer_mem_verify_cntfrq(struct arch |
| static int __init arch_timer_mem_acpi_init(int platform_timer_count) |
| { |
| struct arch_timer_mem *timers, *timer; |
| - struct arch_timer_mem_frame *frame; |
| + struct arch_timer_mem_frame *frame, *best_frame = NULL; |
| int timer_count, i, ret = 0; |
| |
| timers = kcalloc(platform_timer_count, sizeof(*timers), |
| @@ -1432,14 +1430,6 @@ static int __init arch_timer_mem_acpi_in |
| if (ret || !timer_count) |
| goto out; |
| |
| - for (i = 0; i < timer_count; i++) { |
| - ret = arch_timer_mem_verify_cntfrq(&timers[i]); |
| - if (ret) { |
| - pr_err("Disabling MMIO timers due to CNTFRQ mismatch\n"); |
| - goto out; |
| - } |
| - } |
| - |
| /* |
| * While unlikely, it's theoretically possible that none of the frames |
| * in a timer expose the combination of feature we want. |
| @@ -1448,12 +1438,26 @@ static int __init arch_timer_mem_acpi_in |
| timer = &timers[i]; |
| |
| frame = arch_timer_mem_find_best_frame(timer); |
| - if (frame) |
| - break; |
| + if (!best_frame) |
| + best_frame = frame; |
| + |
| + ret = arch_timer_mem_verify_cntfrq(timer); |
| + if (ret) { |
| + pr_err("Disabling MMIO timers due to CNTFRQ mismatch\n"); |
| + goto out; |
| + } |
| + |
| + if (!best_frame) /* implies !frame */ |
| + /* |
| + * Only complain about missing suitable frames if we |
| + * haven't already found one in a previous iteration. |
| + */ |
| + pr_err("Unable to find a suitable frame in timer @ %pa\n", |
| + &timer->cntctlbase); |
| } |
| |
| - if (frame) |
| - ret = arch_timer_mem_frame_register(frame); |
| + if (best_frame) |
| + ret = arch_timer_mem_frame_register(best_frame); |
| out: |
| kfree(timers); |
| return ret; |