Re: [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions
From: Alan Stern
Date: Tue Jul 14 2026 - 15:27:12 EST
On Tue, Jul 14, 2026 at 07:53:03PM +0200, Danilo Krummrich wrote:
> (Cc: driver-core)
>
> On Tue Jul 14, 2026 at 6:26 PM CEST, Alan Stern wrote:
> > I don't see why Rust needs to distinguish between a USB device that is
> > bound and one that is unbound. There isn't much you can do with one
> > that can't be done with the other.
>
> It has nothing to do with Rust, those driver lifecycle rules exist regardless
> and they are present universally, including in C.
>
> The only difference is that in C all the responsibility to enforce them is
> usually on the driver -- e.g. by creating, destroying and calling things in the
> correct order in probe() and remove() -- and a lot of drivers have bugs in this
> regard as a consequence.
>
> With Rust we can enforce those rules with the help of the type system at compile
> time; device context states are a part of that.
I don't really understand how that would work. For example, suppose you
have an object whose type represents an unbound USB interface (I forget
what you are calling this). Then a driver is probed and binds to the
interface. What happens to the object? Is it somehow destroyed and
replaced by a new object of a different type, representing a bound
interface? Or does the object remain unchanged but you create a new
reference to it, of the new type?
Also, what happens while the binding or unbinding procedure is underway,
so the interface is, so to speak, partially bound? The USB stack does
actually take notice of this; see the definition of enum
usb_interface_condition in include/linux/usb.h.
> > Similarly, I don't see why Rust needs to distinguish between an
> > interface that is bound and one that isn't.
> >
> > Even from the point of view of the device core, a device that is bound
> > to a driver is the same kind of data structure as one that isn't bound;
> > the only difference is whether the ->driver pointer is set.
>
> This is a huge understatement.
>
> The state of a device being bound to a driver defines which entity (i.e. which
> driver) is in charge of operating the underlying device, and thus defines who
> owns the device (associated) resources.
That's not how I would describe it. When a device is bound to a driver,
the driver is allowed to create and use associated resource; when the
device is not bound, no such resources should exist.
> Many APIs rely on this, as in they only guarantee valid behavior when called
> from a scope where the device is guaranteed to be bound to a driver, or IOW
> where a driver can prove that it actually operates the device.
I can't think of many APIs like that in the USB stack. One that springs
to mind is encapsulated by checkintf() and check_ctrlrecip() in
core/devio.c, but those are the exception rather than the rule.
> Drivers must only acquire device resources when they are actually bound to the
> corresponding device, and must hand them back before the device is unbound. The
> devres API, for instance, exists for this fundamental reason.
How would having separate types for bound and unbound interfaces enable
Rust to recognize that a driver had not destroyed back a resource in its
unbind callback?
> For instance, we can't have drivers manage IRQs, mess with I/O memory, program
> IOMMU page tables (e.g. through DMA APIs), etc. for devices they are not bound
> to and hence are not allowed to operate (anymore).
>
> Those device resources all have a lifetime that is tied to the lifetime of the
> device being bound to a driver.
>
> Consequently, any asynchronous scopes such as IOCTLs from class device
> registrations, IRQs, work queued on workqueues, etc. must all be synchronized in
> some way such that those asynchronous scopes do not access device resources that
> have already been destroyed on driver unbind.
That is certainly true. But I don't see how it can be enforced at
compile time.
> Or in other words, they must be synchronized against the "bound" scope, which is
> exactly what the Device<Bound> type state in Rust represents.
>
> So, again, all those lifetime rules around the driver lifecycle exist
> universally, it's just that in Rust we enforce them through the type system.
How do you enforce through the type system that, for example, a
workqueue item has completed?
> For instance, tying it back to USB, we don't want that a usb_driver still messes
> with a usb_interface, e.g. initiating transfers after it has been unbound from
> the interface and hence must not operate it anymore. This can easily happen if
> e.g. a class device registration is not properly synchronized and the driver
> still receives IOCTLs after driver unbind. In Rust we know through the "Bound"
> type state which scope provides the guarantee that the device is still bound,
> such that mistakes like this become impossible.
How can you pass types around to different control threads with somewhat
arbitrary synchronization schemes managed at runtime, all while not
allowing a typed reference to exist beyond its lifetime? This seems
very similar to the "pointer zap" problem in C/C++, which as far as I
know, has no real solution.
Alan Stern