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