On Mon, Jul 30, 2018 at 9:24 AM, Peter Maydell <peter.mayd...@linaro.org> wrote:
> In the MPS2 FPGAIO, PSCNTR is a free-running downcounter with
> a reload value configured via the PRESCALE register, and
> COUNTER counts up by 1 every time PSCNTR reaches zero.
> Implement these counters.
>
> We can just increment the counters migration subsection's
> version ID because we only added it in the previous commit,
> so no released QEMU versions will be using it.
>
> Signed-off-by: Peter Maydell <peter.mayd...@linaro.org>

Reviewed-by: Alistair Francis <alistair.fran...@wdc.com>

Alistair

> ---
>  include/hw/misc/mps2-fpgaio.h |  6 +++
>  hw/misc/mps2-fpgaio.c         | 97 +++++++++++++++++++++++++++++++++--
>  2 files changed, 99 insertions(+), 4 deletions(-)
>
> diff --git a/include/hw/misc/mps2-fpgaio.h b/include/hw/misc/mps2-fpgaio.h
> index ec057d38c76..69e265cd4b2 100644
> --- a/include/hw/misc/mps2-fpgaio.h
> +++ b/include/hw/misc/mps2-fpgaio.h
> @@ -37,6 +37,12 @@ typedef struct {
>      uint32_t prescale;
>      uint32_t misc;
>
> +    /* QEMU_CLOCK_VIRTUAL time at which counter and pscntr were last synced 
> */
> +    int64_t pscntr_sync_ticks;
> +    /* Values of COUNTER and PSCNTR at time pscntr_sync_ticks */
> +    uint32_t counter;
> +    uint32_t pscntr;
> +
>      uint32_t prescale_clk;
>
>      /* These hold the CLOCK_VIRTUAL ns tick when the CLK1HZ/CLK100HZ was 
> zero */
> diff --git a/hw/misc/mps2-fpgaio.c b/hw/misc/mps2-fpgaio.c
> index bbc28f641f0..5cf10ebd66a 100644
> --- a/hw/misc/mps2-fpgaio.c
> +++ b/hw/misc/mps2-fpgaio.c
> @@ -43,6 +43,77 @@ static int64_t tickoff_from_counter(int64_t now, uint32_t 
> count, int frq)
>      return now - muldiv64(count, NANOSECONDS_PER_SECOND, frq);
>  }
>
> +static void resync_counter(MPS2FPGAIO *s)
> +{
> +    /*
> +     * Update s->counter and s->pscntr to their true current values
> +     * by calculating how many times PSCNTR has ticked since the
> +     * last time we did a resync.
> +     */
> +    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> +    int64_t elapsed = now - s->pscntr_sync_ticks;
> +
> +    /*
> +     * Round elapsed down to a whole number of PSCNTR ticks, so we don't
> +     * lose time if we do multiple resyncs in a single tick.
> +     */
> +    uint64_t ticks = muldiv64(elapsed, s->prescale_clk, 
> NANOSECONDS_PER_SECOND);
> +
> +    /*
> +     * Work out what PSCNTR and COUNTER have moved to. We assume that
> +     * PSCNTR reloads from PRESCALE one tick-period after it hits zero,
> +     * and that COUNTER increments at the same moment.
> +     */
> +    if (ticks == 0) {
> +        /* We haven't ticked since the last time we were asked */
> +        return;
> +    } else if (ticks < s->pscntr) {
> +        /* We haven't yet reached zero, just reduce the PSCNTR */
> +        s->pscntr -= ticks;
> +    } else {
> +        if (s->prescale == 0) {
> +            /*
> +             * If the reload value is zero then the PSCNTR will stick
> +             * at zero once it reaches it, and so we will increment
> +             * COUNTER every tick after that.
> +             */
> +            s->counter += ticks - s->pscntr;
> +            s->pscntr = 0;
> +        } else {
> +            /*
> +             * This is the complicated bit. This ASCII art diagram gives an
> +             * example with PRESCALE==5 PSCNTR==7:
> +             *
> +             * ticks  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14
> +             * PSCNTR 7  6  5  4  3  2  1  0  5  4  3  2  1  0  5
> +             * cinc                           1                 2
> +             * y            0  1  2  3  4  5  6  7  8  9 10 11 12
> +             * x            0  1  2  3  4  5  0  1  2  3  4  5  0
> +             *
> +             * where x = y % (s->prescale + 1)
> +             * and so PSCNTR = s->prescale - x
> +             * and COUNTER is incremented by y / (s->prescale + 1)
> +             *
> +             * The case where PSCNTR < PRESCALE works out the same,
> +             * though we must be careful to calculate y as 64-bit unsigned
> +             * for all parts of the expression.
> +             * y < 0 is not possible because that implies ticks < s->pscntr.
> +             */
> +            uint64_t y = ticks - s->pscntr + s->prescale;
> +            s->pscntr = s->prescale - (y % (s->prescale + 1));
> +            s->counter += y / (s->prescale + 1);
> +        }
> +    }
> +
> +    /*
> +     * Only advance the sync time to the timestamp of the last PSCNTR tick,
> +     * not all the way to 'now', so we don't lose time if we do multiple
> +     * resyncs in a single tick.
> +     */
> +    s->pscntr_sync_ticks += muldiv64(ticks, NANOSECONDS_PER_SECOND,
> +                                     s->prescale_clk);
> +}
> +
>  static uint64_t mps2_fpgaio_read(void *opaque, hwaddr offset, unsigned size)
>  {
>      MPS2FPGAIO *s = MPS2_FPGAIO(opaque);
> @@ -74,9 +145,12 @@ static uint64_t mps2_fpgaio_read(void *opaque, hwaddr 
> offset, unsigned size)
>          r = counter_from_tickoff(now, s->clk100hz_tick_offset, 100);
>          break;
>      case A_COUNTER:
> +        resync_counter(s);
> +        r = s->counter;
> +        break;
>      case A_PSCNTR:
> -        qemu_log_mask(LOG_UNIMP, "MPS2 FPGAIO: counters unimplemented\n");
> -        r = 0;
> +        resync_counter(s);
> +        r = s->pscntr;
>          break;
>      default:
>          qemu_log_mask(LOG_GUEST_ERROR,
> @@ -107,6 +181,7 @@ static void mps2_fpgaio_write(void *opaque, hwaddr 
> offset, uint64_t value,
>          s->led0 = value & 0x3;
>          break;
>      case A_PRESCALE:
> +        resync_counter(s);
>          s->prescale = value;
>          break;
>      case A_MISC:
> @@ -126,6 +201,14 @@ static void mps2_fpgaio_write(void *opaque, hwaddr 
> offset, uint64_t value,
>          now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
>          s->clk100hz_tick_offset = tickoff_from_counter(now, value, 100);
>          break;
> +    case A_COUNTER:
> +        resync_counter(s);
> +        s->counter = value;
> +        break;
> +    case A_PSCNTR:
> +        resync_counter(s);
> +        s->pscntr = value;
> +        break;
>      default:
>          qemu_log_mask(LOG_GUEST_ERROR,
>                        "MPS2 FPGAIO write: bad offset 0x%x\n", (int) offset);
> @@ -150,6 +233,9 @@ static void mps2_fpgaio_reset(DeviceState *dev)
>      s->misc = 0;
>      s->clk1hz_tick_offset = tickoff_from_counter(now, 0, 1);
>      s->clk100hz_tick_offset = tickoff_from_counter(now, 0, 100);
> +    s->counter = 0;
> +    s->pscntr = 0;
> +    s->pscntr_sync_ticks = now;
>  }
>
>  static void mps2_fpgaio_init(Object *obj)
> @@ -170,12 +256,15 @@ static bool mps2_fpgaio_counters_needed(void *opaque)
>
>  static const VMStateDescription mps2_fpgaio_counters_vmstate = {
>      .name = "mps2-fpgaio/counters",
> -    .version_id = 1,
> -    .minimum_version_id = 1,
> +    .version_id = 2,
> +    .minimum_version_id = 2,
>      .needed = mps2_fpgaio_counters_needed,
>      .fields = (VMStateField[]) {
>          VMSTATE_INT64(clk1hz_tick_offset, MPS2FPGAIO),
>          VMSTATE_INT64(clk100hz_tick_offset, MPS2FPGAIO),
> +        VMSTATE_UINT32(counter, MPS2FPGAIO),
> +        VMSTATE_UINT32(pscntr, MPS2FPGAIO),
> +        VMSTATE_INT64(pscntr_sync_ticks, MPS2FPGAIO),
>          VMSTATE_END_OF_LIST()
>      }
>  };
> --
> 2.17.1
>
>

Reply via email to