/*
 * Copyright (c) 2017 Jean-Paul Etienne <fractalclone@gmail.com>
 * Copyright (c) 2023 Meta
 * Contributors: 2018 Antmicro <www.antmicro.com>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT sifive_plic_1_0_0

/**
 * @brief Platform Level Interrupt Controller (PLIC) driver
 *        for RISC-V processors
 */

#include <stdlib.h>

#include "sw_isr_common.h"

#include <zephyr/debug/symtab.h>
#include <zephyr/kernel.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/device.h>
#include <zephyr/devicetree/interrupt_controller.h>
#include <zephyr/shell/shell.h>

#include <zephyr/sw_isr_table.h>
#include <zephyr/drivers/interrupt_controller/riscv_plic.h>
#include <zephyr/irq.h>

#define PLIC_BASE_ADDR(n) DT_INST_REG_ADDR(n)
/*
 * These registers' offset are defined in the RISCV PLIC specs, see:
 * https://github.com/riscv/riscv-plic-spec
 */
#define CONTEXT_BASE 0x200000
#define CONTEXT_SIZE 0x1000
#define CONTEXT_THRESHOLD 0x00
#define CONTEXT_CLAIM 0x04
#define CONTEXT_ENABLE_BASE 0x2000
#define CONTEXT_ENABLE_SIZE 0x80
#define CONTEXT_PENDING_BASE 0x1000

/*
 * Trigger type is mentioned, but not defined in the RISCV PLIC specs.
 * However, it is defined and supported by at least the Andes & Telink datasheet, and supported
 * in Linux's SiFive PLIC driver
 */
#ifdef CONFIG_PLIC_SUPPORTS_TRIG_TYPE
#define PLIC_TRIG_LEVEL ((uint32_t)0)
#define PLIC_TRIG_EDGE  ((uint32_t)1)
#endif /* CONFIG_PLIC_SUPPORTS_TRIG_TYPE */

/* PLIC registers are 32-bit memory-mapped */
#define PLIC_REG_SIZE 32
#define PLIC_REG_MASK BIT_MASK(LOG2(PLIC_REG_SIZE))

#ifdef CONFIG_TEST_INTC_PLIC
#define INTC_PLIC_STATIC
#define INTC_PLIC_STATIC_INLINE
#else
#define INTC_PLIC_STATIC static
#define INTC_PLIC_STATIC_INLINE static inline
#endif /* CONFIG_TEST_INTC_PLIC */

#ifdef CONFIG_PLIC_IRQ_AFFINITY
#if CONFIG_MP_MAX_NUM_CPUS <= 8
typedef uint8_t plic_cpumask_t;
#elif CONFIG_MP_MAX_NUM_CPUS <= 16
typedef uint16_t plic_cpumask_t;
#elif CONFIG_MP_MAX_NUM_CPUS <= 32
typedef uint32_t plic_cpumask_t;
#else
#error "Currently only supports up to 32 cores"
#endif /* CONFIG_MP_MAX_NUM_CPUS */
#endif /* CONFIG_PLIC_IRQ_AFFINITY */

typedef void (*riscv_plic_irq_config_func_t)(void);
struct plic_config {
	mem_addr_t prio;
	mem_addr_t irq_en;
	mem_addr_t reg;
#ifdef CONFIG_PLIC_SUPPORTS_SOFT_INTERRUPT
	mem_addr_t pend;
#endif /* CONFIG_PLIC_SUPPORTS_SOFT_INTERRUPT */
#ifdef CONFIG_PLIC_SUPPORTS_TRIG_TYPE
	mem_addr_t trig;
#endif /* CONFIG_PLIC_SUPPORTS_TRIG_TYPE */
	uint32_t max_prio;
	/* Number of IRQs that the PLIC physically supports */
	uint32_t riscv_ndev;
	/* Number of IRQs supported in this driver */
	uint32_t nr_irqs;
	uint32_t irq;
	riscv_plic_irq_config_func_t irq_config_func;
	struct _isr_table_entry *isr_table;
	const uint32_t *const hart_context;
};

struct plic_stats {
	uint16_t *const irq_count;
	const int irq_count_len;
};

struct plic_data {
	struct k_spinlock lock;

#ifdef CONFIG_PLIC_SHELL_IRQ_COUNT
	struct plic_stats stats;
#endif /* CONFIG_PLIC_SHELL_IRQ_COUNT */

#ifdef CONFIG_PLIC_IRQ_AFFINITY
	plic_cpumask_t *irq_cpumask;
#endif /* CONFIG_PLIC_IRQ_AFFINITY */

};

static uint32_t save_irq[CONFIG_MP_MAX_NUM_CPUS];
static const struct device *save_dev[CONFIG_MP_MAX_NUM_CPUS];

INTC_PLIC_STATIC_INLINE uint32_t local_irq_to_reg_index(uint32_t local_irq)
{
	return local_irq >> LOG2(PLIC_REG_SIZE);
}

INTC_PLIC_STATIC_INLINE uint32_t local_irq_to_reg_offset(uint32_t local_irq)
{
	return local_irq_to_reg_index(local_irq) * sizeof(uint32_t);
}

static inline uint32_t get_plic_enabled_size(const struct device *dev)
{
	const struct plic_config *config = dev->config;

	return local_irq_to_reg_index(config->nr_irqs) + 1;
}

static ALWAYS_INLINE uint32_t get_hart_context(const struct device *dev, uint32_t hartid)
{
	const struct plic_config *config = dev->config;

	return config->hart_context[hartid];
}

static ALWAYS_INLINE uint32_t get_irq_cpumask(const struct device *dev, uint32_t local_irq)
{
#ifdef CONFIG_PLIC_IRQ_AFFINITY
	const struct plic_data *data = dev->data;

	return data->irq_cpumask[local_irq];
#else
	ARG_UNUSED(dev);
	ARG_UNUSED(local_irq);

	return 0x1;
#endif /* CONFIG_PLIC_IRQ_AFFINITY */
}

static inline mem_addr_t get_context_en_addr(const struct device *dev, uint32_t cpu_num)
{
	const struct plic_config *config = dev->config;
	uint32_t hartid;
	/*
	 * We want to return the irq_en address for the context of given hart.
	 */
#if CONFIG_MP_MAX_NUM_CPUS > 1
	hartid = _kernel.cpus[cpu_num].arch.hartid;
#else
	hartid = arch_proc_id();
#endif
	return config->irq_en + get_hart_context(dev, hartid) * CONTEXT_ENABLE_SIZE;
}

static inline mem_addr_t get_claim_complete_addr(const struct device *dev)
{
	const struct plic_config *config = dev->config;

	/*
	 * We want to return the claim complete addr for the hart's context.
	 */

	return config->reg + get_hart_context(dev, arch_proc_id()) * CONTEXT_SIZE + CONTEXT_CLAIM;
}

static inline mem_addr_t get_threshold_priority_addr(const struct device *dev, uint32_t cpu_num)
{
	const struct plic_config *config = dev->config;
	uint32_t hartid;

#if CONFIG_MP_MAX_NUM_CPUS > 1
	hartid = _kernel.cpus[cpu_num].arch.hartid;
#else
	hartid = arch_proc_id();
#endif

	return config->reg + (get_hart_context(dev, hartid) * CONTEXT_SIZE);
}

static ALWAYS_INLINE uint32_t local_irq_to_irq(const struct device *dev, uint32_t local_irq)
{
	const struct plic_config *config = dev->config;

	return irq_to_level_2(local_irq) | config->irq;
}

#ifdef CONFIG_PLIC_SUPPORTS_SOFT_INTERRUPT
static inline mem_addr_t get_pending_reg(const struct device *dev, uint32_t local_irq)
{
	const struct plic_config *config = dev->config;

	return config->pend + local_irq_to_reg_offset(local_irq);
}
#endif /* CONFIG_PLIC_SUPPORTS_SOFT_INTERRUPT */

/**
 * @brief Determine the PLIC device from the IRQ
 *
 * @param irq IRQ number
 *
 * @return PLIC device of that IRQ
 */
static inline const struct device *get_plic_dev_from_irq(uint32_t irq)
{
#ifdef CONFIG_DYNAMIC_INTERRUPTS
	return z_get_sw_isr_device_from_irq(irq);
#else
	return DEVICE_DT_INST_GET(0);
#endif
}

#ifdef CONFIG_PLIC_SUPPORTS_TRIG_TYPE
/**
 * @brief Return the value of the trigger type register for the IRQ
 *
 * In the event edge irq is enable this will return the trigger
 * value of the irq. In the event edge irq is not supported this
 * routine will return 0
 *
 * @param dev PLIC-instance device
 * @param local_irq PLIC-instance IRQ number to add to the trigger
 *
 * @return Trigger type register value if PLIC supports trigger type, PLIC_TRIG_LEVEL otherwise
 */
static uint32_t riscv_plic_irq_trig_val(const struct device *dev, uint32_t local_irq)
{
	const struct plic_config *config = dev->config;
	mem_addr_t trig_addr = config->trig + local_irq_to_reg_offset(local_irq);
	uint32_t offset = local_irq * CONFIG_PLIC_TRIG_TYPE_BITWIDTH;

	return sys_read32(trig_addr) & GENMASK(offset + CONFIG_PLIC_TRIG_TYPE_BITWIDTH - 1, offset);
}
#endif /* CONFIG_PLIC_SUPPORTS_TRIG_TYPE */

static void plic_irq_enable_set_state(uint32_t irq, bool enable)
{
	const struct device *dev = get_plic_dev_from_irq(irq);
	const uint32_t local_irq = irq_from_level_2(irq);

	for (uint32_t cpu_num = 0; cpu_num < arch_num_cpus(); cpu_num++) {
		mem_addr_t en_addr =
			get_context_en_addr(dev, cpu_num) + local_irq_to_reg_offset(local_irq);
		uint32_t en_value;

		en_value = sys_read32(en_addr);
		WRITE_BIT(en_value, local_irq & PLIC_REG_MASK,
			  enable ? (get_irq_cpumask(dev, local_irq) & BIT(cpu_num)) != 0 : false);
		sys_write32(en_value, en_addr);
	}
}

/**
 * @brief Enable a riscv PLIC-specific interrupt line
 *
 * This routine enables a RISCV PLIC-specific interrupt line.
 * riscv_plic_irq_enable is called by RISCV_PRIVILEGED
 * arch_irq_enable function to enable external interrupts for
 * IRQS level == 2, whenever CONFIG_RISCV_HAS_PLIC variable is set.
 *
 * @param irq IRQ number to enable
 */
void riscv_plic_irq_enable(uint32_t irq)
{
	const struct device *dev = get_plic_dev_from_irq(irq);
	struct plic_data *data = dev->data;
	k_spinlock_key_t key = k_spin_lock(&data->lock);

	plic_irq_enable_set_state(irq, true);

	k_spin_unlock(&data->lock, key);
}

/**
 * @brief Disable a riscv PLIC-specific interrupt line
 *
 * This routine disables a RISCV PLIC-specific interrupt line.
 * riscv_plic_irq_disable is called by RISCV_PRIVILEGED
 * arch_irq_disable function to disable external interrupts, for
 * IRQS level == 2, whenever CONFIG_RISCV_HAS_PLIC variable is set.
 *
 * @param irq IRQ number to disable
 */
void riscv_plic_irq_disable(uint32_t irq)
{
	const struct device *dev = get_plic_dev_from_irq(irq);
	struct plic_data *data = dev->data;
	k_spinlock_key_t key = k_spin_lock(&data->lock);

	plic_irq_enable_set_state(irq, false);

	k_spin_unlock(&data->lock, key);
}

/* Check if the local IRQ of a PLIC instance is enabled */
static int local_irq_is_enabled(const struct device *dev, uint32_t local_irq)
{
	uint32_t bit_position = local_irq & PLIC_REG_MASK;
	int is_enabled = IS_ENABLED(CONFIG_PLIC_IRQ_AFFINITY) ? 0 : 1;

	for (uint32_t cpu_num = 0; cpu_num < arch_num_cpus(); cpu_num++) {
		mem_addr_t en_addr =
			get_context_en_addr(dev, cpu_num) + local_irq_to_reg_offset(local_irq);
		uint32_t en_value = sys_read32(en_addr);

		if (IS_ENABLED(CONFIG_PLIC_IRQ_AFFINITY)) {
			is_enabled |= !!(en_value & BIT(bit_position));
		} else {
			is_enabled &= !!(en_value & BIT(bit_position));
		}
	}

	return is_enabled;
}

/**
 * @brief Check if a riscv PLIC-specific interrupt line is enabled
 *
 * This routine checks if a RISCV PLIC-specific interrupt line is enabled.
 * @param irq IRQ number to check
 *
 * @return 1 or 0
 */
int riscv_plic_irq_is_enabled(uint32_t irq)
{
	const struct device *dev = get_plic_dev_from_irq(irq);
	struct plic_data *data = dev->data;
	const uint32_t local_irq = irq_from_level_2(irq);
	int ret = 0;

	K_SPINLOCK(&data->lock) {
		ret = local_irq_is_enabled(dev, local_irq);
	}

	return ret;
}

/**
 * @brief Set priority of a riscv PLIC-specific interrupt line
 *
 * This routine set the priority of a RISCV PLIC-specific interrupt line.
 * riscv_plic_irq_set_prio is called by riscv arch_irq_priority_set to set
 * the priority of an interrupt whenever CONFIG_RISCV_HAS_PLIC variable is set.
 *
 * @param irq IRQ number for which to set priority
 * @param priority Priority of IRQ to set to
 */
void riscv_plic_set_priority(uint32_t irq, uint32_t priority)
{
	const struct device *dev = get_plic_dev_from_irq(irq);
	const struct plic_config *config = dev->config;
	const uint32_t local_irq = irq_from_level_2(irq);
	mem_addr_t prio_addr = config->prio + (local_irq * sizeof(uint32_t));

	if (priority > config->max_prio) {
		priority = config->max_prio;
	}

	sys_write32(priority, prio_addr);
}

#ifdef CONFIG_PLIC_SUPPORTS_SOFT_INTERRUPT
void riscv_plic_irq_set_pending(uint32_t irq)
{
	const struct device *dev = get_plic_dev_from_irq(irq);
	const uint32_t local_irq = irq_from_level_2(irq);
	mem_addr_t pend_addr = get_pending_reg(dev, local_irq);
	uint32_t pend_value = sys_read32(pend_addr);

	WRITE_BIT(pend_value, local_irq & PLIC_REG_MASK, true);
	sys_write32(pend_value, pend_addr);
}
#endif /* CONFIG_PLIC_SUPPORTS_SOFT_INTERRUPT */

/**
 * @brief Get riscv PLIC-specific interrupt line causing an interrupt
 *
 * This routine returns the RISCV PLIC-specific interrupt line causing an
 * interrupt.
 *
 * @param dev Optional device pointer to get the interrupt line's controller
 *
 * @return PLIC-specific interrupt line causing an interrupt.
 */
unsigned int riscv_plic_get_irq(void)
{
	return save_irq[arch_curr_cpu()->id];
}

/**
 * @brief Get riscv PLIC causing an interrupt
 *
 * This routine returns the RISCV PLIC device causing an interrupt.
 *
 * @return PLIC device causing an interrupt.
 */
const struct device *riscv_plic_get_dev(void)
{
	return save_dev[arch_curr_cpu()->id];
}

#ifdef CONFIG_PLIC_IRQ_AFFINITY
/**
 * @brief Set riscv PLIC-specific interrupt enable by cpu bitmask
 *
 * @param irq IRQ number for which to set smp irq affinity
 * @param cpumask Bitmask to specific which cores can handle IRQ
 */
int riscv_plic_irq_set_affinity(uint32_t irq, uint32_t cpumask)
{
	const struct device *dev = get_plic_dev_from_irq(irq);
	struct plic_data *data = dev->data;
	__maybe_unused const struct plic_config *config = dev->config;
	const uint32_t local_irq = irq_from_level_2(irq);
	k_spinlock_key_t key;

	if (local_irq >= config->nr_irqs) {
		__ASSERT(false, "overflow: irq %d, local_irq %d", irq, local_irq);
		return -EINVAL;
	}

	if ((cpumask & ~BIT_MASK(arch_num_cpus())) != 0) {
		__ASSERT(false, "cpumask: 0x%X", cpumask);
		return -EINVAL;
	}

	key = k_spin_lock(&data->lock);
	/* Updated irq_cpumask for next time setting plic enable register */
	data->irq_cpumask[local_irq] = (plic_cpumask_t)cpumask;

	/* If irq is enabled, apply the new irq affinity */
	if (local_irq_is_enabled(dev, local_irq)) {
		plic_irq_enable_set_state(irq, true);
	}
	k_spin_unlock(&data->lock, key);

	return 0;
}
#endif /* CONFIG_PLIC_IRQ_AFFINITY */

#ifdef CONFIG_PLIC_SHELL_IRQ_COUNT
/**
 * If there's more than one core, irq_count points to a 2D-array: irq_count[NUM_CPUs + 1][nr_irqs]
 *
 *    i.e. NUM_CPUs == 2:
 *      CPU 0    [0 ... nr_irqs - 1]
 *      CPU 1    [0 ... nr_irqs - 1]
 *      TOTAL    [0 ... nr_irqs - 1]
 */
static ALWAYS_INLINE uint16_t *get_irq_hit_count_cpu(const struct device *dev, int cpu,
						     uint32_t local_irq)
{
	const struct plic_config *config = dev->config;
	const struct plic_data *data = dev->data;
	uint32_t offset = local_irq;

	if (CONFIG_MP_MAX_NUM_CPUS > 1) {
		offset = cpu * config->nr_irqs + local_irq;
	}

	return &data->stats.irq_count[offset];
}

static ALWAYS_INLINE uint16_t *get_irq_hit_count_total(const struct device *dev, uint32_t local_irq)
{
	const struct plic_config *config = dev->config;
	const struct plic_data *data = dev->data;
	uint32_t offset = local_irq;

	if (CONFIG_MP_MAX_NUM_CPUS > 1) {
		offset = arch_num_cpus() * config->nr_irqs + local_irq;
	}

	return &data->stats.irq_count[offset];
}
#endif /* CONFIG_PLIC_SHELL_IRQ_COUNT */

static void plic_irq_handler(const struct device *dev)
{
	const struct plic_config *config = dev->config;
	mem_addr_t claim_complete_addr = get_claim_complete_addr(dev);
	struct _isr_table_entry *ite;
	uint32_t cpu_id = arch_curr_cpu()->id;
	/* Get the IRQ number generating the interrupt */
	const uint32_t local_irq = sys_read32(claim_complete_addr);

#ifdef CONFIG_PLIC_SHELL_IRQ_COUNT
	uint16_t *cpu_count = get_irq_hit_count_cpu(dev, cpu_id, local_irq);
	uint16_t *total_count = get_irq_hit_count_total(dev, local_irq);

	/* Cap the count at __UINT16_MAX__ */
	if (*total_count < __UINT16_MAX__) {
		(*cpu_count)++;
		if (CONFIG_MP_MAX_NUM_CPUS > 1) {
			(*total_count)++;
		}
	}
#endif /* CONFIG_PLIC_SHELL_IRQ_COUNT */

	/*
	 * Note: Because PLIC only supports multicast of interrupt, all enabled
	 * targets will receive interrupt notification. Only the fastest target
	 * will claim this interrupt, and other targets will claim ID 0 if
	 * no other pending interrupt now.
	 *
	 * (by RISC-V Privileged Architecture v1.10)
	 */
	if ((CONFIG_MP_MAX_NUM_CPUS > 1) && (local_irq == 0U)) {
		return;
	}

	/*
	 * Save IRQ in save_irq. To be used, if need be, by
	 * subsequent handlers registered in the _sw_isr_table table,
	 * as IRQ number held by the claim_complete register is
	 * cleared upon read.
	 */
	save_irq[cpu_id] = local_irq;
	save_dev[cpu_id] = dev;

	/*
	 * If the IRQ is out of range, call z_irq_spurious.
	 * A call to z_irq_spurious will not return.
	 */
	if ((local_irq == 0U) || (local_irq >= config->nr_irqs)) {
		z_irq_spurious(NULL);
	}

#ifdef CONFIG_PLIC_SUPPORTS_TRIG_EDGE
	uint32_t trig_val = riscv_plic_irq_trig_val(dev, local_irq);
	/*
	 * Edge-triggered interrupts have to be acknowledged first before
	 * getting handled so that we don't miss on the next edge-triggered interrupt.
	 */
	if (trig_val == PLIC_TRIG_EDGE) {
		sys_write32(local_irq, claim_complete_addr);
	}
#endif /* CONFIG_PLIC_SUPPORTS_TRIG_EDGE */

	/* Call the corresponding IRQ handler in _sw_isr_table */
	ite = &config->isr_table[local_irq];
	ite->isr(ite->arg);

	/*
	 * Write to claim_complete register to indicate to
	 * PLIC controller that the IRQ has been handled
	 * for level triggered interrupts.
	 */
#ifdef CONFIG_PLIC_SUPPORTS_TRIG_EDGE
	/* Handle only if level-triggered */
	if (trig_val == PLIC_TRIG_LEVEL) {
		sys_write32(local_irq, claim_complete_addr);
	}
#else
	sys_write32(local_irq, claim_complete_addr);
#endif /* #ifdef CONFIG_PLIC_SUPPORTS_TRIG_EDGE */
}

/**
 * @brief Initialize the Platform Level Interrupt Controller
 *
 * @param dev PLIC device struct
 *
 * @retval 0 on success.
 */
static int plic_init(const struct device *dev)
{
	const struct plic_config *config = dev->config;
	mem_addr_t en_addr, thres_prio_addr;
	mem_addr_t prio_addr = config->prio;

	/* Iterate through each of the contexts, HART + PRIV */
	for (uint32_t cpu_num = 0; cpu_num < arch_num_cpus(); cpu_num++) {
		en_addr = get_context_en_addr(dev, cpu_num);
		thres_prio_addr = get_threshold_priority_addr(dev, cpu_num);

		/* Ensure that all interrupts are disabled initially */
		for (uint32_t i = 0; i < get_plic_enabled_size(dev); i++) {
			sys_write32(0U, en_addr + (i * sizeof(uint32_t)));
		}

		/* Set threshold priority to 0 */
		sys_write32(0U, thres_prio_addr);
	}

	/* Set priority of each interrupt line to 0 initially */
	for (uint32_t i = 0; i < config->nr_irqs; i++) {
		sys_write32(0U, prio_addr + (i * sizeof(uint32_t)));
	}

	/* Configure IRQ for PLIC driver */
	config->irq_config_func();

	return 0;
}

#ifdef CONFIG_PLIC_SHELL
static inline int parse_device(const struct shell *sh, size_t argc, char *argv[],
			       const struct device **plic)
{
	ARG_UNUSED(argc);

	*plic = device_get_binding(argv[1]);
	if (*plic == NULL) {
		shell_error(sh, "PLIC device (%s) not found!\n", argv[1]);
		return -ENODEV;
	}

	return 0;
}

#ifdef CONFIG_PLIC_SHELL_IRQ_COUNT
static int cmd_stats_get(const struct shell *sh, size_t argc, char *argv[])
{
	const struct device *dev;
	int ret = parse_device(sh, argc, argv, &dev);
	uint16_t min_hit = 0;

	if (ret != 0) {
		return ret;
	}

	const struct plic_config *config = dev->config;

	if (argc > 2) {
		min_hit = (uint16_t)shell_strtoul(argv[2], 10, &ret);
		if (ret != 0) {
			shell_error(sh, "Failed to parse %s: %d", argv[2], ret);
			return ret;
		}
		shell_print(sh, "IRQ line with > %d hits:", min_hit);
	}

	shell_fprintf(sh, SHELL_NORMAL, "   IRQ");
	for (int cpu_id = 0; cpu_id < arch_num_cpus(); cpu_id++) {
		shell_fprintf(sh, SHELL_NORMAL, "  CPU%2d", cpu_id);
	}
	if (CONFIG_MP_MAX_NUM_CPUS > 1) {
		shell_fprintf(sh, SHELL_NORMAL, "  Total");
	}
	shell_fprintf(sh, SHELL_NORMAL, "\tISR(ARG)\n");

	for (int i = 0; i < config->nr_irqs; i++) {
		uint16_t *total_count = get_irq_hit_count_total(dev, i);

		if (*total_count <= min_hit) {
			/* Skips printing if total_hit is lesser than min_hit */
			continue;
		}

		shell_fprintf(sh, SHELL_NORMAL, "  %4d", i); /* IRQ number */
		/* Print the IRQ hit counts on each CPU */
		for (int cpu_id = 0; cpu_id < arch_num_cpus(); cpu_id++) {
			uint16_t *cpu_count = get_irq_hit_count_cpu(dev, cpu_id, i);

			shell_fprintf(sh, SHELL_NORMAL, "  %5d", *cpu_count);
		}
		if (CONFIG_MP_MAX_NUM_CPUS > 1) {
			/* If there's > 1 CPU, print the total hit count at the end */
			shell_fprintf(sh, SHELL_NORMAL, "  %5d", *total_count);
		}
#ifdef CONFIG_SYMTAB
		const char *name =
			symtab_find_symbol_name((uintptr_t)config->isr_table[i].isr, NULL);

		shell_fprintf(sh, SHELL_NORMAL, "\t%s(%p)\n", name, config->isr_table[i].arg);
#else
		shell_fprintf(sh, SHELL_NORMAL, "\t%p(%p)\n", (void *)config->isr_table[i].isr,
			      config->isr_table[i].arg);
#endif /* CONFIG_SYMTAB */
	}
	shell_print(sh, "");

	return 0;
}

static int cmd_stats_clear(const struct shell *sh, size_t argc, char *argv[])
{
	const struct device *dev;
	int ret = parse_device(sh, argc, argv, &dev);

	if (ret != 0) {
		return ret;
	}

	const struct plic_data *data = dev->data;
	const struct plic_config *config = dev->config;
	struct plic_stats stat = data->stats;

	memset(stat.irq_count, 0,
	       config->nr_irqs *
		       COND_CODE_1(CONFIG_MP_MAX_NUM_CPUS, (1),
				   (UTIL_INC(CONFIG_MP_MAX_NUM_CPUS))) *
		       sizeof(uint16_t));

	shell_print(sh, "Cleared stats of %s.\n", dev->name);

	return 0;
}
#endif /* CONFIG_PLIC_SHELL_IRQ_COUNT */

#ifdef CONFIG_PLIC_SHELL_IRQ_AFFINITY
static int cmd_affinity_set(const struct shell *sh, size_t argc, char **argv)
{
	ARG_UNUSED(argc);

	uint32_t local_irq, irq, mask;
	const struct device *dev;
	int rc = parse_device(sh, argc, argv, &dev);
	const struct plic_config *config = dev->config;

	if (rc != 0) {
		return rc;
	}

	local_irq = (uint32_t)shell_strtol(argv[2], 10, &rc);
	if (rc != 0) {
		shell_error(sh, "Failed to parse %s: %d", argv[2], rc);
	}

	if (local_irq >= config->nr_irqs) {
		shell_error(sh, "local_irq (%d) > nr_irqs (%d)", local_irq, config->nr_irqs);
		return -EINVAL;
	}

	mask = (uint32_t)shell_strtol(argv[3], 16, &rc);
	if (rc != 0) {
		shell_error(sh, "Failed to parse %s: %d", argv[3], rc);
	}

	if ((mask & ~BIT_MASK(arch_num_cpus())) != 0) {
		shell_error(sh, "cpumask: 0x%X num_cpus: %d", mask, arch_num_cpus());
		return -EINVAL;
	}

	if (local_irq != 0) {
		irq = local_irq_to_irq(dev, local_irq);
		riscv_plic_irq_set_affinity(irq, mask);
		shell_print(sh, "IRQ %d affinity set to 0x%X", local_irq, mask);
	} else {
		for (local_irq = 1; local_irq <= config->nr_irqs; local_irq++) {
			irq = local_irq_to_irq(dev, local_irq);
			riscv_plic_irq_set_affinity(irq, mask);
		}
		shell_print(sh, "All IRQ affinity set to 0x%X", mask);
	}

	return 0;
}

static int cmd_affinity_get(const struct shell *sh, size_t argc, char **argv)
{
	ARG_UNUSED(argc);

	const struct device *dev;
	int rc = parse_device(sh, argc, argv, &dev);
	const struct plic_config *config = dev->config;

	if (rc != 0) {
		return rc;
	}

	shell_print(sh, " IRQ  MASK");
	if (argc == 2) {
		for (uint32_t local_irq = 0; local_irq < config->nr_irqs; local_irq++) {
			shell_print(sh, "%4d  0x%X", local_irq, get_irq_cpumask(dev, local_irq));
		}
	} else {
		uint32_t local_irq = (uint32_t)shell_strtol(argv[2], 10, &rc);

		if (rc != 0) {
			shell_error(sh, "Failed to parse %s: %d", argv[2], rc);
		}

		if (local_irq >= config->nr_irqs) {
			shell_error(sh, "local_irq (%d) > nr_irqs (%d)", local_irq,
				    config->nr_irqs);
			return -EINVAL;
		}

		shell_print(sh, "%4d  0x%X", local_irq, get_irq_cpumask(dev, local_irq));
	}

	return 0;
}
#endif /* CONFIG_PLIC_SHELL_IRQ_AFFINITY */

/* Device name autocompletion support */
static void device_name_get(size_t idx, struct shell_static_entry *entry)
{
	const struct device *dev = shell_device_lookup(idx, "interrupt-controller");

	entry->syntax = (dev != NULL) ? dev->name : NULL;
	entry->handler = NULL;
	entry->help = NULL;
	entry->subcmd = NULL;
}

SHELL_DYNAMIC_CMD_CREATE(dsub_device_name, device_name_get);

#ifdef CONFIG_PLIC_SHELL_IRQ_COUNT
SHELL_STATIC_SUBCMD_SET_CREATE(plic_stats_cmds,
	SHELL_CMD_ARG(get, &dsub_device_name,
		"Read PLIC's stats.\n"
		"Usage: plic stats get <device> [minimum hits]",
		cmd_stats_get, 2, 1),
	SHELL_CMD_ARG(clear, &dsub_device_name,
		"Reset PLIC's stats.\n"
		"Usage: plic stats clear <device>",
		cmd_stats_clear, 2, 0),
	SHELL_SUBCMD_SET_END
);
#endif /* CONFIG_PLIC_SHELL_IRQ_COUNT */

#ifdef CONFIG_PLIC_SHELL_IRQ_AFFINITY
SHELL_STATIC_SUBCMD_SET_CREATE(plic_affinity_cmds,
	SHELL_CMD_ARG(set, &dsub_device_name,
		      "Set IRQ affinity.\n"
		      "Usage: plic affinity set <device> <local_irq> <cpumask>",
		      cmd_affinity_set, 4, 0),
	SHELL_CMD_ARG(get, &dsub_device_name,
		      "Get IRQ affinity.\n"
		      "Usage: plic affinity get <device> <local_irq>",
		      cmd_affinity_get, 2, 1),
	SHELL_SUBCMD_SET_END);
#endif /* CONFIG_PLIC_SHELL_IRQ_AFFINITY */

SHELL_STATIC_SUBCMD_SET_CREATE(plic_cmds,
#ifdef CONFIG_PLIC_SHELL_IRQ_COUNT
	SHELL_CMD(stats, &plic_stats_cmds, "IRQ stats", NULL),
#endif /* CONFIG_PLIC_SHELL_IRQ_COUNT */
#ifdef CONFIG_PLIC_SHELL_IRQ_AFFINITY
	SHELL_CMD(affinity, &plic_affinity_cmds, "IRQ affinity", NULL),
#endif /* CONFIG_PLIC_SHELL_IRQ_AFFINITY */
	SHELL_SUBCMD_SET_END
);

SHELL_CMD_REGISTER(plic, &plic_cmds, "PLIC shell commands", NULL);
#endif /* CONFIG_PLIC_SHELL */

#define PLIC_MIN_IRQ_NUM(n) MIN(DT_INST_PROP(n, riscv_ndev), CONFIG_MAX_IRQ_PER_AGGREGATOR)

#ifdef CONFIG_PLIC_SHELL_IRQ_COUNT
#define PLIC_INTC_IRQ_COUNT_BUF_DEFINE(n)                                                          \
	static uint16_t local_irq_count_##n[COND_CODE_1(CONFIG_MP_MAX_NUM_CPUS, (1),               \
							(UTIL_INC(CONFIG_MP_MAX_NUM_CPUS)))]       \
					   [PLIC_MIN_IRQ_NUM(n)];
#define PLIC_INTC_IRQ_COUNT_INIT(n)                                                                \
	.stats = {                                                                                 \
		.irq_count = &local_irq_count_##n[0][0],                                           \
	},

#else
#define PLIC_INTC_IRQ_COUNT_BUF_DEFINE(n)
#define PLIC_INTC_IRQ_COUNT_INIT(n)
#endif /* CONFIG_PLIC_SHELL_IRQ_COUNT */

#ifdef CONFIG_PLIC_IRQ_AFFINITY
#define PLIC_IRQ_CPUMASK_BUF_DECLARE(n)                                                            \
	static plic_cpumask_t irq_cpumask_##n[PLIC_MIN_IRQ_NUM(n)] = {                             \
		[0 ...(PLIC_MIN_IRQ_NUM(n) - 1)] = CONFIG_PLIC_IRQ_AFFINITY_MASK,                  \
	}
#define PLIC_IRQ_CPUMASK_BUF_INIT(n) .irq_cpumask = &irq_cpumask_##n[0],
#else
#define PLIC_IRQ_CPUMASK_BUF_DECLARE(n)
#define PLIC_IRQ_CPUMASK_BUF_INIT(n)
#endif /* CONFIG_PLIC_IRQ_AFFINITY */

#define PLIC_INTC_DATA_INIT(n)                                                                     \
	PLIC_INTC_IRQ_COUNT_BUF_DEFINE(n);                                                         \
	PLIC_IRQ_CPUMASK_BUF_DECLARE(n);                                                           \
	static struct plic_data plic_data_##n = {                                                  \
		PLIC_INTC_IRQ_COUNT_INIT(n)                                                        \
		PLIC_IRQ_CPUMASK_BUF_INIT(n)                                                       \
	};

#define PLIC_INTC_IRQ_FUNC_DECLARE(n) static void plic_irq_config_func_##n(void)

#define PLIC_INTC_IRQ_FUNC_DEFINE(n)                                                               \
	static void plic_irq_config_func_##n(void)                                                 \
	{                                                                                          \
		IRQ_CONNECT(DT_INST_IRQN(n), 0, plic_irq_handler, DEVICE_DT_INST_GET(n), 0);       \
		irq_enable(DT_INST_IRQN(n));                                                       \
	}

#define HART_CONTEXTS(i, n) IF_ENABLED(IS_EQ(DT_INST_IRQN_BY_IDX(n, i), DT_INST_IRQN(n)), (i,))
#define PLIC_HART_CONTEXT_DECLARE(n)                                                               \
	INTC_PLIC_STATIC const uint32_t plic_hart_contexts_##n[DT_CHILD_NUM(DT_PATH(cpus))] = {    \
		LISTIFY(DT_INST_NUM_IRQS(n), HART_CONTEXTS, (), n)}

#define PLIC_INTC_CONFIG_INIT(n)                                                                   \
	PLIC_INTC_IRQ_FUNC_DECLARE(n);                                                             \
	PLIC_HART_CONTEXT_DECLARE(n);                                                              \
	static const struct plic_config plic_config_##n = {                                        \
		.prio = PLIC_BASE_ADDR(n),                                                         \
		.irq_en = PLIC_BASE_ADDR(n) + CONTEXT_ENABLE_BASE,                                 \
		.reg = PLIC_BASE_ADDR(n) + CONTEXT_BASE,                                           \
		IF_ENABLED(CONFIG_PLIC_SUPPORTS_SOFT_INTERRUPT,                                    \
			   (.pend = PLIC_BASE_ADDR(n) + CONTEXT_PENDING_BASE,))                    \
		IF_ENABLED(CONFIG_PLIC_SUPPORTS_TRIG_TYPE,                                         \
			   (.trig = PLIC_BASE_ADDR(n) + CONFIG_PLIC_TRIG_TYPE_REG_OFFSET,))        \
		.max_prio = DT_INST_PROP(n, riscv_max_priority),                                   \
		.riscv_ndev = DT_INST_PROP(n, riscv_ndev),                                         \
		.nr_irqs = PLIC_MIN_IRQ_NUM(n),                                                    \
		.irq = DT_INST_IRQN(n),                                                            \
		.irq_config_func = plic_irq_config_func_##n,                                       \
		.isr_table = &_sw_isr_table[INTC_INST_ISR_TBL_OFFSET(n)],                          \
		.hart_context = plic_hart_contexts_##n,                                            \
	};                                                                                         \
	PLIC_INTC_IRQ_FUNC_DEFINE(n)

#define PLIC_INTC_DEVICE_INIT(n)                                                                   \
	IRQ_PARENT_ENTRY_DEFINE(                                                                   \
		plic##n, DEVICE_DT_INST_GET(n), DT_INST_IRQN(n),                                   \
		INTC_INST_ISR_TBL_OFFSET(n),                                                       \
		DT_INST_INTC_GET_AGGREGATOR_LEVEL(n));                                             \
	PLIC_INTC_CONFIG_INIT(n)                                                                   \
	PLIC_INTC_DATA_INIT(n)                                                                     \
	DEVICE_DT_INST_DEFINE(n, &plic_init, NULL,                                                 \
			      &plic_data_##n, &plic_config_##n,                                    \
			      PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY,                             \
			      NULL);

DT_INST_FOREACH_STATUS_OKAY(PLIC_INTC_DEVICE_INIT)
