Hierarchy of tasks, parent wakes up

I have a task A create a task B. Task B starts a measurement, an ISR wakes it up.

It seems like the ISR calling xTaskNotifyfromISR and portYIELD_FROM_ISR with the handle of task B, actually wakes up task A.

Is this expected? If yes, what terms should I have search for, to find info about this? If no, any idea what might be going on? Task B is not waiting for anything from A.

No, this is not expected. Can you share your code?

Sure, here goes. I simplified, and had to anonymize some.

/* A task */
static void a_task(void *pvParameters)
{
	BaseType_t was_notified;

	find_start(xTaskGetCurrentTaskHandle(), 1000);

	uint32_t value = UINT16_MAX;

	was_notified = xTaskNotifyWait(
		0x00,
		0, //  Edited from UINT32_MAX. It makes no difference.
		&value,
		pdMS_TO_TICKS(timeout_ms));

	if (!was_notified) {
		PDEBUG("Time out!");
	} else {
		PDEBUG("found: %" PRIu32, value);
	}

	vTaskDelete(NULL);
}

/* --- This is in another module --- */
static TaskHandle_t calling_task_handle;

void find_start(TaskHandle_t task_handle, uint16_t search_resolution)
{
	/* save ref to task A */
	calling_task_handle = task_handle;

	BaseType_t ret;
	ret = xTaskCreate(sensor_find_task,
		"sensor threshold find task", 512,
		&search_resolution, 1,
		&sensor_find_task_handle);
	if (ret != pdPASS) {
		PERROR("%s", __func__);
	}
}

static bool found;
static uint32_t sensor_event_mask;

/* B task */
static void sensor_find_task(void *pvParameters)
{
	found = false;

	uint16_t search_resolution = *(uint16_t *) pvParameters;

	// Do not trigger an interrupt at the first change of the reference.
	// Initialize interrupt, set handler, enable, other GPIO stuff, ...
	do {
		// Change levels to tt

		vTaskDelay(pdMS_TO_TICKS(1));
		if (!found) {
			// Update the search
			// This is actually clamped, while loop exits if maxed.
			value += search_resolution;
		}
	} while (!found);

	// disable ISR

	if (calling_task_handle != NULL) {
		/* Notify task A */
		xTaskNotify(
			calling_task_handle,
			value,
			eSetValueWithOverwrite);
	}

	calling_task_handle = NULL;
	sensor_find_task_handle = NULL;
	vTaskDelete(NULL);
}


static void sensor_pin_isr(void)
{
	uint32_t irq_events = gpio_get_irq_event_mask(sensor_pin);
	gpio_acknowledge_irq(sensor_pin, irq_events);

	if (!(irq_events & sensor_event_mask)) {
		PDEBUG("unexpected irq!");
		return;
	}

	found = true;

	if (sensor_find_task_handle != NULL) {
		/* Notify task B */
		BaseType_t xHigherPriorityTaskWoken;
		xTaskNotifyFromISR(
			sensor_find_task_handle,
			1234,
			eSetValueWithOverwrite,
			&xHigherPriorityTaskWoken);
		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
	}
}

Possibly, the ISR finishes and tries to wake B up when it is no longer in its vTaskDelay. Still, in that case, B should go on and run its stuff after the loop, and notify A with the new value.

Instead, A wakes up with its notified value at 0, before B has the time to process value and notify A with it.

I do not see a call to xTaskNotifyWait here. Your code does the following -

  1. a_task waits for a notification.
  2. sensor_find_task notifies a task.
  3. ISR notifies sensor_find_task.

If sensor_find_task does not wait for a notification, what is the point of sending a notification from ISR?

You are correct. I suppose the original plan was to have a wait instead of a vTaskdelay in the B task, and to terminate it early in case the ISR executed.

I removed the notify from the ISR, unfortunately that made no difference.

So the sequence is now the following -

  1. a_task waits for a notification.
  2. sensor_find_task notifies a task.

What behavior do you observe and what do you expect?

  1. sensor_find_task notifies a task, only after the ISR has set a flag to true

I expect:

  1. ISR set the flag
  2. B notices it, exits the while loop, notifies A
  3. A wakes up and prints the value sent together with the notification from B

I observe, in that order:

  1. ISR sets the flag (I have a short printf in there)
  2. A wakes up with notification value 0
  3. B comes out of the while loop, has correct value before trying to notify A

Is there a way I can see, in A, where the waking notification origins from?

EDIT:
In this case, since you made me notice I don’t need the notification from ISR, I don’t need the ISR at all, I can just read the GPIO level in the task. Still, I would like to understand what is causing the ISR to wake task A. I also have a global gpio ISR, set with gpio_set_irq_enabled_with_callback, but this ISR I set raw with gpio_add_raw_irq_handler.

Calling printf from ISR is not a good idea. Please remove that.

It may be a memory corruption. One way to debug is to put a databreakpoint on the notification state variable of the A’s TCB and see who is updating it.

Calling printf from ISR is not a good idea. Please remove that.

You’re right. In this case, this is a single character without a flush, and was added solely for debugging. The behavior does not change with or without the print, and it will go away once I understand what is going on.

databreakpoint on the notification state variable of the A’s TCB and see who is updating it.

Good suggestion. Unfortunately, my gdb and rp2040 does not seem to support watchpoints. I don’t know if that is fixable, or if the HW just isn’t there.

Anyway, I just found out that another ISR notifies the same task. I didn’t expect this ISR to trigger, so I have more digging to do for that, but it’s another issue.

Thanks for your help, not least confirming that what I was seeing was unexpected (I have been working with Contiki in the past, where waking a thread up had to go through a possible parent thread. This is why I found this suspicious, maybe it’s some light PTSD :wink: ).