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