Skip to content
Open
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
33 changes: 33 additions & 0 deletions examples/linux/rpmsg_udev_scripts/99-rpmsg.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (C) 2026, Advanced Micro Devices, Inc.

# Skip rpmsg_ctrl and rpmsg_ns devices by name using GOTO
SUBSYSTEM=="rpmsg", ACTION=="add", \
KERNEL=="virtio*.rpmsg_ctrl.0.0", GOTO="rpmsg_end"

SUBSYSTEM=="rpmsg", ACTION=="add", \
KERNEL=="virtio*.rpmsg_ns.53.53", GOTO="rpmsg_end"

SUBSYSTEM=="rpmsg", ACTION=="add", \
KERNEL=="rpmsg_ctrl[0-9]*", GOTO="rpmsg_end"

SUBSYSTEM=="rpmsg", ACTION=="remove", \
KERNEL=="rpmsg_ctrl[0-9]*", GOTO="rpmsg_end"

# rpmsg endpoint creation rule
SUBSYSTEM=="rpmsg", KERNEL=="rpmsg[0-9]*", ACTION=="add", \
GROUP="rpmsg", MODE="0660", \
RUN+="/usr/bin/rpmsg_add_ept_symlink.sh %k", GOTO="rpmsg_end"

# rpmsg endpoint creation rule
SUBSYSTEM=="rpmsg", KERNEL=="rpmsg[0-9]*", ACTION=="remove", \
RUN+="/usr/bin/rpmsg_remove_ept_symlink.sh %k", GOTO="rpmsg_end"

# rpmsg channel creation rule
SUBSYSTEM=="rpmsg", ACTION=="add", RUN+="/usr/bin/rpmsg_create_channel.sh %k"

# ttyRPMSG device permission rule
SUBSYSTEM=="tty", KERNEL=="ttyRPMSG[0-9]*", ACTION=="add", \
GROUP="rpmsg", MODE="0660"

LABEL="rpmsg_end"
33 changes: 33 additions & 0 deletions examples/linux/rpmsg_udev_scripts/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (C) 2026, Advanced Micro Devices, Inc.

prefix ?= /usr
bindir ?= $(prefix)/bin
udevdir ?= /etc/udev/rules.d

INSTALL ?= install
INSTALL_PROGRAM ?= $(INSTALL) -m 0755
INSTALL_DATA ?= $(INSTALL) -m 0644

SCRIPTS := \
rpmsg_add_ept_symlink.sh \
rpmsg_create_channel.sh \
rpmsg_remove_ept_symlink.sh

UDEV_RULES := 99-rpmsg.rules

.PHONY: all install uninstall clean

all:

install:
$(INSTALL) -d "$(DESTDIR)$(bindir)"
$(INSTALL) -d "$(DESTDIR)$(udevdir)"
$(INSTALL_PROGRAM) $(SCRIPTS) "$(DESTDIR)$(bindir)"
$(INSTALL_DATA) $(UDEV_RULES) "$(DESTDIR)$(udevdir)"

uninstall:
$(RM) $(addprefix "$(DESTDIR)$(bindir)"/,$(SCRIPTS))
$(RM) $(addprefix "$(DESTDIR)$(udevdir)"/,$(UDEV_RULES))

clean:
131 changes: 131 additions & 0 deletions examples/linux/rpmsg_udev_scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# RPMsg udev Scripts

Udev rules and helper scripts to automatically manage RPMsg endpoint devices.
When an RPMsg channel is created by the remote processor, these scripts handle
device permissions, symlink creation, and endpoint export automatically.

## Files

| File | Description |
|------|-------------|
| `99-rpmsg.rules` | udev rules — triggers scripts on RPMsg char device add/remove events |
| `rpmsg_create_channel.sh` | Creates RPMsg endpoint for a new channel using `rpmsg_export_ept` |
| `rpmsg_add_ept_symlink.sh` | Creates user-friendly symlinks and sets device permissions on endpoint add |
| `rpmsg_remove_ept_symlink.sh` | Removes symlinks when endpoint is removed |

## How It Works

When the remote processor starts and establishes an RPMsg channel, the kernel
creates devices under `/sys/bus/rpmsg/devices/`. The udev rules trigger the
scripts in the following order:

```
Remote processor boots
RPMsg channel created → virtio0.rpmsg-openamp-demo-channel.-1.1024
↓ udev ACTION==add
rpmsg_create_channel.sh → creates /dev/rpmsg0 via rpmsg_export_ept
↓ udev ACTION==add (rpmsg0 endpoint)
rpmsg_add_ept_symlink.sh → creates symlink
└── /dev/rpmsg_rpmsg-openamp-demo-channel.-1.1024.rpmsg0 → /dev/rpmsg0
```

On endpoint removal:
```
udev ACTION==remove (rpmsg0)
rpmsg_remove_ept_symlink.sh → removes symlink


```
When removing the symlink, we need to find out which rpmsgX device is mapped
to which symlink. The symlink name ends with `.rpmsgX`, so the removal script
removes links matching `/dev/rpmsg_*.rpmsgX`.

## Installation
```bash
# Install scripts and udev rule on a target system

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# Install scripts and udev rule on a target system
+ # Install scripts and udev rule on a local system:
+```bash
+ sudo make install
+ ```
+ # Install scripts and udev rule on a target system:
+```bash
+ make DESTDIR=<image-root> install
+ ```

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ack.

sudo make install

# Reload udev rules
sudo udevadm control --reload-rules
sudo udevadm trigger

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I will put udev reload at the end of the installation chapter

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ack.

```

For staged installs used by Yocto and other build systems:

```bash
make DESTDIR=<image-root> install
```

The Makefile supports the standard install variables:

| Variable | Default | Description |
|----------|---------|-------------|
| `DESTDIR` | empty | Staging root used by package/image builds |
| `prefix` | `/usr` | Install prefix |
| `bindir` | `$(prefix)/bin` | Directory for helper scripts |
| `udevdir` | `/etc/udev/rules.d` | Directory for udev rules |

Example:

```bash
make DESTDIR="${D}" prefix=/usr udevdir=/lib/udev/rules.d install
```

## User Group Setup

The udev rule assigns `/dev/rpmsg*` devices to the `rpmsg` group with
`MODE="0660"` — only members of the `rpmsg` group can read and write the
devices.

```bash
# Create rpmsg group, or optionally can be created by default during rootfs build
sudo groupadd rpmsg

# Add user to the group
sudo usermod -aG rpmsg <username>

# Apply group membership without logout
newgrp rpmsg
```

> **Note:** Users must be members of the `rpmsg` group to access `/dev/rpmsg*`
> devices. Changes to group membership require logout/login or `newgrp rpmsg`
> to take effect.

## Symlink Naming Convention

| Symlink | Example | Points to |
|---------|---------|-----------|
| `rpmsg_ept_name.src.dst.rpmsg_dev` | `/dev/rpmsg_rpmsg-openamp-demo-channel.-1.1024.rpmsg0` | `/dev/rpmsg0` |

@arnopo arnopo Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don’t remember all the details of our OpenAMP meeting. Could you please explain the reasoning behind the naming?

It seems to me that the symlink name does not meet the need, and it may not be stable.

For example, on the remote side, you create two RPMsg raw devices with the channel names “dev1” and “dev2”. Depending on the order the remote-side RPMsg channels are created, the symlink could be:

  • /dev/rpmsg_dev1.-1.1024.rpmsg0
  • /dev/rpmsg_dev1.-1.1025.rpmsg1

I’m also not a fan of using a dot in the name. This does not seem to be a common practice under /dev.

What do you think would be a more stable naming approach: /dev/rpmsg_dev1 or simply /dev/dev1?

@tnmysh tnmysh Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Following is the reasoning behind the naming convention:

  1. We want to allow userspace apps to communicate to remote without needing the root access. The apps don't know which /dev/rpmsgX device maps to which rpmsg channels. But they can know the channel name, and ept information ahead of the time. So, the symlink name has "(ch_name).(src_ept).(dst_ept)" in it.

  2. rpmsg_ prefix: The udev scripts don't have to go through all the files under /dev/, so symlink has rpmsg_ prefix. With that prefix, scripts and apps can easily know which files under /dev/ directory they should check.

  3. _rpmsgX suffix: Two cores can create rpmsg channel with the same name, and ept information. In such case, we want to distinguish the second rpmsg channel symlink. That is why added _rpmsgX suffix to the symlink name because only one /dev/rpmsgX device exist per rpmsg channel.

If we don't want dots, then probably I can replace them with underscore ( _ ) or hypen ( - ). If I have to pick from both, I'd pick underscore. But I am open to any other option.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Following is the reasoning behind the naming convention:

  1. We want to allow userspace apps to communicate to remote without needing the root access. The apps don't know which /dev/rpmsgX device maps to which rpmsg channels. But they can know the channel name, and ept information ahead of the time. So, the symlink name has "(ch_name).(src_ept).(dst_ept)" in it.
  2. rpmsg_ prefix: The udev scripts don't have to go through all the files under /dev/, so symlink has rpmsg_ prefix. With that prefix, scripts and apps can easily know which files under /dev/ directory they should check.
  3. _rpmsgX suffix: Two cores can create rpmsg channel with the same name, and ept information. In such case, we want to distinguish the second rpmsg channel symlink. That is why added _rpmsgX suffix to the symlink name because only one /dev/rpmsgX device exist per rpmsg channel.

For point 3, if two cores create the same RPMsg channel, could the addresses also be identical, since you would have two RPMsg instances?

If we don't want dots, then probably I can replace them with underscore ( _ ) or hypen ( - ). If I have to pick from both, I'd pick underscore. But I am open to any other option.

yes seems to me better.

Regarding the symlink, it is used by the application. The symlink name should be a unique name that stays the same after each boot, right?

If you use the EPT address for the symlink name, that depends on factors you cannot guarantee. The EPT addresses depend on the RPMsg channel binding order.


## udev Rule Logic

```
SUBSYSTEM=="rpmsg", ACTION=="add"
├── KERNEL=="virtio*.rpmsg_ctrl.0.0" → SKIP (control device)
├── KERNEL=="virtio*.rpmsg_ns.53.53" → SKIP (namespace device)
├── KERNEL=="rpmsg_ctrl[0-9]*" → SKIP (ctrl device)
├── KERNEL=="rpmsg*[0-9]*" → rpmsg_add_ept_symlink.sh
└── default → rpmsg_create_channel.sh
```

## Requirements

- Linux kernel 6.18 or later (`rpmsg_add_ept_symlink.sh`)
- `rpmsg_export_ept` utility available in `/usr/bin`
- udev running on the target system

## Debugging

# Monitor udev events

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# Monitor udev events
# Monitor udev events
+```bash

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ack.

udevadm monitor --udev --subsystem-match=rpmsg

# Test rule match without executing
udevadm test /sys/bus/rpmsg/devices/<device>

# Check udev logs
journalctl -u systemd-udevd -f
```
59 changes: 59 additions & 0 deletions examples/linux/rpmsg_udev_scripts/rpmsg_add_ept_symlink.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/bin/bash

# SPDX-License-Identifier: BSD-3-Clause
# Copyright (C) 2026, Advanced Micro Devices, Inc.

# valid for kernel 6.18 and later
rpmsg_dev=$1
max_tries=10
retry_delay=0.1
try=1

# find control device for this channel
# e.g. /sys/bus/rpmsg/devices/virtio0.rpmsg_ctrl.0.0/rpmsg/rpmsg_ctrl0/rpmsg0
while [ "$try" -le "$max_tries" ]; do
rpmsg_dev_dir=$(find -L /sys/bus/rpmsg/devices/ -maxdepth 4 -name "$rpmsg_dev" 2>/dev/null)

if [ -n "$rpmsg_dev_dir" ] &&
[ -r "$rpmsg_dev_dir/name" ] &&
[ -r "$rpmsg_dev_dir/src" ] &&
[ -r "$rpmsg_dev_dir/dst" ]; then
ept_name=$(cat "$rpmsg_dev_dir/name") # e.g. rpmsg-openamp-demo-channel
ept_src=$(cat "$rpmsg_dev_dir/src") # e.g. -1
ept_dst=$(cat "$rpmsg_dev_dir/dst") # e.g. 1024

if [ -n "$ept_name" ] &&
[ -n "$ept_src" ] &&
[ -n "$ept_dst" ]; then
break
fi
fi

if [ "$try" -eq "$max_tries" ]; then
echo "failed to find ready sysfs attributes for $rpmsg_dev" >&2
exit 1
fi

sleep "$retry_delay"
try=$((try + 1))
done

ept_symlink_name="rpmsg_$ept_name.$ept_src.$ept_dst.$rpmsg_dev"
ept_dev_path="/dev/$rpmsg_dev"
ept_symlink_path="/dev/$ept_symlink_name"

# create user mode accessible symlink

# create symlink for apps to use. /dev/rpmsg_rpmsg-openamp-demo-channel.-1.1024.rpmsg0
if [ -L "$ept_symlink_path" ]; then
if [ "$(readlink "$ept_symlink_path")" = "$ept_dev_path" ]; then
exit 0
fi

unlink "$ept_symlink_path"
elif [ -e "$ept_symlink_path" ]; then
echo "$ept_symlink_path exists and is not a symlink" >&2
exit 1
fi

ln -s "$ept_dev_path" "$ept_symlink_path"
21 changes: 21 additions & 0 deletions examples/linux/rpmsg_udev_scripts/rpmsg_create_channel.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/bash

# SPDX-License-Identifier: BSD-3-Clause
# Copyright (C) 2026, Advanced Micro Devices, Inc.

# e.g. virtio0.rpmsg-openamp-demo-channel.-1.1024
rpmsg_dev=$1

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

command usage helper seems missing in script. please find a proposal below that should also be adapted and added in other script

proposal to put on top of the file:

usage() {
	echo "Usage: $0 <rpmsg-channel-device>"
	echo ""
	echo "Create an RPMsg endpoint for a channel device announced by the kernel."
	echo ""
	echo "Example:"
	echo "  $0 virtio0.rpmsg-openamp-demo-channel.-1.1024"
}

if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
	usage
	exit 0
fi

then here:

Suggested change
+if [ -z "$rpmsg_dev" ]; then
+ echo "missing rpmsg device name argument" >&2
+ exit 1
+fi

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ack.

# Split from the end so channel names can contain dots.
ch_dest="${rpmsg_dev##*.}"
rpmsg_dev_rest="${rpmsg_dev%.*}"
ch_src="${rpmsg_dev_rest##*.}"
rpmsg_dev_rest="${rpmsg_dev_rest%.*}"
virtio_name="${rpmsg_dev_rest%%.*}"
ch_name="${rpmsg_dev_rest#*.}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Additional check suggested by IA

if [ -z "$virtio_name" ] || [ -z "$ch_name" ] || [ -z "$ch_src" ] || [ -z "$ch_dest" ]; then
	echo "failed to parse rpmsg device name: $rpmsg_dev" >&2
	exit 1
fi

if ! command -v rpmsg_export_ept >/dev/null 2>&1; then
	echo "rpmsg_export_ept is not available in PATH" >&2
	exit 1
fi

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ack.


#find control device for this channel e.g. /dev/rpmsg_ctrl0
rpmsg_ctrl_dev=$(ls "/sys/bus/rpmsg/devices/$rpmsg_dev/subsystem/devices/$virtio_name.rpmsg_ctrl.0.0/rpmsg/")

# create endpoint for this channel e.g. /dev/rpmsg0
res=$(rpmsg_export_ept "/dev/$rpmsg_ctrl_dev" "$ch_name" "$ch_src" "$ch_dest")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

res seems not used

Suggested change
res=$(rpmsg_export_ept "/dev/$rpmsg_ctrl_dev" "$ch_name" "$ch_src" "$ch_dest")
rpmsg_export_ept "/dev/$rpmsg_ctrl_dev" "$ch_name" "$ch_src" "$ch_dest"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ack.

18 changes: 18 additions & 0 deletions examples/linux/rpmsg_udev_scripts/rpmsg_remove_ept_symlink.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

# SPDX-License-Identifier: BSD-3-Clause
# Copyright (C) 2026, Advanced Micro Devices, Inc.

set -e
set +x

# e.g. "rpmsg0"
rpmsg_dev=$1

set +e

# remove symlink ending with this rpmsg device name
for rpmsg_dev_symlink in /dev/rpmsg_*."$rpmsg_dev"; do
[ -L "$rpmsg_dev_symlink" ] || continue
unlink "$rpmsg_dev_symlink" 2>/dev/null
done