/* * Copyright (c) 2019 Intel Corporation. * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include "sample_driver.h" #include "app_shared.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_HEAP_DEFINE(app_a_resource_pool, 256 * 5 + 128); /* 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 const 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(const 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_heap_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_heap_free(&shared_pool, 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) { int ret; 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. */ ret = k_mem_domain_init(&app_a_domain, ARRAY_SIZE(parts), parts); __ASSERT(ret == 0, "k_mem_domain_init failed %d", ret); ARG_UNUSED(ret); 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_heap_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. * * Initialize 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); }