User:Pancakes/arm qemu realview-pb-a

From OSDev Wiki
Jump to navigation Jump to search

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.

See ARM_Overview, (at the end) for links to tutorials, other boards, and stuff.

How To Use It

I just realized that someone who comes to this page might be lost as in how to use this board. You could of course find one and buy it, but if you did that you might find it easier and better just to buy some of the newer things out there.

So the main reason you would be interested in this board is for QEMU. Here is my boot line that I am using and you will likely find pretty useful too. I do not use graphics because the darned window pops up and annoys me because I am mainly looking at serial output. I start all 4 CPUs and set the memory to 64MB. Also specify my flat binary image, and yes it can handle an ELF32 image. It auto detects what type of image it is. The realview-pbx-a9 is the actual board. By default QEMU will normally use the integrator-cp which is a single core board, but it comes with a graphics display that I have detailed how to initialize and draw pixels too somewhere around here.

    qemu-system-arm -M realview-pbx-a9 -smp 4 -m 64 -kernel armos.bin -serial stdio -nographic


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"
	);

A more appropriate way to handle trapping the CPUs might be to rather issue them a //WFI// or //WFE// instruction so they go into low-power mode while waiting for your main CPU to wake them up again with a SGI (software generated interrupt). This is the method that I currently use. Somewhere below you can find code to issue an SGI (its not an instruction).

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.  */