On Fri, 2007-09-07 at 09:11 -0700, Thirumalai Srinivasan wrote:
> >
> >
> >There are further complexities, because the driver cannot know if any
> >upstream callers are still busy until it calls mac_unregister().  And
> >then, because mac_unregister *also* frees the structure, its too late.
> >  
> >
> 
> If there are indeed upstream callers, mi_ref is non-zero and 
> mac_register will fail. So it should be sufficient, if the driver 
> quiesces the chip, and become single threaded on the mac endpoint before 
> calling mac_unregister to avoid the race.

You're missing a big point.  That is that the effort to become single
threaded, *before* this point, may be a significant PITA, particularly
since mac_unregister may fail.

>  Another question, what does 
> xxx_m_stop really mean ? Why can't the driver quiesce everything at this 
> point itself ? If mac_unregister() succeeds, it implies that an 
> xxx_m_stop has happened before that. It doesn't seem right if we can 
> still have upcall threads and notifications going on even after 
> mac_stop() has finished successfully.

Well, perhaps mac_stop says that we shouldn't upcall.  I'm not sure.  If
so, then this would solve the race.  Right now I *think* pretty much
most drivers do shutdown the chip (though I'm not sure all of them do)
in mac_stop, to the point of even dropping the link.

I do know that there are cyclics, etc. that some of these drivers
register in attach(), and don't tear down until detach(), and that they
would *prefer* to do that after unregistration is complete.

One question here... can mac_start() (or any other mac entry point) be
called *while* running the DDI detach code?  It isn't clear to me that
DDI detach is single threaded from MAC's view until after mac_unregister
is called.

In other words, imagine this... 


m_stop() {
... statep->STARTED = B_FALSE;
}

m_start() {
... statep->STARTED = B_TRUE;
}

detach() {
...
        if (statep->STARTED)
                return (DDI_FAILURE);
        /* race point */
        mac_unregister(...);
}

Now what happens if, at "race point" above, mac_start() is called?

One solution to this is to add some complexity to the in the drivers,
like this:

m_start() {
        mutex_enter(&statep->LOCK);
        if (statep->DETACHING) {
                return B_FALSE;
        }
        statep->STARTED = B_TRUE;
        mutex_exit(&statep->LOCK);
}

detach() {
        mutex_enter(&statep->LOCK);
        if (statep->STARTED) {
                mutex_exit(&statep->LOCK);
                return (DDI_FAILURE);
        }
        statep->DETACHING = B_TRUE;
        mutex_exit(&statep->LOCK);
        return (DDI_SUCCESS);
}

So that adds complexity to the driver.  Furthermore, it has a potential
semantic change that I'm uncomfortable with.  Specifically, it isn't
clear to me that a stream in DL_ATTACHED, but not DL_BOUND, state, will
have statep->STARTED set to true.

In otherwords, does the very act of calling mac_open() with for a node
(which holds the device busy from DR) *also* arrange to call
mac_start()?  And does it do so atomically, so that there is no race
where DDI detach can be called between mac_open and mac_start?

All these questions of atomicity are confusing, and lead to a
substantial increase in complexity.  I still maintain the simplest
solution is to separate mac destruction from unregistration from the
framework.

Here's an sample implementation that I think is cleaner:

mac_start() {
        ... /* no need to set or check any flags */
}

mac_stop() {
        /* no need to set or check any flags */
}

detach() {
        if (mac_unregister(mh) == B_FALSE)
                return (DDI_FAILURE);
        /* do any thing else required to go single threaded, etc. */
        mac_destroy(mh);
}

        -- Garrett
> 
> Thirumalai
> 
> Garrett D'Amore wrote:
> 
> >On Fri, 2007-09-07 at 08:25 -0400, Sebastien Roy wrote:
> >  
> >
> >>Paul Durrant wrote:
> >>    
> >>
> >>>On 06/09/07, Garrett D'Amore <[EMAIL PROTECTED]> wrote:
> >>>      
> >>>
> >>>>(Obviously mac_alloc() here would return "mac_t *", but under the covers
> >>>>it is really allocating a whole mac_impl_t.  And mac_free() would be
> >>>>free()'ing a a whole mac_impl_t, though it is taking a mac_t as its
> >>>>argument.)
> >>>>
> >>>>If we want to pursue this course of action, I can put together a webrev
> >>>>with the changes... it will take me a while because there are a *lot* of
> >>>>drivers to change... but I want to get this change in *before* we make
> >>>>GLDv3 public.
> >>>>        
> >>>>
> >>>FWIW, I find the current mac_register_t scheme ugly; I don't see why a
> >>>statically defined mac_register_t cannot simply be passed into
> >>>mac_register(). I don't see why we need separate allocator functions
> >>>there.
> >>>      
> >>>
> >>It's done this way so that the structure can be expanded without 
> >>affecting binary compatibility.  We can add a new field to the structure, 
> >>and the system won't crash and burn when an existing driver registers. 
> >>The framework will allocate an appropriately sized structure, but the 
> >>older driver simply won't fill in the new fields it doesn't know about.
> >>
> >>    
> >>
> >>>I also don't see that you can't break this race using reference
> >>>counting. There's no need for mac_unregister() to actually free the
> >>>mac_impl_t; it could just drop a ref. and the last man out frees.
> >>>Let's try not to complicate the interface because of implementation
> >>>artifacts.
> >>>      
> >>>
> >>I don't believe a reference counting scheme makes a difference in the 
> >>scenario that Thiru described.  He's describing a scenario where the 
> >>mac_impl_t is freed by mac_unregister() before another thread gets a 
> >>chance to examine that mac_impl_t to verify its contents (either for a 
> >>flag or a reference count, it doesn't matter, the end result is still the 
> >>same; boom!)
> >>
> >>IMO, this case is borderline a driver bug.  The driver has called some 
> >>MAC function (which hasn't returned yet), and decides to call 
> >>mac_unregister().  mac_unregister() is documented to invalidate the MAC 
> >>handle, so drivers shouldn't knowingly call mac_unregister() while using 
> >>that handle, or use the handle during or after calls to mac_unregister().
> >>
> >>To me, this is a similar problem to calling free() on a buffer that an 
> >>application knows that it's still using...  Synchronization on the 
> >>allocation and deallocation of that buffer can't be done by the memory 
> >>framework, it has to be done by the application.  The framework can't 
> >>possibly know what the application is going to do with that buffer once 
> >>the free() function has been called.
> >>    
> >>
> >
> >There are further complexities, because the driver cannot know if any
> >upstream callers are still busy until it calls mac_unregister().  And
> >then, because mac_unregister *also* frees the structure, its too late.
> >
> >Forcing drivers to work around this will incur substantial code
> >complexity across every Nemo driver.  (Think an extra lock, flag, and
> >condition variable used with *every* place that a driver calls up into
> >Nemo.)  I think that is a poor decision.
> >
> >     -- Garrett
> >
> >
> >  
> >
> 

_______________________________________________
networking-discuss mailing list
[email protected]

Reply via email to