1 /*
2  * Intel MIC Platform Software Stack (MPSS)
3  *
4  * Copyright(c) 2014 Intel Corporation.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License, version 2, as
8  * published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * General Public License for more details.
14  *
15  * Intel SCIF driver.
16  */
17 #include "scif_main.h"
18 #include "../bus/scif_bus.h"
19 #include "scif_peer_bus.h"
20 
21 static inline struct scif_peer_dev *
dev_to_scif_peer(struct device * dev)22 dev_to_scif_peer(struct device *dev)
23 {
24 	return container_of(dev, struct scif_peer_dev, dev);
25 }
26 
27 struct bus_type scif_peer_bus = {
28 	.name  = "scif_peer_bus",
29 };
30 
scif_peer_release_dev(struct device * d)31 static void scif_peer_release_dev(struct device *d)
32 {
33 	struct scif_peer_dev *sdev = dev_to_scif_peer(d);
34 	struct scif_dev *scifdev = &scif_dev[sdev->dnode];
35 
36 	scif_cleanup_scifdev(scifdev);
37 	kfree(sdev);
38 }
39 
scif_peer_initialize_device(struct scif_dev * scifdev)40 static int scif_peer_initialize_device(struct scif_dev *scifdev)
41 {
42 	struct scif_peer_dev *spdev;
43 	int ret;
44 
45 	spdev = kzalloc(sizeof(*spdev), GFP_KERNEL);
46 	if (!spdev) {
47 		ret = -ENOMEM;
48 		goto err;
49 	}
50 
51 	spdev->dev.parent = scifdev->sdev->dev.parent;
52 	spdev->dev.release = scif_peer_release_dev;
53 	spdev->dnode = scifdev->node;
54 	spdev->dev.bus = &scif_peer_bus;
55 	dev_set_name(&spdev->dev, "scif_peer-dev%u", spdev->dnode);
56 
57 	device_initialize(&spdev->dev);
58 	get_device(&spdev->dev);
59 	rcu_assign_pointer(scifdev->spdev, spdev);
60 
61 	mutex_lock(&scif_info.conflock);
62 	scif_info.total++;
63 	scif_info.maxid = max_t(u32, spdev->dnode, scif_info.maxid);
64 	mutex_unlock(&scif_info.conflock);
65 	return 0;
66 err:
67 	dev_err(&scifdev->sdev->dev,
68 		"dnode %d: initialize_device rc %d\n", scifdev->node, ret);
69 	return ret;
70 }
71 
scif_peer_add_device(struct scif_dev * scifdev)72 static int scif_peer_add_device(struct scif_dev *scifdev)
73 {
74 	struct scif_peer_dev *spdev = rcu_dereference(scifdev->spdev);
75 	char pool_name[16];
76 	int ret;
77 
78 	ret = device_add(&spdev->dev);
79 	put_device(&spdev->dev);
80 	if (ret) {
81 		dev_err(&scifdev->sdev->dev,
82 			"dnode %d: peer device_add failed\n", scifdev->node);
83 		goto put_spdev;
84 	}
85 
86 	scnprintf(pool_name, sizeof(pool_name), "scif-%d", spdev->dnode);
87 	scifdev->signal_pool = dmam_pool_create(pool_name, &scifdev->sdev->dev,
88 						sizeof(struct scif_status), 1,
89 						0);
90 	if (!scifdev->signal_pool) {
91 		dev_err(&scifdev->sdev->dev,
92 			"dnode %d: dmam_pool_create failed\n", scifdev->node);
93 		ret = -ENOMEM;
94 		goto del_spdev;
95 	}
96 	dev_dbg(&spdev->dev, "Added peer dnode %d\n", spdev->dnode);
97 	return 0;
98 del_spdev:
99 	device_del(&spdev->dev);
100 put_spdev:
101 	RCU_INIT_POINTER(scifdev->spdev, NULL);
102 	synchronize_rcu();
103 	put_device(&spdev->dev);
104 
105 	mutex_lock(&scif_info.conflock);
106 	scif_info.total--;
107 	mutex_unlock(&scif_info.conflock);
108 	return ret;
109 }
110 
scif_add_peer_device(struct work_struct * work)111 void scif_add_peer_device(struct work_struct *work)
112 {
113 	struct scif_dev *scifdev = container_of(work, struct scif_dev,
114 						peer_add_work);
115 
116 	scif_peer_add_device(scifdev);
117 }
118 
119 /*
120  * Peer device registration is split into a device_initialize and a device_add.
121  * The reason for doing this is as follows: First, peer device registration
122  * itself cannot be done in the message processing thread and must be delegated
123  * to another workqueue, otherwise if SCIF client probe, called during peer
124  * device registration, calls scif_connect(..), it will block the message
125  * processing thread causing a deadlock. Next, device_initialize is done in the
126  * "top-half" message processing thread and device_add in the "bottom-half"
127  * workqueue. If this is not done, SCIF_CNCT_REQ message processing executing
128  * concurrently with SCIF_INIT message processing is unable to get a reference
129  * on the peer device, thereby failing the connect request.
130  */
scif_peer_register_device(struct scif_dev * scifdev)131 void scif_peer_register_device(struct scif_dev *scifdev)
132 {
133 	int ret;
134 
135 	mutex_lock(&scifdev->lock);
136 	ret = scif_peer_initialize_device(scifdev);
137 	if (ret)
138 		goto exit;
139 	schedule_work(&scifdev->peer_add_work);
140 exit:
141 	mutex_unlock(&scifdev->lock);
142 }
143 
scif_peer_unregister_device(struct scif_dev * scifdev)144 int scif_peer_unregister_device(struct scif_dev *scifdev)
145 {
146 	struct scif_peer_dev *spdev;
147 
148 	mutex_lock(&scifdev->lock);
149 	/* Flush work to ensure device register is complete */
150 	flush_work(&scifdev->peer_add_work);
151 
152 	/*
153 	 * Continue holding scifdev->lock since theoretically unregister_device
154 	 * can be called simultaneously from multiple threads
155 	 */
156 	spdev = rcu_dereference(scifdev->spdev);
157 	if (!spdev) {
158 		mutex_unlock(&scifdev->lock);
159 		return -ENODEV;
160 	}
161 
162 	RCU_INIT_POINTER(scifdev->spdev, NULL);
163 	synchronize_rcu();
164 	mutex_unlock(&scifdev->lock);
165 
166 	dev_dbg(&spdev->dev, "Removing peer dnode %d\n", spdev->dnode);
167 	device_unregister(&spdev->dev);
168 
169 	mutex_lock(&scif_info.conflock);
170 	scif_info.total--;
171 	mutex_unlock(&scif_info.conflock);
172 	return 0;
173 }
174 
scif_peer_bus_init(void)175 int scif_peer_bus_init(void)
176 {
177 	return bus_register(&scif_peer_bus);
178 }
179 
scif_peer_bus_exit(void)180 void scif_peer_bus_exit(void)
181 {
182 	bus_unregister(&scif_peer_bus);
183 }
184