| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * AMD Platform Management Framework Util Layer |
| * |
| * Copyright (c) 2026, Advanced Micro Devices, Inc. |
| * All Rights Reserved. |
| * |
| * Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com> |
| * Sanket Goswami <Sanket.Goswami@amd.com> |
| */ |
| |
| #include <linux/amd-pmf.h> |
| #include <linux/minmax.h> |
| #include <linux/miscdevice.h> |
| #include <linux/mutex.h> |
| #include <linux/uaccess.h> |
| |
| #include "pmf.h" |
| |
| static struct amd_pmf_dev *pmf_dev_handle; |
| static DEFINE_MUTEX(pmf_util_lock); |
| |
| static int amd_pmf_populate_data(struct amd_pmf_dev *pdev, struct amd_pmf_info *info) |
| { |
| struct ta_pmf_shared_memory *ta_sm = NULL; |
| struct ta_pmf_enact_table *in = NULL; |
| int idx; |
| |
| if (!pdev || !info) |
| return -EINVAL; |
| |
| if (!pdev->shbuf) |
| return -EINVAL; |
| |
| ta_sm = pdev->shbuf; |
| in = &ta_sm->pmf_input.enact_table; |
| |
| /* Set size */ |
| info->size = sizeof(*info); |
| |
| /* PMF Feature support flags */ |
| if (is_apmf_func_supported(pdev, APMF_FUNC_AUTO_MODE)) |
| info->features_supported |= AMD_PMF_FEAT_AUTO_MODE; |
| if (is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) |
| info->features_supported |= AMD_PMF_FEAT_STATIC_POWER_SLIDER; |
| if (pdev->smart_pc_enabled) |
| info->features_supported |= AMD_PMF_FEAT_POLICY_BUILDER; |
| if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_AC)) |
| info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC; |
| if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_DC)) |
| info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC; |
| |
| /* Device States */ |
| info->platform_type = in->ev_info.platform_type; |
| info->laptop_placement = in->ev_info.device_state; |
| info->lid_state = in->ev_info.lid_state; |
| info->user_presence = in->ev_info.user_present; |
| info->slider_position = in->ev_info.power_slider; |
| |
| /* Thermal and Power Metrics */ |
| info->power_source = in->ev_info.power_source; |
| info->skin_temp = in->ev_info.skin_temperature; |
| info->gfx_busy = in->ev_info.gfx_busy; |
| info->ambient_light = in->ev_info.ambient_light; |
| info->avg_c0_residency = in->ev_info.avg_c0residency; |
| info->max_c0_residency = in->ev_info.max_c0residency; |
| info->socket_power = in->ev_info.socket_power; |
| |
| /* Custom BIOS input parameters */ |
| for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++) |
| info->bios_input[idx] = amd_pmf_get_ta_custom_bios_inputs(in, idx); |
| |
| /* BIOS output parameters */ |
| for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++) |
| info->bios_output[idx] = pdev->bios_output[idx]; |
| |
| return 0; |
| } |
| |
| static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
| { |
| struct amd_pmf_dev *pdev = filp->private_data; |
| void __user *argp = (void __user *)arg; |
| struct amd_pmf_info info = {}; |
| size_t copy_size; |
| __u64 user_size; |
| int ret; |
| |
| if (cmd != IOCTL_AMD_PMF_POPULATE_DATA) |
| return -ENOTTY; |
| |
| /* First read just the size field from userspace */ |
| if (copy_from_user(&user_size, argp, sizeof(user_size))) |
| return -EFAULT; |
| |
| guard(mutex)(&pmf_util_lock); |
| ret = amd_pmf_populate_data(pdev, &info); |
| if (ret) |
| return ret; |
| |
| copy_size = min_t(size_t, user_size, sizeof(info)); |
| |
| /* Set actual size being copied */ |
| info.size = copy_size; |
| |
| if (copy_to_user(argp, &info, copy_size)) |
| return -EFAULT; |
| |
| return 0; |
| } |
| |
| static int amd_pmf_open(struct inode *inode, struct file *filp) |
| { |
| guard(mutex)(&pmf_util_lock); |
| if (!pmf_dev_handle) |
| return -ENODEV; |
| |
| filp->private_data = pmf_dev_handle; |
| return 0; |
| } |
| |
| static const struct file_operations pmf_if_ops = { |
| .owner = THIS_MODULE, |
| .open = amd_pmf_open, |
| .unlocked_ioctl = amd_pmf_set_ioctl, |
| }; |
| |
| static struct miscdevice amd_pmf_util_if = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "amdpmf_interface", |
| .fops = &pmf_if_ops, |
| }; |
| |
| int amd_pmf_cdev_register(struct amd_pmf_dev *dev) |
| { |
| int ret; |
| |
| guard(mutex)(&pmf_util_lock); |
| pmf_dev_handle = dev; |
| ret = misc_register(&amd_pmf_util_if); |
| if (ret) |
| pmf_dev_handle = NULL; |
| |
| return ret; |
| } |
| |
| void amd_pmf_cdev_unregister(void) |
| { |
| guard(mutex)(&pmf_util_lock); |
| if (pmf_dev_handle) |
| misc_deregister(&amd_pmf_util_if); |
| pmf_dev_handle = NULL; |
| } |