Hi,
I have read the ongoing thread regarding the feature
"submit more than one URB for a given endpoint at once,
and chaining them together". (Bulk endpoints).
I have a very similar problem: my device (Auerswald PABX)
is using an interrupt endpoint to inform the host that there
is data waiting in the device. This data is transfered via
the control endpoint.
So I have some problems:
- from inside the interrupt completion handler, I want to set
up a control message. This is possible, of course. But what
should I do if there is another control message pending?
- There may be more than one data message waiting at the
device. I must have a way to store the information until it's
time to submit the next control message.
I have tried to solve this problem in user space, but this
was not optimal regarding the long latency between signaling
the availability of data and the actual data transfer.
So I wrote a simple collection of functions to form a "chain"
of URBs. A new URB will be submitted if the last one is
completed.
Beware: the status of this collection is: it compiles, but it isn't
tested.
I post this collection here because other developers might find
it usefull. And maybe I get some nice code reviews, because I'm
*not* an experienced kernel developer....
have fun!
Wolfgang M�es
// small library to form a chain of urbs.
// By Wolfgang M�es ([EMAIL PROTECTED])
#include <gpl.licence> // as usual
// urb chain element
struct auerchain; // forward for circular reference
typedef struct
{
struct auerchain *chain; // pointer to the chain to which this element belongs
purb_t urbp; // pointer to attached urb
void *context; // saved URB context
usb_complete_t complete; // saved URB completion function
struct list_head list; // to include element into a list
} auerchainelement_t,*pauerchainelement_t;
// urb chain
typedef struct auerchain
{
pauerchainelement_t active; // element which is submitted to urb
spinlock_t lock; // protection agains interrupts
struct list_head waiting_list; // list of waiting elements
struct list_head free_list; // list of available elements
} auerchain_t,*pauerchain_t;
// completion function for chained urbs
static void auerchain_complete( purb_t urb)
{
unsigned long flags;
int result;
// get pointer to element and to chain
pauerchainelement_t acep = (pauerchainelement_t) urb->context;
pauerchain_t acp = acep->chain;
// restore original entries in urb
urb->context = acep->context;
urb->complete = acep->complete;
dbg ("auerchain_complete called");
// call original completion function
// NOTE: this function may lead to more urbs submitted into the chain.
// (no chain lock at calling complete()!)
// acp->active != NULL is protecting us against recursion.
urb->complete( urb);
// detach element from chain data structure
spin_lock_irqsave (&acp->lock, flags);
if (acp->active != acep) // paranoia debug check
dbg("auerchain_complete: completion on non-active element called!");
else
acp->active = NULL;
// add the used chain element to the list of free elements
list_add_tail (&acep->list, &acp->free_list);
acep = NULL;
// is there a new element waiting in the chain?
if (!acp->active && !list_empty (&acp->waiting_list)) {
// yes: get the entry
struct list_head *tmp = acp->waiting_list.next;
list_del( tmp);
acep = list_entry (tmp, auerchainelement_t, list);
acp->active = acep;
}
spin_unlock_irqrestore (&acp->lock, flags);
// submit the new urb
if (acep) {
urb = acep->urbp;
dbg("auerchain_complete: submitting next urb from chain");
result = usb_submit_urb( urb);
// check for submit errors
if (result) {
urb->status = result;
dbg("auerchain_complete: usb_submit_urb with error code %d", result);
// and do error handling via *this* completion function (recursive)
auerchain_complete( urb);
}
} else {
// simple return without submitting a new urb.
// The empty chain is detected with acp->active == NULL.
};
}
// submit function for chained urbs
// this function may be called from completion context or from user space!
static int auerchain_submit_urb( pauerchain_t acp, purb_t urb)
{
int result;
unsigned long flags;
pauerchainelement_t acep = NULL;
dbg ("auerchain_submit_urb called");
// try to get a chain element
spin_lock_irqsave (&acp->lock, flags);
if (!list_empty (&acp->free_list)) {
// yes: get the entry
struct list_head *tmp = acp->free_list.next;
list_del( tmp);
acep = list_entry (tmp, auerchainelement_t, list);
}
spin_unlock_irqrestore (&acp->lock, flags);
// if no chain element available: return with error
if (!acep) {
return -ENOMEM;
}
// fill in the new chain element values
acep->chain = acp;
acep->context = urb->context;
acep->complete = urb->complete;
acep->urbp = urb;
INIT_LIST_HEAD(&acep->list);
// modify urb
urb->context = acep;
urb->complete = auerchain_complete;
// add element to chain - or start it immediately
spin_lock_irqsave (&acp->lock, flags);
if (acp->active) {
// there is traffic in the chain, simple add element to chain
dbg("adding new urb to end of chain");
list_add_tail (&acep->list, &acp->waiting_list);
acep = NULL;
} else {
// the chain is empty. Prepare restart
acp->active = acep;
}
// Spin has to be removed before usb_submit_urb!
spin_unlock_irqrestore (&acp->lock, flags);
// Submit urb if immediate restart
if (acep) {
dbg("submitting urb immediate");
result = usb_submit_urb( urb);
// check for submit errors
if (result) {
urb->status = result;
dbg("auerchain_submit_urb: usb_submit_urb with error code %d", result);
// and do error handling via completion function
auerchain_complete( urb);
}
}
return 0;
}
// cancel an urb which is submitted to the chain
// the result is 0 if the urb is cancelled, or -EINPROGRESS if
// USB_ASYNC_UNLINK is set and the function is successfully started.
static int auerchain_unlink_urb( pauerchain_t acp, purb_t urb)
{
unsigned long flags;
purb_t urbp;
pauerchainelement_t acep;
struct list_head *tmp;
dbg("auerchain_unlink_urb called");
// search the chain of waiting elements
spin_lock_irqsave (&acp->lock, flags);
list_for_each( tmp, &acp->waiting_list) {
acep = list_entry (tmp, auerchainelement_t, list);
if (acep->urbp == urb) {
list_del( tmp);
urb->context = acep->context;
urb->complete = acep->complete;
list_add_tail (&acep->list, &acp->free_list);
spin_unlock_irqrestore (&acp->lock, flags);
dbg("unlink waiting urb");
urb->status = -ENOENT;
urb->complete( urb);
return 0;
}
}
// not found.
spin_unlock_irqrestore (&acp->lock, flags);
// get the active urb
acep = acp->active;
if (acep) {
urbp = acep->urbp;
// check if we have to cancel the active urb
if (urbp == urb) {
// note that there is a race condition between the check above
// and the unlink() call because of no lock. This race is harmless,
// because the usb module will detect the unlink() after completion.
// We can't use the acp->lock here because the completion function
// wants to grab it.
dbg("unlink active urb");
return usb_unlink_urb( urbp);
}
}
// not found anyway
// ... is some kind of success
return 0;
}
// cancel all urbs which are in the chain.
// this function must not be called from interrupt or completion handler.
static void auerchain_unlink_all( pauerchain_t acp)
{
unsigned long flags;
purb_t urbp;
pauerchainelement_t acep;
dbg("auerchain_unlink_all called");
// clear the chain of waiting elements
spin_lock_irqsave (&acp->lock, flags);
while (!list_empty(&acp->waiting_list)) {
// get the next entry
struct list_head *tmp = acp->waiting_list.next;
list_del( tmp);
acep = list_entry (tmp, auerchainelement_t, list);
urbp = acep->urbp;
urbp->context = acep->context;
urbp->complete = acep->complete;
list_add_tail (&acep->list, &acp->free_list);
spin_unlock_irqrestore (&acp->lock, flags);
dbg( "unlink waiting urb");
urbp->status = -ENOENT;
urbp->complete( urbp);
spin_lock_irqsave (&acp->lock, flags);
}
spin_unlock_irqrestore (&acp->lock, flags);
// clear the active urb
acep = acp->active;
if (acep) {
urbp = acep->urbp;
urbp->transfer_flags &= ~USB_ASYNC_UNLINK;
dbg ("unlink active urb");
usb_unlink_urb( urbp);
}
}
// free the chain.
// this function must not be called from interrupt or completion handler.
static void auerchain_free( pauerchain_t acp)
{
pauerchainelement_t acep;
dbg( "auerchain_free called");
// first, cancel all pending urbs
auerchain_unlink_all( acp);
// free the elements
while (!list_empty(&acp->free_list)) {
// get the next entry
struct list_head *tmp = acp->free_list.next;
list_del( tmp);
acep = list_entry (tmp, auerchainelement_t, list);
kfree( acep);
}
}
// setup a chain.
static int auerchain_setup( pauerchain_t acp, unsigned int numElements)
{
pauerchainelement_t acep;
dbg("auerchain_setup called with %d elements", numElements);
// init the chain data structure
acp->active = NULL;
spin_lock_init (&acp->lock);
INIT_LIST_HEAD (&acp->waiting_list);
INIT_LIST_HEAD (&acp->free_list);
// fill the list of free elements
for (;numElements; numElements--) {
acep = (pauerchainelement_t) kmalloc( sizeof( auerchainelement_t), GFP_KERNEL);
if (!acep) goto ac_fail;
list_add_tail (&acep->list, &acp->free_list);
}
return 0;
ac_fail:// free the elements
while (!list_empty(&acp->free_list)) {
// get the next entry
struct list_head *tmp = acp->free_list.next;
list_del( tmp);
acep = list_entry (tmp, auerchainelement_t, list);
kfree( acep);
}
return -ENOMEM;
}