qemu內存模型(7)地址空間的讀寫

建議先讀

qemu內存模型(6)mm實現(一)實模式

對於tcg模式,讀內存將執行accel/tcg/softmmu_template.h下的函數

WORD_TYPE helper_le_ld_name(CPUArchState *env, target_ulong addr,
                            TCGMemOpIdx oi, uintptr_t retaddr)
# define helper_le_ld_name  glue(glue(helper_le_ld, USUFFIX), MMUSUFFIX)
#define xglue(x, y) x ## y
#define glue(x, y) xglue(x, y)

這個函數其實是一個宏,glue 其實就是簡單的將兩個參數連接起來, 所以對x86平臺展開如下
helper_le_ld_uw_mmu, le表示小端機器,ld 我猜是load的意思, uw呢則表示load的是無符號16位數。
我們來看看函數的實現

108 WORD_TYPE helper_le_ld_name(CPUArchState *env, target_ulong addr,
109                             TCGMemOpIdx oi, uintptr_t retaddr)
110 {
111     unsigned mmu_idx = get_mmuidx(oi);
112     int index = (addr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1);
113     target_ulong tlb_addr = env->tlb_table[mmu_idx][index].ADDR_READ;
114     unsigned a_bits = get_alignment_bits(get_memop(oi));
115     uintptr_t haddr;
116     DATA_TYPE res;
117
118     if (addr & ((1 << a_bits) - 1)) {
119         cpu_unaligned_access(ENV_GET_CPU(env), addr, READ_ACCESS_TYPE,
120                              mmu_idx, retaddr);
121     }
122
123     /* If the TLB entry is for a different page, reload and try again.  */
124     if ((addr & TARGET_PAGE_MASK)
125          != (tlb_addr & (TARGET_PAGE_MASK | TLB_INVALID_MASK))) {
126         if (!VICTIM_TLB_HIT(ADDR_READ, addr)) {
127             tlb_fill(ENV_GET_CPU(env), addr, DATA_SIZE, READ_ACCESS_TYPE,
128                      mmu_idx, retaddr);
129         }
130         tlb_addr = env->tlb_table[mmu_idx][index].ADDR_READ;
131     }
132
133     /* Handle an IO access.  */
134     if (unlikely(tlb_addr & ~TARGET_PAGE_MASK)) {
135         if ((addr & (DATA_SIZE - 1)) != 0) {
136             goto do_unaligned_access;
137         }
138
139         /* ??? Note that the io helpers always read data in the target
140            byte ordering.  We should push the LE/BE request down into io.  */
141         res = glue(io_read, SUFFIX)(env, mmu_idx, index, addr, retaddr);
142         res = TGT_LE(res);
143         return res;
144     }
145
146     /* Handle slow unaligned access (it spans two pages or IO).  */
147     if (DATA_SIZE > 1
148         && unlikely((addr & ~TARGET_PAGE_MASK) + DATA_SIZE - 1
149                     >= TARGET_PAGE_SIZE)) {
150         target_ulong addr1, addr2;
151         DATA_TYPE res1, res2;
152         unsigned shift;
153     do_unaligned_access:
154         addr1 = addr & ~(DATA_SIZE - 1);
155         addr2 = addr1 + DATA_SIZE;
156         res1 = helper_le_ld_name(env, addr1, oi, retaddr);
157         res2 = helper_le_ld_name(env, addr2, oi, retaddr);
158         shift = (addr & (DATA_SIZE - 1)) * 8;
159
160         /* Little-endian combine.  */
161         res = (res1 >> shift) | (res2 << ((DATA_SIZE * 8) - shift));
162         return res;
163     }
164
165     haddr = addr + env->tlb_table[mmu_idx][index].addend;
166 #if DATA_SIZE == 1
167     res = glue(glue(ld, LSUFFIX), _p)((uint8_t *)haddr);
168 #else
169     res = glue(glue(ld, LSUFFIX), _le_p)((uint8_t *)haddr);
170 #endif
171     return res;
172 }

函數參數CPUArchState *env 表示當前cpu的狀態,target_ulong addr是gva,TCGMemOpIdx oi用於指定尋址模式(x86有普通模式和系統管理模式), uintptr_t retaddr 返回值。

111-121行檢查內存訪問是否符合cpu的對齊要求,未滿足則觸發異常

123-130行從tlb_table中找到讀地址(沒有映射的話使用tlb_fill進行映射)

134-144 行從判斷地址是否未一個mmio地址,如果是mmio地址則調用
res = glue(io_read, SUFFIX)(env, mmu_idx, index, addr, retaddr); 進行讀取, 這裏怎麼判斷是否爲io地址呢,參考qemu內存模型(6)mm實現(一)實模式, 其實主要就是根據addr_read的後面幾位。 因爲rom類型讀取執行讀hva,所以對於rom地址的讀取不會進入io_read。

135-147 行是處理ram或者rom的訪問,這裏主要對於一個內存讀取跨頁面的情況,要轉化成兩次helper_le_ld_name調用。

165-170行調用glue(glue(ld, LSUFFIX), _le_p)((uint8_t *)haddr) 進行實際讀取。 這裏怎麼獲取的haddr呢,通過haddr = addr + env->tlb_table[mmu_idx][index].addend; 這個計算得出,也就是說gva+env->tlb_table[mmu_idx][index].addend = hva, 具體爲什麼這麼計算,請參考qemu內存模型(6)mm實現(一)實模式 這篇文章

讀取ram和rom是比較簡單的,所以我們先分析ram,rom的讀取

glue(glue(ld, LSUFFIX), _le_p)((uint8_t *)haddr)

這個宏展開就是

ld_uw_le_p((uint8_t *)haddr)

static inline int lduw_le_p(const void *ptr)
{
    return (uint16_t)le_bswap(lduw_he_p(ptr), 16);
}

/* Any compiler worth its salt will turn these memcpy into native unaligned
   operations.  Thus we don't need to play games with packed attributes, or
   inline byte-by-byte stores.  */

static inline int lduw_he_p(const void *ptr)
{
    uint16_t r;
    memcpy(&r, ptr, sizeof(r));
    return r;
}
#define le_bswap(v, size) (v)

其實就是從該haddr 讀取16bit數據。

分析完ram,rom的讀取再來分析mmio的讀取。

glue(io_read, SUFFIX)(env, mmu_idx, index, addr, retaddr)

展開 io_read_uw
static inline DATA_TYPE glue(io_read, SUFFIX)(CPUArchState *env,
                                              size_t mmu_idx, size_t index,
                                              target_ulong addr,
                                              uintptr_t retaddr)
{
    CPUIOTLBEntry *iotlbentry = &env->iotlb[mmu_idx][index];
    return io_readx(env, iotlbentry, mmu_idx, addr, retaddr, DATA_SIZE);
}

對於io類型的讀取, 使用iotlb作爲索引。 調用io_readx進行具體執行


static uint64_t io_readx(CPUArchState *env, CPUIOTLBEntry *iotlbentry,
                         int mmu_idx,
                         target_ulong addr, uintptr_t retaddr, int size)
{
    CPUState *cpu = ENV_GET_CPU(env);
    hwaddr physaddr = iotlbentry->addr;
    MemoryRegion *mr = iotlb_to_region(cpu, physaddr, iotlbentry->attrs);
    uint64_t val;
    bool locked = false;
    MemTxResult r;

    physaddr = (physaddr & TARGET_PAGE_MASK) + addr;
    cpu->mem_io_pc = retaddr;
    if (mr != &io_mem_rom && mr != &io_mem_notdirty && !cpu->can_do_io) {
        cpu_io_recompile(cpu, retaddr);
    }

    cpu->mem_io_vaddr = addr;

    if (mr->global_locking && !qemu_mutex_iothread_locked()) {
        qemu_mutex_lock_iothread();
        locked = true;
    }
    r = memory_region_dispatch_read(mr, physaddr,
                                    &val, size, iotlbentry->attrs);
    if (r != MEMTX_OK) {
        cpu_transaction_failed(cpu, physaddr, addr, size, MMU_DATA_LOAD,
                               mmu_idx, iotlbentry->attrs, r, retaddr);
    }
    if (locked) {
        qemu_mutex_unlock_iothread();
    }

    return val;
}

首先找到地址對應的MemoryRegion, 然後調用memory_region_dispatch_read進行讀派發

MemTxResult memory_region_dispatch_read(MemoryRegion *mr,
                                        hwaddr addr,
                                        uint64_t *pval,
                                        unsigned size,
                                        MemTxAttrs attrs)
{
    MemTxResult r;

    if (!memory_region_access_valid(mr, addr, size, false)) {
        *pval = unassigned_mem_read(mr, addr, size);
        return MEMTX_DECODE_ERROR;
    }

    r = memory_region_dispatch_read1(mr, addr, pval, size, attrs);
    adjust_endianness(mr, pval, size);
    return r;
}

這裏對訪問權限對齊等做了寫驗證,調用memory_region_dispatch_read1 進行真正的讀取

static MemTxResult memory_region_dispatch_read1(MemoryRegion *mr,
                                                hwaddr addr,
                                                uint64_t *pval,
                                                unsigned size,
                                                MemTxAttrs attrs)
{
    *pval = 0;

    if (mr->ops->read) {
        return access_with_adjusted_size(addr, pval, size,
                                         mr->ops->impl.min_access_size,
                                         mr->ops->impl.max_access_size,
                                         memory_region_read_accessor,
                                         mr, attrs);
    } else if (mr->ops->read_with_attrs) {
        return access_with_adjusted_size(addr, pval, size,
                                         mr->ops->impl.min_access_size,
                                         mr->ops->impl.max_access_size,
                                         memory_region_read_with_attrs_accessor,
                                         mr, attrs);
    } else {
        return access_with_adjusted_size(addr, pval, size, 1, 4,
                                         memory_region_oldmmio_read_accessor,
                                         mr, attrs);
    }
}

如果對應的MemoryRegion設置不同的讀回調,調用access_with_adjusted_size函數,唯一不不同就是第三個參數,分別爲memory_region_read_accessor,memory_region_read_with_attrs_accessor 和memory_region_oldmmio_read_accessor

static MemTxResult access_with_adjusted_size(hwaddr addr,
                                      uint64_t *value,
                                      unsigned size,
                                      unsigned access_size_min,
                                      unsigned access_size_max,
                                      MemTxResult (*access_fn)
                                                  (MemoryRegion *mr,
                                                   hwaddr addr,
                                                   uint64_t *value,
                                                   unsigned size,
                                                   unsigned shift,
                                                   uint64_t mask,
                                                   MemTxAttrs attrs),
                                      MemoryRegion *mr,
                                      MemTxAttrs attrs)
{
    uint64_t access_mask;
    unsigned access_size;
    unsigned i;
    MemTxResult r = MEMTX_OK;

    if (!access_size_min) {
        access_size_min = 1;
    }
    if (!access_size_max) {
        access_size_max = 4;
    }

    /* FIXME: support unaligned access? */
    access_size = MAX(MIN(size, access_size_max), access_size_min);
    access_mask = -1ULL >> (64 - access_size * 8);
    if (memory_region_big_endian(mr)) {
        for (i = 0; i < size; i += access_size) {
            r |= access_fn(mr, addr + i, value, access_size,
                        (size - access_size - i) * 8, access_mask, attrs);
        }
    } else {
        for (i = 0; i < size; i += access_size) {
            r |= access_fn(mr, addr + i, value, access_size, i * 8,
                        access_mask, attrs);
        }
    }
    return r;
}

access_with_adjusted_size 函數很簡單,按照合適的大小去讀取io設備地址,memory_region_read_accessor,memory_region_read_with_attrs_accessor 和memory_region_oldmmio_read_accessor 函數也比較簡單,其實最終都調用了MemoryRegion設置的讀回調函數, 就不分析了。

對於地址空間的讀寫其實實現差不多,這裏就不具體分析了。感興趣的讀者可以自行分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章