| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Intel Speed Select Interface: Mbox via MSR Interface |
| * Copyright (c) 2019, Intel Corporation. |
| * All rights reserved. |
| * |
| * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/cpuhotplug.h> |
| #include <linux/pci.h> |
| #include <linux/sched/signal.h> |
| #include <linux/slab.h> |
| #include <linux/suspend.h> |
| #include <linux/topology.h> |
| #include <linux/uaccess.h> |
| #include <uapi/linux/isst_if.h> |
| #include <asm/cpu_device_id.h> |
| #include <asm/intel-family.h> |
| |
| #include "isst_if_common.h" |
| |
| #define MSR_OS_MAILBOX_INTERFACE 0xB0 |
| #define MSR_OS_MAILBOX_DATA 0xB1 |
| #define MSR_OS_MAILBOX_BUSY_BIT 31 |
| |
| /* |
| * Based on experiments count is never more than 1, as the MSR overhead |
| * is enough to finish the command. So here this is the worst case number. |
| */ |
| #define OS_MAILBOX_RETRY_COUNT 3 |
| |
| static int isst_if_send_mbox_cmd(u8 command, u8 sub_command, u32 parameter, |
| u32 command_data, u32 *response_data) |
| { |
| u32 retries; |
| u64 data; |
| int ret; |
| |
| /* Poll for rb bit == 0 */ |
| retries = OS_MAILBOX_RETRY_COUNT; |
| do { |
| rdmsrl(MSR_OS_MAILBOX_INTERFACE, data); |
| if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) { |
| ret = -EBUSY; |
| continue; |
| } |
| ret = 0; |
| break; |
| } while (--retries); |
| |
| if (ret) |
| return ret; |
| |
| /* Write DATA register */ |
| wrmsrl(MSR_OS_MAILBOX_DATA, command_data); |
| |
| /* Write command register */ |
| data = BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT) | |
| (parameter & GENMASK_ULL(13, 0)) << 16 | |
| (sub_command << 8) | |
| command; |
| wrmsrl(MSR_OS_MAILBOX_INTERFACE, data); |
| |
| /* Poll for rb bit == 0 */ |
| retries = OS_MAILBOX_RETRY_COUNT; |
| do { |
| rdmsrl(MSR_OS_MAILBOX_INTERFACE, data); |
| if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) { |
| ret = -EBUSY; |
| continue; |
| } |
| |
| if (data & 0xff) |
| return -ENXIO; |
| |
| if (response_data) { |
| rdmsrl(MSR_OS_MAILBOX_DATA, data); |
| *response_data = data; |
| } |
| ret = 0; |
| break; |
| } while (--retries); |
| |
| return ret; |
| } |
| |
| struct msrl_action { |
| int err; |
| struct isst_if_mbox_cmd *mbox_cmd; |
| }; |
| |
| /* revisit, smp_call_function_single should be enough for atomic mailbox! */ |
| static void msrl_update_func(void *info) |
| { |
| struct msrl_action *act = info; |
| |
| act->err = isst_if_send_mbox_cmd(act->mbox_cmd->command, |
| act->mbox_cmd->sub_command, |
| act->mbox_cmd->parameter, |
| act->mbox_cmd->req_data, |
| &act->mbox_cmd->resp_data); |
| } |
| |
| static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume) |
| { |
| struct msrl_action action; |
| int ret; |
| |
| action.mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr; |
| |
| if (isst_if_mbox_cmd_invalid(action.mbox_cmd)) |
| return -EINVAL; |
| |
| if (isst_if_mbox_cmd_set_req(action.mbox_cmd) && |
| !capable(CAP_SYS_ADMIN)) |
| return -EPERM; |
| |
| /* |
| * To complete mailbox command, we need to access two MSRs. |
| * So we don't want race to complete a mailbox transcation. |
| * Here smp_call ensures that msrl_update_func() has no race |
| * and also with wait flag, wait for completion. |
| * smp_call_function_single is using get_cpu() and put_cpu(). |
| */ |
| ret = smp_call_function_single(action.mbox_cmd->logical_cpu, |
| msrl_update_func, &action, 1); |
| if (ret) |
| return ret; |
| |
| if (!action.err && !resume && isst_if_mbox_cmd_set_req(action.mbox_cmd)) |
| action.err = isst_store_cmd(action.mbox_cmd->command, |
| action.mbox_cmd->sub_command, |
| action.mbox_cmd->logical_cpu, 1, |
| action.mbox_cmd->parameter, |
| action.mbox_cmd->req_data); |
| *write_only = 0; |
| |
| return action.err; |
| } |
| |
| |
| static int isst_pm_notify(struct notifier_block *nb, |
| unsigned long mode, void *_unused) |
| { |
| switch (mode) { |
| case PM_POST_HIBERNATION: |
| case PM_POST_RESTORE: |
| case PM_POST_SUSPEND: |
| isst_resume_common(); |
| break; |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| static struct notifier_block isst_pm_nb = { |
| .notifier_call = isst_pm_notify, |
| }; |
| |
| static const struct x86_cpu_id isst_if_cpu_ids[] = { |
| X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL), |
| {} |
| }; |
| MODULE_DEVICE_TABLE(x86cpu, isst_if_cpu_ids); |
| |
| static int __init isst_if_mbox_init(void) |
| { |
| struct isst_if_cmd_cb cb; |
| const struct x86_cpu_id *id; |
| u64 data; |
| int ret; |
| |
| id = x86_match_cpu(isst_if_cpu_ids); |
| if (!id) |
| return -ENODEV; |
| |
| /* Check presence of mailbox MSRs */ |
| ret = rdmsrl_safe(MSR_OS_MAILBOX_INTERFACE, &data); |
| if (ret) |
| return ret; |
| |
| ret = rdmsrl_safe(MSR_OS_MAILBOX_DATA, &data); |
| if (ret) |
| return ret; |
| |
| memset(&cb, 0, sizeof(cb)); |
| cb.cmd_size = sizeof(struct isst_if_mbox_cmd); |
| cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd); |
| cb.cmd_callback = isst_if_mbox_proc_cmd; |
| cb.owner = THIS_MODULE; |
| ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb); |
| if (ret) |
| return ret; |
| |
| ret = register_pm_notifier(&isst_pm_nb); |
| if (ret) |
| isst_if_cdev_unregister(ISST_IF_DEV_MBOX); |
| |
| return ret; |
| } |
| module_init(isst_if_mbox_init) |
| |
| static void __exit isst_if_mbox_exit(void) |
| { |
| unregister_pm_notifier(&isst_pm_nb); |
| isst_if_cdev_unregister(ISST_IF_DEV_MBOX); |
| } |
| module_exit(isst_if_mbox_exit) |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("Intel speed select interface mailbox driver"); |