Ah, sorry I missed that it was set to auto-start.
To implement server side of deviodrv is unfortunately not very intuitive because the goal was more to optimize performance than to make it easy to interact with. But basically, you first open a "file" called \\?\deviodrv\filename with FILE_FLAG_OVERLAPPED to get a server side handle. Then you allocate memory and lock it in kernel mode by calling IOCTL_DEVIODRV_LOCK_MEMORY specifying a "tag" as input buffer and the buffer you want to lock as output buffer. (I use the user mode address to the buffer as tag, to make it simple. It needs to match later calls and in case you serve multiple requests simultaneously each buffer needs a unique tag, so this address is an easy way to solve that.) Additionally, you specify an OVERLAPPED structure with an event in the DeviceIoControl call because this call will not return as successfully completed, it should return false and GetLastError should return ERROR_IO_PENDING so that the buffer keeps being locked in memory.
Then, the server loop calls IOCTL_DEVIODRV_EXCHANGE_IO to get a request from the driver. You pass the "tag" that you used in memory lock call as input buffer and NULL as output buffer. You also need another OVERLAPPED structure with another event object. To get the first request, call GetOverlappedResult, which will block until request is finished. If this fails and GetLastError returns ERROR_INSUFFICIENT_BUFFER a larger buffer is needed. Then finish the lock call by calling GetOverlappedResult on the memory lock OVERLAPPED structure, allocate a larger buffer and repeat.
After successful completion of IOCTL_DEVIODRV_EXCHANGE_IO you have the devio protocol header and data in the locked buffer. Then do what is needed with it, fill in the buffer with results and at the next call to IOCTL_DEVIODRV_EXCHANGE_IO the driver will first look at this data and complete the relevant request.
The header format is slightly different in that there is a IMDPROXY_DEVIODRV_BUFFER_HEADER before the usual header. It contains additional fields for reliable asynchronous operation. It is supposed to be opaque to the user mode server end, just keep it as it was filled by the driver for next call to the driver so that it can complete the corresponding request in case there are several outstanding requests.