1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Amlogic Secure Monitor driver |
4 | * |
5 | * Copyright (C) 2016 Endless Mobile, Inc. |
6 | * Author: Carlo Caione <carlo@endlessm.com> |
7 | */ |
8 | |
9 | #define pr_fmt(fmt) "meson-sm: " fmt |
10 | |
11 | #include <linux/arm-smccc.h> |
12 | #include <linux/bug.h> |
13 | #include <linux/io.h> |
14 | #include <linux/module.h> |
15 | #include <linux/of.h> |
16 | #include <linux/of_platform.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/printk.h> |
19 | #include <linux/property.h> |
20 | #include <linux/types.h> |
21 | #include <linux/sizes.h> |
22 | #include <linux/slab.h> |
23 | |
24 | #include <linux/firmware/meson/meson_sm.h> |
25 | |
26 | struct meson_sm_cmd { |
27 | unsigned int index; |
28 | u32 smc_id; |
29 | }; |
30 | #define CMD(d, s) { .index = (d), .smc_id = (s), } |
31 | |
32 | struct meson_sm_chip { |
33 | unsigned int shmem_size; |
34 | u32 cmd_shmem_in_base; |
35 | u32 cmd_shmem_out_base; |
36 | struct meson_sm_cmd cmd[]; |
37 | }; |
38 | |
39 | static const struct meson_sm_chip gxbb_chip = { |
40 | .shmem_size = SZ_4K, |
41 | .cmd_shmem_in_base = 0x82000020, |
42 | .cmd_shmem_out_base = 0x82000021, |
43 | .cmd = { |
44 | CMD(SM_EFUSE_READ, 0x82000030), |
45 | CMD(SM_EFUSE_WRITE, 0x82000031), |
46 | CMD(SM_EFUSE_USER_MAX, 0x82000033), |
47 | CMD(SM_GET_CHIP_ID, 0x82000044), |
48 | CMD(SM_A1_PWRC_SET, 0x82000093), |
49 | CMD(SM_A1_PWRC_GET, 0x82000095), |
50 | { /* sentinel */ }, |
51 | }, |
52 | }; |
53 | |
54 | struct meson_sm_firmware { |
55 | const struct meson_sm_chip *chip; |
56 | void __iomem *sm_shmem_in_base; |
57 | void __iomem *sm_shmem_out_base; |
58 | }; |
59 | |
60 | static u32 meson_sm_get_cmd(const struct meson_sm_chip *chip, |
61 | unsigned int cmd_index) |
62 | { |
63 | const struct meson_sm_cmd *cmd = chip->cmd; |
64 | |
65 | while (cmd->smc_id && cmd->index != cmd_index) |
66 | cmd++; |
67 | |
68 | return cmd->smc_id; |
69 | } |
70 | |
71 | static s32 __meson_sm_call(u32 cmd, u32 arg0, u32 arg1, u32 arg2, |
72 | u32 arg3, u32 arg4) |
73 | { |
74 | struct arm_smccc_res res; |
75 | |
76 | arm_smccc_smc(cmd, arg0, arg1, arg2, arg3, arg4, 0, 0, &res); |
77 | return res.a0; |
78 | } |
79 | |
80 | static void __iomem *meson_sm_map_shmem(u32 cmd_shmem, unsigned int size) |
81 | { |
82 | u32 sm_phy_base; |
83 | |
84 | sm_phy_base = __meson_sm_call(cmd: cmd_shmem, arg0: 0, arg1: 0, arg2: 0, arg3: 0, arg4: 0); |
85 | if (!sm_phy_base) |
86 | return NULL; |
87 | |
88 | return ioremap_cache(offset: sm_phy_base, size); |
89 | } |
90 | |
91 | /** |
92 | * meson_sm_call - generic SMC32 call to the secure-monitor |
93 | * |
94 | * @fw: Pointer to secure-monitor firmware |
95 | * @cmd_index: Index of the SMC32 function ID |
96 | * @ret: Returned value |
97 | * @arg0: SMC32 Argument 0 |
98 | * @arg1: SMC32 Argument 1 |
99 | * @arg2: SMC32 Argument 2 |
100 | * @arg3: SMC32 Argument 3 |
101 | * @arg4: SMC32 Argument 4 |
102 | * |
103 | * Return: 0 on success, a negative value on error |
104 | */ |
105 | int meson_sm_call(struct meson_sm_firmware *fw, unsigned int cmd_index, |
106 | s32 *ret, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4) |
107 | { |
108 | u32 cmd; |
109 | s32 lret; |
110 | |
111 | if (!fw->chip) |
112 | return -ENOENT; |
113 | |
114 | cmd = meson_sm_get_cmd(chip: fw->chip, cmd_index); |
115 | if (!cmd) |
116 | return -EINVAL; |
117 | |
118 | lret = __meson_sm_call(cmd, arg0, arg1, arg2, arg3, arg4); |
119 | |
120 | if (ret) |
121 | *ret = lret; |
122 | |
123 | return 0; |
124 | } |
125 | EXPORT_SYMBOL(meson_sm_call); |
126 | |
127 | /** |
128 | * meson_sm_call_read - retrieve data from secure-monitor |
129 | * |
130 | * @fw: Pointer to secure-monitor firmware |
131 | * @buffer: Buffer to store the retrieved data |
132 | * @bsize: Size of the buffer |
133 | * @cmd_index: Index of the SMC32 function ID |
134 | * @arg0: SMC32 Argument 0 |
135 | * @arg1: SMC32 Argument 1 |
136 | * @arg2: SMC32 Argument 2 |
137 | * @arg3: SMC32 Argument 3 |
138 | * @arg4: SMC32 Argument 4 |
139 | * |
140 | * Return: size of read data on success, a negative value on error |
141 | * When 0 is returned there is no guarantee about the amount of |
142 | * data read and bsize bytes are copied in buffer. |
143 | */ |
144 | int meson_sm_call_read(struct meson_sm_firmware *fw, void *buffer, |
145 | unsigned int bsize, unsigned int cmd_index, u32 arg0, |
146 | u32 arg1, u32 arg2, u32 arg3, u32 arg4) |
147 | { |
148 | s32 size; |
149 | int ret; |
150 | |
151 | if (!fw->chip) |
152 | return -ENOENT; |
153 | |
154 | if (!fw->chip->cmd_shmem_out_base) |
155 | return -EINVAL; |
156 | |
157 | if (bsize > fw->chip->shmem_size) |
158 | return -EINVAL; |
159 | |
160 | if (meson_sm_call(fw, cmd_index, &size, arg0, arg1, arg2, arg3, arg4) < 0) |
161 | return -EINVAL; |
162 | |
163 | if (size < 0 || size > bsize) |
164 | return -EINVAL; |
165 | |
166 | ret = size; |
167 | |
168 | /* In some cases (for example GET_CHIP_ID command), |
169 | * SMC doesn't return the number of bytes read, even |
170 | * though the bytes were actually read into sm_shmem_out. |
171 | * So this check is needed. |
172 | */ |
173 | if (!size) |
174 | size = bsize; |
175 | |
176 | if (buffer) |
177 | memcpy(buffer, fw->sm_shmem_out_base, size); |
178 | |
179 | return ret; |
180 | } |
181 | EXPORT_SYMBOL(meson_sm_call_read); |
182 | |
183 | /** |
184 | * meson_sm_call_write - send data to secure-monitor |
185 | * |
186 | * @fw: Pointer to secure-monitor firmware |
187 | * @buffer: Buffer containing data to send |
188 | * @size: Size of the data to send |
189 | * @cmd_index: Index of the SMC32 function ID |
190 | * @arg0: SMC32 Argument 0 |
191 | * @arg1: SMC32 Argument 1 |
192 | * @arg2: SMC32 Argument 2 |
193 | * @arg3: SMC32 Argument 3 |
194 | * @arg4: SMC32 Argument 4 |
195 | * |
196 | * Return: size of sent data on success, a negative value on error |
197 | */ |
198 | int meson_sm_call_write(struct meson_sm_firmware *fw, void *buffer, |
199 | unsigned int size, unsigned int cmd_index, u32 arg0, |
200 | u32 arg1, u32 arg2, u32 arg3, u32 arg4) |
201 | { |
202 | s32 written; |
203 | |
204 | if (!fw->chip) |
205 | return -ENOENT; |
206 | |
207 | if (size > fw->chip->shmem_size) |
208 | return -EINVAL; |
209 | |
210 | if (!fw->chip->cmd_shmem_in_base) |
211 | return -EINVAL; |
212 | |
213 | memcpy(fw->sm_shmem_in_base, buffer, size); |
214 | |
215 | if (meson_sm_call(fw, cmd_index, &written, arg0, arg1, arg2, arg3, arg4) < 0) |
216 | return -EINVAL; |
217 | |
218 | if (written <= 0 || written > size) |
219 | return -EINVAL; |
220 | |
221 | return written; |
222 | } |
223 | EXPORT_SYMBOL(meson_sm_call_write); |
224 | |
225 | /** |
226 | * meson_sm_get - get pointer to meson_sm_firmware structure. |
227 | * |
228 | * @sm_node: Pointer to the secure-monitor Device Tree node. |
229 | * |
230 | * Return: NULL is the secure-monitor device is not ready. |
231 | */ |
232 | struct meson_sm_firmware *meson_sm_get(struct device_node *sm_node) |
233 | { |
234 | struct platform_device *pdev = of_find_device_by_node(np: sm_node); |
235 | |
236 | if (!pdev) |
237 | return NULL; |
238 | |
239 | return platform_get_drvdata(pdev); |
240 | } |
241 | EXPORT_SYMBOL_GPL(meson_sm_get); |
242 | |
243 | #define SM_CHIP_ID_LENGTH 119 |
244 | #define SM_CHIP_ID_OFFSET 4 |
245 | #define SM_CHIP_ID_SIZE 12 |
246 | |
247 | static ssize_t serial_show(struct device *dev, struct device_attribute *attr, |
248 | char *buf) |
249 | { |
250 | struct platform_device *pdev = to_platform_device(dev); |
251 | struct meson_sm_firmware *fw; |
252 | uint8_t *id_buf; |
253 | int ret; |
254 | |
255 | fw = platform_get_drvdata(pdev); |
256 | |
257 | id_buf = kmalloc(SM_CHIP_ID_LENGTH, GFP_KERNEL); |
258 | if (!id_buf) |
259 | return -ENOMEM; |
260 | |
261 | ret = meson_sm_call_read(fw, id_buf, SM_CHIP_ID_LENGTH, SM_GET_CHIP_ID, |
262 | 0, 0, 0, 0, 0); |
263 | if (ret < 0) { |
264 | kfree(objp: id_buf); |
265 | return ret; |
266 | } |
267 | |
268 | ret = sprintf(buf, fmt: "%12phN\n" , &id_buf[SM_CHIP_ID_OFFSET]); |
269 | |
270 | kfree(objp: id_buf); |
271 | |
272 | return ret; |
273 | } |
274 | |
275 | static DEVICE_ATTR_RO(serial); |
276 | |
277 | static struct attribute *meson_sm_sysfs_attrs[] = { |
278 | &dev_attr_serial.attr, |
279 | NULL, |
280 | }; |
281 | ATTRIBUTE_GROUPS(meson_sm_sysfs); |
282 | |
283 | static const struct of_device_id meson_sm_ids[] = { |
284 | { .compatible = "amlogic,meson-gxbb-sm" , .data = &gxbb_chip }, |
285 | { /* sentinel */ }, |
286 | }; |
287 | |
288 | static int __init meson_sm_probe(struct platform_device *pdev) |
289 | { |
290 | struct device *dev = &pdev->dev; |
291 | const struct meson_sm_chip *chip; |
292 | struct meson_sm_firmware *fw; |
293 | |
294 | fw = devm_kzalloc(dev, size: sizeof(*fw), GFP_KERNEL); |
295 | if (!fw) |
296 | return -ENOMEM; |
297 | |
298 | chip = device_get_match_data(dev); |
299 | if (!chip) |
300 | return -EINVAL; |
301 | |
302 | if (chip->cmd_shmem_in_base) { |
303 | fw->sm_shmem_in_base = meson_sm_map_shmem(cmd_shmem: chip->cmd_shmem_in_base, |
304 | size: chip->shmem_size); |
305 | if (WARN_ON(!fw->sm_shmem_in_base)) |
306 | goto out; |
307 | } |
308 | |
309 | if (chip->cmd_shmem_out_base) { |
310 | fw->sm_shmem_out_base = meson_sm_map_shmem(cmd_shmem: chip->cmd_shmem_out_base, |
311 | size: chip->shmem_size); |
312 | if (WARN_ON(!fw->sm_shmem_out_base)) |
313 | goto unmap_in_base; |
314 | } |
315 | |
316 | fw->chip = chip; |
317 | |
318 | platform_set_drvdata(pdev, data: fw); |
319 | |
320 | if (devm_of_platform_populate(dev)) |
321 | goto unmap_out_base; |
322 | |
323 | pr_info("secure-monitor enabled\n" ); |
324 | |
325 | return 0; |
326 | |
327 | unmap_out_base: |
328 | iounmap(addr: fw->sm_shmem_out_base); |
329 | unmap_in_base: |
330 | iounmap(addr: fw->sm_shmem_in_base); |
331 | out: |
332 | return -EINVAL; |
333 | } |
334 | |
335 | static struct platform_driver meson_sm_driver = { |
336 | .driver = { |
337 | .name = "meson-sm" , |
338 | .of_match_table = of_match_ptr(meson_sm_ids), |
339 | .dev_groups = meson_sm_sysfs_groups, |
340 | }, |
341 | }; |
342 | module_platform_driver_probe(meson_sm_driver, meson_sm_probe); |
343 | MODULE_LICENSE("GPL v2" ); |
344 | |