qemu with hax to log dma reads & writes jcs.org/2018/11/12/vfio

hw/arm/smmu-common: VMSAv8-64 page table walk

This patch implements the page table walk for VMSAv8-64.

Signed-off-by: Eric Auger <eric.auger@redhat.com>
Signed-off-by: Prem Mallappa <prem.mallappa@broadcom.com>
Message-id: 1524665762-31355-4-git-send-email-eric.auger@redhat.com
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>

authored by

Eric Auger and committed by
Peter Maydell
93641948 cac994ef

+343 -1
+222
hw/arm/smmu-common.c
··· 27 27 28 28 #include "qemu/error-report.h" 29 29 #include "hw/arm/smmu-common.h" 30 + #include "smmu-internal.h" 31 + 32 + /* VMSAv8-64 Translation */ 33 + 34 + /** 35 + * get_pte - Get the content of a page table entry located at 36 + * @base_addr[@index] 37 + */ 38 + static int get_pte(dma_addr_t baseaddr, uint32_t index, uint64_t *pte, 39 + SMMUPTWEventInfo *info) 40 + { 41 + int ret; 42 + dma_addr_t addr = baseaddr + index * sizeof(*pte); 43 + 44 + /* TODO: guarantee 64-bit single-copy atomicity */ 45 + ret = dma_memory_read(&address_space_memory, addr, 46 + (uint8_t *)pte, sizeof(*pte)); 47 + 48 + if (ret != MEMTX_OK) { 49 + info->type = SMMU_PTW_ERR_WALK_EABT; 50 + info->addr = addr; 51 + return -EINVAL; 52 + } 53 + trace_smmu_get_pte(baseaddr, index, addr, *pte); 54 + return 0; 55 + } 56 + 57 + /* VMSAv8-64 Translation Table Format Descriptor Decoding */ 58 + 59 + /** 60 + * get_page_pte_address - returns the L3 descriptor output address, 61 + * ie. the page frame 62 + * ARM ARM spec: Figure D4-17 VMSAv8-64 level 3 descriptor format 63 + */ 64 + static inline hwaddr get_page_pte_address(uint64_t pte, int granule_sz) 65 + { 66 + return PTE_ADDRESS(pte, granule_sz); 67 + } 68 + 69 + /** 70 + * get_table_pte_address - return table descriptor output address, 71 + * ie. address of next level table 72 + * ARM ARM Figure D4-16 VMSAv8-64 level0, level1, and level 2 descriptor formats 73 + */ 74 + static inline hwaddr get_table_pte_address(uint64_t pte, int granule_sz) 75 + { 76 + return PTE_ADDRESS(pte, granule_sz); 77 + } 78 + 79 + /** 80 + * get_block_pte_address - return block descriptor output address and block size 81 + * ARM ARM Figure D4-16 VMSAv8-64 level0, level1, and level 2 descriptor formats 82 + */ 83 + static inline hwaddr get_block_pte_address(uint64_t pte, int level, 84 + int granule_sz, uint64_t *bsz) 85 + { 86 + int n = (granule_sz - 3) * (4 - level) + 3; 87 + 88 + *bsz = 1 << n; 89 + return PTE_ADDRESS(pte, n); 90 + } 91 + 92 + SMMUTransTableInfo *select_tt(SMMUTransCfg *cfg, dma_addr_t iova) 93 + { 94 + bool tbi = extract64(iova, 55, 1) ? TBI1(cfg->tbi) : TBI0(cfg->tbi); 95 + uint8_t tbi_byte = tbi * 8; 96 + 97 + if (cfg->tt[0].tsz && 98 + !extract64(iova, 64 - cfg->tt[0].tsz, cfg->tt[0].tsz - tbi_byte)) { 99 + /* there is a ttbr0 region and we are in it (high bits all zero) */ 100 + return &cfg->tt[0]; 101 + } else if (cfg->tt[1].tsz && 102 + !extract64(iova, 64 - cfg->tt[1].tsz, cfg->tt[1].tsz - tbi_byte)) { 103 + /* there is a ttbr1 region and we are in it (high bits all one) */ 104 + return &cfg->tt[1]; 105 + } else if (!cfg->tt[0].tsz) { 106 + /* ttbr0 region is "everything not in the ttbr1 region" */ 107 + return &cfg->tt[0]; 108 + } else if (!cfg->tt[1].tsz) { 109 + /* ttbr1 region is "everything not in the ttbr0 region" */ 110 + return &cfg->tt[1]; 111 + } 112 + /* in the gap between the two regions, this is a Translation fault */ 113 + return NULL; 114 + } 115 + 116 + /** 117 + * smmu_ptw_64 - VMSAv8-64 Walk of the page tables for a given IOVA 118 + * @cfg: translation config 119 + * @iova: iova to translate 120 + * @perm: access type 121 + * @tlbe: IOMMUTLBEntry (out) 122 + * @info: handle to an error info 123 + * 124 + * Return 0 on success, < 0 on error. In case of error, @info is filled 125 + * and tlbe->perm is set to IOMMU_NONE. 126 + * Upon success, @tlbe is filled with translated_addr and entry 127 + * permission rights. 128 + */ 129 + static int smmu_ptw_64(SMMUTransCfg *cfg, 130 + dma_addr_t iova, IOMMUAccessFlags perm, 131 + IOMMUTLBEntry *tlbe, SMMUPTWEventInfo *info) 132 + { 133 + dma_addr_t baseaddr, indexmask; 134 + int stage = cfg->stage; 135 + SMMUTransTableInfo *tt = select_tt(cfg, iova); 136 + uint8_t level, granule_sz, inputsize, stride; 137 + 138 + if (!tt || tt->disabled) { 139 + info->type = SMMU_PTW_ERR_TRANSLATION; 140 + goto error; 141 + } 142 + 143 + granule_sz = tt->granule_sz; 144 + stride = granule_sz - 3; 145 + inputsize = 64 - tt->tsz; 146 + level = 4 - (inputsize - 4) / stride; 147 + indexmask = (1ULL << (inputsize - (stride * (4 - level)))) - 1; 148 + baseaddr = extract64(tt->ttb, 0, 48); 149 + baseaddr &= ~indexmask; 150 + 151 + tlbe->iova = iova; 152 + tlbe->addr_mask = (1 << granule_sz) - 1; 153 + 154 + while (level <= 3) { 155 + uint64_t subpage_size = 1ULL << level_shift(level, granule_sz); 156 + uint64_t mask = subpage_size - 1; 157 + uint32_t offset = iova_level_offset(iova, inputsize, level, granule_sz); 158 + uint64_t pte; 159 + dma_addr_t pte_addr = baseaddr + offset * sizeof(pte); 160 + uint8_t ap; 161 + 162 + if (get_pte(baseaddr, offset, &pte, info)) { 163 + goto error; 164 + } 165 + trace_smmu_ptw_level(level, iova, subpage_size, 166 + baseaddr, offset, pte); 167 + 168 + if (is_invalid_pte(pte) || is_reserved_pte(pte, level)) { 169 + trace_smmu_ptw_invalid_pte(stage, level, baseaddr, 170 + pte_addr, offset, pte); 171 + info->type = SMMU_PTW_ERR_TRANSLATION; 172 + goto error; 173 + } 174 + 175 + if (is_page_pte(pte, level)) { 176 + uint64_t gpa = get_page_pte_address(pte, granule_sz); 177 + 178 + ap = PTE_AP(pte); 179 + if (is_permission_fault(ap, perm)) { 180 + info->type = SMMU_PTW_ERR_PERMISSION; 181 + goto error; 182 + } 183 + 184 + tlbe->translated_addr = gpa + (iova & mask); 185 + tlbe->perm = PTE_AP_TO_PERM(ap); 186 + trace_smmu_ptw_page_pte(stage, level, iova, 187 + baseaddr, pte_addr, pte, gpa); 188 + return 0; 189 + } 190 + if (is_block_pte(pte, level)) { 191 + uint64_t block_size; 192 + hwaddr gpa = get_block_pte_address(pte, level, granule_sz, 193 + &block_size); 194 + 195 + ap = PTE_AP(pte); 196 + if (is_permission_fault(ap, perm)) { 197 + info->type = SMMU_PTW_ERR_PERMISSION; 198 + goto error; 199 + } 200 + 201 + trace_smmu_ptw_block_pte(stage, level, baseaddr, 202 + pte_addr, pte, iova, gpa, 203 + block_size >> 20); 204 + 205 + tlbe->translated_addr = gpa + (iova & mask); 206 + tlbe->perm = PTE_AP_TO_PERM(ap); 207 + return 0; 208 + } 209 + 210 + /* table pte */ 211 + ap = PTE_APTABLE(pte); 212 + 213 + if (is_permission_fault(ap, perm)) { 214 + info->type = SMMU_PTW_ERR_PERMISSION; 215 + goto error; 216 + } 217 + baseaddr = get_table_pte_address(pte, granule_sz); 218 + level++; 219 + } 220 + 221 + info->type = SMMU_PTW_ERR_TRANSLATION; 222 + 223 + error: 224 + tlbe->perm = IOMMU_NONE; 225 + return -EINVAL; 226 + } 227 + 228 + /** 229 + * smmu_ptw - Walk the page tables for an IOVA, according to @cfg 230 + * 231 + * @cfg: translation configuration 232 + * @iova: iova to translate 233 + * @perm: tentative access type 234 + * @tlbe: returned entry 235 + * @info: ptw event handle 236 + * 237 + * return 0 on success 238 + */ 239 + inline int smmu_ptw(SMMUTransCfg *cfg, dma_addr_t iova, IOMMUAccessFlags perm, 240 + IOMMUTLBEntry *tlbe, SMMUPTWEventInfo *info) 241 + { 242 + if (!cfg->aa64) { 243 + /* 244 + * This code path is not entered as we check this while decoding 245 + * the configuration data in the derived SMMU model. 246 + */ 247 + g_assert_not_reached(); 248 + } 249 + 250 + return smmu_ptw_64(cfg, iova, perm, tlbe, info); 251 + } 30 252 31 253 /** 32 254 * The bus number is used for lookup when SID based invalidation occurs.
+99
hw/arm/smmu-internal.h
··· 1 + /* 2 + * ARM SMMU support - Internal API 3 + * 4 + * Copyright (c) 2017 Red Hat, Inc. 5 + * Copyright (C) 2014-2016 Broadcom Corporation 6 + * Written by Prem Mallappa, Eric Auger 7 + * 8 + * This program is free software; you can redistribute it and/or modify 9 + * it under the terms of the GNU General Public License version 2 as 10 + * published by the Free Software Foundation. 11 + * 12 + * This program is distributed in the hope that it will be useful, 13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 + * General Public License for more details. 16 + * 17 + * You should have received a copy of the GNU General Public License along 18 + * with this program; if not, see <http://www.gnu.org/licenses/>. 19 + */ 20 + 21 + #ifndef HW_ARM_SMMU_INTERNAL_H 22 + #define HW_ARM_SMMU_INTERNAL_H 23 + 24 + #define TBI0(tbi) ((tbi) & 0x1) 25 + #define TBI1(tbi) ((tbi) & 0x2 >> 1) 26 + 27 + /* PTE Manipulation */ 28 + 29 + #define ARM_LPAE_PTE_TYPE_SHIFT 0 30 + #define ARM_LPAE_PTE_TYPE_MASK 0x3 31 + 32 + #define ARM_LPAE_PTE_TYPE_BLOCK 1 33 + #define ARM_LPAE_PTE_TYPE_TABLE 3 34 + 35 + #define ARM_LPAE_L3_PTE_TYPE_RESERVED 1 36 + #define ARM_LPAE_L3_PTE_TYPE_PAGE 3 37 + 38 + #define ARM_LPAE_PTE_VALID (1 << 0) 39 + 40 + #define PTE_ADDRESS(pte, shift) \ 41 + (extract64(pte, shift, 47 - shift + 1) << shift) 42 + 43 + #define is_invalid_pte(pte) (!(pte & ARM_LPAE_PTE_VALID)) 44 + 45 + #define is_reserved_pte(pte, level) \ 46 + ((level == 3) && \ 47 + ((pte & ARM_LPAE_PTE_TYPE_MASK) == ARM_LPAE_L3_PTE_TYPE_RESERVED)) 48 + 49 + #define is_block_pte(pte, level) \ 50 + ((level < 3) && \ 51 + ((pte & ARM_LPAE_PTE_TYPE_MASK) == ARM_LPAE_PTE_TYPE_BLOCK)) 52 + 53 + #define is_table_pte(pte, level) \ 54 + ((level < 3) && \ 55 + ((pte & ARM_LPAE_PTE_TYPE_MASK) == ARM_LPAE_PTE_TYPE_TABLE)) 56 + 57 + #define is_page_pte(pte, level) \ 58 + ((level == 3) && \ 59 + ((pte & ARM_LPAE_PTE_TYPE_MASK) == ARM_LPAE_L3_PTE_TYPE_PAGE)) 60 + 61 + /* access permissions */ 62 + 63 + #define PTE_AP(pte) \ 64 + (extract64(pte, 6, 2)) 65 + 66 + #define PTE_APTABLE(pte) \ 67 + (extract64(pte, 61, 2)) 68 + 69 + /* 70 + * TODO: At the moment all transactions are considered as privileged (EL1) 71 + * as IOMMU translation callback does not pass user/priv attributes. 72 + */ 73 + #define is_permission_fault(ap, perm) \ 74 + (((perm) & IOMMU_WO) && ((ap) & 0x2)) 75 + 76 + #define PTE_AP_TO_PERM(ap) \ 77 + (IOMMU_ACCESS_FLAG(true, !((ap) & 0x2))) 78 + 79 + /* Level Indexing */ 80 + 81 + static inline int level_shift(int level, int granule_sz) 82 + { 83 + return granule_sz + (3 - level) * (granule_sz - 3); 84 + } 85 + 86 + static inline uint64_t level_page_mask(int level, int granule_sz) 87 + { 88 + return ~(MAKE_64BIT_MASK(0, level_shift(level, granule_sz))); 89 + } 90 + 91 + static inline 92 + uint64_t iova_level_offset(uint64_t iova, int inputsize, 93 + int level, int gsz) 94 + { 95 + return ((iova & MAKE_64BIT_MASK(0, inputsize)) >> level_shift(level, gsz)) & 96 + MAKE_64BIT_MASK(0, gsz - 3); 97 + } 98 + 99 + #endif
+8 -1
hw/arm/trace-events
··· 4 4 virt_acpi_setup(void) "No fw cfg or ACPI disabled. Bailing out." 5 5 6 6 # hw/arm/smmu-common.c 7 - smmu_add_mr(const char *name) "%s" 7 + smmu_add_mr(const char *name) "%s" 8 + smmu_page_walk(int stage, uint64_t baseaddr, int first_level, uint64_t start, uint64_t end) "stage=%d, baseaddr=0x%"PRIx64", first level=%d, start=0x%"PRIx64", end=0x%"PRIx64 9 + smmu_lookup_table(int level, uint64_t baseaddr, int granule_sz, uint64_t start, uint64_t end, int flags, uint64_t subpage_size) "level=%d baseaddr=0x%"PRIx64" granule=%d, start=0x%"PRIx64" end=0x%"PRIx64" flags=%d subpage_size=0x%"PRIx64 10 + smmu_ptw_level(int level, uint64_t iova, size_t subpage_size, uint64_t baseaddr, uint32_t offset, uint64_t pte) "level=%d iova=0x%"PRIx64" subpage_sz=0x%zx baseaddr=0x%"PRIx64" offset=%d => pte=0x%"PRIx64 11 + smmu_ptw_invalid_pte(int stage, int level, uint64_t baseaddr, uint64_t pteaddr, uint32_t offset, uint64_t pte) "stage=%d level=%d base@=0x%"PRIx64" pte@=0x%"PRIx64" offset=%d pte=0x%"PRIx64 12 + 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" base@=0x%"PRIx64" pte@=0x%"PRIx64" pte=0x%"PRIx64" page address = 0x%"PRIx64 13 + 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 base@=0x%"PRIx64" pte@=0x%"PRIx64" pte=0x%"PRIx64" iova=0x%"PRIx64" block address = 0x%"PRIx64" block size = %d MiB" 14 + 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
+14
include/hw/arm/smmu-common.h
··· 128 128 { 129 129 return PCI_BUILD_BDF(pci_bus_num(sdev->bus), sdev->devfn); 130 130 } 131 + 132 + /** 133 + * smmu_ptw - Perform the page table walk for a given iova / access flags 134 + * pair, according to @cfg translation config 135 + */ 136 + int smmu_ptw(SMMUTransCfg *cfg, dma_addr_t iova, IOMMUAccessFlags perm, 137 + IOMMUTLBEntry *tlbe, SMMUPTWEventInfo *info); 138 + 139 + /** 140 + * select_tt - compute which translation table shall be used according to 141 + * the input iova and translation config and return the TT specific info 142 + */ 143 + SMMUTransTableInfo *select_tt(SMMUTransCfg *cfg, dma_addr_t iova); 144 + 131 145 #endif /* HW_ARM_SMMU_COMMON */