summaryrefslogblamecommitdiffstats
path: root/hw/misc/tz-mpc.c
blob: 30481e1c9094de33beb148abff58ecd747e32a5f (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13












                                                                       
                        


                       
                              
                              
                   
                           
                               











                                                                           


                                




                     
                              
                      
                               
                   
                            

                      


                                    
                    
                             


















                                        




                                                   





                                                                            



                                          















                                                                        

                                           
 



                                                                      
 

                                        
                          
                                                      
                
                                                      
         
                                                                     
                          
                                                      
                
                                                      
         
                                                                      


     








                                                              



                                                                   
                              












                                                                         



                    
                           


























                                                                           













































                                                                         
                              



















                                                                          








                                            






                                                                   









                                                                              
                     











                                                                         
                                                                          


















                                              

































                                                                                






































                                                                               







                                                                           

                              


                                                            
                                               





                                                                            

                              

                                                                    
                                               




























                                                                          

                                                                    
       
                                                               












                                                                            
                                                      













                                                                            









                                                         








































































                                                                             
 
                                              










                                                                               





                                                  
                                  
                                







                                                                
















                                                             
                                                  
































                                                                     
/*
 * ARM AHB5 TrustZone Memory Protection Controller emulation
 *
 * Copyright (c) 2018 Linaro Limited
 * Written by Peter Maydell
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 or
 * (at your option) any later version.
 */

#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qapi/error.h"
#include "trace.h"
#include "hw/sysbus.h"
#include "migration/vmstate.h"
#include "hw/registerfields.h"
#include "hw/irq.h"
#include "hw/misc/tz-mpc.h"
#include "hw/qdev-properties.h"

/* Our IOMMU has two IOMMU indexes, one for secure transactions and one for
 * non-secure transactions.
 */
enum {
    IOMMU_IDX_S,
    IOMMU_IDX_NS,
    IOMMU_NUM_INDEXES,
};

/* Config registers */
REG32(CTRL, 0x00)
    FIELD(CTRL, SEC_RESP, 4, 1)
    FIELD(CTRL, AUTOINC, 8, 1)
    FIELD(CTRL, LOCKDOWN, 31, 1)
REG32(BLK_MAX, 0x10)
REG32(BLK_CFG, 0x14)
REG32(BLK_IDX, 0x18)
REG32(BLK_LUT, 0x1c)
REG32(INT_STAT, 0x20)
    FIELD(INT_STAT, IRQ, 0, 1)
REG32(INT_CLEAR, 0x24)
    FIELD(INT_CLEAR, IRQ, 0, 1)
REG32(INT_EN, 0x28)
    FIELD(INT_EN, IRQ, 0, 1)
REG32(INT_INFO1, 0x2c)
REG32(INT_INFO2, 0x30)
    FIELD(INT_INFO2, HMASTER, 0, 16)
    FIELD(INT_INFO2, HNONSEC, 16, 1)
    FIELD(INT_INFO2, CFG_NS, 17, 1)
REG32(INT_SET, 0x34)
    FIELD(INT_SET, IRQ, 0, 1)
REG32(PIDR4, 0xfd0)
REG32(PIDR5, 0xfd4)
REG32(PIDR6, 0xfd8)
REG32(PIDR7, 0xfdc)
REG32(PIDR0, 0xfe0)
REG32(PIDR1, 0xfe4)
REG32(PIDR2, 0xfe8)
REG32(PIDR3, 0xfec)
REG32(CIDR0, 0xff0)
REG32(CIDR1, 0xff4)
REG32(CIDR2, 0xff8)
REG32(CIDR3, 0xffc)

static const uint8_t tz_mpc_idregs[] = {
    0x04, 0x00, 0x00, 0x00,
    0x60, 0xb8, 0x1b, 0x00,
    0x0d, 0xf0, 0x05, 0xb1,
};

static void tz_mpc_irq_update(TZMPC *s)
{
    qemu_set_irq(s->irq, s->int_stat && s->int_en);
}

static void tz_mpc_iommu_notify(TZMPC *s, uint32_t lutidx,
                                uint32_t oldlut, uint32_t newlut)
{
    /* Called when the LUT word at lutidx has changed from oldlut to newlut;
     * must call the IOMMU notifiers for the changed blocks.
     */
    IOMMUTLBEvent event = {
        .entry = {
            .addr_mask = s->blocksize - 1,
        }
    };
    hwaddr addr = lutidx * s->blocksize * 32;
    int i;

    for (i = 0; i < 32; i++, addr += s->blocksize) {
        bool block_is_ns;

        if (!((oldlut ^ newlut) & (1 << i))) {
            continue;
        }
        /* This changes the mappings for both the S and the NS space,
         * so we need to do four notifies: an UNMAP then a MAP for each.
         */
        block_is_ns = newlut & (1 << i);

        trace_tz_mpc_iommu_notify(addr);
        event.entry.iova = addr;
        event.entry.translated_addr = addr;

        event.type = IOMMU_NOTIFIER_UNMAP;
        event.entry.perm = IOMMU_NONE;
        memory_region_notify_iommu(&s->upstream, IOMMU_IDX_S, event);
        memory_region_notify_iommu(&s->upstream, IOMMU_IDX_NS, event);

        event.type = IOMMU_NOTIFIER_MAP;
        event.entry.perm = IOMMU_RW;
        if (block_is_ns) {
            event.entry.target_as = &s->blocked_io_as;
        } else {
            event.entry.target_as = &s->downstream_as;
        }
        memory_region_notify_iommu(&s->upstream, IOMMU_IDX_S, event);
        if (block_is_ns) {
            event.entry.target_as = &s->downstream_as;
        } else {
            event.entry.target_as = &s->blocked_io_as;
        }
        memory_region_notify_iommu(&s->upstream, IOMMU_IDX_NS, event);
    }
}

static void tz_mpc_autoinc_idx(TZMPC *s, unsigned access_size)
{
    /* Auto-increment BLK_IDX if necessary */
    if (access_size == 4 && (s->ctrl & R_CTRL_AUTOINC_MASK)) {
        s->blk_idx++;
        s->blk_idx %= s->blk_max;
    }
}

static MemTxResult tz_mpc_reg_read(void *opaque, hwaddr addr,
                                   uint64_t *pdata,
                                   unsigned size, MemTxAttrs attrs)
{
    TZMPC *s = TZ_MPC(opaque);
    uint64_t r;
    uint32_t offset = addr & ~0x3;

    if (!attrs.secure && offset < A_PIDR4) {
        /* NS accesses can only see the ID registers */
        qemu_log_mask(LOG_GUEST_ERROR,
                      "TZ MPC register read: NS access to offset 0x%x\n",
                      offset);
        r = 0;
        goto read_out;
    }

    switch (offset) {
    case A_CTRL:
        r = s->ctrl;
        break;
    case A_BLK_MAX:
        r = s->blk_max - 1;
        break;
    case A_BLK_CFG:
        /* We are never in "init in progress state", so this just indicates
         * the block size. s->blocksize == (1 << BLK_CFG + 5), so
         * BLK_CFG == ctz32(s->blocksize) - 5
         */
        r = ctz32(s->blocksize) - 5;
        break;
    case A_BLK_IDX:
        r = s->blk_idx;
        break;
    case A_BLK_LUT:
        r = s->blk_lut[s->blk_idx];
        tz_mpc_autoinc_idx(s, size);
        break;
    case A_INT_STAT:
        r = s->int_stat;
        break;
    case A_INT_EN:
        r = s->int_en;
        break;
    case A_INT_INFO1:
        r = s->int_info1;
        break;
    case A_INT_INFO2:
        r = s->int_info2;
        break;
    case A_PIDR4:
    case A_PIDR5:
    case A_PIDR6:
    case A_PIDR7:
    case A_PIDR0:
    case A_PIDR1:
    case A_PIDR2:
    case A_PIDR3:
    case A_CIDR0:
    case A_CIDR1:
    case A_CIDR2:
    case A_CIDR3:
        r = tz_mpc_idregs[(offset - A_PIDR4) / 4];
        break;
    case A_INT_CLEAR:
    case A_INT_SET:
        qemu_log_mask(LOG_GUEST_ERROR,
                      "TZ MPC register read: write-only offset 0x%x\n",
                      offset);
        r = 0;
        break;
    default:
        qemu_log_mask(LOG_GUEST_ERROR,
                      "TZ MPC register read: bad offset 0x%x\n", offset);
        r = 0;
        break;
    }

    if (size != 4) {
        /* None of our registers are read-sensitive (except BLK_LUT,
         * which can special case the "size not 4" case), so just
         * pull the right bytes out of the word read result.
         */
        r = extract32(r, (addr & 3) * 8, size * 8);
    }

read_out:
    trace_tz_mpc_reg_read(addr, r, size);
    *pdata = r;
    return MEMTX_OK;
}

static MemTxResult tz_mpc_reg_write(void *opaque, hwaddr addr,
                                    uint64_t value,
                                    unsigned size, MemTxAttrs attrs)
{
    TZMPC *s = TZ_MPC(opaque);
    uint32_t offset = addr & ~0x3;

    trace_tz_mpc_reg_write(addr, value, size);

    if (!attrs.secure && offset < A_PIDR4) {
        /* NS accesses can only see the ID registers */
        qemu_log_mask(LOG_GUEST_ERROR,
                      "TZ MPC register write: NS access to offset 0x%x\n",
                      offset);
        return MEMTX_OK;
    }

    if (size != 4) {
        /* Expand the byte or halfword write to a full word size.
         * In most cases we can do this with zeroes; the exceptions
         * are CTRL, BLK_IDX and BLK_LUT.
         */
        uint32_t oldval;

        switch (offset) {
        case A_CTRL:
            oldval = s->ctrl;
            break;
        case A_BLK_IDX:
            oldval = s->blk_idx;
            break;
        case A_BLK_LUT:
            oldval = s->blk_lut[s->blk_idx];
            break;
        default:
            oldval = 0;
            break;
        }
        value = deposit32(oldval, (addr & 3) * 8, size * 8, value);
    }

    if ((s->ctrl & R_CTRL_LOCKDOWN_MASK) &&
        (offset == A_CTRL || offset == A_BLK_LUT || offset == A_INT_EN)) {
        /* Lockdown mode makes these three registers read-only, and
         * the only way out of it is to reset the device.
         */
        qemu_log_mask(LOG_GUEST_ERROR, "TZ MPC register write to offset 0x%x "
                      "while MPC is in lockdown mode\n", offset);
        return MEMTX_OK;
    }

    switch (offset) {
    case A_CTRL:
        /* We don't implement the 'data gating' feature so all other bits
         * are reserved and we make them RAZ/WI.
         */
        s->ctrl = value & (R_CTRL_SEC_RESP_MASK |
                           R_CTRL_AUTOINC_MASK |
                           R_CTRL_LOCKDOWN_MASK);
        break;
    case A_BLK_IDX:
        s->blk_idx = value % s->blk_max;
        break;
    case A_BLK_LUT:
        tz_mpc_iommu_notify(s, s->blk_idx, s->blk_lut[s->blk_idx], value);
        s->blk_lut[s->blk_idx] = value;
        tz_mpc_autoinc_idx(s, size);
        break;
    case A_INT_CLEAR:
        if (value & R_INT_CLEAR_IRQ_MASK) {
            s->int_stat = 0;
            tz_mpc_irq_update(s);
        }
        break;
    case A_INT_EN:
        s->int_en = value & R_INT_EN_IRQ_MASK;
        tz_mpc_irq_update(s);
        break;
    case A_INT_SET:
        if (value & R_INT_SET_IRQ_MASK) {
            s->int_stat = R_INT_STAT_IRQ_MASK;
            tz_mpc_irq_update(s);
        }
        break;
    case A_PIDR4:
    case A_PIDR5:
    case A_PIDR6:
    case A_PIDR7:
    case A_PIDR0:
    case A_PIDR1:
    case A_PIDR2:
    case A_PIDR3:
    case A_CIDR0:
    case A_CIDR1:
    case A_CIDR2:
    case A_CIDR3:
        qemu_log_mask(LOG_GUEST_ERROR,
                      "TZ MPC register write: read-only offset 0x%x\n", offset);
        break;
    default:
        qemu_log_mask(LOG_GUEST_ERROR,
                      "TZ MPC register write: bad offset 0x%x\n", offset);
        break;
    }

    return MEMTX_OK;
}

static const MemoryRegionOps tz_mpc_reg_ops = {
    .read_with_attrs = tz_mpc_reg_read,
    .write_with_attrs = tz_mpc_reg_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .valid.min_access_size = 1,
    .valid.max_access_size = 4,
    .impl.min_access_size = 1,
    .impl.max_access_size = 4,
};

static inline bool tz_mpc_cfg_ns(TZMPC *s, hwaddr addr)
{
    /* Return the cfg_ns bit from the LUT for the specified address */
    hwaddr blknum = addr / s->blocksize;
    hwaddr blkword = blknum / 32;
    uint32_t blkbit = 1U << (blknum % 32);

    /* This would imply the address was larger than the size we
     * defined this memory region to be, so it can't happen.
     */
    assert(blkword < s->blk_max);
    return s->blk_lut[blkword] & blkbit;
}

static MemTxResult tz_mpc_handle_block(TZMPC *s, hwaddr addr, MemTxAttrs attrs)
{
    /* Handle a blocked transaction: raise IRQ, capture info, etc */
    if (!s->int_stat) {
        /* First blocked transfer: capture information into INT_INFO1 and
         * INT_INFO2. Subsequent transfers are still blocked but don't
         * capture information until the guest clears the interrupt.
         */

        s->int_info1 = addr;
        s->int_info2 = 0;
        s->int_info2 = FIELD_DP32(s->int_info2, INT_INFO2, HMASTER,
                                  attrs.requester_id & 0xffff);
        s->int_info2 = FIELD_DP32(s->int_info2, INT_INFO2, HNONSEC,
                                  ~attrs.secure);
        s->int_info2 = FIELD_DP32(s->int_info2, INT_INFO2, CFG_NS,
                                  tz_mpc_cfg_ns(s, addr));
        s->int_stat |= R_INT_STAT_IRQ_MASK;
        tz_mpc_irq_update(s);
    }

    /* Generate bus error if desired; otherwise RAZ/WI */
    return (s->ctrl & R_CTRL_SEC_RESP_MASK) ? MEMTX_ERROR : MEMTX_OK;
}

/* Accesses only reach these read and write functions if the MPC is
 * blocking them; non-blocked accesses go directly to the downstream
 * memory region without passing through this code.
 */
static MemTxResult tz_mpc_mem_blocked_read(void *opaque, hwaddr addr,
                                           uint64_t *pdata,
                                           unsigned size, MemTxAttrs attrs)
{
    TZMPC *s = TZ_MPC(opaque);

    trace_tz_mpc_mem_blocked_read(addr, size, attrs.secure);

    *pdata = 0;
    return tz_mpc_handle_block(s, addr, attrs);
}

static MemTxResult tz_mpc_mem_blocked_write(void *opaque, hwaddr addr,
                                            uint64_t value,
                                            unsigned size, MemTxAttrs attrs)
{
    TZMPC *s = TZ_MPC(opaque);

    trace_tz_mpc_mem_blocked_write(addr, value, size, attrs.secure);

    return tz_mpc_handle_block(s, addr, attrs);
}

static const MemoryRegionOps tz_mpc_mem_blocked_ops = {
    .read_with_attrs = tz_mpc_mem_blocked_read,
    .write_with_attrs = tz_mpc_mem_blocked_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .valid.min_access_size = 1,
    .valid.max_access_size = 8,
    .impl.min_access_size = 1,
    .impl.max_access_size = 8,
};

static IOMMUTLBEntry tz_mpc_translate(IOMMUMemoryRegion *iommu,
                                      hwaddr addr, IOMMUAccessFlags flags,
                                      int iommu_idx)
{
    TZMPC *s = TZ_MPC(container_of(iommu, TZMPC, upstream));
    bool ok;

    IOMMUTLBEntry ret = {
        .iova = addr & ~(s->blocksize - 1),
        .translated_addr = addr & ~(s->blocksize - 1),
        .addr_mask = s->blocksize - 1,
        .perm = IOMMU_RW,
    };

    /* Look at the per-block configuration for this address, and
     * return a TLB entry directing the transaction at either
     * downstream_as or blocked_io_as, as appropriate.
     * If the LUT cfg_ns bit is 1, only non-secure transactions
     * may pass. If the bit is 0, only secure transactions may pass.
     */
    ok = tz_mpc_cfg_ns(s, addr) == (iommu_idx == IOMMU_IDX_NS);

    trace_tz_mpc_translate(addr, flags,
                           iommu_idx == IOMMU_IDX_S ? "S" : "NS",
                           ok ? "pass" : "block");

    ret.target_as = ok ? &s->downstream_as : &s->blocked_io_as;
    return ret;
}

static int tz_mpc_attrs_to_index(IOMMUMemoryRegion *iommu, MemTxAttrs attrs)
{
    /* We treat unspecified attributes like secure. Transactions with
     * unspecified attributes come from places like
     * rom_reset() for initial image load, and we want
     * those to pass through the from-reset "everything is secure" config.
     * All the real during-emulation transactions from the CPU will
     * specify attributes.
     */
    return (attrs.unspecified || attrs.secure) ? IOMMU_IDX_S : IOMMU_IDX_NS;
}

static int tz_mpc_num_indexes(IOMMUMemoryRegion *iommu)
{
    return IOMMU_NUM_INDEXES;
}

static void tz_mpc_reset(DeviceState *dev)
{
    TZMPC *s = TZ_MPC(dev);

    s->ctrl = 0x00000100;
    s->blk_idx = 0;
    s->int_stat = 0;
    s->int_en = 1;
    s->int_info1 = 0;
    s->int_info2 = 0;

    memset(s->blk_lut, 0, s->blk_max * sizeof(uint32_t));
}

static void tz_mpc_init(Object *obj)
{
    DeviceState *dev = DEVICE(obj);
    TZMPC *s = TZ_MPC(obj);

    qdev_init_gpio_out_named(dev, &s->irq, "irq", 1);
}

static void tz_mpc_realize(DeviceState *dev, Error **errp)
{
    Object *obj = OBJECT(dev);
    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
    TZMPC *s = TZ_MPC(dev);
    uint64_t size;

    /* We can't create the upstream end of the port until realize,
     * as we don't know the size of the MR used as the downstream until then.
     * We insist on having a downstream, to avoid complicating the code
     * with handling the "don't know how big this is" case. It's easy
     * enough for the user to create an unimplemented_device as downstream
     * if they have nothing else to plug into this.
     */
    if (!s->downstream) {
        error_setg(errp, "MPC 'downstream' link not set");
        return;
    }

    size = memory_region_size(s->downstream);

    memory_region_init_iommu(&s->upstream, sizeof(s->upstream),
                             TYPE_TZ_MPC_IOMMU_MEMORY_REGION,
                             obj, "tz-mpc-upstream", size);

    /* In real hardware the block size is configurable. In QEMU we could
     * make it configurable but will need it to be at least as big as the
     * target page size so we can execute out of the resulting MRs. Guest
     * software is supposed to check the block size using the BLK_CFG
     * register, so make it fixed at the page size.
     */
    s->blocksize = memory_region_iommu_get_min_page_size(&s->upstream);
    if (size % s->blocksize != 0) {
        error_setg(errp,
                   "MPC 'downstream' size %" PRId64
                   " is not a multiple of %" HWADDR_PRIx " bytes",
                   size, s->blocksize);
        object_unref(OBJECT(&s->upstream));
        return;
    }

    /* BLK_MAX is the max value of BLK_IDX, which indexes an array of 32-bit
     * words, each bit of which indicates one block.
     */
    s->blk_max = DIV_ROUND_UP(size / s->blocksize, 32);

    memory_region_init_io(&s->regmr, obj, &tz_mpc_reg_ops,
                          s, "tz-mpc-regs", 0x1000);
    sysbus_init_mmio(sbd, &s->regmr);

    sysbus_init_mmio(sbd, MEMORY_REGION(&s->upstream));

    /* This memory region is not exposed to users of this device as a
     * sysbus MMIO region, but is instead used internally as something
     * that our IOMMU translate function might direct accesses to.
     */
    memory_region_init_io(&s->blocked_io, obj, &tz_mpc_mem_blocked_ops,
                          s, "tz-mpc-blocked-io", size);

    address_space_init(&s->downstream_as, s->downstream,
                       "tz-mpc-downstream");
    address_space_init(&s->blocked_io_as, &s->blocked_io,
                       "tz-mpc-blocked-io");

    s->blk_lut = g_new0(uint32_t, s->blk_max);
}

static int tz_mpc_post_load(void *opaque, int version_id)
{
    TZMPC *s = TZ_MPC(opaque);

    /* Check the incoming data doesn't point blk_idx off the end of blk_lut. */
    if (s->blk_idx >= s->blk_max) {
        return -1;
    }
    return 0;
}

static const VMStateDescription tz_mpc_vmstate = {
    .name = "tz-mpc",
    .version_id = 1,
    .minimum_version_id = 1,
    .post_load = tz_mpc_post_load,
    .fields = (VMStateField[]) {
        VMSTATE_UINT32(ctrl, TZMPC),
        VMSTATE_UINT32(blk_idx, TZMPC),
        VMSTATE_UINT32(int_stat, TZMPC),
        VMSTATE_UINT32(int_en, TZMPC),
        VMSTATE_UINT32(int_info1, TZMPC),
        VMSTATE_UINT32(int_info2, TZMPC),
        VMSTATE_VARRAY_UINT32(blk_lut, TZMPC, blk_max,
                              0, vmstate_info_uint32, uint32_t),
        VMSTATE_END_OF_LIST()
    }
};

static Property tz_mpc_properties[] = {
    DEFINE_PROP_LINK("downstream", TZMPC, downstream,
                     TYPE_MEMORY_REGION, MemoryRegion *),
    DEFINE_PROP_END_OF_LIST(),
};

static void tz_mpc_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->realize = tz_mpc_realize;
    dc->vmsd = &tz_mpc_vmstate;
    dc->reset = tz_mpc_reset;
    device_class_set_props(dc, tz_mpc_properties);
}

static const TypeInfo tz_mpc_info = {
    .name = TYPE_TZ_MPC,
    .parent = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(TZMPC),
    .instance_init = tz_mpc_init,
    .class_init = tz_mpc_class_init,
};

static void tz_mpc_iommu_memory_region_class_init(ObjectClass *klass,
                                                  void *data)
{
    IOMMUMemoryRegionClass *imrc = IOMMU_MEMORY_REGION_CLASS(klass);

    imrc->translate = tz_mpc_translate;
    imrc->attrs_to_index = tz_mpc_attrs_to_index;
    imrc->num_indexes = tz_mpc_num_indexes;
}

static const TypeInfo tz_mpc_iommu_memory_region_info = {
    .name = TYPE_TZ_MPC_IOMMU_MEMORY_REGION,
    .parent = TYPE_IOMMU_MEMORY_REGION,
    .class_init = tz_mpc_iommu_memory_region_class_init,
};

static void tz_mpc_register_types(void)
{
    type_register_static(&tz_mpc_info);
    type_register_static(&tz_mpc_iommu_memory_region_info);
}

type_init(tz_mpc_register_types);