Auxiliary Bus

In some subsystems, the functionality of the core device (PCI/ACPI/other) istoo complex for a single device to be managed by a monolithic driver (e.g.Sound Open Firmware), multiple devices might implement a common intersectionof functionality (e.g. NICs + RDMA), or a driver may want to export aninterface for another subsystem to drive (e.g. SIOV Physical Function exportVirtual Function management). A split of the functionality into child-devices representing sub-domains of functionality makes it possible tocompartmentalize, layer, and distribute domain-specific concerns via a Linuxdevice-driver model.

An example for this kind of requirement is the audio subsystem where asingle IP is handling multiple entities such as HDMI, Soundwire, localdevices such as mics/speakers etc. The split for the core’s functionalitycan be arbitrary or be defined by the DSP firmware topology and includehooks for test/debug. This allows for the audio core device to be minimaland focused on hardware-specific control and communication.

Each auxiliary_device represents a part of its parent functionality. Thegeneric behavior can be extended and specialized as needed by encapsulatingan auxiliary_device within other domain-specific structures and the use of.ops callbacks. Devices on the auxiliary bus do not share any structures andthe use of a communication channel with the parent is domain-specific.

Note that ops are intended as a way to augment instance behavior within aclass of auxiliary devices, it is not the mechanism for exporting commoninfrastructure from the parent. ConsiderEXPORT_SYMBOL_NS() to conveyinfrastructure from the parent module to the auxiliary module(s).

When Should the Auxiliary Bus Be Used

The auxiliary bus is to be used when a driver and one or more kernelmodules, who share a common header file with the driver, need a mechanism toconnect and provide access to a shared object allocated by theauxiliary_device’s registering driver. The registering driver for theauxiliary_device(s) and the kernel module(s) registering auxiliary_driverscan be from the same subsystem, or from multiple subsystems.

The emphasis here is on a common generic interface that keeps subsystemcustomization out of the bus infrastructure.

One example is a PCI network device that is RDMA-capable and exports a childdevice to be driven by an auxiliary_driver in the RDMA subsystem. The PCIdriver allocates and registers an auxiliary_device for each physicalfunction on the NIC. The RDMA driver registers an auxiliary_driver thatclaims each of these auxiliary_devices. This conveys data/ops published bythe parent PCI device/driver to the RDMA auxiliary_driver.

Another use case is for the PCI device to be split out into multiple subfunctions. For each sub function an auxiliary_device is created. A PCI subfunction driver binds to such devices that creates its own one or more classdevices. A PCI sub function auxiliary device is likely to be contained in astructwith additional attributes such as user defined sub function numberand optional attributes such as resources and a link to the parent device.These attributes could be used by systemd/udev; and hence should beinitialized before a driver binds to an auxiliary_device.

A key requirement for utilizing the auxiliary bus is that there is nodependency on a physical bus, device, register accesses or regmap support.These individual devices split from the core cannot live on the platform busas they are not physical devices that are controlled by DT/ACPI. The sameargument applies for not using MFD in this scenario as MFD relies onindividual function devices being physical devices.

Auxiliary Device Creation

structauxiliary_device

auxiliary device object.

Definition:

struct auxiliary_device {    struct device dev;    const char *name;    u32 id;    struct {        struct xarray irqs;        struct mutex lock;        bool irq_dir_exists;    } sysfs;};

Members

dev

Device,The release and parent fields of the device structure must be filledin

name

Match name found by the auxiliary device driver,

id

unique identitier if multiple devices of the same name are exported,

sysfs

embeddedstructwhich hold all sysfs related fields,

sysfs.irqs

irqs xarray contains irq indices which are used by the device,

sysfs.lock

Synchronize irq sysfs creation,

sysfs.irq_dir_exists

whether “irqs” directory exists,

Description

An auxiliary_device represents a part of its parent device’s functionality.It is given a name that, combined with the registering driversKBUILD_MODNAME, creates a match_name that is used for driver binding, and anid that combined with the match_name provide a unique name to register withthe bus subsystem. For example, a driver registering an auxiliary device isnamed ‘foo_mod.ko’ and the subdevice is named ‘foo_dev’. The match name istherefore ‘foo_mod.foo_dev’.

Registering an auxiliary_device is a three-step process.

First, a ‘structauxiliary_device’ needs to be defined or allocated for eachsub-device desired. The name, id, dev.release, and dev.parent fields ofthis structure must be filled in as follows.

The ‘name’ field is to be given a name that is recognized by the auxiliarydriver. If two auxiliary_devices with the same match_name, eg“foo_mod.foo_dev”, are registered onto the bus, they must have unique idvalues (e.g. “x” and “y”) so that the registered devices names are“foo_mod.foo_dev.x” and “foo_mod.foo_dev.y”. If match_name + id are notunique, then the device_add fails and generates an error message.

The auxiliary_device.dev.type.release or auxiliary_device.dev.release mustbe populated with a non-NULL pointer to successfully register theauxiliary_device. This release call is where resources associated with theauxiliary device must be free’ed. Because once the device is placed on thebus the parent driver can not tell what other code may have a reference tothis data.

The auxiliary_device.dev.parent should be set. Typically to the registeringdrivers device.

Second, callauxiliary_device_init(), which checks several aspects of theauxiliary_devicestructand performs adevice_initialize(). After this stepcompletes, any error state must have a call toauxiliary_device_uninit() inits resolution path.

The third and final step in registering an auxiliary_device is to perform acall toauxiliary_device_add(), which sets the name of the device and addsthe device to the bus.

#define MY_DEVICE_NAME "foo_dev"...structauxiliary_device*my_aux_dev=my_aux_dev_alloc(xxx);// Step 1:my_aux_dev->name=MY_DEVICE_NAME;my_aux_dev->id=my_unique_id_alloc(xxx);my_aux_dev->dev.release=my_aux_dev_release;my_aux_dev->dev.parent=my_dev;// Step 2:if(auxiliary_device_init(my_aux_dev))gotofail;// Step 3:if(auxiliary_device_add(my_aux_dev)){auxiliary_device_uninit(my_aux_dev);gotofail;}...

Unregistering an auxiliary_device is a two-step process to mirror theregister process. First callauxiliary_device_delete(), then callauxiliary_device_uninit().

auxiliary_device_delete(my_dev->my_aux_dev);auxiliary_device_uninit(my_dev->my_aux_dev);
intauxiliary_device_init(structauxiliary_device*auxdev)

check auxiliary_device and initialize

Parameters

structauxiliary_device*auxdev

auxiliary device struct

Description

This is the second step in the three-step process to register anauxiliary_device.

When this function returns an error code, then the device_initialize willnot have been performed, and the caller will be responsible to free anymemory allocated for the auxiliary_device in the error path directly.

It returns 0 on success. On success, the device_initialize has beenperformed. After this point any error unwinding will need to include a calltoauxiliary_device_uninit(). In this post-initialize error scenario, a callto the device’s .release callback will be triggered, and all memory clean-upis expected to be handled there.

int__auxiliary_device_add(structauxiliary_device*auxdev,constchar*modname)

add an auxiliary bus device

Parameters

structauxiliary_device*auxdev

auxiliary bus device to add to the bus

constchar*modname

name of the parent device’s driver module

Description

This is the third step in the three-step process to register anauxiliary_device.

This function must be called after a successful call toauxiliary_device_init(), which will perform the device_initialize. Thismeans that if this returns an error code, then a call toauxiliary_device_uninit() must be performed so that the .release callbackwill be triggered to free the memory associated with the auxiliary_device.

The expectation is that users will call the “auxiliary_device_add” macro sothat the caller’s KBUILD_MODNAME is automatically inserted for the modnameparameter. Only if a user requires a custom name would this version becalled directly.

Auxiliary Device Memory Model and Lifespan

The registering driver is the entity that allocates memory for theauxiliary_device and registers it on the auxiliary bus. It is important tonote that, as opposed to the platform bus, the registering driver is whollyresponsible for the management of the memory used for the device object.

To be clear the memory for the auxiliary_device is freed in therelease()callback defined by the registering driver. The registering driver shouldonly callauxiliary_device_delete() and thenauxiliary_device_uninit() whenit is done with the device. Therelease() function is then automaticallycalled if and when other code releases their reference to the devices.

A parent object, defined in the shared header file, contains theauxiliary_device. It also contains a pointer to the shared object(s), whichalso is defined in the shared header. Both the parent object and the sharedobject(s) are allocated by the registering driver. This layout allows theauxiliary_driver’s registering module to perform acontainer_of() call to gofrom the pointer to the auxiliary_device, that is passed during the call tothe auxiliary_driver’s probe function, up to the parent object, and thenhave access to the shared object(s).

The memory for the shared object(s) must have a lifespan equal to, orgreater than, the lifespan of the memory for the auxiliary_device. Theauxiliary_driver should only consider that the shared object is valid aslong as the auxiliary_device is still registered on the auxiliary bus. Itis up to the registering driver to manage (e.g. free or keep available) thememory for the shared object beyond the life of the auxiliary_device.

The registering driver must unregister all auxiliary devices before its owndriver.remove() is completed. An easy way to ensure this is to use thedevm_add_action_or_reset() call to register a function against the parentdevice which unregisters the auxiliary device object(s).

Finally, any operations which operate on the auxiliary devices must continueto function (if only to return an error) after the registering driverunregisters the auxiliary device.

Auxiliary Drivers

structauxiliary_driver

Definition of an auxiliary bus driver

Definition:

struct auxiliary_driver {    int (*probe)(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id);    void (*remove)(struct auxiliary_device *auxdev);    void (*shutdown)(struct auxiliary_device *auxdev);    int (*suspend)(struct auxiliary_device *auxdev, pm_message_t state);    int (*resume)(struct auxiliary_device *auxdev);    const char *name;    struct device_driver driver;    const struct auxiliary_device_id *id_table;};

Members

probe

Called when a matching device is added to the bus.

remove

Called when device is removed from the bus.

shutdown

Called at shut-down time to quiesce the device.

suspend

Called to put the device to sleep mode. Usually to a power state.

resume

Called to bring a device from sleep mode.

name

Driver name.

driver

Core driver structure.

id_table

Table of devices this driver should match on the bus.

Description

Auxiliary drivers follow the standard driver model convention, wherediscovery/enumeration is handled by the core, and drivers provideprobe()andremove() methods. They support power management and shutdownnotifications using the standard conventions.

Auxiliary drivers register themselves with the bus by callingauxiliary_driver_register(). The id_table contains the match_names ofauxiliary devices that a driver can bind with.

staticconststructauxiliary_device_idmy_auxiliary_id_table[]={{.name="foo_mod.foo_dev"},{},};MODULE_DEVICE_TABLE(auxiliary,my_auxiliary_id_table);structauxiliary_drivermy_drv={.name="myauxiliarydrv",.id_table=my_auxiliary_id_table,.probe=my_drv_probe,.remove=my_drv_remove};
module_auxiliary_driver

module_auxiliary_driver(__auxiliary_driver)

Helper macro for registering an auxiliary driver

Parameters

__auxiliary_driver

auxiliary driver struct

Description

Helper macro for auxiliary drivers which do not do anything special inmodule init/exit. This eliminates a lot of boilerplate. Each module may onlyuse this macro once, and calling it replacesmodule_init() andmodule_exit()

module_auxiliary_driver(my_drv);
int__auxiliary_driver_register(structauxiliary_driver*auxdrv,structmodule*owner,constchar*modname)

register a driver for auxiliary bus devices

Parameters

structauxiliary_driver*auxdrv

auxiliary_driver structure

structmodule*owner

owning module/driver

constchar*modname

KBUILD_MODNAME for parent driver

Description

The expectation is that users will call the “auxiliary_driver_register”macro so that the caller’s KBUILD_MODNAME is automatically inserted for themodname parameter. Only if a user requires a custom name would this versionbe called directly.

voidauxiliary_driver_unregister(structauxiliary_driver*auxdrv)

unregister a driver

Parameters

structauxiliary_driver*auxdrv

auxiliary_driver structure

Example Usage

Auxiliary devices are created and registered by a subsystem-level coredevice that needs to break up its functionality into smaller fragments. Oneway to extend the scope of an auxiliary_device is to encapsulate it within adomain-specific structure defined by the parent device. This structurecontains the auxiliary_device and any associated shared data/callbacksneeded to establish the connection with the parent.

An example is:

structfoo{structauxiliary_deviceauxdev;void(*connect)(structauxiliary_device*auxdev);void(*disconnect)(structauxiliary_device*auxdev);void*data;};

The parent device then registers the auxiliary_device by callingauxiliary_device_init(), and thenauxiliary_device_add(), with the pointerto the auxdev member of the above structure. The parent provides a name forthe auxiliary_device that, combined with the parent’s KBUILD_MODNAME,creates a match_name that is be used for matching and binding with a driver.

Whenever an auxiliary_driver is registered, based on the match_name, theauxiliary_driver’sprobe() is invoked for the matching devices. Theauxiliary_driver can also be encapsulated inside custom drivers that makethe core device’s functionality extensible by adding additionaldomain-specific ops as follows:

structmy_ops{void(*send)(structauxiliary_device*auxdev);void(*receive)(structauxiliary_device*auxdev);};structmy_driver{structauxiliary_driverauxiliary_drv;conststructmy_opsops;};

An example of this type of usage is:

conststructauxiliary_device_idmy_auxiliary_id_table[]={{.name="foo_mod.foo_dev"},{},};conststructmy_opsmy_custom_ops={.send=my_tx,.receive=my_rx,};conststructmy_drivermy_drv={.auxiliary_drv={.name="myauxiliarydrv",.id_table=my_auxiliary_id_table,.probe=my_probe,.remove=my_remove,.shutdown=my_shutdown,},.ops=my_custom_ops,};

Please note that such custom ops approach is valid, but it is hard to implementit right without global locks per-device to protect from auxiliary_drv removalduring call to that ops. In addition, this implementation lacks proper moduledependency, which causes to load/unload races between auxiliary parent and devicesmodules.

The most easiest way to provide these ops reliably without needing tohave a lock is to EXPORT_SYMBOL*() them and rely on already existingmodules infrastructure for validity and correct dependencies chains.