Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  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		u8 state = cdev->state == MIC_RESETTING ?
157					cdev->prev_state : cdev->state;
158		bool call_hw_ops = state != MIC_RESET_FAILED &&
159					state != MIC_READY;
160
161		if (cdev->state != MIC_RESETTING)
162			cosm_set_state(cdev, MIC_RESETTING);
163		cdev->heartbeat_watchdog_enable = false;
164		if (call_hw_ops)
165			cdev->hw_ops->stop(cdev, force);
166		cosm_hw_reset(cdev, force);
167		cosm_set_shutdown_status(cdev, MIC_NOP);
168		if (call_hw_ops && cdev->hw_ops->post_reset)
169			cdev->hw_ops->post_reset(cdev, cdev->state);
170	}
171	mutex_unlock(&cdev->cosm_mutex);
172	flush_work(&cdev->scif_work);
173}
174
175/**
176 * cosm_reset_trigger_work - Trigger MIC reset
177 * @work: The work structure
178 *
179 * This work is scheduled whenever the host wants to reset the MIC.
180 */
181static void cosm_reset_trigger_work(struct work_struct *work)
182{
183	struct cosm_device *cdev = container_of(work, struct cosm_device,
184						reset_trigger_work);
185	cosm_stop(cdev, false);
186}
187
188/**
189 * cosm_reset - Schedule MIC reset
190 * @cdev: pointer to cosm_device instance
191 *
192 * RETURNS: An -EINVAL if the card is already READY or 0 for success.
193 */
194int cosm_reset(struct cosm_device *cdev)
195{
196	int rc = 0;
197
198	mutex_lock(&cdev->cosm_mutex);
199	if (cdev->state != MIC_READY) {
200		if (cdev->state != MIC_RESETTING) {
201			cdev->prev_state = cdev->state;
202			cosm_set_state(cdev, MIC_RESETTING);
203			schedule_work(&cdev->reset_trigger_work);
204		}
205	} else {
206		dev_err(&cdev->dev, "%s %d MIC is READY\n", __func__, __LINE__);
207		rc = -EINVAL;
208	}
209	mutex_unlock(&cdev->cosm_mutex);
210	return rc;
211}
212
213/**
214 * cosm_shutdown - Initiate MIC shutdown.
215 * @cdev: pointer to cosm_device instance
216 *
217 * RETURNS: None
218 */
219int cosm_shutdown(struct cosm_device *cdev)
220{
221	struct cosm_msg msg = { .id = COSM_MSG_SHUTDOWN };
222	int rc = 0;
223
224	mutex_lock(&cdev->cosm_mutex);
225	if (cdev->state != MIC_ONLINE) {
226		rc = -EINVAL;
227		dev_err(&cdev->dev, "%s %d skipping shutdown in state: %s\n",
228			__func__, __LINE__, cosm_state_string[cdev->state]);
229		goto err;
230	}
231
232	if (!cdev->epd) {
233		rc = -ENOTCONN;
234		dev_err(&cdev->dev, "%s %d scif endpoint not connected rc %d\n",
235			__func__, __LINE__, rc);
236		goto err;
237	}
238
239	rc = scif_send(cdev->epd, &msg, sizeof(msg), SCIF_SEND_BLOCK);
240	if (rc < 0) {
241		dev_err(&cdev->dev, "%s %d scif_send failed rc %d\n",
242			__func__, __LINE__, rc);
243		goto err;
244	}
245	cdev->heartbeat_watchdog_enable = false;
246	cosm_set_state(cdev, MIC_SHUTTING_DOWN);
247	rc = 0;
248err:
249	mutex_unlock(&cdev->cosm_mutex);
250	return rc;
251}
252
253static int cosm_driver_probe(struct cosm_device *cdev)
254{
255	int rc;
256
257	/* Initialize SCIF server at first probe */
258	if (atomic_add_return(1, &g_num_dev) == 1) {
259		rc = cosm_scif_init();
260		if (rc)
261			goto scif_exit;
262	}
263	mutex_init(&cdev->cosm_mutex);
264	INIT_WORK(&cdev->reset_trigger_work, cosm_reset_trigger_work);
265	INIT_WORK(&cdev->scif_work, cosm_scif_work);
266	cdev->sysfs_heartbeat_enable = true;
267	cosm_sysfs_init(cdev);
268	cdev->sdev = device_create_with_groups(g_cosm_class, cdev->dev.parent,
269			       MKDEV(0, cdev->index), cdev, cdev->attr_group,
270			       "mic%d", cdev->index);
271	if (IS_ERR(cdev->sdev)) {
272		rc = PTR_ERR(cdev->sdev);
273		dev_err(&cdev->dev, "device_create_with_groups failed rc %d\n",
274			rc);
275		goto scif_exit;
276	}
277
278	cdev->state_sysfs = sysfs_get_dirent(cdev->sdev->kobj.sd,
279		"state");
280	if (!cdev->state_sysfs) {
281		rc = -ENODEV;
282		dev_err(&cdev->dev, "sysfs_get_dirent failed rc %d\n", rc);
283		goto destroy_device;
284	}
285	cosm_create_debug_dir(cdev);
286	return 0;
287destroy_device:
288	device_destroy(g_cosm_class, MKDEV(0, cdev->index));
289scif_exit:
290	if (atomic_dec_and_test(&g_num_dev))
291		cosm_scif_exit();
292	return rc;
293}
294
295static void cosm_driver_remove(struct cosm_device *cdev)
296{
297	cosm_delete_debug_dir(cdev);
298	sysfs_put(cdev->state_sysfs);
299	device_destroy(g_cosm_class, MKDEV(0, cdev->index));
300	flush_work(&cdev->reset_trigger_work);
301	cosm_stop(cdev, false);
302	if (atomic_dec_and_test(&g_num_dev))
303		cosm_scif_exit();
304
305	/* These sysfs entries might have allocated */
306	kfree(cdev->cmdline);
307	kfree(cdev->firmware);
308	kfree(cdev->ramdisk);
309	kfree(cdev->bootmode);
310}
311
312static int cosm_suspend(struct device *dev)
313{
314	struct cosm_device *cdev = dev_to_cosm(dev);
315
316	mutex_lock(&cdev->cosm_mutex);
317	switch (cdev->state) {
318	/**
319	 * Suspend/freeze hooks in userspace have already shutdown the card.
320	 * Card should be 'ready' in most cases. It is however possible that
321	 * some userspace application initiated a boot. In those cases, we
322	 * simply reset the card.
323	 */
324	case MIC_ONLINE:
325	case MIC_BOOTING:
326	case MIC_SHUTTING_DOWN:
327		mutex_unlock(&cdev->cosm_mutex);
328		cosm_stop(cdev, false);
329		break;
330	default:
331		mutex_unlock(&cdev->cosm_mutex);
332		break;
333	}
334	return 0;
335}
336
337static const struct dev_pm_ops cosm_pm_ops = {
338	.suspend = cosm_suspend,
339	.freeze = cosm_suspend
340};
341
342static struct cosm_driver cosm_driver = {
343	.driver = {
344		.name =  KBUILD_MODNAME,
345		.owner = THIS_MODULE,
346		.pm = &cosm_pm_ops,
347	},
348	.probe = cosm_driver_probe,
349	.remove = cosm_driver_remove
350};
351
352static int __init cosm_init(void)
353{
354	int ret;
355
356	cosm_init_debugfs();
357
358	g_cosm_class = class_create(THIS_MODULE, cosm_driver_name);
359	if (IS_ERR(g_cosm_class)) {
360		ret = PTR_ERR(g_cosm_class);
361		pr_err("class_create failed ret %d\n", ret);
362		goto cleanup_debugfs;
363	}
364
365	ida_init(&g_cosm_ida);
366	ret = cosm_register_driver(&cosm_driver);
367	if (ret) {
368		pr_err("cosm_register_driver failed ret %d\n", ret);
369		goto ida_destroy;
370	}
371	return 0;
372ida_destroy:
373	ida_destroy(&g_cosm_ida);
374	class_destroy(g_cosm_class);
375cleanup_debugfs:
376	cosm_exit_debugfs();
377	return ret;
378}
379
380static void __exit cosm_exit(void)
381{
382	cosm_unregister_driver(&cosm_driver);
383	ida_destroy(&g_cosm_ida);
384	class_destroy(g_cosm_class);
385	cosm_exit_debugfs();
386}
387
388module_init(cosm_init);
389module_exit(cosm_exit);
390
391MODULE_AUTHOR("Intel Corporation");
392MODULE_DESCRIPTION("Intel(R) MIC Coprocessor State Management (COSM) Driver");
393MODULE_LICENSE("GPL v2");