1 /*
2  * Copyright (c) 2020 Intel Corporation
3  * Copyright (c) 2023 Huawei France Technologies SASU
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 /* we piggyback the log level using ivshmem knob */
9 #define LOG_MODULE_NAME	ivshmem_doorbell_test
10 #define LOG_LEVEL CONFIG_IVSHMEM_LOG_LEVEL
11 #include <zephyr/logging/log.h>
12 LOG_MODULE_REGISTER(LOG_MODULE_NAME);
13 
14 #include <zephyr/kernel.h>
15 #ifdef CONFIG_USERSPACE
16 #include <zephyr/sys/libc-hooks.h>
17 #endif
18 
19 #include <stdio.h>
20 #include <stdlib.h>
21 
22 #include <zephyr/drivers/virtualization/ivshmem.h>
23 
24 /*
25  * result of an interrupt
26  *
27  * Using the ivshmem API, we will receive a signal event + two integers (the
28  * interrupt itself is dealt in the internal ivshmem driver api)
29  */
30 struct ivshmem_irq {
31 	/* k_poll was signaled or not */
32 	unsigned int signaled;
33 	/* vector received */
34 	int result;
35 };
36 
37 struct ivshmem_ctx {
38 	/* dev, received via DTS */
39 	const struct device *dev;
40 	/* virtual address to access shared memory of ivshmem */
41 	void		*mem;
42 	/* size of shared memory */
43 	size_t		 size;
44 	/* my id for ivshmem protocol */
45 	uint32_t	 id;
46 	/* number of MSI vectors allocated */
47 	uint16_t	 vectors;
48 };
49 
50 #ifdef CONFIG_USERSPACE
51 K_APPMEM_PARTITION_DEFINE(app_a_partition);
52 struct k_mem_domain app_a_domain;
53 
54 #define APP_A_DATA	K_APP_DMEM(app_a_partition)
55 #define APP_A_BSS	K_APP_BMEM(app_a_partition)
56 #else
57 #define APP_A_DATA
58 #define APP_A_BSS
59 #endif
60 
61 /* used for interrupt events, see wait_for_int() */
62 APP_A_BSS static struct ivshmem_irq irq;
63 APP_A_BSS static struct ivshmem_ctx shmem_ctx;
64 
65 /* signal structure necessary for ivshmem API */
66 APP_A_BSS static struct k_poll_signal *sig;
67 
68 /*
69  * wait for an interrupt event.
70  */
wait_for_int(const struct ivshmem_ctx * ctx)71 static int wait_for_int(const struct ivshmem_ctx *ctx)
72 {
73 	int ret;
74 	struct k_poll_event events[] = {
75 		K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL,
76 					 K_POLL_MODE_NOTIFY_ONLY,
77 					 sig),
78 	};
79 
80 	LOG_DBG("%s: waiting interrupt from client...", __func__);
81 	ret = k_poll(events, ARRAY_SIZE(events), K_FOREVER);
82 	if (ret < 0) {
83 		printf("poll failed\n");
84 		return ret;
85 	}
86 
87 	k_poll_signal_check(sig, &irq.signaled, &irq.result);
88 	LOG_DBG("%s: sig %p signaled %u result %d", __func__,
89 		sig, irq.signaled, irq.result);
90 	/* get ready for next signal */
91 	k_poll_signal_reset(sig);
92 	return 0;
93 }
94 
95 /*
96  * host <-> guest communication loop
97  */
ivshmem_event_loop(struct ivshmem_ctx * ctx)98 static void ivshmem_event_loop(struct ivshmem_ctx *ctx)
99 {
100 	int ret;
101 	char *buf;
102 	bool truncated;
103 
104 	buf = malloc(ctx->size);
105 	if (buf == NULL) {
106 		printf("Could not allocate message buffer\n");
107 		return;
108 	}
109 
110 	printf("Use write_shared_memory.sh and ivshmem-client to send a message\n");
111 	while (1) {
112 		/* host --> guest */
113 		ret = wait_for_int(ctx);
114 		if (ret < 0) {
115 			break;
116 		}
117 
118 		/*
119 		 * QEMU may fail here if the shared memory has been downsized;
120 		 * the shared memory object must be protected against rogue
121 		 * users (check README for details)
122 		 */
123 		memcpy(buf, ctx->mem, ctx->size);
124 		truncated = (buf[ctx->size - 1] != '\0');
125 		buf[ctx->size - 1] = '\0';
126 
127 		printf("received IRQ and %s message: %s\n",
128 		       truncated ? "truncated" : "full", buf);
129 	}
130 	free(buf);
131 }
132 
133 /*
134  * setup ivshmem parameters
135  *
136  * allocates a k_object in sig if running on kernelspace
137  */
setup_ivshmem(bool in_kernel)138 static int setup_ivshmem(bool in_kernel)
139 {
140 	int ret;
141 	size_t i;
142 
143 	shmem_ctx.dev = DEVICE_DT_GET(DT_NODELABEL(ivshmem0));
144 	if (!device_is_ready(shmem_ctx.dev)) {
145 		printf("Could not get ivshmem device\n");
146 		return -1;
147 	}
148 
149 	shmem_ctx.size = ivshmem_get_mem(shmem_ctx.dev, (uintptr_t *)&shmem_ctx.mem);
150 	if (shmem_ctx.size == 0) {
151 		printf("Size cannot not be 0");
152 		return -1;
153 	}
154 	if (shmem_ctx.mem == NULL) {
155 		printf("Shared memory cannot be null\n");
156 		return -1;
157 	}
158 
159 	shmem_ctx.id = ivshmem_get_id(shmem_ctx.dev);
160 	LOG_DBG("id for doorbell: %u", shmem_ctx.id);
161 
162 	shmem_ctx.vectors = ivshmem_get_vectors(shmem_ctx.dev);
163 	if (shmem_ctx.vectors == 0) {
164 		printf("ivshmem-doorbell must have vectors\n");
165 		return -1;
166 	}
167 
168 	if (in_kernel) {
169 		sig = k_malloc(sizeof(*sig));
170 		if (sig == NULL) {
171 			printf("could not allocate sig\n");
172 			return -1;
173 		}
174 		k_poll_signal_init(sig);
175 	}
176 
177 	for (i = 0; i < shmem_ctx.vectors; i++) {
178 		ret = ivshmem_register_handler(shmem_ctx.dev, sig, i);
179 		if (ret < 0) {
180 			printf("registering handlers must be supported\n");
181 			return -1;
182 		}
183 	}
184 	return 0;
185 }
186 
ivshmem_sample_failed(void)187 static void ivshmem_sample_failed(void)
188 {
189 	printf("test has failed\n");
190 	while (1) {
191 	}
192 }
193 
194 #ifndef CONFIG_USERSPACE
ivshmem_sample_doorbell(void)195 static void ivshmem_sample_doorbell(void)
196 {
197 	int ret;
198 
199 	ret = setup_ivshmem(true);
200 	if (ret < 0) {
201 		return;
202 	}
203 	ivshmem_event_loop(&shmem_ctx);
204 	/*
205 	 * if ivshmem_event_loop() returns, it means the function failed
206 	 *
207 	 * for kernelspace, if ivshmem_event_loop() fails, it will lead to
208 	 * main(), which, in turn, will call ivshmem_sample_failed()
209 	 */
210 	k_object_free(sig);
211 }
212 #else
user_entry(void * a,void * b,void * c)213 static void user_entry(void *a, void *b, void *c)
214 {
215 	int ret;
216 
217 	ret = setup_ivshmem(false);
218 	if (ret < 0) {
219 		goto fail;
220 	}
221 	ivshmem_event_loop(&shmem_ctx);
222 	/* if ivshmem_event_loop() returns, it means the function failed */
223 fail:
224 	k_object_release(sig);
225 	ivshmem_sample_failed();
226 }
227 
ivshmem_sample_userspace_doorbell(void)228 static void ivshmem_sample_userspace_doorbell(void)
229 {
230 	int ret;
231 	const struct device *dev;
232 	struct k_mem_partition *parts[] = {
233 #if Z_LIBC_PARTITION_EXISTS
234 		&z_libc_partition,
235 #endif
236 #if Z_MALLOC_PARTITION_EXISTS
237 		&z_malloc_partition,
238 #endif
239 		&app_a_partition,
240 	};
241 
242 	ret = k_mem_domain_init(&app_a_domain, ARRAY_SIZE(parts), parts);
243 	if (ret != 0) {
244 		printf("k_mem_domain_init failed %d\n", ret);
245 		return;
246 	}
247 
248 	k_mem_domain_add_thread(&app_a_domain, k_current_get());
249 
250 	dev = DEVICE_DT_GET(DT_NODELABEL(ivshmem0));
251 	if (!device_is_ready(dev)) {
252 		printf("Could not get ivshmem device\n");
253 		return;
254 	}
255 
256 	sig = k_object_alloc(K_OBJ_POLL_SIGNAL);
257 	if (sig == NULL) {
258 		printf("could not allocate sig\n");
259 		return;
260 	}
261 	k_poll_signal_init(sig);
262 
263 	k_object_access_grant(dev, k_current_get());
264 	k_object_access_grant(sig, k_current_get());
265 
266 	k_thread_user_mode_enter(user_entry, NULL, NULL, NULL);
267 }
268 #endif /* CONFIG_USERSPACE */
269 
main(void)270 int main(void)
271 {
272 #ifdef CONFIG_USERSPACE
273 	ivshmem_sample_userspace_doorbell();
274 #else
275 	ivshmem_sample_doorbell();
276 #endif
277 	/* if the code reaches here, it means the setup/loop has failed */
278 	ivshmem_sample_failed();
279 	return 0;
280 }
281