Skip to content

gnrc, nimble/ble: notify network layer of BLE connection events#21608

Merged
benpicco merged 9 commits intoRIOT-OS:masterfrom
elenaf9:gnrc/netapi/event
Dec 9, 2025
Merged

gnrc, nimble/ble: notify network layer of BLE connection events#21608
benpicco merged 9 commits intoRIOT-OS:masterfrom
elenaf9:gnrc/netapi/event

Conversation

@elenaf9
Copy link
Copy Markdown
Contributor

@elenaf9 elenaf9 commented Jul 17, 2025

Contribution description

This PR solves two issues that have been previously discussed in #21580 and #21586:

  • Adding connected BLE nodes to the NIB neighbor cache
  • Notifying RPL when a BLE connection closes and the parent with matching address is unreachable.

Both issues require communication between different netapi layers and are related with each other, so I bundled them in one PR.

Cross-layer Communication with netapi

With the current netapi it is not possible to send arbitrary data between networking threads.
The API only allows to either dispatch packets to multiple threads that are registered in the netreg (gnrc_netapi_dispatch), or to set/get options to/from a single thread whose PID is known (gnrc_netapi_{get,set}).
In my use-case, however, a lower layer (BLE) wants to notify other layers higher up the stack (IPv6, RPL) of an event (e.g., connection established). Ideally, the lower layer shouldn't need to know if/ what higher layers are present, similarly to the case when packets are passed up/ down the stack.

netapi notify

To solve the above, I added a new netapi function gnrc_netapi_notify that dispatches an "notification"-event to all subscribed threads. Like in gnrc_netapi_dispatch the target threads are queried from netreg, but with notify the sending is done synchronously by blocking for an ACK. The latter is needed because contrary to the packets that get allocated in the pktbuf, the notify event data is stack allocated and thus blocking is needed to ensure the data was read before the function returns. This is not ideal, but I wasn't able to come up with a better solution.

Handling BLE connection events

The notify event API is used:

  1. BLE -> IPv6: inform of a (dis-)connected link-layer address
  2. IPv6 -> RPL: inform of new / unreachable neighbors

In 2. the notification is sent from the IPv6 thread rather than from BLE because address translation from l2addr->ipv6 address is solved on that layer.

Testing procedure

Tested on FIT IoT-Testlab with 9 nrf52dk nodes by using the gnrc_networking example + nimble_rpble module.
The entries are added/ removed from the NIB neighbor cache as expected, and the RPL tree updates when connections between parents and children close.

Edit:
Sorry, misunderstood the section as "how did I test it". Actual test instructions:

  1. Add USEMODULE += nimble_rpble to the gnrc_networking and run on min 2. nodes (I tested with nrf52dk)
  2. Init one node as RPL root node: ifconfig 8 add 2001:db8:1::1/64 && rpl root 1 2001:db8:1::1.
  3. Root node should setup itself as RPL root and start advertising. (check with ble info and rpl commands)
  4. Second node should automatically connect to first node and init its RPL dodag with the root node as parent.
  5. On both nodes, the neighbor cache should have one entry with each others info (check withnib neigh)
  6. Stop root node. Neighbor cache on second node should now be empty again, and rpl dodag should be first set to infinite rank, and shortly after cleaned up.

Issues/PRs references

Replaces #21586.
Part of #21574.
Related to (and might solve) #21580.

Additional Nodes

Happy to split up the PR in separate sub-PRs if that's easier, but as already mentioned it all kinda relates to each other.

See self-review for discussion points.

@github-actions github-actions bot added Area: network Area: Networking Area: pkg Area: External package ports Area: BLE Area: Bluetooth Low Energy support Area: sys Area: System labels Jul 17, 2025
Comment on lines +132 to +143
/**
* @brief Iterate over all parents in all DODAGs with @p IPv6 address.
*
* @param[in] idx Index to start searching from.
* @param[in] addr IPV6 address of the parent.
* @param[out] parent Pointer to the parent if one was found. Otherwise NULL.
*
* @return Index > 0 to continue next search from, if parent was found.
* @return -ENONENT if not found
*/
int gnrc_rpl_parent_iter_by_addr(ipv6_addr_t *addr, gnrc_rpl_parent_t **parent, int idx);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Quoting from there for reviewer's convenience:

This function is a bit weird, but it is needed because, theoretically, if we have more than one RPL instance, the same node can be present as parent in multiple dodags, and thus appear multiple times in the gnrc_rpl_parents list.
We want to mark the parent as unreachable in all dodags.

I'm not knowledgeable enough with RPL to comment on this.

Comment on lines +237 to +253
/**
* @brief Adds an unmanaged neighbor entry to the NIB if the interface represents a 6LN node
* and the IPv6 address can be constructed from the L2 address @p l2addr.
*
* @param[in] iface The interface to the neighbor.
* @param[in] l2addr The neighbor's layer 2 address.
* @param[in] l2addr_len Length of @p l2addr.
*
* @return 0 on success.
* @return -ENOTSUP, if the interface does not represent a 6LN or when
* gnrc_netif_t::device_type of the iface does not support IID conversion.
* @return -EINVAL, when @p addr_len is invalid for the
* gnrc_netif_t::device_type of @p netif.
* @return -ENOMEM, if no space is left in neighbor cache.
*/
int gnrc_ipv6_nib_nc_try_set_6ln(unsigned iface, const uint8_t *l2addr,
size_t l2addr_len);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Only possible for 6LN nodes because otherwise we can't statically build an IPv6 address from the link-layer address. The IPv6 address is build as a reverse of the _resolve_addr_from_ipv6 that is used when resolving IPv6-> L2address, following the stateless address autoconfiguration section in the RFC.

@crasbe crasbe added Type: new feature The issue requests / The PR implemements a new feature for RIOT CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR labels Jul 17, 2025
Copy link
Copy Markdown
Contributor

@mguetschow mguetschow left a comment

Choose a reason for hiding this comment

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

Thanks for your PR! Already discussed the design offline, so that looks fine by me. But maybe someone with more knowledge with GNRC and the NETAPI could also comment on the approach (@miri64 maybe?).

Rather high-level comments from my side as I'm not very familiar with GNRC and RPL.

Comment on lines +132 to +143
/**
* @brief Iterate over all parents in all DODAGs with @p IPv6 address.
*
* @param[in] idx Index to start searching from.
* @param[in] addr IPV6 address of the parent.
* @param[out] parent Pointer to the parent if one was found. Otherwise NULL.
*
* @return Index > 0 to continue next search from, if parent was found.
* @return -ENONENT if not found
*/
int gnrc_rpl_parent_iter_by_addr(ipv6_addr_t *addr, gnrc_rpl_parent_t **parent, int idx);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Quoting from there for reviewer's convenience:

This function is a bit weird, but it is needed because, theoretically, if we have more than one RPL instance, the same node can be present as parent in multiple dodags, and thus appear multiple times in the gnrc_rpl_parents list.
We want to mark the parent as unreachable in all dodags.

I'm not knowledgeable enough with RPL to comment on this.

@riot-ci
Copy link
Copy Markdown

riot-ci commented Jul 17, 2025

Murdock results

✔️ PASSED

1f3daf3 sys/atomic_utils: fix compilation with C++ compiler

Success Failures Total Runtime
10950 0 10950 11m:05s

Artifacts

@elenaf9
Copy link
Copy Markdown
Contributor Author

elenaf9 commented Jul 30, 2025

Latest Murdock failure is unrelated I think?

Edit: just saw #21621 (comment).

@elenaf9 elenaf9 force-pushed the gnrc/netapi/event branch from 5423c6a to a7d5ac9 Compare August 29, 2025 22:56
@crasbe crasbe linked an issue Sep 3, 2025 that may be closed by this pull request
Copy link
Copy Markdown
Contributor

@crasbe crasbe left a comment

Choose a reason for hiding this comment

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

I'm unfamiliar with the network subsystem, so have some style related review comments :D

Copy link
Copy Markdown
Contributor

@benpicco benpicco left a comment

Choose a reason for hiding this comment

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

This sounds pretty useful as a general mechanism - some first round of comments

Copy link
Copy Markdown
Member

@miri64 miri64 left a comment

Choose a reason for hiding this comment

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

Cursory review after scrolling through most of the code.

@miri64
Copy link
Copy Markdown
Member

miri64 commented Nov 25, 2025

I ran spec07 (multi hop, which includes unmodified RPL) of the release specifications to check if this breaks something.

Sadly, it does.

test: commands[0]> pytest -k spec07 --non-RC
================================================================================================================================ test session starts ================================================================================================================================
platform linux -- Python 3.13.7, pytest-7.3.2, pluggy-1.6.0 -- /home/mlenders/Repositories/RIOT-OS/Release-Specs/.tox/test/bin/python
cachedir: .tox/test/.pytest_cache
rootdir: /home/mlenders/Repositories/RIOT-OS/Release-Specs
configfile: setup.cfg
plugins: rerunfailures-14.0, cov-7.0.0
collected 241 items / 236 deselected / 5 selected                                                                                                                                                                                                                                   

07-multi-hop/test_spec07.py::test_task01[nodes0] RERUN                                                                                                                                                                                                                        [ 20%]
07-multi-hop/test_spec07.py::test_task01[nodes0] RERUN                                                                                                                                                                                                                        [ 20%]
07-multi-hop/test_spec07.py::test_task01[nodes0] RERUN                                                                                                                                                                                                                        [ 20%]
07-multi-hop/test_spec07.py::test_task01[nodes0] ERROR                                                                                                                                                                                                                        [ 20%]
07-multi-hop/test_spec07.py::test_task02[nodes0] RERUN                                                                                                                                                                                                                        [ 40%]
07-multi-hop/test_spec07.py::test_task02[nodes0] RERUN                                                                                                                                                                                                                        [ 40%]
07-multi-hop/test_spec07.py::test_task02[nodes0] RERUN                                                                                                                                                                                                                        [ 40%]
07-multi-hop/test_spec07.py::test_task02[nodes0] ERROR                                                                                                                                                                                                                        [ 40%]
07-multi-hop/test_spec07.py::test_task03[nodes0] RERUN                                                                                                                                                                                                                        [ 60%]
07-multi-hop/test_spec07.py::test_task03[nodes0] RERUN                                                                                                                                                                                                                        [ 60%]
07-multi-hop/test_spec07.py::test_task03[nodes0] RERUN                                                                                                                                                                                                                        [ 60%]
07-multi-hop/test_spec07.py::test_task03[nodes0] ERROR                                                                                                                                                                                                                        [ 60%]
07-multi-hop/test_spec07.py::test_task04[nodes0] RERUN                                                                                                                                                                                                                        [ 80%]
07-multi-hop/test_spec07.py::test_task04[nodes0] RERUN                                                                                                                                                                                                                        [ 80%]
07-multi-hop/test_spec07.py::test_task04[nodes0] RERUN                                                                                                                                                                                                                        [ 80%]
07-multi-hop/test_spec07.py::test_task04[nodes0] ERROR                                                                                                                                                                                                                        [ 80%]
07-multi-hop/test_spec07.py::test_task05[nodes0] ERROR                                                                                                                                                                                                                        [100%]

====================================================================================================================================== ERRORS =======================================================================================================================================
_______________________________________________________________________________________________________________________ ERROR at setup of test_task01[nodes0] _______________________________________________________________________________________________________________________

riot_ctrl = <function riot_ctrl.<locals>.ctrl at 0x7f1d47555c60>

    @pytest.fixture
    def statically_routed_nodes(riot_ctrl):
        nodes = (
>           riot_ctrl(0, APP, Shell),
            riot_ctrl(1, APP, Shell),
            riot_ctrl(2, APP, Shell),
            riot_ctrl(3, APP, Shell),
        )

07-multi-hop/test_spec07.py:24: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
conftest.py:389: in ctrl
    node.make_run(
.tox/test/lib/python3.13/site-packages/riotctrl/ctrl.py:216: in make_run
    return subprocess.run(command, env=self.env, *runargs, **runkwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

input = None, capture_output = False, timeout = None, check = True, popenargs = (['make', '--no-print-directory', '-C', '/home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp', 'flash'],)
kwargs = {'env': {'BOARD': 'iotlab-m3', 'COLUMNS': '277', 'DEFAULT_PAN_ID': '36860', 'HOME': '/home/mlenders', ...}, 'stderr': None, 'stdout': None}, process = <Popen: returncode: 2 args: ['make', '--no-print-directory', '-C', '/home/ml...>, stdout = None, stderr = None
retcode = 2

    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['make', '--no-print-directory', '-C', '/home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp', 'flash']' returned non-zero exit status 2.

/usr/lib/python3.13/subprocess.py:577: CalledProcessError
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
_______________________________________________________________________________________________________________________ ERROR at setup of test_task02[nodes0] _______________________________________________________________________________________________________________________

riot_ctrl = <function riot_ctrl.<locals>.ctrl at 0x7f1d48105580>

    @pytest.fixture
    def statically_routed_nodes(riot_ctrl):
        nodes = (
>           riot_ctrl(0, APP, Shell),
            riot_ctrl(1, APP, Shell),
            riot_ctrl(2, APP, Shell),
            riot_ctrl(3, APP, Shell),
        )

07-multi-hop/test_spec07.py:24: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
conftest.py:389: in ctrl
    node.make_run(
.tox/test/lib/python3.13/site-packages/riotctrl/ctrl.py:216: in make_run
    return subprocess.run(command, env=self.env, *runargs, **runkwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

input = None, capture_output = False, timeout = None, check = True, popenargs = (['make', '--no-print-directory', '-C', '/home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp', 'flash'],)
kwargs = {'env': {'BOARD': 'iotlab-m3', 'COLUMNS': '277', 'DEFAULT_PAN_ID': '36860', 'HOME': '/home/mlenders', ...}, 'stderr': None, 'stdout': None}, process = <Popen: returncode: 2 args: ['make', '--no-print-directory', '-C', '/home/ml...>, stdout = None, stderr = None
retcode = 2

    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['make', '--no-print-directory', '-C', '/home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp', 'flash']' returned non-zero exit status 2.

/usr/lib/python3.13/subprocess.py:577: CalledProcessError
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
_______________________________________________________________________________________________________________________ ERROR at setup of test_task03[nodes0] _______________________________________________________________________________________________________________________

riot_ctrl = <function riot_ctrl.<locals>.ctrl at 0x7f1d475223e0>, netif_parser = <riotctrl_shell.netif.IfconfigListParser object at 0x7f1d49925400>

    @pytest.fixture
    # pylint: disable=W0621
    def rpl_nodes(riot_ctrl, netif_parser):
        nodes = (
>           riot_ctrl(0, APP, Shell, modules="l2filter_whitelist"),
            riot_ctrl(1, APP, Shell, modules="l2filter_whitelist"),
            riot_ctrl(2, APP, Shell, modules="l2filter_whitelist"),
            riot_ctrl(3, APP, Shell, modules="l2filter_whitelist"),
        )

07-multi-hop/test_spec07.py:112: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
conftest.py:389: in ctrl
    node.make_run(
.tox/test/lib/python3.13/site-packages/riotctrl/ctrl.py:216: in make_run
    return subprocess.run(command, env=self.env, *runargs, **runkwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

input = None, capture_output = False, timeout = None, check = True, popenargs = (['make', '--no-print-directory', '-C', '/home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp', 'flash'],)
kwargs = {'env': {'BOARD': 'iotlab-m3', 'COLUMNS': '277', 'DEFAULT_PAN_ID': '36860', 'HOME': '/home/mlenders', ...}, 'stderr': None, 'stdout': None}, process = <Popen: returncode: 2 args: ['make', '--no-print-directory', '-C', '/home/ml...>, stdout = None, stderr = None
retcode = 2

    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['make', '--no-print-directory', '-C', '/home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp', 'flash']' returned non-zero exit status 2.

/usr/lib/python3.13/subprocess.py:577: CalledProcessError
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
_______________________________________________________________________________________________________________________ ERROR at setup of test_task04[nodes0] _______________________________________________________________________________________________________________________

riot_ctrl = <function riot_ctrl.<locals>.ctrl at 0x7f1d47521080>, netif_parser = <riotctrl_shell.netif.IfconfigListParser object at 0x7f1d49925400>

    @pytest.fixture
    # pylint: disable=W0621
    def rpl_nodes(riot_ctrl, netif_parser):
        nodes = (
>           riot_ctrl(0, APP, Shell, modules="l2filter_whitelist"),
            riot_ctrl(1, APP, Shell, modules="l2filter_whitelist"),
            riot_ctrl(2, APP, Shell, modules="l2filter_whitelist"),
            riot_ctrl(3, APP, Shell, modules="l2filter_whitelist"),
        )

07-multi-hop/test_spec07.py:112: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
conftest.py:389: in ctrl
    node.make_run(
.tox/test/lib/python3.13/site-packages/riotctrl/ctrl.py:216: in make_run
    return subprocess.run(command, env=self.env, *runargs, **runkwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

input = None, capture_output = False, timeout = None, check = True, popenargs = (['make', '--no-print-directory', '-C', '/home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp', 'flash'],)
kwargs = {'env': {'BOARD': 'iotlab-m3', 'COLUMNS': '277', 'DEFAULT_PAN_ID': '36860', 'HOME': '/home/mlenders', ...}, 'stderr': None, 'stdout': None}, process = <Popen: returncode: 2 args: ['make', '--no-print-directory', '-C', '/home/ml...>, stdout = None, stderr = None
retcode = 2

    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['make', '--no-print-directory', '-C', '/home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp', 'flash']' returned non-zero exit status 2.

/usr/lib/python3.13/subprocess.py:577: CalledProcessError
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
_______________________________________________________________________________________________________________________ ERROR at setup of test_task05[nodes0] _______________________________________________________________________________________________________________________

riot_ctrl = <function riot_ctrl.<locals>.ctrl at 0x7f1d47556840>, netif_parser = <riotctrl_shell.netif.IfconfigListParser object at 0x7f1d49925400>

    @pytest.fixture
    # pylint: disable=W0621
    def rpl_nodes(riot_ctrl, netif_parser):
        nodes = (
>           riot_ctrl(0, APP, Shell, modules="l2filter_whitelist"),
            riot_ctrl(1, APP, Shell, modules="l2filter_whitelist"),
            riot_ctrl(2, APP, Shell, modules="l2filter_whitelist"),
            riot_ctrl(3, APP, Shell, modules="l2filter_whitelist"),
        )

07-multi-hop/test_spec07.py:112: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
conftest.py:389: in ctrl
    node.make_run(
.tox/test/lib/python3.13/site-packages/riotctrl/ctrl.py:216: in make_run
    return subprocess.run(command, env=self.env, *runargs, **runkwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

input = None, capture_output = False, timeout = None, check = True, popenargs = (['make', '--no-print-directory', '-C', '/home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp', 'flash'],)
kwargs = {'env': {'BOARD': 'iotlab-m3', 'COLUMNS': '277', 'DEFAULT_PAN_ID': '36860', 'HOME': '/home/mlenders', ...}, 'stderr': None, 'stdout': None}, process = <Popen: returncode: 2 args: ['make', '--no-print-directory', '-C', '/home/ml...>, stdout = None, stderr = None
retcode = 2

    def run(*popenargs,
            input=None, capture_output=False, timeout=None, check=False, **kwargs):
        """Run command with arguments and return a CompletedProcess instance.
    
        The returned instance will have attributes args, returncode, stdout and
        stderr. By default, stdout and stderr are not captured, and those attributes
        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
        or pass capture_output=True to capture both.
    
        If check is True and the exit code was non-zero, it raises a
        CalledProcessError. The CalledProcessError object will have the return code
        in the returncode attribute, and output & stderr attributes if those streams
        were captured.
    
        If timeout (seconds) is given and the process takes too long,
         a TimeoutExpired exception will be raised.
    
        There is an optional argument "input", allowing you to
        pass bytes or a string to the subprocess's stdin.  If you use this argument
        you may not also use the Popen constructor's "stdin" argument, as
        it will be used internally.
    
        By default, all communication is in bytes, and therefore any "input" should
        be bytes, and the stdout and stderr will be bytes. If in text mode, any
        "input" should be a string, and stdout and stderr will be strings decoded
        according to locale encoding, or by "encoding" if set. Text mode is
        triggered by setting any of text, encoding, errors or universal_newlines.
    
        The other arguments are the same as for the Popen constructor.
        """
        if input is not None:
            if kwargs.get('stdin') is not None:
                raise ValueError('stdin and input arguments may not both be used.')
            kwargs['stdin'] = PIPE
    
        if capture_output:
            if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
                raise ValueError('stdout and stderr arguments may not be used '
                                 'with capture_output.')
            kwargs['stdout'] = PIPE
            kwargs['stderr'] = PIPE
    
        with Popen(*popenargs, **kwargs) as process:
            try:
                stdout, stderr = process.communicate(input, timeout=timeout)
            except TimeoutExpired as exc:
                process.kill()
                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                raise
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                raise
            retcode = process.poll()
            if check and retcode:
>               raise CalledProcessError(retcode, process.args,
                                         output=stdout, stderr=stderr)
E               subprocess.CalledProcessError: Command '['make', '--no-print-directory', '-C', '/home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp', 'flash']' returned non-zero exit status 2.

/usr/lib/python3.13/subprocess.py:577: CalledProcessError
------------------------------------------------------------------------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------------------------------------------------------------------------
Building application "tests_gnrc_udp" for "iotlab-m3" with CPU "stm32".

------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------------------------------------------------------------
/usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/bin/ld: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/gnrc_rpl/gnrc_rpl.o: in function `_netapi_notify_event':
/home/mlenders/Repositories/RIOT-OS/RIOT/sys/net/gnrc/routing/rpl/gnrc_rpl.c:322:(.text._event_loop+0x90): undefined reference to `gnrc_netapi_notify_copy_event_data'
collect2: error: ld returned 1 exit status
make: *** [/home/mlenders/Repositories/RIOT-OS/RIOT/Makefile.include:754: /home/mlenders/Repositories/RIOT-OS/RIOT/tests/net/gnrc_udp/bin/iotlab-m3/tests_gnrc_udp.elf] Error 1
----------------------------------------------------------------------------------------------- generated xml file: /home/mlenders/Repositories/RIOT-OS/Release-Specs/test-report.xml -----------------------------------------------------------------------------------------------
============================================================================================================== 236 deselected, 5 errors, 12 rerun in 742.73s (0:12:22) ==============================================================================================================
test: exit 1 (743.38 seconds) /home/mlenders/Repositories/RIOT-OS/Release-Specs> pytest -k spec07 --non-RC pid=650107
  test: FAIL code 1 (743.46=setup[0.08]+cmd[743.38] seconds)
  evaluation failed :( (743.51 seconds)

Interestingly, also the non-RPL tasks are failing, so I think something in IPv6 is broken.

On master it ran fine:
test: commands[0]> pytest -k spec07 --non-RC
================================================================================================================================ test session starts ================================================================================================================================
platform linux -- Python 3.13.7, pytest-7.3.2, pluggy-1.6.0 -- /home/mlenders/Repositories/RIOT-OS/Release-Specs/.tox/test/bin/python
cachedir: .tox/test/.pytest_cache
rootdir: /home/mlenders/Repositories/RIOT-OS/Release-Specs
configfile: setup.cfg
plugins: rerunfailures-14.0, cov-7.0.0
collected 242 items / 237 deselected / 5 selected                                                                                                                                                                                                                                   

07-multi-hop/test_spec07.py::test_task01[nodes0] PASSED                                                                                                                                                                                                                       [ 20%]
07-multi-hop/test_spec07.py::test_task02[nodes0] PASSED                                                                                                                                                                                                                       [ 40%]
07-multi-hop/test_spec07.py::test_task03[nodes0] PASSED                                                                                                                                                                                                                       [ 60%]
07-multi-hop/test_spec07.py::test_task04[nodes0] PASSED                                                                                                                                                                                                                       [ 80%]
07-multi-hop/test_spec07.py::test_task05[nodes0] XFAIL (packet_loss 100.0 >= 10. This is an experimental task, see also https://github.com/RIOT-OS/Release-Specs/issues/142#issuecomment-561677974)                                                                           [100%]

----------------------------------------------------------------------------------------------- generated xml file: /home/mlenders/Repositories/RIOT-OS/Release-Specs/test-report.xml -----------------------------------------------------------------------------------------------
============================================================================================================= 4 passed, 237 deselected, 1 xfailed in 905.91s (0:15:05) ==============================================================================================================
  test: OK (906.74=setup[0.09]+cmd[906.66] seconds)
  congratulations :) (906.79 seconds)

(see also the latest run of the weekly release-tests though that is already 4 days old)

You can test yourself by cloning https://github.com/RIOT-OS/Release-Specs and running

RIOTBASE="<path to RIOT repo>" tox -e test -- -k "spec07" --non-RC

in the root directory of that repo. You need an IoT-LAB account for that and be authenticated with that with iotlab-auth in your shell. See also the README there for more information.

Copy link
Copy Markdown
Contributor

@crasbe crasbe left a comment

Choose a reason for hiding this comment

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

Just some minor style things.

Copy link
Copy Markdown
Contributor

@benpicco benpicco left a comment

Choose a reason for hiding this comment

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

Looks good, please squash!

@benpicco
Copy link
Copy Markdown
Contributor

benpicco commented Dec 9, 2025

Uh looks like CI is not happy yet

@elenaf9
Copy link
Copy Markdown
Contributor Author

elenaf9 commented Dec 9, 2025

Sorry for the many pushes. Took me a few attempts to get the conditional-compilation gates right.
There is still one failing test in Murdock, but that is related to atomic_utils. Is this a known issue? I am not using the module.

@benpicco
Copy link
Copy Markdown
Contributor

benpicco commented Dec 9, 2025

Please squash!

The netapi notify API enables protocol-independent, cross-layer
notification events.
Lower layers in the network stack can use this to inform upper
layers of network events.
If a new L2 connection was established, the node should be inserted in
the neighbor cache.
For 6LN nodes, NIB will never start the usual neighbor discovery process
because it can directly resolve the link-layer address from the IPv6
address. Thus we have to manually add such nodes to the NC by building
the IPv6 address from the L2 address.

If a L2 connection closed, the node is removed again from the NC.
Iterate through all parents whose address match `addr`.
In most cases there will only be a single parent, but if
`GNRC_RPL_INSTANCES_NUMOF > 1` then one node can be parent in multiple
DODAGs.
Handle netapi notification for discovered and unreachable neighbors.
For newly discovered neighbors, send a DIS message to the node. For
unreachable nodes trigger a timeout for RPL parents that match the
address.
`gnrc_rpl` now receives a notification if a new node is reachable (i.e.,
connected) and sends a DIS. No need anymore to also do it in rpble.
@elenaf9
Copy link
Copy Markdown
Contributor Author

elenaf9 commented Dec 9, 2025

Squash done.

  • Old History preserved at elenaf9:archive/gnrc/netapi/event.
  • Manual testing as described in PR description successful.
  • Release-Specs tests 07-multi-hop/spec07 successful apart from task05, which gives XFAIL (packet_loss 100.0 >= 10. This is an experimenta...)
    • Since we last ran task05 successfully on this branch no logic changed, apart from a rebase onto master.
      I tested on master, the test fails there as well. So I think it is unrelated to this PR.

Murdock failure likely unrelated as well, see #21608 (comment).

@benpicco
Copy link
Copy Markdown
Contributor

benpicco commented Dec 9, 2025

The problem is that atomic_utils.h now gets included by a C++ file and C++ is not a superset of C and that header file is not valid C++. It was just never compiled with a C++ compiler before.

A (slightly cheeky) quick fix would be

--- i/sys/include/atomic_utils.h
+++ w/sys/include/atomic_utils.h
@@ -144,9 +144,7 @@
 
 #include "atomic_utils_arch.h" /* IWYU pragma: export */
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+#ifndef __cplusplus
 
 /* NOLINTBEGIN(bugprone-macro-parentheses, readability-inconsistent-declaration-parameter-name)
  *
@@ -1748,9 +1746,7 @@ static inline uint64_t semi_atomic_fetch_and_u64(volatile uint64_t *dest,
 }
 #endif /* HAS_ATOMIC_FETCH_AND_U64 || !HAS_ATOMIC_STORE_U64 */
 
-#ifdef __cplusplus
-}
-#endif
+#endif /* !__cplusplus */
 
 /* NOLINTEND(bugprone-macro-parentheses, readability-inconsistent-declaration-parameter-name) */
 /** @} */

Maybe @maribu has a better idea?

@benpicco
Copy link
Copy Markdown
Contributor

benpicco commented Dec 9, 2025

ah or we just add -fpermissive to CXXEXFLAGS as sugested by the compiler 🤷‍♂️

--- i/pkg/opendsme/Makefile.dep
+++ w/pkg/opendsme/Makefile.dep
@@ -29,7 +29,7 @@ USEMODULE += ieee802154
 USEMODULE += ztimer_usec
 
 USEMODULE += cpp11-compat
-CXXEXFLAGS += -Wno-unused-parameter -Wno-pedantic -Wno-missing-field-initializers -Wno-unused-but-set-variable -Wno-maybe-uninitialized -Wno-unused-variable -Wno-reorder -Wno-address -Wno-sign-compare -Wno-unused-function
+CXXEXFLAGS += -Wno-unused-parameter -Wno-pedantic -Wno-missing-field-initializers -Wno-unused-but-set-variable -Wno-maybe-uninitialized -Wno-unused-variable -Wno-reorder -Wno-address -Wno-sign-compare -Wno-unused-function -fpermissive
 FEATURES_REQUIRED += cpp # basic C++ support
 FEATURES_REQUIRED += libstdcpp # libstdc++ support (for #include <cstdio>)

@maribu
Copy link
Copy Markdown
Member

maribu commented Dec 9, 2025

It would actually be nice to be able to use atomic_utils from C++, as this would be the only option for using atomics across C++ and C code (the C11 atomics and the C++ atomics are not compatible).

The following patch should fix compilation on C++:

diff --git a/sys/include/atomic_utils.h b/sys/include/atomic_utils.h
index 97b3aaaaa9..2c685f4066 100644
--- a/sys/include/atomic_utils.h
+++ b/sys/include/atomic_utils.h
@@ -274,14 +274,14 @@ static inline uint64_t atomic_load_u64(const volatile uint64_t *var);
 static inline unsigned atomic_load_unsigned(const volatile unsigned *var)
 {
     if (sizeof(uint64_t) == sizeof(unsigned)) {
-        return atomic_load_u64((volatile void *)var);
+        return atomic_load_u64((const volatile uint64_t *)(uintptr_t)var);
     }
 
     if (sizeof(uint32_t) == sizeof(unsigned)) {
-        return atomic_load_u32((volatile void *)var);
+        return atomic_load_u32((const volatile uint32_t *)(uintptr_t)var);
     }
 
-    return atomic_load_u16((volatile void *)var);
+    return atomic_load_u16((const volatile uint16_t *)(uintptr_t)var);
 }
 
 /**
@@ -362,13 +362,13 @@ static inline void atomic_store_u64(volatile uint64_t *dest, uint64_t val);
 static inline void atomic_store_unsigned(volatile unsigned *dest, unsigned val)
 {
     if (sizeof(uint64_t) == sizeof(unsigned)) {
-        atomic_store_u64((volatile void *)dest, val);
+        atomic_store_u64((volatile uint64_t *)(uintptr_t)dest, val);
     }
     else if (sizeof(uint32_t) == sizeof(unsigned)) {
-        atomic_store_u32((volatile void *)dest, val);
+        atomic_store_u32((volatile uint32_t *)(uintptr_t)dest, val);
     }
     else {
-        atomic_store_u16((volatile void *)dest, val);
+        atomic_store_u16((volatile uint16_t *)(uintptr_t)dest, val);
     }
 }
 
@@ -462,14 +462,14 @@ static inline unsigned atomic_fetch_add_unsigned(volatile unsigned *dest,
                                                  unsigned summand)
 {
     if (sizeof(unsigned) == sizeof(uint64_t)) {
-        return atomic_fetch_add_u64((volatile void *)dest, summand);
+        return atomic_fetch_add_u64((volatile uint64_t *)(uintptr_t)dest, summand);
     }
 
     if (sizeof(unsigned) == sizeof(uint32_t)) {
-        return atomic_fetch_add_u32((volatile void *)dest, summand);
+        return atomic_fetch_add_u32((volatile uint32_t *)(uintptr_t)dest, summand);
     }
 
-    return atomic_fetch_add_u16((volatile void *)dest, summand);
+    return atomic_fetch_add_u16((volatile uint16_t *)(uintptr_t)dest, summand);
 }
 /** @} */
 
@@ -528,14 +528,14 @@ static inline unsigned atomic_fetch_sub_unsigned(volatile unsigned *dest,
                                                  unsigned subtrahend)
 {
     if (sizeof(unsigned) == sizeof(uint64_t)) {
-        return atomic_fetch_sub_u64((volatile void *)dest, subtrahend);
+        return atomic_fetch_sub_u64((volatile uint64_t *)(uintptr_t)dest, subtrahend);
     }
 
     if (sizeof(unsigned) == sizeof(uint32_t)) {
-        return atomic_fetch_sub_u32((volatile void *)dest, subtrahend);
+        return atomic_fetch_sub_u32((volatile uint32_t *)(uintptr_t)dest, subtrahend);
     }
 
-    return atomic_fetch_sub_u16((volatile void *)dest, subtrahend);
+    return atomic_fetch_sub_u16((volatile uint16_t *)(uintptr_t)dest, subtrahend);
 }
 /** @} */
 
@@ -593,14 +593,14 @@ static inline unsigned atomic_fetch_or_unsigned(volatile unsigned *dest,
                                                 unsigned val)
 {
     if (sizeof(unsigned) == sizeof(uint64_t)) {
-        return atomic_fetch_or_u64((volatile void *)dest, val);
+        return atomic_fetch_or_u64((volatile uint64_t *)(uintptr_t)dest, val);
     }
 
     if (sizeof(unsigned) == sizeof(uint32_t)) {
-        return atomic_fetch_or_u32((volatile void *)dest, val);
+        return atomic_fetch_or_u32((volatile uint32_t *)(uintptr_t)dest, val);
     }
 
-    return atomic_fetch_or_u16((volatile void *)dest, val);
+    return atomic_fetch_or_u16((volatile uint16_t *)(uintptr_t)dest, val);
 }
 /** @} */
 
@@ -658,14 +658,14 @@ static inline unsigned atomic_fetch_xor_unsigned(volatile unsigned *dest,
                                                  unsigned val)
 {
     if (sizeof(unsigned) == sizeof(uint64_t)) {
-        return atomic_fetch_xor_u64((volatile void *)dest, val);
+        return atomic_fetch_xor_u64((volatile uint64_t *)(uintptr_t)dest, val);
     }
 
     if (sizeof(unsigned) == sizeof(uint32_t)) {
-        return atomic_fetch_xor_u32((volatile void *)dest, val);
+        return atomic_fetch_xor_u32((volatile uint32_t *)(uintptr_t)dest, val);
     }
 
-    return atomic_fetch_xor_u16((volatile void *)dest, val);
+    return atomic_fetch_xor_u16((volatile uint16_t *)(uintptr_t)dest, val);
 }
 /** @} */
 
@@ -723,14 +723,14 @@ static inline unsigned atomic_fetch_and_unsigned(volatile unsigned *dest,
                                                  unsigned val)
 {
     if (sizeof(unsigned) == sizeof(uint64_t)) {
-        return atomic_fetch_and_u64((volatile void *)dest, val);
+        return atomic_fetch_and_u64((volatile uint64_t *)(uintptr_t)dest, val);
     }
 
     if (sizeof(unsigned) == sizeof(uint32_t)) {
-        return atomic_fetch_and_u32((volatile void *)dest, val);
+        return atomic_fetch_and_u32((volatile uint32_t *)(uintptr_t)dest, val);
     }
 
-    return atomic_fetch_and_u16((volatile void *)dest, val);
+    return atomic_fetch_and_u16((volatile uint16_t *)(uintptr_t)dest, val);
 }
 /** @} */
 
@@ -935,14 +935,14 @@ static inline unsigned semi_atomic_fetch_add_unsigned(volatile unsigned *dest,
                                                       unsigned summand)
 {
     if (sizeof(unsigned) == sizeof(uint64_t)) {
-        return semi_atomic_fetch_add_u64((volatile void *)dest, summand);
+        return semi_atomic_fetch_add_u64((volatile uint64_t *)(uintptr_t)dest, summand);
     }
 
     if (sizeof(unsigned) == sizeof(uint32_t)) {
-        return semi_atomic_fetch_add_u32((volatile void *)dest, summand);
+        return semi_atomic_fetch_add_u32((volatile uint32_t *)(uintptr_t)dest, summand);
     }
 
-    return semi_atomic_fetch_add_u16((volatile void *)dest, summand);
+    return semi_atomic_fetch_add_u16((volatile uint16_t *)(uintptr_t)dest, summand);
 }
 /** @} */
 
@@ -1001,14 +1001,14 @@ static inline unsigned semi_atomic_fetch_sub_unsigned(volatile unsigned *dest,
                                                       unsigned subtrahend)
 {
     if (sizeof(unsigned) == sizeof(uint64_t)) {
-        return semi_atomic_fetch_sub_u64((volatile void *)dest, subtrahend);
+        return semi_atomic_fetch_sub_u64((volatile uint64_t *)(uintptr_t)dest, subtrahend);
     }
 
     if (sizeof(unsigned) == sizeof(uint32_t)) {
-        return semi_atomic_fetch_sub_u32((volatile void *)dest, subtrahend);
+        return semi_atomic_fetch_sub_u32((volatile uint32_t *)(uintptr_t)dest, subtrahend);
     }
 
-    return semi_atomic_fetch_sub_u16((volatile void *)dest, subtrahend);
+    return semi_atomic_fetch_sub_u16((volatile uint16_t *)(uintptr_t)dest, subtrahend);
 }
 /** @} */
 
@@ -1066,14 +1066,14 @@ static inline unsigned semi_atomic_fetch_or_unsigned(volatile unsigned *dest,
                                                      unsigned val)
 {
     if (sizeof(unsigned) == sizeof(uint64_t)) {
-        return semi_atomic_fetch_or_u64((volatile void *)dest, val);
+        return semi_atomic_fetch_or_u64((volatile uint64_t *)(uintptr_t)dest, val);
     }
 
     if (sizeof(unsigned) == sizeof(uint32_t)) {
-        return semi_atomic_fetch_or_u32((volatile void *)dest, val);
+        return semi_atomic_fetch_or_u32((volatile uint32_t *)(uintptr_t)dest, val);
     }
 
-    return semi_atomic_fetch_or_u16((volatile void *)dest, val);
+    return semi_atomic_fetch_or_u16((volatile uint16_t *)(uintptr_t)dest, val);
 }
 /** @} */
 
@@ -1132,14 +1132,14 @@ static inline unsigned semi_atomic_fetch_xor_unsigned(volatile unsigned *dest,
                                                       unsigned val)
 {
     if (sizeof(unsigned) == sizeof(uint64_t)) {
-        return semi_atomic_fetch_xor_u64((volatile void *)dest, val);
+        return semi_atomic_fetch_xor_u64((volatile uint64_t *)(uintptr_t)dest, val);
     }
 
     if (sizeof(unsigned) == sizeof(uint32_t)) {
-        return semi_atomic_fetch_xor_u32((volatile void *)dest, val);
+        return semi_atomic_fetch_xor_u32((volatile uint32_t *)(uintptr_t)dest, val);
     }
 
-    return semi_atomic_fetch_xor_u16((volatile void *)dest, val);
+    return semi_atomic_fetch_xor_u16((volatile uint16_t *)(uintptr_t)dest, val);
 }
 /** @} */
 
@@ -1198,14 +1198,14 @@ static inline unsigned semi_atomic_fetch_and_unsigned(volatile unsigned *dest,
                                                       unsigned val)
 {
     if (sizeof(unsigned) == sizeof(uint64_t)) {
-        return semi_atomic_fetch_and_u64((volatile void *)dest, val);
+        return semi_atomic_fetch_and_u64((volatile uint64_t *)(uintptr_t)dest, val);
     }
 
     if (sizeof(unsigned) == sizeof(uint32_t)) {
-        return semi_atomic_fetch_and_u32((volatile void *)dest, val);
+        return semi_atomic_fetch_and_u32((volatile uint32_t *)(uintptr_t)dest, val);
     }
 
-    return semi_atomic_fetch_and_u16((volatile void *)dest, val);
+    return semi_atomic_fetch_and_u16((volatile uint16_t *)(uintptr_t)dest, val);
 }
 /** @} */
 

@benpicco benpicco enabled auto-merge December 9, 2025 18:23
@benpicco benpicco added this pull request to the merge queue Dec 9, 2025
Merged via the queue into RIOT-OS:master with commit 2c01ea0 Dec 9, 2025
25 checks passed
@elenaf9 elenaf9 deleted the gnrc/netapi/event branch December 10, 2025 08:08
@leandrolanzieri leandrolanzieri added this to the Release 2026.01 milestone Jan 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: BLE Area: Bluetooth Low Energy support Area: network Area: Networking Area: pkg Area: External package ports Area: sys Area: System CI: no fast fail don't abort PR build after first error CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Type: new feature The issue requests / The PR implemements a new feature for RIOT

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Discussion] IPv6 neighbor discovery with BLE

8 participants