/*++ Copyright (c) 2000 Microsoft Corporation Module Name: usb2lib.c Abstract: interface to usb2lib, usb2 low/full speed scheduling algorithms Environment: kernel or user mode only Notes: Revision History: 10-31-00 : created --*/ #include "common.h" USB2LIB_DATA LibData; VOID USB2LIB_InitializeLib( PULONG HcContextSize, PULONG EndpointContextSize, PULONG TtContextSize, PUSB2LIB_DBGPRINT Usb2LibDbgPrint, PUSB2LIB_DBGBREAK Usb2LibDbgBreak ) /*++ Routine Description: Arguments: Return Value: --*/ { *HcContextSize = sizeof(USB2LIB_HC_CONTEXT); *TtContextSize = sizeof(USB2LIB_TT_CONTEXT); *EndpointContextSize = sizeof(USB2LIB_ENDPOINT_CONTEXT); LibData.DbgPrint = Usb2LibDbgPrint; LibData.DbgBreak = Usb2LibDbgBreak; } VOID USB2LIB_InitController( PUSB2LIB_HC_CONTEXT HcContext ) /*++ Routine Description: Called at init time for an instance of the USB 2 controller Arguments: Return Value: --*/ { DBGPRINT(("USB2LIB_InitController %x\n", HcContext)); HcContext->Sig = SIG_LIB_HC; init_hc(&HcContext->Hc); init_tt(&HcContext->Hc, &HcContext->DummyTt); // set up dummy TT for use by HS endpoints } VOID USB2LIB_InitTt( PUSB2LIB_HC_CONTEXT HcContext, PUSB2LIB_TT_CONTEXT TtContext ) /*++ Routine Description: Arguments: Return Value: --*/ { DBGPRINT(("USB2LIB_InitTt %x %x\n", HcContext, TtContext)); TtContext->Sig = SIG_LIB_TT; init_tt(&HcContext->Hc, &TtContext->Tt); } #if 1 void Shift_to_list_end( int move_ep, PEndpoint RebalanceList[] ) { // int i; PEndpoint ep = RebalanceList[move_ep]; move_ep++; while (RebalanceList[move_ep]) { RebalanceList[move_ep-1] = RebalanceList[move_ep]; move_ep++; } RebalanceList[move_ep-1] = ep; } #endif BOOLEAN Promote_endpoint_periods( PEndpoint ep, PEndpoint RebalanceList[], PULONG RebalanceListEntries ) { int unwind = 0, check_ep; unsigned result; if ((ep->actual_period != 1) && (ep->ep_type == interrupt) && (ep->start_microframe > 2)) { DBGPRINT((">Period Promotion of allocated endpoint\n")); // To promote an endpoint period: // 0) unwind = false // 1) deallocate original endpoint // 2) change new ep period to 1 // 3) (re)allocate new endpoint (with new period 1) // 4) if successful // 5) check endpoints in change list for need of period promotion // 6) deallocate endpoint, move to end of change list, change period to 1, reallocate // 7) if unsuccessful // 8) unwind = true; break // 9) next ep //10) if unwind //11) deallocate orginal ep //12) check change list for promotion endpoint(s) //13) if promoted ep //14) deallocate ep, change back to original period, allocate //15) next ep //16) return false //17) else return true //18) else return false /* // On return, change list will have promoted endpoints in order of reallocation, but it is possible // to have other endpoints interspersed with the promoted endpoints. The corresponding schedule of endpoints // must be adjusted to match the order of the promoted endpoints (since they are reinserted into the budget). // The promoted endpoints (except the original endpoint) are moved to the end of the change list as the // promotion reallocations are done to ensure that they are in the change list in the order of insertion // into the budget. This allows the scheduler to derive the new schedule/budget order from the order the // promoted endpoints appear in the change list. // // This algorithm (critically) depends on the Allocate/Deallocate "appending"/reusing an existing change list // as the "final" change list is composed during the period promotion processing is performed. */ Deallocate_time_for_endpoint(ep, RebalanceList, RebalanceListEntries); ep->saved_period = ep->period; ep->period = 1; // 3) (re)allocate new endpoint (with new period 1) result = Allocate_time_for_endpoint(ep, RebalanceList, RebalanceListEntries); if (!result) { ep->period = ep->saved_period; ep->saved_period = 0; ep->promoted_this_time = 0; return 0; // failed period promotion of original endpoint } } check_ep = 0; while (RebalanceList[check_ep]) { RebalanceList[check_ep]->promoted_this_time = 0; check_ep++; } check_ep = 0; while (RebalanceList[check_ep]) { if ((RebalanceList[check_ep]->actual_period != 1) && (RebalanceList[check_ep]->ep_type == interrupt) && (RebalanceList[check_ep]->start_microframe > 2)) { // 6) deallocate endpoint, move to end of change list, change period to 1, reallocate DBGPRINT((">Period Promoting endpoint\n")); Deallocate_time_for_endpoint( RebalanceList[check_ep], RebalanceList, RebalanceListEntries); // Shift_to_list_end(check_ep, RebalanceList); RebalanceList[check_ep]->promoted_this_time = 1; RebalanceList[check_ep]->saved_period = RebalanceList[check_ep]->period; RebalanceList[check_ep]->period = 1; result = Allocate_time_for_endpoint( RebalanceList[check_ep], RebalanceList, RebalanceListEntries); if (!result) { unwind = 1; break; } } check_ep++; } if (unwind) { DBGPRINT((">Unwinding Promoted endpoints\n")); //11) deallocate orginal ep Deallocate_time_for_endpoint( ep, RebalanceList, RebalanceListEntries); ep->period = ep->saved_period; ep->saved_period = 0; //12) check change list for promotion endpoint(s) check_ep = 0; while (RebalanceList[check_ep]) { //13) if promoted ep if (RebalanceList[check_ep]->promoted_this_time) { //14) deallocate ep, change back to original period, allocate DBGPRINT((">Reallocating Unpromoted endpoint\n")); if(RebalanceList[check_ep]->calc_bus_time != 0) Deallocate_time_for_endpoint( RebalanceList[check_ep], RebalanceList, RebalanceListEntries); RebalanceList[check_ep]->period = RebalanceList[check_ep]->saved_period; RebalanceList[check_ep]->saved_period = 0; // Leave the promoted flag set since order could have changed. // schedule must be reconciled accordingly by the HC code. //RebalanceList[check_ep]->promoted_this_time = 0; result = Allocate_time_for_endpoint( RebalanceList[check_ep], RebalanceList, RebalanceListEntries); } check_ep++; } return 0; } else { return 1; } } BOOLEAN USB2LIB_AllocUsb2BusTime( PUSB2LIB_HC_CONTEXT HcContext, PUSB2LIB_TT_CONTEXT TtContext, PUSB2LIB_ENDPOINT_CONTEXT EndpointContext, PUSB2LIB_BUDGET_PARAMETERS Budget, PVOID RebalanceContext, PVOID RebalanceList, PULONG RebalanceListEntries ) /*++ Routine Description: Arguments: Return Value: --*/ { eptype endpointType; unsigned direction, speed; //PEndpoint changed_ep_list[]; unsigned result; //unsigned changed_eps; PEndpoint ep; BOOLEAN alloced; ULONG ilop; PREBALANCE_LIST rbl; PTT tt; ep = &EndpointContext->Ep; EndpointContext->Sig = SIG_LIB_EP; EndpointContext->RebalanceContext = RebalanceContext; //changed_ep_list = RebalanceList; switch (Budget->TransferType) { case Budget_Iso: DBGPRINT((">Iso \n")); endpointType = isoch; break; case Budget_Interrupt: DBGPRINT((">Interrupt \n")); endpointType = interrupt; break; default: TEST_TRAP(); } if (Budget->Direction == Budget_In) { DBGPRINT((">In \n")); direction = INDIR; } else { DBGPRINT((">Out \n")); direction = OUTDIR; } switch (Budget->Speed) { case Budget_FullSpeed: DBGPRINT((">FullSpeed \n")); speed = FSSPEED; tt = &TtContext->Tt; break; case Budget_HighSpeed: DBGPRINT((">HighSpeed \n")); speed = HSSPEED; tt = &HcContext->DummyTt; // set endpoint to dummy TT so HC can be reached break; case Budget_LowSpeed: DBGPRINT((">LowSpeed \n")); speed = LSSPEED; tt = &TtContext->Tt; break; default: DBGPRINT(("BAD SPEED\n")); } DBGPRINT((">Period %d\n", Budget->Period)); if(Budget->Speed == Budget_HighSpeed) { // This value should be a power of 2, so we don't have to check // but limit its value to MAXFRAMES * 8 if(Budget->Period > MAXMICROFRAMES) { Budget->Period = MAXMICROFRAMES; } } else { // We are full / low speed endpoint // // Round down the period to the nearest power of two (if it isn't already) // for(ilop = MAXFRAMES; ilop >= 1; ilop = ilop >> 1) { if(Budget->Period >= ilop) { break; } } Budget->Period = ilop; } DBGPRINT((">MaxPacket %d\n", Budget->MaxPacket)); DBGPRINT((">Converted Period %d\n", Budget->Period)); DBGPRINT((">RebalanceListEntries %d\n", *RebalanceListEntries)); Set_endpoint( ep, endpointType, direction, speed, Budget->Period, Budget->MaxPacket, tt); // ask John Garney to do the math DBGPRINT((">alloc (ep) %x \n", ep)); result = Allocate_time_for_endpoint(ep, RebalanceList, RebalanceListEntries); // check if successful, period != 1, interrupt, and "late" in frame, // then need to promote period to 1 // DBGPRINT((">Executing Promote_endpoint_periods (ep) %x \n", ep)); if (result) { result = Promote_endpoint_periods(ep, RebalanceList, RebalanceListEntries); } // nonzero indicates success if (result) { // set return parameters DBGPRINT((">Results\n")); DBGPRINT((">num_starts %d \n", ep->num_starts)); DBGPRINT((">num_completes %d \n", ep->num_completes)); DBGPRINT((">start_microframe %d \n", ep->start_microframe)); // this is the schedule offset DBGPRINT((">start_frame %d \n", ep->start_frame)); // period awarded, may be less than requested DBGPRINT((">actual_period %d \n", ep->actual_period)); DBGPRINT((">start_time %d \n", ep->start_time)); DBGPRINT((">calc_bus_time %d \n", ep->calc_bus_time)); DBGPRINT((">promoted_this_time %d \n", ep->promoted_this_time)); alloced = TRUE; } else { alloced = FALSE; } // fix up rebalance list rbl = RebalanceList; ilop = 0; while (rbl->RebalanceContext[ilop]) { PUSB2LIB_ENDPOINT_CONTEXT endpointContext; DBGPRINT((">rb[%d] %x\n", ilop, rbl->RebalanceContext[ilop])); endpointContext = CONTAINING_RECORD(rbl->RebalanceContext[ilop], struct _USB2LIB_ENDPOINT_CONTEXT, Ep); rbl->RebalanceContext[ilop] = endpointContext->RebalanceContext; ilop++; } DBGPRINT((">Change List Size = %d RBE = %d\n", ilop, *RebalanceListEntries)); *RebalanceListEntries = ilop; return alloced; } VOID USB2LIB_FreeUsb2BusTime( PUSB2LIB_HC_CONTEXT HcContext, PUSB2LIB_TT_CONTEXT TtContext, PUSB2LIB_ENDPOINT_CONTEXT EndpointContext, PVOID RebalanceList, PULONG RebalanceListEntries ) /*++ Routine Description: Arguments: Return Value: --*/ { unsigned result; PEndpoint ep; PREBALANCE_LIST rbl; ULONG i; // ASSERT(EndpointContext->Sig == SIG_LIB_EP); ep = &EndpointContext->Ep; DBGPRINT((">dealloc ep Context = 0x%x (ep) %x \n", EndpointContext, ep)); DBGPRINT((">RebalanceListEntries %d \n", *RebalanceListEntries)); Deallocate_time_for_endpoint(ep, RebalanceList, RebalanceListEntries); // fix up rebalance list rbl = RebalanceList; i = 0; while (rbl->RebalanceContext[i]) { PUSB2LIB_ENDPOINT_CONTEXT endpointContext; DBGPRINT((">rb[%d] %x\n", i, rbl->RebalanceContext[i])); endpointContext = CONTAINING_RECORD(rbl->RebalanceContext[i], struct _USB2LIB_ENDPOINT_CONTEXT, Ep); rbl->RebalanceContext[i] = endpointContext->RebalanceContext; i++; } DBGPRINT((">Change List Size = %d RBE = %d\n", i, *RebalanceListEntries)); *RebalanceListEntries = i; } VOID ConvertBtoHFrame(UCHAR BFrame, UCHAR BUFrame, PUCHAR HFrame, PUCHAR HUFrame) { // The budgeter returns funky values that we have to convert to something // that the host controller understands. // If bus micro frame is -1, that means that the start split is scheduled // in the last microframe of the previous bus frame. // to convert to hframes, you simply change the microframe to 0 and // keep the bus frame (see one of the tables in the host controller spec // eg 4-17. if(BUFrame == 0xFF) { *HUFrame = 0; *HFrame = BFrame; } // if the budgeter returns a value in the range from 0-6 // we simply add one to the bus micro frame to get the host // microframe if(BUFrame >= 0 && BUFrame <= 6) { *HUFrame = BUFrame + 1; *HFrame = BFrame; } // if the budgeter returns a value of 7 for the bframe // then the HUframe = 0 and the HUframe = buframe +1 if(BUFrame == 7) { *HUFrame = 0; *HFrame = BFrame + 1; } } UCHAR USB2LIB_GetSMASK(PUSB2LIB_ENDPOINT_CONTEXT EndpointContext) { PEndpoint Ep; UCHAR tmp = 0; Ep = &EndpointContext->Ep; // ASSERT(EndpointContext->Sig == SIG_LIB_EP); if(Ep->speed == HSSPEED) { //DBGPRINT(("in GetSMASK StartUFrame on High Speed Endpoint = 0x%x\n", Ep->start_microframe)); tmp |= 1 << Ep->start_microframe; } else { ULONG ilop; UCHAR HFrame; // H (Host) frame for endpoint UCHAR HUFrame; // H (Host) micro frame for endpoint // For Full and Low Speed Endpoints // the budgeter returns a bframe. Convert to HUFrame to get SMASK ConvertBtoHFrame((UCHAR)Ep->start_frame, (UCHAR)Ep->start_microframe, &HFrame, &HUFrame); for(ilop = 0; ilop < Ep->num_starts; ilop++) { tmp |= 1 << HUFrame++; } } return tmp; } // // I'm too brain dead to calculate this so just do table lookup // // Calculated by 1 << Start H Frame + 2. If Start H Frame + 2 > 7 wrap the bits // to the lower part of the word // eg. hframe 0 +2 means cmask in frames 2,3,4 ==> cmask 0x1c // eg. hframe 5 + 2 means cmasks in frames 7, 8, 9 which means cmask 0x83 #define SIZE_OF_CMASK 8 static UCHAR CMASKS [SIZE_OF_CMASK] = { 0x1c, // Start HUFRAME 0 0x38, // Start HUFRAME 1 0x70, // Start HUFRAME 2 0xE0, // Start HUFRAME 3 0xC1, // Start HUFRAME 4 0x83, // Start HUFRAME 5 0x07, // Start HUFRAME 6 0x0E, // Start HUFRAME 7 }; UCHAR USB2LIB_GetCMASK(PUSB2LIB_ENDPOINT_CONTEXT EndpointContext) { PEndpoint Ep; Ep = &EndpointContext->Ep; // ASSERT(EndpointContext->Sig == SIG_LIB_EP); if(Ep->speed == HSSPEED) { return 0; } else if(Ep->ep_type == interrupt) { UCHAR HFrame; // H (Host) frame for endpoint UCHAR HUFrame; // H (Host) micro frame for endpoint ConvertBtoHFrame((UCHAR)Ep->start_frame, (UCHAR)Ep->start_microframe, &HFrame, &HUFrame); return CMASKS[HUFrame]; } else { // Split ISO! UCHAR HFrame; // H (Host) frame for endpoint UCHAR HUFrame; // H (Host) micro frame for endpoint UCHAR tmp; ULONG NumCompletes; if(Ep->direction == OUTDIR) { // Split iso out -- NO complete splits return 0; } ConvertBtoHFrame((UCHAR)Ep->start_frame, (UCHAR)Ep->start_microframe, &HFrame, &HUFrame); HUFrame += 2; NumCompletes = Ep->num_completes; // ASSERT(NumCompletes > 0); // // Set all CMASKS bits to be set at the end of the frame // for(; HUFrame < 8; HUFrame++) { tmp |= 1 << HUFrame; NumCompletes--; if(!NumCompletes){ break; } } // // Now set all CMASKS bits to be set at the end of the // frame I.E. for the next frame wrap condition // while(NumCompletes) { tmp |= 1 << (HUFrame - 8); NumCompletes--; } //DBGPRINT(("in GetCMASK HFRAME = 0x%x HUFRAME 0x%x\n", HFrame, HUFrame)); return tmp; } } UCHAR USB2LIB_GetStartMicroFrame(PUSB2LIB_ENDPOINT_CONTEXT EndpointContext) { PEndpoint Ep; UCHAR HFrame; // H (Host) frame for endpoint UCHAR HUFrame; // H (Host) micro frame for endpoint Ep = &EndpointContext->Ep; // ASSERT(EndpointContext->Sig == SIG_LIB_EP); ConvertBtoHFrame((UCHAR)Ep->start_frame, (UCHAR)Ep->start_microframe, &HFrame, &HUFrame); return HUFrame; } UCHAR USB2LIB_GetPromotedThisTime(PUSB2LIB_ENDPOINT_CONTEXT EndpointContext) { PEndpoint Ep; UCHAR Promoted = 0; Ep = &EndpointContext->Ep; // ASSERT(EndpointContext->Sig == SIG_LIB_EP); Promoted = (UCHAR) Ep->promoted_this_time; Ep->promoted_this_time = 0; return Promoted; } UCHAR USB2LIB_GetNewPeriod(PUSB2LIB_ENDPOINT_CONTEXT EndpointContext) { PEndpoint Ep; Ep = &EndpointContext->Ep; // ASSERT(EndpointContext->Sig == SIG_LIB_EP); return (UCHAR) Ep->actual_period; } ULONG USB2LIB_GetScheduleOffset(PUSB2LIB_ENDPOINT_CONTEXT EndpointContext) { PEndpoint Ep; Ep = &EndpointContext->Ep; // assert(EndpointContext->Sig == SIG_LIB_EP); return Ep->start_frame; } PVOID USB2LIB_GetEndpoint(PUSB2LIB_ENDPOINT_CONTEXT EndpointContext) { return &(EndpointContext->Ep); } ULONG USB2LIB_GetAllocedBusTime(PUSB2LIB_ENDPOINT_CONTEXT EndpointContext) { PEndpoint Ep; Ep = &EndpointContext->Ep; // assert(EndpointContext->Sig == SIG_LIB_EP); return Ep->calc_bus_time; } PVOID USB2LIB_GetNextEndpoint(PUSB2LIB_ENDPOINT_CONTEXT EndpointContext) { PEndpoint Ep, nextEp; PUSB2LIB_ENDPOINT_CONTEXT nextContext; Ep = &EndpointContext->Ep; nextEp = Ep->next_ep; if (nextEp) { nextContext = CONTAINING_RECORD(nextEp, struct _USB2LIB_ENDPOINT_CONTEXT, Ep); // assert(EndpointContext->Sig == SIG_LIB_EP); return nextContext->RebalanceContext; } else { return NULL; } }