You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
933 lines
25 KiB
933 lines
25 KiB
/* |
|
* Copyright (c) 2021-2025 Espressif Systems (Shanghai) Co., Ltd. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/irq.h> |
|
#include <stdint.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <stdbool.h> |
|
#include <string.h> |
|
#include <soc.h> |
|
#include <zephyr/drivers/interrupt_controller/intc_esp32.h> |
|
#include <esp_memory_utils.h> |
|
#include <esp_attr.h> |
|
#include <esp_cpu.h> |
|
#include <esp_rom_sys.h> |
|
#include <esp_private/rtc_ctrl.h> |
|
#include <limits.h> |
|
#include <assert.h> |
|
#include <soc/soc.h> |
|
|
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(intc_esp32, CONFIG_LOG_DEFAULT_LEVEL); |
|
|
|
#define ETS_INTERNAL_TIMER0_INTR_NO 6 |
|
#define ETS_INTERNAL_TIMER1_INTR_NO 15 |
|
#define ETS_INTERNAL_TIMER2_INTR_NO 16 |
|
#define ETS_INTERNAL_SW0_INTR_NO 7 |
|
#define ETS_INTERNAL_SW1_INTR_NO 29 |
|
#define ETS_INTERNAL_PROFILING_INTR_NO 11 |
|
|
|
#define VECDESC_FL_RESERVED (1 << 0) |
|
#define VECDESC_FL_INIRAM (1 << 1) |
|
#define VECDESC_FL_SHARED (1 << 2) |
|
#define VECDESC_FL_NONSHARED (1 << 3) |
|
|
|
/* |
|
* Define this to debug the choices made when allocating the interrupt. This leads to much debugging |
|
* output within a critical region, which can lead to weird effects like e.g. the interrupt watchdog |
|
* being triggered, that is why it is separate from the normal LOG* scheme. |
|
*/ |
|
#ifdef CONFIG_INTC_ESP32_DECISIONS_LOG |
|
# define INTC_LOG(...) LOG_INF(__VA_ARGS__) |
|
#else |
|
# define INTC_LOG(...) do {} while (false) |
|
#endif |
|
|
|
/* Typedef for C-callable interrupt handler function */ |
|
typedef void (*intc_dyn_handler_t)(const void *); |
|
|
|
/* shared critical section context */ |
|
static int esp_intc_csec; |
|
|
|
static inline void esp_intr_lock(void) |
|
{ |
|
esp_intc_csec = irq_lock(); |
|
} |
|
|
|
static inline void esp_intr_unlock(void) |
|
{ |
|
irq_unlock(esp_intc_csec); |
|
} |
|
|
|
/* Linked list of vector descriptions, sorted by cpu.intno value */ |
|
static struct vector_desc_t *vector_desc_head; /* implicitly initialized to NULL */ |
|
|
|
/* This bitmask has an 1 if the int should be disabled when the flash is disabled. */ |
|
static uint32_t non_iram_int_mask[CONFIG_MP_MAX_NUM_CPUS]; |
|
/* This bitmask has 1 in it if the int was disabled using esp_intr_noniram_disable. */ |
|
static uint32_t non_iram_int_disabled[CONFIG_MP_MAX_NUM_CPUS]; |
|
static bool non_iram_int_disabled_flag[CONFIG_MP_MAX_NUM_CPUS]; |
|
|
|
/* |
|
* Inserts an item into vector_desc list so that the list is sorted |
|
* with an incrementing cpu.intno value. |
|
*/ |
|
static void insert_vector_desc(struct vector_desc_t *to_insert) |
|
{ |
|
struct vector_desc_t *vd = vector_desc_head; |
|
struct vector_desc_t *prev = NULL; |
|
|
|
while (vd != NULL) { |
|
if (vd->cpu > to_insert->cpu) { |
|
break; |
|
} |
|
if (vd->cpu == to_insert->cpu && vd->intno >= to_insert->intno) { |
|
break; |
|
} |
|
prev = vd; |
|
vd = vd->next; |
|
} |
|
if ((vector_desc_head == NULL) || (prev == NULL)) { |
|
/* First item */ |
|
to_insert->next = vd; |
|
vector_desc_head = to_insert; |
|
} else { |
|
prev->next = to_insert; |
|
to_insert->next = vd; |
|
} |
|
} |
|
|
|
/* Returns a vector_desc entry for an intno/cpu, or NULL if none exists. */ |
|
static struct vector_desc_t *find_desc_for_int(int intno, int cpu) |
|
{ |
|
struct vector_desc_t *vd = vector_desc_head; |
|
|
|
while (vd != NULL) { |
|
if (vd->cpu == cpu && vd->intno == intno) { |
|
break; |
|
} |
|
vd = vd->next; |
|
} |
|
return vd; |
|
} |
|
|
|
/* |
|
* Returns a vector_desc entry for an intno/cpu. |
|
* Either returns a preexisting one or allocates a new one and inserts |
|
* it into the list. Returns NULL on malloc fail. |
|
*/ |
|
static struct vector_desc_t *get_desc_for_int(int intno, int cpu) |
|
{ |
|
struct vector_desc_t *vd = find_desc_for_int(intno, cpu); |
|
|
|
if (vd == NULL) { |
|
struct vector_desc_t *newvd = k_malloc(sizeof(struct vector_desc_t)); |
|
|
|
if (newvd == NULL) { |
|
return NULL; |
|
} |
|
memset(newvd, 0, sizeof(struct vector_desc_t)); |
|
newvd->intno = intno; |
|
newvd->cpu = cpu; |
|
insert_vector_desc(newvd); |
|
return newvd; |
|
} else { |
|
return vd; |
|
} |
|
} |
|
|
|
/* |
|
* Returns a vector_desc entry for an source, the cpu parameter is used |
|
* to tell GPIO_INT and GPIO_NMI from different CPUs |
|
*/ |
|
static struct vector_desc_t *find_desc_for_source(int source, int cpu) |
|
{ |
|
struct vector_desc_t *vd = vector_desc_head; |
|
|
|
while (vd != NULL) { |
|
if (!(vd->flags & VECDESC_FL_SHARED)) { |
|
if (vd->source == source && cpu == vd->cpu) { |
|
break; |
|
} |
|
} else if (vd->cpu == cpu) { |
|
/* check only shared vds for the correct cpu, otherwise skip */ |
|
bool found = false; |
|
struct shared_vector_desc_t *svd = vd->shared_vec_info; |
|
|
|
assert(svd != NULL); |
|
while (svd) { |
|
if (svd->source == source) { |
|
found = true; |
|
break; |
|
} |
|
svd = svd->next; |
|
} |
|
if (found) { |
|
break; |
|
} |
|
} |
|
vd = vd->next; |
|
} |
|
return vd; |
|
} |
|
|
|
int esp_intr_mark_shared(int intno, int cpu, bool is_int_ram) |
|
{ |
|
if (intno >= SOC_CPU_INTR_NUM) { |
|
return -EINVAL; |
|
} |
|
if (cpu >= arch_num_cpus()) { |
|
return -EINVAL; |
|
} |
|
|
|
esp_intr_lock(); |
|
struct vector_desc_t *vd = get_desc_for_int(intno, cpu); |
|
|
|
if (vd == NULL) { |
|
esp_intr_unlock(); |
|
return -ENOMEM; |
|
} |
|
vd->flags = VECDESC_FL_SHARED; |
|
if (is_int_ram) { |
|
vd->flags |= VECDESC_FL_INIRAM; |
|
} |
|
esp_intr_unlock(); |
|
|
|
return 0; |
|
} |
|
|
|
int esp_intr_reserve(int intno, int cpu) |
|
{ |
|
if (intno >= SOC_CPU_INTR_NUM) { |
|
return -EINVAL; |
|
} |
|
if (cpu >= arch_num_cpus()) { |
|
return -EINVAL; |
|
} |
|
|
|
esp_intr_lock(); |
|
struct vector_desc_t *vd = get_desc_for_int(intno, cpu); |
|
|
|
if (vd == NULL) { |
|
esp_intr_unlock(); |
|
return -ENOMEM; |
|
} |
|
vd->flags = VECDESC_FL_RESERVED; |
|
esp_intr_unlock(); |
|
|
|
return 0; |
|
} |
|
|
|
/* Returns true if handler for interrupt is not the default unhandled interrupt handler */ |
|
static bool intr_has_handler(int intr, int cpu) |
|
{ |
|
bool r; |
|
|
|
r = _sw_isr_table[intr * CONFIG_MP_MAX_NUM_CPUS + cpu].isr != z_irq_spurious; |
|
|
|
return r; |
|
} |
|
|
|
static bool is_vect_desc_usable(struct vector_desc_t *vd, int flags, int cpu, int force) |
|
{ |
|
/* Check if interrupt is not reserved by design */ |
|
int x = vd->intno; |
|
esp_cpu_intr_desc_t intr_desc; |
|
esp_cpu_intr_get_desc(cpu, x, &intr_desc); |
|
|
|
if (intr_desc.flags & ESP_CPU_INTR_DESC_FLAG_RESVD) { |
|
INTC_LOG("....Unusable: reserved"); |
|
return false; |
|
} |
|
if (intr_desc.flags & ESP_CPU_INTR_DESC_FLAG_SPECIAL && force == -1) { |
|
INTC_LOG("....Unusable: special-purpose int"); |
|
return false; |
|
} |
|
|
|
#ifndef SOC_CPU_HAS_FLEXIBLE_INTC |
|
/* Check if the interrupt level is acceptable */ |
|
if (!(flags & (1 << intr_desc.priority))) { |
|
INTC_LOG("....Unusable: incompatible level"); |
|
return false; |
|
} |
|
/* check if edge/level type matches what we want */ |
|
if (((flags & ESP_INTR_FLAG_EDGE) && (intr_desc.type == ESP_CPU_INTR_TYPE_LEVEL)) || |
|
(((!(flags & ESP_INTR_FLAG_EDGE)) && (intr_desc.type == ESP_CPU_INTR_TYPE_EDGE)))) { |
|
INTC_LOG("....Unusable: incompatible trigger type"); |
|
return false; |
|
} |
|
#endif |
|
|
|
/* check if interrupt is reserved at runtime */ |
|
if (vd->flags & VECDESC_FL_RESERVED) { |
|
INTC_LOG("....Unusable: reserved at runtime."); |
|
return false; |
|
} |
|
|
|
/* Ints can't be both shared and non-shared. */ |
|
assert(!((vd->flags & VECDESC_FL_SHARED) && (vd->flags & VECDESC_FL_NONSHARED))); |
|
/* check if interrupt already is in use by a non-shared interrupt */ |
|
if (vd->flags & VECDESC_FL_NONSHARED) { |
|
INTC_LOG("....Unusable: already in (non-shared) use."); |
|
return false; |
|
} |
|
/* check shared interrupt flags */ |
|
if (vd->flags & VECDESC_FL_SHARED) { |
|
if (flags & ESP_INTR_FLAG_SHARED) { |
|
bool in_iram_flag = ((flags & ESP_INTR_FLAG_IRAM) != 0); |
|
bool desc_in_iram_flag = ((vd->flags & VECDESC_FL_INIRAM) != 0); |
|
/* |
|
* Bail out if int is shared, but iram property |
|
* doesn't match what we want. |
|
*/ |
|
if ((vd->flags & VECDESC_FL_SHARED) && |
|
(desc_in_iram_flag != in_iram_flag)) { |
|
INTC_LOG("....Unusable: shared but iram prop doesn't match"); |
|
return false; |
|
} |
|
} else { |
|
/* |
|
* We need an unshared IRQ; can't use shared ones; |
|
* bail out if this is shared. |
|
*/ |
|
INTC_LOG("...Unusable: int is shared, we need non-shared."); |
|
return false; |
|
} |
|
} else if (intr_has_handler(x, cpu)) { |
|
INTC_LOG("....Unusable: already allocated"); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
/* |
|
* Locate a free interrupt compatible with the flags given. |
|
* The 'force' argument can be -1, or 0-31 to force checking a certain interrupt. |
|
* When a CPU is forced, the INTDESC_SPECIAL marked interrupts are also accepted. |
|
*/ |
|
static int get_available_int(int flags, int cpu, int force, int source) |
|
{ |
|
int x; |
|
int best = -1; |
|
int best_level = 9; |
|
int best_shared_ct = INT_MAX; |
|
/* Default vector desc, for vectors not in the linked list */ |
|
struct vector_desc_t empty_vect_desc; |
|
|
|
memset(&empty_vect_desc, 0, sizeof(struct vector_desc_t)); |
|
|
|
/* Level defaults to any low/med interrupt */ |
|
if (!(flags & ESP_INTR_FLAG_LEVELMASK)) { |
|
flags |= ESP_INTR_FLAG_LOWMED; |
|
} |
|
|
|
INTC_LOG("%s: try to find existing. Cpu: %d, Source: %d", __func__, cpu, source); |
|
struct vector_desc_t *vd = find_desc_for_source(source, cpu); |
|
|
|
if (vd) { |
|
/* if existing vd found, don't need to search any more. */ |
|
INTC_LOG("%s: existing vd found. intno: %d", __func__, vd->intno); |
|
if (force != -1 && force != vd->intno) { |
|
INTC_LOG("%s: intr forced but not match existing. " |
|
"existing intno: %d, force: %d", __func__, vd->intno, force); |
|
} else if (!is_vect_desc_usable(vd, flags, cpu, force)) { |
|
INTC_LOG("%s: existing vd invalid.", __func__); |
|
} else { |
|
best = vd->intno; |
|
} |
|
return best; |
|
} |
|
if (force != -1) { |
|
INTC_LOG("%s: try to find force. " |
|
"Cpu: %d, Source: %d, Force: %d", __func__, cpu, source, force); |
|
/* if force assigned, don't need to search any more. */ |
|
vd = find_desc_for_int(force, cpu); |
|
if (vd == NULL) { |
|
/* if existing vd not found, just check the default state for the intr. */ |
|
empty_vect_desc.intno = force; |
|
vd = &empty_vect_desc; |
|
} |
|
if (is_vect_desc_usable(vd, flags, cpu, force)) { |
|
best = vd->intno; |
|
} else { |
|
INTC_LOG("%s: forced vd invalid.", __func__); |
|
} |
|
return best; |
|
} |
|
|
|
INTC_LOG("%s: start looking. Current cpu: %d", __func__, cpu); |
|
/* No allocated handlers as well as forced intr, iterate over the 32 possible interrupts */ |
|
for (x = 0; x < SOC_CPU_INTR_NUM; x++) { |
|
/* Grab the vector_desc for this vector. */ |
|
vd = find_desc_for_int(x, cpu); |
|
if (vd == NULL) { |
|
empty_vect_desc.intno = x; |
|
vd = &empty_vect_desc; |
|
} |
|
|
|
esp_cpu_intr_desc_t intr_desc; |
|
esp_cpu_intr_get_desc(cpu, x, &intr_desc); |
|
|
|
INTC_LOG("Int %d reserved %d level %d %s hasIsr %d", |
|
x, intr_desc.flags & ESP_CPU_INTR_DESC_FLAG_RESVD, intr_desc.priority, |
|
intr_desc.type == ESP_CPU_INTR_TYPE_LEVEL ? "LEVEL" : "EDGE", |
|
intr_has_handler(x, cpu)); |
|
|
|
if (!is_vect_desc_usable(vd, flags, cpu, force)) { |
|
continue; |
|
} |
|
|
|
if (flags & ESP_INTR_FLAG_SHARED) { |
|
/* We're allocating a shared int. */ |
|
|
|
/* See if int already is used as a shared interrupt. */ |
|
if (vd->flags & VECDESC_FL_SHARED) { |
|
/* |
|
* We can use this already-marked-as-shared interrupt. Count the |
|
* already attached isrs in order to see how useful it is. |
|
*/ |
|
int no = 0; |
|
struct shared_vector_desc_t *svdesc = vd->shared_vec_info; |
|
|
|
while (svdesc != NULL) { |
|
no++; |
|
svdesc = svdesc->next; |
|
} |
|
if (no < best_shared_ct || |
|
best_level > intr_desc.priority) { |
|
/* |
|
* Seems like this shared vector is both okay and has |
|
* the least amount of ISRs already attached to it. |
|
*/ |
|
best = x; |
|
best_shared_ct = no; |
|
best_level = intr_desc.priority; |
|
INTC_LOG("...int %d more usable as a shared int: " |
|
"has %d existing vectors", x, no); |
|
} else { |
|
INTC_LOG("...worse than int %d", best); |
|
} |
|
} else { |
|
if (best == -1) { |
|
/* |
|
* We haven't found a feasible shared interrupt yet. |
|
* This one is still free and usable, even if not |
|
* marked as shared. |
|
* Remember it in case we don't find any other shared |
|
* interrupt that qualifies. |
|
*/ |
|
if (best_level > intr_desc.priority) { |
|
best = x; |
|
best_level = intr_desc.priority; |
|
INTC_LOG("...int %d usable as new shared int", x); |
|
} |
|
} else { |
|
INTC_LOG("...already have a shared int"); |
|
} |
|
} |
|
} else { |
|
/* |
|
* Seems this interrupt is feasible. Select it and break out of the loop |
|
* No need to search further. |
|
*/ |
|
if (best_level > intr_desc.priority) { |
|
best = x; |
|
best_level = intr_desc.priority; |
|
} else { |
|
INTC_LOG("...worse than int %d", best); |
|
} |
|
} |
|
} |
|
INTC_LOG("%s: using int %d", __func__, best); |
|
|
|
/* |
|
* By now we have looked at all potential interrupts and |
|
* hopefully have selected the best one in best. |
|
*/ |
|
return best; |
|
} |
|
|
|
/* Common shared isr handler. Chain-call all ISRs. */ |
|
static void IRAM_ATTR shared_intr_isr(void *arg) |
|
{ |
|
struct vector_desc_t *vd = (struct vector_desc_t *)arg; |
|
struct shared_vector_desc_t *sh_vec = vd->shared_vec_info; |
|
|
|
esp_intr_lock(); |
|
while (sh_vec) { |
|
if (!sh_vec->disabled) { |
|
if (!(sh_vec->statusreg) || (*sh_vec->statusreg & sh_vec->statusmask)) { |
|
sh_vec->isr(sh_vec->arg); |
|
} |
|
} |
|
sh_vec = sh_vec->next; |
|
} |
|
esp_intr_unlock(); |
|
} |
|
|
|
int esp_intr_alloc_intrstatus(int source, |
|
int flags, |
|
uint32_t intrstatusreg, |
|
uint32_t intrstatusmask, |
|
intr_handler_t handler, |
|
void *arg, |
|
intr_handle_t *ret_handle) |
|
{ |
|
intr_handle_data_t *ret = NULL; |
|
int force = -1; |
|
|
|
INTC_LOG("%s (cpu %d): checking args", __func__, esp_cpu_get_core_id()); |
|
/* Shared interrupts should be level-triggered. */ |
|
if ((flags & ESP_INTR_FLAG_SHARED) && (flags & ESP_INTR_FLAG_EDGE)) { |
|
return -EINVAL; |
|
} |
|
/* You can't set an handler / arg for a non-C-callable interrupt. */ |
|
if ((flags & ESP_INTR_FLAG_HIGH) && (handler)) { |
|
return -EINVAL; |
|
} |
|
/* Shared ints should have handler and non-processor-local source */ |
|
if ((flags & ESP_INTR_FLAG_SHARED) && (!handler || source < 0)) { |
|
return -EINVAL; |
|
} |
|
/* Statusreg should have a mask */ |
|
if (intrstatusreg && !intrstatusmask) { |
|
return -EINVAL; |
|
} |
|
/* |
|
* If the ISR is marked to be IRAM-resident, the handler must not be in the cached region |
|
* If we are to allow placing interrupt handlers into the 0x400c0000—0x400c2000 region, |
|
* we need to make sure the interrupt is connected to the CPU0. |
|
* CPU1 does not have access to the RTC fast memory through this region. |
|
*/ |
|
if ((flags & ESP_INTR_FLAG_IRAM) && handler && |
|
!esp_ptr_in_iram(handler) && !esp_ptr_in_rtc_iram_fast(handler)) { |
|
return -EINVAL; |
|
} |
|
|
|
/* |
|
* Default to prio 1 for shared interrupts. |
|
* Default to prio 1, 2 or 3 for non-shared interrupts. |
|
*/ |
|
if ((flags & ESP_INTR_FLAG_LEVELMASK) == 0) { |
|
if (flags & ESP_INTR_FLAG_SHARED) { |
|
flags |= ESP_INTR_FLAG_LEVEL1; |
|
} else { |
|
flags |= ESP_INTR_FLAG_LOWMED; |
|
} |
|
} |
|
INTC_LOG("%s (cpu %d): Args okay." |
|
"Resulting flags 0x%X", __func__, esp_cpu_get_core_id(), flags); |
|
|
|
/* |
|
* Check 'special' interrupt sources. These are tied to one specific |
|
* interrupt, so we have to force get_available_int to only look at that. |
|
*/ |
|
switch (source) { |
|
case ETS_INTERNAL_TIMER0_INTR_SOURCE: |
|
force = ETS_INTERNAL_TIMER0_INTR_NO; |
|
break; |
|
case ETS_INTERNAL_TIMER1_INTR_SOURCE: |
|
force = ETS_INTERNAL_TIMER1_INTR_NO; |
|
break; |
|
case ETS_INTERNAL_TIMER2_INTR_SOURCE: |
|
force = ETS_INTERNAL_TIMER2_INTR_NO; |
|
break; |
|
case ETS_INTERNAL_SW0_INTR_SOURCE: |
|
force = ETS_INTERNAL_SW0_INTR_NO; |
|
break; |
|
case ETS_INTERNAL_SW1_INTR_SOURCE: |
|
force = ETS_INTERNAL_SW1_INTR_NO; |
|
break; |
|
case ETS_INTERNAL_PROFILING_INTR_SOURCE: |
|
force = ETS_INTERNAL_PROFILING_INTR_NO; |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
/* Allocate a return handle. If we end up not needing it, we'll free it later on. */ |
|
ret = k_malloc(sizeof(struct intr_handle_data_t)); |
|
if (ret == NULL) { |
|
return -ENOMEM; |
|
} |
|
|
|
esp_intr_lock(); |
|
int cpu = esp_cpu_get_core_id(); |
|
/* See if we can find an interrupt that matches the flags. */ |
|
int intr = get_available_int(flags, cpu, force, source); |
|
|
|
if (intr == -1) { |
|
/* None found. Bail out. */ |
|
esp_intr_unlock(); |
|
k_free(ret); |
|
return -ENODEV; |
|
} |
|
/* Get an int vector desc for int. */ |
|
struct vector_desc_t *vd = get_desc_for_int(intr, cpu); |
|
|
|
if (vd == NULL) { |
|
esp_intr_unlock(); |
|
k_free(ret); |
|
return -ENOMEM; |
|
} |
|
|
|
/* Allocate that int! */ |
|
if (flags & ESP_INTR_FLAG_SHARED) { |
|
/* Populate vector entry and add to linked list. */ |
|
struct shared_vector_desc_t *sv = k_malloc(sizeof(struct shared_vector_desc_t)); |
|
|
|
if (sv == NULL) { |
|
esp_intr_unlock(); |
|
k_free(ret); |
|
return -ENOMEM; |
|
} |
|
memset(sv, 0, sizeof(struct shared_vector_desc_t)); |
|
sv->statusreg = (uint32_t *)intrstatusreg; |
|
sv->statusmask = intrstatusmask; |
|
sv->isr = handler; |
|
sv->arg = arg; |
|
sv->next = vd->shared_vec_info; |
|
sv->source = source; |
|
sv->disabled = 0; |
|
vd->shared_vec_info = sv; |
|
vd->flags |= VECDESC_FL_SHARED; |
|
|
|
/* Disable interrupt to avoid assert at IRQ install */ |
|
irq_disable(intr); |
|
|
|
/* (Re-)set shared isr handler to new value. */ |
|
irq_connect_dynamic(intr, 0, (intc_dyn_handler_t)shared_intr_isr, vd, 0); |
|
} else { |
|
/* Mark as unusable for other interrupt sources. This is ours now! */ |
|
vd->flags = VECDESC_FL_NONSHARED; |
|
if (handler) { |
|
irq_disable(intr); |
|
irq_connect_dynamic(intr, 0, (intc_dyn_handler_t)handler, arg, 0); |
|
} |
|
if (flags & ESP_INTR_FLAG_EDGE) { |
|
esp_cpu_intr_edge_ack(intr); |
|
} |
|
vd->source = source; |
|
} |
|
if (flags & ESP_INTR_FLAG_IRAM) { |
|
vd->flags |= VECDESC_FL_INIRAM; |
|
non_iram_int_mask[cpu] &= ~(1 << intr); |
|
} else { |
|
vd->flags &= ~VECDESC_FL_INIRAM; |
|
non_iram_int_mask[cpu] |= (1 << intr); |
|
} |
|
if (source >= 0) { |
|
esp_rom_route_intr_matrix(cpu, source, intr); |
|
} |
|
|
|
/* Fill return handle data. */ |
|
ret->vector_desc = vd; |
|
ret->shared_vector_desc = vd->shared_vec_info; |
|
|
|
/* Enable int at CPU-level; */ |
|
irq_enable(intr); |
|
|
|
/* |
|
* If interrupt has to be started disabled, do that now; ints won't be enabled for |
|
* real until the end of the critical section. |
|
*/ |
|
if (flags & ESP_INTR_FLAG_INTRDISABLED) { |
|
esp_intr_disable(ret); |
|
} |
|
|
|
#if SOC_CPU_HAS_FLEXIBLE_INTC |
|
/* Extract the level from the interrupt passed flags */ |
|
int level = esp_intr_flags_to_level(flags); |
|
esp_cpu_intr_set_priority(intr, level); |
|
|
|
if (flags & ESP_INTR_FLAG_EDGE) { |
|
esp_cpu_intr_set_type(intr, ESP_CPU_INTR_TYPE_EDGE); |
|
} else { |
|
esp_cpu_intr_set_type(intr, ESP_CPU_INTR_TYPE_LEVEL); |
|
} |
|
#endif |
|
|
|
#if SOC_INT_PLIC_SUPPORTED |
|
/* Make sure the interrupt is not delegated to user mode (IDF uses machine mode only) */ |
|
RV_CLEAR_CSR(mideleg, BIT(intr)); |
|
#endif |
|
|
|
esp_intr_unlock(); |
|
|
|
/* Fill return handle if needed, otherwise free handle. */ |
|
if (ret_handle != NULL) { |
|
*ret_handle = ret; |
|
} else { |
|
k_free(ret); |
|
} |
|
|
|
LOG_DBG("Connected src %d to int %d (cpu %d)", source, intr, cpu); |
|
|
|
return 0; |
|
} |
|
|
|
int esp_intr_alloc(int source, |
|
int flags, |
|
intr_handler_t handler, |
|
void *arg, |
|
intr_handle_t *ret_handle) |
|
{ |
|
/* |
|
* As an optimization, we can create a table with the possible interrupt status |
|
* registers and masks for every single source there is. We can then add code here to |
|
* look up an applicable value and pass that to the esp_intr_alloc_intrstatus function. |
|
*/ |
|
return esp_intr_alloc_intrstatus(source, flags, 0, 0, handler, arg, ret_handle); |
|
} |
|
|
|
int IRAM_ATTR esp_intr_set_in_iram(intr_handle_t handle, bool is_in_iram) |
|
{ |
|
if (!handle) { |
|
return -EINVAL; |
|
} |
|
struct vector_desc_t *vd = handle->vector_desc; |
|
|
|
if (vd->flags & VECDESC_FL_SHARED) { |
|
return -EINVAL; |
|
} |
|
esp_intr_lock(); |
|
uint32_t mask = (1 << vd->intno); |
|
|
|
if (is_in_iram) { |
|
vd->flags |= VECDESC_FL_INIRAM; |
|
non_iram_int_mask[vd->cpu] &= ~mask; |
|
} else { |
|
vd->flags &= ~VECDESC_FL_INIRAM; |
|
non_iram_int_mask[vd->cpu] |= mask; |
|
} |
|
esp_intr_unlock(); |
|
return 0; |
|
} |
|
|
|
int esp_intr_free(intr_handle_t handle) |
|
{ |
|
bool free_shared_vector = false; |
|
|
|
if (!handle) { |
|
return -EINVAL; |
|
} |
|
|
|
esp_intr_lock(); |
|
esp_intr_disable(handle); |
|
if (handle->vector_desc->flags & VECDESC_FL_SHARED) { |
|
/* Find and kill the shared int */ |
|
struct shared_vector_desc_t *svd = handle->vector_desc->shared_vec_info; |
|
struct shared_vector_desc_t *prevsvd = NULL; |
|
|
|
assert(svd); /* should be something in there for a shared int */ |
|
while (svd != NULL) { |
|
if (svd == handle->shared_vector_desc) { |
|
/* Found it. Now kill it. */ |
|
if (prevsvd) { |
|
prevsvd->next = svd->next; |
|
} else { |
|
handle->vector_desc->shared_vec_info = svd->next; |
|
} |
|
k_free(svd); |
|
break; |
|
} |
|
prevsvd = svd; |
|
svd = svd->next; |
|
} |
|
/* If nothing left, disable interrupt. */ |
|
if (handle->vector_desc->shared_vec_info == NULL) { |
|
free_shared_vector = true; |
|
} |
|
INTC_LOG("%s: Deleting shared int: %s. " |
|
"Shared int is %s", __func__, svd ? "not found or last one" : "deleted", |
|
free_shared_vector ? "empty now." : "still in use"); |
|
} |
|
|
|
if ((handle->vector_desc->flags & VECDESC_FL_NONSHARED) || free_shared_vector) { |
|
INTC_LOG("%s: Disabling int, killing handler", __func__); |
|
|
|
/* Disable interrupt to avoid assert at IRQ install */ |
|
irq_disable(handle->vector_desc->intno); |
|
|
|
/* Reset IRQ handler */ |
|
irq_connect_dynamic(handle->vector_desc->intno, 0, |
|
(intc_dyn_handler_t)z_irq_spurious, |
|
(void *)((int)handle->vector_desc->intno), 0); |
|
/* |
|
* Theoretically, we could free the vector_desc... not sure if that's worth the |
|
* few bytes of memory we save.(We can also not use the same exit path for empty |
|
* shared ints anymore if we delete the desc.) For now, just mark it as free. |
|
*/ |
|
handle->vector_desc->flags &= !(VECDESC_FL_NONSHARED | VECDESC_FL_RESERVED); |
|
/* Also kill non_iram mask bit. */ |
|
non_iram_int_mask[handle->vector_desc->cpu] &= ~(1 << (handle->vector_desc->intno)); |
|
} |
|
esp_intr_unlock(); |
|
k_free(handle); |
|
return 0; |
|
} |
|
|
|
int esp_intr_get_intno(intr_handle_t handle) |
|
{ |
|
return handle->vector_desc->intno; |
|
} |
|
|
|
int esp_intr_get_cpu(intr_handle_t handle) |
|
{ |
|
return handle->vector_desc->cpu; |
|
} |
|
|
|
/** |
|
* Interrupt disabling strategy: |
|
* If the source is >=0 (meaning a muxed interrupt), we disable it by muxing the interrupt to a |
|
* non-connected interrupt. If the source is <0 (meaning an internal, per-cpu interrupt). |
|
* This allows us to, for the muxed CPUs, disable an int from |
|
* the other core. It also allows disabling shared interrupts. |
|
*/ |
|
|
|
/* |
|
* Muxing an interrupt source to interrupt 6, 7, 11, 15, 16 or 29 |
|
* cause the interrupt to effectively be disabled. |
|
*/ |
|
#define INT_MUX_DISABLED_INTNO 6 |
|
|
|
int IRAM_ATTR esp_intr_enable(intr_handle_t handle) |
|
{ |
|
if (!handle) { |
|
return -EINVAL; |
|
} |
|
esp_intr_lock(); |
|
int source; |
|
|
|
if (handle->shared_vector_desc) { |
|
handle->shared_vector_desc->disabled = 0; |
|
source = handle->shared_vector_desc->source; |
|
} else { |
|
source = handle->vector_desc->source; |
|
} |
|
if (source >= 0) { |
|
/* Disabled using int matrix; re-connect to enable */ |
|
esp_rom_route_intr_matrix(handle->vector_desc->cpu, source, |
|
handle->vector_desc->intno); |
|
} else { |
|
/* Re-enable using cpu int ena reg */ |
|
if (handle->vector_desc->cpu != esp_cpu_get_core_id()) { |
|
esp_intr_unlock(); |
|
return -EINVAL; /* Can only enable these ints on this cpu */ |
|
} |
|
irq_enable(handle->vector_desc->intno); |
|
} |
|
esp_intr_unlock(); |
|
return 0; |
|
} |
|
|
|
int IRAM_ATTR esp_intr_disable(intr_handle_t handle) |
|
{ |
|
if (!handle) { |
|
return -EINVAL; |
|
} |
|
esp_intr_lock(); |
|
int source; |
|
bool disabled = 1; |
|
|
|
if (handle->shared_vector_desc) { |
|
handle->shared_vector_desc->disabled = 1; |
|
source = handle->shared_vector_desc->source; |
|
|
|
struct shared_vector_desc_t *svd = handle->vector_desc->shared_vec_info; |
|
|
|
assert(svd != NULL); |
|
while (svd) { |
|
if (svd->source == source && svd->disabled == 0) { |
|
disabled = 0; |
|
break; |
|
} |
|
svd = svd->next; |
|
} |
|
} else { |
|
source = handle->vector_desc->source; |
|
} |
|
|
|
if (source >= 0) { |
|
if (disabled) { |
|
/* Disable using int matrix */ |
|
esp_rom_route_intr_matrix(handle->vector_desc->cpu, source, |
|
INT_MUX_DISABLED_INTNO); |
|
} |
|
} else { |
|
/* Disable using per-cpu regs */ |
|
if (handle->vector_desc->cpu != esp_cpu_get_core_id()) { |
|
esp_intr_unlock(); |
|
return -EINVAL; /* Can only enable these ints on this cpu */ |
|
} |
|
irq_disable(handle->vector_desc->intno); |
|
} |
|
esp_intr_unlock(); |
|
return 0; |
|
} |
|
|
|
void IRAM_ATTR esp_intr_noniram_disable(void) |
|
{ |
|
esp_intr_lock(); |
|
int oldint; |
|
int cpu = esp_cpu_get_core_id(); |
|
int non_iram_ints = ~non_iram_int_mask[cpu]; |
|
|
|
if (non_iram_int_disabled_flag[cpu]) { |
|
abort(); |
|
} |
|
non_iram_int_disabled_flag[cpu] = true; |
|
oldint = esp_cpu_intr_get_enabled_mask(); |
|
esp_cpu_intr_disable(non_iram_ints); |
|
rtc_isr_noniram_disable(cpu); |
|
/* Save which ints we did disable */ |
|
non_iram_int_disabled[cpu] = oldint & non_iram_ints; |
|
esp_intr_unlock(); |
|
} |
|
|
|
void IRAM_ATTR esp_intr_noniram_enable(void) |
|
{ |
|
esp_intr_lock(); |
|
int cpu = esp_cpu_get_core_id(); |
|
int non_iram_ints = non_iram_int_disabled[cpu]; |
|
|
|
if (!non_iram_int_disabled_flag[cpu]) { |
|
abort(); |
|
} |
|
non_iram_int_disabled_flag[cpu] = false; |
|
esp_cpu_intr_enable(non_iram_ints); |
|
rtc_isr_noniram_enable(cpu); |
|
esp_intr_unlock(); |
|
} |
|
|
|
#if defined(CONFIG_RISCV) |
|
/* |
|
* Functions below are implemented to keep consistency with current |
|
* Xtensa chips API behavior. When accessing Zephyr's API |
|
* directly, the CPU IRQs can be enabled or disabled directly. This |
|
* is mostly used to control lines that are not muxed, thus bypass the |
|
* interrupt matrix. For RISCV, these functions are not expected to |
|
* be used via user API, as peripherals are all routed through INTMUX |
|
* and shared interrupts require managing sources state. |
|
*/ |
|
void arch_irq_enable(unsigned int irq) |
|
{ |
|
esp_cpu_intr_enable(1 << irq); |
|
} |
|
|
|
void arch_irq_disable(unsigned int irq) |
|
{ |
|
esp_cpu_intr_disable(1 << irq); |
|
} |
|
|
|
int arch_irq_is_enabled(unsigned int irq) |
|
{ |
|
return !!(esp_cpu_intr_get_enabled_mask() & (1 << irq)); |
|
} |
|
#endif
|
|
|