/*
 * QEMU model of the XMPU Xilinx Memory Protection
 *
 * Copyright (c) 2020 Xilinx Inc.
 *
 * Autogenerated by xregqemu.py 2020-01-13.
 * Written by Joe Komlodi <komlodi@xilinx.com>
 *
 * 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 "qapi/error.h"
#include "qemu/log.h"
#include "migration/vmstate.h"
#include "hw/qdev-properties.h"
#include "sysemu/dma.h"
#include "exec/address-spaces.h"
#include "hw/fdt_generic_util.h"

#include "hw/misc/xlnx-xmpu.h"

void xmpu_update_enabled(XMPU *s)
{
    bool regions_enabled = false;
    bool default_wr = ARRAY_FIELD_EX32(s->regs, CTRL, DEFWRALLOWED);
    bool default_rd = ARRAY_FIELD_EX32(s->regs, CTRL, DEFRDALLOWED);
    int i;

    /* Lookup if this address fits a region.  */
    for (i = NR_XMPU_REGIONS - 1; i >= 0; i--) {
        XMPURegion xr;
        s->decode_region(s, &xr, i);
        if (!xr.config.enable) {
            continue;
        }
        regions_enabled = true;
        break;
    }

    s->enabled = true;
    if (!regions_enabled && default_wr && default_rd) {
        s->enabled = false;
    }
}

void xmpu_flush(XMPU *s)
{
    unsigned int i;

    xmpu_update_enabled(s);
    qemu_set_irq(s->enabled_signal, s->enabled);

    for (i = 0; i < s->cfg.nr_masters; i++) {
        IOMMUTLBEvent entry = {
            .type = IOMMU_NOTIFIER_UNMAP,
            .entry = {
                .target_as = s->masters[i].parent_as,
                .iova = 0,
                .translated_addr = 0,
                .addr_mask = ~0,
                .perm = IOMMU_NONE,
            }
        };
        memory_region_notify_iommu(&s->masters[i].iommu, -1, entry);
        /* Temporary hack.  */
        memory_region_transaction_begin();
        memory_region_set_readonly(MEMORY_REGION(&s->masters[i].iommu), false);
        memory_region_set_readonly(MEMORY_REGION(&s->masters[i].iommu), true);
        memory_region_set_enabled(MEMORY_REGION(&s->masters[i].iommu),
                                  s->enabled);
        memory_region_transaction_commit();
    }
}

MemTxResult xmpu_read_common(void *opaque, XMPU *s, hwaddr addr, uint64_t *val,
                             unsigned size, MemTxAttrs attr)
{
    RegisterInfo *r = &s->regs_info[addr / 4];

    /*
     * Even though register_read_memory does this check for us, there's no way
     * for us to know if it succeeded or failed, so check here as well.
     */
    if (!r->data) {
        qemu_log_mask(LOG_GUEST_ERROR, "%s: Decode error: read from %" HWADDR_PRIx "\n",
                 object_get_canonical_path(OBJECT(s)),
                 addr);
        ARRAY_FIELD_DP32(s->regs, ISR, INV_APB, true);
        *val = 0;
        return MEMTX_DECODE_ERROR;
    }

    *val = register_read_memory(opaque, addr, size);
    return MEMTX_OK;
}

MemTxResult xmpu_write_common(void *opaque, XMPU *s, hwaddr addr, uint64_t val,
                              unsigned size, MemTxAttrs attr)
{
    RegisterInfo *r = &s->regs_info[addr / 4];

    if (!attr.secure) {
        return MEMTX_ERROR;
    }

    /*
     * Even though register_wite_memory does this check for us, there's no way
     * for us to know if it succeeded or failed, so check here as well.
     */
    if (!r->data) {
        qemu_log_mask(LOG_GUEST_ERROR, "%s: Decode error: write to %" HWADDR_PRIx "=%" PRIx64 "\n",
                 object_get_canonical_path(OBJECT(s)),
                 addr, val);
        ARRAY_FIELD_DP32(s->regs, ISR, INV_APB, true);
        return MEMTX_DECODE_ERROR;
    }
    register_write_memory(opaque, addr, val, size);

    return MEMTX_OK;
}

int xmpu_attrs_to_index(IOMMUMemoryRegion *iommu, MemTxAttrs attrs)
{
    uint16_t master_id = attrs.requester_id & 0xffff;

    return (master_id << 1) | attrs.secure;
}

int xmpu_num_indexes(IOMMUMemoryRegion *iommu)
{
    return (1 << 17) - 1;
}

IOMMUTLBEntry xmpu_master_translate(XMPUMaster *xm, hwaddr addr,
                                    bool attr_secure, uint16_t master_id,
                                    bool *sec_vio, int *perm)
{
    XMPU *s = xm->parent;
    XMPURegion xr;
    IOMMUTLBEntry ret = {
        .iova = addr,
        .translated_addr = addr,
        .addr_mask = s->addr_mask,
        .perm = IOMMU_NONE,
    };
    AddressSpace *as_map[] = {
        [IOMMU_NONE] = &xm->down.none.as,
        [IOMMU_RO] = &xm->down.ro.as,
        [IOMMU_WO] = &xm->down.none.as,
        [IOMMU_RW] = &xm->down.rw.as,
    };
    bool default_wr = ARRAY_FIELD_EX32(s->regs, CTRL, DEFWRALLOWED);
    bool default_rd = ARRAY_FIELD_EX32(s->regs, CTRL, DEFRDALLOWED);
    bool sec = attr_secure;
    bool sec_access_check;
    unsigned int nr_matched = 0;
    int i;

    /* No security violation by default.  */
    *sec_vio = false;
    if (!s->enabled) {
        ret.target_as = &xm->down.rw.as;
        ret.perm = IOMMU_RW;
        return ret;
    }

    /* Convert to an absolute address to simplify the compare logic.  */
    addr += xm->base;

    /* Lookup if this address fits a region.  */
    for (i = NR_XMPU_REGIONS - 1; i >= 0; i--) {
        s->decode_region(s, &xr, i);
        if (!xr.config.enable) {
            continue;
        }

        if (s->match(s, &xr, master_id, addr)) {
            nr_matched++;
            xm->curr_region = i;
            /*
             * Determine if this region is accessible by the transactions
             * security domain.
             */
            if (xr.config.nschecktype) {
                /*
                 * In strict mode, secure accesses are not allowed to
                 * non-secure regions (and vice-versa).
                 */
                sec_access_check = (sec != xr.config.regionns);
            } else {
                /*
                 * In relaxed mode secure accesses can access any region
                 * while non-secure can only access non-secure areas.
                 */
                sec_access_check = (sec || xr.config.regionns);
            }

            if (sec_access_check) {
                if (xr.config.rdallowed) {
                    ret.perm |= IOMMU_RO;
                }
                if (xr.config.wrallowed) {
                    ret.perm |= IOMMU_WO;
                }
            } else {
                *sec_vio = true;
            }
            break;
        }
    }

    if (nr_matched == 0) {
        /*
         * The region number for default access is equal to the
         * max number of regions
         */
        xm->curr_region = NR_XMPU_REGIONS;
        if (!s->dis_def_rw) {
            if (default_rd) {
                ret.perm |= IOMMU_RO;
            }
            if (default_wr) {
                ret.perm |= IOMMU_WO;
            }
        }
    }

    *perm = ret.perm;
    ret.target_as = as_map[ret.perm];
    if (ret.perm == IOMMU_RO) {
        ret.target_as = &xm->down.none.as;
        ret.perm = IOMMU_RW;
    }
#if 0
    qemu_log("%s: nr_matched=%d AS=%p addr=%lx - > %lx (%lx) perm=%x\n",
             object_get_canonical_path(OBJECT(s)), nr_matched, ret.target_as,
             ret.iova, ret.translated_addr, (addr | ret.addr_mask) - addr + 1,
             ret.perm);
#endif
    return ret;
}

IOMMUTLBEntry xmpu_translate(IOMMUMemoryRegion *mr, hwaddr addr,
                             IOMMUAccessFlags flags, int iommu_idx)
{
    XMPUMaster *xm;
    IOMMUTLBEntry ret;
    uint16_t master_id = iommu_idx >> 1;
    bool secure = iommu_idx & 1;
    bool sec_vio;
    int perm;

    xm = container_of(mr, XMPUMaster, iommu);
    ret = xmpu_master_translate(xm, addr, secure, master_id, &sec_vio, &perm);
#if 0
    qemu_log("%s: nr_matched=%d addr=%lx - > %lx (%lx) perm=%x\n",
           __func__, nr_matched, ret.iova,
          ret.translated_addr, (addr | ret.addr_mask) - addr + 1, ret.perm);
#endif
    ret.perm = IOMMU_RW;
    return ret;
}

void xmpu_init_common(XMPU *s, Object *obj, const char *tn,
                      const MemoryRegionOps *ops,
                      const RegisterAccessInfo *regs_info, size_t regs_info_sz)
{
    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
    RegisterInfoArray *reg_array;

    memory_region_init_io(&s->iomem, obj, ops, s, tn, s->regs_size * 4);
    reg_array =
        register_init_block32(DEVICE(obj), regs_info,
                              regs_info_sz,
                              s->regs_info, s->regs,
                              ops,
                              XILINX_XMPU_ERR_DEBUG,
                              s->regs_size * 4);
    memory_region_add_subregion(&s->iomem,
                                0x0,
                                &reg_array->mem);
    sysbus_init_irq(sbd, &s->irq_isr);
    sysbus_init_mmio(sbd, &s->iomem);

    qdev_init_gpio_out(DEVICE(sbd), &s->enabled_signal, 1);
}

bool xmpu_parse_reg_common(XMPU *s, const char *tn, const char *iommu_tn,
                           const MemoryRegionOps *zero_ops,
                           FDTGenericRegPropInfo reg, FDTGenericMMap *obj,
                           Error **errp)
{
    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
    ObjectClass *klass = object_class_by_name(tn);
    FDTGenericMMapClass *parent_fmc;
    char *name;
    unsigned int i, mid;
    uint64_t prot_base;

    parent_fmc = FDT_GENERIC_MMAP_CLASS(object_class_get_parent(klass));

    for (i = 0; i < (reg.n - 1); i++) {
        mid = i;
        s->masters[mid].base = reg.a[i + 1];

        if (s->cfg.base > reg.a[i + 1]) {
            error_setg(errp, "xmpu: Memory region base address 0x%"PRIx64
                       "cannot be higher than subregion 0x%"PRIx64,
                       s->cfg.base, reg.a[i + 1]);
            return false;
        }
        prot_base = reg.a[i + 1] - s->cfg.base;

        /* Create the read/write address space */
        name = g_strdup_printf("xmpu-down-rw-master%d", mid);
        memory_region_init_alias(&s->masters[mid].down.rw.mr, OBJECT(s),
                                 name, s->protected_mr,
                                 prot_base, reg.s[i + 1]);
        address_space_init(&s->masters[mid].down.rw.as,
                           &s->masters[mid].down.rw.mr, name);
        g_free(name);

        /* Create the read only address space  */
        name = g_strdup_printf("xmpu-down-ro-master%d", mid);
        memory_region_init_alias(&s->masters[mid].down.ro.mr, OBJECT(s),
                                 name, s->protected_mr,
                                 prot_base, reg.s[i + 1]);
        memory_region_set_readonly(&s->masters[mid].down.ro.mr, true);
        address_space_init(&s->masters[mid].down.ro.as,
                           &s->masters[mid].down.ro.mr, name);
        g_free(name);

        /* Create the no access address space */
        name = g_strdup_printf("xmpu-down-none-master\n");
        memory_region_init_io(&s->masters[mid].down.none.mr, OBJECT(s),
                              zero_ops, &s->masters[mid],
                              name, reg.s[i + 1]);
        address_space_init(&s->masters[mid].down.none.as,
                           &s->masters[mid].down.none.mr, name);
        g_free(name);

        name = g_strdup_printf("xmpu-master-%d\n", mid);
        s->masters[mid].parent_as = address_space_init_shareable(
                                            s->masters[mid].parent_mr,
                                            NULL);

        memory_region_init_iommu(&s->masters[mid].iommu,
                                 sizeof(s->masters[mid].iommu),
                                 iommu_tn,
                                 OBJECT(s),
                                 name, reg.s[i + 1]);
        g_free(name);

        name = g_strdup_printf("xmpu-mr-%d\n", mid);
        memory_region_init(&s->masters[mid].mr, OBJECT(s), name, reg.s[i + 1]);

        memory_region_add_subregion_overlap(&s->masters[mid].mr,
                                            0, &s->masters[mid].down.rw.mr, 0);
        memory_region_add_subregion_overlap(&s->masters[mid].mr,
                                        0,
                                        MEMORY_REGION(&s->masters[mid].iommu),
                                        1);
        memory_region_set_enabled(MEMORY_REGION(&s->masters[mid].iommu), false);
        sysbus_init_mmio(sbd, &s->masters[mid].mr);
        g_free(name);
    }
    s->cfg.nr_masters = (i / 2) + 1;

    return parent_fmc ? parent_fmc->parse_reg(obj, reg, errp) : false;
}
