Looking Glass on Xen

Has there been any attempts to get Looking Glass running under Xen (rather than KVM)?
I personally have a working GPU passthrough setup under Xen, which I don’t really want to change, so I gave it a try.

I had a brief look through how LG works on KVM, and there seems to be two ways it could be ported to Xen:

  • Continue to use ivshmem (technically unsupported on Xen) with some hacks and manually map the shared memory into the host (see below for details). This would be minimal new code
  • Use Xen grant tables (also available via the vchan library) to share memory in the Xen supported way. Would probably need to write Xen-specific drivers both for host and guest

Obviously continuing to use ivshmem would be the least effort, so it’s what I tried so far. While Xen doesn’t allow you to manually add arguments to the qemu command line arguments, it does give you access to the qemu monitor to add devices after the VM’s been created:

xl qemu-monitor-command <guest> 'object_add memory-backend-ram,id=ivshmem,share=on,size=64M'
xl qemu-monitor-command <guest> 'device_add ivshmem-plain,memdev=ivshmem'

(This allocates memory in the guest, so you might need to use xl mem-max <guest> and xl mem-set Domain-0 to raise the guest memory limit and free up memory from Dom0 to move to the guest)

Notice how the memory backend is a memory-backend-ram, rather than a memory-backend-file. This is because Xen doesn’t support file-backed memory backends (qemu will return an error if you try). But this makes Xen allocate the memory, with no direct way for the host to access it. We have to:

  1. Find where in guest memory the memory has been allocated to
  2. Map the memory into the host

Fortunately, there’s some more qemu monitor commands to use to find the memory in the guest:

# xl qemu-monitor-command <guest> 'info pci'
...snip...
  Bus  0, device   7, function 0:
    RAM controller: PCI device 1af4:1110
      PCI subsystem 1af4:1100
      BAR0: 32 bit memory at 0xf6312000 [0xf63120ff].
      BAR2: 64 bit prefetchable memory at 0xf0000000 [0xf3ffffff].
      id ""

Meaning the memory is in guest physical memory [0xf0000000, 0xf3ffffff] (1af4:1110 is the ivshmem PCI ID, and BAR2 is where ivshmem exposes the shared memory). This address can change, so it needs to be re-looked-up every time the guest is booted and ivshmem added.

There might be other ways to find the memory, like via the qemu QOM

I was browsing the QOM to find a “cleaner” way to get the memory info, rather than parse info pci's output, and /objects/ivshmem/ivshmem[0] looks promising:

# xl qemu-monitor-command <guest> 'qom-list /objects/ivshmem/ivshmem[0]'
type (string)
container (link<qemu:memory-region>)
addr (uint64)
size (uint64)
priority (uint32)

But unfortunately, qom-get isn’t in any qemu release yet (it was only added a few days ago in commit 89cf4fe34f4afa671a2ab5d9430021ea12106274), so I’ll have to compile qemu from source before I can test this out

After getting this far and running Looking Glass on the guest, I was able to use another qemu monitor command to double check that LG is indeed writing to the memory segment:

# xl qemu-monitor-command <guest> 'xp/4cw 0xf0000000'
00000000f0000000: '[' '[' 'K' 'V' 'M' 'F' 'R' ']' ']' '\x00' '\x00' '\x00' '\x08' '\x00' '\x00' '\x00'

So now all that’s left is making Looking Glass map in memory from the guest. Fortunately Xen has a library that seems to allow just that: libxenforeignmemory.

This is as far as I’ve gotten, where I’m about to start modifying Looking Glass to map in the shared memory with libxenforeignmemory.

As you can see, a lot of xl qemu-monitor-commands have been involved, which is explicitly unsupported by Xen. Thus this is all a kind of big hack.

Do you know of any other efforts to port Looking Glass to Xen? If I do the porting using these big xl qemu-monitor-command hacks, do you think I’ll be able to upstream the work to Looking Glass (I don’t think I’ll ever be able to get ivshmem support upstreamed to Xen, since grant tables already exist)?

I’ll keep this thread updated on my efforts, so hopefully other people might be able to make use of my (albeit hacky) work

3 Likes

This is the first I’ve seen of this sort of attempt, but I’m not as clued in as gnif.

Looks like you really did your homework and I’ve gotta say, this is really good. I’m looking forward to updates

Unsupported just means that you likely won’t get the sign off from upstream.

I suspect the number of xen passthrough situations are far fewer than KVM. Though, I may be wrong.

You already are at the crux of the issue, you need to partition the Xen people for a method to obtain uncached access to this ram segment, without it there is nothing more that can be done. LG doesn’t need to “spport xen”, it’s the other way around.

1 Like

Just tried to map in the guest ivshmem region with libxenforeignmemory, but it’s failing with “Invalid Argument”.

What’s weird is it’s failing with “Invalid Argument” for any PCI device memory (which ivshmem is), while if I try to map in regular memory, it works just fine.

I’m now asking the xen-devel mailing list if they know what’s going on, while in the meantime also digging through the qemu source code to find out how exactly it creates a PCI device in the guest

(I actually did the below months ago, but never bothered writing it down, so I’m now recalling from memory and might be remembering details wrong)

So I dug into the qemu source code, looking at what exactly’s going on with PCI device memory.

In the ivshmem case, qemu actually maps the device memory to the memdev object specified when creating the ivshmem device. In our case, the memdev object is memory-backend-ram, which is allocated in a completely different area of the guest memory, which you can check with info ramblock (0x420080000 in this example):

# xl qemu-monitor-command <guest> 'info ramblock'
              Block Name    PSize              Offset               Used              Total
...snip...
    0000:00:07.0/ivshmem    4 KiB  0x0000000420080000 0x0000000004000000 0x0000000004000000
...snip...

So when the guest accesses the ivshmem BAR2 (0xf0000000 from an earlier post), qemu (or Xen?) redirects this access to 0x420080000 instead.

This ramblock address (0x420080000) is in fact accessible via the xenforeignmemory library, unlike the BAR2 address (0xf0000000).

So why is the BAR2 address inaccessible?
I’m still not sure, but my best guess right now is that qemu (under Xen) doesn’t actually make the BAR2 address point to the same backing memory as the ramblock address via page table manipulation. But rather, it just leaves the BAR2 address unallocated in the page table, and when the guest accesses it, has the hypervisor trap into qemu to emulate the access to the ramblock.
Since the BAR2 address doesn’t exist in the guest page table, Xen has nothing to map when you try to access it with xenforeignmemory, which bypasses qemu.

If this “ivshmem is emulated” (under Xen) guess is true, it has some big performance implications. Though I’ve been unable to actually verify any performance impact, since I’ve hit another problem after getting access to the memory (TBD in next post)…

Here is my Looking Glass modifications so far:
0001-Quick-and-dirty-Xen-support-via-libxenforeignmemory.patch (7.6 KB)

It adds 3 new options to specify the guest memory details, with an example invocation of:

sudo ./looking-glass-client app:shmXenAddress=0x420080000 app:shmXenSize=64 app:shmXenDomId=1

(See details in previous post about info ramblock on how to obtain the address needed here)

Unfortunately, this doesn’t seem to be enough. When I try to connect to the guest, Looking Glass errors out with LGMP_ERR_QUEUE_TIMEOUT:

$ sudo ./looking-glass-client spice:enable=no app:shmXenAddress=0x420080000 app:shmXenSize=64 app:shmXenDomId=1
  3144159098 [I]               main.c:2266 | main                           | Looking Glass (B3-rc1-9-gef54e1be7f+)
  3144159193 [I]               main.c:2267 | main                           | Locking Method: Atomic
  3144320102 [I]            ivshmem.c:294  | ivshmemOpenXenMem              | 0x420080000: 0x2504d474c 0x00004823 0x00069715 0x00000002
  3144338027 [I]                egl.c:226  | egl_initialize                 | Double buffering is off
  3144338042 [I]               main.c:1646 | try_renderer                   | Using Renderer: EGL
  3144338480 [I]                x11.c:104  | x11Init                        | X11 XInput 2.3 in use
  3144367308 [I]                egl.c:516  | egl_render_startup             | use native: false
  3144445242 [I]                egl.c:576  | egl_render_startup             | Multisampling enabled, max samples: 4
  3144447199 [I]                egl.c:627  | egl_render_startup             | Single buffer mode
  3144453371 [I]                egl.c:645  | egl_render_startup             | EGL       : 1.5
  3144453387 [I]                egl.c:646  | egl_render_startup             | Vendor    : AMD
  3144453392 [I]                egl.c:647  | egl_render_startup             | Renderer  : AMD Radeon (TM) RX 480 Graphics (POLARIS10, DRM 3.40.0, 5.10.0-1-amd64, LLVM 11.0.1)
  3144453395 [I]                egl.c:648  | egl_render_startup             | Version   : OpenGL ES 3.2 Mesa 20.3.3
  3144453403 [I]                egl.c:649  | egl_render_startup             | EGL APIs  : OpenGL OpenGL_ES 
  3144453409 [I]                egl.c:650  | egl_render_startup             | Extensions: EGL_ANDROID_blob_cache EGL_ANDROID_native_fence_sync EGL_CHROMIUM_sync_control EGL_EXT_buffer_age EGL_EXT_create_context_robustness EGL_EXT_image_dma_buf_import EGL_EXT_image_dma_buf_import_modifiers EGL_EXT_swap_buffers_with_damage EGL_KHR_cl_event2 EGL_KHR_config_attribs EGL_KHR_create_context EGL_KHR_create_context_no_error EGL_KHR_fence_sync EGL_KHR_get_all_proc_addresses EGL_KHR_gl_colorspace EGL_KHR_gl_renderbuffer_image EGL_KHR_gl_texture_2D_image EGL_KHR_gl_texture_3D_image EGL_KHR_gl_texture_cubemap_image EGL_KHR_image EGL_KHR_image_base EGL_KHR_image_pixmap EGL_KHR_no_config_context EGL_KHR_reusable_sync EGL_KHR_surfaceless_context EGL_KHR_swap_buffers_with_damage EGL_EXT_pixel_format_float EGL_KHR_wait_sync EGL_MESA_configless_context EGL_MESA_drm_image EGL_MESA_image_dma_buf_export EGL_MESA_query_driver EGL_NOK_texture_from_pixmap EGL_WL_bind_wayland_display 
  3144664783 [I]               main.c:2155 | lg_run                         | Host ready, reported version: B3-rc1-9-gef54e1be7f
  3144664802 [I]               main.c:2156 | lg_run                         | Starting session
  3155655824 [I]               main.c:652  | frameThread                    | Format: FRAME_TYPE_BGRA 2560x1440 stride:2560 pitch:10240 rotation:0
  3156871156 [E]               main.c:594  | frameThread                    | lgmpClientProcess Failed: LGMP_ERR_QUEUE_TIMEOUT

Having a quick look through the LGMP source, there’s multiple places where LGMP_ERR_QUEUE_TIMEOUT can be returned, but all to do with the protocol’s nitty gritty implementation details (subscriber/queue?).

I don’t see any obvious documentation of the LGMP (please point me to it if I missed it!), and I haven’t had the time to work it out from the source code yet to be able to investigate further, so I just have to leave it at that.

If anyone has any ideas of why I get the LGMP_ERR_QUEUE_TIMEOUT error, please let me know.

Timeouts are generated by the host application if the subscriber is taking too long to ack the message/frame. LGMP is a broadcast system to allow multiple listeners, this timeout is to prevent a single bad/hung listener from stalling the pipeline.

Usually the cause is failure to pin CPU cores, pay attention to numa memory allocation, or simply a lack of CPU resources. LGMP uses polling to determine when updates are available.

Since you’re messing with Xen, the issue could be due to cache coherency if Xen is indeed emulating the memory backing.

I assume the LGMP client writes to the same ivshmem area to ack the message? Which the LGMP host then picks back up?
If so, I should be able to easily test this by writing something to the ivshmem area from the VM host, and seeing if the writes appear in the VM guest.

Is there a quick and easy way to read the raw bytes in the ivshmem area inside the guest, without needing to compile any special programs?

Yes.

They certainly are otherwise it wouldn’t even start, however, cache coherency could be an issue giving inconsistent latency/perf due to unseen changes that reside in the local CPU core cache.

No, you have to write something to use the IOCTLs for the IVSHMEM device to obtain the mapped memory, and windows only will allow one application to own this memory at a time.