qemu-arm
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-arm] [PATCH v11 16/17] hw/arm/smmuv3: IOTLB emulation


From: Auger Eric
Subject: Re: [Qemu-arm] [PATCH v11 16/17] hw/arm/smmuv3: IOTLB emulation
Date: Wed, 25 Apr 2018 16:48:29 +0200
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.4.0

Hi Peter,
On 04/17/2018 02:55 PM, Peter Maydell wrote:
> On 12 April 2018 at 08:38, Eric Auger <address@hidden> wrote:
>> We emulate a TLB cache of size SMMU_IOTLB_MAX_SIZE=256.
>> It is implemented as a hash table whose key is a combination
>> of the 16b asid and 48b IOVA.
>>
>> Entries are invalidated on TLB invalidation commands, either
>> globally, or per asid, or per asid/iova.
>>
>> One peculiarity is the NH_VA invalidation command does not
>> convey any information about the size to be invalidated (as
>> opposed to what Intel does, for instance, with the am field).
>> Hence, when NH_VA arrives we both invalidate the 4K and 64K
>> entries, the both granules that we support.
>>
>> Signed-off-by: Eric Auger <address@hidden>
>>
>> ---
>>
>> Credit to Tomasz Nowicki who did the first implementation of
>> this IOTLB implementation, inspired of intel_iommu implemtation.
>> ---
>>  hw/arm/smmu-common.c         |  33 ++++++++++++++
>>  hw/arm/smmuv3.c              | 105 
>> +++++++++++++++++++++++++++++++++++++++++--
>>  hw/arm/trace-events          |   9 ++++
>>  include/hw/arm/smmu-common.h |  11 +++++
>>  4 files changed, 154 insertions(+), 4 deletions(-)
>>
>> diff --git a/hw/arm/smmu-common.c b/hw/arm/smmu-common.c
>> index c271a28..3d25339 100644
>> --- a/hw/arm/smmu-common.c
>> +++ b/hw/arm/smmu-common.c
>> @@ -29,6 +29,36 @@
>>  #include "hw/arm/smmu-common.h"
>>  #include "smmu-internal.h"
>>
>> +/* IOTLB Management */
>> +
>> +inline void smmu_iotlb_inv_all(SMMUState *s)
>> +{
>> +    trace_smmu_iotlb_inv_all();
>> +    g_hash_table_remove_all(s->iotlb);
>> +}
>> +
>> +static gboolean smmu_hash_remove_by_asid(gpointer key, gpointer value,
>> +                                         gpointer user_data)
>> +{
>> +    uint16_t asid = *(uint16_t *)user_data;
>> +
>> +    return ((*(uint64_t *)key) >> IOTLB_KEY_ASID_SHIFT) == asid;
>> +}
>> +
>> +inline void smmu_iotlb_inv_iova(SMMUState *s, uint16_t asid, dma_addr_t 
>> iova)
>> +{
>> +    uint64_t key = SMMU_IOTLB_KEY(asid, iova);
>> +
>> +    trace_smmu_iotlb_inv_iova(asid, iova);
>> +    g_hash_table_remove(s->iotlb, &key);
>> +}
>> +
>> +inline void smmu_iotlb_inv_asid(SMMUState *s, uint16_t asid)
>> +{
>> +    trace_smmu_iotlb_inv_asid(asid);
>> +    g_hash_table_foreach_remove(s->iotlb, smmu_hash_remove_by_asid, &asid);
>> +}
>> +
>>  /* VMSAv8-64 Translation */
>>
>>  /**
>> @@ -327,6 +357,8 @@ static void smmu_base_realize(DeviceState *dev, Error 
>> **errp)
>>          return;
>>      }
>>      s->configs = g_hash_table_new_full(NULL, NULL, NULL, g_free);
>> +    s->iotlb = g_hash_table_new_full(g_int64_hash, g_int64_equal,
>> +                                     g_free, g_free);
>>      s->smmu_pcibus_by_busptr = g_hash_table_new(NULL, NULL);
>>
>>      if (s->primary_bus) {
>> @@ -341,6 +373,7 @@ static void smmu_base_reset(DeviceState *dev)
>>      SMMUState *s = ARM_SMMU(dev);
>>
>>      g_hash_table_remove_all(s->configs);
>> +    g_hash_table_remove_all(s->iotlb);
>>  }
>>
>>  static Property smmu_dev_properties[] = {
>> diff --git a/hw/arm/smmuv3.c b/hw/arm/smmuv3.c
>> index 938052e..081f0fb 100644
>> --- a/hw/arm/smmuv3.c
>> +++ b/hw/arm/smmuv3.c
>> @@ -551,6 +551,8 @@ static SMMUTransCfg *smmuv3_get_config(SMMUDevice *sdev, 
>> SMMUEventInfo *event)
>>                                         sdev->devfn);
>>          cfg = g_new0(SMMUTransCfg, 1);
>>          g_hash_table_insert(bc->configs, sdev, cfg);
>> +        cfg->iotlb_miss = 0;
>> +        cfg->iotlb_hit = 0;
>>
>>          if (smmuv3_decode_config(&sdev->iommu, cfg, event)) {
>>              g_hash_table_remove(bc->configs, sdev);
>> @@ -575,8 +577,12 @@ static IOMMUTLBEntry smmuv3_translate(IOMMUMemoryRegion 
>> *mr, hwaddr addr,
>>      SMMUDevice *sdev = container_of(mr, SMMUDevice, iommu);
>>      SMMUv3State *s = sdev->smmu;
>>      uint32_t sid = smmu_get_sid(sdev);
>> +    SMMUState *bs = ARM_SMMU(s);
>>      SMMUEventInfo event = {.type = SMMU_EVT_OK, .sid = sid};
>> +    uint64_t key_val, page_mask, aligned_addr;
>> +    IOMMUTLBEntry *cached_entry = NULL;
>>      SMMUPTWEventInfo ptw_info = {};
>> +    SMMUTransTableInfo *tt;
>>      SMMUTransCfg *cfg = NULL;
>>      IOMMUTLBEntry entry = {
>>          .target_as = &address_space_memory,
>> @@ -585,6 +591,7 @@ static IOMMUTLBEntry smmuv3_translate(IOMMUMemoryRegion 
>> *mr, hwaddr addr,
>>          .addr_mask = ~(hwaddr)0,
>>          .perm = IOMMU_NONE,
>>      };
>> +    uint64_t *key;
>>      int ret = 0;
>>
>>      if (!smmu_enabled(s)) {
>> @@ -607,7 +614,56 @@ static IOMMUTLBEntry smmuv3_translate(IOMMUMemoryRegion 
>> *mr, hwaddr addr,
>>          goto out;
>>      }
>>
>> -    ret = smmu_ptw(cfg, addr, flag, &entry, &ptw_info);
>> +    tt = select_tt(cfg, addr);
>> +    if (!tt) {
>> +        if (event.record_trans_faults) {
>> +            event.type = SMMU_EVT_F_TRANSLATION;
>> +            event.u.f_translation.addr = addr;
>> +            event.u.f_translation.rnw = flag & 0x1;
>> +        }
>> +        ret = -EINVAL;
>> +        goto out;
>> +    }
>> +
>> +    page_mask = (1ULL << (tt->granule_sz)) - 1;
>> +    aligned_addr = addr & ~page_mask;
>> +
>> +    key_val = SMMU_IOTLB_KEY(cfg->asid, aligned_addr);
>> +
>> +    cached_entry = g_hash_table_lookup(bs->iotlb, &key_val);
>> +    if (cached_entry) {
>> +        cfg->iotlb_hit += 1;
>> +        trace_smmu_iotlb_cache_hit(cfg->asid, aligned_addr,
>> +                                   cfg->iotlb_hit, cfg->iotlb_miss,
>> +                                   100 * cfg->iotlb_hit /
>> +                                   (cfg->iotlb_hit + cfg->iotlb_miss));
>> +        if ((flag & IOMMU_WO) && !(cached_entry->perm & IOMMU_WO)) {
>> +            ret = -EFAULT;
>> +            if (event.record_trans_faults) {
>> +                event.type = SMMU_EVT_F_PERMISSION;
>> +                event.u.f_permission.addr = addr;
>> +                event.u.f_permission.rnw = flag & 0x1;
>> +            }
>> +        }
>> +        goto out;
>> +    }
>> +
>> +    cfg->iotlb_miss += 1;
>> +    trace_smmu_iotlb_cache_miss(cfg->asid, addr & ~page_mask,
>> +                                cfg->iotlb_hit, cfg->iotlb_miss,
>> +                                100 * cfg->iotlb_hit /
>> +                                (cfg->iotlb_hit + cfg->iotlb_miss));
>> +
>> +    if (g_hash_table_size(bs->iotlb) >= SMMU_IOTLB_MAX_SIZE) {
>> +        smmu_iotlb_inv_all(bs);
>> +    }
>> +
>> +    cached_entry = g_new0(IOMMUTLBEntry, 1);
>> +    key = g_new0(uint64_t, 1);
>> +    *key = key_val;
>> +    g_hash_table_insert(bs->iotlb, key, cached_entry);
>> +
>> +    ret = smmu_ptw(cfg, aligned_addr, flag, cached_entry, &ptw_info);
>>      if (ret) {
>>          switch (ptw_info.type) {
>>          case SMMU_PTW_ERR_WALK_EABT:
>> @@ -656,8 +712,14 @@ out:
>>                        mr->parent_obj.name, addr, ret);
>>          entry.perm = IOMMU_NONE;
>>          smmuv3_record_event(s, &event);
>> +        if (cached_entry) {
>> +            smmu_iotlb_inv_iova(bs, cfg->asid, aligned_addr);
>> +        }
> 
> This is doing that weird "insert an entry into the cache
> and then remove it again" logic.
fixed
> 
>>      } else if (!cfg->aborted) {
>>          entry.perm = flag;
>> +        entry.translated_addr = cached_entry->translated_addr +
>> +                                    (addr & page_mask);
>> +        entry.addr_mask = cached_entry->addr_mask;
>>          trace_smmuv3_translate(mr->parent_obj.name, sid, addr,
>>                                 entry.translated_addr, entry.perm);
>>      }
>> @@ -781,10 +843,46 @@ static int smmuv3_cmdq_consume(SMMUv3State *s)
>>              smmuv3_put_config(sdev);
>>              break;
>>          }
>> -        case SMMU_CMD_TLBI_NH_ALL:
>>          case SMMU_CMD_TLBI_NH_ASID:
>> -        case SMMU_CMD_TLBI_NH_VA:
>> +        {
>> +            uint16_t asid = CMD_ASID(&cmd);
>> +
>> +            trace_smmuv3_cmdq_tlbi_nh_asid(asid);
>> +            /* TODO: be more precise and invalidate for @asid */
>> +            smmu_iotlb_inv_asid(bs, asid);
> 
> TODO comment says we should invalidate for this ASID only, but
> the code seems to do that already ?
removed
> 
>> +            break;
>> +        }
>> +        case SMMU_CMD_TLBI_NH_ALL:
>> +        case SMMU_CMD_TLBI_NSNH_ALL:
>> +            trace_smmuv3_cmdq_tlbi_nh();
>> +            smmu_iotlb_inv_all(bs);
>> +            break;
>>          case SMMU_CMD_TLBI_NH_VAA:
>> +        {
>> +            dma_addr_t addr = CMD_ADDR(&cmd);
>> +            uint16_t vmid = CMD_VMID(&cmd);
>> +
>> +            trace_smmuv3_cmdq_tlbi_nh_vaa(vmid, addr);
>> +            smmu_iotlb_inv_all(bs);
>> +            break;
>> +        }
>> +        case SMMU_CMD_TLBI_NH_VA:
>> +        {
>> +            uint16_t asid = CMD_ASID(&cmd);
>> +            uint16_t vmid = CMD_VMID(&cmd);
>> +            dma_addr_t addr = CMD_ADDR(&cmd);
>> +            bool leaf = CMD_LEAF(&cmd);
>> +
>> +            trace_smmuv3_cmdq_tlbi_nh_va(vmid, asid, addr, leaf);
>> +            /**
> 
> This isn't a doc-comment so it shouldn't have the double-asterisk
> doc-comment syntax.
removed
> 
>> +             * we don't know the size of the granule so
>> +             * let's invalidate both 4K entry and 64kB entry.
>> +             * The spec allow to invalidate more than necessary.
>> +             */
>> +            smmu_iotlb_inv_iova(bs, asid, addr & ~0xFFF);
>> +            smmu_iotlb_inv_iova(bs, asid, addr & ~0xFFFF);
>> +            break;
>> +        }
>>          case SMMU_CMD_TLBI_EL3_ALL:
>>          case SMMU_CMD_TLBI_EL3_VA:
>>          case SMMU_CMD_TLBI_EL2_ALL:
>> @@ -793,7 +891,6 @@ static int smmuv3_cmdq_consume(SMMUv3State *s)
>>          case SMMU_CMD_TLBI_EL2_VAA:
>>          case SMMU_CMD_TLBI_S12_VMALL:
>>          case SMMU_CMD_TLBI_S2_IPA:
>> -        case SMMU_CMD_TLBI_NSNH_ALL:
>>          case SMMU_CMD_ATC_INV:
>>          case SMMU_CMD_PRI_RESP:
>>          case SMMU_CMD_RESUME:
>> diff --git a/hw/arm/trace-events b/hw/arm/trace-events
>> index ecc30be..7fdc08e 100644
>> --- a/hw/arm/trace-events
>> +++ b/hw/arm/trace-events
>> @@ -12,6 +12,11 @@ smmu_ptw_invalid_pte(int stage, int level, uint64_t 
>> baseaddr, uint64_t pteaddr,
>>  smmu_ptw_page_pte(int stage, int level,  uint64_t iova, uint64_t baseaddr, 
>> uint64_t pteaddr, uint64_t pte, uint64_t address) "stage=%d level=%d 
>> iova=0x%"PRIx64" address@hidden"PRIx64" address@hidden"PRIx64" 
>> pte=0x%"PRIx64" page address = 0x%"PRIx64
>>  smmu_ptw_block_pte(int stage, int level, uint64_t baseaddr, uint64_t 
>> pteaddr, uint64_t pte, uint64_t iova, uint64_t gpa, int bsize_mb) "stage=%d 
>> level=%d address@hidden"PRIx64" address@hidden"PRIx64" pte=0x%"PRIx64" 
>> iova=0x%"PRIx64" block address = 0x%"PRIx64" block size = %d MiB"
>>  smmu_get_pte(uint64_t baseaddr, int index, uint64_t pteaddr, uint64_t pte) 
>> "baseaddr=0x%"PRIx64" index=0x%x, pteaddr=0x%"PRIx64", pte=0x%"PRIx64
>> +smmu_iotlb_cache_hit(uint16_t asid, uint64_t addr, uint32_t hit, uint32_t 
>> miss, float p) "IOTLB cache HIT asid=%d addr=0x%"PRIx64" hit=%d miss=%d hit 
>> rate=%.1f"
>> +smmu_iotlb_cache_miss(uint16_t asid, uint64_t addr, uint32_t hit, uint32_t 
>> miss, float p) "IOTLB cache MISS asid=%d addr=0x%"PRIx64" hit=%d miss=%d hit 
>> rate=%.1f"
>> +smmu_iotlb_inv_all(void) "IOTLB invalidate all"
>> +smmu_iotlb_inv_asid(uint16_t asid) "IOTLB invalidate asid=%d"
>> +smmu_iotlb_inv_iova(uint16_t asid, uint64_t addr) "IOTLB invalidate asid=%d 
>> addr=0x%"PRIx64
>>
>>  #hw/arm/smmuv3.c
>>  smmuv3_read_mmio(uint64_t addr, uint64_t val, unsigned size, uint32_t r) 
>> "addr: 0x%"PRIx64" val:0x%"PRIx64" size: 0x%x(%d)"
>> @@ -41,6 +46,10 @@ smmuv3_decode_cd(uint32_t oas) "oas=%d"
>>  smmuv3_decode_cd_tt(int i, uint32_t tsz, uint64_t ttb, uint32_t granule_sz, 
>> int initial_level) "TT[%d]:tsz:%d ttb:0x%"PRIx64" granule_sz:%d, 
>> initial_level = %d"
>>  smmuv3_cmdq_cfgi_ste(int streamid) "     |_ streamid =%d"
>>  smmuv3_cmdq_cfgi_ste_range(int start, int end) "     |_ start=0x%d - 
>> end=0x%d"
>> +smmuv3_cmdq_tlbi_nh_va(int vmid, int asid, uint64_t addr, bool leaf) "     
>> |_ vmid =%d asid =%d addr=0x%"PRIx64" leaf=%d"
>> +smmuv3_cmdq_tlbi_nh_vaa(int vmid, uint64_t addr) "     |_ vmid =%d 
>> addr=0x%"PRIx64
>> +smmuv3_cmdq_tlbi_nh(void) ""
>> +smmuv3_cmdq_tlbi_nh_asid(uint16_t asid) "asid=%d"
>>  smmuv3_cmdq_cfgi_cd(uint32_t sid) "     |_ streamid = %d"
>>  smmuv3_config_cache_hit(uint32_t sid) "Config cache HIT for sid %d"
>>  smmuv3_config_cache_miss(uint32_t sid) "Config cache MISS for sid %d"
>> diff --git a/include/hw/arm/smmu-common.h b/include/hw/arm/smmu-common.h
>> index ff07734..1c9c648 100644
>> --- a/include/hw/arm/smmu-common.h
>> +++ b/include/hw/arm/smmu-common.h
>> @@ -68,6 +68,8 @@ typedef struct SMMUTransCfg {
>>      uint8_t tbi;               /* Top Byte Ignore */
>>      uint16_t asid;
>>      SMMUTransTableInfo tt[2];
>> +    uint32_t iotlb_hit;
>> +    uint32_t iotlb_miss;
> 
> Are these just gathering hit/miss statistics for the tracing?
> Could we have a comment saying what they're for? From the
> name of the fields I was expecting them to be boolean flags;
> if they're counts of hits and misses then iotlb_hits and
> iotlb_misses might be better?
yes it does. renamed and comment added.
> 
>>  } SMMUTransCfg;
>>
>>  typedef struct SMMUDevice {
>> @@ -146,4 +148,13 @@ SMMUTransTableInfo *select_tt(SMMUTransCfg *cfg, 
>> dma_addr_t iova);
>>  /* Return the iommu mr associated to @sid, or NULL if none */
>>  IOMMUMemoryRegion *smmu_iommu_mr(SMMUState *s, uint32_t sid);
>>
>> +#define SMMU_IOTLB_MAX_SIZE 256
>> +#define IOTLB_KEY_ASID_SHIFT SMMU_MAX_VA_BITS
>> +#define SMMU_IOTLB_KEY(asid, iova)                       \
>> +    (iova | (uint64_t)(asid) << IOTLB_KEY_ASID_SHIFT);
> 
> This choice of key layout makes future changes to the SMMU
> implementation a bit painful. Currently SMMU_MAX_VA_BITS is 48,
> so since the ASID is 16 bits that's the entirety of the 64 bit key.
> We're also likely at some point to want to include in the key:
>  * 16 bit VMID
>  * larger address sizes (ie bigger SMMU_MAX_VA_BITS)
>  * translation regime (StreamWorld)
> 
> I think it would be better to define a struct and a custom
> equality comparison function rather than using uint64_t and
> g_int64_equal().
Implemented the key as a struct and used a Jenkins hash

Thanks

Eric
> 
>> +
>> +void smmu_iotlb_inv_all(SMMUState *s);
>> +void smmu_iotlb_inv_asid(SMMUState *s, uint16_t asid);
>> +void smmu_iotlb_inv_iova(SMMUState *s, uint16_t asid, dma_addr_t iova);
>> +
>>  #endif  /* HW_ARM_SMMU_COMMON */
>> --
>> 2.5.5
> 
> thanks
> -- PMM
> 



reply via email to

[Prev in Thread] Current Thread [Next in Thread]