From 16745946e096e6c7fe26fc73e411bafc8c3e05ff Mon Sep 17 00:00:00 2001
From: Martin Stein <martin.stein@genode-labs.com>
Date: Wed, 23 Aug 2017 18:19:32 +0200
Subject: [PATCH] hw pit: fix precision reduction to milliseconds

Due to the simplicity of the algorithm that translated from timer ticks
to time, we lost microseconds precision although the timer allows for it.

Ref #2400
---
 repos/base-hw/src/core/spec/x86_64/timer.cc | 25 ++++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

diff --git a/repos/base-hw/src/core/spec/x86_64/timer.cc b/repos/base-hw/src/core/spec/x86_64/timer.cc
index 7ce390d3bc..9d3375d76d 100644
--- a/repos/base-hw/src/core/spec/x86_64/timer.cc
+++ b/repos/base-hw/src/core/spec/x86_64/timer.cc
@@ -64,6 +64,7 @@ Timer_driver::Timer_driver(unsigned)
 
 	/* Calculate timer frequency */
 	ticks_per_ms = pit_calc_timer_freq();
+	assert(ticks_per_ms >= 1000);
 }
 
 
@@ -84,7 +85,29 @@ void Timer::_start_one_shot(time_t const ticks) {
 
 
 time_t Timer::_ticks_to_us(time_t const ticks) const {
-	return (ticks / _driver.ticks_per_ms) * 1000; }
+
+	/*
+	 * If we would do the translation with one division and
+	 * multiplication over the whole argument, we would loose
+	 * microseconds granularity although the timer frequency would
+	 * allow for such granularity. Thus, we treat the most significant
+	 * half and the least significant half of the argument separate.
+	 * Each half is shifted to the best bit position for the
+	 * translation, then translated, and then shifted back.
+	 */
+	enum {
+		HALF_WIDTH = (sizeof(time_t) << 2),
+		MSB_MASK  = ~0UL << HALF_WIDTH,
+		LSB_MASK  = ~0UL >> HALF_WIDTH,
+		MSB_RSHIFT = 10,
+		LSB_LSHIFT = HALF_WIDTH - MSB_RSHIFT,
+	};
+	time_t const msb = ((((ticks & MSB_MASK) >> MSB_RSHIFT)
+	                     * 1000) / _driver.ticks_per_ms) << MSB_RSHIFT;
+	time_t const lsb = ((((ticks & LSB_MASK) << LSB_LSHIFT)
+                         * 1000) / _driver.ticks_per_ms) >> LSB_LSHIFT;
+	return msb + lsb;
+}
 
 
 time_t Timer::us_to_ticks(time_t const us) const {