ivshmem work

This commit is contained in:
Lily Tsuru 2024-12-01 19:24:43 -05:00
parent 11766f6385
commit 7a6ef52144
20 changed files with 724 additions and 162 deletions

3
.gitignore vendored
View file

@ -5,3 +5,6 @@
/agent/bin /agent/bin
/agent/obj /agent/obj
/agent/compile_commands.json /agent/compile_commands.json
# test stuff
/shared/src/test

30
Cargo.lock generated
View file

@ -47,6 +47,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "dlib" name = "dlib"
version = "0.5.2" version = "0.5.2"
@ -83,7 +89,9 @@ name = "fbcserver"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"libc",
"minifb", "minifb",
"nix 0.29.0",
] ]
[[package]] [[package]]
@ -204,9 +212,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.164" version = "0.2.167"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -294,6 +302,18 @@ dependencies = [
"memoffset", "memoffset",
] ]
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.6.0",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.20.2" version = "1.20.2"
@ -558,7 +578,7 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"downcast-rs", "downcast-rs",
"libc", "libc",
"nix", "nix 0.24.3",
"scoped-tls", "scoped-tls",
"wayland-commons", "wayland-commons",
"wayland-scanner", "wayland-scanner",
@ -571,7 +591,7 @@ version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
dependencies = [ dependencies = [
"nix", "nix 0.24.3",
"once_cell", "once_cell",
"smallvec", "smallvec",
"wayland-sys", "wayland-sys",
@ -583,7 +603,7 @@ version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
dependencies = [ dependencies = [
"nix", "nix 0.24.3",
"wayland-client", "wayland-client",
"xcursor", "xcursor",
] ]

View file

@ -5,4 +5,6 @@ edition = "2021"
[dependencies] [dependencies]
byteorder = "1.5.0" byteorder = "1.5.0"
libc = "0.2.167"
minifb = "0.27.0" minifb = "0.27.0"
nix = { version = "0.29.0", features = [ "stat", "mman" ] }

View file

@ -9,6 +9,7 @@ OBJDIR = obj/$(ARCH)/$(CONFIG)
VPATH = src/crt \ VPATH = src/crt \
src/base \ src/base \
src/ \ src/ \
../shared/src
# Objects for crt # Objects for crt
OBJS_STARTUP := $(OBJDIR)/crt0.o \ OBJS_STARTUP := $(OBJDIR)/crt0.o \
@ -21,6 +22,7 @@ OBJS_BASELIB :=
OBJS := $(OBJDIR)/capture.o \ OBJS := $(OBJDIR)/capture.o \
$(OBJDIR)/capture_nvfbc.o \ $(OBJDIR)/capture_nvfbc.o \
$(OBJDIR)/nvfbc_library.o \ $(OBJDIR)/nvfbc_library.o \
$(OBJDIR)/ivshmem.o \
$(OBJDIR)/main.o \ $(OBJDIR)/main.o \
LINK_LIBS := $(VS2022_PATH)/ucrt/lib/$(ARCH)/libucrt$(D).lib \ LINK_LIBS := $(VS2022_PATH)/ucrt/lib/$(ARCH)/libucrt$(D).lib \
@ -30,6 +32,7 @@ LINK_LIBS := $(VS2022_PATH)/ucrt/lib/$(ARCH)/libucrt$(D).lib \
$(VS2022_PATH)/winsdk/lib/$(ARCH)/kernel32.lib \ $(VS2022_PATH)/winsdk/lib/$(ARCH)/kernel32.lib \
$(VS2022_PATH)/winsdk/lib/$(ARCH)/user32.lib \ $(VS2022_PATH)/winsdk/lib/$(ARCH)/user32.lib \
$(VS2022_PATH)/winsdk/lib/$(ARCH)/comctl32.lib \ $(VS2022_PATH)/winsdk/lib/$(ARCH)/comctl32.lib \
$(VS2022_PATH)/winsdk/lib/$(ARCH)/setupapi.lib \
$(VS2022_PATH)/winsdk/lib/$(ARCH)/ws2_32.lib $(VS2022_PATH)/winsdk/lib/$(ARCH)/ws2_32.lib
.PHONY: all dumpinfo clean matrix .PHONY: all dumpinfo clean matrix
@ -40,6 +43,11 @@ publish: $(BINDIR)/$(NAME).exe
cp -rv bin /data/sda/shit/Release/clangent cp -rv bin /data/sda/shit/Release/clangent
chmod -Rvv 777 /data/sda/shit/Release/clangent chmod -Rvv 777 /data/sda/shit/Release/clangent
publish2: $(BINDIR)/$(NAME).exe
cp -rv bin/ /data/sda/shit/Release/ivshtest/
chmod -Rvv 777 /data/sda/shit/Release/ivshtest
dumpinfo: dumpinfo:
echo -e "\e[96mBuilding configuration $(CONFIG) for arch $(ARCH) \e[0m" echo -e "\e[96mBuilding configuration $(CONFIG) for arch $(ARCH) \e[0m"

View file

@ -8,7 +8,7 @@ SDK_INCLUDES := -isystem $(VS2022_PATH)/ucrt/include -isystem $(VS2022_PATH)/crt
# Windows 6.1 # Windows 6.1
BASE_CCFLAGS := $(SDK_INCLUDES) -D_WIN32_WINNT=0x0601 -Ires -std=c17 BASE_CCFLAGS := $(SDK_INCLUDES) -D_WIN32_WINNT=0x0601 -Ires -std=c17
BASE_CXXFLAGS := $(SDK_INCLUDES) -D_CRT_SECURE_NO_WARNINGS -fno-exceptions -fno-rtti -D_WIN32_WINNT=0x0601 -Isrc -Ires -std=c++23 BASE_CXXFLAGS := $(SDK_INCLUDES) -D_CRT_SECURE_NO_WARNINGS -fno-exceptions -fno-rtti -D_WIN32_WINNT=0x0601 -I../shared/src -Isrc -Ires -std=c++23
Release_Valid = yes Release_Valid = yes
Release_CCFLAGS = -O3 -ffast-math -DNDEBUG Release_CCFLAGS = -O3 -ffast-math -DNDEBUG

View file

@ -0,0 +1,69 @@
#include <initguid.h>
DEFINE_GUID (GUID_DEVINTERFACE_IVSHMEM,
0xdf576976,0x569d,0x4672,0x95,0xa0,0xf5,0x7e,0x4e,0xa0,0xb2,0x10);
// {df576976-569d-4672-95a0-f57e4ea0b210}
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
*/
typedef struct IVSHMEM_MMAP
{
IVSHMEM_PEERID peerID; // our peer id
IVSHMEM_SIZE size; // the size of the memory region
PVOID ptr; // pointer to the memory region
UINT16 vectors; // the number of vectors available
}
IVSHMEM_MMAP, *PIVSHMEM_MMAP;
/*
This structure is for use with the IOCTL_IVSHMEM_RING_DOORBELL IOCTL
*/
typedef struct IVSHMEM_RING
{
IVSHMEM_PEERID peerID; // the id of the peer to ring
UINT16 vector; // the doorbell to ring
}
IVSHMEM_RING, *PIVSHMEM_RING;
/*
This structure is for use with the IOCTL_IVSHMEM_REGISTER_EVENT IOCTL
Please Note:
- The IVSHMEM driver has a hard limit of 32 events.
- Events that are singleShot are released after they have been set.
- At this time repeating events are only released when the driver device
handle is closed, closing the event handle doesn't release it from the
drivers list. While this won't cause a problem in the driver, it will
cause you to run out of event slots.
*/
typedef struct IVSHMEM_EVENT
{
UINT16 vector; // the vector that triggers the event
HANDLE event; // the event to trigger
BOOLEAN singleShot; // set to TRUE if you want the driver to only trigger this event once
}
IVSHMEM_EVENT, *PIVSHMEM_EVENT;
#define IOCTL_IVSHMEM_REQUEST_PEERID CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_IVSHMEM_REQUEST_SIZE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_IVSHMEM_REQUEST_MMAP CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_IVSHMEM_RELEASE_MMAP CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_IVSHMEM_RING_DOORBELL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_IVSHMEM_REGISTER_EVENT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x805, METHOD_BUFFERED, FILE_ANY_ACCESS)

View file

@ -1,4 +1,5 @@
// clang-format off // clang-format off
#include "ivshmem.hpp"
#pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "ws2_32.lib")
#include <WinSock2.h> #include <WinSock2.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
@ -11,170 +12,69 @@
#include "Utils.hpp" #include "Utils.hpp"
// clang-format on // clang-format on
#pragma pack(push, 1)
enum class MessageType : u32 {
Resize, // tResizeMessage
Data, // tDataMessage
};
struct tMessageHeader { #include "atomic_spinlock.hpp"
MessageType type;
u32 datalen;
// data
};
struct tResizeMessage {
u32 width;
u32 height;
};
struct tTile {
u32 tile_x;
u32 tile_y;
u32 tile_width;
u32 tile_height;
// u32 tile_rgba[tile_width*tile_height]
};
struct tDataMessage {
u32 tileCount;
};
#pragma pack(pop)
struct tileRect { struct tileRect {
u32 x, y, width, height; u32 x, y, width, height;
}; };
// client for streamserver struct Test {
class cStreamClient { hazelnut::AtomicSpinlock lk{};
SOCKET tcpSocket; std::atomic<u32> sessionId {};
std::atomic<u32> pingPong{};
public:
cStreamClient() = default;
~cStreamClient() {
if(tcpSocket != -1) {
closesocket(tcpSocket);
tcpSocket = -1;
}
}
bool Connect(const char* address, int port) {
tcpSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(tcpSocket == -1) {
return false;
}
sockaddr_in clientSvc {};
clientSvc.sin_family = AF_INET;
inet_pton(AF_INET, address, &clientSvc.sin_addr.s_addr);
clientSvc.sin_port = htons(port);
if(connect(tcpSocket, (SOCKADDR*)&clientSvc, sizeof(clientSvc)) == SOCKET_ERROR) {
printf("No connection socket. Fuck you\n");
return false;
}
// disable Nagle's alrogithm
int nodelay = 1;
setsockopt(tcpSocket, IPPROTO_TCP, TCP_NODELAY, (const char*)&nodelay, sizeof(nodelay));
return true;
}
void SendResize(const tResizeMessage& resize) {
tMessageHeader header;
header.type = MessageType::Resize;
header.datalen = sizeof(tResizeMessage);
send(tcpSocket, (const char*)&header, sizeof(header), 0);
send(tcpSocket, (const char*)&resize, sizeof(resize), 0);
}
void SendData(const u32* pData, u32 width, u32 height, const std::vector<tileRect>& tiles, bool sendFull) {
tMessageHeader header;
header.type = MessageType::Data;
// header.datalen = data.get_size() * sizeof(UINT32);
header.datalen = sizeof(tDataMessage);
// send tile header
// bool sendFull = false;
tDataMessage dm;
// if(tiles.empty()) {
// sendFull = true;
// }
if(sendFull) {
dm.tileCount = 1;
} else {
dm.tileCount = (u32)tiles.size();
}
/*if(tiles.empty()) {
// Send the full screen as a "tile"
dm.tileCount = 1;
sendFull = true;
} else {
}*/
// we have writev() at home
// writev() at home:
send(tcpSocket, (const char*)&header, sizeof(header), 0);
send(tcpSocket, (const char*)&dm, sizeof(dm), 0);
// send each tile
if(!sendFull) {
for(auto& tile : tiles) {
tTile tile_wire { tile.x, tile.y, tile.width, tile.height };
#if 0
std::vector<u32> tileData;
tileData.resize(tile.width * tile.height);
for(u32 y = 0; y < tile.height; ++y) {
auto* pTileLineStart = &pData[(tile.y + y) * width + tile.x];
memcpy(&tileData[y * tile.width], pTileLineStart, tile.width * sizeof(u32));
}
#endif
// send header
send(tcpSocket, (const char*)&tile_wire, (i32)sizeof(tile_wire), 0);
// send(tcpSocket, (const char*)&tileData[0], (i32)tileData.size() * 4, 0);
// send data now
/*for (auto y = tile.y; y < tile.y + tile.height; ++y) {
auto* pTileLineStart = &pData[y * width + tile.x];
send(tcpSocket, (const char*)pTileLineStart, tile.width * sizeof(UINT32), 0);
}*/
}
} else {
tTile tDummyTile { 0, 0, width, height };
send(tcpSocket, (const char*)&tDummyTile, sizeof(tDummyTile), 0);
// send(tcpSocket, (const char*)&pData[0], (i32)((width * height) * sizeof(u32)), 0);
}
send(tcpSocket, (const char*)&pData[0], (i32)((width * height) * sizeof(u32)), 0);
// send(tcpSocket, (const char*)&data.data()[0], data.get_size() * sizeof(UINT32), 0);
}
}; };
int main(int argc, char** argv) { int main(int argc, char** argv) {
WSADATA data; hazelnut::IvshmemDevice dev;
if(WSAStartup(MAKEWORD(2, 2), &data) != NO_ERROR) {
if(!dev.Open()) {
printf("Failed to open ivshmem device\n");
return 1; return 1;
} }
cStreamClient client; auto size = dev.GetSize();
auto ptr = (u32*)dev.GetPointer();
if(!client.Connect("192.168.1.149", 9438)) { printf("opened a %zu MB large ivshmem\n", (size / (1024 * 1024)));
printf("conn failed\n");
return 1; // wipe the first 1mb
memset(&ptr[0], 0, 1*(1024*1024));
printf("wiped memory\n");
// Setup a test struct at the start of ivshmem
auto* pTest = new(&ptr[0]) Test;
// reset pingpong counter
pTest->pingPong.store(0);
u32 tries = 0;
u32 curTries = 10;
pTest->sessionId.store(rand());
while(true) {
// lock
{
auto guard = pTest->lk.lock();
Sleep(5);
}
printf("pingpong %u\n", pTest->pingPong.load());
if(tries++ == curTries) {
tries = 0;
pTest->pingPong.fetch_add(1);
}
} }
return 0;
#if 0
printf("Hazelnut agent\n"); printf("Hazelnut agent\n");
// Create a capture interface // Create a capture interface
@ -216,8 +116,7 @@ int main(int argc, char** argv) {
continue; continue;
} }
// send that to the server
client.SendData(framebuffer.pFramebuffer, framebuffer.width, framebuffer.height, tiles, firstFrame == false);
if(firstFrame) if(firstFrame)
firstFrame = false; firstFrame = false;
@ -227,16 +126,12 @@ int main(int argc, char** argv) {
diff = capture->GetDiffInformation(); diff = capture->GetDiffInformation();
firstFrame = true; firstFrame = true;
client.SendResize({ framebuffer.width, framebuffer.height });
// send empty frame
tiles.clear();
client.SendData(framebuffer.pFramebuffer, framebuffer.width, framebuffer.height, tiles, true);
} else { } else {
printf("Failed to capture\n"); printf("Failed to capture\n");
break; break;
} }
} }
#endif
return 0; return 0;
} }

3
shared/README.md Normal file
View file

@ -0,0 +1,3 @@
# shared code
This is shared between the two ends. It is cross platform C++20.

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <cstddef>
// common types // common types
using u8 = std::uint8_t; using u8 = std::uint8_t;

View file

@ -0,0 +1,49 @@
#pragma once
#include <atomic>
#include <cstdio> //debugging
#include "Utils.hpp"
#pragma pack(push, 1)
namespace hazelnut {
/// A atomic spinlock. Can be held across threads, or virtual machines.
struct AtomicSpinlock {
struct LockGuard {
~LockGuard() { lock.unlock(); }
protected:
friend AtomicSpinlock;
LockGuard(AtomicSpinlock& lock) : lock(lock) {}
AtomicSpinlock& lock;
};
AtomicSpinlock() { __lock.store(0, std::memory_order::seq_cst); }
LockGuard lock() {
lock_manually();
return LockGuard(*this);
}
void lock_manually() {
u32 expected = 0;
while(!__lock.compare_exchange_strong(expected, 1, std::memory_order::seq_cst)) {
//printf("LOCK CONTENDED\n");
expected = 0;
}
}
bool try_lock_manually() {
u32 expected = 0;
return __lock.compare_exchange_strong(expected, 1, std::memory_order::seq_cst);
}
void unlock() { __lock.store(0, std::memory_order::seq_cst); }
std::atomic_uint32_t __lock;
};
} // namespace hazelnut
#pragma pack(pop)

3
shared/src/build_test.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/bash
set -xeuo pipefail
g++ -O3 -std=c++20 ivshmem.cpp test.cpp -o test

40
shared/src/ivshmem.cpp Normal file
View file

@ -0,0 +1,40 @@
#include "ivshmem.hpp"
#include <memory>
#ifdef _WIN32
#include "ivshmem_windows.cpp"
#elif __linux__
#include "ivshmem_linux.cpp"
#else
#error YOU fucked
#endif
namespace hazelnut {
IvshmemDevice::IvshmemDevice() {
pImpl = new Impl;
}
IvshmemDevice::~IvshmemDevice() {
Close();
delete pImpl;
}
bool IvshmemDevice::Open(const char* devName) {
return pImpl->Open(devName);
}
void IvshmemDevice::Close() {
pImpl->Close();
}
void* IvshmemDevice::GetPointer() {
return pImpl->GetPointer();
}
usize IvshmemDevice::GetSize() {
return pImpl->GetSize();
}
} // namespace hazelnut

31
shared/src/ivshmem.hpp Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include "Utils.hpp"
namespace hazelnut {
/// Cross-platform IVSHMEM.
struct IvshmemDevice {
IvshmemDevice();
// forbid copy/move
IvshmemDevice(const IvshmemDevice&) = delete;
IvshmemDevice(IvshmemDevice&&) = delete;
~IvshmemDevice();
/// Opens the device. On Linux [devName] MUST point to a SHMEM file.
bool Open(const char* devName = nullptr);
void Close();
/// Gets pointer to IVSHMEM memory.
void* GetPointer();
usize GetSize();
private:
struct Impl;
Impl* pImpl {};
};
} // namespace hazelnut

View file

@ -0,0 +1,82 @@
#include <dirent.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include "Utils.hpp"
namespace hazelnut {
struct IvshmemDevice::Impl {
int fd;
void* pMap = nullptr;
usize size = 0;
bool OpenDevice(const char* devName) {
// devName is not optional on Linux.
if(devName == nullptr)
return false;
int devFd = open(devName, O_RDWR, static_cast<mode_t>(0600));
if(devFd == -1)
return false;
fd = devFd;
return true;
}
bool MapMemory() {
// Device was opened, let's map memory
struct stat st {};
if(fstat(fd, &st) != 0) {
return false;
}
usize devSize = st.st_size;
void* map = mmap(0, devSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(map == MAP_FAILED) {
printf("map failed\n");
return false;
}
pMap = map;
size = devSize;
return true;
}
void UnmapMemory() {
// Unmap memory.
if(pMap)
munmap(pMap, size);
}
bool Open(const char* pDevName) {
if(!OpenDevice(pDevName))
return false;
if(!MapMemory()) {
Close();
return false;
}
return true; // mapped
}
void Close() {
UnmapMemory();
close(fd);
fd = -1;
}
void* GetPointer() { return pMap; }
usize GetSize() { return size; }
};
} // namespace hazelnut

View file

@ -0,0 +1,40 @@
#pragma once
#include <atomic>
#include "atomic_spinlock.hpp"
namespace hazelnut {
#pragma pack(push, 4)
/// Header for Hazelnut ivshmem. At 0x0 in the ivshmem memory space.
struct alignas(4096) IvshHeader {
std::atomic<u32> magic;
// When this lock is held by the host, the host can read the rest of memory freely.
// We wait for the host to release before updating memory ourselves.
AtomicSpinlock lock;
// See the next page boundary for FrameHeader
};
/// Stored at a page boundary
struct alignas(4096) FrameHeader {
/// The serial of the frame. If this is unchanged between a
/// lock-grab-unlock cycle, the frame has not changed.
std::atomic<u32> serial;
// size
std::atomic<u32> width;
std::atomic<u32> height;
// obtain bits at the next page!
u32* bits() {
return reinterpret_cast<u32*>(this + 1);
}
};
#pragma pack(pop)
}

View file

@ -0,0 +1,168 @@
// clang-format off
#include <windows.h>
#include <setupapi.h>
// IVSHMEM ioctls
#include <virtio/ivshmem.h>
// clang-format on
namespace hazelnut {
template <class T>
struct free_deleter {
void operator()(T* ptr) {
if(ptr)
free(ptr);
}
};
// DOES NOT UNBLESS WITH ->~T() !!!
template <class T>
using unique_malloc_ptr = std::unique_ptr<T, free_deleter<T>>;
// does not bless with placement new
template <class T>
unique_malloc_ptr<T> unique_malloc(usize len) {
return { reinterpret_cast<T*>(malloc(len)), free_deleter<T> {} };
}
struct scoped_devinfo {
scoped_devinfo(HDEVINFO hDevInfo) : hDevInfo(hDevInfo) {}
~scoped_devinfo() { SetupDiDestroyDeviceInfoList(hDevInfo); }
scoped_devinfo(const scoped_devinfo&) = delete;
scoped_devinfo(scoped_devinfo&&) = delete;
operator HDEVINFO() { return hDevInfo; }
private:
HDEVINFO hDevInfo;
};
struct IvshmemDevice::Impl {
HANDLE hIvshmem { INVALID_HANDLE_VALUE };
IVSHMEM_MMAP ivshmemMmapStruct {};
bool OpenDevice() {
memset(&ivshmemMmapStruct, 0, sizeof(IVSHMEM_MMAP));
SP_DEVINFO_DATA devInfoData = {
.cbSize = sizeof(SP_DEVINFO_DATA),
};
SP_DEVICE_INTERFACE_DATA devInterfaceData { .cbSize = sizeof(SP_DEVICE_INTERFACE_DATA) };
auto hDevInfo =
scoped_devinfo { SetupDiGetClassDevsA(&GUID_DEVINTERFACE_IVSHMEM, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE) };
// just do the first one for now
SetupDiEnumDeviceInfo(hDevInfo, 0, &devInfoData);
if(auto le = GetLastError(); le != ERROR_SUCCESS && le != ERROR_NO_MORE_ITEMS) {
return false;
}
if(SetupDiEnumDeviceInterfaces(hDevInfo, &devInfoData, &GUID_DEVINTERFACE_IVSHMEM, 0, &devInterfaceData) == FALSE) {
return false;
}
DWORD dwReq = 0;
SetupDiGetDeviceInterfaceDetail(hDevInfo, &devInterfaceData, nullptr, 0, &dwReq, nullptr);
if(!dwReq) {
return false;
}
auto pData = unique_malloc<SP_DEVICE_INTERFACE_DETAIL_DATA>(dwReq);
pData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if(!SetupDiGetDeviceInterfaceDetail(hDevInfo, &devInterfaceData, pData.get(), dwReq, nullptr, nullptr)) {
return false;
}
hIvshmem = CreateFile(pData->DevicePath, 0, 0, nullptr, OPEN_EXISTING, 0, nullptr);
if(hIvshmem == INVALID_HANDLE_VALUE) {
return false;
}
return true;
}
template <class TInput, class TOutput>
bool Ioctl(u32 ctlCode, TInput& in, TOutput& out) {
// Needed because some versions of the IVSHMEM driver, notably the last one
// that works on Windows 7, assumes return length is not nullptr, and will crash
// the calling process if it is nullptr. Red Hat quality code everyone; it doesn't
// just envelope the Linux ecosystem - it even gets to take over the Windows one too!
DWORD dwDummyRetLength = 0;
if(!DeviceIoControl(hIvshmem, ctlCode, reinterpret_cast<void*>(&in), sizeof(TInput), reinterpret_cast<void*>(&out), sizeof(TOutput),
&dwDummyRetLength, nullptr)) {
return false;
}
return true;
}
// Ioctl() overload for bare
bool Ioctl(u32 ctlCode) {
DWORD dwDummyRetLength = 0;
if(!DeviceIoControl(hIvshmem, ctlCode, nullptr, 0, nullptr, 0, &dwDummyRetLength, nullptr)) {
return false;
}
return true;
}
bool MapMemory() {
// Device was opened, let's map memory
DWORD dwDummyRetLength = 0;
// Yay, one less syscall.
IVSHMEM_MMAP_CONFIG config = { .cacheMode = IVSHMEM_CACHE_WRITECOMBINED };
return Ioctl(IOCTL_IVSHMEM_REQUEST_MMAP, config, this->ivshmemMmapStruct);
}
void UnmapMemory() {
// Unmap memory.
if(ivshmemMmapStruct.ptr) {
// We do not care if this fails since the device will eventually be closed and this is done only
// on agent exit, so the map will be released by the kernel anyhow.
static_cast<void>(Ioctl(IOCTL_IVSHMEM_RELEASE_MMAP));
}
memset(&ivshmemMmapStruct, 0, sizeof(IVSHMEM_MMAP));
}
bool Open(const char* pDevName) {
if(!OpenDevice())
return false;
if(!MapMemory()) {
Close();
return false;
}
return true; // mapped
}
void Close() {
UnmapMemory();
CloseHandle(hIvshmem);
hIvshmem = INVALID_HANDLE_VALUE;
}
void* GetPointer() {
return ivshmemMmapStruct.ptr;
}
usize GetSize() {
return ivshmemMmapStruct.size;
}
};
} // namespace hazelnut

78
shared/src/test.cpp Normal file
View file

@ -0,0 +1,78 @@
#include <thread>
#include "atomic_spinlock.hpp"
#include "ivshmem.hpp"
int main(int argc, char** argv) {
struct Test {
hazelnut::AtomicSpinlock lk {};
std::atomic<u32> sessionId {};
std::atomic<u32> pingPong {};
};
hazelnut::IvshmemDevice dev;
if(!dev.Open("/dev/shm/lg-win7")) {
printf("Failed to open ivshmem device\n");
return 1;
}
auto size = dev.GetSize();
auto ptr = (u32*)dev.GetPointer();
printf("opened a %zu MB large ivshmem\n", (size / (1024 * 1024)));
auto* pTest = (Test*)ptr;
u32 last = 0;
u32 nrSame = 0;
u64 initalizedSessionId = pTest->sessionId.load();
while(true) {
//printf("%d\n", pTest->timestamp.load());
if(initalizedSessionId != pTest->sessionId.load()) {
printf("Agent restarted. Closing\n");
break;
}
// need monotonic way
#if 0
if(auto curTimestamp = pTest->timestamp.load(); curTimestamp < lastTimestamp) {
//printf("Not connected or agent crashed %d, %d\n", pTest->timestamp.load() , time(nullptr));
printf("Not connected or agent crashed. %zu\n", curTimestamp);
break;
} else {
lastTimestamp = curTimestamp;
}
#endif
// lock for a bit
{
if(pTest->lk.try_lock_manually()) {
// failed to lock
continue;
}
auto current = pTest->pingPong.load();
if(current == last) {
pTest->lk.unlock();
continue;
}
nrSame = 0;
last = current;
printf("pingpong %u\n", current);
pTest->lk.unlock();
}
// allow the vm some time to do whatever it is it wants to do
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
return 0;
}

37
src/atomic_spinlock.rs Normal file
View file

@ -0,0 +1,37 @@
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
#[repr(C, packed(4))]
pub struct AtomicSpinlock {
lock: AtomicU32
}
pub struct AtomicSpinlockGuard<'a> {
lock: &'a mut AtomicSpinlock,
}
impl Drop for AtomicSpinlockGuard {
fn drop(&mut self) {
self.lock.store(0, Ordering::SeqCst);
}
}
impl AtomicSpinlock {
fn init(&mut self) {
self.lock.store(0, Ordering::SeqCst);
}
fn lock(&mut self) -> AtomicSpinlockGuard<'_> {
loop {
match self
.sync
.compare_exchange(cur, 1, Ordering::SeqCst, Ordering::SeqCst)
{
Ok(last) => return HzLockGuard { lock: self },
Err(_) => {}
}
}
}
}

30
src/ivshmem.rs Normal file
View file

@ -0,0 +1,30 @@
//! Utilities for ivshmem
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
use crate::atomic_spinlock::AtomicSpinlock;
// at 0x0 in ivshmem map
#[repr(C, align(4096))]
pub struct HzHeader {
magic: AtomicU32,
sync: AtomicSpinlock,
// frame serial. if this is unchanged from prev. load it's not the same
frame_serial: AtomicU32,
}
impl HzHeader {
fn valid(&self) -> bool {
self.magic.load(std::sync::atomic::Ordering::Acquire) == 0xabcd1234
}
}
pub struct HzFrameHeader {
width: AtomicU32,
height: AtomicU32,
}
pub struct Ivshmem {}

View file

@ -6,6 +6,9 @@ use std::{
use byteorder::{LittleEndian, ReadBytesExt}; use byteorder::{LittleEndian, ReadBytesExt};
use minifb::{Window, WindowOptions}; use minifb::{Window, WindowOptions};
mod atomic_spinlock;
mod ivshmem;
#[derive(Debug)] #[derive(Debug)]
enum Message { enum Message {
Resize { width: u32, height: u32 }, Resize { width: u32, height: u32 },