Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Documentation/driver-api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ available subsections can be seen below.
sync_file
vfio-mediated-device
vfio
virtual_bus
xilinx/index
xillybus
zorro
Expand Down
151 changes: 151 additions & 0 deletions Documentation/driver-api/virtual_bus.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
.. SPDX-License-Identifier: GPL-2.0-only
===========
Virtual Bus
===========

See <linux/virtual_bus.h> for the models for virtbus_device and virtbus_driver.

In some subsystems, the functionality of the core device (PCI/ACPI/other) may
be too complex for a single device to be managed as a monolithic block or
a part of the functionality might need to be exposed to a different subsystem.
Splitting the functionality into smaller orthogonal devices would make it
easier to manage data, power management and domain-specific communication with
the hardware. A key requirement for such a split is that there is no dependency
on a physical bus, device, register accesses or regmap support. These
individual devices split from the core cannot live on the platform bus as they
are not physical devices that are controlled by DT/ACPI. The same argument
applies for not using MFD in this scenario as it relies on individual function
devices being physical devices that are DT enumerated.

An example for this kind of requirement is the audio subsystem where a single
IP handles multiple entities such as HDMI, Soundwire, local devices such as
mics/speakers etc. The split for the core's functionality can be arbitrary or
be defined by the DSP firmware topology and include hooks for test/debug. This
allows for the audio core device to be minimal and tightly coupled with
handling the hardware-specific logic and communication.

The virtual bus is intended to be minimal, generic and avoid domain-specific
assumptions. Each virtual bus device represents a part of its parent
functionality. The generic behavior can be extended and specialized as needed
by encapsulating a virtual bus device within other domain-specific structures
and the use of .ops callbacks. Devices on the same virtual bus do not share any
structures and the use of a communication channel with the parent is
domain-specific.

Virtbus Devices
~~~~~~~~~~~~~~~

A virtbus_device is created and registered to represent a part of its parent
device's functionality. It is given a match_name that is used for driver
binding and a release callback that is invoked when the device is unregistered.

.. code-block:: c
struct virtbus_device {
struct device dev;
const char *match_name;
void (*release)(struct virtbus_device *);
u32 id;
};
The virtbus device is enumerated when it is attached to the bus. The device
is assigned a unique ID automatically that will be appended to its name. If
two virtbus_devices both named "foo" are registered onto the bus, they will
have the device names, "foo.x" and "foo.y", where x and y are unique integers.

Virtbus Drivers
~~~~~~~~~~~~~~~

Virtbus drivers follow the standard driver model convention, where
discovery/enumeration is handled by the core, and drivers
provide probe() and remove() methods. They support power management
and shutdown notifications using the standard conventions.

.. code-block:: c
struct virtbus_driver {
int (*probe)(struct virtbus_device *);
int (*remove)(struct virtbus_device *);
void (*shutdown)(struct virtbus_device *);
int (*suspend)(struct virtbus_device *, pm_message_t);
int (*resume)(struct virtbus_device *);
struct device_driver driver;
const struct virtbus_device_id *id_table;
};
Virtbus drivers register themselves with the bus by calling
virtbus_register_driver(). The id_table contains the names of virtbus devices
that a driver can bind with?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why a question mark? A typo?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it was my suggestion with a question mark that was copied verbatim...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, sorry about that. The documentation is so verbose now that I missed it :(


Example Usage
~~~~~~~~~~~~~

Virtbus devices are created and registered by a subsystem-level core device
that needs to break up its functionality into smaller fragments. One way to
extend the scope of a virtbus_device would be to encapsulate it within a
domain-specific structure defined by the parent device. This structure contains
the virtual bus device and any associated shared data/callbacks needed to
establish the connection with the parent.

An example would be:

.. code-block:: c
struct foo {
struct virtbus_device vdev;
void (*connect)(struct virtbus_device *vdev);
void (*disconnect)(struct virtbus_device *vdev);
void *data;
};
The parent device would then register the virtbus_device by calling
virtbus_register_device() with the pointer to the vdev member of the above
structure. The parent would provide a match_name for the virtbus_device that
will be used for matching and binding with a driver.

For the binding to succeed when a virtbus_device is registered, there needs
to be a virtbus_driver registered with the bus that includes the match_name
provided above in its id_table. The virtual bus driver can also be
encapsulated inside custom drivers that make the core device's functionality
extensible by adding additional domain-specific ops as follows:

.. code-block:: c
struct my_ops {
void (*send)(struct virtbus_device *vdev);
void (*receive)(struct virtbus_device *vdev);
};
struct my_driver {
struct virtbus_driver virtbus_drv;
const struct my_ops ops;
};
An example of this type of usage would be:

.. code-block:: c
const struct virtbus_device_id my_virtbus_id_table[] = {
{.name = "foo_dev"},
{ },
};
const struct my_ops my_custom_ops = {
.send = my_tx,
.receive = my_rx,
};
struct my_driver my_drv = {
.virtbus_drv = {
.driver = {
.name = "myvirtbusdrv",
},
.id_table = my_virtbus_id_table,
.probe = my_probe,
.remove = my_remove,
.shutdown = my_shutdown,
},
.ops = my_custom_ops,
};
9 changes: 9 additions & 0 deletions drivers/bus/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,13 @@ config DA8XX_MSTPRI
source "drivers/bus/fsl-mc/Kconfig"
source "drivers/bus/mhi/Kconfig"

config VIRTUAL_BUS
tristate "Software based Virtual Bus"
help
Provides a software bus for virtual bus drivers to register and
attach virtual bus devices. It matches driver and device based on a
unique string and calls the driver's probe routine. One example is
SOF (Sound Open Firmware) clients such as audio cards or test
tools needing to connect with the SOF core.

endmenu
2 changes: 2 additions & 0 deletions drivers/bus/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ obj-$(CONFIG_DA8XX_MSTPRI) += da8xx-mstpri.o

# MHI
obj-$(CONFIG_MHI_BUS) += mhi/

obj-$(CONFIG_VIRTUAL_BUS) += virtual_bus.o
214 changes: 214 additions & 0 deletions drivers/bus/virtual_bus.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Lightweight software based bus for virtual devices
*
* Copyright (c) 2019-2020 Intel Corporation
*
* Please see Documentation/driver-api/virtual_bus.rst for
* more information
*/

#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_irq.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/string.h>
#include <linux/virtual_bus.h>

static DEFINE_IDA(virtbus_dev_ida);
#define VIRTBUS_INVALID_ID ~0U

static const
struct virtbus_device_id *virtbus_match_id(const struct virtbus_device_id *id,
struct virtbus_device *vdev)
{
while (id->name[0]) {
if (!strcmp(vdev->match_name, id->name))
return id;
id++;
}
return NULL;
}

static int virtbus_match(struct device *dev, struct device_driver *drv)
{
struct virtbus_driver *vdrv = to_virtbus_drv(drv);
struct virtbus_device *vdev = to_virtbus_dev(dev);

return !!virtbus_match_id(vdrv->id_table, vdev);
}

static int virtbus_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct virtbus_device *vdev = to_virtbus_dev(dev);

if (add_uevent_var(env, "MODALIAS=%s%s", VIRTBUS_MODULE_PREFIX,
vdev->match_name))
return -ENOMEM;

return 0;
}

static const struct dev_pm_ops virtbus_dev_pm_ops = {
SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend,
pm_generic_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(pm_generic_suspend, pm_generic_resume)
};

struct bus_type virtual_bus_type = {
.name = "virtbus",
.match = virtbus_match,
.uevent = virtbus_uevent,
.pm = &virtbus_dev_pm_ops,
};

/**
* virtbus_release_device - Destroy a virtbus device
* @_dev: device to release
*/
static void virtbus_release_device(struct device *_dev)
{
struct virtbus_device *vdev = to_virtbus_dev(_dev);
u32 ida = vdev->id;

vdev->release(vdev);
if (ida != VIRTBUS_INVALID_ID)
ida_simple_remove(&virtbus_dev_ida, ida);
}

/**
* virtbus_register_device - add a virtual bus device
* @vdev: virtual bus device to add
*/
int virtbus_register_device(struct virtbus_device *vdev)
{
int ret;

if (!vdev->release) {
dev_err(&vdev->dev, "release callback not set for vdev!\n");
return -EINVAL;
}

/* All error paths out of this function after the device_initialize
* must perform a put_device() so that the .release() callback is
* called for an error condition.
*/
device_initialize(&vdev->dev);

vdev->dev.bus = &virtual_bus_type;
vdev->dev.release = virtbus_release_device;

/* All device IDs are automatically allocated */
ret = ida_simple_get(&virtbus_dev_ida, 0, 0, GFP_KERNEL);
if (ret < 0) {
vdev->id = VIRTBUS_INVALID_ID;
dev_err(&vdev->dev, "get IDA idx for virtbus device failed!\n");
goto err;
}

vdev->id = ret;

ret = dev_set_name(&vdev->dev, "%s.%d", vdev->match_name, vdev->id);
if (ret) {
dev_err(&vdev->dev, "dev_set_name failed for device\n");
goto err;
}

dev_dbg(&vdev->dev, "Registering virtbus device '%s'\n",
dev_name(&vdev->dev));

ret = device_add(&vdev->dev);
if (!ret)
return ret;

dev_err(&vdev->dev, "Add device to virtbus failed!: %d\n", ret);

err:
put_device(&vdev->dev);

return ret;
}
EXPORT_SYMBOL_GPL(virtbus_register_device);

static int virtbus_probe_driver(struct device *_dev)
{
struct virtbus_driver *vdrv = to_virtbus_drv(_dev->driver);
struct virtbus_device *vdev = to_virtbus_dev(_dev);
int ret;

ret = dev_pm_domain_attach(_dev, true);
if (ret) {
dev_warn(_dev, "Failed to attach to PM Domain : %d\n", ret);
return ret;
}

ret = vdrv->probe(vdev);
if (ret) {
dev_err(&vdev->dev, "Probe returned error\n");
dev_pm_domain_detach(_dev, true);
}

return ret;
}

static int virtbus_remove_driver(struct device *_dev)
{
struct virtbus_driver *vdrv = to_virtbus_drv(_dev->driver);
struct virtbus_device *vdev = to_virtbus_dev(_dev);
int ret;

ret = vdrv->remove(vdev);
dev_pm_domain_detach(_dev, true);

return ret;
}

static void virtbus_shutdown_driver(struct device *_dev)
{
struct virtbus_driver *vdrv = to_virtbus_drv(_dev->driver);
struct virtbus_device *vdev = to_virtbus_dev(_dev);

vdrv->shutdown(vdev);
}

/**
* __virtbus_register_driver - register a driver for virtual bus devices
* @vdrv: virtbus_driver structure
* @owner: owning module/driver
*/
int __virtbus_register_driver(struct virtbus_driver *vdrv, struct module *owner)
{
if (!vdrv->probe || !vdrv->remove || !vdrv->shutdown || !vdrv->id_table)
return -EINVAL;

vdrv->driver.owner = owner;
vdrv->driver.bus = &virtual_bus_type;
vdrv->driver.probe = virtbus_probe_driver;
vdrv->driver.remove = virtbus_remove_driver;
vdrv->driver.shutdown = virtbus_shutdown_driver;

return driver_register(&vdrv->driver);
}
EXPORT_SYMBOL_GPL(__virtbus_register_driver);

static int __init virtual_bus_init(void)
{
return bus_register(&virtual_bus_type);
}

static void __exit virtual_bus_exit(void)
{
bus_unregister(&virtual_bus_type);
ida_destroy(&virtbus_dev_ida);
}

module_init(virtual_bus_init);
module_exit(virtual_bus_exit);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Virtual Bus");
MODULE_AUTHOR("David Ertman <david.m.ertman@intel.com>");
MODULE_AUTHOR("Kiran Patil <kiran.patil@intel.com>");
Loading