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;
}


Reply via email to