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