Elmi
Posts: 14
Joined: Fri Nov 27, 2015 6:21 am

Timer interrupt example

Sun May 20, 2018 5:36 pm

This question is completely independent from my previous one and is really about plain bare metal programming: I want to use a timer of the fourth CPU-core (Pi 3B) to call an interrupt service routine regularly.
The LED-blinking-examples I find in miscellaneous examples mainly use an endles loop with a counter to implement that. So: can anybody point me to some resources/examples that show how this can be done?
Timer interrupt should be quite fast and run on a high frequency, means it can be much faster than needed for a blinking LED.

Thanks!

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

Re: Timer interrupt example

Mon May 21, 2018 4:43 am

Your question is puzzling the only reason they blink the LED slow is so you can physically see it's blinking if you did it at speeds you are talking it would look like the LED is solid on AKA "you wouldn't know the demo is working".

As for the endless loop well you have to have the core doing something while you wait for the interrupt and as you have nothing else to do in the demo all you can do is loop it.

Take any of the samples set the frequency of operation up to what you want and replace the LED flashing code with what you want it to do ..... END OF STORY. Given your speed you might as well use any of the FIQ examples which will be a little faster in response time.

My advice is just use one of the samples in baremetal first and get it doing what you want (the samples will all be core0). That is proof of principle that what you want is possible.

The core3 starting is really the only bit you will have to work out with its bootstub. It sounds like your other 3 core will be running something like an O/S and so you will need to kick core3 from there and somehow have organization what memory and I/O stuff belongs to core3 so you don't conflict. Assuming your O/S kicks thru the normal bootsub loader Core3 will be sleeping parked like normal in the bootstub. Beyond that given the speed you want you just need to make sure you bring Core3 up to full speed (it starts at a slower default) and make sure you bring at least the Branch prediction and Data cache online (although the full level 2 cache would be better but complex).

Sidebar: I have discussed this in another thread I know that in Raspbian you can control the number of cpu's it uses by editing /boot/cmdline.txt. with the line maxcpus=3. That theoretically leaves core3 parked in the bootstub but then somehow you have to organize Raspbian to agree with a memory block to exclude to run your core3 code in and leave whatever I/O you intend to use alone. The GPU owns memory and there are baremetal mailbox commands you can ask it to allocate you some of its memory so that is a possible way around the memory problem, the I/O is more problematic. The most complex alternative would be to share I/O with O/S (say Raspbian) by setting up a special driver on the shared I/O that communicates with your baremetal core3 program. Whichever approach you take you need to make sure the two systems do not clash over the same memory or I/O. I have no idea if that is the sort of thing you are doing but it crosses much of the sorts of problems your discussion brings up.

Elmi
Posts: 14
Joined: Fri Nov 27, 2015 6:21 am

Re: Timer interrupt example

Mon May 21, 2018 8:24 am

I'm totally aware of the fact that I can't see a LED blinking when it is triggered by a timer. But this is not a problem, I also can check the frequency at an GPO using an oscilloscope.

But: I definitely have to do it with a timer interrupt, the code that has to be executed may be different on every call - and there a primitive loop would not work and would not give a stable execution frequency. So that's why I'm asking for an timer IRQ example - should not be that problem?

I also can do this with core 0, as starting point this would be fine too. An operating system is not involved, so there is no problem with the usage of the other cores.

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

Re: Timer interrupt example

Mon May 21, 2018 2:12 pm

It's trivial to setup do you want 32Bit or 64bit? On a Pi3 it can be either you need to tell me.

Elmi
Posts: 14
Joined: Fri Nov 27, 2015 6:21 am

Re: Timer interrupt example

Mon May 21, 2018 2:40 pm

Hm, what is the difference between both? Finally I would like to have an interrupt-frequency of at least 2 Mhz, so whatever timer fits better for this...

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

Re: Timer interrupt example

Mon May 21, 2018 3:03 pm

There is no real difference for the interrupt most of the difference is starting up the core, the problem is your other cores code will probably either be 32bit or 64bit and as they will kick core3 you need to match it. I haven't looked specifically but I doubt there is very much either way on the speed of interrupt handling.

Basically on the Pi3
A 64bit file is nominally called kernel8.img and the core starts at 0x80000 in 64 bit mode
A 32 bit file is nominally called kernel8-32.img and the core start at 0x8000 in 32 bit mode

So what is your other core code 32 bit or 64 bit?

Take my example I gave of using Raspbian with 3 cores that is a 32bit O/S and so you need core 3 running in 32 bit code you can't run it 64 bit.
I think all the Official Pi O/S are all 32bit mostly because they are supported on all Pi versions. The 32 bit/64 bit option is only available on the Pi3.

If you haven't got your other core code compiled you can chose but be aware you have to compile your other cores code to that mode.

Elmi
Posts: 14
Joined: Fri Nov 27, 2015 6:21 am

Re: Timer interrupt example

Mon May 21, 2018 3:30 pm

OK, there seems to be a misunderstanding: I don't have any operating sytem running, I want to do real bare-metal programming with direct hardware accesses. Means:

- write address of my timer ISR into related register
- write timer divider/compare values into related registers
- enable timer interrupt
- start time by writing related registers
(or whatever is necessary for the hardware timers of this SoC)

So no operating system calls involved, just direct hacking of the hardware.

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

Re: Timer interrupt example

Mon May 21, 2018 5:16 pm

Other than you place the isr address in the vector table what you have described is the exact steps so code it in C because it's trivial and that code doesn't care if you are in 32bit or 64bit because the C compiler deals with it.

Code: Select all

#include <stdint.h>
/*--------------------------------------------------------------------------}
{    LOCAL TIMER INTERRUPT ROUTING REGISTER - QA7_rev3.4.pdf page 18		}
{--------------------------------------------------------------------------*/
typedef union
{
	struct
	{
		enum {
			LOCALTIMER_TO_CORE0_IRQ = 0,
			LOCALTIMER_TO_CORE1_IRQ = 1,
			LOCALTIMER_TO_CORE2_IRQ = 2,
			LOCALTIMER_TO_CORE3_IRQ = 3,
			LOCALTIMER_TO_CORE0_FIQ = 4,
			LOCALTIMER_TO_CORE1_FIQ = 5,
			LOCALTIMER_TO_CORE2_FIQ = 6,
			LOCALTIMER_TO_CORE3_FIQ = 7,
		} Routing: 3;												// @0-2	  Local Timer routing 
		unsigned unused : 29;										// @3-31
	};
	uint32_t Raw32;													// Union to access all 32 bits as a uint32_t
} local_timer_int_route_reg_t;

/*--------------------------------------------------------------------------}
{    LOCAL TIMER CONTROL AND STATUS REGISTER - QA7_rev3.4.pdf page 17		}
{--------------------------------------------------------------------------*/
typedef union
{
	struct
	{
		unsigned ReloadValue : 28;									// @0-27  Reload value 
		unsigned TimerEnable : 1;									// @28	  Timer enable (1 = enabled) 
		unsigned IntEnable : 1;										// @29	  Interrupt enable (1= enabled)
		unsigned unused : 1;										// @30	  Unused
		unsigned IntPending : 1;									// @31	  Timer Interrupt flag (Read-Only) 
	};
	uint32_t Raw32;													// Union to access all 32 bits as a uint32_t
} local_timer_ctrl_status_reg_t;

/*--------------------------------------------------------------------------}
{     LOCAL TIMER CLEAR AND RELOAD REGISTER - QA7_rev3.4.pdf page 18		}
{--------------------------------------------------------------------------*/
typedef union
{
	struct
	{
		unsigned unused : 30;										// @0-29  unused 
		unsigned Reload : 1;										// @30	  Local timer-reloaded when written as 1 
		unsigned IntClear : 1;										// @31	  Interrupt flag clear when written as 1  
	};
	uint32_t Raw32;													// Union to access all 32 bits as a uint32_t
} local_timer_clr_reload_reg_t;

/*--------------------------------------------------------------------------}
{    GENERIC TIMER INTERRUPT CONTROL REGISTER - QA7_rev3.4.pdf page 13		}
{--------------------------------------------------------------------------*/
typedef union
{
	struct
	{
		unsigned nCNTPSIRQ_IRQ : 1;									// @0	Secure physical timer event IRQ enabled, This bit is only valid if bit 4 is clear otherwise it is ignored. 
		unsigned nCNTPNSIRQ_IRQ : 1;								// @1	Non-secure physical timer event IRQ enabled, This bit is only valid if bit 5 is clear otherwise it is ignored
		unsigned nCNTHPIRQ_IRQ : 1;									// @2	Hypervisor physical timer event IRQ enabled, This bit is only valid if bit 6 is clear otherwise it is ignored
		unsigned nCNTVIRQ_IRQ : 1;									// @3	Virtual physical timer event IRQ enabled, This bit is only valid if bit 7 is clear otherwise it is ignored
		unsigned nCNTPSIRQ_FIQ : 1;									// @4	Secure physical timer event FIQ enabled, If set, this bit overrides the IRQ bit (0) 
		unsigned nCNTPNSIRQ_FIQ : 1;								// @5	Non-secure physical timer event FIQ enabled, If set, this bit overrides the IRQ bit (1)
		unsigned nCNTHPIRQ_FIQ : 1;									// @6	Hypervisor physical timer event FIQ enabled, If set, this bit overrides the IRQ bit (2)
		unsigned nCNTVIRQ_FIQ : 1;									// @7	Virtual physical timer event FIQ enabled, If set, this bit overrides the IRQ bit (3)
		unsigned reserved : 24;										// @8-31 reserved
	};
	uint32_t Raw32;													// Union to access all 32 bits as a uint32_t
} generic_timer_int_ctrl_reg_t;

struct __attribute__((__packed__, aligned(4))) QA7Registers {
	local_timer_int_route_reg_t TimerRouting;						// 0x24
	uint32_t GPIORouting;											// 0x28
	uint32_t AXIOutstandingCounters;								// 0x2C
	uint32_t AXIOutstandingIrq;										// 0x30
	local_timer_ctrl_status_reg_t TimerControlStatus;				// 0x34
	local_timer_clr_reload_reg_t TimerClearReload;					// 0x38
	uint32_t unused;												// 0x3C
	generic_timer_int_ctrl_reg_t Core0TimerIntControl;				// 0x40
	generic_timer_int_ctrl_reg_t Core1TimerIntControl;				// 0x44
	generic_timer_int_ctrl_reg_t Core2TimerIntControl;				// 0x48
	generic_timer_int_ctrl_reg_t Core3TimerIntControl;				// 0x4C
};

#define QA7 ((volatile __attribute__((aligned(4))) struct QA7Registers*)(uintptr_t)(0x40000024))


/* Here is your interrupt function */
void __attribute__((interrupt("IRQ")))  irq_handler_stub(void) {

       /* You code goes here */

	local_timer_clr_reload_reg_t temp = { .IntClear = 1, .Reload = 1 };
	QA7->TimerClearReload  = temp;									// Clear interrupt & reload
}

/* here is your main */
int main(void) {

	QA7->TimerRouting.Routing = LOCALTIMER_TO_CORE0_IRQ;			// Route local timer IRQ to Core0

	QA7->TimerControlStatus.ReloadValue = 100;						// Timer period set
	QA7->TimerControlStatus.TimerEnable = 1;						// Timer enabled
	QA7->TimerControlStatus.IntEnable = 1;							// Timer IRQ enabled

	QA7->TimerClearReload.IntClear = 1;								// Clear interrupt
	QA7->TimerClearReload.Reload = 1;								// Reload now

	QA7->Core0TimerIntControl.nCNTPNSIRQ_IRQ = 1;					// We are in NS EL1 so enable IRQ to core0 that level
	QA7->Core0TimerIntControl.nCNTPNSIRQ_FIQ = 0;					// Make sure FIQ is zero

	EnableInterrupts();												// Start interrupts rolling

	while (1) {
	}

	return(0);
}
So basically it's just a pile of register definitions into QA7 at its memory offset with 9 lines of code doing exactly what you described

Code: Select all

QA7->TimerRouting.Routing = LOCALTIMER_TO_CORE0_IRQ; // Route local timer IRQ to Core0
QA7->TimerControlStatus.ReloadValue = 100; // Timer period set
QA7->TimerControlStatus.TimerEnable = 1; 	// Timer enabled
QA7->TimerControlStatus.IntEnable = 1;	 // Timer IRQ enabled
QA7->TimerClearReload.IntClear = 1; // Clear interrupt
QA7->TimerClearReload.Reload = 1;	 // Reload now
QA7->Core0TimerIntControl.nCNTPNSIRQ_IRQ = 1; // We are in NS EL1 so enable IRQ to core0 that level
QA7->Core0TimerIntControl.nCNTPNSIRQ_FIQ = 0; // Make sure FIQ is zero
EnableInterrupts(); // Start interrupts rolling

So I leave you the bit I am confused about.
Your job is to pick the core up, bring the core up to full speed, bring the caches and memory online, clear the BSS section, make sure the vector table has the interrupt address and enter the C code.

Do that and the code works.

If you want to use core3 then simply run the code on core3 changing the first of the nine lines to

Code: Select all

QA7->TimerRouting.Routing = LOCALTIMER_TO_CORE3_IRQ; // Route local timer IRQ to Core3
Last edited by LdB on Mon May 21, 2018 5:34 pm, edited 1 time in total.

Elmi
Posts: 14
Joined: Fri Nov 27, 2015 6:21 am

Re: Timer interrupt example

Mon May 21, 2018 5:34 pm

Yes, this was what I was looking for - thanks!

Return to “Bare metal, Assembly language”