184 lines
4.8 KiB
C
184 lines
4.8 KiB
C
|
/*
|
||
|
* 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);
|
||
|
}
|