Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v4.10 393 lines 9.9 kB view raw
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");