Author Topic: Cortex-M startup files without assembler  (Read 21761 times)

0 Members and 3 Guests are viewing this topic.

Offline TNorthover

  • Contributor
  • Posts: 42
  • Country: us
Re: Cortex-M startup files without assembler
« Reply #25 on: March 10, 2017, 02:48:02 pm »
I don't know.  Having the interrupt controller behavior match the C abi, so you don't need special compiler behavior for ISRs, is really nice.

Unfortunately, even that bit doesn't quite work. The ABI only guarantees 8-byte stack alignment at function call boundaries, but since interrupts can occur anywhere that means a handler must realign the stack on entry (or at least, if it's going to make any calls).

You'll probably get away with it most of the time, but there's an __attribute__((interrupt("IRQ"))) if you want to be certain.
 

Offline andersm

  • Super Contributor
  • ***
  • Posts: 1198
  • Country: fi
Re: Cortex-M startup files without assembler
« Reply #26 on: March 10, 2017, 04:36:20 pm »
The ABI only guarantees 8-byte stack alignment at function call boundaries, but since interrupts can occur anywhere that means a handler must realign the stack on entry (or at least, if it's going to make any calls).
Setting the CCR.STKALIGN bit ensures the stack is 8-byte aligned when entering exceptions. The bit was added in the first update (r1p0) of the Cortex-M3 core, so practically all devices have this feature, though it's not necessarily set by default.

Offline ataradov

  • Super Contributor
  • ***
  • Posts: 12472
  • Country: us
    • Personal site
Re: Cortex-M startup files without assembler
« Reply #27 on: March 10, 2017, 04:43:09 pm »
handler must realign the stack on entry (or at least, if it's going to make any calls).

A quote from ARMv6-M Architecture Reference Manual:
Quote
The ARMv6-M architecture guarantees that all exceptions are entered with 8-byte stack alignment.
However, because exceptions can occur on any instruction boundary, it is possible that the current stack
pointer is not 8-byte aligned when an exception activates.
The AAPCS requires that the stack-pointer be 8-byte aligned on entry to a conforming function. Because
exception handlers are normally written as AAPCS conforming functions, the system must ensure natural
alignment of the stack for all arguments passed. The 8-byte alignment requirement is guaranteed in
hardware in ARMv6-M.

Not even a bit to think about.
Alex
 

Offline TNorthover

  • Contributor
  • Posts: 42
  • Country: us
Re: Cortex-M startup files without assembler
« Reply #28 on: March 10, 2017, 04:52:21 pm »
Not even a bit to think about.

Oh, thanks! I did not know that. Time to fix Clang.
 

Offline Sal Ammoniac

  • Super Contributor
  • ***
  • Posts: 1810
  • Country: us
Re: Cortex-M startup files without assembler
« Reply #29 on: March 10, 2017, 05:06:34 pm »
Code: [Select]
        /* Initialize the C library */
        __libc_init_array();


This is the thing that annoys me the most. This is usually a black box doing who knows what, including dodgy stuff like dynamic memory allocation, etc. I'd like to see a much more transparent C library distributed by the vendors. 
"That's not even wrong" -- Wolfgang Pauli
 

Offline ataradov

  • Super Contributor
  • ***
  • Posts: 12472
  • Country: us
    • Personal site
Re: Cortex-M startup files without assembler
« Reply #30 on: March 10, 2017, 05:14:52 pm »
This is the thing that annoys me the most. This is usually a black box doing who knows what, including dodgy stuff like dynamic memory allocation, etc. I'd like to see a much more transparent C library distributed by the vendors. 
Startup code is part of your application, you can do whatever you want. I personally don't even call this in any of my startup files, because I know that I don't need this.
Alex
 

Offline Bruce Abbott

  • Frequent Contributor
  • **
  • Posts: 628
  • Country: nz
    • Bruce Abbott's R/C Models and Electronics
Re: Cortex-M startup files without assembler
« Reply #31 on: March 10, 2017, 07:31:11 pm »
Startup code is part of your application, you can do whatever you want.
Sure, if you know what you are doing. But most vendors want you to use their frameworks and libraries - so you end up not knowing what you are doing. And the vast majority of example code requires their stuff, so if you want to do 'whatever you want'  then you have a lot to learn. That's why I am here.   

Quote from: ataradov
Quote from: technix
I have figured out a way to use a combination of linker scripts and C language extensions to create Cortex-M startup files without touching any assembler.
What do you mean " figured out"? This feature was the explicit design goal of the core.
So you didn't have to "figure it out"? Good for you! But most of us are using the chip vendor's bloatware, and figuring out how to go fully 'bare metal' is not trivial for us.

I have programmed the STM32F103 'bare metal' in assembler, but until now I didn't know that I could do it in C.  That's why I am interested to see how technix did it. However your code seems to be cleaner. What changes would be need to implement it on STM32?

Quote
Here are my starter projects with no assembly in them at all https://github.com/ataradov/mcu-starter-projects/
Except for the FE310, which has this... ;)

Code: [Select]
asm (" \
.section .init._start \n \
.globl _start \n \
_start: \n \
la gp, _gp \n \
la sp, _stack_top \n \
");



   
 

Offline andyturk

  • Frequent Contributor
  • **
  • Posts: 895
  • Country: us
Re: Cortex-M startup files without assembler
« Reply #32 on: March 10, 2017, 07:35:29 pm »
Code: [Select]
        /* Initialize the C library */
        __libc_init_array();


This is the thing that annoys me the most. This is usually a black box doing who knows what, including dodgy stuff like dynamic memory allocation, etc. I'd like to see a much more transparent C library distributed by the vendors.

It's generated by the linker and contains calls to all your static initializers. If you're not using C++, then (I think) you don't need to call it. If you are using C++, then __libc_init_array() will only do dodgy stuff if your constructors of static objects are doing dodgy stuff.

So, just avoid doing computation, memory allocation, etc. in a constructor for any class with a static instance. My rule is to restrict constructors to using only values that are known at link time.

Another way to make sure you __libc_init_array() is "clean" is to avoid creating global instances at all, and take complete control of when your constructors are called by having a single class with all your application state and constructing it via placement new.

Code: [Select]
    /**
     * @brief returns a reference to the application instance
     * @tparam T the type of application
     *
     * This template uses placement new to statically allocate
     * RAM for the application object, but delay its construction
     * until main() has started to run.
     */
    template<class T>
    T &the() {
      alignas(T) static char memory[sizeof(T)];
      static T *instance;

      if (instance == 0) instance = new(memory) T;
      return *instance;
    }

int main() {
  asm volatile("cpsid i"); // disable interrupts
  SystemInit();

  rtos::initialize_kernel();
  the<WholeApplication>().initialize();
  asm volatile("cpsie i"); // enable interrupts

  rtos::start_kernel();
  return 0; // NOT REACHED
}
 

Offline ataradov

  • Super Contributor
  • ***
  • Posts: 12472
  • Country: us
    • Personal site
Re: Cortex-M startup files without assembler
« Reply #33 on: March 10, 2017, 07:35:55 pm »
But most vendors want you to use their frameworks and libraries - so you end up not knowing what you are doing.
I don't use vendor libraries. They are garbage in most cases anyway.

What changes would be need to implement it on STM32?
Just list your interrupt handlers. That's all.

Except for the FE310, which has this... ;)
Because this core is stupidly designed. And the whole things feels like it came straight from the 80s as far as design goes, so assembly startup is par for the course there.
Alex
 

Offline technixTopic starter

  • Super Contributor
  • ***
  • Posts: 3508
  • Country: cn
  • From Shanghai With Love
    • My Untitled Blog
Re: Cortex-M startup files without assembler
« Reply #34 on: March 11, 2017, 03:19:14 am »
Startup code is part of your application, you can do whatever you want.
Sure, if you know what you are doing. But most vendors want you to use their frameworks and libraries - so you end up not knowing what you are doing. And the vast majority of example code requires their stuff, so if you want to do 'whatever you want'  then you have a lot to learn. That's why I am here.   

Quote from: ataradov
Quote from: technix
I have figured out a way to use a combination of linker scripts and C language extensions to create Cortex-M startup files without touching any assembler.
What do you mean " figured out"? This feature was the explicit design goal of the core.
So you didn't have to "figure it out"? Good for you! But most of us are using the chip vendor's bloatware, and figuring out how to go fully 'bare metal' is not trivial for us.

I have programmed the STM32F103 'bare metal' in assembler, but until now I didn't know that I could do it in C.  That's why I am interested to see how technix did it. However your code seems to be cleaner. What changes would be need to implement it on STM32?

Quote
Here are my starter projects with no assembly in them at all https://github.com/ataradov/mcu-starter-projects/
Except for the FE310, which has this... ;)

Code: [Select]
asm (" \
.section .init._start \n \
.globl _start \n \
_start: \n \
la gp, _gp \n \
la sp, _stack_top \n \
");



 
Something like this:
Code: [Select]
// stm32f303cc_it.c
void __stack(void);
void __attribute__((section(".isr_vector"))) (*ISR_Vector)(void) =
{
    __stack,
    Reset_Handler,
    NMI_Handler,
    // ...
};

void Reset_Handler(void)
{
    extern uint8_t _sidata, _sdata, _edata, _sbss, _ebss;
    SystemInit();
    memcpy(&_sdata, &_sidata, &_edata - &_sdata);
    memset(&_sbss, 0, &_ebss - &_sbss);
    _start(); // call libc
}
and
Code: [Select]
/* stm32f303cc.ld */
MEMORY
{
    IROM (rx) : org = 0x08000000, len = 0x00020000
    IRAM (rw) : org = 0x20000000, len = 0x00005000
}

SECTIONS
{
    __stack = ORIGIN(IRAM) + LENGTH(IRAM);

    . = ORIGIN(IROM);
    .isr_vector : AT(ORIGIN(IROM))
    {
        *(.isr_vector)
        *(.after_vector)
    } > IROM
    /* ... */
    _sidata = .;
    .data : AT(_sidata)
    {
        _sdata = .;
        *(.data)
        . = ALIGN(4);
        _edata = .;
    } > IRAM
    .bss
    {
        _sbss = .;
        *(.bss)
        _ebss = .;
    } > IRAM
}
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4657
  • Country: us
Re: Cortex-M startup files without assembler
« Reply #35 on: March 13, 2017, 10:20:54 am »
Hmm.  Does anyone have a nice SVC handler written in C?   Fetching the imm8 argument looks pretty nasty :-(
It doesn't even seem to be one of the ARM instructions that has a CMSIS-specified C intrinsic associated with it, at least not below the CMSIS/RTOS layer.
And getting C to pass arguments to SVC the way it does to a function call (which would be nice)?
 

Offline andersm

  • Super Contributor
  • ***
  • Posts: 1198
  • Country: fi
Re: Cortex-M startup files without assembler
« Reply #36 on: March 13, 2017, 02:36:34 pm »
Hmm.  Does anyone have a nice SVC handler written in C?   Fetching the imm8 argument looks pretty nasty :-(
It doesn't even seem to be one of the ARM instructions that has a CMSIS-specified C intrinsic associated with it, at least not below the CMSIS/RTOS layer.
And getting C to pass arguments to SVC the way it does to a function call (which would be nice)?
Passing and returning arguments doesn't really require any extra effort, as long as all arguments can be passed in registers. If you need more parameters, you could use a struct and pass a pointer to that. Extracting the SVC is never going to be especially pretty, and needs some assembly, see eg. here.

Offline andyturk

  • Frequent Contributor
  • **
  • Posts: 895
  • Country: us
Re: Cortex-M startup files without assembler
« Reply #37 on: March 13, 2017, 09:21:42 pm »
Hmm.  Does anyone have a nice SVC handler written in C?   Fetching the imm8 argument looks pretty nasty :-(
It doesn't even seem to be one of the ARM instructions that has a CMSIS-specified C intrinsic associated with it, at least not below the CMSIS/RTOS layer.
And getting C to pass arguments to SVC the way it does to a function call (which would be nice)?

You can take a look at how Keil implemented RTX (their RTOS). It uses SVC calls to talk to the kernel. You'll want to be sitting down (with a stiff cocktail in hand) before looking at the code, though ...

https://github.com/ARM-software/CMSIS_5/blob/develop/CMSIS/RTOS2/RTX/Source/core_cm.h#L270
 

Offline bson

  • Supporter
  • ****
  • Posts: 2770
  • Country: us
Re: Cortex-M startup files without assembler
« Reply #38 on: March 14, 2017, 06:57:25 pm »
I haven't written a lot of assembly (lately), but if there's just a small amount needed--usually to manipulate weird registers in the core--I'd much rather do it as asm volatile (). Putting the assembly in C/C++ form means I can use the same constants as in the main program, and also make sure the whole thing is type safe.
Agreed; the other main use for assembly is small inline bits, like for example copy data with some weird coloring in a context where performance matters.  Or even as simple as a mod-255 IP checksum (where it's generally done using a 32 bit accumulator for performance and then folded back).  In this case assembly absolutely should be inlined, but an inline function to wrap it might be a good idea, especially for gcc so it can know about const-ness and (non-)aliasing.  Also makes it easy to reimplement for a different compiler without littering the code with a bunch of #ifdef crap.  Usually when I do things like this I also have a naive C implementation that can be swapped in for testing purposes.  (If it can be done in C.  Some things, like saving thread context if you roll your own scheduler isn't doable in C.)
« Last Edit: March 14, 2017, 07:03:07 pm by bson »
 

Offline technixTopic starter

  • Super Contributor
  • ***
  • Posts: 3508
  • Country: cn
  • From Shanghai With Love
    • My Untitled Blog
Re: Cortex-M startup files without assembler
« Reply #39 on: March 14, 2017, 11:11:23 pm »
I haven't written a lot of assembly (lately), but if there's just a small amount needed--usually to manipulate weird registers in the core--I'd much rather do it as asm volatile (). Putting the assembly in C/C++ form means I can use the same constants as in the main program, and also make sure the whole thing is type safe.
Agreed; the other main use for assembly is small inline bits, like for example copy data with some weird coloring in a context where performance matters.  Or even as simple as a mod-255 IP checksum (where it's generally done using a 32 bit accumulator for performance and then folded back).  In this case assembly absolutely should be inlined, but an inline function to wrap it might be a good idea, especially for gcc so it can know about const-ness and (non-)aliasing.  Also makes it easy to reimplement for a different compiler without littering the code with a bunch of #ifdef crap.  Usually when I do things like this I also have a naive C implementation that can be swapped in for testing purposes.  (If it can be done in C.  Some things, like saving thread context if you roll your own scheduler isn't doable in C.)
Inline assembler for GCC (and LLVM/clang too) supports mixing C symbols in the assembler, making the coding a bit easier as you no longer need to mind the ABI, optimization level and where the variables and arguments would go.
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4657
  • Country: us
Re: Cortex-M startup files without assembler
« Reply #40 on: March 15, 2017, 01:25:47 am »
One problem with "inline assembler" is it throws you pretty quickly into the morass of vendor differences in compiler syntax and behavior.  (well, also true of pure assembler, I guess...)

The SVC case looks especially annoying; I'd *like* the compiler to treat the SVC instruction the same way it treats BL; so that a C function "__svc(arg1, arg2, arg3, arg4, arg5, arg6...) starts by moving arguments into registers, and then sets up a stack frame if there are too many arguments, and the finally does the SVC instruction where it might have done BL for a normal function.  But if I make it inline, then the argument behavior will change, and if I make it a normal function it won't be inlined the way I want, so...

Quote
You'll want to be sitting down (with a stiff cocktail in hand) before looking at the code, though

yeah.  THAT happens.
 

Offline technixTopic starter

  • Super Contributor
  • ***
  • Posts: 3508
  • Country: cn
  • From Shanghai With Love
    • My Untitled Blog
Re: Cortex-M startup files without assembler
« Reply #41 on: March 23, 2017, 02:56:21 am »
I never really used SVC in Cortex-M code... What does it achieve on a system without MPU?
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4657
  • Country: us
Re: Cortex-M startup files without assembler
« Reply #42 on: March 23, 2017, 08:32:03 am »
Quote
I never really used SVC in Cortex-M code... What does it achieve on a system without MPU?

1) Many Cortex-M chips DO have an MPU.
2) even if there is no MPU, there is still a different stack pointer, and maybe "priveledged mode".

So it's potentially useful in any application where you have a clear division between "core services" and "applications", whether that's a full blown OS that uses the MPU, or a much more primitive "this is user code and this is the system code" situation.

(At the moment, it's being amusing as a counter to the "you never need assembly language" boast.)
 

Offline technixTopic starter

  • Super Contributor
  • ***
  • Posts: 3508
  • Country: cn
  • From Shanghai With Love
    • My Untitled Blog
Re: Cortex-M startup files without assembler
« Reply #43 on: March 23, 2017, 09:44:20 am »
Quote
I never really used SVC in Cortex-M code... What does it achieve on a system without MPU?

1) Many Cortex-M chips DO have an MPU.
2) even if there is no MPU, there is still a different stack pointer, and maybe "priveledged mode".

So it's potentially useful in any application where you have a clear division between "core services" and "applications", whether that's a full blown OS that uses the MPU, or a much more primitive "this is user code and this is the system code" situation.

(At the moment, it's being amusing as a counter to the "you never need assembly language" boast.)
Yet the popular STM32F1 don't have the MPU...

Well since I write a lot of code with STM32F1 and I don't use an RTOS, there is no need for assembler. Should I need SVC I would still prefer inline assembler (as I am sticking to GCC anyway.)
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

-->