summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Levy <alevy@redhat.com>2010-08-23 14:50:41 +0300
committerAlon Levy <alevy@redhat.com>2010-08-23 18:53:49 +0300
commit1724de8a4312fcdb59f57e2e8738c624ce902a66 (patch)
treec787916e7b9a6129966ff1fe13e40b3ec1eb2db7
parenta17ccbf323768c3cb977f0f062366ba7cf7f19db (diff)
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.
-rw-r--r--vdservice/vdi_port.cpp259
-rw-r--r--vdservice/vdi_port.h26
-rw-r--r--vdservice/vdservice.cpp23
-rw-r--r--vdservice/vdservice.vcproj4
4 files changed, 222 insertions, 90 deletions
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 <http://www.gnu.org/licenses/>.
*/
+#include <windows.h>
+#include <setupapi.h>
#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 @@
/>
<Tool
Name="VCLinkerTool"
- AdditionalDependencies="WtsApi32.lib Userenv.lib Version.lib"
+ AdditionalDependencies="WtsApi32.lib Userenv.lib Version.lib setupapi.lib"
LinkIncremental="2"
GenerateDebugInformation="true"
SubSystem="1"
@@ -216,7 +216,7 @@
/>
<Tool
Name="VCLinkerTool"
- AdditionalDependencies="WtsApi32.lib Userenv.lib Version.lib"
+ AdditionalDependencies="WtsApi32.lib Userenv.lib Version.lib setupapi.lib"
LinkIncremental="1"
GenerateDebugInformation="true"
SubSystem="1"