From 1724de8a4312fcdb59f57e2e8738c624ce902a66 Mon Sep 17 00:00:00 2001 From: Alon Levy Date: Mon, 23 Aug 2010 14:50:41 +0300 Subject: vdservice: replace vdiport device with virtio-serial device replaced vdiport device by virtio-serial device which is also an easy to use stream between guest and host but is already in qemu. * VDIPortBuffer split off from VDIPort * use setupapi to get device path * setupapi.lib dependency added, magic GUID instead of a magic filename * retry several times to open device, fixes startup race between driver initialization and service start on boot. * limit writes to device, a limitation of current windows driver. * virtio-serial uses overlapped structure and events for async read/write instead of vdi_port special event. --- vdservice/vdi_port.cpp | 259 ++++++++++++++++++++++++++++++++------------- vdservice/vdi_port.h | 26 +++-- vdservice/vdservice.cpp | 23 ++-- vdservice/vdservice.vcproj | 4 +- 4 files changed, 222 insertions(+), 90 deletions(-) (limited to 'vdservice') diff --git a/vdservice/vdi_port.cpp b/vdservice/vdi_port.cpp index 4c0a99d..0d5c0e1 100644 --- a/vdservice/vdi_port.cpp +++ b/vdservice/vdi_port.cpp @@ -15,35 +15,35 @@ along with this program. If not, see . */ +#include +#include #include "stdio.h" #include "vdi_port.h" #include "vdlog.h" -#define VDI_PORT_DEV_NAME TEXT("\\\\.\\VDIPort") -#define FILE_DEVICE_UNKNOWN 0x00000022 -#define METHOD_BUFFERED 0 -#define FILE_ANY_ACCESS 0 +const GUID GUID_VIOSERIAL_PORT = + {0x6fde7521, 0x1b65, 0x48ae, 0xb6, 0x28, 0x80, 0xbe, 0x62, 0x1, 0x60, 0x26}; -#define CTL_CODE(DeviceType, Function, Method, Access) ( \ - ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \ -) +// Current limitation of virtio-serial windows driver (RHBZ 617000) +#define VIOSERIAL_PORT_MAX_WRITE_BYTES 2048 -#define FIRST_AVAIL_IO_FUNC 0x800 -#define RED_TUNNEL_CTL_FUNC FIRST_AVAIL_IO_FUNC - -#define IOCTL_RED_TUNNEL_SET_EVENT \ - CTL_CODE(FILE_DEVICE_UNKNOWN, RED_TUNNEL_CTL_FUNC, METHOD_BUFFERED, FILE_ANY_ACCESS) +// Retry initial connection to device. On boot when vdservice is started the device is +// not immediately available (takes 2 seconds, 30 is for extreme load). +#define VIOSERIAL_PORT_DEVICE_OPEN_MAX_RETRIES 30 +#define VIOSERIAL_PORT_DEVICE_OPEN_RETRY_INTERVAL_MS 1000 #define MIN(a, b) ((a) > (b) ? (b) : (a)) +VDIPort* VDIPort::_singleton; + VDIPort::VDIPort() : _handle (INVALID_HANDLE_VALUE) - , _event (NULL) - , _write_start (_write_ring) - , _write_end (_write_ring) - , _read_start (_read_ring) - , _read_end (_read_ring) { + ZeroMemory(&_write, offsetof(VDIPortBuffer, ring)); + _write.start = _write.end = _write.ring; + ZeroMemory(&_read, offsetof(VDIPortBuffer, ring)); + _read.start = _read.end = _read.ring; + _singleton = this; } VDIPort::~VDIPort() @@ -51,77 +51,172 @@ VDIPort::~VDIPort() if (_handle != INVALID_HANDLE_VALUE) { CloseHandle(_handle); } - if (_event) { - CloseHandle(_event); + if (_read.overlap.hEvent) { + CloseHandle(_read.overlap.hEvent); + } + if (_write.overlap.hEvent) { + CloseHandle(_write.overlap.hEvent); + } +} + +//Based on device.cpp from vioserial test app +//FIXME: remove this call & lib? +PTCHAR get_device_path(IN LPGUID interface_guid) +{ + HDEVINFO dev_info; + SP_DEVICE_INTERFACE_DATA dev_interface; + PSP_DEVICE_INTERFACE_DETAIL_DATA dev_interface_detail = NULL; + ULONG len, req_len = 0; + + dev_info = SetupDiGetClassDevs(interface_guid, NULL, NULL, + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (dev_info == INVALID_HANDLE_VALUE) { + vd_printf("Cannot get class devices"); + return NULL; + } + dev_interface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + if (!SetupDiEnumDeviceInterfaces(dev_info, 0, interface_guid, 0, &dev_interface)) { + vd_printf("Cannot get enumerate device interfaces"); + SetupDiDestroyDeviceInfoList(dev_info); + return NULL; } + SetupDiGetDeviceInterfaceDetail(dev_info, &dev_interface, NULL, 0, &req_len, NULL); + dev_interface_detail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)LocalAlloc(LMEM_FIXED, req_len); + if (dev_interface_detail == NULL) { + vd_printf("Cannot allocate memory"); + SetupDiDestroyDeviceInfoList(dev_info); + return NULL; + } + dev_interface_detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + len = req_len; + if (!SetupDiGetDeviceInterfaceDetail(dev_info, &dev_interface, dev_interface_detail, len, + &req_len, NULL)) { + vd_printf("Cannot get device interface details.\n"); + SetupDiDestroyDeviceInfoList(dev_info); + LocalFree(dev_interface_detail); + return NULL; + } + return dev_interface_detail->DevicePath; } bool VDIPort::init() { - DWORD io_ret_len; - _handle = CreateFile(VDI_PORT_DEV_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, - OPEN_EXISTING, 0, NULL); + PTCHAR path = NULL; + + for (int retry = 0; retry < VIOSERIAL_PORT_DEVICE_OPEN_MAX_RETRIES && path == NULL; retry++) { + if (path = get_device_path((LPGUID)&GUID_VIOSERIAL_PORT)) { + break; + } + Sleep(VIOSERIAL_PORT_DEVICE_OPEN_RETRY_INTERVAL_MS); + } + if (path == NULL) { + vd_printf("GetDevicePath failed - device/driver missing?"); + return false; + } + _handle = CreateFile(path, GENERIC_READ | GENERIC_WRITE , 0, NULL, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (_handle == INVALID_HANDLE_VALUE) { vd_printf("CreateFile() failed: %u", GetLastError()); return false; } - _event = CreateEvent(NULL, FALSE, FALSE, NULL); - if (_event == NULL) { + _write.overlap.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (_write.overlap.hEvent == NULL) { vd_printf("CreateEvent() failed: %u", GetLastError()); return false; } - if (!DeviceIoControl(_handle, IOCTL_RED_TUNNEL_SET_EVENT, &_event, sizeof(_event), - NULL, 0, &io_ret_len, NULL)) { - vd_printf("DeviceIoControl() failed: %u", GetLastError()); + _read.overlap.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (_read.overlap.hEvent == NULL) { + vd_printf("CreateEvent() failed: %u", GetLastError()); return false; } return true; } +size_t VDIPort::write_ring_free_space() +{ + return (BUF_SIZE + _write.start - _write.end - 1) % BUF_SIZE; +} + size_t VDIPort::ring_write(const void* buf, size_t size) { - size_t free_size = (BUF_SIZE + _write_start - _write_end - 1) % BUF_SIZE; + size_t free_size = (BUF_SIZE + _write.start - _write.end - 1) % BUF_SIZE; size_t n; if (size > free_size) { size = free_size; } - if (_write_end < _write_start) { - memcpy(_write_end, buf, size); + if (_write.end < _write.start) { + memcpy(_write.end, buf, size); } else { - n = MIN(size, (size_t)(&_write_ring[BUF_SIZE] - _write_end)); - memcpy(_write_end, buf, n); + n = MIN(size, (size_t)(&_write.ring[BUF_SIZE] - _write.end)); + memcpy(_write.end, buf, n); if (size > n) { - memcpy(_write_ring, (uint8_t*)buf + n, size - n); + memcpy(_write.ring, (uint8_t*)buf + n, size - n); } } - _write_end = _write_ring + (_write_end - _write_ring + size) % BUF_SIZE; + _write.end = _write.ring + (_write.end - _write.ring + size) % BUF_SIZE; return size; } int VDIPort::write() { int size; - int n; + int ret; - if (_write_start == _write_end) { + //FIXME: return VDI_PORT_NO_DATA + if (_write.start == _write.end) { return 0; } - if (_write_start < _write_end) { - size = (int)(_write_end - _write_start); - } else { - size = (int)(&_write_ring[BUF_SIZE] - _write_start); + if (!_write.pending) { + if (_write.start < _write.end) { + size = (int)(_write.end - _write.start); + } else { + size = (int)(&_write.ring[BUF_SIZE] - _write.start); + } + size = MIN(size, VIOSERIAL_PORT_MAX_WRITE_BYTES); + _write.pending = true; + if (WriteFile(_handle, _write.start, size, NULL, &_write.overlap)) { + write_completion(); + } if (GetLastError() != ERROR_IO_PENDING) { + return handle_error(); + } } - if (!WriteFile(_handle, _write_start, size, (LPDWORD)&n, NULL)) { - return handle_error(); + ret = _write.bytes; + _write.bytes = 0; + return ret; +} + +void VDIPort::write_completion() +{ + DWORD bytes; + + if (!_write.pending) { + return; } - _write_start = _write_ring + (_write_start - _write_ring + n) % BUF_SIZE; - return n; + if (!GetOverlappedResult(_handle, &_write.overlap, &bytes, FALSE)) { + vd_printf("GetOverlappedResult failed: %u", GetLastError()); + return; + } + _write.start = _write.ring + (_write.start - _write.ring + bytes) % BUF_SIZE; + _write.bytes = bytes; + _write.pending = false; } size_t VDIPort::read_ring_size() { - return (BUF_SIZE + _read_end - _read_start) % BUF_SIZE; + return (BUF_SIZE + _read.end - _read.start) % BUF_SIZE; +} + +size_t VDIPort::read_ring_continuous_remaining_size() +{ + DWORD size; + + if (_read.start <= _read.end) { + size = MIN(BUF_SIZE - 1, (int)(&_read.ring[BUF_SIZE] - _read.end)); + } else { + size = (DWORD)(_read.start - _read.end - 1); + } + return size; } size_t VDIPort::ring_read(void* buf, size_t size) @@ -129,45 +224,67 @@ size_t VDIPort::ring_read(void* buf, size_t size) size_t n; size_t m = 0; - if (_read_start == _read_end) { + if (_read.start == _read.end) { return 0; } - if (_read_start < _read_end) { - n = MIN(size, (size_t)(_read_end - _read_start)); - memcpy(buf, _read_start, n); + if (_read.start < _read.end) { + n = MIN(size, (size_t)(_read.end - _read.start)); + memcpy(buf, _read.start, n); } else { - n = MIN(size, (size_t)(&_read_ring[BUF_SIZE] - _read_start)); - memcpy(buf, _read_start, n); + n = MIN(size, (size_t)(&_read.ring[BUF_SIZE] - _read.start)); + memcpy(buf, _read.start, n); if (size > n) { - m = MIN(size - n, (size_t)(_read_end - _read_ring)); - memcpy((uint8_t*)buf + n, _read_ring, m); + m = MIN(size - n, (size_t)(_read.end - _read.ring)); + memcpy((uint8_t*)buf + n, _read.ring, m); } } - _read_start = _read_ring + (_read_start - _read_ring + n + m) % BUF_SIZE; + _read.start = _read.ring + (_read.start - _read.ring + n + m) % BUF_SIZE; return n + m; } int VDIPort::read() { int size; - int n; + int ret; - if ((_read_end - _read_ring + 1) % BUF_SIZE == _read_start - _read_ring) { - return 0; - } - if (_read_start == _read_end) { - _read_start = _read_end = _read_ring; - } - if (_read_start <= _read_end) { - size = MIN(BUF_SIZE - 1, (int)(&_read_ring[BUF_SIZE] - _read_end)); - } else { - size = (int)(_read_start - _read_end - 1); + if (!_read.pending) { + //FIXME: read_ring_continuous_remaining_size? return VDI_PORT_BUFFER_FULL + if ((_read.end - _read.ring + 1) % BUF_SIZE == _read.start - _read.ring) { + vd_printf("DEBUG: buffer full"); + return 0; + } + if (_read.start == _read.end) { + _read.start = _read.end = _read.ring; + } + if (_read.start <= _read.end) { + size = MIN(BUF_SIZE - 1, (int)(&_read.ring[BUF_SIZE] - _read.end)); + } else { + size = (int)(_read.start - _read.end - 1); + } + _read.pending = true; + if (ReadFile(_handle, _read.end, size, NULL, &_read.overlap)) { + read_completion(); + } else if (GetLastError() != ERROR_IO_PENDING) { + return handle_error(); + } } - if (!ReadFile(_handle, _read_end, size, (LPDWORD)&n, NULL)) { - return handle_error(); + ret = _read.bytes; + _read.bytes = 0; + return ret; +} + +void VDIPort::read_completion() +{ + DWORD bytes; + + if (!GetOverlappedResult(_handle, &_read.overlap, &bytes, FALSE) && + GetLastError() != ERROR_MORE_DATA) { + vd_printf("GetOverlappedResult failed: %u", GetLastError()); + return; } - _read_end = _read_ring + (_read_end - _read_ring + n) % BUF_SIZE; - return n; + _read.end = _read.ring + (_read.end - _read.ring + bytes) % BUF_SIZE; + _read.bytes = bytes; + _read.pending = false; } int VDIPort::handle_error() @@ -175,8 +292,8 @@ int VDIPort::handle_error() switch (GetLastError()) { case ERROR_CONNECTION_INVALID: vd_printf("port reset"); - _write_start = _write_end = _write_ring; - _read_start = _read_end = _read_ring; + _write.start = _write.end = _write.ring; + _read.start = _read.end = _read.ring; return VDI_PORT_RESET; default: vd_printf("port io failed: %u", GetLastError()); diff --git a/vdservice/vdi_port.h b/vdservice/vdi_port.h index 3af3f18..08cfee1 100644 --- a/vdservice/vdi_port.h +++ b/vdservice/vdi_port.h @@ -31,30 +31,40 @@ #define VDI_PORT_RESET -1 #define VDI_PORT_ERROR -2 +typedef struct VDIPortBuffer { + OVERLAPPED overlap; + uint8_t* start; + uint8_t* end; + bool pending; + int bytes; + uint8_t ring[BUF_SIZE]; +} VDIPortBuffer; + class VDIPort { public: VDIPort(); ~VDIPort(); bool init(); size_t ring_write(const void* buf, size_t size); + size_t write_ring_free_space(); size_t ring_read(void* buf, size_t size); size_t read_ring_size(); + size_t read_ring_continuous_remaining_size(); + HANDLE get_write_event() { return _write.overlap.hEvent; } + HANDLE get_read_event() { return _read.overlap.hEvent; } int write(); int read(); - HANDLE get_event() { return _event;} + void write_completion(); + void read_completion(); private: int handle_error(); private: + static VDIPort* _singleton; HANDLE _handle; - HANDLE _event; - uint8_t _write_ring[BUF_SIZE]; - uint8_t* _write_start; - uint8_t* _write_end; - uint8_t _read_ring[BUF_SIZE]; - uint8_t* _read_start; - uint8_t* _read_end; + VDIPortBuffer _write; + VDIPortBuffer _read; }; // Ring notes: diff --git a/vdservice/vdservice.cpp b/vdservice/vdservice.cpp index 903fff6..61f6a54 100644 --- a/vdservice/vdservice.cpp +++ b/vdservice/vdservice.cpp @@ -34,7 +34,7 @@ #define VD_AGENT_MAX_RESTARTS 10 #define VD_AGENT_RESTART_INTERVAL 3000 #define VD_AGENT_RESTART_COUNT_RESET_INTERVAL 60000 -#define VD_EVENTS_COUNT 4 +#define VD_EVENTS_COUNT 5 #define WINLOGON_FILENAME TEXT("winlogon.exe") #define CREATE_PROC_MAX_RETRIES 10 #define CREATE_PROC_INTERVAL_MS 500 @@ -399,10 +399,11 @@ bool VDService::execute() return false; } vd_printf("Connected to server"); - _events[0] = _vdi_port->get_event(); - _events[1] = _pipe_state.read.overlap.hEvent; - _events[2] = _control_event; - _events[3] = _agent_proc_info.hProcess; + _events[0] = _pipe_state.read.overlap.hEvent; + _events[1] = _control_event; + _events[2] = _vdi_port->get_read_event(); + _events[3] = _vdi_port->get_write_event(); + _events[4] = _agent_proc_info.hProcess; _chunk_size = _chunk_port = 0; read_pipe(); while (_running) { @@ -428,9 +429,7 @@ bool VDService::execute() DWORD wait_ret = WaitForMultipleObjectsEx(events_count, _events, FALSE, cont ? 0 : INFINITE, TRUE); switch (wait_ret) { - case WAIT_OBJECT_0: - break; - case WAIT_OBJECT_0 + 1: { + case WAIT_OBJECT_0 + 0: { DWORD bytes = 0; if (_pipe_connected && _pending_read) { _pending_read = false; @@ -446,10 +445,16 @@ bool VDService::execute() } break; } - case WAIT_OBJECT_0 + 2: + case WAIT_OBJECT_0 + 1: vd_printf("Control event"); break; + case WAIT_OBJECT_0 + 2: + _vdi_port->read_completion(); + break; case WAIT_OBJECT_0 + 3: + _vdi_port->write_completion(); + break; + case WAIT_OBJECT_0 + 4: vd_printf("Agent killed"); if (_system_version == SYS_VER_WIN_XP) { restart_agent(false); diff --git a/vdservice/vdservice.vcproj b/vdservice/vdservice.vcproj index 4e70a8b..85d5c8e 100644 --- a/vdservice/vdservice.vcproj +++ b/vdservice/vdservice.vcproj @@ -65,7 +65,7 @@ />