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