linux/drivers/misc/mic/cosm/cosm_main.c
<<
>>
Prefs
   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");
 394