[common/client] ivshmem: add portable external frames

This commit is contained in:
Geoffrey McRae
2026-09-12 09:49:24 +10:00
parent 6d367d4ab2
commit 382f35e503
28 changed files with 946 additions and 227 deletions

View File

@@ -22,26 +22,34 @@
#define _H_LG_COMMON_IVSHMEM_
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef enum IVSHMEMCacheMode
{
/* Producers normally use write-combined mappings; consumers use cached.
* Platforms without selectable cache modes ignore this policy. */
IVSHMEM_CACHE_MODE_NONCACHED = 0,
IVSHMEM_CACHE_MODE_CACHED = 1,
IVSHMEM_CACHE_MODE_WRITECOMBINED = 2,
}
IVSHMEMCacheMode;
struct IVSHMEM
{
unsigned int size;
void * mem;
size_t size;
void * mem;
// internal use
void * opaque;
void * opaque;
};
void ivshmemOptionsInit(void);
bool ivshmemInit(struct IVSHMEM * dev);
bool ivshmemOpen(struct IVSHMEM * dev);
bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDev);
bool ivshmemOpen(struct IVSHMEM * dev, IVSHMEMCacheMode cacheMode);
bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDev,
IVSHMEMCacheMode cacheMode);
void ivshmemClose(struct IVSHMEM * dev);
void ivshmemFree(struct IVSHMEM * dev);
/* Linux KVMFR support only for now (VM->VM) */
bool ivshmemHasDMA (struct IVSHMEM * dev);
int ivshmemGetDMABuf(struct IVSHMEM * dev, uint64_t offset, uint64_t size);
#endif

View File

@@ -0,0 +1,31 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _H_LG_COMMON_IVSHMEM_DMABUF_
#define _H_LG_COMMON_IVSHMEM_DMABUF_
#include "ivshmem.h"
#include <stdint.h>
bool ivshmemHasDMA (struct IVSHMEM * dev);
int ivshmemGetDMABuf(struct IVSHMEM * dev, uint64_t offset, uint64_t size);
#endif

View File

@@ -18,7 +18,7 @@
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "common/ivshmem.h"
#include "common/ivshmem_dmabuf.h"
#include <dirent.h>
#include <fcntl.h>
@@ -39,9 +39,9 @@
struct IVSHMEMInfo
{
int devFd;
int size;
bool hasDMA;
int devFd;
size_t size;
bool hasDMA;
};
static bool ivshmemDeviceValidator(struct Option * opt, const char ** error)
@@ -115,23 +115,34 @@ void ivshmemOptionsInit(void)
bool ivshmemInit(struct IVSHMEM * dev)
{
// FIXME: split code from ivshmemOpen
DEBUG_ASSERT(dev && !dev->opaque);
dev->size = 0;
dev->mem = NULL;
dev->opaque = NULL;
return true;
}
bool ivshmemOpen(struct IVSHMEM * dev)
bool ivshmemOpen(struct IVSHMEM * dev, IVSHMEMCacheMode cacheMode)
{
return ivshmemOpenDev(dev, option_get_string("app", "shmFile"));
return ivshmemOpenDev(dev, option_get_string("app", "shmFile"),
cacheMode);
}
bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDevice)
bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDevice,
IVSHMEMCacheMode cacheMode)
{
DEBUG_ASSERT(dev);
DEBUG_ASSERT(dev && !dev->opaque && !dev->mem);
int devSize;
int devFd;
bool hasDMA;
size_t devSize;
int devFd;
bool hasDMA;
/* POSIX mmap does not expose the Windows IVSHMEM driver's cache modes. */
(void)cacheMode;
dev->size = 0;
dev->mem = NULL;
dev->opaque = NULL;
DEBUG_INFO("KVMFR Device : %s", shmDevice);
@@ -147,14 +158,15 @@ bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDevice)
}
// get the device size
devSize = ioctl(devFd, KVMFR_DMABUF_GETSIZE, 0);
if (devSize < 0)
const int size = ioctl(devFd, KVMFR_DMABUF_GETSIZE, 0);
if (size <= 0)
{
DEBUG_ERROR("Failed to get the device size");
close(devFd);
return false;
}
hasDMA = true;
devSize = (size_t)size;
hasDMA = true;
}
else
{
@@ -175,7 +187,14 @@ bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDevice)
return false;
}
devSize = st.st_size;
if (st.st_size <= 0 || (uintmax_t)st.st_size > SIZE_MAX)
{
DEBUG_ERROR("Invalid shared memory size: %s", shmDevice);
close(devFd);
return false;
}
devSize = (size_t)st.st_size;
hasDMA = false;
}
@@ -189,6 +208,14 @@ bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDevice)
}
struct IVSHMEMInfo * info = malloc(sizeof(*info));
if (!info)
{
DEBUG_ERROR("Failed to allocate IVSHMEM device state");
munmap(map, devSize);
close(devFd);
return false;
}
info->size = devSize;
info->devFd = devFd;
info->hasDMA = hasDMA;
@@ -220,7 +247,10 @@ void ivshmemClose(struct IVSHMEM * dev)
void ivshmemFree(struct IVSHMEM * dev)
{
// FIXME: split code from ivshmemClose
DEBUG_ASSERT(dev);
if (dev->opaque)
ivshmemClose(dev);
}
bool ivshmemHasDMA(struct IVSHMEM * dev)
@@ -237,7 +267,7 @@ int ivshmemGetDMABuf(struct IVSHMEM * dev, uint64_t offset, uint64_t size)
{
DEBUG_ASSERT(ivshmemHasDMA(dev));
DEBUG_ASSERT(dev && dev->opaque);
DEBUG_ASSERT(offset + size <= dev->size);
DEBUG_ASSERT(offset <= dev->size && size <= dev->size - offset);
static long pageSize = 0;

View File

@@ -27,7 +27,8 @@
#include "ivshmem.h"
#include <setupapi.h>
#include <io.h>
#include <stdlib.h>
#include <string.h>
struct IVSHMEMInfo
{
@@ -70,24 +71,57 @@ static int ivshmemComparator(const void * a_, const void * b_)
return 0;
}
static bool ivshmemCacheModeToDriver(IVSHMEMCacheMode cacheMode,
UINT8 * driverMode)
{
switch (cacheMode)
{
case IVSHMEM_CACHE_MODE_NONCACHED:
*driverMode = IVSHMEM_CACHE_NONCACHED;
return true;
case IVSHMEM_CACHE_MODE_CACHED:
*driverMode = IVSHMEM_CACHE_CACHED;
return true;
case IVSHMEM_CACHE_MODE_WRITECOMBINED:
*driverMode = IVSHMEM_CACHE_WRITECOMBINED;
return true;
}
DEBUG_ERROR("Invalid IVSHMEM cache mode: %d", (int)cacheMode);
return false;
}
bool ivshmemInit(struct IVSHMEM * dev)
{
DEBUG_ASSERT(dev && !dev->opaque);
DEBUG_ASSERT(dev && !dev->opaque && !dev->mem);
HANDLE handle;
HDEVINFO devInfoSet;
PSP_DEVICE_INTERFACE_DETAIL_DATA infData = NULL;
SP_DEVINFO_DATA devInfoData = {0};
dev->size = 0;
dev->mem = NULL;
dev->opaque = NULL;
HANDLE handle = INVALID_HANDLE_VALUE;
HDEVINFO devInfoSet = INVALID_HANDLE_VALUE;
PSP_DEVICE_INTERFACE_DETAIL_DATA infData = NULL;
SP_DEVINFO_DATA devInfoData = {0};
SP_DEVICE_INTERFACE_DATA devInterfaceData = {0};
Vector devices;
devInfoSet = SetupDiGetClassDevs(&GUID_DEVINTERFACE_IVSHMEM, NULL, NULL,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (devInfoSet == INVALID_HANDLE_VALUE)
{
DEBUG_WINERROR("SetupDiGetClassDevs failed", GetLastError());
return false;
}
devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if (!vector_create(&devices, sizeof(struct IVSHMEMData), 1))
{
SetupDiDestroyDeviceInfoList(devInfoSet);
DEBUG_ERROR("Failed to allocate memory");
return false;
}
@@ -95,8 +129,15 @@ bool ivshmemInit(struct IVSHMEM * dev)
for (int i = 0; SetupDiEnumDeviceInfo(devInfoSet, i, &devInfoData); ++i)
{
struct IVSHMEMData * device = vector_push(&devices, NULL);
if (!device)
{
vector_destroy(&devices);
SetupDiDestroyDeviceInfoList(devInfoSet);
return false;
}
DWORD bus, addr;
DWORD bus;
DWORD addr;
if (!SetupDiGetDeviceRegistryProperty(devInfoSet, &devInfoData,
SPDRP_BUSNUMBER, NULL, (void*) &bus, sizeof(bus), NULL))
{
@@ -117,16 +158,19 @@ bool ivshmemInit(struct IVSHMEM * dev)
memcpy(&device->devInfoData, &devInfoData, sizeof(SP_DEVINFO_DATA));
}
if (GetLastError() != ERROR_NO_MORE_ITEMS)
const DWORD enumError = GetLastError();
if (enumError != ERROR_NO_MORE_ITEMS)
{
vector_destroy(&devices);
DEBUG_WINERROR("SetupDiEnumDeviceInfo failed", GetLastError());
SetupDiDestroyDeviceInfoList(devInfoSet);
DEBUG_WINERROR("SetupDiEnumDeviceInfo failed", enumError);
return false;
}
if (vector_size(&devices) == 0)
{
vector_destroy(&devices);
SetupDiDestroyDeviceInfoList(devInfoSet);
DEBUG_ERROR("Failed to find any IVSHMEM devices, unable to continue");
DEBUG_ERROR("Did you remember to add the device to your VM "
"and is the driver installed?");
@@ -145,15 +189,16 @@ bool ivshmemInit(struct IVSHMEM * dev)
DEBUG_INFO(
"IVSHMEM %" PRIuPTR "%c on bus 0x%lx, device 0x%lx, function 0x%lx",
i,
i == shmDevice ? '*' : ' ',
i == (uintptr_t)shmDevice ? '*' : ' ',
bus,
addr >> 16,
addr & 0xFFFF);
}
if (shmDevice >= vector_size(&devices))
if (shmDevice < 0 || (uintptr_t)shmDevice >= vector_size(&devices))
{
vector_destroy(&devices);
SetupDiDestroyDeviceInfoList(devInfoSet);
DEBUG_ERROR("os:shmDevice %d does not exist", shmDevice);
return false;
}
@@ -165,7 +210,9 @@ bool ivshmemInit(struct IVSHMEM * dev)
if (SetupDiEnumDeviceInterfaces(devInfoSet, &devInfoData,
&GUID_DEVINTERFACE_IVSHMEM, 0, &devInterfaceData) == FALSE)
{
DEBUG_WINERROR("SetupDiEnumDeviceInterfaces failed", GetLastError());
const DWORD error = GetLastError();
SetupDiDestroyDeviceInfoList(devInfoSet);
DEBUG_WINERROR("SetupDiEnumDeviceInterfaces failed", error);
return false;
}
@@ -174,25 +221,37 @@ bool ivshmemInit(struct IVSHMEM * dev)
NULL, 0, &reqSize, NULL);
if (!reqSize)
{
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
const DWORD error = GetLastError();
SetupDiDestroyDeviceInfoList(devInfoSet);
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", error);
return false;
}
infData = calloc(1, reqSize);
if (!infData)
{
SetupDiDestroyDeviceInfoList(devInfoSet);
DEBUG_ERROR("Failed to allocate IVSHMEM interface data");
return false;
}
infData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(devInfoSet, &devInterfaceData, infData,
reqSize, NULL, NULL))
{
const DWORD error = GetLastError();
free(infData);
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
SetupDiDestroyDeviceInfoList(devInfoSet);
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", error);
return false;
}
handle = CreateFile(infData->DevicePath, 0, 0, NULL, OPEN_EXISTING, 0, 0);
if (handle == INVALID_HANDLE_VALUE)
{
const DWORD error = GetLastError();
free(infData);
SetupDiDestroyDeviceInfoList(devInfoSet);
DEBUG_WINERROR("CreateFile returned INVALID_HANDLE_VALUE", GetLastError());
DEBUG_WINERROR("CreateFile returned INVALID_HANDLE_VALUE", error);
return false;
}
@@ -200,6 +259,12 @@ bool ivshmemInit(struct IVSHMEM * dev)
SetupDiDestroyDeviceInfoList(devInfoSet);
struct IVSHMEMInfo * info = malloc(sizeof(*info));
if (!info)
{
CloseHandle(handle);
DEBUG_ERROR("Failed to allocate IVSHMEM device state");
return false;
}
info->handle = handle;
dev->opaque = info;
@@ -209,12 +274,16 @@ bool ivshmemInit(struct IVSHMEM * dev)
return true;
}
bool ivshmemOpen(struct IVSHMEM * dev)
bool ivshmemOpen(struct IVSHMEM * dev, IVSHMEMCacheMode cacheMode)
{
DEBUG_ASSERT(dev && dev->opaque && !dev->mem);
struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque;
UINT8 driverMode;
if (!ivshmemCacheModeToDriver(cacheMode, &driverMode))
return false;
IVSHMEM_SIZE size;
if (!DeviceIoControl(info->handle, IOCTL_IVSHMEM_REQUEST_SIZE, NULL, 0, &size,
sizeof(IVSHMEM_SIZE), NULL, NULL))
@@ -223,8 +292,15 @@ bool ivshmemOpen(struct IVSHMEM * dev)
return false;
}
IVSHMEM_MMAP_CONFIG config = { .cacheMode = IVSHMEM_CACHE_WRITECOMBINED };
IVSHMEM_MMAP map = { 0 };
const size_t mapSize = (size_t)size;
if (!mapSize || (IVSHMEM_SIZE)mapSize != size)
{
DEBUG_ERROR("Invalid IVSHMEM size: %" PRIu64, (uint64_t)size);
return false;
}
IVSHMEM_MMAP_CONFIG config = { .cacheMode = driverMode };
IVSHMEM_MMAP map = { 0 };
if (!DeviceIoControl(
info->handle,
IOCTL_IVSHMEM_REQUEST_MMAP,
@@ -236,14 +312,36 @@ bool ivshmemOpen(struct IVSHMEM * dev)
return false;
}
dev->size = (unsigned int)size;
dev->mem = map.ptr;
if (!map.ptr || map.size != size)
{
DEBUG_ERROR("IVSHMEM driver returned an invalid mapping");
DeviceIoControl(info->handle, IOCTL_IVSHMEM_RELEASE_MMAP, NULL, 0, NULL,
0, NULL, NULL);
return false;
}
dev->size = mapSize;
dev->mem = map.ptr;
return true;
}
bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDev,
IVSHMEMCacheMode cacheMode)
{
DEBUG_ASSERT(dev);
(void)shmDev;
(void)cacheMode;
DEBUG_ERROR("Opening an IVSHMEM device by path is not supported on Windows");
return false;
}
void ivshmemClose(struct IVSHMEM * dev)
{
DEBUG_ASSERT(dev && dev->opaque && dev->mem);
DEBUG_ASSERT(dev);
if (!dev->opaque || !dev->mem)
return;
struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque;
@@ -257,10 +355,17 @@ void ivshmemClose(struct IVSHMEM * dev)
void ivshmemFree(struct IVSHMEM * dev)
{
DEBUG_ASSERT(dev && dev->opaque && !dev->mem);
DEBUG_ASSERT(dev);
if (!dev->opaque)
return;
if (dev->mem)
ivshmemClose(dev);
struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque;
CloseHandle(info->handle);
free(info);
dev->opaque = NULL;
}