User:Pancakes/arm qemu realview-pb-a: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
Content added Content deleted
m (added clarification about global PITs and private PITs)
(re-arranged some stuff and corrected from eary theorization of the boot process which was not true)
Line 6: Line 6:


==== Multiple Cores On Boot ====
==== Multiple Cores On Boot ====
From my testing it appears that at least two cores boot on power up. This can cause problems
From my testing it appears that the boot ROM (special boot ROM) grabs the other cores
and puts them into a WFI sleeping loop. They basically go to sleep until an interrupt
because they both start at the same address. In order to control the chaos that will happen I
wakes them then they check there special boot register (explained further below), and
am going to show you how to stop all other CPUs except ''CPU0''. But, the main point is to
if it is set to non-zero they jump to that address. One easy way to wake them is to
catch the CPUs and make them each do what you need them to do.
send a SGI (software generated interrupt) to them. See the section below on waking up

the CPUs.
''During testing I noticed a time or two what appears to be 3 cores booting, but I was unable
to produce the behavior at this time. But, this code should catch any core except for CPU0.''


Below is an example of getting the ID of the CPU and placing it into a busy loop.
<source lang="c">
<source lang="c">
/* stop all cpus but cpu 0 */
/* stop all cpus but cpu 0 */
Line 46: Line 46:
</source>
</source>


==== Hardware ====
==== Waking Up Other Cores ====
To wake up the other cores (with QEMU at least) this is the
This is the only datasheet that I have found, http://infocenter.arm.com/help/topic/com.arm.doc.dui0411d/DUI0411D_realview_platform_baseboard_ug.pdf. I am not sure how accurate it is to the revision the QEMU board was built too.
minimal process required:
<source lang="c">
#define REALVIEWPBA_PERBASE 0x1f000000 /* peripheal base */
#define REALVIEWPBA_GICOFF 0x0100 /* general interrupt controller */
#define REALVIEWPBA_GDIOFF 0x1000 /* GIC distributor */


t = (uint32*)0x10000030;
Here is a fairly accurate memory map of all the MMIO.
t[0] = 0x10000;
<pre>
/* Memory map for RealView Emulation Baseboard: */
/* enable general interrupt controller */
/* 0x10000000 System registers. */
b = (uint8*)(REALVIEWPBA_PERBASE + REALVIEWPBA_GICOFF);
/* 0x10001000 System controller. */
b[0] = 3; /* enable */
/* 0x10002000 Two-Wire Serial Bus. */
/* 0x10003000 Reserved. */
b[4] = 0xff; /* set required priority */
/* 0x10004000 AACI. */
/* enable distributor then send SGI with distributor */
/* 0x10005000 MCI. */
t = (uint32*)(REALVIEWPBA_PERBASE + REALVIEWPBA_GDIOFF);
/* 0x10006000 KMI0. */
/* 0x10007000 KMI1. */
t[0] = 3; /* enable */
/* 0x10009000 UART0. */
t[0xf00 >> 2] = (1 << 24) | 0; /* issue SGI 0 */
/* 0x1000a000 UART1. */
</source>
/* 0x1000b000 UART2. */
You should be able to use any SGI, which are 0-15. There are also other
/* 0x1000c000 UART3. */
things that will wake the CPUs up. The boot code places them to sleep with
/* 0x1000d000 SSPI. */
the ''WFI'' (wait for interrupt) instruction. Once woken the address
/* 0x1000e000 SCI. */
''0x10000030'' is checked if it is non-zero. If it is zero it returns to
/* 0x1000f000 Reserved. */
the ''WFI'' instruction and sleeps, but if it is non-zero it will jump to
/* 0x10010000 Watchdog. */
the 32-bit address specified at ''0x10000030''.
/* 0x10011000 Timer 0+1. */
/* 0x10012000 Timer 2+3. */
/* 0x10013000 GPIO 0. */
/* 0x10014000 GPIO 1. */
/* 0x10015000 GPIO 2. */
/* 0x10002000 Two-Wire Serial Bus - DVI. (PB) */
/* 0x10017000 RTC. */
/* 0x10018000 DMC. */
/* 0x10019000 PCI controller config. */
/* 0x10020000 CLCD. */
/* 0x10030000 DMA Controller. */
/* 0x10080000 SMC. */
/* 0x1e000000 GIC1. (PB) */
/* 0x1e001000 GIC2. (PB) */
/* 0x1e002000 GIC3. (PB) */
/* 0x1e003000 GIC4. (PB) */
/* 0x40000000 NOR flash. */
/* 0x44000000 DoC flash. */
/* 0x48000000 SRAM. */
/* 0x4c000000 Configuration flash. */
/* 0x4e000000 Ethernet. */
/* 0x4f000000 USB. */
/* 0x50000000 PISMO. */
/* 0x54000000 PISMO. */
/* 0x58000000 PISMO. */
/* 0x5c000000 PISMO. */
/* 0x60000000 PCI. */
/* 0x60000000 PCI Self Config. */
/* 0x61000000 PCI Config. */
/* 0x62000000 PCI IO. */
/* 0x63000000 PCI mem 0. */
/* 0x64000000 PCI mem 1. */
/* 0x68000000 PCI mem 2. */
</pre>


==== Setting Up PIC And Timer ====
==== Setting Up PIC And Timer ====
Line 162: Line 134:
serialmmio[0] = 'A';
serialmmio[0] = 'A';
</source>
</source>

==== Waking Up Other Cores ====
To wake up the other cores (with QEMU at least) this is the
minimal process required:
<source lang="c">
#define REALVIEWPBA_PERBASE 0x1f000000 /* peripheal base */
#define REALVIEWPBA_GICOFF 0x0100 /* general interrupt controller */
#define REALVIEWPBA_GDIOFF 0x1000 /* GIC distributor */

t = (uint32*)0x10000030;
t[0] = 0x10000;
/* enable general interrupt controller */
b = (uint8*)(REALVIEWPBA_PERBASE + REALVIEWPBA_GICOFF);
b[0] = 3; /* enable */
b[4] = 0xff; /* set required priority */
/* enable distributor then send SGI with distributor */
t = (uint32*)(REALVIEWPBA_PERBASE + REALVIEWPBA_GDIOFF);
t[0] = 3; /* enable */
t[0xf00 >> 2] = (1 << 24) | 0; /* issue SGI 0 */
</source>
You should be able to use any SGI, which are 0-15. There are also other
things that will wake the CPUs up. The boot code places them to sleep with
the ''WFI'' (wait for interrupt) instruction. Once woken the address
''0x10000030'' is checked if it is non-zero. If it is zero it returns to
the ''WFI'' instruction and sleeps, but if it is non-zero it will jump to
the 32-bit address specified at ''0x10000030''.


==== GICC and GICD ====
==== GICC and GICD ====
Line 197: Line 140:
Each CPU has it's on GICC mapped at the same address (not required by SoCs but recommend). So this makes it not possible for one CPU to access the GICC of another CPU unless they are mapped in different places and made accessible (not sure if possible). So just assume that you can only access the local GICC for with each core.
Each CPU has it's on GICC mapped at the same address (not required by SoCs but recommend). So this makes it not possible for one CPU to access the GICC of another CPU unless they are mapped in different places and made accessible (not sure if possible). So just assume that you can only access the local GICC for with each core.


''Since your primary CPU can not access the GICC for the other cores the boot code (flashed into ROM) on the device will configure the GICC for each core therefore allowing you to send an SGI to wake them up.''
''Since yourprimary CPU can not access the GICC for the other cores the boot code (flashed into ROM) on the device will configure the GICC for each core therefore allowing you to send an SGI to wake them up.''


The GICC base is located at 0x1f000100, and the GICD base is located at 0x1f001000.
The GICC base is located at 0x1f000100, and the GICD base is located at 0x1f001000.
Line 272: Line 215:
</source>
</source>
The above is, of course, part of you execution chain from an IRQ/FIQ exception.
The above is, of course, part of you execution chain from an IRQ/FIQ exception.

==== Hardware ====
This is the only datasheet that I have found, http://infocenter.arm.com/help/topic/com.arm.doc.dui0411d/DUI0411D_realview_platform_baseboard_ug.pdf. I am not sure how accurate it is to the revision the QEMU board was built too.

Here is a fairly accurate memory map of all the MMIO.
<pre>
/* Memory map for RealView Emulation Baseboard: */
/* 0x10000000 System registers. */
/* 0x10001000 System controller. */
/* 0x10002000 Two-Wire Serial Bus. */
/* 0x10003000 Reserved. */
/* 0x10004000 AACI. */
/* 0x10005000 MCI. */
/* 0x10006000 KMI0. */
/* 0x10007000 KMI1. */
/* 0x10009000 UART0. */
/* 0x1000a000 UART1. */
/* 0x1000b000 UART2. */
/* 0x1000c000 UART3. */
/* 0x1000d000 SSPI. */
/* 0x1000e000 SCI. */
/* 0x1000f000 Reserved. */
/* 0x10010000 Watchdog. */
/* 0x10011000 Timer 0+1. */
/* 0x10012000 Timer 2+3. */
/* 0x10013000 GPIO 0. */
/* 0x10014000 GPIO 1. */
/* 0x10015000 GPIO 2. */
/* 0x10002000 Two-Wire Serial Bus - DVI. (PB) */
/* 0x10017000 RTC. */
/* 0x10018000 DMC. */
/* 0x10019000 PCI controller config. */
/* 0x10020000 CLCD. */
/* 0x10030000 DMA Controller. */
/* 0x10080000 SMC. */
/* 0x1e000000 GIC1. (PB) */
/* 0x1e001000 GIC2. (PB) */
/* 0x1e002000 GIC3. (PB) */
/* 0x1e003000 GIC4. (PB) */
/* 0x40000000 NOR flash. */
/* 0x44000000 DoC flash. */
/* 0x48000000 SRAM. */
/* 0x4c000000 Configuration flash. */
/* 0x4e000000 Ethernet. */
/* 0x4f000000 USB. */
/* 0x50000000 PISMO. */
/* 0x54000000 PISMO. */
/* 0x58000000 PISMO. */
/* 0x5c000000 PISMO. */
/* 0x60000000 PCI. */
/* 0x60000000 PCI Self Config. */
/* 0x61000000 PCI Config. */
/* 0x62000000 PCI IO. */
/* 0x63000000 PCI mem 0. */
/* 0x64000000 PCI mem 1. */
/* 0x68000000 PCI mem 2. */
</pre>

Revision as of 17:52, 22 April 2014

ARM QEMU REALVIEW-PB-A

This is an ARM board emulated by QEMU that supports 4 cores. I hope to help ease you into working with this board by giving you the information needed to at least get control over what is going on.


Multiple Cores On Boot

From my testing it appears that the boot ROM (special boot ROM) grabs the other cores and puts them into a WFI sleeping loop. They basically go to sleep until an interrupt wakes them then they check there special boot register (explained further below), and if it is set to non-zero they jump to that address. One easy way to wake them is to send a SGI (software generated interrupt) to them. See the section below on waking up the CPUs.

Below is an example of getting the ID of the CPU and placing it into a busy loop.

	/* stop all cpus but cpu 0 */
	asm("	mrc p15, 0, r0, c0, c0, 5\n\
		and r0, r0, #3\n\
		cmp r0, #0\n\
		____here: bne ____here\n"
	);

This will access the CPUID (for currently execting CPU), and if the ID is not zero then it will be placed into a busy loop thus only allowing CPU0 to continue.

Also, at this point you could branch each CPU to it's own handler and set a stack for each if desired.

This will use the serial to output the letter 'A' for CPU0, 'B' for CPU1, 'C' for CPU2, and 'D' for CPU3. But, from my testing only CPU0 and CPU1 start on a cold boot at your entry address when loading a flat binary file (by default loaded to 0x10000).

	asm("	mrc p15, 0, r0, c0, c0, 5\n\
		and r0, r0, #3\n\
		mov r1, #0x1000\n\
		lsl r1, r1, #16\n\
		orr r1, r1, #0x9000\n\
		mov r2, #65\n\
		add r2, r2, r0\n\
		str r2, [r1]\n\
		cmp r0, #0\n\
		____here: bne ____here\n"
	);

Waking Up Other Cores

To wake up the other cores (with QEMU at least) this is the minimal process required:

#define REALVIEWPBA_PERBASE	0x1f000000	/* peripheal base */
#define REALVIEWPBA_GICOFF	0x0100	/* general interrupt controller */
#define REALVIEWPBA_GDIOFF	0x1000  /* GIC distributor */

	t = (uint32*)0x10000030;
	t[0] = 0x10000;
	
	/* enable general interrupt controller */
	b = (uint8*)(REALVIEWPBA_PERBASE + REALVIEWPBA_GICOFF);
	b[0] = 3;     /* enable */
	b[4] = 0xff;  /* set required priority */
	
	/* enable distributor then send SGI with distributor */
	t = (uint32*)(REALVIEWPBA_PERBASE + REALVIEWPBA_GDIOFF);
	t[0] = 3;     /* enable */
	
	t[0xf00 >> 2] = (1 << 24) | 0;    /* issue SGI 0 */

You should be able to use any SGI, which are 0-15. There are also other things that will wake the CPUs up. The boot code places them to sleep with the WFI (wait for interrupt) instruction. Once woken the address 0x10000030 is checked if it is non-zero. If it is zero it returns to the WFI instruction and sleeps, but if it is non-zero it will jump to the 32-bit address specified at 0x10000030.

Setting Up PIC And Timer

Here is some simple demonstration code to setup the PIC and PIT. For more information refer to the datasheet for each device. This PIT is global to all CPUs so do not confuse it with the private PIT for each CPU (which is exampled further below).

This has ONLY been tested on QEMU. On real hardware you might need to do more, and you should refer to your datasheets on the hardware to diagnose problems. But, for QEMU this code should work for release QEMU-2.0.0-rc0.

#define CTRL_ENABLE			0x80
#define CTRL_MODE_FREE		0x00
#define CTRL_MODE_PERIODIC	0x40
#define CTRL_INT_ENABLE		(1<<5)
#define CTRL_DIV_NONE		0x00
#define CTRL_DIV_16			0x04
#define CTRL_DIV_256		0x08
#define CTRL_SIZE_32		0x02
#define CTRL_ONESHOT		0x01

#define REG_LOAD		0x00
#define REG_VALUE		0x01
#define REG_CTRL		0x02
#define REG_INTCLR		0x03
#define REG_INTSTAT		0x04
#define REG_INTMASK		0x05
#define REG_BGLOAD		0x06


        uint8   *picmmio1;
        uint8   *picmmio0;
        uint32  *pitmmio;
        uint32  *serialmmio;

        picmmio0 = (uint8*)0x1f000100;
        picmmio1 = (uint8*)0x1f001000;
        pit = (uint32*)0x10011000;
        serialmmio = (uint32*)0x10009000;
        

	/* talk to CPU interface*/
	/* enable PIC for CPU 0 */
	picmmio0[0] = 1;
	/* set priority mask for CPU 0 */
	picmmio0[4] = 0xff;

	/* talk to actual PIC stuff */
	/* enable PIC */
	picmmio1[0] = 1;
        /* 36 is the interrupt (on cold boot) for the timer below */	
	picmmio1[0x100 + (36 >> 3)] = (1 << (36 & 7));
	
	pitmmio[REG_LOAD] = KTASKTICKS;
	pitmmio[REG_BGLOAD] = KTASKTICKS;			
	pitmmio[REG_CTRL] = CTRL_ENABLE | CTRL_MODE_PERIODIC | CTRL_SIZE_32 | CTRL_DIV_NONE | CTRL_INT_ENABLE;
        pitmmio[REG_INTCLR] = ~0;		/* make sure interrupt is clear (might not be mandatory) */

        /* write character to serial output */
        serialmmio[0] = 'A';

GICC and GICD

The GICC is known as the CPU Interface and the GICD is known as the Distributor. The GIC is for General Interrupt Controller and actually represents the two devices. The GICC is local to each CPU while the GICD sits in front of these controllers and distributes the interrupts to them.

Each CPU has it's on GICC mapped at the same address (not required by SoCs but recommend). So this makes it not possible for one CPU to access the GICC of another CPU unless they are mapped in different places and made accessible (not sure if possible). So just assume that you can only access the local GICC for with each core.

Since yourprimary CPU can not access the GICC for the other cores the boot code (flashed into ROM) on the device will configure the GICC for each core therefore allowing you to send an SGI to wake them up.

The GICC base is located at 0x1f000100, and the GICD base is located at 0x1f001000.

Peripheral Base

Here are just a few offsets to the CPU specific stuff, except the GICD which (AFAIK) is global.

#define REALVIEWPBA_PERBASE	0x1f000000	/* peripheal base */
#define REALVIEWPBA_SCUOFF	0x0000	/* snoop control unit */
#define REALVIEWPBA_GICOFF	0x0100	/* general interrupt controller */
#define REALVIEWPBA_GTIOFF	0x0200	/* global timer */
#define REALVIEWPBA_PTIOFF	0x0600	/* private timer */
#define REALVIEWPBA_GDIOFF	0x1000  /* GIC distributor */

Private Timer Per CPU (Initialization And Handling Example)

The PTIOFF is a private timer for each CPU. I can not remember if it is specified for the cortex-a9 or the realview-pb. I want to say the cortex-a9, but in any event. Each CPU has it's own timer which is accessed as REALVIEWPBA_PERBASE + REALVIEWPBA_PTIOFF. Here is some example code to enable and timer and handle its interrupt:

#define SET1BF(bf, off, i) bf[off + (i >> 3)] = (1 << (i & 7))

        uint8   *gicc, *gicd;
        uint32  *pt;

        gicc = (uint8*)(REALVIEWPBA_PERBASE + REALVIEWPBA_GICOFF);
        gicd = (uint8*)(REALVIEWPBA_PERBASE + REALVIEWPBA_GDIOFF);
	pt = (uint32*)(REALVIEWPBA_PERBASE + REALVIEWPBA_PTIMER);

	/* enable IRQ and FIQ OR just enable one */
	arm4_cpsrset(arm4_cpsrget() & ~((1 << 7) | (1 << 6)));
	
	/* enable local PIC */
	gicc[0x100 + 0] = 1;
	/* set priority mask*/
	gicc[0x100 + 4] = 0xff;

        /* enable global distributor */
	gicd[0] = 1;	
	
	
	/* enable interrupt 29 in distributor */
	SET1BF(gicd, 0x100, 29);

        /* enable timer */
	pt[2] = 0;
	pt[0] = 0x4000000;
	pt[2] = (1 << 2) | (1 << 1) | (1 << 0);

See the technical documents for exactly meaning of bits. To handle an interrupt you use the following general process.

        uint8   *gicc;
        uint32  *pt;
        uint32  irq;

        gicc = (uint8*)(REALVIEWPBA_PERBASE + REALVIEWPBA_GICOFF);
	pt = (uint32*)(REALVIEWPBA_PERBASE + REALVIEWPBA_PTIMER);

        /* get interrupt to handle (irq == 1023 means spurious interrupt) */
        irq = gicc[3];
        if (irq == 29) {
                /* private timer is interrupt 29 (clear private timer interrupt line) */
                pt[3] = 1;
                /* not sure but you might have to clear interrupt below THEN clear private timer interrupt */
        }
        /* clear interrupt by saying we handled it */
        gicc[4] = irq;

The above is, of course, part of you execution chain from an IRQ/FIQ exception.

Hardware

This is the only datasheet that I have found, http://infocenter.arm.com/help/topic/com.arm.doc.dui0411d/DUI0411D_realview_platform_baseboard_ug.pdf. I am not sure how accurate it is to the revision the QEMU board was built too.

Here is a fairly accurate memory map of all the MMIO.

    /* Memory map for RealView Emulation Baseboard:  */
    /* 0x10000000 System registers.  */
    /* 0x10001000 System controller.  */
    /* 0x10002000 Two-Wire Serial Bus.  */
    /* 0x10003000 Reserved.  */
    /* 0x10004000 AACI.  */
    /* 0x10005000 MCI.  */
    /* 0x10006000 KMI0.  */
    /* 0x10007000 KMI1.  */
    /* 0x10009000 UART0.  */
    /* 0x1000a000 UART1.  */
    /* 0x1000b000 UART2.  */
    /* 0x1000c000 UART3.  */
    /* 0x1000d000 SSPI.  */
    /* 0x1000e000 SCI.  */
    /* 0x1000f000 Reserved.  */
    /* 0x10010000 Watchdog.  */
    /* 0x10011000 Timer 0+1.  */
    /* 0x10012000 Timer 2+3.  */
    /* 0x10013000 GPIO 0.  */
    /* 0x10014000 GPIO 1.  */
    /* 0x10015000 GPIO 2.  */
    /* 0x10002000 Two-Wire Serial Bus - DVI. (PB) */
    /* 0x10017000 RTC.  */
    /* 0x10018000 DMC.  */
    /* 0x10019000 PCI controller config.  */
    /* 0x10020000 CLCD.  */
    /* 0x10030000 DMA Controller.  */
    /* 0x10080000 SMC.  */
    /* 0x1e000000 GIC1. (PB) */
    /* 0x1e001000 GIC2. (PB) */
    /* 0x1e002000 GIC3. (PB) */
    /* 0x1e003000 GIC4. (PB) */
    /* 0x40000000 NOR flash.  */
    /* 0x44000000 DoC flash.  */
    /* 0x48000000 SRAM.  */
    /* 0x4c000000 Configuration flash.  */
    /* 0x4e000000 Ethernet.  */
    /* 0x4f000000 USB.  */
    /* 0x50000000 PISMO.  */
    /* 0x54000000 PISMO.  */
    /* 0x58000000 PISMO.  */
    /* 0x5c000000 PISMO.  */
    /* 0x60000000 PCI.  */
    /* 0x60000000 PCI Self Config.  */
    /* 0x61000000 PCI Config.  */
    /* 0x62000000 PCI IO.  */
    /* 0x63000000 PCI mem 0.  */
    /* 0x64000000 PCI mem 1.  */
    /* 0x68000000 PCI mem 2.  */