[Qemu-devel] [PULL 06/12] aspeed/smc: Add DMA calibration settings

Maintainers: Peter Maydell <peter.maydell@linaro.org>
There is a newer version of this series
[Qemu-devel] [PULL 06/12] aspeed/smc: Add DMA calibration settings
Posted by Peter Maydell 5 years, 7 months ago
From: Cédric Le Goater <clg@kaod.org>

When doing calibration, the SPI clock rate in the CE0 Control Register
and the read delay cycles in the Read Timing Compensation Register are
set using bit[11:4] of the DMA Control Register.

Signed-off-by: Cédric Le Goater <clg@kaod.org>
Acked-by: Joel Stanley <joel@jms.id.au>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Message-id: 20190904070506.1052-7-clg@kaod.org
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
---
 hw/ssi/aspeed_smc.c | 64 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 63 insertions(+), 1 deletion(-)

diff --git a/hw/ssi/aspeed_smc.c b/hw/ssi/aspeed_smc.c
index c1a45c10dc1..7a0cd7607fd 100644
--- a/hw/ssi/aspeed_smc.c
+++ b/hw/ssi/aspeed_smc.c
@@ -77,6 +77,10 @@
 #define   CTRL_CMD_MASK            0xff
 #define   CTRL_DUMMY_HIGH_SHIFT    14
 #define   CTRL_AST2400_SPI_4BYTE   (1 << 13)
+#define CE_CTRL_CLOCK_FREQ_SHIFT   8
+#define CE_CTRL_CLOCK_FREQ_MASK    0xf
+#define CE_CTRL_CLOCK_FREQ(div)                                         \
+    (((div) & CE_CTRL_CLOCK_FREQ_MASK) << CE_CTRL_CLOCK_FREQ_SHIFT)
 #define   CTRL_DUMMY_LOW_SHIFT     6 /* 2 bits [7:6] */
 #define   CTRL_CE_STOP_ACTIVE      (1 << 2)
 #define   CTRL_CMD_MODE_MASK       0x3
@@ -112,7 +116,7 @@
 #define   DMA_CTRL_DELAY_SHIFT  8
 #define   DMA_CTRL_FREQ_MASK    0xf
 #define   DMA_CTRL_FREQ_SHIFT   4
-#define   DMA_CTRL_MODE         (1 << 3)
+#define   DMA_CTRL_CALIB        (1 << 3)
 #define   DMA_CTRL_CKSUM        (1 << 2)
 #define   DMA_CTRL_WRITE        (1 << 1)
 #define   DMA_CTRL_ENABLE       (1 << 0)
@@ -811,6 +815,60 @@ static uint64_t aspeed_smc_read(void *opaque, hwaddr addr, unsigned int size)
     }
 }
 
+static uint8_t aspeed_smc_hclk_divisor(uint8_t hclk_mask)
+{
+    /* HCLK/1 .. HCLK/16 */
+    const uint8_t hclk_divisors[] = {
+        15, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 0
+    };
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(hclk_divisors); i++) {
+        if (hclk_mask == hclk_divisors[i]) {
+            return i + 1;
+        }
+    }
+
+    qemu_log_mask(LOG_GUEST_ERROR, "invalid HCLK mask %x", hclk_mask);
+    return 0;
+}
+
+/*
+ * When doing calibration, the SPI clock rate in the CE0 Control
+ * Register and the read delay cycles in the Read Timing Compensation
+ * Register are set using bit[11:4] of the DMA Control Register.
+ */
+static void aspeed_smc_dma_calibration(AspeedSMCState *s)
+{
+    uint8_t delay =
+        (s->regs[R_DMA_CTRL] >> DMA_CTRL_DELAY_SHIFT) & DMA_CTRL_DELAY_MASK;
+    uint8_t hclk_mask =
+        (s->regs[R_DMA_CTRL] >> DMA_CTRL_FREQ_SHIFT) & DMA_CTRL_FREQ_MASK;
+    uint8_t hclk_div = aspeed_smc_hclk_divisor(hclk_mask);
+    uint32_t hclk_shift = (hclk_div - 1) << 2;
+    uint8_t cs;
+
+    /*
+     * The Read Timing Compensation Register values apply to all CS on
+     * the SPI bus and only HCLK/1 - HCLK/5 can have tunable delays
+     */
+    if (hclk_div && hclk_div < 6) {
+        s->regs[s->r_timings] &= ~(0xf << hclk_shift);
+        s->regs[s->r_timings] |= delay << hclk_shift;
+    }
+
+    /*
+     * TODO: compute the CS from the DMA address and the segment
+     * registers. This is not really a problem for now because the
+     * Timing Register values apply to all CS and software uses CS0 to
+     * do calibration.
+     */
+    cs = 0;
+    s->regs[s->r_ctrl0 + cs] &=
+        ~(CE_CTRL_CLOCK_FREQ_MASK << CE_CTRL_CLOCK_FREQ_SHIFT);
+    s->regs[s->r_ctrl0 + cs] |= CE_CTRL_CLOCK_FREQ(hclk_div);
+}
+
 /*
  * Accumulate the result of the reads to provide a checksum that will
  * be used to validate the read timing settings.
@@ -826,6 +884,10 @@ static void aspeed_smc_dma_checksum(AspeedSMCState *s)
         return;
     }
 
+    if (s->regs[R_DMA_CTRL] & DMA_CTRL_CALIB) {
+        aspeed_smc_dma_calibration(s);
+    }
+
     while (s->regs[R_DMA_LEN]) {
         data = address_space_ldl_le(&s->flash_as, s->regs[R_DMA_FLASH_ADDR],
                                     MEMTXATTRS_UNSPECIFIED, &result);
-- 
2.20.1


Re: [PULL 06/12] aspeed/smc: Add DMA calibration settings
Posted by Peter Maydell 9 months, 2 weeks ago
On Fri, 13 Sept 2019 at 16:50, Peter Maydell <peter.maydell@linaro.org> wrote:
>
> From: Cédric Le Goater <clg@kaod.org>
>
> When doing calibration, the SPI clock rate in the CE0 Control Register
> and the read delay cycles in the Read Timing Compensation Register are
> set using bit[11:4] of the DMA Control Register.
>
> Signed-off-by: Cédric Le Goater <clg@kaod.org>
> Acked-by: Joel Stanley <joel@jms.id.au>
> Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
> Message-id: 20190904070506.1052-7-clg@kaod.org
> Signed-off-by: Peter Maydell <peter.maydell@linaro.org>

Hi; this is an old patch, but Coverity has suddenly decided
it doesn't like it (CID 1547822):

> +static uint8_t aspeed_smc_hclk_divisor(uint8_t hclk_mask)
> +{
> +    /* HCLK/1 .. HCLK/16 */
> +    const uint8_t hclk_divisors[] = {
> +        15, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 0
> +    };
> +    int i;
> +
> +    for (i = 0; i < ARRAY_SIZE(hclk_divisors); i++) {
> +        if (hclk_mask == hclk_divisors[i]) {
> +            return i + 1;
> +        }
> +    }
> +
> +    qemu_log_mask(LOG_GUEST_ERROR, "invalid HCLK mask %x", hclk_mask);
> +    return 0;
> +}
> +
> +/*
> + * When doing calibration, the SPI clock rate in the CE0 Control
> + * Register and the read delay cycles in the Read Timing Compensation
> + * Register are set using bit[11:4] of the DMA Control Register.
> + */
> +static void aspeed_smc_dma_calibration(AspeedSMCState *s)
> +{
> +    uint8_t delay =
> +        (s->regs[R_DMA_CTRL] >> DMA_CTRL_DELAY_SHIFT) & DMA_CTRL_DELAY_MASK;
> +    uint8_t hclk_mask =
> +        (s->regs[R_DMA_CTRL] >> DMA_CTRL_FREQ_SHIFT) & DMA_CTRL_FREQ_MASK;
> +    uint8_t hclk_div = aspeed_smc_hclk_divisor(hclk_mask);
> +    uint32_t hclk_shift = (hclk_div - 1) << 2;

The code of aspeeed_smc_hclk_divisor() has a codepath where it
can return 0, and this callsite doesn't check for 0, and so
Coverity thinks that we might end up shifting -1 by 2 to get
the hclk_shift here, which means we overflow the value, which
it thinks is probably not what we meant to do.

In fact this can't happen, because we always pass aspeed_smc_hclk_divisor()
a value between 0 and 15, and if we do that then we always get back
a value between 1 and 16. So I think the right fix would be
to change the qemu_log_mask()/return 0 to be g_assert_not_reached().

thanks
-- PMM
Re: [PULL 06/12] aspeed/smc: Add DMA calibration settings
Posted by Cédric Le Goater 9 months, 2 weeks ago
On 7/12/24 16:39, Peter Maydell wrote:
> On Fri, 13 Sept 2019 at 16:50, Peter Maydell <peter.maydell@linaro.org> wrote:
>>
>> From: Cédric Le Goater <clg@kaod.org>
>>
>> When doing calibration, the SPI clock rate in the CE0 Control Register
>> and the read delay cycles in the Read Timing Compensation Register are
>> set using bit[11:4] of the DMA Control Register.
>>
>> Signed-off-by: Cédric Le Goater <clg@kaod.org>
>> Acked-by: Joel Stanley <joel@jms.id.au>
>> Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
>> Message-id: 20190904070506.1052-7-clg@kaod.org
>> Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
> 
> Hi; this is an old patch, but Coverity has suddenly decided
> it doesn't like it (CID 1547822):
> 
>> +static uint8_t aspeed_smc_hclk_divisor(uint8_t hclk_mask)
>> +{
>> +    /* HCLK/1 .. HCLK/16 */
>> +    const uint8_t hclk_divisors[] = {
>> +        15, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 0
>> +    };
>> +    int i;
>> +
>> +    for (i = 0; i < ARRAY_SIZE(hclk_divisors); i++) {
>> +        if (hclk_mask == hclk_divisors[i]) {
>> +            return i + 1;
>> +        }
>> +    }
>> +
>> +    qemu_log_mask(LOG_GUEST_ERROR, "invalid HCLK mask %x", hclk_mask);
>> +    return 0;
>> +}
>> +
>> +/*
>> + * When doing calibration, the SPI clock rate in the CE0 Control
>> + * Register and the read delay cycles in the Read Timing Compensation
>> + * Register are set using bit[11:4] of the DMA Control Register.
>> + */
>> +static void aspeed_smc_dma_calibration(AspeedSMCState *s)
>> +{
>> +    uint8_t delay =
>> +        (s->regs[R_DMA_CTRL] >> DMA_CTRL_DELAY_SHIFT) & DMA_CTRL_DELAY_MASK;
>> +    uint8_t hclk_mask =
>> +        (s->regs[R_DMA_CTRL] >> DMA_CTRL_FREQ_SHIFT) & DMA_CTRL_FREQ_MASK;
>> +    uint8_t hclk_div = aspeed_smc_hclk_divisor(hclk_mask);
>> +    uint32_t hclk_shift = (hclk_div - 1) << 2;
> 
> The code of aspeeed_smc_hclk_divisor() has a codepath where it
> can return 0, and this callsite doesn't check for 0, and so
> Coverity thinks that we might end up shifting -1 by 2 to get
> the hclk_shift here, which means we overflow the value, which
> it thinks is probably not what we meant to do.
> 
> In fact this can't happen, because we always pass aspeed_smc_hclk_divisor()
> a value between 0 and 15, and if we do that then we always get back
> a value between 1 and 16. So I think the right fix would be
> to change the qemu_log_mask()/return 0 to be g_assert_not_reached().

I was wondering how to fix it. Thanks for the suggestion. Will do in
the 9.1 cycle.

C.

> 
> thanks
> -- PMM