Legacy Applications Porting Guide

Note

This document is still work in progress.

This guide will help you move your applications from the nanokernel/microkernel model to the unified kernel. The unified kernel was introduced with Zephyr Kernel 1.6.0 which was released late 2016.

A list of the major changes that came with the unified kernel can be found in the section Changes from Version 1 Kernel.

API Changes

As described in the section Kernel APIs the kernel now has one unified and consistent API with new naming.

An application using the old APIs can still be compiled using a legacy interface that translates old APIs to the new APIs. This legacy interface maintained in include/legacy.h can be used as a guide when porting a legacy application to the new kernel.

Same Arguments

In many cases, a simple search and replace is enough to move from the legacy to the new APIs, for example:

Additional Arguments

The number of arguments to some APIs have changed,

  • nano_sem_init() -> k_sem_init()

    This function now accepts 2 additional arguments:

    • Initial semaphore count
    • Permitted semaphore count

    When porting your application, make sure you have set the right arguments. For example, calls to the old API:

    nano_sem_init(sem)
    

    depending on the usage becomes in most cases:

    k_sem_init(sem, 0, UINT_MAX);
    

Return Codes

Many kernel APIs now return 0 to indicate success and a non-zero error code to indicate the reason for failure. You should pay special attention to this change when checking for return codes from kernel APIs, for example:

Application Porting

The existing Synchronization Sample from the Zephyr tree will be used to guide you with porting a legacy application to the new kernel.

The code has been ported to the new kernel and is shown below:

 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
#include <zephyr.h>
#include <misc/printk.h>

/*
 * The hello world demo has two threads that utilize semaphores and sleeping
 * to take turns printing a greeting message at a controlled rate. The demo
 * shows both the static and dynamic approaches for spawning a thread; a real
 * world application would likely use the static approach for both threads.
 */


/* size of stack area used by each thread */
#define STACKSIZE 1024

/* scheduling priority used by each thread */
#define PRIORITY 7

/* delay between greetings (in ms) */
#define SLEEPTIME 500


/*
 * @param my_name      thread identification string
 * @param my_sem       thread's own semaphore
 * @param other_sem    other thread's semaphore
 */
void helloLoop(const char *my_name,
	       struct k_sem *my_sem, struct k_sem *other_sem)
{
	while (1) {
		/* take my semaphore */
		k_sem_take(my_sem, K_FOREVER);

		/* say "hello" */
		printk("%s: Hello World from %s!\n", my_name, CONFIG_ARCH);

		/* wait a while, then let other thread have a turn */
		k_sleep(SLEEPTIME);
		k_sem_give(other_sem);
	}
}

/* define semaphores */

K_SEM_DEFINE(threadA_sem, 1, 1);	/* starts off "available" */
K_SEM_DEFINE(threadB_sem, 0, 1);	/* starts off "not available" */


/* threadB is a dynamic thread that is spawned by threadA */

void threadB(void *dummy1, void *dummy2, void *dummy3)
{
	ARG_UNUSED(dummy1);
	ARG_UNUSED(dummy2);
	ARG_UNUSED(dummy3);

	/* invoke routine to ping-pong hello messages with threadA */
	helloLoop(__func__, &threadB_sem, &threadA_sem);
}

char __noinit __stack threadB_stack_area[STACKSIZE];


/* threadA is a static thread that is spawned automatically */

void threadA(void *dummy1, void *dummy2, void *dummy3)
{
	ARG_UNUSED(dummy1);
	ARG_UNUSED(dummy2);
	ARG_UNUSED(dummy3);

	/* spawn threadB */
	k_thread_spawn(threadB_stack_area, STACKSIZE, threadB, NULL, NULL, NULL,
		       PRIORITY, 0, K_NO_WAIT);

	/* invoke routine to ping-pong hello messages with threadB */
	helloLoop(__func__, &threadA_sem, &threadB_sem);
}

K_THREAD_DEFINE(threadA_id, STACKSIZE, threadA, NULL, NULL, NULL,
		PRIORITY, 0, K_NO_WAIT);

Porting a Nanokernel Application

Below is the code for the application using the legacy kernel:

 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
#include <zephyr.h>
#include <misc/printk.h>


/*
 * Nanokernel version of hello world demo has a task and a fiber that utilize
 * semaphores and timers to take turns printing a greeting message at
 * a controlled rate.
 */


/* specify delay between greetings (in ms); compute equivalent in ticks */

#define SLEEPTIME  500
#define SLEEPTICKS (SLEEPTIME * sys_clock_ticks_per_sec / 1000)

#define STACKSIZE 2000

char __stack fiberStack[STACKSIZE];

struct nano_sem nanoSemTask;
struct nano_sem nanoSemFiber;

void fiberEntry(void)
{
	struct nano_timer timer;
	uint32_t data[2] = {0, 0};

	nano_sem_init(&nanoSemFiber);
	nano_timer_init(&timer, data);

	while (1) {
		/* wait for task to let us have a turn */
		nano_fiber_sem_take(&nanoSemFiber, TICKS_UNLIMITED);

		/* say "hello" */
		printk("%s: Hello World!\n", __func__);

		/* wait a while, then let task have a turn */
		nano_fiber_timer_start(&timer, SLEEPTICKS);
		nano_fiber_timer_test(&timer, TICKS_UNLIMITED);
		nano_fiber_sem_give(&nanoSemTask);
	}
}

void main(void)
{
	struct nano_timer timer;
	uint32_t data[2] = {0, 0};

	task_fiber_start(&fiberStack[0], STACKSIZE,
			(nano_fiber_entry_t) fiberEntry, 0, 0, 7, 0);

	nano_sem_init(&nanoSemTask);
	nano_timer_init(&timer, data);

	while (1) {
		/* say "hello" */
		printk("%s: Hello World!\n", __func__);

		/* wait a while, then let fiber have a turn */
		nano_task_timer_start(&timer, SLEEPTICKS);
		nano_task_timer_test(&timer, TICKS_UNLIMITED);
		nano_task_sem_give(&nanoSemFiber);

		/* now wait for fiber to let us have a turn */
		nano_task_sem_take(&nanoSemTask, TICKS_UNLIMITED);
	}
}

Porting a Microkernel Application

The MDEF feature of the legacy kernel has been eliminated. Consequently, all kernel objects are now defined directly in code.

Below is the code for the application using the legacy kernel:

 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
#include <zephyr.h>
#include <misc/printk.h>

/*
 * Microkernel version of hello world demo has two tasks that utilize
 * semaphores and sleeps to take turns printing a greeting message at
 * a controlled rate.
 */


/* specify delay between greetings (in ms); compute equivalent in ticks */

#define SLEEPTIME  500
#define SLEEPTICKS (SLEEPTIME * sys_clock_ticks_per_sec / 1000)

/*
 *
 * @param taskname    task identification string
 * @param mySem       task's own semaphore
 * @param otherSem    other task's semaphore
 *
 */
void helloLoop(const char *taskname, ksem_t mySem, ksem_t otherSem)
{
	while (1) {
		task_sem_take(mySem, TICKS_UNLIMITED);

		/* say "hello" */
		printk("%s: Hello World from %s!\n", taskname, CONFIG_ARCH);

		/* wait a while, then let other task have a turn */
		task_sleep(SLEEPTICKS);
		task_sem_give(otherSem);
	}
}

void taskA(void)
{
	/* taskA gives its own semaphore, allowing it to say hello right away */
	task_sem_give(TASKASEM);

	/* invoke routine that allows task to ping-pong hello messages with taskB */
	helloLoop(__func__, TASKASEM, TASKBSEM);
}

void taskB(void)
{
	/* invoke routine that allows task to ping-pong hello messages with taskA */
	helloLoop(__func__, TASKBSEM, TASKASEM);
}

A microkernel application defines the used objects in an MDEF file, for this porting sample using the Synchronization Sample, the file is shown below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
% Application       : Hello demo

% TASK NAME  PRIO ENTRY STACK GROUPS
% ==================================
  TASK TASKA    7 taskA  1024 [EXE]
  TASK TASKB    7 taskB  1024 [EXE]

% SEMA NAME
% =============
  SEMA TASKASEM
  SEMA TASKBSEM

In the unified kernel the semaphore will be defined in the code as follows:

1
2
3
4
/* define semaphores */

K_SEM_DEFINE(threadA_sem, 1, 1);	/* starts off "available" */
K_SEM_DEFINE(threadB_sem, 0, 1);	/* starts off "not available" */

The threads (previously named tasks) are defined in the code as follows, for thread A:

1
2
K_THREAD_DEFINE(threadA_id, STACKSIZE, threadA, NULL, NULL, NULL,
		PRIORITY, 0, K_NO_WAIT);

Thread B (taskB in the microkernel) will be spawned dynamically from thread A (See Thread Spawning section):

1
2
3
	/* spawn threadB */
	k_thread_spawn(threadB_stack_area, STACKSIZE, threadB, NULL, NULL, NULL,
		       PRIORITY, 0, K_NO_WAIT);