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