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