zephyr/drivers/interrupt_controller/intc_exti_stm32.c
Martin Gritzan ff2b827143 drivers: stm32-exti: do not lock hwsem on irq disable
Remove the HWSEM locking around stm32_exti_disable().

The STM32 EXTI driver uses the core-local interrupt mask regsiters on
STM32H7x7 asym. dualcore MCUs. There is no need to lock the HWSEM
guarding the EXTI when accessing these registers.

Some sensor drivers toggle their interrupt mask every time the sensor
triggers the IRQ line. Locking the HWSEM fails e.g. in situations where
one coprocessor serivces the sensor and the other coprocessor sets up
its interrupts initially during bootup. This prevents the sensor driver
from locking the HWSEM and causes a kernel panic on the corresponding
CPU.

Note: The opposing stm32_exti_enable() was already correctly without
locking.

Signed-off-by: Martin Gritzan <martin.gritzan@gmail.com>
2023-10-20 15:15:15 +02:00

273 lines
6.4 KiB
C

/*
* Copyright (c) 2016 Open-RnD Sp. z o.o.
* Copyright (c) 2017 RnDity Sp. z o.o.
* Copyright (c) 2019-23 Linaro Limited
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief Driver for External interrupt/event controller in STM32 MCUs
*/
#define EXTI_NODE DT_INST(0, st_stm32_exti)
#include <zephyr/device.h>
#include <soc.h>
#include <stm32_ll_exti.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/util.h>
#include <zephyr/drivers/interrupt_controller/exti_stm32.h>
#include <zephyr/irq.h>
#include "stm32_hsem.h"
/** @brief EXTI line ranges hold by a single ISR */
struct stm32_exti_range {
/** Start of the range */
uint8_t start;
/** Range length */
uint8_t len;
};
#define NUM_EXTI_LINES DT_PROP(DT_NODELABEL(exti), num_lines)
static IRQn_Type exti_irq_table[NUM_EXTI_LINES] = {[0 ... NUM_EXTI_LINES - 1] = 0xFF};
/* wrapper for user callback */
struct __exti_cb {
stm32_exti_callback_t cb;
void *data;
};
/* driver data */
struct stm32_exti_data {
/* per-line callbacks */
struct __exti_cb cb[NUM_EXTI_LINES];
};
void stm32_exti_enable(int line)
{
int irqnum = 0;
if (line >= NUM_EXTI_LINES) {
__ASSERT_NO_MSG(line);
}
/* Get matching exti irq provided line thanks to irq_table */
irqnum = exti_irq_table[line];
if (irqnum == 0xFF) {
__ASSERT_NO_MSG(line);
}
/* Enable requested line interrupt */
#if defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
LL_C2_EXTI_EnableIT_0_31(BIT((uint32_t)line));
#else
LL_EXTI_EnableIT_0_31(BIT((uint32_t)line));
#endif
/* Enable exti irq interrupt */
irq_enable(irqnum);
}
void stm32_exti_disable(int line)
{
if (line < 32) {
#if defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
LL_C2_EXTI_DisableIT_0_31(BIT((uint32_t)line));
#else
LL_EXTI_DisableIT_0_31(BIT((uint32_t)line));
#endif
} else {
__ASSERT_NO_MSG(line);
}
}
/**
* @brief check if interrupt is pending
*
* @param line line number
*/
static inline int stm32_exti_is_pending(int line)
{
if (line < 32) {
#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti)
return (LL_EXTI_IsActiveRisingFlag_0_31(BIT((uint32_t)line)) ||
LL_EXTI_IsActiveFallingFlag_0_31(BIT((uint32_t)line)));
#elif defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
return LL_C2_EXTI_IsActiveFlag_0_31(BIT((uint32_t)line));
#else
return LL_EXTI_IsActiveFlag_0_31(BIT((uint32_t)line));
#endif
} else {
__ASSERT_NO_MSG(line);
return 0;
}
}
/**
* @brief clear pending interrupt bit
*
* @param line line number
*/
static inline void stm32_exti_clear_pending(int line)
{
if (line < 32) {
#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti)
LL_EXTI_ClearRisingFlag_0_31(BIT((uint32_t)line));
LL_EXTI_ClearFallingFlag_0_31(BIT((uint32_t)line));
#elif defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
LL_C2_EXTI_ClearFlag_0_31(BIT((uint32_t)line));
#else
LL_EXTI_ClearFlag_0_31(BIT((uint32_t)line));
#endif
} else {
__ASSERT_NO_MSG(line);
}
}
void stm32_exti_trigger(int line, int trigger)
{
if (line >= 32) {
__ASSERT_NO_MSG(line);
}
z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY);
switch (trigger) {
case STM32_EXTI_TRIG_NONE:
LL_EXTI_DisableRisingTrig_0_31(BIT((uint32_t)line));
LL_EXTI_DisableFallingTrig_0_31(BIT((uint32_t)line));
break;
case STM32_EXTI_TRIG_RISING:
LL_EXTI_EnableRisingTrig_0_31(BIT((uint32_t)line));
LL_EXTI_DisableFallingTrig_0_31(BIT((uint32_t)line));
break;
case STM32_EXTI_TRIG_FALLING:
LL_EXTI_EnableFallingTrig_0_31(BIT((uint32_t)line));
LL_EXTI_DisableRisingTrig_0_31(BIT((uint32_t)line));
break;
case STM32_EXTI_TRIG_BOTH:
LL_EXTI_EnableRisingTrig_0_31(BIT((uint32_t)line));
LL_EXTI_EnableFallingTrig_0_31(BIT((uint32_t)line));
break;
default:
__ASSERT_NO_MSG(trigger);
break;
}
z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID);
}
/**
* @brief EXTI ISR handler
*
* Check EXTI lines in exti_range for pending interrupts
*
* @param exti_range Pointer to a exti_range structure
*/
static void stm32_exti_isr(const void *exti_range)
{
const struct device *dev = DEVICE_DT_GET(EXTI_NODE);
struct stm32_exti_data *data = dev->data;
const struct stm32_exti_range *range = exti_range;
int line;
/* see which bits are set */
for (uint8_t i = 0; i <= range->len; i++) {
line = range->start + i;
/* check if interrupt is pending */
if (stm32_exti_is_pending(line) != 0) {
/* clear pending interrupt */
stm32_exti_clear_pending(line);
/* run callback only if one is registered */
if (!data->cb[line].cb) {
continue;
}
data->cb[line].cb(line, data->cb[line].data);
}
}
}
static void stm32_fill_irq_table(int8_t start, int8_t len, int32_t irqn)
{
for (int i = 0; i < len; i++) {
exti_irq_table[start + i] = irqn;
}
}
/* This macro:
* - populates line_range_x from line_range dt property
* - fill exti_irq_table through stm32_fill_irq_table()
* - calls IRQ_CONNECT for each irq & matching line_range
*/
#define STM32_EXTI_INIT(node_id, interrupts, idx) \
static const struct stm32_exti_range line_range_##idx = { \
DT_PROP_BY_IDX(node_id, line_ranges, UTIL_X2(idx)), \
DT_PROP_BY_IDX(node_id, line_ranges, UTIL_INC(UTIL_X2(idx))) \
}; \
stm32_fill_irq_table(line_range_##idx.start, \
line_range_##idx.len, \
DT_IRQ_BY_IDX(node_id, idx, irq)); \
IRQ_CONNECT(DT_IRQ_BY_IDX(node_id, idx, irq), \
DT_IRQ_BY_IDX(node_id, idx, priority), \
stm32_exti_isr, &line_range_##idx, \
0);
/**
* @brief initialize EXTI device driver
*/
static int stm32_exti_init(const struct device *dev)
{
ARG_UNUSED(dev);
DT_FOREACH_PROP_ELEM(DT_NODELABEL(exti),
interrupt_names,
STM32_EXTI_INIT);
return 0;
}
static struct stm32_exti_data exti_data;
DEVICE_DT_DEFINE(EXTI_NODE, &stm32_exti_init,
NULL,
&exti_data, NULL,
PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY,
NULL);
/**
* @brief set & unset for the interrupt callbacks
*/
int stm32_exti_set_callback(int line, stm32_exti_callback_t cb, void *arg)
{
const struct device *const dev = DEVICE_DT_GET(EXTI_NODE);
struct stm32_exti_data *data = dev->data;
if ((data->cb[line].cb == cb) && (data->cb[line].data == arg)) {
return 0;
}
/* if callback already exists/maybe-running return busy */
if (data->cb[line].cb != NULL) {
return -EBUSY;
}
data->cb[line].cb = cb;
data->cb[line].data = arg;
return 0;
}
void stm32_exti_unset_callback(int line)
{
const struct device *const dev = DEVICE_DT_GET(EXTI_NODE);
struct stm32_exti_data *data = dev->data;
data->cb[line].cb = NULL;
data->cb[line].data = NULL;
}