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