2004-10-23 04:54:43

by Tejun Heo

[permalink] [raw]
Subject: [RFC/PATCH] Per-device parameter support (15/16)

dp_15_devparam_doc.diff

This is the 15th patch of 16 patches for devparam.

Devparam document.


Signed-off-by: Tejun Heo <[email protected]>


Index: linux-devparam-export/Documentation/driver-model/devparam.txt
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-devparam-export/Documentation/driver-model/devparam.txt 2004-10-23 11:09:33.000000000 +0900
@@ -0,0 +1,425 @@
+Per-Device Parameter
+
+Tejun Heo <[email protected]>
+
+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.