建議先讀
對於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設置的讀回調函數, 就不分析了。
對於地址空間的讀寫其實實現差不多,這裏就不具體分析了。感興趣的讀者可以自行分析。