[RFC/PATCH] Per-device parameter support

From: tj
Date: Sat Oct 23 2004 - 01:14:09 EST


Hello,

I rewrote the document, debugged and splitted devparam patch. I'm
attaching the document in this mail and posting 16 patches
(dp_01 - dp_16) which are against Linus's bk tree as of today.

The first 12 patches are prepatory patches for devparam including
generic vector patch and many moduleparam changes. dp_10 and dp_11
are optional but I think it will be better with those patches. Merged
dp_01 - dp_12 is available at the following URL.

http://home-tj.org/devparam/20041023/dp_prep.diff

dp_13 and dp_14 actually implement devparam, and dp_15 is for the
document. dp_16 modifies via-velocity driver to use devparam as an
example. Merged dp_13 to dp_16 is available at

http://home-tj.org/devparam/20041023/dp_body.diff

Also, separate patches are available at the following URLs.

http://home-tj.org/devparam/20041023/dp_01_vector.diff
http://home-tj.org/devparam/20041023/dp_02_param_array_bug.diff
http://home-tj.org/devparam/20041023/dp_03_module_param_flag.diff
http://home-tj.org/devparam/20041023/dp_04_module_param_ranged.diff
http://home-tj.org/devparam/20041023/dp_05_param_array_val_noconst.diff
http://home-tj.org/devparam/20041023/dp_06_param_array_delims.diff
http://home-tj.org/devparam/20041023/dp_07_param_array_delim_colon.diff
http://home-tj.org/devparam/20041023/dp_08_export_param_next_arg.diff
http://home-tj.org/devparam/20041023/dp_09_module_param_empty_prefix.diff
http://home-tj.org/devparam/20041023/dp_10_module_param_unknown_arg.diff
http://home-tj.org/devparam/20041023/dp_11_module_param_arr.diff
http://home-tj.org/devparam/20041023/dp_12_module_param_arr_apply.diff
http://home-tj.org/devparam/20041023/dp_13_devparam.diff
http://home-tj.org/devparam/20041023/dp_14_devparam_apply.diff
http://home-tj.org/devparam/20041023/dp_15_devparam_doc.diff
http://home-tj.org/devparam/20041023/dp_16_DEVPARAM_via-velocity.diff

Also, I'll put the latest version of the document at

http://home-tj.org/devparam/devparam.txt

I'm willing to pour some work into converting drivers to use devparam
(at least the network drivers), so please tell me what you guys think
about the interface, implementation, documentation or whatever.

Thanks.

--
tejun


========================================================================
Per-Device Parameter

Tejun Heo <tj@xxxxxxxxxxx>

19 October 2004


INTRO
=====

Per-device parameter wasn't supported by Linux driver model
previously. It was usually done using the moduleparam facility. As
the name suggests, moduleparam implements per-module parameters and
drivers used fixed-size array to implement per-device parameters.
This results in unnecessary duplication of codes, variation in usage
and random limits on the number of supported devices or devices which
users can specify parameters for. Devparam integrates per-device
parameter support into the driver model to solve these issues.

Devparam aims to

1. Remove duplicated parameter handling codes from drivers
2. Retain user-visible syntax for converted drivers
3. Remove hard-coded random limits on the number of devices
parameters can be specified for
4. Support per-device device, bus and auxiliary (mainly classes)
parameter sets
5. Support sysfs access to per-device paramters


OVERVIEW
========

Parameters are organized into parameter sets or paramsets. Each
paramset is represented by a user-defined structure (e.g. struct
my_dev_paramset). A paramset definition, or paramset_def, describes
the paramset to the driver model - what it contains, how to parse each
parameter and so on.

A device driver passes paramset_defs it wants to use to the driver
model when registering the driver, and before the driver is attached
to a device, the driver model parses user supplied parameters and
fills the paramsets ans pass them to the device driver.

There are three categories of paramsets.

I. DEV PARAMSET

These are device specific parameters used by the driver. This
category will include most parameters currently used by drivers.
Examples for network drivers would be stuff like the size of tx/rx
descriptor ring and hardware checksum enable/disable option.


II. BUS PARAMSET

These are bus specific parameters. Individual device drivers
wouldn't care or know about this paramset. These paramsets are
defined and used only by bus drivers. When a bus driver wants to
accept some common paramters for all devices living on the bus, the
driver will supply the bus paramset_def and all device drivers for the
bus will accept bus parameters. Examples would be PCI command
register setting, PCIe QoS settings and so on.


III. AUX PARAMSETS

Auxiliary paramsets. A driver can specify any number of auxiliary
paramset_defs. Primary usage would be for class parameters for class
devices. For example, if the input layer wants to accept some common
set of parameters for each input device, it can define a paramset_def
and all input device drivers can use it to accept the common
parameters. Once modified to use the facility, the content of the
paramset_def doesn't matter to specific device drivers, so the input
paramset_def can be modified without changing or even recompiling
individual device drivers.


USING DEVPARAM
==============

Paramset definition is defined using DEFINE_DEVICE_PARAMSET and
DEVICE_PARAM_* macros and passed to the driver-model via the
*_paramset_def fields of struct device_driver or struct bus_type. The
usage is very similar to moduleparam; actually, most of devparam is
built using moduleparam.

- DEFINE_DEVICE_PARAMSET(Name, Type, Paramdefs) macro

Defines a struct device_paramset_def @Name which contains
parameters described by @Paramdefs. Each parameter definition
in @Paramdefs describes how to handle a parameter which is
contained in a @Type variable.

- DEVICE_PARAM_*() macros

Syntax is almost identical to module_param_*() macros defined
in moduleparam.h. There are three differences. The first is
that instead of referring directly to a variable to be set,
the field name inside @Type of enclosing
DEFINE_DEVICE_PARAMSET is used. The second is that there's an
extra argument @Dfl which is a string containing the default
value to use when the user didn't specify the parameter.
The last is the additional argument @Desc which serves the
same purpose as MODULE_PARAM_DESC().


When attaching a device, its paramset structures are allocated and
cleared with zero, and for each defined parameter, set function is
called with user supplied argument if it's available or the default
string. If the default string is also NULL, set function isn't
called). (Actually, all parameters are parsed when the device driver
is initialized and cached inside the device_driver structure, but the
end result is the same as described above.)

Device parameters are passed as comma-separated values via
moduleparam facility (the first value is for the first device which
gets attached to the driver, the second value for the second device
and so on). In parameter strings, '\' escapes the following
character, so by using "\," strings containing commas or
comma-separated arrays can be specified. To ease nested array
specification, ':' is also accepted as nested array separator.

It's best explained with examples. I'll present two examples - one
simple and the other more complete. If you're a driver developer just
wanting to receive per-device parameters for your driver, reading the
first example should suffice.


A SIMPLE ONE
============

I'll use an imaginary pci device driver for this example. Let's
say it wants to accept the following parameters.

- One integer parameter named integer_knob which should be in the
range [0, 255] and defaults to 16 when none is specified.
- One string parameter named string which can be as long as 63
characters and defaults to "mung mung".
- A boolean parameter named enable_feature0 which, when 1, sets
MY_FEATURE0 in flags and defaults to 0.

First, a paramset structure needs to be defined.

| struct my_dev_paramset {
| int integer_knob;
| char string[64];
| unsigned flags;
| };

Then, the corresponding my_dev_paramset_def.

| static DEFINE_DEVICE_PARAMSET(my_dev_paramset_def, struct my_dev_paramset,
| DEVICE_PARAM_RANGED(integer_knob, int, 0, 255, "16", 0444,
| "integer_knob does something, [0,255] default 16")
| DEVICE_PARAM_STRING(string, "mung mung", 0444,
| "A string is a string")
| DEVICE_PARAM_FLAG(enable_feature0, flags, MY_FEATURE0, "0", 0444,
| "Enables feature0. Whatever that is.")
| );

We're almost done already. The only thing left is to register the
paramset_def.

| static struct pci_driver my_drv = {
| .name = "my_drv",
| .probe = my_probe,
| .driver.dev_paramset_def = &my_dev_paramset,
| ...
| };
|
| static int __init my_init(void)
| {
| ...
| return pci_register_driver(&my_drv);
| }

And we can use the paramset however we want to.

| static int __devinit my_probe(struct pci_dev *pdev,
| const struct pci_device_id *ent)
| {
| struct my_drv_paramset *ps = pdev->dev.params.dev;
| ....
| }

Now, let's see how a user can specify those device parameters. If
the driver is compiled into the kernel, parameters can be specified in
the boot options.

> my_drv.integer_knob=32,32,64 my_drv.string="bunga,asdf"

The results would be...

integer_knob string flags
----------------------------------------------------------
1st dev: 32 "bunga" 0
2st dev: 32 "asdf" 0
3st dev: 64 "mung mung" 0
4th-Nth: 16 "mung mung" 0

If the module is compiled as a module, parameters can be specified
like the following.

> modprobe my_drv integer_knob=8,8,32 enable_feature0=1,1

integer_knob string flags
----------------------------------------------------------
1st dev: 8 "mung mung" MY_FEATURE0
2st dev: 8 "mung mung" MY_FEATURE0
3st dev: 32 "mung mung" 0
4th-Nth: 16 "mung mung" 0

Note that when a device attaches, the first empty paramset slot is
used. For example, let's say there's device A, B, C and D all of
which are controlled by my_drv, and three paramsets ps0, ps1 and ps2
of which ps2 is the default paramset.

Event Paramset
-----------------------
A attaches ps0
B attaches ps1
C attaches ps2
B detaches
D attaches ps1
B attaches ps2

However, as each device gets its own copy of the paramsets, it can
modify the paramset as needed. Modifying its paramset won't affect
other devices attaching later.


A FULL EXAMPLE
==============

I'll use a pseudo bus, class and driver respectively named dp_bus,
dp_class and dp_drv for explanation. A dp_drv lives on dp_bus and a
dp_drv device implements a class device belonging to dp_class. All of
dp_bus, dp_class and dp_drv accept their own sets of parameters.

Let's look at dp_bus first.

I. DP_BUS

dp_bus defines struct dp_driver (just like struct pci_drv) and
registration unregistration functions (just like
pci_[un]register_driver() functions). So, it defines the following
interface in dp_bus.h.

| struct dp_driver {
| int (*probe)(struct device *dev);
| void (*remove)(struct device *dev);
| struct device_driver driver;
| };
|
| int __dp_register_driver(struct dp_driver *drv, struct module *mod);
| #define dp_register_driver(drv) __dp_register_driver(drv, THIS_MODULE)
| void dp_unregister_driver(struct dp_driver *drv);

Please note that dp_register_driver() is defined as a macro which
passes THIS_MODULE to __dp_register_driver(). This is necessary as
the driver model needs to associate a driver to its module. Other than
that, everything should be apparent.

dp_bus wants to accept the following parameters.

- Three integer parameters named bus_a, bus_b and bus_c.
- An array of intergers which can have 6 elements at maximum.

So, in dp_bus.c, the following structure is defined.

| struct dp_bus_paramset {
| int bus_a, bus_b, bus_c;
| int bus_ar[6], bus_ar_cnt;
| };

Also corresponding dp_bus_paramset_def.

| static DEFINE_DEVICE_PARAMSET(dp_bus_paramset_def, struct dp_bus_paramset,
| DEVICE_PARAM(bus_a, int, "0", 0444,
| "mung mung")
| DEVICE_PARAM(bus_b, int, "1", 0444,
| "bungga bungga")
| DEVICE_PARAM(bus_c, int, "2", 0444,
| "yaong blah blah blah blah")
| DEVICE_PARAM_ARRAY(bus_ar, int, bus_ar_cnt, "1,2,3", 0444,
| "whatever, dude")
| );

So, needed data structures are in place now. All that's left to do
is to use the appropriate hooks. First, we need to set
bus_paramset_def field of each dp driver registered. We can do it in
the __dp_register_driver() function.

| int __dp_register_driver(struct dp_driver *drv, struct module *mod)
| {
| drv->driver.bus = &dp_bus_type;
| drv->driver.probe = dp_probe;
| drv->driver.remove = dp_remove;
| printk("dp_bus: registering driver \"%s\"\n", drv->driver.name);
| return __driver_register(&drv->driver, mod);
| }

Note that probe and remove functions are hooked to dp_probe and
dp_remove. So, when a matching device is found, our dp_probe function
will be called, which looks like the following.

| static int dp_probe(struct device *dev)
| {
| struct dp_driver *drv;
| struct dp_bus_paramset *ps;
|
| drv = container_of(dev->driver, struct dp_driver, driver);
| ps = dev->params.bus;
|
| /* Whatever the bus driver wanna do can come here. */
|
| return drv->probe(dev);
| }

The driver model parses user specified parameter or the default
parameter supplied with paramset_def and set dev->params.bus field to
the result. The bus driver is free to read and modify the structure
as needed. As dp_bus is a pseudo bus, it doesn't really have anything
to do, but a real driver could tweak some bus features (e.g. PCIe QoS
setting) for the device there.

Above are all the interesting parts of dp_bus implementation. Now,
let's look at dp_class.


II. DP_CLASS

dp_class is a dummy class which doesn't do anything but accepting
some parameters and getting devices registered to it. Consequently,
it has a very simple interface.

| extern struct class dp_class;
| extern struct device_paramset_def dp_class_paramset_def;
| struct dp_class_paramset;
| extern int dp_class_device_register(struct class_device *dev,
| struct dp_class_paramset *params);

Note that dp_class_paramset_def is exported. This wasn't necessary
for bus parameters but as device-class association is only known by
the driver of a device, it must be able to access the paramset_defs of
the classes it's going to register a device to. Any driver which
wants to register with dp_class will pass dp_class_paramset_def to the
driver-model using drv.aux_paramset_defs field and pass the resulting
paramset to dp_class_device_register().

The implementation of dp_class isn't very intriguing.
dp_class_paramset_def is defined just like dp_bus_paramset_def. The
differences are that there's no `static' in front of
DEFINE_DEVICE_PARAMSET() and dp_bus_paramset_def needs to be
initialized and released explicitly as there's no registration process
which does them automatically. Also, dp_bus_paramset_def needs to be
EXPORT_SYMBOL()'d as it's gonna be referenced by drivers living in
other modules.

| static int __init dp_class_init(void)
| {
| int ret;
| if ((ret = devparam_setdef_init(&dp_class_paramset_def)) < 0)
| return ret;
| if ((ret = class_register(&dp_class)) < 0) {
| devparam_setdef_release(&dp_class_paramset_def);
| return ret;
| }
| return 0;
| }
|
| static void __exit dp_class_cleanup(void)
| {
| class_unregister(&dp_class);
| devparam_setdef_release(&dp_class_paramset_def);
| }


III. DP_DRV

Okay, here's dp_drv, where everything comes together. dp_drv defines
its own dp_drv_paramset_def just like dp_bus. dp_drv also defines an
array of device_paramset_def's for class paramset.

| static struct device_paramset_def *dp_drv_aux_paramset_defs[] = {
| &dp_class_paramset_def,
| NULL
| };

And they come together by defining the dp_driver structure and
registering it.

| static struct dp_driver my_drv = {
| .driver.name = "bungga",
| .probe = dp_drv_probe,
| .remove = dp_drv_remove,
| .driver.dev_paramset_def = &dp_drv_paramset_def,
| .driver.aux_paramset_defs = dp_drv_aux_paramset_defs
| };
|
| static int __init dp_drv_init(void)
| {
| return dp_register_driver(&my_drv);
| }

Paramsets are accessed and passed to dp_class like the following.

| static int dp_drv_probe(struct device *dev)
| {
| struct dp_drv_paramset *ps = dev->params.dev;
| ...
| ret = dp_class_device_register(priv->class, dev->params.aux[0]);
| ...
| }

Now everyone has its paramset and should be happy and hazy.

Complete source code for dp_bus, dp_class, dp_drv and dp_dev (dp_dev
is for creating pseudo devices which attaches to dp_drv) is available
at the following URL.

http://home-tj.org/devparam/dptest.tar.gz

Happy hacking.
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/