blob: ff85d7ea0d7fa69460aa7cbe3072e24e1ae9defe [file] [log] [blame]
/*
* Copyright (c) 2016, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "pch.hpp"
#include "serial.tmh"
PAGED
_No_competing_thread_
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
SerialInitialize(
_In_ POTTMP_ADAPTER_CONTEXT AdapterContext
)
/*++
Routine Description:
SerialInitialize attempts to find and open the first COM port available, with
the assumption that it should be for the Thread device.
Arguments:
AdapterContext - handle to a OTTMP Adapter
Return Value:
NTSTATUS - A failure here will indicate the serial COM port was not able
to be opened.
--*/
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
PWSTR SymbolicLinkList = NULL;
LogFuncEntry(DRIVER_DEFAULT);
PAGED_CODE();
do
{
WDF_OBJECT_ATTRIBUTES attr = { 0 };
WDF_WORKITEM_CONFIG config = { 0 };
//
// Send Queue Variables
//
InitializeListHead(&AdapterContext->SendQueue);
AdapterContext->SendQueueRunning = false;
WDF_OBJECT_ATTRIBUTES_INIT(&attr);
attr.ParentObject = AdapterContext->Device;
status = WdfSpinLockCreate(&attr, &AdapterContext->SendLock);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "WdfSpinLockCreate(lockSend) failed %!STATUS!", status);
break;
}
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attr, WDF_DEVICE_INFO);
attr.ParentObject = AdapterContext->Device;
WDF_WORKITEM_CONFIG_INIT(&config, SerialSendLoop);
status = WdfWorkItemCreate(&config, &attr, &AdapterContext->SendWorkItem);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "WdfWorkItemCreate(SerialSendLoop) failed %!STATUS!", status);
break;
}
GetWdfDeviceInfo(AdapterContext->SendWorkItem)->AdapterContext = AdapterContext;
//
// Receive Variables
//
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attr, WDF_DEVICE_INFO);
attr.ParentObject = AdapterContext->Device;
WDF_WORKITEM_CONFIG_INIT(&config, SerialRecvLoop);
status = WdfWorkItemCreate(&config, &attr, &AdapterContext->RecvWorkItem);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "WdfWorkItemCreate(SerialRecvLoop) failed %!STATUS!", status);
break;
}
GetWdfDeviceInfo(AdapterContext->RecvWorkItem)->AdapterContext = AdapterContext;
// Query the system for device with SERIAL interface
status =
IoGetDeviceInterfaces(
&GUID_DEVINTERFACE_COMPORT,
NULL,
0,
&SymbolicLinkList // List of symbolic names; separate by NULL, EOL with NULL+NULL.
);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "IoGetDeviceInterfaces failed %!STATUS!", status);
break;
}
// Make sure there is a COM port found
NT_ASSERT(SymbolicLinkList);
if (*SymbolicLinkList == NULL) {
status = STATUS_DEVICE_NOT_CONNECTED;
LogError(DRIVER_DEFAULT, "No COM ports found!");
break;
}
#if DBG
for (PCWSTR sym = SymbolicLinkList; *sym != NULL; sym += wcslen(sym) + 1)
{
LogVerbose(DRIVER_DEFAULT, "Symbolic Name found: %ws", sym);
}
#endif
// Try to open each serial port until we get that one works or we exhaust them all
for (PCWSTR sym = SymbolicLinkList; *sym != NULL; sym += wcslen(sym) + 1)
{
// Initialize the target
status = SerialInitializeTarget(AdapterContext, sym);
// Break on success
if (NT_SUCCESS(status)) {
break;
}
}
} while (false);
// Clean up on failure
if (!NT_SUCCESS(status)) {
SerialUninitialize(AdapterContext);
}
if (SymbolicLinkList) {
ExFreePool(SymbolicLinkList);
SymbolicLinkList = NULL;
}
LogFuncExitNT(DRIVER_DEFAULT, status);
return status;
}
PAGED
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
SerialUninitialize(
_In_ POTTMP_ADAPTER_CONTEXT AdapterContext
)
/*++
Routine Description:
SerialUninitialize cleans up any cached Wdf IoTarget created from SerialInitialize.
Arguments:
AdapterContext - handle to a OTTMP Adapter
--*/
{
LogFuncEntry(DRIVER_DEFAULT);
PAGED_CODE();
// TODO - Clean up work item
// TODO - Clean up send queue
SerialUninitializeTarget(AdapterContext);
if (AdapterContext->RecvWorkItem) {
WdfWorkItemFlush(AdapterContext->RecvWorkItem);
}
LogFuncExit(DRIVER_DEFAULT);
}
PAGED
_No_competing_thread_
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
SerialInitializeTarget(
_In_ POTTMP_ADAPTER_CONTEXT AdapterContext,
_In_ PCWSTR TargetName
)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
WDFIOTARGET tempTarget = WDF_NO_HANDLE;
LogFuncEntry(DRIVER_DEFAULT);
PAGED_CODE();
do
{
DECLARE_UNICODE_STRING_SIZE(PortName, 64); // Maximum name length of the device path to a serial port
WDF_IO_TARGET_OPEN_PARAMS openParams = { 0 };
WDF_OBJECT_ATTRIBUTES attr = { 0 };
// Create the Wdf IoTarget
status = WdfIoTargetCreate(AdapterContext->Device, WDF_NO_OBJECT_ATTRIBUTES, &tempTarget);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "WdfIoTargetCreate failed %!STATUS!", status);
break;
}
// Try the COM port
LogInfo(DRIVER_DEFAULT, "Opening device: %ws", TargetName);
RtlInitUnicodeString(&PortName, TargetName);
WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME(
&openParams,
&PortName,
GENERIC_READ | GENERIC_WRITE);
// Open the port on the target
status = WdfIoTargetOpen(tempTarget, &openParams);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "WdfIoTargetOpen(%wZ) failed %!STATUS!", &PortName, status);
break;
}
AdapterContext->WdfIoTarget = tempTarget;
tempTarget = WDF_NO_HANDLE;
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attr, WDF_DEVICE_INFO);
attr.ParentObject = AdapterContext->Device;
status = WdfRequestCreate(&attr, AdapterContext->WdfIoTarget, &AdapterContext->RecvReadRequest);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "WdfRequestCreate failed %!STATUS!", status);
break;
}
// Try to configure the target
status = SerialConfigure(AdapterContext);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "SerialConfigure failed %!STATUS!", status);
break;
}
} while (false);
// Clean up on failure
if (!NT_SUCCESS(status)) {
SerialUninitializeTarget(AdapterContext);
}
if (tempTarget) {
WdfIoTargetClose(tempTarget);
}
LogFuncExitNT(DRIVER_DEFAULT, status);
return status;
}
PAGED
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
SerialUninitializeTarget(
_In_ POTTMP_ADAPTER_CONTEXT AdapterContext
)
/*++
Routine Description:
SerialUninitializeTarget cleans up any cached Wdf IoTarget created from SerialInitializeTarget.
Arguments:
AdapterContext - handle to a OTTMP Adapter
--*/
{
LogFuncEntry(DRIVER_DEFAULT);
PAGED_CODE();
if (AdapterContext->WdfIoTarget) {
//
// WdfIoTargetStop will cancel all the outstanding I/O and wait
// for them to complete before returning. WdfIoTargetStop with the
// action type WdfIoTargetCancelSentIo can be called at IRQL PASSIVE_LEVEL only.
//
WdfIoTargetStop(AdapterContext->WdfIoTarget, WdfIoTargetCancelSentIo);
WdfIoTargetClose(AdapterContext->WdfIoTarget);
AdapterContext->WdfIoTarget = NULL;
}
LogFuncExit(DRIVER_DEFAULT);
}
_Must_inspect_result_
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
FORCEINLINE
SerialSendIoctl(
_In_ POTTMP_ADAPTER_CONTEXT AdapterContext,
_In_ ULONG IoctlCode,
_In_opt_ PWDF_REQUEST_SEND_OPTIONS RequestOptions = NULL,
_In_opt_ PWDF_MEMORY_DESCRIPTOR InputBuffer = WDF_NO_HANDLE,
_In_opt_ PWDF_MEMORY_DESCRIPTOR OutputBuffer = WDF_NO_HANDLE,
_Out_opt_ PULONG_PTR BytesReturned = NULL
)
/*++
Routine Description:
Helper/Wrapper function for WdfIoTargetSendIoctlSynchronously.
--*/
{
return WdfIoTargetSendIoctlSynchronously(
AdapterContext->WdfIoTarget,
WDF_NO_HANDLE,
IoctlCode,
InputBuffer,
OutputBuffer,
RequestOptions,
BytesReturned);
}
PAGED
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
SerialConfigure(
_In_ POTTMP_ADAPTER_CONTEXT AdapterContext
)
/*++
Routine Description:
SerialInitialize attempts to find and open the first COM port available, with
the assumption that it should be for the Thread device.
Arguments:
AdapterContext - handle to a OTTMP Adapter
Return Value:
NTSTATUS - A failure here will indicate the serial COM port was not able
to be configured as desired.
--*/
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
WDF_MEMORY_DESCRIPTOR inputDesc;
WDF_REQUEST_SEND_OPTIONS wrso = {
sizeof(WDF_REQUEST_SEND_OPTIONS),
WDF_REQUEST_SEND_OPTION_TIMEOUT | WDF_REQUEST_SEND_OPTION_SYNCHRONOUS,
WDF_REL_TIMEOUT_IN_SEC(1) // Nothing should take more than a second to complete
};
LogFuncEntry(DRIVER_DEFAULT);
PAGED_CODE();
do
{
// Initial reset of the device
status = SerialSendIoctl(AdapterContext, IOCTL_SERIAL_RESET_DEVICE, &wrso);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "IOCTL_SERIAL_RESET_DEVICE failed %!STATUS!", status);
break;
}
// 8 bits, no parity, 1 stop bit
{
const SERIAL_LINE_CONTROL slc = { STOP_BIT_1, NO_PARITY, 8 };
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&inputDesc, (PVOID)&slc, sizeof(slc));
status = SerialSendIoctl(AdapterContext, IOCTL_SERIAL_SET_LINE_CONTROL, &wrso, &inputDesc);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "IOCTL_SERIAL_SET_LINE_CONTROL failed %!STATUS!", status );
break;
}
}
// Xon and Xoff characters
{
const SERIAL_CHARS sc = { 0, 0, 0, 0, 0x11, 0x13 };
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&inputDesc, (PVOID)&sc, sizeof(sc));
status = SerialSendIoctl(AdapterContext, IOCTL_SERIAL_SET_CHARS, &wrso, &inputDesc);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "IOCTL_SERIAL_SET_CHARS failed %!STATUS!", status);
break;
}
}
// Baud rate
{
const SERIAL_BAUD_RATE sbr = { 115200 };
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&inputDesc, (PVOID)&sbr, sizeof(sbr));
status = SerialSendIoctl(AdapterContext, IOCTL_SERIAL_SET_BAUD_RATE, &wrso, &inputDesc);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "IOCTL_SERIAL_SET_BAUD_RATE failed %!STATUS!", status);
break;
}
}
/*{
// Only send if CTS is set, Set RTS before sending
const SERIAL_HANDFLOW shf =
{
SERIAL_CTS_HANDSHAKE, SERIAL_RTS_CONTROL,
MAX_SPINEL_COMMAND_LENGTH, MAX_SPINEL_COMMAND_LENGTH
};
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&inputDesc, (PVOID)&shf, sizeof(shf));
status = SerialSendIoctl(AdapterContext, IOCTL_SERIAL_SET_HANDFLOW, &wrso, &inputDesc);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "IOCTL_SERIAL_SET_HANDFLOW failed %!STATUS!", status);
// break; Ignore for now
}
}
status = SerialSendIoctl(AdapterContext, IOCTL_SERIAL_SET_XON, &wrso);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "IOCTL_SERIAL_SET_XON failed %!STATUS!", status);
break;
}
status = SerialSendIoctl(AdapterContext, IOCTL_SERIAL_SET_RTS, &wrso);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "IOCTL_SERIAL_SET_RTS failed %!STATUS!", status);
break;
}
status = SerialSendIoctl(AdapterContext, IOCTL_SERIAL_SET_DTR, &wrso);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "IOCTL_SERIAL_SET_DTR failed %!STATUS!", status);
break;
}*/
{
const SERIAL_TIMEOUTS sto = {
1, 0, 0, // On read, only timeout if more than 1ms *between* bytes (wait forever for first byte)
1, 10 // Write times out after (1ms * n-bytes) + (10ms)
};
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&inputDesc, (PVOID)&sto, sizeof(sto));
status = SerialSendIoctl(AdapterContext, IOCTL_SERIAL_SET_TIMEOUTS, &wrso, &inputDesc);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "IOCTL_SERIAL_SET_TIMEOUTS failed %!STATUS!", status);
break;
}
}
status = SerialFlushAndCheckStatus(AdapterContext);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "SerialFlushAndCheckStatus failed %!STATUS!", status);
break;
}
} while (false);
LogFuncExitNT(DRIVER_DEFAULT, status);
return status;
}
PAGED
_IRQL_requires_(PASSIVE_LEVEL)
NTSTATUS
SerialCheckStatus(
_In_ POTTMP_ADAPTER_CONTEXT AdapterContext,
_In_ bool DataExpected
)
/*++
Routine Description:
SerialCheckStatus validates the current status of the serial COM port.
Arguments:
AdapterContext - handle to a OTTMP Adapter
Return Value:
NTSTATUS - A failure here will indicate the serial COM port is not in an
expected state.
--*/
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
WDF_MEMORY_DESCRIPTOR outputDesc;
WDF_REQUEST_SEND_OPTIONS wrso = {
sizeof(WDF_REQUEST_SEND_OPTIONS),
WDF_REQUEST_SEND_OPTION_TIMEOUT | WDF_REQUEST_SEND_OPTION_SYNCHRONOUS,
WDF_REL_TIMEOUT_IN_SEC(1) // Nothing should take more than a second to complete
};
ULONG_PTR bytesReturned = 0;
SERIAL_STATUS ss = { 0 };
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&outputDesc, (PVOID)&ss, sizeof(ss));
LogFuncEntry(DRIVER_DEFAULT);
PAGED_CODE();
// Check to ensure we are ready to send
status = SerialSendIoctl(AdapterContext, IOCTL_SERIAL_GET_COMMSTATUS, &wrso, WDF_NO_HANDLE, &outputDesc, &bytesReturned);
if (!NT_SUCCESS(status))
{
LogError(DRIVER_DEFAULT, "IOCTL_SERIAL_GET_COMMSTATUS failed %!STATUS!", status);
}
else if (bytesReturned >= sizeof(ss))
{
if (ss.HoldReasons)
{
if (ss.HoldReasons != SERIAL_TX_WAITING_FOR_CTS)
{
LogError(DRIVER_DEFAULT, "HoldReasons is wrong (should only be CTS, but is %x)", ss.HoldReasons );
status = STATUS_INVALID_DEVICE_STATE;
}
else if (!DataExpected)
{
LogError(DRIVER_DEFAULT, "Adapter already has data on init!?!?!");
status = STATUS_INVALID_STATE_TRANSITION;
}
}
if (ss.Errors)
{
LogWarning(DRIVER_DEFAULT, "Unexpected Error %x", ss.Errors);
status = STATUS_UNSUCCESSFUL;
}
}
LogFuncExitNT(DRIVER_DEFAULT, status);
return status;
}
PAGED
_IRQL_requires_(PASSIVE_LEVEL)
NTSTATUS
SerialFlushAndCheckStatus(
_In_ POTTMP_ADAPTER_CONTEXT AdapterContext
)
/*++
Routine Description:
SerialFlushAndCheckStatus flushed and validates the current status of the serial COM port.
Arguments:
AdapterContext - handle to a OTTMP Adapter
Return Value:
NTSTATUS - A failure here will indicate the serial COM port is not in an
expected state.
--*/
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
LogFuncEntry(DRIVER_DEFAULT);
PAGED_CODE();
do
{
WDF_MEMORY_DESCRIPTOR inputDesc;
WDF_REQUEST_SEND_OPTIONS wrso = {
sizeof(WDF_REQUEST_SEND_OPTIONS),
WDF_REQUEST_SEND_OPTION_TIMEOUT | WDF_REQUEST_SEND_OPTION_SYNCHRONOUS,
WDF_REL_TIMEOUT_IN_SEC(1) // Nothing should take more than a second to complete
};
const ULONG flags = SERIAL_PURGE_RXABORT | SERIAL_PURGE_RXCLEAR | SERIAL_PURGE_TXABORT | SERIAL_PURGE_TXCLEAR;
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&inputDesc, (PVOID)&flags, sizeof(flags));
status = SerialSendIoctl(AdapterContext, IOCTL_SERIAL_PURGE, &wrso, &inputDesc);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "IOCTL_SERIAL_PURGE failed %!STATUS!", status);
break;
}
status = SerialSendIoctl(AdapterContext, IOCTL_SERIAL_CLEAR_STATS, &wrso);
if (!NT_SUCCESS(status)) {
LogError(DRIVER_DEFAULT, "IOCTL_SERIAL_CLEAR_STATS failed %!STATUS!", status);
break;
}
status = SerialCheckStatus(AdapterContext, false);
for (int i = 0; !NT_SUCCESS(status) && i < 20; i++)
{
NdisMSleep(1); // just sleep enough to give up our quantum
status = SerialCheckStatus(AdapterContext, false);
}
} while (false);
LogFuncExitNT(DRIVER_DEFAULT, status);
return status;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
bool
SerialPushSend(
_In_ POTTMP_ADAPTER_CONTEXT AdapterContext,
_In_ PSERIAL_SEND_ITEM SendItem
)
{
WdfSpinLockAcquire(AdapterContext->SendLock);
// Start the work item up if it's not already running
if (!AdapterContext->SendQueueRunning)
{
LogVerbose(DRIVER_DEFAULT, "Starting Send Work Item");
AdapterContext->SendQueueRunning = true;
WdfWorkItemEnqueue(AdapterContext->SendWorkItem);
}
// Insert the new item at the end of the list
InsertTailList(&AdapterContext->SendQueue, &SendItem->Link);
WdfSpinLockRelease(AdapterContext->SendLock);
return true;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
PSERIAL_SEND_ITEM
SerialPopSend(
_In_ POTTMP_ADAPTER_CONTEXT AdapterContext
)
{
PLIST_ENTRY current = NULL;
// Grab the head of the list
// Careful, this might have gotten aborted, leaving the list empty
WdfSpinLockAcquire(AdapterContext->SendLock);
if (!IsListEmpty(&AdapterContext->SendQueue))
{
current = RemoveHeadList(&AdapterContext->SendQueue);
}
if (current == NULL)
{
// Do this under the lock, but the state is consumed outside the lock
AdapterContext->SendQueueRunning = false;
LogVerbose(DRIVER_DEFAULT, "Send Work Item Complete");
}
WdfSpinLockRelease(AdapterContext->SendLock);
return current ? CONTAINING_RECORD(current, SERIAL_SEND_ITEM, Link) : NULL;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS
SerialSendData(
_In_ POTTMP_ADAPTER_CONTEXT AdapterContext,
_In_ PNET_BUFFER_LIST NetBufferList
)
/*++
Routine Description:
SerialSendData encodes and queues up the data to be sent over the serial COM port.
Arguments:
AdapterContext - handle to a OTTMP Adapter
NetBufferLists - a single NET_BUFFER_LIST object, containing a signle NET_BUFFER for
Spinel tunnel commands.
DispatchLevel - flag indicating if we are running at dispatch or not
Return Value:
NTSTATUS - A failure here will indicate we either failed to encode or queue the data.
--*/
{
NTSTATUS status = STATUS_SUCCESS;
WDF_OBJECT_ATTRIBUTES attributes;
WDFMEMORY WdfMemBuffer = NULL;
PSERIAL_SEND_ITEM SendItem = NULL;
PUCHAR DecodedBuffer = NULL;
ULONG DecodedBufferLength = NetBufferList->FirstNetBuffer->DataLength;
ULONG EncodedBufferLength;
LogFuncEntry(DRIVER_DEFAULT);
do
{
// Get the decoded buffer from the NBL/NB. We required
// the use of contiguous buffers.
DecodedBuffer = (PUCHAR)NdisGetDataBuffer(NetBufferList->FirstNetBuffer, DecodedBufferLength, NULL, 1, 0);
if (DecodedBuffer == NULL) {
status = STATUS_INVALID_PARAMETER;
break;
}
LogVerbose(DRIVER_DEFAULT, "Sending %u decoded bytes", DecodedBufferLength);
DumpBuffer(DecodedBuffer, DecodedBufferLength);
// Calculate the buffer size required
EncodedBufferLength = HdlcComputeEncodedLength(DecodedBuffer, DecodedBufferLength);
// Allocate the memory
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.ParentObject = AdapterContext->Device;
#pragma warning(push)
#pragma warning(suppress: 28160) // Param 3 could be 0
status = WdfMemoryCreate(
&attributes,
NonPagedPoolNx,
0,
SERIAL_SEND_ITEM_SIZE + EncodedBufferLength,
&WdfMemBuffer,
(PVOID*)&SendItem
);
#pragma warning(pop)
if (!NT_SUCCESS(status)) {
LogWarning(DRIVER_DEFAULT, "WdfMemoryCreate (%u bytes) failed %!STATUS!", (SERIAL_SEND_ITEM_SIZE + EncodedBufferLength), status);
break;
}
SendItem->NetBufferList = NetBufferList;
SendItem->WdfMemory = WdfMemBuffer;
SendItem->EncodedBufferLength = EncodedBufferLength;
// Encode data
if (!HdlcEncodeBuffer(DecodedBuffer, DecodedBufferLength, SendItem->EncodedBuffer, EncodedBufferLength)) {
NT_ASSERT(FALSE); // Should never fail, unless we have a bug in the length computation
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
// Queue data to be sent out
if (!SerialPushSend(AdapterContext, SendItem)) {
status = STATUS_DEVICE_NOT_READY;
break;
}
} while (false);
if (!NT_SUCCESS(status)) {
if (WdfMemBuffer) {
WdfObjectDelete(WdfMemBuffer);
}
}
LogFuncExitNT(DRIVER_DEFAULT, status);
return status;
}
PAGED
_Function_class_(EVT_WDF_WORKITEM)
_IRQL_requires_same_
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
SerialSendLoop(
_In_ WDFWORKITEM WorkItem
)
/*++
Routine Description:
SerialSendLoop handles the actual sending of data over the serial COM port.
Arguments:
WorkItem - handle to a Wdf Device Info object for the Adapter context
--*/
{
POTTMP_ADAPTER_CONTEXT AdapterContext = GetWdfDeviceInfo(WorkItem)->AdapterContext;
WDF_REQUEST_SEND_OPTIONS wrso = {
sizeof(WDF_REQUEST_SEND_OPTIONS),
WDF_REQUEST_SEND_OPTION_TIMEOUT | WDF_REQUEST_SEND_OPTION_SYNCHRONOUS,
WDF_REL_TIMEOUT_IN_SEC(1) // Nothing should take more than a second to complete
};
WDFMEMORY_OFFSET offset = { 0 };
PSERIAL_SEND_ITEM SendItem = NULL;
WDF_MEMORY_DESCRIPTOR wmd;
NTSTATUS status;
WDF_OBJECT_ATTRIBUTES Attributes;
WDF_OBJECT_ATTRIBUTES_INIT(&Attributes);
Attributes.ParentObject = AdapterContext->Device;
LogFuncEntry(DRIVER_DEFAULT);
PAGED_CODE();
#pragma warning(push)
#pragma warning(suppress: 6387) // Param 2 is NULL
WDF_MEMORY_DESCRIPTOR_INIT_HANDLE(&wmd, NULL, &offset);
#pragma warning(pop)
while (NULL != (SendItem = SerialPopSend(AdapterContext)))
{
LogVerbose(DRIVER_DEFAULT, "Sending %u encoded bytes", SendItem->EncodedBufferLength);
DumpBuffer(SendItem->EncodedBuffer, SendItem->EncodedBufferLength);
if (SendItem->EncodedBufferLength > 0)
{
offset.BufferLength = SendItem->EncodedBufferLength;
status =
WdfMemoryCreatePreallocated(
&Attributes,
SendItem->EncodedBuffer,
SendItem->EncodedBufferLength,
&wmd.u.HandleType.Memory);
if (!NT_SUCCESS( status )) {
LogError(DRIVER_DEFAULT, "WdfIoTargetSendWriteSynchronously (%u bytes) failed %!STATUS!", SendItem->EncodedBufferLength, status);
}
else
{
// Send the buffer out
status = WdfIoTargetSendWriteSynchronously(AdapterContext->WdfIoTarget, NULL, &wmd, NULL, &wrso, NULL);
if (!NT_SUCCESS( status )) {
LogError(DRIVER_DEFAULT, "WdfIoTargetSendWriteSynchronously (%u bytes) failed %!STATUS!", SendItem->EncodedBufferLength, status);
}
WdfObjectDelete(wmd.u.HandleType.Memory);
}
}
else
{
status = STATUS_INVALID_PARAMETER;
}
// Complete the NetBufferList
SendItem->NetBufferList->Status = status;
#ifdef OTTMP_LEGACY
NdisMSendNetBufferListsComplete(AdapterContext->Adapter, SendItem->NetBufferList, 0);
#else
NetBufferListsCompleteSend(SendItem->NetBufferList);
#endif
// Hack to sleep 1 ms per 5 bytes sent
NdisMSleep(1000 * (1 + SendItem->EncodedBufferLength / 5));
// Cleanup
WdfObjectDelete(SendItem->WdfMemory);
};
LogFuncExit(DRIVER_DEFAULT);
}
PAGED
_Function_class_(EVT_WDF_WORKITEM)
_IRQL_requires_same_
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
SerialRecvLoop(
_In_ WDFWORKITEM WorkItem
)
{
POTTMP_ADAPTER_CONTEXT AdapterContext = GetWdfDeviceInfo(WorkItem)->AdapterContext;
WDFMEMORY mem = { 0 };
NTSTATUS status;
LogFuncEntry(DRIVER_DEFAULT);
PAGED_CODE();
do
{
WDFREQUEST & request = AdapterContext->RecvReadRequest;
WDF_OBJECT_ATTRIBUTES attributes;
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.ParentObject = AdapterContext->Device;
status =
WdfMemoryCreatePreallocated(
&attributes,
AdapterContext->RecvBuffer + AdapterContext->RecvBufferLength,
MAX_SPINEL_COMMAND_LENGTH,
&mem);
if (!NT_SUCCESS(status))
{
LogError(DRIVER_DEFAULT, "WdfMemoryCreateFromLookaside failed %!STATUS!", status);
break;
}
status = WdfIoTargetFormatRequestForRead(AdapterContext->WdfIoTarget, request, mem, NULL, NULL);
if (!NT_SUCCESS(status))
{
LogError(DRIVER_DEFAULT, "WdfIoTargetFormatRequestForRead failed %!STATUS!", status);
break;
}
else
{
WdfRequestSetCompletionRoutine(request, SerialRecvComplete, AdapterContext);
if (WdfRequestSend(request, AdapterContext->WdfIoTarget, WDF_NO_SEND_OPTIONS))
{
// Send succeeded, no cleanup
mem = NULL;
break;
}
status = WdfRequestGetStatus(request);
if (!NT_SUCCESS(status))
{
LogError(DRIVER_DEFAULT, "WdfRequestSend failed %!STATUS!", status);
}
WDF_REQUEST_REUSE_PARAMS reuseParams = { 0 };
WDF_REQUEST_REUSE_PARAMS_INIT(&reuseParams, WDF_REQUEST_REUSE_NO_FLAGS, STATUS_SUCCESS);
// refresh the request so it is ready to reuse
status = WdfRequestReuse(request, &reuseParams);
if (!NT_SUCCESS(status))
{
NT_ASSERT(NT_SUCCESS(status));
LogError(DRIVER_DEFAULT, "WdfRequestReuse failed %!STATUS!", status);
}
}
} while (false);
if (mem) {
WdfObjectDelete(mem);
}
LogFuncExit(DRIVER_DEFAULT);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
_When_(return==0,_At_(*pNetBufferList, __drv_allocatesMem(mem)))
_When_(return==0,_At_((*pNetBufferList)->FirstNetBuffer, __drv_allocatesMem(mem)))
NTSTATUS
SerialAllocateNetBufferList(
_In_ POTTMP_ADAPTER_CONTEXT AdapterContext,
_In_ ULONG BufferLength,
_Out_ PNET_BUFFER_LIST *pNetBufferList
)
{
NTSTATUS status = STATUS_SUCCESS;
PNET_BUFFER_LIST NetBufferList = NULL;
PNET_BUFFER NetBuffer = NULL;
do
{
#ifdef OTTMP_LEGACY
// Allocate the NetBufferList
NetBufferList = NdisAllocateNetBufferList(AdapterContext->pGlobals->hNblPool, 0, 0);
if (NetBufferList == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
// Allocate the NetBuffer
NetBufferList->FirstNetBuffer = NdisAllocateNetBufferMdlAndData(AdapterContext->pGlobals->hNbPool);
if (NetBufferList->FirstNetBuffer == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
#else
// Grab a NetBufferList from the collection
status = NetBufferListCollectionRetrieveNbls(AdapterContext->ReceiveCollection, 1, &NetBufferList);
if (!NT_SUCCESS(status))
{
break;
}
#endif
NDIS_STATUS ndisStatus = NDIS_STATUS_SUCCESS;
NetBuffer = NetBufferList->FirstNetBuffer;
// If there is no buffer allocated yet, go ahead and allocate the max
if (NET_BUFFER_DATA_LENGTH(NetBuffer) == 0)
{
// Allocate the max buffer size
ndisStatus = NdisRetreatNetBufferDataStart(NetBuffer, MAX_SPINEL_COMMAND_LENGTH, 0, NULL);
if (ndisStatus != NDIS_STATUS_SUCCESS)
{
LogError(DRIVER_DEFAULT, "NdisRetreatNetBufferDataStart failed %!NDIS_STATUS!", ndisStatus);
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
}
// By this point, we should have a NetBuffer with a contiguous memory block of MAX_SPINEL_COMMAND_LENGTH bytes,
// though it's offset could be anywhere in the buffer, from when it was previously used.
// Adjust buffer length to fit the requested length
if (NET_BUFFER_DATA_LENGTH(NetBuffer) > BufferLength)
{
NdisAdvanceNetBufferDataStart(NetBuffer, NET_BUFFER_DATA_LENGTH(NetBuffer) - BufferLength, FALSE, NULL);
}
else if (NET_BUFFER_DATA_LENGTH(NetBuffer) < BufferLength)
{
ndisStatus = NdisRetreatNetBufferDataStart(NetBuffer, BufferLength - NET_BUFFER_DATA_LENGTH(NetBuffer), 0, NULL);
NT_ASSERT(ndisStatus == NDIS_STATUS_SUCCESS);
if (ndisStatus != NDIS_STATUS_SUCCESS)
{
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
}
// Set the output
*pNetBufferList = NetBufferList;
} while (FALSE);
if (!NT_SUCCESS(status))
{
#ifdef OTTMP_LEGACY
if (NetBuffer) NdisFreeNetBuffer(NetBuffer);
if (NetBufferList) NdisFreeNetBufferList(NetBufferList);
#else
if (NetBufferList) NetBufferListsDiscardReceive(NetBufferList);
#endif
}
return status;
}
_Function_class_(EVT_WDF_REQUEST_COMPLETION_ROUTINE)
_IRQL_requires_same_
VOID
SerialRecvComplete(
_In_ WDFREQUEST Request,
_In_ WDFIOTARGET Target,
_In_ PWDF_REQUEST_COMPLETION_PARAMS Params,
_In_ WDFCONTEXT Context
)
{
POTTMP_ADAPTER_CONTEXT AdapterContext = (POTTMP_ADAPTER_CONTEXT)Context;
NTSTATUS status;
LogFuncEntry(DRIVER_DEFAULT);
UNREFERENCED_PARAMETER(Target); // Except for an assert
NT_ASSERT((Target == AdapterContext->WdfIoTarget) || (WDF_NO_HANDLE == AdapterContext->WdfIoTarget));
NT_ASSERT(Request == AdapterContext->RecvReadRequest);
WDFMEMORY mem = Params->Parameters.Read.Buffer;
NT_ASSERT(mem);
status = WdfRequestGetStatus(Request);
if (NT_SUCCESS(status))
{
NT_ASSERT(Params->Type == WdfRequestTypeRead);
NT_ASSERT(Params->Parameters.Read.Offset == 0);
size_t MemoryLength = 0;
NT_ASSERT(AdapterContext->RecvBuffer + AdapterContext->RecvBufferLength == (PUCHAR)WdfMemoryGetBuffer(mem, &MemoryLength));
UNREFERENCED_PARAMETER(MemoryLength);
LogVerbose(DRIVER_DEFAULT, "Received %u encoded bytes", (ULONG)Params->IoStatus.Information);
DumpBuffer(AdapterContext->RecvBuffer + AdapterContext->RecvBufferLength, (ULONG)Params->IoStatus.Information);
AdapterContext->RecvBufferLength += (ULONG)Params->IoStatus.Information;
// Decode and receive
ULONG ReadOffset = 0;
while (AdapterContext->RecvBufferLength > ReadOffset)
{
// Parse, validate and compute the decoded buffer size requirements
ULONG UsedEncodedBufferLength = AdapterContext->RecvBufferLength - ReadOffset;
ULONG DecodedBufferLength = 0;
bool HasGoodBuffer = false;
bool HasCompleteBuffer =
HdlcDecodeBuffer(
AdapterContext->RecvBuffer + ReadOffset,
&UsedEncodedBufferLength,
&DecodedBufferLength,
NULL,
&HasGoodBuffer);
// We should never have used more buffer than available
NT_ASSERT(UsedEncodedBufferLength <= AdapterContext->RecvBufferLength - ReadOffset);
// Did we have a complete (start and end sequence chars) buffer?
if (!HasCompleteBuffer)
{
AdapterContext->RecvBufferLength -= ReadOffset;
LogWarning(DRIVER_DEFAULT, "Buffering %u incomplete bytes", AdapterContext->RecvBufferLength);
NT_ASSERT(AdapterContext->RecvBufferLength < MAX_SPINEL_COMMAND_LENGTH);
memmove_s(AdapterContext->RecvBuffer, sizeof(AdapterContext->RecvBuffer),
AdapterContext->RecvBuffer + ReadOffset, AdapterContext->RecvBufferLength);
break;
}
else
{
// Was the buffer too short or did it's FCS not match?
if (HasGoodBuffer)
{
NT_ASSERT(UsedEncodedBufferLength <= MAX_SPINEL_COMMAND_LENGTH);
// Allocate the NetBufferList & NetBuffer to decode the data to
PNET_BUFFER_LIST NetBufferList = NULL;
status = SerialAllocateNetBufferList(AdapterContext, DecodedBufferLength, &NetBufferList);
if (NT_SUCCESS(status))
{
PNET_BUFFER NetBuffer = NetBufferList->FirstNetBuffer;
NT_ASSERT(DecodedBufferLength == NET_BUFFER_DATA_LENGTH(NetBuffer));
// Get pointer to contiguous buffer
PUCHAR DecodedBuffer = (PUCHAR)NdisGetDataBuffer(NetBuffer, DecodedBufferLength, NULL, 1, 0);
NT_ASSERT(DecodedBuffer);
if (DecodedBuffer)
{
HasCompleteBuffer =
HdlcDecodeBuffer(
AdapterContext->RecvBuffer + ReadOffset,
&UsedEncodedBufferLength,
&DecodedBufferLength,
DecodedBuffer,
&HasGoodBuffer);
NT_ASSERT(HasCompleteBuffer);
NT_ASSERT(HasGoodBuffer);
NT_ASSERT(DecodedBufferLength == NET_BUFFER_DATA_LENGTH(NetBuffer));
LogVerbose(DRIVER_DEFAULT, "Received %u decoded bytes", DecodedBufferLength);
DumpBuffer(DecodedBuffer, DecodedBufferLength);
}
else
{
status = STATUS_INVALID_PARAMETER;
}
if (NT_SUCCESS(status))
{
// Indicate up the new NBL we just created
#ifdef OTTMP_LEGACY
NdisMIndicateReceiveNetBufferLists(
AdapterContext->Adapter,
NetBufferList,
NDIS_DEFAULT_PORT_NUMBER,
1,
0
);
#else
NetBufferListsCompleteReceive(
NetBufferList,
NDIS_DEFAULT_PORT_NUMBER,
0
);
#endif
}
else
{
#ifdef OTTMP_LEGACY
NdisFreeNetBuffer(NetBuffer);
NdisFreeNetBufferList(NetBufferList);
#else
NetBufferListsDiscardReceive(NetBufferList);
#endif
}
}
}
else
{
LogWarning(DRIVER_DEFAULT, "Dropping %u bad bytes", UsedEncodedBufferLength);
DumpBuffer(AdapterContext->RecvBuffer + ReadOffset, UsedEncodedBufferLength);
}
// Skip over used data
ReadOffset += UsedEncodedBufferLength;
}
}
// We read all the buffer, so reset the length
if (AdapterContext->RecvBufferLength == ReadOffset)
{
AdapterContext->RecvBufferLength = 0;
}
}
else
{
LogError(DRIVER_DEFAULT, "Read request failed %!STATUS!", status);
}
WdfObjectDelete(mem);
if (status != STATUS_DELETE_PENDING)
{
WDF_REQUEST_REUSE_PARAMS reuseParams = { 0 };
WDF_REQUEST_REUSE_PARAMS_INIT(&reuseParams, WDF_REQUEST_REUSE_NO_FLAGS, STATUS_SUCCESS);
status = WdfRequestReuse(Request, &reuseParams);
if (!NT_SUCCESS(status))
{
NT_ASSERT(NT_SUCCESS(status));
LogError(DRIVER_DEFAULT, "WdfRequestReuse failed %!STATUS!", status);
}
LogVerbose(DRIVER_DEFAULT, "Starting recv worker");
WdfWorkItemEnqueue(AdapterContext->RecvWorkItem);
}
LogFuncExit(DRIVER_DEFAULT);
}
VOID
DumpLine(
_In_reads_bytes_(aLength) PCUCHAR aBuf,
_In_ size_t aLength
)
{
char buf[80] = {0};
char *cur = buf;
sprintf_s(cur, sizeof(buf) - (cur - buf), "|");
cur += 1;
for (size_t i = 0; i < 16; i++)
{
if (i < aLength)
{
sprintf_s(cur, sizeof(buf) - (cur - buf), " %02X", aBuf[i]);
cur += 3;
}
else
{
sprintf_s(cur, sizeof(buf) - (cur - buf), " ..");
cur += 3;
}
if (!((i + 1) % 8))
{
sprintf_s(cur, sizeof(buf) - (cur - buf), " |");
cur += 2;
}
}
sprintf_s(cur, sizeof(buf) - (cur - buf), " ");
cur += 1;
for (size_t i = 0; i < 16; i++)
{
if (i < aLength && isprint(0x7f & aBuf[i]))
{
char c = 0x7f & aBuf[i];
sprintf_s(cur, sizeof(buf) - (cur - buf), "%c", c);
cur += 1;
}
else
{
sprintf_s(cur, sizeof(buf) - (cur - buf), ".");
cur += 1;
}
}
LogVerbose(DRIVER_DEFAULT, "%s", buf);
}
VOID
DumpBuffer(
_In_reads_bytes_(aLength) PCUCHAR aBuf,
_In_ size_t aLength
)
{
for (size_t i = 0; i < aLength; i += 16)
{
DumpLine(aBuf + i, (aLength - i) < 16 ? (aLength - i) : 16);
}
}