Skip to content

Commit

Permalink
USB fix using a FIQ to implement split transactions
Browse files Browse the repository at this point in the history
This commit adds a FIQ implementaion that schedules
the split transactions using a FIQ so we don't get
held off by the interrupt latency of Linux
  • Loading branch information
Gordon Hollingworth authored and popcornmix committed Jul 2, 2013
1 parent 438b4b4 commit db4fad7
Show file tree
Hide file tree
Showing 10 changed files with 696 additions and 114 deletions.
7 changes: 6 additions & 1 deletion drivers/usb/host/dwc_common_port/dwc_common_linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,12 @@ void DWC_WRITE_REG64(uint64_t volatile *reg, uint64_t value)

void DWC_MODIFY_REG32(uint32_t volatile *reg, uint32_t clear_mask, uint32_t set_mask)
{
unsigned long flags;

local_irq_save(flags);
local_fiq_disable();
writel((readl(reg) & ~clear_mask) | set_mask, reg);
local_irq_restore(flags);
}

#if 0
Expand Down Expand Up @@ -1301,7 +1306,7 @@ EXPORT_SYMBOL(DWC_EXCEPTION);
EXPORT_SYMBOL(__DWC_DEBUG);
#endif

EXPORT_SYMBOL(__DWC_DMA_ALLOC);
EXPORT_SYMBOL(__DWC_DMA_ALLOC);
EXPORT_SYMBOL(__DWC_DMA_ALLOC_ATOMIC);
EXPORT_SYMBOL(__DWC_DMA_FREE);
EXPORT_SYMBOL(__DWC_ALLOC);
Expand Down
37 changes: 27 additions & 10 deletions drivers/usb/host/dwc_otg/dwc_otg_cil_intr.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@
#include "dwc_otg_hcd.h"
#include "dwc_otg_mphi_fix.h"

extern bool fiq_fix_enable;

#ifdef DEBUG
inline const char *op_state_str(dwc_otg_core_if_t * core_if)
{
Expand Down Expand Up @@ -1321,7 +1319,7 @@ static int32_t dwc_otg_handle_lpm_intr(dwc_otg_core_if_t * core_if)
/**
* This function returns the Core Interrupt register.
*/
static inline uint32_t dwc_otg_read_common_intr(dwc_otg_core_if_t * core_if)
static inline uint32_t dwc_otg_read_common_intr(dwc_otg_core_if_t * core_if, gintmsk_data_t *reenable_gintmsk)
{
gahbcfg_data_t gahbcfg = {.d32 = 0 };
gintsts_data_t gintsts;
Expand All @@ -1338,19 +1336,33 @@ static inline uint32_t dwc_otg_read_common_intr(dwc_otg_core_if_t * core_if)
gintmsk_common.b.lpmtranrcvd = 1;
#endif
gintmsk_common.b.restoredone = 1;
/** @todo: The port interrupt occurs while in device
* mode. Added code to CIL to clear the interrupt for now!
*/
gintmsk_common.b.portintr = 1;

if(dwc_otg_is_device_mode(core_if))
{
/** @todo: The port interrupt occurs while in device
* mode. Added code to CIL to clear the interrupt for now!
*/
gintmsk_common.b.portintr = 1;
}
gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts);
gintmsk.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk);
{
unsigned long flags;

// Re-enable the saved interrupts
local_irq_save(flags);
local_fiq_disable();
gintmsk.d32 |= gintmsk_common.d32;
gintsts_saved.d32 &= ~gintmsk_common.d32;
reenable_gintmsk->d32 = gintmsk.d32;
local_irq_restore(flags);
}

gahbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gahbcfg);

#ifdef DEBUG
/* if any common interrupts set */
if (gintsts.d32 & gintmsk_common.d32) {
DWC_DEBUGPL(DBG_ANY, "gintsts=%08x gintmsk=%08x\n",
DWC_DEBUGPL(DBG_ANY, "common_intr: gintsts=%08x gintmsk=%08x\n",
gintsts.d32, gintmsk.d32);
}
#endif
Expand Down Expand Up @@ -1394,6 +1406,7 @@ int32_t dwc_otg_handle_common_intr(void *dev)
{
int retval = 0;
gintsts_data_t gintsts;
gintmsk_data_t reenable_gintmsk;
gpwrdn_data_t gpwrdn = {.d32 = 0 };
dwc_otg_device_t *otg_dev = dev;
dwc_otg_core_if_t *core_if = otg_dev->core_if;
Expand All @@ -1415,7 +1428,7 @@ int32_t dwc_otg_handle_common_intr(void *dev)
}

if (core_if->hibernation_suspend <= 0) {
gintsts.d32 = dwc_otg_read_common_intr(core_if);
gintsts.d32 = dwc_otg_read_common_intr(core_if, &reenable_gintmsk);

if (gintsts.b.modemismatch) {
retval |= dwc_otg_handle_mode_mismatch_intr(core_if);
Expand Down Expand Up @@ -1512,8 +1525,12 @@ int32_t dwc_otg_handle_common_intr(void *dev)
gintsts.b.portintr = 1;
DWC_WRITE_REG32(&core_if->core_global_regs->gintsts,gintsts.d32);
retval |= 1;
reenable_gintmsk.b.portintr = 1;

}

DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, reenable_gintmsk.d32);

} else {
DWC_DEBUGPL(DBG_ANY, "gpwrdn=%08x\n", gpwrdn.d32);

Expand Down
6 changes: 5 additions & 1 deletion drivers/usb/host/dwc_otg/dwc_otg_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,8 @@ static struct dwc_otg_driver_module_params dwc_otg_module_params = {

//Global variable to switch the fiq fix on or off (declared in bcm2708.c)
extern bool fiq_fix_enable;

// Global variable to enable the split transaction fix
bool fiq_split_enable = true;
//Global variable to switch the nak holdoff on or off
bool nak_holdoff_enable = true;

Expand Down Expand Up @@ -1090,6 +1091,7 @@ static int __init dwc_otg_driver_init(void)
}
printk(KERN_DEBUG "dwc_otg: FIQ %s\n", fiq_fix_enable ? "enabled":"disabled");
printk(KERN_DEBUG "dwc_otg: NAK holdoff %s\n", nak_holdoff_enable ? "enabled":"disabled");
printk(KERN_DEBUG "dwc_otg: FIQ split fix %s\n", fiq_split_enable ? "enabled":"disabled");

error = driver_create_file(drv, &driver_attr_version);
#ifdef DEBUG
Expand Down Expand Up @@ -1374,6 +1376,8 @@ module_param(fiq_fix_enable, bool, 0444);
MODULE_PARM_DESC(fiq_fix_enable, "Enable the fiq fix");
module_param(nak_holdoff_enable, bool, 0444);
MODULE_PARM_DESC(nak_holdoff_enable, "Enable the NAK holdoff");
module_param(fiq_split_enable, bool, 0444);
MODULE_PARM_DESC(fiq_split_enable, "Enable the FIQ fix on split transactions");

/** @page "Module Parameters"
*
Expand Down
125 changes: 116 additions & 9 deletions drivers/usb/host/dwc_otg/dwc_otg_hcd.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

#include "dwc_otg_hcd.h"
#include "dwc_otg_regs.h"
#include "dwc_otg_mphi_fix.h"

extern bool microframe_schedule, nak_holdoff_enable;

Expand Down Expand Up @@ -581,6 +582,8 @@ int dwc_otg_hcd_urb_dequeue(dwc_otg_hcd_t * hcd,
*/
dwc_otg_hc_halt(hcd->core_if, qh->channel,
DWC_OTG_HC_XFER_URB_DEQUEUE);

dwc_otg_hcd_release_port(hcd, qh);
}
}

Expand Down Expand Up @@ -716,6 +719,8 @@ static void completion_tasklet_func(void *ptr)

usb_hcd_giveback_urb(hcd->priv, urb, urb->status);

fiq_print(FIQDBG_PORTHUB, "COMPLETE");

DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
}
DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
Expand Down Expand Up @@ -979,6 +984,10 @@ int dwc_otg_hcd_init(dwc_otg_hcd_t * hcd, dwc_otg_core_if_t * core_if)
hcd->frame_list = NULL;
hcd->frame_list_dma = 0;
hcd->periodic_qh_count = 0;

DWC_MEMSET(hcd->hub_port, 0, sizeof(hcd->hub_port));
DWC_MEMSET(hcd->hub_port_alloc, -1, sizeof(hcd->hub_port_alloc));

out:
return retval;
}
Expand Down Expand Up @@ -1124,7 +1133,12 @@ static void assign_and_init_hc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
uint32_t hub_addr, port_addr;
hc->do_split = 1;
hc->xact_pos = qtd->isoc_split_pos;
hc->complete_split = qtd->complete_split;
/* We don't need to do complete splits anymore */
if(fiq_split_enable)
hc->complete_split = qtd->complete_split = 0;
else
hc->complete_split = qtd->complete_split;

hcd->fops->hub_info(hcd, urb->priv, &hub_addr, &port_addr);
hc->hub_addr = (uint8_t) hub_addr;
hc->port_addr = (uint8_t) port_addr;
Expand Down Expand Up @@ -1271,6 +1285,62 @@ static void assign_and_init_hc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
hc->qh = qh;
}

/*
** Check the transaction to see if the port / hub has already been assigned for
** a split transaction
**
** Return 0 - Port is already in use
*/
int dwc_otg_hcd_allocate_port(dwc_otg_hcd_t * hcd, dwc_otg_qh_t *qh)
{
uint32_t hub_addr, port_addr;

if(!fiq_split_enable)
return 0;

hcd->fops->hub_info(hcd, DWC_CIRCLEQ_FIRST(&qh->qtd_list)->urb->priv, &hub_addr, &port_addr);

if(hcd->hub_port[hub_addr] & (1 << port_addr))
{
fiq_print(FIQDBG_PORTHUB, "H%dP%d:S%02d", hub_addr, port_addr, qh->skip_count);

qh->skip_count++;

if(qh->skip_count > 40000)
{
printk_once(KERN_ERR "Error: Having to skip port allocation");
local_fiq_disable();
BUG();
return 0;
}
return 1;
}
else
{
qh->skip_count = 0;
hcd->hub_port[hub_addr] |= 1 << port_addr;
fiq_print(FIQDBG_PORTHUB, "H%dP%d:A %d", hub_addr, port_addr, DWC_CIRCLEQ_FIRST(&qh->qtd_list)->urb->pipe_info.ep_num);
hcd->hub_port_alloc[hub_addr * 16 + port_addr] = dwc_otg_hcd_get_frame_number(hcd);
return 0;
}
}
void dwc_otg_hcd_release_port(dwc_otg_hcd_t * hcd, dwc_otg_qh_t *qh)
{
uint32_t hub_addr, port_addr;

if(!fiq_split_enable)
return;

hcd->fops->hub_info(hcd, DWC_CIRCLEQ_FIRST(&qh->qtd_list)->urb->priv, &hub_addr, &port_addr);

hcd->hub_port[hub_addr] &= ~(1 << port_addr);
hcd->hub_port_alloc[hub_addr * 16 + port_addr] = -1;

fiq_print(FIQDBG_PORTHUB, "H%dP%d:RO%d", hub_addr, port_addr, DWC_CIRCLEQ_FIRST(&qh->qtd_list)->urb->pipe_info.ep_num);

}


/**
* This function selects transactions from the HCD transfer schedule and
* assigns them to available host channels. It is called from HCD interrupt
Expand Down Expand Up @@ -1304,11 +1374,22 @@ dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd)

while (qh_ptr != &hcd->periodic_sched_ready &&
!DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) {

qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry);

if(qh->do_split && dwc_otg_hcd_allocate_port(hcd, qh))
{
qh_ptr = DWC_LIST_NEXT(qh_ptr);
g_next_sched_frame = dwc_frame_num_inc(dwc_otg_hcd_get_frame_number(hcd), 1);
continue;
}

if (microframe_schedule) {
// Make sure we leave one channel for non periodic transactions.
DWC_SPINLOCK_IRQSAVE(channel_lock, &flags);
if (hcd->available_host_channels <= 1) {
DWC_SPINUNLOCK_IRQRESTORE(channel_lock, flags);
if(qh->do_split) dwc_otg_hcd_release_port(hcd, qh);
break;
}
hcd->available_host_channels--;
Expand All @@ -1329,8 +1410,6 @@ dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd)
DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_assigned,
&qh->qh_list_entry);
DWC_SPINUNLOCK_IRQRESTORE(channel_lock, flags);

ret_val = DWC_OTG_TRANSACTION_PERIODIC;
}

/*
Expand Down Expand Up @@ -1369,10 +1448,19 @@ dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd)
qh->nak_frame = 0xffff;
}
}

if (qh->do_split && dwc_otg_hcd_allocate_port(hcd, qh))
{
g_next_sched_frame = dwc_frame_num_inc(dwc_otg_hcd_get_frame_number(hcd), 1);
qh_ptr = DWC_LIST_NEXT(qh_ptr);
continue;
}

if (microframe_schedule) {
DWC_SPINLOCK_IRQSAVE(channel_lock, &flags);
if (hcd->available_host_channels < 1) {
DWC_SPINUNLOCK_IRQRESTORE(channel_lock, flags);
if(qh->do_split) dwc_otg_hcd_release_port(hcd, qh);
break;
}
hcd->available_host_channels--;
Expand All @@ -1396,16 +1484,17 @@ dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd)

g_np_sent++;

if (ret_val == DWC_OTG_TRANSACTION_NONE) {
ret_val = DWC_OTG_TRANSACTION_NON_PERIODIC;
} else {
ret_val = DWC_OTG_TRANSACTION_ALL;
}

if (!microframe_schedule)
hcd->non_periodic_channels++;
}

if(!DWC_LIST_EMPTY(&hcd->periodic_sched_assigned))
ret_val |= DWC_OTG_TRANSACTION_PERIODIC;

if(!DWC_LIST_EMPTY(&hcd->non_periodic_sched_active))
ret_val |= DWC_OTG_TRANSACTION_NON_PERIODIC;


#ifdef DEBUG_HOST_CHANNELS
last_sel_trans_num_avail_hc_at_end = hcd->available_host_channels;
#endif /* DEBUG_HOST_CHANNELS */
Expand Down Expand Up @@ -1522,6 +1611,15 @@ static void process_periodic_channels(dwc_otg_hcd_t * hcd)

qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry);

// Do not send a split start transaction any later than frame .6
// Note, we have to schedule a periodic in .5 to make it go in .6
if(fiq_split_enable && qh->do_split && ((dwc_otg_hcd_get_frame_number(hcd) + 1) & 7) > 6)
{
qh_ptr = qh_ptr->next;
g_next_sched_frame = dwc_otg_hcd_get_frame_number(hcd) | 7;
continue;
}

/*
* Set a flag if we're queuing high-bandwidth in slave mode.
* The flag prevents any halts to get into the request queue in
Expand Down Expand Up @@ -1651,6 +1749,15 @@ static void process_non_periodic_channels(dwc_otg_hcd_t * hcd)

qh = DWC_LIST_ENTRY(hcd->non_periodic_qh_ptr, dwc_otg_qh_t,
qh_list_entry);

// Do not send a split start transaction any later than frame .5
// non periodic transactions will start immediately in this uframe
if(fiq_split_enable && qh->do_split && ((dwc_otg_hcd_get_frame_number(hcd) + 1) & 7) > 6)
{
g_next_sched_frame = dwc_otg_hcd_get_frame_number(hcd) | 7;
break;
}

status =
queue_transaction(hcd, qh->channel,
tx_status.b.nptxfspcavail);
Expand Down
Loading

0 comments on commit db4fad7

Please sign in to comment.