/*++ Copyright (c) 1999, 2000 Microsoft Corporation Module Name: roothub.c Abstract: miniport root hub Environment: kernel mode only Notes: THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. Copyright (c) 1999, 2000 Microsoft Corporation. All Rights Reserved. Revision History: 7-26-00 : created, jsenior implements the following miniport functions: MINIPORT_RH_GetStatus MINIPORT_RH_GetPortStatus MINIPORT_RH_GethubStatus MINIPORT_RH_SetFeaturePortReset MINIPORT_RH_SetFeaturePortSuspend MINIPORT_RH_SetFeaturePortPower MINIPORT_RH_ClearFeaturePortEnable MINIPORT_RH_ClearFeaturePortSuspend MINIPORT_RH_ClearFeaturePortPower MINIPORT_RH_ClearFeaturePortConnectChange MINIPORT_RH_ClearFeaturePortResetChange MINIPORT_RH_ClearFeaturePortEnableChange MINIPORT_RH_ClearFeaturePortSuspendChange MINIPORT_RH_ClearFeaturePortOvercurrentChange --*/ #include "pch.h" typedef struct _UHCI_PORT_RESET_CONTEXT { USHORT PortNumber; BOOLEAN Completing; } UHCI_PORT_RESET_CONTEXT, *PUHCI_PORT_RESET_CONTEXT; VOID UhciRHGetRootHubData( IN PDEVICE_DATA DeviceData, OUT PROOTHUB_DATA HubData ) /*++ return info about the root hub --*/ { HubData->NumberOfPorts = UHCI_NUMBER_PORTS; // D0,D1 (11) - no power switching // D2 (0) - not compund // D5, D15 (0) HubData->HubCharacteristics.us = 0; HubData->HubCharacteristics.PowerSwitchType = 3; HubData->HubCharacteristics.CompoundDevice = 0; if (DeviceData->ControllerFlavor == UHCI_Piix4) { // D3,D4 (01) - overcurrent reported per port HubData->HubCharacteristics.OverCurrentProtection = 1; } else { // D3,D4 (11) - no overcurrent reported HubData->HubCharacteristics.OverCurrentProtection = 11; } HubData->PowerOnToPowerGood = 1; // this value is the current consumed by the hub // brains, for the embeded hub this doesn't make // much sense. // // so we report zero HubData->HubControlCurrent = 0; LOGENTRY(DeviceData, G, '_hub', HubData->NumberOfPorts, DeviceData->PortPowerControl, 0); } //////////////////////////////////////////////////////////////////////////////// // // Hub status // //////////////////////////////////////////////////////////////////////////////// USB_MINIPORT_STATUS UhciRHGetStatus( IN PDEVICE_DATA DeviceData, OUT PUSHORT Status ) /*++ get the device status --*/ { // the root hub is self powered *Status = USB_GETSTATUS_SELF_POWERED; return USBMP_STATUS_SUCCESS; } USB_MINIPORT_STATUS UhciRHGetHubStatus( IN PDEVICE_DATA DeviceData, OUT PRH_HUB_STATUS HubStatus ) /*++ --*/ { // nothing intersting for the root // hub to report HubStatus->ul = 0; return USBMP_STATUS_SUCCESS; } //////////////////////////////////////////////////////////////////////////////// // // Port Enable // //////////////////////////////////////////////////////////////////////////////// USB_MINIPORT_STATUS UhciRHPortEnable( PDEVICE_DATA DeviceData, USHORT PortNumber, USHORT Value ) /*++ --*/ { PHC_REGISTER reg; PORTSC port; reg = DeviceData->Registers; UHCI_ASSERT(DeviceData, PortNumber <= UHCI_NUMBER_PORTS); port.us = READ_PORT_USHORT(®->PortRegister[PortNumber-1].us); LOGENTRY(DeviceData, G, '_spe', port.us, 0, PortNumber); MASK_CHANGE_BITS(port); // writing a 1 enables the port port.PortEnable = Value; WRITE_PORT_USHORT(®->PortRegister[PortNumber-1].us, port.us); return USBMP_STATUS_SUCCESS; } USB_MINIPORT_STATUS UhciRHClearFeaturePortEnable ( IN PDEVICE_DATA DeviceData, IN USHORT PortNumber ) { return UhciRHPortEnable(DeviceData, PortNumber, 0); } USB_MINIPORT_STATUS UhciRHSetFeaturePortEnable( PDEVICE_DATA DeviceData, USHORT PortNumber ) /*++ --*/ { return UhciRHPortEnable(DeviceData, PortNumber, 1); } //////////////////////////////////////////////////////////////////////////////// // // Port Power // //////////////////////////////////////////////////////////////////////////////// USB_MINIPORT_STATUS UhciRHClearFeaturePortPower ( IN PDEVICE_DATA DeviceData, IN USHORT PortNumber ) { // not implemented on uhci return USBMP_STATUS_SUCCESS; } USB_MINIPORT_STATUS UhciRHSetFeaturePortPower( PDEVICE_DATA DeviceData, USHORT PortNumber ) /*++ --*/ { // not implemented on uhci return USBMP_STATUS_SUCCESS; } //////////////////////////////////////////////////////////////////////////////// // // Port Status // //////////////////////////////////////////////////////////////////////////////// USB_MINIPORT_STATUS UhciRHGetPortStatus( PDEVICE_DATA DeviceData, USHORT PortNumber, PRH_PORT_STATUS portStatus ) /*++ get the status of a partuclar port --*/ { PHC_REGISTER reg; PORTSC port; reg = DeviceData->Registers; port.us = READ_PORT_USHORT(®->PortRegister[PortNumber-1].us); portStatus->ul = 0; LOGENTRY(DeviceData, G, '_Pp1', PortNumber, port.us, 0); // map the bits to the port status structure portStatus->Connected = port.PortConnect; portStatus->Enabled = port.PortEnable; // bits 12:2 indicate the true suspend state // we only want to indiacte the port is suspended // if a device is attached. If the device is removed // during suspend the enable bit will be clear if (port.Suspend && port.PortEnable) { portStatus->Suspended = 1; } else { portStatus->Suspended = 0; } if (DeviceData->ControllerFlavor == UHCI_Piix4) { portStatus->OverCurrent = port.Overcurrent; portStatus->OverCurrentChange = port.OvercurrentChange; portStatus->PowerOn = !port.Overcurrent; } else { portStatus->OverCurrent = 0; portStatus->OverCurrentChange = 0; portStatus->PowerOn = 1; // always on } portStatus->Reset = port.PortReset; portStatus->LowSpeed = port.LowSpeedDevice; portStatus->HighSpeed = 0; // this is not a 2.0 HC portStatus->ConnectChange = port.PortConnectChange; if (TEST_BIT(DeviceData->PortInReset, PortNumber-1)) { portStatus->EnableChange = 0; portStatus->ConnectChange = 0; } else { portStatus->EnableChange = port.PortEnableChange; } // these change bits must be emulated if (TEST_BIT(DeviceData->PortSuspendChange, PortNumber-1)) { portStatus->SuspendChange = 1; } if (TEST_BIT(DeviceData->PortResetChange, PortNumber-1)) { portStatus->ResetChange = 1; } LOGENTRY(DeviceData, G, '_gps', PortNumber, portStatus->ul, port.us); return USBMP_STATUS_SUCCESS; } //////////////////////////////////////////////////////////////////////////////// // // Port Reset // // First, we have the VIA specific routines for REVs 0 thru 4 of the VIA // USB host controller. Then the regular routines follow that are run for // all non-broken controllers. // //////////////////////////////////////////////////////////////////////////////// VOID UhciRHSetFeaturePortResetWorker( PDEVICE_DATA DeviceData, PUHCI_PORT_RESET_CONTEXT PortResetContext ); VOID UhciViaRHPortResetComplete( PDEVICE_DATA DeviceData, PUHCI_PORT_RESET_CONTEXT PortResetContext ) /*++ VIA specific hack: Restart the controller. --*/ { PHC_REGISTER reg; USBCMD command; USHORT portNumber; reg = DeviceData->Registers; portNumber = PortResetContext->PortNumber; // This code has been ripped out of the VIA filter driver // that works on Win2K. // Re-start the controller. command.us = READ_PORT_USHORT(®->UsbCommand.us); command.ForceGlobalResume = 0; command.RunStop = 1; WRITE_PORT_USHORT(®->UsbCommand.us, command.us); // Continue with the regular port reset completion stuff. SET_BIT(DeviceData->PortResetChange, portNumber-1); CLEAR_BIT(DeviceData->PortInReset, portNumber-1); // indicate the reset change to the hub USBPORT_INVALIDATE_ROOTHUB(DeviceData); } VOID UhciViaRHSetFeaturePortResetResume( PDEVICE_DATA DeviceData, PUHCI_PORT_RESET_CONTEXT PortResetContext ) /*++ VIA specific hack: Resume the controller. --*/ { PHC_REGISTER reg; USBCMD command; PMINIPORT_CALLBACK callback; reg = DeviceData->Registers; // Resume the controller command.us = READ_PORT_USHORT(®->UsbCommand.us); command.ForceGlobalResume = 1; command.EnterGlobalSuspendMode = 0; WRITE_PORT_USHORT(®->UsbCommand.us, command.us); // // Depending on whether we're in the completion case or not, // we'll either be starting the controller or putting the port // into reset. // callback = PortResetContext->Completing ? UhciViaRHPortResetComplete : UhciRHSetFeaturePortResetWorker; USBPORT_REQUEST_ASYNC_CALLBACK(DeviceData, 20, // callback in 20 ms, as in via filter PortResetContext, sizeof(UHCI_PORT_RESET_CONTEXT), callback); } VOID UhciViaRHSetFeaturePortResetSuspend( PDEVICE_DATA DeviceData, PUHCI_PORT_RESET_CONTEXT PortResetContext ) /*++ VIA specific hack: Suspend the controller. --*/ { PHC_REGISTER reg; USBCMD command; USBSTS status; reg = DeviceData->Registers; status.us = READ_PORT_USHORT(®->UsbStatus.us); UHCI_ASSERT(DeviceData, status.HCHalted); // Suspend the controller command.us = READ_PORT_USHORT(®->UsbCommand.us); command.ForceGlobalResume = 0; command.EnterGlobalSuspendMode = 1; WRITE_PORT_USHORT(®->UsbCommand.us, command.us); USBPORT_REQUEST_ASYNC_CALLBACK(DeviceData, 20, // callback in 20 ms, as in via filter PortResetContext, sizeof(UHCI_PORT_RESET_CONTEXT), UhciViaRHSetFeaturePortResetResume); } VOID UhciViaRHSetFeaturePortResetStop( PDEVICE_DATA DeviceData, PUHCI_PORT_RESET_CONTEXT PortResetContext ) /*++ VIA specific hack: Stop the controller. --*/ { PHC_REGISTER reg; USBCMD command; reg = DeviceData->Registers; // This code has been ripped out of the VIA filter driver // that works on Win2K. // Stop the controller command.us = READ_PORT_USHORT(®->UsbCommand.us); command.RunStop = 0; WRITE_PORT_USHORT(®->UsbCommand.us, command.us); // Wait for the HC to halt USBPORT_REQUEST_ASYNC_CALLBACK(DeviceData, 20, // callback in 20 ms, as in via filter PortResetContext, sizeof(UHCI_PORT_RESET_CONTEXT), UhciViaRHSetFeaturePortResetSuspend); } //////////////////////////////////////////////////////////////////////////////// // // Port Reset // // Generic reset routines. // //////////////////////////////////////////////////////////////////////////////// VOID UhciRHPortResetComplete( PDEVICE_DATA DeviceData, PUHCI_PORT_RESET_CONTEXT PortResetContext ) /*++ complete a port reset --*/ { PHC_REGISTER reg; PORTSC port; USHORT portNumber; int i; reg = DeviceData->Registers; portNumber = PortResetContext->PortNumber; port.us = READ_PORT_USHORT(®->PortRegister[portNumber-1].us); LOGENTRY(DeviceData, G, '_prC', port.us, DeviceData->PortResetChange, portNumber); MASK_CHANGE_BITS(port); // writing a 0 stops reset port.PortReset = 0; WRITE_PORT_USHORT(®->PortRegister[portNumber-1].us, port.us); // spin for zero do { // // a driver may not spin in a loop waiting for a status bit change // without testing for hardware presence inside the loop. // if (FALSE == UhciHardwarePresent(DeviceData)) { return; } port.us = READ_PORT_USHORT(®->PortRegister[portNumber-1].us); } while (port.PortReset != 0); // // Enable the port // for (i=0; i< 10; i++) { // // Need a delay between clearing the port reset and setting // the port enable. VIA suggests delaying 64 USB bit times, // or 43us if those are low-speed bit times.... // BUT, we can't wait in the DPC... // KeStallExecutionProcessor(50); port.us = READ_PORT_USHORT(®->PortRegister[portNumber-1].us); if (port.PortEnable) { // // port is enabled // break; } port.PortEnable = 1; WRITE_PORT_USHORT(®->PortRegister[portNumber-1].us, port.us); } // clear port connect & enable change bits port.PortEnableChange = 1; port.PortConnectChange = 1; WRITE_PORT_USHORT(®->PortRegister[portNumber-1].us, port.us); if (DeviceData->ControllerFlavor >= UHCI_VIA && DeviceData->ControllerFlavor <= UHCI_VIA+0x4) { PortResetContext->Completing = TRUE; UhciViaRHSetFeaturePortResetSuspend(DeviceData, PortResetContext); } else { SET_BIT(DeviceData->PortResetChange, portNumber-1); CLEAR_BIT(DeviceData->PortInReset, portNumber-1); // indicate the reset change to the hub USBPORT_INVALIDATE_ROOTHUB(DeviceData); } } VOID UhciRHSetFeaturePortResetWorker( PDEVICE_DATA DeviceData, PUHCI_PORT_RESET_CONTEXT PortResetContext ) /*++ Do the actual work to put the port in reset --*/ { PHC_REGISTER reg; PORTSC port; USHORT portNumber = PortResetContext->PortNumber; reg = DeviceData->Registers; port.us = READ_PORT_USHORT(®->PortRegister[portNumber-1].us); LOGENTRY(DeviceData, G, '_prw', port.us, 0, portNumber); UHCI_ASSERT(DeviceData, !port.PortReset); // writing a 1 initiates reset LOGENTRY(DeviceData, G, '_nhs', port.us, 0, portNumber); MASK_CHANGE_BITS(port); port.PortReset = 1; WRITE_PORT_USHORT(®->PortRegister[portNumber-1].us, port.us); // schedule a callback to complete the reset. USBPORT_REQUEST_ASYNC_CALLBACK(DeviceData, 10, // callback in 10 ms, PortResetContext, sizeof(UHCI_PORT_RESET_CONTEXT), UhciRHPortResetComplete); } USB_MINIPORT_STATUS UhciRHSetFeaturePortReset( PDEVICE_DATA DeviceData, USHORT PortNumber ) /*++ Put a port in reset --*/ { PORTSC port; UHCI_PORT_RESET_CONTEXT portResetContext; portResetContext.PortNumber = PortNumber; portResetContext.Completing = FALSE; UHCI_ASSERT(DeviceData, PortNumber <= UHCI_NUMBER_PORTS); LOGENTRY(DeviceData, G, '_spr', 0, 0, PortNumber); if (!TEST_BIT(DeviceData->PortInReset, PortNumber-1)) { SET_BIT(DeviceData->PortInReset, PortNumber-1); if (DeviceData->ControllerFlavor >= UHCI_VIA && DeviceData->ControllerFlavor <= UHCI_VIA+0x4) { UhciViaRHSetFeaturePortResetStop(DeviceData, &portResetContext); } else { UhciRHSetFeaturePortResetWorker(DeviceData, &portResetContext); } } else { // // the port is already in reset // UhciKdPrint((DeviceData, 2, "Trying to reset a port already in reset.\n")); return USBMP_STATUS_BUSY; } return USBMP_STATUS_SUCCESS; } //////////////////////////////////////////////////////////////////////////////// // // Port Suspend // //////////////////////////////////////////////////////////////////////////////// USB_MINIPORT_STATUS UhciRHSetFeaturePortSuspend( PDEVICE_DATA DeviceData, USHORT PortNumber ) /*++ Put a port in suspend. --*/ { PHC_REGISTER reg; PORTSC port; reg = DeviceData->Registers; UHCI_ASSERT(DeviceData, PortNumber <= UHCI_NUMBER_PORTS); port.us = READ_PORT_USHORT(®->PortRegister[PortNumber-1].us); LOGENTRY(DeviceData, G, '_sps', port.us, 0, PortNumber); if (!port.Suspend) { // // write the suspend bit // if (DeviceData->ControllerFlavor == UHCI_Piix4 || ANY_VIA(DeviceData)) { // just pretend we did it for the piix4 LOGENTRY(DeviceData, G, '_spo', port.us, 0, PortNumber); } else { MASK_CHANGE_BITS(port); port.Suspend = 1; WRITE_PORT_USHORT(®->PortRegister[PortNumber-1].us, port.us); } LOGENTRY(DeviceData, G, '_sus', port.us, 0, PortNumber); } else { // // stall if the port is already suspended // UhciKdPrint((DeviceData, 2, "Trying to suspend an already suspended port.\n")); } return USBMP_STATUS_SUCCESS; } VOID UhciRHClearFeaturePortSuspendComplete( PDEVICE_DATA DeviceData, PVOID Context ) /*++ complete a port resume. --*/ { PHC_REGISTER reg; PORTSC port; PUHCI_PORT_RESET_CONTEXT portResetContext = Context; USHORT portNumber; reg = DeviceData->Registers; portNumber = portResetContext->PortNumber; port.us = READ_PORT_USHORT(®->PortRegister[portNumber-1].us); LOGENTRY(DeviceData, G, '_prC', port.us, DeviceData->PortSuspendChange, portNumber); MASK_CHANGE_BITS(port); // clear the bits. port.ResumeDetect = 0; port.Suspend = 0; WRITE_PORT_USHORT(®->PortRegister[portNumber-1].us, port.us); SET_BIT(DeviceData->PortSuspendChange, portNumber-1); DeviceData->PortResuming[portNumber-1] = FALSE; // indicate the resume change to the hub USBPORT_INVALIDATE_ROOTHUB(DeviceData); } USB_MINIPORT_STATUS UhciRHClearFeaturePortSuspend( PDEVICE_DATA DeviceData, USHORT PortNumber ) /*++ Resume a port in suspend --*/ { PHC_REGISTER reg; PORTSC port; UHCI_PORT_RESET_CONTEXT portResetContext; reg = DeviceData->Registers; UHCI_ASSERT(DeviceData, PortNumber <= UHCI_NUMBER_PORTS); port.us = READ_PORT_USHORT(®->PortRegister[PortNumber-1].us); LOGENTRY(DeviceData, G, '_rps', port.us, 0, PortNumber); if (DeviceData->ControllerFlavor == UHCI_Piix4 || ANY_VIA(DeviceData)) { // just pretend we did it for the piix4 LOGENTRY(DeviceData, G, '_rpo', port.us, 0, PortNumber); } else { if (!DeviceData->PortResuming[PortNumber-1]) { DeviceData->PortResuming[PortNumber-1] = TRUE; if (!port.ResumeDetect) { // write the resume detect bit MASK_CHANGE_BITS(port); port.ResumeDetect = 1; WRITE_PORT_USHORT(®->PortRegister[PortNumber-1].us, port.us); } // Request to be called back so that we can set the resume to zero portResetContext.PortNumber = PortNumber; USBPORT_REQUEST_ASYNC_CALLBACK(DeviceData, 10, // callback in 10 ms, &portResetContext, sizeof(portResetContext), UhciRHClearFeaturePortSuspendComplete); } else { // stall if the port is already resuming UhciKdPrint((DeviceData, 2, "Trying to resume a port already resuming.\n")); return USBMP_STATUS_BUSY; } } LOGENTRY(DeviceData, G, '_res', port.us, 0, PortNumber); return USBMP_STATUS_SUCCESS; } //////////////////////////////////////////////////////////////////////////////// // // Port Change bits // //////////////////////////////////////////////////////////////////////////////// USB_MINIPORT_STATUS UhciRHClearFeaturePortConnectChange( PDEVICE_DATA DeviceData, USHORT PortNumber ) /*++ --*/ { PHC_REGISTER reg; PORTSC port; reg = DeviceData->Registers; // port.us = READ_PORT_USHORT(®->PortRegister[PortNumber-1].us); LOGENTRY(DeviceData, G, '_pcc', port.us, 0, PortNumber); // writing a 1 zeros the change bit if (port.PortConnectChange == 1) { // mask off other change bits MASK_CHANGE_BITS(port); port.PortConnectChange = 1; WRITE_PORT_USHORT(®->PortRegister[PortNumber-1].us, port.us); } return USBMP_STATUS_SUCCESS; } USB_MINIPORT_STATUS UhciRHClearFeaturePortEnableChange( PDEVICE_DATA DeviceData, USHORT PortNumber ) /*++ --*/ { PHC_REGISTER reg; PORTSC port; LOGENTRY(DeviceData, G, '_cpe', PortNumber, 0, 0); reg = DeviceData->Registers; port.us = READ_PORT_USHORT(®->PortRegister[PortNumber-1].us); MASK_CHANGE_BITS(port); port.PortEnableChange = 1; WRITE_PORT_USHORT(®->PortRegister[PortNumber-1].us, port.us); return USBMP_STATUS_SUCCESS; } USB_MINIPORT_STATUS UhciRHClearFeaturePortResetChange( PDEVICE_DATA DeviceData, USHORT PortNumber ) /*++ Clear the port reset condition. --*/ { // UHCI doesn't have this. CLEAR_BIT(DeviceData->PortResetChange, PortNumber-1); return USBMP_STATUS_SUCCESS; } USB_MINIPORT_STATUS UhciRHClearFeaturePortSuspendChange( PDEVICE_DATA DeviceData, USHORT PortNumber ) /*++ Clear the port suspend condition. --*/ { // UHCI doesn't have this. CLEAR_BIT(DeviceData->PortSuspendChange, PortNumber-1); return USBMP_STATUS_SUCCESS; } USB_MINIPORT_STATUS UhciRHClearFeaturePortOvercurrentChange( PDEVICE_DATA DeviceData, USHORT PortNumber ) /*++ Clear the port overcurrent condition. --*/ { if (DeviceData->ControllerFlavor == UHCI_Piix4) { PHC_REGISTER reg; PORTSC port; reg = DeviceData->Registers; // port.us = READ_PORT_USHORT(®->PortRegister[PortNumber-1].us); LOGENTRY(DeviceData, G, '_cOv', port.us, 0, PortNumber); // writing a 1 zeros the change bit if (port.OvercurrentChange == 1) { // mask off other change bits MASK_CHANGE_BITS(port); port.OvercurrentChange = 1; WRITE_PORT_USHORT(®->PortRegister[PortNumber-1].us, port.us); } } return USBMP_STATUS_SUCCESS; }