/*
* Greybus "Core"
*
- * Copyright 2014 Google Inc.
- * Copyright 2014 Linaro Ltd.
+ * Copyright 2014-2015 Google Inc.
+ * Copyright 2014-2015 Linaro Ltd.
*
* Released under the GPLv2 only.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/types.h>
-#include <linux/module.h>
-#include <linux/moduleparam.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/device.h>
-
#include "greybus.h"
/* Allow greybus to be disabled at boot if needed */
static int greybus_module_match(struct device *dev, struct device_driver *drv)
{
struct greybus_driver *driver = to_greybus_driver(drv);
- struct gb_interface *intf = to_gb_interface(dev);
- const struct greybus_interface_id *id;
+ struct gb_bundle *bundle = to_gb_bundle(dev);
+ const struct greybus_bundle_id *id;
- id = gb_interface_match_id(intf, driver->id_table);
+ id = gb_bundle_match_id(bundle, driver->id_table);
if (id)
return 1;
/* FIXME - Dynamic ids? */
struct gb_bundle *bundle = NULL;
struct gb_connection *connection = NULL;
+ if (is_gb_endo(dev)) {
+ /*
+ * Not much to do for an endo, just fall through, as the
+ * "default" attributes are good enough for us.
+ */
+ return 0;
+ }
+
if (is_gb_module(dev)) {
module = to_gb_module(dev);
} else if (is_gb_interface(dev)) {
static int greybus_probe(struct device *dev)
{
struct greybus_driver *driver = to_greybus_driver(dev->driver);
- struct gb_interface *intf = to_gb_interface(dev);
- const struct greybus_interface_id *id;
+ struct gb_bundle *bundle = to_gb_bundle(dev);
+ const struct greybus_bundle_id *id;
int retval;
/* match id */
- id = gb_interface_match_id(intf, driver->id_table);
+ id = gb_bundle_match_id(bundle, driver->id_table);
if (!id)
return -ENODEV;
- retval = driver->probe(intf, id);
+ retval = driver->probe(bundle, id);
if (retval)
return retval;
static int greybus_remove(struct device *dev)
{
struct greybus_driver *driver = to_greybus_driver(dev->driver);
- struct gb_interface *intf = to_gb_interface(dev);
+ struct gb_bundle *bundle = to_gb_bundle(dev);
- driver->disconnect(intf);
+ driver->disconnect(bundle);
return 0;
}
}
EXPORT_SYMBOL_GPL(greybus_register_driver);
-void greybus_deregister(struct greybus_driver *driver)
+void greybus_deregister_driver(struct greybus_driver *driver)
{
driver_unregister(&driver->driver);
}
-EXPORT_SYMBOL_GPL(greybus_deregister);
+EXPORT_SYMBOL_GPL(greybus_deregister_driver);
static DEFINE_MUTEX(hd_mutex);
hd = container_of(kref, struct greybus_host_device, kref);
+ ida_destroy(&hd->cport_id_map);
kfree(hd);
mutex_unlock(&hd_mutex);
}
struct greybus_host_device *greybus_create_hd(struct greybus_host_driver *driver,
- struct device *parent)
+ struct device *parent,
+ size_t buffer_size_max)
{
struct greybus_host_device *hd;
* Validate that the driver implements all of the callbacks
* so that we don't have to every time we make them.
*/
- if ((!driver->buffer_send) || (!driver->buffer_cancel) ||
- (!driver->submit_svc)) {
+ if ((!driver->message_send) || (!driver->message_cancel)) {
pr_err("Must implement all greybus_host_driver callbacks!\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (buffer_size_max < GB_OPERATION_MESSAGE_SIZE_MIN) {
+ dev_err(parent, "greybus host-device buffers too small\n");
return NULL;
}
+ /*
+ * Make sure to never allocate messages larger than what the Greybus
+ * protocol supports.
+ */
+ if (buffer_size_max > GB_OPERATION_MESSAGE_SIZE_MAX) {
+ dev_warn(parent, "limiting buffer size to %u\n",
+ GB_OPERATION_MESSAGE_SIZE_MAX);
+ buffer_size_max = GB_OPERATION_MESSAGE_SIZE_MAX;
+ }
+
hd = kzalloc(sizeof(*hd) + driver->hd_priv_size, GFP_KERNEL);
if (!hd)
- return NULL;
+ return ERR_PTR(-ENOMEM);
kref_init(&hd->kref);
hd->parent = parent;
INIT_LIST_HEAD(&hd->interfaces);
INIT_LIST_HEAD(&hd->connections);
ida_init(&hd->cport_id_map);
+ hd->buffer_size_max = buffer_size_max;
+
+ /*
+ * Initialize AP's SVC protocol connection:
+ *
+ * This is required as part of early initialization of the host device
+ * as we need this connection in order to start any kind of message
+ * exchange between the AP and the SVC. SVC will start with a
+ * 'get-version' request followed by a 'svc-hello' message and at that
+ * time we will create a fully initialized svc-connection, as we need
+ * endo-id and AP's interface id for that.
+ */
+ if (!gb_ap_svc_connection_create(hd)) {
+ kref_put_mutex(&hd->kref, free_hd, &hd_mutex);
+ return ERR_PTR(-ENOMEM);
+ }
return hd;
}
EXPORT_SYMBOL_GPL(greybus_create_hd);
+int greybus_endo_setup(struct greybus_host_device *hd, u16 endo_id,
+ u8 ap_intf_id)
+{
+ struct gb_endo *endo;
+
+ endo = gb_endo_create(hd, endo_id, ap_intf_id);
+ if (IS_ERR(endo))
+ return PTR_ERR(endo);
+ hd->endo = endo;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(greybus_endo_setup);
+
void greybus_remove_hd(struct greybus_host_device *hd)
{
- /* Tear down all modules that happen to be associated with this host
- * controller */
- gb_remove_interfaces(hd);
+ /*
+ * Tear down all interfaces, modules, and the endo that is associated
+ * with this host controller before freeing the memory associated with
+ * the host controller.
+ */
+ gb_interfaces_remove(hd);
+ gb_endo_remove(hd->endo);
+
+ /* Is the SVC still using the partially uninitialized connection ? */
+ if (hd->initial_svc_connection) {
+ gb_connection_exit(hd->initial_svc_connection);
+ gb_connection_destroy(hd->initial_svc_connection);
+ }
+
+ /*
+ * Make sure there are no leftovers that can potentially corrupt sysfs.
+ */
+ if (WARN_ON(!list_empty(&hd->connections)))
+ gb_hd_connections_exit(hd);
+
kref_put_mutex(&hd->kref, free_hd, &hd_mutex);
}
EXPORT_SYMBOL_GPL(greybus_remove_hd);
{
int retval;
- BUILD_BUG_ON(HOST_DEV_CPORT_ID_MAX >= (long)CPORT_ID_BAD);
+ if (greybus_disabled())
+ return -ENODEV;
- retval = gb_debugfs_init();
- if (retval) {
- pr_err("debugfs failed\n");
- return retval;
- }
+ 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\n");
+ pr_err("bus_register failed (%d)\n", retval);
goto error_bus;
}
- retval = gb_ap_init();
+ retval = gb_operation_init();
if (retval) {
- pr_err("gb_ap_init failed\n");
- goto error_ap;
+ pr_err("gb_operation_init failed (%d)\n", retval);
+ goto error_operation;
}
- retval = gb_operation_init();
+ retval = gb_endo_init();
if (retval) {
- pr_err("gb_operation_init failed\n");
- goto error_operation;
+ pr_err("gb_endo_init failed (%d)\n", retval);
+ goto error_endo;
+ }
+
+ retval = gb_control_protocol_init();
+ if (retval) {
+ pr_err("gb_control_protocol_init failed\n");
+ goto error_control;
+ }
+
+ retval = gb_svc_protocol_init();
+ if (retval) {
+ pr_err("gb_svc_protocol_init failed\n");
+ goto error_svc;
+ }
+
+ retval = gb_firmware_protocol_init();
+ if (retval) {
+ pr_err("gb_firmware_protocol_init failed\n");
+ goto error_firmware;
}
return 0; /* Success */
+error_firmware:
+ gb_svc_protocol_exit();
+error_svc:
+ gb_control_protocol_exit();
+error_control:
+ gb_endo_exit();
+error_endo:
+ gb_operation_exit();
error_operation:
- gb_ap_exit();
-error_ap:
bus_unregister(&greybus_bus_type);
error_bus:
gb_debugfs_cleanup();
static void __exit gb_exit(void)
{
+ gb_firmware_protocol_exit();
+ gb_svc_protocol_exit();
+ gb_control_protocol_exit();
+ gb_endo_exit();
gb_operation_exit();
- gb_ap_exit();
bus_unregister(&greybus_bus_type);
gb_debugfs_cleanup();
}
module_exit(gb_exit);
-
-MODULE_LICENSE("GPL");
+MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@linuxfoundation.org>");