|
@@ -24,93 +24,138 @@ dev_to_scif_peer(struct device *dev)
|
|
|
return container_of(dev, struct scif_peer_dev, dev);
|
|
|
}
|
|
|
|
|
|
-static inline struct scif_peer_driver *
|
|
|
-drv_to_scif_peer(struct device_driver *drv)
|
|
|
-{
|
|
|
- return container_of(drv, struct scif_peer_driver, driver);
|
|
|
-}
|
|
|
+struct bus_type scif_peer_bus = {
|
|
|
+ .name = "scif_peer_bus",
|
|
|
+};
|
|
|
|
|
|
-static int scif_peer_dev_match(struct device *dv, struct device_driver *dr)
|
|
|
+static void scif_peer_release_dev(struct device *d)
|
|
|
{
|
|
|
- return !strncmp(dev_name(dv), dr->name, 4);
|
|
|
+ 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_dev_probe(struct device *d)
|
|
|
+static int scif_peer_initialize_device(struct scif_dev *scifdev)
|
|
|
{
|
|
|
- struct scif_peer_dev *dev = dev_to_scif_peer(d);
|
|
|
- struct scif_peer_driver *drv = drv_to_scif_peer(dev->dev.driver);
|
|
|
+ struct scif_peer_dev *spdev;
|
|
|
+ int ret;
|
|
|
|
|
|
- return drv->probe(dev);
|
|
|
-}
|
|
|
+ spdev = kzalloc(sizeof(*spdev), GFP_KERNEL);
|
|
|
+ if (!spdev) {
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
|
|
|
-static int scif_peer_dev_remove(struct device *d)
|
|
|
-{
|
|
|
- struct scif_peer_dev *dev = dev_to_scif_peer(d);
|
|
|
- struct scif_peer_driver *drv = drv_to_scif_peer(dev->dev.driver);
|
|
|
+ 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);
|
|
|
|
|
|
- drv->remove(dev);
|
|
|
+ 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 struct bus_type scif_peer_bus = {
|
|
|
- .name = "scif_peer_bus",
|
|
|
- .match = scif_peer_dev_match,
|
|
|
- .probe = scif_peer_dev_probe,
|
|
|
- .remove = scif_peer_dev_remove,
|
|
|
-};
|
|
|
-
|
|
|
-int scif_peer_register_driver(struct scif_peer_driver *driver)
|
|
|
+static int scif_peer_add_device(struct scif_dev *scifdev)
|
|
|
{
|
|
|
- driver->driver.bus = &scif_peer_bus;
|
|
|
- return driver_register(&driver->driver);
|
|
|
+ struct scif_peer_dev *spdev = rcu_dereference(scifdev->spdev);
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ dev_dbg(&spdev->dev, "Added peer dnode %d\n", spdev->dnode);
|
|
|
+ return 0;
|
|
|
+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_peer_unregister_driver(struct scif_peer_driver *driver)
|
|
|
+void scif_add_peer_device(struct work_struct *work)
|
|
|
{
|
|
|
- driver_unregister(&driver->driver);
|
|
|
+ struct scif_dev *scifdev = container_of(work, struct scif_dev,
|
|
|
+ peer_add_work);
|
|
|
+
|
|
|
+ scif_peer_add_device(scifdev);
|
|
|
}
|
|
|
|
|
|
-static void scif_peer_release_dev(struct device *d)
|
|
|
+/*
|
|
|
+ * 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)
|
|
|
{
|
|
|
- struct scif_peer_dev *sdev = dev_to_scif_peer(d);
|
|
|
- struct scif_dev *scifdev = &scif_dev[sdev->dnode];
|
|
|
+ int ret;
|
|
|
|
|
|
- scif_cleanup_scifdev(scifdev);
|
|
|
- kfree(sdev);
|
|
|
+ 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);
|
|
|
}
|
|
|
|
|
|
-struct scif_peer_dev *
|
|
|
-scif_peer_register_device(struct scif_dev *scifdev)
|
|
|
+int scif_peer_unregister_device(struct scif_dev *scifdev)
|
|
|
{
|
|
|
- int ret;
|
|
|
struct scif_peer_dev *spdev;
|
|
|
|
|
|
- spdev = kzalloc(sizeof(*spdev), GFP_KERNEL);
|
|
|
- if (!spdev)
|
|
|
- return ERR_PTR(-ENOMEM);
|
|
|
-
|
|
|
- spdev->dev.parent = scifdev->sdev->dev.parent;
|
|
|
- spdev->dev.release = scif_peer_release_dev;
|
|
|
- spdev->dnode = scifdev->node;
|
|
|
- spdev->dev.bus = &scif_peer_bus;
|
|
|
+ mutex_lock(&scifdev->lock);
|
|
|
+ /* Flush work to ensure device register is complete */
|
|
|
+ flush_work(&scifdev->peer_add_work);
|
|
|
|
|
|
- dev_set_name(&spdev->dev, "scif_peer-dev%u", spdev->dnode);
|
|
|
/*
|
|
|
- * device_register() causes the bus infrastructure to look for a
|
|
|
- * matching driver.
|
|
|
+ * Continue holding scifdev->lock since theoretically unregister_device
|
|
|
+ * can be called simultaneously from multiple threads
|
|
|
*/
|
|
|
- ret = device_register(&spdev->dev);
|
|
|
- if (ret)
|
|
|
- goto free_spdev;
|
|
|
- return spdev;
|
|
|
-free_spdev:
|
|
|
- kfree(spdev);
|
|
|
- return ERR_PTR(ret);
|
|
|
-}
|
|
|
-
|
|
|
-void scif_peer_unregister_device(struct scif_peer_dev *sdev)
|
|
|
-{
|
|
|
- device_unregister(&sdev->dev);
|
|
|
+ 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)
|