/*
 * Unit tests for the generic vPCI handler code.
 *
 * Copyright (C) 2017 Citrix Systems R&D
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms and conditions of the GNU General Public
 * License, version 2, as published by the Free Software Foundation.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

#include "emul.h"

/* Single vcpu (current), and single domain with a single PCI device. */
static struct vpci vpci;

static struct domain d;

const struct pci_dev test_pdev = {
    .vpci = &vpci,
};

const static struct vcpu v = {
    .domain = &d
};

const struct vcpu *current = &v;

/* Dummy hooks, write stores data, read fetches it. */
static uint32_t vpci_read8(const struct pci_dev *pdev, unsigned int reg,
                           void *data)
{
    return *(uint8_t *)data;
}

static void vpci_write8(const struct pci_dev *pdev, unsigned int reg,
                        uint32_t val, void *data)
{
    *(uint8_t *)data = val;
}

static uint32_t vpci_read16(const struct pci_dev *pdev, unsigned int reg,
                            void *data)
{
    return *(uint16_t *)data;
}

static void vpci_write16(const struct pci_dev *pdev, unsigned int reg,
                         uint32_t val, void *data)
{
    *(uint16_t *)data = val;
}

static uint32_t vpci_read32(const struct pci_dev *pdev, unsigned int reg,
                            void *data)
{
    return *(uint32_t *)data;
}

static void vpci_write32(const struct pci_dev *pdev, unsigned int reg,
                         uint32_t val, void *data)
{
    *(uint32_t *)data = val;
}

struct mask_data {
    uint32_t val;
    uint32_t rw1c_mask;
};

static uint32_t vpci_read32_mask(const struct pci_dev *pdev, unsigned int reg,
                                 void *data)
{
    const struct mask_data *md = data;

    return md->val;
}

static void vpci_write32_mask(const struct pci_dev *pdev, unsigned int reg,
                              uint32_t val, void *data)
{
    struct mask_data *md = data;

    md->val  = val | (md->val & md->rw1c_mask);
    md->val &= ~(val & md->rw1c_mask);
}

#define VPCI_READ(reg, size, data) ({                           \
    data = vpci_read((pci_sbdf_t){ .sbdf = 0 }, reg, size);     \
})

#define VPCI_READ_CHECK(reg, size, expected) ({                 \
    uint32_t rd;                                                \
                                                                \
    VPCI_READ(reg, size, rd);                                   \
    assert(rd == (expected));                                   \
})

#define VPCI_WRITE(reg, size, data) ({                          \
    vpci_write((pci_sbdf_t){ .sbdf = 0 }, reg, size, data);     \
})

#define VPCI_WRITE_CHECK(reg, size, data) ({                    \
    VPCI_WRITE(reg, size, data);                                \
    VPCI_READ_CHECK(reg, size, data);                           \
})

#define VPCI_ADD_REG(fread, fwrite, off, size, store)                       \
    assert(!vpci_add_register(test_pdev.vpci, fread, fwrite, off, size,     \
                              &store))

#define VPCI_ADD_REG_MASK(fread, fwrite, off, size, store,                     \
                          ro_mask, rw1c_mask, rsvdp_mask, rsvdz_mask)          \
    assert(!vpci_add_register_mask(test_pdev.vpci, fread, fwrite, off, size,   \
                                   &store,                                     \
                                   ro_mask, rw1c_mask, rsvdp_mask, rsvdz_mask))

#define VPCI_ADD_INVALID_REG(fread, fwrite, off, size)                      \
    assert(vpci_add_register(test_pdev.vpci, fread, fwrite, off, size, NULL))

#define VPCI_ADD_INVALID_REG_MASK(fread, fwrite, off, size,                   \
                                  ro_mask, rw1c_mask, rsvdp_mask, rsvdz_mask) \
    assert(vpci_add_register_mask(test_pdev.vpci, fread, fwrite, off, size,   \
                                  NULL, ro_mask, rw1c_mask, rsvdp_mask,       \
                                  rsvdz_mask))

#define VPCI_REMOVE_REG(off, size)                                          \
    assert(!vpci_remove_register(test_pdev.vpci, off, size))

#define VPCI_REMOVE_INVALID_REG(off, size)                                  \
    assert(vpci_remove_register(test_pdev.vpci, off, size))

/* Read a 32b register using all possible sizes. */
void multiread4_check(unsigned int reg, uint32_t val)
{
    unsigned int i;

    /* Read using bytes. */
    for ( i = 0; i < 4; i++ )
        VPCI_READ_CHECK(reg + i, 1, (val >> (i * 8)) & UINT8_MAX);

    /* Read using 2bytes. */
    for ( i = 0; i < 2; i++ )
        VPCI_READ_CHECK(reg + i * 2, 2, (val >> (i * 2 * 8)) & UINT16_MAX);

    VPCI_READ_CHECK(reg, 4, val);
}

void multiwrite4_check(unsigned int reg)
{
    unsigned int i;
    uint32_t val = 0xa2f51732;

    /* Write using bytes. */
    for ( i = 0; i < 4; i++ )
        VPCI_WRITE_CHECK(reg + i, 1, (val >> (i * 8)) & UINT8_MAX);
    multiread4_check(reg, val);

    /* Change the value each time to be sure writes work fine. */
    val = 0x2b836fda;
    /* Write using 2bytes. */
    for ( i = 0; i < 2; i++ )
        VPCI_WRITE_CHECK(reg + i * 2, 2, (val >> (i * 2 * 8)) & UINT16_MAX);
    multiread4_check(reg, val);

    val = 0xc4693beb;
    VPCI_WRITE_CHECK(reg, 4, val);
    multiread4_check(reg, val);
}

int
main(int argc, char **argv)
{
    /* Index storage by offset. */
    uint32_t r0 = 0xdeadbeef;
    uint8_t r5 = 0xef;
    uint8_t r6 = 0xbe;
    uint8_t r7 = 0xef;
    uint16_t r12 = 0x8696;
    uint8_t r16[4] = { };
    uint16_t r20[2] = { };
    uint32_t r24 = 0;
    uint8_t r28, r30;
    struct mask_data r32;
    unsigned int i;
    int rc;

    INIT_LIST_HEAD(&vpci.handlers);
    spin_lock_init(&vpci.lock);

    VPCI_ADD_REG(vpci_read32, vpci_write32, 0, 4, r0);
    VPCI_READ_CHECK(0, 4, r0);
    VPCI_WRITE_CHECK(0, 4, 0xbcbcbcbc);

    VPCI_ADD_REG(vpci_read8, vpci_write8, 5, 1, r5);
    VPCI_READ_CHECK(5, 1, r5);
    VPCI_WRITE_CHECK(5, 1, 0xba);

    VPCI_ADD_REG(vpci_read8, vpci_write8, 6, 1, r6);
    VPCI_READ_CHECK(6, 1, r6);
    VPCI_WRITE_CHECK(6, 1, 0xba);

    VPCI_ADD_REG(vpci_read8, vpci_write8, 7, 1, r7);
    VPCI_READ_CHECK(7, 1, r7);
    VPCI_WRITE_CHECK(7, 1, 0xbd);

    VPCI_ADD_REG(vpci_read16, vpci_write16, 12, 2, r12);
    VPCI_READ_CHECK(12, 2, r12);
    VPCI_READ_CHECK(12, 4, 0xffff8696);

    /*
     * At this point we have the following layout:
     *
     * Note that this refers to the position of the variables,
     * but the value has already changed from the one given at
     * initialization time because write tests have been performed.
     *
     * 32    24    16     8     0
     *  +-----+-----+-----+-----+
     *  |          r0           | 0
     *  +-----+-----+-----+-----+
     *  | r7  |  r6 |  r5 |/////| 32
     *  +-----+-----+-----+-----|
     *  |///////////////////////| 64
     *  +-----------+-----------+
     *  |///////////|    r12    | 96
     *  +-----------+-----------+
     *             ...
     *  / = unhandled.
     */

    /* Try to add an overlapping register handler. */
    VPCI_ADD_INVALID_REG(vpci_read32, vpci_write32, 4, 4);

    /* Try to add a non-aligned register. */
    VPCI_ADD_INVALID_REG(vpci_read16, vpci_write16, 15, 2);

    /* Try to add a register with wrong size. */
    VPCI_ADD_INVALID_REG(vpci_read16, vpci_write16, 8, 3);

    /* Try to add a register with missing handlers. */
    VPCI_ADD_INVALID_REG(NULL, NULL, 8, 2);

    /* Try to add registers with the same bits set in multiple masks. */
    VPCI_ADD_INVALID_REG_MASK(vpci_read32, vpci_write32, 8, 4, 1, 1, 0, 0);
    VPCI_ADD_INVALID_REG_MASK(vpci_read32, vpci_write32, 8, 4, 1, 0, 1, 0);
    VPCI_ADD_INVALID_REG_MASK(vpci_read32, vpci_write32, 8, 4, 1, 0, 0, 1);
    VPCI_ADD_INVALID_REG_MASK(vpci_read32, vpci_write32, 8, 4, 0, 1, 1, 0);
    VPCI_ADD_INVALID_REG_MASK(vpci_read32, vpci_write32, 8, 4, 0, 1, 0, 1);
    VPCI_ADD_INVALID_REG_MASK(vpci_read32, vpci_write32, 8, 4, 0, 0, 1, 1);

    /* Try to add registers with mask bits set beyond the register size */
    VPCI_ADD_INVALID_REG_MASK(vpci_read8, vpci_write8, 8, 1, 0x100U, 0, 0, 0);
    VPCI_ADD_INVALID_REG_MASK(vpci_read8, vpci_write8, 8, 1, 0, 0x100U, 0, 0);
    VPCI_ADD_INVALID_REG_MASK(vpci_read8, vpci_write8, 8, 1, 0, 0, 0x100U, 0);
    VPCI_ADD_INVALID_REG_MASK(vpci_read8, vpci_write8, 8, 1, 0, 0, 0, 0x100U);
    VPCI_ADD_INVALID_REG_MASK(vpci_read16, vpci_write16, 8, 2, 0x10000U,0,0,0);
    VPCI_ADD_INVALID_REG_MASK(vpci_read16, vpci_write16, 8, 2, 0,0x10000U,0,0);
    VPCI_ADD_INVALID_REG_MASK(vpci_read16, vpci_write16, 8, 2, 0,0,0x10000U,0);
    VPCI_ADD_INVALID_REG_MASK(vpci_read16, vpci_write16, 8, 2, 0,0,0,0x10000U);

    /* Read/write of unset register. */
    VPCI_READ_CHECK(8, 4, 0xffffffff);
    VPCI_READ_CHECK(8, 2, 0xffff);
    VPCI_READ_CHECK(8, 1, 0xff);
    VPCI_WRITE(10, 2, 0xbeef);
    VPCI_READ_CHECK(10, 2, 0xffff);

    /* Read of multiple registers */
    VPCI_WRITE_CHECK(7, 1, 0xbd);
    VPCI_READ_CHECK(4, 4, 0xbdbabaff);

    /* Partial read of a register. */
    VPCI_WRITE_CHECK(0, 4, 0x1a1b1c1d);
    VPCI_READ_CHECK(2, 1, 0x1b);
    VPCI_READ_CHECK(6, 2, 0xbdba);

    /* Write of multiple registers. */
    VPCI_WRITE_CHECK(4, 4, 0xaabbccff);

    /* Partial write of a register. */
    VPCI_WRITE_CHECK(2, 1, 0xfe);
    VPCI_WRITE_CHECK(6, 2, 0xfebc);

    /*
     * Test all possible read/write size combinations.
     *
     * Place 4 1B registers at 128bits (16B), 2 2B registers at 160bits
     * (20B) and finally 1 4B register at 192bits (24B).
     *
     * Then perform all possible write and read sizes on each of them.
     *
     *               ...
     * 32     24     16      8      0
     *  +------+------+------+------+
     *  |r16[3]|r16[2]|r16[1]|r16[0]| 16
     *  +------+------+------+------+
     *  |    r20[1]   |    r20[0]   | 20
     *  +-------------+-------------|
     *  |            r24            | 24
     *  +-------------+-------------+
     *
     */
    VPCI_ADD_REG(vpci_read8, vpci_write8, 16, 1, r16[0]);
    VPCI_ADD_REG(vpci_read8, vpci_write8, 17, 1, r16[1]);
    VPCI_ADD_REG(vpci_read8, vpci_write8, 18, 1, r16[2]);
    VPCI_ADD_REG(vpci_read8, vpci_write8, 19, 1, r16[3]);

    VPCI_ADD_REG(vpci_read16, vpci_write16, 20, 2, r20[0]);
    VPCI_ADD_REG(vpci_read16, vpci_write16, 22, 2, r20[1]);

    VPCI_ADD_REG(vpci_read32, vpci_write32, 24, 4, r24);

    /* Check the initial value is 0. */
    multiread4_check(16, 0);
    multiread4_check(20, 0);
    multiread4_check(24, 0);

    multiwrite4_check(16);
    multiwrite4_check(20);
    multiwrite4_check(24);

    /*
     * Check multiple non-consecutive gaps on the same read/write:
     *
     * 32     24     16      8      0
     *  +------+------+------+------+
     *  |//////|  r30 |//////|  r28 | 28
     *  +------+------+------+------+
     *
     */
    VPCI_ADD_REG(vpci_read8, vpci_write8, 28, 1, r28);
    VPCI_ADD_REG(vpci_read8, vpci_write8, 30, 1, r30);
    VPCI_WRITE_CHECK(28, 4, 0xffacffdc);

    /*
     * Test ro/rw1c/rsvdp/rsvdz masks.
     *
     * 32     24     16      8      0
     *  +------+------+------+------+
     *  |rsvdz |rsvdp | rw1c |  ro  | 32
     *  +------+------+------+------+
     *
     */
    r32.rw1c_mask = 0x0000ff00U;
    VPCI_ADD_REG_MASK(vpci_read32_mask, vpci_write32_mask, 32, 4, r32,
                      0x000000ffU   /* RO    */,
                      r32.rw1c_mask /* RW1C  */,
                      0x00ff0000U   /* RsvdP */,
                      0xff000000U   /* RsvdZ */);

    /* ro */
    r32.val = 0x0f0f0f0fU;
    VPCI_READ_CHECK(32, 1, 0x0f);
    VPCI_WRITE(32, 1, 0x5a);
    VPCI_READ_CHECK(32, 1, 0x0f);
    assert(r32.val == 0x000f0f0fU);

    /* rw1c */
    r32.val = 0x0f0f0f0fU;
    VPCI_READ_CHECK(33, 1, 0x0f);
    VPCI_WRITE(33, 1, 0x5a);
    VPCI_READ_CHECK(33, 1, 0x05);
    assert(r32.val == 0x000f050fU);

    /* rsvdp */
    r32.val = 0x0f0f0f0fU;
    VPCI_READ_CHECK(34, 1, 0);
    VPCI_WRITE(34, 1, 0x5a);
    VPCI_READ_CHECK(34, 1, 0);
    assert(r32.val == 0x000f0f0fU);

    /* rsvdz */
    r32.val = 0x0f0f0f0fU;
    VPCI_READ_CHECK(35, 1, 0);
    VPCI_WRITE(35, 1, 0x5a);
    VPCI_READ_CHECK(35, 1, 0);
    assert(r32.val == 0x000f0f0fU);

    /* write all 0's */
    r32.val = 0x0f0f0f0fU;
    VPCI_READ_CHECK(32, 4, 0x00000f0fU);
    VPCI_WRITE(32, 4, 0);
    VPCI_READ_CHECK(32, 4, 0x00000f0fU);
    assert(r32.val == 0x000f0f0fU);

    /* write all 1's */
    r32.val = 0x0f0f0f0fU;
    VPCI_READ_CHECK(32, 4, 0x00000f0fU);
    VPCI_WRITE(32, 4, 0xffffffffU);
    VPCI_READ_CHECK(32, 4, 0x0000000fU);
    assert(r32.val == 0x000f000fU);

    /* Finally try to remove a couple of registers. */
    VPCI_REMOVE_REG(28, 1);
    VPCI_REMOVE_REG(24, 4);
    VPCI_REMOVE_REG(12, 2);

    VPCI_REMOVE_INVALID_REG(20, 1);
    VPCI_REMOVE_INVALID_REG(16, 2);
    VPCI_REMOVE_INVALID_REG(30, 2);

    return 0;
}

/*
 * Local variables:
 * mode: C
 * c-file-style: "BSD"
 * c-basic-offset: 4
 * indent-tabs-mode: nil
 * End:
 */
