The adc_qom_set function didn't free "response", which caused an indirect
memory leak. So use qobject_unref() to fix it.
ASAN shows memory leak stack:
Indirect leak of 593280 byte(s) in 144 object(s) allocated from:
    #0 0x7f9a5e7e8d4e in __interceptor_calloc (/lib64/libasan.so.5+0x112d4e)
    #1 0x7f9a5e607a50 in g_malloc0 (/lib64/libglib-2.0.so.0+0x55a50)
    #2 0x55b1bebf636b in qdict_new ../qobject/qdict.c:30
    #3 0x55b1bec09699 in parse_object ../qobject/json-parser.c:318
    #4 0x55b1bec0b2df in parse_value ../qobject/json-parser.c:546
    #5 0x55b1bec0b6a9 in json_parser_parse ../qobject/json-parser.c:580
    #6 0x55b1bec060d1 in json_message_process_token ../qobject/json-streamer.c:92
    #7 0x55b1bec16a12 in json_lexer_feed_char ../qobject/json-lexer.c:313
    #8 0x55b1bec16fbd in json_lexer_feed ../qobject/json-lexer.c:350
    #9 0x55b1bec06453 in json_message_parser_feed ../qobject/json-streamer.c:121
    #10 0x55b1bebc2d51 in qmp_fd_receive ../tests/qtest/libqtest.c:614
    #11 0x55b1bebc2f5e in qtest_qmp_receive_dict ../tests/qtest/libqtest.c:636
    #12 0x55b1bebc2e6c in qtest_qmp_receive ../tests/qtest/libqtest.c:624
    #13 0x55b1bebc3340 in qtest_vqmp ../tests/qtest/libqtest.c:715
    #14 0x55b1bebc3942 in qtest_qmp ../tests/qtest/libqtest.c:756
    #15 0x55b1bebbd64a in adc_qom_set ../tests/qtest/npcm7xx_adc-test.c:127
    #16 0x55b1bebbd793 in adc_write_input ../tests/qtest/npcm7xx_adc-test.c:140
    #17 0x55b1bebbdf92 in test_convert_external ../tests/qtest/npcm7xx_adc-test.c:246
Reported-by: Euler Robot <euler.robot@huawei.com>
Signed-off-by: Gan Qixin <ganqixin@huawei.com>
Reviewed-by: Hao Wu <wuhaotsh@google.com>
Message-id: 20210118065627.79903-1-ganqixin@huawei.com
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
		
	
		
			
				
	
	
		
			379 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			379 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * QTests for Nuvoton NPCM7xx ADCModules.
 | 
						|
 *
 | 
						|
 * Copyright 2020 Google LLC
 | 
						|
 *
 | 
						|
 * This program is free software; you can redistribute it and/or modify it
 | 
						|
 * under the terms of the GNU General Public License as published by the
 | 
						|
 * Free Software Foundation; either version 2 of the License, or
 | 
						|
 * (at your option) any later version.
 | 
						|
 *
 | 
						|
 * This program is distributed in the hope that it will be useful, but WITHOUT
 | 
						|
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | 
						|
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 | 
						|
 * for more details.
 | 
						|
 */
 | 
						|
 | 
						|
#include "qemu/osdep.h"
 | 
						|
#include "qemu/bitops.h"
 | 
						|
#include "qemu/timer.h"
 | 
						|
#include "libqos/libqtest.h"
 | 
						|
#include "qapi/qmp/qdict.h"
 | 
						|
 | 
						|
#define REF_HZ          (25000000)
 | 
						|
 | 
						|
#define CON_OFFSET      0x0
 | 
						|
#define DATA_OFFSET     0x4
 | 
						|
 | 
						|
#define NUM_INPUTS      8
 | 
						|
#define DEFAULT_IREF    2000000
 | 
						|
#define CONV_CYCLES     20
 | 
						|
#define RESET_CYCLES    10
 | 
						|
#define R0_INPUT        500000
 | 
						|
#define R1_INPUT        1500000
 | 
						|
#define MAX_RESULT      1023
 | 
						|
 | 
						|
#define DEFAULT_CLKDIV  5
 | 
						|
 | 
						|
#define FUSE_ARRAY_BA   0xf018a000
 | 
						|
#define FCTL_OFFSET     0x14
 | 
						|
#define FST_OFFSET      0x0
 | 
						|
#define FADDR_OFFSET    0x4
 | 
						|
#define FDATA_OFFSET    0x8
 | 
						|
#define ADC_CALIB_ADDR  24
 | 
						|
#define FUSE_READ       0x2
 | 
						|
 | 
						|
/* Register field definitions. */
 | 
						|
#define CON_MUX(rv) ((rv) << 24)
 | 
						|
#define CON_INT_EN  BIT(21)
 | 
						|
#define CON_REFSEL  BIT(19)
 | 
						|
#define CON_INT     BIT(18)
 | 
						|
#define CON_EN      BIT(17)
 | 
						|
#define CON_RST     BIT(16)
 | 
						|
#define CON_CONV    BIT(14)
 | 
						|
#define CON_DIV(rv) extract32(rv, 1, 8)
 | 
						|
 | 
						|
#define FST_RDST    BIT(1)
 | 
						|
#define FDATA_MASK  0xff
 | 
						|
 | 
						|
#define MAX_ERROR   10000
 | 
						|
#define MIN_CALIB_INPUT 100000
 | 
						|
#define MAX_CALIB_INPUT 1800000
 | 
						|
 | 
						|
static const uint32_t input_list[] = {
 | 
						|
    100000,
 | 
						|
    500000,
 | 
						|
    1000000,
 | 
						|
    1500000,
 | 
						|
    1800000,
 | 
						|
    2000000,
 | 
						|
};
 | 
						|
 | 
						|
static const uint32_t vref_list[] = {
 | 
						|
    2000000,
 | 
						|
    2200000,
 | 
						|
    2500000,
 | 
						|
};
 | 
						|
 | 
						|
static const uint32_t iref_list[] = {
 | 
						|
    1800000,
 | 
						|
    1900000,
 | 
						|
    2000000,
 | 
						|
    2100000,
 | 
						|
    2200000,
 | 
						|
};
 | 
						|
 | 
						|
static const uint32_t div_list[] = {0, 1, 3, 7, 15};
 | 
						|
 | 
						|
typedef struct ADC {
 | 
						|
    int irq;
 | 
						|
    uint64_t base_addr;
 | 
						|
} ADC;
 | 
						|
 | 
						|
ADC adc = {
 | 
						|
    .irq        = 0,
 | 
						|
    .base_addr  = 0xf000c000
 | 
						|
};
 | 
						|
 | 
						|
static uint32_t adc_read_con(QTestState *qts, const ADC *adc)
 | 
						|
{
 | 
						|
    return qtest_readl(qts, adc->base_addr + CON_OFFSET);
 | 
						|
}
 | 
						|
 | 
						|
static void adc_write_con(QTestState *qts, const ADC *adc, uint32_t value)
 | 
						|
{
 | 
						|
    qtest_writel(qts, adc->base_addr + CON_OFFSET, value);
 | 
						|
}
 | 
						|
 | 
						|
static uint32_t adc_read_data(QTestState *qts, const ADC *adc)
 | 
						|
{
 | 
						|
    return qtest_readl(qts, adc->base_addr + DATA_OFFSET);
 | 
						|
}
 | 
						|
 | 
						|
static uint32_t adc_calibrate(uint32_t measured, uint32_t *rv)
 | 
						|
{
 | 
						|
    return R0_INPUT + (R1_INPUT - R0_INPUT) * (int32_t)(measured - rv[0])
 | 
						|
        / (int32_t)(rv[1] - rv[0]);
 | 
						|
}
 | 
						|
 | 
						|
static void adc_qom_set(QTestState *qts, const ADC *adc,
 | 
						|
        const char *name, uint32_t value)
 | 
						|
{
 | 
						|
    QDict *response;
 | 
						|
    const char *path = "/machine/soc/adc";
 | 
						|
 | 
						|
    g_test_message("Setting properties %s of %s with value %u",
 | 
						|
            name, path, value);
 | 
						|
    response = qtest_qmp(qts, "{ 'execute': 'qom-set',"
 | 
						|
            " 'arguments': { 'path': %s, 'property': %s, 'value': %u}}",
 | 
						|
            path, name, value);
 | 
						|
    /* The qom set message returns successfully. */
 | 
						|
    g_assert_true(qdict_haskey(response, "return"));
 | 
						|
    qobject_unref(response);
 | 
						|
}
 | 
						|
 | 
						|
static void adc_write_input(QTestState *qts, const ADC *adc,
 | 
						|
        uint32_t index, uint32_t value)
 | 
						|
{
 | 
						|
    char name[100];
 | 
						|
 | 
						|
    sprintf(name, "adci[%u]", index);
 | 
						|
    adc_qom_set(qts, adc, name, value);
 | 
						|
}
 | 
						|
 | 
						|
static void adc_write_vref(QTestState *qts, const ADC *adc, uint32_t value)
 | 
						|
{
 | 
						|
    adc_qom_set(qts, adc, "vref", value);
 | 
						|
}
 | 
						|
 | 
						|
static uint32_t adc_calculate_output(uint32_t input, uint32_t ref)
 | 
						|
{
 | 
						|
    uint32_t output;
 | 
						|
 | 
						|
    g_assert_cmpuint(input, <=, ref);
 | 
						|
    output = (input * (MAX_RESULT + 1)) / ref;
 | 
						|
    if (output > MAX_RESULT) {
 | 
						|
        output = MAX_RESULT;
 | 
						|
    }
 | 
						|
 | 
						|
    return output;
 | 
						|
}
 | 
						|
 | 
						|
static uint32_t adc_prescaler(QTestState *qts, const ADC *adc)
 | 
						|
{
 | 
						|
    uint32_t div = extract32(adc_read_con(qts, adc), 1, 8);
 | 
						|
 | 
						|
    return 2 * (div + 1);
 | 
						|
}
 | 
						|
 | 
						|
static int64_t adc_calculate_steps(uint32_t cycles, uint32_t prescale,
 | 
						|
        uint32_t clkdiv)
 | 
						|
{
 | 
						|
    return (NANOSECONDS_PER_SECOND / (REF_HZ >> clkdiv)) * cycles * prescale;
 | 
						|
}
 | 
						|
 | 
						|
static void adc_wait_conv_finished(QTestState *qts, const ADC *adc,
 | 
						|
        uint32_t clkdiv)
 | 
						|
{
 | 
						|
    uint32_t prescaler = adc_prescaler(qts, adc);
 | 
						|
 | 
						|
    /*
 | 
						|
     * ADC should takes roughly 20 cycles to convert one sample. So we assert it
 | 
						|
     * should take 10~30 cycles here.
 | 
						|
     */
 | 
						|
    qtest_clock_step(qts, adc_calculate_steps(CONV_CYCLES / 2, prescaler,
 | 
						|
                clkdiv));
 | 
						|
    /* ADC is still converting. */
 | 
						|
    g_assert_true(adc_read_con(qts, adc) & CON_CONV);
 | 
						|
    qtest_clock_step(qts, adc_calculate_steps(CONV_CYCLES, prescaler, clkdiv));
 | 
						|
    /* ADC has finished conversion. */
 | 
						|
    g_assert_false(adc_read_con(qts, adc) & CON_CONV);
 | 
						|
}
 | 
						|
 | 
						|
/* Check ADC can be reset to default value. */
 | 
						|
static void test_init(gconstpointer adc_p)
 | 
						|
{
 | 
						|
    const ADC *adc = adc_p;
 | 
						|
 | 
						|
    QTestState *qts = qtest_init("-machine quanta-gsj");
 | 
						|
    adc_write_con(qts, adc, CON_REFSEL | CON_INT);
 | 
						|
    g_assert_cmphex(adc_read_con(qts, adc), ==, CON_REFSEL);
 | 
						|
    qtest_quit(qts);
 | 
						|
}
 | 
						|
 | 
						|
/* Check ADC can convert from an internal reference. */
 | 
						|
static void test_convert_internal(gconstpointer adc_p)
 | 
						|
{
 | 
						|
    const ADC *adc = adc_p;
 | 
						|
    uint32_t index, input, output, expected_output;
 | 
						|
    QTestState *qts = qtest_init("-machine quanta-gsj");
 | 
						|
    qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
 | 
						|
 | 
						|
    for (index = 0; index < NUM_INPUTS; ++index) {
 | 
						|
        for (size_t i = 0; i < ARRAY_SIZE(input_list); ++i) {
 | 
						|
            input = input_list[i];
 | 
						|
            expected_output = adc_calculate_output(input, DEFAULT_IREF);
 | 
						|
 | 
						|
            adc_write_input(qts, adc, index, input);
 | 
						|
            adc_write_con(qts, adc, CON_MUX(index) | CON_REFSEL | CON_INT |
 | 
						|
                    CON_EN | CON_CONV);
 | 
						|
            adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV);
 | 
						|
            g_assert_cmphex(adc_read_con(qts, adc), ==, CON_MUX(index) |
 | 
						|
                    CON_REFSEL | CON_EN);
 | 
						|
            g_assert_false(qtest_get_irq(qts, adc->irq));
 | 
						|
            output = adc_read_data(qts, adc);
 | 
						|
            g_assert_cmpuint(output, ==, expected_output);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    qtest_quit(qts);
 | 
						|
}
 | 
						|
 | 
						|
/* Check ADC can convert from an external reference. */
 | 
						|
static void test_convert_external(gconstpointer adc_p)
 | 
						|
{
 | 
						|
    const ADC *adc = adc_p;
 | 
						|
    uint32_t index, input, vref, output, expected_output;
 | 
						|
    QTestState *qts = qtest_init("-machine quanta-gsj");
 | 
						|
    qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
 | 
						|
 | 
						|
    for (index = 0; index < NUM_INPUTS; ++index) {
 | 
						|
        for (size_t i = 0; i < ARRAY_SIZE(input_list); ++i) {
 | 
						|
            for (size_t j = 0; j < ARRAY_SIZE(vref_list); ++j) {
 | 
						|
                input = input_list[i];
 | 
						|
                vref = vref_list[j];
 | 
						|
                expected_output = adc_calculate_output(input, vref);
 | 
						|
 | 
						|
                adc_write_input(qts, adc, index, input);
 | 
						|
                adc_write_vref(qts, adc, vref);
 | 
						|
                adc_write_con(qts, adc, CON_MUX(index) | CON_INT | CON_EN |
 | 
						|
                        CON_CONV);
 | 
						|
                adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV);
 | 
						|
                g_assert_cmphex(adc_read_con(qts, adc), ==,
 | 
						|
                        CON_MUX(index) | CON_EN);
 | 
						|
                g_assert_false(qtest_get_irq(qts, adc->irq));
 | 
						|
                output = adc_read_data(qts, adc);
 | 
						|
                g_assert_cmpuint(output, ==, expected_output);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    qtest_quit(qts);
 | 
						|
}
 | 
						|
 | 
						|
/* Check ADC interrupt files if and only if CON_INT_EN is set. */
 | 
						|
static void test_interrupt(gconstpointer adc_p)
 | 
						|
{
 | 
						|
    const ADC *adc = adc_p;
 | 
						|
    uint32_t index, input, output, expected_output;
 | 
						|
    QTestState *qts = qtest_init("-machine quanta-gsj");
 | 
						|
 | 
						|
    index = 1;
 | 
						|
    input = input_list[1];
 | 
						|
    expected_output = adc_calculate_output(input, DEFAULT_IREF);
 | 
						|
 | 
						|
    qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
 | 
						|
    adc_write_input(qts, adc, index, input);
 | 
						|
    g_assert_false(qtest_get_irq(qts, adc->irq));
 | 
						|
    adc_write_con(qts, adc, CON_MUX(index) | CON_INT_EN | CON_REFSEL | CON_INT
 | 
						|
            | CON_EN | CON_CONV);
 | 
						|
    adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV);
 | 
						|
    g_assert_cmphex(adc_read_con(qts, adc), ==, CON_MUX(index) | CON_INT_EN
 | 
						|
            | CON_REFSEL | CON_INT | CON_EN);
 | 
						|
    g_assert_true(qtest_get_irq(qts, adc->irq));
 | 
						|
    output = adc_read_data(qts, adc);
 | 
						|
    g_assert_cmpuint(output, ==, expected_output);
 | 
						|
 | 
						|
    qtest_quit(qts);
 | 
						|
}
 | 
						|
 | 
						|
/* Check ADC is reset after setting ADC_RST for 10 ADC cycles. */
 | 
						|
static void test_reset(gconstpointer adc_p)
 | 
						|
{
 | 
						|
    const ADC *adc = adc_p;
 | 
						|
    QTestState *qts = qtest_init("-machine quanta-gsj");
 | 
						|
 | 
						|
    for (size_t i = 0; i < ARRAY_SIZE(div_list); ++i) {
 | 
						|
        uint32_t div = div_list[i];
 | 
						|
 | 
						|
        adc_write_con(qts, adc, CON_INT | CON_EN | CON_RST | CON_DIV(div));
 | 
						|
        qtest_clock_step(qts, adc_calculate_steps(RESET_CYCLES,
 | 
						|
                    adc_prescaler(qts, adc), DEFAULT_CLKDIV));
 | 
						|
        g_assert_false(adc_read_con(qts, adc) & CON_EN);
 | 
						|
    }
 | 
						|
    qtest_quit(qts);
 | 
						|
}
 | 
						|
 | 
						|
/* Check ADC Calibration works as desired. */
 | 
						|
static void test_calibrate(gconstpointer adc_p)
 | 
						|
{
 | 
						|
    int i, j;
 | 
						|
    const ADC *adc = adc_p;
 | 
						|
 | 
						|
    for (j = 0; j < ARRAY_SIZE(iref_list); ++j) {
 | 
						|
        uint32_t iref = iref_list[j];
 | 
						|
        uint32_t expected_rv[] = {
 | 
						|
            adc_calculate_output(R0_INPUT, iref),
 | 
						|
            adc_calculate_output(R1_INPUT, iref),
 | 
						|
        };
 | 
						|
        char buf[100];
 | 
						|
        QTestState *qts;
 | 
						|
 | 
						|
        sprintf(buf, "-machine quanta-gsj -global npcm7xx-adc.iref=%u", iref);
 | 
						|
        qts = qtest_init(buf);
 | 
						|
 | 
						|
        /* Check the converted value is correct using the calibration value. */
 | 
						|
        for (i = 0; i < ARRAY_SIZE(input_list); ++i) {
 | 
						|
            uint32_t input;
 | 
						|
            uint32_t output;
 | 
						|
            uint32_t expected_output;
 | 
						|
            uint32_t calibrated_voltage;
 | 
						|
            uint32_t index = 0;
 | 
						|
 | 
						|
            input = input_list[i];
 | 
						|
            /* Calibration only works for input range 0.1V ~ 1.8V. */
 | 
						|
            if (input < MIN_CALIB_INPUT || input > MAX_CALIB_INPUT) {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            expected_output = adc_calculate_output(input, iref);
 | 
						|
 | 
						|
            adc_write_input(qts, adc, index, input);
 | 
						|
            adc_write_con(qts, adc, CON_MUX(index) | CON_REFSEL | CON_INT |
 | 
						|
                    CON_EN | CON_CONV);
 | 
						|
            adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV);
 | 
						|
            g_assert_cmphex(adc_read_con(qts, adc), ==,
 | 
						|
                    CON_REFSEL | CON_MUX(index) | CON_EN);
 | 
						|
            output = adc_read_data(qts, adc);
 | 
						|
            g_assert_cmpuint(output, ==, expected_output);
 | 
						|
 | 
						|
            calibrated_voltage = adc_calibrate(output, expected_rv);
 | 
						|
            g_assert_cmpuint(calibrated_voltage, >, input - MAX_ERROR);
 | 
						|
            g_assert_cmpuint(calibrated_voltage, <, input + MAX_ERROR);
 | 
						|
        }
 | 
						|
 | 
						|
        qtest_quit(qts);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void adc_add_test(const char *name, const ADC* wd,
 | 
						|
        GTestDataFunc fn)
 | 
						|
{
 | 
						|
    g_autofree char *full_name = g_strdup_printf("npcm7xx_adc/%s",  name);
 | 
						|
    qtest_add_data_func(full_name, wd, fn);
 | 
						|
}
 | 
						|
#define add_test(name, td) adc_add_test(#name, td, test_##name)
 | 
						|
 | 
						|
int main(int argc, char **argv)
 | 
						|
{
 | 
						|
    g_test_init(&argc, &argv, NULL);
 | 
						|
 | 
						|
    add_test(init, &adc);
 | 
						|
    add_test(convert_internal, &adc);
 | 
						|
    add_test(convert_external, &adc);
 | 
						|
    add_test(interrupt, &adc);
 | 
						|
    add_test(reset, &adc);
 | 
						|
    add_test(calibrate, &adc);
 | 
						|
 | 
						|
    return g_test_run();
 | 
						|
}
 |