1 /*
2  * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6 
7 #include "ts_rpc_caller_linux.h"
8 
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <linux/tee.h>
12 #include <stdbool.h>
13 #include <stdint.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <sys/ioctl.h>
18 #include <sys/mman.h>
19 #include <sys/stat.h>
20 #include <sys/types.h>
21 #include <unistd.h>
22 #include <util.h>
23 
24 #define INVALID_SESS_ID		  0
25 #define MAX_TEE_DEV_NUM		  16
26 #define TS_TEE_DRV_INVALID_SHM_ID (0)
27 
28 /*
29  * This define is part of linux/tee.h starting from Linux v6.10
30  * Let's keep a copy here in case the kernel headers come from an older version
31  */
32 #ifndef TEE_IMPL_ID_TSTEE
33 #define TEE_IMPL_ID_TSTEE 3
34 #endif
35 
36 struct ts_tee_dev {
37 	uint16_t endpoint_id;
38 	char path[16];
39 };
40 
41 struct ts_rpc_caller_linux_context {
42 	struct ts_tee_dev ts_tee_devs[MAX_TEE_DEV_NUM];
43 	uint32_t session_id;
44 	int fd;
45 };
46 
47 #define TEE_IOC_OPEN_SESSION_NUM_PARAMS 0
open_session(void * context,const struct rpc_uuid * service_uuid,uint16_t endpoint_id)48 static rpc_status_t open_session(void *context, const struct rpc_uuid *service_uuid,
49 				 uint16_t endpoint_id)
50 {
51 	struct ts_rpc_caller_linux_context *caller = (struct ts_rpc_caller_linux_context *)context;
52 	const size_t arg_size = sizeof(struct tee_ioctl_open_session_arg) +
53 				TEE_IOC_OPEN_SESSION_NUM_PARAMS * sizeof(struct tee_ioctl_param);
54 	union {
55 		struct tee_ioctl_open_session_arg arg;
56 		uint8_t data[arg_size];
57 	} buf;
58 	struct tee_ioctl_open_session_arg *arg = NULL;
59 	struct tee_ioctl_buf_data buf_data = { 0 };
60 	struct ts_tee_dev *dev = NULL;
61 	int rc = -1;
62 
63 	if (caller->fd >= 0 || caller->session_id != INVALID_SESS_ID) {
64 		printf("%s():%d session is already opened\n", __func__, __LINE__);
65 		return RPC_ERROR_INVALID_STATE;
66 	}
67 
68 	for (int i = 0; i < ARRAY_SIZE(caller->ts_tee_devs); i++) {
69 		if (caller->ts_tee_devs[i].endpoint_id == endpoint_id) {
70 			dev = &caller->ts_tee_devs[i];
71 			break;
72 		}
73 	}
74 
75 	if (!dev) {
76 		printf("%s():%d cannot find device for 0x%04x\n", __func__, __LINE__, endpoint_id);
77 		return RPC_ERROR_NOT_FOUND;
78 	}
79 
80 	caller->fd = open(dev->path, O_RDWR);
81 	if (caller->fd < 0) {
82 		printf("%s():%d cannot open %s: %d\n", __func__, __LINE__, dev->path, errno);
83 		return RPC_ERROR_INTERNAL;
84 	}
85 
86 	memset(&buf, 0, sizeof(buf));
87 
88 	buf_data.buf_ptr = (uintptr_t)&buf;
89 	buf_data.buf_len = sizeof(buf);
90 
91 	arg = &buf.arg;
92 	arg->num_params = TEE_IOC_OPEN_SESSION_NUM_PARAMS;
93 
94 	memcpy(arg->uuid, service_uuid->uuid, sizeof(service_uuid->uuid));
95 
96 	rc = ioctl(caller->fd, TEE_IOC_OPEN_SESSION, &buf_data);
97 	if (rc) {
98 		close(caller->fd);
99 		caller->fd = -1;
100 		return RPC_ERROR_INTERNAL;
101 	}
102 
103 	caller->session_id = arg->session;
104 
105 	return RPC_SUCCESS;
106 }
107 
find_and_open_session(void * context,const struct rpc_uuid * service_uuid)108 static rpc_status_t find_and_open_session(void *context, const struct rpc_uuid *service_uuid)
109 {
110 	struct ts_rpc_caller_linux_context *caller = (struct ts_rpc_caller_linux_context *)context;
111 
112 	for (int i = 0; i < ARRAY_SIZE(caller->ts_tee_devs); i++) {
113 		if (!open_session(context, service_uuid, caller->ts_tee_devs[i].endpoint_id))
114 			return RPC_SUCCESS;
115 	}
116 
117 	return RPC_ERROR_INTERNAL;
118 }
119 
close_session(void * context)120 rpc_status_t close_session(void *context)
121 {
122 	struct ts_rpc_caller_linux_context *caller = (struct ts_rpc_caller_linux_context *)context;
123 	struct tee_ioctl_close_session_arg arg = { 0 };
124 	int rc = -1;
125 
126 	if (caller->fd < 0) {
127 		printf("%s():%d session is already closed\n", __func__, __LINE__);
128 		return RPC_ERROR_INVALID_STATE;
129 	}
130 
131 	arg.session = caller->session_id;
132 
133 	rc = ioctl(caller->fd, TEE_IOC_CLOSE_SESSION, &arg);
134 	if (rc) {
135 		printf("%s():%d failed to close session: %d\n", __func__, __LINE__, errno);
136 		return RPC_ERROR_INTERNAL;
137 	}
138 
139 	close(caller->fd);
140 	caller->fd = -1;
141 	caller->session_id = INVALID_SESS_ID;
142 
143 	return RPC_SUCCESS;
144 }
145 
create_shared_memory(void * context,size_t size,struct rpc_caller_shared_memory * shared_memory)146 rpc_status_t create_shared_memory(void *context, size_t size,
147 				  struct rpc_caller_shared_memory *shared_memory)
148 {
149 	struct ts_rpc_caller_linux_context *caller = (struct ts_rpc_caller_linux_context *)context;
150 	struct tee_ioctl_shm_alloc_data data = { .size = size };
151 	int shm_fd = -1;
152 
153 	if (!size) {
154 		shared_memory->buffer = NULL;
155 		shared_memory->size = 0;
156 		shared_memory->id = TS_TEE_DRV_INVALID_SHM_ID;
157 
158 		return RPC_SUCCESS;
159 	}
160 
161 	shm_fd = ioctl(caller->fd, TEE_IOC_SHM_ALLOC, &data);
162 	if (shm_fd < 0) {
163 		printf("%s():%d failed to create shared memory: %d\n", __func__, __LINE__, errno);
164 		return RPC_ERROR_INTERNAL;
165 	}
166 
167 	shared_memory->buffer =
168 		mmap(NULL, data.size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
169 	if (shared_memory->buffer == (void *)MAP_FAILED) {
170 		printf("%s():%d failed to map shared memory: %d\n", __func__, __LINE__, errno);
171 		close(shm_fd);
172 		return RPC_ERROR_INTERNAL;
173 	}
174 	close(shm_fd);
175 	shared_memory->size = data.size;
176 	shared_memory->id = data.id;
177 
178 	return RPC_SUCCESS;
179 }
180 
release_shared_memory(void * context,struct rpc_caller_shared_memory * shared_memory)181 rpc_status_t release_shared_memory(void *context, struct rpc_caller_shared_memory *shared_memory)
182 {
183 	(void)context;
184 
185 	if (shared_memory->id == TS_TEE_DRV_INVALID_SHM_ID)
186 		return RPC_SUCCESS;
187 
188 	if (munmap(shared_memory->buffer, shared_memory->size)) {
189 		printf("%s():%d failed to unmap shared memory: %d\n", __func__, __LINE__, errno);
190 		return RPC_ERROR_INTERNAL;
191 	}
192 
193 	*shared_memory = (struct rpc_caller_shared_memory){ 0 };
194 
195 	return RPC_SUCCESS;
196 }
197 
198 #define TEE_IOC_INVOKE_NUM_PARAMS 1
call(void * context,uint16_t opcode,struct rpc_caller_shared_memory * shared_memory,size_t request_length,size_t * response_length,service_status_t * service_status)199 static rpc_status_t call(void *context, uint16_t opcode,
200 			 struct rpc_caller_shared_memory *shared_memory, size_t request_length,
201 			 size_t *response_length, service_status_t *service_status)
202 {
203 	struct ts_rpc_caller_linux_context *caller = (struct ts_rpc_caller_linux_context *)context;
204 	const size_t arg_size = sizeof(struct tee_ioctl_invoke_arg) +
205 				TEE_IOC_INVOKE_NUM_PARAMS * sizeof(struct tee_ioctl_param);
206 	union {
207 		struct tee_ioctl_invoke_arg arg;
208 		uint8_t data[arg_size];
209 	} buf;
210 	struct tee_ioctl_buf_data buf_data = { 0 };
211 	struct tee_ioctl_invoke_arg *arg = NULL;
212 	struct tee_ioctl_param *params = NULL;
213 	int rc = -1;
214 
215 	memset(&buf, 0, sizeof(buf));
216 
217 	buf_data.buf_ptr = (uintptr_t)&buf;
218 	buf_data.buf_len = sizeof(buf);
219 
220 	arg = &buf.arg;
221 	arg->func = opcode;
222 	arg->session = caller->session_id;
223 	arg->num_params = TEE_IOC_INVOKE_NUM_PARAMS;
224 	params = (struct tee_ioctl_param *)(arg + 1);
225 
226 	params[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT;
227 	params[0].a = shared_memory->id;
228 	params[0].b = request_length;
229 	params[0].c = 0;
230 
231 	rc = ioctl(caller->fd, TEE_IOC_INVOKE, &buf_data);
232 	if (rc) {
233 		printf("%s():%d failed to invoke command: %d\n", __func__, __LINE__, errno);
234 		return RPC_ERROR_INTERNAL;
235 	}
236 
237 	*response_length = params[0].a;
238 	*service_status = (int)arg->ret;
239 
240 	return RPC_SUCCESS;
241 }
242 
ts_tee_drv_discover(struct ts_tee_dev * ts_tee_devs,size_t count)243 static void ts_tee_drv_discover(struct ts_tee_dev *ts_tee_devs, size_t count)
244 {
245 	struct tee_ioctl_version_data vers = { 0 };
246 	unsigned int tee_file_index = 0;
247 	unsigned int ts_tee_dev_index = 0;
248 	char path[16];
249 	int rc = -1;
250 	int fd = -1;
251 
252 	for (tee_file_index = 0; tee_file_index < MAX_TEE_DEV_NUM && ts_tee_dev_index < count;
253 	     tee_file_index++) {
254 		snprintf(path, sizeof(path), "/dev/tee%u", tee_file_index);
255 
256 		fd = open(path, O_RDWR);
257 		if (fd < 0)
258 			continue;
259 
260 		memset(&vers, 0, sizeof(vers));
261 
262 		rc = ioctl(fd, TEE_IOC_VERSION, &vers);
263 		close(fd);
264 
265 		if (!rc && vers.impl_id == TEE_IMPL_ID_TSTEE) {
266 			ts_tee_devs[ts_tee_dev_index].endpoint_id = vers.impl_caps;
267 			memcpy(ts_tee_devs[ts_tee_dev_index].path, path, sizeof(path));
268 			ts_tee_dev_index++;
269 		}
270 	}
271 }
272 
ts_rpc_caller_linux_init(struct rpc_caller_interface * rpc_caller)273 rpc_status_t ts_rpc_caller_linux_init(struct rpc_caller_interface *rpc_caller)
274 {
275 	struct ts_rpc_caller_linux_context *context = NULL;
276 
277 	if (!rpc_caller || rpc_caller->context)
278 		return RPC_ERROR_INVALID_VALUE;
279 
280 	context = (struct ts_rpc_caller_linux_context *)calloc(
281 		1, sizeof(struct ts_rpc_caller_linux_context));
282 	if (!context)
283 		return RPC_ERROR_INTERNAL;
284 
285 	context->fd = -1;
286 	context->session_id = INVALID_SESS_ID;
287 
288 	rpc_caller->context = context;
289 	rpc_caller->open_session = open_session;
290 	rpc_caller->find_and_open_session = find_and_open_session;
291 	rpc_caller->close_session = close_session;
292 	rpc_caller->create_shared_memory = create_shared_memory;
293 	rpc_caller->release_shared_memory = release_shared_memory;
294 	rpc_caller->call = call;
295 
296 	ts_tee_drv_discover(context->ts_tee_devs, ARRAY_SIZE(context->ts_tee_devs));
297 
298 	return RPC_SUCCESS;
299 }
300 
ts_rpc_caller_linux_deinit(struct rpc_caller_interface * rpc_caller)301 rpc_status_t ts_rpc_caller_linux_deinit(struct rpc_caller_interface *rpc_caller)
302 {
303 	struct ts_rpc_caller_linux_context *caller = NULL;
304 
305 	if (!rpc_caller || !rpc_caller->context)
306 		return RPC_ERROR_INVALID_VALUE;
307 
308 	caller = (struct ts_rpc_caller_linux_context *)rpc_caller->context;
309 
310 	if (caller->session_id != INVALID_SESS_ID) {
311 		close_session(rpc_caller);
312 		caller->session_id = INVALID_SESS_ID;
313 	}
314 
315 	if (caller->fd >= 0) {
316 		close(caller->fd);
317 		caller->fd = -1;
318 	}
319 
320 	free(rpc_caller->context);
321 
322 	return RPC_SUCCESS;
323 }
324