Subject: Another round of Device Properties.
To: None <tech-kern@netbsd.org>
From: None <eeh@netbsd.org>
List: tech-kern
Date: 07/06/2001 01:45:29
Device Properties redesigned to use Generic Properties.
Eduardo
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.0 Public Data Structures
The device structure will be rearranged to look like this:
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 */
SLIST_HEAD(devpropslist, devprop) dv_props;
void *dv_private;
};
2.1 Flags
PROP_WAIT -- Specify that the call can sleep.
PROP_SEARCH -- Search for properties in parent nodes.
PROP_STRING -- Property is a string.
PROP_CONST -- Property is constant, do not
allocate storage for the data.
2.2 Macros
DEV_PRIVATE(dev) -- Access the private softc data of dev.
DEV_CFA_DECL(name, size, match, attach) -- Declare cfattach structure
DEV_PROTECT(dev) -- Make properties on the device immutable.
DEV_UNPROTECT(dev) -- Remove immutability from a device.
2.3 Transitional Functions
struct device *config_found_sad(struct device *dev, void *aux,
cfprint_t print, cfmatch_t submatch,
struct device *child);
struct cfdata *config_search_ad(cfmatch_t fn, struct device *parent,
void *aux, struct device *child);
struct device *config_attach_ad(struct device *parent, struct cfdata *cf,
void *aux, cfprint_t print, struct device *child);
2.4 New Functions
struct device *dev_config_create(struct device *parent, int flags);
struct device *dev_config_found(struct device *parent, struct device *child,
cfmatch_t submatch, cfprint_t print);
struct cfdata *dev_config_search(struct device *parent, struct device *child,
cfmatch_t fn);
struct device *dev_config_attach(struct device *parent, struct device *child,
struct cfdata *cf, cfprint_t print);
int dev_setprop(struct device *dev, const char *name, void *val,
size_t len, int flags);
int dev_copyprops(struct device *source, struct device *dest,
int flags);
size_t dev_getprop(struct device *dev, const char *name, void *val,
size_t len, int flags);
int dev_delprop(struct device *dev, const char *name, int flags);
size_t dev_mdgetprop(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 dev_setprop(), retrieved by dev_getprop(), and destroyed by dev_delprop().
In addition, there is a machine dependent hook, dev_mdgetprop() that is called if
attempting to retrieve a property fails. dev_mdgetprop() can then use any
arbitrary method to generate property data if it so desires, or cause a
failure to be reported.
This functionality will be implemented in two phases. The first phase will
add these functions, but retain the existing behavior for compatilbility.
dev_config_create(9) will generate a dummy device node that the parent can use
to attach properties to. config_attach*(9) functions will consume the dummy
device node then create the real device node and migrate properties to it.
The second phase will separate the `struct device' from the device's softc.
dev_config_create(9) will create the true device node, which config_found*(9)
will attach to the device tree. If the device probes successfully,
config_attach(9) will allocate a separate device softc structure and link it
to `struct device'. This change will cause breakage in practically all
existing device drivers, so it will be relegated to some future date.
3.0 Changed Data Structures
Several fields will be added to `struct device' and some will be relocated to
compact the size of the structure on 64-bit platforms.
A singly linked list of properties will hang from the `dv_props' field.
A pointer to the device private data will be contained in `dv_private'.
3.1 Changed Functionality
A new device type `DV_NONE' will be added to signify that a device node is as
yet un-probed.
Drivers that expect properties during probe should:
o Define a softc that does not contain a `struct device *'
o Use CFA_DECL() to declare the softc.
By doing that, the `aux' parameter passed in to the device's probe function
will point to a `struct device *', with all all locators attached as
properties, in addition to any properties the parent driver has attached.
3.2 New Functionality
DEV_PRIVATE(dev)
Retrieves a pointer to the device's private data.
DEV_CFA_DECL(name, size, match, attach)
Declare a `struct cfattach' for a new-style device driver. The `size' is the
size of the softc, and the `match' and `attach' parameters should be pointers
to the match and attach functions for that device. The `name' parameter should
match the name referenced by `ioconf.c'.
DEV_PROTECT(dev)
Calling this marks a device node as immutable and properties cannot be
changed.
DEV_UNPROTECT(dev)
Calling this marks a device node as mutable and allows properties to be
changed.
struct device *dev_config_create(struct device *parent, int flags);
dev_config_create() will trigger the creation of an un-configured device node
and attach it to the tree under `parent'. This allows the parent driver to
attach properties to the device node before calling config_found*() so they
are available to the child device during the probe and attach steps. If
PROP_WAIT is not specified then dev_config_create(9) will not sleep to resolve
resource shortages. Returns a pointer to a `struct device' on success, and
NULL on error.
struct device *config_found_sad(struct device *parent, void *aux, cfprint_t print,
cfmatch_t submatch, struct device *child);
In addition to config_found(9), and config_found_sm(9), config_found_sad(9)
can be used to supply device properties to child devices during probe and
attach. The `child' parameter is passed down to config_search(9) and
config_attach(9).
struct cfdata *config_search_ad(cfmatch_t fn, struct device *parent,
void *aux, struct device *child);
config_search_ad(9) takes an additional parameter `child' which is a pointer
to a device which provides a set of properties for devices to use during
probe.
If the device being probed used DEV_CFA_DECL() to declare it's `cfattach'
structure, a `child' device is created if it does not exist. Locator data are
attached to the `child' as properties. It is then passed to the probe routine
as the `aux' parameter. The child driver should cast the `aux' parameter to a
`struct device *' and use it to query for properties.
If the device did not use DEV_CFA_DECL(), the probe routine is called with the
`aux' passed in by the parent in the `aux' field and the `child' device with
locator properties is not available to the *_probe() routine.
struct device *config_attach_ad(struct device *parent, struct cfdata *cf,
void *aux, cfprint_t print, struct device *child)
If a non-NULL `child' is passed in to config_attach_ad(9), a softc of the size
specified by DEV_CFA_DECL() will be allocated and a pointer to it placed in
the device private field of the `child' device. This structure can be
accessed through `DEV_PRIVATE(dev)'.
If a non-NULL `child' is passed in to config_attach_ad(9), a new device node
is allocated of the appropriate size, all properties attached to it are moved
to the newly created device node for the device being attached.
If a NULL value is passed in as the `child', a device structure is allocated
of the appropriate size and locator data are associated with it. If the
device used DEV_CFA_DECL(), then the softc will be accessible through
`DEV_PRIVATE(dev)'.
struct device *dev_config_found(struct device *dev, struct device *child,
cfprint_t print, cfmatch_t submatch);
dev_config_found(9) is intended as an interface for bus drivers that no longer
need to supply an `aux'. The `child' parameter will initally be a dummy
container but later will be the real child device node.
struct cfdata *dev_config_search(cfmatch_t fn, struct device *parent, void *aux,
struct device *child);
dev_config_search(9) is intended as an interface for bus drivers that no
longer need to supply an `aux'. The `child' parameter will initally be a
dummy container but later will be the real child device node.
struct device *dev_config_attach(struct device *parent, struct device *child,
struct cfdata *cf, cfprint_t print);
dev_config_attach(9) is intended as an interface for bus drivers that no
longer need to supply an `aux'. The `child' parameter will initally be a
dummy container but later will be the real child device node.
int dev_setprop(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'. If PROP_WAIT is not specified then dev_setprop(9) will
not sleep for resource shortage. If PROP_CONST is specified, no storage is
allocated for the value, and when the property is queried it will copy `len'
bytes from the location specified by `val', so that data cannot be freed or
the kernel may panic. If PROP_STRING is specified then the property is marked
as being a NUL terminated ASCII string. Returns 0 on success or an error
value. This will fail without modifying the device node if it is protected.
int dev_copyprops(struct device *source, struct device *dest, int flags);
Copy all properties associated with `source' device to `dest' device
structure. It does not traverse the device tree or make any use of
dev_mdgetprop(). If PROP_WAIT is not specified then dev_copyprops(9) will not
sleep for resource shortage. Returns 0 on success or an error value. The
state of properties on the destination device is undefined if the operation
fails. This will fail without modifying the destination device node if it is
protected.
size_t dev_getprop(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 dev_getprop(9) will call dev_mdgetprop(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 dev_delprop(struct device *dev, const char *name, int flags);
Remove a property from a device node. If a NULL is supplied for the name,
dev_delprop(9) will remove all properties from a device node. It returns 0 on
success or an error value. This will fail without modifying the device node
if it is protected.
size_t dev_mdgetprop(struct device *dev, const char *name, void *val,
size_t len, int flags);
If defined an a machine dependent header file, 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
dev_getprop(9) does. It does not need to traverse parent device nodes.
4 Converting Drivers
There are essentially two types of drivers, bus drivers and device drivers.
Bus drivers are distinguished because they can attach other drivers to
themselves. The conversion process consists of four steps. First a protocol
must be established for the use of properties during probe and attach. Then
the bus driver needs to be converted to use the transitional interfaces that
provides both an `aux' and properties. Next the device drivers attaching to
it need to be converted to handle the new interface to use properties rather
than `aux'. Finally, the bus driver needs to be converted to use the new
interface and only use properties instead of providing an `aux'.
A protocol should be carefully defined to provide what ever information a
driver needs from a bus so it can probe and attach. A detailed discussion of
the issues involved is well beyond the scope of this document, but the
protocol should be able to handle layering of bus drivers.
The bus driver is then changed to manage its child device node explicitly.
The driver calls call `dev_config_create()' to allocate a driver and attach
properties to it. It also creates an `aux'. It then calls
`config_found_sad()' with both the `aux' and the device it created with
`dev_config_create()'.
Child drivers can now be modified to the new interface. The `struct device'
is removed from the softc, and `DEV_CFA_DECL()' is used to declare the
device's `cfattach' structure. The device's `*_probe()' and `*_attach()'
routines are modified to cast the `aux' parameter to a `struct device *', and
`dev_getprop()' is used to retrieve properties from it. After the probe,
`DEV_PRIVATE(dev)' is used to access the softc, rather than directly casting
the `struct device *'.
Once all the child drivers for a particular bus are converted, the bus driver
can be modified not to create an `aux' at all, and the call to
`config_found_sad()' is changed to a call to `dev_config_found()'.
Appendix 1
Providing dev_mdsetprop() and dev_mddelprop() 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.
Appendix 2
The config(8) application will eventually be extended to take typed values for
locators and for properties that can be specified in config files.
Appendix 3
A set of protocols need to be developed for the use of properties for
attaching device and bus nodes. Required global properties should be defined,
as well as bus-specific properties.