These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / misc / mic / scif / scif_peer_bus.c
diff --git a/kernel/drivers/misc/mic/scif/scif_peer_bus.c b/kernel/drivers/misc/mic/scif/scif_peer_bus.c
new file mode 100644 (file)
index 0000000..6ffa3bd
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * Intel MIC Platform Software Stack (MPSS)
+ *
+ * Copyright(c) 2014 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * Intel SCIF driver.
+ */
+#include "scif_main.h"
+#include "../bus/scif_bus.h"
+#include "scif_peer_bus.h"
+
+static inline struct scif_peer_dev *
+dev_to_scif_peer(struct device *dev)
+{
+       return container_of(dev, struct scif_peer_dev, dev);
+}
+
+struct bus_type scif_peer_bus = {
+       .name  = "scif_peer_bus",
+};
+
+static void scif_peer_release_dev(struct device *d)
+{
+       struct scif_peer_dev *sdev = dev_to_scif_peer(d);
+       struct scif_dev *scifdev = &scif_dev[sdev->dnode];
+
+       scif_cleanup_scifdev(scifdev);
+       kfree(sdev);
+}
+
+static int scif_peer_initialize_device(struct scif_dev *scifdev)
+{
+       struct scif_peer_dev *spdev;
+       int ret;
+
+       spdev = kzalloc(sizeof(*spdev), GFP_KERNEL);
+       if (!spdev) {
+               ret = -ENOMEM;
+               goto err;
+       }
+
+       spdev->dev.parent = scifdev->sdev->dev.parent;
+       spdev->dev.release = scif_peer_release_dev;
+       spdev->dnode = scifdev->node;
+       spdev->dev.bus = &scif_peer_bus;
+       dev_set_name(&spdev->dev, "scif_peer-dev%u", spdev->dnode);
+
+       device_initialize(&spdev->dev);
+       get_device(&spdev->dev);
+       rcu_assign_pointer(scifdev->spdev, spdev);
+
+       mutex_lock(&scif_info.conflock);
+       scif_info.total++;
+       scif_info.maxid = max_t(u32, spdev->dnode, scif_info.maxid);
+       mutex_unlock(&scif_info.conflock);
+       return 0;
+err:
+       dev_err(&scifdev->sdev->dev,
+               "dnode %d: initialize_device rc %d\n", scifdev->node, ret);
+       return ret;
+}
+
+static int scif_peer_add_device(struct scif_dev *scifdev)
+{
+       struct scif_peer_dev *spdev = rcu_dereference(scifdev->spdev);
+       char pool_name[16];
+       int ret;
+
+       ret = device_add(&spdev->dev);
+       put_device(&spdev->dev);
+       if (ret) {
+               dev_err(&scifdev->sdev->dev,
+                       "dnode %d: peer device_add failed\n", scifdev->node);
+               goto put_spdev;
+       }
+
+       scnprintf(pool_name, sizeof(pool_name), "scif-%d", spdev->dnode);
+       scifdev->signal_pool = dmam_pool_create(pool_name, &scifdev->sdev->dev,
+                                               sizeof(struct scif_status), 1,
+                                               0);
+       if (!scifdev->signal_pool) {
+               dev_err(&scifdev->sdev->dev,
+                       "dnode %d: dmam_pool_create failed\n", scifdev->node);
+               ret = -ENOMEM;
+               goto del_spdev;
+       }
+       dev_dbg(&spdev->dev, "Added peer dnode %d\n", spdev->dnode);
+       return 0;
+del_spdev:
+       device_del(&spdev->dev);
+put_spdev:
+       RCU_INIT_POINTER(scifdev->spdev, NULL);
+       synchronize_rcu();
+       put_device(&spdev->dev);
+
+       mutex_lock(&scif_info.conflock);
+       scif_info.total--;
+       mutex_unlock(&scif_info.conflock);
+       return ret;
+}
+
+void scif_add_peer_device(struct work_struct *work)
+{
+       struct scif_dev *scifdev = container_of(work, struct scif_dev,
+                                               peer_add_work);
+
+       scif_peer_add_device(scifdev);
+}
+
+/*
+ * Peer device registration is split into a device_initialize and a device_add.
+ * The reason for doing this is as follows: First, peer device registration
+ * itself cannot be done in the message processing thread and must be delegated
+ * to another workqueue, otherwise if SCIF client probe, called during peer
+ * device registration, calls scif_connect(..), it will block the message
+ * processing thread causing a deadlock. Next, device_initialize is done in the
+ * "top-half" message processing thread and device_add in the "bottom-half"
+ * workqueue. If this is not done, SCIF_CNCT_REQ message processing executing
+ * concurrently with SCIF_INIT message processing is unable to get a reference
+ * on the peer device, thereby failing the connect request.
+ */
+void scif_peer_register_device(struct scif_dev *scifdev)
+{
+       int ret;
+
+       mutex_lock(&scifdev->lock);
+       ret = scif_peer_initialize_device(scifdev);
+       if (ret)
+               goto exit;
+       schedule_work(&scifdev->peer_add_work);
+exit:
+       mutex_unlock(&scifdev->lock);
+}
+
+int scif_peer_unregister_device(struct scif_dev *scifdev)
+{
+       struct scif_peer_dev *spdev;
+
+       mutex_lock(&scifdev->lock);
+       /* Flush work to ensure device register is complete */
+       flush_work(&scifdev->peer_add_work);
+
+       /*
+        * Continue holding scifdev->lock since theoretically unregister_device
+        * can be called simultaneously from multiple threads
+        */
+       spdev = rcu_dereference(scifdev->spdev);
+       if (!spdev) {
+               mutex_unlock(&scifdev->lock);
+               return -ENODEV;
+       }
+
+       RCU_INIT_POINTER(scifdev->spdev, NULL);
+       synchronize_rcu();
+       mutex_unlock(&scifdev->lock);
+
+       dev_dbg(&spdev->dev, "Removing peer dnode %d\n", spdev->dnode);
+       device_unregister(&spdev->dev);
+
+       mutex_lock(&scif_info.conflock);
+       scif_info.total--;
+       mutex_unlock(&scif_info.conflock);
+       return 0;
+}
+
+int scif_peer_bus_init(void)
+{
+       return bus_register(&scif_peer_bus);
+}
+
+void scif_peer_bus_exit(void)
+{
+       bus_unregister(&scif_peer_bus);
+}