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
send_dbc_cmd(struct psp_dbc_device * dbc_dev,enum psp_platform_access_msg msg)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
send_dbc_nonce(struct psp_dbc_device * dbc_dev)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
send_dbc_parameter(struct psp_dbc_device * dbc_dev)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
dbc_dev_destroy(struct psp_device * psp)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
dbc_ioctl(struct file * filp,unsigned int cmd,unsigned long arg)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
dbc_dev_init(struct psp_device * psp)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, 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