|  | /* | 
|  | * 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); | 
|  | } |