Search code examples
assemblyarmv7zynqcortex-a

Zynq 7000: Minimum asm code to init cpu1 from cpu0


I'm trying to figure out the minimum requirements to initialize cpu1 from cpu0 in an amp configuration on a zynq-7000.

I have a given FSBL that hands over to u-boot with which I copy both programs (cpu0/1) from flash to different locations in ram (with sf read ...).

I can run both programs on cpu0 out of u-boot with go [adr] where adr is the start address of either program. I get the expected output on the uarts.

What does not work is that cpu0 should start cpu1 by writing its start address to register 0xffff_fff0 and after that issuing a system event sev.

I do not enable any caches, MMUs or the SCU because I want to keep it as simple as possible (no synchronizations or flushes) until I achieved to start up cpu1. Or is this actually the problem and I do need any of these?

Currently I only initialize the vector table, print to the uart and additionally for core 0 try to start core 1:

/* CPU 0 */
.section .vector_table, "x"
.global _init
_init:
    b reset     /* reset handler */
    b .         /* software interrupt */
    b .         /* prefetch abort */
    b .         /* data abort */
    b .         /* reserved */
    b .         /* irq */
    b .         /* fiq */

/* ASCII control chars */
.equ asciiLF, 0x0a
.equ asciiCR, 0x0d

.section .text
    uart1fifo: .word 0xe0001030     /* UART 1 rx/tx fifo register */

reset:
    /* Output "0" on UART 1 */
    ldr r0, uart1fifo
    mov r1, #'0'
    str r1, [r0]
    mov r1, #asciiCR
    str r1, [r0]
    mov r1, #asciiLF
    str r1, [r0]

    /* Set cpu1 start address */
    ldr r0, =0x20000000     /* CPU 1 start address */
    ldr r1, =0xfffffff0     /* Register to point to the CPU 1 start address */
    str r0, [r1]

    /* I added a 0.5s wait here which did not help */

    sev     /* Execute SEV to cause CPU 1 to wake up */

    /* Output "." on UART 1 to indicate that we actually went so far */
    ldr r0, uart1fifo
    mov r1, #'.'
    str r1, [r0]
    mov r1, #asciiCR
    str r1, [r0]
    mov r1, #asciiLF
    str r1, [r0]

    b .     /* Endless loop */

I can see the '0' and the '.' on uart 1 when I run the above code on cpu 0.

/* CPU 1 */
.section .vector_table, "x"
.global _init
_init:
    b reset     /* reset handler */
    b .         /* software interrupt */
    b .         /* prefetch abort */
    b .         /* data abort */
    b .         /* reserved */
    b .         /* irq */
    b .         /* fiq */

/* ASCII control chars */
.equ asciiLF, 0x0a
.equ asciiCR, 0x0d

.section .text
    uart0fifo: .word 0xe0000030     /* UART 0 rx/tx fifo register */

reset:
    /* Output "1" on UART 0 */
    ldr r0, uart0fifo
    mov r1, #'1'
    str r1, [r0]
    mov r1, #asciiCR
    str r1, [r0]
    mov r1, #asciiLF
    str r1, [r0]

    b .     /* Endless loop */

Here I can see the '1' on uart 0 when I run it on cpu 0.

I'm new to this and I'm a bit lost. Am I missing something fundamental? What can I try to get this to work?

I've been looking at xapp-1079 but this uses Xilinx' Standalone Libs and it's very difficult for me to filter out what is actually needed. I need a minimum working example so that I can port it to the exotic OS that we run on the first core.


Solution

  • It turned out that cpu1 wasn't in wfe state. I suppose u-boot has already woken it up although it wasn't using it i.e. the state of cpu1 was not smp.

    I had to execute a software reset on cpu 1 and bring it back into wfe state before issuing the sev command according to AR#53828 from xilinx.

    The steps should the link die were:

    • Backup the registers that are overwritten in the next steps.
    • Restore the WFE (wait for event) code in the OCM area. (I used assembly instead of the shown xilinx xsct commands as I don't have access to their IDE):
    mwr 0xFFFFFF00 0xe3e0000f
    mwr 0xFFFFFF04 0xe3a01000
    mwr 0xFFFFFF08 0xe5801000
    mwr 0xFFFFFF0C 0xe320f002
    mwr 0xFFFFFF10 0xe5902000
    mwr 0xFFFFFF14 0xe1520001
    mwr 0xFFFFFF18 0x0afffffb
    mwr 0xFFFFFF1C 0xe1a0f002
    
    • Write the jump instruction to 0x0 that takes cpu1 from 0x0 to the wfe area at 0xffffff00:
    mwr 0x00000000 0xe3e0f0ff
    
    • Perform a software reset on cpu1:
    mwr 0xf8000008 0xdf0d    # slcr unlock
    mwr 0xf8000244 0x2       # A9_RST1_ASSERT
    mwr 0xf8000244 0x22      # A9_RST1_ASSERT | A9_CLKSTOP1
    mwr 0xf8000244 0x20      # A9_CLKSTOP1
    mwr 0xf8000244 0x0       # de-assert / start all
    mwr 0xf8000004 0x767b    # slcr lock
    
    • Write the cpu1 start address to 0xfffffff0.
    • Issue sev.
    • Ensure that cpu1 actually started or just wait a few cycles.
    • Restore the backed up registers.