M0 vs M3/4/7

Hey guys!

I just had a question regarding the Cortex M0 Port.

Why is the M0 Port not using the SVC to start the OS?
On the other hand, why are other M Architectures using it?

I know there is a comment about the VTOR having something todo with it, but i do not understand how this is related?

Furthermore, i have a cypress controller Cortex M0+ which does have a VTOR, do i need to adapt the port?

BR Andy

Partial answer - the original Cortex-M3 ports did not start the scheduler using an SVC - you can see the original code here: https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/V4.0.1/Source/portable/GCC/ARM_CM3/port.c#L127 I forget exactly why we had to change it, but the info may be in the change history or the check-in comments (was using SVN and SourceForge back then). I suspect it may have been an issue with co-ordinating the startup when using the BASEPRI register - which the M0 doesn’t have. I don’t recall there being an issue with VTOR - but the port dates back to 2006 so memory might not be right.

Looks like the change to use an SVC was made in 2008 - the change history doesn’t give much of a clue so I would have to diff the code versions to get more info.

Thanks for the quick reply again Barry.
I might also look into it, because i cannot for the live of me figure it out…

I will just take the CM0 port for now and hope nothing crashes…
BTW, the CM0+ does have a basepri register.

I was just referring to VTOR since this it is how it is documented in the code.

I was also wondering if the same strategy can then apply to any other CMx? (w/o MPU of course)
Since it would decrease IRQs and code overhead on startup. Just a thought…

What is also wildly different is, that you always use some sort of exception context still (also in the old version you linked) to arrange your first programm stack.

where as in the CM0 port it just casually changes directly from bare-metal to OS context?

EDIT:
That is the code section that got me curious:

void vPortStartFirstTask( void )
{
	/* The MSP stack is not reset as, unlike on M3/4 parts, there is no vector
	table offset register that can be used to locate the initial stack value.
	Not all M0 parts have the application vector table at address 0. */
	__asm volatile(
	"	.syntax unified				\n"
	"	ldr  r2, pxCurrentTCBConst2	\n" /* Obtain location of pxCurrentTCB. */
	"	ldr  r3, [r2]				\n"
	"	ldr  r0, [r3]				\n" /* The first item in pxCurrentTCB is the task top of stack. */
	"	adds r0, #32					\n" /* Discard everything up to r0. */
	"	msr  psp, r0					\n" /* This is now the new top of stack to use in the task. */
	"	movs r0, #2					\n" /* Switch to the psp stack. */
	"	msr  CONTROL, r0				\n"
	"	isb							\n"
	"	pop  {r0-r5}					\n" /* Pop the registers that are saved automatically. */
	"	mov  lr, r5					\n" /* lr is now in r5. */
	"	pop  {r3}					\n" /* Return address is now in r3. */
	"	pop  {r2}					\n" /* Pop and discard XPSR. */
	"	cpsie i						\n" /* The first task has its context and interrupts can be enabled. */
	"	bx   r3						\n" /* Finally, jump to the user defined task code. */
	"								\n"
	"	.align 4					\n"
	"pxCurrentTCBConst2: .word pxCurrentTCB	  "
				  );
}

Hi Andreas,

the only gotcha here is that FreeRTOS recycles the startup stack (as normally defined in the linker command file and referenced on most MCUs as offset 0 or 1 in the IVT) as the interrupt stack. That’s really all there is to it; we’ve had this issue numerous times here (for example regarding the case that automatic variables allocated during startup will be invalid once the scheduler is started).

Now the question is how to obtain that stack pointer. On M3+, use the vtor register. On M0 MCUs, there is no vtor, so your second best guess is to assume that the IVT is located at address 0. As the comment implies, this is not always safe to assume, so (in my reading) the interrupt stack starts where the current MSP is as the scheduler is started, so in this port, you would be able to reuse automatic variables of the startup context.

Thanks for the answer! :slight_smile:

I might be a little dense atm, but i cannot deduce what you are explaining to me.
Why are you even interested in the Main stack at this point?

In the other ports you are rewinding the main stack to its base address (to free up more IRQ Space, i think?) but here you are just swapping context and thats it?
You are not touching MSP at all?

Reading the code, you are poping PSP from the first task context, switch to Progress stack and execute first task?

BR Andy

I think this explanation is correct, except that the code is not “swapping contexts” but instead sets up the initial task context (“swapping” would imply that there already is a context swapped out from which obviously isn’t the case with the very first context).

Richard Barry would need to answer the question why the interrupt stack is (if possible) reset to the initial startup stack pointer, but I tend to believe that freeing up space for the IRQ stack is fair enough reason.

Thank you very much for your input. I was wondering if i am overseeing something drastic ^^

You are correct, swappint is be the wrong term.

Okay. So i might wanna adapt the CM0 Port to CM0+ in the same manner as the others do.
Now for my last question.
EDIT: As Barry mentioned, he does not know why they changed to use SVC… I am just very curious at that fact.

Why are we doing this in SVC/PendSV Anyway? Why are the CM3/4/7 ports not also just setting up first context, switch to it and then reverse main stack? Like CM0 does. CM0 never changes into exception context.

BR

well, if Richard (Barry is his last name) doesn’t know, who would? :wink:

No, the CM0+ does not have BASEPRI. If it did, there would probably be a port specific to the plus. So the CM0 port is the right one for you to use. It’s been used successfully on CM0+ for years.

1 Like

You are Right Jeff, i mixed it up with the PRIMASK Register.

It’s been used successfully on CM0+ for years.

Well I wasn’t disputing that the port works.
I was just wondering why the start of the Scheduler/Kernel is done differently for the CM0/+ than all other cortex derivatives?

EDIT:
Looking at the History, it wasn’t always done like that.

+ Update the Cortex-M0 port layers to allow the scheduler to be started without using the SVC handler

But I was wondering why?

I have a thesis:

All other CMx beside the CM0 do revert the main stack at startup. The CM0 just lives with it.
So this is a small optimisation to implement SVC to not have to check for “First Task” in the PendSV Handler everytime, that the CM0 does not use.
So later on, the SVC contents were just moved into the non-exception context, since it was not needed → No Stack reverting is done.

Is my assumption correct?

Oh I thought you were going to do some porting effort for the plus. I misunderstood.

The CM0 doesn’t have privileged/unprivileged mode, so no difference starting the OS with or without an ISR. (No need for an ISR which puts the CPU in privileged mode.) On the other models, the CPU is either in privileged or unprivileged mode, so maybe it’s just a best practice to use an ISR to guarantee the CPU is in privileged mode for any privileged accesses/instructions used during OS startup?

Hey Jeff!

Sorry for the late response, I fell ill for the last 2 days.

Oh, that might be a good hint, that it also has something todo with priv-lvel handling.
I will look into that - didn’t know the CM0 didn’t have that.

BR