General porting procedures

Hello!

Can somebody to clarify me in general porting procedure?,please
I still in progress of mips64 porting and I’ve already implemented basic concepts but to finish it I dont have complete picture

I based on PIC32MZ and used last version of FreeRTOS sources.
As Ive understood I have to do changes according my MCU in the following files and with the following context:

  1. port.c
  • configure the tick interrupt() or interrtupt for regular tick;
  • initinalize the stack for task(pxPortInitialiseStack) - but I dont understand from what I have to get values and ranges for - *pxTopOfStack = (StackType_t) 0xDEADBEEF; и *pxTopOfStack = (StackType_t) 0x12345678;
  • setup the software interrupt(for switich context) in xPortStartScheduler
  1. portmacro.h
  • enable and disable interrupt including - software(for switch context)
  1. port_asm.s
  • Save/Restore the stack pointer to the task to control pxCurrentTCB;
  1. ISR_support.h
  • save SFR, EPC registers and control stack location and procuring interrupt nesting.

Ive tried to find out the verification about my assumptions in here

But couldnt found for complete port

Thus, I asked to correct me, please

Hey, we have a guiding on porting FreeRTOS here: FreeRTOS Porting Guide

Thanks! But this is exactly that I based on and itnt cover all about porting

Thats why I have got declared above questions

Hi @addi,

There isn’t necessarily a more full porting picture - every architecture/compiler is different and thus will require you to do things specific for the architecture/compiler, depending on what hardware you want to utilize, and FreeRTOS features you want to support.

Blockquote * initinalize the stack for task(pxPortInitialiseStack) - but I dont understand from what I have to get values and ranges for - *pxTopOfStack = (StackType_t) 0xDEADBEEF; и *pxTopOfStack = (StackType_t) 0x12345678;

The pxPortInitialiseStack, portSAVE_CONTEXT, and portRESTORE_CONTEXT are closely tied together so will try to explain a bit on how these work. When a FreeRTOS task is not in a running state, the hardware context (like registers, stack pointer, etc.) needs to be stored within the stack of the task, so that portRESTORE_CONTEXT will read the hardware context from the task’s stack, and restore the state of the hardware back to where it was when the task was last running. When a FreeRTOS task is first created, it is not in a running state, so when its eventually scheduled to start running, the call to portRESTORE_CONTEXT is going to initialize the hardware context based on what is stored in its stack. This is the purpose of pxPortInitialiseStack. pxPortInitialiseStack initializes the part of the stack that will hold the initial value of hardware registers (i.e. the stack pointer, CPU registers, etc.) so that they are set properly when a context switch, which calls portRESTORE_CONTEXT, starts running the newly created task. After a task is swapped off, then portSAVE_CONTEXT should save the state of applicable hardware in the same place as expected by portRESTORE_CONTEXT. Normally this is done by just appending this information to the current top of the stack when calling portSAVE_CONTEXT, and and popping it off the stack when calling portRESTORE_CONTEXT. How you choose to implement these functions is up to you and your hardware’s architecture.

Im not sure why they store 0xDEADBEEF and 0x12345678 on the stack but I imagine it’s most likely used to detect stack corruption or overflow.

As per Jason’s reply, those values form the context of a task when the task first starts as they get popped off the stack and into the CPU registers. Values that get popped into control registers are essential to be correct for the task’s wanted state because each bit has a specific meaning. Values that get popped into general purpose registers can normally take value, and often get set to something that helps with debugging. For example, you can set the value that should get popped into R0 to 0, and the value that should get popped into R1 to 1, etc. There can additionally be values like 0xdeadbeef (the actual number doesn’t matter) placed in positions that should not get popped into any register - if you see that value in a register when a task starts you know the code implementation is wrong. So - some values are critical and others just help debugging.

Thanks a lot!
Сould you suggest in what case(in tickinterrupt or in software yeild interrupt) I must to store and restore shadow registers(of SFR)?, please.

Thanks and advance

and is it necessary to define PRIVILEGED_FUNCTION(mpu_wrappers.h) for do that?

The PRIVILEGED_FUNCTION macros are to decorate functions that must be placed in the privileged function memory region. This region is created in the linker script so it is possible to identify the small number of functions that would be allowed to raise privilege in the MPU port.

You will need to understand how shadow registers work in your architecture (when they are updated and restored to the primary registers) and use that information to ensure context saving/restoring works correctly.

The architecture is mip64, and I sure what shadow registers cover hardware interttups, but I didnt find any confirmation regarding software interrupts in open specifcations.
And due to the fact that I have to port yield in my software interrupt, - I have to know that

An application may need to port yield in a hardware interrupt as well. A common pattern is to use a semaphore or queue from an interrupt to inform a task of some external event. Many FreeRTOS API’s have a ‘fromISR’ version and these can cause a context switch.

Thank you for your replay!
Maybe could you help me a little in my current porting problem then, please?

I have got problem in switching of 2 tasks
After when scheduler staterd fill task started and then check task and its all, anothet switchin doesnt occure however fill trander data per queue in cycle, so check has to be also in cycle as its in pending receive

Thats my main:

/* Kernel includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

#include "iolib.h"
int var1_addr;// = 0x1503FF00;
int var2_addr;// = 0x1503FF01;
int var3_addr;// = 0x1503FF03;
	var1_addr = 0xFFFFFFFFB503FF00;//0x07FFFF00;//0x1503FF00;
	var2_addr = 0xFFFFFFFFB503FF08;//0x07FFFF08;//0x1503FF01;
	var3_addr = 0xFFFFFFFFB503FF0B;//0x07FFFF0B;
/* The rate at which data is sent to the queue, specified in milliseconds. */
#define mainQUEUE_SEND_FREQUENCY_MS			( 10 )
#define IOP1_BASE			0xffffffffB610FF50


/* The number of items the queue can hold.  This is 1 as the receive task
will remove items as they are added, meaning the send task should always find
the queue empty. */
#define mainQUEUE_LENGTH					( 16 )


/* Priorities at which the tasks are created. */
#define mainQUEUE_SEND_TASK_PRIORITY	( tskIDLE_PRIORITY + 1 )
#define mainQUEUE_RECEIVE_TASK_PRIORITY	( tskIDLE_PRIORITY + 2 )

 
static void CheckTask( void *pvParameters );
static void FillTask( void *pvParameters );
static void taskA( void* );
static void taskB( void* );

///-----------------------------------------------------------

// The queue used by both tasks. 
static xQueueHandle xQueue = NULL;

unsigned long ulTaskNumber[ 3 ];

uint8_t data[16];

//-----------------------------------------------------------
void main(void)
{

	*((volatile uint16_t* const)(IOP1_BASE  )) = 0x0000;
	*((volatile uint16_t* const)(IOP1_BASE + 0x04)) = 0xAA55;

	xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( unsigned long ) );
	if( xQueue != NULL )
	{
		 xTaskCreate( CheckTask, ( signed char * ) "CheckTask",	configMINIMAL_STACK_SIZE, NULL,  ( tskIDLE_PRIORITY + 2 ), NULL ); // tskIDLE_PRIORITY + 2 , NULL );
		 xTaskCreate( FillTask, ( signed char * ) "FillTask", configMINIMAL_STACK_SIZE, NULL,  ( tskIDLE_PRIORITY + 2 ), NULL ); //tskIDLE_PRIORITY + 1 , NULL );
					*((volatile uint16_t* const)(IOP1_BASE + 0x04)) = 0xAA50;
		vTaskStartScheduler();
	}

	for( ;; );
}

/*-----------------------------------------------------------*/

 void vApplicationTickHook( void )
 {
     for( ;; ){
         asm("nop");
     }
 }

/*-----------------------------------------------------------*/

static void FillTask( void *pvParameters )
{
	portTickType xNextWakeTime;
	const unsigned long ulValueToSend = 8;
	*((volatile uint16_t* const)(IOP1_BASE + 0x04)) = 0xAA54;
    for(int i = 0; i < 16; i++)
        data[i] = i + 5;

	
for( ;;)
{
	
	*((volatile uint16_t* const)(IOP1_BASE + 0x04)) = 0xAA54;
	if( xQueueSend( xQueue, &ulValueToSend, 0 ) != pdPASS )
	 
	*((volatile uint16_t* const)(IOP1_BASE + 0x04)) = 0xAA01;
	else
		*((volatile uint16_t* const)(IOP1_BASE + 0x04)) = 0xAA03;
	*((volatile uint16_t* const)(IOP1_BASE + 0x04)) = 0xAA00;
	
}
	
}
/*-----------------------------------------------------------*/

static void CheckTask( void *pvParameters )
{
unsigned long ulReceivedValue;

    for(;;){
		
	xQueueReceive( xQueue, &ulReceivedValue, 1000);///;portMAX_DELAY );
    	if( ulReceivedValue == 8 )
    	{*((volatile uint16_t* const)(IOP1_BASE + 0x04)) = 0x55AA;
            for(int i = 0;i < 16;i++)
                if(data[i] != i + 5){
    
		}
		 
    }
}

/*-----------------------------------------------------------*/
///

void vAssertCalled( const char *pcFileName, unsigned long ulLine )
{
    asm("di");
		*((volatile uint16_t* const)(IOP1_BASE + 0x04)) = 0xAA56;
	*((volatile uint64_t* const)(var3_addr)) = 0x0000FFFF0000AA56;
	for (;;);
}

And thas my hardware ticktimer handler

_CP0_TIM_VEC4_handler:

		// Cause_DC = 1
		mfc0    a0, C0_CAUSE
		li      a1, ~(1 << 27)
		and     a0, a0, a1
		mtc0    a0, C0_CAUSE
		ehb

		di
		ehb
		ld		sp, xISRStackTop 
		// LA      sp, __stack

		LA		gp, _gp
		/*.macro  portSAVE_CONTEXT*/
		// Make room for the context. First save the current status so it can be
		//manipulated, and the cause and EPC registers so their original values are
		//captured. 
		ADDU        sp, sp, -CTX_SIZE
		REG_S       k1, CTX_K1(sp)

		// k1 is used as the frame pointer. 
		ADDU        k1, zero, sp
		
		// Save the context into the space just created. 
		/*.macro _gpctx_save*/

		/*ld		sp, xISRStackTop*/
  
		/* Save general registers.  */
		/* $27/k1 must be saved prior to using this macro.  */
		.irp num,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,28,29,30,31
		REG_S	$\num, CTX_REG(\num) (k1)
		.endr

		PTR_S   $0, CTX_LINK(k1)	/* Clear the link field.  */

		mfhi	$9
		REG_S	$9, CTX_HI0(k1)
		mflo	$10
		REG_S	$10, CTX_LO0(k1)

		/* Save CP0 registers.  */
		PTR_MFC0 $31, C0_EPC
		ehb
		/* 	mfc0	$10, C0_STATUS */
		/* 	ehb */
		REG_S	$31, CTX_EPC(k1)
		ehb
		/* 	sw		$10, CTX_STATUS(k1) */
		.endm

		// Save the stack pointer. 
		LA          s6, uxSavedTaskStackPointer
		REG_S       k1, (s6)
		REG_L       k1, CTX_K1(sp)

		/*.endm*/ 
		jal		vPortIncrementTick
		nop
		nop
		/*.macro  portRESTORE_CONTEXT*/

		LA          s6, uxSavedTaskStackPointer
		REG_L       k0, (s6)

		// Restore the context. 
		/*.macro _gpctx_load*/
		REG_L	$9, CTX_HI0(k0)
		REG_L	$10, CTX_LO0(k0)
		mthi	$9
		mtlo	$10

		/* Restore the general registers.  */
		.irp num,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
		REG_L	$\num, CTX_REG(\num) (k0)
		.endr

		/* Restore CP0 registers, kernel registers and stack with
	   interrupts disabled.  */
		/*  	di */
		/* 	lw		k1, CTX_STATUS(k0) */
		/* 	mtc0	k1, C0_STATUS */
		REG_L	k1, CTX_EPC(k0)
		PTR_MTC0 k1, C0_EPC

		REG_L	$27, CTX_REG(27) (k0)
		REG_L	$28, CTX_REG(28) (k0)
		REG_L	$29, CTX_REG(29) (k0)
		REG_L	$30, CTX_REG(30) (k0)
		REG_L	$31, CTX_REG(31) (k0)

		/* Finally restore k0/$26.  */
		REG_L	$26, CTX_REG(26)(k0)

		/*.endm*/

		LA          sp, uxSavedTaskStackPointer
		REG_L       sp, (sp)

		ADDU        sp, sp, CTX_SIZE


		/*.endm*/
		// Count = 0
		mtc0	zero, C0_COUNT
		ehb

		mfc0    a0, C0_CAUSE
		li      a1, (1 << 27)
		or      a0, a0, a1
		mtc0    a0, C0_CAUSE
		ehb
		ei
		ehb

		eret
		.end _CP0_TIM_VEC4_handler

And thats my software interrupt handler to yield

SW_VEC1_handler:
	

	#mfc0    a0, C0_CAUSE
		
		di
		ehb

		ld		sp, xISRStackTop
		LA		gp, _gp
		/*portYIELD_SAVE*/
		// Make room for the context. First save the current status so it can be
		//manipulated. 
		daddiu      sp, sp, -CTX_SIZE
		REG_S       k1, CTX_K1(sp)

		// k0cd is used as the frame pointer. 
		move        k1, sp

		// Save the context into the space just created. 
		/*_gpctx_save*/
		.irp num,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,27,28,29,30,31
        rdpgpr  k1, $\num
		REG_S   k1, CTX_REG(\num) (k0)
		.endr

		rdpgpr  k1, $26
		nop
		nop
		REG_S   k1, CTX_REG(26) (k0)

		mfhi	k1
		REG_S	k1,  CTX_HI0(k0)
		mflo	k1
		REG_S	k1,  CTX_LO0(k0)

		/* Save CP0 registers.  */
		PTR_MFC0   k1, C0_EPC
		REG_S      k1, CTX_EPC(k0)

		// Save the stack pointer to the task. 
		LA			s7, pxCurrentTCB
		REG_L		s7, (s7)
		REG_S		k1, (s7) 
		
		jal		vTaskSwitchContext
		nop
		nop

		/*portYIELD_RESTORE*/
		
		// Set the context restore register from the TCB. 
		LA			s0, pxCurrentTCB
		REG_L		s0, (s0)
		REG_L		k0, (s0)

	
		// The _gpctx_load restore code just wholesale copies the
		// status register from the context back to the register loosing
		// any changes that may have occured, 'status' is really global state
		// You dont enable interrupts on one thread and not another...
		// So we just copy the current status value into the saved value
		// so nothing changes on the restore
		//
		/***/
		mfc0	k1, C0_STATUS
		sw		k1, CTX_STATUS(k0)
		/***/
		/*_gpctx_load*/
		REG_L	$9, CTX_HI0(k0)
		REG_L	$10, CTX_LO0(k0)
		mthi	$9
		mtlo	$10

		/* Restore the general registers.  */
		.irp num,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
		REG_L	$\num, CTX_REG(\num) (k0)
		.endr

		/* Restore CP0 registers, kernel registers and stack with
	   interrupts disabled.  */
		/*  	di */
		/* 	lw		k1, CTX_STATUS(k0) */
		/* 	mtc0	k1, C0_STATUS */
		REG_L	k1, CTX_EPC(k0)
		PTR_MTC0 k1, C0_EPC

		REG_L	$27, CTX_REG(27) (k0)
		REG_L	$28, CTX_REG(28) (k0)
		REG_L	$29, CTX_REG(29) (k0)
		REG_L	$30, CTX_REG(30) (k0)
		REG_L	$31, CTX_REG(31) (k0)

		/* Finally restore k0/$26.  */
		REG_L	$26, CTX_REG(26)(k0)


		// Restore the stack pointer from the TCB. 
		LA			sp, pxCurrentTCB
		REG_L		sp, (sp)
		REG_L		sp, (sp)

		// Remove stack frame. 
		daddiu		sp, sp, CTX_SIZE
	
		ei
		ehb
	        
		eret
		.end _SW_VEC1_handler

And I see that I have two consecutive triggers of software interrupt, - one that I do after increment tick and additional from FreeRTOS in portYIELD_WITHIN_API() of xQueueReceive when I stopped in fill task

Please help !

I am not familiar with this architecture so I can only provide general guidance. What is the definition of vPortIncrementTick? You need to call xTaskIncrementTick from the tick interrupt handler and do a context switch whenever it returns true. I do not see that in the code you shared above but do you do that elsewhere?

I’d suggest to do the following -

  1. Verify that the tick count is incrementing.
  2. Just have 2 periodic tasks and verify that those are being scheduled periodically.
  3. Verify that an explicit yield causes the context switch.
  4. Run all these tests.

Thank you very much for detailed answer!!!
I have got the following increment tick function:

void vPortIncrementTick( void )
{
   	if( xTaskIncrementTick() != pdFALSE )
	{
		/* Pend a context switch. */
		portYIELD();
	}
} 

Where, -

#define portYIELD() \
{ \
	mips_eic0_seticureq(1); \ // do software interrupt request
} 

I’ll try to verify tick incrementing and can replay about it, bit later
Thanks again!

I checked tick incrementiation and got the following:

  1. When I stayed in `“CheckTask” task I see incremeting up to 5 and then CPU stoped in:
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t ) 1 )
{
}
  1. incremetation started during tick handler execution
  2. in portYIELD() I entering during tick handler execution and before switching to “CheckTask” task but after first incerementing at first time and at the second time I see enterting after second incrementing and after switching in “CheckTask” task. The rest 3 times of incrementing(till 5) I see without portYIELD() entering
  3. All incremeting have done in:
if( xConstTickCount >= xNextTaskUnblockTime )
{}

For now, remove all the tasks and get one (and then 2) very simple periodic tasks working which do something very minimal (like just incrementing a variable).

Have you correctly implemented critical section related macros in your port?

Thank you for your replay!!
Ive done the following changes to simplify tasks interaction:

/* Kernel includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

#include "iolib.h"

/* The rate at which data is sent to the queue, specified in milliseconds. */
#define mainQUEUE_SEND_FREQUENCY_MS			(1)///( 10 )
#define IOP1_BASE			0xffffffffB610FF50
static void CheckTask( void *pvParameters );
static void FillTask( void *pvParameters );
///-----------------------------------------------------------
int16_t temp=0;
//-----------------------------------------------------------
void main(void)
{
    *((volatile uint16_t* const)(IOP1_BASE  )) = 0x0000;
	*((volatile uint16_t* const)(IOP1_BASE + 0x04)) = 0xAA55;

		 xTaskCreate( CheckTask, ( signed char * ) "CheckTask",	configMINIMAL_STACK_SIZE, NULL,  ( tskIDLE_PRIORITY + 2 ), NULL ); // tskIDLE_PRIORITY + 2 , NULL );
		 xTaskCreate( FillTask, ( signed char * ) "FillTask", configMINIMAL_STACK_SIZE, NULL,  ( tskIDLE_PRIORITY + 2 ), NULL ); //tskIDLE_PRIORITY + 1 , NULL );
     	vTaskStartScheduler();

	for( ;; );
}


/*-----------------------------------------------------------*/

static void FillTask( void *pvParameters )
{

for( ;;)
{
	*((volatile uint16_t* const)(IOP1_BASE + 0x04)) = 0xAA54;
	temp ++ ;
	*((volatile uint16_t* const)(IOP1_BASE + 0x04)) = temp;///0xAA00;
}
	
}
/*-----------------------------------------------------------*/

static void CheckTask( void *pvParameters )
{
unsigned long ulReceivedValue;

    for(;;){
		
		*((volatile uint16_t* const)(IOP1_BASE + 0x04)) = 0x55AA;
		if(temp)
		temp--;
		*((volatile uint16_t* const)(IOP1_BASE + 0x04)) = temp;
		 
    }
}

/*-----------------------------------------------------------*/
///

void vAssertCalled( const char *pcFileName, unsigned long ulLine )
{
    asm("di");
		*((volatile uint16_t* const)(IOP1_BASE + 0x04)) = 0xAA56;
	*((volatile uint64_t* const)(var3_addr)) = 0x0000FFFF0000AA56;
	for (;;);
}
///

And now see the same problem, - absence of cyclic context switching, I still staying in
“CheckTask” after only once switching from “FillTask” to “CheckTask” and no one backward

And can you direct me, please, in question about critical section and macros, - I didnt understand about what macros and maybe function in question

Another question in this regard appeared, - where return adress to “FillTask” is stored and how is possible to rewrite it by mistake?(now I see what EPC at the end do not consist of “FillTask” range, but dont understand what was the reason of changing)

Is it relates to task context and stack pointer location in prologue within tick incrementing handler pr to pxCurrentTCB saving/restoring whithin yield handler?

Or maybe it caused by absense of nesting interrupt processing in contex and yield switching?

Is the tick interrupt firing and tick count incrementing? If yes, can you step through and check that you are calling portYIELD(); in the vPortIncrementTick? And then you can trace from there why the context switch is not happening?

yes it is, and only vPortIncrementTick call portYIELD()
Ive noticed also what after switching in “CheckTask” a havnt got in r26 return address which would be “FillTask” address.
Cause of what I dont understand where return address of previous existed task is locating