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
29static const char cosm_driver_name[] = "mic";
30
31/* COSM ID allocator */
32static struct ida g_cosm_ida;
33/* Class of MIC devices for sysfs accessibility. */
34static struct class *g_cosm_class;
35/* Number of MIC devices */
36static 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 */
42static 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 */
74int 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	}
87retry:
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);
133unlock_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 */
147void 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 */
179static 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 */
192int 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 */
214int 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;
243err:
244	mutex_unlock(&cdev->cosm_mutex);
245	return rc;
246}
247
248static 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;
282destroy_device:
283	device_destroy(g_cosm_class, MKDEV(0, cdev->index));
284scif_exit:
285	if (atomic_dec_and_test(&g_num_dev))
286		cosm_scif_exit();
287	return rc;
288}
289
290static 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
307static 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
332static const struct dev_pm_ops cosm_pm_ops = {
333	.suspend = cosm_suspend,
334	.freeze = cosm_suspend
335};
336
337static 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
347static 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;
367ida_destroy:
368	ida_destroy(&g_cosm_ida);
369	class_destroy(g_cosm_class);
370cleanup_debugfs:
371	cosm_exit_debugfs();
372	return ret;
373}
374
375static 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
383module_init(cosm_init);
384module_exit(cosm_exit);
385
386MODULE_AUTHOR("Intel Corporation");
387MODULE_DESCRIPTION("Intel(R) MIC Coprocessor State Management (COSM) Driver");
388MODULE_LICENSE("GPL v2");
389