-
Notifications
You must be signed in to change notification settings - Fork 140
[RFC] Use virtual bus implementation in SOF #1841
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1ae789d
01424be
8dca3bd
427c856
61a5a04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
ranj063 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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. | ||
ranj063 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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? | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why a question mark? A typo?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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...
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
| }; | ||
| 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, | ||
plbossart marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }; | ||
|
|
||
| /** | ||
| * 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; | ||
ranj063 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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; | ||
ranj063 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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>"); | ||
Uh oh!
There was an error while loading. Please reload this page.