1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2022 Microchip. |
4 | */ |
5 | |
6 | #include <linux/device.h> |
7 | #include <linux/kernel.h> |
8 | #include <linux/module.h> |
9 | #include <linux/rtc.h> |
10 | #include <linux/tee_drv.h> |
11 | |
12 | #define RTC_INFO_VERSION 0x1 |
13 | |
14 | #define TA_CMD_RTC_GET_INFO 0x0 |
15 | #define TA_CMD_RTC_GET_TIME 0x1 |
16 | #define TA_CMD_RTC_SET_TIME 0x2 |
17 | #define TA_CMD_RTC_GET_OFFSET 0x3 |
18 | #define TA_CMD_RTC_SET_OFFSET 0x4 |
19 | |
20 | #define TA_RTC_FEATURE_CORRECTION BIT(0) |
21 | |
22 | struct optee_rtc_time { |
23 | u32 tm_sec; |
24 | u32 tm_min; |
25 | u32 tm_hour; |
26 | u32 tm_mday; |
27 | u32 tm_mon; |
28 | u32 tm_year; |
29 | u32 tm_wday; |
30 | }; |
31 | |
32 | struct optee_rtc_info { |
33 | u64 version; |
34 | u64 features; |
35 | struct optee_rtc_time range_min; |
36 | struct optee_rtc_time range_max; |
37 | }; |
38 | |
39 | /** |
40 | * struct optee_rtc - OP-TEE RTC private data |
41 | * @dev: OP-TEE based RTC device. |
42 | * @ctx: OP-TEE context handler. |
43 | * @session_id: RTC TA session identifier. |
44 | * @shm: Memory pool shared with RTC device. |
45 | * @features: Bitfield of RTC features |
46 | */ |
47 | struct optee_rtc { |
48 | struct device *dev; |
49 | struct tee_context *ctx; |
50 | u32 session_id; |
51 | struct tee_shm *shm; |
52 | u64 features; |
53 | }; |
54 | |
55 | static int optee_rtc_readtime(struct device *dev, struct rtc_time *tm) |
56 | { |
57 | struct optee_rtc *priv = dev_get_drvdata(dev); |
58 | struct tee_ioctl_invoke_arg inv_arg = {0}; |
59 | struct optee_rtc_time *optee_tm; |
60 | struct tee_param param[4] = {0}; |
61 | int ret; |
62 | |
63 | inv_arg.func = TA_CMD_RTC_GET_TIME; |
64 | inv_arg.session = priv->session_id; |
65 | inv_arg.num_params = 4; |
66 | |
67 | /* Fill invoke cmd params */ |
68 | param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT; |
69 | param[0].u.memref.shm = priv->shm; |
70 | param[0].u.memref.size = sizeof(struct optee_rtc_time); |
71 | |
72 | ret = tee_client_invoke_func(ctx: priv->ctx, arg: &inv_arg, param); |
73 | if (ret < 0 || inv_arg.ret != 0) |
74 | return ret ? ret : -EPROTO; |
75 | |
76 | optee_tm = tee_shm_get_va(shm: priv->shm, offs: 0); |
77 | if (IS_ERR(ptr: optee_tm)) |
78 | return PTR_ERR(ptr: optee_tm); |
79 | |
80 | if (param[0].u.memref.size != sizeof(*optee_tm)) |
81 | return -EPROTO; |
82 | |
83 | tm->tm_sec = optee_tm->tm_sec; |
84 | tm->tm_min = optee_tm->tm_min; |
85 | tm->tm_hour = optee_tm->tm_hour; |
86 | tm->tm_mday = optee_tm->tm_mday; |
87 | tm->tm_mon = optee_tm->tm_mon; |
88 | tm->tm_year = optee_tm->tm_year - 1900; |
89 | tm->tm_wday = optee_tm->tm_wday; |
90 | tm->tm_yday = rtc_year_days(day: tm->tm_mday, month: tm->tm_mon, year: tm->tm_year); |
91 | |
92 | return 0; |
93 | } |
94 | |
95 | static int optee_rtc_settime(struct device *dev, struct rtc_time *tm) |
96 | { |
97 | struct optee_rtc *priv = dev_get_drvdata(dev); |
98 | struct tee_ioctl_invoke_arg inv_arg = {0}; |
99 | struct tee_param param[4] = {0}; |
100 | struct optee_rtc_time optee_tm; |
101 | void *rtc_data; |
102 | int ret; |
103 | |
104 | optee_tm.tm_sec = tm->tm_sec; |
105 | optee_tm.tm_min = tm->tm_min; |
106 | optee_tm.tm_hour = tm->tm_hour; |
107 | optee_tm.tm_mday = tm->tm_mday; |
108 | optee_tm.tm_mon = tm->tm_mon; |
109 | optee_tm.tm_year = tm->tm_year + 1900; |
110 | optee_tm.tm_wday = tm->tm_wday; |
111 | |
112 | inv_arg.func = TA_CMD_RTC_SET_TIME; |
113 | inv_arg.session = priv->session_id; |
114 | inv_arg.num_params = 4; |
115 | |
116 | param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT; |
117 | param[0].u.memref.shm = priv->shm; |
118 | param[0].u.memref.size = sizeof(struct optee_rtc_time); |
119 | |
120 | rtc_data = tee_shm_get_va(shm: priv->shm, offs: 0); |
121 | if (IS_ERR(ptr: rtc_data)) |
122 | return PTR_ERR(ptr: rtc_data); |
123 | |
124 | memcpy(rtc_data, &optee_tm, sizeof(struct optee_rtc_time)); |
125 | |
126 | ret = tee_client_invoke_func(ctx: priv->ctx, arg: &inv_arg, param); |
127 | if (ret < 0 || inv_arg.ret != 0) |
128 | return ret ? ret : -EPROTO; |
129 | |
130 | return 0; |
131 | } |
132 | |
133 | static int optee_rtc_readoffset(struct device *dev, long *offset) |
134 | { |
135 | struct optee_rtc *priv = dev_get_drvdata(dev); |
136 | struct tee_ioctl_invoke_arg inv_arg = {0}; |
137 | struct tee_param param[4] = {0}; |
138 | int ret; |
139 | |
140 | if (!(priv->features & TA_RTC_FEATURE_CORRECTION)) |
141 | return -EOPNOTSUPP; |
142 | |
143 | inv_arg.func = TA_CMD_RTC_GET_OFFSET; |
144 | inv_arg.session = priv->session_id; |
145 | inv_arg.num_params = 4; |
146 | |
147 | param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT; |
148 | |
149 | ret = tee_client_invoke_func(ctx: priv->ctx, arg: &inv_arg, param); |
150 | if (ret < 0 || inv_arg.ret != 0) |
151 | return ret ? ret : -EPROTO; |
152 | |
153 | *offset = param[0].u.value.a; |
154 | |
155 | return 0; |
156 | } |
157 | |
158 | static int optee_rtc_setoffset(struct device *dev, long offset) |
159 | { |
160 | struct optee_rtc *priv = dev_get_drvdata(dev); |
161 | struct tee_ioctl_invoke_arg inv_arg = {0}; |
162 | struct tee_param param[4] = {0}; |
163 | int ret; |
164 | |
165 | if (!(priv->features & TA_RTC_FEATURE_CORRECTION)) |
166 | return -EOPNOTSUPP; |
167 | |
168 | inv_arg.func = TA_CMD_RTC_SET_OFFSET; |
169 | inv_arg.session = priv->session_id; |
170 | inv_arg.num_params = 4; |
171 | |
172 | param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; |
173 | param[0].u.value.a = offset; |
174 | |
175 | ret = tee_client_invoke_func(ctx: priv->ctx, arg: &inv_arg, param); |
176 | if (ret < 0 || inv_arg.ret != 0) |
177 | return ret ? ret : -EPROTO; |
178 | |
179 | return 0; |
180 | } |
181 | |
182 | static const struct rtc_class_ops optee_rtc_ops = { |
183 | .read_time = optee_rtc_readtime, |
184 | .set_time = optee_rtc_settime, |
185 | .set_offset = optee_rtc_setoffset, |
186 | .read_offset = optee_rtc_readoffset, |
187 | }; |
188 | |
189 | static int optee_rtc_read_info(struct device *dev, struct rtc_device *rtc, |
190 | u64 *features) |
191 | { |
192 | struct optee_rtc *priv = dev_get_drvdata(dev); |
193 | struct tee_ioctl_invoke_arg inv_arg = {0}; |
194 | struct tee_param param[4] = {0}; |
195 | struct optee_rtc_info *info; |
196 | struct optee_rtc_time *tm; |
197 | int ret; |
198 | |
199 | inv_arg.func = TA_CMD_RTC_GET_INFO; |
200 | inv_arg.session = priv->session_id; |
201 | inv_arg.num_params = 4; |
202 | |
203 | param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT; |
204 | param[0].u.memref.shm = priv->shm; |
205 | param[0].u.memref.size = sizeof(*info); |
206 | |
207 | ret = tee_client_invoke_func(ctx: priv->ctx, arg: &inv_arg, param); |
208 | if (ret < 0 || inv_arg.ret != 0) |
209 | return ret ? ret : -EPROTO; |
210 | |
211 | info = tee_shm_get_va(shm: priv->shm, offs: 0); |
212 | if (IS_ERR(ptr: info)) |
213 | return PTR_ERR(ptr: info); |
214 | |
215 | if (param[0].u.memref.size != sizeof(*info)) |
216 | return -EPROTO; |
217 | |
218 | if (info->version != RTC_INFO_VERSION) |
219 | return -EPROTO; |
220 | |
221 | *features = info->features; |
222 | |
223 | tm = &info->range_min; |
224 | rtc->range_min = mktime64(year: tm->tm_year, mon: tm->tm_mon, day: tm->tm_mday, hour: tm->tm_hour, min: tm->tm_min, |
225 | sec: tm->tm_sec); |
226 | tm = &info->range_max; |
227 | rtc->range_max = mktime64(year: tm->tm_year, mon: tm->tm_mon, day: tm->tm_mday, hour: tm->tm_hour, min: tm->tm_min, |
228 | sec: tm->tm_sec); |
229 | |
230 | return 0; |
231 | } |
232 | |
233 | static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data) |
234 | { |
235 | if (ver->impl_id == TEE_IMPL_ID_OPTEE) |
236 | return 1; |
237 | else |
238 | return 0; |
239 | } |
240 | |
241 | static int optee_rtc_probe(struct device *dev) |
242 | { |
243 | struct tee_client_device *rtc_device = to_tee_client_device(dev); |
244 | struct tee_ioctl_open_session_arg sess_arg; |
245 | struct optee_rtc *priv; |
246 | struct rtc_device *rtc; |
247 | struct tee_shm *shm; |
248 | int ret, err; |
249 | |
250 | memset(&sess_arg, 0, sizeof(sess_arg)); |
251 | |
252 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
253 | if (!priv) |
254 | return -ENOMEM; |
255 | |
256 | rtc = devm_rtc_allocate_device(dev); |
257 | if (IS_ERR(ptr: rtc)) |
258 | return PTR_ERR(ptr: rtc); |
259 | |
260 | /* Open context with TEE driver */ |
261 | priv->ctx = tee_client_open_context(NULL, match: optee_ctx_match, NULL, NULL); |
262 | if (IS_ERR(ptr: priv->ctx)) |
263 | return -ENODEV; |
264 | |
265 | /* Open session with rtc Trusted App */ |
266 | export_uuid(dst: sess_arg.uuid, src: &rtc_device->id.uuid); |
267 | sess_arg.clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL; |
268 | |
269 | ret = tee_client_open_session(ctx: priv->ctx, arg: &sess_arg, NULL); |
270 | if (ret < 0 || sess_arg.ret != 0) { |
271 | dev_err(dev, "tee_client_open_session failed, err: %x\n" , sess_arg.ret); |
272 | err = -EINVAL; |
273 | goto out_ctx; |
274 | } |
275 | priv->session_id = sess_arg.session; |
276 | |
277 | shm = tee_shm_alloc_kernel_buf(ctx: priv->ctx, size: sizeof(struct optee_rtc_info)); |
278 | if (IS_ERR(ptr: shm)) { |
279 | dev_err(priv->dev, "tee_shm_alloc_kernel_buf failed\n" ); |
280 | err = PTR_ERR(ptr: shm); |
281 | goto out_sess; |
282 | } |
283 | |
284 | priv->shm = shm; |
285 | priv->dev = dev; |
286 | dev_set_drvdata(dev, data: priv); |
287 | |
288 | rtc->ops = &optee_rtc_ops; |
289 | |
290 | err = optee_rtc_read_info(dev, rtc, features: &priv->features); |
291 | if (err) { |
292 | dev_err(dev, "Failed to get RTC features from OP-TEE\n" ); |
293 | goto out_shm; |
294 | } |
295 | |
296 | err = devm_rtc_register_device(rtc); |
297 | if (err) |
298 | goto out_shm; |
299 | |
300 | /* |
301 | * We must clear this bit after registering because rtc_register_device |
302 | * will set it if it sees that .set_offset is provided. |
303 | */ |
304 | if (!(priv->features & TA_RTC_FEATURE_CORRECTION)) |
305 | clear_bit(RTC_FEATURE_CORRECTION, addr: rtc->features); |
306 | |
307 | return 0; |
308 | |
309 | out_shm: |
310 | tee_shm_free(shm: priv->shm); |
311 | out_sess: |
312 | tee_client_close_session(ctx: priv->ctx, session: priv->session_id); |
313 | out_ctx: |
314 | tee_client_close_context(ctx: priv->ctx); |
315 | |
316 | return err; |
317 | } |
318 | |
319 | static int optee_rtc_remove(struct device *dev) |
320 | { |
321 | struct optee_rtc *priv = dev_get_drvdata(dev); |
322 | |
323 | tee_client_close_session(ctx: priv->ctx, session: priv->session_id); |
324 | tee_client_close_context(ctx: priv->ctx); |
325 | |
326 | return 0; |
327 | } |
328 | |
329 | static const struct tee_client_device_id optee_rtc_id_table[] = { |
330 | {UUID_INIT(0xf389f8c8, 0x845f, 0x496c, |
331 | 0x8b, 0xbe, 0xd6, 0x4b, 0xd2, 0x4c, 0x92, 0xfd)}, |
332 | {} |
333 | }; |
334 | |
335 | MODULE_DEVICE_TABLE(tee, optee_rtc_id_table); |
336 | |
337 | static struct tee_client_driver optee_rtc_driver = { |
338 | .id_table = optee_rtc_id_table, |
339 | .driver = { |
340 | .name = "optee_rtc" , |
341 | .bus = &tee_bus_type, |
342 | .probe = optee_rtc_probe, |
343 | .remove = optee_rtc_remove, |
344 | }, |
345 | }; |
346 | |
347 | static int __init optee_rtc_mod_init(void) |
348 | { |
349 | return driver_register(drv: &optee_rtc_driver.driver); |
350 | } |
351 | |
352 | static void __exit optee_rtc_mod_exit(void) |
353 | { |
354 | driver_unregister(drv: &optee_rtc_driver.driver); |
355 | } |
356 | |
357 | module_init(optee_rtc_mod_init); |
358 | module_exit(optee_rtc_mod_exit); |
359 | |
360 | MODULE_LICENSE("GPL v2" ); |
361 | MODULE_AUTHOR("Clément Léger <clement.leger@bootlin.com>" ); |
362 | MODULE_DESCRIPTION("OP-TEE based RTC driver" ); |
363 | |