写入mmio寄存器时如何避免缓存?
how to avoid caching when writing to mmio registers?
我正在 virtualbox 中编写自定义 os,但无法成功地从 IOAPIC mmio 寄存器写入和读取。即它似乎忽略了索引寄存器写入。用IOAPIC基地址加载R8
后(由ACPI枚举确定为0xFEC00000),我使用以下例程read/write:
; -----------------------------------------------------------------------------
; IN : RAX = ioapic address, EBX = index register
; OUT: ECX = return value
ioapic_read:
mov [r8], ebx
mov ecx, [r8 + 0x10]
ret
; -----------------------------------------------------------------------------
; IN : RAX = ioapic address, EBX = index register, ECX = value
; OUT: -
ioapic_write:
mov [r8], ebx
mov [r8 + 0x10], ecx
ret
但是 ioapic_read 将始终 return 最后写入的值(由 ioapic_write),而不管使用的索引如何。我有使用 0x9B 的身份分页设置,我认为它应该禁用缓存。
我尝试在每个 mov
之后使用 pause
。没有帮助。在 mov
秒之间尝试了 mfence
秒。没有帮助。
我已确认 0xFEC00000
地址已成功进行身份映射。
似乎仍在进行一些缓存。我错过了什么?
编辑
我发现这不是缓存问题,而是一些更奇怪的问题 - 至少对我无知的大脑来说是这样。我的身份分页按需工作,因此页面错误将在表中生成正确的物理页面。
这似乎有效,但在 IOAPIC mmio 寄存器的情况下,我需要在尝试使用它之前通过对 0xFEC00000 地址进行虚拟读取或写入来导致页面错误。更奇怪的是,我需要事先阅读足够多的说明来做这个假人,否则它就不起作用。例如
这行得通!
mov eax, [os_IOAPICAddress]
mov dword[rax], 0
mov r8, rax
.
.
.
call ioapic_read
...这不是!
mov eax, [os_IOAPICAddress]
mov r8, rax
mov dword[rax], 0
.
.
.
call ioapic_read
我怀疑有一个 pipelining/serializing 问题,但我真的很想了解为什么我需要在 MMIO 寄存器中使用地址之前将地址分页错误放入表中,以及为什么我需要做得足够多提前。在后一种情况下,如何修复它以使其序列化,这样我就不必担心了。
我的身份寻呼例程:
pageFault_identity_0x0E:
pop r8
push rsi rdi rax rcx rdx r9
test r8, 1
jnz exception_gate_14
mov rdx, cr2 ; faulting address
shr rdx, 39
and rdx, 0x1FF ; get 9 bit index
mov rdi, cr3
lea rsi, [rdi + rdx*8]
mov rdi, [rsi]
test rdi, 1
jnz @f
call set_new_page_table
@@:
shr rdi, 12 ; get rid of flags
shl rdi, 12
mov rdx, cr2
shr rdx, 30 ; get 9 bit index
and rdx, 0x1FF
lea rsi, [rdi + rdx*8]
mov rdi, [rsi]
test rdi, 1
jnz @f
call set_new_page_table
@@:
shr rdi, 12 ; get rid of flags
shl rdi, 12
mov rdx, cr2
shr rdx, 21
mov rax, rdx
and rdx, 0x1FF ; get 9 bit index
lea rsi, [rdi + rdx*8]
shl rax, 21
or rax, 0x83
mov [rsi], rax
shr rax, 21
shl rax, 21
pop r9 rdx rcx rax rdi rsi
iretq
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;
; IN: rsi = address of blank entry
; OUT: rdi = base address of new table, changes rax & rcx
;
set_new_page_table: ; make table, get it, zero it, insert base into previous table
movzx rdi, [page_table_count]
shl rdi, 12
add rdi, NEW_PAGE_TABLES
CLEAR_BLOCK rdi, 0x200 ; clears 4096 bytes in rdi, returns rdi + 4096
sub rdi, 0x1000
lea rax, [rdi + 0x3] ; table base address
mov [rsi], rax
inc [page_table_count]
ret
根据原始代码,您似乎正在正确设置页面目录条目位以将 MMIO 区域标记为不可缓存。我确信还有其他问题。在您随后的编辑中,您向我们展示了您的页面错误处理程序 pageFault_identity_0x0
:
pageFault_identity_0x0E:
pop r8
push rsi rdi rax rcx rdx r9
当处理器将控制转移到此页面错误异常处理程序时,它将在堆栈顶部传递一个错误代码作为参数。问题是你把R8的内容替换成错误号,没有保存再恢复寄存器。
您必须修改您的异常处理程序以保留 R8,将内容从错误编号所在的正确堆栈偏移量移动到 R8。请记住确保错误编号不再位于 IRETQ.
之前的堆栈顶部
您遇到的奇怪行为可能与 R8 因页面错误 return 无法正确恢复直接相关。
可能有效的解决方案是:
pageFault_identity_0x0E:
push rsi
push rdi
push rax
push rcx
push rdx
push r9
push r8
mov r8, [rsp+7*8] ; Error Code is at offset RSP+7*8 after all the pushes
; Do exception handling work here
pop r8
pop r9
pop rdx
pop rcx
pop rax
pop rdi
pop rsi
add rsp, 8 ; Remove the error code
iretq
Michael 解决了它,但为了完整起见,我将 post 我的最终实现。
pageFault_identity_0x0E:
test qword[rsp], 1
jnz exception_gate_14
add rsp, 8
push rsi rdi rax rcx rdx
mov rdx, cr2 ; faulting address
.
.
.
pop rdx rcx rax rdi rsi
iretq
编辑:已编辑删除 xchg
。
我正在 virtualbox 中编写自定义 os,但无法成功地从 IOAPIC mmio 寄存器写入和读取。即它似乎忽略了索引寄存器写入。用IOAPIC基地址加载R8
后(由ACPI枚举确定为0xFEC00000),我使用以下例程read/write:
; -----------------------------------------------------------------------------
; IN : RAX = ioapic address, EBX = index register
; OUT: ECX = return value
ioapic_read:
mov [r8], ebx
mov ecx, [r8 + 0x10]
ret
; -----------------------------------------------------------------------------
; IN : RAX = ioapic address, EBX = index register, ECX = value
; OUT: -
ioapic_write:
mov [r8], ebx
mov [r8 + 0x10], ecx
ret
但是 ioapic_read 将始终 return 最后写入的值(由 ioapic_write),而不管使用的索引如何。我有使用 0x9B 的身份分页设置,我认为它应该禁用缓存。
我尝试在每个 mov
之后使用 pause
。没有帮助。在 mov
秒之间尝试了 mfence
秒。没有帮助。
我已确认 0xFEC00000
地址已成功进行身份映射。
似乎仍在进行一些缓存。我错过了什么?
编辑
我发现这不是缓存问题,而是一些更奇怪的问题 - 至少对我无知的大脑来说是这样。我的身份分页按需工作,因此页面错误将在表中生成正确的物理页面。
这似乎有效,但在 IOAPIC mmio 寄存器的情况下,我需要在尝试使用它之前通过对 0xFEC00000 地址进行虚拟读取或写入来导致页面错误。更奇怪的是,我需要事先阅读足够多的说明来做这个假人,否则它就不起作用。例如
这行得通!
mov eax, [os_IOAPICAddress]
mov dword[rax], 0
mov r8, rax
.
.
.
call ioapic_read
...这不是!
mov eax, [os_IOAPICAddress]
mov r8, rax
mov dword[rax], 0
.
.
.
call ioapic_read
我怀疑有一个 pipelining/serializing 问题,但我真的很想了解为什么我需要在 MMIO 寄存器中使用地址之前将地址分页错误放入表中,以及为什么我需要做得足够多提前。在后一种情况下,如何修复它以使其序列化,这样我就不必担心了。
我的身份寻呼例程:
pageFault_identity_0x0E:
pop r8
push rsi rdi rax rcx rdx r9
test r8, 1
jnz exception_gate_14
mov rdx, cr2 ; faulting address
shr rdx, 39
and rdx, 0x1FF ; get 9 bit index
mov rdi, cr3
lea rsi, [rdi + rdx*8]
mov rdi, [rsi]
test rdi, 1
jnz @f
call set_new_page_table
@@:
shr rdi, 12 ; get rid of flags
shl rdi, 12
mov rdx, cr2
shr rdx, 30 ; get 9 bit index
and rdx, 0x1FF
lea rsi, [rdi + rdx*8]
mov rdi, [rsi]
test rdi, 1
jnz @f
call set_new_page_table
@@:
shr rdi, 12 ; get rid of flags
shl rdi, 12
mov rdx, cr2
shr rdx, 21
mov rax, rdx
and rdx, 0x1FF ; get 9 bit index
lea rsi, [rdi + rdx*8]
shl rax, 21
or rax, 0x83
mov [rsi], rax
shr rax, 21
shl rax, 21
pop r9 rdx rcx rax rdi rsi
iretq
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;
; IN: rsi = address of blank entry
; OUT: rdi = base address of new table, changes rax & rcx
;
set_new_page_table: ; make table, get it, zero it, insert base into previous table
movzx rdi, [page_table_count]
shl rdi, 12
add rdi, NEW_PAGE_TABLES
CLEAR_BLOCK rdi, 0x200 ; clears 4096 bytes in rdi, returns rdi + 4096
sub rdi, 0x1000
lea rax, [rdi + 0x3] ; table base address
mov [rsi], rax
inc [page_table_count]
ret
根据原始代码,您似乎正在正确设置页面目录条目位以将 MMIO 区域标记为不可缓存。我确信还有其他问题。在您随后的编辑中,您向我们展示了您的页面错误处理程序 pageFault_identity_0x0
:
pageFault_identity_0x0E:
pop r8
push rsi rdi rax rcx rdx r9
当处理器将控制转移到此页面错误异常处理程序时,它将在堆栈顶部传递一个错误代码作为参数。问题是你把R8的内容替换成错误号,没有保存再恢复寄存器。
您必须修改您的异常处理程序以保留 R8,将内容从错误编号所在的正确堆栈偏移量移动到 R8。请记住确保错误编号不再位于 IRETQ.
之前的堆栈顶部您遇到的奇怪行为可能与 R8 因页面错误 return 无法正确恢复直接相关。
可能有效的解决方案是:
pageFault_identity_0x0E:
push rsi
push rdi
push rax
push rcx
push rdx
push r9
push r8
mov r8, [rsp+7*8] ; Error Code is at offset RSP+7*8 after all the pushes
; Do exception handling work here
pop r8
pop r9
pop rdx
pop rcx
pop rax
pop rdi
pop rsi
add rsp, 8 ; Remove the error code
iretq
Michael 解决了它,但为了完整起见,我将 post 我的最终实现。
pageFault_identity_0x0E:
test qword[rsp], 1
jnz exception_gate_14
add rsp, 8
push rsi rdi rax rcx rdx
mov rdx, cr2 ; faulting address
.
.
.
pop rdx rcx rax rdi rsi
iretq
编辑:已编辑删除 xchg
。