From 438548c42719ad05b7e9e3ac747da9abdcfd2b5d Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 30 Oct 2019 17:28:13 +1100 Subject: [PATCH] [porthole] initial implementation of the porthole device interface This is known as 'introspection' in the gnif/qemu repo, it's name is not final, however porthole is more appropriate but also may not be the final name. Note: This branch is experiemental and may never be released if QEMU do not accept the patch for the new device upstream. --- VERSION | 2 +- porthole/CMakeLists.txt | 25 +++++ porthole/include/porthole/device.h | 85 +++++++++++++++ porthole/src/linux/CMakeLists.txt | 13 +++ porthole/src/msg.h | 54 ++++++++++ porthole/src/porthole.c | 18 ++++ porthole/src/windows/CMakeLists.txt | 16 +++ porthole/src/windows/device.c | 158 ++++++++++++++++++++++++++++ porthole/src/windows/driver.h | 24 +++++ 9 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 porthole/CMakeLists.txt create mode 100644 porthole/include/porthole/device.h create mode 100644 porthole/src/linux/CMakeLists.txt create mode 100644 porthole/src/msg.h create mode 100644 porthole/src/porthole.c create mode 100644 porthole/src/windows/CMakeLists.txt create mode 100644 porthole/src/windows/device.c create mode 100644 porthole/src/windows/driver.h diff --git a/VERSION b/VERSION index 211faa13..5219e617 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -B1-16-g7d6e061ade+1 \ No newline at end of file +B1-17-g0e7e918e2c+1 \ No newline at end of file diff --git a/porthole/CMakeLists.txt b/porthole/CMakeLists.txt new file mode 100644 index 00000000..56a2af3b --- /dev/null +++ b/porthole/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.0) +project(porthole LANGUAGES C) + +include_directories( + ${PROJECT_SOURCE_DIR}/include +) + +add_library(porthole STATIC + src/porthole.c +) + +if(WIN32) + add_subdirectory(src/windows) + target_link_libraries(porthole PRIVATE porthole-windows) +else() + add_subdirectory(src/linux) + target_link_libraries(porthole PRIVATE porthole-linux) +endif() + +target_include_directories(porthole + INTERFACE + include + PRIVATE + src +) diff --git a/porthole/include/porthole/device.h b/porthole/include/porthole/device.h new file mode 100644 index 00000000..6d5c61fd --- /dev/null +++ b/porthole/include/porthole/device.h @@ -0,0 +1,85 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2017-2019 Geoffrey McRae +https://looking-glass.hostfission.com + +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 +#include + +typedef struct PortholeDev *PortholeDev; + +/** + * Open the porthole device + * + * @param handle The returned handle if successful, otherwise undefined + * @param vendor_id The subsystem vendor and device id to match + * @returns true on success + * + * If successful the handle must be closed to free resources when finished. + */ +bool porthole_dev_open(PortholeDev *handle, const uint32_t vendor_id); + +/** + * Close the porthole devce + * + * @param handle The porthole device handle obtained from porthole_dev_open + * + * handle will be set to NULL and is no longer valid after calling this function. + */ +void porthole_dev_close(PortholeDev *handle); + +/** + * Share the provided buffer over the porthole device + * + * @param handle The porthole device + * @param type The type + * @param buffer The buffer to share + * @param size The size of the buffer + * @returns true on success + * + * This function locks the supplied buffer in RAM via the porthole device + * driver and is then shared with the device for use outside the guest. + * + * The type parameter is application defined and is sent along with the buffer + * to the client application for buffer type identification. + * + * If successful the byffer must be unlocked with `porthole_dev_unlock` before + * the buffer can be freed. + * + * This is an expensive operation, the idea is that you allocate fixed buffers + * and share them with the host at initialization. + * + * @note the driver is hard limited to 32 shares. + */ +bool porthole_dev_share(PortholeDev handle, const uint32_t type, void *buffer, size_t size); + +/** + * Unlock a previously shared buffer + * + * @param handle The porthole device + * @param buffer The buffer to unlock + * @param size The size of the buffer + * @returns true on success + * + * Unlocks a previously shared buffer. Once this has been done the buffer can + * be freed or re-used. The client application should no longer attempt to + * access this buffer as it may be paged out of RAM. + * + * Note that this is not strictly required as closing the device will cause + * the driver to cleanup any prior locked buffers. + */ +bool porthole_dev_unlock(PortholeDev handle, void *buffer, size_t size); \ No newline at end of file diff --git a/porthole/src/linux/CMakeLists.txt b/porthole/src/linux/CMakeLists.txt new file mode 100644 index 00000000..c9fb44db --- /dev/null +++ b/porthole/src/linux/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.0) +project(porthole-linux LANGUAGES C) + +add_library(porthole-linux STATIC + device.c +) + +#target_link_libraries(porthole-linux) + +target_include_directories(porthole-linux + PRIVATE + src +) diff --git a/porthole/src/msg.h b/porthole/src/msg.h new file mode 100644 index 00000000..824371e9 --- /dev/null +++ b/porthole/src/msg.h @@ -0,0 +1,54 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2017-2019 Geoffrey McRae +https://looking-glass.hostfission.com + +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 + +typedef struct { + uint64_t id; +} __attribute__ ((packed)) MsgFd; + +typedef struct { + uint64_t fd_id; + uint64_t addr; + uint32_t size; +} __attribute__ ((packed)) MsgSegment; + +typedef struct { + uint32_t type; +} __attribute__ ((packed)) MsgFinish; + +typedef struct { + uint32_t msg; + union + { + MsgFd fd; + MsgSegment segment; + MsgFinish finish; + } u; +} __attribute__ ((packed)) Msg; + +#define INTRO_MSG_RESET 0x1 +#define INTRO_MSG_FD 0x2 +#define INTRO_MSG_SEGMENT 0x3 +#define INTRO_MSG_FINISH 0x4 + +#define INTRO_MSG_RESET_SIZE (sizeof(uint32_t)) +#define INTRO_MSG_FD_SIZE (sizeof(uint32_t) + sizeof(MsgFd)) +#define INTRO_MSG_SEGMENT_SIZE (sizeof(uint32_t) + sizeof(MsgSegment)) +#define INTRO_MSG_FINISH_SIZE (sizeof(uint32_t) + sizeof(MsgFinish)) \ No newline at end of file diff --git a/porthole/src/porthole.c b/porthole/src/porthole.c new file mode 100644 index 00000000..4af9b1bf --- /dev/null +++ b/porthole/src/porthole.c @@ -0,0 +1,18 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2017-2019 Geoffrey McRae +https://looking-glass.hostfission.com + +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 +*/ diff --git a/porthole/src/windows/CMakeLists.txt b/porthole/src/windows/CMakeLists.txt new file mode 100644 index 00000000..c49c4ea6 --- /dev/null +++ b/porthole/src/windows/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.0) +project(porthole-windows LANGUAGES C) + +add_library(porthole-windows STATIC + device.c +) + +target_link_libraries(porthole-windows + setupapi + lg_common +) + +target_include_directories(porthole-windows + PRIVATE + src +) diff --git a/porthole/src/windows/device.c b/porthole/src/windows/device.c new file mode 100644 index 00000000..57375569 --- /dev/null +++ b/porthole/src/windows/device.c @@ -0,0 +1,158 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2017-2019 Geoffrey McRae +https://looking-glass.hostfission.com + +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 "porthole/device.h" +#include "driver.h" + +#include "common/debug.h" + +#include +#include + +struct PortholeDev +{ + HANDLE dev; +}; + +bool porthole_dev_open(PortholeDev *handle, const uint32_t vendor_id) +{ + HDEVINFO devInfo = {0}; + PSP_DEVICE_INTERFACE_DETAIL_DATA infData = NULL; + SP_DEVICE_INTERFACE_DATA devInfData = {0}; + HANDLE dev; + + if (!handle) + { + DEBUG_ERROR("Invalid buffer provided"); + return false; + } + + devInfo = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_DEVICEINTERFACE); + devInfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + for(int devIndex = 0; ; ++devIndex) + { + if (SetupDiEnumDeviceInterfaces(devInfo, NULL, &GUID_DEVINTERFACE_PORTHOLE, devIndex, &devInfData) == FALSE) + { + DWORD error = GetLastError(); + if (error == ERROR_NO_MORE_ITEMS) + { + DEBUG_ERROR("Unable to enumerate the device, is it attached?"); + SetupDiDestroyDeviceInfoList(devInfo); + return false; + } + } + + DWORD reqSize = 0; + SetupDiGetDeviceInterfaceDetail(devInfo, &devInfData, NULL, 0, &reqSize, NULL); + if (!reqSize) + { + DEBUG_WARN("SetupDiGetDeviceInterfaceDetail for %lu failed\n", reqSize); + continue; + } + + infData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)calloc(reqSize, 1); + infData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + if (!SetupDiGetDeviceInterfaceDetail(devInfo, &devInfData, infData, reqSize, NULL, NULL)) + { + free(infData); + DEBUG_WARN("SetupDiGetDeviceInterfaceDetail for %lu failed\n", reqSize); + continue; + } + + /* get the subsys id from the device */ + unsigned int vendorID, deviceID, subsysID; + if (sscanf(infData->DevicePath, "\\\\?\\pci#ven_%4x&dev_%4x&subsys_%8x", &vendorID, &deviceID, &subsysID) != 3) + { + free(infData); + DEBUG_ERROR("Failed to parse: %s", infData->DevicePath); + continue; + } + + if (subsysID != vendor_id) + { + DEBUG_INFO("Skipping device %d, vendor_id 0x%x != 0x%x", devIndex, subsysID, vendor_id); + free(infData); + continue; + } + + dev = CreateFile(infData->DevicePath, 0, 0, NULL, OPEN_EXISTING, 0, 0); + if (dev == INVALID_HANDLE_VALUE) + { + DEBUG_ERROR("Failed to open device %d", devIndex); + free(infData); + continue; + } + + DEBUG_INFO("Device found"); + + free(infData); + break; + } + + *handle = (PortholeDev)calloc(sizeof(struct PortholeDev), 1); + if (!*handle) + { + DEBUG_ERROR("Failed to allocate PortholeDev struct, out of memory!"); + CloseHandle(dev); + return false; + } + + (*handle)->dev = dev; + + return true; +} + +void porthole_dev_close(PortholeDev *handle) +{ + CloseHandle((*handle)->dev); + free(*handle); + *handle = NULL; +} + +bool porthole_dev_share(PortholeDev handle, const uint32_t type, void *buffer, size_t size) +{ + DWORD returned; + + PortholeMsg msg = { + .type = type, + .addr = buffer, + .size = size + }; + + if (!DeviceIoControl(handle->dev, IOCTL_PORTHOLE_SEND_MSG, &msg, sizeof(PortholeMsg), NULL, 0, &returned, NULL)) + return false; + + return true; +} + +bool porthole_dev_unlock(PortholeDev handle, void *buffer, size_t size) +{ + DWORD returned; + + PortholeLockMsg msg = { + .addr = buffer, + .size = size + }; + + if (!DeviceIoControl(handle->dev, IOCTL_PORTHOLE_UNLOCK_BUFFER, &msg , sizeof(PortholeLockMsg), NULL, 0, &returned, NULL)) + return false; + + return true; +} \ No newline at end of file diff --git a/porthole/src/windows/driver.h b/porthole/src/windows/driver.h new file mode 100644 index 00000000..95bf6434 --- /dev/null +++ b/porthole/src/windows/driver.h @@ -0,0 +1,24 @@ +#include +#include + +DEFINE_GUID (GUID_DEVINTERFACE_PORTHOLE, + 0x10ccc0ac,0xf4b0,0x4d78,0xba,0x41,0x1e,0xbb,0x38,0x5a,0x52,0x85); +// {10ccc0ac-f4b0-4d78-ba41-1ebb385a5285} + +typedef struct _PortholeMsg +{ + UINT32 type; + PVOID addr; + UINT32 size; +} +PortholeMsg, *PPortholeMsg; + +typedef struct _PortholeLockMsg +{ + PVOID addr; + UINT32 size; +} +PortholeLockMsg, *PPortholeLockMsg; + +#define IOCTL_PORTHOLE_SEND_MSG CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_PORTHOLE_UNLOCK_BUFFER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) \ No newline at end of file