Use the core k_heap API pervasively within our tree instead of the z_mem_pool wrapper that provided compatibility with the older mempool implementation. Almost all of this is straightforward swapping of one alloc/free call for another. In a few cases where code was holding onto an old-style "mem_block" a local compatibility struct with a single field has been swapped in to keep the invasiveness of the changes down. Note that not all the relevant changes in this patch have in-tree test coverage, though I validated that it all builds. Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
257 lines
7.3 KiB
C
257 lines
7.3 KiB
C
/*
|
|
* 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_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)
|
|
{
|
|
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_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.
|
|
*
|
|
* 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);
|
|
}
|