Skip to the content.

QEMU softmmu 访存 helper 整理

QEMU 为了处理大端小端(le/be), 不同大小(size = 1/2/4/8), 以及访存方向(load/store),定义了一堆类似的 helper。

背景介绍

access size

实际上,这些函数集合处理的大小都是 b w l q 四种,因为这就是访问 IO 空间的所有长度可能性,为了方便,创建出来一堆 helper 。

IO 访问的,最后总是走到 memory_region_dispatch_(read/write) 上面来的。

MemoryRegionOps::impl::min_access_sizeMemoryRegionOps::impl::max_access_size 可以描述设备进行一次 IO 最多和最少传输的

如果一次 IO 的数量越过了 min_access_size / max_access_size,那么可以循环反复调用 MemoryRegionOps::read

endianness

endianness 为什么会产生问题,先从一个简单的情况考虑,使用 load_helper 可以读取到 guest 一个地址上的数值,之后这个地址被加载到 host 的模拟寄存器,并且进行运算。 显然,为了让 host CPU 正确理解这个数值,需要在 load_helper 返回数值的时候,进行装换一下。

到底是调用下面哪一个函数,取决于 guest 是大端还是小端

tcg_target_ulong helper_le_lduw_mmu(CPUArchState *env, target_ulong addr,
                                    TCGMemOpIdx oi, uintptr_t retaddr)
{
    return full_le_lduw_mmu(env, addr, oi, retaddr);
}

static uint64_t full_be_lduw_mmu(CPUArchState *env, target_ulong addr,
                                 TCGMemOpIdx oi, uintptr_t retaddr)
{
    return load_helper(env, addr, oi, retaddr, MO_BEUW, false,
                       full_be_lduw_mmu);
}

目前只有一个 device 是 big endianness 的,那就是 fwcfg.dma

/*
0000000000000510-0000000000000511 (prio 0, i/o): fwcfg
0000000000000514-000000000000051b (prio 0, i/o): fwcfg.dma

在 seabios 的代码中,outl 传递的数值使用 cpu_to_be32 进行了装换

static void
fw_cfg_dma_transfer(void *address, u32 length, u32 control)
{
    QemuCfgDmaAccess access;

    access.address = cpu_to_be64((u64)(u32)address);
    access.length = cpu_to_be32(length);
    access.control = cpu_to_be32(control);

    barrier();

    outl(cpu_to_be32((u32)&access), PORT_QEMU_CFG_DMA_ADDR_LOW);

    while(be32_to_cpu(access.control) & ~QEMU_CFG_DMA_CTL_ERROR) {
        yield();
    }
}

在 QEMU 的 memory_region_write_accessor 中调用 MemoryRegions::ops 的时候,传递给参数的 data 的 tmp 就是 host 的 endianness

    mr->ops->write(mr->opaque, addr, tmp, size);

在 MemoryRegionOps::write 中就可以直接使用了参数 value

static void fw_cfg_dma_mem_write(void *opaque, hwaddr addr,
                                 uint64_t value, unsigned size)
{
    FWCfgState *s = opaque;

    if (size == 4) {
        if (addr == 0) {
            /* FWCfgDmaAccess high address */
            s->dma_addr = value << 32;
        } else if (addr == 4) {
            /* FWCfgDmaAccess low address */
            s->dma_addr |= value;
            fw_cfg_dma_transfer(s);
        }
    } else if (size == 8 && addr == 0) {
        s->dma_addr = value;
        fw_cfg_dma_transfer(s);
    }
}

softmmu 慢速路径访存

当 soft tlb 没有命中之后,会切入到此处, helper_(ret/le)_(ld/st)(sb/ub/sw/uw/l/q/sl/ul)_mmu

简单的组装出参数之后,调用 load_helper/store_helper

CPU 访存

target/i386/ 下的各种 helper 在模拟 CPU 访存的过程,

CPU 访问物理内存

x86_*_phys => address_space_(ld/st)(w/l/q)_(le/be) => (st/ld)(w/l/q/uw/sw)_(le/be)_p

在 target/i386/helper.c 定义了一堆类似下面的函数:

void x86_stw_phys(CPUState *cs, hwaddr addr, uint32_t val)
{
    X86CPU *cpu = X86_CPU(cs);
    CPUX86State *env = &cpu->env;
    MemTxAttrs attrs = cpu_get_mem_attrs(env);
    AddressSpace *as = cpu_addressspace(cs, attrs);

    address_space_stw(as, addr, val, attrs, NULL);
}

address_space_(ld/st)(w/l/q)_(le/be) 之类都是在 memory_ldst.c.inc 中间生成的。

调用 address_space_stl_internal 的 endian 是确定的,就是 DEVICE_NATIVE_ENDIAN 的。

CPU 访问虚拟内存

因为是访问虚拟存储,最终必然要调用到 store_helper / load_helper 的位置, 这些 helper 的作用就是组装这两个 helper 的函数了。

cpu_ldst.h 中的描述应该是相当清晰了。

/*
 * Generate inline load/store functions for all MMU modes (typically
 * at least _user and _kernel) as well as _data versions, for all data
 * sizes.
 *
 * Used by target op helpers.
 *
 * The syntax for the accessors is:
 *
 * load:  cpu_ld{sign}{size}{end}_{mmusuffix}(env, ptr)
 *        cpu_ld{sign}{size}{end}_{mmusuffix}_ra(env, ptr, retaddr)
 *        cpu_ld{sign}{size}{end}_mmuidx_ra(env, ptr, mmu_idx, retaddr)
 *
 * store: cpu_st{size}{end}_{mmusuffix}(env, ptr, val)
 *        cpu_st{size}{end}_{mmusuffix}_ra(env, ptr, val, retaddr)
 *        cpu_st{size}{end}_mmuidx_ra(env, ptr, val, mmu_idx, retaddr)
 *
 * sign is:
 * (empty): for 32 and 64 bit sizes
 *   u    : unsigned
 *   s    : signed
 *
 * size is:
 *   b: 8 bits
 *   w: 16 bits
 *   l: 32 bits
 *   q: 64 bits
 *
 * end is:
 * (empty): for target native endian, or for 8 bit access
 *     _be: for forced big endian
 *     _le: for forced little endian
 *
 * mmusuffix is one of the generic suffixes "data" or "code", or "mmuidx".
 * The "mmuidx" suffix carries an extra mmu_idx argument that specifies
 * the index to use; the "data" and "code" suffixes take the index from
 * cpu_mmu_index().
 */

举一个例子,在 target/i386/tcg/mem_helper.c 中的 helper_cmpxchg16b_unlocked

void helper_cmpxchg16b_unlocked(CPUX86State *env, target_ulong a0)
{
    // ...
    o0 = cpu_ldq_data_ra(env, a0 + 0, ra);
    o1 = cpu_ldq_data_ra(env, a0 + 8, ra);
    // ...

    cpu_stq_data_ra(env, a0 + 0, int128_getlo(newv), ra);
    cpu_stq_data_ra(env, a0 + 8, int128_gethi(newv), ra);
    // ...
}

CPU 访问 IO

在 misc_helper.c 中存在下面的一系列封装,其 x86_stw_phys 的效果非常类似,只是其 address space 是 address_space_io

void helper_outb(CPUX86State *env, uint32_t port, uint32_t data)
{
    address_space_stb(&address_space_io, port, data,
                      cpu_get_mem_attrs(env), NULL);
}

设备访存

设备模拟模块调用的, 比如 ioapic 想要发送中断的时候,就会调用 ioapic_service => stl_le_phys

只是设备访存的时候进行的操作, 设备的 AddressSpace 和 MemTxAttrs 是确定的,所以直接调用到 address_space_* 就可以了。

static inline void glue(stl_le_phys, SUFFIX)(ARG1_DECL, hwaddr addr, uint32_t val)
{
    glue(address_space_stl_le, SUFFIX)(ARG1, addr, val,
                                       MEMTXATTRS_UNSPECIFIED, NULL);
}

如果是 PCI 设备,更多的使用下面 dma 方式。

PCI 设备访存

过下面两组宏,制作出来大量的 dma 相关的辅助函数,这些函数都是主要是给 PCI 设备用的,

#define DEFINE_LDST_DMA(_lname, _sname, _bits, _end) \
    static inline uint##_bits##_t ld##_lname##_##_end##_dma(AddressSpace *as, \
                                                            dma_addr_t addr) \
    {                                                                   \
        uint##_bits##_t val;                                            \
        dma_memory_read(as, addr, &val, (_bits) / 8);                   \
        return _end##_bits##_to_cpu(val);                               \
    }                                                                   \
    static inline void st##_sname##_##_end##_dma(AddressSpace *as,      \
                                                 dma_addr_t addr,       \
                                                 uint##_bits##_t val)   \
    {                                                                   \
        val = cpu_to_##_end##_bits(val);                                \
        dma_memory_write(as, addr, &val, (_bits) / 8);                  \
    }

static inline uint8_t ldub_dma(AddressSpace *as, dma_addr_t addr)
{
    uint8_t val;

    dma_memory_read(as, addr, &val, 1);
    return val;
}

static inline void stb_dma(AddressSpace *as, dma_addr_t addr, uint8_t val)
{
    dma_memory_write(as, addr, &val, 1);
}

DEFINE_LDST_DMA(uw, w, 16, le);
DEFINE_LDST_DMA(l, l, 32, le);
DEFINE_LDST_DMA(q, q, 64, le);
DEFINE_LDST_DMA(uw, w, 16, be);
DEFINE_LDST_DMA(l, l, 32, be);
DEFINE_LDST_DMA(q, q, 64, be);

#undef DEFINE_LDST_DMA
#define PCI_DMA_DEFINE_LDST(_l, _s, _bits)                              \
    static inline uint##_bits##_t ld##_l##_pci_dma(PCIDevice *dev,      \
                                                   dma_addr_t addr)     \
    {                                                                   \
        return ld##_l##_dma(pci_get_address_space(dev), addr);          \
    }                                                                   \
    static inline void st##_s##_pci_dma(PCIDevice *dev,                 \
                                        dma_addr_t addr, uint##_bits##_t val) \
    {                                                                   \
        st##_s##_dma(pci_get_address_space(dev), addr, val);            \
    }

PCI_DMA_DEFINE_LDST(ub, b, 8);
PCI_DMA_DEFINE_LDST(uw_le, w_le, 16)
PCI_DMA_DEFINE_LDST(l_le, l_le, 32);
PCI_DMA_DEFINE_LDST(q_le, q_le, 64);
PCI_DMA_DEFINE_LDST(uw_be, w_be, 16)
PCI_DMA_DEFINE_LDST(l_be, l_be, 32);
PCI_DMA_DEFINE_LDST(q_be, q_be, 64);

总结

进行访存最后总是百川如海,

而在 memory_region_dispatch_write 将会处理 endianness 和 size 的大小。

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。