diff options
| author | Philip Froese <[email protected]> | 2016-10-19 13:25:24 -0700 |
|---|---|---|
| committer | Philip Froese <[email protected]> | 2016-10-19 13:25:24 -0700 |
| commit | 7995677b7883bad51cbadd6bd1d1674dbd689c38 (patch) | |
| tree | bbde63ce3535c264de5202e337a86fb0606819b7 /usb/UcmTcpciCxClientSample/I2C.cpp | |
| parent | ce06de38a6daee9815dbf7eb8ddd8f335612ccc0 (diff) | |
Adding UcmTcpciCxClientSample
Diffstat (limited to 'usb/UcmTcpciCxClientSample/I2C.cpp')
| -rw-r--r-- | usb/UcmTcpciCxClientSample/I2C.cpp | 1048 |
1 files changed, 1048 insertions, 0 deletions
diff --git a/usb/UcmTcpciCxClientSample/I2C.cpp b/usb/UcmTcpciCxClientSample/I2C.cpp new file mode 100644 index 00000000..5a79fced --- /dev/null +++ b/usb/UcmTcpciCxClientSample/I2C.cpp @@ -0,0 +1,1048 @@ +/*++ + +Module Name: + + I2C.c + +Abstract: + + This file contains the definitions for I2C functions and callbacks. + + TODO: If your port controller hardware is not compliant with the Type-C Port Controller + Interface Specification in the respect that it does not use I2C, + you will need to modify this file accordingly. + +Environment: + + Kernel-mode Driver Framework + +--*/ + +#include "Driver.h" +#include "I2C.tmh" + +#ifdef ALLOC_PRAGMA +#pragma alloc_text (PAGE, I2CInitialize) +#pragma alloc_text (PAGE, I2COpen) +#pragma alloc_text (PAGE, I2CClose) +#pragma alloc_text (PAGE, I2CReadSynchronously) +#pragma alloc_text (PAGE, I2CWriteSynchronously) +#pragma alloc_text (PAGE, I2CPerformDeviceReset) +#endif + +NTSTATUS +I2CInitialize( + _In_ PDEVICE_CONTEXT DeviceContext, + _In_ WDFCMRESLIST ResourcesRaw, + _In_ WDFCMRESLIST ResourcesTranslated +) +/*++ + +Routine Description: + + Initialize the I2C resource that provides a communications channel to the + port controller hardware. + +Arguments: + + DeviceContext - Context for a framework device. + + ResourcesRaw - A handle to a framework resource-list object that identifies the raw hardware + resources that the Plug and Play manager has assigned to the device. + + ResourcesTranslated - A handle to a framework resource-list object that identifies the + translated hardware resources that the Plug and Play manager has assigned to the device. + +Return Value: + + NTSTATUS + +--*/ +{ + TRACE_FUNC_ENTRY(TRACE_I2C); + + UNREFERENCED_PARAMETER(ResourcesRaw); + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + WDF_INTERRUPT_CONFIG interruptConfig; + PCM_PARTIAL_RESOURCE_DESCRIPTOR descriptor = nullptr; + ULONG interruptIndex = 0; + BOOLEAN connFound = FALSE; + BOOLEAN interruptFound = FALSE; + ULONG resourceCount; + + // Check for I2C and Interrupt resources from the resources that PnP manager has + // allocated to our device. + resourceCount = WdfCmResourceListGetCount(ResourcesTranslated); + + for (ULONG i = 0; ((connFound == FALSE) || (interruptFound == FALSE)) && (i < resourceCount); i++) + { + descriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, i); + + switch (descriptor->Type) + { + case CmResourceTypeConnection: + // Check for I2C resource + if (descriptor->u.Connection.Class == CM_RESOURCE_CONNECTION_CLASS_SERIAL && + descriptor->u.Connection.Type == CM_RESOURCE_CONNECTION_TYPE_SERIAL_I2C) + { + DeviceContext->I2CConnectionId.LowPart = descriptor->u.Connection.IdLowPart; + DeviceContext->I2CConnectionId.HighPart = descriptor->u.Connection.IdHighPart; + + connFound = TRUE; + + TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_I2C, + "I2C resource found with connection id: 0x%llx", + DeviceContext->I2CConnectionId.QuadPart); + } + break; + + case CmResourceTypeInterrupt: + // We've found an interrupt resource. + interruptFound = TRUE; + interruptIndex = i; + TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_I2C, + "Interrupt resource found at index: %lu", interruptIndex); + break; + + default: + // We don't care about other descriptors. + break; + } + } + + // Fail if either connection or interrupt resource was not found. + if (!connFound) + { + status = STATUS_INSUFFICIENT_RESOURCES; + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "Failed finding required I2C resource. Status: %!STATUS!", status); + + goto Exit; + } + + if (!interruptFound) + { + status = STATUS_INSUFFICIENT_RESOURCES; + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "Failed finding required interrupt resource. Status: %!STATUS!", status); + + goto Exit; + } + + // The alerts from the port controller hardware will be handled in a passive ISR. + // The ISR performs hardware read and write operations which block until the hardware access is complete. + // Waiting is unacceptable at DIRQL, so we perform our ISR at PASSIVE_LEVEL. + WDF_INTERRUPT_CONFIG_INIT(&interruptConfig, OnInterruptPassiveIsr, NULL); + + interruptConfig.PassiveHandling = TRUE; + interruptConfig.InterruptTranslated = WdfCmResourceListGetDescriptor(ResourcesTranslated, interruptIndex); + interruptConfig.InterruptRaw = WdfCmResourceListGetDescriptor(ResourcesRaw, interruptIndex); + + status = WdfInterruptCreate( + DeviceContext->Device, + &interruptConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &DeviceContext->AlertInterrupt); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "WdfInterruptCreate failed. status: %!STATUS!", status); + goto Exit; + } + +Exit: + TRACE_FUNC_EXIT(TRACE_I2C); + return status; +} + +NTSTATUS +I2COpen( + _In_ PDEVICE_CONTEXT DeviceContext +) +/*++ + +Routine Description: + + This routine opens a handle to the I2C controller. + +Arguments: + + DeviceContext - a pointer to the device context + +Return Value: + + NTSTATUS + +--*/ +{ + TRACE_FUNC_ENTRY(TRACE_I2C); + + PAGED_CODE(); + + NTSTATUS status; + WDF_IO_TARGET_OPEN_PARAMS openParams; + WDF_OBJECT_ATTRIBUTES requestAttributes; + WDF_OBJECT_ATTRIBUTES workitemAttributes; + WDF_WORKITEM_CONFIG workitemConfig; + + // Create the device path using the connection ID. + DECLARE_UNICODE_STRING_SIZE(DevicePath, RESOURCE_HUB_PATH_SIZE); + + RESOURCE_HUB_CREATE_PATH_FROM_ID( + &DevicePath, + DeviceContext->I2CConnectionId.LowPart, + DeviceContext->I2CConnectionId.HighPart); + + TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_I2C, + "Opening handle to I2C target via %wZ", &DevicePath); + + status = WdfIoTargetCreate(DeviceContext->Device, WDF_NO_OBJECT_ATTRIBUTES, &DeviceContext->I2CIoTarget); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "WdfIoTargetCreate failed - %!STATUS!", status); + goto Exit; + } + + // Open a handle to the I2C controller. + WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME( + &openParams, + &DevicePath, + (GENERIC_READ | GENERIC_WRITE)); + + openParams.ShareAccess = 0; + openParams.CreateDisposition = FILE_OPEN; + openParams.FileAttributes = FILE_ATTRIBUTE_NORMAL; + + status = WdfIoTargetOpen(DeviceContext->I2CIoTarget, &openParams); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "Failed to open I2C I/O target - %!STATUS!", status); + goto Exit; + } + + // Create a WDFMEMORY object. Do call WdfMemoryAssignBuffer before use it, + status = WdfMemoryCreatePreallocated( + WDF_NO_OBJECT_ATTRIBUTES, + static_cast<PVOID>(&status), // initial value does not matter + sizeof(status), + &DeviceContext->I2CMemory); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "WdfMemoryCreatePreallocated failed with status %!STATUS!", status); + goto Exit; + } + + WDF_OBJECT_ATTRIBUTES_INIT(&requestAttributes); + requestAttributes.ParentObject = DeviceContext->I2CIoTarget; + + for (ULONG i = 0; i < I2CRequestSourceMax; i++) + { + status = WdfRequestCreate(&requestAttributes, DeviceContext->I2CIoTarget, &DeviceContext->OutgoingRequests[i]); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "WdfRequestCreate failed with status %!STATUS!", status); + goto Exit; + } + } + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&workitemAttributes, WORKITEM_CONTEXT); + workitemAttributes.ParentObject = DeviceContext->I2CIoTarget; + + WDF_WORKITEM_CONFIG_INIT(&workitemConfig, EvtWorkItemGetStatus); + status = WdfWorkItemCreate(&workitemConfig, &workitemAttributes, &DeviceContext->I2CWorkItemGetStatus); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "WdfWorkItemCreate failed with status %!STATUS!", status); + goto Exit; + } + + WDF_WORKITEM_CONFIG_INIT(&workitemConfig, EvtWorkItemGetControl); + status = WdfWorkItemCreate(&workitemConfig, &workitemAttributes, &DeviceContext->I2CWorkItemGetControl); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "WdfWorkItemCreate failed with status %!STATUS!", status); + goto Exit; + } + +Exit: + TRACE_FUNC_EXIT(TRACE_I2C); + return status; +} + +void +I2CClose( + _In_ PDEVICE_CONTEXT DeviceContext +) +/*++ + +Routine Description: + + This routine closes a handle to the I2C controller. + +Arguments: + + DeviceContext - a pointer to the device context. + +--*/ +{ + TRACE_FUNC_ENTRY(TRACE_I2C); + + PAGED_CODE(); + + if (DeviceContext->I2CIoTarget != WDF_NO_HANDLE) + { + TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_I2C, "Closing handle to I2C target"); + WdfIoTargetClose(DeviceContext->I2CIoTarget); + } + + TRACE_FUNC_EXIT(TRACE_I2C); +} + +NTSTATUS +I2CWriteAsynchronously( + _In_ PDEVICE_CONTEXT DeviceContext, + _In_ UINT8 RegisterAddress, + _In_reads_bytes_(Length) PVOID Data, + _In_ ULONG Length +) +/*++ + +Routine Description: + + Sends data to the I2C controller to write to the specified register + on the port controller hardware. + + Before calling, DeviceContext->IncomingRequest should be set to a valid value. The IncomingRequest + will be completed automatically when writing is finished. + +Arguments: + + DeviceContext - Context information about the device. + + RegisterAddress - The I2C register address to write data to. + + Data - Pointer to the data to write. + + Length - Length of the data to write. + +Return Value: + + NTSTATUS + +--*/ +{ + TRACE_FUNC_ENTRY(TRACE_I2C); + + NTSTATUS status; + WDF_REQUEST_REUSE_PARAMS reuseParams; + + if (Length == 0) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, "Parameter 'Length' cannot be 0."); + status = STATUS_INVALID_PARAMETER; + goto Exit; + } + + if (REGISTER_ADDR_SIZE + Length > sizeof(DeviceContext->I2CAsyncBuffer)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, "Unexpected value of data length. Length: %lu. Size of buffer: %lu", Length, sizeof(DeviceContext->I2CAsyncBuffer)); + status = STATUS_INVALID_PARAMETER; + goto Exit; + } + + // Reuse the preallocated WDFREQUEST for I2C. + WDF_REQUEST_REUSE_PARAMS_INIT(&reuseParams, WDF_REQUEST_REUSE_NO_FLAGS, STATUS_SUCCESS); + status = WdfRequestReuse(DeviceContext->I2CAsyncRequest, &reuseParams); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "WdfRequestReuse failed with status %!STATUS!", status); + goto Exit; + } + + WdfRequestSetCompletionRoutine(DeviceContext->I2CAsyncRequest, I2COnWriteCompletion, DeviceContext); + + // Combine register address and user data to write into a single buffer. + DeviceContext->I2CAsyncBuffer[0] = RegisterAddress; + + RtlCopyMemory(&DeviceContext->I2CAsyncBuffer[REGISTER_ADDR_SIZE], Data, Length); + + status = WdfMemoryAssignBuffer( + DeviceContext->I2CMemory, + static_cast<PVOID>(DeviceContext->I2CAsyncBuffer), + REGISTER_ADDR_SIZE + Length); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "WdfMemoryAssignBuffer failed with status %!STATUS!", status); + } + + status = WdfIoTargetFormatRequestForWrite( + DeviceContext->I2CIoTarget, + DeviceContext->I2CAsyncRequest, + DeviceContext->I2CMemory, + NULL, + NULL); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "WdfIoTargetFormatRequestForWrite failed with status %!STATUS!", status); + goto Exit; + } + + // Send the request to the I2C I/O Target. + if (WdfRequestSend(DeviceContext->I2CAsyncRequest, DeviceContext->I2CIoTarget, NULL) == FALSE) + { + status = WdfRequestGetStatus(DeviceContext->I2CAsyncRequest); + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "[WDFREQUEST: 0x%p] WdfRequestSend for I2C write failed with status %!STATUS!", + DeviceContext->I2CAsyncRequest, status); + goto Exit; + } + +Exit: + TRACE_FUNC_EXIT(TRACE_I2C); + return status; +} + +NTSTATUS +I2CReadAsynchronously( + _In_ PDEVICE_CONTEXT DeviceContext, + _In_ UINT8 RegisterAddress, + _In_ ULONG Length +) +/*++ + +Routine Description: + + Asynchronously reads data from the port controller's registers over the I2C controller. + + Before calling, DeviceContext->IncomingRequest should be set to a valid value. The IncomingRequest + will be completed automatically when reading is finished, and any data read out from the + controller will be stored in the output buffer of IncomingRequest. + + Note: This function is not used in the sample yet. It is left here for future reference. + +Arguments: + + DeviceContext - Context information for the port controller device. + + RegisterAddress - The I2C register address from which to read data. + + Length - Length of the data to read. + +Return Value: + + NTSTATUS + +--*/ +{ + TRACE_FUNC_ENTRY(TRACE_I2C); + + NTSTATUS status; + WDF_REQUEST_REUSE_PARAMS reuseParams; + + // Store the address. + DeviceContext->I2CRegisterAddress = RegisterAddress; + + if (Length == 0) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, "Parameter 'Length' cannot be 0."); + status = STATUS_INVALID_PARAMETER; + goto Exit; + } + + // Reuse the specified WDFREQUEST + WDF_REQUEST_REUSE_PARAMS_INIT(&reuseParams, WDF_REQUEST_REUSE_NO_FLAGS, STATUS_SUCCESS); + status = WdfRequestReuse(DeviceContext->I2CAsyncRequest, &reuseParams); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "WdfRequestReuse failed with status %!STATUS!", status); + goto Exit; + } + + WdfRequestSetCompletionRoutine(DeviceContext->I2CAsyncRequest, I2COnReadCompletion, DeviceContext); + + // Prepare the I2C transfer. + + if (Length > sizeof(DeviceContext->I2CAsyncBuffer)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, "Unexpected value of data length. Length: %lu. Size of buffer: %lu", Length, sizeof(DeviceContext->I2CAsyncBuffer)); + status = STATUS_INVALID_PARAMETER; + goto Exit; + } + + SPB_TRANSFER_LIST_AND_ENTRIES(I2C_TRANSFER_COUNT) transferList; + SPB_TRANSFER_LIST_INIT(&(transferList.List), I2C_TRANSFER_COUNT); + + transferList.List.Transfers[0] = SPB_TRANSFER_LIST_ENTRY_INIT_SIMPLE( + SpbTransferDirectionToDevice, + 0, + &DeviceContext->I2CRegisterAddress, + REGISTER_ADDR_SIZE); + + transferList.List.Transfers[1] = SPB_TRANSFER_LIST_ENTRY_INIT_SIMPLE( + SpbTransferDirectionFromDevice, + 0, + DeviceContext->I2CAsyncBuffer, + Length); + + // The IOCTL is METHOD_BUFFERED, so the memory (transferList) doesn't + // have to persist until the request is completed. + status = WdfMemoryAssignBuffer( + DeviceContext->I2CMemory, + static_cast<PVOID>(&transferList), + sizeof(transferList)); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "WdfMemoryAssignBuffer failed with status %!STATUS!", status); + } + + status = WdfIoTargetFormatRequestForIoctl( + DeviceContext->I2CIoTarget, + DeviceContext->I2CAsyncRequest, + IOCTL_SPB_EXECUTE_SEQUENCE, + DeviceContext->I2CMemory, + NULL, + NULL, + NULL); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "WdfIoTargetFormatRequestForIoctl failed with status %!STATUS!", status); + goto Exit; + } + + // Send the request to the I2C I/O Target. + if (WdfRequestSend(DeviceContext->I2CAsyncRequest, DeviceContext->I2CIoTarget, NULL) == FALSE) + { + status = WdfRequestGetStatus(DeviceContext->I2CAsyncRequest); + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "[WDFREQUEST: 0x%p] WdfRequestSend for I2C read failed with status %!STATUS!", + DeviceContext->I2CAsyncRequest, status); + goto Exit; + } + +Exit: + TRACE_FUNC_EXIT(TRACE_I2C); + return status; +} + +NTSTATUS +I2CReadSynchronously( + _In_ PDEVICE_CONTEXT DeviceContext, + _In_ I2C_REQUEST_SOURCE RequestSource, + _In_ UINT8 RegisterAddress, + _Out_writes_bytes_(Length) PVOID Data, + _In_ ULONG Length +) +/*++ + +Routine Description: + + Synchronously reads data from the port controller's registers over the I2C controller + for a request originating from the client driver. + +Arguments: + + DeviceContext - Context information for the port controller device. + + RequestSource - Identify the caller so the correct WDFREQUEST can be re-used + + RegisterAddress - The I2C register address from which to read data. + + Length - Length of the data to read. + + Data - The data read from the registers. + +Return Value: + + NTSTATUS + +--*/ +{ + TRACE_FUNC_ENTRY(TRACE_I2C); + + PAGED_CODE(); + + NTSTATUS status; + WDF_REQUEST_SEND_OPTIONS requestOptions; + WDF_MEMORY_DESCRIPTOR memoryDescriptor; + WDF_REQUEST_REUSE_PARAMS reuseParams; + ULONG_PTR bytesTransferred = 0; + UINT8 transferBuffer[I2C_BUFFER_SIZE]; + + WDFREQUEST request = DeviceContext->OutgoingRequests[RequestSource]; + + // Reuse the preallocated WDFREQUEST for internal requests. + WDF_REQUEST_REUSE_PARAMS_INIT(&reuseParams, WDF_REQUEST_REUSE_NO_FLAGS, STATUS_SUCCESS); + status = WdfRequestReuse(request, &reuseParams); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "[WDFREQUEST: 0x%p] WdfRequestReuse for I2CSyncRequest failed with status %!STATUS!", + request, status); + goto Exit; + } + + if (Length == 0) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, "Parameter 'Length' cannot be 0."); + status = STATUS_INVALID_PARAMETER; + goto Exit; + } + + if (Length > sizeof(transferBuffer)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, "Unexpected value of data length. Length: %lu. Size of buffer: %lu", Length, sizeof(transferBuffer)); + status = STATUS_INVALID_PARAMETER; + goto Exit; + } + + // Prepare the I2C transfer. + SPB_TRANSFER_LIST_AND_ENTRIES(I2C_TRANSFER_COUNT) transferList; + SPB_TRANSFER_LIST_INIT(&(transferList.List), I2C_TRANSFER_COUNT); + + transferList.List.Transfers[0] = SPB_TRANSFER_LIST_ENTRY_INIT_SIMPLE( + SpbTransferDirectionToDevice, + 0, + &RegisterAddress, + REGISTER_ADDR_SIZE); + + transferList.List.Transfers[1] = SPB_TRANSFER_LIST_ENTRY_INIT_SIMPLE( + SpbTransferDirectionFromDevice, + 0, + transferBuffer, + Length); + + // Initialize the memory descriptor with the transfer list. + WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memoryDescriptor, &transferList, sizeof(transferList)); + + WDF_REQUEST_SEND_OPTIONS_INIT(&requestOptions, WDF_REQUEST_SEND_OPTION_TIMEOUT); + requestOptions.Timeout = WDF_REL_TIMEOUT_IN_MS(I2C_SYNCHRONOUS_TIMEOUT); + + // Send the request to the I2C I/O Target. + status = WdfIoTargetSendIoctlSynchronously(DeviceContext->I2CIoTarget, + request, + IOCTL_SPB_EXECUTE_SEQUENCE, + &memoryDescriptor, + NULL, + &requestOptions, + &bytesTransferred); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "[WDFREQUEST: 0x%p] WdfIoTargetSendIoctlSynchronously failed with status %!STATUS!", + request, status); + + // A synchronous I2C request failing is a good indicator that I2C is unresponsive. + // Attempt to reset the device. + I2CPerformDeviceReset(DeviceContext); + + goto Exit; + } + + if (bytesTransferred != REGISTER_ADDR_SIZE + Length) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, "Unexpected number of bytes transferred. Expected %lu, transferred %Iu.", REGISTER_ADDR_SIZE + Length, bytesTransferred); + status = STATUS_INFO_LENGTH_MISMATCH; + goto Exit; + } + + // Get the returned data out of the buffer. + RtlCopyMemory(Data, transferBuffer, Length); +Exit: + TRACE_FUNC_EXIT(TRACE_I2C); + return status; +} + +NTSTATUS +I2CReadSynchronouslyMultiple( + _In_ PDEVICE_CONTEXT DeviceContext, + _In_ I2C_REQUEST_SOURCE requestSource, + _Inout_updates_(Count) REGISTER_ITEM* Items, + _In_ ULONG Count +) +/*++ + +Routine Description: + + Synchronously reads data from the port controller's registers over the I2C controller + for a request originating from the client driver. + +Arguments: + + DeviceContext - Context information for the port controller device. + + RequestSource - Identify the caller so the correct WDFREQUEST can be re-used + + Items - Array of (register, data, length) triplets + + Count - Count of elements from the Items array above + +Return Value: + + NTSTATUS + +--*/ +{ + TRACE_FUNC_ENTRY(TRACE_I2C); + + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + PREGISTER_ITEM item; + + for (ULONG i = 0; i < Count; i++) + { + item = &Items[i]; + status = I2CReadSynchronously(DeviceContext, + requestSource, + item->RegisterAddress, + item->Data, + item->Length); + if (!NT_SUCCESS(status)) + { + goto Exit; + } + } + +Exit: + TRACE_FUNC_EXIT(TRACE_I2C); + return status; +} + +NTSTATUS +I2CWriteSynchronously( + _In_ PDEVICE_CONTEXT DeviceContext, + _In_ I2C_REQUEST_SOURCE RequestSource, + _In_ UINT8 RegisterAddress, + _In_reads_(Length) PVOID Data, + _In_ ULONG Length +) +/*++ + +Routine Description: + + Synchronously writes data from the port controller's registers over the I2C controller + for a request originating from the client driver. + +Arguments: + + DeviceContext - Context information for the port controller device. + + RequestSource - Identify the caller so the correct WDFREQUEST can be re-used + + RegisterAddress - The I2C register address from which to write data. + + Data - The data to write. + + Length - Length of the data to write. + +Return Value: + + NTSTATUS + +--*/ +{ + TRACE_FUNC_ENTRY(TRACE_I2C); + + PAGED_CODE(); + + NTSTATUS status; + WDF_REQUEST_SEND_OPTIONS requestOptions; + WDF_MEMORY_DESCRIPTOR memoryDescriptor; + WDF_REQUEST_REUSE_PARAMS reuseParams; + ULONG_PTR bytesTransferred = 0; + UINT8 transferBuffer[I2C_BUFFER_SIZE]; + + WDFREQUEST request = DeviceContext->OutgoingRequests[RequestSource]; + + if (Length == 0) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, "Parameter 'Length' cannot be 0."); + status = STATUS_INVALID_PARAMETER; + goto Exit; + } + + // Reuse the preallocated WDFREQUEST for internal requests. + WDF_REQUEST_REUSE_PARAMS_INIT(&reuseParams, WDF_REQUEST_REUSE_NO_FLAGS, STATUS_SUCCESS); + status = WdfRequestReuse(request, &reuseParams); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "[WDFREQUEST: 0x%p] WdfRequestReuse failed with status %!STATUS!", + request, status); + goto Exit; + } + + transferBuffer[0] = RegisterAddress; + + if (REGISTER_ADDR_SIZE + Length > sizeof(transferBuffer)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, "Unexpected value of length. Length: %lu. Size of buffer: %lu", Length, sizeof(transferBuffer)); + status = STATUS_INVALID_PARAMETER; + goto Exit; + } + + RtlCopyMemory(&transferBuffer[REGISTER_ADDR_SIZE], Data, Length); + + // Initialize the memory descriptor with the write buffer. + WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memoryDescriptor, &transferBuffer, REGISTER_ADDR_SIZE + Length); + + WDF_REQUEST_SEND_OPTIONS_INIT(&requestOptions, WDF_REQUEST_SEND_OPTION_TIMEOUT); + requestOptions.Timeout = WDF_REL_TIMEOUT_IN_MS(I2C_SYNCHRONOUS_TIMEOUT); + + // Send the write request to the I2C I/O Target. + status = WdfIoTargetSendWriteSynchronously( + DeviceContext->I2CIoTarget, + request, + &memoryDescriptor, + NULL, + &requestOptions, + &bytesTransferred); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "[WDFREQUEST: 0x%p] WdfIoTargetSendWriteSynchronously failed with status %!STATUS!", + request, status); + + // A synchronous I2C request failing is a good indicator that I2C is unresponsive. + // Attempt to reset the device. + I2CPerformDeviceReset(DeviceContext); + + goto Exit; + } + + if (bytesTransferred != REGISTER_ADDR_SIZE + Length) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, "Unexpected number of bytes transferred. Expected %lu, transferred %Iu.", REGISTER_ADDR_SIZE + Length, bytesTransferred); + status = STATUS_INFO_LENGTH_MISMATCH; + goto Exit; + } + +Exit: + TRACE_FUNC_EXIT(TRACE_I2C); + return status; +} + +VOID +I2COnReadCompletion( + _In_ WDFREQUEST Request, + _In_ WDFIOTARGET Target, + _In_ PWDF_REQUEST_COMPLETION_PARAMS Params, + _In_ WDFCONTEXT Context +) +/*++ + +Routine Description: + + Completion routine for hardware access after reading. Completes the WDFREQUEST from UcmTcpciCx. + +Arguments: + + Request - A handle to a framework request object that represents the completed I/O request. + + Target - A handle to an I/O target object that represents the I/O target that completed the request. + + Params - A pointer to a WDF_REQUEST_COMPLETION_PARAMS structure that contains + information about the completed request. + + Context - Driver-supplied context information, + which the driver specified in a previous call to WdfRequestSetCompletionRoutine. In this case, + it is of type PDEVICE_CONTEXT. + +--*/ +{ + TRACE_FUNC_ENTRY(TRACE_I2C); + + UNREFERENCED_PARAMETER(Target); + + NTSTATUS status; + PDEVICE_CONTEXT deviceContext; + PVOID outputBuffer; + size_t readLength = 0; + + deviceContext = (PDEVICE_CONTEXT)Context; + + status = Params->IoStatus.Status; + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "[WDFREQUEST: 0x%p] I2CTcpciRequestOnReadCompletion - WDFREQUEST completed with failure status: %!STATUS!", + Request, status); + goto Exit; + } + + readLength = Params->IoStatus.Information; + if (readLength <= REGISTER_ADDR_SIZE) + { + status = STATUS_BUFFER_TOO_SMALL; + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "[WDFREQUEST: 0x%p] Invalid output buffer length: %Ix", + Request, readLength); + readLength = 0; + goto Exit; + } + + // Subtract REGISTER_ADDR_SIZE from readLength to account for only the read bytes. + readLength -= REGISTER_ADDR_SIZE; + + // Retrieve the output buffer of the pending UcmTcpciCx request + status = WdfRequestRetrieveOutputBuffer(deviceContext->IncomingRequest, readLength, &outputBuffer, NULL); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "WdfRequestRetrieveOutputBuffer failed. Status: %!STATUS!", status); + goto Exit; + } + + // Copy the read bytes into the output buffer of the WDFREQUEST from UcmTcpciCx. + RtlCopyMemory(outputBuffer, deviceContext->I2CAsyncBuffer, readLength); + + WdfRequestSetInformation(deviceContext->IncomingRequest, readLength); + +Exit: + + WdfRequestComplete(deviceContext->IncomingRequest, status); + TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_I2C, + "[WDFREQUEST: 0x%p] WDFREQUEST from UcmTcpciCx completed with status: %!STATUS!", deviceContext->IncomingRequest, status); + + TRACE_FUNC_EXIT(TRACE_I2C); +} + +VOID +I2COnWriteCompletion( + _In_ WDFREQUEST Request, + _In_ WDFIOTARGET Target, + _In_ PWDF_REQUEST_COMPLETION_PARAMS Params, + _In_ WDFCONTEXT Context +) +/*++ + +Routine Description: + + Completion routine for hardware access after a write. Completes the WDFREQUEST from UcmTcpciCx. + +Arguments: + + Request - A handle to a framework request object that represents the completed I/O request. + + Target - A handle to an I/O target object that represents the I/O target that completed the request. + + Params - A pointer to a WDF_REQUEST_COMPLETION_PARAMS structure that contains + information about the completed request. + + Context - Driver-supplied context information, + which the driver specified in a previous call to WdfRequestSetCompletionRoutine. In this case, + it is of type PDEVICE_CONTEXT. + +--*/ +{ + TRACE_FUNC_ENTRY(TRACE_I2C); + + UNREFERENCED_PARAMETER(Target); + + NTSTATUS status; + PDEVICE_CONTEXT deviceContext; + + deviceContext = (PDEVICE_CONTEXT)Context; + + status = Params->IoStatus.Status; + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "[WDFREQUEST: 0x%p] I2COnWriteCompletion - WDFREQUEST completed with failure status: %!STATUS!", + Request, status); + goto Exit; + } + +Exit: + // Complete the WDFREQUEST from UcmTcpciCx. + WdfRequestComplete(deviceContext->IncomingRequest, status); + TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_I2C, + "[WDFREQUEST: 0x%p] WDFREQUEST from UcmTcpciCx completed with status: %!STATUS!", + deviceContext->IncomingRequest, status); + + TRACE_FUNC_EXIT(TRACE_I2C); +} + +void +I2CPerformDeviceReset( + _In_ PDEVICE_CONTEXT DeviceContext +) +/*++ + +Routine Description: + + Recovery mechanism for a malfunctioning I2C bus. + Attempt a platform-level device reset. + If unsuccessful or we have exceeded the maximum number of reset attempts, call WdfDeviceSetFailed. + +Arguments: + + DeviceContext - Context information for the port controller device. + +--*/ +{ + TRACE_FUNC_ENTRY(TRACE_I2C); + + PAGED_CODE(); + + NTSTATUS status; + + if (DeviceContext->ResetInterface.DeviceReset != NULL && DeviceContext->ResetAttempts <= MAX_DEVICE_RESET_ATTEMPTS) + { + // Attempt a platform-level device reset (PLDR) to recover from an I2C error. + // This will disconnect the device from the power rail and reconnect it. + + ++DeviceContext->ResetAttempts; + + TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_I2C, + "[WDFDEVICE: 0x%p] Performing PlatformLevelDeviceReset", DeviceContext->Device); + status = DeviceContext->ResetInterface.DeviceReset(DeviceContext->ResetInterface.Context, PlatformLevelDeviceReset, 0, NULL); + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_I2C, + "[WDFDEVICE: 0x%p] PlatformLevelDeviceReset failed with status: %!STATUS!", DeviceContext->Device, status); + + // If PLDR fails, perform WdfDeviceSetFailed. + + TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_I2C, + "[WDFDEVICE: 0x%p] Performing WdfDeviceSetFailed with WdfDeviceFailedAttemptRestart", DeviceContext->Device); + + WdfDeviceSetFailed(DeviceContext->Device, WdfDeviceFailedAttemptRestart); + + TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_I2C, + "[WDFDEVICE: 0x%p] WdfDeviceSetFailed complete", DeviceContext->Device); + } + } + else + { + // Either platform-level device reset failed or DEVICE_RESET_INTERFACE_STANDARD was not + // supported by the bus driver. Use WdfDeviceSetFailed and attempt to restart the device. + // When the driver is reloaded, it will reinitialize I2C. + // If several consecutive restart attempts fail (because the restarted driver again reports an error), + // the framework stops trying to restart the device. + + TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_I2C, + "[WDFDEVICE: 0x%p] Performing WdfDeviceSetFailed with WdfDeviceFailedAttemptRestart",DeviceContext->Device); + + WdfDeviceSetFailed(DeviceContext->Device, WdfDeviceFailedAttemptRestart); + + TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_I2C, + "[WDFDEVICE: 0x%p] WdfDeviceSetFailed complete", DeviceContext->Device); + } + + TRACE_FUNC_EXIT(TRACE_I2C); +} |
