|
@@ -136,115 +136,932 @@ static int dwc2_check_periodic_bandwidth(struct dwc2_hsotg *hsotg,
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Microframe scheduler
|
|
|
- * track the total use in hsotg->frame_usecs
|
|
|
- * keep each qh use in qh->frame_usecs
|
|
|
- * when surrendering the qh then donate the time back
|
|
|
+ * pmap_schedule() - Schedule time in a periodic bitmap (pmap).
|
|
|
+ *
|
|
|
+ * @map: The bitmap representing the schedule; will be updated
|
|
|
+ * upon success.
|
|
|
+ * @bits_per_period: The schedule represents several periods. This is how many
|
|
|
+ * bits are in each period. It's assumed that the beginning
|
|
|
+ * of the schedule will repeat after its end.
|
|
|
+ * @periods_in_map: The number of periods in the schedule.
|
|
|
+ * @num_bits: The number of bits we need per period we want to reserve
|
|
|
+ * in this function call.
|
|
|
+ * @interval: How often we need to be scheduled for the reservation this
|
|
|
+ * time. 1 means every period. 2 means every other period.
|
|
|
+ * ...you get the picture?
|
|
|
+ * @start: The bit number to start at. Normally 0. Must be within
|
|
|
+ * the interval or we return failure right away.
|
|
|
+ * @only_one_period: Normally we'll allow picking a start anywhere within the
|
|
|
+ * first interval, since we can still make all repetition
|
|
|
+ * requirements by doing that. However, if you pass true
|
|
|
+ * here then we'll return failure if we can't fit within
|
|
|
+ * the period that "start" is in.
|
|
|
+ *
|
|
|
+ * The idea here is that we want to schedule time for repeating events that all
|
|
|
+ * want the same resource. The resource is divided into fixed-sized periods
|
|
|
+ * and the events want to repeat every "interval" periods. The schedule
|
|
|
+ * granularity is one bit.
|
|
|
+ *
|
|
|
+ * To keep things "simple", we'll represent our schedule with a bitmap that
|
|
|
+ * contains a fixed number of periods. This gets rid of a lot of complexity
|
|
|
+ * but does mean that we need to handle things specially (and non-ideally) if
|
|
|
+ * the number of the periods in the schedule doesn't match well with the
|
|
|
+ * intervals that we're trying to schedule.
|
|
|
+ *
|
|
|
+ * Here's an explanation of the scheme we'll implement, assuming 8 periods.
|
|
|
+ * - If interval is 1, we need to take up space in each of the 8
|
|
|
+ * periods we're scheduling. Easy.
|
|
|
+ * - If interval is 2, we need to take up space in half of the
|
|
|
+ * periods. Again, easy.
|
|
|
+ * - If interval is 3, we actually need to fall back to interval 1.
|
|
|
+ * Why? Because we might need time in any period. AKA for the
|
|
|
+ * first 8 periods, we'll be in slot 0, 3, 6. Then we'll be
|
|
|
+ * in slot 1, 4, 7. Then we'll be in 2, 5. Then we'll be back to
|
|
|
+ * 0, 3, and 6. Since we could be in any frame we need to reserve
|
|
|
+ * for all of them. Sucks, but that's what you gotta do. Note that
|
|
|
+ * if we were instead scheduling 8 * 3 = 24 we'd do much better, but
|
|
|
+ * then we need more memory and time to do scheduling.
|
|
|
+ * - If interval is 4, easy.
|
|
|
+ * - If interval is 5, we again need interval 1. The schedule will be
|
|
|
+ * 0, 5, 2, 7, 4, 1, 6, 3, 0
|
|
|
+ * - If interval is 6, we need interval 2. 0, 6, 4, 2.
|
|
|
+ * - If interval is 7, we need interval 1.
|
|
|
+ * - If interval is 8, we need interval 8.
|
|
|
+ *
|
|
|
+ * If you do the math, you'll see that we need to pretend that interval is
|
|
|
+ * equal to the greatest_common_divisor(interval, periods_in_map).
|
|
|
+ *
|
|
|
+ * Note that at the moment this function tends to front-pack the schedule.
|
|
|
+ * In some cases that's really non-ideal (it's hard to schedule things that
|
|
|
+ * need to repeat every period). In other cases it's perfect (you can easily
|
|
|
+ * schedule bigger, less often repeating things).
|
|
|
+ *
|
|
|
+ * Here's the algorithm in action (8 periods, 5 bits per period):
|
|
|
+ * |** | |** | |** | |** | | OK 2 bits, intv 2 at 0
|
|
|
+ * |*****| ***|*****| ***|*****| ***|*****| ***| OK 3 bits, intv 3 at 2
|
|
|
+ * |*****|* ***|*****| ***|*****|* ***|*****| ***| OK 1 bits, intv 4 at 5
|
|
|
+ * |** |* |** | |** |* |** | | Remv 3 bits, intv 3 at 2
|
|
|
+ * |*** |* |*** | |*** |* |*** | | OK 1 bits, intv 6 at 2
|
|
|
+ * |**** |* * |**** | * |**** |* * |**** | * | OK 1 bits, intv 1 at 3
|
|
|
+ * |**** |**** |**** | *** |**** |**** |**** | *** | OK 2 bits, intv 2 at 6
|
|
|
+ * |*****|*****|*****| ****|*****|*****|*****| ****| OK 1 bits, intv 1 at 4
|
|
|
+ * |*****|*****|*****| ****|*****|*****|*****| ****| FAIL 1 bits, intv 1
|
|
|
+ * | ***|*****| ***| ****| ***|*****| ***| ****| Remv 2 bits, intv 2 at 0
|
|
|
+ * | ***| ****| ***| ****| ***| ****| ***| ****| Remv 1 bits, intv 4 at 5
|
|
|
+ * | **| ****| **| ****| **| ****| **| ****| Remv 1 bits, intv 6 at 2
|
|
|
+ * | *| ** *| *| ** *| *| ** *| *| ** *| Remv 1 bits, intv 1 at 3
|
|
|
+ * | *| *| *| *| *| *| *| *| Remv 2 bits, intv 2 at 6
|
|
|
+ * | | | | | | | | | Remv 1 bits, intv 1 at 4
|
|
|
+ * |** | |** | |** | |** | | OK 2 bits, intv 2 at 0
|
|
|
+ * |*** | |** | |*** | |** | | OK 1 bits, intv 4 at 2
|
|
|
+ * |*****| |** **| |*****| |** **| | OK 2 bits, intv 2 at 3
|
|
|
+ * |*****|* |** **| |*****|* |** **| | OK 1 bits, intv 4 at 5
|
|
|
+ * |*****|*** |** **| ** |*****|*** |** **| ** | OK 2 bits, intv 2 at 6
|
|
|
+ * |*****|*****|** **| ****|*****|*****|** **| ****| OK 2 bits, intv 2 at 8
|
|
|
+ * |*****|*****|*****| ****|*****|*****|*****| ****| OK 1 bits, intv 4 at 12
|
|
|
+ *
|
|
|
+ * This function is pretty generic and could be easily abstracted if anything
|
|
|
+ * needed similar scheduling.
|
|
|
+ *
|
|
|
+ * Returns either -ENOSPC or a >= 0 start bit which should be passed to the
|
|
|
+ * unschedule routine. The map bitmap will be updated on a non-error result.
|
|
|
*/
|
|
|
-static const unsigned short max_uframe_usecs[] = {
|
|
|
- 100, 100, 100, 100, 100, 100, 30, 0
|
|
|
-};
|
|
|
+static int pmap_schedule(unsigned long *map, int bits_per_period,
|
|
|
+ int periods_in_map, int num_bits,
|
|
|
+ int interval, int start, bool only_one_period)
|
|
|
+{
|
|
|
+ int interval_bits;
|
|
|
+ int to_reserve;
|
|
|
+ int first_end;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (num_bits > bits_per_period)
|
|
|
+ return -ENOSPC;
|
|
|
+
|
|
|
+ /* Adjust interval as per description */
|
|
|
+ interval = gcd(interval, periods_in_map);
|
|
|
+
|
|
|
+ interval_bits = bits_per_period * interval;
|
|
|
+ to_reserve = periods_in_map / interval;
|
|
|
+
|
|
|
+ /* If start has gotten us past interval then we can't schedule */
|
|
|
+ if (start >= interval_bits)
|
|
|
+ return -ENOSPC;
|
|
|
+
|
|
|
+ if (only_one_period)
|
|
|
+ /* Must fit within same period as start; end at begin of next */
|
|
|
+ first_end = (start / bits_per_period + 1) * bits_per_period;
|
|
|
+ else
|
|
|
+ /* Can fit anywhere in the first interval */
|
|
|
+ first_end = interval_bits;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * We'll try to pick the first repetition, then see if that time
|
|
|
+ * is free for each of the subsequent repetitions. If it's not
|
|
|
+ * we'll adjust the start time for the next search of the first
|
|
|
+ * repetition.
|
|
|
+ */
|
|
|
+ while (start + num_bits <= first_end) {
|
|
|
+ int end;
|
|
|
+
|
|
|
+ /* Need to stay within this period */
|
|
|
+ end = (start / bits_per_period + 1) * bits_per_period;
|
|
|
+
|
|
|
+ /* Look for num_bits us in this microframe starting at start */
|
|
|
+ start = bitmap_find_next_zero_area(map, end, start, num_bits,
|
|
|
+ 0);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * We should get start >= end if we fail. We might be
|
|
|
+ * able to check the next microframe depending on the
|
|
|
+ * interval, so continue on (start already updated).
|
|
|
+ */
|
|
|
+ if (start >= end) {
|
|
|
+ start = end;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* At this point we have a valid point for first one */
|
|
|
+ for (i = 1; i < to_reserve; i++) {
|
|
|
+ int ith_start = start + interval_bits * i;
|
|
|
+ int ith_end = end + interval_bits * i;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* Use this as a dumb "check if bits are 0" */
|
|
|
+ ret = bitmap_find_next_zero_area(
|
|
|
+ map, ith_start + num_bits, ith_start, num_bits,
|
|
|
+ 0);
|
|
|
+
|
|
|
+ /* We got the right place, continue checking */
|
|
|
+ if (ret == ith_start)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ /* Move start up for next time and exit for loop */
|
|
|
+ ith_start = bitmap_find_next_zero_area(
|
|
|
+ map, ith_end, ith_start, num_bits, 0);
|
|
|
+ if (ith_start >= ith_end)
|
|
|
+ /* Need a while new period next time */
|
|
|
+ start = end;
|
|
|
+ else
|
|
|
+ start = ith_start - interval_bits * i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* If didn't exit the for loop with a break, we have success */
|
|
|
+ if (i == to_reserve)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (start + num_bits > first_end)
|
|
|
+ return -ENOSPC;
|
|
|
|
|
|
-void dwc2_hcd_init_usecs(struct dwc2_hsotg *hsotg)
|
|
|
+ for (i = 0; i < to_reserve; i++) {
|
|
|
+ int ith_start = start + interval_bits * i;
|
|
|
+
|
|
|
+ bitmap_set(map, ith_start, num_bits);
|
|
|
+ }
|
|
|
+
|
|
|
+ return start;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * pmap_unschedule() - Undo work done by pmap_schedule()
|
|
|
+ *
|
|
|
+ * @map: See pmap_schedule().
|
|
|
+ * @bits_per_period: See pmap_schedule().
|
|
|
+ * @periods_in_map: See pmap_schedule().
|
|
|
+ * @num_bits: The number of bits that was passed to schedule.
|
|
|
+ * @interval: The interval that was passed to schedule.
|
|
|
+ * @start: The return value from pmap_schedule().
|
|
|
+ */
|
|
|
+static void pmap_unschedule(unsigned long *map, int bits_per_period,
|
|
|
+ int periods_in_map, int num_bits,
|
|
|
+ int interval, int start)
|
|
|
{
|
|
|
+ int interval_bits;
|
|
|
+ int to_release;
|
|
|
int i;
|
|
|
|
|
|
- for (i = 0; i < 8; i++)
|
|
|
- hsotg->frame_usecs[i] = max_uframe_usecs[i];
|
|
|
+ /* Adjust interval as per description in pmap_schedule() */
|
|
|
+ interval = gcd(interval, periods_in_map);
|
|
|
+
|
|
|
+ interval_bits = bits_per_period * interval;
|
|
|
+ to_release = periods_in_map / interval;
|
|
|
+
|
|
|
+ for (i = 0; i < to_release; i++) {
|
|
|
+ int ith_start = start + interval_bits * i;
|
|
|
+
|
|
|
+ bitmap_clear(map, ith_start, num_bits);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-static int dwc2_find_single_uframe(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
|
|
|
+/*
|
|
|
+ * cat_printf() - A printf() + strcat() helper
|
|
|
+ *
|
|
|
+ * This is useful for concatenating a bunch of strings where each string is
|
|
|
+ * constructed using printf.
|
|
|
+ *
|
|
|
+ * @buf: The destination buffer; will be updated to point after the printed
|
|
|
+ * data.
|
|
|
+ * @size: The number of bytes in the buffer (includes space for '\0').
|
|
|
+ * @fmt: The format for printf.
|
|
|
+ * @...: The args for printf.
|
|
|
+ */
|
|
|
+static void cat_printf(char **buf, size_t *size, const char *fmt, ...)
|
|
|
{
|
|
|
- unsigned short utime = qh->host_us;
|
|
|
+ va_list args;
|
|
|
int i;
|
|
|
|
|
|
- for (i = 0; i < 8; i++) {
|
|
|
- /* At the start hsotg->frame_usecs[i] = max_uframe_usecs[i] */
|
|
|
- if (utime <= hsotg->frame_usecs[i]) {
|
|
|
- hsotg->frame_usecs[i] -= utime;
|
|
|
- qh->frame_usecs[i] += utime;
|
|
|
- return i;
|
|
|
- }
|
|
|
+ if (*size == 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ va_start(args, fmt);
|
|
|
+ i = vsnprintf(*buf, *size, fmt, args);
|
|
|
+ va_end(args);
|
|
|
+
|
|
|
+ if (i >= *size) {
|
|
|
+ (*buf)[*size - 1] = '\0';
|
|
|
+ *buf += *size;
|
|
|
+ *size = 0;
|
|
|
+ } else {
|
|
|
+ *buf += i;
|
|
|
+ *size -= i;
|
|
|
}
|
|
|
- return -ENOSPC;
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
- * use this for FS apps that can span multiple uframes
|
|
|
+ * pmap_print() - Print the given periodic map
|
|
|
+ *
|
|
|
+ * Will attempt to print out the periodic schedule.
|
|
|
+ *
|
|
|
+ * @map: See pmap_schedule().
|
|
|
+ * @bits_per_period: See pmap_schedule().
|
|
|
+ * @periods_in_map: See pmap_schedule().
|
|
|
+ * @period_name: The name of 1 period, like "uFrame"
|
|
|
+ * @units: The name of the units, like "us".
|
|
|
+ * @print_fn: The function to call for printing.
|
|
|
+ * @print_data: Opaque data to pass to the print function.
|
|
|
*/
|
|
|
-static int dwc2_find_multi_uframe(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
|
|
|
+static void pmap_print(unsigned long *map, int bits_per_period,
|
|
|
+ int periods_in_map, const char *period_name,
|
|
|
+ const char *units,
|
|
|
+ void (*print_fn)(const char *str, void *data),
|
|
|
+ void *print_data)
|
|
|
{
|
|
|
- unsigned short utime = qh->host_us;
|
|
|
- unsigned short xtime;
|
|
|
- int t_left;
|
|
|
+ int period;
|
|
|
+
|
|
|
+ for (period = 0; period < periods_in_map; period++) {
|
|
|
+ char tmp[64];
|
|
|
+ char *buf = tmp;
|
|
|
+ size_t buf_size = sizeof(tmp);
|
|
|
+ int period_start = period * bits_per_period;
|
|
|
+ int period_end = period_start + bits_per_period;
|
|
|
+ int start = 0;
|
|
|
+ int count = 0;
|
|
|
+ bool printed = false;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = period_start; i < period_end + 1; i++) {
|
|
|
+ /* Handle case when ith bit is set */
|
|
|
+ if (i < period_end &&
|
|
|
+ bitmap_find_next_zero_area(map, i + 1,
|
|
|
+ i, 1, 0) != i) {
|
|
|
+ if (count == 0)
|
|
|
+ start = i - period_start;
|
|
|
+ count++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ith bit isn't set; don't care if count == 0 */
|
|
|
+ if (count == 0)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (!printed)
|
|
|
+ cat_printf(&buf, &buf_size, "%s %d: ",
|
|
|
+ period_name, period);
|
|
|
+ else
|
|
|
+ cat_printf(&buf, &buf_size, ", ");
|
|
|
+ printed = true;
|
|
|
+
|
|
|
+ cat_printf(&buf, &buf_size, "%d %s -%3d %s", start,
|
|
|
+ units, start + count - 1, units);
|
|
|
+ count = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (printed)
|
|
|
+ print_fn(tmp, print_data);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dwc2_get_ls_map() - Get the map used for the given qh
|
|
|
+ *
|
|
|
+ * @hsotg: The HCD state structure for the DWC OTG controller.
|
|
|
+ * @qh: QH for the periodic transfer.
|
|
|
+ *
|
|
|
+ * We'll always get the periodic map out of our TT. Note that even if we're
|
|
|
+ * running the host straight in low speed / full speed mode it appears as if
|
|
|
+ * a TT is allocated for us, so we'll use it. If that ever changes we can
|
|
|
+ * add logic here to get a map out of "hsotg" if !qh->do_split.
|
|
|
+ *
|
|
|
+ * Returns: the map or NULL if a map couldn't be found.
|
|
|
+ */
|
|
|
+static unsigned long *dwc2_get_ls_map(struct dwc2_hsotg *hsotg,
|
|
|
+ struct dwc2_qh *qh)
|
|
|
+{
|
|
|
+ unsigned long *map;
|
|
|
+
|
|
|
+ /* Don't expect to be missing a TT and be doing low speed scheduling */
|
|
|
+ if (WARN_ON(!qh->dwc_tt))
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ /* Get the map and adjust if this is a multi_tt hub */
|
|
|
+ map = qh->dwc_tt->periodic_bitmaps;
|
|
|
+ if (qh->dwc_tt->usb_tt->multi)
|
|
|
+ map += DWC2_ELEMENTS_PER_LS_BITMAP * qh->ttport;
|
|
|
+
|
|
|
+ return map;
|
|
|
+}
|
|
|
+
|
|
|
+struct dwc2_qh_print_data {
|
|
|
+ struct dwc2_hsotg *hsotg;
|
|
|
+ struct dwc2_qh *qh;
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * dwc2_qh_print() - Helper function for dwc2_qh_schedule_print()
|
|
|
+ *
|
|
|
+ * @str: The string to print
|
|
|
+ * @data: A pointer to a struct dwc2_qh_print_data
|
|
|
+ */
|
|
|
+static void dwc2_qh_print(const char *str, void *data)
|
|
|
+{
|
|
|
+ struct dwc2_qh_print_data *print_data = data;
|
|
|
+
|
|
|
+ dwc2_sch_dbg(print_data->hsotg, "QH=%p ...%s\n", print_data->qh, str);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dwc2_qh_schedule_print() - Print the periodic schedule
|
|
|
+ *
|
|
|
+ * @hsotg: The HCD state structure for the DWC OTG controller.
|
|
|
+ * @qh: QH to print.
|
|
|
+ */
|
|
|
+static void dwc2_qh_schedule_print(struct dwc2_hsotg *hsotg,
|
|
|
+ struct dwc2_qh *qh)
|
|
|
+{
|
|
|
+ struct dwc2_qh_print_data print_data = { hsotg, qh };
|
|
|
int i;
|
|
|
- int j;
|
|
|
- int k;
|
|
|
|
|
|
- for (i = 0; i < 8; i++) {
|
|
|
- if (hsotg->frame_usecs[i] <= 0)
|
|
|
+ /*
|
|
|
+ * The printing functions are quite slow and inefficient.
|
|
|
+ * If we don't have tracing turned on, don't run unless the special
|
|
|
+ * define is turned on.
|
|
|
+ */
|
|
|
+#ifndef DWC2_PRINT_SCHEDULE
|
|
|
+ return;
|
|
|
+#endif
|
|
|
+
|
|
|
+ if (qh->schedule_low_speed) {
|
|
|
+ unsigned long *map = dwc2_get_ls_map(hsotg, qh);
|
|
|
+
|
|
|
+ dwc2_sch_dbg(hsotg, "QH=%p LS/FS trans: %d=>%d us @ %d us",
|
|
|
+ qh, qh->device_us,
|
|
|
+ DWC2_ROUND_US_TO_SLICE(qh->device_us),
|
|
|
+ DWC2_US_PER_SLICE * qh->ls_start_schedule_slice);
|
|
|
+
|
|
|
+ if (map) {
|
|
|
+ dwc2_sch_dbg(hsotg,
|
|
|
+ "QH=%p Whole low/full speed map %p now:\n",
|
|
|
+ qh, map);
|
|
|
+ pmap_print(map, DWC2_LS_PERIODIC_SLICES_PER_FRAME,
|
|
|
+ DWC2_LS_SCHEDULE_FRAMES, "Frame ", "slices",
|
|
|
+ dwc2_qh_print, &print_data);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < qh->num_hs_transfers; i++) {
|
|
|
+ struct dwc2_hs_transfer_time *trans_time = qh->hs_transfers + i;
|
|
|
+ int uframe = trans_time->start_schedule_us /
|
|
|
+ DWC2_HS_PERIODIC_US_PER_UFRAME;
|
|
|
+ int rel_us = trans_time->start_schedule_us %
|
|
|
+ DWC2_HS_PERIODIC_US_PER_UFRAME;
|
|
|
+
|
|
|
+ dwc2_sch_dbg(hsotg,
|
|
|
+ "QH=%p HS trans #%d: %d us @ uFrame %d + %d us\n",
|
|
|
+ qh, i, trans_time->duration_us, uframe, rel_us);
|
|
|
+ }
|
|
|
+ if (qh->num_hs_transfers) {
|
|
|
+ dwc2_sch_dbg(hsotg, "QH=%p Whole high speed map now:\n", qh);
|
|
|
+ pmap_print(hsotg->hs_periodic_bitmap,
|
|
|
+ DWC2_HS_PERIODIC_US_PER_UFRAME,
|
|
|
+ DWC2_HS_SCHEDULE_UFRAMES, "uFrame", "us",
|
|
|
+ dwc2_qh_print, &print_data);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dwc2_ls_pmap_schedule() - Schedule a low speed QH
|
|
|
+ *
|
|
|
+ * @hsotg: The HCD state structure for the DWC OTG controller.
|
|
|
+ * @qh: QH for the periodic transfer.
|
|
|
+ * @search_slice: We'll start trying to schedule at the passed slice.
|
|
|
+ * Remember that slices are the units of the low speed
|
|
|
+ * schedule (think 25us or so).
|
|
|
+ *
|
|
|
+ * Wraps pmap_schedule() with the right parameters for low speed scheduling.
|
|
|
+ *
|
|
|
+ * Normally we schedule low speed devices on the map associated with the TT.
|
|
|
+ *
|
|
|
+ * Returns: 0 for success or an error code.
|
|
|
+ */
|
|
|
+static int dwc2_ls_pmap_schedule(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh,
|
|
|
+ int search_slice)
|
|
|
+{
|
|
|
+ int slices = DIV_ROUND_UP(qh->device_us, DWC2_US_PER_SLICE);
|
|
|
+ unsigned long *map = dwc2_get_ls_map(hsotg, qh);
|
|
|
+ int slice;
|
|
|
+
|
|
|
+ if (map == NULL)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Schedule on the proper low speed map with our low speed scheduling
|
|
|
+ * parameters. Note that we use the "device_interval" here since
|
|
|
+ * we want the low speed interval and the only way we'd be in this
|
|
|
+ * function is if the device is low speed.
|
|
|
+ *
|
|
|
+ * If we happen to be doing low speed and high speed scheduling for the
|
|
|
+ * same transaction (AKA we have a split) we always do low speed first.
|
|
|
+ * That means we can always pass "false" for only_one_period (that
|
|
|
+ * parameters is only useful when we're trying to get one schedule to
|
|
|
+ * match what we already planned in the other schedule).
|
|
|
+ */
|
|
|
+ slice = pmap_schedule(map, DWC2_LS_PERIODIC_SLICES_PER_FRAME,
|
|
|
+ DWC2_LS_SCHEDULE_FRAMES, slices,
|
|
|
+ qh->device_interval, search_slice, false);
|
|
|
+
|
|
|
+ if (slice < 0)
|
|
|
+ return slice;
|
|
|
+
|
|
|
+ qh->ls_start_schedule_slice = slice;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dwc2_ls_pmap_unschedule() - Undo work done by dwc2_ls_pmap_schedule()
|
|
|
+ *
|
|
|
+ * @hsotg: The HCD state structure for the DWC OTG controller.
|
|
|
+ * @qh: QH for the periodic transfer.
|
|
|
+ */
|
|
|
+static void dwc2_ls_pmap_unschedule(struct dwc2_hsotg *hsotg,
|
|
|
+ struct dwc2_qh *qh)
|
|
|
+{
|
|
|
+ int slices = DIV_ROUND_UP(qh->device_us, DWC2_US_PER_SLICE);
|
|
|
+ unsigned long *map = dwc2_get_ls_map(hsotg, qh);
|
|
|
+
|
|
|
+ /* Schedule should have failed, so no worries about no error code */
|
|
|
+ if (map == NULL)
|
|
|
+ return;
|
|
|
+
|
|
|
+ pmap_unschedule(map, DWC2_LS_PERIODIC_SLICES_PER_FRAME,
|
|
|
+ DWC2_LS_SCHEDULE_FRAMES, slices, qh->device_interval,
|
|
|
+ qh->ls_start_schedule_slice);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dwc2_hs_pmap_schedule - Schedule in the main high speed schedule
|
|
|
+ *
|
|
|
+ * This will schedule something on the main dwc2 schedule.
|
|
|
+ *
|
|
|
+ * We'll start looking in qh->hs_transfers[index].start_schedule_us. We'll
|
|
|
+ * update this with the result upon success. We also use the duration from
|
|
|
+ * the same structure.
|
|
|
+ *
|
|
|
+ * @hsotg: The HCD state structure for the DWC OTG controller.
|
|
|
+ * @qh: QH for the periodic transfer.
|
|
|
+ * @only_one_period: If true we will limit ourselves to just looking at
|
|
|
+ * one period (aka one 100us chunk). This is used if we have
|
|
|
+ * already scheduled something on the low speed schedule and
|
|
|
+ * need to find something that matches on the high speed one.
|
|
|
+ * @index: The index into qh->hs_transfers that we're working with.
|
|
|
+ *
|
|
|
+ * Returns: 0 for success or an error code. Upon success the
|
|
|
+ * dwc2_hs_transfer_time specified by "index" will be updated.
|
|
|
+ */
|
|
|
+static int dwc2_hs_pmap_schedule(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh,
|
|
|
+ bool only_one_period, int index)
|
|
|
+{
|
|
|
+ struct dwc2_hs_transfer_time *trans_time = qh->hs_transfers + index;
|
|
|
+ int us;
|
|
|
+
|
|
|
+ us = pmap_schedule(hsotg->hs_periodic_bitmap,
|
|
|
+ DWC2_HS_PERIODIC_US_PER_UFRAME,
|
|
|
+ DWC2_HS_SCHEDULE_UFRAMES, trans_time->duration_us,
|
|
|
+ qh->host_interval, trans_time->start_schedule_us,
|
|
|
+ only_one_period);
|
|
|
+
|
|
|
+ if (us < 0)
|
|
|
+ return us;
|
|
|
+
|
|
|
+ trans_time->start_schedule_us = us;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dwc2_ls_pmap_unschedule() - Undo work done by dwc2_hs_pmap_schedule()
|
|
|
+ *
|
|
|
+ * @hsotg: The HCD state structure for the DWC OTG controller.
|
|
|
+ * @qh: QH for the periodic transfer.
|
|
|
+ */
|
|
|
+static void dwc2_hs_pmap_unschedule(struct dwc2_hsotg *hsotg,
|
|
|
+ struct dwc2_qh *qh, int index)
|
|
|
+{
|
|
|
+ struct dwc2_hs_transfer_time *trans_time = qh->hs_transfers + index;
|
|
|
+
|
|
|
+ pmap_unschedule(hsotg->hs_periodic_bitmap,
|
|
|
+ DWC2_HS_PERIODIC_US_PER_UFRAME,
|
|
|
+ DWC2_HS_SCHEDULE_UFRAMES, trans_time->duration_us,
|
|
|
+ qh->host_interval, trans_time->start_schedule_us);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dwc2_uframe_schedule_split - Schedule a QH for a periodic split xfer.
|
|
|
+ *
|
|
|
+ * This is the most complicated thing in USB. We have to find matching time
|
|
|
+ * in both the global high speed schedule for the port and the low speed
|
|
|
+ * schedule for the TT associated with the given device.
|
|
|
+ *
|
|
|
+ * Being here means that the host must be running in high speed mode and the
|
|
|
+ * device is in low or full speed mode (and behind a hub).
|
|
|
+ *
|
|
|
+ * @hsotg: The HCD state structure for the DWC OTG controller.
|
|
|
+ * @qh: QH for the periodic transfer.
|
|
|
+ */
|
|
|
+static int dwc2_uframe_schedule_split(struct dwc2_hsotg *hsotg,
|
|
|
+ struct dwc2_qh *qh)
|
|
|
+{
|
|
|
+ int bytecount = dwc2_hb_mult(qh->maxp) * dwc2_max_packet(qh->maxp);
|
|
|
+ int ls_search_slice;
|
|
|
+ int err = 0;
|
|
|
+ int host_interval_in_sched;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The interval (how often to repeat) in the actual host schedule.
|
|
|
+ * See pmap_schedule() for gcd() explanation.
|
|
|
+ */
|
|
|
+ host_interval_in_sched = gcd(qh->host_interval,
|
|
|
+ DWC2_HS_SCHEDULE_UFRAMES);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * We always try to find space in the low speed schedule first, then
|
|
|
+ * try to find high speed time that matches. If we don't, we'll bump
|
|
|
+ * up the place we start searching in the low speed schedule and try
|
|
|
+ * again. To start we'll look right at the beginning of the low speed
|
|
|
+ * schedule.
|
|
|
+ *
|
|
|
+ * Note that this will tend to front-load the high speed schedule.
|
|
|
+ * We may eventually want to try to avoid this by either considering
|
|
|
+ * both schedules together or doing some sort of round robin.
|
|
|
+ */
|
|
|
+ ls_search_slice = 0;
|
|
|
+
|
|
|
+ while (ls_search_slice < DWC2_LS_SCHEDULE_SLICES) {
|
|
|
+ int start_s_uframe;
|
|
|
+ int ssplit_s_uframe;
|
|
|
+ int second_s_uframe;
|
|
|
+ int rel_uframe;
|
|
|
+ int first_count;
|
|
|
+ int middle_count;
|
|
|
+ int end_count;
|
|
|
+ int first_data_bytes;
|
|
|
+ int other_data_bytes;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (qh->schedule_low_speed) {
|
|
|
+ err = dwc2_ls_pmap_schedule(hsotg, qh, ls_search_slice);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If we got an error here there's no other magic we
|
|
|
+ * can do, so bail. All the looping above is only
|
|
|
+ * helpful to redo things if we got a low speed slot
|
|
|
+ * and then couldn't find a matching high speed slot.
|
|
|
+ */
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+ } else {
|
|
|
+ /* Must be missing the tt structure? Why? */
|
|
|
+ WARN_ON_ONCE(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * This will give us a number 0 - 7 if
|
|
|
+ * DWC2_LS_SCHEDULE_FRAMES == 1, or 0 - 15 if == 2, or ...
|
|
|
+ */
|
|
|
+ start_s_uframe = qh->ls_start_schedule_slice /
|
|
|
+ DWC2_SLICES_PER_UFRAME;
|
|
|
+
|
|
|
+ /* Get a number that's always 0 - 7 */
|
|
|
+ rel_uframe = (start_s_uframe % 8);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If we were going to start in uframe 7 then we would need to
|
|
|
+ * issue a start split in uframe 6, which spec says is not OK.
|
|
|
+ * Move on to the next full frame (assuming there is one).
|
|
|
+ *
|
|
|
+ * See 11.18.4 Host Split Transaction Scheduling Requirements
|
|
|
+ * bullet 1.
|
|
|
+ */
|
|
|
+ if (rel_uframe == 7) {
|
|
|
+ if (qh->schedule_low_speed)
|
|
|
+ dwc2_ls_pmap_unschedule(hsotg, qh);
|
|
|
+ ls_search_slice =
|
|
|
+ (qh->ls_start_schedule_slice /
|
|
|
+ DWC2_LS_PERIODIC_SLICES_PER_FRAME + 1) *
|
|
|
+ DWC2_LS_PERIODIC_SLICES_PER_FRAME;
|
|
|
continue;
|
|
|
+ }
|
|
|
|
|
|
/*
|
|
|
- * we need n consecutive slots so use j as a start slot
|
|
|
- * j plus j+1 must be enough time (for now)
|
|
|
+ * For ISOC in:
|
|
|
+ * - start split (frame -1)
|
|
|
+ * - complete split w/ data (frame +1)
|
|
|
+ * - complete split w/ data (frame +2)
|
|
|
+ * - ...
|
|
|
+ * - complete split w/ data (frame +num_data_packets)
|
|
|
+ * - complete split w/ data (frame +num_data_packets+1)
|
|
|
+ * - complete split w/ data (frame +num_data_packets+2, max 8)
|
|
|
+ * ...though if frame was "0" then max is 7...
|
|
|
+ *
|
|
|
+ * For ISOC out we might need to do:
|
|
|
+ * - start split w/ data (frame -1)
|
|
|
+ * - start split w/ data (frame +0)
|
|
|
+ * - ...
|
|
|
+ * - start split w/ data (frame +num_data_packets-2)
|
|
|
+ *
|
|
|
+ * For INTERRUPT in we might need to do:
|
|
|
+ * - start split (frame -1)
|
|
|
+ * - complete split w/ data (frame +1)
|
|
|
+ * - complete split w/ data (frame +2)
|
|
|
+ * - complete split w/ data (frame +3, max 8)
|
|
|
+ *
|
|
|
+ * For INTERRUPT out we might need to do:
|
|
|
+ * - start split w/ data (frame -1)
|
|
|
+ * - complete split (frame +1)
|
|
|
+ * - complete split (frame +2)
|
|
|
+ * - complete split (frame +3, max 8)
|
|
|
+ *
|
|
|
+ * Start adjusting!
|
|
|
*/
|
|
|
- xtime = hsotg->frame_usecs[i];
|
|
|
- for (j = i + 1; j < 8; j++) {
|
|
|
- /*
|
|
|
- * if we add this frame remaining time to xtime we may
|
|
|
- * be OK, if not we need to test j for a complete frame
|
|
|
- */
|
|
|
- if (xtime + hsotg->frame_usecs[j] < utime) {
|
|
|
- if (hsotg->frame_usecs[j] <
|
|
|
- max_uframe_usecs[j])
|
|
|
- continue;
|
|
|
+ ssplit_s_uframe = (start_s_uframe +
|
|
|
+ host_interval_in_sched - 1) %
|
|
|
+ host_interval_in_sched;
|
|
|
+ if (qh->ep_type == USB_ENDPOINT_XFER_ISOC && !qh->ep_is_in)
|
|
|
+ second_s_uframe = start_s_uframe;
|
|
|
+ else
|
|
|
+ second_s_uframe = start_s_uframe + 1;
|
|
|
+
|
|
|
+ /* First data transfer might not be all 188 bytes. */
|
|
|
+ first_data_bytes = 188 -
|
|
|
+ DIV_ROUND_UP(188 * (qh->ls_start_schedule_slice %
|
|
|
+ DWC2_SLICES_PER_UFRAME),
|
|
|
+ DWC2_SLICES_PER_UFRAME);
|
|
|
+ if (first_data_bytes > bytecount)
|
|
|
+ first_data_bytes = bytecount;
|
|
|
+ other_data_bytes = bytecount - first_data_bytes;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * For now, skip OUT xfers where first xfer is partial
|
|
|
+ *
|
|
|
+ * Main dwc2 code assumes:
|
|
|
+ * - INT transfers never get split in two.
|
|
|
+ * - ISOC transfers can always transfer 188 bytes the first
|
|
|
+ * time.
|
|
|
+ *
|
|
|
+ * Until that code is fixed, try again if the first transfer
|
|
|
+ * couldn't transfer everything.
|
|
|
+ *
|
|
|
+ * This code can be removed if/when the rest of dwc2 handles
|
|
|
+ * the above cases. Until it's fixed we just won't be able
|
|
|
+ * to schedule quite as tightly.
|
|
|
+ */
|
|
|
+ if (!qh->ep_is_in &&
|
|
|
+ (first_data_bytes != min_t(int, 188, bytecount))) {
|
|
|
+ dwc2_sch_dbg(hsotg,
|
|
|
+ "QH=%p avoiding broken 1st xfer (%d, %d)\n",
|
|
|
+ qh, first_data_bytes, bytecount);
|
|
|
+ if (qh->schedule_low_speed)
|
|
|
+ dwc2_ls_pmap_unschedule(hsotg, qh);
|
|
|
+ ls_search_slice = (start_s_uframe + 1) *
|
|
|
+ DWC2_SLICES_PER_UFRAME;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Start by assuming transfers for the bytes */
|
|
|
+ qh->num_hs_transfers = 1 + DIV_ROUND_UP(other_data_bytes, 188);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Everything except ISOC OUT has extra transfers. Rules are
|
|
|
+ * complicated. See 11.18.4 Host Split Transaction Scheduling
|
|
|
+ * Requirements bullet 3.
|
|
|
+ */
|
|
|
+ if (qh->ep_type == USB_ENDPOINT_XFER_INT) {
|
|
|
+ if (rel_uframe == 6)
|
|
|
+ qh->num_hs_transfers += 2;
|
|
|
+ else
|
|
|
+ qh->num_hs_transfers += 3;
|
|
|
+
|
|
|
+ if (qh->ep_is_in) {
|
|
|
+ /*
|
|
|
+ * First is start split, middle/end is data.
|
|
|
+ * Allocate full data bytes for all data.
|
|
|
+ */
|
|
|
+ first_count = 4;
|
|
|
+ middle_count = bytecount;
|
|
|
+ end_count = bytecount;
|
|
|
+ } else {
|
|
|
+ /*
|
|
|
+ * First is data, middle/end is complete.
|
|
|
+ * First transfer and second can have data.
|
|
|
+ * Rest should just have complete split.
|
|
|
+ */
|
|
|
+ first_count = first_data_bytes;
|
|
|
+ middle_count = max_t(int, 4, other_data_bytes);
|
|
|
+ end_count = 4;
|
|
|
}
|
|
|
- if (xtime >= utime) {
|
|
|
- t_left = utime;
|
|
|
- for (k = i; k < 8; k++) {
|
|
|
- t_left -= hsotg->frame_usecs[k];
|
|
|
- if (t_left <= 0) {
|
|
|
- qh->frame_usecs[k] +=
|
|
|
- hsotg->frame_usecs[k]
|
|
|
- + t_left;
|
|
|
- hsotg->frame_usecs[k] = -t_left;
|
|
|
- return i;
|
|
|
- } else {
|
|
|
- qh->frame_usecs[k] +=
|
|
|
- hsotg->frame_usecs[k];
|
|
|
- hsotg->frame_usecs[k] = 0;
|
|
|
- }
|
|
|
- }
|
|
|
+ } else {
|
|
|
+ if (qh->ep_is_in) {
|
|
|
+ int last;
|
|
|
+
|
|
|
+ /* Account for the start split */
|
|
|
+ qh->num_hs_transfers++;
|
|
|
+
|
|
|
+ /* Calculate "L" value from spec */
|
|
|
+ last = rel_uframe + qh->num_hs_transfers + 1;
|
|
|
+
|
|
|
+ /* Start with basic case */
|
|
|
+ if (last <= 6)
|
|
|
+ qh->num_hs_transfers += 2;
|
|
|
+ else
|
|
|
+ qh->num_hs_transfers += 1;
|
|
|
+
|
|
|
+ /* Adjust downwards */
|
|
|
+ if (last >= 6 && rel_uframe == 0)
|
|
|
+ qh->num_hs_transfers--;
|
|
|
+
|
|
|
+ /* 1st = start; rest can contain data */
|
|
|
+ first_count = 4;
|
|
|
+ middle_count = min_t(int, 188, bytecount);
|
|
|
+ end_count = middle_count;
|
|
|
+ } else {
|
|
|
+ /* All contain data, last might be smaller */
|
|
|
+ first_count = first_data_bytes;
|
|
|
+ middle_count = min_t(int, 188,
|
|
|
+ other_data_bytes);
|
|
|
+ end_count = other_data_bytes % 188;
|
|
|
}
|
|
|
- /* add the frame time to x time */
|
|
|
- xtime += hsotg->frame_usecs[j];
|
|
|
- /* we must have a fully available next frame or break */
|
|
|
- if (xtime < utime &&
|
|
|
- hsotg->frame_usecs[j] == max_uframe_usecs[j])
|
|
|
- continue;
|
|
|
}
|
|
|
+
|
|
|
+ /* Assign durations per uFrame */
|
|
|
+ qh->hs_transfers[0].duration_us = HS_USECS_ISO(first_count);
|
|
|
+ for (i = 1; i < qh->num_hs_transfers - 1; i++)
|
|
|
+ qh->hs_transfers[i].duration_us =
|
|
|
+ HS_USECS_ISO(middle_count);
|
|
|
+ if (qh->num_hs_transfers > 1)
|
|
|
+ qh->hs_transfers[qh->num_hs_transfers - 1].duration_us =
|
|
|
+ HS_USECS_ISO(end_count);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Assign start us. The call below to dwc2_hs_pmap_schedule()
|
|
|
+ * will start with these numbers but may adjust within the same
|
|
|
+ * microframe.
|
|
|
+ */
|
|
|
+ qh->hs_transfers[0].start_schedule_us =
|
|
|
+ ssplit_s_uframe * DWC2_HS_PERIODIC_US_PER_UFRAME;
|
|
|
+ for (i = 1; i < qh->num_hs_transfers; i++)
|
|
|
+ qh->hs_transfers[i].start_schedule_us =
|
|
|
+ ((second_s_uframe + i - 1) %
|
|
|
+ DWC2_HS_SCHEDULE_UFRAMES) *
|
|
|
+ DWC2_HS_PERIODIC_US_PER_UFRAME;
|
|
|
+
|
|
|
+ /* Try to schedule with filled in hs_transfers above */
|
|
|
+ for (i = 0; i < qh->num_hs_transfers; i++) {
|
|
|
+ err = dwc2_hs_pmap_schedule(hsotg, qh, true, i);
|
|
|
+ if (err)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* If we scheduled all w/out breaking out then we're all good */
|
|
|
+ if (i == qh->num_hs_transfers)
|
|
|
+ break;
|
|
|
+
|
|
|
+ for (; i >= 0; i--)
|
|
|
+ dwc2_hs_pmap_unschedule(hsotg, qh, i);
|
|
|
+
|
|
|
+ if (qh->schedule_low_speed)
|
|
|
+ dwc2_ls_pmap_unschedule(hsotg, qh);
|
|
|
+
|
|
|
+ /* Try again starting in the next microframe */
|
|
|
+ ls_search_slice = (start_s_uframe + 1) * DWC2_SLICES_PER_UFRAME;
|
|
|
}
|
|
|
- return -ENOSPC;
|
|
|
+
|
|
|
+ if (ls_search_slice >= DWC2_LS_SCHEDULE_SLICES)
|
|
|
+ return -ENOSPC;
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
-static int dwc2_find_uframe(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
|
|
|
+/**
|
|
|
+ * dwc2_uframe_schedule_hs - Schedule a QH for a periodic high speed xfer.
|
|
|
+ *
|
|
|
+ * Basically this just wraps dwc2_hs_pmap_schedule() to provide a clean
|
|
|
+ * interface.
|
|
|
+ *
|
|
|
+ * @hsotg: The HCD state structure for the DWC OTG controller.
|
|
|
+ * @qh: QH for the periodic transfer.
|
|
|
+ */
|
|
|
+static int dwc2_uframe_schedule_hs(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
|
|
|
+{
|
|
|
+ /* In non-split host and device time are the same */
|
|
|
+ WARN_ON(qh->host_us != qh->device_us);
|
|
|
+ WARN_ON(qh->host_interval != qh->device_interval);
|
|
|
+ WARN_ON(qh->num_hs_transfers != 1);
|
|
|
+
|
|
|
+ /* We'll have one transfer; init start to 0 before calling scheduler */
|
|
|
+ qh->hs_transfers[0].start_schedule_us = 0;
|
|
|
+ qh->hs_transfers[0].duration_us = qh->host_us;
|
|
|
+
|
|
|
+ return dwc2_hs_pmap_schedule(hsotg, qh, false, 0);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dwc2_uframe_schedule_ls - Schedule a QH for a periodic low/full speed xfer.
|
|
|
+ *
|
|
|
+ * Basically this just wraps dwc2_ls_pmap_schedule() to provide a clean
|
|
|
+ * interface.
|
|
|
+ *
|
|
|
+ * @hsotg: The HCD state structure for the DWC OTG controller.
|
|
|
+ * @qh: QH for the periodic transfer.
|
|
|
+ */
|
|
|
+static int dwc2_uframe_schedule_ls(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
|
|
|
+{
|
|
|
+ /* In non-split host and device time are the same */
|
|
|
+ WARN_ON(qh->host_us != qh->device_us);
|
|
|
+ WARN_ON(qh->host_interval != qh->device_interval);
|
|
|
+ WARN_ON(!qh->schedule_low_speed);
|
|
|
+
|
|
|
+ /* Run on the main low speed schedule (no split = no hub = no TT) */
|
|
|
+ return dwc2_ls_pmap_schedule(hsotg, qh, 0);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dwc2_uframe_schedule - Schedule a QH for a periodic xfer.
|
|
|
+ *
|
|
|
+ * Calls one of the 3 sub-function depending on what type of transfer this QH
|
|
|
+ * is for. Also adds some printing.
|
|
|
+ *
|
|
|
+ * @hsotg: The HCD state structure for the DWC OTG controller.
|
|
|
+ * @qh: QH for the periodic transfer.
|
|
|
+ */
|
|
|
+static int dwc2_uframe_schedule(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
|
|
|
{
|
|
|
int ret;
|
|
|
|
|
|
- if (qh->dev_speed == USB_SPEED_HIGH) {
|
|
|
- /* if this is a hs transaction we need a full frame */
|
|
|
- ret = dwc2_find_single_uframe(hsotg, qh);
|
|
|
- } else {
|
|
|
- /*
|
|
|
- * if this is a fs transaction we may need a sequence
|
|
|
- * of frames
|
|
|
- */
|
|
|
- ret = dwc2_find_multi_uframe(hsotg, qh);
|
|
|
- }
|
|
|
+ if (qh->dev_speed == USB_SPEED_HIGH)
|
|
|
+ ret = dwc2_uframe_schedule_hs(hsotg, qh);
|
|
|
+ else if (!qh->do_split)
|
|
|
+ ret = dwc2_uframe_schedule_ls(hsotg, qh);
|
|
|
+ else
|
|
|
+ ret = dwc2_uframe_schedule_split(hsotg, qh);
|
|
|
+
|
|
|
+ if (ret)
|
|
|
+ dwc2_sch_dbg(hsotg, "QH=%p Failed to schedule %d\n", qh, ret);
|
|
|
+ else
|
|
|
+ dwc2_qh_schedule_print(hsotg, qh);
|
|
|
+
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * dwc2_uframe_unschedule - Undoes dwc2_uframe_schedule().
|
|
|
+ *
|
|
|
+ * @hsotg: The HCD state structure for the DWC OTG controller.
|
|
|
+ * @qh: QH for the periodic transfer.
|
|
|
+ */
|
|
|
+static void dwc2_uframe_unschedule(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < qh->num_hs_transfers; i++)
|
|
|
+ dwc2_hs_pmap_unschedule(hsotg, qh, i);
|
|
|
+
|
|
|
+ if (qh->schedule_low_speed)
|
|
|
+ dwc2_ls_pmap_unschedule(hsotg, qh);
|
|
|
+
|
|
|
+ dwc2_sch_dbg(hsotg, "QH=%p Unscheduled\n", qh);
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* dwc2_pick_first_frame() - Choose 1st frame for qh that's already scheduled
|
|
|
*
|
|
@@ -265,6 +1082,7 @@ static void dwc2_pick_first_frame(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
|
|
|
u16 frame_number;
|
|
|
u16 earliest_frame;
|
|
|
u16 next_active_frame;
|
|
|
+ u16 relative_frame;
|
|
|
u16 interval;
|
|
|
|
|
|
/*
|
|
@@ -292,8 +1110,36 @@ static void dwc2_pick_first_frame(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
|
|
|
goto exit;
|
|
|
}
|
|
|
|
|
|
- /* Adjust interval as per high speed schedule which has 8 uFrame */
|
|
|
- interval = gcd(qh->host_interval, 8);
|
|
|
+ if (qh->dev_speed == USB_SPEED_HIGH || qh->do_split) {
|
|
|
+ /*
|
|
|
+ * We're either at high speed or we're doing a split (which
|
|
|
+ * means we're talking high speed to a hub). In any case
|
|
|
+ * the first frame should be based on when the first scheduled
|
|
|
+ * event is.
|
|
|
+ */
|
|
|
+ WARN_ON(qh->num_hs_transfers < 1);
|
|
|
+
|
|
|
+ relative_frame = qh->hs_transfers[0].start_schedule_us /
|
|
|
+ DWC2_HS_PERIODIC_US_PER_UFRAME;
|
|
|
+
|
|
|
+ /* Adjust interval as per high speed schedule */
|
|
|
+ interval = gcd(qh->host_interval, DWC2_HS_SCHEDULE_UFRAMES);
|
|
|
+
|
|
|
+ } else {
|
|
|
+ /*
|
|
|
+ * Low or full speed directly on dwc2. Just about the same
|
|
|
+ * as high speed but on a different schedule and with slightly
|
|
|
+ * different adjustments. Note that this works because when
|
|
|
+ * the host and device are both low speed then frames in the
|
|
|
+ * controller tick at low speed.
|
|
|
+ */
|
|
|
+ relative_frame = qh->ls_start_schedule_slice /
|
|
|
+ DWC2_LS_PERIODIC_SLICES_PER_FRAME;
|
|
|
+ interval = gcd(qh->host_interval, DWC2_LS_SCHEDULE_FRAMES);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Scheduler messed up if frame is past interval */
|
|
|
+ WARN_ON(relative_frame >= interval);
|
|
|
|
|
|
/*
|
|
|
* We know interval must divide (HFNUM_MAX_FRNUM + 1) now that we've
|
|
@@ -310,7 +1156,7 @@ static void dwc2_pick_first_frame(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
|
|
|
* scheduled for.
|
|
|
*/
|
|
|
next_active_frame = dwc2_frame_num_inc(next_active_frame,
|
|
|
- qh->assigned_uframe);
|
|
|
+ relative_frame);
|
|
|
|
|
|
/*
|
|
|
* We actually need 1 frame before since the next_active_frame is
|
|
@@ -351,9 +1197,7 @@ static int dwc2_do_reserve(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
|
|
|
int status;
|
|
|
|
|
|
if (hsotg->core_params->uframe_sched > 0) {
|
|
|
- status = dwc2_find_uframe(hsotg, qh);
|
|
|
- if (status >= 0)
|
|
|
- qh->assigned_uframe = status;
|
|
|
+ status = dwc2_uframe_schedule(hsotg, qh);
|
|
|
} else {
|
|
|
status = dwc2_periodic_channel_available(hsotg);
|
|
|
if (status) {
|
|
@@ -410,12 +1254,7 @@ static void dwc2_do_unreserve(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
|
|
|
hsotg->periodic_usecs -= qh->host_us;
|
|
|
|
|
|
if (hsotg->core_params->uframe_sched > 0) {
|
|
|
- int i;
|
|
|
-
|
|
|
- for (i = 0; i < 8; i++) {
|
|
|
- hsotg->frame_usecs[i] += qh->frame_usecs[i];
|
|
|
- qh->frame_usecs[i] = 0;
|
|
|
- }
|
|
|
+ dwc2_uframe_unschedule(hsotg, qh);
|
|
|
} else {
|
|
|
/* Release periodic channel reservation */
|
|
|
hsotg->periodic_channels--;
|
|
@@ -606,88 +1445,81 @@ static void dwc2_deschedule_periodic(struct dwc2_hsotg *hsotg,
|
|
|
* @qh: The QH to init
|
|
|
* @urb: Holds the information about the device/endpoint needed to initialize
|
|
|
* the QH
|
|
|
+ * @mem_flags: Flags for allocating memory.
|
|
|
*/
|
|
|
static void dwc2_qh_init(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh,
|
|
|
- struct dwc2_hcd_urb *urb)
|
|
|
+ struct dwc2_hcd_urb *urb, gfp_t mem_flags)
|
|
|
{
|
|
|
- int dev_speed, hub_addr, hub_port;
|
|
|
+ int dev_speed = dwc2_host_get_speed(hsotg, urb->priv);
|
|
|
+ u8 ep_type = dwc2_hcd_get_pipe_type(&urb->pipe_info);
|
|
|
+ bool ep_is_in = !!dwc2_hcd_is_pipe_in(&urb->pipe_info);
|
|
|
+ bool ep_is_isoc = (ep_type == USB_ENDPOINT_XFER_ISOC);
|
|
|
+ bool ep_is_int = (ep_type == USB_ENDPOINT_XFER_INT);
|
|
|
+ u32 hprt = dwc2_readl(hsotg->regs + HPRT0);
|
|
|
+ u32 prtspd = (hprt & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT;
|
|
|
+ bool do_split = (prtspd == HPRT0_SPD_HIGH_SPEED &&
|
|
|
+ dev_speed != USB_SPEED_HIGH);
|
|
|
+ int maxp = dwc2_hcd_get_mps(&urb->pipe_info);
|
|
|
+ int bytecount = dwc2_hb_mult(maxp) * dwc2_max_packet(maxp);
|
|
|
char *speed, *type;
|
|
|
|
|
|
- dev_vdbg(hsotg->dev, "%s()\n", __func__);
|
|
|
-
|
|
|
/* Initialize QH */
|
|
|
qh->hsotg = hsotg;
|
|
|
setup_timer(&qh->unreserve_timer, dwc2_unreserve_timer_fn,
|
|
|
(unsigned long)qh);
|
|
|
- qh->ep_type = dwc2_hcd_get_pipe_type(&urb->pipe_info);
|
|
|
- qh->ep_is_in = dwc2_hcd_is_pipe_in(&urb->pipe_info) ? 1 : 0;
|
|
|
+ qh->ep_type = ep_type;
|
|
|
+ qh->ep_is_in = ep_is_in;
|
|
|
|
|
|
qh->data_toggle = DWC2_HC_PID_DATA0;
|
|
|
- qh->maxp = dwc2_hcd_get_mps(&urb->pipe_info);
|
|
|
+ qh->maxp = maxp;
|
|
|
INIT_LIST_HEAD(&qh->qtd_list);
|
|
|
INIT_LIST_HEAD(&qh->qh_list_entry);
|
|
|
|
|
|
- /* FS/LS Endpoint on HS Hub, NOT virtual root hub */
|
|
|
- dev_speed = dwc2_host_get_speed(hsotg, urb->priv);
|
|
|
+ qh->do_split = do_split;
|
|
|
+ qh->dev_speed = dev_speed;
|
|
|
+
|
|
|
+ if (ep_is_int || ep_is_isoc) {
|
|
|
+ /* Compute scheduling parameters once and save them */
|
|
|
+ int host_speed = do_split ? USB_SPEED_HIGH : dev_speed;
|
|
|
+ struct dwc2_tt *dwc_tt = dwc2_host_get_tt_info(hsotg, urb->priv,
|
|
|
+ mem_flags,
|
|
|
+ &qh->ttport);
|
|
|
+ int device_ns;
|
|
|
|
|
|
- dwc2_host_hub_info(hsotg, urb->priv, &hub_addr, &hub_port);
|
|
|
+ qh->dwc_tt = dwc_tt;
|
|
|
|
|
|
- if ((dev_speed == USB_SPEED_LOW || dev_speed == USB_SPEED_FULL) &&
|
|
|
- hub_addr != 0 && hub_addr != 1) {
|
|
|
- dev_vdbg(hsotg->dev,
|
|
|
- "QH init: EP %d: TT found at hub addr %d, for port %d\n",
|
|
|
- dwc2_hcd_get_ep_num(&urb->pipe_info), hub_addr,
|
|
|
- hub_port);
|
|
|
- qh->do_split = 1;
|
|
|
- }
|
|
|
+ qh->host_us = NS_TO_US(usb_calc_bus_time(host_speed, ep_is_in,
|
|
|
+ ep_is_isoc, bytecount));
|
|
|
+ device_ns = usb_calc_bus_time(dev_speed, ep_is_in,
|
|
|
+ ep_is_isoc, bytecount);
|
|
|
|
|
|
- if (qh->ep_type == USB_ENDPOINT_XFER_INT ||
|
|
|
- qh->ep_type == USB_ENDPOINT_XFER_ISOC) {
|
|
|
- /* Compute scheduling parameters once and save them */
|
|
|
- u32 hprt, prtspd;
|
|
|
-
|
|
|
- /* Todo: Account for split transfers in the bus time */
|
|
|
- int bytecount =
|
|
|
- dwc2_hb_mult(qh->maxp) * dwc2_max_packet(qh->maxp);
|
|
|
-
|
|
|
- qh->host_us = NS_TO_US(usb_calc_bus_time(qh->do_split ?
|
|
|
- USB_SPEED_HIGH : dev_speed, qh->ep_is_in,
|
|
|
- qh->ep_type == USB_ENDPOINT_XFER_ISOC,
|
|
|
- bytecount));
|
|
|
-
|
|
|
- qh->host_interval = urb->interval;
|
|
|
- dwc2_sch_dbg(hsotg, "QH=%p init nxt=%04x, fn=%04x, int=%#x\n",
|
|
|
- qh, qh->next_active_frame, hsotg->frame_number,
|
|
|
- qh->host_interval);
|
|
|
-#if 0
|
|
|
- /* Increase interrupt polling rate for debugging */
|
|
|
- if (qh->ep_type == USB_ENDPOINT_XFER_INT)
|
|
|
- qh->host_interval = 8;
|
|
|
-#endif
|
|
|
- hprt = dwc2_readl(hsotg->regs + HPRT0);
|
|
|
- prtspd = (hprt & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT;
|
|
|
- if (prtspd == HPRT0_SPD_HIGH_SPEED &&
|
|
|
- (dev_speed == USB_SPEED_LOW ||
|
|
|
- dev_speed == USB_SPEED_FULL)) {
|
|
|
- qh->host_interval *= 8;
|
|
|
- dwc2_sch_dbg(hsotg,
|
|
|
- "QH=%p init*8 nxt=%04x, fn=%04x, int=%#x\n",
|
|
|
- qh, qh->next_active_frame,
|
|
|
- hsotg->frame_number, qh->host_interval);
|
|
|
+ if (do_split && dwc_tt)
|
|
|
+ device_ns += dwc_tt->usb_tt->think_time;
|
|
|
+ qh->device_us = NS_TO_US(device_ns);
|
|
|
|
|
|
- }
|
|
|
- dev_dbg(hsotg->dev, "interval=%d\n", qh->host_interval);
|
|
|
- }
|
|
|
|
|
|
- dev_vdbg(hsotg->dev, "DWC OTG HCD QH Initialized\n");
|
|
|
- dev_vdbg(hsotg->dev, "DWC OTG HCD QH - qh = %p\n", qh);
|
|
|
- dev_vdbg(hsotg->dev, "DWC OTG HCD QH - Device Address = %d\n",
|
|
|
- dwc2_hcd_get_dev_addr(&urb->pipe_info));
|
|
|
- dev_vdbg(hsotg->dev, "DWC OTG HCD QH - Endpoint %d, %s\n",
|
|
|
- dwc2_hcd_get_ep_num(&urb->pipe_info),
|
|
|
- dwc2_hcd_is_pipe_in(&urb->pipe_info) ? "IN" : "OUT");
|
|
|
+ qh->device_interval = urb->interval;
|
|
|
+ qh->host_interval = urb->interval * (do_split ? 8 : 1);
|
|
|
|
|
|
- qh->dev_speed = dev_speed;
|
|
|
+ /*
|
|
|
+ * Schedule low speed if we're running the host in low or
|
|
|
+ * full speed OR if we've got a "TT" to deal with to access this
|
|
|
+ * device.
|
|
|
+ */
|
|
|
+ qh->schedule_low_speed = prtspd != HPRT0_SPD_HIGH_SPEED ||
|
|
|
+ dwc_tt;
|
|
|
+
|
|
|
+ if (do_split) {
|
|
|
+ /* We won't know num transfers until we schedule */
|
|
|
+ qh->num_hs_transfers = -1;
|
|
|
+ } else if (dev_speed == USB_SPEED_HIGH) {
|
|
|
+ qh->num_hs_transfers = 1;
|
|
|
+ } else {
|
|
|
+ qh->num_hs_transfers = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* We'll schedule later when we have something to do */
|
|
|
+ }
|
|
|
|
|
|
switch (dev_speed) {
|
|
|
case USB_SPEED_LOW:
|
|
@@ -703,7 +1535,6 @@ static void dwc2_qh_init(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh,
|
|
|
speed = "?";
|
|
|
break;
|
|
|
}
|
|
|
- dev_vdbg(hsotg->dev, "DWC OTG HCD QH - Speed = %s\n", speed);
|
|
|
|
|
|
switch (qh->ep_type) {
|
|
|
case USB_ENDPOINT_XFER_ISOC:
|
|
@@ -723,13 +1554,21 @@ static void dwc2_qh_init(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh,
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- dev_vdbg(hsotg->dev, "DWC OTG HCD QH - Type = %s\n", type);
|
|
|
-
|
|
|
- if (qh->ep_type == USB_ENDPOINT_XFER_INT) {
|
|
|
- dev_vdbg(hsotg->dev, "DWC OTG HCD QH - usecs = %d\n",
|
|
|
- qh->host_us);
|
|
|
- dev_vdbg(hsotg->dev, "DWC OTG HCD QH - interval = %d\n",
|
|
|
- qh->host_interval);
|
|
|
+ dwc2_sch_dbg(hsotg, "QH=%p Init %s, %s speed, %d bytes:\n", qh, type,
|
|
|
+ speed, bytecount);
|
|
|
+ dwc2_sch_dbg(hsotg, "QH=%p ...addr=%d, ep=%d, %s\n", qh,
|
|
|
+ dwc2_hcd_get_dev_addr(&urb->pipe_info),
|
|
|
+ dwc2_hcd_get_ep_num(&urb->pipe_info),
|
|
|
+ ep_is_in ? "IN" : "OUT");
|
|
|
+ if (ep_is_int || ep_is_isoc) {
|
|
|
+ dwc2_sch_dbg(hsotg,
|
|
|
+ "QH=%p ...duration: host=%d us, device=%d us\n",
|
|
|
+ qh, qh->host_us, qh->device_us);
|
|
|
+ dwc2_sch_dbg(hsotg, "QH=%p ...interval: host=%d, device=%d\n",
|
|
|
+ qh, qh->host_interval, qh->device_interval);
|
|
|
+ if (qh->schedule_low_speed)
|
|
|
+ dwc2_sch_dbg(hsotg, "QH=%p ...low speed schedule=%p\n",
|
|
|
+ qh, dwc2_get_ls_map(hsotg, qh));
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -757,7 +1596,7 @@ struct dwc2_qh *dwc2_hcd_qh_create(struct dwc2_hsotg *hsotg,
|
|
|
if (!qh)
|
|
|
return NULL;
|
|
|
|
|
|
- dwc2_qh_init(hsotg, qh, urb);
|
|
|
+ dwc2_qh_init(hsotg, qh, urb, mem_flags);
|
|
|
|
|
|
if (hsotg->core_params->dma_desc_enable > 0 &&
|
|
|
dwc2_hcd_qh_init_ddma(hsotg, qh, mem_flags) < 0) {
|
|
@@ -789,6 +1628,7 @@ void dwc2_hcd_qh_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
|
|
|
dwc2_do_unreserve(hsotg, qh);
|
|
|
spin_unlock_irqrestore(&hsotg->lock, flags);
|
|
|
}
|
|
|
+ dwc2_host_put_tt_info(hsotg, qh->dwc_tt);
|
|
|
|
|
|
if (qh->desc_list)
|
|
|
dwc2_hcd_qh_free_ddma(hsotg, qh);
|
|
@@ -904,6 +1744,8 @@ static int dwc2_next_for_periodic_split(struct dwc2_hsotg *hsotg,
|
|
|
u16 incr;
|
|
|
|
|
|
/*
|
|
|
+ * See dwc2_uframe_schedule_split() for split scheduling.
|
|
|
+ *
|
|
|
* Basically: increment 1 normally, but 2 right after the start split
|
|
|
* (except for ISOC out).
|
|
|
*/
|
|
@@ -1006,9 +1848,17 @@ static int dwc2_next_periodic_start(struct dwc2_hsotg *hsotg,
|
|
|
if (qh->start_active_frame == qh->next_active_frame ||
|
|
|
dwc2_frame_num_gt(prev_frame_number, qh->start_active_frame)) {
|
|
|
u16 ideal_start = qh->start_active_frame;
|
|
|
+ int periods_in_map;
|
|
|
|
|
|
- /* Adjust interval as per gcd with plan length. */
|
|
|
- interval = gcd(interval, 8);
|
|
|
+ /*
|
|
|
+ * Adjust interval as per gcd with map size.
|
|
|
+ * See pmap_schedule() for more details here.
|
|
|
+ */
|
|
|
+ if (qh->do_split || qh->dev_speed == USB_SPEED_HIGH)
|
|
|
+ periods_in_map = DWC2_HS_SCHEDULE_UFRAMES;
|
|
|
+ else
|
|
|
+ periods_in_map = DWC2_LS_SCHEDULE_FRAMES;
|
|
|
+ interval = gcd(interval, periods_in_map);
|
|
|
|
|
|
do {
|
|
|
qh->start_active_frame = dwc2_frame_num_inc(
|