/*
 * QEMU model of the ASU_SHA2 Secure Hash Algorithm
 *
 * Copyright (c) 2023, Advanced Micro Devices, Inc.
 *
 * 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.
 */

/* Autogenerated by xregqemu.py 2023-03-14.  */

#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/stream.h"
#include "hw/hw.h"
#include "qapi/error.h"
#include "crypto/sha2.h"

#ifndef XILINX_ASU_SHA2_ERR_DEBUG
#define XILINX_ASU_SHA2_ERR_DEBUG 0
#endif

#define TYPE_XILINX_ASU_SHA2 "xlnx.asu_sha2"

#define XILINX_ASU_SHA2(obj) \
     OBJECT_CHECK(ASU_SHA2, (obj), TYPE_XILINX_ASU_SHA2)

/* Describe the current state of the hash block.  */
enum State {
    ASU_SHA2_IDLE = 0,
    ASU_SHA2_RESETING,
    ASU_SHA2_RUNNING,
};

enum ASU_SHA_MODE {
    SHA_MODE_256 = 0,
    SHA_MODE_384 = 1,
    SHA_MODE_512 = 2,
    SHA_MODE_RESERVED = 3,
};

#define ASU_SHA2_MAX_DIGEST_LEN (512 >> 3)
#define ASU_SHA2_MAX_BLOCK_SIZE (144)

REG32(SHA_START, 0x0)
    FIELD(SHA_START, VALUE, 0, 1)
REG32(SHA_RESET, 0x4)
    FIELD(SHA_RESET, VALUE, 0, 1)
REG32(SHA_DONE, 0x8)
    FIELD(SHA_DONE, VALUE, 0, 1)
REG32(SHA_DIGEST_0, 0x10)
REG32(SHA_DIGEST_1, 0x14)
REG32(SHA_DIGEST_2, 0x18)
REG32(SHA_DIGEST_3, 0x1c)
REG32(SHA_DIGEST_4, 0x20)
REG32(SHA_DIGEST_5, 0x24)
REG32(SHA_DIGEST_6, 0x28)
REG32(SHA_DIGEST_7, 0x2c)
REG32(SHA_DIGEST_8, 0x30)
REG32(SHA_DIGEST_9, 0x34)
REG32(SHA_DIGEST_10, 0x38)
REG32(SHA_DIGEST_11, 0x3c)
REG32(SHA_DIGEST_12, 0x40)
REG32(SHA_DIGEST_13, 0x44)
REG32(SHA_DIGEST_14, 0x48)
REG32(SHA_DIGEST_15, 0x4c)
REG32(SHA_MODE, 0xa0)
    FIELD(SHA_MODE, VALUE, 0, 2)
REG32(SHA_AUTO_PADDING, 0xa4)
    FIELD(SHA_AUTO_PADDING, ENABLE, 0, 1)
REG32(SHA_SLV_ERR_CTRL, 0xb0)
    FIELD(SHA_SLV_ERR_CTRL, ENABLE, 0, 1)
REG32(SHA_ISR, 0xb4)
    FIELD(SHA_ISR, SLVERR, 1, 1)
    FIELD(SHA_ISR, DONE, 0, 1)
REG32(SHA_IMR, 0xb8)
    FIELD(SHA_IMR, SLVERR, 1, 1)
    FIELD(SHA_IMR, DONE, 0, 1)
REG32(SHA_IER, 0xbc)
    FIELD(SHA_IER, SLVERR, 1, 1)
    FIELD(SHA_IER, DONE, 0, 1)
REG32(SHA_IDR, 0xc0)
    FIELD(SHA_IDR, SLVERR, 1, 1)
    FIELD(SHA_IDR, DONE, 0, 1)

#define ASU_SHA2_R_MAX (R_SHA_IDR + 1)

typedef struct ASU_SHA2 {
    SysBusDevice parent_obj;
    MemoryRegion iomem;
    qemu_irq irq_sha_imr;

    uint32_t state;

    uint32_t regs[ASU_SHA2_R_MAX];
    RegisterInfo regs_info[ASU_SHA2_R_MAX];

    /* Hash algorithm latched when start was asserted.  */
    enum ASU_SHA_MODE alg;

    union {
        struct sha256_ctx sha256;
        struct sha384_ctx sha384;
        struct sha512_ctx sha512;
    } hash_contexts;
} ASU_SHA2;

static void asu_sha2_log_guest_error(ASU_SHA2 *s,
                                     const char *fn,
                                     const char *msg)
{
    char *path = object_get_canonical_path(OBJECT(s));

    qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: %s", path, fn, msg);
    g_free(path);
}

static void asu_sha2_start_postw(RegisterInfo *reg, uint64_t value)
{
    ASU_SHA2 *s = XILINX_ASU_SHA2(reg->opaque);

    if (s->state == ASU_SHA2_RESETING) {
        /* Don't do anything while reset.  */
        return;
    }

    if (!ARRAY_FIELD_EX32(s->regs, SHA_START, VALUE)) {
        /* Writing zero to that field doesn't have any effect.  */
        return;
    }

    /* Start bit is self clearing.  */
    ARRAY_FIELD_DP32(s->regs, SHA_START, VALUE, false);

    /*
     * Drop the SHA_DONE bit, driver will need to wait for this one to be set
     * before reading the digest.
     */
    ARRAY_FIELD_DP32(s->regs, SHA_DONE, VALUE, false);

    s->alg = ARRAY_FIELD_EX32(s->regs, SHA_MODE, VALUE);

    switch (s->alg) {
    case SHA_MODE_256:
        sha256_init(&s->hash_contexts.sha256);
        break;
    case SHA_MODE_384:
        sha384_init(&s->hash_contexts.sha384);
        break;
    case SHA_MODE_512:
        sha512_init(&s->hash_contexts.sha512);
        break;
    default:
        /*
         * Unsupported bit-field, throw a guest error, and don't put the model
         * in RUNNING mode (it won't accept data from the DMA).
         */
        asu_sha2_log_guest_error(s, __func__, "wrong SHA_MODE value\n");
        return;
    }

    /* All is ok.  Indicate the streaming device we can accept data.  */
    s->state = ASU_SHA2_RUNNING;
}

static void asu_sha2_reset(DeviceState *dev)
{
    ASU_SHA2 *s = XILINX_ASU_SHA2(dev);
    unsigned int i;

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

static void asu_sha2_reset_postw(RegisterInfo *reg, uint64_t value)
{
    ASU_SHA2 *s = XILINX_ASU_SHA2(reg->opaque);

    if (ARRAY_FIELD_EX32(s->regs, SHA_RESET, VALUE)) {
        /* Puts the device in reset mode.  */
        s->state = ASU_SHA2_RESETING;
    } else if (s->state == ASU_SHA2_RESETING) {
        /* 1 -> 0 release from reset mode.  */
        asu_sha2_reset(DEVICE(s));
        s->state = ASU_SHA2_IDLE;
    }
}

static void sha_imr_update_irq(ASU_SHA2 *s)
{
    bool pending = s->regs[R_SHA_ISR] & ~s->regs[R_SHA_IMR];
    qemu_set_irq(s->irq_sha_imr, pending);
}

static void sha_isr_postw(RegisterInfo *reg, uint64_t val64)
{
    ASU_SHA2 *s = XILINX_ASU_SHA2(reg->opaque);
    sha_imr_update_irq(s);
}

static uint64_t sha_ier_prew(RegisterInfo *reg, uint64_t val64)
{
    ASU_SHA2 *s = XILINX_ASU_SHA2(reg->opaque);
    uint32_t val = val64;

    s->regs[R_SHA_IMR] &= ~val;
    sha_imr_update_irq(s);
    return 0;
}

static uint64_t sha_idr_prew(RegisterInfo *reg, uint64_t val64)
{
    ASU_SHA2 *s = XILINX_ASU_SHA2(reg->opaque);
    uint32_t val = val64;

    s->regs[R_SHA_IMR] |= val;
    sha_imr_update_irq(s);
    return 0;
}

static const RegisterAccessInfo asu_sha2_regs_info[] = {
    {   .name = "SHA_START",  .addr = A_SHA_START,
        .post_write = asu_sha2_start_postw,
    },{ .name = "SHA_RESET",  .addr = A_SHA_RESET,
        .reset = 0x1,
        .post_write = asu_sha2_reset_postw,
    },{ .name = "SHA_DONE",  .addr = A_SHA_DONE,
        .ro = 0x1,
    },{ .name = "SHA_DIGEST_0",  .addr = A_SHA_DIGEST_0,
        .ro = 0xffffffff,
    },{ .name = "SHA_DIGEST_1",  .addr = A_SHA_DIGEST_1,
        .ro = 0xffffffff,
    },{ .name = "SHA_DIGEST_2",  .addr = A_SHA_DIGEST_2,
        .ro = 0xffffffff,
    },{ .name = "SHA_DIGEST_3",  .addr = A_SHA_DIGEST_3,
        .ro = 0xffffffff,
    },{ .name = "SHA_DIGEST_4",  .addr = A_SHA_DIGEST_4,
        .ro = 0xffffffff,
    },{ .name = "SHA_DIGEST_5",  .addr = A_SHA_DIGEST_5,
        .ro = 0xffffffff,
    },{ .name = "SHA_DIGEST_6",  .addr = A_SHA_DIGEST_6,
        .ro = 0xffffffff,
    },{ .name = "SHA_DIGEST_7",  .addr = A_SHA_DIGEST_7,
        .ro = 0xffffffff,
    },{ .name = "SHA_DIGEST_8",  .addr = A_SHA_DIGEST_8,
        .ro = 0xffffffff,
    },{ .name = "SHA_DIGEST_9",  .addr = A_SHA_DIGEST_9,
        .ro = 0xffffffff,
    },{ .name = "SHA_DIGEST_10",  .addr = A_SHA_DIGEST_10,
        .ro = 0xffffffff,
    },{ .name = "SHA_DIGEST_11",  .addr = A_SHA_DIGEST_11,
        .ro = 0xffffffff,
    },{ .name = "SHA_DIGEST_12",  .addr = A_SHA_DIGEST_12,
        .ro = 0xffffffff,
    },{ .name = "SHA_DIGEST_13",  .addr = A_SHA_DIGEST_13,
        .ro = 0xffffffff,
    },{ .name = "SHA_DIGEST_14",  .addr = A_SHA_DIGEST_14,
        .ro = 0xffffffff,
    },{ .name = "SHA_DIGEST_15",  .addr = A_SHA_DIGEST_15,
        .ro = 0xffffffff,
    },{ .name = "SHA_MODE",  .addr = A_SHA_MODE,
    },{ .name = "SHA_AUTO_PADDING",  .addr = A_SHA_AUTO_PADDING,
    },{ .name = "SHA_SLV_ERR_CTRL",  .addr = A_SHA_SLV_ERR_CTRL,
    },{ .name = "SHA_ISR",  .addr = A_SHA_ISR,
        .w1c = 0x3,
        .post_write = sha_isr_postw,
    },{ .name = "SHA_IMR",  .addr = A_SHA_IMR,
        .reset = 0x3,
        .ro = 0x3,
    },{ .name = "SHA_IER",  .addr = A_SHA_IER,
        .pre_write = sha_ier_prew,
    },{ .name = "SHA_IDR",  .addr = A_SHA_IDR,
        .pre_write = sha_idr_prew,
    }
};

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

static void asu_sha2_init(Object *obj)
{
    ASU_SHA2 *s = XILINX_ASU_SHA2(obj);
    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
    RegisterInfoArray *reg_array;

    memory_region_init(&s->iomem, obj, TYPE_XILINX_ASU_SHA2,
                       ASU_SHA2_R_MAX * 4);
    reg_array =
        register_init_block32(DEVICE(obj), asu_sha2_regs_info,
                              ARRAY_SIZE(asu_sha2_regs_info),
                              s->regs_info, s->regs,
                              &asu_sha2_ops,
                              XILINX_ASU_SHA2_ERR_DEBUG,
                              ASU_SHA2_R_MAX * 4);
    memory_region_add_subregion(&s->iomem,
                                0x0,
                                &reg_array->mem);
    sysbus_init_mmio(sbd, &s->iomem);
    sysbus_init_irq(sbd, &s->irq_sha_imr);
}

static bool asu_sha2_stream_can_push(StreamSink *obj,
                                     StreamCanPushNotifyFn notify,
                                     void *notify_opaque)
{
    ASU_SHA2 *s = XILINX_ASU_SHA2(obj);

    return s->state == ASU_SHA2_RUNNING;
}

static size_t asu_sha2_digest_size(ASU_SHA2 *s)
{
    switch (s->alg) {
    case SHA_MODE_256:
        return 256 / 8;
    case SHA_MODE_384:
        return 384 / 8;
    case SHA_MODE_512:
        return 512 / 8;
    default:
        return 0;
    }
}

static void asu_sha2_update_digest(ASU_SHA2 *s)
{
    uint32_t digest[ASU_SHA2_MAX_DIGEST_LEN >> 2] = {0};
    size_t digest_len = asu_sha2_digest_size(s);

    if (ARRAY_FIELD_EX32(s->regs, SHA_AUTO_PADDING, ENABLE)) {
        switch (s->alg) {
        case SHA_MODE_256:
            sha256_digest(&s->hash_contexts.sha256, digest_len,
                          (uint8_t *)&digest);
            break;
        case SHA_MODE_384:
            sha384_digest(&s->hash_contexts.sha384, digest_len,
                          (uint8_t *)&digest);
            break;
        case SHA_MODE_512:
            sha512_digest(&s->hash_contexts.sha512, digest_len,
                          (uint8_t *)&digest);
            break;
        default:
            break;
        }
    } else {
        switch (s->alg) {
        case SHA_MODE_256:
            sha256_digest_no_pad(&s->hash_contexts.sha256, digest_len,
                                 (uint8_t *)&digest);
            break;
        case SHA_MODE_384:
            sha384_digest_no_pad(&s->hash_contexts.sha384, digest_len,
                                 (uint8_t *)&digest);
            break;
        case SHA_MODE_512:
            sha512_digest_no_pad(&s->hash_contexts.sha512, digest_len,
                                 (uint8_t *)&digest);
            break;
        default:
            break;
        }
    }

    memcpy(&s->regs[R_SHA_DIGEST_0], digest, digest_len);
}

/*
 * Callback from the DMA, consume the data..  Store them in data[] block
 * and pass them through the hash algorithm if a block boundary is crossed.
 */
static size_t asu_sha2_stream_push(StreamSink *obj,
                                   uint8_t *buf,
                                   size_t len,
                                   bool eop)
{
    ASU_SHA2 *s = XILINX_ASU_SHA2(obj);

    /* Is the crypto block ready to accept data?  */
    if (s->state != ASU_SHA2_RUNNING) {
        hw_error("%s: crypto block in bad state %d\n",
                 object_get_canonical_path(OBJECT(s)), s->state);
        return 0;
    }

    switch (s->alg) {
    case SHA_MODE_256:
        sha256_update(&s->hash_contexts.sha256, len, buf);
        break;
    case SHA_MODE_384:
        sha384_update(&s->hash_contexts.sha384, len, buf);
        break;
    case SHA_MODE_512:
        sha512_update(&s->hash_contexts.sha512, len, buf);
        break;
    default:
        /* XXX: Shouldn't happen.  */
        asu_sha2_log_guest_error(s, __func__, "wrong SHA_MODE value\n");
        return len;
    }

    if (eop) {
        /* End of data, handle the padding and updat the digest.  */
        asu_sha2_update_digest(s);
        ARRAY_FIELD_DP32(s->regs, SHA_DONE, VALUE, true);
        ARRAY_FIELD_DP32(s->regs, SHA_ISR, DONE, true);
        sha_imr_update_irq(s);
    }

    return len;
}

static const VMStateDescription vmstate_asu_sha2 = {
    .name = TYPE_XILINX_ASU_SHA2,
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT32_ARRAY(regs, ASU_SHA2, ASU_SHA2_R_MAX),
        VMSTATE_END_OF_LIST(),
    }
};

static void asu_sha2_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    StreamSinkClass *ssc = STREAM_SINK_CLASS(klass);

    dc->vmsd = &vmstate_asu_sha2;
    dc->reset = asu_sha2_reset;

    ssc->push = asu_sha2_stream_push;
    ssc->can_push = asu_sha2_stream_can_push;
}

static const TypeInfo asu_sha2_info = {
    .name          = TYPE_XILINX_ASU_SHA2,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(ASU_SHA2),
    .class_init    = asu_sha2_class_init,
    .instance_init = asu_sha2_init,
    .interfaces = (InterfaceInfo[]) {
        { TYPE_STREAM_SINK },
        { }
    }
};

static void asu_sha2_register_types(void)
{
    type_register_static(&asu_sha2_info);
}

type_init(asu_sha2_register_types)
