1 /*
2  * Intel MIC Platform Software Stack (MPSS)
3  *
4  * Copyright(c) 2015 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  * The full GNU General Public License is included in this distribution in
16  * the file called "COPYING".
17  *
18  * Intel MIC Coprocessor State Management (COSM) Driver
19  *
20  */
21 
22 #include <linux/module.h>
23 #include <linux/delay.h>
24 #include <linux/idr.h>
25 #include <linux/slab.h>
26 #include <linux/cred.h>
27 #include "cosm_main.h"
28 
29 static const char cosm_driver_name[] = "mic";
30 
31 /* COSM ID allocator */
32 static struct ida g_cosm_ida;
33 /* Class of MIC devices for sysfs accessibility. */
34 static struct class *g_cosm_class;
35 /* Number of MIC devices */
36 static atomic_t g_num_dev;
37 
38 /**
39  * cosm_hw_reset - Issue a HW reset for the MIC device
40  * @cdev: pointer to cosm_device instance
41  */
cosm_hw_reset(struct cosm_device * cdev,bool force)42 static void cosm_hw_reset(struct cosm_device *cdev, bool force)
43 {
44 	int i;
45 
46 #define MIC_RESET_TO (45)
47 	if (force && cdev->hw_ops->force_reset)
48 		cdev->hw_ops->force_reset(cdev);
49 	else
50 		cdev->hw_ops->reset(cdev);
51 
52 	for (i = 0; i < MIC_RESET_TO; i++) {
53 		if (cdev->hw_ops->ready(cdev)) {
54 			cosm_set_state(cdev, MIC_READY);
55 			return;
56 		}
57 		/*
58 		 * Resets typically take 10s of seconds to complete.
59 		 * Since an MMIO read is required to check if the
60 		 * firmware is ready or not, a 1 second delay works nicely.
61 		 */
62 		msleep(1000);
63 	}
64 	cosm_set_state(cdev, MIC_RESET_FAILED);
65 }
66 
67 /**
68  * cosm_start - Start the MIC
69  * @cdev: pointer to cosm_device instance
70  *
71  * This function prepares an MIC for boot and initiates boot.
72  * RETURNS: An appropriate -ERRNO error value on error, or 0 for success.
73  */
cosm_start(struct cosm_device * cdev)74 int cosm_start(struct cosm_device *cdev)
75 {
76 	const struct cred *orig_cred;
77 	struct cred *override_cred;
78 	int rc;
79 
80 	mutex_lock(&cdev->cosm_mutex);
81 	if (!cdev->bootmode) {
82 		dev_err(&cdev->dev, "%s %d bootmode not set\n",
83 			__func__, __LINE__);
84 		rc = -EINVAL;
85 		goto unlock_ret;
86 	}
87 retry:
88 	if (cdev->state != MIC_READY) {
89 		dev_err(&cdev->dev, "%s %d MIC state not READY\n",
90 			__func__, __LINE__);
91 		rc = -EINVAL;
92 		goto unlock_ret;
93 	}
94 	if (!cdev->hw_ops->ready(cdev)) {
95 		cosm_hw_reset(cdev, false);
96 		/*
97 		 * The state will either be MIC_READY if the reset succeeded
98 		 * or MIC_RESET_FAILED if the firmware reset failed.
99 		 */
100 		goto retry;
101 	}
102 
103 	/*
104 	 * Set credentials to root to allow non-root user to download initramsfs
105 	 * with 600 permissions
106 	 */
107 	override_cred = prepare_creds();
108 	if (!override_cred) {
109 		dev_err(&cdev->dev, "%s %d prepare_creds failed\n",
110 			__func__, __LINE__);
111 		rc = -ENOMEM;
112 		goto unlock_ret;
113 	}
114 	override_cred->fsuid = GLOBAL_ROOT_UID;
115 	orig_cred = override_creds(override_cred);
116 
117 	rc = cdev->hw_ops->start(cdev, cdev->index);
118 
119 	revert_creds(orig_cred);
120 	put_cred(override_cred);
121 	if (rc)
122 		goto unlock_ret;
123 
124 	/*
125 	 * If linux is being booted, card is treated 'online' only
126 	 * when the scif interface in the card is up. If anything else
127 	 * is booted, we set card to 'online' immediately.
128 	 */
129 	if (!strcmp(cdev->bootmode, "linux"))
130 		cosm_set_state(cdev, MIC_BOOTING);
131 	else
132 		cosm_set_state(cdev, MIC_ONLINE);
133 unlock_ret:
134 	mutex_unlock(&cdev->cosm_mutex);
135 	if (rc)
136 		dev_err(&cdev->dev, "cosm_start failed rc %d\n", rc);
137 	return rc;
138 }
139 
140 /**
141  * cosm_stop - Prepare the MIC for reset and trigger reset
142  * @cdev: pointer to cosm_device instance
143  * @force: force a MIC to reset even if it is already reset and ready.
144  *
145  * RETURNS: None
146  */
cosm_stop(struct cosm_device * cdev,bool force)147 void cosm_stop(struct cosm_device *cdev, bool force)
148 {
149 	mutex_lock(&cdev->cosm_mutex);
150 	if (cdev->state != MIC_READY || force) {
151 		/*
152 		 * Don't call hw_ops if they have been called previously.
153 		 * stop(..) calls device_unregister and will crash the system if
154 		 * called multiple times.
155 		 */
156 		bool call_hw_ops = cdev->state != MIC_RESET_FAILED &&
157 					cdev->state != MIC_READY;
158 
159 		if (cdev->state != MIC_RESETTING)
160 			cosm_set_state(cdev, MIC_RESETTING);
161 		cdev->heartbeat_watchdog_enable = false;
162 		if (call_hw_ops)
163 			cdev->hw_ops->stop(cdev, force);
164 		cosm_hw_reset(cdev, force);
165 		cosm_set_shutdown_status(cdev, MIC_NOP);
166 		if (call_hw_ops && cdev->hw_ops->post_reset)
167 			cdev->hw_ops->post_reset(cdev, cdev->state);
168 	}
169 	mutex_unlock(&cdev->cosm_mutex);
170 	flush_work(&cdev->scif_work);
171 }
172 
173 /**
174  * cosm_reset_trigger_work - Trigger MIC reset
175  * @work: The work structure
176  *
177  * This work is scheduled whenever the host wants to reset the MIC.
178  */
cosm_reset_trigger_work(struct work_struct * work)179 static void cosm_reset_trigger_work(struct work_struct *work)
180 {
181 	struct cosm_device *cdev = container_of(work, struct cosm_device,
182 						reset_trigger_work);
183 	cosm_stop(cdev, false);
184 }
185 
186 /**
187  * cosm_reset - Schedule MIC reset
188  * @cdev: pointer to cosm_device instance
189  *
190  * RETURNS: An -EINVAL if the card is already READY or 0 for success.
191  */
cosm_reset(struct cosm_device * cdev)192 int cosm_reset(struct cosm_device *cdev)
193 {
194 	int rc = 0;
195 
196 	mutex_lock(&cdev->cosm_mutex);
197 	if (cdev->state != MIC_READY) {
198 		cosm_set_state(cdev, MIC_RESETTING);
199 		schedule_work(&cdev->reset_trigger_work);
200 	} else {
201 		dev_err(&cdev->dev, "%s %d MIC is READY\n", __func__, __LINE__);
202 		rc = -EINVAL;
203 	}
204 	mutex_unlock(&cdev->cosm_mutex);
205 	return rc;
206 }
207 
208 /**
209  * cosm_shutdown - Initiate MIC shutdown.
210  * @cdev: pointer to cosm_device instance
211  *
212  * RETURNS: None
213  */
cosm_shutdown(struct cosm_device * cdev)214 int cosm_shutdown(struct cosm_device *cdev)
215 {
216 	struct cosm_msg msg = { .id = COSM_MSG_SHUTDOWN };
217 	int rc = 0;
218 
219 	mutex_lock(&cdev->cosm_mutex);
220 	if (cdev->state != MIC_ONLINE) {
221 		rc = -EINVAL;
222 		dev_err(&cdev->dev, "%s %d skipping shutdown in state: %s\n",
223 			__func__, __LINE__, cosm_state_string[cdev->state]);
224 		goto err;
225 	}
226 
227 	if (!cdev->epd) {
228 		rc = -ENOTCONN;
229 		dev_err(&cdev->dev, "%s %d scif endpoint not connected rc %d\n",
230 			__func__, __LINE__, rc);
231 		goto err;
232 	}
233 
234 	rc = scif_send(cdev->epd, &msg, sizeof(msg), SCIF_SEND_BLOCK);
235 	if (rc < 0) {
236 		dev_err(&cdev->dev, "%s %d scif_send failed rc %d\n",
237 			__func__, __LINE__, rc);
238 		goto err;
239 	}
240 	cdev->heartbeat_watchdog_enable = false;
241 	cosm_set_state(cdev, MIC_SHUTTING_DOWN);
242 	rc = 0;
243 err:
244 	mutex_unlock(&cdev->cosm_mutex);
245 	return rc;
246 }
247 
cosm_driver_probe(struct cosm_device * cdev)248 static int cosm_driver_probe(struct cosm_device *cdev)
249 {
250 	int rc;
251 
252 	/* Initialize SCIF server at first probe */
253 	if (atomic_add_return(1, &g_num_dev) == 1) {
254 		rc = cosm_scif_init();
255 		if (rc)
256 			goto scif_exit;
257 	}
258 	mutex_init(&cdev->cosm_mutex);
259 	INIT_WORK(&cdev->reset_trigger_work, cosm_reset_trigger_work);
260 	INIT_WORK(&cdev->scif_work, cosm_scif_work);
261 	cdev->sysfs_heartbeat_enable = true;
262 	cosm_sysfs_init(cdev);
263 	cdev->sdev = device_create_with_groups(g_cosm_class, cdev->dev.parent,
264 			       MKDEV(0, cdev->index), cdev, cdev->attr_group,
265 			       "mic%d", cdev->index);
266 	if (IS_ERR(cdev->sdev)) {
267 		rc = PTR_ERR(cdev->sdev);
268 		dev_err(&cdev->dev, "device_create_with_groups failed rc %d\n",
269 			rc);
270 		goto scif_exit;
271 	}
272 
273 	cdev->state_sysfs = sysfs_get_dirent(cdev->sdev->kobj.sd,
274 		"state");
275 	if (!cdev->state_sysfs) {
276 		rc = -ENODEV;
277 		dev_err(&cdev->dev, "sysfs_get_dirent failed rc %d\n", rc);
278 		goto destroy_device;
279 	}
280 	cosm_create_debug_dir(cdev);
281 	return 0;
282 destroy_device:
283 	device_destroy(g_cosm_class, MKDEV(0, cdev->index));
284 scif_exit:
285 	if (atomic_dec_and_test(&g_num_dev))
286 		cosm_scif_exit();
287 	return rc;
288 }
289 
cosm_driver_remove(struct cosm_device * cdev)290 static void cosm_driver_remove(struct cosm_device *cdev)
291 {
292 	cosm_delete_debug_dir(cdev);
293 	sysfs_put(cdev->state_sysfs);
294 	device_destroy(g_cosm_class, MKDEV(0, cdev->index));
295 	flush_work(&cdev->reset_trigger_work);
296 	cosm_stop(cdev, false);
297 	if (atomic_dec_and_test(&g_num_dev))
298 		cosm_scif_exit();
299 
300 	/* These sysfs entries might have allocated */
301 	kfree(cdev->cmdline);
302 	kfree(cdev->firmware);
303 	kfree(cdev->ramdisk);
304 	kfree(cdev->bootmode);
305 }
306 
cosm_suspend(struct device * dev)307 static int cosm_suspend(struct device *dev)
308 {
309 	struct cosm_device *cdev = dev_to_cosm(dev);
310 
311 	mutex_lock(&cdev->cosm_mutex);
312 	switch (cdev->state) {
313 	/**
314 	 * Suspend/freeze hooks in userspace have already shutdown the card.
315 	 * Card should be 'ready' in most cases. It is however possible that
316 	 * some userspace application initiated a boot. In those cases, we
317 	 * simply reset the card.
318 	 */
319 	case MIC_ONLINE:
320 	case MIC_BOOTING:
321 	case MIC_SHUTTING_DOWN:
322 		mutex_unlock(&cdev->cosm_mutex);
323 		cosm_stop(cdev, false);
324 		break;
325 	default:
326 		mutex_unlock(&cdev->cosm_mutex);
327 		break;
328 	}
329 	return 0;
330 }
331 
332 static const struct dev_pm_ops cosm_pm_ops = {
333 	.suspend = cosm_suspend,
334 	.freeze = cosm_suspend
335 };
336 
337 static struct cosm_driver cosm_driver = {
338 	.driver = {
339 		.name =  KBUILD_MODNAME,
340 		.owner = THIS_MODULE,
341 		.pm = &cosm_pm_ops,
342 	},
343 	.probe = cosm_driver_probe,
344 	.remove = cosm_driver_remove
345 };
346 
cosm_init(void)347 static int __init cosm_init(void)
348 {
349 	int ret;
350 
351 	cosm_init_debugfs();
352 
353 	g_cosm_class = class_create(THIS_MODULE, cosm_driver_name);
354 	if (IS_ERR(g_cosm_class)) {
355 		ret = PTR_ERR(g_cosm_class);
356 		pr_err("class_create failed ret %d\n", ret);
357 		goto cleanup_debugfs;
358 	}
359 
360 	ida_init(&g_cosm_ida);
361 	ret = cosm_register_driver(&cosm_driver);
362 	if (ret) {
363 		pr_err("cosm_register_driver failed ret %d\n", ret);
364 		goto ida_destroy;
365 	}
366 	return 0;
367 ida_destroy:
368 	ida_destroy(&g_cosm_ida);
369 	class_destroy(g_cosm_class);
370 cleanup_debugfs:
371 	cosm_exit_debugfs();
372 	return ret;
373 }
374 
cosm_exit(void)375 static void __exit cosm_exit(void)
376 {
377 	cosm_unregister_driver(&cosm_driver);
378 	ida_destroy(&g_cosm_ida);
379 	class_destroy(g_cosm_class);
380 	cosm_exit_debugfs();
381 }
382 
383 module_init(cosm_init);
384 module_exit(cosm_exit);
385 
386 MODULE_AUTHOR("Intel Corporation");
387 MODULE_DESCRIPTION("Intel(R) MIC Coprocessor State Management (COSM) Driver");
388 MODULE_LICENSE("GPL v2");
389