162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * AMD Secure Processor Dynamic Boost Control interface 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2023 Advanced Micro Devices, Inc. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: Mario Limonciello <mario.limonciello@amd.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include "dbc.h" 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_cistruct error_map { 1362306a36Sopenharmony_ci u32 psp; 1462306a36Sopenharmony_ci int ret; 1562306a36Sopenharmony_ci}; 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define DBC_ERROR_ACCESS_DENIED 0x0001 1862306a36Sopenharmony_ci#define DBC_ERROR_EXCESS_DATA 0x0004 1962306a36Sopenharmony_ci#define DBC_ERROR_BAD_PARAMETERS 0x0006 2062306a36Sopenharmony_ci#define DBC_ERROR_BAD_STATE 0x0007 2162306a36Sopenharmony_ci#define DBC_ERROR_NOT_IMPLEMENTED 0x0009 2262306a36Sopenharmony_ci#define DBC_ERROR_BUSY 0x000D 2362306a36Sopenharmony_ci#define DBC_ERROR_MESSAGE_FAILURE 0x0307 2462306a36Sopenharmony_ci#define DBC_ERROR_OVERFLOW 0x300F 2562306a36Sopenharmony_ci#define DBC_ERROR_SIGNATURE_INVALID 0x3072 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistatic struct error_map error_codes[] = { 2862306a36Sopenharmony_ci {DBC_ERROR_ACCESS_DENIED, -EACCES}, 2962306a36Sopenharmony_ci {DBC_ERROR_EXCESS_DATA, -E2BIG}, 3062306a36Sopenharmony_ci {DBC_ERROR_BAD_PARAMETERS, -EINVAL}, 3162306a36Sopenharmony_ci {DBC_ERROR_BAD_STATE, -EAGAIN}, 3262306a36Sopenharmony_ci {DBC_ERROR_MESSAGE_FAILURE, -ENOENT}, 3362306a36Sopenharmony_ci {DBC_ERROR_NOT_IMPLEMENTED, -ENOENT}, 3462306a36Sopenharmony_ci {DBC_ERROR_BUSY, -EBUSY}, 3562306a36Sopenharmony_ci {DBC_ERROR_OVERFLOW, -ENFILE}, 3662306a36Sopenharmony_ci {DBC_ERROR_SIGNATURE_INVALID, -EPERM}, 3762306a36Sopenharmony_ci {0x0, 0x0}, 3862306a36Sopenharmony_ci}; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic int send_dbc_cmd(struct psp_dbc_device *dbc_dev, 4162306a36Sopenharmony_ci enum psp_platform_access_msg msg) 4262306a36Sopenharmony_ci{ 4362306a36Sopenharmony_ci int ret; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci dbc_dev->mbox->req.header.status = 0; 4662306a36Sopenharmony_ci ret = psp_send_platform_access_msg(msg, (struct psp_request *)dbc_dev->mbox); 4762306a36Sopenharmony_ci if (ret == -EIO) { 4862306a36Sopenharmony_ci int i; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci dev_dbg(dbc_dev->dev, 5162306a36Sopenharmony_ci "msg 0x%x failed with PSP error: 0x%x\n", 5262306a36Sopenharmony_ci msg, dbc_dev->mbox->req.header.status); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci for (i = 0; error_codes[i].psp; i++) { 5562306a36Sopenharmony_ci if (dbc_dev->mbox->req.header.status == error_codes[i].psp) 5662306a36Sopenharmony_ci return error_codes[i].ret; 5762306a36Sopenharmony_ci } 5862306a36Sopenharmony_ci } 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci return ret; 6162306a36Sopenharmony_ci} 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistatic int send_dbc_nonce(struct psp_dbc_device *dbc_dev) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci int ret; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci dbc_dev->mbox->req.header.payload_size = sizeof(dbc_dev->mbox->dbc_nonce); 6862306a36Sopenharmony_ci ret = send_dbc_cmd(dbc_dev, PSP_DYNAMIC_BOOST_GET_NONCE); 6962306a36Sopenharmony_ci if (ret == -EAGAIN) { 7062306a36Sopenharmony_ci dev_dbg(dbc_dev->dev, "retrying get nonce\n"); 7162306a36Sopenharmony_ci ret = send_dbc_cmd(dbc_dev, PSP_DYNAMIC_BOOST_GET_NONCE); 7262306a36Sopenharmony_ci } 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci return ret; 7562306a36Sopenharmony_ci} 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic int send_dbc_parameter(struct psp_dbc_device *dbc_dev) 7862306a36Sopenharmony_ci{ 7962306a36Sopenharmony_ci dbc_dev->mbox->req.header.payload_size = sizeof(dbc_dev->mbox->dbc_param); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci switch (dbc_dev->mbox->dbc_param.user.msg_index) { 8262306a36Sopenharmony_ci case PARAM_SET_FMAX_CAP: 8362306a36Sopenharmony_ci case PARAM_SET_PWR_CAP: 8462306a36Sopenharmony_ci case PARAM_SET_GFX_MODE: 8562306a36Sopenharmony_ci return send_dbc_cmd(dbc_dev, PSP_DYNAMIC_BOOST_SET_PARAMETER); 8662306a36Sopenharmony_ci case PARAM_GET_FMAX_CAP: 8762306a36Sopenharmony_ci case PARAM_GET_PWR_CAP: 8862306a36Sopenharmony_ci case PARAM_GET_CURR_TEMP: 8962306a36Sopenharmony_ci case PARAM_GET_FMAX_MAX: 9062306a36Sopenharmony_ci case PARAM_GET_FMAX_MIN: 9162306a36Sopenharmony_ci case PARAM_GET_SOC_PWR_MAX: 9262306a36Sopenharmony_ci case PARAM_GET_SOC_PWR_MIN: 9362306a36Sopenharmony_ci case PARAM_GET_SOC_PWR_CUR: 9462306a36Sopenharmony_ci case PARAM_GET_GFX_MODE: 9562306a36Sopenharmony_ci return send_dbc_cmd(dbc_dev, PSP_DYNAMIC_BOOST_GET_PARAMETER); 9662306a36Sopenharmony_ci } 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci return -EINVAL; 9962306a36Sopenharmony_ci} 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_civoid dbc_dev_destroy(struct psp_device *psp) 10262306a36Sopenharmony_ci{ 10362306a36Sopenharmony_ci struct psp_dbc_device *dbc_dev = psp->dbc_data; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci if (!dbc_dev) 10662306a36Sopenharmony_ci return; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci misc_deregister(&dbc_dev->char_dev); 10962306a36Sopenharmony_ci mutex_destroy(&dbc_dev->ioctl_mutex); 11062306a36Sopenharmony_ci psp->dbc_data = NULL; 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_cistatic long dbc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci struct psp_device *psp_master = psp_get_master_device(); 11662306a36Sopenharmony_ci void __user *argp = (void __user *)arg; 11762306a36Sopenharmony_ci struct psp_dbc_device *dbc_dev; 11862306a36Sopenharmony_ci int ret; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci if (!psp_master || !psp_master->dbc_data) 12162306a36Sopenharmony_ci return -ENODEV; 12262306a36Sopenharmony_ci dbc_dev = psp_master->dbc_data; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci mutex_lock(&dbc_dev->ioctl_mutex); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci switch (cmd) { 12762306a36Sopenharmony_ci case DBCIOCNONCE: 12862306a36Sopenharmony_ci if (copy_from_user(&dbc_dev->mbox->dbc_nonce.user, argp, 12962306a36Sopenharmony_ci sizeof(struct dbc_user_nonce))) { 13062306a36Sopenharmony_ci ret = -EFAULT; 13162306a36Sopenharmony_ci goto unlock; 13262306a36Sopenharmony_ci } 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci ret = send_dbc_nonce(dbc_dev); 13562306a36Sopenharmony_ci if (ret) 13662306a36Sopenharmony_ci goto unlock; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci if (copy_to_user(argp, &dbc_dev->mbox->dbc_nonce.user, 13962306a36Sopenharmony_ci sizeof(struct dbc_user_nonce))) { 14062306a36Sopenharmony_ci ret = -EFAULT; 14162306a36Sopenharmony_ci goto unlock; 14262306a36Sopenharmony_ci } 14362306a36Sopenharmony_ci break; 14462306a36Sopenharmony_ci case DBCIOCUID: 14562306a36Sopenharmony_ci dbc_dev->mbox->req.header.payload_size = sizeof(dbc_dev->mbox->dbc_set_uid); 14662306a36Sopenharmony_ci if (copy_from_user(&dbc_dev->mbox->dbc_set_uid.user, argp, 14762306a36Sopenharmony_ci sizeof(struct dbc_user_setuid))) { 14862306a36Sopenharmony_ci ret = -EFAULT; 14962306a36Sopenharmony_ci goto unlock; 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci ret = send_dbc_cmd(dbc_dev, PSP_DYNAMIC_BOOST_SET_UID); 15362306a36Sopenharmony_ci if (ret) 15462306a36Sopenharmony_ci goto unlock; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci if (copy_to_user(argp, &dbc_dev->mbox->dbc_set_uid.user, 15762306a36Sopenharmony_ci sizeof(struct dbc_user_setuid))) { 15862306a36Sopenharmony_ci ret = -EFAULT; 15962306a36Sopenharmony_ci goto unlock; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci break; 16262306a36Sopenharmony_ci case DBCIOCPARAM: 16362306a36Sopenharmony_ci if (copy_from_user(&dbc_dev->mbox->dbc_param.user, argp, 16462306a36Sopenharmony_ci sizeof(struct dbc_user_param))) { 16562306a36Sopenharmony_ci ret = -EFAULT; 16662306a36Sopenharmony_ci goto unlock; 16762306a36Sopenharmony_ci } 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci ret = send_dbc_parameter(dbc_dev); 17062306a36Sopenharmony_ci if (ret) 17162306a36Sopenharmony_ci goto unlock; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci if (copy_to_user(argp, &dbc_dev->mbox->dbc_param.user, 17462306a36Sopenharmony_ci sizeof(struct dbc_user_param))) { 17562306a36Sopenharmony_ci ret = -EFAULT; 17662306a36Sopenharmony_ci goto unlock; 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci break; 17962306a36Sopenharmony_ci default: 18062306a36Sopenharmony_ci ret = -EINVAL; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci } 18362306a36Sopenharmony_ciunlock: 18462306a36Sopenharmony_ci mutex_unlock(&dbc_dev->ioctl_mutex); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci return ret; 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic const struct file_operations dbc_fops = { 19062306a36Sopenharmony_ci .owner = THIS_MODULE, 19162306a36Sopenharmony_ci .unlocked_ioctl = dbc_ioctl, 19262306a36Sopenharmony_ci}; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ciint dbc_dev_init(struct psp_device *psp) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci struct device *dev = psp->dev; 19762306a36Sopenharmony_ci struct psp_dbc_device *dbc_dev; 19862306a36Sopenharmony_ci int ret; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci if (!PSP_FEATURE(psp, DBC)) 20162306a36Sopenharmony_ci return 0; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci dbc_dev = devm_kzalloc(dev, sizeof(*dbc_dev), GFP_KERNEL); 20462306a36Sopenharmony_ci if (!dbc_dev) 20562306a36Sopenharmony_ci return -ENOMEM; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(union dbc_buffer) > PAGE_SIZE); 20862306a36Sopenharmony_ci dbc_dev->mbox = (void *)devm_get_free_pages(dev, GFP_KERNEL | __GFP_ZERO, 0); 20962306a36Sopenharmony_ci if (!dbc_dev->mbox) { 21062306a36Sopenharmony_ci ret = -ENOMEM; 21162306a36Sopenharmony_ci goto cleanup_dev; 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci psp->dbc_data = dbc_dev; 21562306a36Sopenharmony_ci dbc_dev->dev = dev; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci ret = send_dbc_nonce(dbc_dev); 21862306a36Sopenharmony_ci if (ret == -EACCES) { 21962306a36Sopenharmony_ci dev_dbg(dbc_dev->dev, 22062306a36Sopenharmony_ci "dynamic boost control was previously authenticated\n"); 22162306a36Sopenharmony_ci ret = 0; 22262306a36Sopenharmony_ci } 22362306a36Sopenharmony_ci dev_dbg(dbc_dev->dev, "dynamic boost control is %savailable\n", 22462306a36Sopenharmony_ci ret ? "un" : ""); 22562306a36Sopenharmony_ci if (ret) { 22662306a36Sopenharmony_ci ret = 0; 22762306a36Sopenharmony_ci goto cleanup_mbox; 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci dbc_dev->char_dev.minor = MISC_DYNAMIC_MINOR; 23162306a36Sopenharmony_ci dbc_dev->char_dev.name = "dbc"; 23262306a36Sopenharmony_ci dbc_dev->char_dev.fops = &dbc_fops; 23362306a36Sopenharmony_ci dbc_dev->char_dev.mode = 0600; 23462306a36Sopenharmony_ci ret = misc_register(&dbc_dev->char_dev); 23562306a36Sopenharmony_ci if (ret) 23662306a36Sopenharmony_ci goto cleanup_mbox; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci mutex_init(&dbc_dev->ioctl_mutex); 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci return 0; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_cicleanup_mbox: 24362306a36Sopenharmony_ci devm_free_pages(dev, (unsigned long)dbc_dev->mbox); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cicleanup_dev: 24662306a36Sopenharmony_ci psp->dbc_data = NULL; 24762306a36Sopenharmony_ci devm_kfree(dev, dbc_dev); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci return ret; 25062306a36Sopenharmony_ci} 251