linux/drivers/platform/x86/intel_speed_select_if/isst_if_common.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Intel Speed Select Interface: Common functions
   4 * Copyright (c) 2019, Intel Corporation.
   5 * All rights reserved.
   6 *
   7 * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
   8 */
   9
  10#include <linux/cpufeature.h>
  11#include <linux/cpuhotplug.h>
  12#include <linux/fs.h>
  13#include <linux/hashtable.h>
  14#include <linux/miscdevice.h>
  15#include <linux/module.h>
  16#include <linux/pci.h>
  17#include <linux/sched/signal.h>
  18#include <linux/slab.h>
  19#include <linux/uaccess.h>
  20#include <uapi/linux/isst_if.h>
  21
  22#include "isst_if_common.h"
  23
  24#define MSR_THREAD_ID_INFO      0x53
  25#define MSR_CPU_BUS_NUMBER      0x128
  26
  27static struct isst_if_cmd_cb punit_callbacks[ISST_IF_DEV_MAX];
  28
  29static int punit_msr_white_list[] = {
  30        MSR_TURBO_RATIO_LIMIT,
  31        MSR_CONFIG_TDP_CONTROL,
  32        MSR_TURBO_RATIO_LIMIT1,
  33        MSR_TURBO_RATIO_LIMIT2,
  34};
  35
  36struct isst_valid_cmd_ranges {
  37        u16 cmd;
  38        u16 sub_cmd_beg;
  39        u16 sub_cmd_end;
  40};
  41
  42struct isst_cmd_set_req_type {
  43        u16 cmd;
  44        u16 sub_cmd;
  45        u16 param;
  46};
  47
  48static const struct isst_valid_cmd_ranges isst_valid_cmds[] = {
  49        {0xD0, 0x00, 0x03},
  50        {0x7F, 0x00, 0x0B},
  51        {0x7F, 0x10, 0x12},
  52        {0x7F, 0x20, 0x23},
  53        {0x94, 0x03, 0x03},
  54        {0x95, 0x03, 0x03},
  55};
  56
  57static const struct isst_cmd_set_req_type isst_cmd_set_reqs[] = {
  58        {0xD0, 0x00, 0x08},
  59        {0xD0, 0x01, 0x08},
  60        {0xD0, 0x02, 0x08},
  61        {0xD0, 0x03, 0x08},
  62        {0x7F, 0x02, 0x00},
  63        {0x7F, 0x08, 0x00},
  64        {0x95, 0x03, 0x03},
  65};
  66
  67struct isst_cmd {
  68        struct hlist_node hnode;
  69        u64 data;
  70        u32 cmd;
  71        int cpu;
  72        int mbox_cmd_type;
  73        u32 param;
  74};
  75
  76static DECLARE_HASHTABLE(isst_hash, 8);
  77static DEFINE_MUTEX(isst_hash_lock);
  78
  79static int isst_store_new_cmd(int cmd, u32 cpu, int mbox_cmd_type, u32 param,
  80                              u32 data)
  81{
  82        struct isst_cmd *sst_cmd;
  83
  84        sst_cmd = kmalloc(sizeof(*sst_cmd), GFP_KERNEL);
  85        if (!sst_cmd)
  86                return -ENOMEM;
  87
  88        sst_cmd->cpu = cpu;
  89        sst_cmd->cmd = cmd;
  90        sst_cmd->mbox_cmd_type = mbox_cmd_type;
  91        sst_cmd->param = param;
  92        sst_cmd->data = data;
  93
  94        hash_add(isst_hash, &sst_cmd->hnode, sst_cmd->cmd);
  95
  96        return 0;
  97}
  98
  99static void isst_delete_hash(void)
 100{
 101        struct isst_cmd *sst_cmd;
 102        struct hlist_node *tmp;
 103        int i;
 104
 105        hash_for_each_safe(isst_hash, i, tmp, sst_cmd, hnode) {
 106                hash_del(&sst_cmd->hnode);
 107                kfree(sst_cmd);
 108        }
 109}
 110
 111/**
 112 * isst_store_cmd() - Store command to a hash table
 113 * @cmd: Mailbox command.
 114 * @sub_cmd: Mailbox sub-command or MSR id.
 115 * @mbox_cmd_type: Mailbox or MSR command.
 116 * @param: Mailbox parameter.
 117 * @data: Mailbox request data or MSR data.
 118 *
 119 * Stores the command to a hash table if there is no such command already
 120 * stored. If already stored update the latest parameter and data for the
 121 * command.
 122 *
 123 * Return: Return result of store to hash table, 0 for success, others for
 124 * failure.
 125 */
 126int isst_store_cmd(int cmd, int sub_cmd, u32 cpu, int mbox_cmd_type,
 127                   u32 param, u64 data)
 128{
 129        struct isst_cmd *sst_cmd;
 130        int full_cmd, ret;
 131
 132        full_cmd = (cmd & GENMASK_ULL(15, 0)) << 16;
 133        full_cmd |= (sub_cmd & GENMASK_ULL(15, 0));
 134        mutex_lock(&isst_hash_lock);
 135        hash_for_each_possible(isst_hash, sst_cmd, hnode, full_cmd) {
 136                if (sst_cmd->cmd == full_cmd && sst_cmd->cpu == cpu &&
 137                    sst_cmd->mbox_cmd_type == mbox_cmd_type) {
 138                        sst_cmd->param = param;
 139                        sst_cmd->data = data;
 140                        mutex_unlock(&isst_hash_lock);
 141                        return 0;
 142                }
 143        }
 144
 145        ret = isst_store_new_cmd(full_cmd, cpu, mbox_cmd_type, param, data);
 146        mutex_unlock(&isst_hash_lock);
 147
 148        return ret;
 149}
 150EXPORT_SYMBOL_GPL(isst_store_cmd);
 151
 152static void isst_mbox_resume_command(struct isst_if_cmd_cb *cb,
 153                                     struct isst_cmd *sst_cmd)
 154{
 155        struct isst_if_mbox_cmd mbox_cmd;
 156        int wr_only;
 157
 158        mbox_cmd.command = (sst_cmd->cmd & GENMASK_ULL(31, 16)) >> 16;
 159        mbox_cmd.sub_command = sst_cmd->cmd & GENMASK_ULL(15, 0);
 160        mbox_cmd.parameter = sst_cmd->param;
 161        mbox_cmd.req_data = sst_cmd->data;
 162        mbox_cmd.logical_cpu = sst_cmd->cpu;
 163        (cb->cmd_callback)((u8 *)&mbox_cmd, &wr_only, 1);
 164}
 165
 166/**
 167 * isst_resume_common() - Process Resume request
 168 *
 169 * On resume replay all mailbox commands and MSRs.
 170 *
 171 * Return: None.
 172 */
 173void isst_resume_common(void)
 174{
 175        struct isst_cmd *sst_cmd;
 176        int i;
 177
 178        hash_for_each(isst_hash, i, sst_cmd, hnode) {
 179                struct isst_if_cmd_cb *cb;
 180
 181                if (sst_cmd->mbox_cmd_type) {
 182                        cb = &punit_callbacks[ISST_IF_DEV_MBOX];
 183                        if (cb->registered)
 184                                isst_mbox_resume_command(cb, sst_cmd);
 185                } else {
 186                        wrmsrl_safe_on_cpu(sst_cmd->cpu, sst_cmd->cmd,
 187                                           sst_cmd->data);
 188                }
 189        }
 190}
 191EXPORT_SYMBOL_GPL(isst_resume_common);
 192
 193static void isst_restore_msr_local(int cpu)
 194{
 195        struct isst_cmd *sst_cmd;
 196        int i;
 197
 198        mutex_lock(&isst_hash_lock);
 199        for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) {
 200                if (!punit_msr_white_list[i])
 201                        break;
 202
 203                hash_for_each_possible(isst_hash, sst_cmd, hnode,
 204                                       punit_msr_white_list[i]) {
 205                        if (!sst_cmd->mbox_cmd_type && sst_cmd->cpu == cpu)
 206                                wrmsrl_safe(sst_cmd->cmd, sst_cmd->data);
 207                }
 208        }
 209        mutex_unlock(&isst_hash_lock);
 210}
 211
 212/**
 213 * isst_if_mbox_cmd_invalid() - Check invalid mailbox commands
 214 * @cmd: Pointer to the command structure to verify.
 215 *
 216 * Invalid command to PUNIT to may result in instability of the platform.
 217 * This function has a whitelist of commands, which are allowed.
 218 *
 219 * Return: Return true if the command is invalid, else false.
 220 */
 221bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd)
 222{
 223        int i;
 224
 225        if (cmd->logical_cpu >= nr_cpu_ids)
 226                return true;
 227
 228        for (i = 0; i < ARRAY_SIZE(isst_valid_cmds); ++i) {
 229                if (cmd->command == isst_valid_cmds[i].cmd &&
 230                    (cmd->sub_command >= isst_valid_cmds[i].sub_cmd_beg &&
 231                     cmd->sub_command <= isst_valid_cmds[i].sub_cmd_end)) {
 232                        return false;
 233                }
 234        }
 235
 236        return true;
 237}
 238EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_invalid);
 239
 240/**
 241 * isst_if_mbox_cmd_set_req() - Check mailbox command is a set request
 242 * @cmd: Pointer to the command structure to verify.
 243 *
 244 * Check if the given mail box level is set request and not a get request.
 245 *
 246 * Return: Return true if the command is set_req, else false.
 247 */
 248bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *cmd)
 249{
 250        int i;
 251
 252        for (i = 0; i < ARRAY_SIZE(isst_cmd_set_reqs); ++i) {
 253                if (cmd->command == isst_cmd_set_reqs[i].cmd &&
 254                    cmd->sub_command == isst_cmd_set_reqs[i].sub_cmd &&
 255                    cmd->parameter == isst_cmd_set_reqs[i].param) {
 256                        return true;
 257                }
 258        }
 259
 260        return false;
 261}
 262EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_set_req);
 263
 264static int isst_if_get_platform_info(void __user *argp)
 265{
 266        struct isst_if_platform_info info;
 267
 268        info.api_version = ISST_IF_API_VERSION,
 269        info.driver_version = ISST_IF_DRIVER_VERSION,
 270        info.max_cmds_per_ioctl = ISST_IF_CMD_LIMIT,
 271        info.mbox_supported = punit_callbacks[ISST_IF_DEV_MBOX].registered;
 272        info.mmio_supported = punit_callbacks[ISST_IF_DEV_MMIO].registered;
 273
 274        if (copy_to_user(argp, &info, sizeof(info)))
 275                return -EFAULT;
 276
 277        return 0;
 278}
 279
 280
 281struct isst_if_cpu_info {
 282        /* For BUS 0 and BUS 1 only, which we need for PUNIT interface */
 283        int bus_info[2];
 284        int punit_cpu_id;
 285};
 286
 287static struct isst_if_cpu_info *isst_cpu_info;
 288
 289/**
 290 * isst_if_get_pci_dev() - Get the PCI device instance for a CPU
 291 * @cpu: Logical CPU number.
 292 * @bus_number: The bus number assigned by the hardware.
 293 * @dev: The device number assigned by the hardware.
 294 * @fn: The function number assigned by the hardware.
 295 *
 296 * Using cached bus information, find out the PCI device for a bus number,
 297 * device and function.
 298 *
 299 * Return: Return pci_dev pointer or NULL.
 300 */
 301struct pci_dev *isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn)
 302{
 303        int bus_number;
 304
 305        if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids ||
 306            cpu >= num_possible_cpus())
 307                return NULL;
 308
 309        bus_number = isst_cpu_info[cpu].bus_info[bus_no];
 310        if (bus_number < 0)
 311                return NULL;
 312
 313        return pci_get_domain_bus_and_slot(0, bus_number, PCI_DEVFN(dev, fn));
 314}
 315EXPORT_SYMBOL_GPL(isst_if_get_pci_dev);
 316
 317static int isst_if_cpu_online(unsigned int cpu)
 318{
 319        u64 data;
 320        int ret;
 321
 322        ret = rdmsrl_safe(MSR_CPU_BUS_NUMBER, &data);
 323        if (ret) {
 324                /* This is not a fatal error on MSR mailbox only I/F */
 325                isst_cpu_info[cpu].bus_info[0] = -1;
 326                isst_cpu_info[cpu].bus_info[1] = -1;
 327        } else {
 328                isst_cpu_info[cpu].bus_info[0] = data & 0xff;
 329                isst_cpu_info[cpu].bus_info[1] = (data >> 8) & 0xff;
 330        }
 331
 332        ret = rdmsrl_safe(MSR_THREAD_ID_INFO, &data);
 333        if (ret) {
 334                isst_cpu_info[cpu].punit_cpu_id = -1;
 335                return ret;
 336        }
 337        isst_cpu_info[cpu].punit_cpu_id = data;
 338
 339        isst_restore_msr_local(cpu);
 340
 341        return 0;
 342}
 343
 344static int isst_if_online_id;
 345
 346static int isst_if_cpu_info_init(void)
 347{
 348        int ret;
 349
 350        isst_cpu_info = kcalloc(num_possible_cpus(),
 351                                sizeof(*isst_cpu_info),
 352                                GFP_KERNEL);
 353        if (!isst_cpu_info)
 354                return -ENOMEM;
 355
 356        ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
 357                                "platform/x86/isst-if:online",
 358                                isst_if_cpu_online, NULL);
 359        if (ret < 0) {
 360                kfree(isst_cpu_info);
 361                return ret;
 362        }
 363
 364        isst_if_online_id = ret;
 365
 366        return 0;
 367}
 368
 369static void isst_if_cpu_info_exit(void)
 370{
 371        cpuhp_remove_state(isst_if_online_id);
 372        kfree(isst_cpu_info);
 373};
 374
 375static long isst_if_proc_phyid_req(u8 *cmd_ptr, int *write_only, int resume)
 376{
 377        struct isst_if_cpu_map *cpu_map;
 378
 379        cpu_map = (struct isst_if_cpu_map *)cmd_ptr;
 380        if (cpu_map->logical_cpu >= nr_cpu_ids ||
 381            cpu_map->logical_cpu >= num_possible_cpus())
 382                return -EINVAL;
 383
 384        *write_only = 0;
 385        cpu_map->physical_cpu = isst_cpu_info[cpu_map->logical_cpu].punit_cpu_id;
 386
 387        return 0;
 388}
 389
 390static bool match_punit_msr_white_list(int msr)
 391{
 392        int i;
 393
 394        for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) {
 395                if (punit_msr_white_list[i] == msr)
 396                        return true;
 397        }
 398
 399        return false;
 400}
 401
 402static long isst_if_msr_cmd_req(u8 *cmd_ptr, int *write_only, int resume)
 403{
 404        struct isst_if_msr_cmd *msr_cmd;
 405        int ret;
 406
 407        msr_cmd = (struct isst_if_msr_cmd *)cmd_ptr;
 408
 409        if (!match_punit_msr_white_list(msr_cmd->msr))
 410                return -EINVAL;
 411
 412        if (msr_cmd->logical_cpu >= nr_cpu_ids)
 413                return -EINVAL;
 414
 415        if (msr_cmd->read_write) {
 416                if (!capable(CAP_SYS_ADMIN))
 417                        return -EPERM;
 418
 419                ret = wrmsrl_safe_on_cpu(msr_cmd->logical_cpu,
 420                                         msr_cmd->msr,
 421                                         msr_cmd->data);
 422                *write_only = 1;
 423                if (!ret && !resume)
 424                        ret = isst_store_cmd(0, msr_cmd->msr,
 425                                             msr_cmd->logical_cpu,
 426                                             0, 0, msr_cmd->data);
 427        } else {
 428                u64 data;
 429
 430                ret = rdmsrl_safe_on_cpu(msr_cmd->logical_cpu,
 431                                         msr_cmd->msr, &data);
 432                if (!ret) {
 433                        msr_cmd->data = data;
 434                        *write_only = 0;
 435                }
 436        }
 437
 438
 439        return ret;
 440}
 441
 442static long isst_if_exec_multi_cmd(void __user *argp, struct isst_if_cmd_cb *cb)
 443{
 444        unsigned char __user *ptr;
 445        u32 cmd_count;
 446        u8 *cmd_ptr;
 447        long ret;
 448        int i;
 449
 450        /* Each multi command has u32 command count as the first field */
 451        if (copy_from_user(&cmd_count, argp, sizeof(cmd_count)))
 452                return -EFAULT;
 453
 454        if (!cmd_count || cmd_count > ISST_IF_CMD_LIMIT)
 455                return -EINVAL;
 456
 457        cmd_ptr = kmalloc(cb->cmd_size, GFP_KERNEL);
 458        if (!cmd_ptr)
 459                return -ENOMEM;
 460
 461        /* cb->offset points to start of the command after the command count */
 462        ptr = argp + cb->offset;
 463
 464        for (i = 0; i < cmd_count; ++i) {
 465                int wr_only;
 466
 467                if (signal_pending(current)) {
 468                        ret = -EINTR;
 469                        break;
 470                }
 471
 472                if (copy_from_user(cmd_ptr, ptr, cb->cmd_size)) {
 473                        ret = -EFAULT;
 474                        break;
 475                }
 476
 477                ret = cb->cmd_callback(cmd_ptr, &wr_only, 0);
 478                if (ret)
 479                        break;
 480
 481                if (!wr_only && copy_to_user(ptr, cmd_ptr, cb->cmd_size)) {
 482                        ret = -EFAULT;
 483                        break;
 484                }
 485
 486                ptr += cb->cmd_size;
 487        }
 488
 489        kfree(cmd_ptr);
 490
 491        return i ? i : ret;
 492}
 493
 494static long isst_if_def_ioctl(struct file *file, unsigned int cmd,
 495                              unsigned long arg)
 496{
 497        void __user *argp = (void __user *)arg;
 498        struct isst_if_cmd_cb cmd_cb;
 499        struct isst_if_cmd_cb *cb;
 500        long ret = -ENOTTY;
 501
 502        switch (cmd) {
 503        case ISST_IF_GET_PLATFORM_INFO:
 504                ret = isst_if_get_platform_info(argp);
 505                break;
 506        case ISST_IF_GET_PHY_ID:
 507                cmd_cb.cmd_size = sizeof(struct isst_if_cpu_map);
 508                cmd_cb.offset = offsetof(struct isst_if_cpu_maps, cpu_map);
 509                cmd_cb.cmd_callback = isst_if_proc_phyid_req;
 510                ret = isst_if_exec_multi_cmd(argp, &cmd_cb);
 511                break;
 512        case ISST_IF_IO_CMD:
 513                cb = &punit_callbacks[ISST_IF_DEV_MMIO];
 514                if (cb->registered)
 515                        ret = isst_if_exec_multi_cmd(argp, cb);
 516                break;
 517        case ISST_IF_MBOX_COMMAND:
 518                cb = &punit_callbacks[ISST_IF_DEV_MBOX];
 519                if (cb->registered)
 520                        ret = isst_if_exec_multi_cmd(argp, cb);
 521                break;
 522        case ISST_IF_MSR_COMMAND:
 523                cmd_cb.cmd_size = sizeof(struct isst_if_msr_cmd);
 524                cmd_cb.offset = offsetof(struct isst_if_msr_cmds, msr_cmd);
 525                cmd_cb.cmd_callback = isst_if_msr_cmd_req;
 526                ret = isst_if_exec_multi_cmd(argp, &cmd_cb);
 527                break;
 528        default:
 529                break;
 530        }
 531
 532        return ret;
 533}
 534
 535static DEFINE_MUTEX(punit_misc_dev_lock);
 536static int misc_usage_count;
 537static int misc_device_ret;
 538static int misc_device_open;
 539
 540static int isst_if_open(struct inode *inode, struct file *file)
 541{
 542        int i, ret = 0;
 543
 544        /* Fail open, if a module is going away */
 545        mutex_lock(&punit_misc_dev_lock);
 546        for (i = 0; i < ISST_IF_DEV_MAX; ++i) {
 547                struct isst_if_cmd_cb *cb = &punit_callbacks[i];
 548
 549                if (cb->registered && !try_module_get(cb->owner)) {
 550                        ret = -ENODEV;
 551                        break;
 552                }
 553        }
 554        if (ret) {
 555                int j;
 556
 557                for (j = 0; j < i; ++j) {
 558                        struct isst_if_cmd_cb *cb;
 559
 560                        cb = &punit_callbacks[j];
 561                        if (cb->registered)
 562                                module_put(cb->owner);
 563                }
 564        } else {
 565                misc_device_open++;
 566        }
 567        mutex_unlock(&punit_misc_dev_lock);
 568
 569        return ret;
 570}
 571
 572static int isst_if_relase(struct inode *inode, struct file *f)
 573{
 574        int i;
 575
 576        mutex_lock(&punit_misc_dev_lock);
 577        misc_device_open--;
 578        for (i = 0; i < ISST_IF_DEV_MAX; ++i) {
 579                struct isst_if_cmd_cb *cb = &punit_callbacks[i];
 580
 581                if (cb->registered)
 582                        module_put(cb->owner);
 583        }
 584        mutex_unlock(&punit_misc_dev_lock);
 585
 586        return 0;
 587}
 588
 589static const struct file_operations isst_if_char_driver_ops = {
 590        .open = isst_if_open,
 591        .unlocked_ioctl = isst_if_def_ioctl,
 592        .release = isst_if_relase,
 593};
 594
 595static struct miscdevice isst_if_char_driver = {
 596        .minor          = MISC_DYNAMIC_MINOR,
 597        .name           = "isst_interface",
 598        .fops           = &isst_if_char_driver_ops,
 599};
 600
 601/**
 602 * isst_if_cdev_register() - Register callback for IOCTL
 603 * @device_type: The device type this callback handling.
 604 * @cb: Callback structure.
 605 *
 606 * This function registers a callback to device type. On very first call
 607 * it will register a misc device, which is used for user kernel interface.
 608 * Other calls simply increment ref count. Registry will fail, if the user
 609 * already opened misc device for operation. Also if the misc device
 610 * creation failed, then it will not try again and all callers will get
 611 * failure code.
 612 *
 613 * Return: Return the return value from the misc creation device or -EINVAL
 614 * for unsupported device type.
 615 */
 616int isst_if_cdev_register(int device_type, struct isst_if_cmd_cb *cb)
 617{
 618        if (misc_device_ret)
 619                return misc_device_ret;
 620
 621        if (device_type >= ISST_IF_DEV_MAX)
 622                return -EINVAL;
 623
 624        mutex_lock(&punit_misc_dev_lock);
 625        if (misc_device_open) {
 626                mutex_unlock(&punit_misc_dev_lock);
 627                return -EAGAIN;
 628        }
 629        if (!misc_usage_count) {
 630                int ret;
 631
 632                misc_device_ret = misc_register(&isst_if_char_driver);
 633                if (misc_device_ret)
 634                        goto unlock_exit;
 635
 636                ret = isst_if_cpu_info_init();
 637                if (ret) {
 638                        misc_deregister(&isst_if_char_driver);
 639                        misc_device_ret = ret;
 640                        goto unlock_exit;
 641                }
 642        }
 643        memcpy(&punit_callbacks[device_type], cb, sizeof(*cb));
 644        punit_callbacks[device_type].registered = 1;
 645        misc_usage_count++;
 646unlock_exit:
 647        mutex_unlock(&punit_misc_dev_lock);
 648
 649        return misc_device_ret;
 650}
 651EXPORT_SYMBOL_GPL(isst_if_cdev_register);
 652
 653/**
 654 * isst_if_cdev_unregister() - Unregister callback for IOCTL
 655 * @device_type: The device type to unregister.
 656 *
 657 * This function unregisters the previously registered callback. If this
 658 * is the last callback unregistering, then misc device is removed.
 659 *
 660 * Return: None.
 661 */
 662void isst_if_cdev_unregister(int device_type)
 663{
 664        mutex_lock(&punit_misc_dev_lock);
 665        misc_usage_count--;
 666        punit_callbacks[device_type].registered = 0;
 667        if (device_type == ISST_IF_DEV_MBOX)
 668                isst_delete_hash();
 669        if (!misc_usage_count && !misc_device_ret) {
 670                misc_deregister(&isst_if_char_driver);
 671                isst_if_cpu_info_exit();
 672        }
 673        mutex_unlock(&punit_misc_dev_lock);
 674}
 675EXPORT_SYMBOL_GPL(isst_if_cdev_unregister);
 676
 677MODULE_LICENSE("GPL v2");
 678