20061127

Clonable Devices

I've seen questions on clonable devices come up from time to time. When do you want to use a clonable device? There are many times. The most common is to get 'per-open' semantics from a driver. Linux has this by default, but the structure of the vfs system in FreeBSD makes supporting it directly very difficult. Clonable devices are also useful when you want to open /dev/foo and dynamically create /dev/foo0, /dev/foo1, etc. Or when you want to have all attempts to open /dev/rodent/* succeed, yet you don't wish to enumerate all possible devices. I've heard some folks say that they want an open of /dev/tcp/client/hostname to connect to hostname, for example, and you can't enumerate all hostnames. I'll not comment on the wisdom of that design, but it is one that people understand.

Cloning it a bit underdocumented right now, as is most of the cdevsw interaction. But here's a sketch.

First, you need to register a clone event handler. There is one of these for your driver, even if it has multiple instances. The method I recommend is doing it in your attach routine if you haven't done so yet, and removing it in your detach routine:

static eventhandler_tag ehtag;
static int ehtagref;

int
foo_attach(device_t)
{
...
if (ehtag == NULL)
ehtag = EVENTHANDLER_REGISTER(dev_clone, foo_clone, 0, 1000);
ehtagref++;
...
}

int
foo_detach(device_t)
{
if (--ehtagref == 0 && ehtag != NULL)
EVENTHANDLER_DEREGISTER(dev_clone, ehtag);
}

You are then setup with a clone handler that gets called in response to all /dev/ opens that don't already have a static entry in /dev. This is a big magical, but apart from files not appearing in ls it works out well.

static struct clonedevs *foo_dev_clones = NULL;

/* Clone device */
static void
foo_dev_clone(void *arg, struct ucred *cred, char *name, int namelen,
struct cdev **dev)
{
if (*dev != NULL)
return;
/* If you want a /dev/foo control device */
if (strcmp(name, "foo") == 0)
unit = -1;
else if (dev_stdclone(name, NULL, "foo", &unit) != 1)
return; /* Bail on names we don't know */

/* find any existing device, or allocate new unit number */
if (clone_create(&foo_dev_clones, &foo_dev_cdevsw, &unit, dev, 0)) {
*dev = make_dev(&foo_dev_cdevsw, unit2minor(unit),
UID_ROOT, GID_WHEEL, 0600, "foo%d", unit);
if (*dev != NULL) {
dev_ref(*dev);
(*dev)->si_flags |= SI_CHEAPCLONE;
}
}
}

Your open routine will then get the new dev and you can save stuff off in it.


Finally, I'm looking for a good way to edit my posts so that the code comes out indented correctly. If anybody know how to do this, please let me know.

2 comments:

Anonymous said...

In your example, very useful, I see that you omitted clone_setup on attach and clone_cleanup on detach.
Do you think sometime in the future man pages will appear for clone_xxx functions?

Warner Losh said...

Yes. They should be written.