Skip to content

drivers: stepper: refactor ISR safe event mechanism #90424

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion drivers/stepper/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@

zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/stepper.h)

# zephyr-keep-sorted-start
add_subdirectory_ifdef(CONFIG_STEPPER_EVENT_HANDLER event_handler)
add_subdirectory_ifdef(CONFIG_STEP_DIR_STEPPER step_dir)
# zephyr-keep-sorted-stop

# zephyr-keep-sorted-start
add_subdirectory_ifdef(CONFIG_STEPPER_ADI_TMC adi_tmc)
add_subdirectory_ifdef(CONFIG_STEPPER_ALLEGRO allegro)
add_subdirectory_ifdef(CONFIG_STEPPER_TI ti)
add_subdirectory_ifdef(CONFIG_STEP_DIR_STEPPER step_dir)
# zephyr-keep-sorted-stop

zephyr_library()
Expand Down
1 change: 1 addition & 0 deletions drivers/stepper/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ config STEPPER_SHELL
comment "Stepper Driver Common"

rsource "step_dir/Kconfig"
rsource "event_handler/Kconfig"

comment "Stepper Drivers"

Expand Down
18 changes: 0 additions & 18 deletions drivers/stepper/Kconfig.stepper_event_template

This file was deleted.

2 changes: 1 addition & 1 deletion drivers/stepper/adi_tmc/tmc22xx.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ static DEVICE_API(stepper, tmc22xx_stepper_api) = {
.common = STEP_DIR_STEPPER_DT_INST_COMMON_DATA_INIT(inst), \
.resolution = DT_INST_PROP(inst, micro_step_res), \
}; \
DEVICE_DT_INST_DEFINE(inst, tmc22xx_stepper_init, NULL, &tmc22xx_data_##inst, \
STEPPER_DEVICE_DT_INST_DEFINE(inst, tmc22xx_stepper_init, NULL, &tmc22xx_data_##inst, \
&tmc22xx_config_##inst, POST_KERNEL, CONFIG_STEPPER_INIT_PRIORITY, \
&tmc22xx_stepper_api);

Expand Down
45 changes: 20 additions & 25 deletions drivers/stepper/adi_tmc/tmc50xx.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "adi_tmc_spi.h"
#include "adi_tmc5xxx_common.h"

#include <zephyr/drivers/stepper/stepper_event_handler.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(tmc50xx, CONFIG_STEPPER_LOG_LEVEL);

Expand Down Expand Up @@ -175,18 +176,6 @@ static void stallguard_work_handler(struct k_work *work)
}
}


static void execute_callback(const struct device *dev, const enum stepper_event event)
{
struct tmc50xx_stepper_data *data = dev->data;

if (!data->callback) {
LOG_WRN_ONCE("No callback registered");
return;
}
data->callback(dev, event, data->event_cb_user_data);
}

#ifdef CONFIG_STEPPER_ADI_TMC50XX_RAMPSTAT_POLL_STALLGUARD_LOG

static void log_stallguard(struct tmc50xx_stepper_data *stepper_data, const uint32_t drv_status)
Expand All @@ -204,16 +193,16 @@ static void log_stallguard(struct tmc50xx_stepper_data *stepper_data, const uint
const uint8_t sg_result = FIELD_GET(TMC5XXX_DRV_STATUS_SG_RESULT_MASK, drv_status);
const bool sg_status = FIELD_GET(TMC5XXX_DRV_STATUS_SG_STATUS_MASK, drv_status);

LOG_DBG("%s position: %d | sg result: %3d status: %d",
stepper_data->stepper->name, position, sg_result, sg_status);
LOG_DBG("%s position: %d | sg result: %3d status: %d", stepper_data->stepper->name,
position, sg_result, sg_status);
}

#endif

static void rampstat_work_reschedule(struct k_work_delayable *rampstat_callback_dwork)
{
k_work_reschedule(rampstat_callback_dwork,
K_MSEC(CONFIG_STEPPER_ADI_TMC50XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC));
K_MSEC(CONFIG_STEPPER_ADI_TMC50XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC));
}

static void rampstat_work_handler(struct k_work *work)
Expand Down Expand Up @@ -265,25 +254,31 @@ static void rampstat_work_handler(struct k_work *work)

case TMC5XXX_STOP_LEFT_EVENT:
LOG_DBG("RAMPSTAT %s:Left end-stop detected", stepper_data->stepper->name);
execute_callback(stepper_data->stepper,
STEPPER_EVENT_LEFT_END_STOP_DETECTED);
stepper_post_event(stepper_data->stepper, stepper_data->callback,
STEPPER_EVENT_LEFT_END_STOP_DETECTED,
stepper_data->event_cb_user_data);
break;

case TMC5XXX_STOP_RIGHT_EVENT:
LOG_DBG("RAMPSTAT %s:Right end-stop detected", stepper_data->stepper->name);
execute_callback(stepper_data->stepper,
STEPPER_EVENT_RIGHT_END_STOP_DETECTED);
stepper_post_event(stepper_data->stepper, stepper_data->callback,
STEPPER_EVENT_RIGHT_END_STOP_DETECTED,
stepper_data->event_cb_user_data);
break;

case TMC5XXX_POS_REACHED_EVENT:
LOG_DBG("RAMPSTAT %s:Position reached", stepper_data->stepper->name);
execute_callback(stepper_data->stepper, STEPPER_EVENT_STEPS_COMPLETED);
stepper_post_event(stepper_data->stepper, stepper_data->callback,
STEPPER_EVENT_STEPS_COMPLETED,
stepper_data->event_cb_user_data);
break;

case TMC5XXX_STOP_SG_EVENT:
LOG_DBG("RAMPSTAT %s:Stall detected", stepper_data->stepper->name);
stallguard_enable(stepper_data->stepper, false);
execute_callback(stepper_data->stepper, STEPPER_EVENT_STALL_DETECTED);
stepper_post_event(stepper_data->stepper, stepper_data->callback,
STEPPER_EVENT_STALL_DETECTED,
stepper_data->event_cb_user_data);
break;
default:
LOG_ERR("Illegal ramp stat bit field");
Expand Down Expand Up @@ -733,10 +728,10 @@ static DEVICE_API(stepper, tmc50xx_stepper_api) = {
static struct tmc50xx_stepper_data tmc50xx_stepper_data_##child = { \
.stepper = DEVICE_DT_GET(child),};

#define TMC50XX_STEPPER_DEFINE(child) \
DEVICE_DT_DEFINE(child, tmc50xx_stepper_init, NULL, &tmc50xx_stepper_data_##child, \
&tmc50xx_stepper_config_##child, POST_KERNEL, \
CONFIG_STEPPER_INIT_PRIORITY, &tmc50xx_stepper_api);
#define TMC50XX_STEPPER_DEFINE(child) \
STEPPER_DEVICE_DT_DEFINE(child, tmc50xx_stepper_init, NULL, &tmc50xx_stepper_data_##child, \
&tmc50xx_stepper_config_##child, POST_KERNEL, \
CONFIG_STEPPER_INIT_PRIORITY, &tmc50xx_stepper_api);

#define TMC50XX_DEFINE(inst) \
BUILD_ASSERT(DT_INST_CHILD_NUM(inst) <= 2, "tmc50xx can drive two steppers at max"); \
Expand Down
5 changes: 3 additions & 2 deletions drivers/stepper/allegro/a4979.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,8 @@ static DEVICE_API(stepper, a4979_stepper_api) = {
.micro_step_res = DT_INST_PROP(inst, micro_step_res), \
}; \
\
DEVICE_DT_INST_DEFINE(inst, a4979_init, NULL, &a4979_data_##inst, &a4979_config_##inst, \
POST_KERNEL, CONFIG_STEPPER_INIT_PRIORITY, &a4979_stepper_api);
STEPPER_DEVICE_DT_INST_DEFINE(inst, a4979_init, NULL, &a4979_data_##inst, \
&a4979_config_##inst, POST_KERNEL, \
CONFIG_STEPPER_INIT_PRIORITY, &a4979_stepper_api);

DT_INST_FOREACH_STATUS_OKAY(A4979_DEVICE)
7 changes: 7 additions & 0 deletions drivers/stepper/event_handler/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Jilay Sandeep Pandya
# SPDX-License-Identifier: Apache-2.0

zephyr_library()

zephyr_library_sources(stepper_event_handler.c)
zephyr_linker_sources(DATA_SECTIONS iterables.ld)
18 changes: 18 additions & 0 deletions drivers/stepper/event_handler/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Jilay Sandeep Pandya
# SPDX-License-Identifier: Apache-2.0

config STEPPER_EVENT_HANDLER
bool "Stepper event handler"
default y
help
Enable the dispatch of stepper generated events via
a message queue to guarantee that the event handler
code is not run inside of an ISR

config STEPPER_EVENT_HANDLER_QUEUE_LEN
int "Maximum number of pending stepper events"
default 4
depends on STEPPER_EVENT_HANDLER
help
The maximum number of stepper events that can be pending before new events
are dropped.
3 changes: 3 additions & 0 deletions drivers/stepper/event_handler/iterables.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# iterables.ld
#include <zephyr/linker/iterable_sections.h>
ITERABLE_SECTION_RAM(stepper_event_handler, Z_LINK_ITERABLE_SUBALIGN)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to do this as an iterable section?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea is that the event handling data structure is same for all the drivers, hence it could be registered for all the drivers through STEPPER_DEVICE_DT_INST_DEFINE, sensors also does it, for some other purpose though :)

And then via SYS_INIT one could initialize the workq and msgq in terms of event handling for all the drivers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any drawback of having the struct stepper_event_handler, as a data member of the stepper driver? Then the members could be initialised during the driver init and at the same time use the common function from stepper_event_handler.c.

Copy link
Member Author

@jilaypandya jilaypandya May 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah but then each and every driver would have to do this explicitly. And in this way it happens in the background.

Also with CONFIG_STEPPER_EVENT_HANDLER=n, you could switch off this functionality and then the callback would just simply be called without going via the event_handler

82 changes: 82 additions & 0 deletions drivers/stepper/event_handler/stepper_event_handler.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 Jilay Sandeep Pandya
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/init.h>
#include <zephyr/sys/iterable_sections.h>
#include <zephyr/drivers/stepper.h>
#include <zephyr/drivers/stepper/stepper_event_handler.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(stepper_event_handler, CONFIG_STEPPER_LOG_LEVEL);

int stepper_post_event(const struct device *dev, stepper_event_callback_t cb,
enum stepper_event event, void *user_data)
{
if (cb == NULL) {
LOG_ERR("Callback is NULL for device %s", dev->name);
return -EINVAL;
}

if (!k_is_in_isr()) {
cb(dev, event, user_data);
return 0;
}

STRUCT_SECTION_FOREACH(stepper_event_handler, entry) {
if (entry->dev == dev) {
struct stepper_event_data data = {
.event_cb = cb,
.event = event,
.user_data = user_data,
};
LOG_DBG("Posting event %d for device %s with cb %p", event, dev->name,
data.event_cb);
k_msgq_put(&entry->event_msgq, &data, K_NO_WAIT);
k_work_submit(&entry->event_callback_work);
return 0;
}
}
return -ENODEV;
}

static void stepper_work_event_handler(struct k_work *work)
{
struct stepper_event_handler *entry =
CONTAINER_OF(work, struct stepper_event_handler, event_callback_work);
struct stepper_event_data event_data;
int ret;

LOG_DBG("Starting event handler for device %s", entry->dev->name);

ret = k_msgq_get(&entry->event_msgq, &event_data, K_NO_WAIT);
if (ret != 0) {
return;
}

/* Run the callback */
if (event_data.event_cb != NULL) {
LOG_DBG("Handling event %d for device %s with cb %p", event_data.event,
entry->dev->name, event_data.event_cb);
event_data.event_cb(entry->dev, event_data.event, event_data.user_data);
}

/* If there are more pending events, resubmit this work item to handle them */
if (k_msgq_num_used_get(&entry->event_msgq) > 0) {
k_work_submit(work);
}
}

static int stepper_event_handler_init(void)
{
STRUCT_SECTION_FOREACH(stepper_event_handler, entry) {
k_msgq_init(&entry->event_msgq, entry->event_msgq_buffer,
sizeof(struct stepper_event_data),
CONFIG_STEPPER_EVENT_HANDLER_QUEUE_LEN);
k_work_init(&entry->event_callback_work, stepper_work_event_handler);
}
return 0;
}

SYS_INIT(stepper_event_handler_init, APPLICATION, CONFIG_STEPPER_INIT_PRIORITY);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels off imo, the driver should be ready for use after POST_KERNEL init level has run. I get the idea behind it, that drivers do not have to explicitly call an init function but the reduction in complexity would be worth it.

6 changes: 3 additions & 3 deletions drivers/stepper/fake_stepper_controller.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ static DEVICE_API(stepper, fake_stepper_driver_api) = {
\
static struct fake_stepper_data fake_stepper_data_##inst; \
\
DEVICE_DT_INST_DEFINE(inst, fake_stepper_init, NULL, &fake_stepper_data_##inst, NULL, \
POST_KERNEL, CONFIG_STEPPER_INIT_PRIORITY, \
&fake_stepper_driver_api);
STEPPER_DEVICE_DT_INST_DEFINE(inst, fake_stepper_init, NULL, &fake_stepper_data_##inst, \
NULL, POST_KERNEL, CONFIG_STEPPER_INIT_PRIORITY, \
&fake_stepper_driver_api);

DT_INST_FOREACH_STATUS_OKAY(FAKE_STEPPER_INIT)
13 changes: 7 additions & 6 deletions drivers/stepper/gpio_stepper_controller.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <zephyr/drivers/stepper.h>
#include <zephyr/sys/__assert.h>

#include <zephyr/drivers/stepper/stepper_event_handler.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(gpio_stepper_motor_controller, CONFIG_STEPPER_LOG_LEVEL);

Expand Down Expand Up @@ -141,10 +143,8 @@ static void position_mode_task(const struct device *dev)
if (data->step_count) {
(void)k_work_reschedule(&data->stepper_dwork, K_NSEC(data->delay_in_ns));
} else {
if (data->callback) {
data->callback(data->dev, STEPPER_EVENT_STEPS_COMPLETED,
data->event_cb_user_data);
}
stepper_post_event(data->dev, data->callback, STEPPER_EVENT_STEPS_COMPLETED,
data->event_cb_user_data);
(void)k_work_cancel_delayable(&data->stepper_dwork);
}
}
Expand Down Expand Up @@ -359,7 +359,8 @@ static int gpio_stepper_stop(const struct device *dev)
err = energize_coils(dev, true);

if (data->callback && !err) {
data->callback(data->dev, STEPPER_EVENT_STOPPED, data->event_cb_user_data);
stepper_post_event(data->dev, data->callback, STEPPER_EVENT_STOPPED,
data->event_cb_user_data);
}
}
return err;
Expand Down Expand Up @@ -409,7 +410,7 @@ static DEVICE_API(stepper, gpio_stepper_api) = {
}; \
BUILD_ASSERT(DT_INST_PROP(inst, micro_step_res) <= STEPPER_MICRO_STEP_2, \
"gpio_stepper_controller driver supports up to 2 micro steps"); \
DEVICE_DT_INST_DEFINE(inst, gpio_stepper_init, NULL, &gpio_stepper_data_##inst, \
STEPPER_DEVICE_DT_INST_DEFINE(inst, gpio_stepper_init, NULL, &gpio_stepper_data_##inst, \
&gpio_stepper_config_##inst, POST_KERNEL, \
CONFIG_STEPPER_INIT_PRIORITY, &gpio_stepper_api);

Expand Down
4 changes: 0 additions & 4 deletions drivers/stepper/step_dir/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,4 @@ config STEP_DIR_STEPPER_COUNTER_TIMING
help
Enable usage of a counter device for accurate stepping.

module = STEP_DIR
module-str = step_dir
rsource "../Kconfig.stepper_event_template"

endif # STEP_DIR_STEPPER
Loading