Re: [PATCH 1/4] USB: fix failure path in usb_add_hcd()

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Sat, 12 Jun 2010, Sergei Shtylyov wrote:

> >  error_create_attr_group:
> > +	if (HC_IS_RUNNING(hcd->state))
> > +		hcd->state = HC_STATE_QUIESCING;
> > +	spin_lock_irq(&hcd_root_hub_lock);
> > +	hcd->rh_registered = 0;
> > +	spin_unlock_irq(&hcd_root_hub_lock);
> 
>     What's the point of taking a spinlock and disabling interrupts around an 
> atomic write?

That's a good question.  I have stumbled upon exactly the same issue 
in the past.

Well, in fact it's really two questions.  The more trivial one is why
disable interrupts?  That's easy -- since the hcd_root_hub_lock
spinlock can be acquired by code that runs in_interrupt, you have to
make sure that interrupts are disabled whenever you acquire the
spinlock.  If you didn't, and an interrupt occurred while you were
holding the spinlock, and the interrupt handler wanted to lock the
spinlock, you'd be in trouble.

The second, more interesting question is why use the spinlock at all?  
Answer: It's not for protecting the flag; it's for synchronization.

To begin with, you should be aware that setting rh_registered is _not_
an atomic operation.  Although some CPUs may be able to access bitflags
using atomic operations, this certainly isn't guaranteed for all CPUs.
Remember, the rh_registered field is defined by:

	unsigned	rh_registered:1;

and not by:

	unsigned	rh_registered;

To change its value, a CPU might need to load the entire word
containing that field into a register, then alter one bit in the
register, and then write the register back to memory.  That's why
you'll sometimes see code use set_bit(), clear_bit(), and test_bit()
instead of simply using bitflags -- these operations _are_ guaranteed
to be atomic on all CPUs.  In fact, I've got another patch, not yet
submitted, which changes some of the bitflags in usb_hcd to make them 
atomic.

However it wouldn't have made any difference if rh_registered _were_ 
atomic; the spinlock would still be needed.  In fact, this code could 
have been written as:

	hcd->rh_registered = 0;
	spin_lock_irq(&hcd_root_hub_lock);
	spin_unlock_irq(&hcd_root_hub_lock);

although that would look pretty strange.  The reason, as I said above, 
is for synchronization.

hcd->rh_registered is tested in two places: usb_hcd_resume_root_hub() 
and usb_hc_died().  The reason, obviously, is because we don't want to 
autoresume a root hub that isn't registered or is about to be 
unregistered, and likewise for reporting that a root hub has died.

The one thing we don't want to happen is that one CPU sets
hcd->rh_registered to 0, while at the same time or a little later,
another CPU tests hcd->rh_registered and still sees the old value of 1.  
The way to prevent this is to use a synchronization primitive: a lock, 
mutex, or memory barrier.  Since some of the code runs in_interrupt I 
couldn't use a mutex, and memory barriers should be used only in 
special circumstances.  Thus a spinlock was the appropriate solution.

Alan Stern

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux