windows-nt/Source/XPSP1/NT/drivers/wdm/usb/hcd/usb2lib/sched.c
2020-09-26 16:20:57 +08:00

2434 lines
72 KiB
C

// scheduling algorithm for split periodic
/*
* Allocate_time_for_endpoint - adds a new endpoint to the periodic budget tracking list
* Dealloate_time_for_endpoint - removes an endpoint from the budget
*
* Each of these routines return a list of other (previously allocated) endpoints that have had
* their allocation changed due to the (de)allocation of an endpoint.
*
*/
/***************** Design notes *********************
*
* Split transactions require three allocations (and/or budgets).
* 1) the classic bus must count the byte times required in each classic frame.
* This is what a classic bus already normally does.
* 2) a microframe patterned frame budget must be computed for each classic frame.
* This is used to determine what microframes require split transactions.
* 3) the high speed bus must count the byte times required in each HS microframe.
* This is the HS equivalent of what is done for a (classic) bus, i.e same
* general allocation algorithm, just at HS and for microframes (vs. frames).
*
* A frame budget must be maintained for currently configured endpoints.
* A frame budget consists of a (continuous repeating) sequence of frames.
* Each frame consists of the endpoints that are allocated time for that frame.
* Each frame contains the microframe patterns for each configured endpoint for
* split transaction endpoints. A microframe pattern consists of the microframes in a
* frame that contain start-splits (SS) or complete-splits (CS) for an endpoint.
*
* The frame budget is maintained separately from any HC schedule of transactions for endpoints.
* The two must be coherent with each other, but the budget is essentially a pattern to be used
* to construct an HC transaction schedule as clients request data transfer. When a new endpoint
* is allocated, it can change the pattern of previously existing (allocated) endpoints. This
* requires that the corresponding HC schedule be reconciled at some point in time. Precisely how
* the two are reconciled is not dealt with in this algorithm. However, some ideas are captured
* later in these comments.
*
* The microframe patterns budgeted must match the order in which transactions are
* processed on the bus by the host controller and its driver. This makes the
* scheduling software interdependent with the host controller and its driver.
*
* There are several requirements between the computed frame budget and the order in which the
* HC visits transactions:
* 1) (EHCI req): a large isoch transaction (> about 570 bytes?) must be first in the
* frame to avoid having more than 1 microframe with an SS and CS
* This is in lieu of having to sort the transactions into decreasing size through the frame.
* 2) (core spec req): the order of CSs in a microframe for a set of endpoints must be
* identical to the order of SSs used to start the classic transactions
* 3) (implementation requirement due to EHCI and core spec TT rules): the order of endpoints
* for isoch OUTs longer than 188 bytes (e.g. ones with more than one SS) must match the
* budgeted order so that multiple SSs are sequenced correctly across microframes for multiple
* endpoints.
*
* Example, assume endpoint A and B
* are isoch OUT, with A budgeted before B and B requires 2 microframes of SS. A must
* be processed before B so that the SS sequence is:
* microframe N: A-SS1, B-SS1
* microframe N+1: B-SS2
*
* if instead the processing order were allowed to be B and then A this would result in:
*
* microframe N: B-SS1, A-SS1
* microframe N+1: B-SS2
*
* this would result in the TT seeing B-SS1, A-SS1, B-SS2. This is not a valid order for
* long isoch OUT for B. The TT will treat B (initially) as incomplete (due to the A SS) and ignore
* B-SS2.
* 4) (implementation requirement due to EHCI and core spec TT rules): The order of endpoints for
* isoch INs longer than 188 bytes must match the budgeted order (similar to above for OUTs).
* 5) (core spec) Only 2^N periods are allowed.
* 6) Classic (split) budget is based on best case times (specified by core spec), i.e. no bit stuffing.
* However, high speed budgets are based on worst case times, i.e. including bit stuffing.
* 7) (EHCI req) Interrupt endpoints must be ordered in the frame from largest period to smallest period.
* 8) (core spec and EHCI req) Interrupt endpoints have different numbers of CSs. Ones near the end of
* a frame have 2, while others have 3. EHCI requires that late endpoints DON'T have 3, so all
* endpoints can't be treated as having three (to try and allow unordered endpoints in a frame).
* It also might appear that early endpoints could be unordered, while later ones are ordered.
* However, as endpoints are allocated, a previously "undordered" endpoint would then need to
* become "ordered". This seems to be a really hard transition, therefore all interrupt
* endpoints are required to be ordered in a frame.
*
* This scheduling algorithm depends on the HC transaction processing sequence:
* 1) Interrupt endpoints are executed in decreasing period order (e.g. period 16 endpoint
* is executed before a period 8 endpoint). Endpoints with the same period are
* executed in budget order.
* 2) Interrupt endpoints are executed after all isochronous endpoints.
* 3) Isochronous OUT endpoints longer than 188 bytes are executed in budget order.
* 4) Isochronous IN periodic endpoints are executed in budget order. (must be so to ensure
* that CSs for endpoints occur in correct order, e.g. for different lengths)
* Note: this could be relaxed if more CSs were budgeted.
* 5) If there is a large isoch, it is first in the frame.
*
* When transactions are enqueued in the framelist HC data structures, the order
* of the transactions must be the same as that budgeted. This is so that when an endpoint
* is allocated (inserted), the budget computation accurately determines the other affected endpoints.
* The HC is required to process SSs and CSs for a set of endpoints for the same TT in the same order.
* The act of creating the frame list ensures that this order is preserved (without
* requiring any particular order to be explicitly created.
* The physical framelist order from time to time can be different for interrupt endpoints,
* but the TT is not sensitive to order.
*
* This algorithm computes budget order as determined in the routine OK_to_insert (below).
*
* Simplifying scheduling and HC assumptions:
* a) The maximum allocation allowed is reduced by 30 (from 1157 to 1127 FS byte times)
* FS byte times to eliminate needing to deal with an end of frame wrap case
* that would require a TD back pointer.
* b) Isoch IN transactions/endpoints longer than 1023-188-30 FS byte times are artifically
* processed (inserted) at the beginning of a frame. This eliminates another
* end of frame wrap case. Actually this is reduced to 1/2 a frame, since there can only be
* one "large" transaction in a frame.
* c) It is highly desirable to have the least impact on other endpoints due to an allocation.
*
* The isoch portion of the frame budget is maintained such that the frame time is "compacted".
* I.e., there are no "holes" in the allocated frame time.
*
* In contrast, the interrupt portion of the frame budget has "holes" in the allocated frame time.
* This is because the HC visits endpoints in the schedule in increasing period order, and we
* must have the same SS/CS masks for all frames the endpoint is in, while a compacted budget
* would require decreasing period order.
*
* The SS and CS masks used are only computed when the endpoint is configured.
* This eliminates requiring different microframe patterns for different frames.
* This also means that based on the presence of transactions for configured endpoints,
* there can be "holes" in the periodic portion of the classic frame. These holes can be
* reclaimed and used for control and bulk classic transactions.
*
* Endpoints have a single "SS/CS microframe within frame" pattern for all frames
* in which they have transfers. This pattern is computed once when the endpoint is
* configured. It is also recomputed whenever other endpoints in the same frame have their
* allocation time changed due to some other newly allocated endpoint.
*
* The budget frame list for FS/LS devices behind a TT, must be bound/tracked to/by
* that TT. A normal FS/LS frame bus time is maintained in addition to
* the more detailed microframe pattern information.
* Once the microframe pattern is determined, an HS allocation must be made of the
* HS HC budget. This HS allocation is done on a microframe basis for each SS/CS
* that is required to support the classic device (and its TT). The time for each
* SS (and any data per microframe) is allocated in the appropriate microframe(s).
* The time for each CS is allocated in the appropriate microframe(s).
*
* The data requirements (vs. overhead) for all classic IN endpoints for each TT should be tracked
* for each HS microframe. The time for the data portion should never exceed 188 bytes
* (inc. bitstuffing overhead) for CSs for all devices of a TT in a HS microframe. SSs always
* allocate the required time. For CSs, the first classic device must allocate data time in
* all microframes for all splits. However, once the time in a HS microframe for a TT reaches
* 188 data bytes, no more time needs to be allocated from the HS budget for data. This is
* because that is the most data that a classic bus can ever require. It may take more split
* transactions to move that data, but the amount of data can't increase. The additional splits
* just deal with the classic bus operation variation.
*
* The bus times for each period in the frame list must be updated for an endpoint. I.e.
* multiple frame entries will need to be adjusted for periods smaller than the budgeting
* window (i.e. MAXFRAMES). A tree structure is used to link slower period endpoints from different
* frames to the same endpoint of a faster period (similar to the periodic Qhead HC structure.
* There is a tree for isoch endpoints and a separate tree for interrupt endpoints.
* The endpoints lists rooted in each budget frame starts with a dummy SOF endpoint to make link
* traversal easier (i.e. avoids an empty list case).
*
* All bus times are kept in byte time units (either HS or FS). LS bus times are tracked
* as appropriately scaled FS byte times.
*
* A general comment about processing flow in allocate and deallocate. There are three processing
* concepts. All frames that an endpoint is allcoated within, must be visited to do the endpoint
* specific processing. However, other frames must also be visited to deal with the movement of
* their budget times due to the changed endpoint. Therefore all frames are always visited for
* an allocation change. As the frame list is walked, the "frame_bias" ranges over:
* (- ep->start_frame) + MAXFRAMES - 1 ... (- ep->start_frame)
* (frames are visited in reverse order)
* But only in frames that are multiples of the endpoint's period are changes
* made. This causes some of the code "tracking" complexity.
*
* Comments about reconciling HC schedule and changed frame budgets:
* When an isoch endpoint's microframe pattern is recomputed, currently pending transaction
* requests are not manipulated. But the new pattern does affect future transactions.
* (Since the FS/LS device is expecting its transactions anywhere in the frame, the
* microframe "jitter" that results is not relevant.)
*
* However, when an interrupt endpoint is inserted/removed in/from the middle of a frame, the
* other endpoints (with pending/active transactions) affected must have their
* transactions updated. For interrupt endpoints, the split transaction masks are
* in the queue head. To change the interrupt masks:
* 1. the endpoint QH must be made inactive
* 2. the driver must wait for >= 1 frame time to ensure that the HC is not processing the QH
* 3. the split masks are changed
* 4. if the TD did an SS and didn't do a CS in the frame, the endpoint is halted to recover
* (a nasty race condition, but hopefully rare)
* 5. the QH is made active.
*
* To change isoch masks, the current pending/active TDs can be changed in place.
* Existing error mechanisms can handle any race conditions with the HC.
*
* There may be a corner condition in that the budget information shouldn't be used
* (solely) to find transactions in the current schedule. If a client requests transactions
* according to one budget and then the budget changes, and then the client wants to abort
* the old transactions, I think the stack would normally use the budget to determine where
* the transactions are in the schedule, but now the budget may identify "other"
* microframes as having the transaction(s). Coder beware!
*
* Classic HC allocation uses the microframe_info, microframe[0] to count the bandwidth allocation.
*
* History:
*
* 10-3-2000, JIG: fixed incorrect starting time and shift calculations for first interrupt and only large isoch
* 10-4-2000, JIG: moved max allocation into TT and HC structs,
* added inputfile ability to set TT/HC allocation_limit, TT/HC thinktime, and HC speed
* made dump_budget not dump split info for classic HC
* fixed duplicate thinktime addition in calc_bus_time for HS/FS nonsplit allocations
*
****************************************************/
#include "common.h"
#if 0
int min (int a, int b)
{
return (int) ((a<=b) ? a : b);
}
int max (int a, int b)
{
return (int) ((a>=b) ? a : b);
}
#endif
/**
error
**/
error(char *s)
{
// take some action for error, but
// don't return to caller
//printf("error called: %s.\n", s);
DBGPRINT(("error called: %s.\n", s));
// exit(1);
}
/*******
Add_bitstuff
*******/
unsigned Add_bitstuff(
unsigned bus_time)
{
// Bit stuffing is 16.6666% extra.
// But we'll calculate bitstuffing as 16% extra with an add of a 4bit
// shift (i.e. value + value/16) to avoid floats.
return (bus_time + (bus_time>>4));
}
/*******
Allocate_check
*******/
int Allocate_check(
unsigned *used,
unsigned newval,
unsigned limit)
{
int ok = 1;
unsigned t;
// check if this new allocation fits in the currently used amout below the limit.
//
t = *used + newval;
// do allocation even if over limit. This will be fixed/deallocated after the frame is finished.
*used = t;
if ( t > limit)
{
//printf("Allocation of %d+%d out of %d\n", *used, newval, limit);
DBGPRINT(("Allocation of %d+%d out of %d\n", (*used-newval), newval, limit));
error("Allocation check failed");
ok = 0;
}
return ok;
}
/*******
OK_to_Insert
*******/
int OK_to_insert(
PEndpoint curr,
PEndpoint newep
)
{
// Determine if this current endpoint in the budget frame endpoint list is the one before
// which the new endpoint should be inserted.
// DON'T call this routine if there is already a large isoch ep in this frame and this is a
// large isoch ep!!
// Called in a loop for each endpoint while walking the budget frame endpoint list in linkage order.
// on exit when returns 1: curr points to the endpoint before which the new endpoint should be inserted.
int insertion_OK = 0;
/*
There are separate endpoint budget "trees" for isoch and interrupt. A budget tree has the same
organization as the EHCI interrupt qhead tree. This allows a single endpoint data structure to
represent the allocation requirements in all frames allocated to the endpoint.
Order of insertion in a budget frame list of endpoints is as follows for each list.
Isoch:
1. Endpoints in period order, largest period to smallest period
(required to maintain endpoint linkages easily in a "Tree")
(HC will actually enqueue isoch xacts in smallest period to largest period order)
1a. within the same period
newer endpoints are at the front of the sublist, older are at the end
(Since isoch are visited by the HC in reverse order, this results in new
endpoints being added after older endpoints, thereby having least impact on existing
frame traffic)
2. An endpoint larger than LARGEXACT bytes (only ever one possible per frame) is held separated
from normal list
(avoids having a long isoch that requires 2 uframes with SS and CS)
Note that the endpoint could have a period different than 1
Interrupt:
1. Endpoints in period order, largest period to smallest period
(required to maintain endpoint linkages easily in a "Tree")
1.2 within same period, newer endpoints are at the end of the sublist, older are at the front
Corresponding required (for this algorithm) order of transactions in HC frame list is:
1. First, an isoch endpoint larger than LARGEXACT bytes (only ever one possible per frame)
(avoids having a long isoch that requires 2 uframes with SS and CS)
Note that the endpoint could have a period different than 1
2. Isoch endpoint ITDs in INCREASING period order
***** (NOTE: THIS IS opposite AS IN THE BUDGET LIST!!!!) *****
2a. within the same period, oldest allocated eps are first, newest allocated are last
3. interrupts endpoint Qheads in decreasing period order (in "tree" of qheads)
3a. within the same period, oldest allocated (in time) eps are first, newest allocated are last
*/
if (newep->calc_bus_time < LARGEXACT) // only really possible for isoch
{
if (curr) // some endpoints already allocated
{
// This is the correct insertion point if the new ep has a period longer/larger
// than the current ep (so put it before the current ep)
// check if this is the correct period order
if ( ((curr->actual_period < newep->actual_period) && newep->ep_type == interrupt) ||
((curr->actual_period <= newep->actual_period) && newep->ep_type == isoch))
{
insertion_OK = 1;
} else
if (curr == newep) // we are at the current endpoint (due to inserting during a previous frame)
insertion_OK = 1;
} else
insertion_OK = 1; // if first endpoint, always "insert" at head
} //else large xact, so this routine shouldn't be called since the large is held separately
// This routine won't be called if there is already a large in this frame.
return insertion_OK;
}
/*******
Compute_last_isoch_time
********/
int Compute_last_isoch_time(
PEndpoint ep,
int frame
)
{
int t;
PEndpoint p;
p = ep->mytt->frame_budget[frame].isoch_ehead; // dummy SOF
p = p->next_ep; // potential real isoch, could be large isoch or other isoch
if (p)
{
// There is an isoch, so use its start time (since it is the last isoch on the bus.
t = p->start_time + p->calc_bus_time;
} else // There aren't other, nonlarge isoch transactions in the frame
{
// If there is a large isoch, use that
if (ep->mytt->frame_budget[frame].allocated_large)
{
p = ep->mytt->frame_budget[frame].allocated_large;
t = p->start_time + p->calc_bus_time;
} else
// no isoch transactions
t = FS_SOF;
}
return t;
}
/*******
Compute_ep_start_time
********/
int Compute_ep_start_time(
PEndpoint curr_ep,
PEndpoint ep,
PEndpoint last_ep,
int frame
)
{
int t;
PEndpoint p;
// Given that there is a dummy SOF endpoint at the beginning of each list (always), there
// is always a valid last_ep. If the end of list is reached, curr_ep will be zero.
// For isoch endpoints, the "next" ep on the list is the previous start time endpoint
// For interrupt endpoint, the previous ep is the previous start time endpoint.
// For interrupt endpoint, if this is the interrupt endpoint at the beginning of the int list
// (after some isoch), the start time is the end of the isoch.
if (curr_ep) // we will do an insertion in the middle of the list
{
if (ep->ep_type == isoch)
{
t = curr_ep->start_time + curr_ep->calc_bus_time;
} else // interrupt
{
if (last_ep == ep->mytt->frame_budget[frame].int_ehead) // new first interrupt ep
{
t = Compute_last_isoch_time(ep, frame);
} else
t = last_ep->start_time + last_ep->calc_bus_time;
}
} else // empty list or have run off end of budget list
{
if (ep->ep_type == isoch)
{
if (ep->mytt->frame_budget[frame].allocated_large)
{
p = ep->mytt->frame_budget[frame].allocated_large;
t = p->start_time + p->calc_bus_time;
} else
t = FS_SOF;
} else // interrupt
{
if (last_ep != ep->mytt->frame_budget[frame].int_ehead) // is the last ep not the dummy SOF?
{
// non empty list, ran off end of interrupt list,
// this is the last transaction in the frame
t = last_ep->start_time + last_ep->calc_bus_time;
} else // was empty interrupt list
t = Compute_last_isoch_time(ep, frame);
}
} // end if for first endpoint on list
return t;
}
/*******
Compute_nonsplit_overhead
********/
int Compute_nonsplit_overhead(
PEndpoint ep)
{
PHC hc;
hc = ep->mytt->myHC;
if (ep->speed == HSSPEED)
{
if (ep->direction == OUTDIR)
{
if (ep->ep_type == isoch)
{
return HS_TOKEN_SAME_OVERHEAD + HS_DATA_SAME_OVERHEAD + hc->thinktime;
} else // interrupt
{
return HS_TOKEN_SAME_OVERHEAD + HS_DATA_SAME_OVERHEAD +
HS_HANDSHAKE_OVERHEAD + hc->thinktime;
}
} else
{ // IN
if (ep->ep_type == isoch)
{
return HS_TOKEN_TURN_OVERHEAD + HS_DATA_TURN_OVERHEAD + hc->thinktime;
} else // interrupt
{
return HS_TOKEN_TURN_OVERHEAD + HS_DATA_TURN_OVERHEAD +
HS_HANDSHAKE_OVERHEAD + hc->thinktime;
}
} // end of IN overhead calculations
} else if (ep->speed == FSSPEED)
{
if (ep->ep_type == isoch)
{
return FS_ISOCH_OVERHEAD + hc->thinktime;
} else // interrupt
{
return FS_INT_OVERHEAD + hc->thinktime;
}
} else // LS
{
return LS_INT_OVERHEAD + hc->thinktime;
}
}
/*******
Compute_HS_Overheads
********/
Compute_HS_Overheads(
PEndpoint ep,
int *HS_SS,
int *HS_CS)
{
PHC hc;
hc = ep->mytt->myHC;
if (ep->direction == OUTDIR)
{
if (ep->ep_type == isoch)
{
*HS_SS = HS_SPLIT_SAME_OVERHEAD + HS_DATA_SAME_OVERHEAD + hc->thinktime;
*HS_CS = 0;
} else // interrupt
{
*HS_SS = HS_SPLIT_SAME_OVERHEAD + HS_DATA_SAME_OVERHEAD + hc->thinktime;
*HS_CS = HS_SPLIT_TURN_OVERHEAD + HS_HANDSHAKE_OVERHEAD + hc->thinktime;
}
} else
{ // IN
if (ep->ep_type == isoch)
{
*HS_SS = HS_SPLIT_SAME_OVERHEAD + hc->thinktime;
*HS_CS = HS_SPLIT_TURN_OVERHEAD + HS_DATA_TURN_OVERHEAD + hc->thinktime;
} else // interrupt
{
*HS_SS = HS_SPLIT_SAME_OVERHEAD + hc->thinktime;
*HS_CS = HS_SPLIT_TURN_OVERHEAD + HS_DATA_TURN_OVERHEAD + hc->thinktime;
}
} // end of IN overhead calculations
}
/*******
Deallocate_HS
********/
Deallocate_HS(
PEndpoint ep,
int frame_bias)
{
// 1. Calculate last microframe for a nominal complete split
// 2. Calculate HS overheads
// 3. Deallocate HS split overhead time
// 4. Deallocate separate HS data bus time
unsigned i, t, f, HS_SSoverhead, HS_CSoverhead, lastcs, min_used;
int m;
PHC hc;
hc = ep->mytt->myHC;
//***
//*** 1. Calculate last microframe for a nominal complete split
//***
// determine last microframe for complete split (isoch in particular)
//lastcs = floor( (ep->start_time + ep->calc_bus_time) / (float) FS_BYTES_PER_MICROFRAME) + 1;
lastcs = ( (ep->start_time + ep->calc_bus_time) / FS_BYTES_PER_MICROFRAME) + 1;
//***
//*** 2. Calculate HS overheads
//***
Compute_HS_Overheads(ep, &HS_SSoverhead, &HS_CSoverhead);
//***
//*** 3. Deallocate HS split overhead time for this frame
//***
// Deallocate HS time for SS and CS overhead, but treat data differently
// Deallocate HS start split bus time
m = ep->start_microframe;
f = ep->start_frame + frame_bias;
if (m == -1)
{
m = 7;
if (f == 0)
f = MAXFRAMES - 1;
else
f--;
}
for (i=0;i < ep->num_starts; i++)
{
hc->HS_microframe_info[f][m].time_used -= HS_SSoverhead;
ep->mytt->num_starts[f][m]--;
m++;
if (m > 7) {
m = 0;
f = (f + 1) % MAXFRAMES;
}
}
// Deallocate HS complete split bus time
if (ep->num_completes > 0)
{
m = ep->start_microframe + ep->num_starts + 1;
f = ep->start_frame + frame_bias;
for (i = 0; i < ep->num_completes; i++)
{
hc->HS_microframe_info[f][m].time_used -= HS_CSoverhead;
m++;
if (m > 7) {
m = 0;
f = (f + 1) % MAXFRAMES;
}
}
}
//***
//*** 4. Deallocate separate HS data bus time
//***
// Deallocate HS data part of split bus time
if (ep->direction) // OUT
{
// Deallocate full portion of data time in each microframe for OUTs
m = ep->start_microframe;
f = ep->start_frame + frame_bias;
if (m == -1)
{
m = 7;
if (f == 0)
f = MAXFRAMES - 1;
else
f--;
}
for (i=0; i < ep->num_starts; i++) {
min_used = min(
FS_BYTES_PER_MICROFRAME,
Add_bitstuff(ep->max_packet) - FS_BYTES_PER_MICROFRAME * i);
hc->HS_microframe_info[f][m].time_used -= min_used;
m++;
if (m > 7) {
m = 0;
f = (f + 1) % MAXFRAMES;
}
}
} else // IN
{
// Only deallocate at most 188 bytes for all devices behind a TT in
// each microframe.
if (ep->num_completes > 0)
{
m = ep->start_microframe + ep->num_starts + 1;
f = ep->start_frame + frame_bias;
for (i=0; i < ep->num_completes; i++)
{
// update total HS bandwith due to devices behind this tt
// This is used to determine when to deallocate HS bus time.
ep->mytt->HS_split_data_time[f][m] -=
min(Add_bitstuff(ep->max_packet), FS_BYTES_PER_MICROFRAME);
// calculate remaining bytes this endpoint contributes
// to 188 byte limit per microframe and (possible) HS deallocation.
if (ep->mytt->HS_split_data_time[f][m] < FS_BYTES_PER_MICROFRAME)
{
// find adjustment to HS allocation for data
t = min(
//
FS_BYTES_PER_MICROFRAME - ep->mytt->HS_split_data_time[f][m],
Add_bitstuff(ep->max_packet));
hc->HS_microframe_info[f][m].time_used -= t;
}
m++;
if (m > 7) {
m = 0;
f = (f + 1) % MAXFRAMES;
}
}
}
}
}
/*******
Allocate_HS
********/
int Allocate_HS(
PEndpoint ep,
int frame_bias)
{
// 1. Calculate start microframe
// 2. Calculate last microframe for a nominal complete split
// 3. Calculate number of SSs, CSs and HS overheads
// 4. Allocate HS split overhead time
// 5. Allocate separate HS data bus time
unsigned i, t, f, HS_SSoverhead, HS_CSoverhead, lastcs, min_used;
int m, retv;
PHC hc;
retv = 1;
hc = ep->mytt->myHC;
//***
//*** 1. Calculate start microframe
//***
if (frame_bias == 0)
// only update endpoint for first frame since other frames will simply
// reference this endpoint (which has already had its information computed)
//ep->start_microframe = floor(ep->start_time / (float) FS_BYTES_PER_MICROFRAME) - 1;
ep->start_microframe = (ep->start_time / FS_BYTES_PER_MICROFRAME) - 1;
//***
//*** 2. Calculate last microframe for a nominal complete split
//***
// determine last microframe for complete split (isoch in particular)
//lastcs = floor( (ep->start_time + ep->calc_bus_time) / (float) FS_BYTES_PER_MICROFRAME) + 1;
lastcs = ( (ep->start_time + ep->calc_bus_time) / FS_BYTES_PER_MICROFRAME) + 1;
//***
//*** 3. Calculate number of SSs, CSs and HS overheads
//***
Compute_HS_Overheads(ep, &HS_SSoverhead, &HS_CSoverhead);
// determine number of splits (starts and completes)
if (ep->direction == OUTDIR)
{
if (ep->ep_type == isoch)
{
if (frame_bias == 0) {
ep->num_starts = (ep->max_packet / FS_BYTES_PER_MICROFRAME) + 1;
ep->num_completes = 0;
}
} else // interrupt
{
if (frame_bias == 0) {
ep->num_starts = 1;
ep->num_completes = 2;
if (ep->start_microframe + 1 < 6)
ep->num_completes++;
}
}
} else
{ // IN
if (ep->ep_type == isoch)
{
if (frame_bias == 0) {
ep->num_starts = 1;
ep->num_completes = lastcs - (ep->start_microframe + 1);
if (lastcs <= 6)
{
if ((ep->start_microframe + 1) == 0)
ep->num_completes++;
else
ep->num_completes += 2; // this can cause one CS to be in the next frame
}
else if (lastcs == 7)
{
if ((ep->start_microframe + 1) != 0)
ep->num_completes++; // only one more CS if late in the frame.
}
}
} else // interrupt
{
if (frame_bias == 0) {
ep->num_starts = 1;
ep->num_completes = 2;
if (ep->start_microframe + 1 < 6)
ep->num_completes++;
}
}
} // end of IN
//***
//*** 4. Allocate HS split overhead time for this frame
//***
// check avail time for HS splits
// allocate HS time for SS and CS overhead, but treat data differently
// allocate HS start split bus time
m = ep->start_microframe;
f = ep->start_frame + frame_bias;
if (m == -1)
{
m = 7;
if (f == 0)
f = MAXFRAMES - 1;
else
f--;
}
for (i=0;i < ep->num_starts; i++)
{
// go ahead and do the allocations even if the checks fail. This will be deallocated after the
// frame is finished.
if (!Allocate_check(
&hc->HS_microframe_info[f][m].time_used,
HS_SSoverhead,
HS_MAX_PERIODIC_ALLOCATION))
retv = 0;
// Check for >16 SS in a microframe to one TT? Maybe not needed in practice.
if (ep->mytt->num_starts[f][m] + 1 > 16) {
error("too many SSs in microframe");
retv = 0;
}
ep->mytt->num_starts[f][m]++;
m++;
if (m > 7) {
m = 0;
f = (f + 1) % MAXFRAMES;
}
}
// allocate HS complete split bus time
if (ep->num_completes > 0)
{
m = ep->start_microframe + ep->num_starts + 1;
f = ep->start_frame + frame_bias;
for (i = 0; i < ep->num_completes; i++)
{
if (!Allocate_check(
&hc->HS_microframe_info[f][m].time_used,
HS_CSoverhead,
HS_MAX_PERIODIC_ALLOCATION))
retv = 0;
m++;
if (m > 7) {
m = 0;
f = (f + 1) % MAXFRAMES;
}
}
}
//***
//*** 5. Allocate separate HS data bus time
//***
// allocate HS data part of split bus time
if (ep->direction) // OUT
{
// allocate full portion of data time in each microframe for OUTs
m = ep->start_microframe;
f = ep->start_frame + frame_bias;
if (m == -1)
{
m = 7;
if (f == 0)
f = MAXFRAMES - 1;
else
f--;
}
for (i=0; i < ep->num_starts; i++) {
min_used = min(
FS_BYTES_PER_MICROFRAME,
Add_bitstuff(ep->max_packet) - FS_BYTES_PER_MICROFRAME * i);
if (! Allocate_check(
&hc->HS_microframe_info[f][m].time_used,
min_used,
HS_MAX_PERIODIC_ALLOCATION))
retv = 0;
m++;
if (m > 7) {
m = 0;
f = (f + 1) % MAXFRAMES;
}
}
} else // IN
{
// Only allocate at most 188 bytes for all devices behind a TT in
// each microframe.
if (ep->num_completes > 0)
{
m = ep->start_microframe + ep->num_starts + 1;
f = ep->start_frame + frame_bias;
for (i=0; i < ep->num_completes; i++)
{
//calculate remaining bytes this endpoint contributes
// to 188 byte limit per microframe and new (possible) HS allocation.
if (ep->mytt->HS_split_data_time[f][m] < FS_BYTES_PER_MICROFRAME)
{
// find minimum required new contribution of this device:
// either remaining bytes of maximum for TT or the bus time the
// device can contribute
t = min(
// find maximum remaining bytes in this microframe for this tt.
// Don't let it go to negative, which it could when device bus time
// is greater than bytes per microframe (188)
max(
FS_BYTES_PER_MICROFRAME -
ep->mytt->HS_split_data_time[f][m],
0),
Add_bitstuff(ep->max_packet));
if (! Allocate_check(
&hc->HS_microframe_info[f][m].time_used,
t,
HS_MAX_PERIODIC_ALLOCATION))
retv = 0;
}
// update total HS bandwith due to devices behind this tt
// This is used in remove device to determine when to deallocate HS
// bus time.
ep->mytt->HS_split_data_time[f][m] +=
min(Add_bitstuff(ep->max_packet), FS_BYTES_PER_MICROFRAME);
m++;
if (m > 7) {
m = 0;
f = (f + 1) % MAXFRAMES;
}
}
}
}
return retv;
}
/*******
Move_ep
********/
int Move_ep(
PEndpoint curr_ep,
int shift_time,
PEndpoint changed_ep_list[],
int *changed_eps,
int max_changed_eps,
int *err)
{
int i, f;
*err = 1;
// Adjust endpoint allocation, if we haven't already done so.
// This endpoint is adjusted to its new time.
// For interrupt endpoints that are being moved due to deallocation, an endpoint can be moved more than once.
// This can happen when recomputing the adjustment in some previous frame didn't allow the full movement to take place
// because some later frame hadn't yet been recomputed (so it was inconsistent). The later move brings it closer to
// consistency.
// check if we already have this endpoint from some previous frame
// for (i = 0; i < *changed_eps; i++)
for (i = 0; changed_ep_list[i] != 0; i++)
//if ((changed_ep_list[i] == curr_ep) && (curr_ep->moved_this_req))
if (changed_ep_list[i] == curr_ep)
break;
// if ((i >= *changed_eps) || // haven't seen this endpoint before
// ((i < *changed_eps) && !curr_ep->moved_this_req) || // have seen this endpoint this pass
if ((changed_ep_list[i] == 0) || // haven't seen this endpoint before
((changed_ep_list[i] != 0) && !curr_ep->moved_this_req) || // have seen this endpoint this pass
((curr_ep->ep_type == interrupt) && (shift_time < 0)) )
{ // Update NEW changed endpoint
// newly moved endpoints must have their HS bus time deallocated in all period frames in the budget window
// (since the start_time can't be changed without affecting all frames the endpoint is in)
// then their start_time can be changed and the bus time reallocated for all period frames in the budget window
for (f=0; (f + curr_ep->start_frame) < MAXFRAMES; f += curr_ep->actual_period)
Deallocate_HS(curr_ep, f);
curr_ep->start_time += shift_time;
if ((curr_ep->start_time + curr_ep->calc_bus_time) > FS_MAX_PERIODIC_ALLOCATION)
{
error("end of new xact too late");
*err = 0;
}
curr_ep->moved_this_req = 1;
if (changed_ep_list[i] == 0)
{
// don't overrun bounds of array if too small
if (i < max_changed_eps)
{
changed_ep_list[i] = curr_ep;
changed_ep_list[i + 1] = 0; // zero terminated list
} else
{
error("too many changed eps");
*err = 0;
// will be fixed after the frame is finished
}
} // already have this endpoint on the change list
for (f=0; (f + curr_ep->start_frame) < MAXFRAMES; f += curr_ep->actual_period)
if (! Allocate_HS(curr_ep, f))
*err = 0;
return 1;
} else
return 0;
}
/*******
Common_frames
********/
int Common_frames(PEndpoint a, PEndpoint b)
{
PEndpoint maxep, minep;
/* Determine if the two endpoints are present in the same frames based on their
* start_frame and actual_period.
*/
if ((a->actual_period == 1) || (b->actual_period == 1))
return 1;
if (a->actual_period >= b->actual_period)
{
maxep = a;
minep = b;
}
else
{
maxep = b;
minep = a;
}
if ((maxep->start_frame % minep->actual_period) == minep->start_frame)
return 1;
else
return 0;
}
/*******
Deallocate_endpoint_budget
********/
int Deallocate_endpoint_budget(
PEndpoint ep, // endpoint that needs to be removed (bus time deallocated)
PEndpoint changed_ep_list[], // pointer to array to set (on return) with list of
// changed endpoints
int *max_changed_eps, // input: maximum size of (returned) list
// on return: number of changed endpoints
int partial_frames) // number of partial frames that have been allocated
// Normally MAXFRAMES, but this function is also used to unwind partial
// allocations.
{
/*
1. For each frame that endpoint is in:
2. Deallocate HS bus time for this endpoint
3. Deallocate classic bus time
4. Find where endpoint is
5. Unlink this endpoint
6. For isoch:
7. Compute gap
8. For previous (larger/eq period) endpoint in this frame list
9. move endpoint its new earlier position in the frame
(skipping same period endpoints that have already been moved)
10. Setup to move interrupt endpoints
11. For interrupt:
12. For next (smaller/eq period) endpoint in this frame list
13. Compute gap
14. if ep is same period as gap, move it earlier
15. else "ep has faster period"
16. check for gap in "sibling" dependent frames of ep
17. if gap in all, move ep earlier
18. if moved, do next ep
*/
int frame_bias, shift_time, changed_eps, moved, move_status;
unsigned frame_cnt, gap_start, gap_end, i, j, siblings, gap_period;
PEndpoint curr_ep, last_ep, p, head, gap_ep;
// check that this endpoint is already allocated
if (! ep->calc_bus_time)
{
error("endpoint not allocated");
return 0;
}
// handle nonsplit HS deallocation
if ((ep->speed == HSSPEED) && (ep->mytt->myHC->speed == HSSPEED))
{
for (i = (ep->start_frame*MICROFRAMES_PER_FRAME) + ep->start_microframe;
i < MAXFRAMES*MICROFRAMES_PER_FRAME;
i += ep->actual_period)
{
ep->mytt->myHC->HS_microframe_info[i/MICROFRAMES_PER_FRAME][
i % MICROFRAMES_PER_FRAME].time_used -= ep->calc_bus_time;
}
ep->calc_bus_time = 0;
return 1;
} else // split and nonsplit FS/LS deallocation
{
if ((ep->speed != HSSPEED) && (ep->mytt->myHC->speed != HSSPEED))
{
// do classic (nonsplit) deallocation for classic HC
for (i = ep->start_frame; i < MAXFRAMES; i += ep->actual_period)
ep->mytt->myHC->HS_microframe_info[i][0].time_used -= ep->calc_bus_time;
ep->calc_bus_time = 0;
return 1;
}
}
changed_eps = 0;
while (changed_ep_list[changed_eps]) // reset indicators of endpoints changed this pass.
{
changed_ep_list[changed_eps]->moved_this_req = 0;
changed_eps++;
}
// this allows appending changed endpoints onto the current change list.
frame_bias = ep->start_frame;
frame_bias = (- frame_bias) + (partial_frames - 1);
for (frame_cnt=partial_frames; frame_cnt > 0; frame_cnt--)
{
//***
//*** 2. Deallocate HS bus time
//***
// Only do deallocation handling for frames this endpoint is located in
if ((frame_bias % ep->actual_period) == 0)
{
Deallocate_HS(ep, frame_bias);
//***
//*** 3. Deallocate classic bus time
//***
ep->mytt->frame_budget[ep->start_frame + frame_bias].time_used -= ep->calc_bus_time;
}
//***
//*** 4. Find where endpoint is
//***
// endpoint may not be in a particular frame since we process all frames, and the endpoint
// can have a period greater than 1. We process all frames, since the allocated times in a
// frame can be affected by endpoint allocations in other frames.
if (ep->ep_type == isoch)
{
last_ep = ep->mytt->frame_budget[ep->start_frame + frame_bias].isoch_ehead;
curr_ep = last_ep->next_ep; // get past SOF endpoint
} else
{
last_ep = ep->mytt->frame_budget[ep->start_frame + frame_bias].int_ehead;
curr_ep = last_ep->next_ep; // get past dummy SOF endpoint
}
// if this is not a large transaction, then search for it.
if (ep->calc_bus_time <= LARGEXACT)
{
// walk endpoint list for this frame to find where to insert new endpoint
while (curr_ep)
{
if (OK_to_insert(curr_ep, ep))
break;
last_ep = curr_ep;
curr_ep = curr_ep->next_ep;
}
// The isoch search will stop at the beginning of the same period sublist. The endpoint to be removed
// could be further in the sublist, we need to check for that if the ep to be removed isn't the first
// on the list. However, if it turns out that the ep isn't in the list, we can't lose the initial
// location
if ((ep->ep_type == isoch) && curr_ep)
{
p = last_ep;
while ((curr_ep->actual_period == ep->actual_period) && (curr_ep != ep))
{
last_ep = curr_ep;
curr_ep = curr_ep->next_ep;
if (curr_ep == 0)
{
// didn't find endpoint in list, so restore pointers back to initial location
last_ep = p;
curr_ep = last_ep->next_ep;
break;
}
}
}
} else // large transaction, so just get to the end of the isoch list to set up curr_ep and last_ep
{
while (curr_ep)
{
last_ep = curr_ep;
curr_ep = curr_ep->next_ep;
}
// now set up curr_ep to point to the large endpoint, but leave last_ep pointing to the end of the isoch list
curr_ep = ep;
}
//***
//*** 5. Unlink endpoint
//***
// only unlink if the endpoint is in this frame
if ((frame_bias % ep->actual_period) == 0)
{
if (ep->calc_bus_time <= LARGEXACT)
{
// if ((curr_ep == 0) && ((frame_bias % ep->actual_period) == 0))
if (curr_ep != 0)
{
last_ep->next_ep = curr_ep->next_ep;
curr_ep = curr_ep->next_ep;
}
} else
ep->mytt->frame_budget[ep->start_frame + frame_bias].allocated_large = 0;
} // processing of frames this endpoint is in
gap_ep = ep;
gap_period = gap_ep->actual_period;
//***
//*** 6. For isoch
//***
if (ep->ep_type == isoch)
{
// For isoch, when we find the deallocated endpoint in a frame, all isoch endpoints after it must
// be compacted (moved earlier). Since the isoch portion of the frame is kept in increasing period
// order and is compacted, a deallocation results in a recompacted budget.
//***
//*** 7. Compute gap
//***
head = ep->mytt->frame_budget[ep->start_frame + frame_bias].isoch_ehead;
gap_start = ep->start_time;
gap_end = gap_start + ep->calc_bus_time;
// If the deallocated endpoint is the large one and not period 1, we must check for siblings before
// compacting, since large allocations in other frames can prevent a compaction
if ((ep->calc_bus_time > LARGEXACT) && (ep->actual_period != 1))
{
for (i = 0; i < ep->actual_period; i++)
{
if (i != ep->start_frame)
{
p = ep->mytt->frame_budget[i].allocated_large;
if (p)
if (p->start_time + p->calc_bus_time - 1 > gap_start)
{
gap_start = p->start_time + p->calc_bus_time;
if (gap_end - gap_start <= 0)
break;
}
}
}
}
//***
//*** 8. For previous (larger/eq period) endpoint in this frame list
//***
if (gap_end - gap_start > 0)
{
// if large isoch is the one deallocated, update gap period based on the period of the last isoch
// in this frame
if ((ep->calc_bus_time > LARGEXACT) && (last_ep->actual_period < gap_period))
gap_period = last_ep->actual_period;
while (last_ep != head)
{
// The isoch list is "backwards", so curr_ep is earlier in the frame and last_ep is later.
// Compute the gap accordingly.
// curr_ep and last_ep are normally valid endpoints, but there are some corner conditions:
// a) curr_ep can be null when the ep isn't found, but we won't be here if that's true.
// b) last_ep can be dummy_sof when ep is newest, slowest period endpoint, but that will be
// handled as part of the interrupt ep handling initial conditions (and we won't get here)
//***
//*** 9. move endpoint its new earlier position in the frame
//*** (skipping same period endpoints that have already been moved)
//***
shift_time = gap_end - gap_start;
if (shift_time > 0)
{
moved = Move_ep(
last_ep,
- shift_time,
changed_ep_list,
&changed_eps,
*max_changed_eps,
&move_status);
if (! move_status)
error("deallocation move failure!!"); //<<few things can go wrong here, but num eps could >>
if (! moved)
break; // since we have found the part of the frame tree that has already been moved
// previous frames.
}
// rewalk isoch list until we get to "previous" endpoint in list/frame.
// This will be a little nasty since the isoch tree is linked in the opposite order that we
// really need to walk the frame to "compact". Simply re-walk the isoch tree for this frame
// from the head until we get to the previous. This is mostly ok since the isoch tree is
// extremely likely to be normally very short so the processing hit of rewalking the list will
// normally be small.
p = last_ep;
last_ep = ep->mytt->frame_budget[ep->start_frame + frame_bias].isoch_ehead;
curr_ep = last_ep->next_ep; // get past dummy SOF endpoint
// This test always terminates since we can't run off the end of the list due to the entry
// condition of the last_ep not being the (dummy) head.
while (curr_ep != p)
{
last_ep = curr_ep;
curr_ep = curr_ep->next_ep;
}
} // end of isoch endpoints in frame
} // end of shift handling
//***
//*** 10. Setup to move interrupt endpoints
//***
last_ep = ep->mytt->frame_budget[ep->start_frame + frame_bias].int_ehead;
curr_ep = last_ep->next_ep; // get past dummy SOF endpoint
// if there is an isoch endpoint, use the last one as the last_ep for interrupt budget
// processing to allow correct computation of the previous transaction end time.
// if there is a non-large isoch endpoint, use it other if there is a large isoch use that
// otherwise, leave the dummy sof interrupt endpoint as last
if (ep->mytt->frame_budget[ep->start_frame + frame_bias].isoch_ehead->next_ep)
last_ep = ep->mytt->frame_budget[ep->start_frame + frame_bias].isoch_ehead->next_ep;
else if (ep->mytt->frame_budget[ep->start_frame + frame_bias].allocated_large)
last_ep = ep->mytt->frame_budget[ep->start_frame + frame_bias].allocated_large;
} // end isoch
//***
//*** 11. For interrupt
//***
// The interrupt frame budget can have "holes" of unallocated time in a frame. These holes
// can be caused by some endpoint in one frame forcing an endpoint in another frame to be later
// in the frame to avoid collisions. In order to compact after a deallocation, we have to ensure
// that the gap exists in all frames of an endpoint that is a candidate to move.
// An endpoint that has an end time that is greater than ep's start time (but less than the
// end time) advances the gap start time to the (previous) endpoint's end time.
// An endpoint that has a start time that is less than the ep's end time (but greater than the
// start time) reduces the end time to the (later) endpoint's start time.
//
// If the gap end time is greater than the gap start time, we have to move affected endpoints by that
// amount. Otherwise, no endpoints are affected by the removal in this frame.
// if this is the first interrupt on the endpoint list, fixup the last_ep pointer for the gap computation
if ((ep->ep_type == interrupt) && (last_ep == ep->mytt->frame_budget[ep->start_frame + frame_bias].int_ehead))
{
if (ep->mytt->frame_budget[ep->start_frame + frame_bias].isoch_ehead->next_ep)
last_ep = ep->mytt->frame_budget[ep->start_frame + frame_bias].isoch_ehead->next_ep;
else if (ep->mytt->frame_budget[ep->start_frame + frame_bias].allocated_large)
last_ep = ep->mytt->frame_budget[ep->start_frame + frame_bias].allocated_large;
}
//***
//*** 12. For next (smaller/eq period) endpoint in this frame list
//***
while (curr_ep)
{
//***
//*** 13. Compute gap
//***
gap_start = last_ep->start_time + last_ep->calc_bus_time;
gap_end = curr_ep->start_time;
moved = 0;
//***
//*** 14. if ep is same or larger period than gap, move it earlier
//***
if ((gap_period <= curr_ep->actual_period) && (gap_ep->start_frame == curr_ep->start_frame))
{
shift_time = gap_end - gap_start;
if (shift_time > 0)
{
moved = Move_ep(
curr_ep,
- shift_time,
changed_ep_list,
&changed_eps,
*max_changed_eps,
&move_status);
if (! move_status)
error("deallocate move failure 2"); // <<few things, but num eps could fail here>>
}
} else // move candidate has a smaller interrupt period or has a different start_frame
{
//***
//*** 15. else "ep has faster period or different start_frame"
//***
//***
//*** 16. check for gap in "sibling" dependent frames of ep
//***
// siblings are the other frames that the curr_ep is dependent upon. E.g. if curr period
// is 1 and gap_period is 8, there are 7 other frames that need to be checked for a gap
if (Common_frames(curr_ep, ep))
siblings = (gap_period / curr_ep->actual_period);
else // move candidate is not in frames occupied by deleted endpoint
siblings = MAXFRAMES / curr_ep->actual_period;
j = curr_ep->start_frame;
for (i = 0; i < siblings; i++)
{
// find curr_ep in new sibling frame to check gap
// We only look in frames that we know the curr_ep is in.
// We can look in frames that aren't affected by the deleted ep, but don't optimize for now.
// skip the gap start frame since we already know it has a gap
if (j != gap_ep->start_frame) {
last_ep = ep->mytt->frame_budget[j].int_ehead;
p = last_ep->next_ep;
// fixup last_ep if this is the first interrupt ep in the frame after some isoch.
if (ep->mytt->frame_budget[j].isoch_ehead->next_ep)
last_ep = ep->mytt->frame_budget[j].isoch_ehead->next_ep;
else if (ep->mytt->frame_budget[j].allocated_large)
last_ep = ep->mytt->frame_budget[j].allocated_large;
while (p && (p != curr_ep))
{
last_ep = p;
p = p->next_ep;
}
if (last_ep->start_time + last_ep->calc_bus_time - 1 > gap_start)
{
gap_start = last_ep->start_time + last_ep->calc_bus_time;
if (gap_end - gap_start <= 0)
break;
}
}
j += curr_ep->actual_period;
}
//***
//*** 17. if gap in all, move ep earlier
//***
shift_time = gap_end - gap_start;
if (shift_time > 0)
{
moved = Move_ep(
curr_ep,
- shift_time,
changed_ep_list,
&changed_eps,
*max_changed_eps,
&move_status);
if (! move_status)
error("deallocate move failure 3"); // << few things, but num eps could fail here>>
}
} // end of faster period interrupt endpoint move candidate
//***
//*** 18. if moved, do next ep
//***
if (!moved)
break;
gap_ep = curr_ep;
if (gap_period > curr_ep->actual_period)
gap_period = curr_ep->actual_period;
last_ep = curr_ep;
curr_ep = curr_ep->next_ep;
} // end interrupt endpoints in frame
frame_bias--;
} // end for all frames
ep->calc_bus_time = 0;
return 1;
}
/*******
Allocate_time_for_endpoint
********/
int Allocate_time_for_endpoint(
PEndpoint ep, // endpoint that needs to be configured (bus time allocated)
PEndpoint changed_ep_list[], // pointer to array to set (on return) with list of
// changed endpoints
int *max_changed_eps // input: maximum size of (returned) list
// on return: number of changed endpoints
)
{
int shift_time, frame_bias, moved, retv, move_status;
unsigned t, overhead, changed_eps, i, min_used, latest_start, frame_cnt;
PEndpoint curr_ep, last_ep, p;
changed_eps = 0;
retv = 1;
// OVERVIEW of algorithm steps:
// 1. Determine starting frame # for period
// 2. Calculate classic time required
// 3. For all period frames, find the latest starting time so we can check the classic allocation later
// 4. Process each frame data structure for endpoint period in budget window
// 5. Now check allocation for each frame using shift adjustment based on latest start time
// 6a. Now move isoch endpoints, insert new isoch and then move interrupt endpoints
// 6b. Now insert new interrupt and move rest of interrupt endpoints
// 7. Allocate HS bus time
// 8. Allocate classic bus time
//***
//*** 1. Determine starting frame # for period
//***
// Also remember the maximum frame time allocation since it will be used to pass the allocation check.
// Find starting frame number for reasonable balance of all classic frames
ep->start_frame = 0;
ep->start_microframe = 0;
ep->num_completes = 0;
ep->num_starts = 0;
// check that this endpoint isn't already allocated
if (ep->calc_bus_time)
{
error("endpoint already allocated");
return 0;
}
// handle nonsplit HS allocation
// if ((ep->speed == HSSPEED) && (ep->mytt->myHC->speed == HSSPEED)) {
if (ep->speed == HSSPEED) {
min_used = ep->mytt->myHC->HS_microframe_info[0][0].time_used;
if (ep->period > MAXFRAMES*MICROFRAMES_PER_FRAME)
ep->actual_period = MAXFRAMES*MICROFRAMES_PER_FRAME;
else
ep->actual_period = ep->period;
// Look at all candidate frames for this period to find the one with min
// allocated bus time.
//
for (i=1; i < ep->actual_period; i++)
{
if (ep->mytt->myHC->HS_microframe_info[i/MICROFRAMES_PER_FRAME][i % MICROFRAMES_PER_FRAME].time_used < min_used)
{
min_used = ep->mytt->myHC->HS_microframe_info[i/MICROFRAMES_PER_FRAME][i % MICROFRAMES_PER_FRAME].time_used;
ep->start_frame = i/MICROFRAMES_PER_FRAME;
ep->start_microframe = i % MICROFRAMES_PER_FRAME;
}
}
// compute and allocate HS bandwidth
ep->calc_bus_time = Compute_nonsplit_overhead(ep) + Add_bitstuff(ep->max_packet);
for (i = (ep->start_frame*MICROFRAMES_PER_FRAME) + ep->start_microframe;
i < MAXFRAMES*MICROFRAMES_PER_FRAME;
i += ep->actual_period)
{
if (! Allocate_check(
&(ep->mytt->myHC->HS_microframe_info[i/MICROFRAMES_PER_FRAME][
i % MICROFRAMES_PER_FRAME].time_used),
ep->calc_bus_time,
HS_MAX_PERIODIC_ALLOCATION))
retv = 0;
}
if (! retv) // if allocation failed, deallocate
{
for (i = (ep->start_frame*MICROFRAMES_PER_FRAME) + ep->start_microframe;
i < MAXFRAMES*MICROFRAMES_PER_FRAME;
i += ep->actual_period)
{
ep->mytt->myHC->HS_microframe_info[i/MICROFRAMES_PER_FRAME][
i % MICROFRAMES_PER_FRAME].time_used -= ep->calc_bus_time;
}
}
return retv;
} else {
// split or nonsplit FS/LS speed allocation
// classic allocation
if ((ep->speed != HSSPEED) && (ep->mytt->myHC->speed != HSSPEED)) {
min_used = ep->mytt->myHC->HS_microframe_info[0][0].time_used;
if (ep->period > MAXFRAMES)
ep->actual_period = MAXFRAMES;
else
ep->actual_period = ep->period;
// Look at all candidate frames for this period to find the one with min
// allocated bus time.
//
for (i=1; i < ep->actual_period ; i++)
{
if (ep->mytt->myHC->HS_microframe_info[i][0].time_used < min_used)
{
min_used = ep->mytt->myHC->HS_microframe_info[i][0].time_used;
ep->start_frame = i;
}
}
// compute and allocate FS/LS bandwidth
ep->calc_bus_time = Compute_nonsplit_overhead(ep) +
Add_bitstuff((ep->speed?1:8) * ep->max_packet);
for (i = ep->start_frame; i < MAXFRAMES; i += ep->actual_period) {
t = ep->mytt->myHC->HS_microframe_info[i][0].time_used; // can't take address of bitfield (below)
if (! Allocate_check( &t, ep->calc_bus_time, FS_MAX_PERIODIC_ALLOCATION))
retv = 0;
ep->mytt->myHC->HS_microframe_info[i][0].time_used = t;
}
if (! retv) {
for (i = ep->start_frame; i < MAXFRAMES; i += ep->actual_period)
ep->mytt->myHC->HS_microframe_info[i][0].time_used -= ep->calc_bus_time;
}
return retv;
} else {
// split allocation
min_used = ep->mytt->frame_budget[0].time_used;
if (ep->period > MAXFRAMES)
ep->actual_period = MAXFRAMES;
else
ep->actual_period = ep->period;
// Look at all candidate frames for this period to find the one with min
// allocated bus time.
//
for (i=1; i < ep->actual_period ; i++) {
if (ep->mytt->frame_budget[i].time_used < min_used) {
min_used = ep->mytt->frame_budget[i].time_used;
ep->start_frame = i;
}
}
}
}
// above handles all speeds, the rest of this code is for split transaction processing
// There could be multiple frames with a minimum already allocated bus time.
// If there is a frame where this ep would be added at the end of the frame,
// we can avoid doing an insert (which requires other endpoints to be adjusted).
// Currently we don't look for that optimization. It would involve checking the
// frame endpoint list to see if the new endpoint would be the last and if not
// going back to see if there is another candidate frame and trying again (until
// there are no more candidate frames). We could also keep track of the candidate
// frame that had the least affect on other endpoints (for the case where there
// is a frame that makes the new endpoint closest to last).
// <<attempt later maybe>>??
//***
//*** 2. Calculate classic time required
//***
// Calculate classic overhead
if (ep->ep_type == isoch)
{
if (ep->speed == FSSPEED)
overhead = FS_ISOCH_OVERHEAD + ep->mytt->think_time;
else
{
error("low speed isoch illegal"); // illegal, LS isoch
return 0;
}
} else
{ // interrupt
if (ep->speed == FSSPEED)
overhead = FS_INT_OVERHEAD + ep->mytt->think_time;
else
overhead = LS_INT_OVERHEAD + ep->mytt->think_time;
}
// Classic bus time, NOT including bitstuffing overhead (in FS byte times) since we do best case budget
ep->calc_bus_time = ep->max_packet * (ep->speed?1:8) + overhead;
//***
//*** 3. For all period frames, find the latest starting time so we can check the classic allocation later.
//***
// Checking the classic allocation takes two passes through the frame/endpoint lists.
// Or else we would have to remember the last/curr ep pointers for each period frame and loop back
// through that list the second time. (<<Future optimization?>>)
// To find the start time to use for the new endpoint: for each frame, find the insertion point and use
// the "previous" endpoint in the FRAME. Use the previous endpoint end time as a possible start time
// for this new endpoint. Use the end time instead of the insertion point start time since there can
// before endpoints and we want to allocate as contiguously as possible. Using this time, find the latest
// start be unallocated time time across all frames (this keeps the early part of the frame as allocated as
// possible to avoid having to worry about fragmentation of time (and even more complexity).
//
// Once the final start time is known, then for each frame, check the allocation required in each frame.
// The total frame allocation is actually the time of the ending of the last transaction in the frame.
// It is possible that a new allocation can fit in an unallocated "gap" between two other allocated
// endpoints of slower periods. The new allocation is therefore any shift required of later endpoints in
// the frame.
//
// The shift is computed as:
// shift = (final_start_time + new.calc_bus_time) - curr.start_time
// If shift is positive, later endpoints must be moved by the shift amount, otherwise later endpoints
// (and the frame allocation) aren't affected.
latest_start = FS_SOF + HUB_FS_ADJ; // initial start time must be after the SOF transaction
// find latest start
for (i=0; ep->start_frame + i < MAXFRAMES; i += ep->actual_period)
{
if (ep->ep_type == isoch)
{
last_ep = ep->mytt->frame_budget[ep->start_frame + i].isoch_ehead;
curr_ep = last_ep->next_ep; // get past SOF endpoint
} else
{
last_ep = ep->mytt->frame_budget[ep->start_frame + i].int_ehead;
curr_ep = last_ep->next_ep; // get past dummy SOF endpoint
}
// if this frame has a large transaction endpoint, make sure we don't allocate another one
if (ep->mytt->frame_budget[ep->start_frame + i].allocated_large)
{
if (ep->calc_bus_time >= LARGEXACT)
{
error("too many large xacts"); // only one large transaction allowed in a frame.
return 0;
}
}
while (curr_ep) // walk endpoint list for this frame to find where to insert new endpoint
{
// Note: the actual insertion will be done later
//
if (OK_to_insert(curr_ep, ep))
{
break;
}
last_ep = curr_ep;
curr_ep = curr_ep->next_ep;
}
t = Compute_ep_start_time(curr_ep, ep, last_ep, ep->start_frame + i);
// update latest start time as required
if (t > latest_start)
latest_start = t;
} // end of for loop looking for latest start time
// Set the start time for the new endpoint
ep->start_time = latest_start;
if ((ep->start_time + ep->calc_bus_time) > FS_MAX_PERIODIC_ALLOCATION)
{
// error("start time %d past end of frame", ep->start_time + ep->calc_bus_time);
ep->calc_bus_time = 0;
return 0;
}
//***
//*** 4. Process each frame data structure for endpoint period in budget window
//***
changed_eps = 0; // Track number of endpoints that will need to be updated.
while (changed_ep_list[changed_eps]) // reset indicators of endpoints changed this pass.
{
changed_ep_list[changed_eps]->moved_this_req = 0;
changed_eps++;
}
// this allows appending changed endpoints onto the current change list.
// We have to check the allocation of this new endpoint in each frame. We also have to move any
// later endpoints in the frame to their new start times (and adjust their SS/CS allocations as
// appropriate).
//
// Doing this is tricky since:
// A. The actual isoch portion of the frame is organized in reverse order in the budget list
// B. If a large isoch transaction exists in this frame, it is first in the frame
//
// For a new isoch ep:
// We have to find the shift for this endpoint.
// We have to move isoch endpoints up to (but not including) the isoch insertion point endpoint
// We also have to move all interrupt endpoints (to the end of the list and frame) after we
// finish the isoch endpoints.
//
// For a new interrupt ep:
// skip to the insertion point without doing anything
// then move the start times of the rest of the interrupt endpoints until the end of the list (and frame)
//
// Allocate time in each frame of the endpoint period in the budgeting window.
//
// Since the interrupt frame budget is ordered with decreasing period (with holes in the budget),
// we must process all frames in the budget window to correctly move any smaller period interrupt
// endpoints that are affected by this new enpoint, even though the new endpoint is only added in
// its period frames
frame_bias = ep->start_frame;
frame_bias = - frame_bias;
for (frame_cnt=0; frame_cnt < MAXFRAMES; frame_cnt++)
{
//***
//*** 5. Now check allocation for each frame using shift adjustment based on latest start time
//***
if (ep->ep_type == isoch)
{
last_ep = ep->mytt->frame_budget[ep->start_frame + frame_bias].isoch_ehead;
curr_ep = last_ep->next_ep; // get past dummy SOF endpoint
p = last_ep; // save the starting point so we can start over after finding the shift time
// walk endpoint list for this frame to find where to insert new endpoint
while (curr_ep)
{
// Note: the actual insertion will be done later, this just does the allocation check
//
if (OK_to_insert(curr_ep, ep))
break;
last_ep = curr_ep;
curr_ep = curr_ep->next_ep;
}
// shift = (final_start_time + new.calc_bus_time) - curr.start_time
// for isoch the last_ep is the endpoint before which the new one is inserted, i.e. it is "curr"
if (last_ep != ep->mytt->frame_budget[ep->start_frame + frame_bias].isoch_ehead)
// This is somewhere in the "middle" of the list
shift_time = (latest_start + ep->calc_bus_time) - last_ep->start_time;
else
{
if (curr_ep)
{
// There is only one endpoint on the list, so must use 1st (non dummy SOF) int endpoint as
// next ep in frame (if there is one)
if (ep->mytt->frame_budget[ep->start_frame + frame_bias].int_ehead->next_ep)
shift_time = (latest_start + ep->calc_bus_time) -
ep->mytt->frame_budget[ep->start_frame + frame_bias].int_ehead->next_ep->start_time;
else // no int endpoints
shift_time = 0;
} else
// There are no endpoints on the isoch list
shift_time = ep->calc_bus_time;
}
//
// Check classic allocation
//
// check that if new ep is at end of frame, it will fit in the frame
//if ((ep->start_time + ep->calc_bus_time) > FS_MAX_PERIODIC_ALLOCATION)
//{
// error("new xact too late in frame");
// Deallocate_endpoint_budget(ep, changed_ep_list, max_changed_eps, frame_cnt);
// return 0;
//}
// check classic allocation with adjusted start time before proceeding
if (shift_time > 0)
{
// worst frame test to stop remaining processing as early as possible.
t = ep->mytt->frame_budget[ep->start_frame + frame_bias].time_used;
if ( ! Allocate_check(&t, shift_time, FS_MAX_PERIODIC_ALLOCATION))
{
Deallocate_endpoint_budget(ep, changed_ep_list, max_changed_eps, frame_cnt);
return 0;
}
}
//***
//*** 6a. Now move isoch endpoints, insert new isoch and then move interrupt endpoints
//***
last_ep = p;
curr_ep = last_ep->next_ep;
// Walk endpoint list for this frame to find where to insert new isoch endpoint.
// This time we move later isoch endpoints in frame (early endpoints on budget list)
while (curr_ep)
{
if (! OK_to_insert(curr_ep, ep))
{
if (shift_time > 0) {
moved = Move_ep(
curr_ep,
shift_time,
changed_ep_list,
&changed_eps,
*max_changed_eps,
&move_status);
if (! move_status)
retv = 0;
if (! moved) // already have visited the endpoints from here on in this frame
break;
}
} else // insert new endpoint here
break;
last_ep = curr_ep;
curr_ep = curr_ep->next_ep;
}
// Don't insert endpoint if its already been inserted and processed due to a previous endpoint
//
if (curr_ep != ep) {
if ((frame_bias % ep->actual_period) == 0)
// Only allocate new endpoint in its period frames
{
// insert new endpoint
if (ep->calc_bus_time >= LARGEXACT)
{
// save the large ep pointer
ep->mytt->frame_budget[ep->start_frame + frame_bias].allocated_large = ep;
// we don't link the large onto any other endpoints
} else
{ // not large, so link the endpoint onto the list
if (frame_bias == 0)
ep->next_ep = curr_ep;
last_ep->next_ep = ep;
}
}
// now move all interrupt endpoints
// find end of last isoch ep and check if it is after start of first interrupt, if so, interrupt
// eps must be shifted
p = ep->mytt->frame_budget[ep->start_frame + frame_bias].isoch_ehead; // sof
if (p->next_ep)
p = p->next_ep; // last actual isoch , could be null when no isoch allocated in this frame
else
if (ep->mytt->frame_budget[ep->start_frame + frame_bias].allocated_large)
p = ep->mytt->frame_budget[ep->start_frame + frame_bias].allocated_large;
last_ep = ep->mytt->frame_budget[ep->start_frame + frame_bias].int_ehead;
curr_ep = last_ep->next_ep; // get past dummy SOF endpoint
if (curr_ep) { // only move interrupt endpoints that are there
if ((p->start_time + p->calc_bus_time) > curr_ep->start_time) {
// Compute the shift time for all following interrupt endpoints ONCE. Then
// apply it to all endpoints. This ensures that any gaps between endpoints are
// preserved without compression.
shift_time = p->start_time + p->calc_bus_time - curr_ep->start_time;
while (curr_ep)
{
// Note: this doesn't do any insertion, just adjusts start times of interrupt eps
// we haven't seen yet
// << this can exit once it finds an ep we have already seen>>
moved = Move_ep(
curr_ep,
shift_time,
changed_ep_list,
&changed_eps,
*max_changed_eps,
&move_status);
if (! move_status)
retv = 0;
if (! moved)
break;
last_ep = curr_ep;
curr_ep = curr_ep->next_ep;
}
}
}
}
} else // interrupt
{
last_ep = ep->mytt->frame_budget[ep->start_frame + frame_bias].int_ehead;
curr_ep = last_ep->next_ep; // get past dummy SOF endpoint
// walk endpoint list for this frame to find where to insert new endpoint
while (curr_ep)
{
// Note: the actual insertion will be done later, this just does the allocation check
//
if (OK_to_insert(curr_ep, ep))
break;
last_ep = curr_ep;
curr_ep = curr_ep->next_ep;
}
// shift = (final_start_time + new.calc_bus_time) - curr.start_time
if (curr_ep)
shift_time = (latest_start + ep->calc_bus_time) - curr_ep->start_time;
else
shift_time = ep->calc_bus_time;
// only change endpoints in frame if the new interrupt endpoint is in this frame
if ((frame_bias % ep->actual_period) == 0)
{
shift_time = 0;
}
//
// Check classic allocation
//
// check classic allocation with adjusted start time before proceeding
if (shift_time > 0)
{
// worst frame test to stop remaining processing as early as possible.
t = ep->mytt->frame_budget[ep->start_frame + frame_bias].time_used;
if ( ! Allocate_check(&t, shift_time, FS_MAX_PERIODIC_ALLOCATION))
retv = 0;
}
//***
//*** 6b. Now insert new interrupt endpoint and move rest of interrupt endpoints
//***
// insert new endpoint
if (curr_ep != ep) {
if ((frame_bias % ep->actual_period) == 0) {
// Only allocate new endpoint in its period frames
if (frame_bias == 0)
ep->next_ep = curr_ep;
last_ep->next_ep = ep;
last_ep = ep;
}
if (shift_time > 0) {
while (curr_ep)
{
// Move the rest of the interrupt endpoints
//
moved = Move_ep(
curr_ep,
shift_time,
changed_ep_list,
&changed_eps,
*max_changed_eps,
&move_status);
if (! move_status)
retv = 0;
if (! moved)
break;
last_ep = curr_ep;
curr_ep = curr_ep->next_ep;
}
}
}
} // end of interrupt insertion handling
//***
//*** 7. Allocate HS bus time for endpoint
//***
if (frame_bias % ep->actual_period == 0) // Only allocate new endpoint in its period frames
{
if (! Allocate_HS(ep, frame_bias))
retv = 0;
//***
//*** 8. Allocate classic bus time
//***
ep->mytt->frame_budget[ep->start_frame + frame_bias].time_used += ep->calc_bus_time;
}
if (!retv)
{
// some error in this frame, so do partial deallocation and exit
Deallocate_endpoint_budget(ep, changed_ep_list, max_changed_eps, frame_cnt + 1);
return 0;
}
frame_bias++;
} // end of "for each frame in budget window"
return retv;
}
/*******
Deallocate_time_for_endpoint
********/
void
Deallocate_time_for_endpoint(
PEndpoint ep, // endpoint that needs to be removed (bus time deallocated)
PEndpoint changed_ep_list[], // pointer to array to set (on return) with list of
// changed endpoints
int *max_changed_eps // input: maximum size of (returned) list
// on return: number of changed endpoints
)
{
// Deallocate all frames of information
Deallocate_endpoint_budget(ep, changed_ep_list,max_changed_eps, MAXFRAMES);
}
/*******
Set_endpoint()
********/
Set_endpoint(
PEndpoint ep,
eptype t,
unsigned d,
unsigned s,
unsigned p,
unsigned m,
TT *thistt
)
{
ep->ep_type = t;
ep->direction = d;
ep->speed = s;
ep->period = p;
ep->max_packet = m;
ep->mytt = thistt;
ep->calc_bus_time = 0;
ep->start_frame = 0;
ep->start_microframe = 0;
ep->start_time = 0;
ep->num_starts = 0;
ep->num_completes = 0;
ep->actual_period = 0;
ep->next_ep = 0;
ep->saved_period = 0;
ep->promoted_this_time = 0;
ep->id = 0; // not needed for real budgeter
}
void
init_hc(PHC myHC)
{
int i,j;
PEndpoint ep;
// allocate at TT to test with
//myHC.tthead = (PTT) malloc(sizeof(TT));
myHC->thinktime = HS_HC_THINK_TIME;
myHC->allocation_limit = HS_MAX_PERIODIC_ALLOCATION;
myHC->speed = HSSPEED;
for (i=0; i<MAXFRAMES; i++)
{
for (j=0; j < MICROFRAMES_PER_FRAME; j++)
{
myHC->HS_microframe_info[i][j].time_used = 0;
}
}
}
void
init_tt(PHC myHC, PTT myTT)
{
int i,j;
PEndpoint ep;
myTT->think_time = 1;
myTT->myHC = myHC;
myTT->allocation_limit = FS_MAX_PERIODIC_ALLOCATION;
for (i=0; i<MAXFRAMES; i++)
{
myTT->frame_budget[i].time_used = FS_SOF + HUB_FS_ADJ;
myTT->frame_budget[i].allocated_large = 0;
for (j=0; j < MICROFRAMES_PER_FRAME; j++)
{
myTT->HS_split_data_time[i][j] = 0;
myTT->num_starts[i][j] = 0;
}
ep = &myTT->isoch_head[i];
myTT->frame_budget[i].isoch_ehead = ep;
// SOF at the beginning of each frame
Set_endpoint(ep, isoch, OUTDIR, FSSPEED, MAXFRAMES, 0, myTT);
ep->calc_bus_time = FS_SOF + HUB_FS_ADJ;
ep->actual_period = MAXFRAMES;
ep->start_microframe = -1;
ep->start_frame = i;
ep = &myTT->int_head[i];
myTT->frame_budget[i].int_ehead = ep;
// dummy SOF at the beginning of each int frame budget list
Set_endpoint(ep, interrupt, OUTDIR, FSSPEED, MAXFRAMES, 0, myTT);
ep->calc_bus_time = FS_SOF + HUB_FS_ADJ;
ep->actual_period = MAXFRAMES;
ep->start_microframe = -1;
ep->start_frame = i;
}
}