linux/drivers/greybus/core.c

350 lines
8.0 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* Greybus "Core"
*
* Copyright 2014-2015 Google Inc.
* Copyright 2014-2015 Linaro Ltd.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#define CREATE_TRACE_POINTS
#include <linux/greybus.h>
#include "greybus_trace.h"
#define GB_BUNDLE_AUTOSUSPEND_MS 3000
/* Allow greybus to be disabled at boot if needed */
static bool nogreybus;
#ifdef MODULE
module_param(nogreybus, bool, 0444);
#else
core_param(nogreybus, nogreybus, bool, 0444);
#endif
int greybus_disabled(void)
{
return nogreybus;
}
EXPORT_SYMBOL_GPL(greybus_disabled);
static bool greybus_match_one_id(struct gb_bundle *bundle,
const struct greybus_bundle_id *id)
{
if ((id->match_flags & GREYBUS_ID_MATCH_VENDOR) &&
(id->vendor != bundle->intf->vendor_id))
return false;
if ((id->match_flags & GREYBUS_ID_MATCH_PRODUCT) &&
(id->product != bundle->intf->product_id))
return false;
if ((id->match_flags & GREYBUS_ID_MATCH_CLASS) &&
(id->class != bundle->class))
return false;
return true;
}
static const struct greybus_bundle_id *
greybus_match_id(struct gb_bundle *bundle, const struct greybus_bundle_id *id)
{
if (!id)
return NULL;
for (; id->vendor || id->product || id->class || id->driver_info;
id++) {
if (greybus_match_one_id(bundle, id))
return id;
}
return NULL;
}
static int greybus_match_device(struct device *dev, struct device_driver *drv)
{
struct greybus_driver *driver = to_greybus_driver(drv);
struct gb_bundle *bundle;
const struct greybus_bundle_id *id;
if (!is_gb_bundle(dev))
return 0;
bundle = to_gb_bundle(dev);
id = greybus_match_id(bundle, driver->id_table);
if (id)
return 1;
/* FIXME - Dynamic ids? */
return 0;
}
static int greybus_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct gb_host_device *hd;
struct gb_module *module = NULL;
struct gb_interface *intf = NULL;
struct gb_control *control = NULL;
struct gb_bundle *bundle = NULL;
struct gb_svc *svc = NULL;
if (is_gb_host_device(dev)) {
hd = to_gb_host_device(dev);
} else if (is_gb_module(dev)) {
module = to_gb_module(dev);
hd = module->hd;
greybus: add module support Modules in the greybus system sit above the interface, so insert them early in the sysfs tree. We dynamically create them when we have an interface that references a module, as we don't get a "module create" message directly. They also dynamically go away when the last interface associated with a module is removed. Naming scheme for modules/interfaces/bundles/connections is bumped up by one ':', and now looks like the following: /sys/bus/greybus $ tree . ├── devices │   ├── 7 -> ../../../devices/pci0000:00/0000:00:14.0/usb1/1-1/7 │   ├── 7:7 -> ../../../devices/pci0000:00/0000:00:14.0/usb1/1-1/7/7:7 │   ├── 7:7:0 -> ../../../devices/pci0000:00/0000:00:14.0/usb1/1-1/7/7:7/7:7:0 │   └── 7:7:0:1 -> ../../../devices/pci0000:00/0000:00:14.0/usb1/1-1/7/7:7/7:7:0/7:7:0:1 ├── drivers ├── drivers_autoprobe ├── drivers_probe └── uevent 6 directories, 3 files /sys/bus/greybus $ grep . devices/*/uevent devices/7/uevent:DEVTYPE=greybus_module devices/7:7/uevent:DEVTYPE=greybus_interface devices/7:7:0/uevent:DEVTYPE=greybus_bundle devices/7:7:0:1/uevent:DEVTYPE=greybus_connection We still have some "confusion" about interface ids and module ids, which will be cleaned up later when the svc control protocol changes die down, right now we just name a module after the interface as we don't have any modules that have multiple interfaces in our systems. This has been tested with gbsim. Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
2014-12-22 06:10:26 +08:00
} else if (is_gb_interface(dev)) {
intf = to_gb_interface(dev);
module = intf->module;
hd = intf->hd;
} else if (is_gb_control(dev)) {
control = to_gb_control(dev);
intf = control->intf;
module = intf->module;
hd = intf->hd;
} else if (is_gb_bundle(dev)) {
bundle = to_gb_bundle(dev);
intf = bundle->intf;
module = intf->module;
hd = intf->hd;
} else if (is_gb_svc(dev)) {
svc = to_gb_svc(dev);
hd = svc->hd;
} else {
dev_WARN(dev, "uevent for unknown greybus device \"type\"!\n");
return -EINVAL;
}
if (add_uevent_var(env, "BUS=%u", hd->bus_id))
return -ENOMEM;
if (module) {
if (add_uevent_var(env, "MODULE=%u", module->module_id))
return -ENOMEM;
}
if (intf) {
if (add_uevent_var(env, "INTERFACE=%u", intf->interface_id))
return -ENOMEM;
if (add_uevent_var(env, "GREYBUS_ID=%08x/%08x",
intf->vendor_id, intf->product_id))
return -ENOMEM;
}
if (bundle) {
// FIXME
// add a uevent that can "load" a bundle type
// This is what we need to bind a driver to so use the info
// in gmod here as well
if (add_uevent_var(env, "BUNDLE=%u", bundle->id))
return -ENOMEM;
if (add_uevent_var(env, "BUNDLE_CLASS=%02x", bundle->class))
return -ENOMEM;
}
return 0;
}
static void greybus_shutdown(struct device *dev)
{
if (is_gb_host_device(dev)) {
struct gb_host_device *hd;
hd = to_gb_host_device(dev);
gb_hd_shutdown(hd);
}
}
struct bus_type greybus_bus_type = {
.name = "greybus",
.match = greybus_match_device,
.uevent = greybus_uevent,
.shutdown = greybus_shutdown,
};
static int greybus_probe(struct device *dev)
{
struct greybus_driver *driver = to_greybus_driver(dev->driver);
struct gb_bundle *bundle = to_gb_bundle(dev);
const struct greybus_bundle_id *id;
int retval;
/* match id */
id = greybus_match_id(bundle, driver->id_table);
if (!id)
return -ENODEV;
retval = pm_runtime_get_sync(&bundle->intf->dev);
if (retval < 0) {
pm_runtime_put_noidle(&bundle->intf->dev);
return retval;
}
retval = gb_control_bundle_activate(bundle->intf->control, bundle->id);
if (retval) {
pm_runtime_put(&bundle->intf->dev);
return retval;
}
/*
* Unbound bundle devices are always deactivated. During probe, the
* Runtime PM is set to enabled and active and the usage count is
* incremented. If the driver supports runtime PM, it should call
* pm_runtime_put() in its probe routine and pm_runtime_get_sync()
* in remove routine.
*/
pm_runtime_set_autosuspend_delay(dev, GB_BUNDLE_AUTOSUSPEND_MS);
pm_runtime_use_autosuspend(dev);
pm_runtime_get_noresume(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
retval = driver->probe(bundle, id);
if (retval) {
/*
* Catch buggy drivers that fail to destroy their connections.
*/
WARN_ON(!list_empty(&bundle->connections));
gb_control_bundle_deactivate(bundle->intf->control, bundle->id);
pm_runtime_disable(dev);
pm_runtime_set_suspended(dev);
pm_runtime_put_noidle(dev);
pm_runtime_dont_use_autosuspend(dev);
pm_runtime_put(&bundle->intf->dev);
return retval;
}
pm_runtime_put(&bundle->intf->dev);
return 0;
}
static int greybus_remove(struct device *dev)
{
struct greybus_driver *driver = to_greybus_driver(dev->driver);
struct gb_bundle *bundle = to_gb_bundle(dev);
struct gb_connection *connection;
int retval;
retval = pm_runtime_get_sync(dev);
if (retval < 0)
dev_err(dev, "failed to resume bundle: %d\n", retval);
/*
* Disable (non-offloaded) connections early in case the interface is
* already gone to avoid unceccessary operation timeouts during
* driver disconnect. Otherwise, only disable incoming requests.
*/
list_for_each_entry(connection, &bundle->connections, bundle_links) {
if (gb_connection_is_offloaded(connection))
continue;
if (bundle->intf->disconnected)
gb_connection_disable_forced(connection);
else
gb_connection_disable_rx(connection);
}
driver->disconnect(bundle);
/* Catch buggy drivers that fail to destroy their connections. */
WARN_ON(!list_empty(&bundle->connections));
if (!bundle->intf->disconnected)
gb_control_bundle_deactivate(bundle->intf->control, bundle->id);
pm_runtime_put_noidle(dev);
pm_runtime_disable(dev);
pm_runtime_set_suspended(dev);
pm_runtime_dont_use_autosuspend(dev);
pm_runtime_put_noidle(dev);
return 0;
}
int greybus_register_driver(struct greybus_driver *driver, struct module *owner,
const char *mod_name)
{
int retval;
if (greybus_disabled())
return -ENODEV;
driver->driver.bus = &greybus_bus_type;
driver->driver.name = driver->name;
driver->driver.probe = greybus_probe;
driver->driver.remove = greybus_remove;
driver->driver.owner = owner;
driver->driver.mod_name = mod_name;
retval = driver_register(&driver->driver);
if (retval)
return retval;
pr_info("registered new driver %s\n", driver->name);
return 0;
}
EXPORT_SYMBOL_GPL(greybus_register_driver);
void greybus_deregister_driver(struct greybus_driver *driver)
{
driver_unregister(&driver->driver);
}
EXPORT_SYMBOL_GPL(greybus_deregister_driver);
static int __init gb_init(void)
{
int retval;
if (greybus_disabled())
return -ENODEV;
BUILD_BUG_ON(CPORT_ID_MAX >= (long)CPORT_ID_BAD);
gb_debugfs_init();
retval = bus_register(&greybus_bus_type);
if (retval) {
pr_err("bus_register failed (%d)\n", retval);
goto error_bus;
}
retval = gb_hd_init();
if (retval) {
pr_err("gb_hd_init failed (%d)\n", retval);
goto error_hd;
}
retval = gb_operation_init();
if (retval) {
pr_err("gb_operation_init failed (%d)\n", retval);
goto error_operation;
}
return 0; /* Success */
error_operation:
gb_hd_exit();
error_hd:
bus_unregister(&greybus_bus_type);
error_bus:
2014-09-01 07:17:04 +08:00
gb_debugfs_cleanup();
return retval;
}
module_init(gb_init);
static void __exit gb_exit(void)
{
gb_operation_exit();
gb_hd_exit();
bus_unregister(&greybus_bus_type);
2014-09-01 07:17:04 +08:00
gb_debugfs_cleanup();
tracepoint_synchronize_unregister();
}
module_exit(gb_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@linuxfoundation.org>");