From: Pantelis Koukousoulas <pktoss-***@public.gmane.org>
dummy_hcd uses jiffies and seems to assume that HZ=1000 and no
tickless behavior. This makes for some horrible performance in
ordinary desktop kernels (tickless / HZ=250) as found in distros.
This patch ports dummy_hcd to use hrtimers instead, which allows
for reasonable performance in distro kernels as well.
(Around 25MB/s for g_mass_storage, around 100MB/s for UAS).
Implementation uses the tasklet_hrtimer framework. This means
dummy_timer callback is executed in softirq context (as it was
with wheel timers) which keeps required changes to a minimum.
---
drivers/usb/gadget/dummy_hcd.c | 55 +++++++++++++++++++++++++++---------------
1 file changed, 36 insertions(+), 19 deletions(-)
diff --git a/drivers/usb/gadget/dummy_hcd.c b/drivers/usb/gadget/dummy_hcd.c
index 67ebf90..7fb6f76 100644
--- a/drivers/usb/gadget/dummy_hcd.c
+++ b/drivers/usb/gadget/dummy_hcd.c
@@ -166,7 +166,7 @@ enum dummy_rh_state {
struct dummy_hcd {
struct dummy *dum;
enum dummy_rh_state rh_state;
- struct timer_list timer;
+ struct tasklet_hrtimer ttimer;
u32 port_status;
u32 old_status;
unsigned long re_timeout;
@@ -1190,8 +1190,11 @@ static int dummy_urb_enqueue(
urb->error_count = 1; /* mark as a new urb */
/* kick the scheduler, it'll do the rest */
- if (!timer_pending(&dum_hcd->timer))
- mod_timer(&dum_hcd->timer, jiffies + 1);
+ if (!hrtimer_is_queued(&dum_hcd->ttimer.timer)) {
+ tasklet_hrtimer_start(&dum_hcd->ttimer,
+ ms_to_ktime(1),
+ HRTIMER_MODE_REL);
+ }
done:
spin_unlock_irqrestore(&dum_hcd->dum->lock, flags);
@@ -1211,8 +1214,12 @@ static int dummy_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
rc = usb_hcd_check_unlink_urb(hcd, urb, status);
if (!rc && dum_hcd->rh_state != DUMMY_RH_RUNNING &&
- !list_empty(&dum_hcd->urbp_list))
- mod_timer(&dum_hcd->timer, jiffies);
+ !list_empty(&dum_hcd->urbp_list) &&
+ !hrtimer_is_queued(&dum_hcd->ttimer.timer)) {
+ tasklet_hrtimer_start(&dum_hcd->ttimer,
+ ns_to_ktime(100),
+ HRTIMER_MODE_REL);
+ }
spin_unlock_irqrestore(&dum_hcd->dum->lock, flags);
return rc;
@@ -1646,17 +1653,25 @@ static int handle_control_request(struct dummy_hcd *dum_hcd, struct urb *urb,
return ret_val;
}
+
/* drive both sides of the transfers; looks like irq handlers to
* both drivers except the callbacks aren't in_irq().
*/
-static void dummy_timer(unsigned long _dum_hcd)
+static enum hrtimer_restart dummy_timer(struct hrtimer *timer)
{
- struct dummy_hcd *dum_hcd = (struct dummy_hcd *) _dum_hcd;
+ struct tasklet_hrtimer *ttimer = container_of(timer,
+ struct tasklet_hrtimer,
+ timer);
+ struct dummy_hcd *dum_hcd = container_of(ttimer,
+ struct dummy_hcd,
+ ttimer);
+
struct dummy *dum = dum_hcd->dum;
struct urbp *urbp, *tmp;
unsigned long flags;
int limit, total;
int i;
+ ktime_t next_tick;
/* simplistic model for one frame's bandwidth */
switch (dum->gadget.speed) {
@@ -1675,11 +1690,9 @@ static void dummy_timer(unsigned long _dum_hcd)
break;
default:
dev_err(dummy_dev(dum_hcd), "bogus device speed\n");
- return;
+ goto out;
}
- /* FIXME if HZ != 1000 this will probably misbehave ... */
-
/* look at each urb queued by the host side driver */
spin_lock_irqsave(&dum->lock, flags);
@@ -1687,7 +1700,7 @@ static void dummy_timer(unsigned long _dum_hcd)
dev_err(dummy_dev(dum_hcd),
"timer fired with no URBs pending?\n");
spin_unlock_irqrestore(&dum->lock, flags);
- return;
+ goto out;
}
for (i = 0; i < DUMMY_ENDPOINTS; i++) {
@@ -1859,10 +1872,16 @@ return_urb:
dum_hcd->udev = NULL;
} else if (dum_hcd->rh_state == DUMMY_RH_RUNNING) {
/* want a 1 msec delay here */
- mod_timer(&dum_hcd->timer, jiffies + msecs_to_jiffies(1));
+ next_tick = ktime_add(hrtimer_get_expires(&dum_hcd->ttimer.timer),
+ ms_to_ktime(1));
+ tasklet_hrtimer_start(&dum_hcd->ttimer, next_tick,
+ HRTIMER_MODE_ABS);
}
spin_unlock_irqrestore(&dum->lock, flags);
+
+out:
+ return HRTIMER_NORESTART;
}
/*-------------------------------------------------------------------------*/
@@ -2238,7 +2257,9 @@ static int dummy_bus_resume(struct usb_hcd *hcd)
dum_hcd->rh_state = DUMMY_RH_RUNNING;
set_link_state(dum_hcd);
if (!list_empty(&dum_hcd->urbp_list))
- mod_timer(&dum_hcd->timer, jiffies);
+ tasklet_hrtimer_start(&dum_hcd->ttimer,
+ ms_to_ktime(1),
+ HRTIMER_MODE_REL);
hcd->state = HC_STATE_RUNNING;
}
spin_unlock_irq(&dum_hcd->dum->lock);
@@ -2316,9 +2337,7 @@ static DEVICE_ATTR_RO(urbs);
static int dummy_start_ss(struct dummy_hcd *dum_hcd)
{
- init_timer(&dum_hcd->timer);
- dum_hcd->timer.function = dummy_timer;
- dum_hcd->timer.data = (unsigned long)dum_hcd;
+ tasklet_hrtimer_init(&dum_hcd->ttimer, dummy_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
dum_hcd->rh_state = DUMMY_RH_RUNNING;
dum_hcd->stream_en_ep = 0;
INIT_LIST_HEAD(&dum_hcd->urbp_list);
@@ -2347,9 +2366,7 @@ static int dummy_start(struct usb_hcd *hcd)
return dummy_start_ss(dum_hcd);
spin_lock_init(&dum_hcd->dum->lock);
- init_timer(&dum_hcd->timer);
- dum_hcd->timer.function = dummy_timer;
- dum_hcd->timer.data = (unsigned long)dum_hcd;
+ tasklet_hrtimer_init(&dum_hcd->ttimer, dummy_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
dum_hcd->rh_state = DUMMY_RH_RUNNING;
INIT_LIST_HEAD(&dum_hcd->urbp_list);
--
1.9.1
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-***@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html