ehci: Lower timer freq when the periodic schedule is idle

Lower the timer freq if no iso schedule packets complete for 64 frames in
a row.

We can safely do this, without adding latency, because:
1) If there is isoc traffic this will never trigger
2) For async handled interrupt packets (only usb-host), the completion handler
   will immediately schedule the frame_timer from a bh
3) All devices using NAK to signal no data for interrupt endpoints now use
   wakeup, which will immediately schedule the frame_timer from a bh

The advantage of this is that when we only have interrupt packets in the
periodic schedule, async_stepdown can do its work and significantly lower
the frequency at which the frame_timer runs.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
This commit is contained in:
Hans de Goede 2012-11-17 12:47:17 +01:00 committed by Gerd Hoffmann
parent 386ab487eb
commit 8082624099
2 changed files with 34 additions and 6 deletions

View File

@ -114,6 +114,7 @@
#define BUFF_SIZE 5*4096 // Max bytes to transfer per transaction
#define MAX_QH 100 // Max allowable queue heads in a chain
#define MIN_FR_PER_TICK 3 // Min frames to process when catching up
#define PERIODIC_ACTIVE 64
/* Internal periodic / asynchronous schedule state machine states
*/
@ -738,6 +739,19 @@ static int ehci_register_companion(USBBus *bus, USBPort *ports[],
return 0;
}
static void ehci_wakeup_endpoint(USBBus *bus, USBEndpoint *ep)
{
EHCIState *s = container_of(bus, EHCIState, bus);
uint32_t portsc = s->portsc[ep->dev->port->index];
if (portsc & PORTSC_POWNER) {
return;
}
s->periodic_sched_active = PERIODIC_ACTIVE;
qemu_bh_schedule(s->async_bh);
}
static USBDevice *ehci_find_device(EHCIState *ehci, uint8_t addr)
{
USBDevice *dev;
@ -1188,9 +1202,10 @@ static void ehci_async_complete_packet(USBPort *port, USBPacket *packet)
trace_usb_ehci_packet_action(p->queue, p, "wakeup");
p->async = EHCI_ASYNC_FINISHED;
if (p->queue->async) {
qemu_bh_schedule(p->queue->ehci->async_bh);
if (!p->queue->async) {
s->periodic_sched_active = PERIODIC_ACTIVE;
}
qemu_bh_schedule(s->async_bh);
}
static void ehci_execute_complete(EHCIQueue *q)
@ -1344,6 +1359,8 @@ static int ehci_process_itd(EHCIState *ehci,
uint32_t i, len, pid, dir, devaddr, endp;
uint32_t pg, off, ptr1, ptr2, max, mult;
ehci->periodic_sched_active = PERIODIC_ACTIVE;
dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION);
devaddr = get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR);
endp = get_field(itd->bufptr[0], ITD_BUFPTR_EP);
@ -2033,6 +2050,9 @@ static void ehci_advance_state(EHCIState *ehci, int async)
case EST_WRITEBACK:
assert(q != NULL);
again = ehci_state_writeback(q);
if (!async) {
ehci->periodic_sched_active = PERIODIC_ACTIVE;
}
break;
default:
@ -2198,7 +2218,6 @@ static void ehci_frame_timer(void *opaque)
if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE) {
need_timer++;
ehci->async_stepdown = 0;
if (frames > ehci->maxframes) {
skipped_frames = frames - ehci->maxframes;
@ -2222,18 +2241,25 @@ static void ehci_frame_timer(void *opaque)
break;
}
}
if (ehci->periodic_sched_active) {
ehci->periodic_sched_active--;
}
ehci_update_frindex(ehci, 1);
ehci_advance_periodic_state(ehci);
ehci->last_run_ns += FRAME_TIMER_NS;
}
} else {
if (ehci->async_stepdown < ehci->maxframes / 2) {
ehci->async_stepdown++;
}
ehci->periodic_sched_active = 0;
ehci_update_frindex(ehci, frames);
ehci->last_run_ns += FRAME_TIMER_NS * frames;
}
if (ehci->periodic_sched_active) {
ehci->async_stepdown = 0;
} else if (ehci->async_stepdown < ehci->maxframes / 2) {
ehci->async_stepdown++;
}
/* Async is not inside loop since it executes everything it can once
* called
*/
@ -2301,6 +2327,7 @@ static USBPortOps ehci_port_ops = {
static USBBusOps ehci_bus_ops = {
.register_companion = ehci_register_companion,
.wakeup_endpoint = ehci_wakeup_endpoint,
};
static int usb_ehci_post_load(void *opaque, int version_id)

View File

@ -311,6 +311,7 @@ struct EHCIState {
uint64_t last_run_ns;
uint32_t async_stepdown;
uint32_t periodic_sched_active;
bool int_req_by_async;
};