Files
yttrium-virtio-gpu/vioserial/sys/Control.c
T
Martin Drab 284a7bb964 [vioser]: Do not wait infinitely when power down or removal is imminent
This commit prevents the Virtio Serial Driver from potentially infinitely waiting for the hypervisor when switching to the "WdfPowerDeviceD3Final" state (that happens when the system is being shut down, or the device is being removed). In such case, the XxxEvtDeviceD0Exit callback routine must finish within certain time interval. That is achieved by enhancing VIOSerialSendCtrlMsg by an extra parameter determining whether long waits are possible.

Signed-off-By: Martin Drab <[email protected]>
2023-10-26 11:54:22 +02:00

227 lines
6.8 KiB
C

#include "precomp.h"
#include "vioser.h"
#include "public.h"
#if defined(EVENT_TRACING)
#include "Control.tmh"
#endif
static
VOID
VIOSerialHandleCtrlMsg(
IN WDFDEVICE Device,
IN PPORT_BUFFER buf
);
#define CTRL_MSG_INITIAL_WAIT (-1000000LL)
#define CTRL_MSG_NORMAL_WAIT (-10000000LL)
VOID
VIOSerialSendCtrlMsg(
IN WDFDEVICE Device,
IN ULONG id,
IN USHORT event,
IN USHORT value,
IN BOOLEAN LongWaitAllowed
)
{
struct VirtIOBufferDescriptor sg;
struct virtqueue *vq;
UINT len;
PPORTS_DEVICE pContext = GetPortsDevice(Device);
VIRTIO_CONSOLE_CONTROL *cpkt;
if (!pContext->isHostMultiport || !pContext->ControlDmaBlock)
{
return;
}
vq = pContext->c_ovq;
cpkt = pContext->ControlDmaBlock->get_slice(pContext->ControlDmaBlock, &sg.physAddr);
if (!cpkt) {
TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP, "<-- %s can't get a slice\n", __FUNCTION__);
return;
}
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_PNP, "--> %s vq = %p, &cmd %p\n", __FUNCTION__, vq, cpkt);
cpkt->id = id;
cpkt->event = event;
cpkt->value = value;
sg.length = sizeof(*cpkt);
WdfWaitLockAcquire(pContext->COutVqLock, NULL);
if(0 <= virtqueue_add_buf(vq, &sg, 1, 0, &cpkt, NULL, 0))
{
LARGE_INTEGER interval;
virtqueue_kick(vq);
interval.QuadPart = CTRL_MSG_INITIAL_WAIT;
while(!virtqueue_get_buf(vq, &len))
{
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_PNP, "--> %s waiting for the hypervisor (%lld ms)\n", __FUNCTION__, -interval.QuadPart / 10000);
KeDelayExecutionThread(KernelMode, FALSE, &interval);
if (!LongWaitAllowed &&
interval.QuadPart == CTRL_MSG_NORMAL_WAIT)
{
if (!virtqueue_get_buf(vq, &len))
{
TraceEvents(TRACE_LEVEL_WARNING, DBG_PNP, "--> %s log waits not possible, exiting the wait loop\n", __FUNCTION__);
}
break;
}
interval.QuadPart = CTRL_MSG_NORMAL_WAIT;
}
}
WdfWaitLockRelease(pContext->COutVqLock);
pContext->ControlDmaBlock->return_slice(pContext->ControlDmaBlock, cpkt);
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_PNP, "<-- %s\n", __FUNCTION__);
}
VOID
VIOSerialCtrlWorkHandler(
IN WDFDEVICE Device
)
{
struct virtqueue *vq;
PPORT_BUFFER buf;
UINT len;
NTSTATUS status = STATUS_SUCCESS;
PPORTS_DEVICE pContext = GetPortsDevice(Device);
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_PNP, "--> %s\n", __FUNCTION__);
vq = pContext->c_ivq;
ASSERT(vq);
WdfSpinLockAcquire(pContext->CInVqLock);
while ((buf = virtqueue_get_buf(vq, &len)))
{
WdfSpinLockRelease(pContext->CInVqLock);
buf->len = len;
buf->offset = 0;
VIOSerialHandleCtrlMsg(Device, buf);
WdfSpinLockAcquire(pContext->CInVqLock);
status = VIOSerialAddInBuf(vq, buf);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP, "%s::%d Error adding buffer to queue\n", __FUNCTION__, __LINE__);
VIOSerialFreeBuffer(buf);
}
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_PNP, "<-- %s\n", __FUNCTION__);
WdfSpinLockRelease(pContext->CInVqLock);
}
VOID
VIOSerialHandleCtrlMsg(
IN WDFDEVICE Device,
IN PPORT_BUFFER buf
)
{
PPORTS_DEVICE pContext = GetPortsDevice(Device);
PVIRTIO_CONSOLE_CONTROL cpkt;
PVIOSERIAL_PORT port;
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_PNP, "--> %s\n", __FUNCTION__);
cpkt = (PVIRTIO_CONSOLE_CONTROL)((ULONG_PTR)buf->va_buf + buf->offset);
port = VIOSerialFindPortById(Device, cpkt->id);
if (!port && (cpkt->event != VIRTIO_CONSOLE_PORT_ADD))
{
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_PNP, "Invalid index %u in control packet\n", cpkt->id);
}
switch (cpkt->event)
{
case VIRTIO_CONSOLE_PORT_ADD:
if (port)
{
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_PNP, "VIRTIO_CONSOLE_PORT_ADD id = %d\n", cpkt->id);
break;
}
if (cpkt->id >= pContext->consoleConfig.max_nr_ports)
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP, "Out-of-bound id %u\n", cpkt->id);
break;
}
VIOSerialAddPort(Device, cpkt->id);
break;
case VIRTIO_CONSOLE_PORT_REMOVE:
if (!port)
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP, "VIRTIO_CONSOLE_PORT_REMOVE invalid id = %d\n", cpkt->id);
break;
}
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_PNP, "VIRTIO_CONSOLE_PORT_REMOVE id = %d\n", cpkt->id);
VIOSerialRemovePort(Device, port);
break;
case VIRTIO_CONSOLE_CONSOLE_PORT:
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_PNP,
"VIRTIO_CONSOLE_CONSOLE_PORT id = %d value = %u\n", cpkt->id, cpkt->value);
if (port && cpkt->value)
{
VIOSerialInitPortConsole(Device,port);
}
break;
case VIRTIO_CONSOLE_RESIZE:
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_PNP, "VIRTIO_CONSOLE_RESIZE id = %d\n", cpkt->id);
break;
case VIRTIO_CONSOLE_PORT_OPEN:
if (port)
{
BOOLEAN Connected = (BOOLEAN)cpkt->value;
TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP, "VIRTIO_CONSOLE_PORT_OPEN id = %d, HostConnected = %d\n", cpkt->id, Connected);
if (port->HostConnected != Connected)
{
VIOSerialPortPnpNotify(Device, port, Connected);
}
// Someone is listening. Trigger a check to see if we have
// something waiting to be told.
if (port->HostConnected)
{
WDF_INTERRUPT_INFO info;
WDFINTERRUPT *interrupt;
WDF_INTERRUPT_INFO_INIT(&info);
WdfInterruptGetInfo(pContext->QueuesInterrupt, &info);
// Check if MSI is enabled and notify the right interrupt.
interrupt = (info.Vector == 0) ? &pContext->WdfInterrupt :
&pContext->QueuesInterrupt;
WdfInterruptQueueDpcForIsr(*interrupt);
}
}
else
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP, "VIRTIO_CONSOLE_PORT_OPEN invalid id = %d\n", cpkt->id);
}
break;
case VIRTIO_CONSOLE_PORT_NAME:
if (port)
{
VIOSerialPortCreateName(Device, port, buf);
}
break;
default:
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_PNP, "%s UNKNOWN event = %d\n", __FUNCTION__, cpkt->event);
}
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_PNP, "<-- %s\n", __FUNCTION__);
}