Code


Q&A

  • Where is the virt to physical address translated?
  • What is the fast path without tlb?

Addr Translation

tlb_vaddr_to_host

In accel/tcg/cputlb.c: tlb_vaddr_to_host.

  • If tlb hit: return host vaddr as guest physical address.
  • If tlb miss: tlb_fill: Called to resize the TLB. All the caller’s prior references to the TLB table must be discard and looked up again via tlb_entry().

    // accel/tcg/cputlb.c: 
    void *tlb_vaddr_to_host(CPUArchState *env, abi_ptr addr,
                        MMUAccessType access_type, int mmu_idx)
    {
    CPUTLBEntry *entry = tlb_entry(env, mmu_idx, addr);
    target_ulong tlb_addr, page;
    //...
    page = addr & TARGET_PAGE_MASK;
    tlb_addr = tlb_read_ofs(entry, elt_ofs);
    
    if (!tlb_hit_page(tlb_addr, page)) {
        uintptr_t index = tlb_index(env, mmu_idx, addr);
    
        if (!victim_tlb_hit(env, mmu_idx, index, elt_ofs, page)) {
            CPUState *cs = env_cpu(env);
            CPUClass *cc = CPU_GET_CLASS(cs);
    
            if (!cc->tlb_fill(cs, addr, 0, access_type, mmu_idx, true, 0)) {
                /* Non-faulting page table read failed.  */
                return NULL;
            }
    	     trace_guest_mem_before_exec(env_cpu(env), addr, 0xbee);
    
            /* TLB resize via tlb_fill may have moved the entry.  */
            entry = tlb_entry(env, mmu_idx, addr);
        }
        tlb_addr = tlb_read_ofs(entry, elt_ofs);
    }
    
    if (tlb_addr & ~TARGET_PAGE_MASK) {
        /* IO access */
        return NULL;
    }
    
    void *paddr = (void *)((uintptr_t)addr + entry->addend);
    trace_guest_mem_before_exec(env_cpu(env), addr, 0xbdd);
    trace_guest_mem_before_exec(env_cpu(env), paddr, 0xbdd);
    return paddr;
    }
get_page_addr_code_hostp

in accel/tcg/cputlb.c: get_page_addr_code_hostp:

// accel/tcg/cputlb.c:
tb_page_addr_t get_page_addr_code_hostp(CPUArchState *env, target_ulong addr,
                                        void **hostp)
{
    uintptr_t mmu_idx = cpu_mmu_index(env, true);
    uintptr_t index = tlb_index(env, mmu_idx, addr);
    CPUTLBEntry *entry = tlb_entry(env, mmu_idx, addr);
    void *p;

    if (unlikely(!tlb_hit(entry->addr_code, addr))) {
        if (!VICTIM_TLB_HIT(addr_code, addr)) {
            tlb_fill(env_cpu(env), addr, 0, MMU_INST_FETCH, mmu_idx, 0);
            index = tlb_index(env, mmu_idx, addr);
            entry = tlb_entry(env, mmu_idx, addr);
            //...
        }
        assert(tlb_hit(entry->addr_code, addr));
    }
    //...
    p = (void *)((uintptr_t)addr + entry->addend);
    if (hostp) {
        *hostp = p;
    }
    trace_guest_mem_before_exec(env_cpu(env), addr, 0xbff);
    trace_guest_mem_before_exec(env_cpu(env), p, 0xbff);

    return qemu_ram_addr_from_host_nofail(p);
}
probe_access
// accel/tcg/cputlb.c

/*
 * Probe for whether the specified guest access is permitted. If it is not
 * permitted then an exception will be taken in the same way as if this
 * were a real access (and we will not return).
 * If the size is 0 or the page requires I/O access, returns NULL; otherwise,
 * returns the address of the host page similar to tlb_vaddr_to_host().
 */
void *probe_access(CPUArchState *env, target_ulong addr, int size,
                   MMUAccessType access_type, int mmu_idx, uintptr_t retaddr)

CPUTLBEntry:

typedef struct CPUTLBEntry {
    /* bit TARGET_LONG_BITS to TARGET_PAGE_BITS : virtual address
       bit TARGET_PAGE_BITS-1..4  : Nonzero for accesses that should not
                                    go directly to ram.
       bit 3                      : indicates that the entry is invalid
       bit 2..0                   : zero
    */
    union {
        struct {
            target_ulong addr_read;
            target_ulong addr_write;
            target_ulong addr_code;
            /* Addend to virtual address to get host address.  IO accesses
               use the corresponding iotlb value.  */
            uintptr_t addend;
        };
        /* padding to get a power of two size */
        uint8_t dummy[1 << CPU_TLB_ENTRY_BITS];
    };
} CPUTLBEntry;
TLB Entry Creation

accel/tcg/cputlb.c: tlb_set_page_with_attrs()

  1. Compute the host virtual address for this new RAMBlock, store as addend : addend = (uintptr_t)memory_region_get_ram_ptr(section->mr) + xlat;

  2. Subtract the guest virtual addr page number, store in tlb entry’s addend: tn.addend = addend - vaddr_page;

  3. Set other fields, such as addr_read/write/code, etc.

    // accel/tcg/cputlb.c: 
    
    /* Add a new TLB entry. At most one entry for a given virtual address
    * is permitted. Only a single TARGET_PAGE_SIZE region is mapped, the
    * supplied size is only used by tlb_flush_page.
    *
    * Called from TCG-generated code, which is under an RCU read-side
    * critical section.
    */
    void tlb_set_page_with_attrs(CPUState *cpu, target_ulong vaddr,
                             hwaddr paddr, MemTxAttrs attrs, int prot,
                             int mmu_idx, target_ulong size)
    {
    CPUArchState *env = cpu->env_ptr;
    CPUTLB *tlb = env_tlb(env);
    CPUTLBDesc *desc = &tlb->d[mmu_idx];
    MemoryRegionSection *section;
    
    uintptr_t addend;
    CPUTLBEntry *te, tn;
    hwaddr iotlb, xlat, sz, paddr_page;
    target_ulong vaddr_page;
    int asidx = cpu_asidx_from_attrs(cpu, attrs);
    
    vaddr_page = vaddr & TARGET_PAGE_MASK;
    paddr_page = paddr & TARGET_PAGE_MASK;
    
    section = address_space_translate_for_iotlb(cpu, asidx, paddr_page,
                                                &xlat, &sz, attrs, &prot);
    assert(sz >= TARGET_PAGE_SIZE);
    
    tlb_debug("vaddr=" TARGET_FMT_lx " paddr=0x" TARGET_FMT_plx
              " prot=%x idx=%d\n",
              vaddr, paddr, prot, mmu_idx);
    
    address = vaddr_page;
    if (size < TARGET_PAGE_SIZE) {
        /* Repeat the MMU check and TLB fill on every access.  */
        address |= TLB_INVALID_MASK;
    }
    if (attrs.byte_swap) {
        address |= TLB_BSWAP;
    }
    
    is_ram = memory_region_is_ram(section->mr);
    is_romd = memory_region_is_romd(section->mr);
    
    if (is_ram || is_romd) {
        /* RAM and ROMD both have associated host memory. */
        addend = (uintptr_t)memory_region_get_ram_ptr(section->mr) + xlat;
    } else {
        /* I/O does not; force the host address to NULL. */
        addend = 0;
    }
    
    write_address = address;
    if (is_ram) {
        iotlb = memory_region_get_ram_addr(section->mr) + xlat;
        /*
         * Computing is_clean is expensive; avoid all that unless
         * the page is actually writable.
         */
        if (prot & PAGE_WRITE) {
            if (section->readonly) {
                write_address |= TLB_DISCARD_WRITE;
            } else if (cpu_physical_memory_is_clean(iotlb)) {
                write_address |= TLB_NOTDIRTY;
            }
        }
    } else {
        /* I/O or ROMD */
        iotlb = memory_region_section_get_iotlb(cpu, section) + xlat;
        /*
         * Writes to romd devices must go through MMIO to enable write.
         * Reads to romd devices go through the ram_ptr found above,
         * but of course reads to I/O must go through MMIO.
         */
        write_address |= TLB_MMIO;
        if (!is_romd) {
            address = write_address;
        }
    }
    
    index = tlb_index(env, mmu_idx, vaddr_page);
    te = tlb_entry(env, mmu_idx, vaddr_page);
    
    /*
     * Hold the TLB lock for the rest of the function. We could acquire/release
     * the lock several times in the function, but it is faster to amortize the
     * acquisition cost by acquiring it just once. Note that this leads to
     * a longer critical section, but this is not a concern since the TLB lock
     * is unlikely to be contended.
     */
    qemu_spin_lock(&tlb->c.lock);
    
    /* Note that the tlb is no longer clean.  */
    tlb->c.dirty |= 1 << mmu_idx;
    
    /* Make sure there's no cached translation for the new page.  */
    tlb_flush_vtlb_page_locked(env, mmu_idx, vaddr_page);
    
    /*
     * Only evict the old entry to the victim tlb if it's for a
     * different page; otherwise just overwrite the stale data.
     */
    if (!tlb_hit_page_anyprot(te, vaddr_page) && !tlb_entry_is_empty(te)) {
        unsigned vidx = desc->vindex++ % CPU_VTLB_SIZE;
        CPUTLBEntry *tv = &desc->vtable[vidx];
    
        /* Evict the old entry into the victim tlb.  */
        copy_tlb_helper_locked(tv, te);
        desc->viotlb[vidx] = desc->iotlb[index];
        tlb_n_used_entries_dec(env, mmu_idx);
    }
    
    /* refill the tlb */
    /*
     * At this point iotlb contains a physical section number in the lower
     * TARGET_PAGE_BITS, and either
     *  + the ram_addr_t of the page base of the target RAM (RAM)
     *  + the offset within section->mr of the page base (I/O, ROMD)
     * We subtract the vaddr_page (which is page aligned and thus won't
     * disturb the low bits) to give an offset which can be added to the
     * (non-page-aligned) vaddr of the eventual memory access to get
     * the MemoryRegion offset for the access. Note that the vaddr we
     * subtract here is that of the page base, and not the same as the
     * vaddr we add back in io_readx()/io_writex()/get_page_addr_code().
     */
    desc->iotlb[index].addr = iotlb - vaddr_page;
    desc->iotlb[index].attrs = attrs;
    
    /* Now calculate the new entry */
    tn.addend = addend - vaddr_page;
    if (prot & PAGE_READ) {
        tn.addr_read = address;
        if (wp_flags & BP_MEM_READ) {
            tn.addr_read |= TLB_WATCHPOINT;
        }
    } else {
        tn.addr_read = -1;
    }
    
    if (prot & PAGE_EXEC) {
        tn.addr_code = address;
    } else {
        tn.addr_code = -1;
    }
    
    tn.addr_write = -1;
    if (prot & PAGE_WRITE) {
        tn.addr_write = write_address;
        if (prot & PAGE_WRITE_INV) {
            tn.addr_write |= TLB_INVALID_MASK;
        }
        if (wp_flags & BP_MEM_WRITE) {
            tn.addr_write |= TLB_WATCHPOINT;
        }
    }
    
    copy_tlb_helper_locked(te, &tn);
    tlb_n_used_entries_inc(env, mmu_idx);
    qemu_spin_unlock(&tlb->c.lock);
    }

TLB hit test

Given a virtual address, get its page number.

Get TLB entry for the virtual address, get tlb.addr_read/write/code field, the virtual address in tlb entry.

Get the page number of the virtual address of guest, and test valid bit.

If valid tlb entry, and the page number matches, then TLB hit.

The physical address is virtual addr + tlb.addend.

/**
 * tlb_hit_page: return true if page aligned @addr is a hit against the
 * TLB entry @tlb_addr
 *
 * @addr: virtual address to test (must be page aligned)
 * @tlb_addr: TLB entry address (a CPUTLBEntry addr_read/write/code value)
 */
static inline bool tlb_hit_page(target_ulong tlb_addr, target_ulong addr)
{

    // Get the page number of the virtual address of guest, 
    // and test valid bit.
    // If valid tlb entry, and the page number matches, then TLB hit.

    return addr == (tlb_addr & (TARGET_PAGE_MASK | TLB_INVALID_MASK));
}

/**
 * tlb_hit: return true if @addr is a hit against the TLB entry @tlb_addr
 *
 * @addr: virtual address to test (need not be page aligned)
 * @tlb_addr: TLB entry address (a CPUTLBEntry addr_read/write/code value)
 */
static inline bool tlb_hit(target_ulong tlb_addr, target_ulong addr)
{
    return tlb_hit_page(tlb_addr, addr & TARGET_PAGE_MASK);
}

Reference 1


  1. reference ↩
Created Jun 21, 2020 // Last Updated May 18, 2021

If you could revise
the fundmental principles of
computer system design
to improve security...

... what would you change?