Compare commits

...

44 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
Geoffrey McRae
0e7e918e2c [client] cleanup and re-order startup/shutdown code 2019-10-26 12:03:10 +11:00
Geoffrey McRae
7d6e061ade [client] properly shutdown on failure to connect to the spice server 2019-10-26 11:27:05 +11:00
Geoffrey McRae
66891aa536 [client] don't require wayland-egl, fixes #204 2019-10-26 11:23:04 +11:00
Geoffrey McRae
1d7a2ccf82 [c-host] windows: update ivshmem driver header and usage 2019-10-24 19:46:09 +11:00
Geoffrey McRae
e1bfb1234b [common] obey the destination buffer size 2019-10-14 18:08:06 +11:00
Geoffrey McRae
9377fdfc37 [all] bump KVMFR version due to incompatible changes 2019-10-14 17:19:19 +11:00
Geoffrey McRae
5f1d17ba1f [host] cosmetics 2019-10-09 19:52:31 +11:00
Geoffrey McRae
4c0ca1c8e7 [client] fix xor support for masked color cursors
fixes #200
2019-10-09 19:48:42 +11:00
Geoffrey McRae
8ef1aee35c [common] fix bug in framebuffer_read 2019-10-09 14:11:45 +11:00
Geoffrey McRae
4168cc8d78 [all] fix the version 2019-10-09 14:04:36 +11:00
Geoffrey McRae
bca54ab1f6 [client/host] added new asyncronous memory copy
This changes the method of the memory copy from the host application to
the guest. Instead of performing a full copy from the capture device
into shared memory, and then flagging the new frame, we instead set a
write pointer, flag the client that there is a new frame and then copy
in chunks of 1024 bytes until the entire frame is copied. The client
upon seeing the new frame flag begins to poll at high frequency the
write pointer and upon each update copies as much as it can into the
texture.

This should improve latency but also slightly increase CPU usage on the
client due to the high frequency polling.
2019-10-09 13:53:02 +11:00
Geoffrey McRae
6d2c464436 [client] egl: improved streaming texture syncronization 2019-08-30 12:09:05 +10:00
Geoffrey McRae
e93bd7a3bf [client] fix shutdown race condition with the frame thread 2019-08-30 11:54:26 +10:00
Geoffrey McRae
da94075e7b [client] egl: more verbose error on texture egl failures 2019-08-30 11:40:38 +10:00
Geoffrey McRae
69522495de [client] fix invalid shutdown of renderer outside of it's thread 2019-08-30 11:36:28 +10:00
Geoffrey McRae
fce88fc72c [EGL] add debug printf helper 2019-08-30 11:33:43 +10:00
47 changed files with 2337 additions and 567 deletions

View File

@@ -1 +1 @@
B1-rc6-6-gb979752989+1
B1-43-g83047cbc3e+1

3
arbiter/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
bin/
build/
*.swp

62
arbiter/CMakeLists.txt Normal file
View File

@@ -0,0 +1,62 @@
cmake_minimum_required(VERSION 3.0)
project(looking-glass-arbiter 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
)
set(SOURCES
src/main.c
)
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
add_subdirectory("${PROJECT_TOP}/porthole" "${CMAKE_BINARY_DIR}/porthole")
add_executable(looking-glass-arbiter ${SOURCES})
target_link_libraries(looking-glass-arbiter
${EXE_FLAGS}
lg_common
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

@@ -21,6 +21,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdbool.h>
#include <stdint.h>
#include "common/framebuffer.h"
typedef enum CaptureResult
{
@@ -55,7 +56,6 @@ typedef struct CaptureFrame
unsigned int pitch;
unsigned int stride;
CaptureFormat format;
void * data;
}
CaptureFrame;
@@ -84,7 +84,8 @@ typedef struct CaptureInterface
unsigned int (*getMaxFrameSize)();
CaptureResult (*capture )();
CaptureResult (*getFrame )(CaptureFrame * frame );
CaptureResult (*waitFrame )(CaptureFrame * frame );
CaptureResult (*getFrame )(FrameBuffer frame );
CaptureResult (*getPointer)(CapturePointer * pointer);
}
CaptureInterface;

View File

@@ -766,7 +766,7 @@ static CaptureResult dxgi_capture()
return CAPTURE_RESULT_OK;
}
static CaptureResult dxgi_getFrame(CaptureFrame * frame)
static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
{
assert(this);
assert(this->initialized);
@@ -778,7 +778,6 @@ static CaptureResult dxgi_getFrame(CaptureFrame * frame)
if (this->stop)
return CAPTURE_RESULT_REINIT;
// only reset the event if we used the texture
os_resetEvent(tex->mapped);
frame->width = this->width;
@@ -787,7 +786,16 @@ static CaptureResult dxgi_getFrame(CaptureFrame * frame)
frame->stride = this->stride;
frame->format = this->format;
memcpy(frame->data, tex->map.pData, this->pitch * this->height);
return CAPTURE_RESULT_OK;
}
static CaptureResult dxgi_getFrame(FrameBuffer frame)
{
assert(this);
assert(this->initialized);
Texture * tex = &this->texture[this->texRIndex];
framebuffer_write(frame, tex->map.pData, this->pitch * this->height);
os_signalEvent(tex->free);
if (++this->texRIndex == this->maxTextures)
@@ -867,6 +875,7 @@ struct CaptureInterface Capture_DXGI =
.free = dxgi_free,
.getMaxFrameSize = dxgi_getMaxFrameSize,
.capture = dxgi_capture,
.waitFrame = dxgi_waitFrame,
.getFrame = dxgi_getFrame,
.getPointer = dxgi_getPointer
};

View File

@@ -23,6 +23,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "windows/debug.h"
#include "windows/mousehook.h"
#include "common/option.h"
#include "common/framebuffer.h"
#include <assert.h>
#include <stdlib.h>
#include <windows.h>
@@ -236,7 +237,7 @@ static CaptureResult nvfbc_capture()
return CAPTURE_RESULT_OK;
}
static CaptureResult nvfbc_getFrame(CaptureFrame * frame)
static CaptureResult nvfbc_waitFrame(CaptureFrame * frame)
{
if (!os_waitEvent(this->frameEvent, 1000))
return CAPTURE_RESULT_TIMEOUT;
@@ -266,7 +267,16 @@ static CaptureResult nvfbc_getFrame(CaptureFrame * frame)
#endif
frame->format = this->grabInfo.bIsHDR ? CAPTURE_FMT_RGBA10 : CAPTURE_FMT_BGRA;
memcpy(frame->data, this->frameBuffer, frame->pitch * frame->height);
return CAPTURE_RESULT_OK;
}
static CaptureResult nvfbc_getFrame(FrameBuffer frame)
{
framebuffer_write(
frame,
this->frameBuffer,
this->grabInfo.dwHeight * this->grabInfo.dwBufferWidth * 4
);
return CAPTURE_RESULT_OK;
}
@@ -310,6 +320,7 @@ struct CaptureInterface Capture_NVFBC =
.free = nvfbc_free,
.getMaxFrameSize = nvfbc_getMaxFrameSize,
.capture = nvfbc_capture,
.waitFrame = nvfbc_waitFrame,
.getFrame = nvfbc_getFrame,
.getPointer = nvfbc_getPointer
};

View File

@@ -368,11 +368,16 @@ bool os_shmemMmap(void **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,
NULL, 0,
&config, sizeof(IVSHMEM_MMAP_CONFIG),
&app.shmemMap, sizeof(IVSHMEM_MMAP),
NULL, NULL))
{

View File

@@ -49,7 +49,7 @@ struct app
uint8_t * frames;
unsigned int frameSize;
uint8_t * frame[MAX_FRAMES];
FrameBuffer frame[MAX_FRAMES];
unsigned int frameOffset[MAX_FRAMES];
bool running;
@@ -168,9 +168,7 @@ static int frameThread(void * opaque)
while(app.running)
{
frame.data = app.frame[frameIndex];
switch(app.iface->getFrame(&frame))
switch(app.iface->waitFrame(&frame))
{
case CAPTURE_RESULT_OK:
break;
@@ -226,7 +224,9 @@ static int frameThread(void * opaque)
fi->dataPos = app.frameOffset[frameIndex];
frameValid = true;
framebuffer_prepare(app.frame[frameIndex]);
INTERLOCKED_OR8(&fi->flags, KVMFR_FRAME_FLAG_UPDATE);
app.iface->getFrame(app.frame[frameIndex]);
if (++frameIndex == MAX_FRAMES)
frameIndex = 0;
@@ -363,14 +363,14 @@ int app_main(int argc, char * argv[])
app.frames = (uint8_t *)ALIGN_UP(app.pointerData + app.pointerDataSize);
app.frameSize = ALIGN_DN((shmemSize - (app.frames - shmemMap)) / MAX_FRAMES);
DEBUG_INFO("Max Cursor Size : %u MiB" , app.pointerDataSize / 1048576);
DEBUG_INFO("Max Frame Size : %u MiB" , app.frameSize / 1048576);
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)
{
app.frame [i] = app.frames + i * app.frameSize;
app.frameOffset[i] = app.frame[i] - shmemMap;
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]);
}

View File

@@ -26,6 +26,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "app.h"
#include "common/KVMFR.h"
#include "common/framebuffer.h"
#define IS_LG_RENDERER_VALID(x) \
((x)->get_name && \
@@ -89,7 +90,7 @@ typedef void (* LG_RendererDeInitialize)(void * opaque);
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect);
typedef bool (* LG_RendererOnMouseShape)(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
typedef bool (* LG_RendererOnMouseEvent)(void * opaque, const bool visible , const int x, const int y);
typedef bool (* LG_RendererOnFrameEvent)(void * opaque, const LG_RendererFormat format, const uint8_t * data);
typedef bool (* LG_RendererOnFrameEvent)(void * opaque, const LG_RendererFormat format, const FrameBuffer frame);
typedef void (* LG_RendererOnAlert )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag);
typedef bool (* LG_RendererRender )(void * opaque, SDL_Window *window);
typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS);

View File

@@ -4,10 +4,13 @@ project(renderer_EGL LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(RENDERER_EGL_PKGCONFIG REQUIRED
egl
wayland-egl
gl
)
pkg_check_modules(RENDERER_EGL_OPT_PKGCONFIG
wayland-egl
)
include(MakeObject)
make_object(
EGL_SHADER
@@ -31,6 +34,7 @@ make_object(
add_library(renderer_EGL STATIC
egl.c
debug.c
shader.c
texture.c
model.c
@@ -45,6 +49,7 @@ add_library(renderer_EGL STATIC
target_link_libraries(renderer_EGL
${RENDERER_EGL_PKGCONFIG_LIBRARIES}
${RENDERER_EGL_OPT_PKGCONFIG_LIBRARIES}
lg_common
fonts
)
@@ -54,4 +59,5 @@ target_include_directories(renderer_EGL
src
${EGL_SHADER_INCS}
${RENDERER_EGL_PKGCONFIG_INCLUDE_DIRS}
${RENDERER_EGL_OPT_PKGCONFIG_INCLUDE_DIRS}
)

View File

@@ -207,24 +207,10 @@ void egl_cursor_render(EGL_Cursor * cursor)
uint8_t * data = cursor->data;
// tmp buffer for masked colour
uint32_t tmp[cursor->width * cursor->height];
switch(cursor->type)
{
case LG_CURSOR_MASKED_COLOR:
{
for(int i = 0; i < cursor->width * cursor->height; ++i)
{
const uint32_t c = ((uint32_t *)data)[i];
tmp[i] = (c & ~0xFF000000) | (c & 0xFF000000 ? 0x0 : 0xFF000000);
}
data = (uint8_t *)tmp;
// fall through to LG_CURSOR_COLOR
//
// technically we should also create an XOR texture from the data but this
// usage seems very rare in modern software.
}
// fall through
case LG_CURSOR_COLOR:
{
@@ -262,33 +248,42 @@ void egl_cursor_render(EGL_Cursor * cursor)
LG_UNLOCK(cursor->lock);
}
if (cursor->type == LG_CURSOR_MONOCHROME)
glEnable(GL_BLEND);
switch(cursor->type)
{
glEnable(GL_BLEND);
case LG_CURSOR_MONOCHROME:
{
egl_shader_use(cursor->shader);
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h / 2);
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
egl_model_set_texture(cursor->model, cursor->texture);
egl_model_render(cursor->model);
egl_shader_use(cursor->shader);
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h / 2);
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
egl_model_set_texture(cursor->model, cursor->texture);
egl_model_render(cursor->model);
egl_shader_use(cursor->shaderMono);
glUniform4f(cursor->uMousePosMono, cursor->x, cursor->y, cursor->w, cursor->h / 2);
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_model_set_texture(cursor->model, cursor->textureMono);
egl_model_render(cursor->model);
break;
}
egl_shader_use(cursor->shaderMono);
glUniform4f(cursor->uMousePosMono, cursor->x, cursor->y, cursor->w, cursor->h / 2);
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_model_set_texture(cursor->model, cursor->textureMono);
egl_model_render(cursor->model);
case LG_CURSOR_COLOR:
{
egl_shader_use(cursor->shader);
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
egl_model_render(cursor->model);
break;
}
glDisable(GL_BLEND);
}
else
{
glEnable(GL_BLEND);
egl_shader_use(cursor->shader);
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h);
glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA);
egl_model_render(cursor->model);
glDisable(GL_BLEND);
case LG_CURSOR_MASKED_COLOR:
{
egl_shader_use(cursor->shaderMono);
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h);
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_model_render(cursor->model);
break;
}
}
glDisable(GL_BLEND);
}

View File

@@ -0,0 +1,58 @@
/*
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 <GL/gl.h>
#include <stdarg.h>
#include <stdio.h>
void egl_debug_printf(char * format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
GLenum error = glGetError();
switch(error)
{
case GL_NO_ERROR:
fprintf(stderr, " (GL_NO_ERROR)\n");
break;
case GL_INVALID_ENUM:
fprintf(stderr, " (GL_INVALID_ENUM)\n");
break;
case GL_INVALID_VALUE:
fprintf(stderr, " (GL_INVALID_VALUE)\n");
break;
case GL_INVALID_OPERATION:
fprintf(stderr, " (GL_INVALID_OPERATION)\n");
break;
case GL_INVALID_FRAMEBUFFER_OPERATION:
fprintf(stderr, " (GL_INVALID_FRAMEBUFFER_OPERATION)\n");
break;
case GL_OUT_OF_MEMORY:
fprintf(stderr, " (GL_OUT_OF_MEMORY)\n");
break;
}
}

View File

@@ -0,0 +1,27 @@
/*
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 <common/debug.h>
#define EGL_DEBUG_PRINT(type, fmt, ...) do {egl_debug_printf(type " %20s:%-4u | %-30s | " fmt, STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
#define EGL_ERROR(fmt, ...) EGL_DEBUG_PRINT("[E]", fmt, ##__VA_ARGS__)
void egl_debug_printf(char * format, ...);

View File

@@ -60,7 +60,7 @@ struct EGL_Desktop
enum EGL_PixelFormat pixFmt;
unsigned int width, height;
unsigned int pitch;
const uint8_t * data;
FrameBuffer frame;
bool update;
// night vision
@@ -181,7 +181,7 @@ void egl_desktop_free(EGL_Desktop ** desktop)
*desktop = NULL;
}
bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const uint8_t * data)
bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer frame)
{
if (sourceChanged)
{
@@ -217,7 +217,7 @@ bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged,
desktop->width = format.width;
desktop->height = format.height;
desktop->pitch = format.pitch;
desktop->data = data;
desktop->frame = frame;
desktop->update = true;
/* defer the actual update as the format has changed and we need to issue GL commands first */
@@ -226,7 +226,7 @@ bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged,
}
/* update the texture now */
return egl_texture_update(desktop->texture, data);
return egl_texture_update_from_frame(desktop->texture, frame);
}
void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged)
@@ -253,7 +253,7 @@ void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged)
if (desktop->update)
{
desktop->update = false;
egl_texture_update(desktop->texture, desktop->data);
egl_texture_update_from_frame(desktop->texture, desktop->frame);
}
}

View File

@@ -28,6 +28,6 @@ typedef struct EGL_Desktop EGL_Desktop;
bool egl_desktop_init(EGL_Desktop ** desktop);
void egl_desktop_free(EGL_Desktop ** desktop);
bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const uint8_t * data);
bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer frame);
void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged);
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest);

View File

@@ -296,7 +296,7 @@ bool egl_on_mouse_event(void * opaque, const bool visible, const int x, const in
return true;
}
bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const uint8_t * data)
bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const FrameBuffer frame)
{
struct Inst * this = (struct Inst *)opaque;
this->sourceChanged = (
@@ -312,7 +312,7 @@ bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const uin
this->useNearest = this->width < format.width || this->height < format.height;
if (!egl_desktop_prepare_update(this->desktop, this->sourceChanged, format, data))
if (!egl_desktop_prepare_update(this->desktop, this->sourceChanged, format, frame))
{
DEBUG_INFO("Failed to prepare to update the desktop");
return false;

View File

@@ -19,6 +19,9 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "texture.h"
#include "common/debug.h"
#include "common/locking.h"
#include "common/framebuffer.h"
#include "debug.h"
#include "utils.h"
#include <stdlib.h>
@@ -43,14 +46,14 @@ struct EGL_Texture
GLenum format;
GLenum dataType;
bool hasPBO;
GLuint pbo[2];
int pboRIndex;
int pboWIndex;
int pboCount;
size_t pboBufferSize;
void * pboMap[2];
GLsync pboSync[2];
bool hasPBO;
GLuint pbo[2];
int pboRIndex;
int pboWIndex;
volatile int pboCount;
size_t pboBufferSize;
void * pboMap[2];
GLsync pboSync[2];
};
bool egl_texture_init(EGL_Texture ** texture)
@@ -233,7 +236,7 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
if (!texture->pboMap[i])
{
DEBUG_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
return false;
}
}
@@ -258,7 +261,7 @@ bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
if (++texture->pboWIndex == 2)
texture->pboWIndex = 0;
++texture->pboCount;
INTERLOCKED_INC(&texture->pboCount);
}
else
{
@@ -276,6 +279,24 @@ bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
return true;
}
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer frame)
{
if (!texture->streaming)
return false;
if (texture->pboCount == 2)
return true;
framebuffer_read(frame, texture->pboMap[texture->pboWIndex], texture->pboBufferSize);
texture->pboSync[texture->pboWIndex] = 0;
if (++texture->pboWIndex == 2)
texture->pboWIndex = 0;
INTERLOCKED_INC(&texture->pboCount);
return true;
}
enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
{
if (!texture->streaming)
@@ -302,7 +323,7 @@ enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
/* wait for the buffer to be ready */
pos = texture->pboRIndex;
switch(glClientWaitSync(texture->pboSync[pos], GL_SYNC_FLUSH_COMMANDS_BIT, 0))
switch(glClientWaitSync(texture->pboSync[pos], 0, 0))
{
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:
@@ -313,7 +334,7 @@ enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
case GL_WAIT_FAILED:
glDeleteSync(texture->pboSync[pos]);
DEBUG_ERROR("glClientWaitSync failed");
EGL_ERROR("glClientWaitSync failed");
return EGL_TEX_STATUS_ERROR;
}
@@ -336,7 +357,7 @@ enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
/* advance the read index */
if (++texture->pboRIndex == 2)
texture->pboRIndex = 0;
--texture->pboCount;
INTERLOCKED_DEC(&texture->pboCount);
texture->ready = true;

View File

@@ -21,6 +21,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdbool.h>
#include "shader.h"
#include "common/framebuffer.h"
#include <GL/gl.h>
@@ -46,6 +47,7 @@ void egl_texture_free(EGL_Texture ** tex);
bool egl_texture_setup (EGL_Texture * texture, enum EGL_PixelFormat pixfmt, size_t width, size_t height, size_t stride, bool streaming);
bool egl_texture_update (EGL_Texture * texture, const uint8_t * buffer);
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer frame);
enum EGL_TexStatus egl_texture_process(EGL_Texture * texture);
enum EGL_TexStatus egl_texture_bind (EGL_Texture * texture);
int egl_texture_count (EGL_Texture * texture);

View File

@@ -32,8 +32,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "common/debug.h"
#include "common/option.h"
#include "common/framebuffer.h"
#include "utils.h"
#include "lg-decoders.h"
#include "dynamic/fonts.h"
#include "ll.h"
@@ -122,8 +122,8 @@ struct Inst
GLuint vboFormat;
GLuint dataFormat;
size_t texSize;
const LG_Decoder* decoder;
void * decoderData;
size_t texPos;
FrameBuffer frame;
uint64_t drawStart;
bool hasBuffers;
@@ -140,7 +140,6 @@ struct Inst
bool hasTextures, hasFrames;
GLuint frames[BUFFER_COUNT];
GLsync fences[BUFFER_COUNT];
void * decoderFrames[BUFFER_COUNT];
GLuint textures[TEXTURE_COUNT];
struct ll * alerts;
int alertList;
@@ -362,7 +361,7 @@ bool opengl_on_mouse_event(void * opaque, const bool visible, const int x, const
return false;
}
bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const uint8_t * data)
bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const FrameBuffer frame)
{
struct Inst * this = (struct Inst *)opaque;
if (!this)
@@ -394,12 +393,7 @@ bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const
LG_UNLOCK(this->formatLock);
LG_LOCK(this->syncLock);
if (!this->decoder->decode(this->decoderData, data, format.pitch))
{
DEBUG_ERROR("decode returned failure");
LG_UNLOCK(this->syncLock);
return false;
}
this->frame = frame;
this->frameUpdate = true;
LG_UNLOCK(this->syncLock);
@@ -849,13 +843,21 @@ static bool configure(struct Inst * this, SDL_Window *window)
switch(this->format.type)
{
case FRAME_TYPE_BGRA:
case FRAME_TYPE_RGBA:
case FRAME_TYPE_RGBA10:
this->decoder = &LGD_NULL;
this->intFormat = GL_RGBA8;
this->vboFormat = GL_BGRA;
this->dataFormat = GL_UNSIGNED_BYTE;
break;
case FRAME_TYPE_YUV420:
this->decoder = &LGD_YUV420;
case FRAME_TYPE_RGBA:
this->intFormat = GL_RGBA8;
this->vboFormat = GL_RGBA;
this->dataFormat = GL_UNSIGNED_BYTE;
break;
case FRAME_TYPE_RGBA10:
this->intFormat = GL_RGB10_A2;
this->vboFormat = GL_RGBA;
this->dataFormat = GL_UNSIGNED_INT_2_10_10_10_REV;
break;
default:
@@ -863,128 +865,73 @@ static bool configure(struct Inst * this, SDL_Window *window)
return false;
}
DEBUG_INFO("Using decoder: %s", this->decoder->name);
if (!this->decoder->create(&this->decoderData))
{
DEBUG_ERROR("Failed to create the decoder");
return false;
}
if (!this->decoder->initialize(
this->decoderData,
this->format,
window))
{
DEBUG_ERROR("Failed to initialize decoder");
return false;
}
switch(this->decoder->get_out_format(this->decoderData))
{
case LG_OUTPUT_BGRA:
this->intFormat = GL_RGBA8;
this->vboFormat = GL_BGRA;
this->dataFormat = GL_UNSIGNED_BYTE;
break;
case LG_OUTPUT_RGBA:
this->intFormat = GL_RGBA8;
this->vboFormat = GL_RGBA;
this->dataFormat = GL_UNSIGNED_BYTE;
break;
case LG_OUTPUT_RGBA10:
this->intFormat = GL_RGB10_A2;
this->vboFormat = GL_RGBA;
this->dataFormat = GL_UNSIGNED_INT_2_10_10_10_REV;
break;
case LG_OUTPUT_YUV420:
// fixme
this->intFormat = GL_RGBA8;
this->vboFormat = GL_BGRA;
this->dataFormat = GL_UNSIGNED_BYTE;
break;
default:
DEBUG_ERROR("Format not supported");
LG_UNLOCK(this->formatLock);
return false;
}
// calculate the texture size in bytes
this->texSize =
this->format.height *
this->decoder->get_frame_pitch(this->decoderData);
this->texSize = this->format.height * this->format.pitch;
this->texPos = 0;
// generate the pixel unpack buffers if the decoder isn't going to do it for us
if (!this->decoder->has_gl)
glGenBuffers(BUFFER_COUNT, this->vboID);
if (check_gl_error("glGenBuffers"))
{
glGenBuffers(BUFFER_COUNT, this->vboID);
if (check_gl_error("glGenBuffers"))
{
LG_UNLOCK(this->formatLock);
return false;
}
this->hasBuffers = true;
LG_UNLOCK(this->formatLock);
return false;
}
this->hasBuffers = true;
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;
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)
for(int i = 0; i < BUFFER_COUNT; ++i)
{
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->vboID[i]);
if (check_gl_error("glBindBuffer"))
{
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->vboID[i]);
if (check_gl_error("glBindBuffer"))
{
LG_UNLOCK(this->formatLock);
return false;
}
glBufferData(
GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD,
this->texSize,
this->texPixels[i],
GL_STREAM_DRAW);
if (check_gl_error("glBufferData"))
{
LG_UNLOCK(this->formatLock);
return false;
}
LG_UNLOCK(this->formatLock);
return false;
}
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0);
}
else
{
for(int i = 0; i < BUFFER_COUNT; ++i)
glBufferData(
GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD,
this->texSize,
this->texPixels[i],
GL_STREAM_DRAW);
if (check_gl_error("glBufferData"))
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[i]);
if (check_gl_error("glBindBuffer"))
{
LG_UNLOCK(this->formatLock);
return false;
}
glBufferData(
GL_PIXEL_UNPACK_BUFFER,
this->texSize,
NULL,
GL_STREAM_DRAW
);
if (check_gl_error("glBufferData"))
{
LG_UNLOCK(this->formatLock);
return false;
}
LG_UNLOCK(this->formatLock);
return false;
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0);
}
else
{
for(int i = 0; i < BUFFER_COUNT; ++i)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[i]);
if (check_gl_error("glBindBuffer"))
{
LG_UNLOCK(this->formatLock);
return false;
}
glBufferData(
GL_PIXEL_UNPACK_BUFFER,
this->texSize,
NULL,
GL_STREAM_DRAW
);
if (check_gl_error("glBufferData"))
{
LG_UNLOCK(this->formatLock);
return false;
}
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
// create the frame textures
@@ -1023,26 +970,11 @@ static bool configure(struct Inst * this, SDL_Window *window)
return false;
}
if (this->decoder->has_gl)
{
if (!this->decoder->init_gl_texture(
this->decoderData,
GL_TEXTURE_2D,
this->frames[i],
&this->decoderFrames[i]))
{
LG_UNLOCK(this->formatLock);
return false;
}
}
else
{
// configure the texture
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
// configure the texture
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// create the display lists
glNewList(this->texList + i, GL_COMPILE);
@@ -1082,19 +1014,6 @@ static void deconfigure(struct Inst * this)
if (this->hasFrames)
{
if (this->decoder->has_gl)
{
for(int i = 0; i < BUFFER_COUNT; ++i)
{
if (this->decoderFrames[i])
this->decoder->free_gl_texture(
this->decoderData,
this->decoderFrames[i]
);
this->decoderFrames[i] = NULL;
}
}
glDeleteTextures(BUFFER_COUNT, this->frames);
this->hasFrames = false;
}
@@ -1121,12 +1040,6 @@ static void deconfigure(struct Inst * this)
}
}
if (this->decoderData)
{
this->decoder->destroy(this->decoderData);
this->decoderData = NULL;
}
this->configured = false;
}
@@ -1277,6 +1190,23 @@ static void update_mouse_shape(struct Inst * this, bool * newShape)
LG_UNLOCK(this->mouseLock);
}
static bool opengl_buffer_fn(void * opaque, const void * data, size_t size)
{
struct Inst * this = (struct Inst *)opaque;
// update the buffer, this performs a DMA transfer if possible
glBufferSubData(
GL_PIXEL_UNPACK_BUFFER,
this->texPos,
size,
data
);
check_gl_error("glBufferSubData");
this->texPos += size;
return true;
}
static bool draw_frame(struct Inst * this)
{
LG_LOCK(this->syncLock);
@@ -1293,96 +1223,70 @@ static bool draw_frame(struct Inst * this)
LG_UNLOCK(this->syncLock);
LG_LOCK(this->formatLock);
if (this->decoder->has_gl)
if (glIsSync(this->fences[this->texIndex]))
{
if (!this->decoder->update_gl_texture(
this->decoderData,
this->decoderFrames[this->texIndex]
))
switch(glClientWaitSync(this->fences[this->texIndex], 0, GL_TIMEOUT_IGNORED))
{
LG_UNLOCK(this->formatLock);
DEBUG_ERROR("Failed to update the texture from the decoder");
return false;
case GL_ALREADY_SIGNALED:
break;
case GL_CONDITION_SATISFIED:
DEBUG_WARN("Had to wait for the sync");
break;
case GL_TIMEOUT_EXPIRED:
DEBUG_WARN("Timeout expired, DMA transfers are too slow!");
break;
case GL_WAIT_FAILED:
DEBUG_ERROR("Wait failed %s", gluErrorString(glGetError()));
break;
}
glDeleteSync(this->fences[this->texIndex]);
this->fences[this->texIndex] = NULL;
}
else
glBindTexture(GL_TEXTURE_2D, this->frames[this->texIndex]);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[this->texIndex]);
glPixelStorei(GL_UNPACK_ALIGNMENT , 4);
glPixelStorei(GL_UNPACK_ROW_LENGTH , this->format.stride);
this->texPos = 0;
framebuffer_read_fn(
this->frame,
opengl_buffer_fn,
this->format.height * this->format.stride,
this
);
// update the texture
glTexSubImage2D(
GL_TEXTURE_2D,
0,
0,
0,
this->format.width ,
this->format.height,
this->vboFormat,
this->dataFormat,
(void*)0
);
if (check_gl_error("glTexSubImage2D"))
{
if (glIsSync(this->fences[this->texIndex]))
{
switch(glClientWaitSync(this->fences[this->texIndex], 0, GL_TIMEOUT_IGNORED))
{
case GL_ALREADY_SIGNALED:
break;
case GL_CONDITION_SATISFIED:
DEBUG_WARN("Had to wait for the sync");
break;
case GL_TIMEOUT_EXPIRED:
DEBUG_WARN("Timeout expired, DMA transfers are too slow!");
break;
case GL_WAIT_FAILED:
DEBUG_ERROR("Wait failed %s", gluErrorString(glGetError()));
break;
}
glDeleteSync(this->fences[this->texIndex]);
this->fences[this->texIndex] = NULL;
}
const uint8_t * data = this->decoder->get_buffer(this->decoderData);
if (!data)
{
LG_UNLOCK(this->formatLock);
DEBUG_ERROR("Failed to get the buffer from the decoder");
return false;
}
glBindTexture(GL_TEXTURE_2D, this->frames[this->texIndex]);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[this->texIndex]);
glPixelStorei(GL_UNPACK_ALIGNMENT , 4);
glPixelStorei(GL_UNPACK_ROW_LENGTH ,
this->decoder->get_frame_stride(this->decoderData)
DEBUG_ERROR("texIndex: %u, width: %u, height: %u, vboFormat: %x, texSize: %lu",
this->texIndex, this->format.width, this->format.height, this->vboFormat, this->texSize
);
// update the buffer, this performs a DMA transfer if possible
glBufferSubData(
GL_PIXEL_UNPACK_BUFFER,
0,
this->texSize,
data
);
check_gl_error("glBufferSubData");
// update the texture
glTexSubImage2D(
GL_TEXTURE_2D,
0,
0,
0,
this->format.width ,
this->format.height,
this->vboFormat,
this->dataFormat,
(void*)0
);
if (check_gl_error("glTexSubImage2D"))
{
DEBUG_ERROR("texIndex: %u, width: %u, height: %u, vboFormat: %x, texSize: %lu",
this->texIndex, this->format.width, this->format.height, this->vboFormat, this->texSize
);
}
// set a fence so we don't overwrite a buffer in use
this->fences[this->texIndex] =
glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
// unbind the buffer
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
// set a fence so we don't overwrite a buffer in use
this->fences[this->texIndex] =
glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
// unbind the buffer
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
const bool mipmap = this->opt.mipmap && (
(this->format.width > this->destRect.w) ||
(this->format.height > this->destRect.h));

View File

@@ -48,7 +48,11 @@ static int cursorThread(void * unused);
static int renderThread(void * unused);
static int frameThread (void * unused);
struct AppState state;
static SDL_Thread *t_spice = NULL;
static SDL_Thread *t_render = NULL;
static SDL_Cursor *cursor = NULL;
struct AppState state;
// this structure is initialized in config.c
struct AppParams params = { 0 };
@@ -155,7 +159,15 @@ static int renderThread(void * unused)
}
state.running = false;
SDL_WaitThread(t_cursor, NULL);
if (t_cursor)
SDL_WaitThread(t_cursor, NULL);
if (state.t_frame)
SDL_WaitThread(state.t_frame, NULL);
state.lgr->deinitialize(state.lgrData);
state.lgr = NULL;
return 0;
}
@@ -368,9 +380,8 @@ static int frameThread(void * unused)
SDL_SetWindowSize(state.window, header.width, header.height);
updatePositionInfo();
}
const uint8_t * data = (const uint8_t *)state.shm + header.dataPos;
if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, data))
FrameBuffer frame = (FrameBuffer)((uint8_t *)state.shm + header.dataPos);
if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, frame))
{
DEBUG_ERROR("renderer on frame event returned failure");
break;
@@ -582,7 +593,10 @@ int eventFilter(void * userdata, SDL_Event * event)
case SDL_QUIT:
{
if (!params.ignoreQuit)
{
DEBUG_INFO("Quit event received, exiting...");
state.running = false;
}
return 0;
}
@@ -991,11 +1005,8 @@ static void release_key_binds()
app_release_keybind(&state.kbCtrlAltFn[i]);
}
int run()
static int lg_run()
{
DEBUG_INFO("Looking Glass (" BUILD_VERSION ")");
DEBUG_INFO("Locking Method: " LG_LOCK_MODE);
memset(&state, 0, sizeof(state));
state.running = true;
state.scaleX = 1.0f;
@@ -1044,6 +1055,45 @@ int run()
signal(SIGINT , int_handler);
signal(SIGTERM, int_handler);
// try map the shared memory
state.shm = (struct KVMFRHeader *)map_memory();
if (!state.shm)
{
DEBUG_ERROR("Failed to map memory");
return -1;
}
// try to connect to the spice server
if (params.useSpiceInput || params.useSpiceClipboard)
{
spice_set_clipboard_cb(
spiceClipboardNotice,
spiceClipboardData,
spiceClipboardRelease,
spiceClipboardRequest);
if (!spice_connect(params.spiceHost, params.spicePort, ""))
{
DEBUG_ERROR("Failed to connect to spice server");
return -1;
}
while(state.running && !spice_ready())
if (!spice_process())
{
state.running = false;
DEBUG_ERROR("Failed to process spice messages");
return -1;
}
if (!(t_spice = SDL_CreateThread(spiceThread, "spiceThread", NULL)))
{
DEBUG_ERROR("spice create thread failed");
return -1;
}
}
// select and init a renderer
LG_RendererParams lgrParams;
lgrParams.showFPS = params.showFPS;
Uint32 sdlFlags;
@@ -1079,6 +1129,7 @@ int run()
return -1;
}
// all our ducks are in a line, create the window
state.window = SDL_CreateWindow(
params.windowTitle,
params.center ? SDL_WINDOWPOS_CENTERED : params.x,
@@ -1127,18 +1178,18 @@ int run()
{
state.frameTime = 1e9 / (current.refresh_rate * 2);
}
else
else
{
DEBUG_WARN("Unable to capture monitor refresh rate using the default FPS Limit: 200");
state.frameTime = 1e9 / 200;
}
}
else
else
{
DEBUG_INFO("Using the FPS Limit from args: %d", params.fpsLimit);
state.frameTime = 1e9 / params.fpsLimit;
}
register_key_binds();
// set the compositor hint to bypass for low latency
@@ -1172,12 +1223,6 @@ int run()
return -1;
}
if (!state.window)
{
DEBUG_ERROR("failed to create window");
return -1;
}
if (state.lgc)
{
DEBUG_INFO("Using Clipboard: %s", state.lgc->getName());
@@ -1190,7 +1235,6 @@ int run()
state.cbRequestList = ll_new();
}
SDL_Cursor *cursor = NULL;
if (params.hideMouse)
{
// work around SDL_ShowCursor being non functional
@@ -1200,132 +1244,89 @@ int run()
SDL_ShowCursor(SDL_DISABLE);
}
SDL_Thread *t_spice = NULL;
SDL_Thread *t_frame = NULL;
SDL_Thread *t_render = NULL;
while(1)
// start the renderThread so we don't just display junk
if (!(t_render = SDL_CreateThread(renderThread, "renderThread", NULL)))
{
state.shm = (struct KVMFRHeader *)map_memory();
if (!state.shm)
{
DEBUG_ERROR("Failed to map memory");
break;
}
// start the renderThread so we don't just display junk
if (!(t_render = SDL_CreateThread(renderThread, "renderThread", NULL)))
{
DEBUG_ERROR("render create thread failed");
break;
}
if (params.useSpiceInput || params.useSpiceClipboard)
{
spice_set_clipboard_cb(
spiceClipboardNotice,
spiceClipboardData,
spiceClipboardRelease,
spiceClipboardRequest);
if (!spice_connect(params.spiceHost, params.spicePort, ""))
{
DEBUG_ERROR("Failed to connect to spice server");
return 0;
}
while(state.running && !spice_ready())
if (!spice_process())
{
state.running = false;
DEBUG_ERROR("Failed to process spice messages");
break;
}
if (!(t_spice = SDL_CreateThread(spiceThread, "spiceThread", NULL)))
{
DEBUG_ERROR("spice create thread failed");
break;
}
}
// ensure mouse acceleration is identical in server mode
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE);
SDL_SetEventFilter(eventFilter, NULL);
// flag the host that we are starting up this is important so that
// the host wakes up if it is waiting on an interrupt, the host will
// also send us the current mouse shape since we won't know it yet
DEBUG_INFO("Waiting for host to signal it's ready...");
__sync_or_and_fetch(&state.shm->flags, KVMFR_HEADER_FLAG_RESTART);
while(state.running && (state.shm->flags & KVMFR_HEADER_FLAG_RESTART))
SDL_WaitEventTimeout(NULL, 1000);
if (!state.running)
break;
DEBUG_INFO("Host ready, starting session");
// check the header's magic and version are valid
if (memcmp(state.shm->magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC)) != 0)
{
DEBUG_ERROR("Invalid header magic, is the host running?");
break;
}
if (state.shm->version != KVMFR_HEADER_VERSION)
{
DEBUG_ERROR("KVMFR version missmatch, expected %u but got %u", KVMFR_HEADER_VERSION, state.shm->version);
DEBUG_ERROR("This is not a bug, ensure you have the right version of looking-glass-host.exe on the guest");
break;
}
if (!(t_frame = SDL_CreateThread(frameThread, "frameThread", NULL)))
{
DEBUG_ERROR("frame create thread failed");
break;
}
bool *closeAlert = NULL;
while(state.running)
{
SDL_WaitEventTimeout(NULL, 1000);
if (closeAlert == NULL)
{
if (state.shm->flags & KVMFR_HEADER_FLAG_PAUSED)
{
if (state.lgr && params.showAlerts)
state.lgr->on_alert(
state.lgrData,
LG_ALERT_WARNING,
"Stream Paused",
&closeAlert
);
}
}
else
{
if (!(state.shm->flags & KVMFR_HEADER_FLAG_PAUSED))
{
*closeAlert = true;
closeAlert = NULL;
}
}
}
break;
DEBUG_ERROR("render create thread failed");
return -1;
}
// ensure mouse acceleration is identical in server mode
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE);
SDL_SetEventFilter(eventFilter, NULL);
// flag the host that we are starting up this is important so that
// the host wakes up if it is waiting on an interrupt, the host will
// also send us the current mouse shape since we won't know it yet
DEBUG_INFO("Waiting for host to signal it's ready...");
__sync_or_and_fetch(&state.shm->flags, KVMFR_HEADER_FLAG_RESTART);
while(state.running && (state.shm->flags & KVMFR_HEADER_FLAG_RESTART))
SDL_WaitEventTimeout(NULL, 1000);
if (!state.running)
return -1;
DEBUG_INFO("Host ready, starting session");
// check the header's magic and version are valid
if (memcmp(state.shm->magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC)) != 0)
{
DEBUG_ERROR("Invalid header magic, is the host running?");
return -1;
}
if (state.shm->version != KVMFR_HEADER_VERSION)
{
DEBUG_ERROR("KVMFR version missmatch, expected %u but got %u", KVMFR_HEADER_VERSION, state.shm->version);
DEBUG_ERROR("This is not a bug, ensure you have the right version of looking-glass-host.exe on the guest");
return -1;
}
if (!(state.t_frame = SDL_CreateThread(frameThread, "frameThread", NULL)))
{
DEBUG_ERROR("frame create thread failed");
return -1;
}
bool *closeAlert = NULL;
while(state.running)
{
SDL_WaitEventTimeout(NULL, 1000);
if (closeAlert == NULL)
{
if (state.shm->flags & KVMFR_HEADER_FLAG_PAUSED)
{
if (state.lgr && params.showAlerts)
state.lgr->on_alert(
state.lgrData,
LG_ALERT_WARNING,
"Stream Paused",
&closeAlert
);
}
}
else
{
if (!(state.shm->flags & KVMFR_HEADER_FLAG_PAUSED))
{
*closeAlert = true;
closeAlert = NULL;
}
}
}
return 0;
}
static void lg_shutdown()
{
state.running = false;
if (t_render)
SDL_WaitThread(t_render, NULL);
if (t_frame)
SDL_WaitThread(t_frame, NULL);
// if spice is still connected send key up events for any pressed keys
if (params.useSpiceInput && spice_ready())
{
@@ -1345,9 +1346,6 @@ int run()
spice_disconnect();
}
if (state.lgr)
state.lgr->deinitialize(state.lgrData);
if (state.lgc)
{
state.lgc->free();
@@ -1370,12 +1368,15 @@ int run()
close(state.shmFD);
}
release_key_binds();
SDL_Quit();
return 0;
}
int main(int argc, char * argv[])
{
DEBUG_INFO("Looking Glass (" BUILD_VERSION ")");
DEBUG_INFO("Locking Method: " LG_LOCK_MODE);
if (!installCrashHandler("/proc/self/exe"))
DEBUG_WARN("Failed to install the crash handler");
@@ -1391,9 +1392,10 @@ int main(int argc, char * argv[])
if (params.grabKeyboard)
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1");
const int ret = run();
release_key_binds();
const int ret = lg_run();
lg_shutdown();
config_free();
return ret;
}
}

View File

@@ -49,6 +49,8 @@ struct AppState
void * lgrData;
bool lgrResize;
SDL_Thread * t_frame;
const LG_Clipboard * lgc;
SpiceDataType cbType;
struct ll * cbRequestList;
@@ -130,4 +132,4 @@ struct KeybindHandle
// forwards
extern struct AppState state;
extern struct AppParams params;
extern struct AppParams params;

View File

@@ -10,9 +10,10 @@ if(ENABLE_BACKTRACE)
endif()
set(COMMON_SOURCES
src/objectlist.c
src/stringutils.c
src/stringlist.c
src/option.c
src/framebuffer.c
)
set(LINUX_SOURCES

View File

@@ -21,7 +21,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdint.h>
#define KVMFR_HEADER_MAGIC "[[KVMFR]]"
#define KVMFR_HEADER_VERSION 8
#define KVMFR_HEADER_VERSION 9
typedef enum FrameType
{
@@ -49,7 +49,7 @@ CursorType;
typedef struct KVMFRCursor
{
uint8_t flags; // KVMFR_CURSOR_FLAGS
volatile uint8_t flags; // KVMFR_CURSOR_FLAGS
int16_t x, y; // cursor x & y position
uint32_t version; // shape version
@@ -65,7 +65,7 @@ KVMFRCursor;
typedef struct KVMFRFrame
{
uint8_t flags; // KVMFR_FRAME_FLAGS
volatile uint8_t flags; // KVMFR_FRAME_FLAGS
FrameType type; // the frame data type
uint32_t width; // the width
uint32_t height; // the height
@@ -83,7 +83,7 @@ typedef struct KVMFRHeader
{
char magic[sizeof(KVMFR_HEADER_MAGIC)];
uint32_t version; // version of this structure
uint8_t flags; // KVMFR_HEADER_FLAGS
volatile uint8_t flags; // KVMFR_HEADER_FLAGS
KVMFRFrame frame; // the frame information
KVMFRCursor cursor; // the cursor information
}

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

@@ -0,0 +1,48 @@
/*
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
*/
#pragma once
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
typedef struct stFrameBuffer * FrameBuffer;
typedef bool (*FrameBufferReadFn)(void * opaque, const void * src, size_t size);
/**
* Read data from the KVMFRFrame into the dst buffer
*/
bool framebuffer_read(const FrameBuffer frame, void * dst, size_t size);
/**
* Read data from the KVMFRFrame using a callback
*/
bool framebuffer_read_fn(const FrameBuffer frame, FrameBufferReadFn fn, size_t size, void * opaque);
/**
* Prepare the framebuffer for writing
*/
void framebuffer_prepare(const FrameBuffer frame);
/**
* Write data from the src buffer into the KVMFRFrame
*/
bool framebuffer_write(const FrameBuffer frame, const void * src, size_t size);

View File

@@ -19,9 +19,13 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#pragma once
#if defined(__GCC__) || defined(__GNUC__)
#define INTERLOCKED_AND8 __sync_fetch_and_and
#define INTERLOCKED_OR8 __sync_fetch_and_or
#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_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);
}

95
common/src/framebuffer.c Normal file
View File

@@ -0,0 +1,95 @@
/*
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/framebuffer.h"
#include "common/debug.h"
#include <string.h>
#define FB_CHUNK_SIZE 1024
struct stFrameBuffer
{
uint64_t wp;
uint8_t data[0];
};
bool framebuffer_read(const FrameBuffer frame, void * dst, size_t size)
{
uint8_t *d = (uint8_t*)dst;
uint64_t rp = 0;
while(rp < size)
{
/* spinlock */
while(rp == frame->wp) { }
/* copy what we can */
uint64_t avail = frame->wp - rp;
avail = avail > size ? size : avail;
memcpy(d, frame->data + rp, avail);
rp += avail;
d += avail;
size -= avail;
}
return true;
}
bool framebuffer_read_fn(const FrameBuffer frame, FrameBufferReadFn fn, size_t size, void * opaque)
{
uint64_t rp = 0;
while(rp < size)
{
/* spinlock */
while(rp == frame->wp) { }
/* copy what we can */
uint64_t avail = frame->wp - rp;
avail = avail > size ? size : avail;
if (!fn(opaque, frame->data + rp, avail))
return false;
rp += avail;
size -= avail;
}
return true;
}
/**
* Prepare the framebuffer for writing
*/
void framebuffer_prepare(const FrameBuffer frame)
{
frame->wp = 0;
}
bool framebuffer_write(FrameBuffer frame, const void * src, size_t size)
{
/* copy in chunks */
while(size)
{
size_t copy = size < FB_CHUNK_SIZE ? FB_CHUNK_SIZE : size;
memcpy(frame->data + frame->wp, src, copy);
__sync_fetch_and_add(&frame->wp, copy);
size -= copy;
}
return true;
}

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

@@ -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];
}

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

@@ -0,0 +1,31 @@
/*
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
{
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
*/

31
porthole/src/types.h Normal file
View File

@@ -0,0 +1,31 @@
/*
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 "porthole/types.h"
typedef struct
{
unsigned int size;
void * data;
}
PortholeSegment;
#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

@@ -7,6 +7,19 @@ DEFINE_GUID (GUID_DEVINTERFACE_IVSHMEM,
typedef UINT16 IVSHMEM_PEERID;
typedef UINT64 IVSHMEM_SIZE;
#define IVSHMEM_CACHE_NONCACHED 0
#define IVSHMEM_CACHE_CACHED 1
#define IVSHMEM_CACHE_WRITECOMBINED 2
/*
This structure is for use with the IOCTL_IVSHMEM_REQUEST_MMAP IOCTL
*/
typedef struct IVSHMEM_MMAP_CONFIG
{
UINT8 cacheMode; // the caching mode of the mapping, see IVSHMEM_CACHE_* for options
}
IVSHMEM_MMAP_CONFIG, *PIVSHMEM_MMAP_CONFIG;
/*
This structure is for use with the IOCTL_IVSHMEM_REQUEST_MMAP IOCTL
*/