/*
 * QEMU model of the PMC_GLOBAL PMC Global PUF Control Registers
 *
 * Copyright (c) 2020 Xilinx Inc.
 *
 * The model is to enable QEMU to support XilPuf software service
 * (xilpuf_example.c and xilpuf_regeneration_example.c).
 *
 * However, the fictitious helper-data from the registration model, by design,
 * are very much "clonable", so that they can be readily sharable by different
 * QEMU invocatons by different users on different host systems.
 *
 * Autogenerated by xregqemu.py 2020-06-11.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "qemu/osdep.h"
#include "hw/sysbus.h"
#include "hw/register.h"
#include "qemu/bitops.h"
#include "qemu/log.h"
#include "migration/vmstate.h"

#include "hw/irq.h"
#include "hw/qdev-properties.h"
#include "hw/sysbus.h"
#include "hw/register.h"

#include "hw/fdt_generic_util.h"
#include "hw/misc/xlnx-versal-pufhd.h"

#ifndef VERSAL_PUFOP_ERR_DEBUG
#define VERSAL_PUFOP_ERR_DEBUG 0
#endif

#define TYPE_VERSAL_PUFOP "xlnx,versal-puf-ctrl"

#define VERSAL_PUFOP(obj) \
     OBJECT_CHECK(Versal_PUFOP, (obj), TYPE_VERSAL_PUFOP)

REG32(PUF_CMD, 0x0)
REG32(PUF_CFG0, 0x4)
    FIELD(PUF_CFG0, CFG0, 2, 30)
    FIELD(PUF_CFG0, HASH_SEL, 1, 1)
    FIELD(PUF_CFG0, GLBL_FILTER, 0, 1)
REG32(PUF_CFG1, 0x8)
REG32(PUF_SHUT, 0xc)
    FIELD(PUF_SHUT, SOSET, 24, 8)
    FIELD(PUF_SHUT, SOPEN, 0, 24)
REG32(PUF_STATUS, 0x10)
    FIELD(PUF_STATUS, DONE, 30, 1)
    FIELD(PUF_STATUS, KEY_RDY, 3, 1)
    FIELD(PUF_STATUS, ID_RDY, 2, 1)
    FIELD(PUF_STATUS, ID_ZERO, 1, 1)
    FIELD(PUF_STATUS, SYN_WRD_RDY, 0, 1)
REG32(PUF_WORD, 0x18)
REG32(PUF_SYN_ADDR, 0x20)
REG32(PUF_AUX, 0x24)
    FIELD(PUF_AUX, DATA, 4, 24)
REG32(PUF_CHASH, 0x28)
REG32(PUF_CLEAR, 0x2c)
    FIELD(PUF_CLEAR, ID, 0, 1)
REG32(PUF_ID_0, 0x30)
REG32(PUF_ID_1, 0x34)
REG32(PUF_ID_2, 0x38)
REG32(PUF_ID_3, 0x3c)
REG32(PUF_ID_4, 0x40)
REG32(PUF_ID_5, 0x44)
REG32(PUF_ID_6, 0x48)
REG32(PUF_ID_7, 0x4c)

#define R_MAX (R_PUF_ID_7 + 1)

#define BASEOF_PUF_ID(_A) (&_A[R_PUF_ID_0])
#define SIZEOF_PUF_ID     (4 * (R_PUF_ID_7 - R_PUF_ID_0 + 1))

/*
 * API Definition for XilPuf's XPUF_PMC_GLOBAL_PUF_ list of registers.
 *
 * See also:
 *  https://github.com/Xilinx/embeddedsw/blob/release-2020.1/lib/sw_services/xilpuf/src/xpuf_hw.h
 */
enum Versal_PUF_Ops {
    /* Commands */
    XPUF_CMD_NO_OP = 0,
    XPUF_CMD_REGISTRATION = 1,      /* Emit key, helper-data, and id */
    XPUF_CMD_REGEN_ON_DEMAND = 2,   /* Emit key and id from helper-data */
    XPUF_CMD_REGEN_ID_ONLY = 3,     /* Emit only id from helper-data */

    /* Supported configs */
    XPUF_CFG1_INIT_VAL_4K  = 0x0c230090,
    XPUF_CFG1_INIT_VAL_12K = 0x00230150,

    /*
     * Flag to indicate regeneration using helper-data from efuse; see
     *  https://github.com/Xilinx/embeddedsw/blob/release-2020.1/lib/sw_services/xilpuf/src/xpuf.c#L296
     */
    XPUF_EFUSE_SYN_ADD_INIT = 0xF1250A04,
};

/*
 * Model object
 */
typedef struct Versal_PUFOP {
    SysBusDevice parent_obj;
    MemoryRegion iomem;

    ZynqMPAESKeySink *puf_keysink;
    XlnxEFuse *efuse;

    qemu_irq err_out;

    Versal_PUFHD *pufhd;

    uint32_t regs[R_MAX];
    RegisterInfo regs_info[R_MAX];
} Versal_PUFOP;


static void versal_pufop_clear_key(Versal_PUFOP *s)
{
    uint8_t zero[256 / 8];

    memset(zero, 0, sizeof(zero));

    if (s->puf_keysink) {
        zynqmp_aes_key_update(s->puf_keysink, zero, sizeof(zero));
    }
}

static void versal_pufop_clear_id(Versal_PUFOP *s)
{
    memset(BASEOF_PUF_ID(s->regs), 0, SIZEOF_PUF_ID);
    ARRAY_FIELD_DP32(s->regs, PUF_STATUS, ID_ZERO, 1);
}

static void versal_pufop_clear_info(Versal_PUFOP *s)
{
    versal_pufop_clear_key(s);
    versal_pufop_clear_id(s);
}

static void versal_pufop_export_id(Versal_PUFOP *s, const Versal_PUFExtra *info)
{
    memcpy(BASEOF_PUF_ID(s->regs), info->puf_id, SIZEOF_PUF_ID);
    ARRAY_FIELD_DP32(s->regs, PUF_STATUS, ID_RDY, 1);
    ARRAY_FIELD_DP32(s->regs, PUF_STATUS, ID_ZERO, 0);
}

static void versal_pufop_done(Versal_PUFOP *s, const Versal_PUFExtra *info)
{
    versal_pufop_export_id(s, info);
    ARRAY_FIELD_DP32(s->regs, PUF_STATUS, KEY_RDY, !info->id_only);
    ARRAY_FIELD_DP32(s->regs, PUF_STATUS, DONE, 1);
}

static void versal_pufop_regis_start(Versal_PUFOP *s)
{
    bool is_12k;

    versal_pufop_clear_info(s);

    switch (s->regs[R_PUF_CFG1]) {
    case XPUF_CFG1_INIT_VAL_4K:
        is_12k = false;
        break;
    case XPUF_CFG1_INIT_VAL_12K:
        is_12k = true;
        break;
    default:
        qemu_log("warning: PUF-REGISTRATION: Unsupported CFG1 %#02x\n",
                 s->regs[R_PUF_CFG1]);
        goto inval_request;
    }

    g_free(s->pufhd);
    s->pufhd = versal_pufhd_new(s->puf_keysink, is_12k);
    if (s->pufhd == NULL) {
        goto inval_request;
    }

    /*
     * Indicate readiness of the 1st helper-data word. However,
     * the actual 1st word should only be placed into PUF_WORD
     * when PUF_WORD register is read.
     */
    s->regs[R_PUF_STATUS] = 0;
    ARRAY_FIELD_DP32(s->regs, PUF_STATUS, SYN_WRD_RDY, 1);

    return;

 inval_request:
    qemu_irq_pulse(s->err_out);
}

static void versal_pufop_regis_done(Versal_PUFOP *s,
                                    const Versal_PUFExtra *info)
{
    ARRAY_FIELD_DP32(s->regs, PUF_AUX, DATA, info->aux);
    s->regs[R_PUF_CHASH] = info->c_hash;

    versal_pufop_done(s, info);
}

static bool versal_pufop_regen_efuse(Versal_PUFOP *s, Versal_PUFRegen *hd)
{
    g_autofree XlnxEFusePufData *pd = NULL;

    if (!s->efuse) {
        qemu_log("warning: PUF-REGENERATION: eFUSE not connected.\n");
        return false;
    }

    /* Get flags only */
    pd = xlnx_efuse_get_puf(s->efuse, 1);
    if (!pd) {
        qemu_log("warning: PUF-REGENERATION: eFUSE data not available.\n");
        return false;
    }

    /* Enforce regeneration policy as stated in eFUSE */
    if (pd->puf_dis) {
        qemu_log("warning: PUF-REGENERATION: eFUSE PUF_REGEN_DISABLE: 1\n");
        return false;
    }

    /*
     * Check to make sure PUF helper-data in eFUSE has not been
     * marked as invalidated.
     */
    if (!pd->pufsyn_len) {
        qemu_log("warning: PUF-REGENERATION: eFUSE PUF_SYN_INVALID: 1\n");
        return false;
    }

    hd->source = Versal_PUFRegen_EFUSE;
    hd->efuse.dev = s->efuse;
    hd->info.aux = pd->puf_aux;
    hd->info.c_hash = pd->puf_chash;

    return true;
}

static bool versal_pufop_regen_mem(Versal_PUFOP *s, Versal_PUFRegen *hd)
{
    hd->source = Versal_PUFRegen_MEM;

    hd->mem.as = &address_space_memory;
    hd->mem.attr = MEMTXATTRS_UNSPECIFIED;
    hd->mem.addr = s->regs[R_PUF_SYN_ADDR];

    hd->info.aux = ARRAY_FIELD_EX32(s->regs, PUF_AUX, DATA);
    hd->info.c_hash = s->regs[R_PUF_CHASH];

    return true;
}

static void versal_pufop_regen(Versal_PUFOP *s, bool id_only)
{
    Versal_PUFRegen hd_src;
    bool ok;

    versal_pufop_clear_info(s);
    s->regs[R_PUF_STATUS] = 0;

    memset(&hd_src, 0, sizeof(hd_src));
    hd_src.info.id_only = id_only;

    if (s->regs[R_PUF_SYN_ADDR] == XPUF_EFUSE_SYN_ADD_INIT) {
        ok = versal_pufop_regen_efuse(s, &hd_src);
    } else {
        ok = versal_pufop_regen_mem(s, &hd_src);
    }

    if (ok) {
        ok = versal_pufhd_regen(&hd_src, s->puf_keysink);
    }

    if (ok) {
        versal_pufop_done(s, &hd_src.info);
    } else {
        versal_pufop_clear_info(s);
        qemu_irq_pulse(s->err_out);
    }
}

static void versal_pufop_cmd_post_write(RegisterInfo *reg, uint64_t val64)
{
    Versal_PUFOP *s = VERSAL_PUFOP(reg->opaque);

    s->regs[R_PUF_CMD] = (uint32_t)val64;

    switch (s->regs[R_PUF_CMD]) {
    case XPUF_CMD_NO_OP:
        break;
    case XPUF_CMD_REGISTRATION:
        versal_pufop_regis_start(s);
        break;
    case XPUF_CMD_REGEN_ON_DEMAND:
        versal_pufop_regen(s, false);
        break;
    case XPUF_CMD_REGEN_ID_ONLY:
        versal_pufop_regen(s, true);
        break;
    default:
        qemu_log("warning: Unsupported PUF-service request %#02x\n",
                 s->regs[R_PUF_CMD]);
        break;
    }
}

static uint64_t versal_pufop_word_post_read(RegisterInfo *reg, uint64_t val)
{
    Versal_PUFOP *s = VERSAL_PUFOP(reg->opaque);
    Versal_PUFExtra info;
    bool last;

    memset(&info, 0, sizeof(info));

    switch (s->regs[R_PUF_CMD]) {
    case XPUF_CMD_REGISTRATION:
        last = versal_pufhd_next(s->pufhd, &s->regs[R_PUF_WORD], &info);
        if (last) {
            versal_pufop_regis_done(s, &info);
        }

        ARRAY_FIELD_DP32(s->regs, PUF_STATUS, SYN_WRD_RDY, 1);
        break;
    case XPUF_CMD_NO_OP:
    case XPUF_CMD_REGEN_ON_DEMAND:
    case XPUF_CMD_REGEN_ID_ONLY:
        break;  /* PUF_WORD not used for regen */
    default:
        qemu_log("warning: Unsupported PUF-service request %#02x\n",
                 s->regs[R_PUF_CMD]);
        break;
    }

    val = s->regs[R_PUF_WORD];
    return val;
}

static void versal_pufop_clear_post_write(RegisterInfo *reg, uint64_t val64)
{
    Versal_PUFOP *s = VERSAL_PUFOP(reg->opaque);

    if (val64 == R_PUF_CLEAR_ID_MASK) {
        versal_pufop_clear_id(s);
    }

    s->regs[R_PUF_CLEAR] = 0;   /* Always self-clearing */
}

static const RegisterAccessInfo versal_pufop_regs_info[] = {
    {   .name = "PUF_CMD",  .addr = A_PUF_CMD,
        .post_write = versal_pufop_cmd_post_write,
    },{ .name = "PUF_CFG0",  .addr = A_PUF_CFG0,
        .reset = 0x2,
    },{ .name = "PUF_CFG1",  .addr = A_PUF_CFG1,
        .reset = 0x80080,
    },{ .name = "PUF_SHUT",  .addr = A_PUF_SHUT,
        .reset = 0x1000020,
    },{ .name = "PUF_STATUS",  .addr = A_PUF_STATUS,
        .rsvd = 0xbffffff0,
        .ro = 0xffffffff,
    },{ .name = "PUF_WORD",  .addr = A_PUF_WORD,
        .ro = 0xffffffff,
        .post_read = versal_pufop_word_post_read,
    },{ .name = "PUF_SYN_ADDR",  .addr = A_PUF_SYN_ADDR,
        .reset = 0xf0e80a04,
    },{ .name = "PUF_AUX",  .addr = A_PUF_AUX,
        .rsvd = 0xf000000f,
    },{ .name = "PUF_CHASH",  .addr = A_PUF_CHASH,
    },{ .name = "PUF_CLEAR",  .addr = A_PUF_CLEAR,
        .rsvd = 0xfffffffe,
        .post_write = versal_pufop_clear_post_write,
    },{ .name = "PUF_ID_0",  .addr = A_PUF_ID_0,
        .ro = 0xffffffff,
    },{ .name = "PUF_ID_1",  .addr = A_PUF_ID_1,
        .ro = 0xffffffff,
    },{ .name = "PUF_ID_2",  .addr = A_PUF_ID_2,
        .ro = 0xffffffff,
    },{ .name = "PUF_ID_3",  .addr = A_PUF_ID_3,
        .ro = 0xffffffff,
    },{ .name = "PUF_ID_4",  .addr = A_PUF_ID_4,
        .ro = 0xffffffff,
    },{ .name = "PUF_ID_5",  .addr = A_PUF_ID_5,
        .ro = 0xffffffff,
    },{ .name = "PUF_ID_6",  .addr = A_PUF_ID_6,
        .ro = 0xffffffff,
    },{ .name = "PUF_ID_7",  .addr = A_PUF_ID_7,
        .ro = 0xffffffff,
    }
};

static void versal_pufop_reset(DeviceState *dev)
{
    Versal_PUFOP *s = VERSAL_PUFOP(dev);
    unsigned int i;

    versal_pufop_clear_info(s);

    for (i = 0; i < ARRAY_SIZE(s->regs_info); ++i) {
        register_reset(&s->regs_info[i]);
    }
}

static const MemoryRegionOps versal_pufop_ops = {
    .read = register_read_memory,
    .write = register_write_memory,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .valid = {
        .min_access_size = 4,
        .max_access_size = 4,
    },
};

static void versal_pufop_init(Object *obj)
{
    Versal_PUFOP *s = VERSAL_PUFOP(obj);
    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
    RegisterInfoArray *reg_array;

    memory_region_init(&s->iomem, obj, TYPE_VERSAL_PUFOP, R_MAX * 4);
    reg_array =
        register_init_block32(DEVICE(obj), versal_pufop_regs_info,
                              ARRAY_SIZE(versal_pufop_regs_info),
                              s->regs_info, s->regs,
                              &versal_pufop_ops,
                              VERSAL_PUFOP_ERR_DEBUG,
                              R_MAX * 4);
    memory_region_add_subregion(&s->iomem,
                                0x0,
                                &reg_array->mem);
    sysbus_init_mmio(sbd, &s->iomem);

    /* Output signals */
    qdev_init_gpio_out_named(DEVICE(obj), &s->err_out, "puf-error", 1);
}

static const VMStateDescription vmstate_versal_pufop = {
    .name = TYPE_VERSAL_PUFOP,
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT32_ARRAY(regs, Versal_PUFOP, R_MAX),
        VMSTATE_END_OF_LIST(),
    }
};

static Property versal_pufop_props[] = {
    DEFINE_PROP_LINK("efuse",
                     Versal_PUFOP, efuse,
                     TYPE_XLNX_EFUSE, XlnxEFuse *),

    DEFINE_PROP_LINK("zynqmp-aes-key-sink-puf",
                     Versal_PUFOP, puf_keysink,
                     TYPE_ZYNQMP_AES_KEY_SINK, ZynqMPAESKeySink *),

    DEFINE_PROP_END_OF_LIST(),
};

static const FDTGenericGPIOSet versal_pufop_gpios[] = {
    {
      .names = &fdt_generic_gpio_name_set_gpio,
      .gpios = (FDTGenericGPIOConnection[]) {
        { .name = "puf-error", .fdt_index = 0, .range = 1 },
        { },
      },
    },
    { },
};

static void versal_pufop_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    FDTGenericGPIOClass *fggc = FDT_GENERIC_GPIO_CLASS(klass);

    dc->reset = versal_pufop_reset;
    dc->vmsd = &vmstate_versal_pufop;
    device_class_set_props(dc, versal_pufop_props);

    fggc->controller_gpios = versal_pufop_gpios;
}

static const TypeInfo versal_pufop_info = {
    .name          = TYPE_VERSAL_PUFOP,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(Versal_PUFOP),
    .class_init    = versal_pufop_class_init,
    .instance_init = versal_pufop_init,
    .interfaces = (InterfaceInfo[]) {
        { TYPE_FDT_GENERIC_GPIO },
        { }
    },
};

static void versal_pufop_register_types(void)
{
    type_register_static(&versal_pufop_info);
}

type_init(versal_pufop_register_types)
