Zynq UltraScale+ MPSoC IPI Hardware - 2022.1 English

Libmetal and OpenAMP User Guide (UG1186)

Document ID
Release Date
2022.1 English

The IPI (Inter Processor Interrupt) can be used for notification messages between processors. The following example does not use the IPI shared buffer. Libmetal does not provide IPI drivers. It only provides a way to interact with IPI as a device. You need to manage the IPI.

For users of libmetal, the libmetal library is used to access IPI as a generic device. You need to define how to access IPI in your application. Using a standalone IPI driver, the driver defines the method used to send and receive messages between IPI blocks.

Note: Libmetal in Linux user space does not allow use of IPI buffer. Because the IPI buffer is only used for the interaction with PMU firmware and it can only be accessed from Arm ® trusted firmware (ATF).

You can interact with the IPI registers via metal_io_read32() and metal_io_write32() , and handle IPI interrupt with libmetal IRQ APIs.

Following, is an example of how to access Zynq ® UltraScale+ MPSoC IPI registers, and handle IPI interrupts.

This is an example of IPI libmetal device static definition for baremetal/FreeRTOS:

static struct metal_device ipi_dev = { /* IPI device */

.name = "ff310000.ipi", /* device name */

.bus = NULL, /* bus. This field is NULL as it does not need to be set. It will be set in metal_device_open() */

.num_regions = 1, /* number of I/O regions in device */

.regions = { /* define structure of each I/O region in device */


.virt = (void*) 0xFF3100, /* virtual address */

.physmap = 0xFF3100, /* physical address */

.size = 0x1000, /* size of region */

.page_shift = (sizeof(metal_phys_addr_t) << 3), /* page shift */

.page_mask = (unsigned long) (-1), /* page mask */

.mem_flags = DEVICE_NONSHARED | PRIV_RW_USER_RW, /* memory flags */

.ops = {NULL},/* user-defined memory operations. We do not have any custom operations so leave this as NULL.*/



.node = {NULL}, /* node to point to device in list of nodes on bus. This will be set in metal_device_open, so leave as NULL */

.irq_num = 1, /* The number of IRQs per device. In this case we are using only one interrupt. */

.irq_info = (void*) 65, /* IRQ ID*/


* Open the IPI device, use the IPI device as follows:

/* open the IPI device */

metal_device_open("generic", "ff310000.ipi", &ipi);

/* Get the IPI device libmetal I/O region */

io_region = metal_device_io_region(ipi, 0);

/* disable IPI interrupt */

metal_io_write32(ipi_io_region, IPI_IDR_OFFSET, IPI_MASK);

/* clear old IPI interrupt */

metal_io_write32(ipi_io_region, IPI_ISR_OFFSET, IPI_MASK);

/* Register IPI irq handler */

metal_irq_register(ipi_irq, ipi_irq_handler, ipi_dev, private_data);

/* Enable IPI interrupt */

metal_io_write32(ipi_io_region, IPI_IER_OFFSET, IPI_MASK);