1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * AMD Secure Processor Dynamic Boost Control interface 4 * 5 * Copyright (C) 2023 Advanced Micro Devices, Inc. 6 * 7 * Author: Mario Limonciello <mario.limonciello@amd.com> 8 */ 9 10 #include "dbc.h" 11 12 struct error_map { 13 u32 psp; 14 int ret; 15 }; 16 17 #define DBC_ERROR_ACCESS_DENIED 0x0001 18 #define DBC_ERROR_EXCESS_DATA 0x0004 19 #define DBC_ERROR_BAD_PARAMETERS 0x0006 20 #define DBC_ERROR_BAD_STATE 0x0007 21 #define DBC_ERROR_NOT_IMPLEMENTED 0x0009 22 #define DBC_ERROR_BUSY 0x000D 23 #define DBC_ERROR_MESSAGE_FAILURE 0x0307 24 #define DBC_ERROR_OVERFLOW 0x300F 25 #define DBC_ERROR_SIGNATURE_INVALID 0x3072 26 27 static struct error_map error_codes[] = { 28 {DBC_ERROR_ACCESS_DENIED, -EACCES}, 29 {DBC_ERROR_EXCESS_DATA, -E2BIG}, 30 {DBC_ERROR_BAD_PARAMETERS, -EINVAL}, 31 {DBC_ERROR_BAD_STATE, -EAGAIN}, 32 {DBC_ERROR_MESSAGE_FAILURE, -ENOENT}, 33 {DBC_ERROR_NOT_IMPLEMENTED, -ENOENT}, 34 {DBC_ERROR_BUSY, -EBUSY}, 35 {DBC_ERROR_OVERFLOW, -ENFILE}, 36 {DBC_ERROR_SIGNATURE_INVALID, -EPERM}, 37 {0x0, 0x0}, 38 }; 39 40 static int send_dbc_cmd(struct psp_dbc_device *dbc_dev, 41 enum psp_platform_access_msg msg) 42 { 43 int ret; 44 45 dbc_dev->mbox->req.header.status = 0; 46 ret = psp_send_platform_access_msg(msg, (struct psp_request *)dbc_dev->mbox); 47 if (ret == -EIO) { 48 int i; 49 50 dev_dbg(dbc_dev->dev, 51 "msg 0x%x failed with PSP error: 0x%x\n", 52 msg, dbc_dev->mbox->req.header.status); 53 54 for (i = 0; error_codes[i].psp; i++) { 55 if (dbc_dev->mbox->req.header.status == error_codes[i].psp) 56 return error_codes[i].ret; 57 } 58 } 59 60 return ret; 61 } 62 63 static int send_dbc_nonce(struct psp_dbc_device *dbc_dev) 64 { 65 int ret; 66 67 dbc_dev->mbox->req.header.payload_size = sizeof(dbc_dev->mbox->dbc_nonce); 68 ret = send_dbc_cmd(dbc_dev, PSP_DYNAMIC_BOOST_GET_NONCE); 69 if (ret == -EAGAIN) { 70 dev_dbg(dbc_dev->dev, "retrying get nonce\n"); 71 ret = send_dbc_cmd(dbc_dev, PSP_DYNAMIC_BOOST_GET_NONCE); 72 } 73 74 return ret; 75 } 76 77 static int send_dbc_parameter(struct psp_dbc_device *dbc_dev) 78 { 79 dbc_dev->mbox->req.header.payload_size = sizeof(dbc_dev->mbox->dbc_param); 80 81 switch (dbc_dev->mbox->dbc_param.user.msg_index) { 82 case PARAM_SET_FMAX_CAP: 83 case PARAM_SET_PWR_CAP: 84 case PARAM_SET_GFX_MODE: 85 return send_dbc_cmd(dbc_dev, PSP_DYNAMIC_BOOST_SET_PARAMETER); 86 case PARAM_GET_FMAX_CAP: 87 case PARAM_GET_PWR_CAP: 88 case PARAM_GET_CURR_TEMP: 89 case PARAM_GET_FMAX_MAX: 90 case PARAM_GET_FMAX_MIN: 91 case PARAM_GET_SOC_PWR_MAX: 92 case PARAM_GET_SOC_PWR_MIN: 93 case PARAM_GET_SOC_PWR_CUR: 94 case PARAM_GET_GFX_MODE: 95 return send_dbc_cmd(dbc_dev, PSP_DYNAMIC_BOOST_GET_PARAMETER); 96 } 97 98 return -EINVAL; 99 } 100 101 void dbc_dev_destroy(struct psp_device *psp) 102 { 103 struct psp_dbc_device *dbc_dev = psp->dbc_data; 104 105 if (!dbc_dev) 106 return; 107 108 misc_deregister(&dbc_dev->char_dev); 109 mutex_destroy(&dbc_dev->ioctl_mutex); 110 psp->dbc_data = NULL; 111 } 112 113 static long dbc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 114 { 115 struct psp_device *psp_master = psp_get_master_device(); 116 void __user *argp = (void __user *)arg; 117 struct psp_dbc_device *dbc_dev; 118 int ret; 119 120 if (!psp_master || !psp_master->dbc_data) 121 return -ENODEV; 122 dbc_dev = psp_master->dbc_data; 123 124 mutex_lock(&dbc_dev->ioctl_mutex); 125 126 switch (cmd) { 127 case DBCIOCNONCE: 128 if (copy_from_user(&dbc_dev->mbox->dbc_nonce.user, argp, 129 sizeof(struct dbc_user_nonce))) { 130 ret = -EFAULT; 131 goto unlock; 132 } 133 134 ret = send_dbc_nonce(dbc_dev); 135 if (ret) 136 goto unlock; 137 138 if (copy_to_user(argp, &dbc_dev->mbox->dbc_nonce.user, 139 sizeof(struct dbc_user_nonce))) { 140 ret = -EFAULT; 141 goto unlock; 142 } 143 break; 144 case DBCIOCUID: 145 dbc_dev->mbox->req.header.payload_size = sizeof(dbc_dev->mbox->dbc_set_uid); 146 if (copy_from_user(&dbc_dev->mbox->dbc_set_uid.user, argp, 147 sizeof(struct dbc_user_setuid))) { 148 ret = -EFAULT; 149 goto unlock; 150 } 151 152 ret = send_dbc_cmd(dbc_dev, PSP_DYNAMIC_BOOST_SET_UID); 153 if (ret) 154 goto unlock; 155 156 if (copy_to_user(argp, &dbc_dev->mbox->dbc_set_uid.user, 157 sizeof(struct dbc_user_setuid))) { 158 ret = -EFAULT; 159 goto unlock; 160 } 161 break; 162 case DBCIOCPARAM: 163 if (copy_from_user(&dbc_dev->mbox->dbc_param.user, argp, 164 sizeof(struct dbc_user_param))) { 165 ret = -EFAULT; 166 goto unlock; 167 } 168 169 ret = send_dbc_parameter(dbc_dev); 170 if (ret) 171 goto unlock; 172 173 if (copy_to_user(argp, &dbc_dev->mbox->dbc_param.user, 174 sizeof(struct dbc_user_param))) { 175 ret = -EFAULT; 176 goto unlock; 177 } 178 break; 179 default: 180 ret = -EINVAL; 181 182 } 183 unlock: 184 mutex_unlock(&dbc_dev->ioctl_mutex); 185 186 return ret; 187 } 188 189 static const struct file_operations dbc_fops = { 190 .owner = THIS_MODULE, 191 .unlocked_ioctl = dbc_ioctl, 192 }; 193 194 int dbc_dev_init(struct psp_device *psp) 195 { 196 struct device *dev = psp->dev; 197 struct psp_dbc_device *dbc_dev; 198 int ret; 199 200 if (!PSP_FEATURE(psp, DBC)) 201 return 0; 202 203 dbc_dev = devm_kzalloc(dev, sizeof(*dbc_dev), GFP_KERNEL); 204 if (!dbc_dev) 205 return -ENOMEM; 206 207 BUILD_BUG_ON(sizeof(union dbc_buffer) > PAGE_SIZE); 208 dbc_dev->mbox = (void *)devm_get_free_pages(dev, GFP_KERNEL | __GFP_ZERO, 0); 209 if (!dbc_dev->mbox) { 210 ret = -ENOMEM; 211 goto cleanup_dev; 212 } 213 214 psp->dbc_data = dbc_dev; 215 dbc_dev->dev = dev; 216 217 ret = send_dbc_nonce(dbc_dev); 218 if (ret == -EACCES) { 219 dev_dbg(dbc_dev->dev, 220 "dynamic boost control was previously authenticated\n"); 221 ret = 0; 222 } 223 dev_dbg(dbc_dev->dev, "dynamic boost control is %savailable\n", 224 ret ? "un" : ""); 225 if (ret) { 226 ret = 0; 227 goto cleanup_mbox; 228 } 229 230 dbc_dev->char_dev.minor = MISC_DYNAMIC_MINOR; 231 dbc_dev->char_dev.name = "dbc"; 232 dbc_dev->char_dev.fops = &dbc_fops; 233 dbc_dev->char_dev.mode = 0600; 234 ret = misc_register(&dbc_dev->char_dev); 235 if (ret) 236 goto cleanup_mbox; 237 238 mutex_init(&dbc_dev->ioctl_mutex); 239 240 return 0; 241 242 cleanup_mbox: 243 devm_free_pages(dev, (unsigned long)dbc_dev->mbox); 244 245 cleanup_dev: 246 psp->dbc_data = NULL; 247 devm_kfree(dev, dbc_dev); 248 249 return ret; 250 } 251