// 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 */ #include #include #include #include #include #include #include #include #include #include #include #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, }; #define ICPU(model) { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, } static const struct x86_cpu_id isst_if_cpu_ids[] = { ICPU(INTEL_FAM6_SKYLAKE_X), {} }; 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");