greybus: Merge branch 'master' into branch 'svc'.
[cascardo/linux.git] / drivers / staging / greybus / core.c
index 15408ec..af71d02 100644 (file)
@@ -1,21 +1,14 @@
 /*
  * 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 */
@@ -34,10 +27,10 @@ EXPORT_SYMBOL_GPL(greybus_disabled);
 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? */
@@ -51,6 +44,14 @@ static int greybus_uevent(struct device *dev, struct kobj_uevent_env *env)
        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)) {
@@ -97,16 +98,16 @@ struct bus_type greybus_bus_type = {
 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;
 
@@ -116,9 +117,9 @@ static int greybus_probe(struct device *dev)
 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;
 }
 
@@ -145,11 +146,11 @@ int greybus_register_driver(struct greybus_driver *driver, struct module *owner,
 }
 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);
@@ -160,12 +161,14 @@ static void free_hd(struct kref *kref)
 
        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;
 
@@ -173,15 +176,29 @@ struct greybus_host_device *greybus_create_hd(struct greybus_host_driver *driver
         * 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;
@@ -189,16 +206,63 @@ struct greybus_host_device *greybus_create_hd(struct greybus_host_driver *driver
        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);
@@ -207,37 +271,60 @@ static int __init gb_init(void)
 {
        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();
@@ -248,12 +335,14 @@ module_init(gb_init);
 
 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>");