我正在使用基于 QEMU 的 x86 模拟器 MARSSx86 做一些工作。我正在尝试在来宾操作系统和模拟器之间发送一些数据。通常,这是通过使用开发人员提供的库来处理的,该库写入内存中为此类通信保留的特殊区域。但是,我正在尝试在内核空间中执行此操作,而库依赖于执行 mmap,这显然行不通。据我了解,我需要做的就是访问一个已知的物理地址并在那里写入一些数据。
我对内存操作非常陌生,尤其是在内核中,但我觉得这应该是直截了当的。我相信我可以只使用 ioremap,但它没有提供我理解的可用虚拟地址,这是实现代码所需要的。为了让事情更清楚,这里是设置映射的代码:
#define bits(x, i, l) (((x) >> (i)) & bitmask(l))
#define LO32(x) (W32)((x) & 0xffffffffLL)
#define bitmask(l) (((l) == 64) ? (W64)(-1LL) : ((1LL << (l))-1LL))
static int ptlsim_check_status __attribute__((common)) = 0;
static W64 supported_ptlcall_methods __attribute__((common)) = 0;
static int selected_ptlcall_method __attribute__((common)) = -1;
static W64 ptlcall_mmio_page_physaddr __attribute__((common)) = 0;
static W64* ptlcall_mmio_page_virtaddr __attribute__((common)) = NULL;
static W16 ptlcall_io_port __attribute__((common)) = 0;
static int ptlsim_ptlcall_init() {
W32 rax = PTLSIM_CPUID_MAGIC;
W32 rbx = 0;
W32 rcx = 0;
W32 rdx = 0;
int ptlcall_mmio_page_offset;
static const char* mmap_filename = "/dev/mem";
// a.k.a. cpuid(PTLSIM_CPUID_MAGIC, rax, rbx, rcx, rdx);
asm volatile("cpuid" : "+a" (rax), "+b" (rbx), "+c" (rcx), "+d" (rdx) : : "memory");
if (rax != PTLSIM_CPUID_FOUND) {
ptlsim_check_status = -1;
return 0;
}
supported_ptlcall_methods = rbx;
ptlcall_mmio_page_physaddr = (bits(rdx, 0, 16) << 32) | LO32(rcx);
ptlcall_mmio_page_offset = (ptlcall_mmio_page_physaddr & 0xfff);
ptlcall_mmio_page_physaddr &= ~0xfff;
ptlcall_io_port = bits(rdx, 16, 16);
if (supported_ptlcall_methods & PTLCALL_METHOD_MMIO) {
// We use O_SYNC to guarantee uncached accesses:
int fd = open(mmap_filename, O_RDWR|O_LARGEFILE|O_SYNC, 0);
if (fd < 0) {
fprintf(stderr, "ptlsim_ptlcall_init: cannot open %s for MMIO to physaddr %p (%s)\n",
mmap_filename, (void*)ptlcall_mmio_page_physaddr, strerror(errno));
supported_ptlcall_methods &= ~PTLCALL_METHOD_MMIO;
ptlsim_check_status = -2;
return 0;
}
ptlcall_mmio_page_virtaddr = (W64*)mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, ptlcall_mmio_page_physaddr);
if (((int)(Waddr)ptlcall_mmio_page_virtaddr) == -1) {
fprintf(stderr, "ptlsim_ptlcall_init: cannot mmap %s (fd %d) for MMIO to physaddr %p (%s)\n", mmap_filename, fd, (void*)ptlcall_mmio_page_physaddr, strerror(errno));
supported_ptlcall_methods &= ~PTLCALL_METHOD_MMIO;
ptlsim_check_status = -3;
close(fd);
return 0;
}
// Adjust the pointer to the actual trigger word within the page (usually always offset 0)
ptlcall_mmio_page_virtaddr = (W64*)(((Waddr)ptlcall_mmio_page_virtaddr) + ptlcall_mmio_page_offset);
close(fd);
selected_ptlcall_method = PTLCALL_METHOD_MMIO;
fprintf(stderr, "ptlsim_ptlcall_init: mapped PTLcall MMIO page at phys %p, virt %p\n", (void*)ptlcall_mmio_page_physaddr, (void*)ptlcall_mmio_page_virtaddr);
}
ptlsim_check_status = +1;
return 1;
}
这是设置完成后进行实际通信的代码(为了简洁起见,我显然在这里留下了很多内容,但如果缺少重要的东西,请告诉我,我可以编辑它):
static inline W64 ptlcall_single(const char* command, int flush) {
struct PTLsimCommandDescriptor desc;
desc.command = (W64)command;
desc.length = strlen(command);
return ptlcall(PTLCALL_ENQUEUE, (W64)&desc, 1, flush, 0, 0, 0);
}
static inline W64 ptlcall(W64 op, W64 arg1, W64 arg2, W64 arg3, W64 arg4, W64 arg5, W64 arg6)
{
if (!is_running_under_ptlsim())
return (W64)(-ENOSYS);
if (selected_ptlcall_method == PTLCALL_METHOD_MMIO) {
return do_ptlcall_mmio(op, arg1, arg2, arg3, arg4, arg5, arg6);
} else
return (W64)(-ENOSYS);
}
static inline W64 do_ptlcall_mmio(W64 callid, W64 arg1, W64 arg2, W64 arg3, W64 arg4, W64 arg5, W64 arg6)
{
W64 rc;
asm volatile ("movq %[arg4],%%r10\n"
"movq %[arg5],%%r8\n"
"movq %[arg6],%%r9\n"
"mfence\n"
"smsw %[target]\n"
: "=a" (rc),
[target] "=m" (*ptlcall_mmio_page_virtaddr)
: [callid] "a" (callid),
[arg1] "D" ((W64)(arg1)),
[arg2] "S" ((W64)(arg2)),
[arg3] "d" ((W64)(arg3)),
[arg4] "g" ((W64)(arg4)),
[arg5] "g" ((W64)(arg5)),
[arg6] "g" ((W64)(arg6))
: "r11","rcx","memory" ,"r8", "r10", "r9");
return rc;
}
基本上,我想把所有这些代码放在内核中,然后重写第一块代码中的内存操作,以便第二块代码可以不加修改地运行。根据我的发现,我觉得 remap_pfn_range 可能是我需要的功能,但我不知道如何在这种情况下使用它。我意识到这很多,所以即使他们没有回答整个问题,任何指针都会受到赞赏。