How to read/write an I/O port in aarch64?

Hello, I was trying to write one byte to an I/O port, but I am failing finding the correct instructions in the instruction set of arm64 aarch64 architecture. I failed to find the answer in previous posts of this forum too.

To be sure I posted the question correctly, please consider a NS16550 uart.

I want, basically, to use `outb` (or `inb`) instructions, but I do not find the correct syntax. An equivalent example in i386 is:

void dbg_io_write_8(uint16_t port, uint8_t val)
{
    asm volatile (
        "outb    %%al, %%dx;"
        /* Outputs  */ : /* None */
        /* Inputs   */ : "a" (val), "d" (port)
        /* Clobbers */ : /* None */
        );
}

And, the equivalent for reading:

uint8_t dbg_io_read_8(uint16_t port)
{
    uint8_t val;

    asm volatile (
        "inb     %%dx, %%al;"
        /* Outputs  */ : "=a" (val)
        /* Inputs   */ : "d" (port)
        /* Clobbers */ : /* None */
        );

    return val;
}


Thanks for helping!
  • Aarch64 is not x86. Maybe you first read about the architecture?

  • I want to use `outb` (or `inb`) instructions, but I do not find the correct syntax.

    The Intel CPUs have a separate address space for "IO", but the ARM architecture only has "memory", so all IO devices are accessed as if they were memory.  In the olden days this was called "memory mapped IO."

    In CortexM (32bit ARM), you can access IO devices like:

    void *uartBase = (void *)0x60002000;
    static inline uint8_t readUartPort(void *base, int offset) {
       uint32_t *regPtr = ((uint32_t *)base) + offset;
       return *regPtr;
    }

    Note the inconsistancy of uint8 vs uint32 - some peripherals will have full 32bit IO registers, other (for "backward compatibility") will have 8bit registers at 32bit-aligned addresses, and still others packed 8bit registers.  Adjust accordingly.

    For ARM64, the address will probably be different, and you may need to deal with physical vs virtual address spaces and caching.  All depending on the exact hardware.

    Often the IO register layout of a peripheral will be laid out as a C structure:

    typedef struct USART_struct {
        register8_t RXDATA;  /* Receive Data */
        register8_t TXDATA;  /* Transmit Data */
        register8_t STATUS;  /* Status */
        register8_t CTRLA;  /* Control A */
          :

    In which case you'd have:

    USART_struct *uartBase = (USART_struct *) 0x60002000;
    uartBase->TXDATA = '\n';

    (if the peripherals are on-chip, the bases are probably defined for you as well.)