[dif_plic] Introduce DIF PLIC library

PLIC (Platform Level Interrupt Controller) multiplexes various device
interrupts onto the external interrupt lines. Single PLIC can facilitate
interrupt delivery to 1 or more targets.

This change introduces the PLIC DIF (Device Interface Functions) for
programming PLIC.

There is a number of important details that derive from the PLIC integration with
the Earl Grey, and the PLIC implementation itself:
 1) The sequence of interrupt sources as passed to the PLIC gateway,
    dictates the register layouts and the interrupt bit indexes in these
    registers. It also determines interrupt source IDs.
 2) Same peripheral interrupts are always packed together in the
    registers, and their IDs are consecutive.
 3) There is a set of per target registers that control the interrupt
    delivery to these targets (Interrupt Enable, Threshold and
    Claim/Complete).
 4) Any interrupt source can interrupt any of the PLIC supported targets
    providing the target specific registers are configured appropriately.

Note:
Presently, PLIC documentation is generated with a default parameterisation,
which does not match the Earl Grey PLIC implementation.

Relevant verilog source files to understand the Earl Grey PLIC
implementation:
hw/top_earlgrey/rtl/autogen/rv_plic_reg_top.sv:
registers implementation inside PLIC

hw/top_earlgrey/rtl/autogen/rv_plic.sv:
PLIC implementation

hw/top_earlgrey/rtl/autogen/top_earlgrey.sv:
IP blocks connected together, in particular "intr_vector" (size, how it is
assigned, and how it is passed to PLIC).

Signed-off-by: Silvestrs Timofejevs <silvestrst@lowrisc.org>
diff --git a/sw/device/lib/dif/dif_plic.c b/sw/device/lib/dif/dif_plic.c
new file mode 100644
index 0000000..2e86ffd
--- /dev/null
+++ b/sw/device/lib/dif/dif_plic.c
@@ -0,0 +1,338 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/dif/dif_plic.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "sw/device/lib/base/mmio.h"
+
+// Interrupt Pending register offsets from the PLIC base.
+#define RV_PLIC_IP0 0x0u
+#define RV_PLIC_IP1 0x4u
+
+// Level/Edge register offsets from the PLIC base.
+#define RV_PLIC_LE0 0x8u
+#define RV_PLIC_LE1 0xCu
+
+// Priority register offsets from the PLIC base.
+#define RV_PLIC_PRIO0 0x10u
+
+// Interrupt Enable register offsets from the PLIC base.
+#define RV_PLIC_IE00 0x200u
+#define RV_PLIC_IE01 0x204u
+
+// Threshold register offsets from the PLIC base.
+#define RV_PLIC_THRESHOLD0 0x208u
+
+// Interrupt Claim register offsets from the PLIC base.
+#define RV_PLIC_CC0 0x20cu
+
+// The highest interrupt priority.
+#define RV_PLIC_MAX_PRIORITY 0x3u
+
+// These defines are used to calculate the IRQ index in IP, LE, IE registers.
+// These registers are 32bit wide, and in order to accommodate all the IRQs,
+// multiple of the same type registers are defined (IE00, IE01, ...). For
+// example, IRQ ID 33 corresponds to bit 0 in registers IP1, LE1, IE01.
+#define PLIC_ID_TO_INDEX_REG_SIZE 32u
+#define PLIC_ID_TO_INDEX(id) ((uint32_t)id - 1)
+
+/**
+ * PLIC register info.
+ *
+ * This data type is used to store IRQ source bit offset within a register,
+ * and the offset of this register inside the PLIC.
+ */
+typedef struct plic_reg_info {
+  ptrdiff_t offset;  /*<< Register offset. */
+  uint8_t bit_index; /*<< Bit index within the register. */
+} plic_reg_info_t;
+
+/**
+ * PLIC target specific register offsets.
+ *
+ * PLIC is designed to support multiple targets, and every target has a set
+ * of its own registers. This data type is used to store PLIC target specific
+ * register offsets.
+ */
+typedef struct plic_target_reg_offset {
+  ptrdiff_t ie;        /*<< Interrupt Enable register offset. */
+  ptrdiff_t cc;        /*<< Claim/complete register offset. */
+  ptrdiff_t threshold; /*<< Threshold register offset. */
+} plic_target_reg_offset_t;
+
+/**
+ * PLIC peripheral IRQ range.
+ *
+ * PLIC IRQ source IDs are grouped together by a peripheral they belong to.
+ * Meaning that all IRQ IDs of the same peripheral are guaranteed to be
+ * consecutive. This data type is used to store IRQ ID ranges of a peripheral.
+ */
+typedef struct plic_peripheral_range {
+  dif_plic_irq_id_t first_irq_id; /*<< The first IRQ ID of a peripheral. */
+  dif_plic_irq_id_t last_irq_id;  /*<< The last IRQ ID of a peripheral. */
+} plic_peripheral_range_t;
+
+// An array of target specific set of register offsets. Every supported PLIC
+// target must have an entry in this array.
+static const plic_target_reg_offset_t
+    plic_target_reg_offsets[kDifPlicTargetCount] = {
+            [kDifPlicTargetIbex0] =
+                {
+                    .ie = RV_PLIC_IE00,
+                    .cc = RV_PLIC_CC0,
+                    .threshold = RV_PLIC_THRESHOLD0,
+                },
+};
+
+// An array of IRQ source ID ranges per peripheral. Every peripheral supported
+// by the PLIC, must have an entry in this array.
+static const plic_peripheral_range_t
+    plic_peripheral_ranges[kDifPlicPeripheralCount] = {
+            [kDifPlicPeripheralGpio] =
+                {
+                    .first_irq_id = kDifPlicIrqIdGpio0,
+                    .last_irq_id = kDifPlicIrqIdGpio31,
+                },
+            [kDifPlicPeripheralUart] =
+                {
+                    .first_irq_id = kDifPlicIrqIdUartTxWatermark,
+                    .last_irq_id = kDifPlicIrqIdUartRxParityErr,
+                },
+            [kDifPlicPeripheralSpiDevice] =
+                {
+                    .first_irq_id = kDifPlicIrqIdSpiDeviceRxF,
+                    .last_irq_id = kDifPlicIrqIdSpiDeviceTxUnderflow,
+                },
+            [kDifPlicPeripheralFlashCtrl] =
+                {
+                    .first_irq_id = kDifPlicIrqIdFlashCtrlProgEmpty,
+                    .last_irq_id = kDifPlicIrqIdFlashCtrlOpError,
+                },
+            [kDifPlicPeripheralHmac] =
+                {
+                    .first_irq_id = kDifPlicIrqIdHmacDone,
+                    .last_irq_id = kDifPlicIrqIdHmacErr,
+                },
+            [kDifPlicPeripheralAlertHandler] =
+                {
+                    .first_irq_id = kDifPlicIrqIdAlertHandlerClassA,
+                    .last_irq_id = kDifPlicIrqIdAlertHandlerClassD,
+                },
+            [kDifPlicPeripheralNmiGen] =
+                {
+                    .first_irq_id = kDifPlicIrqIdNmiGenEsc0,
+                    .last_irq_id = kDifPlicIrqIdNmiGenEsc3,
+                },
+};
+
+/**
+ * Get an IE, IP or LE register offset (IE00, IE01, ...) from an IRQ source ID.
+ *
+ * With more than 32 IRQ sources, there is a multiple of these registers to
+ * accommodate all the bits (1 bit per IRQ source). This function calculates
+ * the offset for a specific IRQ source ID (ID 33 would be IE01, ...).
+ */
+static ptrdiff_t plic_offset_from_reg0(dif_plic_irq_id_t irq) {
+  uint8_t register_index = PLIC_ID_TO_INDEX(irq) / PLIC_ID_TO_INDEX_REG_SIZE;
+  return register_index * sizeof(uint32_t);
+}
+
+/**
+ * Get an IE, IP, LE register bit index from an IRQ source ID.
+ *
+ * With more than 32 IRQ sources, there is a multiple of these registers to
+ * accommodate all the bits (1 bit per IRQ source). This function calculates
+ * the bit position within a register for a specifci IRQ source ID (ID 33 would
+ * be bit 0).
+ */
+static uint8_t plic_reg_bit_index_from_irq_id(dif_plic_irq_id_t irq) {
+  return PLIC_ID_TO_INDEX(irq) % PLIC_ID_TO_INDEX_REG_SIZE;
+}
+
+/**
+ * Get a target and an IRQ source specific Interrupt Enable register info.
+ */
+static void plic_irq_enable_reg_info(dif_plic_irq_id_t irq,
+                                     dif_plic_target_t target,
+                                     plic_reg_info_t *reg_info) {
+  ptrdiff_t offset = plic_offset_from_reg0(irq);
+  reg_info->offset = plic_target_reg_offsets[target].ie + offset;
+  reg_info->bit_index = plic_reg_bit_index_from_irq_id(irq);
+}
+
+/**
+ * Get an IRQ source specific Level/Edge register info.
+ */
+static void plic_irq_trigger_type_reg_info(dif_plic_irq_id_t irq,
+                                           plic_reg_info_t *reg_info) {
+  ptrdiff_t offset = plic_offset_from_reg0(irq);
+  reg_info->offset = RV_PLIC_LE0 + offset;
+  reg_info->bit_index = plic_reg_bit_index_from_irq_id(irq);
+}
+
+/**
+ * Get an IRQ source specific Interrupt Pending register info.
+ */
+static void plic_irq_pending_reg_info(dif_plic_irq_id_t irq,
+                                      plic_reg_info_t *reg_info) {
+  ptrdiff_t offset = plic_offset_from_reg0(irq);
+  reg_info->offset = RV_PLIC_IP0 + offset;
+  reg_info->bit_index = plic_reg_bit_index_from_irq_id(irq);
+}
+
+/**
+ * Get a PRIO register offset (PRIO0, PRIO1, ...) from an IRQ source ID.
+ *
+ * There is one PRIO register per IRQ source, this function calculates the IRQ
+ * source specific PRIO register offset.
+ */
+static ptrdiff_t plic_priority_reg_offset(dif_plic_irq_id_t irq) {
+  ptrdiff_t offset = PLIC_ID_TO_INDEX(irq) * sizeof(uint32_t);
+  return RV_PLIC_PRIO0 + offset;
+}
+
+bool dif_plic_init(mmio_region_t base_addr, dif_plic_t *plic) {
+  if (plic == NULL) {
+    return false;
+  }
+
+  plic->base_addr = base_addr;
+
+  return true;
+}
+
+bool dif_plic_irq_enable_set(const dif_plic_t *plic, dif_plic_irq_id_t irq,
+                             dif_plic_target_t target,
+                             dif_plic_enable_t enable) {
+  if (plic == NULL) {
+    return false;
+  }
+
+  // Get a target and an IRQ source specific Interrupt Enable register info.
+  plic_reg_info_t reg_info;
+  plic_irq_enable_reg_info(irq, target, &reg_info);
+
+  if (enable == kDifPlicEnable) {
+    mmio_region_nonatomic_set_bit32(plic->base_addr, reg_info.offset,
+                                    reg_info.bit_index);
+  } else {
+    mmio_region_nonatomic_clear_bit32(plic->base_addr, reg_info.offset,
+                                      reg_info.bit_index);
+  }
+
+  return true;
+}
+
+bool dif_plic_irq_trigger_type_set(const dif_plic_t *plic,
+                                   dif_plic_irq_id_t irq,
+                                   dif_plic_enable_t enable) {
+  if (plic == NULL) {
+    return false;
+  }
+
+  // Get an IRQ source specific Level/Edge register info.
+  plic_reg_info_t reg_info;
+  plic_irq_trigger_type_reg_info(irq, &reg_info);
+
+  if (enable == kDifPlicEnable) {
+    mmio_region_nonatomic_set_bit32(plic->base_addr, reg_info.offset,
+                                    reg_info.bit_index);
+  } else {
+    mmio_region_nonatomic_clear_bit32(plic->base_addr, reg_info.offset,
+                                      reg_info.bit_index);
+  }
+
+  return true;
+}
+
+bool dif_plic_irq_priority_set(const dif_plic_t *plic, dif_plic_irq_id_t irq,
+                               uint32_t priority) {
+  if (plic == NULL || priority > RV_PLIC_MAX_PRIORITY) {
+    return false;
+  }
+
+  ptrdiff_t offset = plic_priority_reg_offset(irq);
+  mmio_region_write32(plic->base_addr, offset, priority);
+
+  return true;
+}
+
+bool dif_plic_target_threshold_set(const dif_plic_t *plic,
+                                   dif_plic_target_t target,
+                                   uint32_t threshold) {
+  if (plic == NULL || threshold > RV_PLIC_MAX_PRIORITY) {
+    return false;
+  }
+
+  ptrdiff_t threshold_offset = plic_target_reg_offsets[target].threshold;
+  mmio_region_write32(plic->base_addr, threshold_offset, threshold);
+
+  return true;
+}
+
+bool dif_plic_irq_pending_status_get(const dif_plic_t *plic,
+                                     dif_plic_irq_id_t irq,
+                                     dif_plic_flag_t *status) {
+  if (plic == NULL || status == NULL) {
+    return false;
+  }
+
+  plic_reg_info_t reg_info;
+  plic_irq_pending_reg_info(irq, &reg_info);
+
+  if (mmio_region_get_bit32(plic->base_addr, reg_info.offset,
+                            reg_info.bit_index)) {
+    *status = kDifPlicSet;
+  } else {
+    *status = kDifPlicUnset;
+  }
+
+  return true;
+}
+
+bool dif_plic_irq_claim(const dif_plic_t *plic, dif_plic_target_t target,
+                        dif_irq_claim_data_t *claim_data) {
+  if (plic == NULL || claim_data == NULL) {
+    return false;
+  }
+
+  // Get an IRQ ID from the target specific CC register.
+  ptrdiff_t cc_offset = plic_target_reg_offsets[target].cc;
+  uint32_t irq_id = mmio_region_read32(plic->base_addr, cc_offset);
+
+  // Validate an IRQ source, and determine which peripheral it belongs to.
+  dif_plic_irq_id_t irq_src = (dif_plic_irq_id_t)irq_id;
+  for (dif_plic_peripheral_t peripheral = kDifPlicPeripheralGpio;
+       peripheral < kDifPlicPeripheralCount; ++peripheral) {
+    if (irq_src < plic_peripheral_ranges[peripheral].first_irq_id ||
+        irq_src > plic_peripheral_ranges[peripheral].last_irq_id) {
+      continue;
+    }
+
+    claim_data->peripheral = peripheral;
+    claim_data->source = irq_src;
+    claim_data->cc_offset = cc_offset;
+    return true;
+  }
+
+  return false;
+}
+
+bool dif_plic_irq_complete(const dif_plic_t *plic,
+                           const dif_irq_claim_data_t *complete_data) {
+  if (plic == NULL || complete_data == NULL) {
+    return false;
+  }
+
+  // Write back the claimed IRQ ID to the target specific CC register,
+  // to notify the PLIC of the IRQ completion.
+  mmio_region_write32(plic->base_addr, complete_data->cc_offset,
+                      (uint32_t)complete_data->source);
+
+  return true;
+}
diff --git a/sw/device/lib/dif/dif_plic.h b/sw/device/lib/dif/dif_plic.h
new file mode 100644
index 0000000..b176363
--- /dev/null
+++ b/sw/device/lib/dif/dif_plic.h
@@ -0,0 +1,271 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_LIB_DIF_DIF_PLIC_H_
+#define OPENTITAN_SW_DEVICE_LIB_DIF_DIF_PLIC_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "sw/device/lib/base/mmio.h"
+
+/**
+ * PLIC IRQ IDs enumeration.
+ *
+ * Enumeration of all PLIC interrupt source IDs. The IRQ IDs belonging to
+ * the same peripheral are guaranteed to be consecutive.
+ */
+typedef enum dif_plic_irq_id {
+  kDifPlicIrqIdNone = 0,             /**< No IRQ */
+  kDifPlicIrqIdGpio0 = 1,            /**< GPIO pin 0. */
+  kDifPlicIrqIdGpio1 = 2,            /**< GPIO pin 1. */
+  kDifPlicIrqIdGpio2 = 3,            /**< GPIO pin 2. */
+  kDifPlicIrqIdGpio3 = 4,            /**< GPIO pin 3. */
+  kDifPlicIrqIdGpio4 = 5,            /**< GPIO pin 4. */
+  kDifPlicIrqIdGpio5 = 6,            /**< GPIO pin 5. */
+  kDifPlicIrqIdGpio6 = 7,            /**< GPIO pin 6. */
+  kDifPlicIrqIdGpio7 = 8,            /**< GPIO pin 7. */
+  kDifPlicIrqIdGpio8 = 9,            /**< GPIO pin 8. */
+  kDifPlicIrqIdGpio9 = 10,           /**< GPIO pin 9. */
+  kDifPlicIrqIdGpio10 = 11,          /**< GPIO pin 10. */
+  kDifPlicIrqIdGpio11 = 12,          /**< GPIO pin 11. */
+  kDifPlicIrqIdGpio12 = 13,          /**< GPIO pin 12. */
+  kDifPlicIrqIdGpio13 = 14,          /**< GPIO pin 13. */
+  kDifPlicIrqIdGpio14 = 15,          /**< GPIO pin 14. */
+  kDifPlicIrqIdGpio15 = 16,          /**< GPIO pin 15. */
+  kDifPlicIrqIdGpio16 = 17,          /**< GPIO pin 16. */
+  kDifPlicIrqIdGpio17 = 18,          /**< GPIO pin 17. */
+  kDifPlicIrqIdGpio18 = 19,          /**< GPIO pin 18. */
+  kDifPlicIrqIdGpio19 = 20,          /**< GPIO pin 19. */
+  kDifPlicIrqIdGpio20 = 21,          /**< GPIO pin 20. */
+  kDifPlicIrqIdGpio21 = 22,          /**< GPIO pin 21. */
+  kDifPlicIrqIdGpio22 = 23,          /**< GPIO pin 22. */
+  kDifPlicIrqIdGpio23 = 24,          /**< GPIO pin 23. */
+  kDifPlicIrqIdGpio24 = 25,          /**< GPIO pin 24. */
+  kDifPlicIrqIdGpio25 = 26,          /**< GPIO pin 25. */
+  kDifPlicIrqIdGpio26 = 27,          /**< GPIO pin 26. */
+  kDifPlicIrqIdGpio27 = 28,          /**< GPIO pin 27. */
+  kDifPlicIrqIdGpio28 = 29,          /**< GPIO pin 28. */
+  kDifPlicIrqIdGpio29 = 30,          /**< GPIO pin 29. */
+  kDifPlicIrqIdGpio30 = 31,          /**< GPIO pin 30. */
+  kDifPlicIrqIdGpio31 = 32,          /**< GPIO pin 31. */
+  kDifPlicIrqIdUartTxWatermark = 33, /**< UART TX FIFO watermark. */
+  kDifPlicIrqIdUartRxWatermark = 34, /**< UART RX FIFO watermark. */
+  kDifPlicIrqIdUartTxOverflow = 35,  /**< UART TX FIFO overflow. */
+  kDifPlicIrqIdUartRxOverflow = 36,  /**< UART RX FIFO overflow. */
+  kDifPlicIrqIdUartRxFrameErr = 37,  /**< UART RX frame error. */
+  kDifPlicIrqIdUartRxBreakErr = 38,  /**< UART RX break error. */
+  kDifPlicIrqIdUartRxTimeout = 39,   /**< UART RX timeout. */
+  kDifPlicIrqIdUartRxParityErr = 40, /**< UART RX parity error. */
+  kDifPlicIrqIdSpiDeviceRxF = 41,    /**< SPI RX SRAM FIFO full. */
+  kDifPlicIrqIdSpiDeviceRxLvl = 42,  /**< SPI RX SRAM FIFO above the level. */
+  kDifPlicIrqIdSpiDeviceTxLvl = 43,  /**< SPI TX SRAM FIFO under the level. */
+  kDifPlicIrqIdSpiDeviceRxErr = 44,  /**< SPI MOSI in FwMode has error. */
+  kDifPlicIrqIdSpiDeviceRxOverflow = 45,  /**< SPI RX Async FIFO overflow. */
+  kDifPlicIrqIdSpiDeviceTxUnderflow = 46, /**< SPI TX Async FIFO underflow. */
+  kDifPlicIrqIdFlashCtrlProgEmpty = 47,   /**< Flash prog FIFO empty. */
+  kDifPlicIrqIdFlashCtrlProgLvl = 48,   /**< Flash prog FIFO drained to lvl. */
+  kDifPlicIrqIdFlashCtrlRdFull = 49,    /**< Flash read FIFO full. */
+  kDifPlicIrqIdFlashCtrlRdLvl = 50,     /**< Flash read FIFO filled to lvl. */
+  kDifPlicIrqIdFlashCtrlOpDone = 51,    /**< Flash operation complete. */
+  kDifPlicIrqIdFlashCtrlOpError = 52,   /**< Flash operation failed with err. */
+  kDifPlicIrqIdHmacDone = 53,           /**< HMAC done. */
+  kDifPlicIrqIdHmacFifoFull = 54,       /**< HMAC FIFO full. */
+  kDifPlicIrqIdHmacErr = 55,            /**< HMAC error. */
+  kDifPlicIrqIdAlertHandlerClassA = 56, /**< Alert Handler class A alert. */
+  kDifPlicIrqIdAlertHandlerClassB = 57, /**< Alert Handler class B alert. */
+  kDifPlicIrqIdAlertHandlerClassC = 58, /**< Alert Handler class C alert. */
+  kDifPlicIrqIdAlertHandlerClassD = 59, /**< Alert Handler class D alert. */
+  kDifPlicIrqIdNmiGenEsc0 = 60,         /**< NMI Gen escalation interrupt 0. */
+  kDifPlicIrqIdNmiGenEsc1 = 61,         /**< NMI Gen escalation interrupt 1. */
+  kDifPlicIrqIdNmiGenEsc2 = 62,         /**< NMI Gen escalation interrupt 2. */
+  kDifPlicIrqIdNmiGenEsc3 = 63,         /**< NMI Gen escalation interrupt 3. */
+  kDifPlicIrqIdLast = kDifPlicIrqIdNmiGenEsc3, /**< The last valid IRQ ID. */
+} dif_plic_irq_id_t;
+
+/**
+ * PLIC IRQ source peripheral enumeration.
+ *
+ * Enumeration used to determine which peripheral asserted the corresponding
+ * IRQ source.
+ */
+typedef enum dif_plic_peripheral {
+  kDifPlicPeripheralGpio = 0,     /**< GPIO */
+  kDifPlicPeripheralUart,         /**< UART */
+  kDifPlicPeripheralSpiDevice,    /**< SPI */
+  kDifPlicPeripheralFlashCtrl,    /**< Flash */
+  kDifPlicPeripheralHmac,         /**< HMAC */
+  kDifPlicPeripheralAlertHandler, /**< Alert handler */
+  kDifPlicPeripheralNmiGen,       /**< NMI generator */
+  kDifPlicPeripheralCount,        /**< Number of PLIC peripherals */
+} dif_plic_peripheral_t;
+
+/**
+ * PLIC external interrupt target.
+ *
+ * Enumeration used to determine which set of IE, CC, threshold registers to
+ * access dependent on the target.
+ */
+typedef enum dif_plic_target {
+  kDifPlicTargetIbex0 = 0, /* Ibex CPU */
+  kDifPlicTargetCount,
+} dif_plic_target_t;
+
+/**
+ * Generic enable/disable enumeration.
+ *
+ * Enumeration used to enable/disable bits, flags, ...
+ */
+typedef enum dif_plic_enable {
+  kDifPlicEnable = 0,
+  kDifPlicDisable,
+} dif_plic_enable_t;
+
+/**
+ * Generic set/unset enumeration.
+ *
+ * Enumeration used to set/unset, or get the state of bits,flags ...
+ */
+typedef enum dif_plic_flag {
+  kDifPlicSet = 0,
+  kDifPlicUnset,
+} dif_plic_flag_t;
+
+/**
+ * PLIC CC (IRQ claim register data).
+ *
+ * Data that is populated by the PLIC DIF when interrupt is claimed. Is used by
+ * the PLIC DIF consumers to establish the IRQ source ID, and the peripheral
+ * that asserted this IRQ.
+ */
+typedef struct dif_irq_claim_data {
+  dif_plic_peripheral_t peripheral; /**< Peripheral that interrupted */
+  dif_plic_irq_id_t source;         /**< IRQ type */
+  ptrdiff_t cc_offset;              /**< Target specifc CC offset */
+} dif_irq_claim_data_t;
+
+/**
+ * PLIC instance state.
+ *
+ * PLIC persistent data that is required by all the PLIC API.
+ */
+typedef struct dif_plic {
+  mmio_region_t base_addr; /**< PLIC instance base address */
+} dif_plic_t;
+
+/**
+ * Initialises an instance of PLIC.
+ *
+ * Information that must be retained, and is required to program PLIC, shall
+ * be stored in @p arg2.
+ *
+ * @param base_addr Base address of an instance of the PLIC IP block
+ * @param plic PLIC state data
+ * @return true if the function was successful, false otherwise
+ */
+bool dif_plic_init(mmio_region_t base_addr, dif_plic_t *plic);
+
+/**
+ * Enables/disables handling of IRQ source in PLIC.
+ *
+ * This operation does not affect the IRQ generation in the peripherals, which
+ * must be configured in a corresponding peripheral itself.
+ * @param plic PLIC state data
+ * @param irq Interrupt Source ID
+ * @param target Target to enable the IRQ for
+ * @param enable Enable/disable the IRQ handling
+ * @return true if the function was successful, false otherwise
+ */
+bool dif_plic_irq_enable_set(const dif_plic_t *plic, dif_plic_irq_id_t irq,
+                             dif_plic_target_t target,
+                             dif_plic_enable_t enable);
+
+/**
+ * Sets the IRQ trigger type (edge/level).
+ *
+ * Sets the behaviour of the Interrupt Gateway for a particular IRQ to be edge
+ * or level triggered.
+ * @param plic PLIC state data
+ * @param irq Interrupt source ID
+ * @param enable Enable for the edge triggered, disable for level triggered IRQs
+ * @return true if the function was successful, false otherwise
+ */
+bool dif_plic_irq_trigger_type_set(const dif_plic_t *plic,
+                                   dif_plic_irq_id_t irq,
+                                   dif_plic_enable_t enable);
+
+/**
+ * Sets IRQ source priority (0-3).
+ *
+ * In order for PLIC to set a CC register and assert the external interrupt line
+ * to the target (Ibex, ...), the priority of an IRQ source must be higher than
+ * the threshold for this source.
+ * @param plic PLIC state data
+ * @param irq Interrupt source ID
+ * @param priority Priority to be set
+ * @return true if the function was successful, false otherwise
+ */
+bool dif_plic_irq_priority_set(const dif_plic_t *plic, dif_plic_irq_id_t irq,
+                               uint32_t priority);
+
+/**
+ * Sets the target priority threshold.
+ *
+ * Sets the target priority threshold. PLIC will only interrupt a target when
+ * IRQ source priority is set higher than the priority threshold for the
+ * corresponding target.
+ * @param plic PLIC state data
+ * @param target Target to set the IRQ priority threshold for
+ * @param threshold IRQ priority threshold to be set
+ * @return true if the function was successful, false otherwise
+ */
+bool dif_plic_target_threshold_set(const dif_plic_t *plic,
+                                   dif_plic_target_t target,
+                                   uint32_t threshold);
+
+/**
+ * Checks the Interrupt Pending bit.
+ *
+ * Gets the status of the PLIC Interrupt Pending register bit for a specific IRQ
+ * source.
+ * @param plic PLIC state data
+ * @param irq Interrupt source ID
+ * @param status Flag indicating whether the IRQ pending bit is set in PLIC
+ * @return true if the function was successful, false otherwise
+ */
+bool dif_plic_irq_pending_status_get(const dif_plic_t *plic,
+                                     dif_plic_irq_id_t irq,
+                                     dif_plic_flag_t *status);
+
+/**
+ * Claims an IRQ and gets the information about the source.
+ *
+ * Claims an IRQ and returns the IRQ related data to the caller. This function
+ * reads a target specific CC register. dif_plic_irq_complete must be called
+ * after the claimed IRQ has been serviced.
+ *
+ * @param plic PLIC state data
+ * @param target Target that claimed the IRQ
+ * @param claim_data Data that describes the origin and belonging of the IRQ.
+ * @return true if the function was successful, false otherwise
+ */
+bool dif_plic_irq_claim(const dif_plic_t *plic, dif_plic_target_t target,
+                        dif_irq_claim_data_t *claim_data);
+
+/**
+ * Completes the claimed IRQ.
+ *
+ * Finishes servicing of the claimed IRQ by writing the IRQ source ID back to a
+ * target specific CC register. This function must be called after
+ * dif_plic_claim_irq, when a caller has finished servicing the IRQ.
+ *
+ * @param plic PLIC state data
+ * @param complete_data Previously claimed IRQ data that is used to signal PLIC
+ *                      of the IRQ servicing completion.
+ * @return true if the function was successful, false otherwise
+ */
+bool dif_plic_irq_complete(const dif_plic_t *plic,
+                           const dif_irq_claim_data_t *complete_data);
+
+#endif  // OPENTITAN_SW_DEVICE_LIB_DIF_DIF_PLIC_H_
diff --git a/sw/device/lib/dif/meson.build b/sw/device/lib/dif/meson.build
index 2edd53e..b6e16ba 100644
--- a/sw/device/lib/dif/meson.build
+++ b/sw/device/lib/dif/meson.build
@@ -16,3 +16,12 @@
     ]
   )
 )
+
+# PLIC DIF library (dif_plic)
+dif_plic = declare_dependency(
+  link_with: static_library(
+    'dif_plic_ot',
+    sources: ['dif_plic.c'],
+    dependencies: [sw_lib_mmio]
+  )
+)