Subject: Device Properties: The Last Incarnation
To: None <tech-kern@netbsd.org>
From: None <eeh@netbsd.org>
List: tech-kern
Date: 03/03/2001 01:56:00
These are the machine-independent parts of the device
properties implementation. It should cause no impact
on existing drivers unless they attempt to use device
properties during attach.
Locators are a bit of a problem. Wildcarded locators
are all `-1' which doesn't help much, and always adding
them to the device node by config_search/config_attach
would overwrite any locators that the parent bus has
attached. So, instead, I prepend them with `loc-'
when creating properties.
A sample attach implementation using locators will
be sent separately.
Eduado
Index: sys/device.h
===================================================================
RCS file: /cvsroot/syssrc/sys/sys/device.h,v
retrieving revision 1.45
diff -u -r1.45 device.h
--- sys/device.h 2000/12/01 02:07:04 1.45
+++ sys/device.h 2001/03/03 01:48:49
@@ -88,6 +88,7 @@
* Note that all ``system'' device types are listed here.
*/
enum devclass {
+ DV_EMPTY,
DV_DULL, /* generic, no special info */
DV_CPU, /* CPU (carries resource utilization) */
DV_DISK, /* disk drive (label, etc) */
@@ -104,18 +105,43 @@
DVACT_DEACTIVATE /* deactivate the device */
};
+/*
+ * Device properties list entry.
+ *
+ * It contains both the data and name string within it. The
+ * size of dp_val can be calculated as:
+ *
+ * (dp_len + strlen(dp_name) + 1)
+ *
+ * rounded up to sizeof(long).
+ */
+#define PROP_WAIT 1
+#define PROP_SEARCH 2
+#define PROP_STRING 4
+#define PROP_CONST 8
+
+struct devprop {
+ SLIST_ENTRY(devprop) dp_list;
+ size_t dp_len;
+ void *dp_val; /* This contains the value data and name string */
+ const char dp_name[1]; /* This contains the name string and maybe value. */
+};
+
struct device {
enum devclass dv_class; /* this device's classification */
TAILQ_ENTRY(device) dv_list; /* entry on list of all devices */
struct cfdata *dv_cfdata; /* config data that found us */
int dv_unit; /* device unit number */
+ int dv_flags; /* misc. flags; see below */
char dv_xname[16]; /* external name (name + unit) */
struct device *dv_parent; /* pointer to parent device */
- int dv_flags; /* misc. flags; see below */
+ SLIST_HEAD(devpropslist, devprop) dv_props;
+ void *dv_private;
};
/* dv_flags */
#define DVF_ACTIVE 0x0001 /* device is activated */
+#define DVF_SOFTC 0x0002 /* separate softc has been allocated. */
TAILQ_HEAD(devicelist, device);
@@ -232,6 +258,16 @@
int pdev_count;
};
+/*
+ * Macros to support new driver functionality.
+ */
+#define DEV_PRIVATE(d) (d)->dv_private
+#define DEV_CFA_DECL(n, s, m, a) \
+struct cfattach n = { -(s), m, a }
+
+#define DEV_PROTECT(dev)
+#define DEV_UNPROTECT(dev)
+
#ifdef _KERNEL
extern struct devicelist alldevs; /* list of all devices */
@@ -241,12 +277,38 @@
extern __volatile int config_pending; /* semaphore for mountroot */
void configure(void);
-struct cfdata *config_search(cfmatch_t, struct device *, void *);
-struct cfdata *config_rootsearch(cfmatch_t, const char *, void *);
-struct device *config_found_sm(struct device *, void *, cfprint_t, cfmatch_t);
+
+/* Old functions */
struct device *config_rootfound(const char *, void *);
+#if 0
+struct cfdata *config_rootsearch(cfmatch_t, const char *, void *);
+struct cfdata *config_search(cfmatch_t, struct device *, void *);
struct device *config_attach(struct device *, struct cfdata *, void *,
- cfprint_t);
+ cfprint_t, struct device *);
+#endif
+/* Transitional functions */
+struct device *config_found_sad(struct device *, void *, cfprint_t, cfmatch_t,
+ struct device *);
+struct cfdata *config_search_ad(cfmatch_t, struct device *, void *, struct device *);
+struct cfdata *config_rootsearch_ad(cfmatch_t, const char *, void *,
+ struct device *);
+struct device *config_attach_ad(struct device *, struct cfdata *, void *,
+ cfprint_t, struct device *);
+
+/* New functions */
+struct device *dev_config_create(struct device *, int);
+#if 0
+struct device *dev_config_found(struct device *, struct device *,
+ cfmatch_t, cfprint_t);
+struct cfdata *dev_config_search(struct device *, struct device *, cfmatch_t);
+struct device *dev_config_attach(struct device *, struct device *,
+ struct cfdata *, cfprint_t);
+#else
+#define dev_config_found(d, c, m, p) config_found_sad((d), NULL, (p), (m), (c))
+#define dev_config_search(d, c, f) config_search_ad((f), (d), NULL, (c))
+#define dev_config_attach(d, n, c, p) config_attach_ad((d), (c), NULL, (p), (n))
+#endif
+
int config_detach(struct device *, int);
int config_activate(struct device *);
int config_deactivate(struct device *);
@@ -265,11 +327,21 @@
void evcnt_detach(struct evcnt *);
/* compatibility definitions */
-#define config_found(d, a, p) config_found_sm((d), (a), (p), NULL)
+#define config_search(f, d, a) config_search_ad((f), (d), (a), NULL)
+#define config_rootsearch(f, d, a) config_search_ad((f), (d), (a), NULL)
+#define config_found(d, a, p) config_found_sm((d), (a), (p), NULL)
+#define config_found_sm(d, a, p, s) config_found_sad((d), (a), (p), (s), NULL)
+#define config_attach(d, c, a, p) config_attach_ad((d), (c), (a), (p), NULL)
/* convenience definitions */
#define device_lookup(cfd, unit) \
(((unit) < (cfd)->cd_ndevs) ? (cfd)->cd_devs[(unit)] : NULL)
+
+/* Device properties */
+int dev_setprop(struct device *, const char *, void *, size_t, int);
+int dev_copyprobs(struct device *, struct device *, int);
+size_t dev_getprop(struct device *, const char *, void *, size_t, int);
+int dev_delprop(struct device *, const char *, int);
#endif /* _KERNEL */
Index: kern/subr_autoconf.c
===================================================================
RCS file: /cvsroot/syssrc/sys/kern/subr_autoconf.c,v
retrieving revision 1.55
diff -u -r1.55 subr_autoconf.c
--- kern/subr_autoconf.c 2000/07/08 18:11:02 1.55
+++ kern/subr_autoconf.c 2001/03/03 01:48:50
@@ -97,6 +97,9 @@
* Autoconfiguration subroutines.
*/
+static void zap_devprops(struct device *);
+static struct device *dev_create(struct device *, ssize_t, int);
+
/*
* ioconf.c exports exactly two names: cfdata and cfroots. All system
* devices and drivers are found via these tables.
@@ -115,7 +118,9 @@
};
static char *number(char *, int);
-static void mapply(struct matchinfo *, struct cfdata *);
+static void mapply(struct matchinfo *, struct cfdata *, struct device *);
+static void locset(struct device *, struct cfdata *);
+static void locrm(struct device *, struct cfdata *);
struct deferred_config {
TAILQ_ENTRY(deferred_config) dc_queue;
@@ -172,27 +177,75 @@
}
/*
+ * Set locators as properties on device node.
+ */
+static void
+locset(struct device *d, struct cfdata *cf)
+{
+ int i;
+ int flags = PROP_CONST;
+ char buf[32];
+
+ if (!cold) flags |= PROP_WAIT;
+
+ for (i=0; cf->cf_locnames[i]; i++) {
+ sprintf(buf, "loc-%s", cf->cf_locnames[i]);
+ dev_setprop(d, buf, &cf->cf_loc[i],
+ sizeof(int), flags);
+ }
+}
+
+/*
+ * Remove locator properties from device node.
+ */
+static void
+locrm(struct device *d, struct cfdata *cf)
+{
+ int i;
+ char buf[32];
+
+ for (i=0; cf->cf_locnames[i]; i++) {
+ sprintf(buf, "loc-%s", cf->cf_locnames[i]);
+ dev_delprop(d, buf, 0);
+ }
+}
+
+/*
* Apply the matching function and choose the best. This is used
* a few times and we want to keep the code small.
*/
static void
-mapply(struct matchinfo *m, struct cfdata *cf)
+mapply(struct matchinfo *m, struct cfdata *cf, struct device *child)
{
int pri;
+ void *aux = m->aux;
+ if ((ssize_t)cf->cf_attach->ca_devsize < 0) {
+ /* New-style device driver */
+ if (!child)
+ panic("mapply: no device for new-style driver\n");
+ locset(child, cf);
+ child->dv_private = aux;
+ aux = child;
+ }
if (m->fn != NULL)
+ /* Someday the submatch function should use properties too. */
pri = (*m->fn)(m->parent, cf, m->aux);
else {
if (cf->cf_attach->ca_match == NULL) {
panic("mapply: no match function for '%s' device\n",
cf->cf_driver->cd_name);
}
- pri = (*cf->cf_attach->ca_match)(m->parent, cf, m->aux);
+ pri = (*cf->cf_attach->ca_match)(m->parent, cf, aux);
}
if (pri > m->pri) {
m->match = cf;
m->pri = pri;
}
+ if (child) {
+ locrm(child, cf);
+ child->dv_private = NULL;
+ }
}
/*
@@ -207,7 +260,8 @@
* can be ignored).
*/
struct cfdata *
-config_search(cfmatch_t fn, struct device *parent, void *aux)
+config_search_ad(cfmatch_t fn, struct device *parent, void *aux,
+ struct device *child)
{
struct cfdata *cf;
short *p;
@@ -218,6 +272,7 @@
m.aux = aux;
m.match = NULL;
m.pri = 0;
+
for (cf = cfdata; cf->cf_driver; cf++) {
/*
* Skip cf if no longer eligible, otherwise scan through
@@ -227,7 +282,7 @@
continue;
for (p = cf->cf_parents; *p >= 0; p++)
if (parent->dv_cfdata == &cfdata[*p])
- mapply(&m, cf);
+ mapply(&m, cf, child);
}
return (m.match);
}
@@ -237,7 +292,8 @@
* This is much like config_search, but there is no parent.
*/
struct cfdata *
-config_rootsearch(cfmatch_t fn, const char *rootname, void *aux)
+config_rootsearch_ad(cfmatch_t fn, const char *rootname, void *aux,
+ struct device *root)
{
struct cfdata *cf;
short *p;
@@ -256,7 +312,7 @@
for (p = cfroots; *p >= 0; p++) {
cf = &cfdata[*p];
if (strcmp(cf->cf_driver->cd_name, rootname) == 0)
- mapply(&m, cf);
+ mapply(&m, cf, root);
}
return (m.match);
}
@@ -272,13 +328,18 @@
* not configured, call the given `print' function and return 0.
*/
struct device *
-config_found_sm(struct device *parent, void *aux, cfprint_t print,
- cfmatch_t submatch)
+config_found_sad(struct device *parent, void *aux, cfprint_t print,
+ cfmatch_t submatch, struct device *d)
{
struct cfdata *cf;
- if ((cf = config_search(submatch, parent, aux)) != NULL)
- return (config_attach(parent, cf, aux, print));
+ /*
+ * Creating the dummy device here is really optional. But
+ * it allows old-style bus drivers to attach new-style children.
+ */
+ if (!d) d = dev_create(ROOT, 0, cold ? M_NOWAIT : M_WAITOK);
+ if ((cf = config_search_ad(submatch, parent, aux, d)) != NULL)
+ return (config_attach_ad(parent, cf, aux, print, d));
if (print)
printf("%s", msgs[(*print)(aux, parent->dv_xname)]);
return (NULL);
@@ -291,9 +352,11 @@
config_rootfound(const char *rootname, void *aux)
{
struct cfdata *cf;
+ struct device *root;
- if ((cf = config_rootsearch((cfmatch_t)NULL, rootname, aux)) != NULL)
- return (config_attach(ROOT, cf, aux, (cfprint_t)NULL));
+ root = dev_create(ROOT, 0, cold ? M_NOWAIT : M_WAITOK);
+ if ((cf = config_rootsearch_ad((cfmatch_t)NULL, rootname, aux, root)) != NULL)
+ return (config_attach_ad(ROOT, cf, aux, (cfprint_t)NULL, root));
printf("root device %s not configured\n", rootname);
return (NULL);
}
@@ -313,23 +376,51 @@
}
/*
+ * Allocate an empty device node. If `parent' is provided then the
+ * new node is attached to the tree under `parent'. If `size'
+ * is provided, then the new device node is allocated to be that
+ * size.
+ */
+static struct device *
+dev_create(struct device *parent, ssize_t size, int wait) {
+ struct device * dev;
+
+ /* get memory for all device vars */
+ if (size == 0)
+ size = sizeof(struct device);
+ dev = (struct device *)malloc(size, M_DEVBUF, wait);
+ if (!dev)
+ panic("dev_create: memory allocation for device failed");
+ memset(dev, 0, size);
+ TAILQ_INSERT_TAIL(&alldevs, dev, dv_list); /* link up */
+ dev->dv_class = DV_EMPTY;
+ if (parent)
+ dev->dv_parent = parent;
+ SLIST_INIT(&dev->dv_props);
+ return (dev);
+}
+
+/*
* Attach a found device. Allocates memory for device variables.
*/
struct device *
-config_attach(struct device *parent, struct cfdata *cf, void *aux,
- cfprint_t print)
+config_attach_ad(struct device *parent, struct cfdata *cf, void *aux,
+ cfprint_t print, struct device *propdev)
{
struct device *dev;
struct cfdriver *cd;
struct cfattach *ca;
size_t lname, lunit;
+ ssize_t softsize;
const char *xunit;
int myunit;
char num[10];
cd = cf->cf_driver;
ca = cf->cf_attach;
- if (ca->ca_devsize < sizeof(struct device))
+ softsize = ca->ca_devsize;
+ if (softsize < 0) softsize = -softsize;
+ if (softsize < sizeof(struct device))
panic("config_attach");
#ifndef __BROKEN_CONFIG_UNIT_USAGE
if (cf->cf_fstate == FSTATE_STAR) {
@@ -360,19 +451,46 @@
panic("config_attach: device name too long");
/* get memory for all device vars */
- dev = (struct device *)malloc(ca->ca_devsize, M_DEVBUF,
- cold ? M_NOWAIT : M_WAITOK);
- if (!dev)
- panic("config_attach: memory allocation for device softc failed");
- memset(dev, 0, ca->ca_devsize);
- TAILQ_INSERT_TAIL(&alldevs, dev, dv_list); /* link up */
+ if (softsize < 0) {
+ /* New-style device */
+ if (propdev) {
+ /* Great, we can use the propdev. */
+ dev = propdev;
+ } else {
+ dev = dev_create(parent, 0, cold ? M_NOWAIT : M_WAITOK);
+ if (!dev)
+ panic("config_attach: memory allocation for device failed");
+ }
+ dev->dv_private = malloc(softsize, M_DEVBUF,
+ cold ? M_NOWAIT : M_WAITOK);
+ if (!dev->dv_private)
+ panic("config_attach: memory allocation for device softc failed");
+ dev->dv_flags |= DVF_SOFTC;
+ } else {
+ dev = (struct device *)malloc(softsize, M_DEVBUF,
+ cold ? M_NOWAIT : M_WAITOK);
+ if (!dev)
+ panic("config_attach: memory allocation for device softc failed");
+ memset(dev, 0, softsize);
+ TAILQ_INSERT_TAIL(&alldevs, dev, dv_list); /* link up */
+ SLIST_INIT(&dev->dv_props);
+ dev->dv_private = dev; /* Point private to ourself... */
+ }
dev->dv_class = cd->cd_class;
dev->dv_cfdata = cf;
dev->dv_unit = myunit;
memcpy(dev->dv_xname, cd->cd_name, lname);
memcpy(dev->dv_xname + lname, xunit, lunit);
dev->dv_parent = parent;
- dev->dv_flags = DVF_ACTIVE; /* always initially active */
+ dev->dv_flags |= DVF_ACTIVE; /* always initially active */
+ if (propdev && propdev != dev) {
+ /* Inherit (steal) properties from dummy device */
+ SLIST_FIRST(&dev->dv_props) =
+ SLIST_FIRST(&propdev->dv_props);
+ SLIST_INIT(&propdev->dv_props);
+ /* destroy propdev */
+ config_detach(propdev, 0);
+ }
if (parent == ROOT)
printf("%s (root)", dev->dv_xname);
@@ -457,6 +575,20 @@
#endif
int rv = 0, i;
+ if (dev->dv_class == DV_EMPTY) {
+ /*
+ * The device is a dummy that never fully attached.
+ * Simply remove it from the global list and free it.
+ */
+ TAILQ_REMOVE(&alldevs, dev, dv_list);
+ zap_devprops(dev);
+ if (dev->dv_flags & DVF_SOFTC)
+ /* Separately allocated softc */
+ free(dev->dv_private, M_DEVBUF);
+ free(dev, M_DEVBUF);
+ return (0);
+ }
+
cf = dev->dv_cfdata;
#ifdef DIAGNOSTIC
if (cf->cf_fstate != FSTATE_FOUND && cf->cf_fstate != FSTATE_STAR)
@@ -464,7 +596,7 @@
#endif
ca = cf->cf_attach;
cd = cf->cf_driver;
-
+
/*
* Ensure the device is deactivated. If the device doesn't
* have an activation entry point, we allow DVF_ACTIVE to
@@ -473,7 +605,7 @@
*/
if (ca->ca_activate != NULL)
rv = config_deactivate(dev);
-
+
/*
* Try to detach the device. If that's not possible, then
* we either panic() (for the forced but failed case), or
@@ -490,13 +622,13 @@
return (rv);
else
panic("config_detach: forced detach of %s failed (%d)",
- dev->dv_xname, rv);
+ dev->dv_xname, rv);
}
-
+
/*
* The device has now been successfully detached.
*/
-
+
#ifdef DIAGNOSTIC
/*
* Sanity: If you're successfully detached, you should have no
@@ -505,15 +637,15 @@
* the list.)
*/
for (d = TAILQ_NEXT(dev, dv_list); d != NULL;
- d = TAILQ_NEXT(d, dv_list)) {
+ d = TAILQ_NEXT(d, dv_list)) {
if (d->dv_parent == dev) {
printf("config_detach: detached device %s"
- " has children %s\n", dev->dv_xname, d->dv_xname);
+ " has children %s\n", dev->dv_xname, d->dv_xname);
panic("config_detach");
}
}
#endif
-
+
/*
* Mark cfdata to show that the unit can be reused, if possible.
*/
@@ -544,9 +676,15 @@
/*
* Remove from cfdriver's array, tell the world, and free softc.
*/
- cd->cd_devs[dev->dv_unit] = NULL;
- if ((flags & DETACH_QUIET) == 0)
- printf("%s detached\n", dev->dv_xname);
+ if (cd) {
+ cd->cd_devs[dev->dv_unit] = NULL;
+ if ((flags & DETACH_QUIET) == 0)
+ printf("%s detached\n", dev->dv_xname);
+ }
+ zap_devprops(dev);
+ if (dev->dv_flags & DVF_SOFTC)
+ /* Separately allocated softc */
+ free(dev->dv_private, M_DEVBUF);
free(dev, M_DEVBUF);
/*
@@ -762,4 +900,186 @@
{
TAILQ_REMOVE(&allevents, ev, ev_list);
+}
+
+/*
+ * Device property management routines.
+ */
+#ifdef DEBUG
+int devprop_debug = 1;
+#endif
+
+/*
+ * Create a dummy device you can attach properties to.
+ */
+struct device *
+dev_config_create(struct device *parent, int flags)
+{
+ struct device *dev;
+
+ dev = dev_create(parent, 0, (flags & PROP_WAIT) ? M_WAITOK : M_NOWAIT);
+ return (dev);
+}
+
+int
+dev_setprop(struct device *dev, const char *name, void *val,
+ size_t len, int flags)
+{
+ struct devprop *prop;
+ size_t blen;
+ size_t nlen;
+
+#ifdef DEBUG
+ if (devprop_debug)
+ printf("dev_setprop(%p, %s, %p %s, %ld, %x)\n",
+ dev, name, val, (flags & PROP_STRING) ?
+ (char *)val : "", len, flags);
+#endif
+ /* First clear out any duplicates.... */
+ dev_delprop(dev, name, 0);
+
+ /* Calculate how large a buffer we need. */
+ nlen = strlen(name);
+ blen = sizeof(struct devprop) + nlen;
+ if ((flags & PROP_CONST) == 0)
+ /* Allocate storage for prop. */
+ blen += len;
+
+ prop = (struct devprop *)malloc(blen, M_DEVBUF,
+ (flags & PROP_WAIT)? M_WAITOK : M_NOWAIT);
+ if (!prop) {
+#ifdef DEBUG
+ if (devprop_debug) {
+ printf("dev_setprop failed: no memory for %ld\n",
+ blen);
+ }
+#endif
+ if (flags == M_NOWAIT)
+ return EAGAIN;
+ return ENOMEM;
+ }
+
+ /* Set up the devprop structure. */
+ if (flags & PROP_STRING)
+ prop->dp_len = -len;
+ else
+ prop->dp_len = len;
+ strcpy((char *)&prop->dp_name[0], name);
+ if (flags & PROP_CONST) {
+ /* Use existing storage */
+ prop->dp_val = val;
+ } else {
+ /* Copy data */
+ prop->dp_val = (void *)&prop->dp_name[nlen+1];
+ bcopy(val, (caddr_t)prop->dp_val, len);
+ }
+
+ SLIST_INSERT_HEAD(&dev->dv_props, prop, dp_list);
+
+ return 0;
+}
+
+size_t
+dev_getprop(struct device *dev, const char *name, void *val,
+ size_t len, int flags)
+{
+ struct devprop *prop;
+ ssize_t plen;
+
+#ifdef DEBUG
+ if (devprop_debug) {
+ printf("dev_getprop(%p, %s, %p, %ld, %x)\n",
+ dev, name, val, len, flags);
+ if (SLIST_EMPTY(&dev->dv_props))
+ printf("devlist %p empty\n", dev);
+ }
+#endif
+ SLIST_FOREACH(prop, &dev->dv_props, dp_list) {
+#ifdef DEBUG
+ if (devprop_debug)
+ printf("checking %s %s ", prop->dp_name,
+ (ssize_t)prop->dp_len < 0 ? (char *)prop->dp_val : "");
+#endif
+ if (strcmp(name, prop->dp_name) == 0) {
+ /* Found */
+#ifdef DEBUG
+ if (devprop_debug)
+ printf("found %s %p %s len %ld at %p\n",
+ prop->dp_name,
+ prop->dp_val,
+ (ssize_t)prop->dp_len < 0 ?
+ (char *)prop->dp_val : "",
+ prop->dp_len,
+ prop);
+#endif
+ plen = prop->dp_len;
+
+ /* Fix len of STRING props. */
+ if (plen < 0) plen = -plen;
+
+ /* Copy only as much as we can. */
+ len = min(len, plen);
+ bcopy(prop->dp_val, val, len);
+ return (plen);
+ }
+ }
+ /* Not found -- try md_getprop */
+#if defined (get_mdprop)
+ {
+ ssize_t res = dev_mdgetprop(dev->dv_parent, name, val
+ len, flags);
+ if (res != -1) return (res);
+ }
+#endif
+ /* Not found -- try parents? */
+ if (flags & PROP_SEARCH) {
+ if (dev->dv_parent)
+ return (dev_getprop(dev->dv_parent, name,
+ val, len, flags));
+ }
+#ifdef DEBUG
+ if (devprop_debug) {
+ printf("%s no found\n", name);
+ }
+#endif
+ return (-1);
+}
+
+int
+dev_delprop(struct device *dev, const char *name, int flags)
+{
+ struct devprop *prop;
+
+#ifdef DEBUG_N
+ if (devprop_debug)
+ printf("dev_delprop(%p, %s, %x)\n",
+ dev, name, flags);
+#endif
+ /* If no name is specified, zap all properties. */
+ if (!name) {
+ zap_devprops(dev);
+ return (0);
+ }
+ SLIST_FOREACH(prop, &dev->dv_props, dp_list) {
+ if (strcmp(name, prop->dp_name) == 0) {
+ /* Found */
+ SLIST_REMOVE(&dev->dv_props, prop, devprop, dp_list);
+ free(prop, M_DEVBUF);
+ return (0);
+ }
+ }
+ return (0);
+}
+
+/* Delete all device properties 'cause we're detaching the device. */
+void
+zap_devprops(struct device *dev)
+{
+ struct devprop *prop;
+
+ while (!SLIST_EMPTY(&dev->dv_props)) {
+ prop = SLIST_FIRST(&dev->dv_props);
+ SLIST_REMOVE_HEAD(&dev->dv_props, dp_list);
+ free(prop, M_DEVBUF);
+ }
}