mvidakovicns
Posts: 6
Joined: Sun Mar 25, 2018 11:26 am

Re: Yet Another Bare Metal Tutorial for the RPi3

Sun Apr 01, 2018 5:28 pm

Hello,

I have modified the raspi-tutorial 13_debugger demo (https://github.com/bztsrc/raspi3-tutori ... 3_debugger) to work with Timer Interrupts. I wanted to create a code which would be called every 1 microsecond, and I wanted that function to be called from the Timer Interrupt.

I would like to check if I am doing this the proper way.

First of all, here is the modified main.c file:

Code: Select all

// set up serial console
uart_init();
// set up timer perod (1 microsecond)
timer_irq_setup(1);
// enable interrupts
enable_irq();

// echo everything back
while(1) {
  uart_send(uart_getc()); 
}
The timer_irq_setup() function is used to set up the timer this way:

Code: Select all

unsigned int divisor;
// Make sure clock is stopped, illegal to change anything while running
// #define ARMTIMER_CONTROL ((volatile unsigned int*)(MMIO_BASE+0xB408))
// #define RPI_ARMTIMER_CTRL_ENABLE        ( 1 << 7 )
*ARMTIMER_CONTROL &= ~(RPI_ARMTIMER_CTRL_ENABLE); 
// Get GPU clock (it varies between 200-450Mhz)
mbox[0] = (5 + 3) * 4;                  // length of the message
mbox[1] = MBOX_REQUEST;         // this is a request message
  
mbox[2] = MBOX_TAG_GET_CLOCK_RATE;   // #define MBOX_TAG_GET_CLOCK_RATE	0x00030002
mbox[3] = 8;                    // buffer size
mbox[4] = 8;
mbox[5] = 4;                    // CLOCK ID: CORE
mbox[6] = 0;                    // Clock Frequency

mbox[7] = MBOX_TAG_LAST;

// send the message to the GPU and receive answer
if (mbox_call(MBOX_CH_PROP)) {
	
  printf("\nclock freq: %d Hz\n", mbox[6]);
	
  // The prescaler divider is set to 250 (based on GPU=250MHz to give 1Mhz clock)
  mbox[6] /= 250;												
  // Divisor we would need at current clock speed
  divisor = ((unsigned long int)period_in_us * mbox[6]) / 1000000;			
  printf("\nclock divisor: %d\n", divisor);
  
  // Enable the timer interrupt IRQ
  // #define IRQ_ENABLE_BASIC_IRQS    ((volatile unsigned int*)(MMIO_BASE+0xB218))
  // #define RPI_BASIC_ARM_TIMER_IRQ         (1 << 0)
  *IRQ_ENABLE_BASIC_IRQS |=  RPI_BASIC_ARM_TIMER_IRQ;
  // Set the load value to divisor
  // #define ARMTIMER_LOAD    ((volatile unsigned int*)(MMIO_BASE+0xB400))
  *ARMTIMER_LOAD = divisor;	

  // #define ARMTIMER_CONTROL ((volatile unsigned int*)(MMIO_BASE+0xB408))
  // #define RPI_ARMTIMER_CTRL_23BIT         ( 1 << 1 )
  // #define RPI_ARMTIMER_CTRL_PRESCALE_1     ( 0 << 2 )
  // #define RPI_ARMTIMER_CTRL_IRQ_ENABLE    ( 1 << 5 )
  // #define RPI_ARMTIMER_CTRL_ENABLE        ( 1 << 7 )
  *ARMTIMER_CONTROL |= RPI_ARMTIMER_CTRL_23BIT | RPI_ARMTIMER_CTRL_PRESCALE_1 | RPI_ARMTIMER_CTRL_IRQ_ENABLE | RPI_ARMTIMER_CTRL_ENABLE; 
  printf("irq_setup finished. Control: %x", *ARMTIMER_CONTROL);
}
The enable_irq() function enables interrupts this way:

Code: Select all

asm volatile("msr daifclr,#2");
Then I have modified original vectors in the start.s file:

Code: Select all

_vectors:
    // synchronous
    .align  7
    //str     x30, [sp, #-16]!     // push x30
    //bl      dbg_saveregs
    //mov     x0, #0
    //bl      dbg_decodeexc
    //bl      dbg_main
    eret

    // IRQ
    .align  7
    //str     x30, [sp, #-16]!     // push x30
    //bl      dbg_saveregs
    //mov     x0, #1
    //bl      dbg_decodeexc
    str   x0, [sp, #-16]!         // push {x0}
    str   x1, [sp, #-16]!         // push {x0}
    str   x2, [sp, #-16]!         // push {x0}
    str   x3, [sp, #-16]!         // push {x0}
    str   x4, [sp, #-16]!         // push {x0}
    str   w0, [sp, #-16]!         // push {x0}
    str   w1, [sp, #-16]!         // push {x0}
    str   w2, [sp, #-16]!         // push {x0}
    str   w3, [sp, #-16]!         // push {x0}
    str   w4, [sp, #-16]!         // push {x0}
    bl      dbg_main
    ldr   w4, [sp], #16           // pop {x0}
    ldr   w3, [sp], #16           // pop {x0}
    ldr   w2, [sp], #16           // pop {x0}
    ldr   w1, [sp], #16           // pop {x0}
    ldr   w0, [sp], #16           // pop {x0}
    ldr   x4, [sp], #16           // pop {x0}
    ldr   x3, [sp], #16           // pop {x0}
    ldr   x2, [sp], #16           // pop {x0}
    ldr   x1, [sp], #16           // pop {x0}
    ldr   x0, [sp], #16           // pop {x0}
    
    eret

    // FIQ
    .align  7
    //str     x30, [sp, #-16]!     // push x30
    //bl      dbg_saveregs
    //mov     x0, #2
    //bl      dbg_decodeexc
    //bl      dbg_main
    eret

    // SError
    .align  7
    //str     x30, [sp, #-16]!     // push x30
    //bl      dbg_saveregs
    //mov     x0, #3
    //bl      dbg_decodeexc
    //bl      dbg_main
    eret
    

As you can see, I have commented out all other causes for the exception, and left only the IRQ. Before calling the dbg_main() function, I have backed-up all registers that were used in that function (I have left the generated assembler files to see which registers were used).

Here is the dbg_main() function:

Code: Select all

void dbg_main()
{
  // clear pending irq
  // #define ARMTIMER_ACQ     ((volatile unsigned int*)(MMIO_BASE+0xB40C))
  *ARMTIMER_ACQ = 1; 
  ...
}
This dbg_main() function is being called every microsecond and I set some GPIO pins there.
My question is: is this the right way to have the timer-generated interrupt code witten in C, for this bare metal platform?

Best regards,
Milan
Last edited by mvidakovicns on Sun Apr 01, 2018 7:15 pm, edited 6 times in total.

mvidakovicns
Posts: 6
Joined: Sun Mar 25, 2018 11:26 am

Java-based client for the raspi3-tutorial loader (Rasbootin64)

Sun Apr 01, 2018 7:05 pm

Hi everyone,

I have created a Java program which can be used to send files to the raspi3-tutorial loader (https://github.com/bztsrc/raspi3-tutori ... spbootin64).

I have posted the program on the github:
https://github.com/milanvidakovic/Raspbootin64Client

This Java program tries to connect to the serial port (given as the first command line parameter) and then tries to upload the given kernel8.img file (as the second command line parameter) to the Raspbootin64 loader. It looks like this:

Code: Select all

java -jar Raspbootin64Client.jar COM3 C:\Temp\kernel8.img
Or, you can start this program like this:

Code: Select all

java -jar Raspbootin64Client.jar gui
This will start the GUI and then you can do the same, but this time, you can use the built-in terminal to see what is RPI sending, and to type text to be sent via serial back to the RPI.

If the upload was successfull, the loader will start the uploaded program.

bzt
Posts: 98
Joined: Sat Oct 14, 2017 9:57 pm

Re: Yet Another Bare Metal Tutorial for the RPi3

Tue Apr 03, 2018 5:31 pm

Hey Milan,

Thank you very much for your java code, I've linked that from my tutorials. :-)

About the timer code, it looks okay to me, but you should consider that executing the debugger every microsec is not very useful. Either you should not re-enable the irq until you finished with the debugger, or (as long as you have only one interrupt) you could disable interrupts and enable them on debugger exit. Or your timer isr should do nothing more than increase a counter. Answering your question's C part, that's the correct way. Gcc has some support to define the vector in C (as well as for fiq handler), but I think it's clearer and more compatible to have a small asm wrapper which sets up arguments and calls a common C handler.

Cheers,
bzt

mvidakovicns
Posts: 6
Joined: Sun Mar 25, 2018 11:26 am

Re: Yet Another Bare Metal Tutorial for the RPi3

Wed Apr 04, 2018 4:39 pm

Hi,
Thank you for your comments and linking my code.

Regarding the timer interrupt, I have changed period from 1 microsecond to 20 microseconds.
I have just one interrupt and I have recycled your dbg_main() to be called every 20 microseconds. Here is my dbg_main():

Code: Select all

void dbg_main()
{
  // clear pending irq
  // #define ARMTIMER_ACQ     ((volatile unsigned int*)(MMIO_BASE+0xB40C))
  *ARMTIMER_ACQ = 1; 
  ...
}
Now, what exactly *ARMTIMER_ACQ = 1 does?
Does it re-enable interrupt, or, it disables interrupt? I haven't found useful information about "clearing pending interrupts". If I omit that code from the dbg_main(), there will be no more interrupts. Therefore, I conclude that it re-enables interrupt to happen again.

Should I put the

Code: Select all

*ARMTIMER_ACQ = 1;
at the end of my interrupt routine (dbg_main())?

How could I achieve the other your suggestion to "disable interrupts and enable them on debugger exit"? Could you give me some example?

I am not experienced with ARM or low level RPI programming so I would appreciate help.

Best regards,
Milan

bzt
Posts: 98
Joined: Sat Oct 14, 2017 9:57 pm

Re: Yet Another Bare Metal Tutorial for the RPi3

Thu Apr 05, 2018 1:59 pm

Hi,

In order to get IRQs working on any architecture, you'll have to do the following things:
1. program the peripheral to generate IRQs
2. program the IRQ Controller to route the IRQ from the peripheral to a specific CPU core (routes can be hardwired)
3. enable interrupts on CPU core so that when it receives an IRQ, it should run an interrupt service routine
4. the ISR should process what it want to process, then acknowledge the IRQ in the controller
5. if required, acknowledge the IRQ on the peripheral
6. the CPU must re-enable interrupts

Now depending on the architecture and the peripheral, some parts are done automatically. On x86 for example the programmable interrupt timer does not require acknowledge, but the real time clock peripheral does. Regardless both timers needs it's IRQ to be acknowledged in the IRQ controller (PIC/APIC). Also the CPU interrupts are disabled automatically when the ISR is called, and they are re-enabled automatically when the CPU flags are restored from the stack.
Now on AArch64 the basic principles are the same. As I've already wrote, I didn't had the time to dig deep into the BCM, so I'm describing more variations. When the BCM timer generates an IRQ, it has to be routed to a CPU core. There's an Interrupt Controller peripheral on the BCM chip, see it's documentation. Your timer_irq_setup() should enable it (point 2) as well as IRQ generation in the timer peripheral (point 1). Whether a CPU can receive an IRQ and interrupt normal code flow, is controlled by a flag, that's what your enable_irq() code sets. I don't know whether this flag is cleared when the exception handler (ISR) is executed, but if so, you have to re-enable it in the ISR. That ACQ you asked about is an acknowledge in the timer peripheral. I don't know if one need to acknowledge an IRQ in the controller too on RPi, but if so, you'll have to do that in the ISR too.

Now when to acknowledge and re-enable interrupts depends on what you want to achive. Basically there are four schools:
1. non-reentrant: the exception handler must be atomic, non-interruptible. Therefore you should acknowledge IRQ (in the peripheral and the controller) and re-enable interrupts (in the CPU) at the end of the ISR. Stable, easy to code, but not so good at performance. DOS is a good example.
2. reentrant: you allow to interrupt an ISR by a new IRQ. To code this, you'll want to acknowledge and re-enable interrupts as soon as possible in the ISR. Really good performance, but could be compatibility and stability issues. Linux is an example.
3. stacking: when an IRQ is received, the handler stacks the call. The real handler routine is called independently. That way when an IRQ handler is executed when a new IRQ fires, it's not handled, but stacked an control is given back to the original IRQ handler as soon as possible. Minix uses this approach (or at least Minix2 did for sure).
4. multually-reentrant: most tricky way, but it's stable and has a good performance too. There you'll enable all *other* IRQs and re-enable interrupts as soon as possible, but acknowledge the IRQ when it's fully handled, at the end of the ISR. This is a re-entrant solution, where different kind of IRQ handlers may interrupt a specific IRQ handler. For example the timer interrupt handler never will be interrupted by the same timer interrupt; but it may be interrupted by a keyboard interrupt. Because a handler can be only interrupted by a different kind, no locking needed and reasonably easy to write to be stabe, yet it has a good performance. Usually used in micro-kernels.

As you can see, it's really up to you. Disabling interrupts in CPU will stop all IRQs firing, so does the lack of acknowledgement in the IRQ controller. On the other hand masking specific IRQs in the controller and delaying acknowliding IRQ in the peripheral only stops a certain IRQ from firing again, but allows other IRQs.

Cheers,
bzt

mvidakovicns
Posts: 6
Joined: Sun Mar 25, 2018 11:26 am

Re: Yet Another Bare Metal Tutorial for the RPi3

Thu Apr 05, 2018 3:54 pm

Hi,
Thanks for the detailed description. I have done the first way and it appears to work. I have re-enabled the interrupts at the end of the routine and it works.
Best regards,
Milan

bzt
Posts: 98
Joined: Sat Oct 14, 2017 9:57 pm

Re: Yet Another Bare Metal Tutorial for the RPi3

Wed Apr 11, 2018 11:03 am

Hi Milan,

I've found a very good tutorial which also covers timer interrupts in depth: RPi OS tutorials

Cheers,
bzt

mvidakovicns
Posts: 6
Joined: Sun Mar 25, 2018 11:26 am

Re: Yet Another Bare Metal Tutorial for the RPi3

Sun Apr 15, 2018 10:12 am

Hi,
Thanks for the tutorial!
Best regards,
Milan

Jekberg
Posts: 3
Joined: Tue May 01, 2018 4:14 pm

Re: Yet Another Bare Metal Tutorial for the RPi3

Tue May 01, 2018 4:40 pm

Hello,

I have been trying to add a few extra lines of uart_puts to test a few things, however, whenever I add another line, it seems that my Raspberry PI 3 B+ is not able to print anything.

Please help!

stewartnjohnston
Posts: 1
Joined: Fri Jun 01, 2018 11:49 pm

Re: Yet Another Bare Metal Tutorial for the RPi3

Fri Jun 01, 2018 11:58 pm

Thanks for the tutorial!

I'm having trouble building the cross compile env. Could you please help? I get the following error when making the binutils

aarch64/binutils-2.30$ make -j4
make[1]: Entering directory '/home/stewart/Documents/RaspberryPI/KernelHacking/aarch64/binutils-2.30'
make[1]: Nothing to be done for 'all-target'.
Configuring in ./intl
Configuring in ./isl
Configuring in ./etc
Configuring in ./libiberty
configure: error: cannot find sources () in .././isl
Makefile:5750: recipe for target 'configure-isl' failed
make[1]: *** [configure-isl] Error 1
make[1]: *** Waiting for unfinished jobs....

When list the binutils-2.30 directory I see:

lrwxrwxrwx 1 stewart stewart 11 Jun 1 18:59 isl -> ../isl-0.18

so the link is there.

What do you think the problem is?

Thanks for your help.

Stewart

LdB
Posts: 754
Joined: Wed Dec 07, 2016 2:29 pm

Re: Yet Another Bare Metal Tutorial for the RPi3

Sat Jun 02, 2018 6:56 am

It tells you what the problem is it can't locate the isl source files .. did you download the full package or just the library?

Return to “Bare metal, Assembly language”

Who is online

Users browsing this forum: No registered users and 4 guests