Compare commits

..

28 Commits

Author SHA1 Message Date
Geoffrey McRae
ce5c36705e [porthole] fix invalid pointer dereference 2019-11-12 16:51:04 +11:00
Geoffrey McRae
7aee4eed11 [porthole] added missing file and adjusted linux code 2019-11-12 15:55:13 +11:00
Geoffrey McRae
416c4bbb48 [porthole] added missing include 2019-11-12 15:50:12 +11:00
Geoffrey McRae
76007092d5 [porthole] make the segments private and add a method to obtain the ptr 2019-11-12 15:43:44 +11:00
Geoffrey McRae
453b8e6a4d [porthole] added connection state support 2019-11-12 15:18:53 +11:00
Geoffrey McRae
968b313993 [porthole] updated windows driver defines 2019-11-12 13:08:37 +11:00
Geoffrey McRae
4dde82c646 [porthole] process all messages sent from qemu 2019-11-05 14:56:07 +11:00
Geoffrey McRae
4c424cdbdf [porthole] fix unmap control flow 2019-11-05 01:30:33 +11:00
Geoffrey McRae
963e7f8393 [porthole] prevent double include of headers 2019-11-05 01:09:11 +11:00
Geoffrey McRae
f93c918aa5 [porthole] added memory copy utility functions 2019-11-05 00:50:39 +11:00
Geoffrey McRae
d5f409b02e [common] add include for abort to debug.h 2019-11-05 00:20:05 +11:00
Geoffrey McRae
75cea21cfc [porthole] added receive timeout to allow for clean shutdown 2019-11-04 23:24:34 +11:00
Geoffrey McRae
df2a3b6151 [porthole] bug fixes 2019-11-04 23:07:26 +11:00
Geoffrey McRae
fad4d18973 [porthole] added missing header 2019-11-04 22:42:17 +11:00
Geoffrey McRae
f4ad730cc4 [arbiter] initial arbiter program for porthole communications 2019-11-04 22:39:27 +11:00
Geoffrey McRae
67ddb70932 [porthole] link pthreads and fix function type 2019-11-04 22:25:19 +11:00
Geoffrey McRae
27c3a93d15 [porthole] added unmap logic and response 2019-11-04 22:05:50 +11:00
Geoffrey McRae
df9798c819 [common] added objectlist_pop and objectlist_remove methods 2019-11-04 22:05:21 +11:00
Geoffrey McRae
1dfa0ed218 [common] added missing file to the repository 2019-11-04 21:10:21 +11:00
Geoffrey McRae
01f5238a9d [porthole] initial client implementation 2019-11-04 21:09:13 +11:00
Geoffrey McRae
c382a5acb1 [common] objectlists store void* not char* 2019-11-04 21:08:29 +11:00
Geoffrey McRae
5e3a46beb9 [common] add DEBUG_FATAL 2019-11-04 21:08:17 +11:00
Geoffrey McRae
6ed4e23b80 [common] fix objectlist_push type 2019-11-04 17:41:12 +11:00
Geoffrey McRae
0851ae6f14 [common] converted stringlist to a generic objectlist 2019-11-04 16:41:57 +11:00
Geoffrey McRae
caebddce4d [porthole] cosmetics, remove tabs 2019-10-31 23:46:46 +11:00
Geoffrey McRae
01da541815 [porthole] update in accordance with the recent windows driver changes 2019-10-31 23:45:08 +11:00
Geoffrey McRae
9d6bb57eff [porthole] cosmetics: remove tabs 2019-10-30 17:39:27 +11:00
Geoffrey McRae
438548c427 [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.
2019-10-30 17:28:13 +11:00
77 changed files with 3166 additions and 3018 deletions

12
.github/FUNDING.yml vendored
View File

@@ -1,12 +0,0 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: gnif
open_collective: # Replace with a single Open Collective username
ko_fi: lookingglass
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

1
.gitignore vendored
View File

@@ -7,4 +7,3 @@ module/modules.order
*.a
*.o
*.exe
*/build

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "LGMP"]
path = LGMP
url = https://github.com/gnif/LGMP.git

1
LGMP

Submodule LGMP deleted from 26d9f9a59e

View File

@@ -1 +1 @@
B1-83-gb5d91ccc21+1
B1-43-g83047cbc3e+1

View File

@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.0)
project(looking-glass-obs C)
project(looking-glass-arbiter C)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
@@ -15,9 +15,6 @@ if(OPTIMIZE_FOR_NATIVE)
endif()
endif()
option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON)
add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.")
add_compile_options(
"-Wall"
"-Werror"
@@ -25,7 +22,6 @@ add_compile_options(
"-ffast-math"
"-fdata-sections"
"-ffunction-sections"
"-fpic"
"$<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>"
)
@@ -40,7 +36,6 @@ execute_process(
)
add_definitions(-D BUILD_VERSION='"${BUILD_VERSION}"')
add_definitions(-D ATOMIC_LOCKING)
get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
include_directories(
@@ -48,25 +43,20 @@ include_directories(
${CMAKE_BINARY_DIR}/include
)
link_libraries(
${CMAKE_DL_LIBS}
rt
m
)
set(SOURCES
main.c
lg.c
src/main.c
)
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
add_subdirectory("${PROJECT_TOP}/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp")
add_subdirectory("${PROJECT_TOP}/porthole" "${CMAKE_BINARY_DIR}/porthole")
add_library(looking-glass-obs SHARED ${SOURCES})
target_link_libraries(looking-glass-obs
add_executable(looking-glass-arbiter ${SOURCES})
target_link_libraries(looking-glass-arbiter
${EXE_FLAGS}
lg_common
lgmp
porthole
)
install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-arbiter DESTINATION bin/ COMPONENT binary)
feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES)

55
arbiter/src/main.c Normal file
View File

@@ -0,0 +1,55 @@
#include "common/debug.h"
#include "common/option.h"
#include "porthole/client.h"
static struct Option options[] =
{
// app options
{
.module = "host",
.name = "socket",
.description = "The porthole host socket",
.type = OPTION_TYPE_STRING,
.value.x_string = "/var/tmp/porthole",
},
{0}
};
static void map_event(uint32_t type, PortholeMap * map)
{
DEBUG_INFO("map_event: %u, %u, %u", type, map->id, map->size);
}
static void unmap_event(uint32_t id)
{
DEBUG_INFO("unmap_event: %u", id);
}
static void discon_event()
{
DEBUG_INFO("discon_event");
}
int main(int argc, char * argv[])
{
option_register(options);
if (!option_parse(argc, argv))
return -1;
if (!option_validate())
return -1;
PortholeClient phc;
if (!porthole_client_open(
&phc,
option_get_string("host", "socket"),
map_event,
unmap_event,
discon_event))
{
return -1;
}
porthole_client_close(&phc);
return 0;
}

View File

@@ -41,6 +41,7 @@ get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
include_directories(
${PROJECT_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
${PROJECT_TOP}/vendor/ivshmem
${PKGCONFIG_INCLUDE_DIRS}
${GMP_INCLUDE_DIR}
)
@@ -52,8 +53,7 @@ set(SOURCES
src/app.c
)
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
add_subdirectory("${PROJECT_TOP}/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp" )
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
add_subdirectory(platform)
if(WIN32)
@@ -64,7 +64,6 @@ endif()
target_link_libraries(looking-glass-host
lg_common
platform
lgmp
)
set_target_properties(looking-glass-host PROPERTIES LINK_FLAGS "-Wl,--gc-sections")
install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-host DESTINATION bin/ COMPONENT binary)

View File

@@ -2,26 +2,37 @@
## What is this?
The Looking Glass Host application for the Guest Virtual Machine.
This is a rewrite of the host application in pure C using the MinGW toolchain.
## What platforms does this support?
## Why make this?
Currently only Windows is supported however there is some initial support for Linux at this time.
Several reasons:
1. The client is written in C and I would like to unify the project's language
2. The host is currently hard to build using MinGW and is very Windows specific
3. The host is a jumbled mess of code from all the experimentation going on
4. I would eventually like to be able to port this to run on Linux guests
## When will it be ready?
Soon :)
## Will it replace the C++ host?
Yes, but only when it is feature complete.
## Why doesn't this use CMake?
It does now...
~~Because win-builds doesn't distribute it, so to make it easy for everyone to compile we do not require it.~~
## How do I build it?
#### For Windows on Windows
1. download and install msys2 x86_64 from http://www.msys2.org/ following the setup instructions provided
3. execute `pacman -Fy` and then `pacman -Sy git make mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake`
4. run "C:\msys64\mingw64.exe"
5. checkout the project
`git clone https://github.com/gnif/LookingGlass.git`
6. configure the project and build it
```
mkdir LookingGlass/c-host/build
cd LookingGlass/c-host/build
mkdir build
cd build
cmake -G "MSYS Makefiles" ..
make
```

View File

@@ -71,27 +71,21 @@ typedef struct CapturePointer
}
CapturePointer;
typedef bool (*CaptureGetPointerBuffer )(void ** data, uint32_t * size);
typedef void (*CapturePostPointerBuffer)(CapturePointer pointer);
typedef struct CaptureInterface
{
const char * (*getName )();
void (*initOptions )();
bool(*create)(
CaptureGetPointerBuffer getPointerBufferFn,
CapturePostPointerBuffer postPointerBufferFn
);
bool (*init )();
bool (*create )();
bool (*init )(void * pointerShape, const unsigned int pointerSize);
void (*stop )();
bool (*deinit )();
void (*free )();
unsigned int (*getMaxFrameSize)();
CaptureResult (*capture )();
CaptureResult (*waitFrame )(CaptureFrame * frame);
CaptureResult (*getFrame )(FrameBuffer frame);
CaptureResult (*waitFrame )(CaptureFrame * frame );
CaptureResult (*getFrame )(FrameBuffer frame );
CaptureResult (*getPointer)(CapturePointer * pointer);
}
CaptureInterface;

View File

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
@@ -26,4 +26,29 @@ bool app_init();
void app_quit();
// these must be implemented for each OS
const char * os_getExecutable();
const char * os_getExecutable();
unsigned int os_shmemSize();
bool os_shmemMmap(void **ptr);
void os_shmemUnmap();
// os specific thread functions
typedef struct osThreadHandle osThreadHandle;
typedef int (*osThreadFunction)(void * opaque);
bool os_createThread(const char * name, osThreadFunction function, void * opaque, osThreadHandle ** handle);
bool os_joinThread (osThreadHandle * handle, int * resultCode);
// os specific event functions
#define TIMEOUT_INFINITE ((unsigned int)~0)
typedef struct osEventHandle osEventHandle;
osEventHandle * os_createEvent(bool autoReset);
void os_freeEvent (osEventHandle * handle);
bool os_waitEvent (osEventHandle * handle, unsigned int timeout);
bool os_waitEvents (osEventHandle * handles[], int count, bool waitAll, unsigned int timeout);
bool os_signalEvent(osEventHandle * handle);
bool os_resetEvent (osEventHandle * handle);

View File

@@ -20,7 +20,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "interface/capture.h"
#include "interface/platform.h"
#include "common/debug.h"
#include "common/event.h"
#include <string.h>
#include <assert.h>
#include <stdlib.h>
@@ -38,7 +37,7 @@ struct xcb
uint32_t seg;
int shmID;
void * data;
LGEvent * frameEvent;
osEventHandle * frameEvent;
unsigned int width;
unsigned int height;
@@ -68,7 +67,7 @@ static bool xcb_create()
this = (struct xcb *)calloc(sizeof(struct xcb), 1);
this->shmID = -1;
this->data = (void *)-1;
this->frameEvent = lgCreateEvent(true, 20);
this->frameEvent = os_createEvent(true);
if (!this->frameEvent)
{
@@ -85,7 +84,7 @@ static bool xcb_init()
assert(this);
assert(!this->initialized);
lgResetEvent(this->frameEvent);
os_resetEvent(this->frameEvent);
this->xcb = xcb_connect(NULL, NULL);
if (!this->xcb || xcb_connection_has_error(this->xcb))
@@ -159,7 +158,7 @@ static bool xcb_deinit()
static void xcb_free()
{
lgFreeEvent(this->frameEvent);
os_freeEvent(this->frameEvent);
free(this);
this = NULL;
}
@@ -188,29 +187,20 @@ static CaptureResult xcb_capture()
0);
this->hasFrame = true;
lgSignalEvent(this->frameEvent);
os_signalEvent(this->frameEvent);
}
return CAPTURE_RESULT_OK;
}
static CaptureResult xcb_waitFrame(CaptureFrame * frame)
{
lgWaitEvent(this->frameEvent, TIMEOUT_INFINITE);
frame->width = this->width;
frame->height = this->height;
frame->pitch = this->width * 4;
frame->stride = this->width;
frame->format = CAPTURE_FMT_BGRA;
return CAPTURE_RESULT_OK;
}
static CaptureResult xcb_getFrame(FrameBuffer frame)
static CaptureResult xcb_getFrame(CaptureFrame * frame)
{
assert(this);
assert(this->initialized);
assert(frame);
assert(frame->data);
os_waitEvent(this->frameEvent, TIMEOUT_INFINITE);
xcb_shm_get_image_reply_t * img;
img = xcb_shm_get_image_reply(this->xcb, this->imgC, NULL);
@@ -220,7 +210,12 @@ static CaptureResult xcb_getFrame(FrameBuffer frame)
return CAPTURE_RESULT_ERROR;
}
framebuffer_write(frame, this->data, this->width * this->height * 4);
frame->width = this->width;
frame->height = this->height;
frame->pitch = this->width * 4;
frame->stride = this->width;
frame->format = CAPTURE_FMT_BGRA;
memcpy(frame->data, this->data, this->width * this->height * 4);
free(img);
this->hasFrame = false;
@@ -242,7 +237,6 @@ struct CaptureInterface Capture_XCB =
.free = xcb_free,
.getMaxFrameSize = xcb_getMaxFrameSize,
.capture = xcb_capture,
.waitFrame = xcb_waitFrame,
.getFrame = xcb_getFrame,
.getPointer = xcb_getPointer
};

View File

@@ -20,26 +20,38 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "interface/platform.h"
#include "common/debug.h"
#include "common/option.h"
#include "common/thread.h"
#include <assert.h>
#include <getopt.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
struct app
{
const char * executable;
const char * executable;
unsigned int shmSize;
int shmFD;
void * shmMap;
};
struct app app = { 0 };
static struct app app;
int main(int argc, char * argv[])
struct osThreadHandle
{
app.executable = argv[0];
int result = app_main(argc, argv);
return result;
}
const char * name;
osThreadFunction function;
void * opaque;
pthread_t handle;
int resultCode;
};
void sigHandler(int signo)
{
@@ -47,8 +59,177 @@ void sigHandler(int signo)
app_quit();
}
static int uioOpenFile(const char * shmDevice, const char * file)
{
int len = snprintf(NULL, 0, "/sys/class/uio/%s/%s", shmDevice, file);
char * path = malloc(len + 1);
sprintf(path, "/sys/class/uio/%s/%s", shmDevice, file);
int fd = open(path, O_RDONLY);
if (fd < 0)
{
free(path);
return -1;
}
free(path);
return fd;
}
static char * uioGetName(const char * shmDevice)
{
int fd = uioOpenFile(shmDevice, "name");
if (fd < 0)
return NULL;
char * name = malloc(32);
int len = read(fd, name, 31);
if (len <= 0)
{
free(name);
close(fd);
return NULL;
}
name[len] = '\0';
close(fd);
while(len > 0 && name[len-1] == '\n')
{
--len;
name[len] = '\0';
}
return name;
}
static int shmOpenDev(const char * shmDevice)
{
int len = snprintf(NULL, 0, "/dev/%s", shmDevice);
char * path = malloc(len + 1);
sprintf(path, "/dev/%s", shmDevice);
int fd = open(path, O_RDWR, (mode_t)0600);
if (fd < 0)
{
DEBUG_ERROR("Failed to open: %s", path);
DEBUG_ERROR("Did you remmeber to modprobe the kvmfr module?");
free(path);
return -1;
}
free(path);
return fd;
}
static bool shmDeviceValidator(struct Option * opt, const char ** error)
{
char * name = uioGetName(opt->value.x_string);
if (!name)
{
*error = "Failed to get the uio device name";
return false;
}
if (strcmp(name, "KVMFR") != 0)
{
free(name);
*error = "Device is not a KVMFR device";
return false;
}
free(name);
return true;
}
static StringList shmDeviceGetValues(struct Option * option)
{
StringList sl = stringlist_new(true);
DIR * d = opendir("/sys/class/uio");
if (!d)
return sl;
struct dirent * dir;
while((dir = readdir(d)) != NULL)
{
if (dir->d_name[0] == '.')
continue;
char * name = uioGetName(dir->d_name);
if (!name)
continue;
if (strcmp(name, "KVMFR") == 0)
stringlist_push(sl, strdup(dir->d_name));
free(name);
}
closedir(d);
return sl;
}
int main(int argc, char * argv[])
{
app.executable = argv[0];
struct Option options[] =
{
{
.module = "os",
.name = "shmDevice",
.description = "The IVSHMEM device to use",
.type = OPTION_TYPE_STRING,
.value.x_string = "uio0",
.validator = shmDeviceValidator,
.getValues = shmDeviceGetValues
},
{0}
};
option_register(options);
int result = app_main(argc, argv);
os_shmemUnmap();
close(app.shmFD);
return result;
}
bool app_init()
{
const char * shmDevice = option_get_string("os", "shmDevice");
// get the device size
int fd = uioOpenFile(shmDevice, "maps/map0/size");
if (fd < 0)
{
DEBUG_ERROR("Failed to open %s/size", shmDevice);
DEBUG_ERROR("Did you remmeber to modprobe the kvmfr module?");
return false;
}
char size[32];
int len = read(fd, size, sizeof(size) - 1);
if (len <= 0)
{
DEBUG_ERROR("Failed to read the device size");
close(fd);
return false;
}
size[len] = '\0';
close(fd);
app.shmSize = strtoul(size, NULL, 16);
// open the device
app.shmFD = shmOpenDev(shmDevice);
app.shmMap = MAP_FAILED;
if (app.shmFD < 0)
return false;
DEBUG_INFO("KVMFR Device : %s", shmDevice);
signal(SIGINT, sigHandler);
return true;
}
@@ -56,4 +237,217 @@ bool app_init()
const char * os_getExecutable()
{
return app.executable;
}
unsigned int os_shmemSize()
{
return app.shmSize;
}
bool os_shmemMmap(void **ptr)
{
if (app.shmMap == MAP_FAILED)
{
app.shmMap = mmap(0, app.shmSize, PROT_READ | PROT_WRITE, MAP_SHARED, app.shmFD, 0);
if (app.shmMap == MAP_FAILED)
{
const char * shmDevice = option_get_string("os", "shmDevice");
DEBUG_ERROR("Failed to map the shared memory device: %s", shmDevice);
return false;
}
}
*ptr = app.shmMap;
return true;
}
void os_shmemUnmap()
{
if (app.shmMap == MAP_FAILED)
return;
munmap(app.shmMap, app.shmSize);
app.shmMap = MAP_FAILED;
}
static void * threadWrapper(void * opaque)
{
osThreadHandle * handle = (osThreadHandle *)opaque;
handle->resultCode = handle->function(handle->opaque);
return NULL;
}
bool os_createThread(const char * name, osThreadFunction function, void * opaque, osThreadHandle ** handle)
{
*handle = (osThreadHandle*)malloc(sizeof(osThreadHandle));
(*handle)->name = name;
(*handle)->function = function;
(*handle)->opaque = opaque;
if (pthread_create(&(*handle)->handle, NULL, threadWrapper, *handle) != 0)
{
DEBUG_ERROR("pthread_create failed for thread: %s", name);
free(*handle);
*handle = NULL;
return false;
}
return true;
}
bool os_joinThread(osThreadHandle * handle, int * resultCode)
{
if (pthread_join(handle->handle, NULL) != 0)
{
DEBUG_ERROR("pthread_join failed for thread: %s", handle->name);
free(handle);
return false;
}
if (resultCode)
*resultCode = handle->resultCode;
free(handle);
return true;
}
struct osEventHandle
{
pthread_mutex_t mutex;
pthread_cond_t cond;
bool flag;
bool autoReset;
};
osEventHandle * os_createEvent(bool autoReset)
{
osEventHandle * handle = (osEventHandle *)calloc(sizeof(osEventHandle), 1);
if (!handle)
{
DEBUG_ERROR("Failed to allocate memory");
return NULL;
}
if (pthread_mutex_init(&handle->mutex, NULL) != 0)
{
DEBUG_ERROR("Failed to create the mutex");
free(handle);
return NULL;
}
if (pthread_cond_init(&handle->cond, NULL) != 0)
{
pthread_mutex_destroy(&handle->mutex);
free(handle);
return NULL;
}
handle->autoReset = autoReset;
return handle;
}
void os_freeEvent(osEventHandle * handle)
{
assert(handle);
pthread_cond_destroy (&handle->cond );
pthread_mutex_destroy(&handle->mutex);
free(handle);
}
bool os_waitEvent(osEventHandle * handle, unsigned int timeout)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
while(!handle->flag)
{
if (timeout == TIMEOUT_INFINITE)
{
if (pthread_cond_wait(&handle->cond, &handle->mutex) != 0)
{
DEBUG_ERROR("Wait to wait on the condition");
return false;
}
}
else
{
struct timespec ts;
ts.tv_sec = timeout / 1000;
ts.tv_nsec = (timeout % 1000) * 1000000;
switch(pthread_cond_timedwait(&handle->cond, &handle->mutex, &ts))
{
case ETIMEDOUT:
return false;
default:
DEBUG_ERROR("Timed wait failed");
return false;
}
}
}
if (handle->autoReset)
handle->flag = false;
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
return true;
}
bool os_signalEvent(osEventHandle * handle)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
handle->flag = true;
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
if (pthread_cond_signal(&handle->cond) != 0)
{
DEBUG_ERROR("Failed to signal the condition");
return false;
}
return true;
}
bool os_resetEvent(osEventHandle * handle)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
handle->flag = false;
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
return true;
}

View File

@@ -7,6 +7,7 @@ include_directories(
add_library(platform_Windows STATIC
src/platform.c
src/windebug.c
src/mousehook.c
)
@@ -23,6 +24,7 @@ target_link_libraries(platform_Windows
"${PROJECT_BINARY_DIR}/resource.o"
lg_common
capture
setupapi
)
target_include_directories(platform_Windows

View File

@@ -20,10 +20,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "interface/capture.h"
#include "interface/platform.h"
#include "common/debug.h"
#include "common/windebug.h"
#include "common/option.h"
#include "common/locking.h"
#include "common/event.h"
#include "windows/debug.h"
#include <assert.h>
#include <dxgi.h>
@@ -32,8 +30,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "dxgi_extra.h"
#define LOCKED(x) INTERLOCKED_SECTION(this->deviceContextLock, x)
enum TextureState
{
TEXTURE_STATE_UNUSED,
@@ -46,35 +42,41 @@ typedef struct Texture
enum TextureState state;
ID3D11Texture2D * tex;
D3D11_MAPPED_SUBRESOURCE map;
osEventHandle * mapped;
osEventHandle * free;
}
Texture;
typedef struct Pointer
{
unsigned int version;
unsigned int x, y;
unsigned int w, h;
bool visible;
unsigned int pitch;
CaptureFormat format;
}
Pointer;
// locals
struct iface
{
bool initialized;
LARGE_INTEGER perfFreq;
LARGE_INTEGER frameTime;
bool stop;
IDXGIFactory1 * factory;
IDXGIAdapter1 * adapter;
IDXGIOutput * output;
ID3D11Device * device;
ID3D11DeviceContext * deviceContext;
volatile int deviceContextLock;
bool useAcquireLock;
D3D_FEATURE_LEVEL featureLevel;
IDXGIOutputDuplication * dup;
int maxTextures;
Texture * texture;
int texRIndex;
int texWIndex;
volatile int texReady;
bool needsRelease;
CaptureGetPointerBuffer getPointerBufferFn;
CapturePostPointerBuffer postPointerBufferFn;
LGEvent * frameEvent;
osEventHandle * pointerEvent;
unsigned int width;
unsigned int height;
@@ -82,8 +84,14 @@ struct iface
unsigned int stride;
CaptureFormat format;
int lastPointerX, lastPointerY;
bool lastPointerVisible;
// pointer state
Pointer lastPointer;
Pointer pointer;
// pointer shape
void * pointerShape;
unsigned int pointerSize;
unsigned int pointerUsed;
};
static bool dpiDone = false;
@@ -126,20 +134,13 @@ static void dxgi_initOptions()
.type = OPTION_TYPE_INT,
.value.x_int = 3
},
{
.module = "dxgi",
.name = "useAcquireLock",
.description = "Enable locking around `AcquireFrame` (use if freezing, may lower performance)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
},
{0}
};
option_register(options);
}
static bool dxgi_create(CaptureGetPointerBuffer getPointerBufferFn, CapturePostPointerBuffer postPointerBufferFn)
static bool dxgi_create()
{
assert(!this);
this = calloc(sizeof(struct iface), 1);
@@ -149,10 +150,10 @@ static bool dxgi_create(CaptureGetPointerBuffer getPointerBufferFn, CapturePostP
return false;
}
this->frameEvent = lgCreateEvent(true, 17); // 60Hz = 16.7ms
if (!this->frameEvent)
this->pointerEvent = os_createEvent(true);
if (!this->pointerEvent)
{
DEBUG_ERROR("failed to create the frame event");
DEBUG_ERROR("failed to create the pointer event");
free(this);
return false;
}
@@ -161,14 +162,11 @@ static bool dxgi_create(CaptureGetPointerBuffer getPointerBufferFn, CapturePostP
if (this->maxTextures <= 0)
this->maxTextures = 1;
this->useAcquireLock = option_get_bool("dxgi", "useAcquireLock");
this->texture = calloc(sizeof(struct Texture), this->maxTextures);
this->getPointerBufferFn = getPointerBufferFn;
this->postPointerBufferFn = postPointerBufferFn;
this->texture = calloc(sizeof(struct Texture), this->maxTextures);
return true;
}
static bool dxgi_init()
static bool dxgi_init(void * pointerShape, const unsigned int pointerSize)
{
assert(this);
@@ -191,12 +189,13 @@ static bool dxgi_init()
HRESULT status;
DXGI_OUTPUT_DESC outputDesc;
this->stop = false;
this->texRIndex = 0;
this->texWIndex = 0;
this->texReady = 0;
this->pointerShape = pointerShape;
this->pointerSize = pointerSize;
this->pointerUsed = 0;
lgResetEvent(this->frameEvent );
this->stop = false;
this->texRIndex = 0;
this->texWIndex = 0;
status = CreateDXGIFactory1(&IID_IDXGIFactory1, (void **)&this->factory);
if (FAILED(status))
@@ -213,12 +212,7 @@ static bool dxgi_init()
if (optAdapter)
{
DXGI_ADAPTER_DESC1 adapterDesc;
status = IDXGIAdapter1_GetDesc1(this->adapter, &adapterDesc);
if (FAILED(status))
{
DEBUG_WINERROR("Failed to get the device description", status);
goto fail;
}
IDXGIAdapter1_GetDesc1(this->adapter, &adapterDesc);
const size_t s = (wcslen(adapterDesc.Description)+1) * 2;
char * desc = malloc(s);
@@ -280,18 +274,7 @@ static bool dxgi_init()
goto fail;
}
static const D3D_FEATURE_LEVEL win8[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
static const D3D_FEATURE_LEVEL win10[] =
static const D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_12_1,
D3D_FEATURE_LEVEL_12_0,
@@ -304,19 +287,6 @@ static bool dxgi_init()
D3D_FEATURE_LEVEL_9_1
};
const D3D_FEATURE_LEVEL * featureLevels;
unsigned int featureLevelCount;
if (IsWindows8())
{
featureLevels = win8;
featureLevelCount = sizeof(win8) / sizeof(D3D_FEATURE_LEVEL);
}
else
{
featureLevels = win10;
featureLevelCount = sizeof(win10) / sizeof(D3D_FEATURE_LEVEL);
}
IDXGIAdapter * tmp;
status = IDXGIAdapter1_QueryInterface(this->adapter, &IID_IDXGIAdapter, (void **)&tmp);
if (FAILED(status))
@@ -330,12 +300,11 @@ static bool dxgi_init()
D3D_DRIVER_TYPE_UNKNOWN,
NULL,
D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
featureLevels, featureLevelCount,
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
D3D11_SDK_VERSION,
&this->device,
&this->featureLevel,
&this->deviceContext);
this->deviceContextLock = 0;
IDXGIAdapter_Release(tmp);
@@ -358,7 +327,6 @@ static bool dxgi_init()
DEBUG_INFO("Shared Sys Mem : %u MiB" , (unsigned)(adapterDesc.SharedSystemMemory / 1048576));
DEBUG_INFO("Feature Level : 0x%x" , this->featureLevel);
DEBUG_INFO("Capture Size : %u x %u", this->width, this->height);
DEBUG_INFO("AcquireLock : %s" , this->useAcquireLock ? "enabled" : "disabled");
// bump up our priority
{
@@ -374,20 +342,6 @@ static bool dxgi_init()
IDXGIDevice_Release(dxgi);
}
// try to reduce the latency
{
IDXGIDevice1 * dxgi;
status = ID3D11Device_QueryInterface(this->device, &IID_IDXGIDevice1, (void **)&dxgi);
if (FAILED(status))
{
DEBUG_WINERROR("failed to query DXGI interface from device", status);
goto fail;
}
IDXGIDevice1_SetMaximumFrameLatency(dxgi, 1);
IDXGIDevice1_Release(dxgi);
}
IDXGIOutput5 * output5 = NULL;
status = IDXGIOutput_QueryInterface(this->output, &IID_IDXGIOutput5, (void **)&output5);
if (FAILED(status))
@@ -495,6 +449,23 @@ static bool dxgi_init()
DEBUG_WINERROR("Failed to create texture", status);
goto fail;
}
this->texture[i].free = os_createEvent(true);
if (!this->texture[i].free)
{
DEBUG_ERROR("Failed to create the texture free event");
goto fail;
}
// pre-signal the free events to flag as unused
os_signalEvent(this->texture[i].free);
this->texture[i].mapped = os_createEvent(false);
if (!this->texture[i].mapped)
{
DEBUG_ERROR("Failed to create the texture mapped event");
goto fail;
}
}
// map the texture simply to get the pitch and stride
@@ -509,8 +480,6 @@ static bool dxgi_init()
this->stride = mapping.RowPitch / 4;
ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource *)this->texture[0].tex, 0);
QueryPerformanceFrequency(&this->perfFreq) ;
QueryPerformanceCounter (&this->frameTime);
this->initialized = true;
return true;
@@ -522,6 +491,9 @@ fail:
static void dxgi_stop()
{
this->stop = true;
os_signalEvent(this->texture[this->texRIndex].mapped);
os_signalEvent(this->pointerEvent);
}
static bool dxgi_deinit()
@@ -543,6 +515,20 @@ static bool dxgi_deinit()
ID3D11Texture2D_Release(this->texture[i].tex);
this->texture[i].tex = NULL;
}
if (this->texture[i].free)
{
os_signalEvent(this->texture[i].free);
os_freeEvent(this->texture[i].free);
this->texture[i].free = NULL;
}
if (this->texture[i].mapped)
{
os_signalEvent(this->texture[i].mapped);
os_freeEvent(this->texture[i].mapped);
this->texture[i].mapped = NULL;
}
}
if (this->dup)
@@ -599,6 +585,7 @@ static void dxgi_free()
if (this->initialized)
dxgi_deinit();
os_freeEvent(this->pointerEvent);
free(this->texture);
free(this);
@@ -618,26 +605,43 @@ static CaptureResult dxgi_capture()
assert(this);
assert(this->initialized);
Texture * tex;
CaptureResult result;
HRESULT status;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
IDXGIResource * res;
// if the read texture is pending a mapping
for(int i = 0; i < this->maxTextures; ++i)
{
if (this->texture[i].state != TEXTURE_STATE_PENDING_MAP)
continue;
Texture * tex = &this->texture[i];
// try to map the resource, but don't wait for it
status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map);
if (status != DXGI_ERROR_WAS_STILL_DRAWING)
{
if (FAILED(status))
{
DEBUG_WINERROR("Failed to map the texture", status);
IDXGIResource_Release(res);
return CAPTURE_RESULT_ERROR;
}
// successful map, set the state and signal that there is a frame available
tex->state = TEXTURE_STATE_MAPPED;
os_signalEvent(tex->mapped);
}
}
// release the prior frame
result = dxgi_releaseFrame();
if (result != CAPTURE_RESULT_OK)
return result;
if (this->useAcquireLock)
{
LOCKED({
status = IDXGIOutputDuplication_AcquireNextFrame(this->dup, 1, &frameInfo, &res);
});
}
else
status = IDXGIOutputDuplication_AcquireNextFrame(this->dup, 1000, &frameInfo, &res);
status = IDXGIOutputDuplication_AcquireNextFrame(this->dup, 1, &frameInfo, &res);
switch(status)
{
case S_OK:
@@ -658,10 +662,19 @@ static CaptureResult dxgi_capture()
if (frameInfo.LastPresentTime.QuadPart != 0)
{
tex = &this->texture[this->texWIndex];
Texture * tex = &this->texture[this->texWIndex];
// check if the texture is free, if not skip the frame to keep up
if (tex->state == TEXTURE_STATE_UNUSED)
if (!os_waitEvent(tex->free, 0))
{
/*
NOTE: This is only informational for when debugging, skipping frames is
OK as we are likely getting frames faster then the client can render
them (ie, vsync off in a title)
*/
//DEBUG_WARN("Frame skipped");
}
else
{
ID3D11Texture2D * src;
status = IDXGIResource_QueryInterface(res, &IID_ID3D11Texture2D, (void **)&src);
@@ -672,62 +685,56 @@ static CaptureResult dxgi_capture()
return CAPTURE_RESULT_ERROR;
}
LOCKED({
// issue the copy from GPU to CPU RAM and release the src
ID3D11DeviceContext_CopyResource(this->deviceContext,
(ID3D11Resource *)tex->tex, (ID3D11Resource *)src);
});
// if the texture was mapped, unmap it
if (tex->state == TEXTURE_STATE_MAPPED)
{
ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource*)tex->tex, 0);
tex->map.pData = NULL;
}
// issue the copy from GPU to CPU RAM and release the src
ID3D11DeviceContext_CopyResource(this->deviceContext,
(ID3D11Resource *)tex->tex, (ID3D11Resource *)src);
ID3D11Texture2D_Release(src);
// set the state, and signal
// pending map
tex->state = TEXTURE_STATE_PENDING_MAP;
INTERLOCKED_INC(&this->texReady);
lgSignalEvent(this->frameEvent);
// advance the write index
// advance our write pointer
if (++this->texWIndex == this->maxTextures)
this->texWIndex = 0;
// update the last frame time
this->frameTime.QuadPart = frameInfo.LastPresentTime.QuadPart;
}
}
IDXGIResource_Release(res);
// if the pointer has moved or changed state
bool postPointer = false;
CapturePointer pointer = { 0 };
void * pointerShape = NULL;
UINT pointerShapeSize = 0;
bool signalPointer = false;
if (frameInfo.LastMouseUpdateTime.QuadPart)
{
if (
frameInfo.PointerPosition.Position.x != this->lastPointerX ||
frameInfo.PointerPosition.Position.y != this->lastPointerY ||
frameInfo.PointerPosition.Visible != this->lastPointerVisible
frameInfo.PointerPosition.Position.x != this->lastPointer.x ||
frameInfo.PointerPosition.Position.y != this->lastPointer.y ||
frameInfo.PointerPosition.Visible != this->lastPointer.visible
)
{
this->lastPointerX = frameInfo.PointerPosition.Position.x;
this->lastPointerY = frameInfo.PointerPosition.Position.y;
this->lastPointerVisible = frameInfo.PointerPosition.Visible;
postPointer = true;
this->pointer.x = frameInfo.PointerPosition.Position.x;
this->pointer.y = frameInfo.PointerPosition.Position.y;
this->pointer.visible = frameInfo.PointerPosition.Visible;
signalPointer = true;
}
}
// if the pointer shape has changed
if (frameInfo.PointerShapeBufferSize > 0)
{
uint32_t bufferSize;
if(!this->getPointerBufferFn(&pointerShape, &bufferSize))
DEBUG_WARN("Failed to obtain a buffer for the pointer shape");
// update the buffer
if (frameInfo.PointerShapeBufferSize > this->pointerSize)
DEBUG_WARN("The pointer shape is too large to fit in the buffer, ignoring the shape");
else
{
DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo;
LOCKED({status = IDXGIOutputDuplication_GetFramePointerShape(this->dup, bufferSize, pointerShape, &pointerShapeSize, &shapeInfo);});
status = IDXGIOutputDuplication_GetFramePointerShape(this->dup, this->pointerSize, this->pointerShape, &this->pointerUsed, &shapeInfo);
if (FAILED(status))
{
DEBUG_WINERROR("Failed to get the new pointer shape", status);
@@ -736,30 +743,25 @@ static CaptureResult dxgi_capture()
switch(shapeInfo.Type)
{
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : pointer.format = CAPTURE_FMT_COLOR ; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: pointer.format = CAPTURE_FMT_MASKED; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME : pointer.format = CAPTURE_FMT_MONO ; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : this->pointer.format = CAPTURE_FMT_COLOR ; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: this->pointer.format = CAPTURE_FMT_MASKED; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME : this->pointer.format = CAPTURE_FMT_MONO ; break;
default:
DEBUG_ERROR("Unsupported cursor format");
return CAPTURE_RESULT_ERROR;
}
pointer.shapeUpdate = true;
pointer.width = shapeInfo.Width;
pointer.height = shapeInfo.Height;
pointer.pitch = shapeInfo.Pitch;
postPointer = true;
this->pointer.w = shapeInfo.Width;
this->pointer.h = shapeInfo.Height;
this->pointer.pitch = shapeInfo.Pitch;
++this->pointer.version;
signalPointer = true;
}
}
// post back the pointer information
if (postPointer)
{
pointer.x = this->lastPointerX;
pointer.y = this->lastPointerY;
pointer.visible = this->lastPointerVisible;
this->postPointerBufferFn(pointer);
}
// signal about the pointer update
if (signalPointer)
os_signalEvent(this->pointerEvent);
return CAPTURE_RESULT_OK;
}
@@ -769,31 +771,14 @@ static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
assert(this);
assert(this->initialized);
// NOTE: the event may be signaled when there are no frames available
if(this->texReady == 0)
{
if (!lgWaitEvent(this->frameEvent, 1000))
return CAPTURE_RESULT_TIMEOUT;
if (this->texReady == 0)
return CAPTURE_RESULT_TIMEOUT;
}
Texture * tex = &this->texture[this->texRIndex];
// try to map the resource, but don't wait for it
HRESULT status;
LOCKED({status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map);});
if (status == DXGI_ERROR_WAS_STILL_DRAWING)
if (!os_waitEvent(tex->mapped, 1000))
return CAPTURE_RESULT_TIMEOUT;
if (FAILED(status))
{
DEBUG_WINERROR("Failed to map the texture", status);
return CAPTURE_RESULT_ERROR;
}
if (this->stop)
return CAPTURE_RESULT_REINIT;
tex->state = TEXTURE_STATE_MAPPED;
os_resetEvent(tex->mapped);
frame->width = this->width;
frame->height = this->height;
@@ -801,7 +786,6 @@ static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
frame->stride = this->stride;
frame->format = this->format;
INTERLOCKED_DEC(&this->texReady);
return CAPTURE_RESULT_OK;
}
@@ -811,10 +795,8 @@ static CaptureResult dxgi_getFrame(FrameBuffer frame)
assert(this->initialized);
Texture * tex = &this->texture[this->texRIndex];
framebuffer_write(frame, tex->map.pData, this->pitch * this->height);
LOCKED({ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource*)tex->tex, 0);});
tex->state = TEXTURE_STATE_UNUSED;
os_signalEvent(tex->free);
if (++this->texRIndex == this->maxTextures)
this->texRIndex = 0;
@@ -822,14 +804,41 @@ static CaptureResult dxgi_getFrame(FrameBuffer frame)
return CAPTURE_RESULT_OK;
}
static CaptureResult dxgi_getPointer(CapturePointer * pointer)
{
assert(this);
assert(this->initialized);
if (!os_waitEvent(this->pointerEvent, 1000))
return CAPTURE_RESULT_TIMEOUT;
if (this->stop)
return CAPTURE_RESULT_REINIT;
Pointer p;
memcpy(&p, &this->pointer, sizeof(Pointer));
pointer->x = p.x;
pointer->y = p.y;
pointer->width = p.w;
pointer->height = p.h;
pointer->pitch = p.pitch;
pointer->visible = p.visible;
pointer->format = p.format;
pointer->shapeUpdate = p.version > this->lastPointer.version;
memcpy(&this->lastPointer, &p, sizeof(Pointer));
return CAPTURE_RESULT_OK;
}
static CaptureResult dxgi_releaseFrame()
{
assert(this);
if (!this->needsRelease)
return CAPTURE_RESULT_OK;
HRESULT status;
LOCKED({status = IDXGIOutputDuplication_ReleaseFrame(this->dup);});
HRESULT status = IDXGIOutputDuplication_ReleaseFrame(this->dup);
switch(status)
{
case S_OK:
@@ -867,5 +876,6 @@ struct CaptureInterface Capture_DXGI =
.getMaxFrameSize = dxgi_getMaxFrameSize,
.capture = dxgi_capture,
.waitFrame = dxgi_waitFrame,
.getFrame = dxgi_getFrame
.getFrame = dxgi_getFrame,
.getPointer = dxgi_getPointer
};

View File

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
@@ -19,12 +19,11 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "interface/capture.h"
#include "interface/platform.h"
#include "common/windebug.h"
#include "windows/platform.h"
#include "windows/debug.h"
#include "windows/mousehook.h"
#include "common/option.h"
#include "common/framebuffer.h"
#include "common/event.h"
#include "common/thread.h"
#include <assert.h>
#include <stdlib.h>
#include <windows.h>
@@ -37,11 +36,9 @@ struct iface
bool stop;
NvFBCHandle nvfbc;
bool seperateCursor;
CaptureGetPointerBuffer getPointerBufferFn;
CapturePostPointerBuffer postPointerBufferFn;
LGThread * pointerThread;
bool seperateCursor;
void * pointerShape;
unsigned int pointerSize;
unsigned int maxWidth, maxHeight;
unsigned int width , height;
@@ -49,8 +46,8 @@ struct iface
NvFBCFrameGrabInfo grabInfo;
LGEvent * frameEvent;
LGEvent * cursorEvents[2];
osEventHandle * frameEvent;
osEventHandle * cursorEvents[2];
int mouseX, mouseY, mouseHotX, mouseHotY;
bool mouseVisible;
@@ -59,7 +56,6 @@ struct iface
static struct iface * this = NULL;
static void nvfbc_free();
static int pointerThread(void * unused);
static void getDesktopSize(unsigned int * width, unsigned int * height)
{
@@ -79,7 +75,7 @@ static void on_mouseMove(int x, int y)
{
this->mouseX = x;
this->mouseY = y;
lgSignalEvent(this->cursorEvents[0]);
os_signalEvent(this->cursorEvents[0]);
}
static const char * nvfbc_getName()
@@ -104,9 +100,7 @@ static void nvfbc_initOptions()
option_register(options);
}
static bool nvfbc_create(
CaptureGetPointerBuffer getPointerBufferFn,
CapturePostPointerBuffer postPointerBufferFn)
static bool nvfbc_create()
{
if (!NvFBCInit())
return false;
@@ -141,7 +135,7 @@ static bool nvfbc_create(
}
free(privData);
this->frameEvent = lgCreateEvent(true, 17);
this->frameEvent = os_createEvent(true);
if (!this->frameEvent)
{
DEBUG_ERROR("failed to create the frame event");
@@ -149,18 +143,20 @@ static bool nvfbc_create(
return false;
}
this->seperateCursor = option_get_bool("nvfbc", "decoupleCursor");
this->getPointerBufferFn = getPointerBufferFn;
this->postPointerBufferFn = postPointerBufferFn;
this->seperateCursor = option_get_bool("nvfbc", "decoupleCursor");
return true;
}
static bool nvfbc_init()
static bool nvfbc_init(void * pointerShape, const unsigned int pointerSize)
{
this->stop = false;
this->stop = false;
this->pointerShape = pointerShape;
this->pointerSize = pointerSize;
getDesktopSize(&this->width, &this->height);
lgResetEvent(this->frameEvent);
os_resetEvent(this->frameEvent);
HANDLE event;
if (!NvFBCToSysSetup(
@@ -178,48 +174,28 @@ static bool nvfbc_init()
return false;
}
this->cursorEvents[0] = lgCreateEvent(true, 10);
this->cursorEvents[0] = os_createEvent(true);
mouseHook_install(on_mouseMove);
if (this->seperateCursor)
this->cursorEvents[1] = lgWrapEvent(event);
this->cursorEvents[1] = os_wrapEvent(event);
DEBUG_INFO("Cursor mode : %s", this->seperateCursor ? "decoupled" : "integrated");
Sleep(100);
if (!lgCreateThread("NvFBCPointer", pointerThread, NULL, &this->pointerThread))
{
DEBUG_ERROR("Failed to create the NvFBCPointer thread");
return false;
}
return true;
}
static void nvfbc_stop()
{
this->stop = true;
lgSignalEvent(this->cursorEvents[0]);
lgSignalEvent(this->frameEvent);
if (this->pointerThread)
{
lgJoinThread(this->pointerThread, NULL);
this->pointerThread = NULL;
}
os_signalEvent(this->cursorEvents[0]);
os_signalEvent(this->frameEvent);
}
static bool nvfbc_deinit()
{
mouseHook_remove();
if (this->cursorEvents[0])
{
lgFreeEvent(this->cursorEvents[0]);
this->cursorEvents[0] = NULL;
}
return true;
}
@@ -228,7 +204,7 @@ static void nvfbc_free()
NvFBCToSysRelease(&this->nvfbc);
if (this->frameEvent)
lgFreeEvent(this->frameEvent);
os_freeEvent(this->frameEvent);
free(this);
this = NULL;
@@ -257,13 +233,13 @@ static CaptureResult nvfbc_capture()
return result;
memcpy(&this->grabInfo, &grabInfo, sizeof(grabInfo));
lgSignalEvent(this->frameEvent);
os_signalEvent(this->frameEvent);
return CAPTURE_RESULT_OK;
}
static CaptureResult nvfbc_waitFrame(CaptureFrame * frame)
{
if (!lgWaitEvent(this->frameEvent, 1000))
if (!os_waitEvent(this->frameEvent, 1000))
return CAPTURE_RESULT_TIMEOUT;
if (this->stop)
@@ -304,51 +280,32 @@ static CaptureResult nvfbc_getFrame(FrameBuffer frame)
return CAPTURE_RESULT_OK;
}
static int pointerThread(void * unused)
static CaptureResult nvfbc_getPointer(CapturePointer * pointer)
{
while(!this->stop)
osEventHandle * events[2];
memcpy(&events, &this->cursorEvents, sizeof(osEventHandle *) * 2);
if (!os_waitEvents(events, this->seperateCursor ? 2 : 1, false, 1000))
return CAPTURE_RESULT_TIMEOUT;
if (this->stop)
return CAPTURE_RESULT_REINIT;
CaptureResult result;
pointer->shapeUpdate = false;
if (this->seperateCursor && events[1])
{
LGEvent * events[2];
memcpy(&events, &this->cursorEvents, sizeof(LGEvent *) * 2);
if (!lgWaitEvents(events, this->seperateCursor ? 2 : 1, false, 1000))
continue;
if (this->stop)
break;
CaptureResult result;
CapturePointer pointer = { 0 };
pointer.shapeUpdate = false;
if (this->seperateCursor && events[1])
{
void * data;
uint32_t size;
if (!this->getPointerBufferFn(&data, &size))
{
DEBUG_WARN("failed to get a pointer buffer");
continue;
}
result = NvFBCToSysGetCursor(this->nvfbc, &pointer, data, size);
if (result != CAPTURE_RESULT_OK)
{
DEBUG_WARN("NvFBCToSysGetCursor failed");
continue;
}
this->mouseVisible = pointer.visible;
this->mouseHotX = pointer.x;
this->mouseHotY = pointer.y;
}
pointer.visible = this->mouseVisible;
pointer.x = this->mouseX - this->mouseHotX;
pointer.y = this->mouseY - this->mouseHotY;
this->postPointerBufferFn(pointer);
result = NvFBCToSysGetCursor(this->nvfbc, pointer, this->pointerShape, this->pointerSize);
this->mouseVisible = pointer->visible;
this->mouseHotX = pointer->x;
this->mouseHotY = pointer->y;
if (result != CAPTURE_RESULT_OK)
return result;
}
return 0;
pointer->visible = this->mouseVisible;
pointer->x = this->mouseX - this->mouseHotX;
pointer->y = this->mouseY - this->mouseHotY;
return CAPTURE_RESULT_OK;
}
struct CaptureInterface Capture_NVFBC =
@@ -364,5 +321,6 @@ struct CaptureInterface Capture_NVFBC =
.getMaxFrameSize = nvfbc_getMaxFrameSize,
.capture = nvfbc_capture,
.waitFrame = nvfbc_waitFrame,
.getFrame = nvfbc_getFrame
.getFrame = nvfbc_getFrame,
.getPointer = nvfbc_getPointer
};

View File

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
@@ -18,7 +18,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "wrapper.h"
#include "common/windebug.h"
#include "windows/debug.h"
#include <windows.h>
#include <NvFBC/nvFBCToSys.h>

View File

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
@@ -19,9 +19,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#pragma once
#include "debug.h"
#include "common/debug.h"
#include <windows.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
@@ -31,8 +30,6 @@ void DebugWinError(const char * file, const unsigned int line, const char * func
#define DEBUG_WINERROR(x, y) DebugWinError(STRIPPATH(__FILE__), __LINE__, __FUNCTION__, x, y)
bool IsWindows8();
#ifdef __cplusplus
}
#endif

View File

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
@@ -17,29 +17,7 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include <stdint.h>
#if defined(_WIN32)
#include "interface/platform.h"
#include <windows.h>
#else
#include <time.h>
#endif
static inline uint64_t getMicrotime()
{
#if defined(_WIN32)
static LARGE_INTEGER freq = { 0 };
if (!freq.QuadPart)
QueryPerformanceFrequency(&freq);
LARGE_INTEGER time;
QueryPerformanceCounter(&time);
return time.QuadPart / (freq.QuadPart / 1000000LL);
#else
struct timespec time;
clock_gettime(CLOCK_MONOTONIC, &time);
return (uint64_t)time.tv_sec * 1000000LL + time.tv_nsec / 1000LL;
#endif
}
osEventHandle * os_wrapEvent(HANDLE event);

View File

@@ -18,7 +18,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "windows/mousehook.h"
#include "common/windebug.h"
#include "windows/debug.h"
#include "platform.h"
#include <windows.h>

View File

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
@@ -18,40 +18,54 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "platform.h"
#include "windows/platform.h"
#include "windows/mousehook.h"
#include <windows.h>
#include <setupapi.h>
#include <shellapi.h>
#include <fcntl.h>
#include "interface/platform.h"
#include "common/debug.h"
#include "common/windebug.h"
#include "common/option.h"
#include "common/locking.h"
#include "common/thread.h"
#include "windows/debug.h"
#include "ivshmem.h"
#define ID_MENU_OPEN_LOG 3000
#define ID_MENU_EXIT 3001
struct AppState
{
LARGE_INTEGER perfFreq;
HINSTANCE hInst;
int argc;
char ** argv;
char executable[MAX_PATH + 1];
HANDLE shmemHandle;
bool shmemOwned;
IVSHMEM_MMAP shmemMap;
HWND messageWnd;
HMENU trayMenu;
};
static struct AppState app = {0};
static struct AppState app =
{
.shmemHandle = INVALID_HANDLE_VALUE,
.shmemOwned = false,
.shmemMap = {0}
};
// undocumented API to adjust the system timer resolution (yes, its a nasty hack)
typedef NTSTATUS (__stdcall *ZwSetTimerResolution_t)(ULONG RequestedResolution, BOOLEAN Set, PULONG ActualResolution);
static ZwSetTimerResolution_t ZwSetTimerResolution = NULL;
struct osThreadHandle
{
const char * name;
osThreadFunction function;
void * opaque;
HANDLE handle;
DWORD threadID;
int resultCode;
};
LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
@@ -143,21 +157,6 @@ static BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
/* this is a bit of a hack but without this --help will produce no output in a windows command prompt */
if (!IsDebuggerPresent() && AttachConsole(ATTACH_PARENT_PROCESS))
{
HANDLE std_err = GetStdHandle(STD_ERROR_HANDLE);
HANDLE std_out = GetStdHandle(STD_OUTPUT_HANDLE);
int std_err_fd = _open_osfhandle((intptr_t)std_err, _O_TEXT);
int std_out_fd = _open_osfhandle((intptr_t)std_out, _O_TEXT);
if (std_err_fd > 0)
*stderr = *_fdopen(std_err_fd, "w");
if (std_out_fd > 0)
*stdout = *_fdopen(std_out_fd, "w");
}
int result = 0;
app.hInst = hInstance;
@@ -169,6 +168,13 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
struct Option options[] =
{
{
.module = "os",
.name = "shmDevice",
.description = "The IVSHMEM device to use",
.type = OPTION_TYPE_INT,
.value.x_int = 0
},
{
.module = "os",
.name = "logFile",
@@ -222,8 +228,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_EXIT , "Exit" );
// create the application thread
LGThread * thread;
if (!lgCreateThread("appThread", appThread, NULL, &thread))
osThreadHandle * thread;
if (!os_createThread("appThread", appThread, NULL, &thread))
{
DEBUG_ERROR("Failed to create the main application thread");
result = -1;
@@ -253,13 +259,17 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
shutdown:
DestroyMenu(app.trayMenu);
app_quit();
if (!lgJoinThread(thread, &result))
if (!os_joinThread(thread, &result))
{
DEBUG_ERROR("Failed to join the main application thread");
result = -1;
}
finish:
os_shmemUnmap();
if (app.shmemHandle != INVALID_HANDLE_VALUE)
CloseHandle(app.shmemHandle);
for(int i = 0; i < app.argc; ++i)
free(app.argv[i]);
@@ -270,6 +280,7 @@ finish:
bool app_init()
{
const int shmDevice = option_get_int ("os", "shmDevice");
const char * logFile = option_get_string("os", "logFile" );
// redirect stderr to a file
@@ -279,17 +290,55 @@ bool app_init()
// always flush stderr
setbuf(stderr, NULL);
// Increase the timer resolution
ZwSetTimerResolution = (ZwSetTimerResolution_t)GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwSetTimerResolution");
if (ZwSetTimerResolution)
HDEVINFO deviceInfoSet;
PSP_DEVICE_INTERFACE_DETAIL_DATA infData = NULL;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
deviceInfoSet = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_DEVICEINTERFACE);
memset(&deviceInterfaceData, 0, sizeof(SP_DEVICE_INTERFACE_DATA));
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if (SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &GUID_DEVINTERFACE_IVSHMEM, shmDevice, &deviceInterfaceData) == FALSE)
{
ULONG actualResolution;
ZwSetTimerResolution(1, true, &actualResolution);
DEBUG_INFO("System timer resolution: %.2f ns", (float)actualResolution / 100.0f);
DWORD error = GetLastError();
if (error == ERROR_NO_MORE_ITEMS)
{
DEBUG_WINERROR("Unable to enumerate the device, is it attached?", error);
return false;
}
DEBUG_WINERROR("SetupDiEnumDeviceInterfaces failed", error);
return false;
}
// get the performance frequency for spinlocks
QueryPerformanceFrequency(&app.perfFreq);
DWORD reqSize = 0;
SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, NULL, 0, &reqSize, NULL);
if (!reqSize)
{
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
return false;
}
infData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)calloc(reqSize, 1);
infData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, infData, reqSize, NULL, NULL))
{
free(infData);
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
return false;
}
app.shmemHandle = CreateFile(infData->DevicePath, 0, 0, NULL, OPEN_EXISTING, 0, 0);
if (app.shmemHandle == INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(deviceInfoSet);
free(infData);
DEBUG_WINERROR("CreateFile returned INVALID_HANDLE_VALUE", GetLastError());
return false;
}
free(infData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return true;
}
@@ -297,4 +346,211 @@ bool app_init()
const char * os_getExecutable()
{
return app.executable;
}
unsigned int os_shmemSize()
{
IVSHMEM_SIZE size;
if (!DeviceIoControl(app.shmemHandle, IOCTL_IVSHMEM_REQUEST_SIZE, NULL, 0, &size, sizeof(IVSHMEM_SIZE), NULL, NULL))
{
DEBUG_WINERROR("DeviceIoControl Failed", GetLastError());
return 0;
}
return (unsigned int)size;
}
bool os_shmemMmap(void **ptr)
{
if (app.shmemOwned)
{
*ptr = app.shmemMap.ptr;
return true;
}
IVSHMEM_MMAP_CONFIG config =
{
.cacheMode = IVSHMEM_CACHE_WRITECOMBINED
};
memset(&app.shmemMap, 0, sizeof(IVSHMEM_MMAP));
if (!DeviceIoControl(
app.shmemHandle,
IOCTL_IVSHMEM_REQUEST_MMAP,
&config, sizeof(IVSHMEM_MMAP_CONFIG),
&app.shmemMap, sizeof(IVSHMEM_MMAP),
NULL, NULL))
{
DEBUG_WINERROR("DeviceIoControl Failed", GetLastError());
return false;
}
*ptr = app.shmemMap.ptr;
app.shmemOwned = true;
return true;
}
void os_shmemUnmap()
{
if (!app.shmemOwned)
return;
if (!DeviceIoControl(app.shmemHandle, IOCTL_IVSHMEM_RELEASE_MMAP, NULL, 0, NULL, 0, NULL, NULL))
DEBUG_WINERROR("DeviceIoControl failed", GetLastError());
else
app.shmemOwned = false;
}
static DWORD WINAPI threadWrapper(LPVOID lpParameter)
{
osThreadHandle * handle = (osThreadHandle *)lpParameter;
handle->resultCode = handle->function(handle->opaque);
return 0;
}
bool os_createThread(const char * name, osThreadFunction function, void * opaque, osThreadHandle ** handle)
{
*handle = (osThreadHandle *)malloc(sizeof(osThreadHandle));
(*handle)->name = name;
(*handle)->function = function;
(*handle)->opaque = opaque;
(*handle)->handle = CreateThread(NULL, 0, threadWrapper, *handle, 0, &(*handle)->threadID);
if (!(*handle)->handle)
{
free(*handle);
*handle = NULL;
DEBUG_WINERROR("CreateThread failed", GetLastError());
return false;
}
return true;
}
bool os_joinThread(osThreadHandle * handle, int * resultCode)
{
while(true)
{
switch(WaitForSingleObject(handle->handle, INFINITE))
{
case WAIT_OBJECT_0:
if (resultCode)
*resultCode = handle->resultCode;
CloseHandle(handle->handle);
free(handle);
return true;
case WAIT_ABANDONED:
case WAIT_TIMEOUT:
continue;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for thread failed", GetLastError());
CloseHandle(handle->handle);
free(handle);
return false;
}
break;
}
DEBUG_WINERROR("Unknown failure waiting for thread", GetLastError());
return false;
}
osEventHandle * os_createEvent(bool autoReset)
{
HANDLE event = CreateEvent(NULL, autoReset ? FALSE : TRUE, FALSE, NULL);
if (!event)
{
DEBUG_WINERROR("Failed to create the event", GetLastError());
return NULL;
}
return (osEventHandle*)event;
}
osEventHandle * os_wrapEvent(HANDLE event)
{
return (osEventHandle*)event;
}
void os_freeEvent(osEventHandle * handle)
{
CloseHandle((HANDLE)handle);
}
bool os_waitEvent(osEventHandle * handle, unsigned int timeout)
{
const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout;
while(true)
{
switch(WaitForSingleObject((HANDLE)handle, to))
{
case WAIT_OBJECT_0:
return true;
case WAIT_ABANDONED:
continue;
case WAIT_TIMEOUT:
if (timeout == TIMEOUT_INFINITE)
continue;
return false;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for event failed", GetLastError());
return false;
}
DEBUG_ERROR("Unknown wait event return code");
return false;
}
}
bool os_waitEvents(osEventHandle * handles[], int count, bool waitAll, unsigned int timeout)
{
const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout;
while(true)
{
DWORD result = WaitForMultipleObjects(count, (HANDLE*)handles, waitAll, to);
if (result >= WAIT_OBJECT_0 && result < count)
{
// null non signalled events from the handle list
for(int i = 0; i < count; ++i)
if (i != result && !os_waitEvent(handles[i], 0))
handles[i] = NULL;
return true;
}
if (result >= WAIT_ABANDONED_0 && result - WAIT_ABANDONED_0 < count)
continue;
switch(result)
{
case WAIT_TIMEOUT:
if (timeout == TIMEOUT_INFINITE)
continue;
return false;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for event failed", GetLastError());
return false;
}
DEBUG_ERROR("Unknown wait event return code");
return false;
}
}
bool os_signalEvent(osEventHandle * handle)
{
return SetEvent((HANDLE)handle);
}
bool os_resetEvent(osEventHandle * handle)
{
return ResetEvent((HANDLE)handle);
}

View File

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
@@ -17,7 +17,7 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "common/windebug.h"
#include "windows/debug.h"
#include <stdio.h>
void DebugWinError(const char * file, const unsigned int line, const char * function, const char * desc, HRESULT status)
@@ -39,28 +39,4 @@ void DebugWinError(const char * file, const unsigned int line, const char * func
fprintf(stderr, "[E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", file, line, function, desc, (int)status, buffer);
LocalFree(buffer);
}
/* credit for this function to: https://stackoverflow.com/questions/17399302/how-can-i-detect-windows-8-1-in-a-desktop-application */
inline static BOOL CompareWindowsVersion(DWORD dwMajorVersion, DWORD dwMinorVersion)
{
OSVERSIONINFOEX ver;
DWORDLONG dwlConditionMask = 0;
ZeroMemory(&ver, sizeof(OSVERSIONINFOEX));
ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
ver.dwMajorVersion = dwMajorVersion;
ver.dwMinorVersion = dwMinorVersion;
VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_EQUAL);
VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_EQUAL);
return VerifyVersionInfo(&ver, VER_MAJORVERSION | VER_MINORVERSION, dwlConditionMask);
}
bool IsWindows8()
{
return
(CompareWindowsVersion(6, 3) == TRUE) ||
(CompareWindowsVersion(6, 2) == TRUE);
}

View File

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
@@ -25,10 +25,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "common/locking.h"
#include "common/KVMFR.h"
#include "common/crash.h"
#include "common/thread.h"
#include "common/ivshmem.h"
#include <lgmp/host.h>
#include <stdio.h>
#include <inttypes.h>
@@ -38,65 +34,124 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#define ALIGN_DN(x) ((uintptr_t)(x) & ~0x7F)
#define ALIGN_UP(x) ALIGN_DN(x + 0x7F)
#define LGMP_Q_FRAME_LEN 2
#define LGMP_Q_POINTER_LEN 20
static const struct LGMPQueueConfig FRAME_QUEUE_CONFIG =
{
.queueID = LGMP_Q_FRAME,
.numMessages = LGMP_Q_FRAME_LEN,
.subTimeout = 1000
};
static const struct LGMPQueueConfig POINTER_QUEUE_CONFIG =
{
.queueID = LGMP_Q_POINTER,
.numMessages = LGMP_Q_POINTER_LEN,
.subTimeout = 1000
};
#define MAX_POINTER_SIZE (sizeof(KVMFRCursor) + (128 * 128 * 4))
#define MAX_FRAMES 2
struct app
{
PLGMPHost lgmp;
unsigned int clientInstance;
PLGMPHostQueue pointerQueue;
PLGMPMemory pointerMemory[LGMP_Q_POINTER_LEN];
PLGMPMemory pointerShape;
bool pointerShapeValid;
unsigned int pointerIndex;
size_t maxFrameSize;
PLGMPHostQueue frameQueue;
PLGMPMemory frameMemory[LGMP_Q_FRAME_LEN];
unsigned int frameIndex;
KVMFRHeader * shmHeader;
uint8_t * pointerData;
unsigned int pointerDataSize;
unsigned int pointerOffset;
CaptureInterface * iface;
bool running;
bool reinit;
LGThread * lgmpThread;
LGThread * frameThread;
uint8_t * frames;
unsigned int frameSize;
FrameBuffer frame[MAX_FRAMES];
unsigned int frameOffset[MAX_FRAMES];
bool running;
bool reinit;
osThreadHandle * pointerThread;
osThreadHandle * frameThread;
};
static struct app app;
static int lgmpThread(void * opaque)
static int pointerThread(void * opaque)
{
LGMP_STATUS status;
DEBUG_INFO("Pointer thread started");
volatile KVMFRCursor * ci = &(app.shmHeader->cursor);
uint8_t flags;
bool pointerValid = false;
bool shapeValid = false;
unsigned int clientInstance = 0;
CapturePointer pointer = { 0 };
while(app.running)
{
if ((status = lgmpHostProcess(app.lgmp)) != LGMP_OK)
bool resend = false;
pointer.shapeUpdate = false;
switch(app.iface->getPointer(&pointer))
{
DEBUG_ERROR("lgmpHostProcess Failed: %s", lgmpStatusString(status));
break;
case CAPTURE_RESULT_OK:
{
pointerValid = true;
break;
}
case CAPTURE_RESULT_REINIT:
{
app.reinit = true;
DEBUG_INFO("Pointer thread reinit");
return 0;
}
case CAPTURE_RESULT_ERROR:
{
DEBUG_ERROR("Failed to get the pointer");
return 0;
}
case CAPTURE_RESULT_TIMEOUT:
{
// if the pointer is valid and the client has restarted, send it
if (pointerValid && clientInstance != app.clientInstance)
{
resend = true;
break;
}
continue;
}
}
usleep(1000);
clientInstance = app.clientInstance;
// wait for the client to finish with the previous update
while((ci->flags & ~KVMFR_CURSOR_FLAG_UPDATE) != 0 && app.running)
usleep(1000);
flags = KVMFR_CURSOR_FLAG_UPDATE;
ci->x = pointer.x;
ci->y = pointer.y;
flags |= KVMFR_CURSOR_FLAG_POS;
if (pointer.visible)
flags |= KVMFR_CURSOR_FLAG_VISIBLE;
// if we have shape data
if (pointer.shapeUpdate || (shapeValid && resend))
{
switch(pointer.format)
{
case CAPTURE_FMT_COLOR : ci->type = CURSOR_TYPE_COLOR ; break;
case CAPTURE_FMT_MONO : ci->type = CURSOR_TYPE_MONOCHROME ; break;
case CAPTURE_FMT_MASKED: ci->type = CURSOR_TYPE_MASKED_COLOR; break;
default:
DEBUG_ERROR("Invalid pointer format: %d", pointer.format);
continue;
}
ci->width = pointer.width;
ci->height = pointer.height;
ci->pitch = pointer.pitch;
ci->dataPos = app.pointerOffset;
++ci->version;
shapeValid = true;
flags |= KVMFR_CURSOR_FLAG_SHAPE;
}
// update the flags for the client
ci->flags = flags;
}
app.running = false;
DEBUG_INFO("Pointer thread stopped");
return 0;
}
@@ -104,20 +159,18 @@ static int frameThread(void * opaque)
{
DEBUG_INFO("Frame thread started");
bool frameValid = false;
bool repeatFrame = false;
int frameIndex = 0;
CaptureFrame frame = { 0 };
volatile KVMFRFrame * fi = &(app.shmHeader->frame);
(void)frameIndex;
(void)repeatFrame;
bool frameValid = false;
int frameIndex = 0;
unsigned int clientInstance = 0;
CaptureFrame frame = { 0 };
while(app.running)
{
switch(app.iface->waitFrame(&frame))
{
case CAPTURE_RESULT_OK:
repeatFrame = false;
break;
case CAPTURE_RESULT_REINIT:
@@ -135,10 +188,11 @@ static int frameThread(void * opaque)
case CAPTURE_RESULT_TIMEOUT:
{
if (frameValid && lgmpHostQueueNewSubs(app.frameQueue) > 0)
if (frameValid && clientInstance != app.clientInstance)
{
// resend the last frame
repeatFrame = true;
if (--frameIndex < 0)
frameIndex = MAX_FRAMES - 1;
break;
}
@@ -146,26 +200,12 @@ static int frameThread(void * opaque)
}
}
//wait until there is room in the queue
if (lgmpHostQueuePending(app.frameQueue) == LGMP_Q_FRAME_LEN)
{
if (!app.running)
break;
}
clientInstance = app.clientInstance;
// if we are repeating a frame just send the last frame again
if (repeatFrame)
{
lgmpHostQueuePost(app.frameQueue, 0, app.frameMemory[app.frameIndex]);
continue;
}
// wait for the client to finish with the previous frame
while(fi->flags & KVMFR_FRAME_FLAG_UPDATE && app.running)
usleep(1000);
// we increment the index first so that if we need to repeat a frame
// the index still points to the latest valid frame
if (frameIndex++ == LGMP_Q_FRAME_LEN)
frameIndex = 0;
KVMFRFrame * fi = lgmpHostMemPtr(app.frameMemory[app.frameIndex]);
switch(frame.format)
{
case CAPTURE_FMT_BGRA : fi->type = FRAME_TYPE_BGRA ; break;
@@ -181,14 +221,15 @@ static int frameThread(void * opaque)
fi->height = frame.height;
fi->stride = frame.stride;
fi->pitch = frame.pitch;
fi->dataPos = app.frameOffset[frameIndex];
frameValid = true;
FrameBuffer fb = (FrameBuffer)(fi + 1);
framebuffer_prepare(fb);
framebuffer_prepare(app.frame[frameIndex]);
INTERLOCKED_OR8(&fi->flags, KVMFR_FRAME_FLAG_UPDATE);
app.iface->getFrame(app.frame[frameIndex]);
/* we post and then get the frame, this is intentional! */
lgmpHostQueuePost(app.frameQueue, 0, app.frameMemory[app.frameIndex]);
app.iface->getFrame(fb);
if (++frameIndex == MAX_FRAMES)
frameIndex = 0;
}
DEBUG_INFO("Frame thread stopped");
return 0;
@@ -197,13 +238,13 @@ static int frameThread(void * opaque)
bool startThreads()
{
app.running = true;
if (!lgCreateThread("LGMPThread", lgmpThread, NULL, &app.lgmpThread))
if (!os_createThread("CursorThread", pointerThread, NULL, &app.pointerThread))
{
DEBUG_ERROR("Failed to create the LGMP thread");
DEBUG_ERROR("Failed to create the pointer thread");
return false;
}
if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread))
if (!os_createThread("FrameThread", frameThread, NULL, &app.frameThread))
{
DEBUG_ERROR("Failed to create the frame thread");
return false;
@@ -219,19 +260,19 @@ bool stopThreads()
app.running = false;
app.iface->stop();
if (app.frameThread && !lgJoinThread(app.frameThread, NULL))
if (app.frameThread && !os_joinThread(app.frameThread, NULL))
{
DEBUG_WARN("Failed to join the frame thread");
ok = false;
}
app.frameThread = NULL;
if (app.lgmpThread && !lgJoinThread(app.lgmpThread, NULL))
if (app.pointerThread && !os_joinThread(app.pointerThread, NULL))
{
DEBUG_WARN("Failed to join the LGMP thread");
DEBUG_WARN("Failed to join the pointer thread");
ok = false;
}
app.lgmpThread = NULL;
app.pointerThread = NULL;
return ok;
}
@@ -241,7 +282,7 @@ static bool captureStart()
DEBUG_INFO("Using : %s", app.iface->getName());
const unsigned int maxFrameSize = app.iface->getMaxFrameSize();
if (maxFrameSize > app.maxFrameSize)
if (maxFrameSize > app.frameSize)
{
DEBUG_ERROR("Maximum frame size of %d bytes excceds maximum space available", maxFrameSize);
return false;
@@ -258,7 +299,7 @@ static bool captureRestart()
if (!stopThreads())
return false;
if (!app.iface->deinit() || !app.iface->init())
if (!app.iface->deinit() || !app.iface->init(app.pointerData, app.pointerDataSize))
{
DEBUG_ERROR("Failed to reinitialize the capture device");
return false;
@@ -270,94 +311,12 @@ static bool captureRestart()
return true;
}
bool captureGetPointerBuffer(void ** data, uint32_t * size)
{
// spin until there is room
while(lgmpHostQueuePending(app.pointerQueue) == LGMP_Q_POINTER_LEN)
{
DEBUG_INFO("pending");
if (!app.running)
return false;
}
PLGMPMemory mem = app.pointerMemory[app.pointerIndex];
*data = ((uint8_t*)lgmpHostMemPtr(mem)) + sizeof(KVMFRCursor);
*size = MAX_POINTER_SIZE - sizeof(KVMFRCursor);
return true;
}
void capturePostPointerBuffer(CapturePointer pointer)
{
PLGMPMemory mem;
const bool newClient = lgmpHostQueueNewSubs(app.pointerQueue) > 0;
if (pointer.shapeUpdate || newClient)
{
if (pointer.shapeUpdate)
{
// swap the latest shape buffer out of rotation
PLGMPMemory tmp = app.pointerShape;
app.pointerShape = app.pointerMemory[app.pointerIndex];
app.pointerMemory[app.pointerIndex] = tmp;
}
// use the last known shape buffer
mem = app.pointerShape;
}
else
{
mem = app.pointerMemory[app.pointerIndex];
if (++app.pointerIndex == LGMP_Q_POINTER_LEN)
app.pointerIndex = 0;
}
KVMFRCursor *cursor = lgmpHostMemPtr(mem);
cursor->x = pointer.x;
cursor->y = pointer.y;
cursor->visible = pointer.visible;
if (pointer.shapeUpdate)
{
// remember which slot has the latest shape
cursor->width = pointer.width;
cursor->height = pointer.height;
cursor->pitch = pointer.pitch;
switch(pointer.format)
{
case CAPTURE_FMT_COLOR : cursor->type = CURSOR_TYPE_COLOR ; break;
case CAPTURE_FMT_MONO : cursor->type = CURSOR_TYPE_MONOCHROME ; break;
case CAPTURE_FMT_MASKED: cursor->type = CURSOR_TYPE_MASKED_COLOR; break;
default:
DEBUG_ERROR("Invalid pointer type");
return;
}
app.pointerShapeValid = true;
}
const uint32_t sendShape =
((pointer.shapeUpdate || newClient) && app.pointerShapeValid) ? 1 : 0;
LGMP_STATUS status;
while ((status = lgmpHostQueuePost(app.pointerQueue, sendShape, mem)) != LGMP_OK)
{
if (status == LGMP_ERR_QUEUE_FULL)
continue;
DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer): %s", lgmpStatusString(status));
return;
}
}
// this is called from the platform specific startup routine
int app_main(int argc, char * argv[])
{
if (!installCrashHandler(os_getExecutable()))
DEBUG_WARN("Failed to install the crash handler");
ivshmemOptionsInit();
// register capture interface options
for(int i = 0; CaptureInterfaces[i]; ++i)
if (CaptureInterfaces[i]->initOptions)
@@ -384,80 +343,50 @@ int app_main(int argc, char * argv[])
if (!app_init())
return -1;
DEBUG_INFO("Looking Glass Host (" BUILD_VERSION ")");
unsigned int shmemSize = os_shmemSize();
uint8_t * shmemMap = NULL;
int exitcode = 0;
struct IVSHMEM shmDev;
if (!ivshmemOpen(&shmDev))
DEBUG_INFO("Looking Glass Host (" BUILD_VERSION ")");
DEBUG_INFO("IVSHMEM Size : %u MiB", shmemSize / 1048576);
if (!os_shmemMmap((void **)&shmemMap) || !shmemMap)
{
DEBUG_ERROR("Failed to open the IVSHMEM device");
DEBUG_ERROR("Failed to map the shared memory");
return -1;
}
DEBUG_INFO("IVSHMEM Address : 0x%" PRIXPTR, (uintptr_t)shmemMap);
int exitcode = 0;
DEBUG_INFO("IVSHMEM Size : %u MiB", shmDev.size / 1048576);
DEBUG_INFO("IVSHMEM Address : 0x%" PRIXPTR, (uintptr_t)shmDev.mem);
app.shmHeader = (KVMFRHeader *)shmemMap;
app.pointerData = (uint8_t *)ALIGN_UP(shmemMap + sizeof(KVMFRHeader));
app.pointerDataSize = 1048576; // 1MB fixed for pointer size, should be more then enough
app.pointerOffset = app.pointerData - shmemMap;
app.frames = (uint8_t *)ALIGN_UP(app.pointerData + app.pointerDataSize);
app.frameSize = ALIGN_DN((shmemSize - (app.frames - shmemMap)) / MAX_FRAMES);
LGMP_STATUS status;
if ((status = lgmpHostInit(shmDev.mem, shmDev.size, &app.lgmp)) != LGMP_OK)
DEBUG_INFO("Max Cursor Size : %u MiB", app.pointerDataSize / 1048576);
DEBUG_INFO("Max Frame Size : %u MiB", app.frameSize / 1048576);
DEBUG_INFO("Cursor : 0x%" PRIXPTR " (0x%08x)", (uintptr_t)app.pointerData, app.pointerOffset);
for (int i = 0; i < MAX_FRAMES; ++i)
{
DEBUG_ERROR("lgmpHostInit Failed: %s", lgmpStatusString(status));
goto fail;
app.frame [i] = (FrameBuffer)(app.frames + i * app.frameSize);
app.frameOffset[i] = (uint8_t *)app.frame[i] - shmemMap;
DEBUG_INFO("Frame %d : 0x%" PRIXPTR " (0x%08x)", i, (uintptr_t)app.frame[i], app.frameOffset[i]);
}
if ((status = lgmpHostQueueNew(app.lgmp, FRAME_QUEUE_CONFIG, &app.frameQueue)) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostQueueCreate Failed (Frame): %s", lgmpStatusString(status));
goto fail;
}
if ((status = lgmpHostQueueNew(app.lgmp, POINTER_QUEUE_CONFIG, &app.pointerQueue)) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostQueueNew Failed (Pointer): %s", lgmpStatusString(status));
goto fail;
}
for(int i = 0; i < LGMP_Q_POINTER_LEN; ++i)
{
if ((status = lgmpHostMemAlloc(app.lgmp, MAX_POINTER_SIZE, &app.pointerMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer): %s", lgmpStatusString(status));
goto fail;
}
}
app.pointerShapeValid = false;
if ((status = lgmpHostMemAlloc(app.lgmp, MAX_POINTER_SIZE, &app.pointerShape)) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer Shape): %s", lgmpStatusString(status));
goto fail;
}
app.maxFrameSize = ALIGN_DN(lgmpHostMemAvail(app.lgmp) / LGMP_Q_FRAME_LEN);
for(int i = 0; i < LGMP_Q_FRAME_LEN; ++i)
{
if ((status = lgmpHostMemAlloc(app.lgmp, app.maxFrameSize, &app.frameMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Frame): %s", lgmpStatusString(status));
goto fail;
}
}
DEBUG_INFO("Max Pointer Size : %u KiB", (unsigned int)MAX_POINTER_SIZE / 1024);
DEBUG_INFO("Max Frame Size : %u MiB", (unsigned int)(app.maxFrameSize / 1048576LL));
CaptureInterface * iface = NULL;
for(int i = 0; CaptureInterfaces[i]; ++i)
{
iface = CaptureInterfaces[i];
DEBUG_INFO("Trying : %s", iface->getName());
if (!iface->create(captureGetPointerBuffer, capturePostPointerBuffer))
if (!iface->create())
{
iface = NULL;
continue;
}
if (iface->init())
if (iface->init(app.pointerData, app.pointerDataSize))
break;
iface->free();
@@ -473,14 +402,31 @@ int app_main(int argc, char * argv[])
app.iface = iface;
// initialize the shared memory headers
memcpy(app.shmHeader->magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC));
app.shmHeader->version = KVMFR_HEADER_VERSION;
// zero and notify the client we are starting
memset(&(app.shmHeader->frame ), 0, sizeof(KVMFRFrame ));
memset(&(app.shmHeader->cursor), 0, sizeof(KVMFRCursor));
app.shmHeader->flags &= ~KVMFR_HEADER_FLAG_RESTART;
if (!captureStart())
{
exitcode = -1;
goto exit;
}
volatile char * flags = (volatile char *)&(app.shmHeader->flags);
while(app.running)
{
if (INTERLOCKED_AND8(flags, ~(KVMFR_HEADER_FLAG_RESTART)) & KVMFR_HEADER_FLAG_RESTART)
{
DEBUG_INFO("Client restarted");
++app.clientInstance;
}
if (app.reinit && !captureRestart())
{
exitcode = -1;
@@ -519,15 +465,7 @@ exit:
iface->deinit();
iface->free();
fail:
for(int i = 0; i < LGMP_Q_FRAME_LEN; ++i)
lgmpHostMemFree(&app.frameMemory[i]);
for(int i = 0; i < LGMP_Q_POINTER_LEN; ++i)
lgmpHostMemFree(&app.pointerMemory[i]);
lgmpHostMemFree(&app.pointerShape);
lgmpHostFree(&app.lgmp);
ivshmemClose(&shmDev);
os_shmemUnmap();
return exitcode;
}

View File

@@ -70,7 +70,6 @@ include_directories(
link_libraries(
${PKGCONFIG_LIBRARIES}
${GMP_LIBRARIES}
${CMAKE_DL_LIBS}
rt
m
)
@@ -85,8 +84,6 @@ set(SOURCES
)
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
add_subdirectory("${PROJECT_TOP}/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp" )
add_subdirectory(spice)
add_subdirectory(renderers)
add_subdirectory(clipboards)
@@ -98,7 +95,6 @@ target_compile_options(looking-glass-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER})
target_link_libraries(looking-glass-client
${EXE_FLAGS}
lg_common
lgmp
spice
renderers
clipboards

View File

@@ -53,6 +53,7 @@ struct Inst
LG_RendererParams params;
struct Options opt;
EGLNativeDisplayType nativeDisp;
EGLNativeWindowType nativeWind;
EGLDisplay display;
EGLConfig configs;
@@ -371,7 +372,7 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
{
case SDL_SYSWM_X11:
{
this->display = eglGetPlatformDisplay(EGL_PLATFORM_X11_KHR, wminfo.info.x11.display, NULL);
this->nativeDisp = (EGLNativeDisplayType)wminfo.info.x11.display;
this->nativeWind = (EGLNativeWindowType)wminfo.info.x11.window;
break;
}
@@ -381,7 +382,7 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
{
int width, height;
SDL_GetWindowSize(window, &width, &height);
this->display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, wminfo.info.wl.display, NULL);
this->nativeDisp = (EGLNativeDisplayType)wminfo.info.wl.display;
this->nativeWind = (EGLNativeWindowType)wl_egl_window_create(wminfo.info.wl.surface, width, height);
break;
}
@@ -392,6 +393,7 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
return false;
}
this->display = eglGetDisplay(this->nativeDisp);
if (this->display == EGL_NO_DISPLAY)
{
DEBUG_ERROR("eglGetDisplay failed");

View File

@@ -169,15 +169,8 @@ struct Inst
static bool _check_gl_error(unsigned int line, const char * name);
#define check_gl_error(name) _check_gl_error(__LINE__, name)
enum ConfigStatus
{
CONFIG_STATUS_OK,
CONFIG_STATUS_ERROR,
CONFIG_STATUS_NOOP
};
static void deconfigure(struct Inst * this);
static enum ConfigStatus configure(struct Inst * this, SDL_Window *window);
static bool configure(struct Inst * this, SDL_Window *window);
static void update_mouse_shape(struct Inst * this, bool * newShape);
static bool draw_frame(struct Inst * this);
static void draw_mouse(struct Inst * this);
@@ -556,20 +549,10 @@ bool opengl_render(void * opaque, SDL_Window * window)
if (!this)
return false;
switch(configure(this, window))
{
case CONFIG_STATUS_ERROR:
DEBUG_ERROR("configure failed");
if (configure(this, window))
if (!draw_frame(this))
return false;
case CONFIG_STATUS_NOOP :
break;
case CONFIG_STATUS_OK :
if (!draw_frame(this))
return false;
}
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
@@ -845,13 +828,13 @@ static bool _check_gl_error(unsigned int line, const char * name)
return true;
}
static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
static bool configure(struct Inst * this, SDL_Window *window)
{
LG_LOCK(this->formatLock);
if (!this->reconfigure)
{
LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_NOOP;
return this->configured;
}
if (this->configured)
@@ -879,7 +862,7 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
default:
DEBUG_ERROR("Unknown/unsupported compression type");
return CONFIG_STATUS_ERROR;
return false;
}
// calculate the texture size in bytes
@@ -897,36 +880,30 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
if (this->amdPinnedMemSupport)
{
const int pagesize = getpagesize();
this->texPixels[0] = memalign(pagesize, this->texSize * BUFFER_COUNT);
memset(this->texPixels[0], 0, this->texSize * BUFFER_COUNT);
for(int i = 1; i < BUFFER_COUNT; ++i)
this->texPixels[i] = this->texPixels[0] + this->texSize;
for(int i = 0; i < BUFFER_COUNT; ++i)
{
this->texPixels[i] = aligned_alloc(pagesize, this->texSize);
if (!this->texPixels[i])
{
DEBUG_ERROR("Failed to allocate memory for texture");
return CONFIG_STATUS_ERROR;
}
memset(this->texPixels[i], 0, this->texSize);
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->vboID[i]);
if (check_gl_error("glBindBuffer"))
{
LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR;
return false;
}
glBufferData(
GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD,
this->texSize,
this->texPixels[i],
GL_STREAM_DRAW
);
GL_STREAM_DRAW);
if (check_gl_error("glBufferData"))
{
LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR;
return false;
}
}
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0);
@@ -939,7 +916,7 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
if (check_gl_error("glBindBuffer"))
{
LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR;
return false;
}
glBufferData(
@@ -951,7 +928,7 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
if (check_gl_error("glBufferData"))
{
LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR;
return false;
}
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
@@ -962,7 +939,7 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
if (check_gl_error("glGenTextures"))
{
LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR;
return false;
}
this->hasFrames = true;
@@ -973,7 +950,7 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
if (check_gl_error("glBindTexture"))
{
LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR;
return false;
}
glTexImage2D(
@@ -990,7 +967,7 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
if (check_gl_error("glTexImage2D"))
{
LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR;
return false;
}
// configure the texture
@@ -1021,7 +998,7 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
this->reconfigure = false;
LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_OK;
return true;
}
static void deconfigure(struct Inst * this)
@@ -1049,6 +1026,9 @@ static void deconfigure(struct Inst * this)
if (this->amdPinnedMemSupport)
{
if (this->texPixels[0])
free(this->texPixels[0]);
for(int i = 0; i < BUFFER_COUNT; ++i)
{
if (this->fences[i])
@@ -1056,12 +1036,7 @@ static void deconfigure(struct Inst * this)
glDeleteSync(this->fences[i]);
this->fences[i] = NULL;
}
if (this->texPixels[i])
{
free(this->texPixels[i]);
this->texPixels[i] = NULL;
}
this->texPixels[i] = NULL;
}
}
@@ -1282,7 +1257,7 @@ static bool draw_frame(struct Inst * this)
framebuffer_read_fn(
this->frame,
opengl_buffer_fn,
this->format.height * this->format.stride * 4,
this->format.height * this->format.stride,
this
);

View File

@@ -1,6 +1,5 @@
cmake_minimum_required(VERSION 3.0)
project(spice LANGUAGES C)
set(CMAKE_C_STANDARD 11)
find_package(PkgConfig)
pkg_check_modules(SPICE_PKGCONFIG REQUIRED

View File

@@ -733,9 +733,8 @@ bool spice_connect_channel(struct SpiceChannel * channel)
if (spice.family != AF_UNIX)
{
const int flag = 1;
setsockopt(channel->socket, IPPROTO_TCP, TCP_NODELAY , &flag, sizeof(int));
setsockopt(channel->socket, IPPROTO_TCP, TCP_QUICKACK, &flag, sizeof(int));
int flag = 1;
setsockopt(channel->socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int));
}
if (connect(channel->socket, &spice.addr.addr, addrSize) == -1)
@@ -1287,7 +1286,7 @@ bool spice_read_nl(const struct SpiceChannel * channel, void * buffer, const ssi
if (!channel->connected)
{
DEBUG_ERROR("not connected");
return false;
return -1;
}
if (!buffer)

View File

@@ -52,6 +52,22 @@ static struct Option options[] =
.type = OPTION_TYPE_STRING,
.value.x_string = NULL,
},
{
.module = "app",
.name = "shmFile",
.description = "The path to the shared memory file",
.shortopt = 'f',
.type = OPTION_TYPE_STRING,
.value.x_string = "/dev/shm/looking-glass",
},
{
.module = "app",
.name = "shmSize",
.description = "Specify the size in MB of the shared memory file (0 = detect)",
.shortopt = 'L',
.type = OPTION_TYPE_INT,
.value.x_int = 0,
},
{
.module = "app",
.name = "renderer",
@@ -135,13 +151,6 @@ static struct Option options[] =
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
{
.module = "win",
.name = "forceAspect",
.description = "Force the window to maintain the aspect ratio",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
{
.module = "win",
.name = "borderless",
@@ -371,6 +380,8 @@ bool config_load(int argc, char * argv[])
}
// setup the application params for the basic types
params.shmFile = option_get_string("app", "shmFile" );
params.shmSize = option_get_int ("app", "shmSize" ) * 1048576;
params.cursorPollInterval = option_get_int ("app", "cursorPollInterval");
params.framePollInterval = option_get_int ("app", "framePollInterval" );
@@ -378,7 +389,6 @@ bool config_load(int argc, char * argv[])
params.autoResize = option_get_bool ("win", "autoResize" );
params.allowResize = option_get_bool ("win", "allowResize" );
params.keepAspect = option_get_bool ("win", "keepAspect" );
params.forceAspect = option_get_bool ("win", "forceAspect" );
params.borderless = option_get_bool ("win", "borderless" );
params.fullscreen = option_get_bool ("win", "fullScreen" );
params.maximize = option_get_bool ("win", "maximize" );
@@ -558,4 +568,4 @@ static char * optScancodeToString(struct Option * opt)
char * str;
alloc_sprintf(&str, "%d = %s", opt->value.x_int, SDL_GetScancodeName(opt->value.x_int));
return str;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -23,10 +23,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "interface/app.h"
#include "dynamic/renderers.h"
#include "dynamic/clipboards.h"
#include "common/ivshmem.h"
#include "spice/spice.h"
#include <lgmp/client.h>
struct AppState
{
@@ -43,37 +41,24 @@ struct AppState
LG_RendererRect dstRect;
SDL_Point cursor;
bool cursorVisible;
bool serverMode;
bool haveCursorPos;
bool drawCursor;
bool cursorInView;
bool updateCursor;
bool initialCursorSync;
float scaleX, scaleY;
float accX, accY;
int curLastX;
int curLastY;
bool haveCurLocal;
int curLocalX;
int curLocalY;
bool haveAligned;
bool haveCursorPos;
float scaleX, scaleY;
float accX, accY;
const LG_Renderer * lgr;
void * lgrData;
bool lgrResize;
SDL_Thread * t_frame;
const LG_Clipboard * lgc;
SpiceDataType cbType;
struct ll * cbRequestList;
SDL_SysWMinfo wminfo;
SDL_Window * window;
struct IVSHMEM shm;
PLGMPClient lgmp;
PLGMPClientQueue frameQueue;
PLGMPClientQueue pointerQueue;
int shmFD;
struct KVMFRHeader * shm;
unsigned int shmSize;
uint64_t frameTime;
uint64_t lastFrameTime;
@@ -81,10 +66,6 @@ struct AppState
uint64_t frameCount;
uint64_t renderCount;
uint64_t resizeTimeout;
bool resizeDone;
KeybindHandle kbFS;
KeybindHandle kbInput;
KeybindHandle kbMouseSensInc;
@@ -100,7 +81,6 @@ struct AppParams
bool autoResize;
bool allowResize;
bool keepAspect;
bool forceAspect;
bool borderless;
bool fullscreen;
bool maximize;
@@ -108,6 +88,8 @@ struct AppParams
bool center;
int x, y;
unsigned int w, h;
const char * shmFile;
unsigned int shmSize;
unsigned int fpsLimit;
bool showFPS;
bool useSpiceInput;

View File

@@ -9,17 +9,33 @@ if(ENABLE_BACKTRACE)
add_definitions(-DENABLE_BACKTRACE)
endif()
add_subdirectory(src/platform)
set(COMMON_SOURCES
src/objectlist.c
src/stringutils.c
src/stringlist.c
src/option.c
src/framebuffer.c
)
add_library(lg_common STATIC ${COMMON_SOURCES})
target_link_libraries(lg_common lg_common_platform)
set(LINUX_SOURCES
src/crash.linux.c
src/sysinfo.linux.c
)
set(WINDOWS_SOURCES
src/crash.windows.c
src/sysinfo.windows.c
)
if(WIN32)
set(SOURCES ${COMMON_SOURCES} ${WINDOWS_SOURCES})
add_library(lg_common STATIC ${SOURCES})
else()
set(SOURCES ${COMMON_SOURCES} ${LINUX_SOURCES})
add_library(lg_common STATIC ${SOURCES})
if(ENABLE_BACKTRACE)
target_link_libraries(lg_common bfd)
endif()
endif()
target_include_directories(lg_common
INTERFACE

View File

@@ -20,8 +20,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdint.h>
#define LGMP_Q_POINTER 1
#define LGMP_Q_FRAME 2
#define KVMFR_HEADER_MAGIC "[[KVMFR]]"
#define KVMFR_HEADER_VERSION 9
typedef enum FrameType
{
@@ -42,23 +42,49 @@ typedef enum CursorType
}
CursorType;
#define KVMFR_CURSOR_FLAG_UPDATE 1 // cursor update available
#define KVMFR_CURSOR_FLAG_VISIBLE 2 // cursor is visible
#define KVMFR_CURSOR_FLAG_SHAPE 4 // shape updated
#define KVMFR_CURSOR_FLAG_POS 8 // position updated
typedef struct KVMFRCursor
{
volatile uint8_t flags; // KVMFR_CURSOR_FLAGS
int16_t x, y; // cursor x & y position
bool visible; // cursor visible
uint32_t version; // shape version
CursorType type; // shape buffer data type
uint32_t width; // width of the shape
uint32_t height; // height of the shape
uint32_t pitch; // row length in bytes of the shape
uint64_t dataPos; // offset to the shape data
}
KVMFRCursor;
#define KVMFR_FRAME_FLAG_UPDATE 1 // frame update available
typedef struct KVMFRFrame
{
volatile uint8_t flags; // KVMFR_FRAME_FLAGS
FrameType type; // the frame data type
uint32_t width; // the width
uint32_t height; // the height
uint32_t stride; // the row stride (zero if compressed data)
uint32_t pitch; // the row pitch (stride in bytes or the compressed frame size)
uint64_t dataPos; // offset to the frame
}
KVMFRFrame;
KVMFRFrame;
#define KVMFR_HEADER_FLAG_RESTART 1 // restart signal from client
#define KVMFR_HEADER_FLAG_READY 2 // ready signal from client
#define KVMFR_HEADER_FLAG_PAUSED 4 // capture has been paused by the host
typedef struct KVMFRHeader
{
char magic[sizeof(KVMFR_HEADER_MAGIC)];
uint32_t version; // version of this structure
volatile uint8_t flags; // KVMFR_HEADER_FLAGS
KVMFRFrame frame; // the frame information
KVMFRCursor cursor; // the cursor information
}
KVMFRHeader;

View File

@@ -18,6 +18,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <stdlib.h>
#if defined(_WIN32) && !defined(__GNUC__)
#define DIRECTORY_SEPARATOR '\\'
@@ -53,6 +54,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#define DEBUG_WARN(fmt, ...) DEBUG_PRINT("[W]", fmt, ##__VA_ARGS__)
#define DEBUG_ERROR(fmt, ...) DEBUG_PRINT("[E]", fmt, ##__VA_ARGS__)
#define DEBUG_FIXME(fmt, ...) DEBUG_PRINT("[F]", fmt, ##__VA_ARGS__)
#define DEBUG_FATAL(fmt, ...) do {DEBUG_PRINT("[C]", fmt, ##__VA_ARGS__); abort();} while (0)
#if defined(DEBUG_SPICE) | defined(DEBUG_IVSHMEM)
#define DEBUG_PROTO(fmt, args...) DEBUG_PRINT("[P]", fmt, ##args)

View File

@@ -1,37 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
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
*/
#pragma once
#include <stdbool.h>
#define TIMEOUT_INFINITE ((unsigned int)~0)
typedef struct LGEvent LGEvent;
LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime);
void lgFreeEvent (LGEvent * handle);
bool lgWaitEvent (LGEvent * handle, unsigned int timeout);
bool lgWaitEvents (LGEvent * handles[], int count, bool waitAll, unsigned int timeout);
bool lgSignalEvent(LGEvent * handle);
bool lgResetEvent (LGEvent * handle);
// os specific method to wrap/convert a native event into a LGEvent
// for windows this is an event HANDLE
LGEvent * lgWrapEvent(void * handle);

View File

@@ -18,20 +18,14 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#define INTERLOCKED_AND8 __sync_fetch_and_and
#define INTERLOCKED_OR8 __sync_fetch_and_or
#define INTERLOCKED_INC(x) __sync_fetch_and_add((x), 1)
#define INTERLOCKED_DEC(x) __sync_fetch_and_sub((x), 1)
#define INTERLOCKED_GET(x) __sync_fetch_and_add((x), 0)
#define INTERLOCKED_CE(x, c, v) __sync_val_compare_and_swap((x), (c), (v))
#define INTERLOCKED_ENTER(lock) \
while(__sync_lock_test_and_set(&(lock), 1)) while((lock));
#define INTERLOCKED_EXIT(lock) \
__sync_lock_release(&(lock));
#define INTERLOCKED_SECTION(lock, x) \
while(__sync_lock_test_and_set(&(lock), 1)) while((lock)); \
x\
__sync_lock_release(&(lock));
#if defined(__GCC__) || defined(__GNUC__)
#define INTERLOCKED_AND8 __sync_fetch_and_and
#define INTERLOCKED_OR8 __sync_fetch_and_or
#define INTERLOCKED_INC(x) __sync_fetch_and_add((x), 1)
#define INTERLOCKED_DEC(x) __sync_fetch_and_sub((x), 1)
#else
#define INTERLOCKED_OR8 InterlockedOr8
#define INTERLOCKED_AND8 InterlockedAnd8
#define INTERLOCKED_INC InterlockedIncrement
#define INTERLOCKED_DEC InterlockedDecrement
#endif

View File

@@ -0,0 +1,35 @@
/*
KVMGFX Client - A KVM Client for VGA Passthrough
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
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 <stdbool.h>
typedef struct ObjectList * ObjectList;
typedef void (*ObjectFreeFn)(void * object);
ObjectList objectlist_new (ObjectFreeFn free_fn);
void objectlist_free (ObjectList * ol);
int objectlist_push (ObjectList ol, void * object);
void * objectlist_pop (ObjectList ol);
bool objectlist_remove(ObjectList ol, unsigned int index);
unsigned int objectlist_count (ObjectList ol);
void * objectlist_at (ObjectList ol, unsigned int index);
// generic free method
void objectlist_free_item(void *object);

View File

@@ -17,12 +17,31 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdbool.h>
#include "objectlist.h"
typedef struct StringList * StringList;
typedef ObjectList StringList;
StringList stringlist_new (bool owns_strings);
void stringlist_free (StringList * sl);
int stringlist_push (StringList sl, char * str);
unsigned int stringlist_count(StringList sl);
char * stringlist_at (StringList sl, unsigned int index);
inline static StringList stringlist_new(bool owns_strings)
{
return objectlist_new(owns_strings ? objectlist_free_item : 0);
}
inline static void stringlist_free(StringList * sl)
{
return objectlist_free(sl);
}
inline static int stringlist_push (StringList sl, char * str)
{
return objectlist_push(sl, str);
}
inline static unsigned int stringlist_count(StringList sl)
{
return objectlist_count(sl);
}
inline static char * stringlist_at(StringList sl, unsigned int index)
{
return objectlist_at(sl, index);
}

View File

@@ -18,5 +18,4 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
// sprintf but with buffer allocation
int alloc_sprintf(char ** str, const char * format, ...)
__attribute__ ((format (printf, 2, 3)));
int alloc_sprintf(char ** str, const char * format, ...);

112
common/src/objectlist.c Normal file
View File

@@ -0,0 +1,112 @@
/*
KVMGFX Client - A KVM Client for VGA Passthrough
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
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 "common/objectlist.h"
#include <stdlib.h>
#include <string.h>
struct ObjectList
{
ObjectFreeFn free_fn;
unsigned int size;
unsigned int count;
void ** list;
};
ObjectList objectlist_new(ObjectFreeFn free_fn)
{
ObjectList ol = malloc(sizeof(struct ObjectList));
ol->free_fn = free_fn;
ol->size = 32;
ol->count = 0;
ol->list = malloc(sizeof(void *) * ol->size);
return ol;
}
void objectlist_free(ObjectList * ol)
{
if ((*ol)->free_fn)
for(unsigned int i = 0; i < (*ol)->count; ++i)
(*ol)->free_fn((*ol)->list[i]);
free((*ol)->list);
free((*ol));
*ol = NULL;
}
int objectlist_push(ObjectList ol, void * object)
{
if (ol->count == ol->size)
{
ol->size += 32;
ol->list = realloc(ol->list, sizeof(void *) * ol->size);
}
unsigned int index = ol->count;
ol->list[ol->count++] = object;
return index;
}
void * objectlist_pop(ObjectList ol)
{
if (ol->count == 0)
return NULL;
return ol->list[--ol->count];
}
bool objectlist_remove(ObjectList ol, unsigned int index)
{
if (index >= ol->count)
return false;
if (ol->free_fn)
ol->free_fn(ol->list[index]);
void ** newlist = malloc(sizeof(void *) * ol->size);
--ol->count;
memcpy(&newlist[0], &ol->list[0], index * sizeof(void *));
memcpy(&newlist[index], &ol->list[index + 1], (ol->count - index) * sizeof(void *));
free(ol->list);
ol->list = newlist;
return true;
}
unsigned int objectlist_count(ObjectList ol)
{
return ol->count;
}
void * objectlist_at(ObjectList ol, unsigned int index)
{
if (index >= ol->count)
return NULL;
return ol->list[index];
}
void objectlist_free_item(void *object)
{
free(object);
}

View File

@@ -600,7 +600,7 @@ void option_print()
maxLen = alloc_sprintf(
&line,
"%-*s | Short | %-*s | Description",
(int)(strlen(state.groups[g].module) + state.groups[g].pad + 1),
strlen(state.groups[g].module) + state.groups[g].pad + 1,
"Long",
valueLen,
"Value"

View File

@@ -1,11 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(lg_common_platform LANGUAGES C)
if (UNIX)
add_subdirectory("linux")
elseif(WIN32)
add_subdirectory("windows")
endif()
add_library(lg_common_platform INTERFACE)
target_link_libraries(lg_common_platform INTERFACE lg_common_platform_code)

View File

@@ -1,23 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(lg_common_platform_code LANGUAGES C)
include_directories(
${PROJECT_SOURCE_DIR}/include
)
add_library(lg_common_platform_code STATIC
crash.c
sysinfo.c
thread.c
event.c
ivshmem.c
)
if(ENABLE_BACKTRACE)
target_link_libraries(lg_common_platform_code bfd)
endif()
target_link_libraries(lg_common_platform_code
lg_common
pthread
)

View File

@@ -1,170 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
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 "common/event.h"
#include "common/debug.h"
#include <stdlib.h>
#include <pthread.h>
#include <assert.h>
#include <errno.h>
#include <stdatomic.h>
#include <stdint.h>
struct LGEvent
{
pthread_mutex_t mutex;
pthread_cond_t cond;
uint32_t flag;
bool autoReset;
};
LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime)
{
LGEvent * handle = (LGEvent *)calloc(sizeof(LGEvent), 1);
if (!handle)
{
DEBUG_ERROR("Failed to allocate memory");
return NULL;
}
if (pthread_mutex_init(&handle->mutex, NULL) != 0)
{
DEBUG_ERROR("Failed to create the mutex");
free(handle);
return NULL;
}
if (pthread_cond_init(&handle->cond, NULL) != 0)
{
pthread_mutex_destroy(&handle->mutex);
free(handle);
return NULL;
}
handle->autoReset = autoReset;
return handle;
}
void lgFreeEvent(LGEvent * handle)
{
assert(handle);
pthread_cond_destroy (&handle->cond );
pthread_mutex_destroy(&handle->mutex);
free(handle);
}
bool lgWaitEvent(LGEvent * handle, unsigned int timeout)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
while(!atomic_load(&handle->flag))
{
if (timeout == TIMEOUT_INFINITE)
{
if (pthread_cond_wait(&handle->cond, &handle->mutex) != 0)
{
DEBUG_ERROR("Wait to wait on the condition");
return false;
}
}
else
{
struct timespec ts;
ts.tv_sec = timeout / 1000;
ts.tv_nsec = (timeout % 1000) * 1000000;
switch(pthread_cond_timedwait(&handle->cond, &handle->mutex, &ts))
{
case ETIMEDOUT:
return false;
default:
DEBUG_ERROR("Timed wait failed");
return false;
}
}
}
if (handle->autoReset)
atomic_store(&handle->flag, 0);
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
return true;
}
bool lgSignalEvent(LGEvent * handle)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
atomic_store(&handle->flag, 1);
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
if (pthread_cond_signal(&handle->cond) != 0)
{
DEBUG_ERROR("Failed to signal the condition");
return false;
}
return true;
}
bool lgResetEvent(LGEvent * handle)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
atomic_store(&handle->flag, 0);
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
return true;
}

View File

@@ -1,266 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
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 "common/ivshmem.h"
#include <assert.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>
#include "common/debug.h"
#include "common/option.h"
#include "common/stringutils.h"
struct IVSHMEMInfo
{
int fd;
int size;
};
static int uioOpenFile(const char * shmDevice, const char * file)
{
char * path;
alloc_sprintf(&path, "/sys/class/uio/%s/%s", shmDevice, file);
int fd = open(path, O_RDONLY);
if (fd < 0)
{
free(path);
return -1;
}
free(path);
return fd;
}
static char * uioGetName(const char * shmDevice)
{
int fd = uioOpenFile(shmDevice, "name");
if (fd < 0)
return NULL;
char * name = malloc(32);
int len = read(fd, name, 31);
if (len <= 0)
{
free(name);
close(fd);
return NULL;
}
name[len] = '\0';
close(fd);
while(len > 0 && name[len-1] == '\n')
{
--len;
name[len] = '\0';
}
return name;
}
static bool ivshmemDeviceValidator(struct Option * opt, const char ** error)
{
// if it's not a uio device, it must be a file on disk
if (strlen(opt->value.x_string) > 3 && memcmp(opt->value.x_string, "uio", 3) != 0)
{
struct stat st;
if (stat(opt->value.x_string, &st) != 0)
{
*error = "Invalid path to the ivshmem file specified";
return false;
}
return true;
}
char * name = uioGetName(opt->value.x_string);
if (!name)
{
*error = "Failed to get the uio device name";
return false;
}
if (strcmp(name, "KVMFR") != 0)
{
free(name);
*error = "Device is not a KVMFR device";
return false;
}
free(name);
return true;
}
static StringList ivshmemDeviceGetValues(struct Option * option)
{
StringList sl = stringlist_new(true);
DIR * d = opendir("/sys/class/uio");
if (!d)
return sl;
struct dirent * dir;
while((dir = readdir(d)) != NULL)
{
if (dir->d_name[0] == '.')
continue;
char * name = uioGetName(dir->d_name);
if (!name)
continue;
if (strcmp(name, "KVMFR") == 0)
stringlist_push(sl, strdup(dir->d_name));
free(name);
}
closedir(d);
return sl;
}
void ivshmemOptionsInit()
{
struct Option options[] =
{
{
.module = "app",
.name = "shmFile",
.shortopt = 'f',
.description = "The path to the shared memory file, or the name of the uio device to use, ie: uio0",
.type = OPTION_TYPE_STRING,
.value.x_string = "/dev/shm/looking-glass",
.validator = ivshmemDeviceValidator,
.getValues = ivshmemDeviceGetValues
},
{0}
};
option_register(options);
}
bool ivshmemOpen(struct IVSHMEM * dev)
{
return ivshmemOpenDev(dev, option_get_string("app", "shmFile"));
}
bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDevice)
{
assert(dev);
unsigned int devSize;
int devFD;
dev->opaque = NULL;
DEBUG_INFO("KVMFR Device : %s", shmDevice);
if (strlen(shmDevice) > 3 && memcmp(shmDevice, "uio", 3) == 0)
{
// get the device size
int fd = uioOpenFile(shmDevice, "maps/map0/size");
if (fd < 0)
{
DEBUG_ERROR("Failed to open %s/size", shmDevice);
DEBUG_ERROR("Did you remmeber to modprobe the kvmfr module?");
return false;
}
char size[32];
int len = read(fd, size, sizeof(size) - 1);
if (len <= 0)
{
DEBUG_ERROR("Failed to read the device size");
close(fd);
return false;
}
size[len] = '\0';
close(fd);
devSize = strtoul(size, NULL, 16);
char * path;
alloc_sprintf(&path, "/dev/%s", shmDevice);
devFD = open(path, O_RDWR, (mode_t)0600);
if (devFD < 0)
{
DEBUG_ERROR("Failed to open: %s", path);
DEBUG_ERROR("Did you remmeber to modprobe the kvmfr module?");
free(path);
return false;
}
free(path);
}
else
{
struct stat st;
if (stat(shmDevice, &st) != 0)
{
DEBUG_ERROR("Failed to stat: %s", shmDevice);
return false;
}
devSize = st.st_size;
devFD = open(shmDevice, O_RDWR, (mode_t)0600);
if (devFD < 0)
{
DEBUG_ERROR("Failed to open: %s", shmDevice);
return 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);
return false;
}
struct IVSHMEMInfo * info =
(struct IVSHMEMInfo *)malloc(sizeof(struct IVSHMEMInfo));
info->size = devSize;
info->fd = devFD;
dev->opaque = info;
dev->size = devSize;
dev->mem = map;
return true;
}
void ivshmemClose(struct IVSHMEM * dev)
{
assert(dev);
if (!dev->opaque)
return;
struct IVSHMEMInfo * info =
(struct IVSHMEMInfo *)dev->opaque;
munmap(dev->mem, info->size);
close(info->fd);
free(info);
dev->mem = NULL;
dev->size = 0;
dev->opaque = NULL;
}

View File

@@ -1,74 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
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 "common/thread.h"
#include <stdlib.h>
#include <pthread.h>
#include "common/debug.h"
struct LGThread
{
const char * name;
LGThreadFunction function;
void * opaque;
pthread_t handle;
int resultCode;
};
static void * threadWrapper(void * opaque)
{
LGThread * handle = (LGThread *)opaque;
handle->resultCode = handle->function(handle->opaque);
return NULL;
}
bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle)
{
*handle = (LGThread*)malloc(sizeof(LGThread));
(*handle)->name = name;
(*handle)->function = function;
(*handle)->opaque = opaque;
if (pthread_create(&(*handle)->handle, NULL, threadWrapper, *handle) != 0)
{
DEBUG_ERROR("pthread_create failed for thread: %s", name);
free(*handle);
*handle = NULL;
return false;
}
return true;
}
bool lgJoinThread(LGThread * handle, int * resultCode)
{
if (pthread_join(handle->handle, NULL) != 0)
{
DEBUG_ERROR("pthread_join failed for thread: %s", handle->name);
free(handle);
return false;
}
if (resultCode)
*resultCode = handle->resultCode;
free(handle);
return true;
}

View File

@@ -1,20 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(lg_common_platform_code LANGUAGES C)
include_directories(
${PROJECT_TOP}/vendor/ivshmem
)
add_library(lg_common_platform_code STATIC
crash.c
sysinfo.c
thread.c
event.c
windebug.c
ivshmem.c
)
target_link_libraries(lg_common_platform_code
lg_common
setupapi
)

View File

@@ -1,221 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
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 "common/event.h"
#include "common/windebug.h"
#include "common/time.h"
#include <windows.h>
struct LGEvent
{
volatile int lock;
bool reset;
HANDLE handle;
bool wrapped;
unsigned int msSpinTime;
volatile bool signaled;
};
LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime)
{
LGEvent * event = (LGEvent *)malloc(sizeof(LGEvent));
if (!event)
{
DEBUG_ERROR("out of ram");
return NULL;
}
event->lock = 0;
event->reset = autoReset;
event->handle = CreateEvent(NULL, autoReset ? FALSE : TRUE, FALSE, NULL);
event->wrapped = false;
event->msSpinTime = msSpinTime;
event->signaled = false;
if (!event->handle)
{
DEBUG_WINERROR("Failed to create the event", GetLastError());
free(event);
return NULL;
}
return event;
}
LGEvent * lgWrapEvent(void * handle)
{
LGEvent * event = (LGEvent *)malloc(sizeof(LGEvent));
if (!event)
{
DEBUG_ERROR("out of ram");
return NULL;
}
event->lock = 0;
event->reset = false;
event->handle = (HANDLE)handle;
event->wrapped = true;
event->msSpinTime = 0;
event->signaled = false;
return event;
}
void lgFreeEvent(LGEvent * event)
{
CloseHandle(event->handle);
}
bool lgWaitEvent(LGEvent * event, unsigned int timeout)
{
// wrapped events can't be enahnced
if (!event->wrapped)
{
if (event->signaled)
{
if (event->reset)
event->signaled = false;
return true;
}
if (timeout == 0)
{
bool ret = event->signaled;
if (event->reset)
event->signaled = false;
return ret;
}
if (event->msSpinTime)
{
unsigned int spinTime = event->msSpinTime;
if (timeout != TIMEOUT_INFINITE)
{
if (timeout > event->msSpinTime)
timeout -= event->msSpinTime;
else
{
spinTime -= timeout;
timeout = 0;
}
}
uint64_t now = getMicrotime();
uint64_t end = now + spinTime * 1000;
while(!event->signaled)
{
now = getMicrotime();
if (now >= end)
break;
}
if (event->signaled)
{
if (event->reset)
event->signaled = false;
return true;
}
}
}
const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout;
while(true)
{
switch(WaitForSingleObject(event->handle, to))
{
case WAIT_OBJECT_0:
if (!event->reset)
event->signaled = true;
return true;
case WAIT_ABANDONED:
continue;
case WAIT_TIMEOUT:
if (timeout == TIMEOUT_INFINITE)
continue;
return false;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for event failed", GetLastError());
return false;
}
DEBUG_ERROR("Unknown wait event return code");
return false;
}
}
bool lgWaitEvents(LGEvent * events[], int count, bool waitAll, unsigned int timeout)
{
const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout;
HANDLE * handles = (HANDLE *)malloc(sizeof(HANDLE) * count);
for(int i = 0; i < count; ++i)
handles[i] = events[i]->handle;
while(true)
{
DWORD result = WaitForMultipleObjects(count, handles, waitAll, to);
if (result >= WAIT_OBJECT_0 && result < count)
{
// null non signaled events from the handle list
for(int i = 0; i < count; ++i)
if (i != result && !lgWaitEvent(events[i], 0))
handles[i] = NULL;
free(handles);
return true;
}
if (result >= WAIT_ABANDONED_0 && result - WAIT_ABANDONED_0 < count)
continue;
switch(result)
{
case WAIT_TIMEOUT:
if (timeout == TIMEOUT_INFINITE)
continue;
free(handles);
return false;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for event failed", GetLastError());
free(handles);
return false;
}
DEBUG_ERROR("Unknown wait event return code");
free(handles);
return false;
}
}
bool lgSignalEvent(LGEvent * event)
{
event->signaled = true;
return SetEvent(event->handle);
}
bool lgResetEvent(LGEvent * event)
{
event->signaled = false;
return ResetEvent(event->handle);
}

View File

@@ -1,153 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
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 "common/ivshmem.h"
#include "common/option.h"
#include "common/windebug.h"
#include <windows.h>
#include "ivshmem.h"
#include <assert.h>
#include <setupapi.h>
#include <io.h>
struct IVSHMEMInfo
{
HANDLE handle;
};
void ivshmemOptionsInit()
{
static struct Option options[] = {
{
.module = "os",
.name = "shmDevice",
.description = "The IVSHMEM device to use",
.type = OPTION_TYPE_INT,
.value.x_int = 0
},
{0}
};
option_register(options);
}
bool ivshmemOpen(struct IVSHMEM * dev)
{
assert(dev);
HANDLE devHandle;
{
HDEVINFO devInfoSet;
PSP_DEVICE_INTERFACE_DETAIL_DATA infData = NULL;
SP_DEVICE_INTERFACE_DATA devInterfaceData = {0};
devInfoSet = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_DEVICEINTERFACE);
devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
const int shmDevice = option_get_int("os", "shmDevice");
if (SetupDiEnumDeviceInterfaces(devInfoSet, NULL, &GUID_DEVINTERFACE_IVSHMEM, shmDevice, &devInterfaceData) == FALSE)
{
DWORD error = GetLastError();
if (error == ERROR_NO_MORE_ITEMS)
{
DEBUG_WINERROR("Unable to enumerate the device, is it attached?", error);
return false;
}
DEBUG_WINERROR("SetupDiEnumDeviceInterfaces failed", error);
return false;
}
DWORD reqSize = 0;
SetupDiGetDeviceInterfaceDetail(devInfoSet, &devInterfaceData, NULL, 0, &reqSize, NULL);
if (!reqSize)
{
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
return false;
}
infData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)calloc(reqSize, 1);
infData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(devInfoSet, &devInterfaceData, infData, reqSize, NULL, NULL))
{
free(infData);
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
return false;
}
devHandle = CreateFile(infData->DevicePath, 0, 0, NULL, OPEN_EXISTING, 0, 0);
if (devHandle == INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(devInfoSet);
free(infData);
DEBUG_WINERROR("CreateFile returned INVALID_HANDLE_VALUE", GetLastError());
return false;
}
free(infData);
SetupDiDestroyDeviceInfoList(devInfoSet);
}
IVSHMEM_SIZE size;
if (!DeviceIoControl(devHandle, IOCTL_IVSHMEM_REQUEST_SIZE, NULL, 0, &size, sizeof(IVSHMEM_SIZE), NULL, NULL))
{
DEBUG_WINERROR("DeviceIoControl Failed", GetLastError());
return 0;
}
IVSHMEM_MMAP_CONFIG config = { .cacheMode = IVSHMEM_CACHE_WRITECOMBINED };
IVSHMEM_MMAP map = { 0 };
if (!DeviceIoControl(
devHandle,
IOCTL_IVSHMEM_REQUEST_MMAP,
&config, sizeof(IVSHMEM_MMAP_CONFIG),
&map , sizeof(IVSHMEM_MMAP),
NULL, NULL))
{
DEBUG_WINERROR("DeviceIoControl Failed", GetLastError());
return false;
}
struct IVSHMEMInfo * info =
(struct IVSHMEMInfo *)malloc(sizeof(struct IVSHMEMInfo));
info->handle = devHandle;
dev->opaque = info;
dev->size = (unsigned int)size;
dev->mem = map.ptr;
return true;
}
void ivshmemClose(struct IVSHMEM * dev)
{
assert(dev);
struct IVSHMEMInfo * info =
(struct IVSHMEMInfo *)dev->opaque;
if (!DeviceIoControl(info->handle, IOCTL_IVSHMEM_RELEASE_MMAP, NULL, 0, NULL, 0, NULL, NULL))
DEBUG_WINERROR("DeviceIoControl failed", GetLastError());
free(info);
}

View File

@@ -1,93 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
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 "common/thread.h"
#include "common/debug.h"
#include "common/windebug.h"
#include <windows.h>
struct LGThread
{
const char * name;
LGThreadFunction function;
void * opaque;
HANDLE handle;
DWORD threadID;
int resultCode;
};
static DWORD WINAPI threadWrapper(LPVOID lpParameter)
{
LGThread * handle = (LGThread *)lpParameter;
handle->resultCode = handle->function(handle->opaque);
return 0;
}
bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle)
{
*handle = (LGThread *)malloc(sizeof(LGThread));
(*handle)->name = name;
(*handle)->function = function;
(*handle)->opaque = opaque;
(*handle)->handle = CreateThread(NULL, 0, threadWrapper, *handle, 0, &(*handle)->threadID);
if (!(*handle)->handle)
{
free(*handle);
*handle = NULL;
DEBUG_WINERROR("CreateThread failed", GetLastError());
return false;
}
return true;
}
bool lgJoinThread(LGThread * handle, int * resultCode)
{
while(true)
{
switch(WaitForSingleObject(handle->handle, INFINITE))
{
case WAIT_OBJECT_0:
if (resultCode)
*resultCode = handle->resultCode;
CloseHandle(handle->handle);
free(handle);
return true;
case WAIT_ABANDONED:
case WAIT_TIMEOUT:
continue;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for thread failed", GetLastError());
CloseHandle(handle->handle);
free(handle);
return false;
}
break;
}
DEBUG_WINERROR("Unknown failure waiting for thread", GetLastError());
return false;
}

View File

@@ -1,79 +0,0 @@
/*
KVMGFX Client - A KVM Client for VGA Passthrough
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
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 "common/stringlist.h"
#include <stdlib.h>
struct StringList
{
bool owns_strings;
unsigned int size;
unsigned int count;
char ** list;
};
StringList stringlist_new(bool owns_strings)
{
StringList sl = malloc(sizeof(struct StringList));
sl->owns_strings = owns_strings;
sl->size = 32;
sl->count = 0;
sl->list = malloc(sizeof(char *) * sl->size);
return sl;
}
void stringlist_free(StringList * sl)
{
if ((*sl)->owns_strings)
for(unsigned int i = 0; i < (*sl)->count; ++i)
free((*sl)->list[i]);
free((*sl)->list);
free((*sl));
*sl = NULL;
}
int stringlist_push (StringList sl, char * str)
{
if (sl->count == sl->size)
{
sl->size += 32;
sl->list = realloc(sl->list, sizeof(char *) * sl->size);
}
unsigned int index = sl->count;
sl->list[sl->count++] = str;
return index;
}
unsigned int stringlist_count(StringList sl)
{
return sl->count;
}
char * stringlist_at(StringList sl, unsigned int index)
{
if (index >= sl->count)
return NULL;
return sl->list[index];
}

View File

@@ -21,24 +21,26 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdlib.h>
#include <stdarg.h>
static int valloc_sprintf(char ** str, const char * format, va_list ap)
int alloc_sprintf(char ** str, const char * format, ...)
{
if (!str)
return -1;
*str = NULL;
va_list ap1;
va_copy(ap1, ap);
int len = vsnprintf(NULL, 0, format, ap1);
va_end(ap1);
va_list ap;
va_start(ap, format);
int len = vsnprintf(NULL, 0, format, ap);
va_end(ap);
if (len < 0)
return len;
*str = malloc(len+1);
va_start(ap, format);
int ret = vsnprintf(*str, len + 1, format, ap);
va_end(ap);
if (ret < 0)
{
free(*str);
@@ -46,14 +48,5 @@ static int valloc_sprintf(char ** str, const char * format, va_list ap)
return ret;
}
return ret;
}
int alloc_sprintf(char ** str, const char * format, ...)
{
va_list ap;
va_start(ap, format);
int ret = valloc_sprintf(str, format, ap);
va_end(ap);
return ret;
}

232
obs/lg.c
View File

@@ -1,232 +0,0 @@
#include <obs/obs-module.h>
#include <common/ivshmem.h>
#include <common/KVMFR.h>
#include <common/framebuffer.h>
#include <lgmp/client.h>
#include <stdio.h>
typedef struct
{
obs_source_t * context;
bool valid;
char * shmFile;
uint32_t width, height;
FrameType type;
struct IVSHMEM shmDev;
PLGMPClient lgmp;
PLGMPClientQueue frameQueue;
gs_texture_t * texture;
}
LGPlugin;
static void lgUpdate(void * data, obs_data_t * settings);
static const char * lgGetName(void * unused)
{
return obs_module_text("Looking Glass Client");
}
static void * lgCreate(obs_data_t * settings, obs_source_t * context)
{
LGPlugin * this = bzalloc(sizeof(LGPlugin));
this->context = context;
lgUpdate(this, settings);
return this;
}
static void deinit(LGPlugin * this)
{
lgmpClientFree(&this->lgmp);
if (this->shmFile)
{
bfree(this->shmFile);
this->shmFile = NULL;
}
if (this->shmDev.mem)
ivshmemClose(&this->shmDev);
this->valid = false;
}
static void lgDestroy(void * data)
{
LGPlugin * this = (LGPlugin *)data;
deinit(this);
bfree(this);
}
static void lgGetDefaults(obs_data_t * defaults)
{
obs_data_set_default_string(defaults, "shmFile", "/dev/shm/looking-glass");
}
static obs_properties_t * lgGetProperties(void * data)
{
obs_properties_t * props = obs_properties_create();
obs_properties_add_text(props, "shmFile", obs_module_text("SHM File"), OBS_TEXT_DEFAULT);
return props;
}
static void lgUpdate(void * data, obs_data_t * settings)
{
LGPlugin * this = (LGPlugin *)data;
deinit(this);
this->shmFile = bstrdup(obs_data_get_string(settings, "shmFile"));
if (!ivshmemOpenDev(&this->shmDev, this->shmFile))
return;
if (lgmpClientInit(this->shmDev.mem, this->shmDev.size, &this->lgmp) != LGMP_OK)
return;
if (lgmpClientSubscribe(this->lgmp, LGMP_Q_FRAME, &this->frameQueue) != LGMP_OK)
return;
this->valid = true;
}
static void lgVideoTick(void * data, float seconds)
{
LGPlugin * this = (LGPlugin *)data;
if (!this->valid)
return;
LGMP_STATUS status;
LGMPMessage msg;
if ((status = lgmpClientAdvanceToLast(this->frameQueue)) != LGMP_OK)
{
if (status == LGMP_ERR_QUEUE_EMPTY)
return;
printf("lgmpClientAdvanceToLast: %s\n", lgmpStatusString(status));
this->valid = false;
return;
}
if ((status = lgmpClientProcess(this->frameQueue, &msg)) != LGMP_OK)
{
if (status == LGMP_ERR_QUEUE_EMPTY)
return;
printf("lgmpClientProcess: %s\n", lgmpStatusString(status));
this->valid = false;
return;
}
obs_enter_graphics();
KVMFRFrame * frame = (KVMFRFrame *)msg.mem;
if (this->width != frame->width ||
this->height != frame->height ||
this->type != frame->type)
{
if (this->texture)
gs_texture_destroy(this->texture);
this->texture = NULL;
this->width = frame->width;
this->height = frame->height;
this->type = frame->type;
}
if (!this->texture)
{
enum gs_color_format format;
switch(this->type)
{
case FRAME_TYPE_BGRA : format = GS_BGRA ; break;
case FRAME_TYPE_RGBA : format = GS_RGBA ; break;
case FRAME_TYPE_RGBA10: format = GS_R10G10B10A2; break;
default:
printf("invalid type %d\n", this->type);
this->valid = false;
obs_leave_graphics();
return;
}
this->texture = gs_texture_create(
this->width, this->height, format, 1, NULL, GS_DYNAMIC);
if (!this->texture)
{
printf("create texture failed\n");
this->valid = false;
obs_leave_graphics();
return;
}
}
FrameBuffer fb = (FrameBuffer)(frame + 1);
uint8_t *texData;
uint32_t linesize;
gs_texture_map(this->texture, &texData, &linesize);
if (linesize == frame->pitch)
framebuffer_read(fb, texData, frame->height * frame->pitch);
gs_texture_unmap(this->texture);
// gs_texture_set_image(this->texture, frameData, frame->pitch, false);
lgmpClientMessageDone(this->frameQueue);
obs_leave_graphics();
}
static void lgVideoRender(void * data, gs_effect_t * effect)
{
LGPlugin * this = (LGPlugin *)data;
if (!this->texture)
return;
effect = obs_get_base_effect(OBS_EFFECT_OPAQUE);
gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image");
gs_effect_set_texture(image, this->texture);
while (gs_effect_loop(effect, "Draw")) {
gs_draw_sprite(this->texture, 0, 0, 0);
}
}
static uint32_t lgGetWidth(void * data)
{
LGPlugin * this = (LGPlugin *)data;
if (!this->valid)
return 0;
return this->width;
}
static uint32_t lgGetHeight(void * data)
{
LGPlugin * this = (LGPlugin *)data;
if (!this->valid)
return 0;
return this->height;
}
struct obs_source_info lg_source =
{
.id = "looking-glass-obs",
.type = OBS_SOURCE_TYPE_INPUT,
.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW |
OBS_SOURCE_DO_NOT_DUPLICATE,
.get_name = lgGetName,
.create = lgCreate,
.destroy = lgDestroy,
.update = lgUpdate,
.get_defaults = lgGetDefaults,
.get_properties = lgGetProperties,
.video_tick = lgVideoTick,
.video_render = lgVideoRender,
.get_width = lgGetWidth,
.get_height = lgGetHeight,
// .icon_type = OBS_ICON_TYPE_DESKTOP_CAPTURE
};

View File

@@ -1,34 +0,0 @@
#include <obs/obs-module.h>
#include <stdio.h>
#ifdef _WIN32
#undef EXPORT
#define EXPORT __declspec(dllexport)
#endif
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("looking-glass-obs", "en-US")
MODULE_EXPORT const char *obs_module_description(void)
{
return "Looking Glass Client";
}
extern struct obs_source_info lg_source;
MODULE_EXPORT bool obs_module_load(void)
{
obs_register_source(&lg_source);
return true;
}
#if defined(_WIN32) && defined(__GNUC__)
/* GCC requires a DLL entry point even without any standard library included. */
/* Types extracted from windows.h to avoid polluting the rest of the file. */
int __stdcall DallMainCRTStartup(void* instance, unsigned reason, void* reserved)
{
(void) instance;
(void) reason;
(void) reserved;
return 1;
}
#endif

33
porthole/CMakeLists.txt Normal file
View File

@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.0)
project(porthole LANGUAGES C)
include_directories(
${PROJECT_SOURCE_DIR}/include
)
add_library(porthole STATIC
src/porthole.c
src/util.c
)
if(WIN32)
add_subdirectory(src/windows)
set(PLATFORM "windows")
else()
add_subdirectory(src/linux)
set(PLATFORM "linux")
endif()
target_link_libraries(porthole
PRIVATE
porthole-${PLATFORM}
lg_common
pthread
)
target_include_directories(porthole
INTERFACE
include
PRIVATE
src
)

View File

@@ -0,0 +1,81 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
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
*/
#pragma once
#include "types.h"
typedef struct PortholeClient *PortholeClient;
/**
* Memory map event callback.
*
* @param type The type ID provided by the guest
* @param map The new mapping
*
* @note this is called from the socket thread
*/
typedef void (*PortholeMapEvent)(uint32_t type, PortholeMap * map);
/**
* Memory unmap event callback.
*
* @param id The id of the mapping that has been unmapped by the guest
*
* @note this is called from the socket thread
*/
typedef void (*PortholeUnmapEvent)(uint32_t id);
/**
* Unexpected client disconnection event.
*
* When this occurs all mappings become invalid and should no longer be used.
*
* @note this is called from the socket thread
*/
typedef void (*PortholeDisconEvent)();
/**
* Open the porthole device
*
* @param handle Returned handle if successful, otherwise undefined
* @param socket_path Path to the unix socket of the porthole char device
* @param map_event Callback for map events from the guest
* @param unmap_event Callback for unmap events from the guest
* @param discon_event Callback for client socket disconnection
* @returns true on success
*
* If successful the handle must be closed to free resources when finished.
*/
bool porthole_client_open(
PortholeClient * handle,
const char * socket_path,
PortholeMapEvent map_cb,
PortholeUnmapEvent unmap_cb,
PortholeDisconEvent discon_cb);
/**
* Close the porthole devce
*
* @param handle The porthole client handle obtained from porthole_client_open
*
* handle will be set to NULL and is no longer valid after calling this
* function and all mappings will become invalid.
*/
void porthole_client_close(PortholeClient * handle);

View File

@@ -0,0 +1,123 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
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
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
typedef struct PortholeDev *PortholeDev;
typedef int PortholeID;
typedef enum PortholeState
{
PH_STATE_NEW_SESSION,
PH_STATE_CONNECTED,
PH_STATE_DISCONNECTED
}
PortholeState;
/**
* Open the porthole device
*
* @param handle The returned handle if successful, otherwise undefined
* @param vendor_id The subsystem vendor and device id to match
* @return 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);
/**
* Get the state of the porthole device
*
* @param handle The porthole device handle obtained form porthole_dev_open
* @return The current state of the connection
*
* This method will return the current state of the porthole device
*
* * PH_STATE_NEW_SESSION = The client has connected
* * PH_STATE_CONNECTED = The client is still connected
* * PH_STATE_DISCONNECTED = There is no client connection
*/
PortholeState porthole_dev_get_state(PortholeDev handle);
/**
* Wait for the specified state
*
* @param handle The porthole device handle obtained from porthole_dev_open
* @param state The state to wait for
* @param timeout The maximum amount of time to wait in milliseconds for the state (0 = infinite)
* @return true on success, false on timeout
*/
bool porthole_dev_wait_state(PortholeDev handle, const PortholeState state, const unsigned int timeout);
/**
* 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
* @return the porthole mapping ID, or -1 on failure
*
* 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 device & driver are hard limited to 32 shares.
*/
PortholeID porthole_dev_map(PortholeDev handle, const uint32_t type, void *buffer, size_t size);
/**
* Unmap a previously shared buffer
*
* @param handle The porthole device
* @param id The porthole map id returned by porthole_dev_share
* @return true on success
*
* Unmaps 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.
*
* The client application will be notified that the mapping is about to become
* invalid and is expected to clean up and notify when it is done. If your
* application hangs calling this method the issue is very likely with your
* client application.
*/
bool porthole_dev_unmap(PortholeDev handle, PortholeID id);

View File

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
@@ -20,9 +20,12 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#pragma once
#include <stdbool.h>
#include <stdint.h>
typedef struct LGThread LGThread;
typedef int (*LGThreadFunction)(void * opaque);
bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle);
bool lgJoinThread (LGThread * handle, int * resultCode);
typedef struct
{
uint32_t id;
unsigned int size;
unsigned int num_segments;
}
PortholeMap;

View File

@@ -0,0 +1,62 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
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
*/
#pragma once
#include "types.h"
#include <sys/types.h>
/**
* Copy from memory to a PortholeMap
*
* @param src The source buffer
* @param dst The destination map
* @param len The data length to copy
* @param off The offset into the dst PortholeMap
*/
void porthole_copy_mem_to_map(void * src, PortholeMap * dst, size_t len, off_t off);
/**
* Copy from a PortholeMap to memory
*
* @param src The source buffer
* @param dst The destination buffer
* @param len The data length to copy
* @param off The offset into the src PortholeMap
*/
void porthole_copy_map_to_mem(PortholeMap * src, void * dst, size_t len, off_t off);
/**
* Copy from a PortholeMap to a PortholeMap
*
* @param src The source buffer
* @param dst The destination buffer
* @param len The data length to copy
* @param src_off The offset into the src PortholeMap
* @param dst_off The offset into the dst PortholeMap
*/
void porthole_copy_map_to_map(PortholeMap * src, PortholeMap * dst, size_t len, off_t src_off, off_t dst_off);
/**
* Get the pointer to the base of a PortholeMap
*
* @param map The map to get the pointer for
* @return The base address of the mapping, or NULL if the mapping is not contiguous
*/
void * porthole_get_map_ptr(PortholeMap *map);

View File

@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.0)
project(porthole-linux LANGUAGES C)
add_library(porthole-linux STATIC
client.c
)
target_link_libraries(porthole-linux
lg_common
)
target_include_directories(porthole-linux
PRIVATE
src
)

438
porthole/src/linux/client.c Normal file
View File

@@ -0,0 +1,438 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
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/client.h"
#include "common/objectlist.h"
#include "common/debug.h"
#include "../types.h"
#include "../phmsg.h"
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <pthread.h>
#include <errno.h>
typedef struct
{
uint32_t id;
int fd;
int refcount;
uint8_t * map;
size_t size;
}
SharedFD;
typedef struct
{
SharedFD * sfd;
uint64_t addr;
uint32_t size;
}
Segment;
typedef struct
{
uint32_t id;
ObjectList segments;
size_t size;
}
Mapping;
struct PortholeClient
{
int socket;
char rxbuf[1024];
int rxbuf_len;
PortholeMapEvent map_cb;
PortholeUnmapEvent unmap_cb;
PortholeDisconEvent discon_cb;
ObjectList fds;
ObjectList intmaps;
Mapping * current;
ObjectList maps;
bool running;
pthread_t thread;
bool thread_valid;
};
// forwards
static void * porthole_socket_thread(void * opaque);
static void porthole_free_map(Mapping * map);
static void porthole_sharedfd_free_handler(void * opaque);
static void porthole_intmaps_free_handler(void * opaque);
static void porthole_segment_free_handler(void * opaque);
static Mapping * porthole_intmap_new();
static void porthole_sharedfd_new(PortholeClient handle, const uint32_t id, const int fd);
static void porthole_segment_new(ObjectList fds, Mapping *map, const uint32_t fd_id, const uint64_t addr, const uint32_t size);
static void porthole_do_map(PortholeClient handle, Mapping * map, const uint32_t type);
// implementation
bool porthole_client_open(
PortholeClient * handle,
const char * socket_path,
PortholeMapEvent map_cb,
PortholeUnmapEvent unmap_cb,
PortholeDisconEvent discon_cb)
{
assert(handle);
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1)
{
DEBUG_ERROR("Failed to create a unix socket");
return false;
}
struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));
struct sockaddr_un addr = { .sun_family = AF_UNIX };
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)-1);
if (connect(fd, (const struct sockaddr*)&addr, sizeof(addr)) == -1)
{
DEBUG_ERROR("Failed to connect to the socket");
close(fd);
return false;
}
*handle = (PortholeClient)calloc(sizeof(struct PortholeClient), 1);
(*handle)->socket = fd;
(*handle)->map_cb = map_cb;
(*handle)->unmap_cb = unmap_cb;
(*handle)->discon_cb = discon_cb;
(*handle)->fds = objectlist_new(porthole_sharedfd_free_handler);
(*handle)->intmaps = objectlist_new(porthole_intmaps_free_handler);
(*handle)->maps = objectlist_new(objectlist_free_item);
(*handle)->running = true;
if (pthread_create(&(*handle)->thread, NULL, porthole_socket_thread, *handle) != 0)
{
DEBUG_ERROR("Failed to create porthole socket thread");
porthole_client_close(handle);
return false;
}
(*handle)->thread_valid = true;
return true;
}
void porthole_client_close(PortholeClient * handle)
{
assert(handle && *handle);
if ((*handle)->thread_valid)
{
(*handle)->running = false;
pthread_join((*handle)->thread, NULL);
}
close((*handle)->socket);
if ((*handle)->current)
porthole_free_map((*handle)->current);
objectlist_free(&(*handle)->maps );
objectlist_free(&(*handle)->intmaps);
objectlist_free(&(*handle)->fds );
free(*handle);
*handle = NULL;
}
static void * porthole_socket_thread(void * opaque)
{
PortholeClient handle = (PortholeClient)opaque;
DEBUG_INFO("Porthole socket thread started");
while(handle->running)
{
struct iovec io =
{
.iov_base = &handle->rxbuf[handle->rxbuf_len],
.iov_len = sizeof(handle->rxbuf) - handle->rxbuf_len
};
char cbuffer[256] = {0};
struct msghdr msghdr =
{
.msg_iov = &io,
.msg_iovlen = 1,
.msg_control = &cbuffer,
.msg_controllen = sizeof(cbuffer)
};
handle->rxbuf_len = recvmsg(handle->socket, &msghdr, 0);
if (handle->rxbuf_len < 0)
{
handle->rxbuf_len = 0;
if (errno == EAGAIN || errno == EWOULDBLOCK)
continue;
DEBUG_ERROR("Failed to recieve the message");
if (handle->discon_cb)
handle->discon_cb();
break;
}
int pos = 0;
while(pos != handle->rxbuf_len)
{
PHMsg *msg = (PHMsg *)&handle->rxbuf[pos];
// check for an incomplete message
if (pos + msg->len > handle->rxbuf_len)
break;
assert(msg->len >= PH_MSG_BASE_SIZE);
pos += msg->len;
/* process the message */
switch(msg->msg)
{
case PH_MSG_MAP:
if (handle->current)
{
DEBUG_WARN("Started a new map before finishing the last one");
porthole_free_map(handle->current);
}
handle->current = porthole_intmap_new();
break;
case PH_MSG_FD:
{
struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msghdr);
int * fds = (int *)CMSG_DATA(cmsg);
porthole_sharedfd_new(handle, msg->u.fd.id, fds[0]);
break;
}
case PH_MSG_SEGMENT:
{
if (!handle->current)
DEBUG_FATAL("Segment sent before map, this is a bug in the guest porthole device or driver");
porthole_segment_new(
handle->fds,
handle->current,
msg->u.segment.fd_id,
msg->u.segment.addr,
msg->u.segment.size
);
break;
}
case PH_MSG_FINISH:
if (!handle->current)
DEBUG_FATAL("Finished map before starting one");
handle->current->id = msg->u.finish.id;
objectlist_push(handle->intmaps, handle->current);
porthole_do_map(handle, handle->current, msg->u.finish.type);
handle->current = NULL;
break;
case PH_MSG_UNMAP:
{
// notify the application of the unmap
handle->unmap_cb(msg->u.unmap.id);
// remove the PortholeMap object
unsigned int count = objectlist_count(handle->maps);
for(unsigned int i = 0; i < count; ++i)
{
PortholeMap *m = (PortholeMap *)objectlist_at(handle->maps, i);
if (m->id == msg->u.unmap.id)
{
objectlist_remove(handle->maps, i);
break;
}
}
// remove the internal mapping object
count = objectlist_count(handle->intmaps);
for(unsigned int i = 0; i < count; ++i)
{
Mapping *m = (Mapping *)objectlist_at(handle->intmaps, i);
if (m->id == msg->u.unmap.id)
{
objectlist_remove(handle->intmaps, i);
break;
}
}
// reply to the guest to allow it to continue
uint32_t reply = PH_MSG_UNMAP;
msghdr.msg_controllen = 0;
io.iov_base = &reply;
io.iov_len = sizeof(reply);
if (sendmsg(handle->socket, &msghdr, 0) < 0)
{
DEBUG_ERROR("Failed to respond to the guest");
handle->running = false;
if (handle->discon_cb)
handle->discon_cb();
}
break;
}
}
}
// save any remaining bytes for the next recv
handle->rxbuf_len -= pos;
memmove(handle->rxbuf, &handle->rxbuf[pos], handle->rxbuf_len);
}
handle->running = false;
DEBUG_INFO("Porthole socket thread stopped");
return NULL;
}
static void porthole_sharedfd_new(PortholeClient handle, const uint32_t id, const int fd)
{
struct stat st;
fstat(fd, &st);
SharedFD * sfd = (SharedFD *)calloc(sizeof(SharedFD), 1);
sfd->id = id;
sfd->fd = fd;
sfd->size = st.st_size;
DEBUG_INFO("Guest FD ID %u (FD:%d, Size:%lu)", sfd->id, sfd->fd, sfd->size);
objectlist_push(handle->fds, sfd);
}
static void porthole_sharedfd_inc_ref(SharedFD * sfd)
{
if (sfd->refcount == 0)
{
sfd->map = mmap(NULL, sfd->size, PROT_READ | PROT_WRITE, MAP_SHARED, sfd->fd, 0);
if(!sfd->map)
DEBUG_FATAL("Failed to map shared memory");
}
++sfd->refcount;
}
static void porthole_sharedfd_dec_ref(SharedFD * sfd)
{
if (sfd->refcount == 0)
return;
munmap(sfd->map, sfd->size);
sfd->map = NULL;
--sfd->refcount;
}
static void porthole_sharedfd_free_handler(void * opaque)
{
SharedFD * sfd = (SharedFD *)opaque;
if (sfd->map)
{
munmap(sfd->map, sfd->size);
sfd->map = NULL;
}
close(sfd->fd);
free(sfd);
}
static Mapping * porthole_intmap_new()
{
Mapping * map = (Mapping *)calloc(sizeof(Mapping), 1);
map->segments = objectlist_new(porthole_segment_free_handler);
return map;
}
static void porthole_free_map(Mapping * map)
{
objectlist_free(&map->segments);
free(map);
}
static void porthole_intmaps_free_handler(void * opaque)
{
porthole_free_map((Mapping *)opaque);
}
static void porthole_segment_new(ObjectList fds, Mapping *map, const uint32_t fd_id, const uint64_t addr, const uint32_t size)
{
Segment * seg = calloc(sizeof(Segment), 1);
seg->addr = addr;
seg->size = size;
const unsigned int count = objectlist_count(fds);
for(unsigned int i = 0; i < count; ++i)
{
SharedFD *sfd = (SharedFD*)objectlist_at(fds, i);
if (sfd->id == fd_id)
{
seg->sfd = sfd;
break;
}
}
if (!seg->sfd)
DEBUG_FATAL("Unable to find the FD for the segment, this is a bug in the porthole device!");
map->size += size;
porthole_sharedfd_inc_ref(seg->sfd);
objectlist_push(map->segments, seg);
}
static void porthole_segment_free_handler(void * opaque)
{
Segment * seg = (Segment *)opaque;
porthole_sharedfd_dec_ref(seg->sfd);
free(seg);
}
static void porthole_do_map(PortholeClient handle, Mapping * map, const uint32_t type)
{
const unsigned int count = objectlist_count(map->segments);
PortholeMap *m = calloc(sizeof(PortholeMap) + sizeof(PortholeSegment) * count, 1);
m->id = map->id;
m->size = map->size;
m->num_segments = count;
for(unsigned int i = 0; i < count; ++i)
{
Segment * seg = (Segment *)objectlist_at(map->segments, i);
PH_SEGMENTS(m)[i].size = seg->size;
PH_SEGMENTS(m)[i].data = seg->sfd->map + seg->addr;
}
objectlist_push(handle->maps, m);
handle->map_cb(type, m);
}

64
porthole/src/phmsg.h Normal file
View File

@@ -0,0 +1,64 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
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 <stdint.h>
typedef struct {
uint32_t id; // the ID of the FD
} __attribute__ ((packed)) PHMsgFd;
typedef struct {
uint32_t fd_id; // the ID of the FD for this segment
uint32_t size; // the size of this segment
uint64_t addr; // the base address of this segment
} __attribute__ ((packed)) PHMsgSegment;
typedef struct {
uint32_t type; // the application defined type
uint32_t id; // the ID of the new mapping
} __attribute__ ((packed)) PHMsgFinish;
typedef struct {
uint32_t id; // the mapping ID
} __attribute__ ((packed)) PHMsgUnmap;
typedef struct {
uint8_t len; // the message length
uint8_t msg; // the message ID
union
{
PHMsgFd fd;
PHMsgSegment segment;
PHMsgFinish finish;
PHMsgUnmap unmap;
} u;
} __attribute__ ((packed)) PHMsg;
#define PH_MSG_MAP 0x1 // start of a map sequence
#define PH_MSG_FD 0x2 // file descriptor
#define PH_MSG_SEGMENT 0x3 // map segment
#define PH_MSG_FINISH 0x4 // finish of map sequence
#define PH_MSG_UNMAP 0x5 // unmap a previous map
#define PH_MSG_BASE_SIZE (sizeof(uint8_t) + sizeof(uint8_t))
#define PH_MSG_MAP_SIZE (PH_MSG_BASE_SIZE)
#define PH_MSG_FD_SIZE (PH_MSG_BASE_SIZE + sizeof(PHMsgFd))
#define PH_MSG_SEGMENT_SIZE (PH_MSG_BASE_SIZE + sizeof(PHMsgSegment))
#define PH_MSG_FINISH_SIZE (PH_MSG_BASE_SIZE + sizeof(PHMsgFinish))
#define PH_MSG_UNMAP_SIZE (PH_MSG_BASE_SIZE + sizeof(PHMsgUnmap))

18
porthole/src/porthole.c Normal file
View File

@@ -0,0 +1,18 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
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
*/

View File

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
@@ -19,18 +19,13 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#pragma once
#include <stdbool.h>
#include "porthole/types.h"
struct IVSHMEM
typedef struct
{
unsigned int size;
void * mem;
void * data;
}
PortholeSegment;
// internal use
void * opaque;
};
void ivshmemOptionsInit();
bool ivshmemOpen(struct IVSHMEM * dev);
bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDev);
void ivshmemClose(struct IVSHMEM * dev);
#define PH_SEGMENTS(map) ((PortholeSegment *)((map)+1))

161
porthole/src/util.c Normal file
View File

@@ -0,0 +1,161 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
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 "types.h"
#include "porthole/util.h"
#include "common/debug.h"
#include <string.h>
void porthole_copy_mem_to_map(void * src, PortholeMap * dst, size_t len, off_t off)
{
if (off + len > dst->size)
DEBUG_FATAL("Attempt to write beyond the length of destination mapping");
/* find the start segment */
PortholeSegment * seg = PH_SEGMENTS(dst);
while(off)
{
if (seg->size > off)
break;
off -= seg->size;
++seg;
}
/* copy into each segment until the length has been satisfied */
while(len)
{
char * buf = (char *)seg->data + off;
size_t avail = seg->size - off;
off = 0;
if (avail > len)
avail = len;
memcpy(buf, src, avail);
src = (char *)src + avail;
len -= avail;
++seg;
}
}
void porthole_copy_map_to_mem(PortholeMap * src, void * dst, size_t len, off_t off)
{
if (off + len > src->size)
DEBUG_FATAL("Attempt to read beyond the length of the source mapping");
/* find the start segment */
PortholeSegment * seg = PH_SEGMENTS(src);
while(off)
{
if (seg->size > off)
break;
off -= seg->size;
++seg;
}
/* copy from each segment until the length has been satisfied */
while(len)
{
char * buf = (char *)seg->data + off;
size_t avail = seg->size - off;
off = 0;
if (avail > len)
avail = len;
memcpy(dst, buf, avail);
dst = (char *)dst + avail;
len -= avail;
++seg;
}
}
void porthole_copy_map_to_map(PortholeMap * src, PortholeMap * dst, size_t len, off_t src_off, off_t dst_off)
{
if (src_off + len > src->size)
DEBUG_FATAL("Attempt to read beyond th elength of the source mapping");
if (dst_off + len > dst->size)
DEBUG_FATAL("Attempt to write beyond the length of the destination mapping");
/* find the start segments */
PortholeSegment * src_seg = PH_SEGMENTS(src);
while(src_off)
{
if (src_seg->size > src_off)
break;
src_off -= src_seg->size;
++src_seg;
}
PortholeSegment * dst_seg = PH_SEGMENTS(dst);
while(dst_off)
{
if (dst_seg->size > dst_off)
break;
dst_off -= dst_seg->size;
++dst_seg;
}
while(len)
{
char * src_buf = (char *)src_seg->data + src_off;
char * dst_buf = (char *)dst_seg->data + dst_off;
size_t src_avail = src_seg->size - src_off;
size_t dst_avail = dst_seg->size - dst_off;
src_off = 0;
dst_off = 0;
size_t avail = src_avail > dst_avail ? dst_avail : src_avail;
if (avail > len)
avail = len;
memcpy(dst_buf, src_buf, avail);
src_avail -= avail;
dst_avail -= avail;
if (src_avail == 0)
++src_seg;
else
src_off = src_avail;
if (dst_avail == 0)
++dst_seg;
else
dst_off = dst_avail;
len -= avail;
}
}
void * porthole_get_map_ptr(PortholeMap *map)
{
if (map->num_segments != 1)
return NULL;
return PH_SEGMENTS(map)[0].data;
}

View File

@@ -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
)

View File

@@ -0,0 +1,256 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
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 <windows.h>
#include <setupapi.h>
#include <assert.h>
struct PortholeDev
{
HANDLE dev;
bool connected;
PortholeEvents events;
};
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;
assert(handle);
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;
/* create the events and register them */
(*handle)->events.connect = CreateEvent(NULL, FALSE, FALSE, NULL);
(*handle)->events.disconnect = CreateEvent(NULL, FALSE, FALSE, NULL);
DWORD returned;
if (!DeviceIoControl(dev, IOCTL_PORTHOLE_REGISTER_EVENTS, &(*handle)->events, sizeof(PortholeEvents), NULL, 0, &returned, NULL))
{
DEBUG_ERROR("Failed to register the events");
CloseHandle((*handle)->events.connect );
CloseHandle((*handle)->events.disconnect);
CloseHandle(dev);
free(*handle);
*handle = NULL;
return false;
}
return true;
}
void porthole_dev_close(PortholeDev *handle)
{
assert(handle && *handle);
CloseHandle((*handle)->events.connect );
CloseHandle((*handle)->events.disconnect);
CloseHandle((*handle)->dev);
free(*handle);
*handle = NULL;
}
static PortholeState get_state(PortholeDev handle, unsigned int timeout)
{
if (handle->connected)
{
switch(WaitForSingleObject(handle->events.disconnect, timeout))
{
case WAIT_OBJECT_0:
handle->connected = false;
return PH_STATE_DISCONNECTED;
case WAIT_TIMEOUT:
return PH_STATE_CONNECTED;
default:
DEBUG_FATAL("Error waiting on disconnect event");
break;
}
}
switch(WaitForSingleObject(handle->events.connect, timeout))
{
case WAIT_OBJECT_0:
handle->connected = true;
return PH_STATE_NEW_SESSION;
case WAIT_TIMEOUT:
return PH_STATE_DISCONNECTED;
default:
DEBUG_FATAL("Error waiting on connection event");
break;
}
}
PortholeState porthole_dev_get_state(PortholeDev handle)
{
return get_state(handle, 0);
}
bool porthole_dev_wait_state(PortholeDev handle, const PortholeState state, const unsigned int timeout)
{
const DWORD to = (timeout == 0) ? INFINITE : timeout;
PortholeState lastState = get_state(handle, 0);
if (state == lastState)
return true;
while(true)
{
PortholeState nextState;
switch(lastState)
{
case PH_STATE_DISCONNECTED:
nextState = PH_STATE_NEW_SESSION;
break;
case PH_STATE_NEW_SESSION:
nextState = PH_STATE_CONNECTED;
break;
case PH_STATE_CONNECTED:
nextState = PH_STATE_DISCONNECTED;
break;
}
PortholeState newState = get_state(handle, to);
if (newState == lastState || newState != nextState)
return false;
if (newState == state)
return true;
lastState = newState;
}
}
PortholeID porthole_dev_map(PortholeDev handle, const uint32_t type, void *buffer, size_t size)
{
assert(handle);
DWORD returned;
PortholeMsg msg = {
.type = type,
.addr = buffer,
.size = size
};
PortholeMapID out;
if (!DeviceIoControl(handle->dev, IOCTL_PORTHOLE_SEND_MSG, &msg, sizeof(PortholeMsg), &out, sizeof(PortholeMapID), &returned, NULL))
return -1;
PortholeID ret = out;
return ret;
}
bool porthole_dev_unmap(PortholeDev handle, PortholeID id)
{
assert(handle);
DWORD returned;
PortholeMapID msg = id;
if (!DeviceIoControl(handle->dev, IOCTL_PORTHOLE_UNLOCK_BUFFER, &msg, sizeof(PortholeMapID), NULL, 0, &returned, NULL))
return false;
return true;
}

View File

@@ -0,0 +1,27 @@
#include <windows.h>
#include <initguid.h>
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 int PortholeMapID, *PPortholeMapID;
typedef struct _PortholeEvents
{
HANDLE connect;
HANDLE disconnect;
}
PortholeEvents, *PPortholeEvents;
#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)
#define IOCTL_PORTHOLE_REGISTER_EVENTS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)

View File

@@ -1,5 +0,0 @@
##This directory contains programs for LG performance profiling.
###Directories:
* `client` - dummy client that profiles the host application's performance.

View File

@@ -1,66 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(profiler-client C)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
include(GNUInstallDirs)
include(CheckCCompilerFlag)
include(FeatureSummary)
option(OPTIMIZE_FOR_NATIVE "Build with -march=native" ON)
if(OPTIMIZE_FOR_NATIVE)
CHECK_C_COMPILER_FLAG("-march=native" COMPILER_SUPPORTS_MARCH_NATIVE)
if(COMPILER_SUPPORTS_MARCH_NATIVE)
add_compile_options("-march=native")
endif()
endif()
add_compile_options(
"-Wall"
"-Werror"
"-Wfatal-errors"
"-ffast-math"
"-fdata-sections"
"-ffunction-sections"
"$<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>"
)
set(EXE_FLAGS "-Wl,--gc-sections")
set(CMAKE_C_STANDARD 11)
execute_process(
COMMAND cat ../../VERSION
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
OUTPUT_VARIABLE BUILD_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
add_definitions(-D BUILD_VERSION='"${BUILD_VERSION}"')
get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/../.." ABSOLUTE)
include_directories(
${PROJECT_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
)
link_libraries(
rt
m
)
set(SOURCES
src/main.c
)
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
add_subdirectory("${PROJECT_TOP}/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp" )
add_executable(profiler-client ${SOURCES})
target_compile_options(profiler-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER})
target_link_libraries(profiler-client
${EXE_FLAGS}
lg_common
lgmp
)
feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES)

View File

@@ -1,230 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
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 "common/debug.h"
#include "common/option.h"
#include "common/crash.h"
#include "common/KVMFR.h"
#include "common/locking.h"
#include "common/stringutils.h"
#include "common/ivshmem.h"
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/mman.h>
#include <pwd.h>
#include <string.h>
#include <time.h>
#include <lgmp/client.h>
#define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; })
#define max(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; })
struct state
{
bool running;
struct IVSHMEM shmDev;
};
struct state state;
static struct Option options[] =
{
{
.module = "app",
.name = "configFile",
.description = "A file to read additional configuration from",
.shortopt = 'C',
.type = OPTION_TYPE_STRING,
.value.x_string = NULL
},
{0}
};
static bool config_load(int argc, char * argv[])
{
// load any global options first
struct stat st;
if (stat("/etc/looking-glass-client.ini", &st) >= 0)
{
DEBUG_INFO("Loading config from: /etc/looking-glass-client.ini");
if (!option_load("/etc/looking-glass-client.ini"))
return false;
}
// load user's local options
struct passwd * pw = getpwuid(getuid());
char * localFile;
alloc_sprintf(&localFile, "%s/.looking-glass-client.ini", pw->pw_dir);
if (stat(localFile, &st) >= 0)
{
DEBUG_INFO("Loading config from: %s", localFile);
if (!option_load(localFile))
{
free(localFile);
return false;
}
}
free(localFile);
if (!option_parse(argc, argv))
return false;
// if a file was specified to also load, do it
const char * configFile = option_get_string("app", "configFile");
if (configFile)
{
DEBUG_INFO("Loading config from: %s", configFile);
if (!option_load(configFile))
return false;
}
if (!option_validate())
return false;
return true;
}
static inline uint64_t nanotime()
{
struct timespec time;
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
return ((uint64_t)time.tv_sec * 1e9) + time.tv_nsec;
}
static int run()
{
PLGMPClient lgmp;
PLGMPClientQueue frameQueue;
LGMP_STATUS status;
if ((status = lgmpClientInit(state.shmDev.mem, state.shmDev.size, &lgmp)) != LGMP_OK)
{
DEBUG_ERROR("lgmpClientInit: %s", lgmpStatusString(status));
return -1;
}
if ((status = lgmpClientSubscribe(lgmp, LGMP_Q_FRAME, &frameQueue) != LGMP_OK))
{
DEBUG_ERROR("lgmpClientSubscribe: %s", lgmpStatusString(status));
return -1;
}
struct perf
{
uint64_t min, max, ttl;
unsigned int count;
};
unsigned int frameCount = 0;
uint64_t lastFrameTime = 0;
struct perf p1 = {};
struct perf p5 = {};
struct perf p10 = {};
struct perf p30 = {};
// start accepting frames
while(state.running)
{
LGMPMessage msg;
if ((status = lgmpClientProcess(frameQueue, &msg)) != LGMP_OK)
{
if (status == LGMP_ERR_QUEUE_EMPTY)
continue;
DEBUG_ERROR("lgmpClientProcess: %s", lgmpStatusString(status));
return -1;
}
lgmpClientMessageDone(frameQueue);
uint64_t frameTime = nanotime();
uint64_t diff = frameTime - lastFrameTime;
if (frameCount++ == 0)
{
lastFrameTime = frameTime;
p1.min = p5.min = p10.min = p30.min = diff;
continue;
}
++p1 .count;
++p5 .count;
++p10.count;
++p30.count;
#define UPDATE(p, interval) \
if (p.ttl + diff >= (1e9 * interval)) \
{ \
fprintf(stdout, "%02d, min:%9lu ns (%5.2f ms) max:%9lu ns (%5.2f ms) avg:%9lu ns (%5.2f ms)\n", \
interval, \
p.min , ((float)p.min / 1e6f), \
p.max , ((float)p.max / 1e6f), \
p.ttl / p.count, (((float)p.ttl / p.count) / 1e6f)\
); \
p.min = p.max = p.ttl = diff; p.count = 1; \
} \
else \
{ \
p.min = min(p.min, diff); \
p.max = max(p.max, diff); \
p.ttl += diff; \
}
UPDATE(p1 , 1 );
UPDATE(p5 , 5 );
UPDATE(p10, 10);
UPDATE(p30, 30);
lastFrameTime = frameTime;
}
return 0;
}
int main(int argc, char * argv[])
{
DEBUG_INFO("Looking Glass (" BUILD_VERSION ") - Client Profiler");
if (!installCrashHandler("/proc/self/exe"))
DEBUG_WARN("Failed to install the crash handler");
option_register(options);
ivshmemOptionsInit();
if (!config_load(argc, argv))
{
option_free();
return -1;
}
// init the global state vars
state.running = true;
int ret = -1;
if (ivshmemOpen(&state.shmDev))
ret = run();
ivshmemClose(&state.shmDev);
option_free();
return ret;
}