/*++ Copyright (c) 2013 Minoca Corp. All Rights Reserved Module Name: smsc95.c Abstract: This module implements support for the driver portion of the SMSC95xx family of USB Ethernet Controllers. Author: Evan Green 7-Nov-2013 Environment: Kernel --*/ // // ------------------------------------------------------------------- Includes // #include #include #include #include "smsc95.h" // // ---------------------------------------------------------------- Definitions // // // ------------------------------------------------------ Data Type Definitions // // // ----------------------------------------------- Internal Function Prototypes // KSTATUS Sm95AddDevice ( PVOID Driver, PSTR DeviceId, PSTR ClassId, PSTR CompatibleIds, PVOID DeviceToken ); VOID Sm95DispatchStateChange ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID Sm95DispatchOpen ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID Sm95DispatchClose ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID Sm95DispatchIo ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID Sm95DispatchSystemControl ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID Sm95DestroyLink ( PVOID DeviceContext ); KSTATUS Sm95pInitializeDeviceStructures ( PVOID OsDevice, PSM95_DEVICE *NewDevice ); VOID Sm95pDestroyDeviceStructures ( PSM95_DEVICE Device ); VOID Sm95pDeviceAddReference ( PSM95_DEVICE Device ); VOID Sm95pDeviceReleaseReference ( PSM95_DEVICE Device ); KSTATUS Sm95pSetUpUsbDevice ( PSM95_DEVICE Device ); KSTATUS Sm95pStartDevice ( PIRP Irp, PSM95_DEVICE Device ); KSTATUS Sm95pStopDevice ( PIRP Irp, PSM95_DEVICE Device ); // // -------------------------------------------------------------------- Globals // PDRIVER Sm95Driver = NULL; // // ------------------------------------------------------------------ Functions // KSTATUS DriverEntry ( PDRIVER Driver ) /*++ Routine Description: This routine is the entry point for the SMSC95xx driver. It registers its other dispatch functions, and performs driver-wide initialization. Arguments: Driver - Supplies a pointer to the driver object. Return Value: STATUS_SUCCESS on success. Failure code on error. --*/ { DRIVER_FUNCTION_TABLE FunctionTable; KSTATUS Status; Sm95Driver = Driver; RtlZeroMemory(&FunctionTable, sizeof(DRIVER_FUNCTION_TABLE)); FunctionTable.Version = DRIVER_FUNCTION_TABLE_VERSION; FunctionTable.AddDevice = Sm95AddDevice; FunctionTable.DispatchStateChange = Sm95DispatchStateChange; FunctionTable.DispatchOpen = Sm95DispatchOpen; FunctionTable.DispatchClose = Sm95DispatchClose; FunctionTable.DispatchIo = Sm95DispatchIo; FunctionTable.DispatchSystemControl = Sm95DispatchSystemControl; Status = IoRegisterDriverFunctions(Driver, &FunctionTable); return Status; } KSTATUS Sm95AddDevice ( PVOID Driver, PSTR DeviceId, PSTR ClassId, PSTR CompatibleIds, PVOID DeviceToken ) /*++ Routine Description: This routine is called when a device is detected for which the SMSC95xx driver acts as the function driver. The driver will attach itself to the stack. Arguments: Driver - Supplies a pointer to the driver being called. DeviceId - Supplies a pointer to a string with the device ID. ClassId - Supplies a pointer to a string containing the device's class ID. CompatibleIds - Supplies a pointer to a string containing device IDs that would be compatible with this device. DeviceToken - Supplies an opaque token that the driver can use to identify the device in the system. This token should be used when attaching to the stack. Return Value: STATUS_SUCCESS on success. Failure code if the driver was unsuccessful in attaching itself. --*/ { PSM95_DEVICE Device; KSTATUS Status; Status = Sm95pInitializeDeviceStructures(DeviceToken, &Device); if (!KSUCCESS(Status)) { goto AddDeviceEnd; } Status = IoAttachDriverToDevice(Driver, DeviceToken, Device); if (!KSUCCESS(Status)) { goto AddDeviceEnd; } AddDeviceEnd: if (!KSUCCESS(Status)) { if (Device != NULL) { Sm95pDeviceReleaseReference(Device); } } return Status; } VOID Sm95DispatchStateChange ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles State Change IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { KSTATUS Status; ASSERT(Irp->MajorCode == IrpMajorStateChange); if (Irp->Direction == IrpUp) { switch (Irp->MinorCode) { case IrpMinorQueryResources: case IrpMinorQueryChildren: IoCompleteIrp(Sm95Driver, Irp, STATUS_SUCCESS); break; case IrpMinorStartDevice: Status = Sm95pStartDevice(Irp, DeviceContext); if (!KSUCCESS(Status)) { IoCompleteIrp(Sm95Driver, Irp, Status); } break; case IrpMinorRemoveDevice: Status = Sm95pStopDevice(Irp, DeviceContext); if (!KSUCCESS(Status)) { IoCompleteIrp(Sm95Driver, Irp, Status); break; } break; default: break; } } return; } VOID Sm95DispatchOpen ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles Open IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { return; } VOID Sm95DispatchClose ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles Close IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { return; } VOID Sm95DispatchIo ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles I/O IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { return; } VOID Sm95DispatchSystemControl ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles System Control IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { PSM95_DEVICE Device; PSYSTEM_CONTROL_DEVICE_INFORMATION DeviceInformationRequest; KSTATUS Status; ASSERT(Irp->MajorCode == IrpMajorSystemControl); Device = DeviceContext; if (Irp->Direction == IrpDown) { switch (Irp->MinorCode) { case IrpMinorSystemControlDeviceInformation: DeviceInformationRequest = Irp->U.SystemControl.SystemContext; Status = NetGetSetLinkDeviceInformation( Device->NetworkLink, &(DeviceInformationRequest->Uuid), DeviceInformationRequest->Data, &(DeviceInformationRequest->DataSize), DeviceInformationRequest->Set); IoCompleteIrp(Sm95Driver, Irp, Status); break; default: break; } } return; } KSTATUS Sm95pAddNetworkDevice ( PSM95_DEVICE Device ) /*++ Routine Description: This routine adds the device to core networking's available links. Arguments: Device - Supplies a pointer to the device to add. Return Value: Status code. --*/ { NET_LINK_PROPERTIES Properties; KSTATUS Status; if (Device->NetworkLink != NULL) { Status = STATUS_SUCCESS; goto AddNetworkDeviceEnd; } // // Add a link to the core networking library. // RtlZeroMemory(&Properties, sizeof(NET_LINK_PROPERTIES)); Properties.Version = NET_LINK_PROPERTIES_VERSION; Properties.TransmitAlignment = MmGetIoBufferAlignment(); Properties.Device = Device->OsDevice; Properties.DeviceContext = Device; Properties.PacketSizeInformation.MaxPacketSize = SM95_MAX_PACKET_SIZE; Properties.PacketSizeInformation.HeaderSize = SM95_TRANSMIT_HEADER_SIZE; Properties.DataLinkType = NetDomainEthernet; Properties.MaxPhysicalAddress = MAX_ULONG; Properties.PhysicalAddress.Domain = NetDomainEthernet; RtlCopyMemory(&(Properties.PhysicalAddress.Address), &(Device->MacAddress), sizeof(Device->MacAddress)); Properties.Interface.Send = Sm95Send; Properties.Interface.GetSetInformation = Sm95GetSetInformation; Properties.Interface.DestroyLink = Sm95DestroyLink; Status = NetAddLink(&Properties, &(Device->NetworkLink)); if (!KSUCCESS(Status)) { goto AddNetworkDeviceEnd; } // // The networking core now references the device structure. Add a // reference for it. // Sm95pDeviceAddReference(Device); AddNetworkDeviceEnd: if (!KSUCCESS(Status)) { if (Device->NetworkLink != NULL) { NetRemoveLink(Device->NetworkLink); Device->NetworkLink = NULL; } } return Status; } VOID Sm95DestroyLink ( PVOID DeviceContext ) /*++ Routine Description: This routine notifies the device layer that the networking core is in the process of destroying the link and will no longer call into the device for this link. This allows the device layer to release any context that was supporting the device link interface. Arguments: DeviceContext - Supplies a pointer to the device context associated with the link being destroyed. Return Value: None. --*/ { Sm95pDeviceReleaseReference(DeviceContext); return; } // // --------------------------------------------------------- Internal Functions // KSTATUS Sm95pInitializeDeviceStructures ( PVOID OsDevice, PSM95_DEVICE *NewDevice ) /*++ Routine Description: This routine initializes an SMSC95xx device. Arguments: OsDevice - Supplies a pointer to the system token that represents this device. NewDevice - Supplies a pointer where the new structure will be returned. Return Value: Status code. --*/ { ULONG BufferAlignment; PSM95_DEVICE Device; ULONG Index; ULONG IoBufferFlags; UINTN IoBufferSize; ULONG MaxControlSize; ULONG MaxHighSpeedBurstSize; ULONG MaxInterruptSize; PHYSICAL_ADDRESS PhysicalAddress; KSTATUS Status; PUSB_TRANSFER UsbTransfer; PVOID VirtualAddress; Device = MmAllocatePagedPool(sizeof(SM95_DEVICE), SM95_ALLOCATION_TAG); if (Device == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto InitializeDeviceStructuresEnd; } RtlZeroMemory(Device, sizeof(SM95_DEVICE)); Device->OsDevice = OsDevice; Device->UsbCoreHandle = INVALID_HANDLE; Device->ReferenceCount = 1; INITIALIZE_LIST_HEAD(&(Device->BulkOutFreeTransferList)); Device->BulkOutListLock = KeCreateQueuedLock(); if (Device->BulkOutListLock == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto InitializeDeviceStructuresEnd; } // // Attempt to attach to the USB core. // Status = UsbDriverAttach(OsDevice, Sm95Driver, &(Device->UsbCoreHandle)); if (!KSUCCESS(Status)) { goto InitializeDeviceStructuresEnd; } Status = Sm95pSetUpUsbDevice(Device); if (!KSUCCESS(Status)) { goto InitializeDeviceStructuresEnd; } // // Create an I/O buffer for the control and receive transfers. // BufferAlignment = MmGetIoBufferAlignment(); MaxHighSpeedBurstSize = ALIGN_RANGE_UP(SM95_HIGH_SPEED_BURST_SIZE, BufferAlignment); MaxControlSize = ALIGN_RANGE_UP(SM95_MAX_CONTROL_TRANSFER_SIZE, BufferAlignment); MaxInterruptSize = ALIGN_RANGE_UP(SM95_MAX_INTERRUPT_TRANSFER_SIZE, BufferAlignment); IoBufferSize = (MaxHighSpeedBurstSize * SM95_BULK_IN_TRANSFER_COUNT) + MaxControlSize + MaxInterruptSize; IoBufferFlags = IO_BUFFER_FLAG_PHYSICALLY_CONTIGUOUS; Device->IoBuffer = MmAllocateNonPagedIoBuffer(0, MAX_ULONG, BufferAlignment, IoBufferSize, IoBufferFlags); if (Device->IoBuffer == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto InitializeDeviceStructuresEnd; } ASSERT(Device->IoBuffer->FragmentCount == 1); ASSERT(Device->IoBuffer->Fragment[0].VirtualAddress != NULL); PhysicalAddress = Device->IoBuffer->Fragment[0].PhysicalAddress; VirtualAddress = Device->IoBuffer->Fragment[0].VirtualAddress; // // Set up the bulk in transfers that are used to receive packets. // for (Index = 0; Index < SM95_BULK_IN_TRANSFER_COUNT; Index += 1) { UsbTransfer = UsbAllocateTransfer(Device->UsbCoreHandle, Device->BulkInEndpoint, SM95_HIGH_SPEED_BURST_SIZE, 0); if (UsbTransfer == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto InitializeDeviceStructuresEnd; } UsbTransfer->Buffer = VirtualAddress; UsbTransfer->BufferPhysicalAddress = PhysicalAddress; UsbTransfer->Direction = UsbTransferDirectionIn; UsbTransfer->Length = SM95_HIGH_SPEED_BURST_SIZE; UsbTransfer->BufferActualLength = MaxHighSpeedBurstSize; UsbTransfer->UserData = Device; UsbTransfer->CallbackRoutine = Sm95BulkInTransferCompletion; Device->BulkInTransfer[Index] = UsbTransfer; PhysicalAddress += MaxHighSpeedBurstSize; VirtualAddress += MaxHighSpeedBurstSize; } // // Set up the control transfer that's used for register reads and writes. // Device->ControlTransfer = UsbAllocateTransfer( Device->UsbCoreHandle, 0, SM95_MAX_CONTROL_TRANSFER_SIZE, 0); if (Device->ControlTransfer == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto InitializeDeviceStructuresEnd; } Device->ControlTransfer->Buffer = VirtualAddress; Device->ControlTransfer->BufferPhysicalAddress = PhysicalAddress; Device->ControlTransfer->BufferActualLength = MaxControlSize; VirtualAddress += MaxControlSize; PhysicalAddress += MaxControlSize; // // Set up the interrupt transfer that's used for link change notifications. // Device->InterruptTransfer = UsbAllocateTransfer( Device->UsbCoreHandle, Device->InterruptEndpoint, SM95_MAX_INTERRUPT_TRANSFER_SIZE, 0); if (Device->InterruptTransfer == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto InitializeDeviceStructuresEnd; } Device->InterruptTransfer->Buffer = VirtualAddress; Device->InterruptTransfer->BufferPhysicalAddress = PhysicalAddress; Device->InterruptTransfer->BufferActualLength = MaxInterruptSize; Device->InterruptTransfer->Direction = UsbTransferDirectionIn; Device->InterruptTransfer->Length = sizeof(ULONG); Device->InterruptTransfer->UserData = Device; Device->InterruptTransfer->CallbackRoutine = Sm95InterruptTransferCompletion; Status = STATUS_SUCCESS; InitializeDeviceStructuresEnd: if (!KSUCCESS(Status)) { if (Device != NULL) { Sm95pDeviceReleaseReference(Device); Device = NULL; } } *NewDevice = Device; return Status; } VOID Sm95pDestroyDeviceStructures ( PSM95_DEVICE Device ) /*++ Routine Description: This routine destroys an SMSC95xx device structure. Arguments: Device - Supplies a pointer to the device. Return Value: None. --*/ { ULONG Index; // // Destroy all the allocated transfers. For good measure, make sure they // are cancelled. // for (Index = 0; Index < SM95_BULK_IN_TRANSFER_COUNT; Index += 1) { if (Device->BulkInTransfer[Index] != NULL) { UsbCancelTransfer(Device->BulkInTransfer[Index], TRUE); UsbDestroyTransfer(Device->BulkInTransfer[Index]); } } if (Device->ControlTransfer != NULL) { UsbCancelTransfer(Device->ControlTransfer, TRUE); UsbDestroyTransfer(Device->ControlTransfer); } if (Device->InterruptTransfer != NULL) { UsbCancelTransfer(Device->InterruptTransfer, TRUE); UsbDestroyTransfer(Device->InterruptTransfer); } if (Device->IoBuffer != NULL) { MmFreeIoBuffer(Device->IoBuffer); } // // There should be no active bulk out transfers, so destroy all the free // transfers. // Sm95pDestroyBulkOutTransfers(Device); if (Device->BulkOutListLock != NULL) { KeDestroyQueuedLock(Device->BulkOutListLock); } MmFreePagedPool(Device); return; } VOID Sm95pDeviceAddReference ( PSM95_DEVICE Device ) /*++ Routine Description: This routine increments the reference count of the given SM95 device. Arguments: Device - Supplies a pointer to the SM95 device. Return Value: None. --*/ { ULONG OldReferenceCount; OldReferenceCount = RtlAtomicAdd32(&(Device->ReferenceCount), 1); ASSERT((OldReferenceCount != 0) & (OldReferenceCount < 0x20000000)); return; } VOID Sm95pDeviceReleaseReference ( PSM95_DEVICE Device ) /*++ Routine Description: This routine decrements the reference count of the given SM95 device. Arguments: Device - Supplies a pointer to the SM95 device. Return Value: None. --*/ { ULONG OldReferenceCount; OldReferenceCount = RtlAtomicAdd32(&(Device->ReferenceCount), -1); ASSERT(OldReferenceCount != 0); if (OldReferenceCount == 1) { Sm95pDestroyDeviceStructures(Device); } return; } KSTATUS Sm95pSetUpUsbDevice ( PSM95_DEVICE Device ) /*++ Routine Description: This routine claims the proper interface for the device and finds the bulk in, bulk out, and interrupt endpoints. Arguments: Device - Supplies a pointer to the device. Return Value: Status code. --*/ { PUSB_CONFIGURATION_DESCRIPTION Configuration; PLIST_ENTRY CurrentEntry; USB_TRANSFER_DIRECTION Direction; PUSB_ENDPOINT_DESCRIPTION Endpoint; UCHAR EndpointType; PUSB_INTERFACE_DESCRIPTION Interface; KSTATUS Status; if (Device->InterfaceClaimed != FALSE) { ASSERT((Device->BulkInEndpoint != 0) && (Device->BulkOutEndpoint != 0) && (Device->InterruptEndpoint != 0)); Status = STATUS_SUCCESS; goto SetUpUsbDeviceEnd; } // // If the configuration isn't yet set, set the first one. // Configuration = UsbGetActiveConfiguration(Device->UsbCoreHandle); if (Configuration == NULL) { Status = UsbSetConfiguration(Device->UsbCoreHandle, 0, TRUE); if (!KSUCCESS(Status)) { goto SetUpUsbDeviceEnd; } Configuration = UsbGetActiveConfiguration(Device->UsbCoreHandle); ASSERT(Configuration != NULL); } // // Get and verify the interface. // Interface = UsbGetDesignatedInterface(Device->OsDevice, Device->UsbCoreHandle); if (Interface == NULL) { Status = STATUS_NO_INTERFACE; goto SetUpUsbDeviceEnd; } if (Interface->Descriptor.Class != UsbInterfaceClassVendor) { Status = STATUS_NO_INTERFACE; goto SetUpUsbDeviceEnd; } // // Locate the IN and OUT bulk endpoints, and the interrupt endpoint. // CurrentEntry = Interface->EndpointListHead.Next; while (CurrentEntry != &(Interface->EndpointListHead)) { if ((Device->BulkInEndpoint != 0) && (Device->BulkOutEndpoint != 0) && (Device->InterruptEndpoint != 0)) { break; } Endpoint = LIST_VALUE(CurrentEntry, USB_ENDPOINT_DESCRIPTION, ListEntry); CurrentEntry = CurrentEntry->Next; // // Deconstruct the components of the endpoint descriptor. // EndpointType = Endpoint->Descriptor.Attributes & USB_ENDPOINT_ATTRIBUTES_TYPE_MASK; if ((Endpoint->Descriptor.EndpointAddress & USB_ENDPOINT_ADDRESS_DIRECTION_IN) != 0) { Direction = UsbTransferDirectionIn; } else { Direction = UsbTransferDirectionOut; } // // Look to match the endpoint up to one of the required ones. // if (EndpointType == USB_ENDPOINT_ATTRIBUTES_TYPE_BULK) { if ((Device->BulkInEndpoint == 0) && (Direction == UsbTransferDirectionIn)) { Device->BulkInEndpoint = Endpoint->Descriptor.EndpointAddress; } else if ((Device->BulkOutEndpoint == 0) && (Direction == UsbTransferDirectionOut)) { Device->BulkOutEndpoint = Endpoint->Descriptor.EndpointAddress; } } else if (EndpointType == USB_ENDPOINT_ATTRIBUTES_TYPE_INTERRUPT) { if ((Device->InterruptEndpoint == 0) && (Direction == UsbTransferDirectionIn)) { Device->InterruptEndpoint = Endpoint->Descriptor.EndpointAddress; } } } if ((Device->BulkInEndpoint == 0) || (Device->BulkOutEndpoint == 0) || (Device->InterruptEndpoint == 0)) { Status = STATUS_INVALID_CONFIGURATION; goto SetUpUsbDeviceEnd; } // // Everything's all ready, claim the interface. // Status = UsbClaimInterface(Device->UsbCoreHandle, Interface->Descriptor.InterfaceNumber); if (!KSUCCESS(Status)) { goto SetUpUsbDeviceEnd; } Device->InterfaceNumber = Interface->Descriptor.InterfaceNumber; Device->InterfaceClaimed = TRUE; Status = STATUS_SUCCESS; SetUpUsbDeviceEnd: return Status; } KSTATUS Sm95pStartDevice ( PIRP Irp, PSM95_DEVICE Device ) /*++ Routine Description: This routine starts the SMSC95xx LAN device. Arguments: Irp - Supplies a pointer to the start IRP. Device - Supplies a pointer to the device information. Return Value: Status code. --*/ { KSTATUS Status; // // Start up the controller. // Status = Sm95pInitialize(Device); if (!KSUCCESS(Status)) { goto StartDeviceEnd; } StartDeviceEnd: return Status; } KSTATUS Sm95pStopDevice ( PIRP Irp, PSM95_DEVICE Device ) /*++ Routine Description: This routine stops the SMSC95xx LAN device. Arguments: Irp - Supplies a pointer to the removal IRP. Device - Supplies a pointer to the device information. Return Value: Status code. --*/ { // // Detach the device from USB. This will cancel all transfer attached to // the device, including the in-flight bulk out transfers that this driver // does not track. // if (Device->UsbCoreHandle != INVALID_HANDLE) { UsbDetachDevice(Device->UsbCoreHandle); } if (Device->InterfaceClaimed != FALSE) { UsbReleaseInterface(Device->UsbCoreHandle, Device->InterfaceNumber); } if (Device->UsbCoreHandle != INVALID_HANDLE) { UsbDeviceClose(Device->UsbCoreHandle); } // // The device is gone, notify the networking core that the link has bene // removed. // if (Device->NetworkLink != NULL) { NetRemoveLink(Device->NetworkLink); Device->NetworkLink = NULL; } Sm95pDeviceReleaseReference(Device); return STATUS_SUCCESS; }