Re: ALSA: hda: Make proper use of timecounter

From: Takashi Iwai
Date: Fri Nov 26 2021 - 03:57:48 EST


On Wed, 24 Nov 2021 23:40:01 +0100,
Thomas Gleixner wrote:
>
> HDA uses a timecounter to read a hardware clock running at 24 MHz. The
> conversion factor is set with a mult value of 125 and a shift value of 0,
> which is not converting the hardware clock to nanoseconds, it is converting
> to 1/3 nanoseconds because the conversion factor from 24Mhz to nanoseconds
> is 125/3. The usage sites divide the "nanoseconds" value returned by
> timecounter_read() by 3 to get a real nanoseconds value.
>
> There is a lengthy comment in azx_timecounter_init() explaining this
> choice. That comment makes blatantly wrong assumptions about how
> timecounters work and what can overflow.
>
> The comment says:
>
> * Applying the 1/3 factor as part of the multiplication
> * requires at least 20 bits for a decent precision, however
> * overflows occur after about 4 hours or less, not a option.
>
> timecounters operate on time deltas between two readouts of a clock and use
> the mult/shift pair to calculate a precise nanoseconds value:
>
> delta_nsec = (delta_clock * mult) >> shift;
>
> The fractional part is also taken into account and preserved to prevent
> accumulated rounding errors. For details see cyclecounter_cyc2ns().
>
> The mult/shift pair has to be chosen so that the multiplication of the
> maximum expected delta value does not result in a 64bit overflow. As the
> counter wraps around on 32bit, the maximum observable delta between two
> reads is (1 << 32) - 1 which is about 178.9 seconds.
>
> That in turn means the maximum multiplication factor which fits into an u32
> will not cause a 64bit overflow ever because it's guaranteed that:
>
> ((1 << 32) - 1) ^ 2 < (1 << 64)
>
> The resulting correct multiplication factor is 2796202667 and the shift
> value is 26, i.e. 26 bit precision. The overflow of the multiplication
> would happen exactly at a clock readout delta of 6597069765 which is way
> after the wrap around of the hardware clock at around 274.8 seconds which
> is off from the claimed 4 hours by more than an order of magnitude.
>
> If the counter ever wraps around the last read value then the calculation
> is off by the number of wrap arounds times 178.9 seconds because the
> overflow cannot be observed.
>
> Use clocks_calc_mult_shift(), which calculates the most accurate mult/shift
> pair based on the given clock frequency, and remove the bogus comment along
> with the divisions at the readout sites.
>
> Fixes: 5d890f591d15 ("ALSA: hda: support for wallclock timestamps")
> Signed-off-by: Thomas Gleixner <tglx@xxxxxxxxxxxxx>

Looks like a nice fix / cleanup. Pierre, could you check it?


thanks,

Takashi


> ---
> sound/hda/hdac_stream.c | 14 ++++----------
> sound/pci/hda/hda_controller.c | 1 -
> sound/soc/intel/skylake/skl-pcm.c | 1 -
> 3 files changed, 4 insertions(+), 12 deletions(-)
>
> --- a/sound/hda/hdac_stream.c
> +++ b/sound/hda/hdac_stream.c
> @@ -534,17 +534,11 @@ static void azx_timecounter_init(struct
> cc->mask = CLOCKSOURCE_MASK(32);
>
> /*
> - * Converting from 24 MHz to ns means applying a 125/3 factor.
> - * To avoid any saturation issues in intermediate operations,
> - * the 125 factor is applied first. The division is applied
> - * last after reading the timecounter value.
> - * Applying the 1/3 factor as part of the multiplication
> - * requires at least 20 bits for a decent precision, however
> - * overflows occur after about 4 hours or less, not a option.
> + * Calculate the optimal mult/shift values. The counter wraps
> + * around after ~178.9 seconds.
> */
> -
> - cc->mult = 125; /* saturation after 195 years */
> - cc->shift = 0;
> + clocks_calc_mult_shift(&cc->mult, &cc->shift, 24000000,
> + NSEC_PER_SEC, 178);
>
> nsec = 0; /* audio time is elapsed time since trigger */
> timecounter_init(tc, cc, nsec);
> --- a/sound/pci/hda/hda_controller.c
> +++ b/sound/pci/hda/hda_controller.c
> @@ -504,7 +504,6 @@ static int azx_get_time_info(struct snd_
> snd_pcm_gettime(substream->runtime, system_ts);
>
> nsec = timecounter_read(&azx_dev->core.tc);
> - nsec = div_u64(nsec, 3); /* can be optimized */
> if (audio_tstamp_config->report_delay)
> nsec = azx_adjust_codec_delay(substream, nsec);
>
> --- a/sound/soc/intel/skylake/skl-pcm.c
> +++ b/sound/soc/intel/skylake/skl-pcm.c
> @@ -1251,7 +1251,6 @@ static int skl_platform_soc_get_time_inf
> snd_pcm_gettime(substream->runtime, system_ts);
>
> nsec = timecounter_read(&hstr->tc);
> - nsec = div_u64(nsec, 3); /* can be optimized */
> if (audio_tstamp_config->report_delay)
> nsec = skl_adjust_codec_delay(substream, nsec);
>
>