Subject: More Device Properties
To: None <tech-kern@netbsd.org>
From: None <eeh@netbsd.org>
List: tech-kern
Date: 02/14/2001 17:51:31
Here's an enhanced version of the Device Properties proposal I 
sent out some time ago.  Some names have been changed to remove
conflicts with existing MD functionality and enhancements have
been made for passing properties during probe and attach.

Eventually I would like to deprecate the current aux vector and
convert all drivers to use device properties.


				Device Properties


1	Summary

I propose adding properties and accessors to device nodes.  There are a number
of cases where drivers need to pass information to other drivers or acquire
information that is not available to them and accessible only through machine
dependent code.  To solve these sorts of problems I propose adding a property
framework to allow arbitrary data to be associated with particular device nodes.

Examples of data that are currently problematical but could easily be passed
as properties are device locators, which require and arbitrary bus-specific
structure to pass betwen the parent bus node and the child device node, and
MAC addresses for ethernet devices, which on some machines are available by a
simple firmware call, but on others require the driver to dig bits out of an
EPROM.

The most useful aspects of device properties occur when the parent driver is
able to add properties which a child device uses during its attach routine.
The current infrastructure makes this impossible since the child device
structure is not available to the parent until after it has finished
attaching.


2	Synopsis

2.1	Changed Functions

   struct device *config_found_smd(struct device *dev, void *aux, 
			cfprint_t print, cfmatch_t submatch, 
			struct device *propdev);
   struct cfdata *config_search(cfmatch_t fn, struct device *parent, 
			void *aux, struct device *propdev);
   struct device *config_attach(struct device *parent, struct cfdata *cf, 
			void *aux, cfprint_t print, struct device *propdev)


2.2	New Functions

   struct device *devprop_create(void);
   void devprop_destroy(struct device *);
   int set_devprop(struct device *dev, const char *name, void *val, 
			size_t len, int flags);
   size_t get_devprop(struct device *dev, const char *name, void *val, 
			size_t len, int flags);
   int del_devprop(struct device *dev, const char *name, int flags);

   size_t get_mdprop(struct device *dev, const char *name, void *val, 
			size_t len, int flags);


3	Description

A property is a tuple that consists of a pointer to a device node (struct
device *), string, and an arbitrary amount of data.  This tuple is established
by set_devprop(), retrieved by get_devprop(), and destroyed by del_devprop().  In
addition, there is a machine dependent hook, get_mdprop()[1] that is called if
attempting to retrieve a property fails.  get_mdprop() can then use any
arbitrary method to generate property data if it so desires, or cause a failure
to be reported.


3.1	New Functionality

int set_devprop(struct device *dev, const char *name, void *val, size_t len, int flags);

Create a property `name' and attach it to `dev' with a `len' byte value 
copied from location `val'.  Returns 0 on success or an error value.


size_t get_devprop(struct device *dev, const char *name, void *val, 
			size_t len, int flags);

Retrieve a property called `name' associated with `dev'.  If the property is
not found it will call get_mdprop(9).  If the flag PROP_INHERIT is set, and
there is no property with that name associated with this device node, it will
try to retrieve the property from any parent device nodes.  Returns -1 if
the property cannot be found, otherwise it returns the length of the value
data and if `val' is not NULL it copies up to `len' bytes of the property data
to the location pointed to by `val'.


int del_devprop(struct device *dev, const char *name, int flags);

Remove a property from a device node.  If the flag PROP_INHERIT is set it will
look for that property in any parent device nodes.  If the flag PROP_ALL is set
it will remove the property from that device node and all parent device nodes.
It returns 0 on success or an error value.


extern void * get_mdprop(struct device *dev, const char *name, void *val, 
			size_t len, int flags);

This function is called when ever an attempt is made to retrieve a property
from a device node but the property is not found.  It allows machine dependent
code to look up properties from other locations.  It should be implemented to
behave the same way as get_devprop(9) does.

struct device *devprop_create(void);

Properties require a device structure to contain them, so devprop_create(9) is
used to create a temporary device to which properties can be attached.  It can
be passed in to config_found_smd(9) to make these properties available to the
child device as it probes and attaches.

void devprop_destroy(struct device *);

This is used to destroy the temporary device once it is no longer required.

 
3.2	Changed Functionality


struct device *config_found_smd(struct device *dev, void *aux, 	cfprint_t print, 
			cfmatch_t submatch, struct device *propdev);

In addition to config_found(9), and config_found_sm(9), config_found_smd(9)
can be used to supply device properties to child devices during probe and 
attach.  The `propdev' parameter is passed down to config_search(9) and
config_attach(9).

struct cfdata *config_search(cfmatch_t fn, struct device *parent, void *aux, 
			struct device *propdev);

config_search(9) now takes an additional parameter `propdev' which is a
pointer to a device which provides a set of properties for devices to use
during probe.  If `propdev' is not NULL, locator data are attached to it as
properties, it is passed to the probe routine as the `aux' parameter and
the value passed in as `aux' is ignored.  The probe routine needs to know to
cast `aux' to a pointer to a device and extract properties rather than use it
as a pointer to configuration data.

struct device *config_attach(struct device *parent, struct cfdata *cf, 
			void *aux, cfprint_t print, struct device *propdev)

If a non-NULL `propdev' is passed in to config_attach(9), all properties
attached to it are moved to the newly created device node for the device
being attached.



[1] Providing set_mdprop() and del_mdprop() would be possible but make the
framework much more complicated.  If permanent storage of properties is
desired it should use some machine dependent method since non-volatile
storage is not necessarily available on all architectures.


Here's a patch which provides most of the functionality, with the exception
of the changes to config_search(9).


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/02/14 17:45:45
@@ -104,6 +104,27 @@
 	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_INHERIT	2
+#define PROP_ALL	4
+
+struct devprop {
+	SLIST_ENTRY(devprop) dp_list;
+	const char *dp_name;	/* Points into end of dp_val */
+	size_t dp_len;
+	long dp_val[1];		/* This contains the value data and name string */
+};
+
 struct device {
 	enum	devclass dv_class;	/* this device's classification */
 	TAILQ_ENTRY(device) dv_list;	/* entry on list of all devices */
@@ -112,6 +133,7 @@
 	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;
 };
 
 /* dv_flags */
@@ -241,12 +263,14 @@
 extern __volatile int config_pending; 	/* semaphore for mountroot */
 
 void configure(void);
-struct cfdata *config_search(cfmatch_t, struct device *, void *);
+struct cfdata *config_search(cfmatch_t, struct device *, void * 
+/* Not yet: , struct device * */);
 struct cfdata *config_rootsearch(cfmatch_t, const char *, void *);
-struct device *config_found_sm(struct device *, void *, cfprint_t, cfmatch_t);
+struct device *config_found_smd(struct device *, void *, cfprint_t, cfmatch_t, 
+				struct device *);
 struct device *config_rootfound(const char *, void *);
 struct device *config_attach(struct device *, struct cfdata *, void *,
-    cfprint_t);
+    cfprint_t, struct device *);
 int config_detach(struct device *, int);
 int config_activate(struct device *);
 int config_deactivate(struct device *);
@@ -265,11 +289,19 @@
 void	evcnt_detach(struct evcnt *);
 
 /* compatibility definitions */
-#define config_found(d, a, p)	config_found_sm((d), (a), (p), NULL)
+#define config_found(d, a, p)		config_found_sm((d), (a), (p), NULL)
+#define config_found_sm(d, a, p, s)	config_found_smd((d), (a), (p), (s), NULL)
 
 /* convenience definitions */
 #define	device_lookup(cfd, unit)					\
 	(((unit) < (cfd)->cd_ndevs) ? (cfd)->cd_devs[(unit)] : NULL)
+
+/* Device properties */
+struct device *propdev_create(void);
+void propdev_destroy(struct device *);
+int set_devprop(struct device *, const char *, void *, size_t, int);
+size_t get_devprop(struct device *, const char *, void *, size_t, int);
+int del_devprop(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/02/14 17:45:45
@@ -97,6 +97,8 @@
  * Autoconfiguration subroutines.
  */
 
+static void zap_devprops(struct device *);
+
 /*
  * ioconf.c exports exactly two names: cfdata and cfroots.  All system
  * devices and drivers are found via these tables.
@@ -207,7 +209,8 @@
  * can be ignored).
  */
 struct cfdata *
-config_search(cfmatch_t fn, struct device *parent, void *aux)
+config_search(cfmatch_t fn, struct device *parent, void *aux 
+	      /* Not yet:,  struct device *pdev */)
 {
 	struct cfdata *cf;
 	short *p;
@@ -272,13 +275,13 @@
  * 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_smd(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));
+		return (config_attach(parent, cf, aux, print, d));
 	if (print)
 		printf("%s", msgs[(*print)(aux, parent->dv_xname)]);
 	return (NULL);
@@ -293,7 +296,7 @@
 	struct cfdata *cf;
 
 	if ((cf = config_rootsearch((cfmatch_t)NULL, rootname, aux)) != NULL)
-		return (config_attach(ROOT, cf, aux, (cfprint_t)NULL));
+		return (config_attach(ROOT, cf, aux, (cfprint_t)NULL, NULL));
 	printf("root device %s not configured\n", rootname);
 	return (NULL);
 }
@@ -317,7 +320,7 @@
  */
 struct device *
 config_attach(struct device *parent, struct cfdata *cf, void *aux,
-	cfprint_t print)
+	cfprint_t print, struct device *propdev)
 {
 	struct device *dev;
 	struct cfdriver *cd;
@@ -373,6 +376,14 @@
 	memcpy(dev->dv_xname + lname, xunit, lunit);
 	dev->dv_parent = parent;
 	dev->dv_flags = DVF_ACTIVE;	/* always initially active */
+	SLIST_INIT(&dev->dv_props);
+	if (propdev) {
+		/* Inherit (steal) properties from dummy device */
+		SLIST_FIRST(&dev->dv_props) = 
+			SLIST_FIRST(&propdev->dv_props);
+printf("Moving properties from %p to %p\n", propdev, dev);
+		SLIST_INIT(&propdev->dv_props);
+	}
 
 	if (parent == ROOT)
 		printf("%s (root)", dev->dv_xname);
@@ -547,6 +558,7 @@
 	cd->cd_devs[dev->dv_unit] = NULL;
 	if ((flags & DETACH_QUIET) == 0)
 		printf("%s detached\n", dev->dv_xname);
+	zap_devprops(dev);
 	free(dev, M_DEVBUF);
 
 	/*
@@ -762,4 +774,176 @@
 {
 
 	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 *
+propdev_create()
+{
+	struct device *dev;
+
+	dev = (struct device *)malloc(sizeof(struct device), M_DEVBUF,
+	    cold ? M_NOWAIT : M_WAITOK);
+	if (dev) {
+		memset(dev, 0, sizeof(struct device));
+		SLIST_INIT(&dev->dv_props);
+	}
+#ifdef DEBUG
+	if (devprop_debug)
+		printf("creating propdev %p\n", dev);
+#endif
+
+	return (dev);
+}
+
+void
+propdev_destroy(struct device *d)
+{
+	zap_devprops(d);
+	free(d, M_DEVBUF);
+}
+
+int 
+set_devprop(struct device *dev, const char *name, void *val, 
+			size_t len, int flags)
+{
+	struct devprop *prop;
+	size_t blen;
+	
+#ifdef DEBUG
+	if (devprop_debug)
+		printf("set_devprop(%p, %s, %p, %ld, %x)\n", 
+		       dev, name, val, len, flags);
+#endif
+	/* First clear out any duplicates.... */
+	del_devprop(dev, name, 0);
+
+	/* Calculate how large a buffer we need. */
+	blen = sizeof(struct devprop) - sizeof(long) 
+		+ strlen(name) + len;
+	blen = roundup(blen, sizeof(long));
+	prop = (struct devprop *)malloc(blen, M_DEVBUF, 
+		(flags & PROP_WAIT)? M_WAITOK : M_NOWAIT);
+	if (!prop) {
+		if (flags == M_NOWAIT)
+			return EAGAIN;
+		return ENOMEM;
+	}
+
+	/* Set up the devprop structure. */
+	prop->dp_len = len;
+	prop->dp_name = ((caddr_t)&prop->dp_val[0]);
+	bcopy(val, (caddr_t)prop->dp_name, len);
+	prop->dp_name += len;
+#ifdef DEBUG
+printf("copying from %p to %p in prop at %p\n",
+       name, (char *)prop->dp_name, prop);
+#endif
+	strcpy((char *)prop->dp_name, name);
+
+	SLIST_INSERT_HEAD(&dev->dv_props, prop, dp_list);
+
+	return 0;
+}
+
+size_t 
+get_devprop(struct device *dev, const char *name, void *val, 
+			size_t len, int flags)
+{
+	struct devprop *prop;
+
+#ifdef DEBUG
+	if (devprop_debug) {
+		printf("get_devprop(%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\n", prop->dp_name);
+#endif
+		if (strcmp(name, prop->dp_name) == 0) {
+			/* Found */
+#ifdef DEBUG
+			if (devprop_debug)
+				printf("found %s\n", prop->dp_name);
+#endif
+			len = min(len, prop->dp_len);
+			bcopy(&prop->dp_val[0], val, len);
+			return (prop->dp_len);
+		}
+	}
+	/* Not found -- try md_getprop */
+#if defined (get_mdprop)
+	{
+		size_t res = md_getprop(dev->dv_parent, name, val
+					len, flags);
+		if (res != -1) return (res);
+	}
+#endif
+	/* Not found -- try parents? */
+	if (flags & PROP_INHERIT) {
+		if (dev->dv_parent)
+			return (get_devprop(dev->dv_parent, name,
+					    val, len, flags));
+	}
+	return (-1);
+}
+
+int 
+del_devprop(struct device *dev, const char *name, int flags)
+{
+	struct devprop *prop;
+
+#ifdef DEBUG
+	if (devprop_debug)
+		printf("del_devprop(%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);
+			if (flags & PROP_ALL) break;
+			return (0);
+		}
+	}
+	/* Not found -- try parents? */
+	if (flags & (PROP_INHERIT|PROP_ALL)) {
+		if (dev->dv_parent)
+			return (del_devprop(dev->dv_parent, name, flags));
+	}
+	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);
+	}
 }