/** * 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 */ #include "common/ivshmem_dmabuf.h" #include #include #include #include #include #include #include #include #include #include "common/array.h" #include "common/debug.h" #include "common/option.h" #include "common/sysinfo.h" #include "common/stringutils.h" #include "module/kvmfr.h" struct IVSHMEMInfo { int devFd; size_t size; bool hasDMA; }; static bool ivshmemDeviceValidator(struct Option * opt, const char ** error) { // if it's not a kvmfr device, it must be a file on disk if (strlen(opt->value.x_string) > 3 && memcmp(opt->value.x_string, "kvmfr", 5) != 0) { struct stat st; if (stat(opt->value.x_string, &st) != 0) { *error = "Invalid path to the ivshmem file specified"; return false; } return true; } return true; } static StringList ivshmemDeviceGetValues(struct Option * option) { StringList sl = stringlist_new(true); DIR * d = opendir("/sys/class/kvmfr"); if (!d) return sl; struct dirent * dir; while((dir = readdir(d)) != NULL) { if (dir->d_name[0] == '.') continue; char * devName; alloc_sprintf(&devName, "/dev/%s", dir->d_name); stringlist_push(sl, devName); } closedir(d); return sl; } void ivshmemOptionsInit(void) { char * shmFile; struct stat st; // if there is a kvmfr device, default to using it instead if (stat("/dev/kvmfr0", &st) == 0) shmFile = "/dev/kvmfr0"; else shmFile = "/dev/shm/looking-glass"; struct Option options[] = { { .module = "app", .name = "shmFile", .shortopt = 'f', .description = "The path to the shared memory file, or the name of the kvmfr device to use, e.g. kvmfr0", .type = OPTION_TYPE_STRING, .value.x_string = shmFile, .validator = ivshmemDeviceValidator, .getValues = ivshmemDeviceGetValues }, {0} }; option_register(options); } bool ivshmemInit(struct IVSHMEM * dev) { DEBUG_ASSERT(dev && !dev->opaque); dev->size = 0; dev->mem = NULL; dev->opaque = NULL; return true; } bool ivshmemOpen(struct IVSHMEM * dev, IVSHMEMCacheMode cacheMode) { return ivshmemOpenDev(dev, option_get_string("app", "shmFile"), cacheMode); } bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDevice, IVSHMEMCacheMode cacheMode) { DEBUG_ASSERT(dev && !dev->opaque && !dev->mem); 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); if (strlen(shmDevice) > 8 && memcmp(shmDevice, "/dev/kvmfr", 10) == 0) { devFd = open(shmDevice, O_RDWR, (mode_t)0600); if (devFd < 0) { DEBUG_ERROR("Failed to open: %s", shmDevice); DEBUG_ERROR("%s", strerror(errno)); return false; } // get the device size const int size = ioctl(devFd, KVMFR_DMABUF_GETSIZE, 0); if (size <= 0) { DEBUG_ERROR("Failed to get the device size"); close(devFd); return false; } devSize = (size_t)size; hasDMA = true; } else { devFd = open(shmDevice, O_RDWR, (mode_t)0600); if (devFd < 0) { DEBUG_ERROR("Failed to open: %s", shmDevice); DEBUG_ERROR("%s", strerror(errno)); return false; } struct stat st; if (fstat(devFd, &st) != 0) { DEBUG_ERROR("Failed to stat: %s", shmDevice); DEBUG_ERROR("%s", strerror(errno)); close(devFd); return false; } 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; } void * map = mmap(0, devSize, PROT_READ | PROT_WRITE, MAP_SHARED, devFd, 0); if (map == MAP_FAILED) { DEBUG_ERROR("Failed to map the shared memory device: %s", shmDevice); DEBUG_ERROR("%s", strerror(errno)); close(devFd); return false; } 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; dev->opaque = info; dev->size = devSize; dev->mem = map; return true; } void ivshmemClose(struct IVSHMEM * dev) { DEBUG_ASSERT(dev); if (!dev->opaque) return; struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque; munmap(dev->mem, info->size); close(info->devFd); free(info); dev->mem = NULL; dev->size = 0; dev->opaque = NULL; } void ivshmemFree(struct IVSHMEM * dev) { DEBUG_ASSERT(dev); if (dev->opaque) ivshmemClose(dev); } bool ivshmemHasDMA(struct IVSHMEM * dev) { DEBUG_ASSERT(dev && dev->opaque); struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque; return info->hasDMA; } int ivshmemGetDMABuf(struct IVSHMEM * dev, uint64_t offset, uint64_t size) { DEBUG_ASSERT(ivshmemHasDMA(dev)); DEBUG_ASSERT(dev && dev->opaque); DEBUG_ASSERT(offset <= dev->size && size <= dev->size - offset); static long pageSize = 0; if (!pageSize) pageSize = sysinfo_getPageSize(); struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque; // align to the page size size = ALIGN_PAD(size, pageSize); const struct kvmfr_dmabuf_create create = { .flags = KVMFR_DMABUF_FLAG_CLOEXEC, .offset = offset, .size = size }; int fd = ioctl(info->devFd, KVMFR_DMABUF_CREATE, &create); if (fd < 0) DEBUG_ERROR("Failed to create the dma buffer"); return fd; }