usb: dwc2: host: Totally redo the microframe scheduler
This totally reimplements the microframe scheduler in dwc2 to attempt to handle periodic splits properly. The old code didn't even try, so this was a significant effort since periodic splits are one of the most complicated things in USB. I've attempted to keep the old "don't use the microframe" schduler around for now, but not sure it's needed. It has also only been lightly tested. I think it's pretty certain that this scheduler isn't perfect and might have some bugs, but it seems much better than what was there before. With this change my stressful USB test (USB webcam + USB audio + some keyboards) crackles less. Acked-by: John Youn <johnyoun@synopsys.com> Signed-off-by: Douglas Anderson <dianders@chromium.org> Tested-by: Heiko Stuebner <heiko@sntech.de> Tested-by: Stefan Wahren <stefan.wahren@i2se.com> Signed-off-by: Felipe Balbi <balbi@kernel.org>
This commit is contained in:
parent
9cf1a601d2
commit
9f9f09b048
@ -592,6 +592,84 @@ struct dwc2_hregs_backup {
|
||||
bool valid;
|
||||
};
|
||||
|
||||
/*
|
||||
* Constants related to high speed periodic scheduling
|
||||
*
|
||||
* We have a periodic schedule that is DWC2_HS_SCHEDULE_UFRAMES long. From a
|
||||
* reservation point of view it's assumed that the schedule goes right back to
|
||||
* the beginning after the end of the schedule.
|
||||
*
|
||||
* What does that mean for scheduling things with a long interval? It means
|
||||
* we'll reserve time for them in every possible microframe that they could
|
||||
* ever be scheduled in. ...but we'll still only actually schedule them as
|
||||
* often as they were requested.
|
||||
*
|
||||
* We keep our schedule in a "bitmap" structure. This simplifies having
|
||||
* to keep track of and merge intervals: we just let the bitmap code do most
|
||||
* of the heavy lifting. In a way scheduling is much like memory allocation.
|
||||
*
|
||||
* We schedule 100us per uframe or 80% of 125us (the maximum amount you're
|
||||
* supposed to schedule for periodic transfers). That's according to spec.
|
||||
*
|
||||
* Note that though we only schedule 80% of each microframe, the bitmap that we
|
||||
* keep the schedule in is tightly packed (AKA it doesn't have 100us worth of
|
||||
* space for each uFrame).
|
||||
*
|
||||
* Requirements:
|
||||
* - DWC2_HS_SCHEDULE_UFRAMES must even divide 0x4000 (HFNUM_MAX_FRNUM + 1)
|
||||
* - DWC2_HS_SCHEDULE_UFRAMES must be 8 times DWC2_LS_SCHEDULE_FRAMES (probably
|
||||
* could be any multiple of 8 times DWC2_LS_SCHEDULE_FRAMES, but there might
|
||||
* be bugs). The 8 comes from the USB spec: number of microframes per frame.
|
||||
*/
|
||||
#define DWC2_US_PER_UFRAME 125
|
||||
#define DWC2_HS_PERIODIC_US_PER_UFRAME 100
|
||||
|
||||
#define DWC2_HS_SCHEDULE_UFRAMES 8
|
||||
#define DWC2_HS_SCHEDULE_US (DWC2_HS_SCHEDULE_UFRAMES * \
|
||||
DWC2_HS_PERIODIC_US_PER_UFRAME)
|
||||
|
||||
/*
|
||||
* Constants related to low speed scheduling
|
||||
*
|
||||
* For high speed we schedule every 1us. For low speed that's a bit overkill,
|
||||
* so we make up a unit called a "slice" that's worth 25us. There are 40
|
||||
* slices in a full frame and we can schedule 36 of those (90%) for periodic
|
||||
* transfers.
|
||||
*
|
||||
* Our low speed schedule can be as short as 1 frame or could be longer. When
|
||||
* we only schedule 1 frame it means that we'll need to reserve a time every
|
||||
* frame even for things that only transfer very rarely, so something that runs
|
||||
* every 2048 frames will get time reserved in every frame. Our low speed
|
||||
* schedule can be longer and we'll be able to handle more overlap, but that
|
||||
* will come at increased memory cost and increased time to schedule.
|
||||
*
|
||||
* Note: one other advantage of a short low speed schedule is that if we mess
|
||||
* up and miss scheduling we can jump in and use any of the slots that we
|
||||
* happened to reserve.
|
||||
*
|
||||
* With 25 us per slice and 1 frame in the schedule, we only need 4 bytes for
|
||||
* the schedule. There will be one schedule per TT.
|
||||
*
|
||||
* Requirements:
|
||||
* - DWC2_US_PER_SLICE must evenly divide DWC2_LS_PERIODIC_US_PER_FRAME.
|
||||
*/
|
||||
#define DWC2_US_PER_SLICE 25
|
||||
#define DWC2_SLICES_PER_UFRAME (DWC2_US_PER_UFRAME / DWC2_US_PER_SLICE)
|
||||
|
||||
#define DWC2_ROUND_US_TO_SLICE(us) \
|
||||
(DIV_ROUND_UP((us), DWC2_US_PER_SLICE) * \
|
||||
DWC2_US_PER_SLICE)
|
||||
|
||||
#define DWC2_LS_PERIODIC_US_PER_FRAME \
|
||||
900
|
||||
#define DWC2_LS_PERIODIC_SLICES_PER_FRAME \
|
||||
(DWC2_LS_PERIODIC_US_PER_FRAME / \
|
||||
DWC2_US_PER_SLICE)
|
||||
|
||||
#define DWC2_LS_SCHEDULE_FRAMES 1
|
||||
#define DWC2_LS_SCHEDULE_SLICES (DWC2_LS_SCHEDULE_FRAMES * \
|
||||
DWC2_LS_PERIODIC_SLICES_PER_FRAME)
|
||||
|
||||
/**
|
||||
* struct dwc2_hsotg - Holds the state of the driver, including the non-periodic
|
||||
* and periodic schedules
|
||||
@ -682,7 +760,9 @@ struct dwc2_hregs_backup {
|
||||
* This value is in microseconds per (micro)frame. The
|
||||
* assumption is that all periodic transfers may occur in
|
||||
* the same (micro)frame.
|
||||
* @frame_usecs: Internal variable used by the microframe scheduler
|
||||
* @hs_periodic_bitmap: Bitmap used by the microframe scheduler any time the
|
||||
* host is in high speed mode; low speed schedules are
|
||||
* stored elsewhere since we need one per TT.
|
||||
* @frame_number: Frame number read from the core at SOF. The value ranges
|
||||
* from 0 to HFNUM_MAX_FRNUM.
|
||||
* @periodic_qh_count: Count of periodic QHs, if using several eps. Used for
|
||||
@ -803,7 +883,8 @@ struct dwc2_hsotg {
|
||||
struct list_head periodic_sched_queued;
|
||||
struct list_head split_order;
|
||||
u16 periodic_usecs;
|
||||
u16 frame_usecs[8];
|
||||
unsigned long hs_periodic_bitmap[
|
||||
DIV_ROUND_UP(DWC2_HS_SCHEDULE_US, BITS_PER_LONG)];
|
||||
u16 frame_number;
|
||||
u16 periodic_qh_count;
|
||||
bool bus_suspended;
|
||||
|
@ -2252,6 +2252,90 @@ void dwc2_host_hub_info(struct dwc2_hsotg *hsotg, void *context, int *hub_addr,
|
||||
*hub_port = urb->dev->ttport;
|
||||
}
|
||||
|
||||
/**
|
||||
* dwc2_host_get_tt_info() - Get the dwc2_tt associated with context
|
||||
*
|
||||
* This will get the dwc2_tt structure (and ttport) associated with the given
|
||||
* context (which is really just a struct urb pointer).
|
||||
*
|
||||
* The first time this is called for a given TT we allocate memory for our
|
||||
* structure. When everyone is done and has called dwc2_host_put_tt_info()
|
||||
* then the refcount for the structure will go to 0 and we'll free it.
|
||||
*
|
||||
* @hsotg: The HCD state structure for the DWC OTG controller.
|
||||
* @qh: The QH structure.
|
||||
* @context: The priv pointer from a struct dwc2_hcd_urb.
|
||||
* @mem_flags: Flags for allocating memory.
|
||||
* @ttport: We'll return this device's port number here. That's used to
|
||||
* reference into the bitmap if we're on a multi_tt hub.
|
||||
*
|
||||
* Return: a pointer to a struct dwc2_tt. Don't forget to call
|
||||
* dwc2_host_put_tt_info()! Returns NULL upon memory alloc failure.
|
||||
*/
|
||||
|
||||
struct dwc2_tt *dwc2_host_get_tt_info(struct dwc2_hsotg *hsotg, void *context,
|
||||
gfp_t mem_flags, int *ttport)
|
||||
{
|
||||
struct urb *urb = context;
|
||||
struct dwc2_tt *dwc_tt = NULL;
|
||||
|
||||
if (urb->dev->tt) {
|
||||
*ttport = urb->dev->ttport;
|
||||
|
||||
dwc_tt = urb->dev->tt->hcpriv;
|
||||
if (dwc_tt == NULL) {
|
||||
size_t bitmap_size;
|
||||
|
||||
/*
|
||||
* For single_tt we need one schedule. For multi_tt
|
||||
* we need one per port.
|
||||
*/
|
||||
bitmap_size = DWC2_ELEMENTS_PER_LS_BITMAP *
|
||||
sizeof(dwc_tt->periodic_bitmaps[0]);
|
||||
if (urb->dev->tt->multi)
|
||||
bitmap_size *= urb->dev->tt->hub->maxchild;
|
||||
|
||||
dwc_tt = kzalloc(sizeof(*dwc_tt) + bitmap_size,
|
||||
mem_flags);
|
||||
if (dwc_tt == NULL)
|
||||
return NULL;
|
||||
|
||||
dwc_tt->usb_tt = urb->dev->tt;
|
||||
dwc_tt->usb_tt->hcpriv = dwc_tt;
|
||||
}
|
||||
|
||||
dwc_tt->refcount++;
|
||||
}
|
||||
|
||||
return dwc_tt;
|
||||
}
|
||||
|
||||
/**
|
||||
* dwc2_host_put_tt_info() - Put the dwc2_tt from dwc2_host_get_tt_info()
|
||||
*
|
||||
* Frees resources allocated by dwc2_host_get_tt_info() if all current holders
|
||||
* of the structure are done.
|
||||
*
|
||||
* It's OK to call this with NULL.
|
||||
*
|
||||
* @hsotg: The HCD state structure for the DWC OTG controller.
|
||||
* @dwc_tt: The pointer returned by dwc2_host_get_tt_info.
|
||||
*/
|
||||
void dwc2_host_put_tt_info(struct dwc2_hsotg *hsotg, struct dwc2_tt *dwc_tt)
|
||||
{
|
||||
/* Model kfree and make put of NULL a no-op */
|
||||
if (dwc_tt == NULL)
|
||||
return;
|
||||
|
||||
WARN_ON(dwc_tt->refcount < 1);
|
||||
|
||||
dwc_tt->refcount--;
|
||||
if (!dwc_tt->refcount) {
|
||||
dwc_tt->usb_tt->hcpriv = NULL;
|
||||
kfree(dwc_tt);
|
||||
}
|
||||
}
|
||||
|
||||
int dwc2_host_get_speed(struct dwc2_hsotg *hsotg, void *context)
|
||||
{
|
||||
struct urb *urb = context;
|
||||
@ -3197,9 +3281,6 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq)
|
||||
hsotg->hc_ptr_array[i] = channel;
|
||||
}
|
||||
|
||||
if (hsotg->core_params->uframe_sched > 0)
|
||||
dwc2_hcd_init_usecs(hsotg);
|
||||
|
||||
/* Initialize hsotg start work */
|
||||
INIT_DELAYED_WORK(&hsotg->start_work, dwc2_hcd_start_func);
|
||||
|
||||
|
@ -212,6 +212,43 @@ enum dwc2_transaction_type {
|
||||
DWC2_TRANSACTION_ALL,
|
||||
};
|
||||
|
||||
/* The number of elements per LS bitmap (per port on multi_tt) */
|
||||
#define DWC2_ELEMENTS_PER_LS_BITMAP DIV_ROUND_UP(DWC2_LS_SCHEDULE_SLICES, \
|
||||
BITS_PER_LONG)
|
||||
|
||||
/**
|
||||
* struct dwc2_tt - dwc2 data associated with a usb_tt
|
||||
*
|
||||
* @refcount: Number of Queue Heads (QHs) holding a reference.
|
||||
* @usb_tt: Pointer back to the official usb_tt.
|
||||
* @periodic_bitmaps: Bitmap for which parts of the 1ms frame are accounted
|
||||
* for already. Each is DWC2_ELEMENTS_PER_LS_BITMAP
|
||||
* elements (so sizeof(long) times that in bytes).
|
||||
*
|
||||
* This structure is stored in the hcpriv of the official usb_tt.
|
||||
*/
|
||||
struct dwc2_tt {
|
||||
int refcount;
|
||||
struct usb_tt *usb_tt;
|
||||
unsigned long periodic_bitmaps[];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dwc2_hs_transfer_time - Info about a transfer on the high speed bus.
|
||||
*
|
||||
* @start_schedule_usecs: The start time on the main bus schedule. Note that
|
||||
* the main bus schedule is tightly packed and this
|
||||
* time should be interpreted as tightly packed (so
|
||||
* uFrame 0 starts at 0 us, uFrame 1 starts at 100 us
|
||||
* instead of 125 us).
|
||||
* @duration_us: How long this transfer goes.
|
||||
*/
|
||||
|
||||
struct dwc2_hs_transfer_time {
|
||||
u32 start_schedule_us;
|
||||
u16 duration_us;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dwc2_qh - Software queue head structure
|
||||
*
|
||||
@ -237,18 +274,33 @@ enum dwc2_transaction_type {
|
||||
* @td_first: Index of first activated isochronous transfer descriptor
|
||||
* @td_last: Index of last activated isochronous transfer descriptor
|
||||
* @host_us: Bandwidth in microseconds per transfer as seen by host
|
||||
* @device_us: Bandwidth in microseconds per transfer as seen by device
|
||||
* @host_interval: Interval between transfers as seen by the host. If
|
||||
* the host is high speed and the device is low speed this
|
||||
* will be 8 times device interval.
|
||||
* @next_active_frame: (Micro)frame before we next need to put something on
|
||||
* @device_interval: Interval between transfers as seen by the device.
|
||||
* interval.
|
||||
* @next_active_frame: (Micro)frame _before_ we next need to put something on
|
||||
* the bus. We'll move the qh to active here. If the
|
||||
* host is in high speed mode this will be a uframe. If
|
||||
* the host is in low speed mode this will be a full frame.
|
||||
* @start_active_frame: If we are partway through a split transfer, this will be
|
||||
* what next_active_frame was when we started. Otherwise
|
||||
* it should always be the same as next_active_frame.
|
||||
* @assigned_uframe: The uframe (0 -7) assigned by dwc2_find_uframe().
|
||||
* @frame_usecs: Internal variable used by the microframe scheduler
|
||||
* @num_hs_transfers: Number of transfers in hs_transfers.
|
||||
* Normally this is 1 but can be more than one for splits.
|
||||
* Always >= 1 unless the host is in low/full speed mode.
|
||||
* @hs_transfers: Transfers that are scheduled as seen by the high speed
|
||||
* bus. Not used if host is in low or full speed mode (but
|
||||
* note that it IS USED if the device is low or full speed
|
||||
* as long as the HOST is in high speed mode).
|
||||
* @ls_start_schedule_slice: Start time (in slices) on the low speed bus
|
||||
* schedule that's being used by this device. This
|
||||
* will be on the periodic_bitmap in a
|
||||
* "struct dwc2_tt". Not used if this device is high
|
||||
* speed. Note that this is in "schedule slice" which
|
||||
* is tightly packed.
|
||||
* @ls_duration_us: Duration on the low speed bus schedule.
|
||||
* @ntd: Actual number of transfer descriptors in a list
|
||||
* @qtd_list: List of QTDs for this QH
|
||||
* @channel: Host channel currently processing transfers for this QH
|
||||
@ -261,8 +313,12 @@ enum dwc2_transaction_type {
|
||||
* descriptor and indicates original XferSize value for the
|
||||
* descriptor
|
||||
* @unreserve_timer: Timer for releasing periodic reservation.
|
||||
* @dwc2_tt: Pointer to our tt info (or NULL if no tt).
|
||||
* @ttport: Port number within our tt.
|
||||
* @tt_buffer_dirty True if clear_tt_buffer_complete is pending
|
||||
* @unreserve_pending: True if we planned to unreserve but haven't yet.
|
||||
* @schedule_low_speed: True if we have a low/full speed component (either the
|
||||
* host is in low/full speed mode or do_split).
|
||||
*
|
||||
* A Queue Head (QH) holds the static characteristics of an endpoint and
|
||||
* maintains a list of transfers (QTDs) for that endpoint. A QH structure may
|
||||
@ -280,11 +336,14 @@ struct dwc2_qh {
|
||||
u8 td_first;
|
||||
u8 td_last;
|
||||
u16 host_us;
|
||||
u16 device_us;
|
||||
u16 host_interval;
|
||||
u16 device_interval;
|
||||
u16 next_active_frame;
|
||||
u16 start_active_frame;
|
||||
u16 assigned_uframe;
|
||||
u16 frame_usecs[8];
|
||||
s16 num_hs_transfers;
|
||||
struct dwc2_hs_transfer_time hs_transfers[DWC2_HS_SCHEDULE_UFRAMES];
|
||||
u32 ls_start_schedule_slice;
|
||||
u16 ntd;
|
||||
struct list_head qtd_list;
|
||||
struct dwc2_host_chan *channel;
|
||||
@ -294,8 +353,11 @@ struct dwc2_qh {
|
||||
u32 desc_list_sz;
|
||||
u32 *n_bytes;
|
||||
struct timer_list unreserve_timer;
|
||||
struct dwc2_tt *dwc_tt;
|
||||
int ttport;
|
||||
unsigned tt_buffer_dirty:1;
|
||||
unsigned unreserve_pending:1;
|
||||
unsigned schedule_low_speed:1;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -462,7 +524,6 @@ extern void dwc2_hcd_queue_transactions(struct dwc2_hsotg *hsotg,
|
||||
|
||||
/* Schedule Queue Functions */
|
||||
/* Implemented in hcd_queue.c */
|
||||
extern void dwc2_hcd_init_usecs(struct dwc2_hsotg *hsotg);
|
||||
extern struct dwc2_qh *dwc2_hcd_qh_create(struct dwc2_hsotg *hsotg,
|
||||
struct dwc2_hcd_urb *urb,
|
||||
gfp_t mem_flags);
|
||||
@ -728,6 +789,12 @@ extern void dwc2_host_start(struct dwc2_hsotg *hsotg);
|
||||
extern void dwc2_host_disconnect(struct dwc2_hsotg *hsotg);
|
||||
extern void dwc2_host_hub_info(struct dwc2_hsotg *hsotg, void *context,
|
||||
int *hub_addr, int *hub_port);
|
||||
extern struct dwc2_tt *dwc2_host_get_tt_info(struct dwc2_hsotg *hsotg,
|
||||
void *context, gfp_t mem_flags,
|
||||
int *ttport);
|
||||
|
||||
extern void dwc2_host_put_tt_info(struct dwc2_hsotg *hsotg,
|
||||
struct dwc2_tt *dwc_tt);
|
||||
extern int dwc2_host_get_speed(struct dwc2_hsotg *hsotg, void *context);
|
||||
extern void dwc2_host_complete(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd,
|
||||
int status);
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user