1 /*
2 * Copyright (c) 2019 Intel Corporation.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <kernel.h>
8 #include <device.h>
9 #include <sys/libc-hooks.h>
10 #include <logging/log.h>
11
12 #include "sample_driver.h"
13 #include "main.h"
14 #include "app_a.h"
15 #include "app_syscall.h"
16
17 LOG_MODULE_REGISTER(app_a);
18
19 #define MAX_MSGS 8
20
21 /* Resource pool for allocations made by the kernel on behalf of system
22 * calls. Needed for k_queue_alloc_append()
23 */
24 K_HEAP_DEFINE(app_a_resource_pool, 256 * 5 + 128);
25
26 /* Define app_a_partition, where all globals for this app will be routed.
27 * The partition starting address and size are populated by build system
28 * and linker magic.
29 */
30 K_APPMEM_PARTITION_DEFINE(app_a_partition);
31
32 /* Memory domain for application A, set up and installed in app_a_entry() */
33 static struct k_mem_domain app_a_domain;
34
35 /* Message queue for IPC between the driver callback and the monitor thread.
36 *
37 * This message queue is being statically initialized, no need to call
38 * k_msgq_init() on it.
39 */
40 K_MSGQ_DEFINE(mqueue, SAMPLE_DRIVER_MSG_SIZE, MAX_MSGS, 4);
41
42 /* Processing thread. This takes data that has been processed by application
43 * B and writes it to the sample_driver, completing the control loop
44 */
45 struct k_thread writeback_thread;
46 K_THREAD_STACK_DEFINE(writeback_stack, 2048);
47
48 /* Global data used by application A. By tagging with APP_A_BSS or APP_A_DATA,
49 * we ensure all this gets linked into the continuous region denoted by
50 * app_a_partition.
51 */
52 APP_A_BSS const struct device *sample_device;
53 APP_A_BSS unsigned int pending_count;
54
55 /* ISR-level callback function. Runs in supervisor mode. Does what's needed
56 * to get the data into this application's accessible memory and have the
57 * worker thread running in user mode do the rest.
58 */
sample_callback(const struct device * dev,void * context,void * data)59 void sample_callback(const struct device *dev, void *context, void *data)
60 {
61 int ret;
62
63 ARG_UNUSED(context);
64
65 LOG_DBG("sample callback with %p", data);
66
67 /* All the callback does is place the data payload into the
68 * message queue. This will wake up the monitor thread for further
69 * processing.
70 *
71 * We use a message queue because it will perform a data copy for us
72 * when buffering this data.
73 */
74 ret = k_msgq_put(&mqueue, data, K_NO_WAIT);
75 if (ret) {
76 LOG_ERR("k_msgq_put failed with %d", ret);
77 }
78 }
79
monitor_entry(void * p1,void * p2,void * p3)80 static void monitor_entry(void *p1, void *p2, void *p3)
81 {
82 int ret;
83 void *payload;
84 unsigned int monitor_count = 0;
85
86 ARG_UNUSED(p1);
87 ARG_UNUSED(p2);
88 ARG_UNUSED(p3);
89
90 /* Monitor thread, running in user mode. Responsible for pulling
91 * data out of the message queue for further writeback.
92 */
93 LOG_DBG("monitor thread entered");
94
95 ret = sample_driver_state_set(sample_device, true);
96 if (ret != 0) {
97 LOG_ERR("couldn't start driver interrupts");
98 k_oops();
99 }
100
101 while (monitor_count < NUM_LOOPS) {
102 payload = sys_heap_alloc(&shared_pool,
103 SAMPLE_DRIVER_MSG_SIZE);
104 if (payload == NULL) {
105 LOG_ERR("couldn't alloc memory from shared pool");
106 k_oops();
107 continue;
108 }
109
110 /* Sleep waiting for some data to appear in the queue,
111 * and then copy it into the payload buffer.
112 */
113 LOG_DBG("monitor thread waiting for data...");
114 ret = k_msgq_get(&mqueue, payload, K_FOREVER);
115 if (ret != 0) {
116 LOG_ERR("k_msgq_get() failed with %d", ret);
117 k_oops();
118 }
119
120
121 LOG_INF("monitor thread got data payload #%u", monitor_count);
122 LOG_DBG("pending payloads: %u", pending_count);
123
124 /* Put the payload in the queue for data to process by
125 * app B. This does not copy the data. Because we are using
126 * k_queue from user mode, we need to use the
127 * k_queue_alloc_append() variant, which needs to allocate
128 * some memory on the kernel side from our thread
129 * resource pool.
130 */
131 pending_count++;
132 k_queue_alloc_append(&shared_queue_incoming, payload);
133 monitor_count++;
134 }
135
136 /* Tell the driver to stop delivering interrupts, we're closing up
137 * shop
138 */
139 ret = sample_driver_state_set(sample_device, false);
140 if (ret != 0) {
141 LOG_ERR("couldn't disable driver");
142 k_oops();
143 }
144 LOG_DBG("monitor thread exiting");
145 }
146
writeback_entry(void * p1,void * p2,void * p3)147 static void writeback_entry(void *p1, void *p2, void *p3)
148 {
149 void *data;
150 unsigned int writeback_count = 0;
151 int ret;
152
153 ARG_UNUSED(p1);
154 ARG_UNUSED(p2);
155 ARG_UNUSED(p3);
156
157 LOG_DBG("writeback thread entered");
158
159 while (writeback_count < NUM_LOOPS) {
160 /* Grab a data payload processed by Application B,
161 * send it to the driver, and free the buffer.
162 */
163 data = k_queue_get(&shared_queue_outgoing, K_FOREVER);
164 if (data == NULL) {
165 LOG_ERR("no data?");
166 k_oops();
167 }
168
169 LOG_INF("writing processed data back to the sample device");
170 sample_driver_write(sample_device, data);
171 sys_heap_free(&shared_pool, data);
172 pending_count--;
173 writeback_count++;
174 }
175
176 /* Fairly meaningless example to show an application-defined system
177 * call being defined and used.
178 */
179 ret = magic_syscall(&writeback_count);
180 if (ret != 0) {
181 LOG_ERR("no more magic!");
182 k_oops();
183 }
184
185 LOG_DBG("writeback thread exiting");
186 LOG_INF("SUCCESS");
187 }
188
189 /* Supervisor mode setup function for application A */
app_a_entry(void * p1,void * p2,void * p3)190 void app_a_entry(void *p1, void *p2, void *p3)
191 {
192 struct k_mem_partition *parts[] = {
193 #if Z_LIBC_PARTITION_EXISTS
194 &z_libc_partition,
195 #endif
196 &app_a_partition, &shared_partition
197 };
198
199 sample_device = device_get_binding(SAMPLE_DRIVER_NAME_0);
200 if (sample_device == NULL) {
201 LOG_ERR("bad sample device");
202 k_oops();
203 }
204
205 /* Initialize a memory domain with the specified partitions
206 * and add ourself to this domain. We need access to our own
207 * partition, the shared partition, and any common libc partition
208 * if it exists.
209 */
210 k_mem_domain_init(&app_a_domain, ARRAY_SIZE(parts), parts);
211 k_mem_domain_add_thread(&app_a_domain, k_current_get());
212
213 /* Assign a resource pool to serve for kernel-side allocations on
214 * behalf of application A. Needed for k_queue_alloc_append().
215 */
216 k_thread_heap_assign(k_current_get(), &app_a_resource_pool);
217
218 /* Set the callback function for the sample driver. This has to be
219 * done from supervisor mode, as this code will run in supervisor
220 * mode in IRQ context.
221 */
222 sample_driver_set_callback(sample_device, sample_callback, NULL);
223
224 /* Set up the writeback thread, which takes processed data from
225 * application B and sends it to the sample device.
226 *
227 * This child thread automatically inherits the memory domain of
228 * this thread that created it; it will be a member of app_a_domain.
229 *
230 * Initiailize this thread with K_FOREVER timeout so we can
231 * modify its permissions and then start it.
232 */
233 k_thread_create(&writeback_thread, writeback_stack,
234 K_THREAD_STACK_SIZEOF(writeback_stack),
235 writeback_entry, NULL, NULL, NULL,
236 -1, K_USER, K_FOREVER);
237 k_thread_access_grant(&writeback_thread, &shared_queue_outgoing,
238 sample_device);
239 k_thread_start(&writeback_thread);
240
241 /* We are about to drop to user mode and become the monitor thread.
242 * Grant ourselves access to the kernel objects we need for
243 * the monitor thread to function.
244 *
245 * Monitor thread needs access to the message queue shared with the
246 * ISR, and the queue to send data to the processing thread in
247 * App B.
248 */
249 k_thread_access_grant(k_current_get(), &mqueue, sample_device,
250 &shared_queue_incoming);
251
252 /* We now do a one-way transition to user mode, and will end up
253 * in monitor_thread(). We could create another thread which just
254 * starts in user mode, but this lets us re-use the current one.
255 */
256 k_thread_user_mode_enter(monitor_entry, NULL, NULL, NULL);
257 }
258