added my Recipes

This commit is contained in:
2024-07-11 14:16:35 +02:00
parent 38bc4f53ac
commit 09b621d929
7118 changed files with 525762 additions and 3 deletions

View File

@@ -0,0 +1,17 @@
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,43 @@
This README file contains information on the contents of the vrpmdv-webserver layer.
Please see the corresponding sections below for details.
Dependencies
============
URI: <first dependency>
branch: <branch name>
URI: <second dependency>
branch: <branch name>
.
.
.
Patches
=======
Please submit any patches against the vrpmdv-webserver layer to the xxxx mailing list (xxxx@zzzz.org)
and cc: the maintainer:
Maintainer: Markus Lehr <markus@malehr.de>
Table of Contents
=================
I. Adding the vrpmdv-webserver layer to your build
II. Misc
I. Adding the vrpmdv-webserver layer to your build
=================================================
Run 'bitbake-layers add-layer vrpmdv-webserver'
II. Misc
========
--- replace with specific information about the vrpmdv-webserver layer ---
~/STM32MPU_workspace/STM32MP1-Ecosystem-V6.1.0/Distribution-Package-VRDevice/layers/meta-st/meta-st-openstlinux/

View File

@@ -0,0 +1,12 @@
BBPATH = "${TOPDIR}"
BBFILES ?= ""
BBLAYERS ?= " \
##OEROOT##/meta \
##OEROOT##/meta-poky \
##OEROOT##/meta-yocto-bsp \
##OEROOT##/vrpmdv-webserver \
##OEROOT##/meta-openembedded/meta-oe \
##OEROOT##/meta-openembedded/meta-python \
##OEROOT##/meta-openembedded/meta-webserver \
"

View File

@@ -0,0 +1,22 @@
### Shell environment set up for builds. ###
You can now run 'bitbake <target>'
Common targets are:
core-image-minimal
core-image-full-cmdline
core-image-sato
core-image-weston
meta-toolchain
meta-ide-support
#Targets available from golemos layer:
vrpmdv-image
vrpmdv-extended-image
vrpmdv-dev-image
Other commonly useful commands are:
- 'devtool' and 'recipetool' handle common recipe tasks
- 'bitbake-layers' handles common layer tasks
- 'oe-pkgdata-util' handles common target package tasks

View File

@@ -0,0 +1,27 @@
#@DESCRIPTION: ST GPLv3 exception
#--------------------------
# License settings
#
# We dont want (L)GPL-3.0+
INCOMPATIBLE_LICENSE = "GPLv3.0 GPLv3 GPL-3.0 GPLv3+ LGPLv3.0 LGPLv3 LGPL-3.0 LGPLv3+"
OPENSTLINUX_ALLOW_GPLv3 = "gettext"
# for alsa-utils:
OPENSTLINUX_ALLOW_GPLv3 =+ " gettext bash "
# for systemd
OPENSTLINUX_ALLOW_GPLv3 =+ " bash readline "
# for udev:
OPENSTLINUX_ALLOW_GPLv3 =+ " gawk coreutils gdbm m4 "
# for bluez
OPENSTLINUX_ALLOW_GPLv3 =+ " python3-pycairo "
# for framework-tools-ui
OPENSTLINUX_ALLOW_GPLv3 =+ " grep dosfstools elfutils cpio gzip which "
OPENSTLINUX_ALLOW_GPLv3 += "${@bb.utils.contains('DISTRO_FEATURES', 'tpm', 'nano msmtp', '', d)}"
INCOMPATIBLE_LICENSE_EXCEPTIONS:LGPL-3.0-or-later = "${OPENSTLINUX_ALLOW_GPLv3}"
INCOMPATIBLE_LICENSE_EXCEPTIONS:LGPL-3.0-only = "${OPENSTLINUX_ALLOW_GPLv3}"
INCOMPATIBLE_LICENSE_EXCEPTIONS:GPL-3.0-only = "${OPENSTLINUX_ALLOW_GPLv3}"
INCOMPATIBLE_LICENSE_EXCEPTIONS:GPL-3.0-or-later = "${OPENSTLINUX_ALLOW_GPLv3}"

View File

@@ -0,0 +1,32 @@
#@DESCRIPTION: openstlinux default distro naming
# Init OPENSTLINUX_RELEASE flag to snapshot
OPENSTLINUX_RELEASE ??= "snapshot"
DISTRO = "openstlinux"
DISTRO_NAME = "ST OpenSTLinux (A Yocto Project Based Distro)"
DISTRO_VERSION = "4.2.2-${@bb.utils.contains('OPENSTLINUX_RELEASE', 'snapshot', 'snapshot-${DATE}', '${OPENSTLINUX_RELEASE}', d)}"
# Don't include the DATE variable in the sstate package signatures
DISTRO_VERSION[vardepsexclude] = "DATE"
DISTRO_CODENAME = "mickledore"
# Warning: SDK_VENDOR does not contains a valid OS/ARCH name like : linux, arm
SDK_VENDOR = "-ostl_sdk"
SDK_VERSION := "${@'${DISTRO_VERSION}'.replace('snapshot-${DATE}','snapshot')}"
# Don't include the DATE variable in the sstate package signatures
SDK_VERSION[vardepsexclude] = "DATE"
MAINTAINER = "Christophe Priouzeau <christophe.priouzeau@st.com>"
# Warning: TARGET_VENDOR does not contains a valid OS/ARCH name like : linux, arm
STM32MP_TARGETVENDOR ?= "-ostl"
TARGET_VENDOR = "${STM32MP_TARGETVENDOR}"
# Append distro name to each image name
IMAGE_BASENAME:append = "-${DISTRO}"
# Add image name for generated SDK and set default SDK install folder
SDK_NAME = "${IMAGE_LINK_NAME}-${SDK_ARCH}"
SDKPATHINSTALL = "/opt/st/${MACHINE}/${SDK_VERSION}"
# Don't include the DATE variable in the sstate package signatures
SDKPATHINSTALL[vardepsexclude] = "DATE"

View File

@@ -0,0 +1,110 @@
# Distribution definition for openstlinux
# from dev manual:
# Your configuration file needs to set the following variables:
#
# DISTRO_NAME [required]
# DISTRO_VERSION [required]
# DISTRO_FEATURES [required if creating from scratch]
# DISTRO_EXTRA_RDEPENDS [optional]
# DISTRO_EXTRA_RRECOMMENDS [optional]
# TCLIBC [required if creating from scratch]
LOCALCONF_VERSION = "2"
LAYER_CONF_VERSION ?= "7"
# =========================================================================
# Set default distro naming
# =========================================================================
require openstlinux-default-naming.inc
# =========================================================================
# Enable uninative support
# =========================================================================
require conf/distro/include/no-static-libs.inc
require conf/distro/include/yocto-uninative.inc
INHERIT += "uninative"
# =========================================================================
# DISTRO features
# =========================================================================
DISTRO_FEATURES = "alsa"
DISTRO_FEATURES += "argp"
DISTRO_FEATURES += "ext2"
DISTRO_FEATURES += "ext4"
DISTRO_FEATURES += "largefile"
DISTRO_FEATURES += "ipv4"
DISTRO_FEATURES += "ipv6"
DISTRO_FEATURES += "multiarch"
DISTRO_FEATURES += "pci"
DISTRO_FEATURES += "wifi"
DISTRO_FEATURES += "nfs"
DISTRO_FEATURES += "usbgadget"
DISTRO_FEATURES += "usbhost"
DISTRO_FEATURES += "xattr"
DISTRO_FEATURES += "zeroconf"
DISTRO_FEATURES += "bluetooth"
DISTRO_FEATURES += "bluez5"
# add support of gstreamer
DISTRO_FEATURES:append = " gstreamer "
# add support of optee
DISTRO_FEATURES:append = " optee "
# add support of splashscreen
DISTRO_FEATURES:append = " splashscreen "
# add support of wayland
DISTRO_FEATURES:append = " wayland pam "
# add support of X11
DISTRO_FEATURES:append = " x11 "
# add support of KDE (since OE thud version)
DISTRO_FEATURES:append = " kde "
# add support of systemd
DISTRO_FEATURES:append = " systemd "
# add support of efi
DISTRO_FEATURES:append = " efi "
# add support of InitRD installation package
DISTRO_FEATURES:append = " initrd "
# add support of autoresize through InitRD
DISTRO_FEATURES:append = " autoresize "
# add support of tpm2
#DISTRO_FEATURES:append = " tpm2 "
# Disabling pulseaudio
#DISTRO_FEATURES_BACKFILL_CONSIDERED += "pulseaudio"
DISTRO_FEATURES:append = "pulseaudio"
# Disabling sysvinit
DISTRO_FEATURES_BACKFILL_CONSIDERED += "sysvinit"
VIRTUAL-RUNTIME_init_manager = "systemd"
VIRTUAL-RUNTIME_initscripts = "systemd-compat-units"
# =========================================================================
# Preferred version
# =========================================================================
# v4l-utils
#PREFERRED_VERSION_v4l-utils ?= "1.6.2"
# =========================================================================
# IMAGE addons
# =========================================================================
IMAGE_LINGUAS = "en-us en-gb"
LICENSE_FLAGS_ACCEPTED += " non-commercial commercial"
# X11 addons
DISTRO_EXTRA_RDEPENDS:append = " ${@bb.utils.contains('DISTRO_FEATURES', 'x11', 'xf86-video-modesetting', '', d)} "
# INITRD addons to image
DISTRO_EXTRA_RRECOMMENDS:append = " ${@bb.utils.contains('COMBINED_FEATURES', 'initrd', '${INITRD_PACKAGE}', '', d)} "

View File

@@ -0,0 +1,16 @@
#@DESCRIPTION: ST default distro providers
PREFERRED_PROVIDER_libevent = "libevent-fb"
PREFERRED_PROVIDER_jpeg = "libjpeg-turbo"
PREFERRED_PROVIDER_jpeg-native = "libjpeg-turbo-native"
#--------------------------
# Preferred provider
# for openvt tools
PREFERRED_PROVIDER_console-tools = "kbd"
#--------------------------
# Preferred provider
# splash
SPLASH = "${@bb.utils.contains('COMBINED_FEATURES', 'splashscreen', 'psplash-drm', '', d)}"
PREFERRED_PROVIDER_virtual/psplash = "${@bb.utils.contains('COMBINED_FEATURES', 'splashscreen', 'psplash-drm', '', d)}"

View File

@@ -0,0 +1,61 @@
#@DESCRIPTION: ST default distro rules
# Set common distrooverrides for all ST distro
DISTROOVERRIDES =. "openstlinuxcommon:"
#--------------------------
# Mirror settings
#
YOCTOPROJECT_MIRROR_URL = "http://downloads.yoctoproject.org/mirror/sources/"
MIRRORS =+ "\
ftp://.*/.* ${YOCTOPROJECT_MIRROR_URL} \n \
http://.*/.* ${YOCTOPROJECT_MIRROR_URL} \n \
https://.*/.* ${YOCTOPROJECT_MIRROR_URL} \n"
INHERIT += "own-mirrors"
SOURCE_MIRROR_URL ??= "${YOCTOPROJECT_MIRROR_URL}"
#--------------------------
# The CONNECTIVITY_CHECK_URI's are used to test whether we can succesfully
# fetch from the network (and warn you if not).
# URI's to check can be set in the CONNECTIVITY_CHECK_URIS variable
# using the same syntax as for SRC_URI. If the variable is not set
# the check is skipped
#
CONNECTIVITY_CHECK_URIS ?= "https://www.example.com/"
#--------------------------
# OELAYOUT_ABI allows us to notify users when the format of TMPDIR changes in
# an incompatible way. Such changes should usually be detailed in the commit
# that breaks the format and have been previously discussed on the mailing list
# with general agreement from the core team.
#
OELAYOUT_ABI = "12"
#--------------------------
# Add the manifest and the licenses file on image generated
COPY_LIC_MANIFEST = "1"
#--------------------------
# Configure InitRD image installation package
INITRD_PACKAGE = "st-initrd"
# Init default InitRD image to ST resize image
INITRD_IMAGE ?= "${@bb.utils.contains('COMBINED_FEATURES', 'autoresize', 'st-image-resize-initrd', '', d)}"
#--------------------------
# Tune build config display
#
BUILDCFG_VARS += "DISTRO_CODENAME"
BUILDCFG_VARS += "${@'ACCEPT_EULA_' + d.getVar('MACHINE')}"
BUILDCFG_VARS += "GCCVERSION"
BUILDCFG_VARS += "PREFERRED_PROVIDER_virtual/kernel"
#--------------------------
# Configure build info file for rootfs availability
#
DISTRO_EXTRA_RDEPENDS:append = " build-info-openstlinux "
# Set IMAGE_BUILDINFO_FILE to configure the expected file name
IMAGE_BUILDINFO_FILE ?= "${sysconfdir}/build"
# Avoid conflict with image-buildinfo class that may feed same file on rootfs
INHERIT:remove = "${@bb.utils.contains('DISTRO_EXTRA_RDEPENDS', 'build-info-openstlinux', 'image-buildinfo', '', d)}"

View File

@@ -0,0 +1,49 @@
# Distro Layer configuration
# include and overwrite default poky distro
#@NAME: vrmpdv
#@DESCRIPTION: OpenSTLinux featuring vrpm Device - no X11, no Wayland
# require conf/distro/poky.conf
require include/st-default-distro-rules.inc
require include/st-default-distro-providers.inc
require include/openstlinux.inc
# =========================================================================
# Distribution definition for: st-openstlinux-weston-
# =========================================================================
DISTRO = "vrpmdv"
DISTRO_NAME = "VRPMDV-Linux"
DISTRO_VERSION = "1.0"
# MAINTAINER = "markus@malehr.de"
# =========================================================================
# Do not activate this because then the tsv file creation failed
# =========================================================================
# TARGET_VENDOR = "-markuslehr"
DISTRO_FEATURES_BACKFILL_CONSIDERED += "sysvinit"
VIRTUAL-RUNTIME_init_manager = "systemd"
VIRTUAL-RUNTIME_initscripts = "systemd-compat-units"
# Override these in poky based distros
VRPMDV_WEB_DEFAULT_EXTRA_RDEPENDS = "packagegroup-core-boot"
VRPMDV_WEB_DEFAULT_EXTRA_RRECOMMENDS = "kernel-module-af-packet"
# =========================================================================
# DISTRO features
# =========================================================================
DISTRO_EXTRA_RDEPENDS += " ${VRPMDV_WEB_DEFAULT_EXTRA_RDEPENDS}"
DISTRO_EXTRA_RRECOMMENDS += " ${VRPMDV_WEB_DEFAULT_EXTRA_RRECOMMENDS}"
# DISTRO_FEATURES ?= " ${DISTRO_FEATURES_LIBC} "
DISTRO_FEATURES ?= "${DISTRO_FEATURES_DEFAULT} ${DISTRO_FEATURES_LIBC} ${POKY_DEFAULT_DISTRO_FEATURES}"
DISTRO_FEATURES:remove = " wayland x11 "
DISTRO_FEATURES:remove = " x11 "

View File

@@ -0,0 +1,26 @@
DISTRO ?= "vrmpdv"
# We have a conf and classes directory, add to BBPATH
BBPATH .= ":${LAYERDIR}"
# We have recipes-* directories, add to BBFILES
BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
${LAYERDIR}/recipes-*/*/*.bbappend"
BBFILE_COLLECTIONS += "vrpmdv"
BBFILE_PATTERN_vrpmdv = "^${LAYERDIR}/"
BBFILE_PRIORITY_vrpmdv = "6"
LAYERDEPENDS_vrpmdv = "core"
LAYERSERIES_COMPAT_vrpmdv = "mickledore"
# MACHINE_EXTRA_RDEPENDS += "vrpmdv-monitoring-driver"
#IMAGE_INSTALL += "nginx"
#IMAGE_INSTALL += "python3-gunicorn"
#IMAGE_INSTALL += "python3-flask"
#IMAGE_INSTALL += "python3-flask-cors"
#IMAGE_INSTALL += "python3-cors"
IMAGE_INSTALL += "vrpmdv-rtservice"
# IMAGE_INSTALL += "vrpmdv-py-rtservice"
IMAGE_INSTALL += "vrpmdv-web"
#IMAGE_INSTALL += "vrpmdv-monitoring-driver"

View File

@@ -0,0 +1,2 @@
MACHINE ??= "vrmp-device"
DISTRO ?= "vrmpdv-web"

View File

@@ -0,0 +1,69 @@
# LAYER_CONF_VERSION is increased each time build/conf/bblayers.conf
# changes incompatibly
LCONF_VERSION = "7"
BBPATH = "${TOPDIR}"
BBFILES ?= ""
OEROOT := "${@os.path.abspath(os.path.dirname(d.getVar('FILE')) + '/../..')}"
#--------------------------
# Layers var definition for buildsystem
#
OPENEMBEDDED = "${OPENEMBEDDED_NON_REMOVABLE}"
OPENEMBEDDED_NON_REMOVABLE = "${OEROOT}/layers/openembedded-core/meta"
POKY = "${POKY_NON_REMOVABLE}"
POKY += "${OEROOT}/layers/poky/meta-yocto-bsp"
POKY_NON_REMOVABLE = "${OEROOT}/layers/poky/meta"
#--------------------------
#
BASELAYERS ?= " \
${OEROOT}/layers/meta-openembedded/meta-gnome \
${OEROOT}/layers/meta-openembedded/meta-initramfs \
${OEROOT}/layers/meta-openembedded/meta-multimedia \
${OEROOT}/layers/meta-openembedded/meta-networking \
${OEROOT}/layers/meta-openembedded/meta-webserver \
${OEROOT}/layers/meta-openembedded/meta-filesystems \
${OEROOT}/layers/meta-openembedded/meta-perl \
${OEROOT}/layers/meta-st/meta-st-openstlinux/recipes-vrpmdv \
"
BBLAYERS_NON_REMOVABLE ?= " \
${@'${OPENEMBEDDED_NON_REMOVABLE}' if os.path.isfile('${OEROOT}/layers/openembedded-core/meta/conf/layer.conf') else '${POKY_NON_REMOVABLE}'} \
"
BSPLAYER ?= " \
${@'${OEROOT}/layers/meta-st/meta-st-cannes2' if os.path.isfile('${OEROOT}/layers/meta-st/meta-st-cannes2/conf/layer.conf') else ''} \
${@'${OEROOT}/layers/meta-st/meta-st-stm32mp' if os.path.isfile('${OEROOT}/layers/meta-st/meta-st-stm32mp/conf/layer.conf') else ''} \
"
ADDONSLAYERS = ""
# linaro
ADDONSLAYERS += "${@'${OEROOT}/layers/meta-linaro/meta-linaro' if os.path.isfile('${OEROOT}/layers/meta-linaro/meta-linaro/conf/layer.conf') else ''}"
ADDONSLAYERS += "${@'${OEROOT}/layers/meta-linaro/meta-linaro-toolchain' if os.path.isfile('${OEROOT}/layers/meta-linaro/meta-linaro-toolchain/conf/layer.conf') else ''}"
ADDONSLAYERS += "${@'${OEROOT}/layers/meta-openembedded/meta-networking' if os.path.isfile('${OEROOT}/layers/meta-linaro/meta-linaro/conf/layer.conf') else ''}"
# Qt5
ADDONSLAYERS += "${@'${OEROOT}/layers/meta-qt5' if os.path.isfile('${OEROOT}/layers/meta-qt5/conf/layer.conf') else ''}"
# Security layer
ADDONSLAYERS += " \
${@'${OEROOT}/layers/meta-security \
${OEROOT}/layers/meta-security/meta-tpm' \
if os.path.isfile('${OEROOT}/layers/meta-security/conf/layer.conf') else ''} \
"
# specific to framework
FRAMEWORKLAYERS += "${@'${OEROOT}/layers/meta-st/meta-st-openstlinux' if os.path.isfile('${OEROOT}/layers/meta-st/meta-st-openstlinux/conf/layer.conf') else ''}"
# add BSP layer
BBLAYERS += " \
${BASELAYERS} \
${BSPLAYER} \
${ADDONSLAYERS} \
${FRAMEWORKLAYERS} \
${@'${OPENEMBEDDED}' if os.path.isfile('${OEROOT}/layers/openembedded-core/meta/conf/layer.conf') else '${POKY}'} \
"

View File

@@ -0,0 +1,9 @@
Available images for OpenSTLinux layers are:
- Official OpenSTLinux images:
st-image-weston - OpenSTLinux weston image with basic Wayland support (if enable in distro)
- Other OpenSTLinux images:
- Supported images:
st-image-core - OpenSTLinux core image

View File

@@ -0,0 +1,318 @@
#
# This file is your local configuration file and is where all local user settings
# are placed. The comments in this file give some guide to the options a new user
# to the system might want to change but pretty much any configuration option can
# be set in this file. More adventurous users can look at
# local.conf.sample.extended which contains other examples of configuration which
# can be placed in this file but new users likely won't need any of them
# initially. There's also site.conf.sample which contains examples of site specific
# information such as proxy server addresses.
#
# Lines starting with the '#' character are commented out and in some cases the
# default values are provided as comments to show people example syntax. Enabling
# the option is a question of removing the # character and making any change to the
# variable as required.
#
# Machine Selection
#
# You need to select a specific machine to target the build with. There are a selection
# of emulated machines available which can boot and run in the QEMU emulator:
#
#MACHINE ?= "qemuarm"
#MACHINE ?= "qemuarm64"
#MACHINE ?= "qemumips"
#MACHINE ?= "qemumips64"
#MACHINE ?= "qemuppc"
#MACHINE ?= "qemux86"
#MACHINE ?= "qemux86-64"
#
# This sets the default machine to be qemux86-64 if no other machine is selected:
MACHINE ??= "qemux86-64"
# These are some of the more commonly used values. Looking at the files in the
# meta/conf/machine directory, or the conf/machine directory of any additional layers
# you add in will show all the available machines.
#
# Default policy config
#
# The distribution setting controls which policy settings are used as defaults.
# The default value is fine for general Yocto project use, at least initially.
# Ultimately when creating custom policy, people will likely end up subclassing
# these defaults.
#
# This sets the default distribution to be nodistro if no other distribution is selected:
DISTRO ??= "nodistro"
#
# Where to place downloads
#
# During a first build the system will download many different source code tarballs
# from various upstream projects. This can take a while, particularly if your network
# connection is slow. These are all stored in DL_DIR. When wiping and rebuilding you
# can preserve this directory to speed up this part of subsequent builds. This directory
# is safe to share between multiple builds on the same machine too.
#
# The default is a downloads directory under TOPDIR which is the build directory.
#
#DL_DIR ?= "${TOPDIR}/downloads"
#
# Where to place shared-state files
#
# BitBake has the capability to accelerate builds based on previously built output.
# This is done using "shared state" files which can be thought of as cache objects
# and this option determines where those files are placed.
#
# You can wipe out TMPDIR leaving this directory intact and the build would regenerate
# from these files if no changes were made to the configuration. If changes were made
# to the configuration, only shared state files where the state was still valid would
# be used (done using checksums).
#
# The default is a sstate-cache directory under TOPDIR.
#
#SSTATE_DIR ?= "${TOPDIR}/sstate-cache"
#
# Where to place the build output
#
# This option specifies where the bulk of the building work should be done and
# where BitBake should place its temporary files and output. Keep in mind that
# this includes the extraction and compilation of many applications and the toolchain
# which can use Gigabytes of hard disk space.
#
# The default is a tmp directory under TOPDIR.
#
#TMPDIR = "${TOPDIR}/tmp"
#
# Package Management configuration
#
# This variable lists which packaging formats to enable. Multiple package backends
# can be enabled at once and the first item listed in the variable will be used
# to generate the root filesystems.
# Options are:
# - 'package_deb' for debian style deb files
# - 'package_ipk' for ipk files are used by opkg (a debian style embedded package manager)
# - 'package_rpm' for rpm style packages
# E.g.: PACKAGE_CLASSES ?= "package_rpm package_deb package_ipk"
# We default to ipk:
PACKAGE_CLASSES ?= "package_ipk"
#
# SDK target architecture
#
# This variable specifies the architecture to build SDK items for and means
# you can build the SDK packages for architectures other than the machine you are
# running the build on (i.e. building i686 packages on an x86_64 host).
# Supported values are i686, x86_64, aarch64
#SDKMACHINE ?= "i686"
#
# Extra image configuration defaults
#
# The EXTRA_IMAGE_FEATURES variable allows extra packages to be added to the generated
# images. Some of these options are added to certain image types automatically. The
# variable can contain the following options:
# "dbg-pkgs" - add -dbg packages for all installed packages
# (adds symbol information for debugging/profiling)
# "src-pkgs" - add -src packages for all installed packages
# (adds source code for debugging)
# "dev-pkgs" - add -dev packages for all installed packages
# (useful if you want to develop against libs in the image)
# "ptest-pkgs" - add -ptest packages for all ptest-enabled packages
# (useful if you want to run the package test suites)
# "tools-sdk" - add development tools (gcc, make, pkgconfig etc.)
# "tools-debug" - add debugging tools (gdb, strace)
# "eclipse-debug" - add Eclipse remote debugging support
# "tools-profile" - add profiling tools (oprofile, lttng, valgrind)
# "tools-testapps" - add useful testing tools (ts_print, aplay, arecord etc.)
# "debug-tweaks" - make an image suitable for development
# e.g. ssh root access has a blank password
# There are other application targets that can be used here too, see
# meta/classes-recipe/image.bbclass and
# meta/classes-recipe/core-image.bbclass for more details.
# We default to enabling the debugging tweaks.
EXTRA_IMAGE_FEATURES ?= "debug-tweaks"
#
# Additional image features
#
# The following is a list of additional classes to use when building images which
# enable extra features. Some available options which can be included in this variable
# are:
# - 'buildstats' collect build statistics
USER_CLASSES ?= "buildstats"
#
# Runtime testing of images
#
# The build system can test booting virtual machine images under qemu (an emulator)
# after any root filesystems are created and run tests against those images. It can also
# run tests against any SDK that are built. To enable this uncomment these lines.
# See meta/classes-recipe/test{image,sdk}.bbclass for further details.
#IMAGE_CLASSES += "testimage testsdk"
#TESTIMAGE_AUTO:qemuall = "1"
#
# Interactive shell configuration
#
# Under certain circumstances the system may need input from you and to do this it
# can launch an interactive shell. It needs to do this since the build is
# multithreaded and needs to be able to handle the case where more than one parallel
# process may require the user's attention. The default is iterate over the available
# terminal types to find one that works.
#
# Examples of the occasions this may happen are when resolving patches which cannot
# be applied, to use the devshell or the kernel menuconfig
#
# Supported values are auto, gnome, xfce, rxvt, screen, konsole (KDE 3.x only), none
# Note: currently, Konsole support only works for KDE 3.x due to the way
# newer Konsole versions behave
#OE_TERMINAL = "auto"
# By default disable interactive patch resolution (tasks will just fail instead):
PATCHRESOLVE = "noop"
#
# Disk Space Monitoring during the build
#
# Monitor the disk space during the build. If there is less that 1GB of space or less
# than 100K inodes in any key build location (TMPDIR, DL_DIR, SSTATE_DIR), gracefully
# shutdown the build. If there is less than 100MB or 1K inodes, perform a hard halt
# of the build. The reason for this is that running completely out of space can corrupt
# files and damages the build in ways which may not be easily recoverable.
# It's necessary to monitor /tmp, if there is no space left the build will fail
# with very exotic errors.
BB_DISKMON_DIRS ??= "\
STOPTASKS,${TMPDIR},1G,100K \
STOPTASKS,${DL_DIR},1G,100K \
STOPTASKS,${SSTATE_DIR},1G,100K \
STOPTASKS,/tmp,100M,100K \
HALT,${TMPDIR},100M,1K \
HALT,${DL_DIR},100M,1K \
HALT,${SSTATE_DIR},100M,1K \
HALT,/tmp,10M,1K"
#
# Shared-state files from other locations
#
# As mentioned above, shared state files are prebuilt cache data objects which can be
# used to accelerate build time. This variable can be used to configure the system
# to search other mirror locations for these objects before it builds the data itself.
#
# This can be a filesystem directory, or a remote url such as https or ftp. These
# would contain the sstate-cache results from previous builds (possibly from other
# machines). This variable works like fetcher MIRRORS/PREMIRRORS and points to the
# cache locations to check for the shared objects.
# NOTE: if the mirror uses the same structure as SSTATE_DIR, you need to add PATH
# at the end as shown in the examples below. This will be substituted with the
# correct path within the directory structure.
#SSTATE_MIRRORS ?= "\
#file://.* https://someserver.tld/share/sstate/PATH;downloadfilename=PATH \
#file://.* file:///some/local/dir/sstate/PATH"
#
# Qemu configuration
#
# By default native qemu will build with a builtin VNC server where graphical output can be
# seen. The line below enables the SDL UI frontend too.
PACKAGECONFIG:append:pn-qemu-system-native = " sdl"
# By default libsdl2-native will be built, if you want to use your host's libSDL instead of
# the minimal libsdl built by libsdl2-native then uncomment the ASSUME_PROVIDED line below.
#ASSUME_PROVIDED += "libsdl2-native"
# You can also enable the Gtk UI frontend, which takes somewhat longer to build, but adds
# a handy set of menus for controlling the emulator.
#PACKAGECONFIG:append:pn-qemu-system-native = " gtk+"
#
# Hash Equivalence
#
# Enable support for automatically running a local hash equivalence server and
# instruct bitbake to use a hash equivalence aware signature generator. Hash
# equivalence improves reuse of sstate by detecting when a given sstate
# artifact can be reused as equivalent, even if the current task hash doesn't
# match the one that generated the artifact.
#
# A shared hash equivalent server can be set with "<HOSTNAME>:<PORT>" format
#
#BB_HASHSERVE = "auto"
#BB_SIGNATURE_HANDLER = "OEEquivHash"
#
# Memory Resident Bitbake
#
# Bitbake's server component can stay in memory after the UI for the current command
# has completed. This means subsequent commands can run faster since there is no need
# for bitbake to reload cache files and so on. Number is in seconds, after which the
# server will shut down.
#
#BB_SERVER_TIMEOUT = "60"
# CONF_VERSION is increased each time build/conf/ changes incompatibly and is used to
# track the version of this file when it was generated. This can safely be ignored if
# this doesn't mean anything to you.
CONF_VERSION = "2"
# =========================================================================
# ST SPecific
# =========================================================================
#
# Set GLIBC_GENERATE_LOCALES to the locales you wish to generate should you not
# wish to perform the time-consuming step of generating all LIBC locales.
# NOTE: If removing en_US.UTF-8 you will also need to uncomment, and set
# appropriate value for IMAGE_LINGUAS.
# WARNING: this may break localisation!
GLIBC_GENERATE_LOCALES = "en_GB.UTF-8 en_US.UTF-8"
IMAGE_LINGUAS ?= "en-gb"
# Additional image generation features
#
# The following is a list of classes to import to use in the generation of images
# currently an example class is image_types_uboot
# IMAGE_CLASSES = " image_types_uboot"
# Support of devshell
INHERIT += "devshell"
# Remove the old image before the new one generated to save disk space
RM_OLD_IMAGE = "1"
# Nice debug data
INHERIT += "buildhistory"
BUILDHISTORY_COMMIT = "1"
# Clean up working directory after build
INHERIT += "rm_work"
# To generate debug image with all symbol
#IMAGE_GEN_DEBUGFS = "1"
# force the usage of debian package
PACKAGE_CLASSES = "package_deb"
# To enable archiver for recipes that are configured
#ST_ARCHIVER_ENABLE = "1"
# Setup environment for builds binary reproducibility
REPRODUCIBLE_TIMESTAMP_ROOTFS = ""
# Setup eSDK
SDK_EXT_TYPE="minimal"
SDK_INCLUDE_TOOLCHAIN="1"
# Enable PR server to avoid version-going-backward issue
PRSERV_HOST = "localhost:0"
# =========================================================================
# Configure STM32MP default version to github
# =========================================================================
#STM32MP_SOURCE_SELECTION:pn-linux-stm32mp = "github"
#STM32MP_SOURCE_SELECTION:pn-optee-os-stm32mp = "github"
#STM32MP_SOURCE_SELECTION:pn-tf-a-stm32mp = "github"
#STM32MP_SOURCE_SELECTION:pn-u-boot-stm32mp = "github"

View File

@@ -0,0 +1,76 @@
{
"files.associations": {
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"csignal": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"any": "cpp",
"array": "cpp",
"atomic": "cpp",
"strstream": "cpp",
"bit": "cpp",
"*.tcc": "cpp",
"bitset": "cpp",
"chrono": "cpp",
"codecvt": "cpp",
"compare": "cpp",
"complex": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstdint": "cpp",
"deque": "cpp",
"forward_list": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"ranges": "cpp",
"semaphore": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"cfenv": "cpp",
"cinttypes": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"valarray": "cpp",
"variant": "cpp"
}
}

View File

@@ -0,0 +1,11 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name vrpmdv-web.local;
location / {
proxy_pass http://unix:/run/vrpmdv_web.sock;
}
}

View File

@@ -0,0 +1,11 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name vrpmdv-web.local;
location / {
proxy_pass http://unix:/run/vrpmdv_web.sock;
}
}

View File

@@ -0,0 +1,14 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/vrpmdv.local;
index index.html index.htm;
server_name vrpmdv.local;
location / {
try_files $uri $uri/ =404;
}
}

View File

@@ -0,0 +1,29 @@
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"
DEPENDS = "systemd"
SRC_URI += " \
file://vrpmdv.local \
file://vrpmdv-setup.local \
"
SYSTEMD_AUTO_ENABLE = "enable"
SYSTEMD_SERVICE:${PN} = "nginx.service"
EXTRA_OECONF = "\
--without-http_rewrite_module \
"
# --without-pcre \
do_install:append () {
install -d ${D}${sysconfdir}/nginx/sites-available
install -D -m 644 ${WORKDIR}/vrpmdv.local ${D}${sysconfdir}nginx/sites-available/
install -D -m 644 ${WORKDIR}/vrpmdv-setup.local ${D}${sysconfdir}/nginx/sites-available/
rm -rf ${D}${systemd_unitdir}/system/nginx/sites-enabled/default_server
ln -s ${sysconfdir}/nginx/sites-available/vrpmdv-setup.local ${D}${sysconfdir}/nginx/sites-enabled/
}

View File

@@ -0,0 +1,26 @@
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"
DEPENDS = "systemd"
SRC_URI += " \
file://vrpmdv-web.local \
"
SYSTEMD_AUTO_ENABLE = "enable"
SYSTEMD_SERVICE:${PN} = "nginx.service"
EXTRA_OECONF = "\
--without-http_rewrite_module \
"
do_install:append () {
install -d ${D}${sysconfdir}/nginx/sites-available
install -D -m 644 ${WORKDIR}/vrpmdv-web.local ${D}${sysconfdir}/nginx/sites-available/
# rm -rf ${D}${systemd_unitdir}/system/nginx/sites-enabled/default_server
rm -rf ${D}${sysconfdir}/nginx/sites-enabled/default_server
ln -s ${sysconfdir}/nginx/sites-available/vrpmdv-web.local ${D}${sysconfdir}/nginx/sites-enabled/
}

View File

@@ -0,0 +1,20 @@
[Unit]
Description=gunicorn daemon for vrpmdv_web
Requires=vrpmdv_web.socket
After=network.target
[Service]
User=root
Group=www-data
Type=notify
RuntimeDirectory=gunicorn
WorkingDirectory=/var/www/vrpmdv-web.local/
ExecStart=/usr/bin/gunicorn vrpmdvserver:app
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=gunicorn socket for vrpmdv_web
[Socket]
ListenStream=/run/vrpmdv_web.sock
SocketUser=www
[Install]
WantedBy=sockets.target

View File

@@ -0,0 +1,35 @@
SUMMARY = "Install and start a systemd services"
SECTION = "mlgunicorn"
LICENSE = "CLOSED"
inherit systemd
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
SRC_URI += "file://vrpmdv_setup_page.service"
SRC_URI += "file://vrpmdv_setup_page.socket"
SYSTEMD_AUTO_ENABLE = "enable"
SYSTEMD_SERVICE:${PN} = "vrpmdv_setup_page.service vrpmdv_setup_page.socket "
FILES:${PN} += "${systemd_system_unitdir}"
FILES:${PN} += "${systemd_system_unitdir}/vrpmdv_setup_page.service"
FILES:${PN} += "${systemd_system_unitdir}/vrpmdv_setup_page.socket"
do_install:append () {
install -d ${D}/${systemd_system_unitdir}
install -m 0644 ${WORKDIR}/vrpmdv_setup_page.service ${D}/${systemd_system_unitdir}
install -m 0644 ${WORKDIR}/vrpmdv_setup_page.socket ${D}/${systemd_system_unitdir}
install -d ${D}${sysconfdir}/systemd/system/multi-user.target.wants/
install -d ${D}${sysconfdir}/systemd/system/sockets.target.wants/
ln -s ${systemd_system_unitdir}/datalogger_setup_page.socket ${D}${sysconfdir}/systemd/system/sockets.target.wants/vrpmdv_setup_page.socket
ln -s ${systemd_system_unitdir}/datalogger_setup_page.service ${D}${sysconfdir}/systemd/system/multi-user.target.wants/vrpmdv_setup_page.service
}
REQUIRED_DISTRO_FEATURES= "systemd"

View File

@@ -0,0 +1,35 @@
SUMMARY = "Install and start a systemd services"
SECTION = "mlgunicorn"
LICENSE = "CLOSED"
inherit systemd
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
SRC_URI += "file://vrpmdv_web.service"
SRC_URI += "file://vrpmdv_web.socket"
SYSTEMD_AUTO_ENABLE = "enable"
SYSTEMD_SERVICE:${PN} = "vrpmdv_web.service vrpmdv_web.socket "
FILES:${PN} += "${systemd_system_unitdir}"
FILES:${PN} += "${systemd_system_unitdir}/vrpmdv_web.service"
FILES:${PN} += "${systemd_system_unitdir}/vrpmdv_web.socket"
do_install:append () {
install -d ${D}/${systemd_system_unitdir}
install -m 0644 ${WORKDIR}/vrpmdv_web.service ${D}/${systemd_system_unitdir}
install -m 0644 ${WORKDIR}/vrpmdv_web.socket ${D}/${systemd_system_unitdir}
install -d ${D}${sysconfdir}/systemd/system/multi-user.target.wants/
install -d ${D}${sysconfdir}/systemd/system/sockets.target.wants/
ln -s ${systemd_system_unitdir}/vrpmdv_web.socket ${D}${sysconfdir}/systemd/system/sockets.target.wants/vrpmdv_web.socket
ln -s ${systemd_system_unitdir}/vrpmdv_web.service ${D}${sysconfdir}/systemd/system/multi-user.target.wants/vrpmdv_web.service
}
REQUIRED_DISTRO_FEATURES= "systemd"

View File

@@ -0,0 +1,8 @@
{
"files.associations": {
"rpmsg.h": "c",
"types.h": "c",
"ioctl.h": "c",
"wait.h": "c"
}
}

View File

@@ -0,0 +1 @@
,markus,U2204VM,02.05.2024 10:15,file:///home/markus/.config/libreoffice/4;

View File

@@ -0,0 +1 @@
SUBSYSTEM=="misc", KERNEL=="mon-datafile", GROUP="dialout", MODE="0666"

View File

@@ -0,0 +1,26 @@
# Makefile for VRPMDV Monitoring Datafile
obj-m := vrpmdv-mon-datafile.o
SRC := $(shell pwd)
all:
$(MAKE) -C $(KERNEL_SRC) M=$(SRC)
modules_install:
$(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules_install
clean:
rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c
rm -f Module.markers Module.symvers modules.order
rm -rf .tmp_versions Modules.symvers
# obj-m = vrpmdv-monitoring-controler.o
# KVERSION = $(shell uname -r)
# all:
# make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules
# clean:
# make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean

View File

@@ -0,0 +1,116 @@
include <condition_variable>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// wait until main() sends data
std::unique_lock lk(m);
cv.wait(lk, []{ return ready; });
// after the wait, we own the lock
std::cout << "Worker thread is processing data\n";
data += " after processing";
// send data back to main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
// manual unlocking is done before notifying, to avoid waking up
// the waiting thread only to block again (see notify_one for details)
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
data = "Example data";
// send data to the worker thread
{
std::lock_guard lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// wait for the worker
{
std::unique_lock lk(m);
cv.wait(lk, []{ return processed; });
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
--------------------
// waiting for timeout after 5 seconds
std::chrono::seconds timeoutPeriod = 5;
auto timePoint = std::chrono::system_clock::now() + timeoutPeriod;
std::unique_lock<std::mutex> uLock(myDataMutex);
while(!DataAreReadyForProcessing())
{
if (myCondVar.wait_until(uLock, timePoint) //<##
== std::cv_status::timeout)
{
// data conditions where not fulfilled within
// the time period; e.g. do some error handling
break;
}
}
--
if (myCondVar.wait_for(uLock, timeoutPeriod,
DataAreReadyForProcessing))
{
// data conditions where fulfilled
// regular processing
}
else // timeout occured, conditions are not fulfilled
{
// e.g. do some error handling
}
-------------
static int rpmsg_sample_probe(struct rpmsg_device *rpdev)
{
int ret;
struct instance_data *idata;
dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
rpdev->src, rpdev->dst);
idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
if (!idata)
return -ENOMEM;
dev_set_drvdata(&rpdev->dev, idata);
/* send a message to our remote processor */
ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
if (ret) {
dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
return 0;

View File

@@ -0,0 +1,750 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) STMicroelectronics 2019 - All Rights Reserved
* Author: Jean-Philippe Romain <jean-philippe.romain@st.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#include <linux/miscdevice.h>
#include <linux/eventfd.h>
#include <linux/of_platform.h>
#include <linux/list.h>
#include <linux/types.h>
#define RPMSG_SDB_DRIVER_VERSION "1.0"
/*
* Static global variables
*/
static const char rpmsg_sdb_driver_name[] = "vrpmdv-mon-datafile";
static int LastBufferId;
struct rpmsg_sdb_ioctl_set_efd {
int bufferId, eventfd;
};
struct rpmsg_sdb_ioctl_get_data_size {
int bufferId;
uint32_t size;
};
/* ioctl numbers */
/* _IOW means userland is writing and kernel is reading */
/* _IOR means userland is reading and kernel is writing */
/* _IOWR means userland and kernel can both read and write */
#define RPMSG_SDB_IOCTL_SET_EFD _IOW('R', 0x00, struct rpmsg_sdb_ioctl_set_efd *)
#define RPMSG_SDB_IOCTL_GET_DATA_SIZE _IOWR('R', 0x01, struct rpmsg_sdb_ioctl_get_data_size *)
struct sdb_buf_t {
int index; /* index of buffer */
size_t size; /* buffer size */
size_t writing_size; /* size of data written by copro */
dma_addr_t paddr; /* physical address*/
void *vaddr; /* virtual address */
void *uaddr; /* mapped address for userland */
struct eventfd_ctx *efd_ctx; /* eventfd context */
struct list_head buflist; /* reference in the buffers list */
};
struct rpmsg_sdb_t {
struct mutex mutex; /* mutex to protect the ioctls */
struct miscdevice mdev; /* misc device ref */
struct rpmsg_device *rpdev; /* handle rpmsg device */
struct list_head buffer_list; /* buffer instances list */
};
struct device *rpmsg_sdb_dev;
struct vRCMDeviceData {
uint32_t packageNo; //current package Number
uint32_t packageCount; //complete package Number
uint32_t dataQuantity; //number of uint32_t in data
uint32_t data[]; //the data
};
static int rpmsg_sdb_format_txbuf_string(struct sdb_buf_t *buffer, char *bufinfo_str, size_t bufinfo_str_size)
{
pr_info("rpmsg_sdb(%s): Buffer index:%d, addr:%08x, size:%08x\n",
__func__,
buffer->index,
buffer->paddr,
buffer->size);
return snprintf(bufinfo_str, bufinfo_str_size, "B%dA%08xL%08x", buffer->index, buffer->paddr, buffer->size);
}
// static long rpmsg_sdb_decode_rxbuf_string(char *rxbuf_str, int *buffer_id, size_t *size)
// {
// int ret = 0;
// char *sub_str;
// long bsize;
// long bufid;
// const char delimiter[2] = {'L','\0'};
// //__u32* data = (__u32*) rxbuf_str;
// struct vRCMDeviceData* pdata = (struct vRCMDeviceData*) rxbuf_str;
// //pr_info("rpmsg_sdb(%s): rxbuf_str:%s\n", __func__, rxbuf_str);
// pr_info("rpmsg_sdb(%s): packageno:%d\n", __func__, pdata->packageNo);
// pr_info("rpmsg_sdb(%s): packageCount:%d\n", __func__, pdata->packageCount);
// pr_info("rpmsg_sdb(%s): dataquantity:%d\n", __func__, pdata->dataQuantity);
// pr_info("rpmsg_sdb(%s): data:%d\n", __func__, (pdata->data)[0]);
// // pr_info("rpmsg_sdb(%s): rxbuf_str:%d\n", __func__, data[0]);
// // pr_info("rpmsg_sdb(%s): rxbuf_str:%d\n", __func__, data[1]);
// // pr_info("rpmsg_sdb(%s): rxbuf_str:%d\n", __func__, data[2]);
// /* Get first part containing the buffer id */
// // sub_str = strsep(&rxbuf_str, delimiter);
// // // pr_info("rpmsg_sdb(%s): sub_str:%s\n", __func__, sub_str);
// // /* Save Buffer id and size: template BxLyyyyyyyy*/
// // ret = kstrtol(&sub_str[1], 10, &bufid);
// // if (ret < 0) {
// // // pr_info("rpmsg_sdb(ERROR): Extract of buffer id failed(%d)", ret);
// // goto out;
// // }
// // ret = kstrtol(&rxbuf_str[2], 16, &bsize);
// // if (ret < 0) {
// // // pr_info("rpmsg_sdb(ERROR): Extract of buffer size failed(%d)", ret);
// // goto out;
// // }
// // *size = (size_t)bsize;
// // *buffer_id = (int)bufid;
// // out:
// return ret;
// }
static int rpmsg_sdb_send_buf_info(struct rpmsg_sdb_t *rpmsg_sdb, struct sdb_buf_t *buffer)
{
int count = 0, ret = 0;
const unsigned char *tbuf;
char mybuf[32];
int msg_size;
struct rpmsg_device *_rpdev;
pr_info("rpmsg_sdb(%s): start rpmsg_sdb_send_buf_info\n", __func__);
_rpdev = rpmsg_sdb->rpdev;
msg_size = rpmsg_get_mtu(_rpdev->ept);
pr_info("rpmsg_sdb(%s): checked msg site:%d\n", __func__, msg_size);
if (msg_size < 0)
return msg_size;
pr_info("rpmsg_sdb(%s): Call rpmsg_sdb_format_txbuf_string\n", __func__);
count = rpmsg_sdb_format_txbuf_string(buffer, mybuf, 32);
tbuf = &mybuf[0];
do {
/* send a message to our remote processor */
pr_info("rpmsg_sdb(%s): send a message to our remote processor\n", __func__);
ret = rpmsg_send(_rpdev->ept, (void *)tbuf,
count > msg_size ? msg_size : count);
if (ret) {
dev_err(&_rpdev->dev, "rpmsg_send failed: %d\n", ret);
pr_info("rpmsg_sdb(%s): error by send a message to our remote processor\n", __func__);
return ret;
}
else {
pr_info("rpmsg_sdb(%s): send succesfully a message to our remote processor\n", __func__);
}
if (count > msg_size) {
count -= msg_size;
tbuf += msg_size;
} else {
count = 0;
}
} while (count > 0);
return count;
}
static int rpmsg_sdb_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long vsize = vma->vm_end - vma->vm_start;
unsigned long size = PAGE_ALIGN(vsize);
unsigned long NumPages = size >> PAGE_SHIFT;
unsigned long align = get_order(size);
pgprot_t prot = pgprot_noncached(vma->vm_page_prot);
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *_buffer;
pr_info("rpmsg_sdb(%s): start rpmsg_sdb_mmap\n", __func__);
if (align > CONFIG_CMA_ALIGNMENT)
align = CONFIG_CMA_ALIGNMENT;
if (rpmsg_sdb_dev == NULL)
return -ENOMEM;
pr_info("rpmsg_sdb(%s): start rpmsg_sdb_mmap - coherent_dma_mask\n", __func__);
rpmsg_sdb_dev->coherent_dma_mask = DMA_BIT_MASK(32);
rpmsg_sdb_dev->dma_mask = &rpmsg_sdb_dev->coherent_dma_mask;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Field the last buffer entry which is the last one created */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
pr_info("rpmsg_sdb(%s): start rpmsg_sdb_mmap - bufferlist not empty\n", __func__);
_buffer = list_last_entry(&_rpmsg_sdb->buffer_list,
struct sdb_buf_t, buflist);
_buffer->uaddr = NULL;
_buffer->size = NumPages * PAGE_SIZE;
_buffer->writing_size = -1;
_buffer->vaddr = dma_alloc_coherent(rpmsg_sdb_dev,
_buffer->size,
&_buffer->paddr,
GFP_KERNEL);
if (!_buffer->vaddr) {
pr_info("rpmsg_sdb(ERROR): Memory allocation issue\n");
return -ENOMEM;
}
pr_info("rpmsg_sdb(%s): dma_alloc_coherent done - paddr[%d]:%x - vaddr[%d]:%p\n",
__func__,
_buffer->index,
_buffer->paddr,
_buffer->index,
_buffer->vaddr);
/* Get address for userland */
if (remap_pfn_range(vma, vma->vm_start,
(_buffer->paddr >> PAGE_SHIFT) + vma->vm_pgoff,
size, prot)) {
pr_info("rpmsg_sdb(%s): remap_pfn_range could not be done\n");
return -EAGAIN;
}
_buffer->uaddr = (void *)vma->vm_start;
pr_info("rpmsg_sdb(%s): _buffer->uaddr = %p\n", __func__, _buffer->uaddr);
/* Send information to remote proc */
pr_info("rpmsg_sdb(%s): Send information to remote proc\n", __func__);
rpmsg_sdb_send_buf_info(_rpmsg_sdb, _buffer);
} else {
dev_err(rpmsg_sdb_dev, "No existing buffer entry exist in the list !!!");
pr_debug("rpmsg_sdb(%s): No existing buffer entry exist in the list !!!\n", __func__);
return -EINVAL;
}
/* Increment for number of requested buffer */
LastBufferId++;
return 0;
}
/**
* rpmsg_sdb_open - Open Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_open(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Initialize the buffer list*/
pr_debug("rpmsg_sdb(%s): Init bufferlist", __func__);
pr_info("rpmsg_sdb(%s): start Init bufferlist", __func__);
INIT_LIST_HEAD(&_rpmsg_sdb->buffer_list);
pr_info("rpmsg_sdb(%s): success Init bufferlist", __func__);
mutex_init(&_rpmsg_sdb->mutex);
return 0;
}
/**
* rpmsg_sdb_close - Close Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_close(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *pos, *next;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
mutex_lock(&_rpmsg_sdb->mutex);
list_for_each_entry_safe(pos, next, &_rpmsg_sdb->buffer_list, buflist) {
/* Free the CMA allocation */
pr_info("rpmsg_sdb(%s): Free the CMA allocation 3", __func__);
dma_free_coherent(rpmsg_sdb_dev, pos->size, pos->vaddr, pos->paddr);
/* Remove the buffer from the list */
list_del(&pos->buflist);
/* Free the buffer */
kfree(pos);
}
mutex_unlock(&_rpmsg_sdb->mutex);
pr_info("rpmsg_sdb(%s): Free the CMA allocation 4 - done", __func__);
/* Reset LastBufferId */
LastBufferId = 0;
return 0;
}
/**
* rpmsg_sdb_ioctl - IOCTL
*
* @session: ibmvmc_file_session struct
* @cmd: cmd field
* @arg: Argument field
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static long rpmsg_sdb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int idx = 0;
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *buffer, *lastbuffer;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_ioctl_set_efd q_set_efd;
struct rpmsg_sdb_ioctl_get_data_size q_get_dat_size;
void __user *argp = (void __user *)arg;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
switch (cmd) {
case RPMSG_SDB_IOCTL_SET_EFD:
mutex_lock(&_rpmsg_sdb->mutex);
/* Get index from the last buffer in the list */
pr_info("mon-datafile: set EFD\n");
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
lastbuffer = list_last_entry(&_rpmsg_sdb->buffer_list, struct sdb_buf_t, buflist);
idx = lastbuffer->index;
/* Check last index was properly initiated*/
if (lastbuffer->vaddr == NULL) {
// pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - previous buffer was not allocated\n");
pr_info("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - previous buffer was not allocated\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EBADE;
}
/* increment this index for the next buffer creation*/
idx++;
}
if (copy_from_user(&q_set_efd, (struct rpmsg_sdb_ioctl_set_efd *)argp,
sizeof(struct rpmsg_sdb_ioctl_set_efd))) {
// pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - copy from user failed\n");
pr_info("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - copy from user failed\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EFAULT;
}
/* create a new buffer which will be added in the buffer list */
buffer = kmalloc(sizeof(struct sdb_buf_t), GFP_KERNEL);
buffer->index = idx;
buffer->vaddr = NULL;
buffer->efd_ctx = eventfd_ctx_fdget(q_set_efd.eventfd);
list_add_tail(&buffer->buflist, &_rpmsg_sdb->buffer_list);
pr_info("rpmsg_sdb(SUCCES): RPMSG_SDB_IOCTL_SET_EFD \n");
mutex_unlock(&_rpmsg_sdb->mutex);
break;
case RPMSG_SDB_IOCTL_GET_DATA_SIZE:
//pr_info("mon-datafile: get datasize\n");
if (copy_from_user(&q_get_dat_size, (struct rpmsg_sdb_ioctl_get_data_size *)argp,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy from user failed\n");
return -EFAULT;
}
/* Get the index of the requested buffer and then look-up in the buffer list*/
idx = q_get_dat_size.bufferId;
list_for_each(pos, &_rpmsg_sdb->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == idx) {
/* Get the writing size*/
q_get_dat_size.size = datastructureptr->writing_size;
break;
}
}
if (copy_to_user((struct rpmsg_sdb_ioctl_get_data_size *)argp, &q_get_dat_size,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy to user failed\n");
return -EFAULT;
}
/* Reset the writing size*/
datastructureptr->writing_size = -1;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct file_operations rpmsg_sdb_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = rpmsg_sdb_ioctl,
.mmap = rpmsg_sdb_mmap,
.open = rpmsg_sdb_open,
.release = rpmsg_sdb_close,
};
static int rpmsg_sdb_drv_cb(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src)
{
int ret = 0;
int buffer_id = 0;
size_t buffer_size;
char *rpmsg_RxBuf;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
int packLength = 0;
//pr_info("mon-datafile: receive msg from Copro: %s, len:%d\n", __func__, len);
// struct rpmsg_sdb_t {
// struct mutex mutex; /* mutex to protect the ioctls */
// struct miscdevice mdev; /* misc device ref */
// struct rpmsg_device *rpdev; /* handle rpmsg device */
// struct list_head buffer_list; /* buffer instances list */
// };
rpmsg_RxBuf = (char *)kmalloc(len+1, GFP_KERNEL);
memcpy(rpmsg_RxBuf, data, len);
rpmsg_RxBuf[len] = 0;
// ret = rpmsg_sdb_decode_rxbuf_string(rpmsg_RxBuf, &buffer_id, &buffer_size);
// if (ret < 0)
// goto out;
// if (buffer_id > LastBufferId) {
// ret = -EINVAL;
// goto out;
// }
struct vRCMDeviceData* pdata = (struct vRCMDeviceData*) rpmsg_RxBuf;
//pr_info("rpmsg_sdb(%s): rxbuf_str:%s\n", __func__, rxbuf_str);
// pr_info("rpmsg_sdb(%s): packageno:%d\n", __func__, pdata->packageNo);
// pr_info("rpmsg_sdb(%s): packageCount:%d\n", __func__, pdata->packageCount);
// pr_info("rpmsg_sdb(%s): dataquantity:%d\n", __func__, pdata->dataQuantity);
// pr_info("rpmsg_sdb(%s): data:%d\n", __func__, (pdata->data)[0]);
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpdev->dev);
if (len == 0) {
dev_err(rpmsg_sdb_dev, "(%s) Empty lenght requested\n", __func__);
return -EINVAL;
}
if (drv == NULL) {
dev_err(rpmsg_sdb_dev, "(%s) no driver found\n", __func__);
return -EINVAL;
}
//copy data to structure
//1. get buffer
// struct vRCMDeviceData* pdata = (struct vRCMDeviceData*) kmalloc(len+1, GFP_KERNEL);
// memcpy(pdata, data, len);
// if (ret < 0)
// goto out;
// if (buffer_id > LastBufferId) {
// ret = -EINVAL;
// goto out;
// }
/**
rpmsg_RxBuf = (char *)kmalloc(len+1, GFP_KERNEL);
memcpy(rpmsg_RxBuf, data, len);
rpmsg_RxBuf[len] = 0;
ret = rpmsg_sdb_decode_rxbuf_string(rpmsg_RxBuf, &buffer_id, &buffer_size);
kfree(rpmsg_RxBuf);
if (ret < 0)
goto out;
if (buffer_id > LastBufferId) {
ret = -EINVAL;
goto out;
}
*/
/* Signal to User space application */
mutex_lock(&drv->mutex);
list_for_each(pos, &drv->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
/**
* struct vRCMDeviceData {
uint32_t packageNo; //current package Number
uint32_t packageCount; //complete package Number
uint32_t dataQuantity; //number of uint32_t in data
uint32_t data[]; //the data
};
*/
//copy data to buffer
// struct sdb_buf_t {
// int index; /* index of buffer */
// size_t size; /* buffer size */
// size_t writing_size; /* size of data written by copro */
// dma_addr_t paddr; /* physical address*/
// void *vaddr; /* virtual address */
// void *uaddr; /* mapped address for userland */
// struct eventfd_ctx *efd_ctx; /* eventfd context */
// struct list_head buflist; /* reference in the buffers list */
// };
//get the buffer as array of struct vRCMDeviceData
//struct vRCMDeviceData* vrBuff = (struct vRCMDeviceData*) (datastructureptr->vaddr);
if (pdata->packageNo == 1) {
pr_info("rpmsg_sdb(%s): devAdress :%p - %x \n", __func__, datastructureptr->vaddr, datastructureptr->vaddr);
pr_info("rpmsg_sdb(%s): userAdress:%p - %x \n", __func__, datastructureptr->uaddr, datastructureptr->uaddr);
//reset writing size
datastructureptr->writing_size = 0;
}
__u8 * vrBuff = (__u8*) (datastructureptr->vaddr);
struct vRCMDeviceData* startAdress = (struct vRCMDeviceData*) (vrBuff + datastructureptr->writing_size);//&(vrBuff[pdata->packageNo]);
//struct vRCMDeviceData* startAdress = vrBuff + datastructureptr->writing_size;//&(vrBuff[pdata->packageNo]);
// pr_info("rpmsg_sdb(%s): startAdress:%d\n", __func__, startAdress);
if (virt_addr_valid(startAdress)) {
// pr_info("rpmsg_sdb(%s): startAdress:%d is valid\n", __func__, startAdress);
datastructureptr->writing_size = datastructureptr->writing_size + len;
if (datastructureptr->writing_size > datastructureptr->size) {
pr_info("(%s) Writing size is bigger than buffer size\n", __func__);
ret = -EINVAL;
goto out;
}
memcpy(startAdress, rpmsg_RxBuf, len);
pr_info("rpmsg_sdb(%s): devAdress[%x] - startadress->packageNo[%x] is valid! devpackNo:%d - packno:%d ; devpackcount:%d - packcount:%d ; devdata:%d - data:%d ; devdata1:%d - data1:%d ; devdata2:%d - data2:%d ; devdata3:%d - data3:%d ; devdata4:%d - data4:%d ; devdata5:%d - data5:%d ; devdata6:%d - data6:%d \n"
, __func__
, startAdress
, &(startAdress->packageNo)
, pdata->packageNo
,startAdress->packageNo
, pdata->packageCount
, startAdress->packageCount
, pdata->dataQuantity
, startAdress->dataQuantity
, pdata->data[0]
, startAdress->data[0]
, pdata->data[1]
, startAdress->data[1]
, pdata->data[2]
, startAdress->data[2]
, pdata->data[3]
, startAdress->data[3]
, pdata->data[4]
, startAdress->data[4]
, pdata->data[5]
, startAdress->data[5]);
}
else {
pr_info("rpmsg_sdb(%s): startAdress[%d]:%p is invalid!!\n", __func__, startAdress);
}
// if (datastructureptr->index == buffer_id) {
// datastructureptr->writing_size = buffer_size;
// if (datastructureptr->writing_size > datastructureptr->size) {
// dev_err(rpmsg_sdb_dev, "(%s) Writing size is bigger than buffer size\n", __func__);
// ret = -EINVAL;
// goto out;
// }
if ((pdata != NULL) && (pdata->packageNo == pdata->packageCount)) {
pr_info("rpmsg_sdb(%s): signal bufferfull buffer=%d!\n", __func__, datastructureptr->index);
eventfd_signal(datastructureptr->efd_ctx, 1);
break;
}
}
mutex_unlock(&drv->mutex);
/* Signal to User space application */
/** list_for_each(pos, &drv->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == buffer_id) {
datastructureptr->writing_size = buffer_size;
if (datastructureptr->writing_size > datastructureptr->size) {
dev_err(rpmsg_sdb_dev, "(%s) Writing size is bigger than buffer size\n", __func__);
ret = -EINVAL;
goto out;
}
eventfd_signal(datastructureptr->efd_ctx, 1);
break;
}
}
*/
out:
kfree(rpmsg_RxBuf);
//kfree(pdata);
return ret;
}
static int rpmsg_sdb_drv_probe(struct rpmsg_device *rpdev)
{
int ret = 0;
struct device *dev = &rpdev->dev;
struct rpmsg_sdb_t *rpmsg_sdb;
pr_info("mon-datafile: registering started\n");
rpmsg_sdb = devm_kzalloc(dev, sizeof(*rpmsg_sdb), GFP_KERNEL);
if (!rpmsg_sdb)
return -ENOMEM;
mutex_init(&rpmsg_sdb->mutex);
rpmsg_sdb->rpdev = rpdev;
rpmsg_sdb->mdev.name = "mon-datafile";//"rpmsg-sdb";
rpmsg_sdb->mdev.minor = MISC_DYNAMIC_MINOR;
rpmsg_sdb->mdev.fops = &rpmsg_sdb_fops;
pr_info("mon-datafile: Set Driver data\n");
dev_set_drvdata(&rpdev->dev, rpmsg_sdb);
/* Register misc device */
ret = misc_register(&rpmsg_sdb->mdev);
if (ret) {
dev_err(dev, "Failed to register device\n");
pr_info("mon-datafile: Failed to register device\n");
goto err_out;
}
rpmsg_sdb_dev = rpmsg_sdb->mdev.this_device;
//pr_info("rpmsg_sdb: Failed to register device\n");
dev_info(dev, "%s probed\n", rpmsg_sdb_driver_name);
err_out:
return ret;
}
static void rpmsg_sdb_drv_remove(struct rpmsg_device *rpmsgdev)
{
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpmsgdev->dev);
pr_info("rpmsg_sdb(%s): remove misc device %s !\n", __func__, drv->mdev.name);
misc_deregister(&drv->mdev);
}
static struct rpmsg_device_id rpmsg_driver_sdb_id_table[] = {
{ .name = "vrpmdv-mon-datafile" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_sdb_id_table);
//static struct rpmsg_driver rpmsg_sdb_rmpsg_drv = {
static struct rpmsg_driver vrpmdv_monitoring_data = {
.drv.name = KBUILD_MODNAME,
.drv.owner = THIS_MODULE,
.id_table = rpmsg_driver_sdb_id_table,
.probe = rpmsg_sdb_drv_probe,
.callback = rpmsg_sdb_drv_cb,
.remove = rpmsg_sdb_drv_remove,
};
module_rpmsg_driver(vrpmdv_monitoring_data);
// static int __init rpmsg_sdb_drv_init(void)
// {
// int ret = 0;
// /* Register rpmsg device */
// ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// if (ret) {
// pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
// return ret;
// }
// pr_info("rpmsg_sdb: Init done\n");
// return ret;
// }
// static void __exit rpmsg_sdb_drv_exit(void)
// {
// unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// pr_info("rpmsg_sdb: Exit\n");
// }
// module_init(rpmsg_sdb_drv_init);
// module_exit(rpmsg_sdb_drv_exit);
MODULE_AUTHOR("Markus Lehr <markus@malehr.de>");
MODULE_DESCRIPTION("shared data buffer over RPMSG");
MODULE_VERSION(RPMSG_SDB_DRIVER_VERSION);
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,16 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv-monitoring.c \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
RPROVIDES:${PN} += "kernel-module-vrpmdv-monitoring"

View File

@@ -0,0 +1,541 @@
/* Copyright 2021 Philipp Schuster
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in the
* Software without restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies
* or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// ##################################################################################################
/*
* This is a Linux kernel module/driver called "gnl_foobar_xmpl" which shows you the basics of using
* Generic Netlink in the kernel. It registers a Netlink family called "gnl_foobar_xmpl". See
* "gnl_foobar_xmpl_prop.h" for common properties of the family. "Generic Netlink" offers us a lot of
* convenient functions to register new/custom Netlink families on the fly during runtime. We use this
* functionality here. It implements simple IPC between Userland and Kernel (Kernel responds to userland).
*
* Check "gnl_foobar_xmpl_prop.h" for common properties of the family first, afterwards follow the code here.
*
* You can find some more interesting documentation about Generic Netlink here:
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
*/
// basic definitions for kernel module development
#include <linux/module.h>
// definitions for generic netlink families, policies etc;
// transitive dependencies for basic netlink, sockets etc
#include <net/genetlink.h>
// required for locking inside the .dumpit callback demonstration
#include <linux/mutex.h>
// data/vars/enums/properties that describes our protocol that we implement
// on top of generic netlink (like functions we want to trigger on the receiving side)
#include "gnl_foobar_xmpl_prop.h"
// Module/Driver description.
// You can see this for example when executing `$ modinfo ./gnl_foobar_xmpl.ko` (after build).
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Philipp Schuster <phip1611@gmail.com>");
MODULE_DESCRIPTION(
"Linux driver that registers the custom Netlink family "
"\"" FAMILY_NAME "\" via Generic Netlink and responds to echo messages "
"according to the properties specified in \"gnl_foobar_xmpl_prop.h\"."
);
/* ######################## CONVENIENT LOGGING MACROS ######################## */
// (Re)definition of some convenient logging macros from <linux/printk.h>. You can see the logging
// messages when printing the kernel log, e.g. with `$ sudo dmesg`.
// See https://elixir.bootlin.com/linux/latest/source/include/linux/printk.h
// with this redefinition we can easily prefix all log messages from pr_* logging macros
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* ########################################################################### */
/**
* Data structure required for our .dumpit callback handler to
* know about the progress of an ongoing dump.
* See the dumpit callback handler how it is used.
*/
struct {
// from <linux/mutex.h>
/**
* Only one process is allowed per dump process. We need a lock for that.
*/
struct mutex mtx;
/**
* Number that describes how many packets we need to send until we are done
* during an ongoing dumpit process. 0 = done.
*/
int unsigned runs_to_go;
/**
* Number that describes how many packets per dump are sent in total.
* Constant per dump.
*/
int unsigned total_runs;
} dumpit_cb_progress_data;
// Documentation is on the implementation of this function.
int gnl_cb_echo_doit(struct sk_buff *sender_skb, struct genl_info *info);
// Documentation is on the implementation of this function.
int gnl_cb_echo_dumpit(struct sk_buff *pre_allocated_skb, struct netlink_callback *cb);
// Documentation is on the implementation of this function.
int gnl_cb_echo_dumpit_before(struct netlink_callback *cb);
// Documentation is on the implementation of this function.
int gnl_cb_echo_dumpit_before_after(struct netlink_callback *cb);
// Documentation is on the implementation of this function.
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info);
/**
* The length of `struct genl_ops gnl_foobar_xmpl_ops[]`. Not necessarily
* the number of commands in `enum GNlFoobarXmplCommand`. It depends on your application logic.
* For example, you can use the same command multiple times and - dependent by flag -
* invoke a different callback handler. In our simple example we just use one .doit callback
* per operation/command.
*/
#define GNL_FOOBAR_OPS_LEN (GNL_FOOBAR_XMPL_COMMAND_COUNT)
/**
* Array with all operations that our protocol on top of Generic Netlink
* supports. An operation is the glue between a command ("cmd" field in `struct genlmsghdr` of
* received Generic Netlink message) and the corresponding ".doit" callback function.
* See: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L148
*/
struct genl_ops gnl_foobar_xmpl_ops[GNL_FOOBAR_OPS_LEN] = {
{
/* The "cmd" field in `struct genlmsghdr` of received Generic Netlink message */
.cmd = GNL_FOOBAR_XMPL_C_ECHO_MSG,
/* TODO Use case ? */
.flags = 0,
/* TODO Use case ? */
.internal_flags = 0,
/* Callback handler when a request with the specified ".cmd" above is received.
* Always validates the payload except one set NO_STRICT_VALIDATION flag in ".validate"
* See: https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*
* Quote from: https://lwn.net/Articles/208755
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*
*/
.doit = gnl_cb_echo_doit,
/* This callback is similar in use to the standard Netlink 'dumpit' callback.
* The 'dumpit' callback is invoked when a Generic Netlink message is received
* with the NLM_F_DUMP flag set.
*
* A dump can be understand as a "GET ALL DATA OF THE GIVEN ENTITY", i.e.
* the userland can receive as long as the .dumpit callback returns data.
*
* .dumpit is not mandatory, but either it or .doit must be provided, see
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L367
*
* To be honest I don't know in what use case one should use .dumpit and why
* it is useful, because you can achieve the same also with .doit handlers.
* Anyway, this is just an example/tutorial.
*
* Quote from: https://lwn.net/Articles/208755
* "The main difference between a 'dumpit' handler and a 'doit' handler is
* that a 'dumpit' handler does not allocate a message buffer for a response;
* a pre-allocated sk_buff is passed to the 'dumpit' handler as the first
* parameter. The 'dumpit' handler should fill the message buffer with the
* appropriate response message and return the size of the sk_buff,
* i.e. sk_buff->len, and the message buffer will automatically be sent to the
* Generic Netlink client that initiated the request. As long as the 'dumpit'
* handler returns a value greater than zero it will be called again with a
* newly allocated message buffer to fill, when the handler has no more data
* to send it should return zero; error conditions are indicated by returning
* a negative value. If necessary, state can be preserved in the
* netlink_callback parameter which is passed to the 'dumpit' handler; the
* netlink_callback parameter values will be preserved across handler calls
* for a single request."
*
* You can see the check for the NLM_F_DUMP-flag here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L780
*/
.dumpit = gnl_cb_echo_dumpit,
/* Start callback for dumps. Can be used to lock data structures. */
.start = gnl_cb_echo_dumpit_before,
/* Completion callback for dumps. Can be used for cleanup after a dump and releasing locks. */
.done = gnl_cb_echo_dumpit_before_after,
/*
0 (= "validate strictly") or value `enum genl_validate_flags`
* see: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L108
*/
.validate = 0,
},
{
.cmd = GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR,
.flags = 0,
.internal_flags = 0,
.doit = gnl_cb_doit_reply_with_nlmsg_err,
// .dumpit is not required, only optional; application specific/dependent on your use case
// in a real application you probably have different .dumpit handlers per operation/command
.dumpit = NULL,
// in a real application you probably have different .start handlers per operation/command
.start = NULL,
// in a real application you probably have different .done handlers per operation/command
.done = NULL,
.validate = 0,
}
};
/**
* Attribute policy: defines which attribute has which type (e.g int, char * etc).
* This get validated for each received Generic Netlink message, if not deactivated
* in `gnl_foobar_xmpl_ops[].validate`.
* See https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*/
static struct nla_policy gnl_foobar_xmpl_policy[GNL_FOOBAR_XMPL_ATTRIBUTE_ENUM_LEN] = {
// In case you are seeing this syntax for the first time (I also learned this just after a few years of
// coding with C myself): The following static array initiations are equivalent:
// `int a[2] = {1, 2}` <==> `int a[2] = {[0] => 1, [1] => 2}`.
[GNL_FOOBAR_XMPL_A_UNSPEC] = {.type = NLA_UNSPEC},
// You can set this to NLA_U32 for testing and send an ECHO message from the userland
// It will fail in this case and you see a entry in the kernel log.
// `enum GNL_FOOBAR_XMPL_ATTRIBUTE::GNL_FOOBAR_XMPL_A_MSG` is a null-terminated C-String
[GNL_FOOBAR_XMPL_A_MSG] = {.type = NLA_NUL_STRING},
};
/**
* Definition of the Netlink family we want to register using Generic Netlink functionality
*/
static struct genl_family gnl_foobar_xmpl_family = {
// automatically assign an id
.id = 0,
// we don't use custom additional header info / user specific header
.hdrsize = 0,
// The name of this family, used by userspace application to get the numeric ID
.name = FAMILY_NAME,
// family specific version number; can be used to evolve application over time (multiple versions)
.version = 1,
// delegates all incoming requests to callback functions
.ops = gnl_foobar_xmpl_ops,
// length of array `gnl_foobar_xmpl_ops`
.n_ops = GNL_FOOBAR_OPS_LEN,
// attribute policy (for validation of messages). Enforced automatically, except ".validate" in
// corresponding ".ops"-field is set accordingly.
.policy = gnl_foobar_xmpl_policy,
// Number of attributes / bounds check for policy (array length)
.maxattr = GNL_FOOBAR_XMPL_ATTRIBUTE_ENUM_LEN,
// Owning Kernel module of the Netlink family we register.
.module = THIS_MODULE,
// Actually not necessary because this memory region would be zeroed anyway during module load,
// but this way one sees all possible options.
// if your application must handle multiple netlink calls in parallel (where one should not block the next
// from starting), set this to true! otherwise all netlink calls are mutually exclusive
.parallel_ops = 0,
// set to true if the family can handle network namespaces and should be presented in all of them
.netnsok = 0,
// called before an operation's doit callback, it may do additional, common, filtering and return an error
.pre_doit = NULL,
// called after an operation's doit callback, it may undo operations done by pre_doit, for example release locks
.post_doit = NULL,
};
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_ECHO` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_echo_doit(struct sk_buff *sender_skb, struct genl_info *info) {
struct nlattr *na;
struct sk_buff *reply_skb;
int rc;
void *msg_head;
char *recv_msg;
pr_info("%s() invoked\n", __func__);
if (info == NULL) {
// should never happen
pr_err("An error occurred in %s():\n", __func__);
return -EINVAL;
}
/*
* For each attribute there is an index in info->attrs which points to a nlattr structure
* in this structure the data is stored.
*/
na = info->attrs[GNL_FOOBAR_XMPL_A_MSG];
if (!na) {
pr_err("no info->attrs[%i]\n", GNL_FOOBAR_XMPL_A_MSG);
return -EINVAL; // we return here because we expect to recv a msg
}
recv_msg = (char *) nla_data(na);
if (recv_msg == NULL) {
pr_err("error while receiving data\n");
} else {
pr_info("received: '%s'\n", recv_msg);
}
// Send a message back
// ---------------------
// Allocate some memory, since the size is not yet known use NLMSG_GOODSIZE
reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (reply_skb == NULL) {
pr_err("An error occurred in %s():\n", __func__);
return -ENOMEM;
}
// Create the message headers
// Add header to netlink message;
// afterwards the buffer looks like this:
// ----------------------------------
// | netlink header |
// | generic netlink header |
// | <space for netlink attributes> |
// ----------------------------------
msg_head = genlmsg_put(reply_skb, // buffer for netlink message: struct sk_buff *
// According to my findings: this is not used for routing
// This can be used in an application specific way to target
// different endpoints within the same user application
// but general rule: just put sender port id here
info->snd_portid, // sending port (not process) id: int
info->snd_seq + 1, // sequence number: int (might be used by receiver, but not mandatory)
&gnl_foobar_xmpl_family, // struct genl_family *
0, // flags for Netlink header: int; application specific and not mandatory
// The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
GNL_FOOBAR_XMPL_C_ECHO_MSG
);
if (msg_head == NULL) {
rc = ENOMEM;
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Add a GNL_FOOBAR_XMPL_A_MSG attribute (actual value/payload to be sent)
// echo the value we just received
rc = nla_put_string(reply_skb, GNL_FOOBAR_XMPL_A_MSG, recv_msg);
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Finalize the message:
// Corrects the netlink message header (length) to include the appended
// attributes. Only necessary if attributes have been added to the message.
genlmsg_end(reply_skb, msg_head);
// Send the message back
rc = genlmsg_reply(reply_skb, info);
// same as genlmsg_unicast(genl_info_net(info), reply_skb, info->snd_portid)
// see https://elixir.bootlin.com/linux/v5.8.9/source/include/net/genetlink.h#L326
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
return 0;
}
/**
* ".dumpit"-callback function if a Generic Netlink with command ECHO_MSG and flag `NLM_F_DUMP` is received.
* Please look into the comments where this is used as ".dumpit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".dumpit" callbacks.
*
* A dump must be understand of "give me all data of a given entity"
* rather than a "dump of the netlink message itself" for debugging etc!
*
* This handler requires `gnl_cb_echo_dumpit_before` to run before a dump and `gnl_cb_echo_dumpit_after` after a dump.
*
* For the sake of simplicity, we use the ECHO_MSG command for the dump. In fact, we don't expect a
* MSG-Attribute here, unlike the regular ECHO_MSG handler. We reply with a dump of
* "all messages that we got" (application specific, hard coded in this example).
*/
int gnl_cb_echo_dumpit(struct sk_buff *pre_allocated_skb, struct netlink_callback *cb) {
void *msg_head;
int ret;
static const char HELLO_FROM_DUMPIT_MSG[] = "You set the flag NLM_F_DUMP; this message is "
"brought to you by .dumpit callback :)";
pr_info("Called %s()\n", __func__);
if (dumpit_cb_progress_data.runs_to_go == 0) {
pr_info("no more data to send in dumpit cb\n");
// mark that dump is done;
return 0;
} else {
dumpit_cb_progress_data.runs_to_go--;
pr_info("%s: %d more runs to do\n", __func__, dumpit_cb_progress_data.runs_to_go);
}
msg_head = genlmsg_put(pre_allocated_skb, // buffer for netlink message: struct sk_buff *
// According to my findings: this is not used for routing
// This can be used in an application specific way to target
// different endpoints within the same user application
// but general rule: just put sender port id here
cb->nlh->nlmsg_pid, // sending port (not process) id: int
// sequence number: int (might be used by receiver, but not mandatory)
// sequence 0, 1, 2...
dumpit_cb_progress_data.total_runs - dumpit_cb_progress_data.runs_to_go - 1,
&gnl_foobar_xmpl_family, // struct genl_family *
0, // flags: int (for netlink header); we don't check them in the userland; application specific
// this way we can trigger a specific command/callback on the receiving side or imply
// on which type of command we are currently answering; this is application specific
GNL_FOOBAR_XMPL_C_ECHO_MSG // cmd: u8 (for generic netlink header);
);
if (msg_head == NULL) {
pr_info("An error occurred in %s(): genlmsg_put() failed\n", __func__);
return -ENOMEM;
}
ret = nla_put_string(
pre_allocated_skb,
GNL_FOOBAR_XMPL_A_MSG,
HELLO_FROM_DUMPIT_MSG
);
if (ret < 0) {
pr_info("An error occurred in %s():\n", __func__);
return ret;
}
genlmsg_end(pre_allocated_skb, msg_head);
// return the length of data we wrote into the pre-allocated buffer
return pre_allocated_skb->len;
}
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info) {
pr_info("%s() invoked, a NLMSG_ERR response will be sent back\n", __func__);
/*
* Generic Netlink is smart enough and sends a NLMSG_ERR reply automatically as reply
* Quote from https://lwn.net/Articles/208755/:
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*/
return -EINVAL;
}
/**
* Called before a dump with `gnl_cb_echo_dumpit()` starts.
* See where this is assigned in `struct genl_ops gnl_foobar_xmpl_ops[]` as
* `.start` callback for more comments.
*
* @return success (0) or error.
*/
int gnl_cb_echo_dumpit_before(struct netlink_callback *cb) {
int ret;
static int unsigned const dump_runs = 3;
pr_info("%s: dump started. acquire lock. initialize dump runs_to_go (number of receives userland can make) to %d runs\n", __func__, dump_runs);
// Lock the mutex like mutex_lock(), and return 0 if the mutex has been acquired or sleep until the mutex becomes available
// If a signal arrives while waiting for the lock then this function returns -EINTR.
ret = mutex_lock_interruptible(&dumpit_cb_progress_data.mtx);
if (ret != 0) {
pr_err("Failed to get lock!\n");
return ret;
}
dumpit_cb_progress_data.total_runs = dump_runs;
dumpit_cb_progress_data.runs_to_go = dump_runs;
return 0;
}
/**
* Called before a dump with `gnl_cb_echo_dumpit()` starts.
* See where this is assigned in `struct genl_ops gnl_foobar_xmpl_ops[]` as
* `.start` callback for more comments.
*
* @return success (0) or error.
*/
int gnl_cb_echo_dumpit_before_after(struct netlink_callback *cb) {
pr_info("%s: dump done. release lock\n", __func__);
mutex_unlock(&dumpit_cb_progress_data.mtx);
return 0;
}
/**
* Module/driver initializer. Called on module load/insertion.
*
* @return success (0) or error code.
*/
static int __init gnl_foobar_xmpl_module_init(void) {
int rc;
pr_info("Generic Netlink Example Module inserted.\n");
// Register family with its operations and policies
rc = genl_register_family(&gnl_foobar_xmpl_family);
if (rc != 0) {
pr_err("FAILED: genl_register_family(): %i\n", rc);
pr_err("An error occurred while inserting the generic netlink example module\n");
return -1;
} else {
pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
mutex_init(&dumpit_cb_progress_data.mtx);
return 0;
}
/**
* Module/driver uninitializer. Called on module unload/removal.
*
* @return success (0) or error code.
*/
static void __exit gnl_foobar_xmpl_module_exit(void) {
int ret;
pr_info("Generic Netlink Example Module unloaded.\n");
// Unregister the family
ret = genl_unregister_family(&gnl_foobar_xmpl_family);
if (ret != 0) {
pr_err("genl_unregister_family() failed: %i\n", ret);
return;
} else {
pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
mutex_destroy(&dumpit_cb_progress_data.mtx);
}
module_init(gnl_foobar_xmpl_module_init);
module_exit(gnl_foobar_xmpl_module_exit);

View File

@@ -0,0 +1,536 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) STMicroelectronics 2019 - All Rights Reserved
* Author: Jean-Philippe Romain <jean-philippe.romain@st.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#include <linux/miscdevice.h>
#include <linux/eventfd.h>
#include <linux/of_platform.h>
#include <linux/list.h>
#define RPMSG_SDB_DRIVER_VERSION "1.0"
/*
* Static global variables
*/
static const char rpmsg_sdb_driver_name[] = "stm32-rpmsg-sdb";
static int LastBufferId;
struct rpmsg_sdb_ioctl_set_efd {
int bufferId, eventfd;
};
struct rpmsg_sdb_ioctl_get_data_size {
int bufferId;
uint32_t size;
};
/* ioctl numbers */
/* _IOW means userland is writing and kernel is reading */
/* _IOR means userland is reading and kernel is writing */
/* _IOWR means userland and kernel can both read and write */
#define RPMSG_SDB_IOCTL_SET_EFD _IOW('R', 0x00, struct rpmsg_sdb_ioctl_set_efd *)
#define RPMSG_SDB_IOCTL_GET_DATA_SIZE _IOWR('R', 0x01, struct rpmsg_sdb_ioctl_get_data_size *)
struct sdb_buf_t {
int index; /* index of buffer */
size_t size; /* buffer size */
size_t writing_size; /* size of data written by copro */
dma_addr_t paddr; /* physical address*/
void *vaddr; /* virtual address */
void *uaddr; /* mapped address for userland */
struct eventfd_ctx *efd_ctx; /* eventfd context */
struct list_head buflist; /* reference in the buffers list */
};
struct rpmsg_sdb_t {
struct mutex mutex; /* mutex to protect the ioctls */
struct miscdevice mdev; /* misc device ref */
struct rpmsg_device *rpdev; /* handle rpmsg device */
struct list_head buffer_list; /* buffer instances list */
};
struct device *rpmsg_sdb_dev;
static int rpmsg_sdb_format_txbuf_string(struct sdb_buf_t *buffer, char *bufinfo_str, size_t bufinfo_str_size)
{
pr_debug("rpmsg_sdb(%s): Buffer index:%d, addr:%08x, size:%08x\n",
__func__,
buffer->index,
buffer->paddr,
buffer->size);
return snprintf(bufinfo_str, bufinfo_str_size, "B%dA%08xL%08x", buffer->index, buffer->paddr, buffer->size);
}
static long rpmsg_sdb_decode_rxbuf_string(char *rxbuf_str, int *buffer_id, size_t *size)
{
int ret = 0;
char *sub_str;
long bsize;
long bufid;
const char delimiter[2] = {'L','\0'};
pr_debug("rpmsg_sdb(%s): rxbuf_str:%s\n", __func__, rxbuf_str);
/* Get first part containing the buffer id */
sub_str = strsep(&rxbuf_str, delimiter);
pr_debug("rpmsg_sdb(%s): sub_str:%s\n", __func__, sub_str);
/* Save Buffer id and size: template BxLyyyyyyyy*/
ret = kstrtol(&sub_str[1], 10, &bufid);
if (ret < 0) {
pr_err("rpmsg_sdb(ERROR): Extract of buffer id failed(%d)", ret);
goto out;
}
ret = kstrtol(&rxbuf_str[2], 16, &bsize);
if (ret < 0) {
pr_err("rpmsg_sdb(ERROR): Extract of buffer size failed(%d)", ret);
goto out;
}
*size = (size_t)bsize;
*buffer_id = (int)bufid;
out:
return ret;
}
static int rpmsg_sdb_send_buf_info(struct rpmsg_sdb_t *rpmsg_sdb, struct sdb_buf_t *buffer)
{
int count = 0, ret = 0;
const unsigned char *tbuf;
char mybuf[32];
int msg_size;
struct rpmsg_device *_rpdev;
_rpdev = rpmsg_sdb->rpdev;
msg_size = rpmsg_get_buffer_size(_rpdev->ept);
if (msg_size < 0)
return msg_size;
count = rpmsg_sdb_format_txbuf_string(buffer, mybuf, 32);
tbuf = &mybuf[0];
do {
/* send a message to our remote processor */
ret = rpmsg_send(_rpdev->ept, (void *)tbuf,
count > msg_size ? msg_size : count);
if (ret) {
dev_err(&_rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
if (count > msg_size) {
count -= msg_size;
tbuf += msg_size;
} else {
count = 0;
}
} while (count > 0);
return count;
}
static int rpmsg_sdb_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long vsize = vma->vm_end - vma->vm_start;
unsigned long size = PAGE_ALIGN(vsize);
unsigned long NumPages = size >> PAGE_SHIFT;
unsigned long align = get_order(size);
pgprot_t prot = pgprot_noncached(vma->vm_page_prot);
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *_buffer;
if (align > CONFIG_CMA_ALIGNMENT)
align = CONFIG_CMA_ALIGNMENT;
if (rpmsg_sdb_dev == NULL)
return -ENOMEM;
rpmsg_sdb_dev->coherent_dma_mask = DMA_BIT_MASK(32);
rpmsg_sdb_dev->dma_mask = &rpmsg_sdb_dev->coherent_dma_mask;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Field the last buffer entry which is the last one created */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
_buffer = list_last_entry(&_rpmsg_sdb->buffer_list,
struct sdb_buf_t, buflist);
_buffer->uaddr = NULL;
_buffer->size = NumPages * PAGE_SIZE;
_buffer->writing_size = -1;
_buffer->vaddr = dma_alloc_coherent(rpmsg_sdb_dev,
_buffer->size,
&_buffer->paddr,
GFP_KERNEL);
if (!_buffer->vaddr) {
pr_err("rpmsg_sdb(ERROR): Memory allocation issue\n");
return -ENOMEM;
}
pr_debug("rpmsg_sdb(%s): dma_alloc_coherent done - paddr[%d]:%x - vaddr[%d]:%p\n",
__func__,
_buffer->index,
_buffer->paddr,
_buffer->index,
_buffer->vaddr);
/* Get address for userland */
if (remap_pfn_range(vma, vma->vm_start,
(_buffer->paddr >> PAGE_SHIFT) + vma->vm_pgoff,
size, prot))
return -EAGAIN;
_buffer->uaddr = (void *)vma->vm_start;
/* Send information to remote proc */
rpmsg_sdb_send_buf_info(_rpmsg_sdb, _buffer);
} else {
dev_err(rpmsg_sdb_dev, "No existing buffer entry exist in the list !!!");
return -EINVAL;
}
/* Increment for number of requested buffer */
LastBufferId++;
return 0;
}
/**
* rpmsg_sdb_open - Open Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_open(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Initialize the buffer list*/
INIT_LIST_HEAD(&_rpmsg_sdb->buffer_list);
mutex_init(&_rpmsg_sdb->mutex);
return 0;
}
/**
* rpmsg_sdb_close - Close Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_close(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *pos, *next;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
list_for_each_entry_safe(pos, next, &_rpmsg_sdb->buffer_list, buflist) {
/* Free the CMA allocation */
pr_debug("rpmsg_sdb(%s): Free the CMA allocation: pos->size:%08x, pos->vaddr:%08x, pos->paddr:%08x\n",
__func__,
pos->size,
pos->vaddr,
pos->paddr);
dma_free_coherent(rpmsg_sdb_dev, pos->size, pos->vaddr,
pos->paddr);
/* Remove the buffer from the list */
list_del(&pos->buflist);
/* Free the buffer */
kfree(pos);
}
/* Reset LastBufferId */
LastBufferId = 0;
return 0;
}
/**
* rpmsg_sdb_ioctl - IOCTL
*
* @session: ibmvmc_file_session struct
* @cmd: cmd field
* @arg: Argument field
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static long rpmsg_sdb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int idx = 0;
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *buffer, *lastbuffer;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_ioctl_set_efd q_set_efd;
struct rpmsg_sdb_ioctl_get_data_size q_get_dat_size;
void __user *argp = (void __user *)arg;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
switch (cmd) {
case RPMSG_SDB_IOCTL_SET_EFD:
mutex_lock(&_rpmsg_sdb->mutex);
/* Get index from the last buffer in the list */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
lastbuffer = list_last_entry(&_rpmsg_sdb->buffer_list, struct sdb_buf_t, buflist);
idx = lastbuffer->index;
/* Check last index was properly initiated*/
if (lastbuffer->vaddr == NULL) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - previous buffer was not allocated\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EBADE;
}
/* increment this index for the next buffer creation*/
idx++;
}
if (copy_from_user(&q_set_efd, (struct rpmsg_sdb_ioctl_set_efd *)argp,
sizeof(struct rpmsg_sdb_ioctl_set_efd))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - copy from user failed\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EFAULT;
}
/* create a new buffer which will be added in the buffer list */
buffer = kmalloc(sizeof(struct sdb_buf_t), GFP_KERNEL);
buffer->index = idx;
buffer->vaddr = NULL;
buffer->efd_ctx = eventfd_ctx_fdget(q_set_efd.eventfd);
list_add_tail(&buffer->buflist, &_rpmsg_sdb->buffer_list);
mutex_unlock(&_rpmsg_sdb->mutex);
break;
case RPMSG_SDB_IOCTL_GET_DATA_SIZE:
if (copy_from_user(&q_get_dat_size, (struct rpmsg_sdb_ioctl_get_data_size *)argp,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy from user failed\n");
return -EFAULT;
}
/* Get the index of the requested buffer and then look-up in the buffer list*/
idx = q_get_dat_size.bufferId;
list_for_each(pos, &_rpmsg_sdb->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == idx) {
/* Get the writing size*/
q_get_dat_size.size = datastructureptr->writing_size;
break;
}
}
if (copy_to_user((struct rpmsg_sdb_ioctl_get_data_size *)argp, &q_get_dat_size,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy to user failed\n");
return -EFAULT;
}
/* Reset the writing size*/
datastructureptr->writing_size = -1;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct file_operations rpmsg_sdb_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = rpmsg_sdb_ioctl,
.mmap = rpmsg_sdb_mmap,
.open = rpmsg_sdb_open,
.release = rpmsg_sdb_close,
};
static int rpmsg_sdb_drv_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret = 0;
int buffer_id = 0;
size_t buffer_size;
char *rpmsg_RxBuf;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpdev->dev);
if (len == 0) {
dev_err(rpmsg_sdb_dev, "(%s) Empty lenght requested\n", __func__);
return -EINVAL;
}
//dev_err(rpmsg_sdb_dev, "(%s) lenght: %d\n", __func__,len);
rpmsg_RxBuf = (char *)kmalloc(len+1, GFP_KERNEL);
memcpy(rpmsg_RxBuf, data, len);
rpmsg_RxBuf[len] = 0;
ret = rpmsg_sdb_decode_rxbuf_string(rpmsg_RxBuf, &buffer_id, &buffer_size);
kfree(rpmsg_RxBuf);
if (ret < 0)
goto out;
if (buffer_id > LastBufferId) {
ret = -EINVAL;
goto out;
}
/* Signal to User space application */
list_for_each(pos, &drv->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == buffer_id) {
datastructureptr->writing_size = buffer_size;
if (datastructureptr->writing_size > datastructureptr->size) {
dev_err(rpmsg_sdb_dev, "(%s) Writing size is bigger than buffer size\n", __func__);
ret = -EINVAL;
goto out;
}
eventfd_signal(datastructureptr->efd_ctx, 1);
break;
}
/* TODO: quid if nothing find during the loop ? */
}
out:
return ret;
}
static int rpmsg_sdb_drv_probe(struct rpmsg_device *rpdev)
{
int ret = 0;
struct device *dev = &rpdev->dev;
struct rpmsg_sdb_t *rpmsg_sdb;
rpmsg_sdb = devm_kzalloc(dev, sizeof(*rpmsg_sdb), GFP_KERNEL);
if (!rpmsg_sdb)
return -ENOMEM;
mutex_init(&rpmsg_sdb->mutex);
rpmsg_sdb->rpdev = rpdev;
rpmsg_sdb->mdev.name = "rpmsg-sdb";
rpmsg_sdb->mdev.minor = MISC_DYNAMIC_MINOR;
rpmsg_sdb->mdev.fops = &rpmsg_sdb_fops;
dev_set_drvdata(&rpdev->dev, rpmsg_sdb);
/* Register misc device */
ret = misc_register(&rpmsg_sdb->mdev);
if (ret) {
dev_err(dev, "Failed to register device\n");
goto err_out;
}
rpmsg_sdb_dev = rpmsg_sdb->mdev.this_device;
dev_info(dev, "%s probed\n", rpmsg_sdb_driver_name);
err_out:
return ret;
}
static void rpmsg_sdb_drv_remove(struct rpmsg_device *rpmsgdev)
{
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpmsgdev->dev);
misc_deregister(&drv->mdev);
}
static struct rpmsg_device_id rpmsg_driver_sdb_id_table[] = {
{ .name = "rpmsg-sdb-channel" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_sdb_id_table);
static struct rpmsg_driver rpmsg_sdb_rmpsg_drv = {
.drv.name = KBUILD_MODNAME,
.drv.owner = THIS_MODULE,
.id_table = rpmsg_driver_sdb_id_table,
.probe = rpmsg_sdb_drv_probe,
.callback = rpmsg_sdb_drv_cb,
.remove = rpmsg_sdb_drv_remove,
};
static int __init rpmsg_sdb_drv_init(void)
{
int ret = 0;
/* Register rpmsg device */
ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
if (ret) {
pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
return ret;
}
pr_info("rpmsg_sdb: Init done\n");
return ret;
}
static void __exit rpmsg_sdb_drv_exit(void)
{
unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
pr_info("rpmsg_sdb: Exit\n");
}
module_init(rpmsg_sdb_drv_init);
module_exit(rpmsg_sdb_drv_exit);
MODULE_AUTHOR("Jean-Philippe Romain <jean-philippe.romain@st.com>");
MODULE_DESCRIPTION("shared data buffer over RPMSG");
MODULE_VERSION(RPMSG_SDB_DRIVER_VERSION);
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,541 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) STMicroelectronics 2019 - All Rights Reserved
* Author: Jean-Philippe Romain <jean-philippe.romain@st.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#include <linux/miscdevice.h>
#include <linux/eventfd.h>
#include <linux/of_platform.h>
#include <linux/list.h>
#define RPMSG_SDB_DRIVER_VERSION "1.0"
/*
* Static global variables
*/
static const char rpmsg_sdb_driver_name[] = "stm32-rpmsg-sdb";
static int LastBufferId;
struct rpmsg_sdb_ioctl_set_efd {
int bufferId, eventfd;
};
struct rpmsg_sdb_ioctl_get_data_size {
int bufferId;
uint32_t size;
};
/* ioctl numbers */
/* _IOW means userland is writing and kernel is reading */
/* _IOR means userland is reading and kernel is writing */
/* _IOWR means userland and kernel can both read and write */
#define RPMSG_SDB_IOCTL_SET_EFD _IOW('R', 0x00, struct rpmsg_sdb_ioctl_set_efd *)
#define RPMSG_SDB_IOCTL_GET_DATA_SIZE _IOWR('R', 0x01, struct rpmsg_sdb_ioctl_get_data_size *)
struct sdb_buf_t {
int index; /* index of buffer */
size_t size; /* buffer size */
size_t writing_size; /* size of data written by copro */
dma_addr_t paddr; /* physical address*/
void *vaddr; /* virtual address */
void *uaddr; /* mapped address for userland */
struct eventfd_ctx *efd_ctx; /* eventfd context */
struct list_head buflist; /* reference in the buffers list */
};
struct rpmsg_sdb_t {
struct mutex mutex; /* mutex to protect the ioctls */
struct miscdevice mdev; /* misc device ref */
struct rpmsg_device *rpdev; /* handle rpmsg device */
struct list_head buffer_list; /* buffer instances list */
};
struct device *rpmsg_sdb_dev;
static int rpmsg_sdb_format_txbuf_string(struct sdb_buf_t *buffer, char *bufinfo_str, size_t bufinfo_str_size)
{
pr_debug("rpmsg_sdb(%s): Buffer index:%d, addr:%08x, size:%08x\n",
__func__,
buffer->index,
buffer->paddr,
buffer->size);
return snprintf(bufinfo_str, bufinfo_str_size, "B%dA%08xL%08x", buffer->index, buffer->paddr, buffer->size);
}
static long rpmsg_sdb_decode_rxbuf_string(char *rxbuf_str, int *buffer_id, size_t *size)
{
int ret = 0;
char *sub_str;
long bsize;
long bufid;
const char delimiter[2] = {'L','\0'};
pr_debug("rpmsg_sdb(%s): rxbuf_str:%s\n", __func__, rxbuf_str);
/* Get first part containing the buffer id */
sub_str = strsep(&rxbuf_str, delimiter);
pr_debug("rpmsg_sdb(%s): sub_str:%s\n", __func__, sub_str);
/* Save Buffer id and size: template BxLyyyyyyyy*/
ret = kstrtol(&sub_str[1], 10, &bufid);
if (ret < 0) {
pr_err("rpmsg_sdb(ERROR): Extract of buffer id failed(%d)", ret);
goto out;
}
ret = kstrtol(&rxbuf_str[2], 16, &bsize);
if (ret < 0) {
pr_err("rpmsg_sdb(ERROR): Extract of buffer size failed(%d)", ret);
goto out;
}
*size = (size_t)bsize;
*buffer_id = (int)bufid;
out:
return ret;
}
static int rpmsg_sdb_send_buf_info(struct rpmsg_sdb_t *rpmsg_sdb, struct sdb_buf_t *buffer)
{
int count = 0, ret = 0;
const unsigned char *tbuf;
char mybuf[32];
int msg_size;
struct rpmsg_device *_rpdev;
_rpdev = rpmsg_sdb->rpdev;
msg_size = rpmsg_get_mtu(_rpdev->ept);
if (msg_size < 0)
return msg_size;
count = rpmsg_sdb_format_txbuf_string(buffer, mybuf, 32);
tbuf = &mybuf[0];
do {
/* send a message to our remote processor */
ret = rpmsg_send(_rpdev->ept, (void *)tbuf,
count > msg_size ? msg_size : count);
if (ret) {
dev_err(&_rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
if (count > msg_size) {
count -= msg_size;
tbuf += msg_size;
} else {
count = 0;
}
} while (count > 0);
return count;
}
static int rpmsg_sdb_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long vsize = vma->vm_end - vma->vm_start;
unsigned long size = PAGE_ALIGN(vsize);
unsigned long NumPages = size >> PAGE_SHIFT;
unsigned long align = get_order(size);
pgprot_t prot = pgprot_noncached(vma->vm_page_prot);
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *_buffer;
if (align > CONFIG_CMA_ALIGNMENT)
align = CONFIG_CMA_ALIGNMENT;
if (rpmsg_sdb_dev == NULL)
return -ENOMEM;
rpmsg_sdb_dev->coherent_dma_mask = DMA_BIT_MASK(32);
rpmsg_sdb_dev->dma_mask = &rpmsg_sdb_dev->coherent_dma_mask;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Field the last buffer entry which is the last one created */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
_buffer = list_last_entry(&_rpmsg_sdb->buffer_list,
struct sdb_buf_t, buflist);
_buffer->uaddr = NULL;
_buffer->size = NumPages * PAGE_SIZE;
_buffer->writing_size = -1;
_buffer->vaddr = dma_alloc_coherent(rpmsg_sdb_dev,
_buffer->size,
&_buffer->paddr,
GFP_KERNEL);
if (!_buffer->vaddr) {
pr_err("rpmsg_sdb(ERROR): Memory allocation issue\n");
return -ENOMEM;
}
pr_debug("rpmsg_sdb(%s): dma_alloc_coherent done - paddr[%d]:%x - vaddr[%d]:%p\n",
__func__,
_buffer->index,
_buffer->paddr,
_buffer->index,
_buffer->vaddr);
/* Get address for userland */
if (remap_pfn_range(vma, vma->vm_start,
(_buffer->paddr >> PAGE_SHIFT) + vma->vm_pgoff,
size, prot))
return -EAGAIN;
_buffer->uaddr = (void *)vma->vm_start;
/* Send information to remote proc */
rpmsg_sdb_send_buf_info(_rpmsg_sdb, _buffer);
} else {
dev_err(rpmsg_sdb_dev, "No existing buffer entry exist in the list !!!");
return -EINVAL;
}
/* Increment for number of requested buffer */
LastBufferId++;
return 0;
}
/**
* rpmsg_sdb_open - Open Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_open(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Initialize the buffer list*/
INIT_LIST_HEAD(&_rpmsg_sdb->buffer_list);
mutex_init(&_rpmsg_sdb->mutex);
return 0;
}
/**
* rpmsg_sdb_close - Close Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_close(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *pos, *next;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
list_for_each_entry_safe(pos, next, &_rpmsg_sdb->buffer_list, buflist) {
/* Free the CMA allocation */
pr_debug("rpmsg_sdb(%s): Free the CMA allocation: pos->size:%08x, pos->vaddr:%08x, pos->paddr:%08x\n",
__func__,
pos->size,
pos->vaddr,
pos->paddr);
dma_free_coherent(rpmsg_sdb_dev, pos->size, pos->vaddr,
pos->paddr);
/* Remove the buffer from the list */
list_del(&pos->buflist);
/* Free the buffer */
kfree(pos);
}
/* Reset LastBufferId */
LastBufferId = 0;
return 0;
}
/**
* rpmsg_sdb_ioctl - IOCTL
*
* @session: ibmvmc_file_session struct
* @cmd: cmd field
* @arg: Argument field
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static long rpmsg_sdb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int idx = 0;
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *buffer, *lastbuffer;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_ioctl_set_efd q_set_efd;
struct rpmsg_sdb_ioctl_get_data_size q_get_dat_size;
void __user *argp = (void __user *)arg;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
switch (cmd) {
case RPMSG_SDB_IOCTL_SET_EFD:
mutex_lock(&_rpmsg_sdb->mutex);
/* Get index from the last buffer in the list */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
lastbuffer = list_last_entry(&_rpmsg_sdb->buffer_list, struct sdb_buf_t, buflist);
idx = lastbuffer->index;
/* Check last index was properly initiated*/
if (lastbuffer->vaddr == NULL) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - previous buffer was not allocated\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EBADE;
}
/* increment this index for the next buffer creation*/
idx++;
}
if (copy_from_user(&q_set_efd, (struct rpmsg_sdb_ioctl_set_efd *)argp,
sizeof(struct rpmsg_sdb_ioctl_set_efd))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - copy from user failed\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EFAULT;
}
/* create a new buffer which will be added in the buffer list */
buffer = kmalloc(sizeof(struct sdb_buf_t), GFP_KERNEL);
buffer->index = idx;
buffer->vaddr = NULL;
buffer->efd_ctx = eventfd_ctx_fdget(q_set_efd.eventfd);
list_add_tail(&buffer->buflist, &_rpmsg_sdb->buffer_list);
mutex_unlock(&_rpmsg_sdb->mutex);
break;
case RPMSG_SDB_IOCTL_GET_DATA_SIZE:
if (copy_from_user(&q_get_dat_size, (struct rpmsg_sdb_ioctl_get_data_size *)argp,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy from user failed\n");
return -EFAULT;
}
/* Get the index of the requested buffer and then look-up in the buffer list*/
idx = q_get_dat_size.bufferId;
list_for_each(pos, &_rpmsg_sdb->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == idx) {
/* Get the writing size*/
q_get_dat_size.size = datastructureptr->writing_size;
break;
}
}
if (copy_to_user((struct rpmsg_sdb_ioctl_get_data_size *)argp, &q_get_dat_size,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy to user failed\n");
return -EFAULT;
}
/* Reset the writing size*/
datastructureptr->writing_size = -1;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct file_operations rpmsg_sdb_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = rpmsg_sdb_ioctl,
.mmap = rpmsg_sdb_mmap,
.open = rpmsg_sdb_open,
.release = rpmsg_sdb_close,
};
static int rpmsg_sdb_drv_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret = 0;
int buffer_id = 0;
size_t buffer_size;
char *rpmsg_RxBuf;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpdev->dev);
if (len == 0) {
dev_err(rpmsg_sdb_dev, "(%s) Empty lenght requested\n", __func__);
return -EINVAL;
}
//dev_err(rpmsg_sdb_dev, "(%s) lenght: %d\n", __func__,len);
rpmsg_RxBuf = (char *)kmalloc(len+1, GFP_KERNEL);
memcpy(rpmsg_RxBuf, data, len);
rpmsg_RxBuf[len] = 0;
ret = rpmsg_sdb_decode_rxbuf_string(rpmsg_RxBuf, &buffer_id, &buffer_size);
kfree(rpmsg_RxBuf);
if (ret < 0)
goto out;
if (buffer_id > LastBufferId) {
ret = -EINVAL;
goto out;
}
/* Signal to User space application */
list_for_each(pos, &drv->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == buffer_id) {
datastructureptr->writing_size = buffer_size;
if (datastructureptr->writing_size > datastructureptr->size) {
dev_err(rpmsg_sdb_dev, "(%s) Writing size is bigger than buffer size\n", __func__);
ret = -EINVAL;
goto out;
}
eventfd_signal(datastructureptr->efd_ctx, 1);
break;
}
/* TODO: quid if nothing find during the loop ? */
}
out:
return ret;
}
static int rpmsg_sdb_drv_probe(struct rpmsg_device *rpdev)
{
int ret = 0;
struct device *dev = &rpdev->dev;
struct rpmsg_sdb_t *rpmsg_sdb;
pr_info("rpmsg_sdb: registering startetd\n");
rpmsg_sdb = devm_kzalloc(dev, sizeof(*rpmsg_sdb), GFP_KERNEL);
if (!rpmsg_sdb)
return -ENOMEM;
mutex_init(&rpmsg_sdb->mutex);
rpmsg_sdb->rpdev = rpdev;
rpmsg_sdb->mdev.name = "rpmsg-sdb";
rpmsg_sdb->mdev.minor = MISC_DYNAMIC_MINOR;
rpmsg_sdb->mdev.fops = &rpmsg_sdb_fops;
pr_info("rpmsg_sdb: Set Driver data\n");
dev_set_drvdata(&rpdev->dev, rpmsg_sdb);
/* Register misc device */
ret = misc_register(&rpmsg_sdb->mdev);
if (ret) {
dev_err(dev, "Failed to register device\n");
pr_info("rpmsg_sdb: Failed to register device\n");
goto err_out;
}
rpmsg_sdb_dev = rpmsg_sdb->mdev.this_device;
pr_info("rpmsg_sdb: Failed to register device\n");
dev_info(dev, "%s probed\n", rpmsg_sdb_driver_name);
err_out:
return ret;
}
static void rpmsg_sdb_drv_remove(struct rpmsg_device *rpmsgdev)
{
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpmsgdev->dev);
misc_deregister(&drv->mdev);
}
static struct rpmsg_device_id rpmsg_driver_sdb_id_table[] = {
{ .name = "rpmsg-sdb-channel" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_sdb_id_table);
static struct rpmsg_driver rpmsg_sdb_rmpsg_drv = {
.drv.name = KBUILD_MODNAME,
.drv.owner = THIS_MODULE,
.id_table = rpmsg_driver_sdb_id_table,
.probe = rpmsg_sdb_drv_probe,
.callback = rpmsg_sdb_drv_cb,
.remove = rpmsg_sdb_drv_remove,
};
static int __init rpmsg_sdb_drv_init(void)
{
int ret = 0;
/* Register rpmsg device */
ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
if (ret) {
pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
return ret;
}
pr_info("rpmsg_sdb: Init done\n");
return ret;
}
static void __exit rpmsg_sdb_drv_exit(void)
{
unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
pr_info("rpmsg_sdb: Exit\n");
}
module_init(rpmsg_sdb_drv_init);
module_exit(rpmsg_sdb_drv_exit);
MODULE_AUTHOR("Jean-Philippe Romain <jean-philippe.romain@st.com>");
MODULE_DESCRIPTION("shared data buffer over RPMSG");
MODULE_VERSION(RPMSG_SDB_DRIVER_VERSION);
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,287 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 STMicroelectronics - All Rights Reserved
*
* The rpmsg tty driver implements serial communication on the RPMsg bus to makes
* possible for user-space programs to send and receive rpmsg messages as a standard
* tty protocol.
*
* The remote processor can instantiate a new tty by requesting a "rpmsg-tty" RPMsg service.
* The "rpmsg-tty" service is directly used for data exchange. No flow control is implemented yet.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#define RPMSG_TTY_NAME "ttyRPMSG"
#define MAX_TTY_RPMSG 32
static DEFINE_IDR(tty_idr); /* tty instance id */
static DEFINE_MUTEX(idr_lock); /* protects tty_idr */
static struct tty_driver *rpmsg_tty_driver;
struct rpmsg_tty_port {
struct tty_port port; /* TTY port data */
int id; /* TTY rpmsg index */
struct rpmsg_device *rpdev; /* rpmsg device */
};
static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src)
{
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
int copied;
if (!len)
return -EINVAL;
copied = tty_insert_flip_string(&cport->port, data, len);
if (copied != len)
dev_err_ratelimited(&rpdev->dev, "Trunc buffer: available space is %d\n", copied);
tty_flip_buffer_push(&cport->port);
return 0;
}
static int rpmsg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
{
struct rpmsg_tty_port *cport = idr_find(&tty_idr, tty->index);
struct tty_port *port;
tty->driver_data = cport;
port = tty_port_get(&cport->port);
return tty_port_install(port, driver, tty);
}
static void rpmsg_tty_cleanup(struct tty_struct *tty)
{
tty_port_put(tty->port);
}
static int rpmsg_tty_open(struct tty_struct *tty, struct file *filp)
{
return tty_port_open(tty->port, tty, filp);
}
static void rpmsg_tty_close(struct tty_struct *tty, struct file *filp)
{
return tty_port_close(tty->port, tty, filp);
}
static int rpmsg_tty_write(struct tty_struct *tty, const u8 *buf, int len)
{
struct rpmsg_tty_port *cport = tty->driver_data;
struct rpmsg_device *rpdev;
int msg_max_size, msg_size;
int ret;
rpdev = cport->rpdev;
msg_max_size = rpmsg_get_mtu(rpdev->ept);
if (msg_max_size < 0)
return msg_max_size;
msg_size = min(len, msg_max_size);
/*
* Use rpmsg_trysend instead of rpmsg_send to send the message so the caller is not
* hung until a rpmsg buffer is available. In such case rpmsg_trysend returns -ENOMEM.
*/
ret = rpmsg_trysend(rpdev->ept, (void *)buf, msg_size);
if (ret) {
dev_dbg_ratelimited(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
return msg_size;
}
static unsigned int rpmsg_tty_write_room(struct tty_struct *tty)
{
struct rpmsg_tty_port *cport = tty->driver_data;
int size;
size = rpmsg_get_mtu(cport->rpdev->ept);
if (size < 0)
return 0;
return size;
}
static void rpmsg_tty_hangup(struct tty_struct *tty)
{
tty_port_hangup(tty->port);
}
static const struct tty_operations rpmsg_tty_ops = {
.install = rpmsg_tty_install,
.open = rpmsg_tty_open,
.close = rpmsg_tty_close,
.write = rpmsg_tty_write,
.write_room = rpmsg_tty_write_room,
.hangup = rpmsg_tty_hangup,
.cleanup = rpmsg_tty_cleanup,
};
static struct rpmsg_tty_port *rpmsg_tty_alloc_cport(void)
{
struct rpmsg_tty_port *cport;
int ret;
cport = kzalloc(sizeof(*cport), GFP_KERNEL);
if (!cport)
return ERR_PTR(-ENOMEM);
mutex_lock(&idr_lock);
ret = idr_alloc(&tty_idr, cport, 0, MAX_TTY_RPMSG, GFP_KERNEL);
mutex_unlock(&idr_lock);
if (ret < 0) {
kfree(cport);
return ERR_PTR(ret);
}
cport->id = ret;
return cport;
}
static void rpmsg_tty_destruct_port(struct tty_port *port)
{
struct rpmsg_tty_port *cport = container_of(port, struct rpmsg_tty_port, port);
mutex_lock(&idr_lock);
idr_remove(&tty_idr, cport->id);
mutex_unlock(&idr_lock);
kfree(cport);
}
static const struct tty_port_operations rpmsg_tty_port_ops = {
.destruct = rpmsg_tty_destruct_port,
};
static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
{
struct rpmsg_tty_port *cport;
struct device *dev = &rpdev->dev;
struct device *tty_dev;
int ret;
cport = rpmsg_tty_alloc_cport();
if (IS_ERR(cport))
return dev_err_probe(dev, PTR_ERR(cport), "Failed to alloc tty port\n");
tty_port_init(&cport->port);
cport->port.ops = &rpmsg_tty_port_ops;
tty_dev = tty_port_register_device(&cport->port, rpmsg_tty_driver,
cport->id, dev);
if (IS_ERR(tty_dev)) {
ret = dev_err_probe(dev, PTR_ERR(tty_dev), "Failed to register tty port\n");
tty_port_put(&cport->port);
return ret;
}
cport->rpdev = rpdev;
dev_set_drvdata(dev, cport);
dev_dbg(dev, "New channel: 0x%x -> 0x%x: " RPMSG_TTY_NAME "%d\n",
rpdev->src, rpdev->dst, cport->id);
return 0;
}
static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
{
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
dev_dbg(&rpdev->dev, "Removing rpmsg tty device %d\n", cport->id);
/* User hang up to release the tty */
tty_port_tty_hangup(&cport->port, false);
tty_unregister_device(rpmsg_tty_driver, cport->id);
tty_port_put(&cport->port);
}
static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = {
{ .name = "rpmsg-tty" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_tty_id_table);
static struct rpmsg_driver rpmsg_tty_rpmsg_drv = {
.drv.name = KBUILD_MODNAME,
.id_table = rpmsg_driver_tty_id_table,
.probe = rpmsg_tty_probe,
.callback = rpmsg_tty_cb,
.remove = rpmsg_tty_remove,
};
static int __init rpmsg_tty_init(void)
{
int ret;
rpmsg_tty_driver = tty_alloc_driver(MAX_TTY_RPMSG, TTY_DRIVER_REAL_RAW |
TTY_DRIVER_DYNAMIC_DEV);
if (IS_ERR(rpmsg_tty_driver))
return PTR_ERR(rpmsg_tty_driver);
rpmsg_tty_driver->driver_name = "rpmsg_tty";
rpmsg_tty_driver->name = RPMSG_TTY_NAME;
rpmsg_tty_driver->major = 0;
rpmsg_tty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
/* Disable unused mode by default */
rpmsg_tty_driver->init_termios = tty_std_termios;
rpmsg_tty_driver->init_termios.c_lflag &= ~(ECHO | ICANON);
rpmsg_tty_driver->init_termios.c_oflag &= ~(OPOST | ONLCR);
tty_set_operations(rpmsg_tty_driver, &rpmsg_tty_ops);
ret = tty_register_driver(rpmsg_tty_driver);
if (ret < 0) {
pr_err("Couldn't install driver: %d\n", ret);
goto error_put;
}
ret = register_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
if (ret < 0) {
pr_err("Couldn't register driver: %d\n", ret);
goto error_unregister;
}
return 0;
error_unregister:
tty_unregister_driver(rpmsg_tty_driver);
error_put:
tty_driver_kref_put(rpmsg_tty_driver);
return ret;
}
static void __exit rpmsg_tty_exit(void)
{
unregister_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
tty_unregister_driver(rpmsg_tty_driver);
tty_driver_kref_put(rpmsg_tty_driver);
idr_destroy(&tty_idr);
}
module_init(rpmsg_tty_init);
module_exit(rpmsg_tty_exit);
MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>");
MODULE_DESCRIPTION("remote processor messaging tty driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,553 @@
/* Copyright 2024 Markus Lehr
*
*
* This Software is owned by Markus Lehr.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// ##################################################################################################
/*
* This is a Linux kernel module/driver called "vrpmdv-monitoring-cmd " which holds the family ID
* and functions to handle the monitoring in a Generic Netlink in the kernel.
* "It registers a Netlink family called "vrpmdv-monitoring_cmd".
*
*
* You can find some more interesting documentation about Generic Netlink here:
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#include <linux/miscdevice.h>
#include <linux/eventfd.h>
#include <linux/of_platform.h>
#include <linux/list.h>
#define VRPMDV_MON_DRIVER_VERSION "1.0"
/*
* Static global variables
*/
static const char rpmsg_sdb_driver_name[] = "vrpmdv-mon-datafile";
static int LastBufferId;
struct rpmsg_sdb_ioctl_set_efd {
int bufferId, eventfd;
};
struct rpmsg_sdb_ioctl_get_data_size {
int bufferId;
uint32_t size;
};
/* ioctl numbers */
/* _IOW means userland is writing and kernel is reading */
/* _IOR means userland is reading and kernel is writing */
/* _IOWR means userland and kernel can both read and write */
#define RPMSG_SDB_IOCTL_SET_EFD _IOW('R', 0x00, struct rpmsg_sdb_ioctl_set_efd *)
#define RPMSG_SDB_IOCTL_GET_DATA_SIZE _IOWR('R', 0x01, struct rpmsg_sdb_ioctl_get_data_size *)
struct sdb_buf_t {
int index; /* index of buffer */
size_t size; /* buffer size */
size_t writing_size; /* size of data written by copro */
dma_addr_t paddr; /* physical address*/
void *vaddr; /* virtual address */
void *uaddr; /* mapped address for userland */
struct eventfd_ctx *efd_ctx; /* eventfd context */
struct list_head buflist; /* reference in the buffers list */
};
struct rpmsg_sdb_t {
struct mutex mutex; /* mutex to protect the ioctls */
struct miscdevice mdev; /* misc device ref */
struct rpmsg_device *rpdev; /* handle rpmsg device */
struct list_head buffer_list; /* buffer instances list */
};
struct device *rpmsg_sdb_dev;
static int rpmsg_sdb_format_txbuf_string(struct sdb_buf_t *buffer, char *bufinfo_str, size_t bufinfo_str_size)
{
pr_debug("rpmsg_sdb(%s): Buffer index:%d, addr:%08x, size:%08x\n",
__func__,
buffer->index,
buffer->paddr,
buffer->size);
return snprintf(bufinfo_str, bufinfo_str_size, "B%dA%08xL%08x", buffer->index, buffer->paddr, buffer->size);
}
static long rpmsg_sdb_decode_rxbuf_string(char *rxbuf_str, int *buffer_id, size_t *size)
{
int ret = 0;
char *sub_str;
long bsize;
long bufid;
const char delimiter[2] = {'L','\0'};
pr_debug("rpmsg_sdb(%s): rxbuf_str:%s\n", __func__, rxbuf_str);
/* Get first part containing the buffer id */
sub_str = strsep(&rxbuf_str, delimiter);
pr_debug("rpmsg_sdb(%s): sub_str:%s\n", __func__, sub_str);
/* Save Buffer id and size: template BxLyyyyyyyy*/
ret = kstrtol(&sub_str[1], 10, &bufid);
if (ret < 0) {
pr_err("rpmsg_sdb(ERROR): Extract of buffer id failed(%d)", ret);
goto out;
}
ret = kstrtol(&rxbuf_str[2], 16, &bsize);
if (ret < 0) {
pr_err("rpmsg_sdb(ERROR): Extract of buffer size failed(%d)", ret);
goto out;
}
*size = (size_t)bsize;
*buffer_id = (int)bufid;
out:
return ret;
}
static int rpmsg_sdb_send_buf_info(struct rpmsg_sdb_t *rpmsg_sdb, struct sdb_buf_t *buffer)
{
int count = 0, ret = 0;
const unsigned char *tbuf;
char mybuf[32];
int msg_size;
struct rpmsg_device *_rpdev;
_rpdev = rpmsg_sdb->rpdev;
msg_size = rpmsg_get_mtu(_rpdev->ept);
if (msg_size < 0)
return msg_size;
count = rpmsg_sdb_format_txbuf_string(buffer, mybuf, 32);
tbuf = &mybuf[0];
do {
/* send a message to our remote processor */
ret = rpmsg_send(_rpdev->ept, (void *)tbuf,
count > msg_size ? msg_size : count);
if (ret) {
dev_err(&_rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
if (count > msg_size) {
count -= msg_size;
tbuf += msg_size;
} else {
count = 0;
}
} while (count > 0);
return count;
}
static int rpmsg_sdb_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long vsize = vma->vm_end - vma->vm_start;
unsigned long size = PAGE_ALIGN(vsize);
unsigned long NumPages = size >> PAGE_SHIFT;
unsigned long align = get_order(size);
pgprot_t prot = pgprot_noncached(vma->vm_page_prot);
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *_buffer;
if (align > CONFIG_CMA_ALIGNMENT)
align = CONFIG_CMA_ALIGNMENT;
if (rpmsg_sdb_dev == NULL)
return -ENOMEM;
rpmsg_sdb_dev->coherent_dma_mask = DMA_BIT_MASK(32);
rpmsg_sdb_dev->dma_mask = &rpmsg_sdb_dev->coherent_dma_mask;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Field the last buffer entry which is the last one created */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
_buffer = list_last_entry(&_rpmsg_sdb->buffer_list,
struct sdb_buf_t, buflist);
_buffer->uaddr = NULL;
_buffer->size = NumPages * PAGE_SIZE;
_buffer->writing_size = -1;
_buffer->vaddr = dma_alloc_coherent(rpmsg_sdb_dev,
_buffer->size,
&_buffer->paddr,
GFP_KERNEL);
if (!_buffer->vaddr) {
pr_err("rpmsg_sdb(ERROR): Memory allocation issue\n");
return -ENOMEM;
}
pr_debug("rpmsg_sdb(%s): dma_alloc_coherent done - paddr[%d]:%x - vaddr[%d]:%p\n",
__func__,
_buffer->index,
_buffer->paddr,
_buffer->index,
_buffer->vaddr);
/* Get address for userland */
if (remap_pfn_range(vma, vma->vm_start,
(_buffer->paddr >> PAGE_SHIFT) + vma->vm_pgoff,
size, prot))
return -EAGAIN;
_buffer->uaddr = (void *)vma->vm_start;
/* Send information to remote proc */
rpmsg_sdb_send_buf_info(_rpmsg_sdb, _buffer);
} else {
dev_err(rpmsg_sdb_dev, "No existing buffer entry exist in the list !!!");
return -EINVAL;
}
/* Increment for number of requested buffer */
LastBufferId++;
return 0;
}
/**
* rpmsg_sdb_open - Open Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_open(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Initialize the buffer list*/
INIT_LIST_HEAD(&_rpmsg_sdb->buffer_list);
mutex_init(&_rpmsg_sdb->mutex);
return 0;
}
/**
* rpmsg_sdb_close - Close Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_close(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *pos, *next;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
list_for_each_entry_safe(pos, next, &_rpmsg_sdb->buffer_list, buflist) {
/* Free the CMA allocation */
pr_debug("rpmsg_sdb(%s): Free the CMA allocation: pos->size:%08x, pos->vaddr:%08x, pos->paddr:%08x\n",
__func__,
pos->size,
pos->vaddr,
pos->paddr);
dma_free_coherent(rpmsg_sdb_dev, pos->size, pos->vaddr,
pos->paddr);
/* Remove the buffer from the list */
list_del(&pos->buflist);
/* Free the buffer */
kfree(pos);
}
/* Reset LastBufferId */
LastBufferId = 0;
return 0;
}
/**
* rpmsg_sdb_ioctl - IOCTL
*
* @session: ibmvmc_file_session struct
* @cmd: cmd field
* @arg: Argument field
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static long rpmsg_sdb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int idx = 0;
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *buffer, *lastbuffer;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_ioctl_set_efd q_set_efd;
struct rpmsg_sdb_ioctl_get_data_size q_get_dat_size;
void __user *argp = (void __user *)arg;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
switch (cmd) {
case RPMSG_SDB_IOCTL_SET_EFD:
mutex_lock(&_rpmsg_sdb->mutex);
/* Get index from the last buffer in the list */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
lastbuffer = list_last_entry(&_rpmsg_sdb->buffer_list, struct sdb_buf_t, buflist);
idx = lastbuffer->index;
/* Check last index was properly initiated*/
if (lastbuffer->vaddr == NULL) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - previous buffer was not allocated\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EBADE;
}
/* increment this index for the next buffer creation*/
idx++;
}
if (copy_from_user(&q_set_efd, (struct rpmsg_sdb_ioctl_set_efd *)argp,
sizeof(struct rpmsg_sdb_ioctl_set_efd))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - copy from user failed\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EFAULT;
}
/* create a new buffer which will be added in the buffer list */
buffer = kmalloc(sizeof(struct sdb_buf_t), GFP_KERNEL);
buffer->index = idx;
buffer->vaddr = NULL;
buffer->efd_ctx = eventfd_ctx_fdget(q_set_efd.eventfd);
list_add_tail(&buffer->buflist, &_rpmsg_sdb->buffer_list);
mutex_unlock(&_rpmsg_sdb->mutex);
break;
case RPMSG_SDB_IOCTL_GET_DATA_SIZE:
if (copy_from_user(&q_get_dat_size, (struct rpmsg_sdb_ioctl_get_data_size *)argp,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy from user failed\n");
return -EFAULT;
}
/* Get the index of the requested buffer and then look-up in the buffer list*/
idx = q_get_dat_size.bufferId;
list_for_each(pos, &_rpmsg_sdb->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == idx) {
/* Get the writing size*/
q_get_dat_size.size = datastructureptr->writing_size;
break;
}
}
if (copy_to_user((struct rpmsg_sdb_ioctl_get_data_size *)argp, &q_get_dat_size,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy to user failed\n");
return -EFAULT;
}
/* Reset the writing size*/
datastructureptr->writing_size = -1;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct file_operations rpmsg_sdb_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = rpmsg_sdb_ioctl,
.mmap = rpmsg_sdb_mmap,
.open = rpmsg_sdb_open,
.release = rpmsg_sdb_close,
};
static int rpmsg_sdb_drv_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret = 0;
int buffer_id = 0;
size_t buffer_size;
char *rpmsg_RxBuf;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpdev->dev);
if (len == 0) {
dev_err(rpmsg_sdb_dev, "(%s) Empty lenght requested\n", __func__);
return -EINVAL;
}
//dev_err(rpmsg_sdb_dev, "(%s) lenght: %d\n", __func__,len);
rpmsg_RxBuf = (char *)kmalloc(len+1, GFP_KERNEL);
memcpy(rpmsg_RxBuf, data, len);
rpmsg_RxBuf[len] = 0;
ret = rpmsg_sdb_decode_rxbuf_string(rpmsg_RxBuf, &buffer_id, &buffer_size);
kfree(rpmsg_RxBuf);
if (ret < 0)
goto out;
if (buffer_id > LastBufferId) {
ret = -EINVAL;
goto out;
}
/* Signal to User space application */
list_for_each(pos, &drv->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == buffer_id) {
datastructureptr->writing_size = buffer_size;
if (datastructureptr->writing_size > datastructureptr->size) {
dev_err(rpmsg_sdb_dev, "(%s) Writing size is bigger than buffer size\n", __func__);
ret = -EINVAL;
goto out;
}
eventfd_signal(datastructureptr->efd_ctx, 1);
break;
}
/* TODO: quid if nothing find during the loop ? */
}
out:
return ret;
}
static int rpmsg_sdb_drv_probe(struct rpmsg_device *rpdev)
{
int ret = 0;
struct device *dev = &rpdev->dev;
struct rpmsg_sdb_t *rpmsg_sdb;
rpmsg_sdb = devm_kzalloc(dev, sizeof(*rpmsg_sdb), GFP_KERNEL);
if (!rpmsg_sdb)
return -ENOMEM;
mutex_init(&rpmsg_sdb->mutex);
rpmsg_sdb->rpdev = rpdev;
rpmsg_sdb->mdev.name = "rpmsg-sdb";
rpmsg_sdb->mdev.minor = MISC_DYNAMIC_MINOR;
rpmsg_sdb->mdev.fops = &rpmsg_sdb_fops;
dev_set_drvdata(&rpdev->dev, rpmsg_sdb);
/* Register misc device */
ret = misc_register(&rpmsg_sdb->mdev);
if (ret) {
dev_err(dev, "Failed to register device\n");
goto err_out;
}
rpmsg_sdb_dev = rpmsg_sdb->mdev.this_device;
dev_info(dev, "%s probed\n", rpmsg_sdb_driver_name);
err_out:
return ret;
}
static void rpmsg_sdb_drv_remove(struct rpmsg_device *rpmsgdev)
{
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpmsgdev->dev);
misc_deregister(&drv->mdev);
}
static struct rpmsg_device_id rpmsg_driver_sdb_id_table[] = {
{ .name = "rpmsg-sdb-channel" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_sdb_id_table);
static struct rpmsg_driver rpmsg_sdb_rmpsg_drv = {
.drv.name = KBUILD_MODNAME,
.drv.owner = THIS_MODULE,
.id_table = rpmsg_driver_sdb_id_table,
.probe = rpmsg_sdb_drv_probe,
.callback = rpmsg_sdb_drv_cb,
.remove = rpmsg_sdb_drv_remove,
};
static int __init rpmsg_sdb_drv_init(void)
{
int ret = 0;
/* Register rpmsg device */
ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
if (ret) {
pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
return ret;
}
pr_info("rpmsg_sdb: Init done\n");
return ret;
}
static void __exit rpmsg_sdb_drv_exit(void)
{
unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
pr_info("rpmsg_sdb: Exit\n");
}
module_init(rpmsg_sdb_drv_init);
module_exit(rpmsg_sdb_drv_exit);
MODULE_AUTHOR("Jean-Philippe Romain <jean-philippe.romain@st.com>");
MODULE_DESCRIPTION("shared data buffer over RPMSG");
MODULE_VERSION(RPMSG_SDB_DRIVER_VERSION);
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,552 @@
/* Copyright 2024 Markus Lehr
*
*
* This Software is owned by Markus Lehr.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// ##################################################################################################
/*
* This is a Linux kernel module/driver called "vrpmdv-monitoring-cmd " which holds the family ID
* and functions to handle the monitoring in a Generic Netlink in the kernel.
* "It registers a Netlink family called "vrpmdv-monitoring_cmd".
*
*
* You can find some more interesting documentation about Generic Netlink here:
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#include <linux/miscdevice.h>
#include <linux/eventfd.h>
#include <linux/of_platform.h>
#include <linux/list.h>
#define VRPMDV_MON_DRIVER_VERSION "1.0"
/*
* Static global variables
*/
static const char vrpmdv_mon_datafile[] = "vrpmdv-mon-datafile";
static int LastBufferId;
struct rpmsg_sdb_ioctl_set_efd {
int bufferId, eventfd;
};
struct rpmsg_sdb_ioctl_get_data_size {
int bufferId;
uint32_t size;
};
/* ioctl numbers */
/* _IOW means userland is writing and kernel is reading */
/* _IOR means userland is reading and kernel is writing */
/* _IOWR means userland and kernel can both read and write */
#define RPMSG_SDB_IOCTL_SET_EFD _IOW('R', 0x00, struct rpmsg_sdb_ioctl_set_efd *)
#define RPMSG_SDB_IOCTL_GET_DATA_SIZE _IOWR('R', 0x01, struct rpmsg_sdb_ioctl_get_data_size *)
struct sdb_buf_t {
int index; /* index of buffer */
size_t size; /* buffer size */
size_t writing_size; /* size of data written by copro */
dma_addr_t paddr; /* physical address*/
void *vaddr; /* virtual address */
void *uaddr; /* mapped address for userland */
struct eventfd_ctx *efd_ctx; /* eventfd context */
struct list_head buflist; /* reference in the buffers list */
};
struct rpmsg_sdb_t {
struct mutex mutex; /* mutex to protect the ioctls */
struct miscdevice mdev; /* misc device ref */
struct rpmsg_device *rpdev; /* handle rpmsg device */
struct list_head buffer_list; /* buffer instances list */
};
struct device *rpmsg_sdb_dev;
static int rpmsg_sdb_format_txbuf_string(struct sdb_buf_t *buffer, char *bufinfo_str, size_t bufinfo_str_size)
{
pr_debug("vrpmdv-mon-datafile(%s): Buffer index:%d, addr:%08x, size:%08x\n",
__func__,
buffer->index,
buffer->paddr,
buffer->size);
return snprintf(bufinfo_str, bufinfo_str_size, "B%dA%08xL%08x", buffer->index, buffer->paddr, buffer->size);
}
static long rpmsg_sdb_decode_rxbuf_string(char *rxbuf_str, int *buffer_id, size_t *size)
{
int ret = 0;
char *sub_str;
long bsize;
long bufid;
const char delimiter[2] = {'L','\0'};
pr_debug("vrpmdv-mon-datafile(%s): rxbuf_str:%s\n", __func__, rxbuf_str);
/* Get first part containing the buffer id */
sub_str = strsep(&rxbuf_str, delimiter);
pr_debug("vrpmdv-mon-datafile(%s): sub_str:%s\n", __func__, sub_str);
/* Save Buffer id and size: template BxLyyyyyyyy*/
ret = kstrtol(&sub_str[1], 10, &bufid);
if (ret < 0) {
pr_err("vrpmdv-mon-datafile(ERROR): Extract of buffer id failed(%d)", ret);
goto out;
}
ret = kstrtol(&rxbuf_str[2], 16, &bsize);
if (ret < 0) {
pr_err("vrpmdv-mon-datafile(ERROR): Extract of buffer size failed(%d)", ret);
goto out;
}
*size = (size_t)bsize;
*buffer_id = (int)bufid;
out:
return ret;
}
static int rpmsg_sdb_send_buf_info(struct rpmsg_sdb_t *rpmsg_sdb, struct sdb_buf_t *buffer)
{
int count = 0, ret = 0;
const unsigned char *tbuf;
char mybuf[32];
int msg_size;
struct rpmsg_device *_rpdev;
_rpdev = rpmsg_sdb->rpdev;
msg_size = rpmsg_get_mtu(_rpdev->ept);
if (msg_size < 0)
return msg_size;
count = rpmsg_sdb_format_txbuf_string(buffer, mybuf, 32);
tbuf = &mybuf[0];
do {
/* send a message to our remote processor */
ret = rpmsg_send(_rpdev->ept, (void *)tbuf,
count > msg_size ? msg_size : count);
if (ret) {
dev_err(&_rpdev->dev, "vrpmdv-mon-datafile failed: %d\n", ret);
return ret;
}
if (count > msg_size) {
count -= msg_size;
tbuf += msg_size;
} else {
count = 0;
}
} while (count > 0);
return count;
}
static int rpmsg_sdb_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long vsize = vma->vm_end - vma->vm_start;
unsigned long size = PAGE_ALIGN(vsize);
unsigned long NumPages = size >> PAGE_SHIFT;
unsigned long align = get_order(size);
pgprot_t prot = pgprot_noncached(vma->vm_page_prot);
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *_buffer;
if (align > CONFIG_CMA_ALIGNMENT)
align = CONFIG_CMA_ALIGNMENT;
if (rpmsg_sdb_dev == NULL)
return -ENOMEM;
rpmsg_sdb_dev->coherent_dma_mask = DMA_BIT_MASK(32);
rpmsg_sdb_dev->dma_mask = &rpmsg_sdb_dev->coherent_dma_mask;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Field the last buffer entry which is the last one created */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
_buffer = list_last_entry(&_rpmsg_sdb->buffer_list,
struct sdb_buf_t, buflist);
_buffer->uaddr = NULL;
_buffer->size = NumPages * PAGE_SIZE;
_buffer->writing_size = -1;
_buffer->vaddr = dma_alloc_coherent(rpmsg_sdb_dev,
_buffer->size,
&_buffer->paddr,
GFP_KERNEL);
if (!_buffer->vaddr) {
pr_err("vrpmdv-mon-datafile(ERROR): Memory allocation issue\n");
return -ENOMEM;
}
pr_debug("vrpmdv-mon-datafile(%s): dma_alloc_coherent done - paddr[%d]:%x - vaddr[%d]:%p\n",
__func__,
_buffer->index,
_buffer->paddr,
_buffer->index,
_buffer->vaddr);
/* Get address for userland */
if (remap_pfn_range(vma, vma->vm_start,
(_buffer->paddr >> PAGE_SHIFT) + vma->vm_pgoff,
size, prot))
return -EAGAIN;
_buffer->uaddr = (void *)vma->vm_start;
/* Send information to remote proc */
rpmsg_sdb_send_buf_info(_rpmsg_sdb, _buffer);
} else {
dev_err(rpmsg_sdb_dev, "No existing buffer entry exist in the list !!!");
return -EINVAL;
}
/* Increment for number of requested buffer */
LastBufferId++;
return 0;
}
/**
* rpmsg_sdb_open - Open Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_open(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Initialize the buffer list*/
INIT_LIST_HEAD(&_rpmsg_sdb->buffer_list);
mutex_init(&_rpmsg_sdb->mutex);
return 0;
}
/**
* rpmsg_sdb_close - Close Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_close(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *pos, *next;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,mdev);
list_for_each_entry_safe(pos, next, &_rpmsg_sdb->buffer_list, buflist) {
/* Free the CMA allocation */
pr_debug("vrpmdv-mon-datafile(%s): Free the CMA allocation: pos->size:%08x, pos->vaddr:%08x, pos->paddr:%08x\n",
__func__,
pos->size,
pos->vaddr,
pos->paddr);
dma_free_coherent(rpmsg_sdb_dev, pos->size, pos->vaddr,
pos->paddr);
/* Remove the buffer from the list */
list_del(&pos->buflist);
/* Free the buffer */
kfree(pos);
}
/* Reset LastBufferId */
LastBufferId = 0;
return 0;
}
/**
* rpmsg_sdb_ioctl - IOCTL
*
* @session: ibmvmc_file_session struct
* @cmd: cmd field
* @arg: Argument field
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static long rpmsg_sdb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int idx = 0;
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *buffer, *lastbuffer;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_ioctl_set_efd q_set_efd;
struct rpmsg_sdb_ioctl_get_data_size q_get_dat_size;
void __user *argp = (void __user *)arg;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
switch (cmd) {
case RPMSG_SDB_IOCTL_SET_EFD:
mutex_lock(&_rpmsg_sdb->mutex);
/* Get index from the last buffer in the list */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
lastbuffer = list_last_entry(&_rpmsg_sdb->buffer_list, struct sdb_buf_t, buflist);
idx = lastbuffer->index;
/* Check last index was properly initiated*/
if (lastbuffer->vaddr == NULL) {
pr_err("vrpmdv-mon-datafile(ERROR): RPMSG_SDB_IOCTL_SET_EFD - previous buffer was not allocated\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EBADE;
}
/* increment this index for the next buffer creation*/
idx++;
}
if (copy_from_user(&q_set_efd, (struct rpmsg_sdb_ioctl_set_efd *)argp,
sizeof(struct rpmsg_sdb_ioctl_set_efd))) {
pr_err("vrpmdv-mon-datafile(ERROR): RPMSG_SDB_IOCTL_SET_EFD - copy from user failed\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EFAULT;
}
/* create a new buffer which will be added in the buffer list */
buffer = kmalloc(sizeof(struct sdb_buf_t), GFP_KERNEL);
buffer->index = idx;
buffer->vaddr = NULL;
buffer->efd_ctx = eventfd_ctx_fdget(q_set_efd.eventfd);
list_add_tail(&buffer->buflist, &_rpmsg_sdb->buffer_list);
mutex_unlock(&_rpmsg_sdb->mutex);
break;
case RPMSG_SDB_IOCTL_GET_DATA_SIZE:
if (copy_from_user(&q_get_dat_size, (struct rpmsg_sdb_ioctl_get_data_size *)argp,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("vrpmdv-mon-datafile(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy from user failed\n");
return -EFAULT;
}
/* Get the index of the requested buffer and then look-up in the buffer list*/
idx = q_get_dat_size.bufferId;
list_for_each(pos, &_rpmsg_sdb->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == idx) {
/* Get the writing size*/
q_get_dat_size.size = datastructureptr->writing_size;
break;
}
}
if (copy_to_user((struct rpmsg_sdb_ioctl_get_data_size *)argp, &q_get_dat_size,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("vrpmdv-mon-datafile(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy to user failed\n");
return -EFAULT;
}
/* Reset the writing size*/
datastructureptr->writing_size = -1;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct file_operations rpmsg_sdb_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = rpmsg_sdb_ioctl,
.mmap = rpmsg_sdb_mmap,
.open = rpmsg_sdb_open,
.release = rpmsg_sdb_close,
};
static int rpmsg_sdb_drv_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret = 0;
int buffer_id = 0;
size_t buffer_size;
char *rpmsg_RxBuf;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpdev->dev);
if (len == 0) {
dev_err(rpmsg_sdb_dev, "(%s) Empty lenght requested\n", __func__);
return -EINVAL;
}
//dev_err(rpmsg_sdb_dev, "(%s) lenght: %d\n", __func__,len);
rpmsg_RxBuf = (char *)kmalloc(len+1, GFP_KERNEL);
memcpy(rpmsg_RxBuf, data, len);
rpmsg_RxBuf[len] = 0;
ret = rpmsg_sdb_decode_rxbuf_string(rpmsg_RxBuf, &buffer_id, &buffer_size);
kfree(rpmsg_RxBuf);
if (ret < 0)
goto out;
if (buffer_id > LastBufferId) {
ret = -EINVAL;
goto out;
}
/* Signal to User space application */
list_for_each(pos, &drv->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == buffer_id) {
datastructureptr->writing_size = buffer_size;
if (datastructureptr->writing_size > datastructureptr->size) {
dev_err(rpmsg_sdb_dev, "(%s) Writing size is bigger than buffer size\n", __func__);
ret = -EINVAL;
goto out;
}
eventfd_signal(datastructureptr->efd_ctx, 1);
break;
}
/* TODO: quid if nothing find during the loop ? */
}
out:
return ret;
}
static int rpmsg_sdb_drv_probe(struct rpmsg_device *rpdev)
{
int ret = 0;
struct device *dev = &rpdev->dev;
struct rpmsg_sdb_t *rpmsg_sdb;
rpmsg_sdb = devm_kzalloc(dev, sizeof(*rpmsg_sdb), GFP_KERNEL);
if (!rpmsg_sdb)
return -ENOMEM;
mutex_init(&rpmsg_sdb->mutex);
rpmsg_sdb->rpdev = rpdev;
rpmsg_sdb->mdev.name = "vrpmdv-mon-datafile"; //rpmsg-sdb
rpmsg_sdb->mdev.minor = MISC_DYNAMIC_MINOR;
rpmsg_sdb->mdev.fops = &rpmsg_sdb_fops;
dev_set_drvdata(&rpdev->dev, rpmsg_sdb);
/* Register misc device */
ret = misc_register(&rpmsg_sdb->mdev);
if (ret) {
dev_err(dev, "Failed to register device\n");
goto err_out;
}
rpmsg_sdb_dev = rpmsg_sdb->mdev.this_device;
dev_info(dev, "%s probed\n", vrpmdv_mon_datafile);
err_out:
return ret;
}
static void rpmsg_sdb_drv_remove(struct rpmsg_device *rpmsgdev)
{
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpmsgdev->dev);
misc_deregister(&drv->mdev);
}
static struct rpmsg_device_id rpmsg_driver_sdb_id_table[] = {
{ .name = "vrpmdv-mon-datafile" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_sdb_id_table);
static struct rpmsg_driver rpmsg_sdb_rmpsg_drv = {
.drv.name = KBUILD_MODNAME,
.drv.owner = THIS_MODULE,
.id_table = rpmsg_driver_sdb_id_table,
.probe = rpmsg_sdb_drv_probe,
.callback = rpmsg_sdb_drv_cb,
.remove = rpmsg_sdb_drv_remove,
};
static int __init rpmsg_sdb_drv_init(void)
{
int ret = 0;
/* Register rpmsg device */
ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
if (ret) {
pr_err("vrpmdv-mon-datafile(ERROR): Failed to register device\n");
return ret;
}
pr_info("vrpmdv-mon-datafile: Init done\n");
return ret;
}
static void __exit rpmsg_sdb_drv_exit(void)
{
unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
pr_info("vrpmdv-mon-datafile: Exit\n");
}
module_init(rpmsg_sdb_drv_init);
module_exit(rpmsg_sdb_drv_exit);
MODULE_AUTHOR("Markus Lehr <markus@malehr.de>");
MODULE_DESCRIPTION("shared data buffer over RPMSG");
MODULE_VERSION(VRPMDV_MON_DRIVER_VERSION);
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,706 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) STMicroelectronics 2019 - All Rights Reserved
* Author: Jean-Philippe Romain <jean-philippe.romain@st.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#include <linux/miscdevice.h>
#include <linux/eventfd.h>
#include <linux/of_platform.h>
#include <linux/list.h>
#include <linux/types.h>
#define RPMSG_SDB_DRIVER_VERSION "1.0"
/*
* Static global variables
*/
static const char rpmsg_sdb_driver_name[] = "vrpmdv-mon-datafile";
static int LastBufferId;
struct rpmsg_sdb_ioctl_set_efd {
int bufferId, eventfd;
};
struct rpmsg_sdb_ioctl_get_data_size {
int bufferId;
uint32_t size;
};
/* ioctl numbers */
/* _IOW means userland is writing and kernel is reading */
/* _IOR means userland is reading and kernel is writing */
/* _IOWR means userland and kernel can both read and write */
#define RPMSG_SDB_IOCTL_SET_EFD _IOW('R', 0x00, struct rpmsg_sdb_ioctl_set_efd *)
#define RPMSG_SDB_IOCTL_GET_DATA_SIZE _IOWR('R', 0x01, struct rpmsg_sdb_ioctl_get_data_size *)
struct sdb_buf_t {
int index; /* index of buffer */
size_t size; /* buffer size */
size_t writing_size; /* size of data written by copro */
dma_addr_t paddr; /* physical address*/
void *vaddr; /* virtual address */
void *uaddr; /* mapped address for userland */
struct eventfd_ctx *efd_ctx; /* eventfd context */
struct list_head buflist; /* reference in the buffers list */
};
struct rpmsg_sdb_t {
struct mutex mutex; /* mutex to protect the ioctls */
struct miscdevice mdev; /* misc device ref */
struct rpmsg_device *rpdev; /* handle rpmsg device */
struct list_head buffer_list; /* buffer instances list */
};
struct device *rpmsg_sdb_dev;
struct vRCMDeviceData {
uint32_t packageNo; //current package Number
uint32_t packageCount; //complete package Number
uint32_t dataQuantity; //number of uint32_t in data
uint32_t data[]; //the data
};
static int rpmsg_sdb_format_txbuf_string(struct sdb_buf_t *buffer, char *bufinfo_str, size_t bufinfo_str_size)
{
pr_info("rpmsg_sdb(%s): Buffer index:%d, addr:%08x, size:%08x\n",
__func__,
buffer->index,
buffer->paddr,
buffer->size);
return snprintf(bufinfo_str, bufinfo_str_size, "B%dA%08xL%08x", buffer->index, buffer->paddr, buffer->size);
}
static long rpmsg_sdb_decode_rxbuf_string(char *rxbuf_str, int *buffer_id, size_t *size)
{
int ret = 0;
char *sub_str;
long bsize;
long bufid;
const char delimiter[2] = {'L','\0'};
//__u32* data = (__u32*) rxbuf_str;
struct vRCMDeviceData* pdata = (struct vRCMDeviceData*) rxbuf_str;
//pr_info("rpmsg_sdb(%s): rxbuf_str:%s\n", __func__, rxbuf_str);
pr_info("rpmsg_sdb(%s): packageno:%d\n", __func__, pdata->packageNo);
pr_info("rpmsg_sdb(%s): packageCount:%d\n", __func__, pdata->packageCount);
pr_info("rpmsg_sdb(%s): dataquantity:%d\n", __func__, pdata->dataQuantity);
pr_info("rpmsg_sdb(%s): data:%d\n", __func__, (pdata->data)[0]);
// pr_info("rpmsg_sdb(%s): rxbuf_str:%d\n", __func__, data[0]);
// pr_info("rpmsg_sdb(%s): rxbuf_str:%d\n", __func__, data[1]);
// pr_info("rpmsg_sdb(%s): rxbuf_str:%d\n", __func__, data[2]);
/* Get first part containing the buffer id */
// sub_str = strsep(&rxbuf_str, delimiter);
// // pr_info("rpmsg_sdb(%s): sub_str:%s\n", __func__, sub_str);
// /* Save Buffer id and size: template BxLyyyyyyyy*/
// ret = kstrtol(&sub_str[1], 10, &bufid);
// if (ret < 0) {
// // pr_info("rpmsg_sdb(ERROR): Extract of buffer id failed(%d)", ret);
// goto out;
// }
// ret = kstrtol(&rxbuf_str[2], 16, &bsize);
// if (ret < 0) {
// // pr_info("rpmsg_sdb(ERROR): Extract of buffer size failed(%d)", ret);
// goto out;
// }
// *size = (size_t)bsize;
// *buffer_id = (int)bufid;
// out:
return ret;
}
static int rpmsg_sdb_send_buf_info(struct rpmsg_sdb_t *rpmsg_sdb, struct sdb_buf_t *buffer)
{
int count = 0, ret = 0;
const unsigned char *tbuf;
char mybuf[32];
int msg_size;
struct rpmsg_device *_rpdev;
pr_info("rpmsg_sdb(%s): start rpmsg_sdb_send_buf_info\n", __func__);
_rpdev = rpmsg_sdb->rpdev;
msg_size = rpmsg_get_mtu(_rpdev->ept);
pr_info("rpmsg_sdb(%s): checked msg site:%d\n", __func__, msg_size);
if (msg_size < 0)
return msg_size;
pr_info("rpmsg_sdb(%s): Call rpmsg_sdb_format_txbuf_string\n", __func__);
count = rpmsg_sdb_format_txbuf_string(buffer, mybuf, 32);
tbuf = &mybuf[0];
do {
/* send a message to our remote processor */
pr_info("rpmsg_sdb(%s): send a message to our remote processor\n", __func__);
ret = rpmsg_send(_rpdev->ept, (void *)tbuf,
count > msg_size ? msg_size : count);
if (ret) {
dev_err(&_rpdev->dev, "rpmsg_send failed: %d\n", ret);
pr_info("rpmsg_sdb(%s): error by send a message to our remote processor\n", __func__);
return ret;
}
else {
pr_info("rpmsg_sdb(%s): send succesfully a message to our remote processor\n", __func__);
}
if (count > msg_size) {
count -= msg_size;
tbuf += msg_size;
} else {
count = 0;
}
} while (count > 0);
return count;
}
static int rpmsg_sdb_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long vsize = vma->vm_end - vma->vm_start;
unsigned long size = PAGE_ALIGN(vsize);
unsigned long NumPages = size >> PAGE_SHIFT;
unsigned long align = get_order(size);
pgprot_t prot = pgprot_noncached(vma->vm_page_prot);
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *_buffer;
pr_info("rpmsg_sdb(%s): start rpmsg_sdb_mmap\n", __func__);
if (align > CONFIG_CMA_ALIGNMENT)
align = CONFIG_CMA_ALIGNMENT;
if (rpmsg_sdb_dev == NULL)
return -ENOMEM;
pr_info("rpmsg_sdb(%s): start rpmsg_sdb_mmap - coherent_dma_mask\n", __func__);
rpmsg_sdb_dev->coherent_dma_mask = DMA_BIT_MASK(32);
rpmsg_sdb_dev->dma_mask = &rpmsg_sdb_dev->coherent_dma_mask;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Field the last buffer entry which is the last one created */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
pr_info("rpmsg_sdb(%s): start rpmsg_sdb_mmap - bufferlist not empty\n", __func__);
_buffer = list_last_entry(&_rpmsg_sdb->buffer_list,
struct sdb_buf_t, buflist);
_buffer->uaddr = NULL;
_buffer->size = NumPages * PAGE_SIZE;
_buffer->writing_size = -1;
_buffer->vaddr = dma_alloc_coherent(rpmsg_sdb_dev,
_buffer->size,
&_buffer->paddr,
GFP_KERNEL);
if (!_buffer->vaddr) {
pr_info("rpmsg_sdb(ERROR): Memory allocation issue\n");
return -ENOMEM;
}
pr_info("rpmsg_sdb(%s): dma_alloc_coherent done - paddr[%d]:%x - vaddr[%d]:%p\n",
__func__,
_buffer->index,
_buffer->paddr,
_buffer->index,
_buffer->vaddr);
/* Get address for userland */
if (remap_pfn_range(vma, vma->vm_start,
(_buffer->paddr >> PAGE_SHIFT) + vma->vm_pgoff,
size, prot)) {
pr_info("rpmsg_sdb(%s): remap_pfn_range could not be done\n");
return -EAGAIN;
}
_buffer->uaddr = (void *)vma->vm_start;
pr_info("rpmsg_sdb(%s): _buffer->uaddr = %p\n", __func__, _buffer->uaddr);
/* Send information to remote proc */
pr_info("rpmsg_sdb(%s): Send information to remote proc\n", __func__);
rpmsg_sdb_send_buf_info(_rpmsg_sdb, _buffer);
} else {
dev_err(rpmsg_sdb_dev, "No existing buffer entry exist in the list !!!");
pr_debug("rpmsg_sdb(%s): No existing buffer entry exist in the list !!!\n", __func__);
return -EINVAL;
}
/* Increment for number of requested buffer */
LastBufferId++;
return 0;
}
/**
* rpmsg_sdb_open - Open Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_open(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Initialize the buffer list*/
pr_debug("rpmsg_sdb(%s): Init bufferlist", __func__);
pr_info("rpmsg_sdb(%s): start Init bufferlist", __func__);
INIT_LIST_HEAD(&_rpmsg_sdb->buffer_list);
pr_info("rpmsg_sdb(%s): success Init bufferlist", __func__);
mutex_init(&_rpmsg_sdb->mutex);
return 0;
}
/**
* rpmsg_sdb_close - Close Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_close(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *pos, *next;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
list_for_each_entry_safe(pos, next, &_rpmsg_sdb->buffer_list, buflist) {
/* Free the CMA allocation */
pr_info("rpmsg_sdb(%s): Free the CMA allocation 3", __func__);
dma_free_coherent(rpmsg_sdb_dev, pos->size, pos->vaddr, pos->paddr);
/* Remove the buffer from the list */
list_del(&pos->buflist);
/* Free the buffer */
kfree(pos);
}
pr_info("rpmsg_sdb(%s): Free the CMA allocation 4 - done", __func__);
/* Reset LastBufferId */
LastBufferId = 0;
return 0;
}
/**
* rpmsg_sdb_ioctl - IOCTL
*
* @session: ibmvmc_file_session struct
* @cmd: cmd field
* @arg: Argument field
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static long rpmsg_sdb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int idx = 0;
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *buffer, *lastbuffer;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_ioctl_set_efd q_set_efd;
struct rpmsg_sdb_ioctl_get_data_size q_get_dat_size;
void __user *argp = (void __user *)arg;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
switch (cmd) {
case RPMSG_SDB_IOCTL_SET_EFD:
mutex_lock(&_rpmsg_sdb->mutex);
/* Get index from the last buffer in the list */
pr_info("mon-datafile: set EFD\n");
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
lastbuffer = list_last_entry(&_rpmsg_sdb->buffer_list, struct sdb_buf_t, buflist);
idx = lastbuffer->index;
/* Check last index was properly initiated*/
if (lastbuffer->vaddr == NULL) {
// pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - previous buffer was not allocated\n");
pr_info("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - previous buffer was not allocated\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EBADE;
}
/* increment this index for the next buffer creation*/
idx++;
}
if (copy_from_user(&q_set_efd, (struct rpmsg_sdb_ioctl_set_efd *)argp,
sizeof(struct rpmsg_sdb_ioctl_set_efd))) {
// pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - copy from user failed\n");
pr_info("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - copy from user failed\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EFAULT;
}
/* create a new buffer which will be added in the buffer list */
buffer = kmalloc(sizeof(struct sdb_buf_t), GFP_KERNEL);
buffer->index = idx;
buffer->vaddr = NULL;
buffer->efd_ctx = eventfd_ctx_fdget(q_set_efd.eventfd);
list_add_tail(&buffer->buflist, &_rpmsg_sdb->buffer_list);
pr_info("rpmsg_sdb(SUCCES): RPMSG_SDB_IOCTL_SET_EFD \n");
mutex_unlock(&_rpmsg_sdb->mutex);
break;
case RPMSG_SDB_IOCTL_GET_DATA_SIZE:
pr_info("mon-datafile: get datasize\n");
if (copy_from_user(&q_get_dat_size, (struct rpmsg_sdb_ioctl_get_data_size *)argp,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy from user failed\n");
return -EFAULT;
}
/* Get the index of the requested buffer and then look-up in the buffer list*/
idx = q_get_dat_size.bufferId;
list_for_each(pos, &_rpmsg_sdb->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == idx) {
/* Get the writing size*/
q_get_dat_size.size = datastructureptr->writing_size;
break;
}
}
if (copy_to_user((struct rpmsg_sdb_ioctl_get_data_size *)argp, &q_get_dat_size,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy to user failed\n");
return -EFAULT;
}
/* Reset the writing size*/
datastructureptr->writing_size = -1;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct file_operations rpmsg_sdb_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = rpmsg_sdb_ioctl,
.mmap = rpmsg_sdb_mmap,
.open = rpmsg_sdb_open,
.release = rpmsg_sdb_close,
};
static int rpmsg_sdb_drv_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret = 0;
int buffer_id = 0;
size_t buffer_size;
char *rpmsg_RxBuf;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
//pr_info("mon-datafile: receive msg from Copro: %s, len:%d\n", __func__, len);
// struct rpmsg_sdb_t {
// struct mutex mutex; /* mutex to protect the ioctls */
// struct miscdevice mdev; /* misc device ref */
// struct rpmsg_device *rpdev; /* handle rpmsg device */
// struct list_head buffer_list; /* buffer instances list */
// };
rpmsg_RxBuf = (char *)kmalloc(len+1, GFP_KERNEL);
memcpy(rpmsg_RxBuf, data, len);
rpmsg_RxBuf[len] = 0;
// ret = rpmsg_sdb_decode_rxbuf_string(rpmsg_RxBuf, &buffer_id, &buffer_size);
// if (ret < 0)
// goto out;
// if (buffer_id > LastBufferId) {
// ret = -EINVAL;
// goto out;
// }
struct vRCMDeviceData* pdata = (struct vRCMDeviceData*) rpmsg_RxBuf;
//pr_info("rpmsg_sdb(%s): rxbuf_str:%s\n", __func__, rxbuf_str);
// pr_info("rpmsg_sdb(%s): packageno:%d\n", __func__, pdata->packageNo);
// pr_info("rpmsg_sdb(%s): packageCount:%d\n", __func__, pdata->packageCount);
// pr_info("rpmsg_sdb(%s): dataquantity:%d\n", __func__, pdata->dataQuantity);
// pr_info("rpmsg_sdb(%s): data:%d\n", __func__, (pdata->data)[0]);
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpdev->dev);
if (len == 0) {
dev_err(rpmsg_sdb_dev, "(%s) Empty lenght requested\n", __func__);
return -EINVAL;
}
if (drv == NULL) {
dev_err(rpmsg_sdb_dev, "(%s) no driver found\n", __func__);
return -EINVAL;
}
//copy data to structure
//1. get buffer
// struct vRCMDeviceData* pdata = (struct vRCMDeviceData*) kmalloc(len+1, GFP_KERNEL);
// memcpy(pdata, data, len);
// if (ret < 0)
// goto out;
// if (buffer_id > LastBufferId) {
// ret = -EINVAL;
// goto out;
// }
/**
rpmsg_RxBuf = (char *)kmalloc(len+1, GFP_KERNEL);
memcpy(rpmsg_RxBuf, data, len);
rpmsg_RxBuf[len] = 0;
ret = rpmsg_sdb_decode_rxbuf_string(rpmsg_RxBuf, &buffer_id, &buffer_size);
kfree(rpmsg_RxBuf);
if (ret < 0)
goto out;
if (buffer_id > LastBufferId) {
ret = -EINVAL;
goto out;
}
*/
/* Signal to User space application */
list_for_each(pos, &drv->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
/**
* struct vRCMDeviceData {
uint32_t packageNo; //current package Number
uint32_t packageCount; //complete package Number
uint32_t dataQuantity; //number of uint32_t in data
uint32_t data[]; //the data
};
*/
//copy data to buffer
// struct sdb_buf_t {
// int index; /* index of buffer */
// size_t size; /* buffer size */
// size_t writing_size; /* size of data written by copro */
// dma_addr_t paddr; /* physical address*/
// void *vaddr; /* virtual address */
// void *uaddr; /* mapped address for userland */
// struct eventfd_ctx *efd_ctx; /* eventfd context */
// struct list_head buflist; /* reference in the buffers list */
// };
//get the buffer as array of struct vRCMDeviceData
struct vRCMDeviceData* vrBuff = (struct vRCMDeviceData*) (datastructureptr->vaddr);
struct vRCMDeviceData* startAdress = &(vrBuff[pdata->packageNo]);
// pr_info("rpmsg_sdb(%s): startAdress:%d\n", __func__, startAdress);
if (virt_addr_valid(startAdress)) {
// pr_info("rpmsg_sdb(%s): startAdress:%d is valid\n", __func__, startAdress);
memcpy(startAdress, rpmsg_RxBuf, len);
}
else {
pr_info("rpmsg_sdb(%s): startAdress[%d]:%p is invalid!!\n", __func__, startAdress);
}
// if (datastructureptr->index == buffer_id) {
// datastructureptr->writing_size = buffer_size;
// if (datastructureptr->writing_size > datastructureptr->size) {
// dev_err(rpmsg_sdb_dev, "(%s) Writing size is bigger than buffer size\n", __func__);
// ret = -EINVAL;
// goto out;
// }
if ((pdata != NULL) && (pdata->packageNo == pdata->packageCount)) {
pr_info("rpmsg_sdb(%s): signal bufferfull!\n", __func__);
eventfd_signal(datastructureptr->efd_ctx, 1);
break;
}
}
/* Signal to User space application */
/** list_for_each(pos, &drv->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == buffer_id) {
datastructureptr->writing_size = buffer_size;
if (datastructureptr->writing_size > datastructureptr->size) {
dev_err(rpmsg_sdb_dev, "(%s) Writing size is bigger than buffer size\n", __func__);
ret = -EINVAL;
goto out;
}
eventfd_signal(datastructureptr->efd_ctx, 1);
break;
}
}
*/
out:
kfree(rpmsg_RxBuf);
kfree(pdata);
return ret;
}
static int rpmsg_sdb_drv_probe(struct rpmsg_device *rpdev)
{
int ret = 0;
struct device *dev = &rpdev->dev;
struct rpmsg_sdb_t *rpmsg_sdb;
pr_info("mon-datafile: registering started\n");
rpmsg_sdb = devm_kzalloc(dev, sizeof(*rpmsg_sdb), GFP_KERNEL);
if (!rpmsg_sdb)
return -ENOMEM;
mutex_init(&rpmsg_sdb->mutex);
rpmsg_sdb->rpdev = rpdev;
rpmsg_sdb->mdev.name = "mon-datafile";//"rpmsg-sdb";
rpmsg_sdb->mdev.minor = MISC_DYNAMIC_MINOR;
rpmsg_sdb->mdev.fops = &rpmsg_sdb_fops;
pr_info("mon-datafile: Set Driver data\n");
dev_set_drvdata(&rpdev->dev, rpmsg_sdb);
/* Register misc device */
ret = misc_register(&rpmsg_sdb->mdev);
if (ret) {
dev_err(dev, "Failed to register device\n");
pr_info("mon-datafile: Failed to register device\n");
goto err_out;
}
rpmsg_sdb_dev = rpmsg_sdb->mdev.this_device;
//pr_info("rpmsg_sdb: Failed to register device\n");
dev_info(dev, "%s probed\n", rpmsg_sdb_driver_name);
err_out:
return ret;
}
static void rpmsg_sdb_drv_remove(struct rpmsg_device *rpmsgdev)
{
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpmsgdev->dev);
misc_deregister(&drv->mdev);
}
static struct rpmsg_device_id rpmsg_driver_sdb_id_table[] = {
{ .name = "vrpmdv-mon-datafile" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_sdb_id_table);
//static struct rpmsg_driver rpmsg_sdb_rmpsg_drv = {
static struct rpmsg_driver vrpmdv_monitoring_data = {
.drv.name = KBUILD_MODNAME,
.drv.owner = THIS_MODULE,
.id_table = rpmsg_driver_sdb_id_table,
.probe = rpmsg_sdb_drv_probe,
.callback = rpmsg_sdb_drv_cb,
.remove = rpmsg_sdb_drv_remove,
};
module_rpmsg_driver(vrpmdv_monitoring_data);
// static int __init rpmsg_sdb_drv_init(void)
// {
// int ret = 0;
// /* Register rpmsg device */
// ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// if (ret) {
// pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
// return ret;
// }
// pr_info("rpmsg_sdb: Init done\n");
// return ret;
// }
// static void __exit rpmsg_sdb_drv_exit(void)
// {
// unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// pr_info("rpmsg_sdb: Exit\n");
// }
// module_init(rpmsg_sdb_drv_init);
// module_exit(rpmsg_sdb_drv_exit);
MODULE_AUTHOR("Markus Lehr <markus@malehr.de>");
MODULE_DESCRIPTION("shared data buffer over RPMSG");
MODULE_VERSION(RPMSG_SDB_DRIVER_VERSION);
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,28 @@
/* Copyright 2024 Markus Lehr
*
*
* This Software is owned by Markus Lehr.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// ##################################################################################################
/*
* This is a Linux kernel module/driver called "vrpmdv-monitoring-cmd " which holds the family ID
* and functions to handle the monitoring in a Generic Netlink in the kernel.
* "It registers a Netlink family called "vrpmdv-monitoring_cmd".
*
*
* You can find some more interesting documentation about Generic Netlink here:
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
*/
MODULE_AUTHOR("Markus Lehr <markus@malehr.de>");
MODULE_DESCRIPTION("shared data buffer over RPMSG");
MODULE_VERSION(VRPMDV_MON_DRIVER_VERSION);
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,804 @@
/* Copyright 2024 Markus Lehr
*
*
* This Software is owned by Markus Lehr.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// ##################################################################################################
/*
* This is a Linux kernel module/driver called "vrpmdv-monitoring-cmd " which holds the family ID
* and functions to handle the monitoring in a Generic Netlink in the kernel.
* "It registers a Netlink family called "vrpmdv-monitoring_cmd".
*
*
* You can find some more interesting documentation about Generic Netlink here:
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
*/
#include <linux/kernel.h>
#include <linux/rpmsg.h>
// basic definitions for kernel module development
#include <linux/module.h>
// definitions for generic netlink families, policies etc;
// transitive dependencies for basic netlink, sockets etc
#include <net/genetlink.h>
// required for locking inside the .dumpit callback demonstration
#include <linux/mutex.h>
#include <linux/wait.h>
// data/vars/enums/properties that describes our protocol that we implement
// on top of generic netlink (like functions we want to trigger on the receiving side)
//#include "vrpmdv-monitoring-cmd.h"
/* ######################## CONVENIENT LOGGING MACROS ######################## */
// (Re)definition of some convenient logging macros from <linux/printk.h>. You can see the logging
// messages when printing the kernel log, e.g. with `$ sudo dmesg`.
// See https://elixir.bootlin.com/linux/latest/source/include/linux/printk.h
// with this redefinition we can easily prefix all log messages from pr_* logging macros
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* ########################################################################### */
#define MSG "hello Monitoring!"
static int count = 100;
module_param(count, int, 0644);
struct instance_data {
int rx_count;
};
// std::chrono::seconds timeoutPeriod = 5;
// auto timePoint = std::chrono::system_clock::now() + timeoutPeriod;
/** ----- NETLINK Driver defintion ------------------*/
/**
* Generic Netlink will create a Netlink family with this name. Kernel will asign
* a numeric ID and afterwards we can talk to the family with its ID. To get
* the ID we use Generic Netlink in the userland and pass the family name.
*
* Short for: Generic Netlink VRPMDV Monitoring gnl_foobar_mcmd
*/
#define FAMILY_NAME "gnl-vrpmdv-mcmd"
/**
* These are the attributes that we want to share in gnl_foobar_xmpl.
* You can understand an attribute as a semantic type. This is
* the payload of Netlink messages.
* GNl: Generic Netlink
*/
enum GNL_VRPMDV_XMPL_ATTRIBUTE {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_A_UNSPEC,
/** We expect a MSG to be a null-terminated C-string. */
GNL_VRPMDV_MCMD_A_MSG,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_A_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN (__GNL_VRPMDV_MCMD_A_MAX)
/**
* The number of actual usable attributes in `enum GNL_VRPMDV_MCMD_ATTRIBUTE`.
* This is `GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_ATTRIBUTE_COUNT (GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN - 1)
/**
* Enumeration of all commands (functions) that our custom protocol on top
* of generic netlink supports. This can be understood as the action that
* we want to trigger on the receiving side.
*/
enum GNL_VRPMDV_MCMD_COMMAND {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_C_UNSPEC,
// first real command is "1" (>0)
/**
* When this command is received, we expect the attribute `GNL_VRPMDV_MCMD_ATTRIBUTE::GNL_VRPMDV_MCMD_A_MSG` to
* be present in the Generic Netlink request message. The kernel reads the message from the packet and
* sent it to the copro. THe result will be return by creating a new Generic Netlink response message
* with an corresponding attribute/payload.
*
* This command/signaling mechanism is independent of the Netlink flag `NLM_F_ECHO (0x08)`. We use it as
* "echo specific data" instead of return a 1:1 copy of the package, which you could do with
* `NLM_F_ECHO (0x08)` for example.
*/
GNL_VRPMDV_MCMD_C_MSG,
/**
* Provokes a NLMSG_ERR answer to this request as described in netlink manpage
* (https://man7.org/linux/man-pages/man7/netlink.7.html).
*/
GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_C_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN (__GNL_VRPMDV_MCMD_C_MAX)
/**
* The number of actual usable commands in `enum GNL_FOOBAR_XMPL_COMMAND`.
* This is `GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_COMMAND_COUNT (GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN - 1)
#define NODATARECEIVED 0
#define DATARECEIVED 1
/**
* Data structure required for our .doit callback handler to
* know about the progress of an ongoing cmd execution.
* See the cmd callback handler how it is used.
*/
// struct {
// // from <linux/mutex.h>
// /**
// * rpmsg wait for response from copro side.
// */
// struct mutex sendMTX;
// /**
// * rpmsg wait for response from copro side.
// */
// struct mutex receiveCV;
// /**
// * Wait Queue: if it is signaled we have received data from copro
// */
// wait_queue_head_t receive_queue;
// /**
// * Waitflag: 0= no data received, 1 = data received
// */
// int receive_queue_flag = NODATARECEIVED;
// /**
// * Condition vaiable signal we have received data from copro
// */
// // std::condition_variable cv;
// // /**
// // * Number that describes how many packets we need to send until we are done
// // * during an ongoing dumpit process. 0 = done.
// // */
// // int unsigned runs_to_go;
// // /**
// // * Number that describes how many packets per dump are sent in total.
// // * Constant per dump.
// // */
// // int unsigned total_runs;
// //the rpmsg device which sends the data to the copro
// struct rpmsg_device *rpdev; /* handle rpmsg device */
// } cmd_cb_progress_data;
struct rpmsg_vrpmdv_mon_t{
// from <linux/mutex.h>
/**
* rpmsg wait for response from copro side.
*/
struct mutex sendMTX;
/**
* rpmsg wait for response from copro side.
*/
struct mutex receiveCV;
/**
* Wait Queue: if it is signaled we have received data from copro
*/
wait_queue_head_t receive_queue;
/**
* Waitflag: 0= no data received, 1 = data received
*/
int receive_queue_flag;
//the rpmsg device which sends the data to the copro
struct rpmsg_device* rpdev; /* handle rpmsg device */
};
struct rpmsg_vrpmdv_mon_t vrpmdv_mon;
// struct mutex mutex; /* mutex to protect the ioctls */
// struct miscdevice mdev; /* misc device ref */
// struct rpmsg_device *rpdev; /* handle rpmsg device */
// struct list_head buffer_list; /* buffer instances list */
// Documentation is on the implementation of this function.
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info);
// Documentation is on the implementation of this function.
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info);
/**
* The length of `struct genl_ops gnl_foobar_xmpl_ops[]`. Not necessarily
* the number of commands in `enum GNlFoobarXmplCommand`. It depends on your application logic.
* For example, you can use the same command multiple times and - dependent by flag -
* invoke a different callback handler. In our simple example we just use one .doit callback
* per operation/command.
*/
#define GNL_VRPMDV_OPS_LEN (GNL_VRPMDV_MCMD_COMMAND_COUNT)
/**
* Array with all operations that our protocol on top of Generic Netlink
* supports. An operation is the glue between a command ("cmd" field in `struct genlmsghdr` of
* received Generic Netlink message) and the corresponding ".doit" callback function.
* See: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L148
*/
struct genl_ops gnl_vrpmdv_mcmd_ops[GNL_VRPMDV_OPS_LEN] = {
{
/* The "cmd" field in `struct genlmsghdr` of received Generic Netlink message */
.cmd = GNL_VRPMDV_MCMD_C_MSG,
/* TODO Use case ? */
.flags = 0,
/* TODO Use case ? */
.internal_flags = 0,
/* Callback handler when a request with the specified ".cmd" above is received.
* Always validates the payload except one set NO_STRICT_VALIDATION flag in ".validate"
* See: https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*
* Quote from: https://lwn.net/Articles/208755
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*
*/
.doit = gnl_cb_vrpmdv_doit,
/* This callback is similar in use to the standard Netlink 'dumpit' callback.
* The 'dumpit' callback is invoked when a Generic Netlink message is received
* with the NLM_F_DUMP flag set.
*
* A dump can be understand as a "GET ALL DATA OF THE GIVEN ENTITY", i.e.
* the userland can receive as long as the .dumpit callback returns data.
*
* .dumpit is not mandatory, but either it or .doit must be provided, see
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L367
*
* To be honest I don't know in what use case one should use .dumpit and why
* it is useful, because you can achieve the same also with .doit handlers.
* Anyway, this is just an example/tutorial.
*
* Quote from: https://lwn.net/Articles/208755
* "The main difference between a 'dumpit' handler and a 'doit' handler is
* that a 'dumpit' handler does not allocate a message buffer for a response;
* a pre-allocated sk_buff is passed to the 'dumpit' handler as the first
* parameter. The 'dumpit' handler should fill the message buffer with the
* appropriate response message and return the size of the sk_buff,
* i.e. sk_buff->len, and the message buffer will automatically be sent to the
* Generic Netlink client that initiated the request. As long as the 'dumpit'
* handler returns a value greater than zero it will be called again with a
* newly allocated message buffer to fill, when the handler has no more data
* to send it should return zero; error conditions are indicated by returning
* a negative value. If necessary, state can be preserved in the
* netlink_callback parameter which is passed to the 'dumpit' handler; the
* netlink_callback parameter values will be preserved across handler calls
* for a single request."
*
* You can see the check for the NLM_F_DUMP-flag here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L780
*/
.dumpit = NULL, //gnl_cb_echo_dumpit,
/* Start callback for dumps. Can be used to lock data structures. */
.start = NULL, //gnl_cb_echo_dumpit_before,
/* Completion callback for dumps. Can be used for cleanup after a dump and releasing locks. */
.done = NULL, //gnl_cb_echo_dumpit_before_after,
/*
0 (= "validate strictly") or value `enum genl_validate_flags`
* see: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L108
*/
.validate = 0,
},
{
.cmd = GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
.flags = 0,
.internal_flags = 0,
.doit = gnl_cb_doit_reply_with_nlmsg_err,
// .dumpit is not required, only optional; application specific/dependent on your use case
// in a real application you probably have different .dumpit handlers per operation/command
.dumpit = NULL,
// in a real application you probably have different .start handlers per operation/command
.start = NULL,
// in a real application you probably have different .done handlers per operation/command
.done = NULL,
.validate = 0,
}
};
/**
* Attribute policy: defines which attribute has which type (e.g int, char * etc).
* This get validated for each received Generic Netlink message, if not deactivated
* in `gnl_foobar_xmpl_ops[].validate`.
* See https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*/
static struct nla_policy gnl_vrpmdv_mcmd_policy[GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN] = {
// In case you are seeing this syntax for the first time (I also learned this just after a few years of
// coding with C myself): The following static array initiations are equivalent:
// `int a[2] = {1, 2}` <==> `int a[2] = {[0] => 1, [1] => 2}`.
[GNL_VRPMDV_MCMD_A_UNSPEC] = {.type = NLA_UNSPEC},
// You can set this to NLA_U32 for testing and send an ECHO message from the userland
// It will fail in this case and you see a entry in the kernel log.
// `enum GNL_FOOBAR_XMPL_ATTRIBUTE::GNL_FOOBAR_XMPL_A_MSG` is a null-terminated C-String
[GNL_VRPMDV_MCMD_A_MSG] = {.type = NLA_NUL_STRING},
};
/**
* Definition of the Netlink family we want to register using Generic Netlink functionality
*/
static struct genl_family gnl_vrpmdv_mcmd_family = {
// automatically assign an id
.id = 0,
// we don't use custom additional header info / user specific header
.hdrsize = 0,
// The name of this family, used by userspace application to get the numeric ID
.name = FAMILY_NAME,
// family specific version number; can be used to evolve application over time (multiple versions)
.version = 1,
// delegates all incoming requests to callback functions
.ops = gnl_vrpmdv_mcmd_ops,
// length of array `gnl_foobar_xmpl_ops`
.n_ops = GNL_VRPMDV_OPS_LEN,
// attribute policy (for validation of messages). Enforced automatically, except ".validate" in
// corresponding ".ops"-field is set accordingly.
.policy = gnl_vrpmdv_mcmd_policy,
// Number of attributes / bounds check for policy (array length)
.maxattr = GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN,
// Owning Kernel module of the Netlink family we register.
.module = THIS_MODULE,
// Actually not necessary because this memory region would be zeroed anyway during module load,
// but this way one sees all possible options.
// if your application must handle multiple netlink calls in parallel (where one should not block the next
// from starting), set this to true! otherwise all netlink calls are mutually exclusive
.parallel_ops = 0,
// set to true if the family can handle network namespaces and should be presented in all of them
.netnsok = 0,
// called before an operation's doit callback, it may do additional, common, filtering and return an error
.pre_doit = NULL,
// called after an operation's doit callback, it may undo operations done by pre_doit, for example release locks
.post_doit = NULL,
};
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_VRPMDV_MCMD_C_MSG` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_vrpmdv_mcmd_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info) {
struct nlattr *na;
struct sk_buff *reply_skb;
int rc;
void *msg_head;
char *recv_msg;
pr_info("%s() invoked\n", __func__);
if (info == NULL) {
// should never happen
pr_err("An error occurred in %s():\n", __func__);
return -EINVAL;
}
/*
* For each attribute there is an index in info->attrs which points to a nlattr structure
* in this structure the data is stored.
*/
na = info->attrs[GNL_VRPMDV_MCMD_A_MSG];
if (!na) {
pr_err("no info->attrs[%i]\n", GNL_VRPMDV_MCMD_A_MSG);
return -EINVAL; // we return here because we expect to recv a msg
}
recv_msg = (char *) nla_data(na);
if (recv_msg == NULL) {
pr_err("error while receiving data\n");
} else {
pr_info("received: '%s'\n", recv_msg);
}
//aquire lock for cmd repmsg channel
// std::lock sendlk(cmd_cb_progress_data.sendmtx);
//send the message to the copro over RPMSG
rc = rpmsg_send(vrpmdv_mon.rpdev->ept, recv_msg, strlen(recv_msg));
if (rc) {
pr_err("rpmsg_send failed: %d\n", rc);
return rc;
}
// struct rpmsg_vrpmdv_mon_t *drv = dev_get_drvdata(&rpdev->dev);
{
pr_info("wait for response\n");
// wait until receive_queue_flag=1 , that means we have received data from Copro
wait_event_interruptible(vrpmdv_mon.receive_queue, vrpmdv_mon.receive_queue_flag != 0 );
pr_info("received data \n");
//Copy data
vrpmdv_mon.receive_queue_flag = NODATARECEIVED;
// std::unique_lock lk(cmd_cb_progress_data.receivemtx);
// if (myCondVar.wait_until(uLock, timePoint) == std::cv_status::timeout)
// {
// dev_err(&cmd_cb_progress_data.rpdev, "rpmsg_send failed, timeout: \n");
// return -1:
// }
pr_info("get response: '%s'\n", recv_msg);
}
// Send a message back after we receive the reply from rpmsg channel
// ---------------------
// Allocate some memory, since the size is not yet known use NLMSG_GOODSIZE
reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (reply_skb == NULL) {
pr_err("An error occurred in %s():\n", __func__);
return -ENOMEM;
}
// Create the message headers
// Add header to netlink message;
// afterwards the buffer looks like this:
// ----------------------------------
// | netlink header |
// | generic netlink header |
// | <space for netlink attributes> |
// ----------------------------------
// msg_head = genlmsg_put(reply_skb, // buffer for netlink message: struct sk_buff *
// // According to my findings: this is not used for routing
// // This can be used in an application specific way to target
// // different endpoints within the same user application
// // but general rule: just put sender port id here
// info->snd_portid, // sending port (not process) id: int
// info->snd_seq + 1, // sequence number: int (might be used by receiver, but not mandatory)
// &gnl_vrpmdv_mcmd_family, // struct genl_family *
// 0, // flags for Netlink header: int; application specific and not mandatory
// // The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
// GNL_VRPMDV_MCMD_C_MSG
// );
msg_head = genlmsg_put_reply(reply_skb, // buffer for netlink message: struct sk_buff *
info, // info
&gnl_vrpmdv_mcmd_family, // struct genl_family *
0, // flags for Netlink header: int; application specific and not mandatory
// The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
info->genlhdr->cmd
);
if (msg_head == NULL) {
rc = ENOMEM;
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Add a GNL_VRPMDV_MCMD_A_MSG attribute (actual value/payload to be sent)
// echo the value we just received
rc = nla_put_string(reply_skb, GNL_VRPMDV_MCMD_A_MSG, recv_msg);
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Finalize the message:
// Corrects the netlink message header (length) to include the appended
// attributes. Only necessary if attributes have been added to the message.
genlmsg_end(reply_skb, msg_head);
// Send the message back
rc = genlmsg_reply(reply_skb, info);
// same as genlmsg_unicast(genl_info_net(info), reply_skb, info->snd_portid)
// see https://elixir.bootlin.com/linux/v5.8.9/source/include/net/genetlink.h#L326
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
return 0;
}
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info) {
pr_info("%s() invoked, a NLMSG_ERR response will be sent back\n", __func__);
/*
* Generic Netlink is smart enough and sends a NLMSG_ERR reply automatically as reply
* Quote from https://lwn.net/Articles/208755/:
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*/
return -EINVAL;
}
/** ----- NETLINK Driver defintion ------------------*/
/**
* callback that is called after the copro send data
* we have to copy it in a buffer for the netlink and later send it back to the userland
*
*/
static int vrpmdv_monitoring_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret = 0;
struct instance_data *idata = dev_get_drvdata(&rpdev->dev);
dev_info(&rpdev->dev, "incoming msg %d (src: 0x%x)\n",
++idata->rx_count, src);
print_hex_dump_debug(__func__, DUMP_PREFIX_NONE, 16, 1, data, len,
true);
vrpmdv_mon.receive_queue_flag= DATARECEIVED;
wake_up_interruptible(&(vrpmdv_mon.receive_queue));
// /* samples should not live forever */
// if (idata->rx_count >= count) {
// dev_info(&rpdev->dev, "goodbye!\n");
// return 0;
// }
/* send a new message now */
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
// if (ret)
// dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
static int vrpmdv_monitoring_probe(struct rpmsg_device *rpdev)
{
int rc;
// int ret;
// struct instance_data *idata;
dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
rpdev->src, rpdev->dst);
// idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
// if (!idata)
// return -ENOMEM;
// dev_set_drvdata(&rpdev->dev, idata);
// /* send a message to our remote processor to */
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
// if (ret) {
// dev_err(&rpdev->dev, "vrpmdv_monitoring_controler_send failed: %d\n", ret);
// return ret;
// }
// return 0;
// struct device *dev;
// dev = &rpdev->dev;
// struct rpmsg_vrpmdv_mon_t *rpmsg_vrpmdv_mon;
// rpmsg_vrpmdv_mon = devm_kzalloc(dev, sizeof(*rpmsg_vrpmdv_mon), GFP_KERNEL);
// if (!rpmsg_vrpmdv_mon)
// return -ENOMEM;
mutex_init(&(vrpmdv_mon.sendMTX));
init_waitqueue_head (&(vrpmdv_mon.receive_queue));
vrpmdv_mon.rpdev = rpdev;
// dev_set_drvdata(&rpdev->dev, rpmsg_vrpmdv_mon);
pr_info("RPMSG CMD Device set.\n");
/** NEU **/
// if (cmd_cb_progress_data.rpdev == NULL) {
// cmd_cb_progress_data.rpdev = rpdev;
// pr_info("RPMSG CMD Device set.\n");
// }
// else {
// pr_info("Error: RPMSG CMD Device already set. Don't set it twice\n");
// }
pr_info("Generic Netlink VRPMDV-Monitroring_Controler Module started.\n");
// Register family with its operations and policies
rc = genl_register_family(&gnl_vrpmdv_mcmd_family);
if (rc != 0) {
pr_err("FAILED: genl_register_family(): %i\n", rc);
pr_err("An error occurred while inserting the generic netlink example module\n");
return -1;
} else {
pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
return 0;
}
static void vrpmdv_monitoring_remove(struct rpmsg_device *rpdev)
{
int ret;
pr_info("Generic Netlink Example Module unloaded.\n");
// Unregister the family
ret = genl_unregister_family(&gnl_vrpmdv_mcmd_family);
if (ret != 0) {
pr_err("genl_unregister_family() failed: %i\n", ret);
return;
} else {
pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
mutex_destroy(&(vrpmdv_mon.sendMTX));
wake_up_interruptible(&(vrpmdv_mon.receive_queue));
dev_info(&rpdev->dev, "vrpmdv-monitoring controler driver is removed\n");
}
static struct rpmsg_device_id vrpmdv_monitoring_controler_id_table[] = {
{ .name = "vrpmdv-monitoring-controler" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, vrpmdv_monitoring_controler_id_table);
static struct rpmsg_driver vrpmdv_monitoring_controler = {
.drv.name = KBUILD_MODNAME,
.id_table = vrpmdv_monitoring_controler_id_table,
.probe = vrpmdv_monitoring_probe,
.callback = vrpmdv_monitoring_cb,
.remove = vrpmdv_monitoring_remove,
};
module_rpmsg_driver(vrpmdv_monitoring_controler);
// static struct rpmsg_driver vrpmdv_monitoring_data = {
// .drv.name = KBUILD_MODNAME,
// .id_table = vrpmdv_monitoring_controler_id_table,
// .probe = vrpmdv_monitoring_probe,
// .callback = vrpmdv_monitoring_cb,
// .remove = vrpmdv_monitoring_remove,
// };
// module_rpmsg_driver(vrpmdv_monitoring_data);
MODULE_DESCRIPTION("Remote processor messaging vrpmdv monitoring controler");
MODULE_LICENSE("GPL v2");
// /**
// * Module/driver initializer. Called on module load/insertion.
// *
// * @return success (0) or error code.
// */
// static int __init gnl_foobar_xmpl_module_init(void) {
// int rc;
// pr_info("Generic Netlink Example Module inserted.\n");
// // Register family with its operations and policies
// rc = genl_register_family(&gnl_foobar_xmpl_family);
// if (rc != 0) {
// pr_err("FAILED: genl_register_family(): %i\n", rc);
// pr_err("An error occurred while inserting the generic netlink example module\n");
// return -1;
// } else {
// pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_init(&dumpit_cb_progress_data.mtx);
// return 0;
// }
// /**
// * Module/driver uninitializer. Called on module unload/removal.
// *
// * @return success (0) or error code.
// */
// static void __exit gnl_foobar_xmpl_module_exit(void) {
// int ret;
// pr_info("Generic Netlink Example Module unloaded.\n");
// // Unregister the family
// ret = genl_unregister_family(&gnl_foobar_xmpl_family);
// if (ret != 0) {
// pr_err("genl_unregister_family() failed: %i\n", ret);
// return;
// } else {
// pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_destroy(&dumpit_cb_progress_data.mtx);
// }
// ----
// static int __init rpmsg_sdb_drv_init(void)
// {
// int ret = 0;
// /* Register rpmsg device */
// ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// if (ret) {
// pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
// return ret;
// }
// pr_info("rpmsg_sdb: Init done\n");
// return ret;
// }
// static void __exit rpmsg_sdb_drv_exit(void)
// {
// unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// pr_info("rpmsg_sdb: Exit\n");
// }
// module_init(rpmsg_sdb_drv_init);
// module_exit(rpmsg_sdb_drv_exit);

View File

@@ -0,0 +1,24 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "CLOSED"
# LICENSE = "GPL-2.0-only"
# LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://stm32_rpmsg_sdb.c \
"
# file://75-rpmsg-sdb.rules \
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
# do_install:append() {
# udev rules for rpmsg-sdb
# install -d ${D}${sysconfdir}/udev/rules.d/
# install -m 0644 ${WORKDIR}/75-rpmsg-sdb.rules ${D}${sysconfdir}/udev/rules.d/75-rpmsg-sdb.rules
# }
#FILES:${PN} += "${sysconfdir}/udev/rules.d/"
RPROVIDES:${PN} += "kernel-module-vrpmdv-monitoring-driver"

View File

@@ -0,0 +1,26 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv-monitoring.c \
file://75-vrpmdv-monitoring.rules \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
do_install:append() {
# udev rules for vrpmdv-monitoring
install -d ${D}${sysconfdir}/udev/rules.d/
install -m 0644 ${WORKDIR}/75-vrpmdv-monitoring.rules ${D}${sysconfdir}/udev/rules.d/75-vrpmdv-monitoring.rules
}
FILES:${PN} += "${sysconfdir}/udev/rules.d/"
RPROVIDES:${PN} += "kernel-module-vrpmdv-monitoring"

View File

@@ -0,0 +1,26 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv-monitoring.c \
file://75-vrpmdv-monitoring.rules \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
do_install:append() {
# udev rules for vrpmdv-monitoring
install -d ${D}${sysconfdir}/udev/rules.d/
install -m 0644 ${WORKDIR}/75-vrpmdv-monitoring.rules ${D}${sysconfdir}/udev/rules.d/75-vrpmdv-monitoring.rules
}
FILES:${PN} += "${sysconfdir}/udev/rules.d/"
FILES:${PN} += "${base_libdir}/modules/"
RPROVIDES:${PN} += "vrpmdv-monitoring"

View File

@@ -0,0 +1,25 @@
SUMMARY = "VRPMDV Monitoring Controler"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv-mon-datafile.c \
file://75-mon-datafile.rules \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
do_install:append() {
# udev rules for rpmsg-sdb
install -d ${D}${sysconfdir}/udev/rules.d/
install -m 0644 ${WORKDIR}/75-mon-datafile.rules ${D}${sysconfdir}/udev/rules.d/75-mon-datafile.rules
}
FILES:${PN} += "${sysconfdir}/udev/rules.d/"
RPROVIDES:${PN} += "kernel-module-vrpmdv-mon-datafile"

View File

@@ -0,0 +1,8 @@
{
"files.associations": {
"rpmsg.h": "c",
"types.h": "c",
"ioctl.h": "c",
"wait.h": "c"
}
}

View File

@@ -0,0 +1,26 @@
# Makefile for VRPMDV Monitoring Controler
obj-m := vrpmdv-mon-log.o
SRC := $(shell pwd)
all:
$(MAKE) -C $(KERNEL_SRC) M=$(SRC)
modules_install:
$(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules_install
clean:
rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c
rm -f Module.markers Module.symvers modules.order
rm -rf .tmp_versions Modules.symvers
# obj-m = vrpmdv-monitoring-controler.o
# KVERSION = $(shell uname -r)
# all:
# make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules
# clean:
# make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean

View File

@@ -0,0 +1,116 @@
include <condition_variable>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// wait until main() sends data
std::unique_lock lk(m);
cv.wait(lk, []{ return ready; });
// after the wait, we own the lock
std::cout << "Worker thread is processing data\n";
data += " after processing";
// send data back to main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
// manual unlocking is done before notifying, to avoid waking up
// the waiting thread only to block again (see notify_one for details)
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
data = "Example data";
// send data to the worker thread
{
std::lock_guard lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// wait for the worker
{
std::unique_lock lk(m);
cv.wait(lk, []{ return processed; });
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
--------------------
// waiting for timeout after 5 seconds
std::chrono::seconds timeoutPeriod = 5;
auto timePoint = std::chrono::system_clock::now() + timeoutPeriod;
std::unique_lock<std::mutex> uLock(myDataMutex);
while(!DataAreReadyForProcessing())
{
if (myCondVar.wait_until(uLock, timePoint) //<##
== std::cv_status::timeout)
{
// data conditions where not fulfilled within
// the time period; e.g. do some error handling
break;
}
}
--
if (myCondVar.wait_for(uLock, timeoutPeriod,
DataAreReadyForProcessing))
{
// data conditions where fulfilled
// regular processing
}
else // timeout occured, conditions are not fulfilled
{
// e.g. do some error handling
}
-------------
static int rpmsg_sample_probe(struct rpmsg_device *rpdev)
{
int ret;
struct instance_data *idata;
dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
rpdev->src, rpdev->dst);
idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
if (!idata)
return -ENOMEM;
dev_set_drvdata(&rpdev->dev, idata);
/* send a message to our remote processor */
ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
if (ret) {
dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
return 0;

View File

@@ -0,0 +1,795 @@
/* Copyright 2024 Markus Lehr
*
*
* This Software is owned by Markus Lehr.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// ##################################################################################################
/*
* This is a Linux kernel module/driver called "vrpmdv-monitoring-cmd " which holds the family ID
* and functions to handle the monitoring in a Generic Netlink in the kernel.
* "It registers a Netlink family called "vrpmdv-monitoring_cmd".
*
*
* You can find some more interesting documentation about Generic Netlink here:
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
*/
#include <linux/kernel.h>
#include <linux/rpmsg.h>
// basic definitions for kernel module development
#include <linux/module.h>
// definitions for generic netlink families, policies etc;
// transitive dependencies for basic netlink, sockets etc
#include <net/genetlink.h>
// required for locking inside the .dumpit callback demonstration
#include <linux/mutex.h>
#include <linux/wait.h>
// data/vars/enums/properties that describes our protocol that we implement
// on top of generic netlink (like functions we want to trigger on the receiving side)
//#include "vrpmdv-monitoring-cmd.h"
/* ######################## CONVENIENT LOGGING MACROS ######################## */
// (Re)definition of some convenient logging macros from <linux/printk.h>. You can see the logging
// messages when printing the kernel log, e.g. with `$ sudo dmesg`.
// See https://elixir.bootlin.com/linux/latest/source/include/linux/printk.h
// with this redefinition we can easily prefix all log messages from pr_* logging macros
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* ########################################################################### */
#define MSG "hello Monitoring!"
static bool retok = true;
static int count = 100;
module_param(count, int, 0644);
struct instance_data {
int rx_count;
};
// std::chrono::seconds timeoutPeriod = 5;
// auto timePoint = std::chrono::system_clock::now() + timeoutPeriod;
/** ----- NETLINK Driver defintion ------------------*/
/**
* Generic Netlink will create a Netlink family with this name. Kernel will asign
* a numeric ID and afterwards we can talk to the family with its ID. To get
* the ID we use Generic Netlink in the userland and pass the family name.
*
* Short for: Generic Netlink VRPMDV Monitoring gnl_foobar_mcmd
*/
#define FAMILY_NAME "gnl-vrpmdv-mcmd"
/**
* These are the attributes that we want to share in gnl_foobar_xmpl.
* You can understand an attribute as a semantic type. This is
* the payload of Netlink messages.
* GNl: Generic Netlink
*/
enum GNL_VRPMDV_XMPL_ATTRIBUTE {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_A_UNSPEC,
/** We expect a MSG to be a null-terminated C-string. */
GNL_VRPMDV_MCMD_A_MSG,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_A_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN (__GNL_VRPMDV_MCMD_A_MAX)
/**
* The number of actual usable attributes in `enum GNL_VRPMDV_MCMD_ATTRIBUTE`.
* This is `GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_ATTRIBUTE_COUNT (GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN - 1)
/**
* Enumeration of all commands (functions) that our custom protocol on top
* of generic netlink supports. This can be understood as the action that
* we want to trigger on the receiving side.
*/
enum GNL_VRPMDV_MCMD_COMMAND {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_C_UNSPEC,
// first real command is "1" (>0)
/**
* When this command is received, we expect the attribute `GNL_VRPMDV_MCMD_ATTRIBUTE::GNL_VRPMDV_MCMD_A_MSG` to
* be present in the Generic Netlink request message. The kernel reads the message from the packet and
* sent it to the copro. THe result will be return by creating a new Generic Netlink response message
* with an corresponding attribute/payload.
*
* This command/signaling mechanism is independent of the Netlink flag `NLM_F_ECHO (0x08)`. We use it as
* "echo specific data" instead of return a 1:1 copy of the package, which you could do with
* `NLM_F_ECHO (0x08)` for example.
*/
GNL_VRPMDV_MCMD_C_MSG,
/**
* Provokes a NLMSG_ERR answer to this request as described in netlink manpage
* (https://man7.org/linux/man-pages/man7/netlink.7.html).
*/
GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_C_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN (__GNL_VRPMDV_MCMD_C_MAX)
/**
* The number of actual usable commands in `enum GNL_FOOBAR_XMPL_COMMAND`.
* This is `GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_COMMAND_COUNT (GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN - 1)
#define NODATARECEIVED 0
#define DATARECEIVED 1
/**
* rpmsg wait for response from copro side.
*/
struct mutex sendMTX;
/**
* rpmsg wait for response from copro side.
*/
struct mutex receiveCV;
/**
* Wait Queue: if it is signaled we have received data from copro
*/
wait_queue_head_t receive_queue;
/**
* Waitflag: 0= no data received, 1 = data received
*/
int receive_queue_flag = NODATARECEIVED;
char* received_bytes = NULL;
int received_len = 0;
//the rpmsg device which sends the data to the copro
struct rpmsg_device* prpdev = NULL; /* handle rpmsg device */
// Documentation is on the implementation of this function.
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info);
// Documentation is on the implementation of this function.
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info);
/**
* The length of `struct genl_ops gnl_foobar_xmpl_ops[]`. Not necessarily
* the number of commands in `enum GNlFoobarXmplCommand`. It depends on your application logic.
* For example, you can use the same command multiple times and - dependent by flag -
* invoke a different callback handler. In our simple example we just use one .doit callback
* per operation/command.
*/
#define GNL_VRPMDV_OPS_LEN (GNL_VRPMDV_MCMD_COMMAND_COUNT)
/**
* Array with all operations that our protocol on top of Generic Netlink
* supports. An operation is the glue between a command ("cmd" field in `struct genlmsghdr` of
* received Generic Netlink message) and the corresponding ".doit" callback function.
* See: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L148
*/
struct genl_ops gnl_vrpmdv_mcmd_ops[GNL_VRPMDV_OPS_LEN] = {
{
/* The "cmd" field in `struct genlmsghdr` of received Generic Netlink message */
.cmd = GNL_VRPMDV_MCMD_C_MSG,
/* TODO Use case ? */
.flags = 0,
/* TODO Use case ? */
.internal_flags = 0,
/* Callback handler when a request with the specified ".cmd" above is received.
* Always validates the payload except one set NO_STRICT_VALIDATION flag in ".validate"
* See: https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*
* Quote from: https://lwn.net/Articles/208755
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*
*/
.doit = gnl_cb_vrpmdv_doit,
/* This callback is similar in use to the standard Netlink 'dumpit' callback.
* The 'dumpit' callback is invoked when a Generic Netlink message is received
* with the NLM_F_DUMP flag set.
*
* A dump can be understand as a "GET ALL DATA OF THE GIVEN ENTITY", i.e.
* the userland can receive as long as the .dumpit callback returns data.
*
* .dumpit is not mandatory, but either it or .doit must be provided, see
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L367
*
* To be honest I don't know in what use case one should use .dumpit and why
* it is useful, because you can achieve the same also with .doit handlers.
* Anyway, this is just an example/tutorial.
*
* Quote from: https://lwn.net/Articles/208755
* "The main difference between a 'dumpit' handler and a 'doit' handler is
* that a 'dumpit' handler does not allocate a message buffer for a response;
* a pre-allocated sk_buff is passed to the 'dumpit' handler as the first
* parameter. The 'dumpit' handler should fill the message buffer with the
* appropriate response message and return the size of the sk_buff,
* i.e. sk_buff->len, and the message buffer will automatically be sent to the
* Generic Netlink client that initiated the request. As long as the 'dumpit'
* handler returns a value greater than zero it will be called again with a
* newly allocated message buffer to fill, when the handler has no more data
* to send it should return zero; error conditions are indicated by returning
* a negative value. If necessary, state can be preserved in the
* netlink_callback parameter which is passed to the 'dumpit' handler; the
* netlink_callback parameter values will be preserved across handler calls
* for a single request."
*
* You can see the check for the NLM_F_DUMP-flag here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L780
*/
.dumpit = NULL, //gnl_cb_echo_dumpit,
/* Start callback for dumps. Can be used to lock data structures. */
.start = NULL, //gnl_cb_echo_dumpit_before,
/* Completion callback for dumps. Can be used for cleanup after a dump and releasing locks. */
.done = NULL, //gnl_cb_echo_dumpit_before_after,
/*
0 (= "validate strictly") or value `enum genl_validate_flags`
* see: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L108
*/
.validate = 0,
},
{
.cmd = GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
.flags = 0,
.internal_flags = 0,
.doit = gnl_cb_doit_reply_with_nlmsg_err,
// .dumpit is not required, only optional; application specific/dependent on your use case
// in a real application you probably have different .dumpit handlers per operation/command
.dumpit = NULL,
// in a real application you probably have different .start handlers per operation/command
.start = NULL,
// in a real application you probably have different .done handlers per operation/command
.done = NULL,
.validate = 0,
}
};
/**
* Attribute policy: defines which attribute has which type (e.g int, char * etc).
* This get validated for each received Generic Netlink message, if not deactivated
* in `gnl_foobar_xmpl_ops[].validate`.
* See https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*/
static struct nla_policy gnl_vrpmdv_mcmd_policy[GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN] = {
// In case you are seeing this syntax for the first time (I also learned this just after a few years of
// coding with C myself): The following static array initiations are equivalent:
// `int a[2] = {1, 2}` <==> `int a[2] = {[0] => 1, [1] => 2}`.
[GNL_VRPMDV_MCMD_A_UNSPEC] = {.type = NLA_UNSPEC},
// You can set this to NLA_U32 for testing and send an ECHO message from the userland
// It will fail in this case and you see a entry in the kernel log.
// `enum GNL_FOOBAR_XMPL_ATTRIBUTE::GNL_FOOBAR_XMPL_A_MSG` is a null-terminated C-String
[GNL_VRPMDV_MCMD_A_MSG] = {.type = NLA_NUL_STRING},
};
/**
* Definition of the Netlink family we want to register using Generic Netlink functionality
*/
static struct genl_family gnl_vrpmdv_mcmd_family = {
// automatically assign an id
.id = 0,
// we don't use custom additional header info / user specific header
.hdrsize = 0,
// The name of this family, used by userspace application to get the numeric ID
.name = FAMILY_NAME,
// family specific version number; can be used to evolve application over time (multiple versions)
.version = 1,
// delegates all incoming requests to callback functions
.ops = gnl_vrpmdv_mcmd_ops,
// length of array `gnl_foobar_xmpl_ops`
.n_ops = GNL_VRPMDV_OPS_LEN,
// attribute policy (for validation of messages). Enforced automatically, except ".validate" in
// corresponding ".ops"-field is set accordingly.
.policy = gnl_vrpmdv_mcmd_policy,
// Number of attributes / bounds check for policy (array length)
.maxattr = GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN,
// Owning Kernel module of the Netlink family we register.
.module = THIS_MODULE,
// Actually not necessary because this memory region would be zeroed anyway during module load,
// but this way one sees all possible options.
// if your application must handle multiple netlink calls in parallel (where one should not block the next
// from starting), set this to true! otherwise all netlink calls are mutually exclusive
.parallel_ops = 0,
// set to true if the family can handle network namespaces and should be presented in all of them
.netnsok = 0,
// called before an operation's doit callback, it may do additional, common, filtering and return an error
.pre_doit = NULL,
// called after an operation's doit callback, it may undo operations done by pre_doit, for example release locks
.post_doit = NULL,
};
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_VRPMDV_MCMD_C_MSG` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_vrpmdv_mcmd_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info) {
struct nlattr *na;
struct sk_buff *reply_skb;
int rc;
void *msg_head;
char *recv_msg;
pr_info("%s() invoked\n", __func__);
if (info == NULL) {
// should never happen
pr_err("An error occurred in %s():\n", __func__);
return -EINVAL;
}
/*
* For each attribute there is an index in info->attrs which points to a nlattr structure
* in this structure the data is stored.
*/
na = info->attrs[GNL_VRPMDV_MCMD_A_MSG];
if (!na) {
pr_err("no info->attrs[%i]\n", GNL_VRPMDV_MCMD_A_MSG);
return -EINVAL; // we return here because we expect to recv a msg
}
recv_msg = (char *) nla_data(na);
if (recv_msg == NULL) {
pr_err("error while receiving data\n");
} else {
pr_info("received: '%s'\n", recv_msg);
}
//aquire lock for cmd repmsg channel
// std::lock sendlk(cmd_cb_progress_data.sendmtx);
//send the message to the copro over RPMSG
if (prpdev) {
rc = rpmsg_send(prpdev->ept, recv_msg, strlen(recv_msg));
if (rc) {
pr_err("rpmsg_send failed: %d\n", rc);
return rc;
}
// struct rpmsg_vrpmdv_mon_t *drv = dev_get_drvdata(&rpdev->dev);
// Send a message back after we receive the reply from rpmsg channel
// ---------------------
// Allocate some memory, since the size is not yet known use NLMSG_GOODSIZE
reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (reply_skb == NULL) {
pr_err("An error occurred in %s():\n", __func__);
return -ENOMEM;
}
// Create the message headersprpdev
// Add header to netlink message;
// afterwards the buffer looks like this:
// ----------------------------------
// | netlink header |
// | generic netlink header |
// | <space for netlink attributes> |
// ----------------------------------
// msg_head = genlmsg_put(reply_skb, // buffer for netlink message: struct sk_buff *
// // According to my findings: this is not used for routing
// // This can be used in an application specific way to target
// // different endpoints within the same user application
// // but general rule: just put sender port id here
// info->snd_portid, // sending port (not process) id: int
// info->snd_seq + 1, // sequence number: int (might be used by receiver, but not mandatory)
// &gnl_vrpmdv_mcmd_family, // struct genl_family *
// 0, // flags for Netlink header: int; application specific and not mandatory
// // The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
// GNL_VRPMDV_MCMD_C_MSG
// );
msg_head = genlmsg_put_reply(reply_skb, // buffer for netlink message: struct sk_buff *
info, // info
&gnl_vrpmdv_mcmd_family, // struct genl_family *
0, // flags for Netlink header: int; application specific and not mandatory
// The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
info->genlhdr->cmd
);
if (msg_head == NULL) {
rc = ENOMEM;
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
{
pr_info("wait for response\n");
// wait until receive_queue_flag=1 , that means we have received data from Copro
wait_event_interruptible(receive_queue, receive_queue_flag != 0 );
//Copy data
receive_queue_flag = NODATARECEIVED;
// std::unique_lock lk(cmd_cb_progress_data.receivemtx);
// if (myCondVar.wait_until(uLock, timePoint) == std::cv_status::timeout)
// {
// dev_err(&cmd_cb_progress_data.rpdev, "rpmsg_send failed, timeout: \n");
// return -1:
// }
//pr_info("get response: '%s'\n", recv_msg);
if (received_len > 0) {
pr_info("received data from copro %s\n", received_bytes);
}
else {
pr_err("don't received data from Copro \n");
}
}
// Add a GNL_VRPMDV_MCMD_A_MSG attribute (actual value/payload to be sent)
// echo the value we just received
rc = nla_put_string(reply_skb, GNL_VRPMDV_MCMD_A_MSG, received_bytes);
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
//free the buffer
kfree(received_bytes);
received_bytes = NULL;
return -rc;
}
// Finalize the message:
// Corrects the netlink message header (length) to include the appended
// attributes. Only necessary if attributes have been added to the message.
genlmsg_end(reply_skb, msg_head);
// Send the message back
rc = genlmsg_reply(reply_skb, info);
// same as genlmsg_unicast(genl_info_net(info), reply_skb, info->snd_portid)
// see https://elixir.bootlin.com/linux/v5.8.9/source/include/net/genetlink.h#L326
kfree(received_bytes);
received_bytes = NULL;
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
return 0;
}
pr_info("Device not set in Probe. Should not happen");
return -1;
}
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info) {
pr_info("%s() invoked, a NLMSG_ERR response will be sent back\n", __func__);
/*
* Generic Netlink is smart enough and sends a NLMSG_ERR reply automatically as reply
* Quote from https://lwn.net/Articles/208755/:
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*/
return -EINVAL;
}
/** ----- NETLINK Driver defintion ------------------*/
/**
* callback that is called after the copro send data
* we have to copy it in a buffer for the netlink and later send it back to the userland
*
*/
static int vrpmdv_monitoring_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret = 0;
// struct instance_data *idata = dev_get_drvdata(&rpdev->dev);
// dev_info(&rpdev->dev, "incoming msg %d (src: 0x%x)\n",
// ++idata->rx_count, src);
print_hex_dump_debug(__func__, DUMP_PREFIX_NONE, 16, 1, data, len,
true);
if (len == 0) {
pr_err("(%s) Empty lenght requested\n", __func__);
return -EINVAL;
}
else {
pr_info("received logging: %s\n", (char*) data);
}
ret = rpmsg_send(rpdev->ept, retok, sizeof(retok));
// received_bytes = (char *)kmalloc(len+1, GFP_KERNEL);
// memcpy(received_bytes, data, len);
// rpmsg_RxBuf[len] = 0;
// received_bytes = (char *)kmalloc(len, GFP_KERNEL);
// memcpy(received_bytes, data, len);
// received_len = len;
// receive_queue_flag= DATARECEIVED;
// wake_up_interruptible(&receive_queue);
// /* samples should not live forever */
// if (idata->rx_count >= count) {
// dev_info(&rpdev->dev, "goodbye!\n");
// return 0;
// }
/* send a new message now */
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
// if (ret)
// dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
static int vrpmdv_mon_log_probe(struct rpmsg_device *rpdev)
{
//int rc;
// int ret;
// struct instance_data *idata;
// dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
// rpdev->src, rpdev->dst);
pr_info("RPMSG mon logger device driver started.\n");
// idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
// if (!idata)
// return -ENOMEM;
// dev_set_drvdata(&rpdev->dev, idata);
// /* send a message to our remote processor to */
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
// if (ret) {
// dev_err(&rpdev->dev, "vrpmdv_monitoring_controler_send failed: %d\n", ret);
// return ret;
// }
// return 0;
// struct device *dev;
// dev = &rpdev->dev;
// struct rpmsg_vrpmdv_mon_t *rpmsg_vrpmdv_mon;
// rpmsg_vrpmdv_mon = devm_kzalloc(dev, sizeof(*rpmsg_vrpmdv_mon), GFP_KERNEL);
// if (!rpmsg_vrpmdv_mon)
// return -ENOMEM;
// mutex_init(&sendMTX);
// init_waitqueue_head (&receive_queue);
prpdev = rpdev;
// dev_set_drvdata(&rpdev->dev, rpmsg_vrpmdv_mon);
pr_info("RPMSG Logger Device set.\n");
/** NEU **/
// if (cmd_cb_progress_data.rpdev == NULL) {
// cmd_cb_progress_data.rpdev = rpdev;
// pr_info("RPMSG CMD Device set.\n");
// }
// else {
// pr_info("Error: RPMSG CMD Device already set. Don't set it twice\n");
// }
//pr_info("Generic Netlink VRPMDV-Mon-Log Module started.\n");
// Register family with its operations and policies
// rc = genl_register_family(&gnl_vrpmdv_mcmd_family);
// if (rc != 0) {
// pr_err("FAILED: genl_register_family(): %i\n", rc);
// pr_err("An error occurred while inserting the generic netlink example module\n");
// return -1;
// } else {
// pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
return 0;
}
static void vrpmdv_mon_log_remove(struct rpmsg_device *rpdev)
{
//int ret;
pr_info("Mon Logger unloaded.\n");
// Unregister the family
// ret = genl_unregister_family(&gnl_vrpmdv_mcmd_family);
// if (ret != 0) {
// pr_err("genl_unregister_family() failed: %i\n", ret);
// return;
// } else {
// pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_destroy(&sendMTX);
// wake_up_interruptible(&receive_queue);
pr_info("vrpmdv-mon logger driver is removed\n");
// dev_info(&rpdev->dev, "vrpmdv-monitoring controler driver is removed\n");
}
static struct rpmsg_device_id vrpmdv_mon_log_id_table[] = {
{ .name = "vrpmdv-mon-log" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, vrpmdv_mon_log_id_table);
static struct rpmsg_driver vrpmdv_mon_log = {
.drv.name = KBUILD_MODNAME,
.id_table = vrpmdv_mon_log_id_table,
.probe = vrpmdv_mon_log_probe,
.callback = vrpmdv_mon_log_cb,
.remove = vrpmdv_mon_log_remove,
};
module_rpmsg_driver(vrpmdv_mon_log);
// static struct rpmsg_driver vrpmdv_monitoring_data = {
// .drv.name = KBUILD_MODNAME,
// .id_table = vrpmdv_monitoring_controler_id_table,
// .probe = vrpmdv_monitoring_probe,
// .callback = vrpmdv_monitoring_cb,
// .remove = vrpmdv_monitoring_remove,
// };
// module_rpmsg_driver(vrpmdv_monitoring_data);
MODULE_DESCRIPTION("Remote processor messaging vrpmdv monitoring controler");
MODULE_LICENSE("GPL v2");
// /**
// * Module/driver initializer. Called on module load/insertion.
// *
// * @return success (0) or error code.
// */
// static int __init gnl_foobar_xmpl_module_init(void) {
// int rc;
// pr_info("Generic Netlink Example Module inserted.\n");
// // Register family with its operations and policies
// rc = genl_register_family(&gnl_foobar_xmpl_family);
// if (rc != 0) {
// pr_err("FAILED: genl_register_family(): %i\n", rc);
// pr_err("An error occurred while inserting the generic netlink example module\n");
// return -1;
// } else {
// pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_init(&dumpit_cb_progress_data.mtx);
// return 0;
// }
// /**
// * Module/driver uninitializer. Called on module unload/removal.
// *
// * @return success (0) or error code.
// */
// static void __exit gnl_foobar_xmpl_module_exit(void) {
// int ret;
// pr_info("Generic Netlink Example Module unloaded.\n");
// // Unregister the family
// ret = genl_unregister_family(&gnl_foobar_xmpl_family);
// if (ret != 0) {
// pr_err("genl_unregister_family() failed: %i\n", ret);
// return;
// } else {
// pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_destroy(&dumpit_cb_progress_data.mtx);
// }
// ----
// static int __init rpmsg_sdb_drv_init(void)
// {
// int ret = 0;
// /* Register rpmsg device */
// ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// if (ret) {
// pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
// return ret;
// }
// pr_info("rpmsg_sdb: Init done\n");
// return ret;
// }
// static void __exit rpmsg_sdb_drv_exit(void)
// {
// unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// pr_info("rpmsg_sdb: Exit\n");
// }
// module_init(rpmsg_sdb_drv_init);
// module_exit(rpmsg_sdb_drv_exit);

View File

@@ -0,0 +1,287 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 STMicroelectronics - All Rights Reserved
*
* The rpmsg tty driver implements serial communication on the RPMsg bus to makes
* possible for user-space programs to send and receive rpmsg messages as a standard
* tty protocol.
*
* The remote processor can instantiate a new tty by requesting a "rpmsg-tty" RPMsg service.
* The "rpmsg-tty" service is directly used for data exchange. No flow control is implemented yet.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#define RPMSG_TTY_NAME "ttyRPMSG"
#define MAX_TTY_RPMSG 32
static DEFINE_IDR(tty_idr); /* tty instance id */
static DEFINE_MUTEX(idr_lock); /* protects tty_idr */
static struct tty_driver *rpmsg_tty_driver;
struct rpmsg_tty_port {
struct tty_port port; /* TTY port data */
int id; /* TTY rpmsg index */
struct rpmsg_device *rpdev; /* rpmsg device */
};
static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src)
{
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
int copied;
if (!len)
return -EINVAL;
copied = tty_insert_flip_string(&cport->port, data, len);
if (copied != len)
dev_err_ratelimited(&rpdev->dev, "Trunc buffer: available space is %d\n", copied);
tty_flip_buffer_push(&cport->port);
return 0;
}
static int rpmsg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
{
struct rpmsg_tty_port *cport = idr_find(&tty_idr, tty->index);
struct tty_port *port;
tty->driver_data = cport;
port = tty_port_get(&cport->port);
return tty_port_install(port, driver, tty);
}
static void rpmsg_tty_cleanup(struct tty_struct *tty)
{
tty_port_put(tty->port);
}
static int rpmsg_tty_open(struct tty_struct *tty, struct file *filp)
{
return tty_port_open(tty->port, tty, filp);
}
static void rpmsg_tty_close(struct tty_struct *tty, struct file *filp)
{
return tty_port_close(tty->port, tty, filp);
}
static int rpmsg_tty_write(struct tty_struct *tty, const u8 *buf, int len)
{
struct rpmsg_tty_port *cport = tty->driver_data;
struct rpmsg_device *rpdev;
int msg_max_size, msg_size;
int ret;
rpdev = cport->rpdev;
msg_max_size = rpmsg_get_mtu(rpdev->ept);
if (msg_max_size < 0)
return msg_max_size;
msg_size = min(len, msg_max_size);
/*
* Use rpmsg_trysend instead of rpmsg_send to send the message so the caller is not
* hung until a rpmsg buffer is available. In such case rpmsg_trysend returns -ENOMEM.
*/
ret = rpmsg_trysend(rpdev->ept, (void *)buf, msg_size);
if (ret) {
dev_dbg_ratelimited(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
return msg_size;
}
static unsigned int rpmsg_tty_write_room(struct tty_struct *tty)
{
struct rpmsg_tty_port *cport = tty->driver_data;
int size;
size = rpmsg_get_mtu(cport->rpdev->ept);
if (size < 0)
return 0;
return size;
}
static void rpmsg_tty_hangup(struct tty_struct *tty)
{
tty_port_hangup(tty->port);
}
static const struct tty_operations rpmsg_tty_ops = {
.install = rpmsg_tty_install,
.open = rpmsg_tty_open,
.close = rpmsg_tty_close,
.write = rpmsg_tty_write,
.write_room = rpmsg_tty_write_room,
.hangup = rpmsg_tty_hangup,
.cleanup = rpmsg_tty_cleanup,
};
static struct rpmsg_tty_port *rpmsg_tty_alloc_cport(void)
{
struct rpmsg_tty_port *cport;
int ret;
cport = kzalloc(sizeof(*cport), GFP_KERNEL);
if (!cport)
return ERR_PTR(-ENOMEM);
mutex_lock(&idr_lock);
ret = idr_alloc(&tty_idr, cport, 0, MAX_TTY_RPMSG, GFP_KERNEL);
mutex_unlock(&idr_lock);
if (ret < 0) {
kfree(cport);
return ERR_PTR(ret);
}
cport->id = ret;
return cport;
}
static void rpmsg_tty_destruct_port(struct tty_port *port)
{
struct rpmsg_tty_port *cport = container_of(port, struct rpmsg_tty_port, port);
mutex_lock(&idr_lock);
idr_remove(&tty_idr, cport->id);
mutex_unlock(&idr_lock);
kfree(cport);
}
static const struct tty_port_operations rpmsg_tty_port_ops = {
.destruct = rpmsg_tty_destruct_port,
};
static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
{
struct rpmsg_tty_port *cport;
struct device *dev = &rpdev->dev;
struct device *tty_dev;
int ret;
cport = rpmsg_tty_alloc_cport();
if (IS_ERR(cport))
return dev_err_probe(dev, PTR_ERR(cport), "Failed to alloc tty port\n");
tty_port_init(&cport->port);
cport->port.ops = &rpmsg_tty_port_ops;
tty_dev = tty_port_register_device(&cport->port, rpmsg_tty_driver,
cport->id, dev);
if (IS_ERR(tty_dev)) {
ret = dev_err_probe(dev, PTR_ERR(tty_dev), "Failed to register tty port\n");
tty_port_put(&cport->port);
return ret;
}
cport->rpdev = rpdev;
dev_set_drvdata(dev, cport);
dev_dbg(dev, "New channel: 0x%x -> 0x%x: " RPMSG_TTY_NAME "%d\n",
rpdev->src, rpdev->dst, cport->id);
return 0;
}
static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
{
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
dev_dbg(&rpdev->dev, "Removing rpmsg tty device %d\n", cport->id);
/* User hang up to release the tty */
tty_port_tty_hangup(&cport->port, false);
tty_unregister_device(rpmsg_tty_driver, cport->id);
tty_port_put(&cport->port);
}
static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = {
{ .name = "rpmsg-tty" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_tty_id_table);
static struct rpmsg_driver rpmsg_tty_rpmsg_drv = {
.drv.name = KBUILD_MODNAME,
.id_table = rpmsg_driver_tty_id_table,
.probe = rpmsg_tty_probe,
.callback = rpmsg_tty_cb,
.remove = rpmsg_tty_remove,
};
static int __init rpmsg_tty_init(void)
{
int ret;
rpmsg_tty_driver = tty_alloc_driver(MAX_TTY_RPMSG, TTY_DRIVER_REAL_RAW |
TTY_DRIVER_DYNAMIC_DEV);
if (IS_ERR(rpmsg_tty_driver))
return PTR_ERR(rpmsg_tty_driver);
rpmsg_tty_driver->driver_name = "vrpmdv_rpmsg_tty";
rpmsg_tty_driver->name = RPMSG_TTY_NAME;
rpmsg_tty_driver->major = 0;
rpmsg_tty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
/* Disable unused mode by default */
rpmsg_tty_driver->init_termios = tty_std_termios;
rpmsg_tty_driver->init_termios.c_lflag &= ~(ECHO | ICANON);
rpmsg_tty_driver->init_termios.c_oflag &= ~(OPOST | ONLCR);
tty_set_operations(rpmsg_tty_driver, &rpmsg_tty_ops);
ret = tty_register_driver(rpmsg_tty_driver);
if (ret < 0) {
pr_err("Couldn't install driver: %d\n", ret);
goto error_put;
}
ret = register_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
if (ret < 0) {
pr_err("Couldn't register driver: %d\n", ret);
goto error_unregister;
}
return 0;
error_unregister:
tty_unregister_driver(rpmsg_tty_driver);
error_put:
tty_driver_kref_put(rpmsg_tty_driver);
return ret;
}
static void __exit rpmsg_tty_exit(void)
{
unregister_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
tty_unregister_driver(rpmsg_tty_driver);
tty_driver_kref_put(rpmsg_tty_driver);
idr_destroy(&tty_idr);
}
module_init(rpmsg_tty_init);
module_exit(rpmsg_tty_exit);
MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>");
MODULE_DESCRIPTION("remote processor messaging tty driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,16 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv-monitoring.c \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
RPROVIDES:${PN} += "kernel-module-vrpmdv-monitoring"

View File

@@ -0,0 +1,541 @@
/* Copyright 2021 Philipp Schuster
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in the
* Software without restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies
* or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// ##################################################################################################
/*
* This is a Linux kernel module/driver called "gnl_foobar_xmpl" which shows you the basics of using
* Generic Netlink in the kernel. It registers a Netlink family called "gnl_foobar_xmpl". See
* "gnl_foobar_xmpl_prop.h" for common properties of the family. "Generic Netlink" offers us a lot of
* convenient functions to register new/custom Netlink families on the fly during runtime. We use this
* functionality here. It implements simple IPC between Userland and Kernel (Kernel responds to userland).
*
* Check "gnl_foobar_xmpl_prop.h" for common properties of the family first, afterwards follow the code here.
*
* You can find some more interesting documentation about Generic Netlink here:
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
*/
// basic definitions for kernel module development
#include <linux/module.h>
// definitions for generic netlink families, policies etc;
// transitive dependencies for basic netlink, sockets etc
#include <net/genetlink.h>
// required for locking inside the .dumpit callback demonstration
#include <linux/mutex.h>
// data/vars/enums/properties that describes our protocol that we implement
// on top of generic netlink (like functions we want to trigger on the receiving side)
#include "gnl_foobar_xmpl_prop.h"
// Module/Driver description.
// You can see this for example when executing `$ modinfo ./gnl_foobar_xmpl.ko` (after build).
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Philipp Schuster <phip1611@gmail.com>");
MODULE_DESCRIPTION(
"Linux driver that registers the custom Netlink family "
"\"" FAMILY_NAME "\" via Generic Netlink and responds to echo messages "
"according to the properties specified in \"gnl_foobar_xmpl_prop.h\"."
);
/* ######################## CONVENIENT LOGGING MACROS ######################## */
// (Re)definition of some convenient logging macros from <linux/printk.h>. You can see the logging
// messages when printing the kernel log, e.g. with `$ sudo dmesg`.
// See https://elixir.bootlin.com/linux/latest/source/include/linux/printk.h
// with this redefinition we can easily prefix all log messages from pr_* logging macros
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* ########################################################################### */
/**
* Data structure required for our .dumpit callback handler to
* know about the progress of an ongoing dump.
* See the dumpit callback handler how it is used.
*/
struct {
// from <linux/mutex.h>
/**
* Only one process is allowed per dump process. We need a lock for that.
*/
struct mutex mtx;
/**
* Number that describes how many packets we need to send until we are done
* during an ongoing dumpit process. 0 = done.
*/
int unsigned runs_to_go;
/**
* Number that describes how many packets per dump are sent in total.
* Constant per dump.
*/
int unsigned total_runs;
} dumpit_cb_progress_data;
// Documentation is on the implementation of this function.
int gnl_cb_echo_doit(struct sk_buff *sender_skb, struct genl_info *info);
// Documentation is on the implementation of this function.
int gnl_cb_echo_dumpit(struct sk_buff *pre_allocated_skb, struct netlink_callback *cb);
// Documentation is on the implementation of this function.
int gnl_cb_echo_dumpit_before(struct netlink_callback *cb);
// Documentation is on the implementation of this function.
int gnl_cb_echo_dumpit_before_after(struct netlink_callback *cb);
// Documentation is on the implementation of this function.
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info);
/**
* The length of `struct genl_ops gnl_foobar_xmpl_ops[]`. Not necessarily
* the number of commands in `enum GNlFoobarXmplCommand`. It depends on your application logic.
* For example, you can use the same command multiple times and - dependent by flag -
* invoke a different callback handler. In our simple example we just use one .doit callback
* per operation/command.
*/
#define GNL_FOOBAR_OPS_LEN (GNL_FOOBAR_XMPL_COMMAND_COUNT)
/**
* Array with all operations that our protocol on top of Generic Netlink
* supports. An operation is the glue between a command ("cmd" field in `struct genlmsghdr` of
* received Generic Netlink message) and the corresponding ".doit" callback function.
* See: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L148
*/
struct genl_ops gnl_foobar_xmpl_ops[GNL_FOOBAR_OPS_LEN] = {
{
/* The "cmd" field in `struct genlmsghdr` of received Generic Netlink message */
.cmd = GNL_FOOBAR_XMPL_C_ECHO_MSG,
/* TODO Use case ? */
.flags = 0,
/* TODO Use case ? */
.internal_flags = 0,
/* Callback handler when a request with the specified ".cmd" above is received.
* Always validates the payload except one set NO_STRICT_VALIDATION flag in ".validate"
* See: https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*
* Quote from: https://lwn.net/Articles/208755
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*
*/
.doit = gnl_cb_echo_doit,
/* This callback is similar in use to the standard Netlink 'dumpit' callback.
* The 'dumpit' callback is invoked when a Generic Netlink message is received
* with the NLM_F_DUMP flag set.
*
* A dump can be understand as a "GET ALL DATA OF THE GIVEN ENTITY", i.e.
* the userland can receive as long as the .dumpit callback returns data.
*
* .dumpit is not mandatory, but either it or .doit must be provided, see
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L367
*
* To be honest I don't know in what use case one should use .dumpit and why
* it is useful, because you can achieve the same also with .doit handlers.
* Anyway, this is just an example/tutorial.
*
* Quote from: https://lwn.net/Articles/208755
* "The main difference between a 'dumpit' handler and a 'doit' handler is
* that a 'dumpit' handler does not allocate a message buffer for a response;
* a pre-allocated sk_buff is passed to the 'dumpit' handler as the first
* parameter. The 'dumpit' handler should fill the message buffer with the
* appropriate response message and return the size of the sk_buff,
* i.e. sk_buff->len, and the message buffer will automatically be sent to the
* Generic Netlink client that initiated the request. As long as the 'dumpit'
* handler returns a value greater than zero it will be called again with a
* newly allocated message buffer to fill, when the handler has no more data
* to send it should return zero; error conditions are indicated by returning
* a negative value. If necessary, state can be preserved in the
* netlink_callback parameter which is passed to the 'dumpit' handler; the
* netlink_callback parameter values will be preserved across handler calls
* for a single request."
*
* You can see the check for the NLM_F_DUMP-flag here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L780
*/
.dumpit = gnl_cb_echo_dumpit,
/* Start callback for dumps. Can be used to lock data structures. */
.start = gnl_cb_echo_dumpit_before,
/* Completion callback for dumps. Can be used for cleanup after a dump and releasing locks. */
.done = gnl_cb_echo_dumpit_before_after,
/*
0 (= "validate strictly") or value `enum genl_validate_flags`
* see: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L108
*/
.validate = 0,
},
{
.cmd = GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR,
.flags = 0,
.internal_flags = 0,
.doit = gnl_cb_doit_reply_with_nlmsg_err,
// .dumpit is not required, only optional; application specific/dependent on your use case
// in a real application you probably have different .dumpit handlers per operation/command
.dumpit = NULL,
// in a real application you probably have different .start handlers per operation/command
.start = NULL,
// in a real application you probably have different .done handlers per operation/command
.done = NULL,
.validate = 0,
}
};
/**
* Attribute policy: defines which attribute has which type (e.g int, char * etc).
* This get validated for each received Generic Netlink message, if not deactivated
* in `gnl_foobar_xmpl_ops[].validate`.
* See https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*/
static struct nla_policy gnl_foobar_xmpl_policy[GNL_FOOBAR_XMPL_ATTRIBUTE_ENUM_LEN] = {
// In case you are seeing this syntax for the first time (I also learned this just after a few years of
// coding with C myself): The following static array initiations are equivalent:
// `int a[2] = {1, 2}` <==> `int a[2] = {[0] => 1, [1] => 2}`.
[GNL_FOOBAR_XMPL_A_UNSPEC] = {.type = NLA_UNSPEC},
// You can set this to NLA_U32 for testing and send an ECHO message from the userland
// It will fail in this case and you see a entry in the kernel log.
// `enum GNL_FOOBAR_XMPL_ATTRIBUTE::GNL_FOOBAR_XMPL_A_MSG` is a null-terminated C-String
[GNL_FOOBAR_XMPL_A_MSG] = {.type = NLA_NUL_STRING},
};
/**
* Definition of the Netlink family we want to register using Generic Netlink functionality
*/
static struct genl_family gnl_foobar_xmpl_family = {
// automatically assign an id
.id = 0,
// we don't use custom additional header info / user specific header
.hdrsize = 0,
// The name of this family, used by userspace application to get the numeric ID
.name = FAMILY_NAME,
// family specific version number; can be used to evolve application over time (multiple versions)
.version = 1,
// delegates all incoming requests to callback functions
.ops = gnl_foobar_xmpl_ops,
// length of array `gnl_foobar_xmpl_ops`
.n_ops = GNL_FOOBAR_OPS_LEN,
// attribute policy (for validation of messages). Enforced automatically, except ".validate" in
// corresponding ".ops"-field is set accordingly.
.policy = gnl_foobar_xmpl_policy,
// Number of attributes / bounds check for policy (array length)
.maxattr = GNL_FOOBAR_XMPL_ATTRIBUTE_ENUM_LEN,
// Owning Kernel module of the Netlink family we register.
.module = THIS_MODULE,
// Actually not necessary because this memory region would be zeroed anyway during module load,
// but this way one sees all possible options.
// if your application must handle multiple netlink calls in parallel (where one should not block the next
// from starting), set this to true! otherwise all netlink calls are mutually exclusive
.parallel_ops = 0,
// set to true if the family can handle network namespaces and should be presented in all of them
.netnsok = 0,
// called before an operation's doit callback, it may do additional, common, filtering and return an error
.pre_doit = NULL,
// called after an operation's doit callback, it may undo operations done by pre_doit, for example release locks
.post_doit = NULL,
};
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_ECHO` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_echo_doit(struct sk_buff *sender_skb, struct genl_info *info) {
struct nlattr *na;
struct sk_buff *reply_skb;
int rc;
void *msg_head;
char *recv_msg;
pr_info("%s() invoked\n", __func__);
if (info == NULL) {
// should never happen
pr_err("An error occurred in %s():\n", __func__);
return -EINVAL;
}
/*
* For each attribute there is an index in info->attrs which points to a nlattr structure
* in this structure the data is stored.
*/
na = info->attrs[GNL_FOOBAR_XMPL_A_MSG];
if (!na) {
pr_err("no info->attrs[%i]\n", GNL_FOOBAR_XMPL_A_MSG);
return -EINVAL; // we return here because we expect to recv a msg
}
recv_msg = (char *) nla_data(na);
if (recv_msg == NULL) {
pr_err("error while receiving data\n");
} else {
pr_info("received: '%s'\n", recv_msg);
}
// Send a message back
// ---------------------
// Allocate some memory, since the size is not yet known use NLMSG_GOODSIZE
reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (reply_skb == NULL) {
pr_err("An error occurred in %s():\n", __func__);
return -ENOMEM;
}
// Create the message headers
// Add header to netlink message;
// afterwards the buffer looks like this:
// ----------------------------------
// | netlink header |
// | generic netlink header |
// | <space for netlink attributes> |
// ----------------------------------
msg_head = genlmsg_put(reply_skb, // buffer for netlink message: struct sk_buff *
// According to my findings: this is not used for routing
// This can be used in an application specific way to target
// different endpoints within the same user application
// but general rule: just put sender port id here
info->snd_portid, // sending port (not process) id: int
info->snd_seq + 1, // sequence number: int (might be used by receiver, but not mandatory)
&gnl_foobar_xmpl_family, // struct genl_family *
0, // flags for Netlink header: int; application specific and not mandatory
// The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
GNL_FOOBAR_XMPL_C_ECHO_MSG
);
if (msg_head == NULL) {
rc = ENOMEM;
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Add a GNL_FOOBAR_XMPL_A_MSG attribute (actual value/payload to be sent)
// echo the value we just received
rc = nla_put_string(reply_skb, GNL_FOOBAR_XMPL_A_MSG, recv_msg);
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Finalize the message:
// Corrects the netlink message header (length) to include the appended
// attributes. Only necessary if attributes have been added to the message.
genlmsg_end(reply_skb, msg_head);
// Send the message back
rc = genlmsg_reply(reply_skb, info);
// same as genlmsg_unicast(genl_info_net(info), reply_skb, info->snd_portid)
// see https://elixir.bootlin.com/linux/v5.8.9/source/include/net/genetlink.h#L326
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
return 0;
}
/**
* ".dumpit"-callback function if a Generic Netlink with command ECHO_MSG and flag `NLM_F_DUMP` is received.
* Please look into the comments where this is used as ".dumpit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".dumpit" callbacks.
*
* A dump must be understand of "give me all data of a given entity"
* rather than a "dump of the netlink message itself" for debugging etc!
*
* This handler requires `gnl_cb_echo_dumpit_before` to run before a dump and `gnl_cb_echo_dumpit_after` after a dump.
*
* For the sake of simplicity, we use the ECHO_MSG command for the dump. In fact, we don't expect a
* MSG-Attribute here, unlike the regular ECHO_MSG handler. We reply with a dump of
* "all messages that we got" (application specific, hard coded in this example).
*/
int gnl_cb_echo_dumpit(struct sk_buff *pre_allocated_skb, struct netlink_callback *cb) {
void *msg_head;
int ret;
static const char HELLO_FROM_DUMPIT_MSG[] = "You set the flag NLM_F_DUMP; this message is "
"brought to you by .dumpit callback :)";
pr_info("Called %s()\n", __func__);
if (dumpit_cb_progress_data.runs_to_go == 0) {
pr_info("no more data to send in dumpit cb\n");
// mark that dump is done;
return 0;
} else {
dumpit_cb_progress_data.runs_to_go--;
pr_info("%s: %d more runs to do\n", __func__, dumpit_cb_progress_data.runs_to_go);
}
msg_head = genlmsg_put(pre_allocated_skb, // buffer for netlink message: struct sk_buff *
// According to my findings: this is not used for routing
// This can be used in an application specific way to target
// different endpoints within the same user application
// but general rule: just put sender port id here
cb->nlh->nlmsg_pid, // sending port (not process) id: int
// sequence number: int (might be used by receiver, but not mandatory)
// sequence 0, 1, 2...
dumpit_cb_progress_data.total_runs - dumpit_cb_progress_data.runs_to_go - 1,
&gnl_foobar_xmpl_family, // struct genl_family *
0, // flags: int (for netlink header); we don't check them in the userland; application specific
// this way we can trigger a specific command/callback on the receiving side or imply
// on which type of command we are currently answering; this is application specific
GNL_FOOBAR_XMPL_C_ECHO_MSG // cmd: u8 (for generic netlink header);
);
if (msg_head == NULL) {
pr_info("An error occurred in %s(): genlmsg_put() failed\n", __func__);
return -ENOMEM;
}
ret = nla_put_string(
pre_allocated_skb,
GNL_FOOBAR_XMPL_A_MSG,
HELLO_FROM_DUMPIT_MSG
);
if (ret < 0) {
pr_info("An error occurred in %s():\n", __func__);
return ret;
}
genlmsg_end(pre_allocated_skb, msg_head);
// return the length of data we wrote into the pre-allocated buffer
return pre_allocated_skb->len;
}
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info) {
pr_info("%s() invoked, a NLMSG_ERR response will be sent back\n", __func__);
/*
* Generic Netlink is smart enough and sends a NLMSG_ERR reply automatically as reply
* Quote from https://lwn.net/Articles/208755/:
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*/
return -EINVAL;
}
/**
* Called before a dump with `gnl_cb_echo_dumpit()` starts.
* See where this is assigned in `struct genl_ops gnl_foobar_xmpl_ops[]` as
* `.start` callback for more comments.
*
* @return success (0) or error.
*/
int gnl_cb_echo_dumpit_before(struct netlink_callback *cb) {
int ret;
static int unsigned const dump_runs = 3;
pr_info("%s: dump started. acquire lock. initialize dump runs_to_go (number of receives userland can make) to %d runs\n", __func__, dump_runs);
// Lock the mutex like mutex_lock(), and return 0 if the mutex has been acquired or sleep until the mutex becomes available
// If a signal arrives while waiting for the lock then this function returns -EINTR.
ret = mutex_lock_interruptible(&dumpit_cb_progress_data.mtx);
if (ret != 0) {
pr_err("Failed to get lock!\n");
return ret;
}
dumpit_cb_progress_data.total_runs = dump_runs;
dumpit_cb_progress_data.runs_to_go = dump_runs;
return 0;
}
/**
* Called before a dump with `gnl_cb_echo_dumpit()` starts.
* See where this is assigned in `struct genl_ops gnl_foobar_xmpl_ops[]` as
* `.start` callback for more comments.
*
* @return success (0) or error.
*/
int gnl_cb_echo_dumpit_before_after(struct netlink_callback *cb) {
pr_info("%s: dump done. release lock\n", __func__);
mutex_unlock(&dumpit_cb_progress_data.mtx);
return 0;
}
/**
* Module/driver initializer. Called on module load/insertion.
*
* @return success (0) or error code.
*/
static int __init gnl_foobar_xmpl_module_init(void) {
int rc;
pr_info("Generic Netlink Example Module inserted.\n");
// Register family with its operations and policies
rc = genl_register_family(&gnl_foobar_xmpl_family);
if (rc != 0) {
pr_err("FAILED: genl_register_family(): %i\n", rc);
pr_err("An error occurred while inserting the generic netlink example module\n");
return -1;
} else {
pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
mutex_init(&dumpit_cb_progress_data.mtx);
return 0;
}
/**
* Module/driver uninitializer. Called on module unload/removal.
*
* @return success (0) or error code.
*/
static void __exit gnl_foobar_xmpl_module_exit(void) {
int ret;
pr_info("Generic Netlink Example Module unloaded.\n");
// Unregister the family
ret = genl_unregister_family(&gnl_foobar_xmpl_family);
if (ret != 0) {
pr_err("genl_unregister_family() failed: %i\n", ret);
return;
} else {
pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
mutex_destroy(&dumpit_cb_progress_data.mtx);
}
module_init(gnl_foobar_xmpl_module_init);
module_exit(gnl_foobar_xmpl_module_exit);

View File

@@ -0,0 +1,536 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) STMicroelectronics 2019 - All Rights Reserved
* Author: Jean-Philippe Romain <jean-philippe.romain@st.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#include <linux/miscdevice.h>
#include <linux/eventfd.h>
#include <linux/of_platform.h>
#include <linux/list.h>
#define RPMSG_SDB_DRIVER_VERSION "1.0"
/*
* Static global variables
*/
static const char rpmsg_sdb_driver_name[] = "stm32-rpmsg-sdb";
static int LastBufferId;
struct rpmsg_sdb_ioctl_set_efd {
int bufferId, eventfd;
};
struct rpmsg_sdb_ioctl_get_data_size {
int bufferId;
uint32_t size;
};
/* ioctl numbers */
/* _IOW means userland is writing and kernel is reading */
/* _IOR means userland is reading and kernel is writing */
/* _IOWR means userland and kernel can both read and write */
#define RPMSG_SDB_IOCTL_SET_EFD _IOW('R', 0x00, struct rpmsg_sdb_ioctl_set_efd *)
#define RPMSG_SDB_IOCTL_GET_DATA_SIZE _IOWR('R', 0x01, struct rpmsg_sdb_ioctl_get_data_size *)
struct sdb_buf_t {
int index; /* index of buffer */
size_t size; /* buffer size */
size_t writing_size; /* size of data written by copro */
dma_addr_t paddr; /* physical address*/
void *vaddr; /* virtual address */
void *uaddr; /* mapped address for userland */
struct eventfd_ctx *efd_ctx; /* eventfd context */
struct list_head buflist; /* reference in the buffers list */
};
struct rpmsg_sdb_t {
struct mutex mutex; /* mutex to protect the ioctls */
struct miscdevice mdev; /* misc device ref */
struct rpmsg_device *rpdev; /* handle rpmsg device */
struct list_head buffer_list; /* buffer instances list */
};
struct device *rpmsg_sdb_dev;
static int rpmsg_sdb_format_txbuf_string(struct sdb_buf_t *buffer, char *bufinfo_str, size_t bufinfo_str_size)
{
pr_debug("rpmsg_sdb(%s): Buffer index:%d, addr:%08x, size:%08x\n",
__func__,
buffer->index,
buffer->paddr,
buffer->size);
return snprintf(bufinfo_str, bufinfo_str_size, "B%dA%08xL%08x", buffer->index, buffer->paddr, buffer->size);
}
static long rpmsg_sdb_decode_rxbuf_string(char *rxbuf_str, int *buffer_id, size_t *size)
{
int ret = 0;
char *sub_str;
long bsize;
long bufid;
const char delimiter[2] = {'L','\0'};
pr_debug("rpmsg_sdb(%s): rxbuf_str:%s\n", __func__, rxbuf_str);
/* Get first part containing the buffer id */
sub_str = strsep(&rxbuf_str, delimiter);
pr_debug("rpmsg_sdb(%s): sub_str:%s\n", __func__, sub_str);
/* Save Buffer id and size: template BxLyyyyyyyy*/
ret = kstrtol(&sub_str[1], 10, &bufid);
if (ret < 0) {
pr_err("rpmsg_sdb(ERROR): Extract of buffer id failed(%d)", ret);
goto out;
}
ret = kstrtol(&rxbuf_str[2], 16, &bsize);
if (ret < 0) {
pr_err("rpmsg_sdb(ERROR): Extract of buffer size failed(%d)", ret);
goto out;
}
*size = (size_t)bsize;
*buffer_id = (int)bufid;
out:
return ret;
}
static int rpmsg_sdb_send_buf_info(struct rpmsg_sdb_t *rpmsg_sdb, struct sdb_buf_t *buffer)
{
int count = 0, ret = 0;
const unsigned char *tbuf;
char mybuf[32];
int msg_size;
struct rpmsg_device *_rpdev;
_rpdev = rpmsg_sdb->rpdev;
msg_size = rpmsg_get_buffer_size(_rpdev->ept);
if (msg_size < 0)
return msg_size;
count = rpmsg_sdb_format_txbuf_string(buffer, mybuf, 32);
tbuf = &mybuf[0];
do {
/* send a message to our remote processor */
ret = rpmsg_send(_rpdev->ept, (void *)tbuf,
count > msg_size ? msg_size : count);
if (ret) {
dev_err(&_rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
if (count > msg_size) {
count -= msg_size;
tbuf += msg_size;
} else {
count = 0;
}
} while (count > 0);
return count;
}
static int rpmsg_sdb_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long vsize = vma->vm_end - vma->vm_start;
unsigned long size = PAGE_ALIGN(vsize);
unsigned long NumPages = size >> PAGE_SHIFT;
unsigned long align = get_order(size);
pgprot_t prot = pgprot_noncached(vma->vm_page_prot);
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *_buffer;
if (align > CONFIG_CMA_ALIGNMENT)
align = CONFIG_CMA_ALIGNMENT;
if (rpmsg_sdb_dev == NULL)
return -ENOMEM;
rpmsg_sdb_dev->coherent_dma_mask = DMA_BIT_MASK(32);
rpmsg_sdb_dev->dma_mask = &rpmsg_sdb_dev->coherent_dma_mask;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Field the last buffer entry which is the last one created */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
_buffer = list_last_entry(&_rpmsg_sdb->buffer_list,
struct sdb_buf_t, buflist);
_buffer->uaddr = NULL;
_buffer->size = NumPages * PAGE_SIZE;
_buffer->writing_size = -1;
_buffer->vaddr = dma_alloc_coherent(rpmsg_sdb_dev,
_buffer->size,
&_buffer->paddr,
GFP_KERNEL);
if (!_buffer->vaddr) {
pr_err("rpmsg_sdb(ERROR): Memory allocation issue\n");
return -ENOMEM;
}
pr_debug("rpmsg_sdb(%s): dma_alloc_coherent done - paddr[%d]:%x - vaddr[%d]:%p\n",
__func__,
_buffer->index,
_buffer->paddr,
_buffer->index,
_buffer->vaddr);
/* Get address for userland */
if (remap_pfn_range(vma, vma->vm_start,
(_buffer->paddr >> PAGE_SHIFT) + vma->vm_pgoff,
size, prot))
return -EAGAIN;
_buffer->uaddr = (void *)vma->vm_start;
/* Send information to remote proc */
rpmsg_sdb_send_buf_info(_rpmsg_sdb, _buffer);
} else {
dev_err(rpmsg_sdb_dev, "No existing buffer entry exist in the list !!!");
return -EINVAL;
}
/* Increment for number of requested buffer */
LastBufferId++;
return 0;
}
/**
* rpmsg_sdb_open - Open Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_open(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Initialize the buffer list*/
INIT_LIST_HEAD(&_rpmsg_sdb->buffer_list);
mutex_init(&_rpmsg_sdb->mutex);
return 0;
}
/**
* rpmsg_sdb_close - Close Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_close(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *pos, *next;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
list_for_each_entry_safe(pos, next, &_rpmsg_sdb->buffer_list, buflist) {
/* Free the CMA allocation */
pr_debug("rpmsg_sdb(%s): Free the CMA allocation: pos->size:%08x, pos->vaddr:%08x, pos->paddr:%08x\n",
__func__,
pos->size,
pos->vaddr,
pos->paddr);
dma_free_coherent(rpmsg_sdb_dev, pos->size, pos->vaddr,
pos->paddr);
/* Remove the buffer from the list */
list_del(&pos->buflist);
/* Free the buffer */
kfree(pos);
}
/* Reset LastBufferId */
LastBufferId = 0;
return 0;
}
/**
* rpmsg_sdb_ioctl - IOCTL
*
* @session: ibmvmc_file_session struct
* @cmd: cmd field
* @arg: Argument field
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static long rpmsg_sdb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int idx = 0;
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *buffer, *lastbuffer;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_ioctl_set_efd q_set_efd;
struct rpmsg_sdb_ioctl_get_data_size q_get_dat_size;
void __user *argp = (void __user *)arg;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
switch (cmd) {
case RPMSG_SDB_IOCTL_SET_EFD:
mutex_lock(&_rpmsg_sdb->mutex);
/* Get index from the last buffer in the list */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
lastbuffer = list_last_entry(&_rpmsg_sdb->buffer_list, struct sdb_buf_t, buflist);
idx = lastbuffer->index;
/* Check last index was properly initiated*/
if (lastbuffer->vaddr == NULL) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - previous buffer was not allocated\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EBADE;
}
/* increment this index for the next buffer creation*/
idx++;
}
if (copy_from_user(&q_set_efd, (struct rpmsg_sdb_ioctl_set_efd *)argp,
sizeof(struct rpmsg_sdb_ioctl_set_efd))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - copy from user failed\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EFAULT;
}
/* create a new buffer which will be added in the buffer list */
buffer = kmalloc(sizeof(struct sdb_buf_t), GFP_KERNEL);
buffer->index = idx;
buffer->vaddr = NULL;
buffer->efd_ctx = eventfd_ctx_fdget(q_set_efd.eventfd);
list_add_tail(&buffer->buflist, &_rpmsg_sdb->buffer_list);
mutex_unlock(&_rpmsg_sdb->mutex);
break;
case RPMSG_SDB_IOCTL_GET_DATA_SIZE:
if (copy_from_user(&q_get_dat_size, (struct rpmsg_sdb_ioctl_get_data_size *)argp,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy from user failed\n");
return -EFAULT;
}
/* Get the index of the requested buffer and then look-up in the buffer list*/
idx = q_get_dat_size.bufferId;
list_for_each(pos, &_rpmsg_sdb->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == idx) {
/* Get the writing size*/
q_get_dat_size.size = datastructureptr->writing_size;
break;
}
}
if (copy_to_user((struct rpmsg_sdb_ioctl_get_data_size *)argp, &q_get_dat_size,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy to user failed\n");
return -EFAULT;
}
/* Reset the writing size*/
datastructureptr->writing_size = -1;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct file_operations rpmsg_sdb_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = rpmsg_sdb_ioctl,
.mmap = rpmsg_sdb_mmap,
.open = rpmsg_sdb_open,
.release = rpmsg_sdb_close,
};
static int rpmsg_sdb_drv_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret = 0;
int buffer_id = 0;
size_t buffer_size;
char *rpmsg_RxBuf;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpdev->dev);
if (len == 0) {
dev_err(rpmsg_sdb_dev, "(%s) Empty lenght requested\n", __func__);
return -EINVAL;
}
//dev_err(rpmsg_sdb_dev, "(%s) lenght: %d\n", __func__,len);
rpmsg_RxBuf = (char *)kmalloc(len+1, GFP_KERNEL);
memcpy(rpmsg_RxBuf, data, len);
rpmsg_RxBuf[len] = 0;
ret = rpmsg_sdb_decode_rxbuf_string(rpmsg_RxBuf, &buffer_id, &buffer_size);
kfree(rpmsg_RxBuf);
if (ret < 0)
goto out;
if (buffer_id > LastBufferId) {
ret = -EINVAL;
goto out;
}
/* Signal to User space application */
list_for_each(pos, &drv->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == buffer_id) {
datastructureptr->writing_size = buffer_size;
if (datastructureptr->writing_size > datastructureptr->size) {
dev_err(rpmsg_sdb_dev, "(%s) Writing size is bigger than buffer size\n", __func__);
ret = -EINVAL;
goto out;
}
eventfd_signal(datastructureptr->efd_ctx, 1);
break;
}
/* TODO: quid if nothing find during the loop ? */
}
out:
return ret;
}
static int rpmsg_sdb_drv_probe(struct rpmsg_device *rpdev)
{
int ret = 0;
struct device *dev = &rpdev->dev;
struct rpmsg_sdb_t *rpmsg_sdb;
rpmsg_sdb = devm_kzalloc(dev, sizeof(*rpmsg_sdb), GFP_KERNEL);
if (!rpmsg_sdb)
return -ENOMEM;
mutex_init(&rpmsg_sdb->mutex);
rpmsg_sdb->rpdev = rpdev;
rpmsg_sdb->mdev.name = "rpmsg-sdb";
rpmsg_sdb->mdev.minor = MISC_DYNAMIC_MINOR;
rpmsg_sdb->mdev.fops = &rpmsg_sdb_fops;
dev_set_drvdata(&rpdev->dev, rpmsg_sdb);
/* Register misc device */
ret = misc_register(&rpmsg_sdb->mdev);
if (ret) {
dev_err(dev, "Failed to register device\n");
goto err_out;
}
rpmsg_sdb_dev = rpmsg_sdb->mdev.this_device;
dev_info(dev, "%s probed\n", rpmsg_sdb_driver_name);
err_out:
return ret;
}
static void rpmsg_sdb_drv_remove(struct rpmsg_device *rpmsgdev)
{
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpmsgdev->dev);
misc_deregister(&drv->mdev);
}
static struct rpmsg_device_id rpmsg_driver_sdb_id_table[] = {
{ .name = "rpmsg-sdb-channel" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_sdb_id_table);
static struct rpmsg_driver rpmsg_sdb_rmpsg_drv = {
.drv.name = KBUILD_MODNAME,
.drv.owner = THIS_MODULE,
.id_table = rpmsg_driver_sdb_id_table,
.probe = rpmsg_sdb_drv_probe,
.callback = rpmsg_sdb_drv_cb,
.remove = rpmsg_sdb_drv_remove,
};
static int __init rpmsg_sdb_drv_init(void)
{
int ret = 0;
/* Register rpmsg device */
ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
if (ret) {
pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
return ret;
}
pr_info("rpmsg_sdb: Init done\n");
return ret;
}
static void __exit rpmsg_sdb_drv_exit(void)
{
unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
pr_info("rpmsg_sdb: Exit\n");
}
module_init(rpmsg_sdb_drv_init);
module_exit(rpmsg_sdb_drv_exit);
MODULE_AUTHOR("Jean-Philippe Romain <jean-philippe.romain@st.com>");
MODULE_DESCRIPTION("shared data buffer over RPMSG");
MODULE_VERSION(RPMSG_SDB_DRIVER_VERSION);
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,287 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 STMicroelectronics - All Rights Reserved
*
* The rpmsg tty driver implements serial communication on the RPMsg bus to makes
* possible for user-space programs to send and receive rpmsg messages as a standard
* tty protocol.
*
* The remote processor can instantiate a new tty by requesting a "rpmsg-tty" RPMsg service.
* The "rpmsg-tty" service is directly used for data exchange. No flow control is implemented yet.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#define RPMSG_TTY_NAME "ttyRPMSG"
#define MAX_TTY_RPMSG 32
static DEFINE_IDR(tty_idr); /* tty instance id */
static DEFINE_MUTEX(idr_lock); /* protects tty_idr */
static struct tty_driver *rpmsg_tty_driver;
struct rpmsg_tty_port {
struct tty_port port; /* TTY port data */
int id; /* TTY rpmsg index */
struct rpmsg_device *rpdev; /* rpmsg device */
};
static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src)
{
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
int copied;
if (!len)
return -EINVAL;
copied = tty_insert_flip_string(&cport->port, data, len);
if (copied != len)
dev_err_ratelimited(&rpdev->dev, "Trunc buffer: available space is %d\n", copied);
tty_flip_buffer_push(&cport->port);
return 0;
}
static int rpmsg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
{
struct rpmsg_tty_port *cport = idr_find(&tty_idr, tty->index);
struct tty_port *port;
tty->driver_data = cport;
port = tty_port_get(&cport->port);
return tty_port_install(port, driver, tty);
}
static void rpmsg_tty_cleanup(struct tty_struct *tty)
{
tty_port_put(tty->port);
}
static int rpmsg_tty_open(struct tty_struct *tty, struct file *filp)
{
return tty_port_open(tty->port, tty, filp);
}
static void rpmsg_tty_close(struct tty_struct *tty, struct file *filp)
{
return tty_port_close(tty->port, tty, filp);
}
static int rpmsg_tty_write(struct tty_struct *tty, const u8 *buf, int len)
{
struct rpmsg_tty_port *cport = tty->driver_data;
struct rpmsg_device *rpdev;
int msg_max_size, msg_size;
int ret;
rpdev = cport->rpdev;
msg_max_size = rpmsg_get_mtu(rpdev->ept);
if (msg_max_size < 0)
return msg_max_size;
msg_size = min(len, msg_max_size);
/*
* Use rpmsg_trysend instead of rpmsg_send to send the message so the caller is not
* hung until a rpmsg buffer is available. In such case rpmsg_trysend returns -ENOMEM.
*/
ret = rpmsg_trysend(rpdev->ept, (void *)buf, msg_size);
if (ret) {
dev_dbg_ratelimited(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
return msg_size;
}
static unsigned int rpmsg_tty_write_room(struct tty_struct *tty)
{
struct rpmsg_tty_port *cport = tty->driver_data;
int size;
size = rpmsg_get_mtu(cport->rpdev->ept);
if (size < 0)
return 0;
return size;
}
static void rpmsg_tty_hangup(struct tty_struct *tty)
{
tty_port_hangup(tty->port);
}
static const struct tty_operations rpmsg_tty_ops = {
.install = rpmsg_tty_install,
.open = rpmsg_tty_open,
.close = rpmsg_tty_close,
.write = rpmsg_tty_write,
.write_room = rpmsg_tty_write_room,
.hangup = rpmsg_tty_hangup,
.cleanup = rpmsg_tty_cleanup,
};
static struct rpmsg_tty_port *rpmsg_tty_alloc_cport(void)
{
struct rpmsg_tty_port *cport;
int ret;
cport = kzalloc(sizeof(*cport), GFP_KERNEL);
if (!cport)
return ERR_PTR(-ENOMEM);
mutex_lock(&idr_lock);
ret = idr_alloc(&tty_idr, cport, 0, MAX_TTY_RPMSG, GFP_KERNEL);
mutex_unlock(&idr_lock);
if (ret < 0) {
kfree(cport);
return ERR_PTR(ret);
}
cport->id = ret;
return cport;
}
static void rpmsg_tty_destruct_port(struct tty_port *port)
{
struct rpmsg_tty_port *cport = container_of(port, struct rpmsg_tty_port, port);
mutex_lock(&idr_lock);
idr_remove(&tty_idr, cport->id);
mutex_unlock(&idr_lock);
kfree(cport);
}
static const struct tty_port_operations rpmsg_tty_port_ops = {
.destruct = rpmsg_tty_destruct_port,
};
static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
{
struct rpmsg_tty_port *cport;
struct device *dev = &rpdev->dev;
struct device *tty_dev;
int ret;
cport = rpmsg_tty_alloc_cport();
if (IS_ERR(cport))
return dev_err_probe(dev, PTR_ERR(cport), "Failed to alloc tty port\n");
tty_port_init(&cport->port);
cport->port.ops = &rpmsg_tty_port_ops;
tty_dev = tty_port_register_device(&cport->port, rpmsg_tty_driver,
cport->id, dev);
if (IS_ERR(tty_dev)) {
ret = dev_err_probe(dev, PTR_ERR(tty_dev), "Failed to register tty port\n");
tty_port_put(&cport->port);
return ret;
}
cport->rpdev = rpdev;
dev_set_drvdata(dev, cport);
dev_dbg(dev, "New channel: 0x%x -> 0x%x: " RPMSG_TTY_NAME "%d\n",
rpdev->src, rpdev->dst, cport->id);
return 0;
}
static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
{
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
dev_dbg(&rpdev->dev, "Removing rpmsg tty device %d\n", cport->id);
/* User hang up to release the tty */
tty_port_tty_hangup(&cport->port, false);
tty_unregister_device(rpmsg_tty_driver, cport->id);
tty_port_put(&cport->port);
}
static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = {
{ .name = "rpmsg-tty" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_tty_id_table);
static struct rpmsg_driver rpmsg_tty_rpmsg_drv = {
.drv.name = KBUILD_MODNAME,
.id_table = rpmsg_driver_tty_id_table,
.probe = rpmsg_tty_probe,
.callback = rpmsg_tty_cb,
.remove = rpmsg_tty_remove,
};
static int __init rpmsg_tty_init(void)
{
int ret;
rpmsg_tty_driver = tty_alloc_driver(MAX_TTY_RPMSG, TTY_DRIVER_REAL_RAW |
TTY_DRIVER_DYNAMIC_DEV);
if (IS_ERR(rpmsg_tty_driver))
return PTR_ERR(rpmsg_tty_driver);
rpmsg_tty_driver->driver_name = "rpmsg_tty";
rpmsg_tty_driver->name = RPMSG_TTY_NAME;
rpmsg_tty_driver->major = 0;
rpmsg_tty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
/* Disable unused mode by default */
rpmsg_tty_driver->init_termios = tty_std_termios;
rpmsg_tty_driver->init_termios.c_lflag &= ~(ECHO | ICANON);
rpmsg_tty_driver->init_termios.c_oflag &= ~(OPOST | ONLCR);
tty_set_operations(rpmsg_tty_driver, &rpmsg_tty_ops);
ret = tty_register_driver(rpmsg_tty_driver);
if (ret < 0) {
pr_err("Couldn't install driver: %d\n", ret);
goto error_put;
}
ret = register_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
if (ret < 0) {
pr_err("Couldn't register driver: %d\n", ret);
goto error_unregister;
}
return 0;
error_unregister:
tty_unregister_driver(rpmsg_tty_driver);
error_put:
tty_driver_kref_put(rpmsg_tty_driver);
return ret;
}
static void __exit rpmsg_tty_exit(void)
{
unregister_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
tty_unregister_driver(rpmsg_tty_driver);
tty_driver_kref_put(rpmsg_tty_driver);
idr_destroy(&tty_idr);
}
module_init(rpmsg_tty_init);
module_exit(rpmsg_tty_exit);
MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>");
MODULE_DESCRIPTION("remote processor messaging tty driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,804 @@
/* Copyright 2024 Markus Lehr
*
*
* This Software is owned by Markus Lehr.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// ##################################################################################################
/*
* This is a Linux kernel module/driver called "vrpmdv-monitoring-cmd " which holds the family ID
* and functions to handle the monitoring in a Generic Netlink in the kernel.
* "It registers a Netlink family called "vrpmdv-monitoring_cmd".
*
*
* You can find some more interesting documentation about Generic Netlink here:
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
*/
#include <linux/kernel.h>
#include <linux/rpmsg.h>
// basic definitions for kernel module development
#include <linux/module.h>
// definitions for generic netlink families, policies etc;
// transitive dependencies for basic netlink, sockets etc
#include <net/genetlink.h>
// required for locking inside the .dumpit callback demonstration
#include <linux/mutex.h>
#include <linux/wait.h>
// data/vars/enums/properties that describes our protocol that we implement
// on top of generic netlink (like functions we want to trigger on the receiving side)
//#include "vrpmdv-monitoring-cmd.h"
/* ######################## CONVENIENT LOGGING MACROS ######################## */
// (Re)definition of some convenient logging macros from <linux/printk.h>. You can see the logging
// messages when printing the kernel log, e.g. with `$ sudo dmesg`.
// See https://elixir.bootlin.com/linux/latest/source/include/linux/printk.h
// with this redefinition we can easily prefix all log messages from pr_* logging macros
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* ########################################################################### */
#define MSG "hello Monitoring!"
static int count = 100;
module_param(count, int, 0644);
struct instance_data {
int rx_count;
};
// std::chrono::seconds timeoutPeriod = 5;
// auto timePoint = std::chrono::system_clock::now() + timeoutPeriod;
/** ----- NETLINK Driver defintion ------------------*/
/**
* Generic Netlink will create a Netlink family with this name. Kernel will asign
* a numeric ID and afterwards we can talk to the family with its ID. To get
* the ID we use Generic Netlink in the userland and pass the family name.
*
* Short for: Generic Netlink VRPMDV Monitoring gnl_foobar_mcmd
*/
#define FAMILY_NAME "gnl-vrpmdv-mcmd"
/**
* These are the attributes that we want to share in gnl_foobar_xmpl.
* You can understand an attribute as a semantic type. This is
* the payload of Netlink messages.
* GNl: Generic Netlink
*/
enum GNL_VRPMDV_XMPL_ATTRIBUTE {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_A_UNSPEC,
/** We expect a MSG to be a null-terminated C-string. */
GNL_VRPMDV_MCMD_A_MSG,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_A_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN (__GNL_VRPMDV_MCMD_A_MAX)
/**
* The number of actual usable attributes in `enum GNL_VRPMDV_MCMD_ATTRIBUTE`.
* This is `GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_ATTRIBUTE_COUNT (GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN - 1)
/**
* Enumeration of all commands (functions) that our custom protocol on top
* of generic netlink supports. This can be understood as the action that
* we want to trigger on the receiving side.
*/
enum GNL_VRPMDV_MCMD_COMMAND {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_C_UNSPEC,
// first real command is "1" (>0)
/**
* When this command is received, we expect the attribute `GNL_VRPMDV_MCMD_ATTRIBUTE::GNL_VRPMDV_MCMD_A_MSG` to
* be present in the Generic Netlink request message. The kernel reads the message from the packet and
* sent it to the copro. THe result will be return by creating a new Generic Netlink response message
* with an corresponding attribute/payload.
*
* This command/signaling mechanism is independent of the Netlink flag `NLM_F_ECHO (0x08)`. We use it as
* "echo specific data" instead of return a 1:1 copy of the package, which you could do with
* `NLM_F_ECHO (0x08)` for example.
*/
GNL_VRPMDV_MCMD_C_MSG,
/**
* Provokes a NLMSG_ERR answer to this request as described in netlink manpage
* (https://man7.org/linux/man-pages/man7/netlink.7.html).
*/
GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_C_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN (__GNL_VRPMDV_MCMD_C_MAX)
/**
* The number of actual usable commands in `enum GNL_FOOBAR_XMPL_COMMAND`.
* This is `GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_COMMAND_COUNT (GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN - 1)
#define NODATARECEIVED 0
#define DATARECEIVED 1
/**
* Data structure required for our .doit callback handler to
* know about the progress of an ongoing cmd execution.
* See the cmd callback handler how it is used.
*/
// struct {
// // from <linux/mutex.h>
// /**
// * rpmsg wait for response from copro side.
// */
// struct mutex sendMTX;
// /**
// * rpmsg wait for response from copro side.
// */
// struct mutex receiveCV;
// /**
// * Wait Queue: if it is signaled we have received data from copro
// */
// wait_queue_head_t receive_queue;
// /**
// * Waitflag: 0= no data received, 1 = data received
// */
// int receive_queue_flag = NODATARECEIVED;
// /**
// * Condition vaiable signal we have received data from copro
// */
// // std::condition_variable cv;
// // /**
// // * Number that describes how many packets we need to send until we are done
// // * during an ongoing dumpit process. 0 = done.
// // */
// // int unsigned runs_to_go;
// // /**
// // * Number that describes how many packets per dump are sent in total.
// // * Constant per dump.
// // */
// // int unsigned total_runs;
// //the rpmsg device which sends the data to the copro
// struct rpmsg_device *rpdev; /* handle rpmsg device */
// } cmd_cb_progress_data;
struct rpmsg_vrpmdv_mon_t{
// from <linux/mutex.h>
/**
* rpmsg wait for response from copro side.
*/
struct mutex sendMTX;
/**
* rpmsg wait for response from copro side.
*/
struct mutex receiveCV;
/**
* Wait Queue: if it is signaled we have received data from copro
*/
wait_queue_head_t receive_queue;
/**
* Waitflag: 0= no data received, 1 = data received
*/
int receive_queue_flag;
//the rpmsg device which sends the data to the copro
struct rpmsg_device* rpdev; /* handle rpmsg device */
};
struct rpmsg_vrpmdv_mon_t vrpmdv_mon;
// struct mutex mutex; /* mutex to protect the ioctls */
// struct miscdevice mdev; /* misc device ref */
// struct rpmsg_device *rpdev; /* handle rpmsg device */
// struct list_head buffer_list; /* buffer instances list */
// Documentation is on the implementation of this function.
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info);
// Documentation is on the implementation of this function.
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info);
/**
* The length of `struct genl_ops gnl_foobar_xmpl_ops[]`. Not necessarily
* the number of commands in `enum GNlFoobarXmplCommand`. It depends on your application logic.
* For example, you can use the same command multiple times and - dependent by flag -
* invoke a different callback handler. In our simple example we just use one .doit callback
* per operation/command.
*/
#define GNL_VRPMDV_OPS_LEN (GNL_VRPMDV_MCMD_COMMAND_COUNT)
/**
* Array with all operations that our protocol on top of Generic Netlink
* supports. An operation is the glue between a command ("cmd" field in `struct genlmsghdr` of
* received Generic Netlink message) and the corresponding ".doit" callback function.
* See: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L148
*/
struct genl_ops gnl_vrpmdv_mcmd_ops[GNL_VRPMDV_OPS_LEN] = {
{
/* The "cmd" field in `struct genlmsghdr` of received Generic Netlink message */
.cmd = GNL_VRPMDV_MCMD_C_MSG,
/* TODO Use case ? */
.flags = 0,
/* TODO Use case ? */
.internal_flags = 0,
/* Callback handler when a request with the specified ".cmd" above is received.
* Always validates the payload except one set NO_STRICT_VALIDATION flag in ".validate"
* See: https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*
* Quote from: https://lwn.net/Articles/208755
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*
*/
.doit = gnl_cb_vrpmdv_doit,
/* This callback is similar in use to the standard Netlink 'dumpit' callback.
* The 'dumpit' callback is invoked when a Generic Netlink message is received
* with the NLM_F_DUMP flag set.
*
* A dump can be understand as a "GET ALL DATA OF THE GIVEN ENTITY", i.e.
* the userland can receive as long as the .dumpit callback returns data.
*
* .dumpit is not mandatory, but either it or .doit must be provided, see
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L367
*
* To be honest I don't know in what use case one should use .dumpit and why
* it is useful, because you can achieve the same also with .doit handlers.
* Anyway, this is just an example/tutorial.
*
* Quote from: https://lwn.net/Articles/208755
* "The main difference between a 'dumpit' handler and a 'doit' handler is
* that a 'dumpit' handler does not allocate a message buffer for a response;
* a pre-allocated sk_buff is passed to the 'dumpit' handler as the first
* parameter. The 'dumpit' handler should fill the message buffer with the
* appropriate response message and return the size of the sk_buff,
* i.e. sk_buff->len, and the message buffer will automatically be sent to the
* Generic Netlink client that initiated the request. As long as the 'dumpit'
* handler returns a value greater than zero it will be called again with a
* newly allocated message buffer to fill, when the handler has no more data
* to send it should return zero; error conditions are indicated by returning
* a negative value. If necessary, state can be preserved in the
* netlink_callback parameter which is passed to the 'dumpit' handler; the
* netlink_callback parameter values will be preserved across handler calls
* for a single request."
*
* You can see the check for the NLM_F_DUMP-flag here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L780
*/
.dumpit = NULL, //gnl_cb_echo_dumpit,
/* Start callback for dumps. Can be used to lock data structures. */
.start = NULL, //gnl_cb_echo_dumpit_before,
/* Completion callback for dumps. Can be used for cleanup after a dump and releasing locks. */
.done = NULL, //gnl_cb_echo_dumpit_before_after,
/*
0 (= "validate strictly") or value `enum genl_validate_flags`
* see: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L108
*/
.validate = 0,
},
{
.cmd = GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
.flags = 0,
.internal_flags = 0,
.doit = gnl_cb_doit_reply_with_nlmsg_err,
// .dumpit is not required, only optional; application specific/dependent on your use case
// in a real application you probably have different .dumpit handlers per operation/command
.dumpit = NULL,
// in a real application you probably have different .start handlers per operation/command
.start = NULL,
// in a real application you probably have different .done handlers per operation/command
.done = NULL,
.validate = 0,
}
};
/**
* Attribute policy: defines which attribute has which type (e.g int, char * etc).
* This get validated for each received Generic Netlink message, if not deactivated
* in `gnl_foobar_xmpl_ops[].validate`.
* See https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*/
static struct nla_policy gnl_vrpmdv_mcmd_policy[GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN] = {
// In case you are seeing this syntax for the first time (I also learned this just after a few years of
// coding with C myself): The following static array initiations are equivalent:
// `int a[2] = {1, 2}` <==> `int a[2] = {[0] => 1, [1] => 2}`.
[GNL_VRPMDV_MCMD_A_UNSPEC] = {.type = NLA_UNSPEC},
// You can set this to NLA_U32 for testing and send an ECHO message from the userland
// It will fail in this case and you see a entry in the kernel log.
// `enum GNL_FOOBAR_XMPL_ATTRIBUTE::GNL_FOOBAR_XMPL_A_MSG` is a null-terminated C-String
[GNL_VRPMDV_MCMD_A_MSG] = {.type = NLA_NUL_STRING},
};
/**
* Definition of the Netlink family we want to register using Generic Netlink functionality
*/
static struct genl_family gnl_vrpmdv_mcmd_family = {
// automatically assign an id
.id = 0,
// we don't use custom additional header info / user specific header
.hdrsize = 0,
// The name of this family, used by userspace application to get the numeric ID
.name = FAMILY_NAME,
// family specific version number; can be used to evolve application over time (multiple versions)
.version = 1,
// delegates all incoming requests to callback functions
.ops = gnl_vrpmdv_mcmd_ops,
// length of array `gnl_foobar_xmpl_ops`
.n_ops = GNL_VRPMDV_OPS_LEN,
// attribute policy (for validation of messages). Enforced automatically, except ".validate" in
// corresponding ".ops"-field is set accordingly.
.policy = gnl_vrpmdv_mcmd_policy,
// Number of attributes / bounds check for policy (array length)
.maxattr = GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN,
// Owning Kernel module of the Netlink family we register.
.module = THIS_MODULE,
// Actually not necessary because this memory region would be zeroed anyway during module load,
// but this way one sees all possible options.
// if your application must handle multiple netlink calls in parallel (where one should not block the next
// from starting), set this to true! otherwise all netlink calls are mutually exclusive
.parallel_ops = 0,
// set to true if the family can handle network namespaces and should be presented in all of them
.netnsok = 0,
// called before an operation's doit callback, it may do additional, common, filtering and return an error
.pre_doit = NULL,
// called after an operation's doit callback, it may undo operations done by pre_doit, for example release locks
.post_doit = NULL,
};
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_VRPMDV_MCMD_C_MSG` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_vrpmdv_mcmd_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info) {
struct nlattr *na;
struct sk_buff *reply_skb;
int rc;
void *msg_head;
char *recv_msg;
pr_info("%s() invoked\n", __func__);
if (info == NULL) {
// should never happen
pr_err("An error occurred in %s():\n", __func__);
return -EINVAL;
}
/*
* For each attribute there is an index in info->attrs which points to a nlattr structure
* in this structure the data is stored.
*/
na = info->attrs[GNL_VRPMDV_MCMD_A_MSG];
if (!na) {
pr_err("no info->attrs[%i]\n", GNL_VRPMDV_MCMD_A_MSG);
return -EINVAL; // we return here because we expect to recv a msg
}
recv_msg = (char *) nla_data(na);
if (recv_msg == NULL) {
pr_err("error while receiving data\n");
} else {
pr_info("received: '%s'\n", recv_msg);
}
//aquire lock for cmd repmsg channel
// std::lock sendlk(cmd_cb_progress_data.sendmtx);
//send the message to the copro over RPMSG
rc = rpmsg_send(vrpmdv_mon.rpdev->ept, recv_msg, strlen(recv_msg));
if (rc) {
pr_err("rpmsg_send failed: %d\n", rc);
return rc;
}
// struct rpmsg_vrpmdv_mon_t *drv = dev_get_drvdata(&rpdev->dev);
{
pr_info("wait for response\n");
// wait until receive_queue_flag=1 , that means we have received data from Copro
wait_event_interruptible(vrpmdv_mon.receive_queue, vrpmdv_mon.receive_queue_flag != 0 );
pr_info("received data \n");
//Copy data
vrpmdv_mon.receive_queue_flag = NODATARECEIVED;
// std::unique_lock lk(cmd_cb_progress_data.receivemtx);
// if (myCondVar.wait_until(uLock, timePoint) == std::cv_status::timeout)
// {
// dev_err(&cmd_cb_progress_data.rpdev, "rpmsg_send failed, timeout: \n");
// return -1:
// }
pr_info("get response: '%s'\n", recv_msg);
}
// Send a message back after we receive the reply from rpmsg channel
// ---------------------
// Allocate some memory, since the size is not yet known use NLMSG_GOODSIZE
reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (reply_skb == NULL) {
pr_err("An error occurred in %s():\n", __func__);
return -ENOMEM;
}
// Create the message headers
// Add header to netlink message;
// afterwards the buffer looks like this:
// ----------------------------------
// | netlink header |
// | generic netlink header |
// | <space for netlink attributes> |
// ----------------------------------
// msg_head = genlmsg_put(reply_skb, // buffer for netlink message: struct sk_buff *
// // According to my findings: this is not used for routing
// // This can be used in an application specific way to target
// // different endpoints within the same user application
// // but general rule: just put sender port id here
// info->snd_portid, // sending port (not process) id: int
// info->snd_seq + 1, // sequence number: int (might be used by receiver, but not mandatory)
// &gnl_vrpmdv_mcmd_family, // struct genl_family *
// 0, // flags for Netlink header: int; application specific and not mandatory
// // The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
// GNL_VRPMDV_MCMD_C_MSG
// );
msg_head = genlmsg_put_reply(reply_skb, // buffer for netlink message: struct sk_buff *
info, // info
&gnl_vrpmdv_mcmd_family, // struct genl_family *
0, // flags for Netlink header: int; application specific and not mandatory
// The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
info->genlhdr->cmd
);
if (msg_head == NULL) {
rc = ENOMEM;
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Add a GNL_VRPMDV_MCMD_A_MSG attribute (actual value/payload to be sent)
// echo the value we just received
rc = nla_put_string(reply_skb, GNL_VRPMDV_MCMD_A_MSG, recv_msg);
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Finalize the message:
// Corrects the netlink message header (length) to include the appended
// attributes. Only necessary if attributes have been added to the message.
genlmsg_end(reply_skb, msg_head);
// Send the message back
rc = genlmsg_reply(reply_skb, info);
// same as genlmsg_unicast(genl_info_net(info), reply_skb, info->snd_portid)
// see https://elixir.bootlin.com/linux/v5.8.9/source/include/net/genetlink.h#L326
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
return 0;
}
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info) {
pr_info("%s() invoked, a NLMSG_ERR response will be sent back\n", __func__);
/*
* Generic Netlink is smart enough and sends a NLMSG_ERR reply automatically as reply
* Quote from https://lwn.net/Articles/208755/:
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*/
return -EINVAL;
}
/** ----- NETLINK Driver defintion ------------------*/
/**
* callback that is called after the copro send data
* we have to copy it in a buffer for the netlink and later send it back to the userland
*
*/
static int vrpmdv_monitoring_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret = 0;
struct instance_data *idata = dev_get_drvdata(&rpdev->dev);
dev_info(&rpdev->dev, "incoming msg %d (src: 0x%x)\n",
++idata->rx_count, src);
print_hex_dump_debug(__func__, DUMP_PREFIX_NONE, 16, 1, data, len,
true);
vrpmdv_mon.receive_queue_flag= DATARECEIVED;
wake_up_interruptible(&(vrpmdv_mon.receive_queue));
// /* samples should not live forever */
// if (idata->rx_count >= count) {
// dev_info(&rpdev->dev, "goodbye!\n");
// return 0;
// }
/* send a new message now */
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
// if (ret)
// dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
static int vrpmdv_monitoring_probe(struct rpmsg_device *rpdev)
{
int rc;
// int ret;
// struct instance_data *idata;
dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
rpdev->src, rpdev->dst);
// idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
// if (!idata)
// return -ENOMEM;
// dev_set_drvdata(&rpdev->dev, idata);
// /* send a message to our remote processor to */
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
// if (ret) {
// dev_err(&rpdev->dev, "vrpmdv_monitoring_controler_send failed: %d\n", ret);
// return ret;
// }
// return 0;
// struct device *dev;
// dev = &rpdev->dev;
// struct rpmsg_vrpmdv_mon_t *rpmsg_vrpmdv_mon;
// rpmsg_vrpmdv_mon = devm_kzalloc(dev, sizeof(*rpmsg_vrpmdv_mon), GFP_KERNEL);
// if (!rpmsg_vrpmdv_mon)
// return -ENOMEM;
mutex_init(&(vrpmdv_mon.sendMTX));
init_waitqueue_head (&(vrpmdv_mon.receive_queue));
vrpmdv_mon.rpdev = rpdev;
// dev_set_drvdata(&rpdev->dev, rpmsg_vrpmdv_mon);
pr_info("RPMSG CMD Device set.\n");
/** NEU **/
// if (cmd_cb_progress_data.rpdev == NULL) {
// cmd_cb_progress_data.rpdev = rpdev;
// pr_info("RPMSG CMD Device set.\n");
// }
// else {
// pr_info("Error: RPMSG CMD Device already set. Don't set it twice\n");
// }
pr_info("Generic Netlink VRPMDV-Monitroring_Controler Module started.\n");
// Register family with its operations and policies
rc = genl_register_family(&gnl_vrpmdv_mcmd_family);
if (rc != 0) {
pr_err("FAILED: genl_register_family(): %i\n", rc);
pr_err("An error occurred while inserting the generic netlink example module\n");
return -1;
} else {
pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
return 0;
}
static void vrpmdv_monitoring_remove(struct rpmsg_device *rpdev)
{
int ret;
pr_info("Generic Netlink Example Module unloaded.\n");
// Unregister the family
ret = genl_unregister_family(&gnl_vrpmdv_mcmd_family);
if (ret != 0) {
pr_err("genl_unregister_family() failed: %i\n", ret);
return;
} else {
pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
mutex_destroy(&(vrpmdv_mon.sendMTX));
wake_up_interruptible(&(vrpmdv_mon.receive_queue));
dev_info(&rpdev->dev, "vrpmdv-monitoring controler driver is removed\n");
}
static struct rpmsg_device_id vrpmdv_monitoring_controler_id_table[] = {
{ .name = "vrpmdv-monitoring-controler" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, vrpmdv_monitoring_controler_id_table);
static struct rpmsg_driver vrpmdv_monitoring_controler = {
.drv.name = KBUILD_MODNAME,
.id_table = vrpmdv_monitoring_controler_id_table,
.probe = vrpmdv_monitoring_probe,
.callback = vrpmdv_monitoring_cb,
.remove = vrpmdv_monitoring_remove,
};
module_rpmsg_driver(vrpmdv_monitoring_controler);
// static struct rpmsg_driver vrpmdv_monitoring_data = {
// .drv.name = KBUILD_MODNAME,
// .id_table = vrpmdv_monitoring_controler_id_table,
// .probe = vrpmdv_monitoring_probe,
// .callback = vrpmdv_monitoring_cb,
// .remove = vrpmdv_monitoring_remove,
// };
// module_rpmsg_driver(vrpmdv_monitoring_data);
MODULE_DESCRIPTION("Remote processor messaging vrpmdv monitoring controler");
MODULE_LICENSE("GPL v2");
// /**
// * Module/driver initializer. Called on module load/insertion.
// *
// * @return success (0) or error code.
// */
// static int __init gnl_foobar_xmpl_module_init(void) {
// int rc;
// pr_info("Generic Netlink Example Module inserted.\n");
// // Register family with its operations and policies
// rc = genl_register_family(&gnl_foobar_xmpl_family);
// if (rc != 0) {
// pr_err("FAILED: genl_register_family(): %i\n", rc);
// pr_err("An error occurred while inserting the generic netlink example module\n");
// return -1;
// } else {
// pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_init(&dumpit_cb_progress_data.mtx);
// return 0;
// }
// /**
// * Module/driver uninitializer. Called on module unload/removal.
// *
// * @return success (0) or error code.
// */
// static void __exit gnl_foobar_xmpl_module_exit(void) {
// int ret;
// pr_info("Generic Netlink Example Module unloaded.\n");
// // Unregister the family
// ret = genl_unregister_family(&gnl_foobar_xmpl_family);
// if (ret != 0) {
// pr_err("genl_unregister_family() failed: %i\n", ret);
// return;
// } else {
// pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_destroy(&dumpit_cb_progress_data.mtx);
// }
// ----
// static int __init rpmsg_sdb_drv_init(void)
// {
// int ret = 0;
// /* Register rpmsg device */
// ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// if (ret) {
// pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
// return ret;
// }
// pr_info("rpmsg_sdb: Init done\n");
// return ret;
// }
// static void __exit rpmsg_sdb_drv_exit(void)
// {
// unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// pr_info("rpmsg_sdb: Exit\n");
// }
// module_init(rpmsg_sdb_drv_init);
// module_exit(rpmsg_sdb_drv_exit);

View File

@@ -0,0 +1,24 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "CLOSED"
# LICENSE = "GPL-2.0-only"
# LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://stm32_rpmsg_sdb.c \
"
# file://75-rpmsg-sdb.rules \
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
# do_install:append() {
# udev rules for rpmsg-sdb
# install -d ${D}${sysconfdir}/udev/rules.d/
# install -m 0644 ${WORKDIR}/75-rpmsg-sdb.rules ${D}${sysconfdir}/udev/rules.d/75-rpmsg-sdb.rules
# }
#FILES:${PN} += "${sysconfdir}/udev/rules.d/"
RPROVIDES:${PN} += "kernel-module-vrpmdv-monitoring-driver"

View File

@@ -0,0 +1,26 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv-monitoring.c \
file://75-vrpmdv-monitoring.rules \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
do_install:append() {
# udev rules for vrpmdv-monitoring
install -d ${D}${sysconfdir}/udev/rules.d/
install -m 0644 ${WORKDIR}/75-vrpmdv-monitoring.rules ${D}${sysconfdir}/udev/rules.d/75-vrpmdv-monitoring.rules
}
FILES:${PN} += "${sysconfdir}/udev/rules.d/"
RPROVIDES:${PN} += "kernel-module-vrpmdv-monitoring"

View File

@@ -0,0 +1,26 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv-monitoring.c \
file://75-vrpmdv-monitoring.rules \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
do_install:append() {
# udev rules for vrpmdv-monitoring
install -d ${D}${sysconfdir}/udev/rules.d/
install -m 0644 ${WORKDIR}/75-vrpmdv-monitoring.rules ${D}${sysconfdir}/udev/rules.d/75-vrpmdv-monitoring.rules
}
FILES:${PN} += "${sysconfdir}/udev/rules.d/"
FILES:${PN} += "${base_libdir}/modules/"
RPROVIDES:${PN} += "vrpmdv-monitoring"

View File

@@ -0,0 +1,18 @@
SUMMARY = "VRPMDV Monitoring Controler"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv_rpmsg_tty.c \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
# RPROVIDES:${PN} += "kernel-module-vrpmdv-mon-log"
RPROVIDES:${PN} += "kernel-module-vrpmdv-rpmsg-tty"

View File

@@ -0,0 +1,8 @@
{
"files.associations": {
"rpmsg.h": "c",
"types.h": "c",
"ioctl.h": "c",
"wait.h": "c"
}
}

View File

@@ -0,0 +1 @@
SUBSYSTEM=="misc", KERNEL=="ttyMON", GROUP="tty", MODE="0666"

View File

@@ -0,0 +1,26 @@
# Makefile for VRPMDV Monitoring Controler
obj-m := vrpmdv-mon-tty.o
SRC := $(shell pwd)
all:
$(MAKE) -C $(KERNEL_SRC) M=$(SRC)
modules_install:
$(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules_install
clean:
rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c
rm -f Module.markers Module.symvers modules.order
rm -rf .tmp_versions Modules.symvers
# obj-m = vrpmdv-monitoring-controler.o
# KVERSION = $(shell uname -r)
# all:
# make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules
# clean:
# make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean

View File

@@ -0,0 +1,116 @@
include <condition_variable>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// wait until main() sends data
std::unique_lock lk(m);
cv.wait(lk, []{ return ready; });
// after the wait, we own the lock
std::cout << "Worker thread is processing data\n";
data += " after processing";
// send data back to main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
// manual unlocking is done before notifying, to avoid waking up
// the waiting thread only to block again (see notify_one for details)
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
data = "Example data";
// send data to the worker thread
{
std::lock_guard lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// wait for the worker
{
std::unique_lock lk(m);
cv.wait(lk, []{ return processed; });
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
--------------------
// waiting for timeout after 5 seconds
std::chrono::seconds timeoutPeriod = 5;
auto timePoint = std::chrono::system_clock::now() + timeoutPeriod;
std::unique_lock<std::mutex> uLock(myDataMutex);
while(!DataAreReadyForProcessing())
{
if (myCondVar.wait_until(uLock, timePoint) //<##
== std::cv_status::timeout)
{
// data conditions where not fulfilled within
// the time period; e.g. do some error handling
break;
}
}
--
if (myCondVar.wait_for(uLock, timeoutPeriod,
DataAreReadyForProcessing))
{
// data conditions where fulfilled
// regular processing
}
else // timeout occured, conditions are not fulfilled
{
// e.g. do some error handling
}
-------------
static int rpmsg_sample_probe(struct rpmsg_device *rpdev)
{
int ret;
struct instance_data *idata;
dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
rpdev->src, rpdev->dst);
idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
if (!idata)
return -ENOMEM;
dev_set_drvdata(&rpdev->dev, idata);
/* send a message to our remote processor */
ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
if (ret) {
dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
return 0;

View File

@@ -0,0 +1,795 @@
/* Copyright 2024 Markus Lehr
*
*
* This Software is owned by Markus Lehr.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// ##################################################################################################
/*
* This is a Linux kernel module/driver called "vrpmdv-monitoring-cmd " which holds the family ID
* and functions to handle the monitoring in a Generic Netlink in the kernel.
* "It registers a Netlink family called "vrpmdv-monitoring_cmd".
*
*
* You can find some more interesting documentation about Generic Netlink here:
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
*/
#include <linux/kernel.h>
#include <linux/rpmsg.h>
// basic definitions for kernel module development
#include <linux/module.h>
// definitions for generic netlink families, policies etc;
// transitive dependencies for basic netlink, sockets etc
#include <net/genetlink.h>
// required for locking inside the .dumpit callback demonstration
#include <linux/mutex.h>
#include <linux/wait.h>
// data/vars/enums/properties that describes our protocol that we implement
// on top of generic netlink (like functions we want to trigger on the receiving side)
//#include "vrpmdv-monitoring-cmd.h"
/* ######################## CONVENIENT LOGGING MACROS ######################## */
// (Re)definition of some convenient logging macros from <linux/printk.h>. You can see the logging
// messages when printing the kernel log, e.g. with `$ sudo dmesg`.
// See https://elixir.bootlin.com/linux/latest/source/include/linux/printk.h
// with this redefinition we can easily prefix all log messages from pr_* logging macros
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* ########################################################################### */
#define MSG "hello Monitoring!"
static bool retok = true;
static int count = 100;
module_param(count, int, 0644);
struct instance_data {
int rx_count;
};
// std::chrono::seconds timeoutPeriod = 5;
// auto timePoint = std::chrono::system_clock::now() + timeoutPeriod;
/** ----- NETLINK Driver defintion ------------------*/
/**
* Generic Netlink will create a Netlink family with this name. Kernel will asign
* a numeric ID and afterwards we can talk to the family with its ID. To get
* the ID we use Generic Netlink in the userland and pass the family name.
*
* Short for: Generic Netlink VRPMDV Monitoring gnl_foobar_mcmd
*/
#define FAMILY_NAME "gnl-vrpmdv-mcmd"
/**
* These are the attributes that we want to share in gnl_foobar_xmpl.
* You can understand an attribute as a semantic type. This is
* the payload of Netlink messages.
* GNl: Generic Netlink
*/
enum GNL_VRPMDV_XMPL_ATTRIBUTE {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_A_UNSPEC,
/** We expect a MSG to be a null-terminated C-string. */
GNL_VRPMDV_MCMD_A_MSG,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_A_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN (__GNL_VRPMDV_MCMD_A_MAX)
/**
* The number of actual usable attributes in `enum GNL_VRPMDV_MCMD_ATTRIBUTE`.
* This is `GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_ATTRIBUTE_COUNT (GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN - 1)
/**
* Enumeration of all commands (functions) that our custom protocol on top
* of generic netlink supports. This can be understood as the action that
* we want to trigger on the receiving side.
*/
enum GNL_VRPMDV_MCMD_COMMAND {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_C_UNSPEC,
// first real command is "1" (>0)
/**
* When this command is received, we expect the attribute `GNL_VRPMDV_MCMD_ATTRIBUTE::GNL_VRPMDV_MCMD_A_MSG` to
* be present in the Generic Netlink request message. The kernel reads the message from the packet and
* sent it to the copro. THe result will be return by creating a new Generic Netlink response message
* with an corresponding attribute/payload.
*
* This command/signaling mechanism is independent of the Netlink flag `NLM_F_ECHO (0x08)`. We use it as
* "echo specific data" instead of return a 1:1 copy of the package, which you could do with
* `NLM_F_ECHO (0x08)` for example.
*/
GNL_VRPMDV_MCMD_C_MSG,
/**
* Provokes a NLMSG_ERR answer to this request as described in netlink manpage
* (https://man7.org/linux/man-pages/man7/netlink.7.html).
*/
GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_C_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN (__GNL_VRPMDV_MCMD_C_MAX)
/**
* The number of actual usable commands in `enum GNL_FOOBAR_XMPL_COMMAND`.
* This is `GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_COMMAND_COUNT (GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN - 1)
#define NODATARECEIVED 0
#define DATARECEIVED 1
/**
* rpmsg wait for response from copro side.
*/
struct mutex sendMTX;
/**
* rpmsg wait for response from copro side.
*/
struct mutex receiveCV;
/**
* Wait Queue: if it is signaled we have received data from copro
*/
wait_queue_head_t receive_queue;
/**
* Waitflag: 0= no data received, 1 = data received
*/
int receive_queue_flag = NODATARECEIVED;
char* received_bytes = NULL;
int received_len = 0;
//the rpmsg device which sends the data to the copro
struct rpmsg_device* prpdev = NULL; /* handle rpmsg device */
// Documentation is on the implementation of this function.
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info);
// Documentation is on the implementation of this function.
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info);
/**
* The length of `struct genl_ops gnl_foobar_xmpl_ops[]`. Not necessarily
* the number of commands in `enum GNlFoobarXmplCommand`. It depends on your application logic.
* For example, you can use the same command multiple times and - dependent by flag -
* invoke a different callback handler. In our simple example we just use one .doit callback
* per operation/command.
*/
#define GNL_VRPMDV_OPS_LEN (GNL_VRPMDV_MCMD_COMMAND_COUNT)
/**
* Array with all operations that our protocol on top of Generic Netlink
* supports. An operation is the glue between a command ("cmd" field in `struct genlmsghdr` of
* received Generic Netlink message) and the corresponding ".doit" callback function.
* See: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L148
*/
struct genl_ops gnl_vrpmdv_mcmd_ops[GNL_VRPMDV_OPS_LEN] = {
{
/* The "cmd" field in `struct genlmsghdr` of received Generic Netlink message */
.cmd = GNL_VRPMDV_MCMD_C_MSG,
/* TODO Use case ? */
.flags = 0,
/* TODO Use case ? */
.internal_flags = 0,
/* Callback handler when a request with the specified ".cmd" above is received.
* Always validates the payload except one set NO_STRICT_VALIDATION flag in ".validate"
* See: https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*
* Quote from: https://lwn.net/Articles/208755
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*
*/
.doit = gnl_cb_vrpmdv_doit,
/* This callback is similar in use to the standard Netlink 'dumpit' callback.
* The 'dumpit' callback is invoked when a Generic Netlink message is received
* with the NLM_F_DUMP flag set.
*
* A dump can be understand as a "GET ALL DATA OF THE GIVEN ENTITY", i.e.
* the userland can receive as long as the .dumpit callback returns data.
*
* .dumpit is not mandatory, but either it or .doit must be provided, see
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L367
*
* To be honest I don't know in what use case one should use .dumpit and why
* it is useful, because you can achieve the same also with .doit handlers.
* Anyway, this is just an example/tutorial.
*
* Quote from: https://lwn.net/Articles/208755
* "The main difference between a 'dumpit' handler and a 'doit' handler is
* that a 'dumpit' handler does not allocate a message buffer for a response;
* a pre-allocated sk_buff is passed to the 'dumpit' handler as the first
* parameter. The 'dumpit' handler should fill the message buffer with the
* appropriate response message and return the size of the sk_buff,
* i.e. sk_buff->len, and the message buffer will automatically be sent to the
* Generic Netlink client that initiated the request. As long as the 'dumpit'
* handler returns a value greater than zero it will be called again with a
* newly allocated message buffer to fill, when the handler has no more data
* to send it should return zero; error conditions are indicated by returning
* a negative value. If necessary, state can be preserved in the
* netlink_callback parameter which is passed to the 'dumpit' handler; the
* netlink_callback parameter values will be preserved across handler calls
* for a single request."
*
* You can see the check for the NLM_F_DUMP-flag here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L780
*/
.dumpit = NULL, //gnl_cb_echo_dumpit,
/* Start callback for dumps. Can be used to lock data structures. */
.start = NULL, //gnl_cb_echo_dumpit_before,
/* Completion callback for dumps. Can be used for cleanup after a dump and releasing locks. */
.done = NULL, //gnl_cb_echo_dumpit_before_after,
/*
0 (= "validate strictly") or value `enum genl_validate_flags`
* see: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L108
*/
.validate = 0,
},
{
.cmd = GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
.flags = 0,
.internal_flags = 0,
.doit = gnl_cb_doit_reply_with_nlmsg_err,
// .dumpit is not required, only optional; application specific/dependent on your use case
// in a real application you probably have different .dumpit handlers per operation/command
.dumpit = NULL,
// in a real application you probably have different .start handlers per operation/command
.start = NULL,
// in a real application you probably have different .done handlers per operation/command
.done = NULL,
.validate = 0,
}
};
/**
* Attribute policy: defines which attribute has which type (e.g int, char * etc).
* This get validated for each received Generic Netlink message, if not deactivated
* in `gnl_foobar_xmpl_ops[].validate`.
* See https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*/
static struct nla_policy gnl_vrpmdv_mcmd_policy[GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN] = {
// In case you are seeing this syntax for the first time (I also learned this just after a few years of
// coding with C myself): The following static array initiations are equivalent:
// `int a[2] = {1, 2}` <==> `int a[2] = {[0] => 1, [1] => 2}`.
[GNL_VRPMDV_MCMD_A_UNSPEC] = {.type = NLA_UNSPEC},
// You can set this to NLA_U32 for testing and send an ECHO message from the userland
// It will fail in this case and you see a entry in the kernel log.
// `enum GNL_FOOBAR_XMPL_ATTRIBUTE::GNL_FOOBAR_XMPL_A_MSG` is a null-terminated C-String
[GNL_VRPMDV_MCMD_A_MSG] = {.type = NLA_NUL_STRING},
};
/**
* Definition of the Netlink family we want to register using Generic Netlink functionality
*/
static struct genl_family gnl_vrpmdv_mcmd_family = {
// automatically assign an id
.id = 0,
// we don't use custom additional header info / user specific header
.hdrsize = 0,
// The name of this family, used by userspace application to get the numeric ID
.name = FAMILY_NAME,
// family specific version number; can be used to evolve application over time (multiple versions)
.version = 1,
// delegates all incoming requests to callback functions
.ops = gnl_vrpmdv_mcmd_ops,
// length of array `gnl_foobar_xmpl_ops`
.n_ops = GNL_VRPMDV_OPS_LEN,
// attribute policy (for validation of messages). Enforced automatically, except ".validate" in
// corresponding ".ops"-field is set accordingly.
.policy = gnl_vrpmdv_mcmd_policy,
// Number of attributes / bounds check for policy (array length)
.maxattr = GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN,
// Owning Kernel module of the Netlink family we register.
.module = THIS_MODULE,
// Actually not necessary because this memory region would be zeroed anyway during module load,
// but this way one sees all possible options.
// if your application must handle multiple netlink calls in parallel (where one should not block the next
// from starting), set this to true! otherwise all netlink calls are mutually exclusive
.parallel_ops = 0,
// set to true if the family can handle network namespaces and should be presented in all of them
.netnsok = 0,
// called before an operation's doit callback, it may do additional, common, filtering and return an error
.pre_doit = NULL,
// called after an operation's doit callback, it may undo operations done by pre_doit, for example release locks
.post_doit = NULL,
};
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_VRPMDV_MCMD_C_MSG` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_vrpmdv_mcmd_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info) {
struct nlattr *na;
struct sk_buff *reply_skb;
int rc;
void *msg_head;
char *recv_msg;
pr_info("%s() invoked\n", __func__);
if (info == NULL) {
// should never happen
pr_err("An error occurred in %s():\n", __func__);
return -EINVAL;
}
/*
* For each attribute there is an index in info->attrs which points to a nlattr structure
* in this structure the data is stored.
*/
na = info->attrs[GNL_VRPMDV_MCMD_A_MSG];
if (!na) {
pr_err("no info->attrs[%i]\n", GNL_VRPMDV_MCMD_A_MSG);
return -EINVAL; // we return here because we expect to recv a msg
}
recv_msg = (char *) nla_data(na);
if (recv_msg == NULL) {
pr_err("error while receiving data\n");
} else {
pr_info("received: '%s'\n", recv_msg);
}
//aquire lock for cmd repmsg channel
// std::lock sendlk(cmd_cb_progress_data.sendmtx);
//send the message to the copro over RPMSG
if (prpdev) {
rc = rpmsg_send(prpdev->ept, recv_msg, strlen(recv_msg));
if (rc) {
pr_err("rpmsg_send failed: %d\n", rc);
return rc;
}
// struct rpmsg_vrpmdv_mon_t *drv = dev_get_drvdata(&rpdev->dev);
// Send a message back after we receive the reply from rpmsg channel
// ---------------------
// Allocate some memory, since the size is not yet known use NLMSG_GOODSIZE
reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (reply_skb == NULL) {
pr_err("An error occurred in %s():\n", __func__);
return -ENOMEM;
}
// Create the message headersprpdev
// Add header to netlink message;
// afterwards the buffer looks like this:
// ----------------------------------
// | netlink header |
// | generic netlink header |
// | <space for netlink attributes> |
// ----------------------------------
// msg_head = genlmsg_put(reply_skb, // buffer for netlink message: struct sk_buff *
// // According to my findings: this is not used for routing
// // This can be used in an application specific way to target
// // different endpoints within the same user application
// // but general rule: just put sender port id here
// info->snd_portid, // sending port (not process) id: int
// info->snd_seq + 1, // sequence number: int (might be used by receiver, but not mandatory)
// &gnl_vrpmdv_mcmd_family, // struct genl_family *
// 0, // flags for Netlink header: int; application specific and not mandatory
// // The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
// GNL_VRPMDV_MCMD_C_MSG
// );
msg_head = genlmsg_put_reply(reply_skb, // buffer for netlink message: struct sk_buff *
info, // info
&gnl_vrpmdv_mcmd_family, // struct genl_family *
0, // flags for Netlink header: int; application specific and not mandatory
// The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
info->genlhdr->cmd
);
if (msg_head == NULL) {
rc = ENOMEM;
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
{
pr_info("wait for response\n");
// wait until receive_queue_flag=1 , that means we have received data from Copro
wait_event_interruptible(receive_queue, receive_queue_flag != 0 );
//Copy data
receive_queue_flag = NODATARECEIVED;
// std::unique_lock lk(cmd_cb_progress_data.receivemtx);
// if (myCondVar.wait_until(uLock, timePoint) == std::cv_status::timeout)
// {
// dev_err(&cmd_cb_progress_data.rpdev, "rpmsg_send failed, timeout: \n");
// return -1:
// }
//pr_info("get response: '%s'\n", recv_msg);
if (received_len > 0) {
pr_info("received data from copro %s\n", received_bytes);
}
else {
pr_err("don't received data from Copro \n");
}
}
// Add a GNL_VRPMDV_MCMD_A_MSG attribute (actual value/payload to be sent)
// echo the value we just received
rc = nla_put_string(reply_skb, GNL_VRPMDV_MCMD_A_MSG, received_bytes);
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
//free the buffer
kfree(received_bytes);
received_bytes = NULL;
return -rc;
}
// Finalize the message:
// Corrects the netlink message header (length) to include the appended
// attributes. Only necessary if attributes have been added to the message.
genlmsg_end(reply_skb, msg_head);
// Send the message back
rc = genlmsg_reply(reply_skb, info);
// same as genlmsg_unicast(genl_info_net(info), reply_skb, info->snd_portid)
// see https://elixir.bootlin.com/linux/v5.8.9/source/include/net/genetlink.h#L326
kfree(received_bytes);
received_bytes = NULL;
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
return 0;
}
pr_info("Device not set in Probe. Should not happen");
return -1;
}
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info) {
pr_info("%s() invoked, a NLMSG_ERR response will be sent back\n", __func__);
/*
* Generic Netlink is smart enough and sends a NLMSG_ERR reply automatically as reply
* Quote from https://lwn.net/Articles/208755/:
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*/
return -EINVAL;
}
/** ----- NETLINK Driver defintion ------------------*/
/**
* callback that is called after the copro send data
* we have to copy it in a buffer for the netlink and later send it back to the userland
*
*/
static int vrpmdv_monitoring_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret = 0;
// struct instance_data *idata = dev_get_drvdata(&rpdev->dev);
// dev_info(&rpdev->dev, "incoming msg %d (src: 0x%x)\n",
// ++idata->rx_count, src);
print_hex_dump_debug(__func__, DUMP_PREFIX_NONE, 16, 1, data, len,
true);
if (len == 0) {
pr_err("(%s) Empty lenght requested\n", __func__);
return -EINVAL;
}
else {
pr_info("received logging: %s\n", (char*) data);
}
ret = rpmsg_send(rpdev->ept, retok, sizeof(retok));
// received_bytes = (char *)kmalloc(len+1, GFP_KERNEL);
// memcpy(received_bytes, data, len);
// rpmsg_RxBuf[len] = 0;
// received_bytes = (char *)kmalloc(len, GFP_KERNEL);
// memcpy(received_bytes, data, len);
// received_len = len;
// receive_queue_flag= DATARECEIVED;
// wake_up_interruptible(&receive_queue);
// /* samples should not live forever */
// if (idata->rx_count >= count) {
// dev_info(&rpdev->dev, "goodbye!\n");
// return 0;
// }
/* send a new message now */
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
// if (ret)
// dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
static int vrpmdv_mon_log_probe(struct rpmsg_device *rpdev)
{
//int rc;
// int ret;
// struct instance_data *idata;
// dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
// rpdev->src, rpdev->dst);
pr_info("RPMSG mon logger device driver started.\n");
// idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
// if (!idata)
// return -ENOMEM;
// dev_set_drvdata(&rpdev->dev, idata);
// /* send a message to our remote processor to */
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
// if (ret) {
// dev_err(&rpdev->dev, "vrpmdv_monitoring_controler_send failed: %d\n", ret);
// return ret;
// }
// return 0;
// struct device *dev;
// dev = &rpdev->dev;
// struct rpmsg_vrpmdv_mon_t *rpmsg_vrpmdv_mon;
// rpmsg_vrpmdv_mon = devm_kzalloc(dev, sizeof(*rpmsg_vrpmdv_mon), GFP_KERNEL);
// if (!rpmsg_vrpmdv_mon)
// return -ENOMEM;
// mutex_init(&sendMTX);
// init_waitqueue_head (&receive_queue);
prpdev = rpdev;
// dev_set_drvdata(&rpdev->dev, rpmsg_vrpmdv_mon);
pr_info("RPMSG Logger Device set.\n");
/** NEU **/
// if (cmd_cb_progress_data.rpdev == NULL) {
// cmd_cb_progress_data.rpdev = rpdev;
// pr_info("RPMSG CMD Device set.\n");
// }
// else {
// pr_info("Error: RPMSG CMD Device already set. Don't set it twice\n");
// }
//pr_info("Generic Netlink VRPMDV-Mon-Log Module started.\n");
// Register family with its operations and policies
// rc = genl_register_family(&gnl_vrpmdv_mcmd_family);
// if (rc != 0) {
// pr_err("FAILED: genl_register_family(): %i\n", rc);
// pr_err("An error occurred while inserting the generic netlink example module\n");
// return -1;
// } else {
// pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
return 0;
}
static void vrpmdv_mon_log_remove(struct rpmsg_device *rpdev)
{
//int ret;
pr_info("Mon Logger unloaded.\n");
// Unregister the family
// ret = genl_unregister_family(&gnl_vrpmdv_mcmd_family);
// if (ret != 0) {
// pr_err("genl_unregister_family() failed: %i\n", ret);
// return;
// } else {
// pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_destroy(&sendMTX);
// wake_up_interruptible(&receive_queue);
pr_info("vrpmdv-mon logger driver is removed\n");
// dev_info(&rpdev->dev, "vrpmdv-monitoring controler driver is removed\n");
}
static struct rpmsg_device_id vrpmdv_mon_log_id_table[] = {
{ .name = "vrpmdv-mon-log" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, vrpmdv_mon_log_id_table);
static struct rpmsg_driver vrpmdv_mon_log = {
.drv.name = KBUILD_MODNAME,
.id_table = vrpmdv_mon_log_id_table,
.probe = vrpmdv_mon_log_probe,
.callback = vrpmdv_mon_log_cb,
.remove = vrpmdv_mon_log_remove,
};
module_rpmsg_driver(vrpmdv_mon_log);
// static struct rpmsg_driver vrpmdv_monitoring_data = {
// .drv.name = KBUILD_MODNAME,
// .id_table = vrpmdv_monitoring_controler_id_table,
// .probe = vrpmdv_monitoring_probe,
// .callback = vrpmdv_monitoring_cb,
// .remove = vrpmdv_monitoring_remove,
// };
// module_rpmsg_driver(vrpmdv_monitoring_data);
MODULE_DESCRIPTION("Remote processor messaging vrpmdv monitoring controler");
MODULE_LICENSE("GPL v2");
// /**
// * Module/driver initializer. Called on module load/insertion.
// *
// * @return success (0) or error code.
// */
// static int __init gnl_foobar_xmpl_module_init(void) {
// int rc;
// pr_info("Generic Netlink Example Module inserted.\n");
// // Register family with its operations and policies
// rc = genl_register_family(&gnl_foobar_xmpl_family);
// if (rc != 0) {
// pr_err("FAILED: genl_register_family(): %i\n", rc);
// pr_err("An error occurred while inserting the generic netlink example module\n");
// return -1;
// } else {
// pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_init(&dumpit_cb_progress_data.mtx);
// return 0;
// }
// /**
// * Module/driver uninitializer. Called on module unload/removal.
// *
// * @return success (0) or error code.
// */
// static void __exit gnl_foobar_xmpl_module_exit(void) {
// int ret;
// pr_info("Generic Netlink Example Module unloaded.\n");
// // Unregister the family
// ret = genl_unregister_family(&gnl_foobar_xmpl_family);
// if (ret != 0) {
// pr_err("genl_unregister_family() failed: %i\n", ret);
// return;
// } else {
// pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_destroy(&dumpit_cb_progress_data.mtx);
// }
// ----
// static int __init rpmsg_sdb_drv_init(void)
// {
// int ret = 0;
// /* Register rpmsg device */
// ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// if (ret) {
// pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
// return ret;
// }
// pr_info("rpmsg_sdb: Init done\n");
// return ret;
// }
// static void __exit rpmsg_sdb_drv_exit(void)
// {
// unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// pr_info("rpmsg_sdb: Exit\n");
// }
// module_init(rpmsg_sdb_drv_init);
// module_exit(rpmsg_sdb_drv_exit);

View File

@@ -0,0 +1,308 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 STMicroelectronics - All Rights Reserved
*
* The rpmsg tty driver implements serial communication on the RPMsg bus to makes
* possible for user-space programs to send and receive rpmsg messages as a standard
* tty protocol.
*
* The remote processor can instantiate a new tty by requesting a "rpmsg-tty" RPMsg service.
* The "rpmsg-tty" service is directly used for data exchange. No flow control is implemented yet.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#define RPMSG_TTY_NAME "ttyMON"
#define MAX_TTY_RPMSG 32
static DEFINE_IDR(tty_idr); /* tty instance id */
static DEFINE_MUTEX(idr_lock); /* protects tty_idr */
static struct tty_driver *rpmsg_tty_driver;
struct rpmsg_tty_port {
struct tty_port port; /* TTY port data */
int id; /* TTY rpmsg index */
struct rpmsg_device *rpdev; /* rpmsg device */
};
static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src)
{
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
int copied;
pr_info("%s() invoked\n", __func__);
if (!len)
return -EINVAL;
copied = tty_insert_flip_string(&cport->port, data, len);
pr_info("%s() received\n", data);
if (copied != len)
dev_err_ratelimited(&rpdev->dev, "Trunc buffer: available space is %d\n", copied);
pr_info("%s() error cop\n", __func__);
tty_flip_buffer_push(&cport->port);
pr_info("%s() invoked. succes!!\n", __func__);
return 0;
}
static int rpmsg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
{
pr_info("%s() invoked\n", __func__);
struct rpmsg_tty_port *cport = idr_find(&tty_idr, tty->index);
struct tty_port *port;
tty->driver_data = cport;
port = tty_port_get(&cport->port);
return tty_port_install(port, driver, tty);
}
static void rpmsg_tty_cleanup(struct tty_struct *tty)
{
pr_info("%s() invoked\n", __func__);
tty_port_put(tty->port);
}
static int rpmsg_tty_open(struct tty_struct *tty, struct file *filp)
{
pr_info("%s() invoked\n", __func__);
return tty_port_open(tty->port, tty, filp);
}
static void rpmsg_tty_close(struct tty_struct *tty, struct file *filp)
{
pr_info("%s() invoked\n", __func__);
return tty_port_close(tty->port, tty, filp);
}
static int rpmsg_tty_write(struct tty_struct *tty, const u8 *buf, int len)
{
pr_info("%s() invoked\n", __func__);
struct rpmsg_tty_port *cport = tty->driver_data;
struct rpmsg_device *rpdev;
int msg_max_size, msg_size;
int ret;
rpdev = cport->rpdev;
msg_max_size = rpmsg_get_mtu(rpdev->ept);
if (msg_max_size < 0)
return msg_max_size;
msg_size = min(len, msg_max_size);
/*
* Use rpmsg_trysend instead of rpmsg_send to send the message so the caller is not
* hung until a rpmsg buffer is available. In such case rpmsg_trysend returns -ENOMEM.
*/
ret = rpmsg_trysend(rpdev->ept, (void *)buf, msg_size);
if (ret) {
dev_dbg_ratelimited(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
pr_info("%s() invoked. send succes!!\n", __func__);
return msg_size;
}
static unsigned int rpmsg_tty_write_room(struct tty_struct *tty)
{
pr_info("%s() invoked\n", __func__);
struct rpmsg_tty_port *cport = tty->driver_data;
int size;
size = rpmsg_get_mtu(cport->rpdev->ept);
if (size < 0)
return 0;
pr_info("%s() invoked. write success\n", __func__);
return size;
}
static void rpmsg_tty_hangup(struct tty_struct *tty)
{
pr_info("%s() invoked\n", __func__);
tty_port_hangup(tty->port);
}
static const struct tty_operations rpmsg_tty_ops = {
.install = rpmsg_tty_install,
.open = rpmsg_tty_open,
.close = rpmsg_tty_close,
.write = rpmsg_tty_write,
.write_room = rpmsg_tty_write_room,
.hangup = rpmsg_tty_hangup,
.cleanup = rpmsg_tty_cleanup,
};
static struct rpmsg_tty_port *rpmsg_tty_alloc_cport(void)
{
pr_info("%s() invoked\n", __func__);
struct rpmsg_tty_port *cport;
int ret;
cport = kzalloc(sizeof(*cport), GFP_KERNEL);
if (!cport)
return ERR_PTR(-ENOMEM);
mutex_lock(&idr_lock);
ret = idr_alloc(&tty_idr, cport, 0, MAX_TTY_RPMSG, GFP_KERNEL);
mutex_unlock(&idr_lock);
if (ret < 0) {
kfree(cport);
return ERR_PTR(ret);
}
cport->id = ret;
return cport;
}
static void rpmsg_tty_destruct_port(struct tty_port *port)
{
pr_info("%s() invoked\n", __func__);
struct rpmsg_tty_port *cport = container_of(port, struct rpmsg_tty_port, port);
mutex_lock(&idr_lock);
idr_remove(&tty_idr, cport->id);
mutex_unlock(&idr_lock);
kfree(cport);
}
static const struct tty_port_operations rpmsg_tty_port_ops = {
.destruct = rpmsg_tty_destruct_port,
};
static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
{
pr_info("%s() invoked\n", __func__);
struct rpmsg_tty_port *cport;
struct device *dev = &rpdev->dev;
struct device *tty_dev;
int ret;
cport = rpmsg_tty_alloc_cport();
if (IS_ERR(cport))
return dev_err_probe(dev, PTR_ERR(cport), "Failed to alloc tty mon port\n");
tty_port_init(&cport->port);
cport->port.ops = &rpmsg_tty_port_ops;
tty_dev = tty_port_register_device(&cport->port, rpmsg_tty_driver,
cport->id, dev);
if (IS_ERR(tty_dev)) {
ret = dev_err_probe(dev, PTR_ERR(tty_dev), "Failed to register tty mon port\n");
tty_port_put(&cport->port);
return ret;
}
cport->rpdev = rpdev;
dev_set_drvdata(dev, cport);
dev_dbg(dev, "New channel: 0x%x -> 0x%x: " RPMSG_TTY_NAME "%d\n",
rpdev->src, rpdev->dst, cport->id);
return 0;
}
static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
{
pr_info("%s() invoked\n", __func__);
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
dev_dbg(&rpdev->dev, "Removing mon tty device %d\n", cport->id);
/* User hang up to release the tty */
tty_port_tty_hangup(&cport->port, false);
tty_unregister_device(rpmsg_tty_driver, cport->id);
tty_port_put(&cport->port);
}
static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = {
{ .name = "vrpmdv-mon-tty" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_tty_id_table);
static struct rpmsg_driver rpmsg_tty_rpmsg_drv = {
.drv.name = KBUILD_MODNAME,
.id_table = rpmsg_driver_tty_id_table,
.probe = rpmsg_tty_probe,
.callback = rpmsg_tty_cb,
.remove = rpmsg_tty_remove,
};
static int __init rpmsg_tty_init(void)
{
pr_info("%s() invoked\n", __func__);
int ret;
rpmsg_tty_driver = tty_alloc_driver(MAX_TTY_RPMSG, TTY_DRIVER_REAL_RAW |
TTY_DRIVER_DYNAMIC_DEV);
if (IS_ERR(rpmsg_tty_driver))
return PTR_ERR(rpmsg_tty_driver);
rpmsg_tty_driver->driver_name = "vrpmdv-mon-tty";
rpmsg_tty_driver->name = RPMSG_TTY_NAME;
rpmsg_tty_driver->major = 0;
rpmsg_tty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
/* Disable unused mode by default */
rpmsg_tty_driver->init_termios = tty_std_termios;
rpmsg_tty_driver->init_termios.c_lflag &= ~(ECHO | ICANON);
rpmsg_tty_driver->init_termios.c_oflag &= ~(OPOST | ONLCR);
tty_set_operations(rpmsg_tty_driver, &rpmsg_tty_ops);
ret = tty_register_driver(rpmsg_tty_driver);
if (ret < 0) {
pr_err("Couldn't install mon tty driver: %d\n", ret);
goto error_put;
}
ret = register_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
if (ret < 0) {
pr_err("Couldn't register mon tty driver: %d\n", ret);
goto error_unregister;
}
pr_info("%s() invoked. Install complete!\n", __func__);
return 0;
error_unregister:
pr_info("%s() invoked unregister.\n", __func__);
tty_unregister_driver(rpmsg_tty_driver);
error_put:
pr_info("%s() invoked. error put\n", __func__);
tty_driver_kref_put(rpmsg_tty_driver);
return ret;
}
static void __exit rpmsg_tty_exit(void)
{
unregister_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
tty_unregister_driver(rpmsg_tty_driver);
tty_driver_kref_put(rpmsg_tty_driver);
idr_destroy(&tty_idr);
}
module_init(rpmsg_tty_init);
module_exit(rpmsg_tty_exit);
MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>");
MODULE_DESCRIPTION("remote processor messaging tty driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,287 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 STMicroelectronics - All Rights Reserved
*
* The rpmsg tty driver implements serial communication on the RPMsg bus to makes
* possible for user-space programs to send and receive rpmsg messages as a standard
* tty protocol.
*
* The remote processor can instantiate a new tty by requesting a "rpmsg-tty" RPMsg service.
* The "rpmsg-tty" service is directly used for data exchange. No flow control is implemented yet.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#define RPMSG_TTY_NAME "ttyMON"
#define MAX_TTY_RPMSG 32
static DEFINE_IDR(tty_idr); /* tty instance id */
static DEFINE_MUTEX(idr_lock); /* protects tty_idr */
static struct tty_driver *rpmsg_tty_driver;
struct rpmsg_tty_port {
struct tty_port port; /* TTY port data */
int id; /* TTY rpmsg index */
struct rpmsg_device *rpdev; /* rpmsg device */
};
static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src)
{
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
int copied;
if (!len)
return -EINVAL;
copied = tty_insert_flip_string(&cport->port, data, len);
if (copied != len)
dev_err_ratelimited(&rpdev->dev, "Trunc buffer: available space is %d\n", copied);
tty_flip_buffer_push(&cport->port);
return 0;
}
static int rpmsg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
{
struct rpmsg_tty_port *cport = idr_find(&tty_idr, tty->index);
struct tty_port *port;
tty->driver_data = cport;
port = tty_port_get(&cport->port);
return tty_port_install(port, driver, tty);
}
static void rpmsg_tty_cleanup(struct tty_struct *tty)
{
tty_port_put(tty->port);
}
static int rpmsg_tty_open(struct tty_struct *tty, struct file *filp)
{
return tty_port_open(tty->port, tty, filp);
}
static void rpmsg_tty_close(struct tty_struct *tty, struct file *filp)
{
return tty_port_close(tty->port, tty, filp);
}
static int rpmsg_tty_write(struct tty_struct *tty, const u8 *buf, int len)
{
struct rpmsg_tty_port *cport = tty->driver_data;
struct rpmsg_device *rpdev;
int msg_max_size, msg_size;
int ret;
rpdev = cport->rpdev;
msg_max_size = rpmsg_get_mtu(rpdev->ept);
if (msg_max_size < 0)
return msg_max_size;
msg_size = min(len, msg_max_size);
/*
* Use rpmsg_trysend instead of rpmsg_send to send the message so the caller is not
* hung until a rpmsg buffer is available. In such case rpmsg_trysend returns -ENOMEM.
*/
ret = rpmsg_trysend(rpdev->ept, (void *)buf, msg_size);
if (ret) {
dev_dbg_ratelimited(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
return msg_size;
}
static unsigned int rpmsg_tty_write_room(struct tty_struct *tty)
{
struct rpmsg_tty_port *cport = tty->driver_data;
int size;
size = rpmsg_get_mtu(cport->rpdev->ept);
if (size < 0)
return 0;
return size;
}
static void rpmsg_tty_hangup(struct tty_struct *tty)
{
tty_port_hangup(tty->port);
}
static const struct tty_operations rpmsg_tty_ops = {
.install = rpmsg_tty_install,
.open = rpmsg_tty_open,
.close = rpmsg_tty_close,
.write = rpmsg_tty_write,
.write_room = rpmsg_tty_write_room,
.hangup = rpmsg_tty_hangup,
.cleanup = rpmsg_tty_cleanup,
};
static struct rpmsg_tty_port *rpmsg_tty_alloc_cport(void)
{
struct rpmsg_tty_port *cport;
int ret;
cport = kzalloc(sizeof(*cport), GFP_KERNEL);
if (!cport)
return ERR_PTR(-ENOMEM);
mutex_lock(&idr_lock);
ret = idr_alloc(&tty_idr, cport, 0, MAX_TTY_RPMSG, GFP_KERNEL);
mutex_unlock(&idr_lock);
if (ret < 0) {
kfree(cport);
return ERR_PTR(ret);
}
cport->id = ret;
return cport;
}
static void rpmsg_tty_destruct_port(struct tty_port *port)
{
struct rpmsg_tty_port *cport = container_of(port, struct rpmsg_tty_port, port);
mutex_lock(&idr_lock);
idr_remove(&tty_idr, cport->id);
mutex_unlock(&idr_lock);
kfree(cport);
}
static const struct tty_port_operations rpmsg_tty_port_ops = {
.destruct = rpmsg_tty_destruct_port,
};
static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
{
struct rpmsg_tty_port *cport;
struct device *dev = &rpdev->dev;
struct device *tty_dev;
int ret;
cport = rpmsg_tty_alloc_cport();
if (IS_ERR(cport))
return dev_err_probe(dev, PTR_ERR(cport), "Failed to alloc tty mon port\n");
tty_port_init(&cport->port);
cport->port.ops = &rpmsg_tty_port_ops;
tty_dev = tty_port_register_device(&cport->port, rpmsg_tty_driver,
cport->id, dev);
if (IS_ERR(tty_dev)) {
ret = dev_err_probe(dev, PTR_ERR(tty_dev), "Failed to register tty mon port\n");
tty_port_put(&cport->port);
return ret;
}
cport->rpdev = rpdev;
dev_set_drvdata(dev, cport);
dev_dbg(dev, "New channel: 0x%x -> 0x%x: " RPMSG_TTY_NAME "%d\n",
rpdev->src, rpdev->dst, cport->id);
return 0;
}
static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
{
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
dev_dbg(&rpdev->dev, "Removing mon tty device %d\n", cport->id);
/* User hang up to release the tty */
tty_port_tty_hangup(&cport->port, false);
tty_unregister_device(rpmsg_tty_driver, cport->id);
tty_port_put(&cport->port);
}
static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = {
{ .name = "vrpmdv-mon-tty" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_tty_id_table);
static struct rpmsg_driver rpmsg_tty_rpmsg_drv = {
.drv.name = KBUILD_MODNAME,
.id_table = rpmsg_driver_tty_id_table,
.probe = rpmsg_tty_probe,
.callback = rpmsg_tty_cb,
.remove = rpmsg_tty_remove,
};
static int __init rpmsg_tty_init(void)
{
int ret;
rpmsg_tty_driver = tty_alloc_driver(MAX_TTY_RPMSG, TTY_DRIVER_REAL_RAW |
TTY_DRIVER_DYNAMIC_DEV);
if (IS_ERR(rpmsg_tty_driver))
return PTR_ERR(rpmsg_tty_driver);
rpmsg_tty_driver->driver_name = "vrpmdv-mon-tty";
rpmsg_tty_driver->name = RPMSG_TTY_NAME;
rpmsg_tty_driver->major = 0;
rpmsg_tty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
/* Disable unused mode by default */
rpmsg_tty_driver->init_termios = tty_std_termios;
rpmsg_tty_driver->init_termios.c_lflag &= ~(ECHO | ICANON);
rpmsg_tty_driver->init_termios.c_oflag &= ~(OPOST | ONLCR);
tty_set_operations(rpmsg_tty_driver, &rpmsg_tty_ops);
ret = tty_register_driver(rpmsg_tty_driver);
if (ret < 0) {
pr_err("Couldn't install mon tty driver: %d\n", ret);
goto error_put;
}
ret = register_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
if (ret < 0) {
pr_err("Couldn't register mon tty driver: %d\n", ret);
goto error_unregister;
}
return 0;
error_unregister:
tty_unregister_driver(rpmsg_tty_driver);
error_put:
tty_driver_kref_put(rpmsg_tty_driver);
return ret;
}
static void __exit rpmsg_tty_exit(void)
{
unregister_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
tty_unregister_driver(rpmsg_tty_driver);
tty_driver_kref_put(rpmsg_tty_driver);
idr_destroy(&tty_idr);
}
module_init(rpmsg_tty_init);
module_exit(rpmsg_tty_exit);
MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>");
MODULE_DESCRIPTION("remote processor messaging tty driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,16 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv-monitoring.c \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
RPROVIDES:${PN} += "kernel-module-vrpmdv-monitoring"

View File

@@ -0,0 +1,541 @@
/* Copyright 2021 Philipp Schuster
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in the
* Software without restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies
* or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// ##################################################################################################
/*
* This is a Linux kernel module/driver called "gnl_foobar_xmpl" which shows you the basics of using
* Generic Netlink in the kernel. It registers a Netlink family called "gnl_foobar_xmpl". See
* "gnl_foobar_xmpl_prop.h" for common properties of the family. "Generic Netlink" offers us a lot of
* convenient functions to register new/custom Netlink families on the fly during runtime. We use this
* functionality here. It implements simple IPC between Userland and Kernel (Kernel responds to userland).
*
* Check "gnl_foobar_xmpl_prop.h" for common properties of the family first, afterwards follow the code here.
*
* You can find some more interesting documentation about Generic Netlink here:
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
*/
// basic definitions for kernel module development
#include <linux/module.h>
// definitions for generic netlink families, policies etc;
// transitive dependencies for basic netlink, sockets etc
#include <net/genetlink.h>
// required for locking inside the .dumpit callback demonstration
#include <linux/mutex.h>
// data/vars/enums/properties that describes our protocol that we implement
// on top of generic netlink (like functions we want to trigger on the receiving side)
#include "gnl_foobar_xmpl_prop.h"
// Module/Driver description.
// You can see this for example when executing `$ modinfo ./gnl_foobar_xmpl.ko` (after build).
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Philipp Schuster <phip1611@gmail.com>");
MODULE_DESCRIPTION(
"Linux driver that registers the custom Netlink family "
"\"" FAMILY_NAME "\" via Generic Netlink and responds to echo messages "
"according to the properties specified in \"gnl_foobar_xmpl_prop.h\"."
);
/* ######################## CONVENIENT LOGGING MACROS ######################## */
// (Re)definition of some convenient logging macros from <linux/printk.h>. You can see the logging
// messages when printing the kernel log, e.g. with `$ sudo dmesg`.
// See https://elixir.bootlin.com/linux/latest/source/include/linux/printk.h
// with this redefinition we can easily prefix all log messages from pr_* logging macros
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* ########################################################################### */
/**
* Data structure required for our .dumpit callback handler to
* know about the progress of an ongoing dump.
* See the dumpit callback handler how it is used.
*/
struct {
// from <linux/mutex.h>
/**
* Only one process is allowed per dump process. We need a lock for that.
*/
struct mutex mtx;
/**
* Number that describes how many packets we need to send until we are done
* during an ongoing dumpit process. 0 = done.
*/
int unsigned runs_to_go;
/**
* Number that describes how many packets per dump are sent in total.
* Constant per dump.
*/
int unsigned total_runs;
} dumpit_cb_progress_data;
// Documentation is on the implementation of this function.
int gnl_cb_echo_doit(struct sk_buff *sender_skb, struct genl_info *info);
// Documentation is on the implementation of this function.
int gnl_cb_echo_dumpit(struct sk_buff *pre_allocated_skb, struct netlink_callback *cb);
// Documentation is on the implementation of this function.
int gnl_cb_echo_dumpit_before(struct netlink_callback *cb);
// Documentation is on the implementation of this function.
int gnl_cb_echo_dumpit_before_after(struct netlink_callback *cb);
// Documentation is on the implementation of this function.
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info);
/**
* The length of `struct genl_ops gnl_foobar_xmpl_ops[]`. Not necessarily
* the number of commands in `enum GNlFoobarXmplCommand`. It depends on your application logic.
* For example, you can use the same command multiple times and - dependent by flag -
* invoke a different callback handler. In our simple example we just use one .doit callback
* per operation/command.
*/
#define GNL_FOOBAR_OPS_LEN (GNL_FOOBAR_XMPL_COMMAND_COUNT)
/**
* Array with all operations that our protocol on top of Generic Netlink
* supports. An operation is the glue between a command ("cmd" field in `struct genlmsghdr` of
* received Generic Netlink message) and the corresponding ".doit" callback function.
* See: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L148
*/
struct genl_ops gnl_foobar_xmpl_ops[GNL_FOOBAR_OPS_LEN] = {
{
/* The "cmd" field in `struct genlmsghdr` of received Generic Netlink message */
.cmd = GNL_FOOBAR_XMPL_C_ECHO_MSG,
/* TODO Use case ? */
.flags = 0,
/* TODO Use case ? */
.internal_flags = 0,
/* Callback handler when a request with the specified ".cmd" above is received.
* Always validates the payload except one set NO_STRICT_VALIDATION flag in ".validate"
* See: https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*
* Quote from: https://lwn.net/Articles/208755
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*
*/
.doit = gnl_cb_echo_doit,
/* This callback is similar in use to the standard Netlink 'dumpit' callback.
* The 'dumpit' callback is invoked when a Generic Netlink message is received
* with the NLM_F_DUMP flag set.
*
* A dump can be understand as a "GET ALL DATA OF THE GIVEN ENTITY", i.e.
* the userland can receive as long as the .dumpit callback returns data.
*
* .dumpit is not mandatory, but either it or .doit must be provided, see
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L367
*
* To be honest I don't know in what use case one should use .dumpit and why
* it is useful, because you can achieve the same also with .doit handlers.
* Anyway, this is just an example/tutorial.
*
* Quote from: https://lwn.net/Articles/208755
* "The main difference between a 'dumpit' handler and a 'doit' handler is
* that a 'dumpit' handler does not allocate a message buffer for a response;
* a pre-allocated sk_buff is passed to the 'dumpit' handler as the first
* parameter. The 'dumpit' handler should fill the message buffer with the
* appropriate response message and return the size of the sk_buff,
* i.e. sk_buff->len, and the message buffer will automatically be sent to the
* Generic Netlink client that initiated the request. As long as the 'dumpit'
* handler returns a value greater than zero it will be called again with a
* newly allocated message buffer to fill, when the handler has no more data
* to send it should return zero; error conditions are indicated by returning
* a negative value. If necessary, state can be preserved in the
* netlink_callback parameter which is passed to the 'dumpit' handler; the
* netlink_callback parameter values will be preserved across handler calls
* for a single request."
*
* You can see the check for the NLM_F_DUMP-flag here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L780
*/
.dumpit = gnl_cb_echo_dumpit,
/* Start callback for dumps. Can be used to lock data structures. */
.start = gnl_cb_echo_dumpit_before,
/* Completion callback for dumps. Can be used for cleanup after a dump and releasing locks. */
.done = gnl_cb_echo_dumpit_before_after,
/*
0 (= "validate strictly") or value `enum genl_validate_flags`
* see: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L108
*/
.validate = 0,
},
{
.cmd = GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR,
.flags = 0,
.internal_flags = 0,
.doit = gnl_cb_doit_reply_with_nlmsg_err,
// .dumpit is not required, only optional; application specific/dependent on your use case
// in a real application you probably have different .dumpit handlers per operation/command
.dumpit = NULL,
// in a real application you probably have different .start handlers per operation/command
.start = NULL,
// in a real application you probably have different .done handlers per operation/command
.done = NULL,
.validate = 0,
}
};
/**
* Attribute policy: defines which attribute has which type (e.g int, char * etc).
* This get validated for each received Generic Netlink message, if not deactivated
* in `gnl_foobar_xmpl_ops[].validate`.
* See https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*/
static struct nla_policy gnl_foobar_xmpl_policy[GNL_FOOBAR_XMPL_ATTRIBUTE_ENUM_LEN] = {
// In case you are seeing this syntax for the first time (I also learned this just after a few years of
// coding with C myself): The following static array initiations are equivalent:
// `int a[2] = {1, 2}` <==> `int a[2] = {[0] => 1, [1] => 2}`.
[GNL_FOOBAR_XMPL_A_UNSPEC] = {.type = NLA_UNSPEC},
// You can set this to NLA_U32 for testing and send an ECHO message from the userland
// It will fail in this case and you see a entry in the kernel log.
// `enum GNL_FOOBAR_XMPL_ATTRIBUTE::GNL_FOOBAR_XMPL_A_MSG` is a null-terminated C-String
[GNL_FOOBAR_XMPL_A_MSG] = {.type = NLA_NUL_STRING},
};
/**
* Definition of the Netlink family we want to register using Generic Netlink functionality
*/
static struct genl_family gnl_foobar_xmpl_family = {
// automatically assign an id
.id = 0,
// we don't use custom additional header info / user specific header
.hdrsize = 0,
// The name of this family, used by userspace application to get the numeric ID
.name = FAMILY_NAME,
// family specific version number; can be used to evolve application over time (multiple versions)
.version = 1,
// delegates all incoming requests to callback functions
.ops = gnl_foobar_xmpl_ops,
// length of array `gnl_foobar_xmpl_ops`
.n_ops = GNL_FOOBAR_OPS_LEN,
// attribute policy (for validation of messages). Enforced automatically, except ".validate" in
// corresponding ".ops"-field is set accordingly.
.policy = gnl_foobar_xmpl_policy,
// Number of attributes / bounds check for policy (array length)
.maxattr = GNL_FOOBAR_XMPL_ATTRIBUTE_ENUM_LEN,
// Owning Kernel module of the Netlink family we register.
.module = THIS_MODULE,
// Actually not necessary because this memory region would be zeroed anyway during module load,
// but this way one sees all possible options.
// if your application must handle multiple netlink calls in parallel (where one should not block the next
// from starting), set this to true! otherwise all netlink calls are mutually exclusive
.parallel_ops = 0,
// set to true if the family can handle network namespaces and should be presented in all of them
.netnsok = 0,
// called before an operation's doit callback, it may do additional, common, filtering and return an error
.pre_doit = NULL,
// called after an operation's doit callback, it may undo operations done by pre_doit, for example release locks
.post_doit = NULL,
};
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_ECHO` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_echo_doit(struct sk_buff *sender_skb, struct genl_info *info) {
struct nlattr *na;
struct sk_buff *reply_skb;
int rc;
void *msg_head;
char *recv_msg;
pr_info("%s() invoked\n", __func__);
if (info == NULL) {
// should never happen
pr_err("An error occurred in %s():\n", __func__);
return -EINVAL;
}
/*
* For each attribute there is an index in info->attrs which points to a nlattr structure
* in this structure the data is stored.
*/
na = info->attrs[GNL_FOOBAR_XMPL_A_MSG];
if (!na) {
pr_err("no info->attrs[%i]\n", GNL_FOOBAR_XMPL_A_MSG);
return -EINVAL; // we return here because we expect to recv a msg
}
recv_msg = (char *) nla_data(na);
if (recv_msg == NULL) {
pr_err("error while receiving data\n");
} else {
pr_info("received: '%s'\n", recv_msg);
}
// Send a message back
// ---------------------
// Allocate some memory, since the size is not yet known use NLMSG_GOODSIZE
reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (reply_skb == NULL) {
pr_err("An error occurred in %s():\n", __func__);
return -ENOMEM;
}
// Create the message headers
// Add header to netlink message;
// afterwards the buffer looks like this:
// ----------------------------------
// | netlink header |
// | generic netlink header |
// | <space for netlink attributes> |
// ----------------------------------
msg_head = genlmsg_put(reply_skb, // buffer for netlink message: struct sk_buff *
// According to my findings: this is not used for routing
// This can be used in an application specific way to target
// different endpoints within the same user application
// but general rule: just put sender port id here
info->snd_portid, // sending port (not process) id: int
info->snd_seq + 1, // sequence number: int (might be used by receiver, but not mandatory)
&gnl_foobar_xmpl_family, // struct genl_family *
0, // flags for Netlink header: int; application specific and not mandatory
// The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
GNL_FOOBAR_XMPL_C_ECHO_MSG
);
if (msg_head == NULL) {
rc = ENOMEM;
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Add a GNL_FOOBAR_XMPL_A_MSG attribute (actual value/payload to be sent)
// echo the value we just received
rc = nla_put_string(reply_skb, GNL_FOOBAR_XMPL_A_MSG, recv_msg);
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Finalize the message:
// Corrects the netlink message header (length) to include the appended
// attributes. Only necessary if attributes have been added to the message.
genlmsg_end(reply_skb, msg_head);
// Send the message back
rc = genlmsg_reply(reply_skb, info);
// same as genlmsg_unicast(genl_info_net(info), reply_skb, info->snd_portid)
// see https://elixir.bootlin.com/linux/v5.8.9/source/include/net/genetlink.h#L326
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
return 0;
}
/**
* ".dumpit"-callback function if a Generic Netlink with command ECHO_MSG and flag `NLM_F_DUMP` is received.
* Please look into the comments where this is used as ".dumpit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".dumpit" callbacks.
*
* A dump must be understand of "give me all data of a given entity"
* rather than a "dump of the netlink message itself" for debugging etc!
*
* This handler requires `gnl_cb_echo_dumpit_before` to run before a dump and `gnl_cb_echo_dumpit_after` after a dump.
*
* For the sake of simplicity, we use the ECHO_MSG command for the dump. In fact, we don't expect a
* MSG-Attribute here, unlike the regular ECHO_MSG handler. We reply with a dump of
* "all messages that we got" (application specific, hard coded in this example).
*/
int gnl_cb_echo_dumpit(struct sk_buff *pre_allocated_skb, struct netlink_callback *cb) {
void *msg_head;
int ret;
static const char HELLO_FROM_DUMPIT_MSG[] = "You set the flag NLM_F_DUMP; this message is "
"brought to you by .dumpit callback :)";
pr_info("Called %s()\n", __func__);
if (dumpit_cb_progress_data.runs_to_go == 0) {
pr_info("no more data to send in dumpit cb\n");
// mark that dump is done;
return 0;
} else {
dumpit_cb_progress_data.runs_to_go--;
pr_info("%s: %d more runs to do\n", __func__, dumpit_cb_progress_data.runs_to_go);
}
msg_head = genlmsg_put(pre_allocated_skb, // buffer for netlink message: struct sk_buff *
// According to my findings: this is not used for routing
// This can be used in an application specific way to target
// different endpoints within the same user application
// but general rule: just put sender port id here
cb->nlh->nlmsg_pid, // sending port (not process) id: int
// sequence number: int (might be used by receiver, but not mandatory)
// sequence 0, 1, 2...
dumpit_cb_progress_data.total_runs - dumpit_cb_progress_data.runs_to_go - 1,
&gnl_foobar_xmpl_family, // struct genl_family *
0, // flags: int (for netlink header); we don't check them in the userland; application specific
// this way we can trigger a specific command/callback on the receiving side or imply
// on which type of command we are currently answering; this is application specific
GNL_FOOBAR_XMPL_C_ECHO_MSG // cmd: u8 (for generic netlink header);
);
if (msg_head == NULL) {
pr_info("An error occurred in %s(): genlmsg_put() failed\n", __func__);
return -ENOMEM;
}
ret = nla_put_string(
pre_allocated_skb,
GNL_FOOBAR_XMPL_A_MSG,
HELLO_FROM_DUMPIT_MSG
);
if (ret < 0) {
pr_info("An error occurred in %s():\n", __func__);
return ret;
}
genlmsg_end(pre_allocated_skb, msg_head);
// return the length of data we wrote into the pre-allocated buffer
return pre_allocated_skb->len;
}
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info) {
pr_info("%s() invoked, a NLMSG_ERR response will be sent back\n", __func__);
/*
* Generic Netlink is smart enough and sends a NLMSG_ERR reply automatically as reply
* Quote from https://lwn.net/Articles/208755/:
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*/
return -EINVAL;
}
/**
* Called before a dump with `gnl_cb_echo_dumpit()` starts.
* See where this is assigned in `struct genl_ops gnl_foobar_xmpl_ops[]` as
* `.start` callback for more comments.
*
* @return success (0) or error.
*/
int gnl_cb_echo_dumpit_before(struct netlink_callback *cb) {
int ret;
static int unsigned const dump_runs = 3;
pr_info("%s: dump started. acquire lock. initialize dump runs_to_go (number of receives userland can make) to %d runs\n", __func__, dump_runs);
// Lock the mutex like mutex_lock(), and return 0 if the mutex has been acquired or sleep until the mutex becomes available
// If a signal arrives while waiting for the lock then this function returns -EINTR.
ret = mutex_lock_interruptible(&dumpit_cb_progress_data.mtx);
if (ret != 0) {
pr_err("Failed to get lock!\n");
return ret;
}
dumpit_cb_progress_data.total_runs = dump_runs;
dumpit_cb_progress_data.runs_to_go = dump_runs;
return 0;
}
/**
* Called before a dump with `gnl_cb_echo_dumpit()` starts.
* See where this is assigned in `struct genl_ops gnl_foobar_xmpl_ops[]` as
* `.start` callback for more comments.
*
* @return success (0) or error.
*/
int gnl_cb_echo_dumpit_before_after(struct netlink_callback *cb) {
pr_info("%s: dump done. release lock\n", __func__);
mutex_unlock(&dumpit_cb_progress_data.mtx);
return 0;
}
/**
* Module/driver initializer. Called on module load/insertion.
*
* @return success (0) or error code.
*/
static int __init gnl_foobar_xmpl_module_init(void) {
int rc;
pr_info("Generic Netlink Example Module inserted.\n");
// Register family with its operations and policies
rc = genl_register_family(&gnl_foobar_xmpl_family);
if (rc != 0) {
pr_err("FAILED: genl_register_family(): %i\n", rc);
pr_err("An error occurred while inserting the generic netlink example module\n");
return -1;
} else {
pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
mutex_init(&dumpit_cb_progress_data.mtx);
return 0;
}
/**
* Module/driver uninitializer. Called on module unload/removal.
*
* @return success (0) or error code.
*/
static void __exit gnl_foobar_xmpl_module_exit(void) {
int ret;
pr_info("Generic Netlink Example Module unloaded.\n");
// Unregister the family
ret = genl_unregister_family(&gnl_foobar_xmpl_family);
if (ret != 0) {
pr_err("genl_unregister_family() failed: %i\n", ret);
return;
} else {
pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
mutex_destroy(&dumpit_cb_progress_data.mtx);
}
module_init(gnl_foobar_xmpl_module_init);
module_exit(gnl_foobar_xmpl_module_exit);

View File

@@ -0,0 +1,536 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) STMicroelectronics 2019 - All Rights Reserved
* Author: Jean-Philippe Romain <jean-philippe.romain@st.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#include <linux/miscdevice.h>
#include <linux/eventfd.h>
#include <linux/of_platform.h>
#include <linux/list.h>
#define RPMSG_SDB_DRIVER_VERSION "1.0"
/*
* Static global variables
*/
static const char rpmsg_sdb_driver_name[] = "stm32-rpmsg-sdb";
static int LastBufferId;
struct rpmsg_sdb_ioctl_set_efd {
int bufferId, eventfd;
};
struct rpmsg_sdb_ioctl_get_data_size {
int bufferId;
uint32_t size;
};
/* ioctl numbers */
/* _IOW means userland is writing and kernel is reading */
/* _IOR means userland is reading and kernel is writing */
/* _IOWR means userland and kernel can both read and write */
#define RPMSG_SDB_IOCTL_SET_EFD _IOW('R', 0x00, struct rpmsg_sdb_ioctl_set_efd *)
#define RPMSG_SDB_IOCTL_GET_DATA_SIZE _IOWR('R', 0x01, struct rpmsg_sdb_ioctl_get_data_size *)
struct sdb_buf_t {
int index; /* index of buffer */
size_t size; /* buffer size */
size_t writing_size; /* size of data written by copro */
dma_addr_t paddr; /* physical address*/
void *vaddr; /* virtual address */
void *uaddr; /* mapped address for userland */
struct eventfd_ctx *efd_ctx; /* eventfd context */
struct list_head buflist; /* reference in the buffers list */
};
struct rpmsg_sdb_t {
struct mutex mutex; /* mutex to protect the ioctls */
struct miscdevice mdev; /* misc device ref */
struct rpmsg_device *rpdev; /* handle rpmsg device */
struct list_head buffer_list; /* buffer instances list */
};
struct device *rpmsg_sdb_dev;
static int rpmsg_sdb_format_txbuf_string(struct sdb_buf_t *buffer, char *bufinfo_str, size_t bufinfo_str_size)
{
pr_debug("rpmsg_sdb(%s): Buffer index:%d, addr:%08x, size:%08x\n",
__func__,
buffer->index,
buffer->paddr,
buffer->size);
return snprintf(bufinfo_str, bufinfo_str_size, "B%dA%08xL%08x", buffer->index, buffer->paddr, buffer->size);
}
static long rpmsg_sdb_decode_rxbuf_string(char *rxbuf_str, int *buffer_id, size_t *size)
{
int ret = 0;
char *sub_str;
long bsize;
long bufid;
const char delimiter[2] = {'L','\0'};
pr_debug("rpmsg_sdb(%s): rxbuf_str:%s\n", __func__, rxbuf_str);
/* Get first part containing the buffer id */
sub_str = strsep(&rxbuf_str, delimiter);
pr_debug("rpmsg_sdb(%s): sub_str:%s\n", __func__, sub_str);
/* Save Buffer id and size: template BxLyyyyyyyy*/
ret = kstrtol(&sub_str[1], 10, &bufid);
if (ret < 0) {
pr_err("rpmsg_sdb(ERROR): Extract of buffer id failed(%d)", ret);
goto out;
}
ret = kstrtol(&rxbuf_str[2], 16, &bsize);
if (ret < 0) {
pr_err("rpmsg_sdb(ERROR): Extract of buffer size failed(%d)", ret);
goto out;
}
*size = (size_t)bsize;
*buffer_id = (int)bufid;
out:
return ret;
}
static int rpmsg_sdb_send_buf_info(struct rpmsg_sdb_t *rpmsg_sdb, struct sdb_buf_t *buffer)
{
int count = 0, ret = 0;
const unsigned char *tbuf;
char mybuf[32];
int msg_size;
struct rpmsg_device *_rpdev;
_rpdev = rpmsg_sdb->rpdev;
msg_size = rpmsg_get_buffer_size(_rpdev->ept);
if (msg_size < 0)
return msg_size;
count = rpmsg_sdb_format_txbuf_string(buffer, mybuf, 32);
tbuf = &mybuf[0];
do {
/* send a message to our remote processor */
ret = rpmsg_send(_rpdev->ept, (void *)tbuf,
count > msg_size ? msg_size : count);
if (ret) {
dev_err(&_rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
if (count > msg_size) {
count -= msg_size;
tbuf += msg_size;
} else {
count = 0;
}
} while (count > 0);
return count;
}
static int rpmsg_sdb_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long vsize = vma->vm_end - vma->vm_start;
unsigned long size = PAGE_ALIGN(vsize);
unsigned long NumPages = size >> PAGE_SHIFT;
unsigned long align = get_order(size);
pgprot_t prot = pgprot_noncached(vma->vm_page_prot);
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *_buffer;
if (align > CONFIG_CMA_ALIGNMENT)
align = CONFIG_CMA_ALIGNMENT;
if (rpmsg_sdb_dev == NULL)
return -ENOMEM;
rpmsg_sdb_dev->coherent_dma_mask = DMA_BIT_MASK(32);
rpmsg_sdb_dev->dma_mask = &rpmsg_sdb_dev->coherent_dma_mask;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Field the last buffer entry which is the last one created */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
_buffer = list_last_entry(&_rpmsg_sdb->buffer_list,
struct sdb_buf_t, buflist);
_buffer->uaddr = NULL;
_buffer->size = NumPages * PAGE_SIZE;
_buffer->writing_size = -1;
_buffer->vaddr = dma_alloc_coherent(rpmsg_sdb_dev,
_buffer->size,
&_buffer->paddr,
GFP_KERNEL);
if (!_buffer->vaddr) {
pr_err("rpmsg_sdb(ERROR): Memory allocation issue\n");
return -ENOMEM;
}
pr_debug("rpmsg_sdb(%s): dma_alloc_coherent done - paddr[%d]:%x - vaddr[%d]:%p\n",
__func__,
_buffer->index,
_buffer->paddr,
_buffer->index,
_buffer->vaddr);
/* Get address for userland */
if (remap_pfn_range(vma, vma->vm_start,
(_buffer->paddr >> PAGE_SHIFT) + vma->vm_pgoff,
size, prot))
return -EAGAIN;
_buffer->uaddr = (void *)vma->vm_start;
/* Send information to remote proc */
rpmsg_sdb_send_buf_info(_rpmsg_sdb, _buffer);
} else {
dev_err(rpmsg_sdb_dev, "No existing buffer entry exist in the list !!!");
return -EINVAL;
}
/* Increment for number of requested buffer */
LastBufferId++;
return 0;
}
/**
* rpmsg_sdb_open - Open Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_open(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Initialize the buffer list*/
INIT_LIST_HEAD(&_rpmsg_sdb->buffer_list);
mutex_init(&_rpmsg_sdb->mutex);
return 0;
}
/**
* rpmsg_sdb_close - Close Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_close(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *pos, *next;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
list_for_each_entry_safe(pos, next, &_rpmsg_sdb->buffer_list, buflist) {
/* Free the CMA allocation */
pr_debug("rpmsg_sdb(%s): Free the CMA allocation: pos->size:%08x, pos->vaddr:%08x, pos->paddr:%08x\n",
__func__,
pos->size,
pos->vaddr,
pos->paddr);
dma_free_coherent(rpmsg_sdb_dev, pos->size, pos->vaddr,
pos->paddr);
/* Remove the buffer from the list */
list_del(&pos->buflist);
/* Free the buffer */
kfree(pos);
}
/* Reset LastBufferId */
LastBufferId = 0;
return 0;
}
/**
* rpmsg_sdb_ioctl - IOCTL
*
* @session: ibmvmc_file_session struct
* @cmd: cmd field
* @arg: Argument field
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static long rpmsg_sdb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int idx = 0;
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *buffer, *lastbuffer;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_ioctl_set_efd q_set_efd;
struct rpmsg_sdb_ioctl_get_data_size q_get_dat_size;
void __user *argp = (void __user *)arg;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
switch (cmd) {
case RPMSG_SDB_IOCTL_SET_EFD:
mutex_lock(&_rpmsg_sdb->mutex);
/* Get index from the last buffer in the list */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
lastbuffer = list_last_entry(&_rpmsg_sdb->buffer_list, struct sdb_buf_t, buflist);
idx = lastbuffer->index;
/* Check last index was properly initiated*/
if (lastbuffer->vaddr == NULL) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - previous buffer was not allocated\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EBADE;
}
/* increment this index for the next buffer creation*/
idx++;
}
if (copy_from_user(&q_set_efd, (struct rpmsg_sdb_ioctl_set_efd *)argp,
sizeof(struct rpmsg_sdb_ioctl_set_efd))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - copy from user failed\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EFAULT;
}
/* create a new buffer which will be added in the buffer list */
buffer = kmalloc(sizeof(struct sdb_buf_t), GFP_KERNEL);
buffer->index = idx;
buffer->vaddr = NULL;
buffer->efd_ctx = eventfd_ctx_fdget(q_set_efd.eventfd);
list_add_tail(&buffer->buflist, &_rpmsg_sdb->buffer_list);
mutex_unlock(&_rpmsg_sdb->mutex);
break;
case RPMSG_SDB_IOCTL_GET_DATA_SIZE:
if (copy_from_user(&q_get_dat_size, (struct rpmsg_sdb_ioctl_get_data_size *)argp,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy from user failed\n");
return -EFAULT;
}
/* Get the index of the requested buffer and then look-up in the buffer list*/
idx = q_get_dat_size.bufferId;
list_for_each(pos, &_rpmsg_sdb->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == idx) {
/* Get the writing size*/
q_get_dat_size.size = datastructureptr->writing_size;
break;
}
}
if (copy_to_user((struct rpmsg_sdb_ioctl_get_data_size *)argp, &q_get_dat_size,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy to user failed\n");
return -EFAULT;
}
/* Reset the writing size*/
datastructureptr->writing_size = -1;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct file_operations rpmsg_sdb_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = rpmsg_sdb_ioctl,
.mmap = rpmsg_sdb_mmap,
.open = rpmsg_sdb_open,
.release = rpmsg_sdb_close,
};
static int rpmsg_sdb_drv_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret = 0;
int buffer_id = 0;
size_t buffer_size;
char *rpmsg_RxBuf;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpdev->dev);
if (len == 0) {
dev_err(rpmsg_sdb_dev, "(%s) Empty lenght requested\n", __func__);
return -EINVAL;
}
//dev_err(rpmsg_sdb_dev, "(%s) lenght: %d\n", __func__,len);
rpmsg_RxBuf = (char *)kmalloc(len+1, GFP_KERNEL);
memcpy(rpmsg_RxBuf, data, len);
rpmsg_RxBuf[len] = 0;
ret = rpmsg_sdb_decode_rxbuf_string(rpmsg_RxBuf, &buffer_id, &buffer_size);
kfree(rpmsg_RxBuf);
if (ret < 0)
goto out;
if (buffer_id > LastBufferId) {
ret = -EINVAL;
goto out;
}
/* Signal to User space application */
list_for_each(pos, &drv->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == buffer_id) {
datastructureptr->writing_size = buffer_size;
if (datastructureptr->writing_size > datastructureptr->size) {
dev_err(rpmsg_sdb_dev, "(%s) Writing size is bigger than buffer size\n", __func__);
ret = -EINVAL;
goto out;
}
eventfd_signal(datastructureptr->efd_ctx, 1);
break;
}
/* TODO: quid if nothing find during the loop ? */
}
out:
return ret;
}
static int rpmsg_sdb_drv_probe(struct rpmsg_device *rpdev)
{
int ret = 0;
struct device *dev = &rpdev->dev;
struct rpmsg_sdb_t *rpmsg_sdb;
rpmsg_sdb = devm_kzalloc(dev, sizeof(*rpmsg_sdb), GFP_KERNEL);
if (!rpmsg_sdb)
return -ENOMEM;
mutex_init(&rpmsg_sdb->mutex);
rpmsg_sdb->rpdev = rpdev;
rpmsg_sdb->mdev.name = "rpmsg-sdb";
rpmsg_sdb->mdev.minor = MISC_DYNAMIC_MINOR;
rpmsg_sdb->mdev.fops = &rpmsg_sdb_fops;
dev_set_drvdata(&rpdev->dev, rpmsg_sdb);
/* Register misc device */
ret = misc_register(&rpmsg_sdb->mdev);
if (ret) {
dev_err(dev, "Failed to register device\n");
goto err_out;
}
rpmsg_sdb_dev = rpmsg_sdb->mdev.this_device;
dev_info(dev, "%s probed\n", rpmsg_sdb_driver_name);
err_out:
return ret;
}
static void rpmsg_sdb_drv_remove(struct rpmsg_device *rpmsgdev)
{
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpmsgdev->dev);
misc_deregister(&drv->mdev);
}
static struct rpmsg_device_id rpmsg_driver_sdb_id_table[] = {
{ .name = "rpmsg-sdb-channel" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_sdb_id_table);
static struct rpmsg_driver rpmsg_sdb_rmpsg_drv = {
.drv.name = KBUILD_MODNAME,
.drv.owner = THIS_MODULE,
.id_table = rpmsg_driver_sdb_id_table,
.probe = rpmsg_sdb_drv_probe,
.callback = rpmsg_sdb_drv_cb,
.remove = rpmsg_sdb_drv_remove,
};
static int __init rpmsg_sdb_drv_init(void)
{
int ret = 0;
/* Register rpmsg device */
ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
if (ret) {
pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
return ret;
}
pr_info("rpmsg_sdb: Init done\n");
return ret;
}
static void __exit rpmsg_sdb_drv_exit(void)
{
unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
pr_info("rpmsg_sdb: Exit\n");
}
module_init(rpmsg_sdb_drv_init);
module_exit(rpmsg_sdb_drv_exit);
MODULE_AUTHOR("Jean-Philippe Romain <jean-philippe.romain@st.com>");
MODULE_DESCRIPTION("shared data buffer over RPMSG");
MODULE_VERSION(RPMSG_SDB_DRIVER_VERSION);
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,287 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 STMicroelectronics - All Rights Reserved
*
* The rpmsg tty driver implements serial communication on the RPMsg bus to makes
* possible for user-space programs to send and receive rpmsg messages as a standard
* tty protocol.
*
* The remote processor can instantiate a new tty by requesting a "rpmsg-tty" RPMsg service.
* The "rpmsg-tty" service is directly used for data exchange. No flow control is implemented yet.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#define RPMSG_TTY_NAME "ttyRPMSG"
#define MAX_TTY_RPMSG 32
static DEFINE_IDR(tty_idr); /* tty instance id */
static DEFINE_MUTEX(idr_lock); /* protects tty_idr */
static struct tty_driver *rpmsg_tty_driver;
struct rpmsg_tty_port {
struct tty_port port; /* TTY port data */
int id; /* TTY rpmsg index */
struct rpmsg_device *rpdev; /* rpmsg device */
};
static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src)
{
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
int copied;
if (!len)
return -EINVAL;
copied = tty_insert_flip_string(&cport->port, data, len);
if (copied != len)
dev_err_ratelimited(&rpdev->dev, "Trunc buffer: available space is %d\n", copied);
tty_flip_buffer_push(&cport->port);
return 0;
}
static int rpmsg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
{
struct rpmsg_tty_port *cport = idr_find(&tty_idr, tty->index);
struct tty_port *port;
tty->driver_data = cport;
port = tty_port_get(&cport->port);
return tty_port_install(port, driver, tty);
}
static void rpmsg_tty_cleanup(struct tty_struct *tty)
{
tty_port_put(tty->port);
}
static int rpmsg_tty_open(struct tty_struct *tty, struct file *filp)
{
return tty_port_open(tty->port, tty, filp);
}
static void rpmsg_tty_close(struct tty_struct *tty, struct file *filp)
{
return tty_port_close(tty->port, tty, filp);
}
static int rpmsg_tty_write(struct tty_struct *tty, const u8 *buf, int len)
{
struct rpmsg_tty_port *cport = tty->driver_data;
struct rpmsg_device *rpdev;
int msg_max_size, msg_size;
int ret;
rpdev = cport->rpdev;
msg_max_size = rpmsg_get_mtu(rpdev->ept);
if (msg_max_size < 0)
return msg_max_size;
msg_size = min(len, msg_max_size);
/*
* Use rpmsg_trysend instead of rpmsg_send to send the message so the caller is not
* hung until a rpmsg buffer is available. In such case rpmsg_trysend returns -ENOMEM.
*/
ret = rpmsg_trysend(rpdev->ept, (void *)buf, msg_size);
if (ret) {
dev_dbg_ratelimited(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
return msg_size;
}
static unsigned int rpmsg_tty_write_room(struct tty_struct *tty)
{
struct rpmsg_tty_port *cport = tty->driver_data;
int size;
size = rpmsg_get_mtu(cport->rpdev->ept);
if (size < 0)
return 0;
return size;
}
static void rpmsg_tty_hangup(struct tty_struct *tty)
{
tty_port_hangup(tty->port);
}
static const struct tty_operations rpmsg_tty_ops = {
.install = rpmsg_tty_install,
.open = rpmsg_tty_open,
.close = rpmsg_tty_close,
.write = rpmsg_tty_write,
.write_room = rpmsg_tty_write_room,
.hangup = rpmsg_tty_hangup,
.cleanup = rpmsg_tty_cleanup,
};
static struct rpmsg_tty_port *rpmsg_tty_alloc_cport(void)
{
struct rpmsg_tty_port *cport;
int ret;
cport = kzalloc(sizeof(*cport), GFP_KERNEL);
if (!cport)
return ERR_PTR(-ENOMEM);
mutex_lock(&idr_lock);
ret = idr_alloc(&tty_idr, cport, 0, MAX_TTY_RPMSG, GFP_KERNEL);
mutex_unlock(&idr_lock);
if (ret < 0) {
kfree(cport);
return ERR_PTR(ret);
}
cport->id = ret;
return cport;
}
static void rpmsg_tty_destruct_port(struct tty_port *port)
{
struct rpmsg_tty_port *cport = container_of(port, struct rpmsg_tty_port, port);
mutex_lock(&idr_lock);
idr_remove(&tty_idr, cport->id);
mutex_unlock(&idr_lock);
kfree(cport);
}
static const struct tty_port_operations rpmsg_tty_port_ops = {
.destruct = rpmsg_tty_destruct_port,
};
static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
{
struct rpmsg_tty_port *cport;
struct device *dev = &rpdev->dev;
struct device *tty_dev;
int ret;
cport = rpmsg_tty_alloc_cport();
if (IS_ERR(cport))
return dev_err_probe(dev, PTR_ERR(cport), "Failed to alloc tty port\n");
tty_port_init(&cport->port);
cport->port.ops = &rpmsg_tty_port_ops;
tty_dev = tty_port_register_device(&cport->port, rpmsg_tty_driver,
cport->id, dev);
if (IS_ERR(tty_dev)) {
ret = dev_err_probe(dev, PTR_ERR(tty_dev), "Failed to register tty port\n");
tty_port_put(&cport->port);
return ret;
}
cport->rpdev = rpdev;
dev_set_drvdata(dev, cport);
dev_dbg(dev, "New channel: 0x%x -> 0x%x: " RPMSG_TTY_NAME "%d\n",
rpdev->src, rpdev->dst, cport->id);
return 0;
}
static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
{
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
dev_dbg(&rpdev->dev, "Removing rpmsg tty device %d\n", cport->id);
/* User hang up to release the tty */
tty_port_tty_hangup(&cport->port, false);
tty_unregister_device(rpmsg_tty_driver, cport->id);
tty_port_put(&cport->port);
}
static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = {
{ .name = "rpmsg-tty" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_tty_id_table);
static struct rpmsg_driver rpmsg_tty_rpmsg_drv = {
.drv.name = KBUILD_MODNAME,
.id_table = rpmsg_driver_tty_id_table,
.probe = rpmsg_tty_probe,
.callback = rpmsg_tty_cb,
.remove = rpmsg_tty_remove,
};
static int __init rpmsg_tty_init(void)
{
int ret;
rpmsg_tty_driver = tty_alloc_driver(MAX_TTY_RPMSG, TTY_DRIVER_REAL_RAW |
TTY_DRIVER_DYNAMIC_DEV);
if (IS_ERR(rpmsg_tty_driver))
return PTR_ERR(rpmsg_tty_driver);
rpmsg_tty_driver->driver_name = "rpmsg_tty";
rpmsg_tty_driver->name = RPMSG_TTY_NAME;
rpmsg_tty_driver->major = 0;
rpmsg_tty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
/* Disable unused mode by default */
rpmsg_tty_driver->init_termios = tty_std_termios;
rpmsg_tty_driver->init_termios.c_lflag &= ~(ECHO | ICANON);
rpmsg_tty_driver->init_termios.c_oflag &= ~(OPOST | ONLCR);
tty_set_operations(rpmsg_tty_driver, &rpmsg_tty_ops);
ret = tty_register_driver(rpmsg_tty_driver);
if (ret < 0) {
pr_err("Couldn't install driver: %d\n", ret);
goto error_put;
}
ret = register_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
if (ret < 0) {
pr_err("Couldn't register driver: %d\n", ret);
goto error_unregister;
}
return 0;
error_unregister:
tty_unregister_driver(rpmsg_tty_driver);
error_put:
tty_driver_kref_put(rpmsg_tty_driver);
return ret;
}
static void __exit rpmsg_tty_exit(void)
{
unregister_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
tty_unregister_driver(rpmsg_tty_driver);
tty_driver_kref_put(rpmsg_tty_driver);
idr_destroy(&tty_idr);
}
module_init(rpmsg_tty_init);
module_exit(rpmsg_tty_exit);
MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>");
MODULE_DESCRIPTION("remote processor messaging tty driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,804 @@
/* Copyright 2024 Markus Lehr
*
*
* This Software is owned by Markus Lehr.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// ##################################################################################################
/*
* This is a Linux kernel module/driver called "vrpmdv-monitoring-cmd " which holds the family ID
* and functions to handle the monitoring in a Generic Netlink in the kernel.
* "It registers a Netlink family called "vrpmdv-monitoring_cmd".
*
*
* You can find some more interesting documentation about Generic Netlink here:
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
*/
#include <linux/kernel.h>
#include <linux/rpmsg.h>
// basic definitions for kernel module development
#include <linux/module.h>
// definitions for generic netlink families, policies etc;
// transitive dependencies for basic netlink, sockets etc
#include <net/genetlink.h>
// required for locking inside the .dumpit callback demonstration
#include <linux/mutex.h>
#include <linux/wait.h>
// data/vars/enums/properties that describes our protocol that we implement
// on top of generic netlink (like functions we want to trigger on the receiving side)
//#include "vrpmdv-monitoring-cmd.h"
/* ######################## CONVENIENT LOGGING MACROS ######################## */
// (Re)definition of some convenient logging macros from <linux/printk.h>. You can see the logging
// messages when printing the kernel log, e.g. with `$ sudo dmesg`.
// See https://elixir.bootlin.com/linux/latest/source/include/linux/printk.h
// with this redefinition we can easily prefix all log messages from pr_* logging macros
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* ########################################################################### */
#define MSG "hello Monitoring!"
static int count = 100;
module_param(count, int, 0644);
struct instance_data {
int rx_count;
};
// std::chrono::seconds timeoutPeriod = 5;
// auto timePoint = std::chrono::system_clock::now() + timeoutPeriod;
/** ----- NETLINK Driver defintion ------------------*/
/**
* Generic Netlink will create a Netlink family with this name. Kernel will asign
* a numeric ID and afterwards we can talk to the family with its ID. To get
* the ID we use Generic Netlink in the userland and pass the family name.
*
* Short for: Generic Netlink VRPMDV Monitoring gnl_foobar_mcmd
*/
#define FAMILY_NAME "gnl-vrpmdv-mcmd"
/**
* These are the attributes that we want to share in gnl_foobar_xmpl.
* You can understand an attribute as a semantic type. This is
* the payload of Netlink messages.
* GNl: Generic Netlink
*/
enum GNL_VRPMDV_XMPL_ATTRIBUTE {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_A_UNSPEC,
/** We expect a MSG to be a null-terminated C-string. */
GNL_VRPMDV_MCMD_A_MSG,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_A_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN (__GNL_VRPMDV_MCMD_A_MAX)
/**
* The number of actual usable attributes in `enum GNL_VRPMDV_MCMD_ATTRIBUTE`.
* This is `GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_ATTRIBUTE_COUNT (GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN - 1)
/**
* Enumeration of all commands (functions) that our custom protocol on top
* of generic netlink supports. This can be understood as the action that
* we want to trigger on the receiving side.
*/
enum GNL_VRPMDV_MCMD_COMMAND {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_C_UNSPEC,
// first real command is "1" (>0)
/**
* When this command is received, we expect the attribute `GNL_VRPMDV_MCMD_ATTRIBUTE::GNL_VRPMDV_MCMD_A_MSG` to
* be present in the Generic Netlink request message. The kernel reads the message from the packet and
* sent it to the copro. THe result will be return by creating a new Generic Netlink response message
* with an corresponding attribute/payload.
*
* This command/signaling mechanism is independent of the Netlink flag `NLM_F_ECHO (0x08)`. We use it as
* "echo specific data" instead of return a 1:1 copy of the package, which you could do with
* `NLM_F_ECHO (0x08)` for example.
*/
GNL_VRPMDV_MCMD_C_MSG,
/**
* Provokes a NLMSG_ERR answer to this request as described in netlink manpage
* (https://man7.org/linux/man-pages/man7/netlink.7.html).
*/
GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_C_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN (__GNL_VRPMDV_MCMD_C_MAX)
/**
* The number of actual usable commands in `enum GNL_FOOBAR_XMPL_COMMAND`.
* This is `GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_COMMAND_COUNT (GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN - 1)
#define NODATARECEIVED 0
#define DATARECEIVED 1
/**
* Data structure required for our .doit callback handler to
* know about the progress of an ongoing cmd execution.
* See the cmd callback handler how it is used.
*/
// struct {
// // from <linux/mutex.h>
// /**
// * rpmsg wait for response from copro side.
// */
// struct mutex sendMTX;
// /**
// * rpmsg wait for response from copro side.
// */
// struct mutex receiveCV;
// /**
// * Wait Queue: if it is signaled we have received data from copro
// */
// wait_queue_head_t receive_queue;
// /**
// * Waitflag: 0= no data received, 1 = data received
// */
// int receive_queue_flag = NODATARECEIVED;
// /**
// * Condition vaiable signal we have received data from copro
// */
// // std::condition_variable cv;
// // /**
// // * Number that describes how many packets we need to send until we are done
// // * during an ongoing dumpit process. 0 = done.
// // */
// // int unsigned runs_to_go;
// // /**
// // * Number that describes how many packets per dump are sent in total.
// // * Constant per dump.
// // */
// // int unsigned total_runs;
// //the rpmsg device which sends the data to the copro
// struct rpmsg_device *rpdev; /* handle rpmsg device */
// } cmd_cb_progress_data;
struct rpmsg_vrpmdv_mon_t{
// from <linux/mutex.h>
/**
* rpmsg wait for response from copro side.
*/
struct mutex sendMTX;
/**
* rpmsg wait for response from copro side.
*/
struct mutex receiveCV;
/**
* Wait Queue: if it is signaled we have received data from copro
*/
wait_queue_head_t receive_queue;
/**
* Waitflag: 0= no data received, 1 = data received
*/
int receive_queue_flag;
//the rpmsg device which sends the data to the copro
struct rpmsg_device* rpdev; /* handle rpmsg device */
};
struct rpmsg_vrpmdv_mon_t vrpmdv_mon;
// struct mutex mutex; /* mutex to protect the ioctls */
// struct miscdevice mdev; /* misc device ref */
// struct rpmsg_device *rpdev; /* handle rpmsg device */
// struct list_head buffer_list; /* buffer instances list */
// Documentation is on the implementation of this function.
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info);
// Documentation is on the implementation of this function.
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info);
/**
* The length of `struct genl_ops gnl_foobar_xmpl_ops[]`. Not necessarily
* the number of commands in `enum GNlFoobarXmplCommand`. It depends on your application logic.
* For example, you can use the same command multiple times and - dependent by flag -
* invoke a different callback handler. In our simple example we just use one .doit callback
* per operation/command.
*/
#define GNL_VRPMDV_OPS_LEN (GNL_VRPMDV_MCMD_COMMAND_COUNT)
/**
* Array with all operations that our protocol on top of Generic Netlink
* supports. An operation is the glue between a command ("cmd" field in `struct genlmsghdr` of
* received Generic Netlink message) and the corresponding ".doit" callback function.
* See: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L148
*/
struct genl_ops gnl_vrpmdv_mcmd_ops[GNL_VRPMDV_OPS_LEN] = {
{
/* The "cmd" field in `struct genlmsghdr` of received Generic Netlink message */
.cmd = GNL_VRPMDV_MCMD_C_MSG,
/* TODO Use case ? */
.flags = 0,
/* TODO Use case ? */
.internal_flags = 0,
/* Callback handler when a request with the specified ".cmd" above is received.
* Always validates the payload except one set NO_STRICT_VALIDATION flag in ".validate"
* See: https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*
* Quote from: https://lwn.net/Articles/208755
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*
*/
.doit = gnl_cb_vrpmdv_doit,
/* This callback is similar in use to the standard Netlink 'dumpit' callback.
* The 'dumpit' callback is invoked when a Generic Netlink message is received
* with the NLM_F_DUMP flag set.
*
* A dump can be understand as a "GET ALL DATA OF THE GIVEN ENTITY", i.e.
* the userland can receive as long as the .dumpit callback returns data.
*
* .dumpit is not mandatory, but either it or .doit must be provided, see
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L367
*
* To be honest I don't know in what use case one should use .dumpit and why
* it is useful, because you can achieve the same also with .doit handlers.
* Anyway, this is just an example/tutorial.
*
* Quote from: https://lwn.net/Articles/208755
* "The main difference between a 'dumpit' handler and a 'doit' handler is
* that a 'dumpit' handler does not allocate a message buffer for a response;
* a pre-allocated sk_buff is passed to the 'dumpit' handler as the first
* parameter. The 'dumpit' handler should fill the message buffer with the
* appropriate response message and return the size of the sk_buff,
* i.e. sk_buff->len, and the message buffer will automatically be sent to the
* Generic Netlink client that initiated the request. As long as the 'dumpit'
* handler returns a value greater than zero it will be called again with a
* newly allocated message buffer to fill, when the handler has no more data
* to send it should return zero; error conditions are indicated by returning
* a negative value. If necessary, state can be preserved in the
* netlink_callback parameter which is passed to the 'dumpit' handler; the
* netlink_callback parameter values will be preserved across handler calls
* for a single request."
*
* You can see the check for the NLM_F_DUMP-flag here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L780
*/
.dumpit = NULL, //gnl_cb_echo_dumpit,
/* Start callback for dumps. Can be used to lock data structures. */
.start = NULL, //gnl_cb_echo_dumpit_before,
/* Completion callback for dumps. Can be used for cleanup after a dump and releasing locks. */
.done = NULL, //gnl_cb_echo_dumpit_before_after,
/*
0 (= "validate strictly") or value `enum genl_validate_flags`
* see: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L108
*/
.validate = 0,
},
{
.cmd = GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
.flags = 0,
.internal_flags = 0,
.doit = gnl_cb_doit_reply_with_nlmsg_err,
// .dumpit is not required, only optional; application specific/dependent on your use case
// in a real application you probably have different .dumpit handlers per operation/command
.dumpit = NULL,
// in a real application you probably have different .start handlers per operation/command
.start = NULL,
// in a real application you probably have different .done handlers per operation/command
.done = NULL,
.validate = 0,
}
};
/**
* Attribute policy: defines which attribute has which type (e.g int, char * etc).
* This get validated for each received Generic Netlink message, if not deactivated
* in `gnl_foobar_xmpl_ops[].validate`.
* See https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*/
static struct nla_policy gnl_vrpmdv_mcmd_policy[GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN] = {
// In case you are seeing this syntax for the first time (I also learned this just after a few years of
// coding with C myself): The following static array initiations are equivalent:
// `int a[2] = {1, 2}` <==> `int a[2] = {[0] => 1, [1] => 2}`.
[GNL_VRPMDV_MCMD_A_UNSPEC] = {.type = NLA_UNSPEC},
// You can set this to NLA_U32 for testing and send an ECHO message from the userland
// It will fail in this case and you see a entry in the kernel log.
// `enum GNL_FOOBAR_XMPL_ATTRIBUTE::GNL_FOOBAR_XMPL_A_MSG` is a null-terminated C-String
[GNL_VRPMDV_MCMD_A_MSG] = {.type = NLA_NUL_STRING},
};
/**
* Definition of the Netlink family we want to register using Generic Netlink functionality
*/
static struct genl_family gnl_vrpmdv_mcmd_family = {
// automatically assign an id
.id = 0,
// we don't use custom additional header info / user specific header
.hdrsize = 0,
// The name of this family, used by userspace application to get the numeric ID
.name = FAMILY_NAME,
// family specific version number; can be used to evolve application over time (multiple versions)
.version = 1,
// delegates all incoming requests to callback functions
.ops = gnl_vrpmdv_mcmd_ops,
// length of array `gnl_foobar_xmpl_ops`
.n_ops = GNL_VRPMDV_OPS_LEN,
// attribute policy (for validation of messages). Enforced automatically, except ".validate" in
// corresponding ".ops"-field is set accordingly.
.policy = gnl_vrpmdv_mcmd_policy,
// Number of attributes / bounds check for policy (array length)
.maxattr = GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN,
// Owning Kernel module of the Netlink family we register.
.module = THIS_MODULE,
// Actually not necessary because this memory region would be zeroed anyway during module load,
// but this way one sees all possible options.
// if your application must handle multiple netlink calls in parallel (where one should not block the next
// from starting), set this to true! otherwise all netlink calls are mutually exclusive
.parallel_ops = 0,
// set to true if the family can handle network namespaces and should be presented in all of them
.netnsok = 0,
// called before an operation's doit callback, it may do additional, common, filtering and return an error
.pre_doit = NULL,
// called after an operation's doit callback, it may undo operations done by pre_doit, for example release locks
.post_doit = NULL,
};
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_VRPMDV_MCMD_C_MSG` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_vrpmdv_mcmd_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info) {
struct nlattr *na;
struct sk_buff *reply_skb;
int rc;
void *msg_head;
char *recv_msg;
pr_info("%s() invoked\n", __func__);
if (info == NULL) {
// should never happen
pr_err("An error occurred in %s():\n", __func__);
return -EINVAL;
}
/*
* For each attribute there is an index in info->attrs which points to a nlattr structure
* in this structure the data is stored.
*/
na = info->attrs[GNL_VRPMDV_MCMD_A_MSG];
if (!na) {
pr_err("no info->attrs[%i]\n", GNL_VRPMDV_MCMD_A_MSG);
return -EINVAL; // we return here because we expect to recv a msg
}
recv_msg = (char *) nla_data(na);
if (recv_msg == NULL) {
pr_err("error while receiving data\n");
} else {
pr_info("received: '%s'\n", recv_msg);
}
//aquire lock for cmd repmsg channel
// std::lock sendlk(cmd_cb_progress_data.sendmtx);
//send the message to the copro over RPMSG
rc = rpmsg_send(vrpmdv_mon.rpdev->ept, recv_msg, strlen(recv_msg));
if (rc) {
pr_err("rpmsg_send failed: %d\n", rc);
return rc;
}
// struct rpmsg_vrpmdv_mon_t *drv = dev_get_drvdata(&rpdev->dev);
{
pr_info("wait for response\n");
// wait until receive_queue_flag=1 , that means we have received data from Copro
wait_event_interruptible(vrpmdv_mon.receive_queue, vrpmdv_mon.receive_queue_flag != 0 );
pr_info("received data \n");
//Copy data
vrpmdv_mon.receive_queue_flag = NODATARECEIVED;
// std::unique_lock lk(cmd_cb_progress_data.receivemtx);
// if (myCondVar.wait_until(uLock, timePoint) == std::cv_status::timeout)
// {
// dev_err(&cmd_cb_progress_data.rpdev, "rpmsg_send failed, timeout: \n");
// return -1:
// }
pr_info("get response: '%s'\n", recv_msg);
}
// Send a message back after we receive the reply from rpmsg channel
// ---------------------
// Allocate some memory, since the size is not yet known use NLMSG_GOODSIZE
reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (reply_skb == NULL) {
pr_err("An error occurred in %s():\n", __func__);
return -ENOMEM;
}
// Create the message headers
// Add header to netlink message;
// afterwards the buffer looks like this:
// ----------------------------------
// | netlink header |
// | generic netlink header |
// | <space for netlink attributes> |
// ----------------------------------
// msg_head = genlmsg_put(reply_skb, // buffer for netlink message: struct sk_buff *
// // According to my findings: this is not used for routing
// // This can be used in an application specific way to target
// // different endpoints within the same user application
// // but general rule: just put sender port id here
// info->snd_portid, // sending port (not process) id: int
// info->snd_seq + 1, // sequence number: int (might be used by receiver, but not mandatory)
// &gnl_vrpmdv_mcmd_family, // struct genl_family *
// 0, // flags for Netlink header: int; application specific and not mandatory
// // The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
// GNL_VRPMDV_MCMD_C_MSG
// );
msg_head = genlmsg_put_reply(reply_skb, // buffer for netlink message: struct sk_buff *
info, // info
&gnl_vrpmdv_mcmd_family, // struct genl_family *
0, // flags for Netlink header: int; application specific and not mandatory
// The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
info->genlhdr->cmd
);
if (msg_head == NULL) {
rc = ENOMEM;
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Add a GNL_VRPMDV_MCMD_A_MSG attribute (actual value/payload to be sent)
// echo the value we just received
rc = nla_put_string(reply_skb, GNL_VRPMDV_MCMD_A_MSG, recv_msg);
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Finalize the message:
// Corrects the netlink message header (length) to include the appended
// attributes. Only necessary if attributes have been added to the message.
genlmsg_end(reply_skb, msg_head);
// Send the message back
rc = genlmsg_reply(reply_skb, info);
// same as genlmsg_unicast(genl_info_net(info), reply_skb, info->snd_portid)
// see https://elixir.bootlin.com/linux/v5.8.9/source/include/net/genetlink.h#L326
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
return 0;
}
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info) {
pr_info("%s() invoked, a NLMSG_ERR response will be sent back\n", __func__);
/*
* Generic Netlink is smart enough and sends a NLMSG_ERR reply automatically as reply
* Quote from https://lwn.net/Articles/208755/:
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*/
return -EINVAL;
}
/** ----- NETLINK Driver defintion ------------------*/
/**
* callback that is called after the copro send data
* we have to copy it in a buffer for the netlink and later send it back to the userland
*
*/
static int vrpmdv_monitoring_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret = 0;
struct instance_data *idata = dev_get_drvdata(&rpdev->dev);
dev_info(&rpdev->dev, "incoming msg %d (src: 0x%x)\n",
++idata->rx_count, src);
print_hex_dump_debug(__func__, DUMP_PREFIX_NONE, 16, 1, data, len,
true);
vrpmdv_mon.receive_queue_flag= DATARECEIVED;
wake_up_interruptible(&(vrpmdv_mon.receive_queue));
// /* samples should not live forever */
// if (idata->rx_count >= count) {
// dev_info(&rpdev->dev, "goodbye!\n");
// return 0;
// }
/* send a new message now */
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
// if (ret)
// dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
static int vrpmdv_monitoring_probe(struct rpmsg_device *rpdev)
{
int rc;
// int ret;
// struct instance_data *idata;
dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
rpdev->src, rpdev->dst);
// idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
// if (!idata)
// return -ENOMEM;
// dev_set_drvdata(&rpdev->dev, idata);
// /* send a message to our remote processor to */
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
// if (ret) {
// dev_err(&rpdev->dev, "vrpmdv_monitoring_controler_send failed: %d\n", ret);
// return ret;
// }
// return 0;
// struct device *dev;
// dev = &rpdev->dev;
// struct rpmsg_vrpmdv_mon_t *rpmsg_vrpmdv_mon;
// rpmsg_vrpmdv_mon = devm_kzalloc(dev, sizeof(*rpmsg_vrpmdv_mon), GFP_KERNEL);
// if (!rpmsg_vrpmdv_mon)
// return -ENOMEM;
mutex_init(&(vrpmdv_mon.sendMTX));
init_waitqueue_head (&(vrpmdv_mon.receive_queue));
vrpmdv_mon.rpdev = rpdev;
// dev_set_drvdata(&rpdev->dev, rpmsg_vrpmdv_mon);
pr_info("RPMSG CMD Device set.\n");
/** NEU **/
// if (cmd_cb_progress_data.rpdev == NULL) {
// cmd_cb_progress_data.rpdev = rpdev;
// pr_info("RPMSG CMD Device set.\n");
// }
// else {
// pr_info("Error: RPMSG CMD Device already set. Don't set it twice\n");
// }
pr_info("Generic Netlink VRPMDV-Monitroring_Controler Module started.\n");
// Register family with its operations and policies
rc = genl_register_family(&gnl_vrpmdv_mcmd_family);
if (rc != 0) {
pr_err("FAILED: genl_register_family(): %i\n", rc);
pr_err("An error occurred while inserting the generic netlink example module\n");
return -1;
} else {
pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
return 0;
}
static void vrpmdv_monitoring_remove(struct rpmsg_device *rpdev)
{
int ret;
pr_info("Generic Netlink Example Module unloaded.\n");
// Unregister the family
ret = genl_unregister_family(&gnl_vrpmdv_mcmd_family);
if (ret != 0) {
pr_err("genl_unregister_family() failed: %i\n", ret);
return;
} else {
pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
mutex_destroy(&(vrpmdv_mon.sendMTX));
wake_up_interruptible(&(vrpmdv_mon.receive_queue));
dev_info(&rpdev->dev, "vrpmdv-monitoring controler driver is removed\n");
}
static struct rpmsg_device_id vrpmdv_monitoring_controler_id_table[] = {
{ .name = "vrpmdv-monitoring-controler" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, vrpmdv_monitoring_controler_id_table);
static struct rpmsg_driver vrpmdv_monitoring_controler = {
.drv.name = KBUILD_MODNAME,
.id_table = vrpmdv_monitoring_controler_id_table,
.probe = vrpmdv_monitoring_probe,
.callback = vrpmdv_monitoring_cb,
.remove = vrpmdv_monitoring_remove,
};
module_rpmsg_driver(vrpmdv_monitoring_controler);
// static struct rpmsg_driver vrpmdv_monitoring_data = {
// .drv.name = KBUILD_MODNAME,
// .id_table = vrpmdv_monitoring_controler_id_table,
// .probe = vrpmdv_monitoring_probe,
// .callback = vrpmdv_monitoring_cb,
// .remove = vrpmdv_monitoring_remove,
// };
// module_rpmsg_driver(vrpmdv_monitoring_data);
MODULE_DESCRIPTION("Remote processor messaging vrpmdv monitoring controler");
MODULE_LICENSE("GPL v2");
// /**
// * Module/driver initializer. Called on module load/insertion.
// *
// * @return success (0) or error code.
// */
// static int __init gnl_foobar_xmpl_module_init(void) {
// int rc;
// pr_info("Generic Netlink Example Module inserted.\n");
// // Register family with its operations and policies
// rc = genl_register_family(&gnl_foobar_xmpl_family);
// if (rc != 0) {
// pr_err("FAILED: genl_register_family(): %i\n", rc);
// pr_err("An error occurred while inserting the generic netlink example module\n");
// return -1;
// } else {
// pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_init(&dumpit_cb_progress_data.mtx);
// return 0;
// }
// /**
// * Module/driver uninitializer. Called on module unload/removal.
// *
// * @return success (0) or error code.
// */
// static void __exit gnl_foobar_xmpl_module_exit(void) {
// int ret;
// pr_info("Generic Netlink Example Module unloaded.\n");
// // Unregister the family
// ret = genl_unregister_family(&gnl_foobar_xmpl_family);
// if (ret != 0) {
// pr_err("genl_unregister_family() failed: %i\n", ret);
// return;
// } else {
// pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_destroy(&dumpit_cb_progress_data.mtx);
// }
// ----
// static int __init rpmsg_sdb_drv_init(void)
// {
// int ret = 0;
// /* Register rpmsg device */
// ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// if (ret) {
// pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
// return ret;
// }
// pr_info("rpmsg_sdb: Init done\n");
// return ret;
// }
// static void __exit rpmsg_sdb_drv_exit(void)
// {
// unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// pr_info("rpmsg_sdb: Exit\n");
// }
// module_init(rpmsg_sdb_drv_init);
// module_exit(rpmsg_sdb_drv_exit);

View File

@@ -0,0 +1,24 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "CLOSED"
# LICENSE = "GPL-2.0-only"
# LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://stm32_rpmsg_sdb.c \
"
# file://75-rpmsg-sdb.rules \
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
# do_install:append() {
# udev rules for rpmsg-sdb
# install -d ${D}${sysconfdir}/udev/rules.d/
# install -m 0644 ${WORKDIR}/75-rpmsg-sdb.rules ${D}${sysconfdir}/udev/rules.d/75-rpmsg-sdb.rules
# }
#FILES:${PN} += "${sysconfdir}/udev/rules.d/"
RPROVIDES:${PN} += "kernel-module-vrpmdv-monitoring-driver"

View File

@@ -0,0 +1,26 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv-monitoring.c \
file://75-vrpmdv-monitoring.rules \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
do_install:append() {
# udev rules for vrpmdv-monitoring
install -d ${D}${sysconfdir}/udev/rules.d/
install -m 0644 ${WORKDIR}/75-vrpmdv-monitoring.rules ${D}${sysconfdir}/udev/rules.d/75-vrpmdv-monitoring.rules
}
FILES:${PN} += "${sysconfdir}/udev/rules.d/"
RPROVIDES:${PN} += "kernel-module-vrpmdv-monitoring"

View File

@@ -0,0 +1,26 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv-monitoring.c \
file://75-vrpmdv-monitoring.rules \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
do_install:append() {
# udev rules for vrpmdv-monitoring
install -d ${D}${sysconfdir}/udev/rules.d/
install -m 0644 ${WORKDIR}/75-vrpmdv-monitoring.rules ${D}${sysconfdir}/udev/rules.d/75-vrpmdv-monitoring.rules
}
FILES:${PN} += "${sysconfdir}/udev/rules.d/"
FILES:${PN} += "${base_libdir}/modules/"
RPROVIDES:${PN} += "vrpmdv-monitoring"

View File

@@ -0,0 +1,24 @@
SUMMARY = "VRPMDV Monitoring TTY"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv-mon-tty.c \
file://75-mon-tty.rules \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
do_install:append() {
# udev rules for tty
install -d ${D}${sysconfdir}/udev/rules.d/
install -m 0644 ${WORKDIR}/75-mon-tty.rules ${D}${sysconfdir}/udev/rules.d/75-mon-tty.rules
}
FILES:${PN} += "${sysconfdir}/udev/rules.d/"
RPROVIDES:${PN} += "kernel-module-vrpmdv-mon-tty"

View File

@@ -0,0 +1,8 @@
{
"files.associations": {
"rpmsg.h": "c",
"types.h": "c",
"ioctl.h": "c",
"wait.h": "c"
}
}

View File

@@ -0,0 +1 @@
,markus,U2204VM,02.05.2024 10:15,file:///home/markus/.config/libreoffice/4;

View File

@@ -0,0 +1,26 @@
# Makefile for VRPMDV Monitoring Controler
obj-m := vrpmdv-monitoring-controler.o
SRC := $(shell pwd)
all:
$(MAKE) -C $(KERNEL_SRC) M=$(SRC)
modules_install:
$(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules_install
clean:
rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c
rm -f Module.markers Module.symvers modules.order
rm -rf .tmp_versions Modules.symvers
# obj-m = vrpmdv-monitoring-controler.o
# KVERSION = $(shell uname -r)
# all:
# make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules
# clean:
# make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean

View File

@@ -0,0 +1,116 @@
include <condition_variable>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// wait until main() sends data
std::unique_lock lk(m);
cv.wait(lk, []{ return ready; });
// after the wait, we own the lock
std::cout << "Worker thread is processing data\n";
data += " after processing";
// send data back to main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
// manual unlocking is done before notifying, to avoid waking up
// the waiting thread only to block again (see notify_one for details)
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
data = "Example data";
// send data to the worker thread
{
std::lock_guard lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// wait for the worker
{
std::unique_lock lk(m);
cv.wait(lk, []{ return processed; });
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
--------------------
// waiting for timeout after 5 seconds
std::chrono::seconds timeoutPeriod = 5;
auto timePoint = std::chrono::system_clock::now() + timeoutPeriod;
std::unique_lock<std::mutex> uLock(myDataMutex);
while(!DataAreReadyForProcessing())
{
if (myCondVar.wait_until(uLock, timePoint) //<##
== std::cv_status::timeout)
{
// data conditions where not fulfilled within
// the time period; e.g. do some error handling
break;
}
}
--
if (myCondVar.wait_for(uLock, timeoutPeriod,
DataAreReadyForProcessing))
{
// data conditions where fulfilled
// regular processing
}
else // timeout occured, conditions are not fulfilled
{
// e.g. do some error handling
}
-------------
static int rpmsg_sample_probe(struct rpmsg_device *rpdev)
{
int ret;
struct instance_data *idata;
dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
rpdev->src, rpdev->dst);
idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
if (!idata)
return -ENOMEM;
dev_set_drvdata(&rpdev->dev, idata);
/* send a message to our remote processor */
ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
if (ret) {
dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
return 0;

View File

@@ -0,0 +1,541 @@
/* Copyright 2024 Markus Lehr
*
*
* This Software is owned by Markus Lehr.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// ##################################################################################################
/*
* This is a Linux kernel module/driver called "vrpmdv-monitoring-cmd " which holds the family ID
* and functions to handle the monitoring in a Generic Netlink in the kernel.
* "It registers a Netlink family called "vrpmdv-monitoring_cmd".
*
*
* You can find some more interesting documentation about Generic Netlink here:
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
*/
// basic definitions for kernel module development
#include <linux/module.h>
// definitions for generic netlink families, policies etc;
// transitive dependencies for basic netlink, sockets etc
#include <net/genetlink.h>
// required for locking inside the .dumpit callback demonstration
#include <linux/mutex.h>
// data/vars/enums/properties that describes our protocol that we implement
// on top of generic netlink (like functions we want to trigger on the receiving side)
#include "gnl_vrpmdv_mcmd.h"
// Module/Driver description.
// You can see this for example when executing `$ modinfo ./gnl_vromdv_cmd.ko` (after build).
MODULE_LICENSE("GPL");
MODULE_AUTHOR("MArkus Lehr <markus@malehr.de>");
MODULE_DESCRIPTION(
"Linux driver that registers the custom Netlink family "
"\"" FAMILY_NAME "\" via Generic Netlink and responds to echo messages "
"according to the properties specified in \"gnl_vrpmd_mcmd.h\"."
);
/* ######################## CONVENIENT LOGGING MACROS ######################## */
// (Re)definition of some convenient logging macros from <linux/printk.h>. You can see the logging
// messages when printing the kernel log, e.g. with `$ sudo dmesg`.
// See https://elixir.bootlin.com/linux/latest/source/include/linux/printk.h
// with this redefinition we can easily prefix all log messages from pr_* logging macros
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* ########################################################################### */
/**
* Data structure required for our .dumpit callback handler to
* know about the progress of an ongoing dump.
* See the dumpit callback handler how it is used.
*/
struct {
// from <linux/mutex.h>
/**
* Only one process is allowed per dump process. We need a lock for that.
*/
struct mutex mtx;
/**
* Number that describes how many packets we need to send until we are done
* during an ongoing dumpit process. 0 = done.
*/
int unsigned runs_to_go;
/**
* Number that describes how many packets per dump are sent in total.
* Constant per dump.
*/
int unsigned total_runs;
} dumpit_cb_progress_data;
// Documentation is on the implementation of this function.
int gnl_cb_echo_doit(struct sk_buff *sender_skb, struct genl_info *info);
// Documentation is on the implementation of this function.
int gnl_cb_echo_dumpit(struct sk_buff *pre_allocated_skb, struct netlink_callback *cb);
// Documentation is on the implementation of this function.
int gnl_cb_echo_dumpit_before(struct netlink_callback *cb);
// Documentation is on the implementation of this function.
int gnl_cb_echo_dumpit_before_after(struct netlink_callback *cb);
// Documentation is on the implementation of this function.
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info);
/**
* The length of `struct genl_ops gnl_foobar_xmpl_ops[]`. Not necessarily
* the number of commands in `enum GNlFoobarXmplCommand`. It depends on your application logic.
* For example, you can use the same command multiple times and - dependent by flag -
* invoke a different callback handler. In our simple example we just use one .doit callback
* per operation/command.
*/
#define GNL_VRPMDV_OPS_LEN (GNL_VRPMDV_MCMD_COMMAND_COUNT)
/**
* Array with all operations that our protocol on top of Generic Netlink
* supports. An operation is the glue between a command ("cmd" field in `struct genlmsghdr` of
* received Generic Netlink message) and the corresponding ".doit" callback function.
* See: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L148
*/
struct genl_ops gnl_vrpmdv_mcmd_ops[GNL_VRPMDV_OPS_LEN] = {
{
/* The "cmd" field in `struct genlmsghdr` of received Generic Netlink message */
.cmd = GNL_VRPMDV_MCMD_C_MSG,
/* TODO Use case ? */
.flags = 0,
/* TODO Use case ? */
.internal_flags = 0,
/* Callback handler when a request with the specified ".cmd" above is received.
* Always validates the payload except one set NO_STRICT_VALIDATION flag in ".validate"
* See: https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*
* Quote from: https://lwn.net/Articles/208755
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*
*/
.doit = gnl_cb_vrpmdv_doit,
/* This callback is similar in use to the standard Netlink 'dumpit' callback.
* The 'dumpit' callback is invoked when a Generic Netlink message is received
* with the NLM_F_DUMP flag set.
*
* A dump can be understand as a "GET ALL DATA OF THE GIVEN ENTITY", i.e.
* the userland can receive as long as the .dumpit callback returns data.
*
* .dumpit is not mandatory, but either it or .doit must be provided, see
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L367
*
* To be honest I don't know in what use case one should use .dumpit and why
* it is useful, because you can achieve the same also with .doit handlers.
* Anyway, this is just an example/tutorial.
*
* Quote from: https://lwn.net/Articles/208755
* "The main difference between a 'dumpit' handler and a 'doit' handler is
* that a 'dumpit' handler does not allocate a message buffer for a response;
* a pre-allocated sk_buff is passed to the 'dumpit' handler as the first
* parameter. The 'dumpit' handler should fill the message buffer with the
* appropriate response message and return the size of the sk_buff,
* i.e. sk_buff->len, and the message buffer will automatically be sent to the
* Generic Netlink client that initiated the request. As long as the 'dumpit'
* handler returns a value greater than zero it will be called again with a
* newly allocated message buffer to fill, when the handler has no more data
* to send it should return zero; error conditions are indicated by returning
* a negative value. If necessary, state can be preserved in the
* netlink_callback parameter which is passed to the 'dumpit' handler; the
* netlink_callback parameter values will be preserved across handler calls
* for a single request."
*
* You can see the check for the NLM_F_DUMP-flag here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L780
*/
.dumpit = gnl_cb_echo_dumpit,
/* Start callback for dumps. Can be used to lock data structures. */
.start = gnl_cb_echo_dumpit_before,
/* Completion callback for dumps. Can be used for cleanup after a dump and releasing locks. */
.done = gnl_cb_echo_dumpit_before_after,
/*
0 (= "validate strictly") or value `enum genl_validate_flags`
* see: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L108
*/
.validate = 0,
},
{
.cmd = GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
.flags = 0,
.internal_flags = 0,
.doit = gnl_cb_doit_reply_with_nlmsg_err,
// .dumpit is not required, only optional; application specific/dependent on your use case
// in a real application you probably have different .dumpit handlers per operation/command
.dumpit = NULL,
// in a real application you probably have different .start handlers per operation/command
.start = NULL,
// in a real application you probably have different .done handlers per operation/command
.done = NULL,
.validate = 0,
}
};
/**
* Attribute policy: defines which attribute has which type (e.g int, char * etc).
* This get validated for each received Generic Netlink message, if not deactivated
* in `gnl_foobar_xmpl_ops[].validate`.
* See https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*/
static struct nla_policy gnl_vrpmdv_mcmd_policy[GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN] = {
// In case you are seeing this syntax for the first time (I also learned this just after a few years of
// coding with C myself): The following static array initiations are equivalent:
// `int a[2] = {1, 2}` <==> `int a[2] = {[0] => 1, [1] => 2}`.
[GNL_VRPMDV_MCMD_A_UNSPEC] = {.type = NLA_UNSPEC},
// You can set this to NLA_U32 for testing and send an ECHO message from the userland
// It will fail in this case and you see a entry in the kernel log.
// `enum GNL_FOOBAR_XMPL_ATTRIBUTE::GNL_FOOBAR_XMPL_A_MSG` is a null-terminated C-String
[GNL_VRPMDV_MCMD_A_MSG] = {.type = NLA_NUL_STRING},
};
/**
* Definition of the Netlink family we want to register using Generic Netlink functionality
*/
static struct genl_family gnl_vrpmdv_mcmd_family = {
// automatically assign an id
.id = 0,
// we don't use custom additional header info / user specific header
.hdrsize = 0,
// The name of this family, used by userspace application to get the numeric ID
.name = FAMILY_NAME,
// family specific version number; can be used to evolve application over time (multiple versions)
.version = 1,
// delegates all incoming requests to callback functions
.ops = gnl_vrpmdv_mcmd_ops,
// length of array `gnl_foobar_xmpl_ops`
.n_ops = GNL_VRPMDV_OPS_LEN,
// attribute policy (for validation of messages). Enforced automatically, except ".validate" in
// corresponding ".ops"-field is set accordingly.
.policy = gnl_vrpmdv_mcmd_policy,
// Number of attributes / bounds check for policy (array length)
.maxattr = GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN,
// Owning Kernel module of the Netlink family we register.
.module = THIS_MODULE,
// Actually not necessary because this memory region would be zeroed anyway during module load,
// but this way one sees all possible options.
// if your application must handle multiple netlink calls in parallel (where one should not block the next
// from starting), set this to true! otherwise all netlink calls are mutually exclusive
.parallel_ops = 0,
// set to true if the family can handle network namespaces and should be presented in all of them
.netnsok = 0,
// called before an operation's doit callback, it may do additional, common, filtering and return an error
.pre_doit = NULL,
// called after an operation's doit callback, it may undo operations done by pre_doit, for example release locks
.post_doit = NULL,
};
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_ECHO` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_echo_doit(struct sk_buff *sender_skb, struct genl_info *info) {
struct nlattr *na;
struct sk_buff *reply_skb;
int rc;
void *msg_head;
char *recv_msg;
pr_info("%s() invoked\n", __func__);
if (info == NULL) {
// should never happen
pr_err("An error occurred in %s():\n", __func__);
return -EINVAL;
}
/*
* For each attribute there is an index in info->attrs which points to a nlattr structure
* in this structure the data is stored.
*/
na = info->attrs[GNL_FOOBAR_XMPL_A_MSG];
if (!na) {
pr_err("no info->attrs[%i]\n", GNL_FOOBAR_XMPL_A_MSG);
return -EINVAL; // we return here because we expect to recv a msg
}
recv_msg = (char *) nla_data(na);
if (recv_msg == NULL) {
pr_err("error while receiving data\n");
} else {
pr_info("received: '%s'\n", recv_msg);
}
// Send a message back
// ---------------------
// Allocate some memory, since the size is not yet known use NLMSG_GOODSIZE
reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (reply_skb == NULL) {
pr_err("An error occurred in %s():\n", __func__);
return -ENOMEM;
}
// Create the message headers
// Add header to netlink message;
// afterwards the buffer looks like this:
// ----------------------------------
// | netlink header |
// | generic netlink header |
// | <space for netlink attributes> |
// ----------------------------------
msg_head = genlmsg_put(reply_skb, // buffer for netlink message: struct sk_buff *
// According to my findings: this is not used for routing
// This can be used in an application specific way to target
// different endpoints within the same user application
// but general rule: just put sender port id here
info->snd_portid, // sending port (not process) id: int
info->snd_seq + 1, // sequence number: int (might be used by receiver, but not mandatory)
&gnl_foobar_xmpl_family, // struct genl_family *
0, // flags for Netlink header: int; application specific and not mandatory
// The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
GNL_FOOBAR_XMPL_C_ECHO_MSG
);
if (msg_head == NULL) {
rc = ENOMEM;
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Add a GNL_FOOBAR_XMPL_A_MSG attribute (actual value/payload to be sent)
// echo the value we just received
rc = nla_put_string(reply_skb, GNL_FOOBAR_XMPL_A_MSG, recv_msg);
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Finalize the message:
// Corrects the netlink message header (length) to include the appended
// attributes. Only necessary if attributes have been added to the message.
genlmsg_end(reply_skb, msg_head);
// Send the message back
rc = genlmsg_reply(reply_skb, info);
// same as genlmsg_unicast(genl_info_net(info), reply_skb, info->snd_portid)
// see https://elixir.bootlin.com/linux/v5.8.9/source/include/net/genetlink.h#L326
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
return 0;
}
/** ---------------------- not needed ML */
/**
* ".dumpit"-callback function if a Generic Netlink with command ECHO_MSG and flag `NLM_F_DUMP` is received.
* Please look into the comments where this is used as ".dumpit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".dumpit" callbacks.
*
* A dump must be understand of "give me all data of a given entity"
* rather than a "dump of the netlink message itself" for debugging etc!
*
* This handler requires `gnl_cb_echo_dumpit_before` to run before a dump and `gnl_cb_echo_dumpit_after` after a dump.
*
* For the sake of simplicity, we use the ECHO_MSG command for the dump. In fact, we don't expect a
* MSG-Attribute here, unlike the regular ECHO_MSG handler. We reply with a dump of
* "all messages that we got" (application specific, hard coded in this example).
*/
int gnl_cb_echo_dumpit(struct sk_buff *pre_allocated_skb, struct netlink_callback *cb) {
void *msg_head;
int ret;
static const char HELLO_FROM_DUMPIT_MSG[] = "You set the flag NLM_F_DUMP; this message is "
"brought to you by .dumpit callback :)";
pr_info("Called %s()\n", __func__);
if (dumpit_cb_progress_data.runs_to_go == 0) {
pr_info("no more data to send in dumpit cb\n");
// mark that dump is done;
return 0;
} else {
dumpit_cb_progress_data.runs_to_go--;
pr_info("%s: %d more runs to do\n", __func__, dumpit_cb_progress_data.runs_to_go);
}
msg_head = genlmsg_put(pre_allocated_skb, // buffer for netlink message: struct sk_buff *
// According to my findings: this is not used for routing
// This can be used in an application specific way to target
// different endpoints within the same user application
// but general rule: just put sender port id here
cb->nlh->nlmsg_pid, // sending port (not process) id: int
// sequence number: int (might be used by receiver, but not mandatory)
// sequence 0, 1, 2...
dumpit_cb_progress_data.total_runs - dumpit_cb_progress_data.runs_to_go - 1,
&gnl_foobar_xmpl_family, // struct genl_family *
0, // flags: int (for netlink header); we don't check them in the userland; application specific
// this way we can trigger a specific command/callback on the receiving side or imply
// on which type of command we are currently answering; this is application specific
GNL_FOOBAR_XMPL_C_ECHO_MSG // cmd: u8 (for generic netlink header);
);
if (msg_head == NULL) {
pr_info("An error occurred in %s(): genlmsg_put() failed\n", __func__);
return -ENOMEM;
}
ret = nla_put_string(
pre_allocated_skb,
GNL_FOOBAR_XMPL_A_MSG,
HELLO_FROM_DUMPIT_MSG
);
if (ret < 0) {
pr_info("An error occurred in %s():\n", __func__);
return ret;
}
genlmsg_end(pre_allocated_skb, msg_head);
// return the length of data we wrote into the pre-allocated buffer
return pre_allocated_skb->len;
}
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info) {
pr_info("%s() invoked, a NLMSG_ERR response will be sent back\n", __func__);
/*
* Generic Netlink is smart enough and sends a NLMSG_ERR reply automatically as reply
* Quote from https://lwn.net/Articles/208755/:
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*/
return -EINVAL;
}
/**
* Called before a dump with `gnl_cb_echo_dumpit()` starts.
* See where this is assigned in `struct genl_ops gnl_foobar_xmpl_ops[]` as
* `.start` callback for more comments.
*
* @return success (0) or error.
*/
int gnl_cb_echo_dumpit_before(struct netlink_callback *cb) {
int ret;
static int unsigned const dump_runs = 3;
pr_info("%s: dump started. acquire lock. initialize dump runs_to_go (number of receives userland can make) to %d runs\n", __func__, dump_runs);
// Lock the mutex like mutex_lock(), and return 0 if the mutex has been acquired or sleep until the mutex becomes available
// If a signal arrives while waiting for the lock then this function returns -EINTR.
ret = mutex_lock_interruptible(&dumpit_cb_progress_data.mtx);
if (ret != 0) {
pr_err("Failed to get lock!\n");
return ret;
}
dumpit_cb_progress_data.total_runs = dump_runs;
dumpit_cb_progress_data.runs_to_go = dump_runs;
return 0;
}
/**
* Called before a dump with `gnl_cb_echo_dumpit()` starts.
* See where this is assigned in `struct genl_ops gnl_foobar_xmpl_ops[]` as
* `.start` callback for more comments.
*
* @return success (0) or error.
*/
int gnl_cb_echo_dumpit_before_after(struct netlink_callback *cb) {
pr_info("%s: dump done. release lock\n", __func__);
mutex_unlock(&dumpit_cb_progress_data.mtx);
return 0;
}
/** ---------------------- not needed ML */
/** ---------------------- chenge this in the probe of the rpmsg driver ML */
/**
* Module/driver initializer. Called on module load/insertion.
*
* @return success (0) or error code.
*/
static int __init gnl_foobar_xmpl_module_init(void) {
int rc;
pr_info("Generic Netlink Example Module inserted.\n");
// Register family with its operations and policies
rc = genl_register_family(&gnl_foobar_xmpl_family);
if (rc != 0) {
pr_err("FAILED: genl_register_family(): %i\n", rc);
pr_err("An error occurred while inserting the generic netlink example module\n");
return -1;
} else {
pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
mutex_init(&dumpit_cb_progress_data.mtx);
return 0;
}
/**
* Module/driver uninitializer. Called on module unload/removal.
*
* @return success (0) or error code.
*/
static void __exit gnl_foobar_xmpl_module_exit(void) {
int ret;
pr_info("Generic Netlink Example Module unloaded.\n");
// Unregister the family
ret = genl_unregister_family(&gnl_foobar_xmpl_family);
if (ret != 0) {
pr_err("genl_unregister_family() failed: %i\n", ret);
return;
} else {
pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
mutex_destroy(&dumpit_cb_progress_data.mtx);
}
module_init(gnl_foobar_xmpl_module_init);
module_exit(gnl_foobar_xmpl_module_exit);
/** ---------------------- chenge this in the probe of the rpmsg driver ML */

View File

@@ -0,0 +1,98 @@
/* Copyright 2021 Philipp Schuster
*
* This Software is owned by Markus Lehr.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
/*
* This file describes the common properties of our custom Netlink family on top of Generic Netlink.
* It is used by all C projects, i.e. the Kernel driver, and the userland components.
*/
/**
* Generic Netlink will create a Netlink family with this name. Kernel will asign
* a numeric ID and afterwards we can talk to the family with its ID. To get
* the ID we use Generic Netlink in the userland and pass the family name.
*
* Short for: Generic Netlink VRPMDV Monitoring gnl_foobar_mcmd
*/
#define FAMILY_NAME "gnl-vrpmdv-mcmd"
/**
* These are the attributes that we want to share in gnl_foobar_xmpl.
* You can understand an attribute as a semantic type. This is
* the payload of Netlink messages.
* GNl: Generic Netlink
*/
enum GNL_VRPMDV_XMPL_ATTRIBUTE {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_A_UNSPEC,
/** We expect a MSG to be a null-terminated C-string. */
GNL_VRPMDV_MCMD_A_MSG,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_A_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_FOOBAR_XMPL_ATTRIBUTE_ENUM_LEN (__GNL_VRPMDV_MCMD_A_MAX)
/**
* The number of actual usable attributes in `enum GNL_VRPMDV_MCMD_ATTRIBUTE`.
* This is `GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_ATTRIBUTE_COUNT (GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN - 1)
/**
* Enumeration of all commands (functions) that our custom protocol on top
* of generic netlink supports. This can be understood as the action that
* we want to trigger on the receiving side.
*/
enum GNL_VRPMDV_MCMD_COMMAND {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_C_UNSPEC,
// first real command is "1" (>0)
/**
* When this command is received, we expect the attribute `GNL_VRPMDV_MCMD_ATTRIBUTE::GNL_VRPMDV_MCMD_A_MSG` to
* be present in the Generic Netlink request message. The kernel reads the message from the packet and
* sent it to the copro. THe result will be return by creating a new Generic Netlink response message
* with an corresponding attribute/payload.
*
* This command/signaling mechanism is independent of the Netlink flag `NLM_F_ECHO (0x08)`. We use it as
* "echo specific data" instead of return a 1:1 copy of the package, which you could do with
* `NLM_F_ECHO (0x08)` for example.
*/
GNL_VRPMDV_MCMD_C_MSG,
/**
* Provokes a NLMSG_ERR answer to this request as described in netlink manpage
* (https://man7.org/linux/man-pages/man7/netlink.7.html).
*/
GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_C_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN (__GNL_VRPMDV_MCMD_C_MAX)
/**
* The number of actual usable commands in `enum GNL_FOOBAR_XMPL_COMMAND`.
* This is `GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_COMMAND_COUNT (GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN - 1)

View File

@@ -0,0 +1,865 @@
/* Copyright 2024 Markus Lehr
*
*
* This Software is owned by Markus Lehr.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// ##################################################################################################
/*
* This is a Linux kernel module/driver called "vrpmdv-monitoring-cmd " which holds the family ID
* and functions to handle the monitoring in a Generic Netlink in the kernel.
* "It registers a Netlink family called "vrpmdv-monitoring_cmd".
*
*
* You can find some more interesting documentation about Generic Netlink here:
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
*/
#include <linux/kernel.h>
#include <linux/rpmsg.h>
// basic definitions for kernel module development
#include <linux/module.h>
// definitions for generic netlink families, policies etc;
// transitive dependencies for basic netlink, sockets etc
#include <net/genetlink.h>
// required for locking inside the .dumpit callback demonstration
#include <linux/mutex.h>
#include <linux/wait.h>
// data/vars/enums/properties that describes our protocol that we implement
// on top of generic netlink (like functions we want to trigger on the receiving side)
//#include "vrpmdv-monitoring-cmd.h"
/* ######################## CONVENIENT LOGGING MACROS ######################## */
// (Re)definition of some convenient logging macros from <linux/printk.h>. You can see the logging
// messages when printing the kernel log, e.g. with `$ sudo dmesg`.
// See https://elixir.bootlin.com/linux/latest/source/include/linux/printk.h
// with this redefinition we can easily prefix all log messages from pr_* logging macros
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* ########################################################################### */
#define MSG "hello Monitoring!"
static int count = 100;
module_param(count, int, 0644);
struct instance_data {
int rx_count;
};
// std::chrono::seconds timeoutPeriod = 5;
// auto timePoint = std::chrono::system_clock::now() + timeoutPeriod;
/** ----- NETLINK Driver defintion ------------------*/
/**
* Generic Netlink will create a Netlink family with this name. Kernel will asign
* a numeric ID and afterwards we can talk to the family with its ID. To get
* the ID we use Generic Netlink in the userland and pass the family name.
*
* Short for: Generic Netlink VRPMDV Monitoring gnl_foobar_mcmd
*/
#define FAMILY_NAME "gnl-vrpmdv-mcmd"
/**
* These are the attributes that we want to share in gnl_foobar_xmpl.
* You can understand an attribute as a semantic type. This is
* the payload of Netlink messages.
* GNl: Generic Netlink
*/
enum GNL_VRPMDV_XMPL_ATTRIBUTE {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_A_UNSPEC,
/** We expect a MSG to be a null-terminated C-string. */
GNL_VRPMDV_MCMD_A_MSG,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_A_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN (__GNL_VRPMDV_MCMD_A_MAX)
/**
* The number of actual usable attributes in `enum GNL_VRPMDV_MCMD_ATTRIBUTE`.
* This is `GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_ATTRIBUTE_COUNT (GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN - 1)
/**
* Enumeration of all commands (functions) that our custom protocol on top
* of generic netlink supports. This can be understood as the action that
* we want to trigger on the receiving side.
*/
enum GNL_VRPMDV_MCMD_COMMAND {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_C_UNSPEC,
// first real command is "1" (>0)
/**
* When this command is received, we expect the attribute `GNL_VRPMDV_MCMD_ATTRIBUTE::GNL_VRPMDV_MCMD_A_MSG` to
* be present in the Generic Netlink request message. The kernel reads the message from the packet and
* sent it to the copro. THe result will be return by creating a new Generic Netlink response message
* with an corresponding attribute/payload.
*
* This command/signaling mechanism is independent of the Netlink flag `NLM_F_ECHO (0x08)`. We use it as
* "echo specific data" instead of return a 1:1 copy of the package, which you could do with
* `NLM_F_ECHO (0x08)` for example.
*/
GNL_VRPMDV_MCMD_C_MSG,
/**
* Provokes a NLMSG_ERR answer to this request as described in netlink manpage
* (https://man7.org/linux/man-pages/man7/netlink.7.html).
*/
GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_C_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN (__GNL_VRPMDV_MCMD_C_MAX)
/**
* The number of actual usable commands in `enum GNL_FOOBAR_XMPL_COMMAND`.
* This is `GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_COMMAND_COUNT (GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN - 1)
#define NODATARECEIVED 0
#define DATARECEIVED 1
/**
* Data structure required for our .doit callback handler to
* know about the progress of an ongoing cmd execution.
* See the cmd callback handler how it is used.
*/
// struct {
// // from <linux/mutex.h>
// /**
// * rpmsg wait for response from copro side.
// */
// struct mutex sendMTX;
// /**
// * rpmsg wait for response from copro side.
// */
// struct mutex receiveCV;
// /**
// * Wait Queue: if it is signaled we have received data from copro
// */
// wait_queue_head_t receive_queue;
// /**
// * Waitflag: 0= no data received, 1 = data received
// */
// int receive_queue_flag = NODATARECEIVED;
// /**
// * Condition vaiable signal we have received data from copro
// */
// // std::condition_variable cv;
// // /**
// // * Number that describes how many packets we need to send until we are done
// // * during an ongoing dumpit process. 0 = done.
// // */
// // int unsigned runs_to_go;
// // /**
// // * Number that describes how many packets per dump are sent in total.
// // * Constant per dump.
// // */
// // int unsigned total_runs;
// //the rpmsg device which sends the data to the copro
// struct rpmsg_device *rpdev; /* handle rpmsg device */
// } cmd_cb_progress_data;
// struct rpmsg_vrpmdv_mon_t{
// // from <linux/mutex.h>
// /**
// * rpmsg wait for response from copro side.
// */
// struct mutex sendMTX;
// /**
// * rpmsg wait for response from copro side.
// */
// struct mutex receiveCV;
// /**
// * Wait Queue: if it is signaled we have received data from copro
// */
// wait_queue_head_t receive_queue;
// /**
// * Waitflag: 0= no data received, 1 = data received
// */
// int receive_queue_flag;
// //the rpmsg device which sends the data to the copro
// struct rpmsg_device* rpdev; /* handle rpmsg device */
// };
// struct rpmsg_vrpmdv_mon_t vrpmdv_mon;
/**
* rpmsg wait for response from copro side.
*/
struct mutex sendMTX;
/**
* rpmsg wait for response from copro side.
*/
struct mutex receiveCV;
/**
* Wait Queue: if it is signaled we have received data from copro
*/
wait_queue_head_t receive_queue;
/**
* Waitflag: 0= no data received, 1 = data received
*/
int receive_queue_flag = NODATARECEIVED;
char* received_bytes = NULL;
int received_len = 0;
//the rpmsg device which sends the data to the copro
struct rpmsg_device* prpdev = NULL; /* handle rpmsg device */
// struct mutex mutex; /* mutex to protect the ioctls */
// struct miscdevice mdev; /* misc device ref */
// struct rpmsg_device *rpdev; /* handle rpmsg device */
// struct list_head buffer_list; /* buffer instances list */
// Documentation is on the implementation of this function.
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info);
// Documentation is on the implementation of this function.
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info);
/**
* The length of `struct genl_ops gnl_foobar_xmpl_ops[]`. Not necessarily
* the number of commands in `enum GNlFoobarXmplCommand`. It depends on your application logic.
* For example, you can use the same command multiple times and - dependent by flag -
* invoke a different callback handler. In our simple example we just use one .doit callback
* per operation/command.
*/
#define GNL_VRPMDV_OPS_LEN (GNL_VRPMDV_MCMD_COMMAND_COUNT)
/**
* Array with all operations that our protocol on top of Generic Netlink
* supports. An operation is the glue between a command ("cmd" field in `struct genlmsghdr` of
* received Generic Netlink message) and the corresponding ".doit" callback function.
* See: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L148
*/
struct genl_ops gnl_vrpmdv_mcmd_ops[GNL_VRPMDV_OPS_LEN] = {
{
/* The "cmd" field in `struct genlmsghdr` of received Generic Netlink message */
.cmd = GNL_VRPMDV_MCMD_C_MSG,
/* TODO Use case ? */
.flags = 0,
/* TODO Use case ? */
.internal_flags = 0,
/* Callback handler when a request with the specified ".cmd" above is received.
* Always validates the payload except one set NO_STRICT_VALIDATION flag in ".validate"
* See: https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*
* Quote from: https://lwn.net/Articles/208755
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*
*/
.doit = gnl_cb_vrpmdv_doit,
/* This callback is similar in use to the standard Netlink 'dumpit' callback.
* The 'dumpit' callback is invoked when a Generic Netlink message is received
* with the NLM_F_DUMP flag set.
*
* A dump can be understand as a "GET ALL DATA OF THE GIVEN ENTITY", i.e.
* the userland can receive as long as the .dumpit callback returns data.
*
* .dumpit is not mandatory, but either it or .doit must be provided, see
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L367
*
* To be honest I don't know in what use case one should use .dumpit and why
* it is useful, because you can achieve the same also with .doit handlers.
* Anyway, this is just an example/tutorial.
*
* Quote from: https://lwn.net/Articles/208755
* "The main difference between a 'dumpit' handler and a 'doit' handler is
* that a 'dumpit' handler does not allocate a message buffer for a response;
* a pre-allocated sk_buff is passed to the 'dumpit' handler as the first
* parameter. The 'dumpit' handler should fill the message buffer with the
* appropriate response message and return the size of the sk_buff,
* i.e. sk_buff->len, and the message buffer will automatically be sent to the
* Generic Netlink client that initiated the request. As long as the 'dumpit'
* handler returns a value greater than zero it will be called again with a
* newly allocated message buffer to fill, when the handler has no more data
* to send it should return zero; error conditions are indicated by returning
* a negative value. If necessary, state can be preserved in the
* netlink_callback parameter which is passed to the 'dumpit' handler; the
* netlink_callback parameter values will be preserved across handler calls
* for a single request."
*
* You can see the check for the NLM_F_DUMP-flag here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L780
*/
.dumpit = NULL, //gnl_cb_echo_dumpit,
/* Start callback for dumps. Can be used to lock data structures. */
.start = NULL, //gnl_cb_echo_dumpit_before,
/* Completion callback for dumps. Can be used for cleanup after a dump and releasing locks. */
.done = NULL, //gnl_cb_echo_dumpit_before_after,
/*
0 (= "validate strictly") or value `enum genl_validate_flags`
* see: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L108
*/
.validate = 0,
},
{
.cmd = GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
.flags = 0,
.internal_flags = 0,
.doit = gnl_cb_doit_reply_with_nlmsg_err,
// .dumpit is not required, only optional; application specific/dependent on your use case
// in a real application you probably have different .dumpit handlers per operation/command
.dumpit = NULL,
// in a real application you probably have different .start handlers per operation/command
.start = NULL,
// in a real application you probably have different .done handlers per operation/command
.done = NULL,
.validate = 0,
}
};
/**
* Attribute policy: defines which attribute has which type (e.g int, char * etc).
* This get validated for each received Generic Netlink message, if not deactivated
* in `gnl_foobar_xmpl_ops[].validate`.
* See https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*/
static struct nla_policy gnl_vrpmdv_mcmd_policy[GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN] = {
// In case you are seeing this syntax for the first time (I also learned this just after a few years of
// coding with C myself): The following static array initiations are equivalent:
// `int a[2] = {1, 2}` <==> `int a[2] = {[0] => 1, [1] => 2}`.
[GNL_VRPMDV_MCMD_A_UNSPEC] = {.type = NLA_UNSPEC},
// You can set this to NLA_U32 for testing and send an ECHO message from the userland
// It will fail in this case and you see a entry in the kernel log.
// `enum GNL_FOOBAR_XMPL_ATTRIBUTE::GNL_FOOBAR_XMPL_A_MSG` is a null-terminated C-String
[GNL_VRPMDV_MCMD_A_MSG] = {.type = NLA_NUL_STRING},
};
/**
* Definition of the Netlink family we want to register using Generic Netlink functionality
*/
static struct genl_family gnl_vrpmdv_mcmd_family = {
// automatically assign an id
.id = 0,
// we don't use custom additional header info / user specific header
.hdrsize = 0,
// The name of this family, used by userspace application to get the numeric ID
.name = FAMILY_NAME,
// family specific version number; can be used to evolve application over time (multiple versions)
.version = 1,
// delegates all incoming requests to callback functions
.ops = gnl_vrpmdv_mcmd_ops,
// length of array `gnl_foobar_xmpl_ops`
.n_ops = GNL_VRPMDV_OPS_LEN,
// attribute policy (for validation of messages). Enforced automatically, except ".validate" in
// corresponding ".ops"-field is set accordingly.
.policy = gnl_vrpmdv_mcmd_policy,
// Number of attributes / bounds check for policy (array length)
.maxattr = GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN,
// Owning Kernel module of the Netlink family we register.
.module = THIS_MODULE,
// Actually not necessary because this memory region would be zeroed anyway during module load,
// but this way one sees all possible options.
// if your application must handle multiple netlink calls in parallel (where one should not block the next
// from starting), set this to true! otherwise all netlink calls are mutually exclusive
.parallel_ops = 0,
// set to true if the family can handle network namespaces and should be presented in all of them
.netnsok = 0,
// called before an operation's doit callback, it may do additional, common, filtering and return an error
.pre_doit = NULL,
// called after an operation's doit callback, it may undo operations done by pre_doit, for example release locks
.post_doit = NULL,
};
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_VRPMDV_MCMD_C_MSG` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_vrpmdv_mcmd_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info) {
struct nlattr *na;
struct sk_buff *reply_skb;
int rc;
void *msg_head;
char *recv_msg;
pr_info("%s() invoked\n", __func__);
if (info == NULL) {
// should never happen
pr_err("An error occurred in %s():\n", __func__);
return -EINVAL;
}
/*
* For each attribute there is an index in info->attrs which points to a nlattr structure
* in this structure the data is stored.
*/
na = info->attrs[GNL_VRPMDV_MCMD_A_MSG];
if (!na) {
pr_err("no info->attrs[%i]\n", GNL_VRPMDV_MCMD_A_MSG);
return -EINVAL; // we return here because we expect to recv a msg
}
recv_msg = (char *) nla_data(na);
if (recv_msg == NULL) {
pr_err("error while receiving data\n");
} else {
pr_info("received: '%s'\n", recv_msg);
}
//aquire lock for cmd repmsg channel
// std::lock sendlk(cmd_cb_progress_data.sendmtx);
//send the message to the copro over RPMSG
if (prpdev) {
rc = rpmsg_send(prpdev->ept, recv_msg, strlen(recv_msg));
if (rc) {
pr_err("rpmsg_send failed: %d\n", rc);
return rc;
}
// struct rpmsg_vrpmdv_mon_t *drv = dev_get_drvdata(&rpdev->dev);
// Send a message back after we receive the reply from rpmsg channel
// ---------------------
// Allocate some memory, since the size is not yet known use NLMSG_GOODSIZE
reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (reply_skb == NULL) {
pr_err("An error occurred in %s():\n", __func__);
return -ENOMEM;
}
// Create the message headersprpdev
// Add header to netlink message;
// afterwards the buffer looks like this:
// ----------------------------------
// | netlink header |
// | generic netlink header |
// | <space for netlink attributes> |
// ----------------------------------
// msg_head = genlmsg_put(reply_skb, // buffer for netlink message: struct sk_buff *
// // According to my findings: this is not used for routing
// // This can be used in an application specific way to target
// // different endpoints within the same user application
// // but general rule: just put sender port id here
// info->snd_portid, // sending port (not process) id: int
// info->snd_seq + 1, // sequence number: int (might be used by receiver, but not mandatory)
// &gnl_vrpmdv_mcmd_family, // struct genl_family *
// 0, // flags for Netlink header: int; application specific and not mandatory
// // The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
// GNL_VRPMDV_MCMD_C_MSG
// );
msg_head = genlmsg_put_reply(reply_skb, // buffer for netlink message: struct sk_buff *
info, // info
&gnl_vrpmdv_mcmd_family, // struct genl_family *
0, // flags for Netlink header: int; application specific and not mandatory
// The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
info->genlhdr->cmd
);
if (msg_head == NULL) {
rc = ENOMEM;
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
{
pr_info("wait for response\n");
// wait until receive_queue_flag=1 , that means we have received data from Copro
wait_event_interruptible(receive_queue, receive_queue_flag != 0 );
//Copy data
receive_queue_flag = NODATARECEIVED;
// std::unique_lock lk(cmd_cb_progress_data.receivemtx);
// if (myCondVar.wait_until(uLock, timePoint) == std::cv_status::timeout)
// {
// dev_err(&cmd_cb_progress_data.rpdev, "rpmsg_send failed, timeout: \n");
// return -1:
// }
//pr_info("get response: '%s'\n", recv_msg);
if (received_len > 0) {
pr_info("received data from copro %s\n", received_bytes);
}
else {
pr_err("don't received data from Copro \n");
}
}
// Add a GNL_VRPMDV_MCMD_A_MSG attribute (actual value/payload to be sent)
// echo the value we just received
rc = nla_put_string(reply_skb, GNL_VRPMDV_MCMD_A_MSG, received_bytes);
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
//free the buffer
kfree(received_bytes);
received_bytes = NULL;
return -rc;
}
// Finalize the message:
// Corrects the netlink message header (length) to include the appended
// attributes. Only necessary if attributes have been added to the message.
genlmsg_end(reply_skb, msg_head);
// Send the message back
rc = genlmsg_reply(reply_skb, info);
// same as genlmsg_unicast(genl_info_net(info), reply_skb, info->snd_portid)
// see https://elixir.bootlin.com/linux/v5.8.9/source/include/net/genetlink.h#L326
kfree(received_bytes);
received_bytes = NULL;
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
return 0;
}
pr_info("Device not set in Probe. Should not happen");
return -1;
}
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info) {
pr_info("%s() invoked, a NLMSG_ERR response will be sent back\n", __func__);
/*
* Generic Netlink is smart enough and sends a NLMSG_ERR reply automatically as reply
* Quote from https://lwn.net/Articles/208755/:
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*/
return -EINVAL;
}
/** ----- NETLINK Driver defintion ------------------*/
/**
* callback that is called after the copro send data
* we have to copy it in a buffer for the netlink and later send it back to the userland
*
*/
static int vrpmdv_monitoring_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret = 0;
// struct instance_data *idata = dev_get_drvdata(&rpdev->dev);
// dev_info(&rpdev->dev, "incoming msg %d (src: 0x%x)\n",
// ++idata->rx_count, src);
print_hex_dump_debug(__func__, DUMP_PREFIX_NONE, 16, 1, data, len,
true);
if (len == 0) {
pr_err("(%s) Empty lenght requested\n", __func__);
return -EINVAL;
}
else {
pr_info("received data from copro %s\n", (char*) data);
}
// received_bytes = (char *)kmalloc(len+1, GFP_KERNEL);
// memcpy(received_bytes, data, len);
// rpmsg_RxBuf[len] = 0;
received_bytes = (char *)kmalloc(len, GFP_KERNEL);
memcpy(received_bytes, data, len);
received_len = len;
receive_queue_flag= DATARECEIVED;
wake_up_interruptible(&receive_queue);
// /* samples should not live forever */
// if (idata->rx_count >= count) {
// dev_info(&rpdev->dev, "goodbye!\n");
// return 0;
// }
/* send a new message now */
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
// if (ret)
// dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
static int vrpmdv_monitoring_probe(struct rpmsg_device *rpdev)
{
int rc;
// int ret;
// struct instance_data *idata;
// dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
// rpdev->src, rpdev->dst);
pr_info("RPMSG monitroing control device driver started.\n");
// idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
// if (!idata)
// return -ENOMEM;
// dev_set_drvdata(&rpdev->dev, idata);
// /* send a message to our remote processor to */
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
// if (ret) {
// dev_err(&rpdev->dev, "vrpmdv_monitoring_controler_send failed: %d\n", ret);
// return ret;
// }
// return 0;
// struct device *dev;
// dev = &rpdev->dev;
// struct rpmsg_vrpmdv_mon_t *rpmsg_vrpmdv_mon;
// rpmsg_vrpmdv_mon = devm_kzalloc(dev, sizeof(*rpmsg_vrpmdv_mon), GFP_KERNEL);
// if (!rpmsg_vrpmdv_mon)
// return -ENOMEM;
mutex_init(&sendMTX);
init_waitqueue_head (&receive_queue);
prpdev = rpdev;
// dev_set_drvdata(&rpdev->dev, rpmsg_vrpmdv_mon);
pr_info("RPMSG CMD Device set.\n");
/** NEU **/
// if (cmd_cb_progress_data.rpdev == NULL) {
// cmd_cb_progress_data.rpdev = rpdev;
// pr_info("RPMSG CMD Device set.\n");
// }
// else {
// pr_info("Error: RPMSG CMD Device already set. Don't set it twice\n");
// }
pr_info("Generic Netlink VRPMDV-Monitroring_Controler Module started.\n");
// Register family with its operations and policies
rc = genl_register_family(&gnl_vrpmdv_mcmd_family);
if (rc != 0) {
pr_err("FAILED: genl_register_family(): %i\n", rc);
pr_err("An error occurred while inserting the generic netlink example module\n");
return -1;
} else {
pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
return 0;
}
static void vrpmdv_monitoring_remove(struct rpmsg_device *rpdev)
{
int ret;
pr_info("Generic Netlink Example Module unloaded.\n");
// Unregister the family
ret = genl_unregister_family(&gnl_vrpmdv_mcmd_family);
if (ret != 0) {
pr_err("genl_unregister_family() failed: %i\n", ret);
return;
} else {
pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
mutex_destroy(&sendMTX);
wake_up_interruptible(&receive_queue);
pr_info("vrpmdv-monitoring controler driver is removed\n");
// dev_info(&rpdev->dev, "vrpmdv-monitoring controler driver is removed\n");
}
static struct rpmsg_device_id vrpmdv_monitoring_controler_id_table[] = {
{ .name = "vrpmdv-monitoring-controler" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, vrpmdv_monitoring_controler_id_table);
static struct rpmsg_driver vrpmdv_monitoring_controler = {
.drv.name = KBUILD_MODNAME,
.id_table = vrpmdv_monitoring_controler_id_table,
.probe = vrpmdv_monitoring_probe,
.callback = vrpmdv_monitoring_cb,
.remove = vrpmdv_monitoring_remove,
};
module_rpmsg_driver(vrpmdv_monitoring_controler);
// static struct rpmsg_driver vrpmdv_monitoring_data = {
// .drv.name = KBUILD_MODNAME,
// .id_table = vrpmdv_monitoring_controler_id_table,
// .probe = vrpmdv_monitoring_probe,
// .callback = vrpmdv_monitoring_cb,
// .remove = vrpmdv_monitoring_remove,
// };
// module_rpmsg_driver(vrpmdv_monitoring_data);
MODULE_DESCRIPTION("Remote processor messaging vrpmdv monitoring controler");
MODULE_LICENSE("GPL v2");
// /**
// * Module/driver initializer. Called on module load/insertion.
// *
// * @return success (0) or error code.
// */
// static int __init gnl_foobar_xmpl_module_init(void) {
// int rc;
// pr_info("Generic Netlink Example Module inserted.\n");
// // Register family with its operations and policies
// rc = genl_register_family(&gnl_foobar_xmpl_family);
// if (rc != 0) {
// pr_err("FAILED: genl_register_family(): %i\n", rc);
// pr_err("An error occurred while inserting the generic netlink example module\n");
// return -1;
// } else {
// pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_init(&dumpit_cb_progress_data.mtx);
// return 0;
// }
// /**
// * Module/driver uninitializer. Called on module unload/removal.
// *
// * @return success (0) or error code.
// */
// static void __exit gnl_foobar_xmpl_module_exit(void) {
// int ret;
// pr_info("Generic Netlink Example Module unloaded.\n");
// // Unregister the family
// ret = genl_unregister_family(&gnl_foobar_xmpl_family);
// if (ret != 0) {
// pr_err("genl_unregister_family() failed: %i\n", ret);
// return;
// } else {
// pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_destroy(&dumpit_cb_progress_data.mtx);
// }
// ----
// static int __init rpmsg_sdb_drv_init(void)
// {
// int ret = 0;
// /* Register rpmsg device */
// ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// if (ret) {
// pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
// return ret;
// }
// pr_info("rpmsg_sdb: Init done\n");
// return ret;
// }
// static void __exit rpmsg_sdb_drv_exit(void)
// {
// unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// pr_info("rpmsg_sdb: Exit\n");
// }
// module_init(rpmsg_sdb_drv_init);
// module_exit(rpmsg_sdb_drv_exit);

View File

@@ -0,0 +1,16 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv-monitoring.c \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
RPROVIDES:${PN} += "kernel-module-vrpmdv-monitoring"

View File

@@ -0,0 +1,541 @@
/* Copyright 2021 Philipp Schuster
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in the
* Software without restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies
* or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// ##################################################################################################
/*
* This is a Linux kernel module/driver called "gnl_foobar_xmpl" which shows you the basics of using
* Generic Netlink in the kernel. It registers a Netlink family called "gnl_foobar_xmpl". See
* "gnl_foobar_xmpl_prop.h" for common properties of the family. "Generic Netlink" offers us a lot of
* convenient functions to register new/custom Netlink families on the fly during runtime. We use this
* functionality here. It implements simple IPC between Userland and Kernel (Kernel responds to userland).
*
* Check "gnl_foobar_xmpl_prop.h" for common properties of the family first, afterwards follow the code here.
*
* You can find some more interesting documentation about Generic Netlink here:
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
*/
// basic definitions for kernel module development
#include <linux/module.h>
// definitions for generic netlink families, policies etc;
// transitive dependencies for basic netlink, sockets etc
#include <net/genetlink.h>
// required for locking inside the .dumpit callback demonstration
#include <linux/mutex.h>
// data/vars/enums/properties that describes our protocol that we implement
// on top of generic netlink (like functions we want to trigger on the receiving side)
#include "gnl_foobar_xmpl_prop.h"
// Module/Driver description.
// You can see this for example when executing `$ modinfo ./gnl_foobar_xmpl.ko` (after build).
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Philipp Schuster <phip1611@gmail.com>");
MODULE_DESCRIPTION(
"Linux driver that registers the custom Netlink family "
"\"" FAMILY_NAME "\" via Generic Netlink and responds to echo messages "
"according to the properties specified in \"gnl_foobar_xmpl_prop.h\"."
);
/* ######################## CONVENIENT LOGGING MACROS ######################## */
// (Re)definition of some convenient logging macros from <linux/printk.h>. You can see the logging
// messages when printing the kernel log, e.g. with `$ sudo dmesg`.
// See https://elixir.bootlin.com/linux/latest/source/include/linux/printk.h
// with this redefinition we can easily prefix all log messages from pr_* logging macros
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* ########################################################################### */
/**
* Data structure required for our .dumpit callback handler to
* know about the progress of an ongoing dump.
* See the dumpit callback handler how it is used.
*/
struct {
// from <linux/mutex.h>
/**
* Only one process is allowed per dump process. We need a lock for that.
*/
struct mutex mtx;
/**
* Number that describes how many packets we need to send until we are done
* during an ongoing dumpit process. 0 = done.
*/
int unsigned runs_to_go;
/**
* Number that describes how many packets per dump are sent in total.
* Constant per dump.
*/
int unsigned total_runs;
} dumpit_cb_progress_data;
// Documentation is on the implementation of this function.
int gnl_cb_echo_doit(struct sk_buff *sender_skb, struct genl_info *info);
// Documentation is on the implementation of this function.
int gnl_cb_echo_dumpit(struct sk_buff *pre_allocated_skb, struct netlink_callback *cb);
// Documentation is on the implementation of this function.
int gnl_cb_echo_dumpit_before(struct netlink_callback *cb);
// Documentation is on the implementation of this function.
int gnl_cb_echo_dumpit_before_after(struct netlink_callback *cb);
// Documentation is on the implementation of this function.
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info);
/**
* The length of `struct genl_ops gnl_foobar_xmpl_ops[]`. Not necessarily
* the number of commands in `enum GNlFoobarXmplCommand`. It depends on your application logic.
* For example, you can use the same command multiple times and - dependent by flag -
* invoke a different callback handler. In our simple example we just use one .doit callback
* per operation/command.
*/
#define GNL_FOOBAR_OPS_LEN (GNL_FOOBAR_XMPL_COMMAND_COUNT)
/**
* Array with all operations that our protocol on top of Generic Netlink
* supports. An operation is the glue between a command ("cmd" field in `struct genlmsghdr` of
* received Generic Netlink message) and the corresponding ".doit" callback function.
* See: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L148
*/
struct genl_ops gnl_foobar_xmpl_ops[GNL_FOOBAR_OPS_LEN] = {
{
/* The "cmd" field in `struct genlmsghdr` of received Generic Netlink message */
.cmd = GNL_FOOBAR_XMPL_C_ECHO_MSG,
/* TODO Use case ? */
.flags = 0,
/* TODO Use case ? */
.internal_flags = 0,
/* Callback handler when a request with the specified ".cmd" above is received.
* Always validates the payload except one set NO_STRICT_VALIDATION flag in ".validate"
* See: https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*
* Quote from: https://lwn.net/Articles/208755
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*
*/
.doit = gnl_cb_echo_doit,
/* This callback is similar in use to the standard Netlink 'dumpit' callback.
* The 'dumpit' callback is invoked when a Generic Netlink message is received
* with the NLM_F_DUMP flag set.
*
* A dump can be understand as a "GET ALL DATA OF THE GIVEN ENTITY", i.e.
* the userland can receive as long as the .dumpit callback returns data.
*
* .dumpit is not mandatory, but either it or .doit must be provided, see
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L367
*
* To be honest I don't know in what use case one should use .dumpit and why
* it is useful, because you can achieve the same also with .doit handlers.
* Anyway, this is just an example/tutorial.
*
* Quote from: https://lwn.net/Articles/208755
* "The main difference between a 'dumpit' handler and a 'doit' handler is
* that a 'dumpit' handler does not allocate a message buffer for a response;
* a pre-allocated sk_buff is passed to the 'dumpit' handler as the first
* parameter. The 'dumpit' handler should fill the message buffer with the
* appropriate response message and return the size of the sk_buff,
* i.e. sk_buff->len, and the message buffer will automatically be sent to the
* Generic Netlink client that initiated the request. As long as the 'dumpit'
* handler returns a value greater than zero it will be called again with a
* newly allocated message buffer to fill, when the handler has no more data
* to send it should return zero; error conditions are indicated by returning
* a negative value. If necessary, state can be preserved in the
* netlink_callback parameter which is passed to the 'dumpit' handler; the
* netlink_callback parameter values will be preserved across handler calls
* for a single request."
*
* You can see the check for the NLM_F_DUMP-flag here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L780
*/
.dumpit = gnl_cb_echo_dumpit,
/* Start callback for dumps. Can be used to lock data structures. */
.start = gnl_cb_echo_dumpit_before,
/* Completion callback for dumps. Can be used for cleanup after a dump and releasing locks. */
.done = gnl_cb_echo_dumpit_before_after,
/*
0 (= "validate strictly") or value `enum genl_validate_flags`
* see: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L108
*/
.validate = 0,
},
{
.cmd = GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR,
.flags = 0,
.internal_flags = 0,
.doit = gnl_cb_doit_reply_with_nlmsg_err,
// .dumpit is not required, only optional; application specific/dependent on your use case
// in a real application you probably have different .dumpit handlers per operation/command
.dumpit = NULL,
// in a real application you probably have different .start handlers per operation/command
.start = NULL,
// in a real application you probably have different .done handlers per operation/command
.done = NULL,
.validate = 0,
}
};
/**
* Attribute policy: defines which attribute has which type (e.g int, char * etc).
* This get validated for each received Generic Netlink message, if not deactivated
* in `gnl_foobar_xmpl_ops[].validate`.
* See https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*/
static struct nla_policy gnl_foobar_xmpl_policy[GNL_FOOBAR_XMPL_ATTRIBUTE_ENUM_LEN] = {
// In case you are seeing this syntax for the first time (I also learned this just after a few years of
// coding with C myself): The following static array initiations are equivalent:
// `int a[2] = {1, 2}` <==> `int a[2] = {[0] => 1, [1] => 2}`.
[GNL_FOOBAR_XMPL_A_UNSPEC] = {.type = NLA_UNSPEC},
// You can set this to NLA_U32 for testing and send an ECHO message from the userland
// It will fail in this case and you see a entry in the kernel log.
// `enum GNL_FOOBAR_XMPL_ATTRIBUTE::GNL_FOOBAR_XMPL_A_MSG` is a null-terminated C-String
[GNL_FOOBAR_XMPL_A_MSG] = {.type = NLA_NUL_STRING},
};
/**
* Definition of the Netlink family we want to register using Generic Netlink functionality
*/
static struct genl_family gnl_foobar_xmpl_family = {
// automatically assign an id
.id = 0,
// we don't use custom additional header info / user specific header
.hdrsize = 0,
// The name of this family, used by userspace application to get the numeric ID
.name = FAMILY_NAME,
// family specific version number; can be used to evolve application over time (multiple versions)
.version = 1,
// delegates all incoming requests to callback functions
.ops = gnl_foobar_xmpl_ops,
// length of array `gnl_foobar_xmpl_ops`
.n_ops = GNL_FOOBAR_OPS_LEN,
// attribute policy (for validation of messages). Enforced automatically, except ".validate" in
// corresponding ".ops"-field is set accordingly.
.policy = gnl_foobar_xmpl_policy,
// Number of attributes / bounds check for policy (array length)
.maxattr = GNL_FOOBAR_XMPL_ATTRIBUTE_ENUM_LEN,
// Owning Kernel module of the Netlink family we register.
.module = THIS_MODULE,
// Actually not necessary because this memory region would be zeroed anyway during module load,
// but this way one sees all possible options.
// if your application must handle multiple netlink calls in parallel (where one should not block the next
// from starting), set this to true! otherwise all netlink calls are mutually exclusive
.parallel_ops = 0,
// set to true if the family can handle network namespaces and should be presented in all of them
.netnsok = 0,
// called before an operation's doit callback, it may do additional, common, filtering and return an error
.pre_doit = NULL,
// called after an operation's doit callback, it may undo operations done by pre_doit, for example release locks
.post_doit = NULL,
};
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_ECHO` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_echo_doit(struct sk_buff *sender_skb, struct genl_info *info) {
struct nlattr *na;
struct sk_buff *reply_skb;
int rc;
void *msg_head;
char *recv_msg;
pr_info("%s() invoked\n", __func__);
if (info == NULL) {
// should never happen
pr_err("An error occurred in %s():\n", __func__);
return -EINVAL;
}
/*
* For each attribute there is an index in info->attrs which points to a nlattr structure
* in this structure the data is stored.
*/
na = info->attrs[GNL_FOOBAR_XMPL_A_MSG];
if (!na) {
pr_err("no info->attrs[%i]\n", GNL_FOOBAR_XMPL_A_MSG);
return -EINVAL; // we return here because we expect to recv a msg
}
recv_msg = (char *) nla_data(na);
if (recv_msg == NULL) {
pr_err("error while receiving data\n");
} else {
pr_info("received: '%s'\n", recv_msg);
}
// Send a message back
// ---------------------
// Allocate some memory, since the size is not yet known use NLMSG_GOODSIZE
reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (reply_skb == NULL) {
pr_err("An error occurred in %s():\n", __func__);
return -ENOMEM;
}
// Create the message headers
// Add header to netlink message;
// afterwards the buffer looks like this:
// ----------------------------------
// | netlink header |
// | generic netlink header |
// | <space for netlink attributes> |
// ----------------------------------
msg_head = genlmsg_put(reply_skb, // buffer for netlink message: struct sk_buff *
// According to my findings: this is not used for routing
// This can be used in an application specific way to target
// different endpoints within the same user application
// but general rule: just put sender port id here
info->snd_portid, // sending port (not process) id: int
info->snd_seq + 1, // sequence number: int (might be used by receiver, but not mandatory)
&gnl_foobar_xmpl_family, // struct genl_family *
0, // flags for Netlink header: int; application specific and not mandatory
// The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
GNL_FOOBAR_XMPL_C_ECHO_MSG
);
if (msg_head == NULL) {
rc = ENOMEM;
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Add a GNL_FOOBAR_XMPL_A_MSG attribute (actual value/payload to be sent)
// echo the value we just received
rc = nla_put_string(reply_skb, GNL_FOOBAR_XMPL_A_MSG, recv_msg);
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Finalize the message:
// Corrects the netlink message header (length) to include the appended
// attributes. Only necessary if attributes have been added to the message.
genlmsg_end(reply_skb, msg_head);
// Send the message back
rc = genlmsg_reply(reply_skb, info);
// same as genlmsg_unicast(genl_info_net(info), reply_skb, info->snd_portid)
// see https://elixir.bootlin.com/linux/v5.8.9/source/include/net/genetlink.h#L326
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
return 0;
}
/**
* ".dumpit"-callback function if a Generic Netlink with command ECHO_MSG and flag `NLM_F_DUMP` is received.
* Please look into the comments where this is used as ".dumpit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".dumpit" callbacks.
*
* A dump must be understand of "give me all data of a given entity"
* rather than a "dump of the netlink message itself" for debugging etc!
*
* This handler requires `gnl_cb_echo_dumpit_before` to run before a dump and `gnl_cb_echo_dumpit_after` after a dump.
*
* For the sake of simplicity, we use the ECHO_MSG command for the dump. In fact, we don't expect a
* MSG-Attribute here, unlike the regular ECHO_MSG handler. We reply with a dump of
* "all messages that we got" (application specific, hard coded in this example).
*/
int gnl_cb_echo_dumpit(struct sk_buff *pre_allocated_skb, struct netlink_callback *cb) {
void *msg_head;
int ret;
static const char HELLO_FROM_DUMPIT_MSG[] = "You set the flag NLM_F_DUMP; this message is "
"brought to you by .dumpit callback :)";
pr_info("Called %s()\n", __func__);
if (dumpit_cb_progress_data.runs_to_go == 0) {
pr_info("no more data to send in dumpit cb\n");
// mark that dump is done;
return 0;
} else {
dumpit_cb_progress_data.runs_to_go--;
pr_info("%s: %d more runs to do\n", __func__, dumpit_cb_progress_data.runs_to_go);
}
msg_head = genlmsg_put(pre_allocated_skb, // buffer for netlink message: struct sk_buff *
// According to my findings: this is not used for routing
// This can be used in an application specific way to target
// different endpoints within the same user application
// but general rule: just put sender port id here
cb->nlh->nlmsg_pid, // sending port (not process) id: int
// sequence number: int (might be used by receiver, but not mandatory)
// sequence 0, 1, 2...
dumpit_cb_progress_data.total_runs - dumpit_cb_progress_data.runs_to_go - 1,
&gnl_foobar_xmpl_family, // struct genl_family *
0, // flags: int (for netlink header); we don't check them in the userland; application specific
// this way we can trigger a specific command/callback on the receiving side or imply
// on which type of command we are currently answering; this is application specific
GNL_FOOBAR_XMPL_C_ECHO_MSG // cmd: u8 (for generic netlink header);
);
if (msg_head == NULL) {
pr_info("An error occurred in %s(): genlmsg_put() failed\n", __func__);
return -ENOMEM;
}
ret = nla_put_string(
pre_allocated_skb,
GNL_FOOBAR_XMPL_A_MSG,
HELLO_FROM_DUMPIT_MSG
);
if (ret < 0) {
pr_info("An error occurred in %s():\n", __func__);
return ret;
}
genlmsg_end(pre_allocated_skb, msg_head);
// return the length of data we wrote into the pre-allocated buffer
return pre_allocated_skb->len;
}
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info) {
pr_info("%s() invoked, a NLMSG_ERR response will be sent back\n", __func__);
/*
* Generic Netlink is smart enough and sends a NLMSG_ERR reply automatically as reply
* Quote from https://lwn.net/Articles/208755/:
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*/
return -EINVAL;
}
/**
* Called before a dump with `gnl_cb_echo_dumpit()` starts.
* See where this is assigned in `struct genl_ops gnl_foobar_xmpl_ops[]` as
* `.start` callback for more comments.
*
* @return success (0) or error.
*/
int gnl_cb_echo_dumpit_before(struct netlink_callback *cb) {
int ret;
static int unsigned const dump_runs = 3;
pr_info("%s: dump started. acquire lock. initialize dump runs_to_go (number of receives userland can make) to %d runs\n", __func__, dump_runs);
// Lock the mutex like mutex_lock(), and return 0 if the mutex has been acquired or sleep until the mutex becomes available
// If a signal arrives while waiting for the lock then this function returns -EINTR.
ret = mutex_lock_interruptible(&dumpit_cb_progress_data.mtx);
if (ret != 0) {
pr_err("Failed to get lock!\n");
return ret;
}
dumpit_cb_progress_data.total_runs = dump_runs;
dumpit_cb_progress_data.runs_to_go = dump_runs;
return 0;
}
/**
* Called before a dump with `gnl_cb_echo_dumpit()` starts.
* See where this is assigned in `struct genl_ops gnl_foobar_xmpl_ops[]` as
* `.start` callback for more comments.
*
* @return success (0) or error.
*/
int gnl_cb_echo_dumpit_before_after(struct netlink_callback *cb) {
pr_info("%s: dump done. release lock\n", __func__);
mutex_unlock(&dumpit_cb_progress_data.mtx);
return 0;
}
/**
* Module/driver initializer. Called on module load/insertion.
*
* @return success (0) or error code.
*/
static int __init gnl_foobar_xmpl_module_init(void) {
int rc;
pr_info("Generic Netlink Example Module inserted.\n");
// Register family with its operations and policies
rc = genl_register_family(&gnl_foobar_xmpl_family);
if (rc != 0) {
pr_err("FAILED: genl_register_family(): %i\n", rc);
pr_err("An error occurred while inserting the generic netlink example module\n");
return -1;
} else {
pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
mutex_init(&dumpit_cb_progress_data.mtx);
return 0;
}
/**
* Module/driver uninitializer. Called on module unload/removal.
*
* @return success (0) or error code.
*/
static void __exit gnl_foobar_xmpl_module_exit(void) {
int ret;
pr_info("Generic Netlink Example Module unloaded.\n");
// Unregister the family
ret = genl_unregister_family(&gnl_foobar_xmpl_family);
if (ret != 0) {
pr_err("genl_unregister_family() failed: %i\n", ret);
return;
} else {
pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
mutex_destroy(&dumpit_cb_progress_data.mtx);
}
module_init(gnl_foobar_xmpl_module_init);
module_exit(gnl_foobar_xmpl_module_exit);

View File

@@ -0,0 +1,536 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) STMicroelectronics 2019 - All Rights Reserved
* Author: Jean-Philippe Romain <jean-philippe.romain@st.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#include <linux/miscdevice.h>
#include <linux/eventfd.h>
#include <linux/of_platform.h>
#include <linux/list.h>
#define RPMSG_SDB_DRIVER_VERSION "1.0"
/*
* Static global variables
*/
static const char rpmsg_sdb_driver_name[] = "stm32-rpmsg-sdb";
static int LastBufferId;
struct rpmsg_sdb_ioctl_set_efd {
int bufferId, eventfd;
};
struct rpmsg_sdb_ioctl_get_data_size {
int bufferId;
uint32_t size;
};
/* ioctl numbers */
/* _IOW means userland is writing and kernel is reading */
/* _IOR means userland is reading and kernel is writing */
/* _IOWR means userland and kernel can both read and write */
#define RPMSG_SDB_IOCTL_SET_EFD _IOW('R', 0x00, struct rpmsg_sdb_ioctl_set_efd *)
#define RPMSG_SDB_IOCTL_GET_DATA_SIZE _IOWR('R', 0x01, struct rpmsg_sdb_ioctl_get_data_size *)
struct sdb_buf_t {
int index; /* index of buffer */
size_t size; /* buffer size */
size_t writing_size; /* size of data written by copro */
dma_addr_t paddr; /* physical address*/
void *vaddr; /* virtual address */
void *uaddr; /* mapped address for userland */
struct eventfd_ctx *efd_ctx; /* eventfd context */
struct list_head buflist; /* reference in the buffers list */
};
struct rpmsg_sdb_t {
struct mutex mutex; /* mutex to protect the ioctls */
struct miscdevice mdev; /* misc device ref */
struct rpmsg_device *rpdev; /* handle rpmsg device */
struct list_head buffer_list; /* buffer instances list */
};
struct device *rpmsg_sdb_dev;
static int rpmsg_sdb_format_txbuf_string(struct sdb_buf_t *buffer, char *bufinfo_str, size_t bufinfo_str_size)
{
pr_debug("rpmsg_sdb(%s): Buffer index:%d, addr:%08x, size:%08x\n",
__func__,
buffer->index,
buffer->paddr,
buffer->size);
return snprintf(bufinfo_str, bufinfo_str_size, "B%dA%08xL%08x", buffer->index, buffer->paddr, buffer->size);
}
static long rpmsg_sdb_decode_rxbuf_string(char *rxbuf_str, int *buffer_id, size_t *size)
{
int ret = 0;
char *sub_str;
long bsize;
long bufid;
const char delimiter[2] = {'L','\0'};
pr_debug("rpmsg_sdb(%s): rxbuf_str:%s\n", __func__, rxbuf_str);
/* Get first part containing the buffer id */
sub_str = strsep(&rxbuf_str, delimiter);
pr_debug("rpmsg_sdb(%s): sub_str:%s\n", __func__, sub_str);
/* Save Buffer id and size: template BxLyyyyyyyy*/
ret = kstrtol(&sub_str[1], 10, &bufid);
if (ret < 0) {
pr_err("rpmsg_sdb(ERROR): Extract of buffer id failed(%d)", ret);
goto out;
}
ret = kstrtol(&rxbuf_str[2], 16, &bsize);
if (ret < 0) {
pr_err("rpmsg_sdb(ERROR): Extract of buffer size failed(%d)", ret);
goto out;
}
*size = (size_t)bsize;
*buffer_id = (int)bufid;
out:
return ret;
}
static int rpmsg_sdb_send_buf_info(struct rpmsg_sdb_t *rpmsg_sdb, struct sdb_buf_t *buffer)
{
int count = 0, ret = 0;
const unsigned char *tbuf;
char mybuf[32];
int msg_size;
struct rpmsg_device *_rpdev;
_rpdev = rpmsg_sdb->rpdev;
msg_size = rpmsg_get_buffer_size(_rpdev->ept);
if (msg_size < 0)
return msg_size;
count = rpmsg_sdb_format_txbuf_string(buffer, mybuf, 32);
tbuf = &mybuf[0];
do {
/* send a message to our remote processor */
ret = rpmsg_send(_rpdev->ept, (void *)tbuf,
count > msg_size ? msg_size : count);
if (ret) {
dev_err(&_rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
if (count > msg_size) {
count -= msg_size;
tbuf += msg_size;
} else {
count = 0;
}
} while (count > 0);
return count;
}
static int rpmsg_sdb_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long vsize = vma->vm_end - vma->vm_start;
unsigned long size = PAGE_ALIGN(vsize);
unsigned long NumPages = size >> PAGE_SHIFT;
unsigned long align = get_order(size);
pgprot_t prot = pgprot_noncached(vma->vm_page_prot);
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *_buffer;
if (align > CONFIG_CMA_ALIGNMENT)
align = CONFIG_CMA_ALIGNMENT;
if (rpmsg_sdb_dev == NULL)
return -ENOMEM;
rpmsg_sdb_dev->coherent_dma_mask = DMA_BIT_MASK(32);
rpmsg_sdb_dev->dma_mask = &rpmsg_sdb_dev->coherent_dma_mask;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Field the last buffer entry which is the last one created */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
_buffer = list_last_entry(&_rpmsg_sdb->buffer_list,
struct sdb_buf_t, buflist);
_buffer->uaddr = NULL;
_buffer->size = NumPages * PAGE_SIZE;
_buffer->writing_size = -1;
_buffer->vaddr = dma_alloc_coherent(rpmsg_sdb_dev,
_buffer->size,
&_buffer->paddr,
GFP_KERNEL);
if (!_buffer->vaddr) {
pr_err("rpmsg_sdb(ERROR): Memory allocation issue\n");
return -ENOMEM;
}
pr_debug("rpmsg_sdb(%s): dma_alloc_coherent done - paddr[%d]:%x - vaddr[%d]:%p\n",
__func__,
_buffer->index,
_buffer->paddr,
_buffer->index,
_buffer->vaddr);
/* Get address for userland */
if (remap_pfn_range(vma, vma->vm_start,
(_buffer->paddr >> PAGE_SHIFT) + vma->vm_pgoff,
size, prot))
return -EAGAIN;
_buffer->uaddr = (void *)vma->vm_start;
/* Send information to remote proc */
rpmsg_sdb_send_buf_info(_rpmsg_sdb, _buffer);
} else {
dev_err(rpmsg_sdb_dev, "No existing buffer entry exist in the list !!!");
return -EINVAL;
}
/* Increment for number of requested buffer */
LastBufferId++;
return 0;
}
/**
* rpmsg_sdb_open - Open Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_open(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
/* Initialize the buffer list*/
INIT_LIST_HEAD(&_rpmsg_sdb->buffer_list);
mutex_init(&_rpmsg_sdb->mutex);
return 0;
}
/**
* rpmsg_sdb_close - Close Session
*
* @inode: inode struct
* @file: file struct
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static int rpmsg_sdb_close(struct inode *inode, struct file *file)
{
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *pos, *next;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
list_for_each_entry_safe(pos, next, &_rpmsg_sdb->buffer_list, buflist) {
/* Free the CMA allocation */
pr_debug("rpmsg_sdb(%s): Free the CMA allocation: pos->size:%08x, pos->vaddr:%08x, pos->paddr:%08x\n",
__func__,
pos->size,
pos->vaddr,
pos->paddr);
dma_free_coherent(rpmsg_sdb_dev, pos->size, pos->vaddr,
pos->paddr);
/* Remove the buffer from the list */
list_del(&pos->buflist);
/* Free the buffer */
kfree(pos);
}
/* Reset LastBufferId */
LastBufferId = 0;
return 0;
}
/**
* rpmsg_sdb_ioctl - IOCTL
*
* @session: ibmvmc_file_session struct
* @cmd: cmd field
* @arg: Argument field
*
* Return:
* 0 - Success
* Non-zero - Failure
*/
static long rpmsg_sdb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int idx = 0;
struct rpmsg_sdb_t *_rpmsg_sdb;
struct sdb_buf_t *buffer, *lastbuffer;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_ioctl_set_efd q_set_efd;
struct rpmsg_sdb_ioctl_get_data_size q_get_dat_size;
void __user *argp = (void __user *)arg;
_rpmsg_sdb = container_of(file->private_data, struct rpmsg_sdb_t,
mdev);
switch (cmd) {
case RPMSG_SDB_IOCTL_SET_EFD:
mutex_lock(&_rpmsg_sdb->mutex);
/* Get index from the last buffer in the list */
if (!list_empty(&_rpmsg_sdb->buffer_list)) {
lastbuffer = list_last_entry(&_rpmsg_sdb->buffer_list, struct sdb_buf_t, buflist);
idx = lastbuffer->index;
/* Check last index was properly initiated*/
if (lastbuffer->vaddr == NULL) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - previous buffer was not allocated\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EBADE;
}
/* increment this index for the next buffer creation*/
idx++;
}
if (copy_from_user(&q_set_efd, (struct rpmsg_sdb_ioctl_set_efd *)argp,
sizeof(struct rpmsg_sdb_ioctl_set_efd))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_SET_EFD - copy from user failed\n");
mutex_unlock(&_rpmsg_sdb->mutex);
return -EFAULT;
}
/* create a new buffer which will be added in the buffer list */
buffer = kmalloc(sizeof(struct sdb_buf_t), GFP_KERNEL);
buffer->index = idx;
buffer->vaddr = NULL;
buffer->efd_ctx = eventfd_ctx_fdget(q_set_efd.eventfd);
list_add_tail(&buffer->buflist, &_rpmsg_sdb->buffer_list);
mutex_unlock(&_rpmsg_sdb->mutex);
break;
case RPMSG_SDB_IOCTL_GET_DATA_SIZE:
if (copy_from_user(&q_get_dat_size, (struct rpmsg_sdb_ioctl_get_data_size *)argp,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy from user failed\n");
return -EFAULT;
}
/* Get the index of the requested buffer and then look-up in the buffer list*/
idx = q_get_dat_size.bufferId;
list_for_each(pos, &_rpmsg_sdb->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == idx) {
/* Get the writing size*/
q_get_dat_size.size = datastructureptr->writing_size;
break;
}
}
if (copy_to_user((struct rpmsg_sdb_ioctl_get_data_size *)argp, &q_get_dat_size,
sizeof(struct rpmsg_sdb_ioctl_get_data_size))) {
pr_err("rpmsg_sdb(ERROR): RPMSG_SDB_IOCTL_GET_DATA_SIZE - copy to user failed\n");
return -EFAULT;
}
/* Reset the writing size*/
datastructureptr->writing_size = -1;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct file_operations rpmsg_sdb_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = rpmsg_sdb_ioctl,
.mmap = rpmsg_sdb_mmap,
.open = rpmsg_sdb_open,
.release = rpmsg_sdb_close,
};
static int rpmsg_sdb_drv_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret = 0;
int buffer_id = 0;
size_t buffer_size;
char *rpmsg_RxBuf;
struct list_head *pos;
struct sdb_buf_t *datastructureptr = NULL;
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpdev->dev);
if (len == 0) {
dev_err(rpmsg_sdb_dev, "(%s) Empty lenght requested\n", __func__);
return -EINVAL;
}
//dev_err(rpmsg_sdb_dev, "(%s) lenght: %d\n", __func__,len);
rpmsg_RxBuf = (char *)kmalloc(len+1, GFP_KERNEL);
memcpy(rpmsg_RxBuf, data, len);
rpmsg_RxBuf[len] = 0;
ret = rpmsg_sdb_decode_rxbuf_string(rpmsg_RxBuf, &buffer_id, &buffer_size);
kfree(rpmsg_RxBuf);
if (ret < 0)
goto out;
if (buffer_id > LastBufferId) {
ret = -EINVAL;
goto out;
}
/* Signal to User space application */
list_for_each(pos, &drv->buffer_list)
{
datastructureptr = list_entry(pos, struct sdb_buf_t, buflist);
if (datastructureptr->index == buffer_id) {
datastructureptr->writing_size = buffer_size;
if (datastructureptr->writing_size > datastructureptr->size) {
dev_err(rpmsg_sdb_dev, "(%s) Writing size is bigger than buffer size\n", __func__);
ret = -EINVAL;
goto out;
}
eventfd_signal(datastructureptr->efd_ctx, 1);
break;
}
/* TODO: quid if nothing find during the loop ? */
}
out:
return ret;
}
static int rpmsg_sdb_drv_probe(struct rpmsg_device *rpdev)
{
int ret = 0;
struct device *dev = &rpdev->dev;
struct rpmsg_sdb_t *rpmsg_sdb;
rpmsg_sdb = devm_kzalloc(dev, sizeof(*rpmsg_sdb), GFP_KERNEL);
if (!rpmsg_sdb)
return -ENOMEM;
mutex_init(&rpmsg_sdb->mutex);
rpmsg_sdb->rpdev = rpdev;
rpmsg_sdb->mdev.name = "rpmsg-sdb";
rpmsg_sdb->mdev.minor = MISC_DYNAMIC_MINOR;
rpmsg_sdb->mdev.fops = &rpmsg_sdb_fops;
dev_set_drvdata(&rpdev->dev, rpmsg_sdb);
/* Register misc device */
ret = misc_register(&rpmsg_sdb->mdev);
if (ret) {
dev_err(dev, "Failed to register device\n");
goto err_out;
}
rpmsg_sdb_dev = rpmsg_sdb->mdev.this_device;
dev_info(dev, "%s probed\n", rpmsg_sdb_driver_name);
err_out:
return ret;
}
static void rpmsg_sdb_drv_remove(struct rpmsg_device *rpmsgdev)
{
struct rpmsg_sdb_t *drv = dev_get_drvdata(&rpmsgdev->dev);
misc_deregister(&drv->mdev);
}
static struct rpmsg_device_id rpmsg_driver_sdb_id_table[] = {
{ .name = "rpmsg-sdb-channel" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_sdb_id_table);
static struct rpmsg_driver rpmsg_sdb_rmpsg_drv = {
.drv.name = KBUILD_MODNAME,
.drv.owner = THIS_MODULE,
.id_table = rpmsg_driver_sdb_id_table,
.probe = rpmsg_sdb_drv_probe,
.callback = rpmsg_sdb_drv_cb,
.remove = rpmsg_sdb_drv_remove,
};
static int __init rpmsg_sdb_drv_init(void)
{
int ret = 0;
/* Register rpmsg device */
ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
if (ret) {
pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
return ret;
}
pr_info("rpmsg_sdb: Init done\n");
return ret;
}
static void __exit rpmsg_sdb_drv_exit(void)
{
unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
pr_info("rpmsg_sdb: Exit\n");
}
module_init(rpmsg_sdb_drv_init);
module_exit(rpmsg_sdb_drv_exit);
MODULE_AUTHOR("Jean-Philippe Romain <jean-philippe.romain@st.com>");
MODULE_DESCRIPTION("shared data buffer over RPMSG");
MODULE_VERSION(RPMSG_SDB_DRIVER_VERSION);
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,287 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 STMicroelectronics - All Rights Reserved
*
* The rpmsg tty driver implements serial communication on the RPMsg bus to makes
* possible for user-space programs to send and receive rpmsg messages as a standard
* tty protocol.
*
* The remote processor can instantiate a new tty by requesting a "rpmsg-tty" RPMsg service.
* The "rpmsg-tty" service is directly used for data exchange. No flow control is implemented yet.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#define RPMSG_TTY_NAME "ttyRPMSG"
#define MAX_TTY_RPMSG 32
static DEFINE_IDR(tty_idr); /* tty instance id */
static DEFINE_MUTEX(idr_lock); /* protects tty_idr */
static struct tty_driver *rpmsg_tty_driver;
struct rpmsg_tty_port {
struct tty_port port; /* TTY port data */
int id; /* TTY rpmsg index */
struct rpmsg_device *rpdev; /* rpmsg device */
};
static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src)
{
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
int copied;
if (!len)
return -EINVAL;
copied = tty_insert_flip_string(&cport->port, data, len);
if (copied != len)
dev_err_ratelimited(&rpdev->dev, "Trunc buffer: available space is %d\n", copied);
tty_flip_buffer_push(&cport->port);
return 0;
}
static int rpmsg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
{
struct rpmsg_tty_port *cport = idr_find(&tty_idr, tty->index);
struct tty_port *port;
tty->driver_data = cport;
port = tty_port_get(&cport->port);
return tty_port_install(port, driver, tty);
}
static void rpmsg_tty_cleanup(struct tty_struct *tty)
{
tty_port_put(tty->port);
}
static int rpmsg_tty_open(struct tty_struct *tty, struct file *filp)
{
return tty_port_open(tty->port, tty, filp);
}
static void rpmsg_tty_close(struct tty_struct *tty, struct file *filp)
{
return tty_port_close(tty->port, tty, filp);
}
static int rpmsg_tty_write(struct tty_struct *tty, const u8 *buf, int len)
{
struct rpmsg_tty_port *cport = tty->driver_data;
struct rpmsg_device *rpdev;
int msg_max_size, msg_size;
int ret;
rpdev = cport->rpdev;
msg_max_size = rpmsg_get_mtu(rpdev->ept);
if (msg_max_size < 0)
return msg_max_size;
msg_size = min(len, msg_max_size);
/*
* Use rpmsg_trysend instead of rpmsg_send to send the message so the caller is not
* hung until a rpmsg buffer is available. In such case rpmsg_trysend returns -ENOMEM.
*/
ret = rpmsg_trysend(rpdev->ept, (void *)buf, msg_size);
if (ret) {
dev_dbg_ratelimited(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
return msg_size;
}
static unsigned int rpmsg_tty_write_room(struct tty_struct *tty)
{
struct rpmsg_tty_port *cport = tty->driver_data;
int size;
size = rpmsg_get_mtu(cport->rpdev->ept);
if (size < 0)
return 0;
return size;
}
static void rpmsg_tty_hangup(struct tty_struct *tty)
{
tty_port_hangup(tty->port);
}
static const struct tty_operations rpmsg_tty_ops = {
.install = rpmsg_tty_install,
.open = rpmsg_tty_open,
.close = rpmsg_tty_close,
.write = rpmsg_tty_write,
.write_room = rpmsg_tty_write_room,
.hangup = rpmsg_tty_hangup,
.cleanup = rpmsg_tty_cleanup,
};
static struct rpmsg_tty_port *rpmsg_tty_alloc_cport(void)
{
struct rpmsg_tty_port *cport;
int ret;
cport = kzalloc(sizeof(*cport), GFP_KERNEL);
if (!cport)
return ERR_PTR(-ENOMEM);
mutex_lock(&idr_lock);
ret = idr_alloc(&tty_idr, cport, 0, MAX_TTY_RPMSG, GFP_KERNEL);
mutex_unlock(&idr_lock);
if (ret < 0) {
kfree(cport);
return ERR_PTR(ret);
}
cport->id = ret;
return cport;
}
static void rpmsg_tty_destruct_port(struct tty_port *port)
{
struct rpmsg_tty_port *cport = container_of(port, struct rpmsg_tty_port, port);
mutex_lock(&idr_lock);
idr_remove(&tty_idr, cport->id);
mutex_unlock(&idr_lock);
kfree(cport);
}
static const struct tty_port_operations rpmsg_tty_port_ops = {
.destruct = rpmsg_tty_destruct_port,
};
static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
{
struct rpmsg_tty_port *cport;
struct device *dev = &rpdev->dev;
struct device *tty_dev;
int ret;
cport = rpmsg_tty_alloc_cport();
if (IS_ERR(cport))
return dev_err_probe(dev, PTR_ERR(cport), "Failed to alloc tty port\n");
tty_port_init(&cport->port);
cport->port.ops = &rpmsg_tty_port_ops;
tty_dev = tty_port_register_device(&cport->port, rpmsg_tty_driver,
cport->id, dev);
if (IS_ERR(tty_dev)) {
ret = dev_err_probe(dev, PTR_ERR(tty_dev), "Failed to register tty port\n");
tty_port_put(&cport->port);
return ret;
}
cport->rpdev = rpdev;
dev_set_drvdata(dev, cport);
dev_dbg(dev, "New channel: 0x%x -> 0x%x: " RPMSG_TTY_NAME "%d\n",
rpdev->src, rpdev->dst, cport->id);
return 0;
}
static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
{
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
dev_dbg(&rpdev->dev, "Removing rpmsg tty device %d\n", cport->id);
/* User hang up to release the tty */
tty_port_tty_hangup(&cport->port, false);
tty_unregister_device(rpmsg_tty_driver, cport->id);
tty_port_put(&cport->port);
}
static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = {
{ .name = "rpmsg-tty" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_tty_id_table);
static struct rpmsg_driver rpmsg_tty_rpmsg_drv = {
.drv.name = KBUILD_MODNAME,
.id_table = rpmsg_driver_tty_id_table,
.probe = rpmsg_tty_probe,
.callback = rpmsg_tty_cb,
.remove = rpmsg_tty_remove,
};
static int __init rpmsg_tty_init(void)
{
int ret;
rpmsg_tty_driver = tty_alloc_driver(MAX_TTY_RPMSG, TTY_DRIVER_REAL_RAW |
TTY_DRIVER_DYNAMIC_DEV);
if (IS_ERR(rpmsg_tty_driver))
return PTR_ERR(rpmsg_tty_driver);
rpmsg_tty_driver->driver_name = "rpmsg_tty";
rpmsg_tty_driver->name = RPMSG_TTY_NAME;
rpmsg_tty_driver->major = 0;
rpmsg_tty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
/* Disable unused mode by default */
rpmsg_tty_driver->init_termios = tty_std_termios;
rpmsg_tty_driver->init_termios.c_lflag &= ~(ECHO | ICANON);
rpmsg_tty_driver->init_termios.c_oflag &= ~(OPOST | ONLCR);
tty_set_operations(rpmsg_tty_driver, &rpmsg_tty_ops);
ret = tty_register_driver(rpmsg_tty_driver);
if (ret < 0) {
pr_err("Couldn't install driver: %d\n", ret);
goto error_put;
}
ret = register_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
if (ret < 0) {
pr_err("Couldn't register driver: %d\n", ret);
goto error_unregister;
}
return 0;
error_unregister:
tty_unregister_driver(rpmsg_tty_driver);
error_put:
tty_driver_kref_put(rpmsg_tty_driver);
return ret;
}
static void __exit rpmsg_tty_exit(void)
{
unregister_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
tty_unregister_driver(rpmsg_tty_driver);
tty_driver_kref_put(rpmsg_tty_driver);
idr_destroy(&tty_idr);
}
module_init(rpmsg_tty_init);
module_exit(rpmsg_tty_exit);
MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>");
MODULE_DESCRIPTION("remote processor messaging tty driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,804 @@
/* Copyright 2024 Markus Lehr
*
*
* This Software is owned by Markus Lehr.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// ##################################################################################################
/*
* This is a Linux kernel module/driver called "vrpmdv-monitoring-cmd " which holds the family ID
* and functions to handle the monitoring in a Generic Netlink in the kernel.
* "It registers a Netlink family called "vrpmdv-monitoring_cmd".
*
*
* You can find some more interesting documentation about Generic Netlink here:
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
*/
#include <linux/kernel.h>
#include <linux/rpmsg.h>
// basic definitions for kernel module development
#include <linux/module.h>
// definitions for generic netlink families, policies etc;
// transitive dependencies for basic netlink, sockets etc
#include <net/genetlink.h>
// required for locking inside the .dumpit callback demonstration
#include <linux/mutex.h>
#include <linux/wait.h>
// data/vars/enums/properties that describes our protocol that we implement
// on top of generic netlink (like functions we want to trigger on the receiving side)
//#include "vrpmdv-monitoring-cmd.h"
/* ######################## CONVENIENT LOGGING MACROS ######################## */
// (Re)definition of some convenient logging macros from <linux/printk.h>. You can see the logging
// messages when printing the kernel log, e.g. with `$ sudo dmesg`.
// See https://elixir.bootlin.com/linux/latest/source/include/linux/printk.h
// with this redefinition we can easily prefix all log messages from pr_* logging macros
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* ########################################################################### */
#define MSG "hello Monitoring!"
static int count = 100;
module_param(count, int, 0644);
struct instance_data {
int rx_count;
};
// std::chrono::seconds timeoutPeriod = 5;
// auto timePoint = std::chrono::system_clock::now() + timeoutPeriod;
/** ----- NETLINK Driver defintion ------------------*/
/**
* Generic Netlink will create a Netlink family with this name. Kernel will asign
* a numeric ID and afterwards we can talk to the family with its ID. To get
* the ID we use Generic Netlink in the userland and pass the family name.
*
* Short for: Generic Netlink VRPMDV Monitoring gnl_foobar_mcmd
*/
#define FAMILY_NAME "gnl-vrpmdv-mcmd"
/**
* These are the attributes that we want to share in gnl_foobar_xmpl.
* You can understand an attribute as a semantic type. This is
* the payload of Netlink messages.
* GNl: Generic Netlink
*/
enum GNL_VRPMDV_XMPL_ATTRIBUTE {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_A_UNSPEC,
/** We expect a MSG to be a null-terminated C-string. */
GNL_VRPMDV_MCMD_A_MSG,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_A_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN (__GNL_VRPMDV_MCMD_A_MAX)
/**
* The number of actual usable attributes in `enum GNL_VRPMDV_MCMD_ATTRIBUTE`.
* This is `GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_ATTRIBUTE_COUNT (GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN - 1)
/**
* Enumeration of all commands (functions) that our custom protocol on top
* of generic netlink supports. This can be understood as the action that
* we want to trigger on the receiving side.
*/
enum GNL_VRPMDV_MCMD_COMMAND {
/**
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
* We do the same, although I'm not sure, if this is really enforced by code.
*/
GNL_VRPMDV_MCMD_C_UNSPEC,
// first real command is "1" (>0)
/**
* When this command is received, we expect the attribute `GNL_VRPMDV_MCMD_ATTRIBUTE::GNL_VRPMDV_MCMD_A_MSG` to
* be present in the Generic Netlink request message. The kernel reads the message from the packet and
* sent it to the copro. THe result will be return by creating a new Generic Netlink response message
* with an corresponding attribute/payload.
*
* This command/signaling mechanism is independent of the Netlink flag `NLM_F_ECHO (0x08)`. We use it as
* "echo specific data" instead of return a 1:1 copy of the package, which you could do with
* `NLM_F_ECHO (0x08)` for example.
*/
GNL_VRPMDV_MCMD_C_MSG,
/**
* Provokes a NLMSG_ERR answer to this request as described in netlink manpage
* (https://man7.org/linux/man-pages/man7/netlink.7.html).
*/
GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
/** Unused marker field to get the length/count of enum entries. No real attribute. */
__GNL_VRPMDV_MCMD_C_MAX,
};
/**
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
*/
#define GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN (__GNL_VRPMDV_MCMD_C_MAX)
/**
* The number of actual usable commands in `enum GNL_FOOBAR_XMPL_COMMAND`.
* This is `GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN` - 1 because "UNSPEC" is never used.
*/
#define GNL_VRPMDV_MCMD_COMMAND_COUNT (GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN - 1)
#define NODATARECEIVED 0
#define DATARECEIVED 1
/**
* Data structure required for our .doit callback handler to
* know about the progress of an ongoing cmd execution.
* See the cmd callback handler how it is used.
*/
// struct {
// // from <linux/mutex.h>
// /**
// * rpmsg wait for response from copro side.
// */
// struct mutex sendMTX;
// /**
// * rpmsg wait for response from copro side.
// */
// struct mutex receiveCV;
// /**
// * Wait Queue: if it is signaled we have received data from copro
// */
// wait_queue_head_t receive_queue;
// /**
// * Waitflag: 0= no data received, 1 = data received
// */
// int receive_queue_flag = NODATARECEIVED;
// /**
// * Condition vaiable signal we have received data from copro
// */
// // std::condition_variable cv;
// // /**
// // * Number that describes how many packets we need to send until we are done
// // * during an ongoing dumpit process. 0 = done.
// // */
// // int unsigned runs_to_go;
// // /**
// // * Number that describes how many packets per dump are sent in total.
// // * Constant per dump.
// // */
// // int unsigned total_runs;
// //the rpmsg device which sends the data to the copro
// struct rpmsg_device *rpdev; /* handle rpmsg device */
// } cmd_cb_progress_data;
struct rpmsg_vrpmdv_mon_t{
// from <linux/mutex.h>
/**
* rpmsg wait for response from copro side.
*/
struct mutex sendMTX;
/**
* rpmsg wait for response from copro side.
*/
struct mutex receiveCV;
/**
* Wait Queue: if it is signaled we have received data from copro
*/
wait_queue_head_t receive_queue;
/**
* Waitflag: 0= no data received, 1 = data received
*/
int receive_queue_flag;
//the rpmsg device which sends the data to the copro
struct rpmsg_device* rpdev; /* handle rpmsg device */
};
struct rpmsg_vrpmdv_mon_t vrpmdv_mon;
// struct mutex mutex; /* mutex to protect the ioctls */
// struct miscdevice mdev; /* misc device ref */
// struct rpmsg_device *rpdev; /* handle rpmsg device */
// struct list_head buffer_list; /* buffer instances list */
// Documentation is on the implementation of this function.
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info);
// Documentation is on the implementation of this function.
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info);
/**
* The length of `struct genl_ops gnl_foobar_xmpl_ops[]`. Not necessarily
* the number of commands in `enum GNlFoobarXmplCommand`. It depends on your application logic.
* For example, you can use the same command multiple times and - dependent by flag -
* invoke a different callback handler. In our simple example we just use one .doit callback
* per operation/command.
*/
#define GNL_VRPMDV_OPS_LEN (GNL_VRPMDV_MCMD_COMMAND_COUNT)
/**
* Array with all operations that our protocol on top of Generic Netlink
* supports. An operation is the glue between a command ("cmd" field in `struct genlmsghdr` of
* received Generic Netlink message) and the corresponding ".doit" callback function.
* See: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L148
*/
struct genl_ops gnl_vrpmdv_mcmd_ops[GNL_VRPMDV_OPS_LEN] = {
{
/* The "cmd" field in `struct genlmsghdr` of received Generic Netlink message */
.cmd = GNL_VRPMDV_MCMD_C_MSG,
/* TODO Use case ? */
.flags = 0,
/* TODO Use case ? */
.internal_flags = 0,
/* Callback handler when a request with the specified ".cmd" above is received.
* Always validates the payload except one set NO_STRICT_VALIDATION flag in ".validate"
* See: https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*
* Quote from: https://lwn.net/Articles/208755
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*
*/
.doit = gnl_cb_vrpmdv_doit,
/* This callback is similar in use to the standard Netlink 'dumpit' callback.
* The 'dumpit' callback is invoked when a Generic Netlink message is received
* with the NLM_F_DUMP flag set.
*
* A dump can be understand as a "GET ALL DATA OF THE GIVEN ENTITY", i.e.
* the userland can receive as long as the .dumpit callback returns data.
*
* .dumpit is not mandatory, but either it or .doit must be provided, see
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L367
*
* To be honest I don't know in what use case one should use .dumpit and why
* it is useful, because you can achieve the same also with .doit handlers.
* Anyway, this is just an example/tutorial.
*
* Quote from: https://lwn.net/Articles/208755
* "The main difference between a 'dumpit' handler and a 'doit' handler is
* that a 'dumpit' handler does not allocate a message buffer for a response;
* a pre-allocated sk_buff is passed to the 'dumpit' handler as the first
* parameter. The 'dumpit' handler should fill the message buffer with the
* appropriate response message and return the size of the sk_buff,
* i.e. sk_buff->len, and the message buffer will automatically be sent to the
* Generic Netlink client that initiated the request. As long as the 'dumpit'
* handler returns a value greater than zero it will be called again with a
* newly allocated message buffer to fill, when the handler has no more data
* to send it should return zero; error conditions are indicated by returning
* a negative value. If necessary, state can be preserved in the
* netlink_callback parameter which is passed to the 'dumpit' handler; the
* netlink_callback parameter values will be preserved across handler calls
* for a single request."
*
* You can see the check for the NLM_F_DUMP-flag here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L780
*/
.dumpit = NULL, //gnl_cb_echo_dumpit,
/* Start callback for dumps. Can be used to lock data structures. */
.start = NULL, //gnl_cb_echo_dumpit_before,
/* Completion callback for dumps. Can be used for cleanup after a dump and releasing locks. */
.done = NULL, //gnl_cb_echo_dumpit_before_after,
/*
0 (= "validate strictly") or value `enum genl_validate_flags`
* see: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L108
*/
.validate = 0,
},
{
.cmd = GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
.flags = 0,
.internal_flags = 0,
.doit = gnl_cb_doit_reply_with_nlmsg_err,
// .dumpit is not required, only optional; application specific/dependent on your use case
// in a real application you probably have different .dumpit handlers per operation/command
.dumpit = NULL,
// in a real application you probably have different .start handlers per operation/command
.start = NULL,
// in a real application you probably have different .done handlers per operation/command
.done = NULL,
.validate = 0,
}
};
/**
* Attribute policy: defines which attribute has which type (e.g int, char * etc).
* This get validated for each received Generic Netlink message, if not deactivated
* in `gnl_foobar_xmpl_ops[].validate`.
* See https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
*/
static struct nla_policy gnl_vrpmdv_mcmd_policy[GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN] = {
// In case you are seeing this syntax for the first time (I also learned this just after a few years of
// coding with C myself): The following static array initiations are equivalent:
// `int a[2] = {1, 2}` <==> `int a[2] = {[0] => 1, [1] => 2}`.
[GNL_VRPMDV_MCMD_A_UNSPEC] = {.type = NLA_UNSPEC},
// You can set this to NLA_U32 for testing and send an ECHO message from the userland
// It will fail in this case and you see a entry in the kernel log.
// `enum GNL_FOOBAR_XMPL_ATTRIBUTE::GNL_FOOBAR_XMPL_A_MSG` is a null-terminated C-String
[GNL_VRPMDV_MCMD_A_MSG] = {.type = NLA_NUL_STRING},
};
/**
* Definition of the Netlink family we want to register using Generic Netlink functionality
*/
static struct genl_family gnl_vrpmdv_mcmd_family = {
// automatically assign an id
.id = 0,
// we don't use custom additional header info / user specific header
.hdrsize = 0,
// The name of this family, used by userspace application to get the numeric ID
.name = FAMILY_NAME,
// family specific version number; can be used to evolve application over time (multiple versions)
.version = 1,
// delegates all incoming requests to callback functions
.ops = gnl_vrpmdv_mcmd_ops,
// length of array `gnl_foobar_xmpl_ops`
.n_ops = GNL_VRPMDV_OPS_LEN,
// attribute policy (for validation of messages). Enforced automatically, except ".validate" in
// corresponding ".ops"-field is set accordingly.
.policy = gnl_vrpmdv_mcmd_policy,
// Number of attributes / bounds check for policy (array length)
.maxattr = GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN,
// Owning Kernel module of the Netlink family we register.
.module = THIS_MODULE,
// Actually not necessary because this memory region would be zeroed anyway during module load,
// but this way one sees all possible options.
// if your application must handle multiple netlink calls in parallel (where one should not block the next
// from starting), set this to true! otherwise all netlink calls are mutually exclusive
.parallel_ops = 0,
// set to true if the family can handle network namespaces and should be presented in all of them
.netnsok = 0,
// called before an operation's doit callback, it may do additional, common, filtering and return an error
.pre_doit = NULL,
// called after an operation's doit callback, it may undo operations done by pre_doit, for example release locks
.post_doit = NULL,
};
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_VRPMDV_MCMD_C_MSG` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_vrpmdv_mcmd_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info) {
struct nlattr *na;
struct sk_buff *reply_skb;
int rc;
void *msg_head;
char *recv_msg;
pr_info("%s() invoked\n", __func__);
if (info == NULL) {
// should never happen
pr_err("An error occurred in %s():\n", __func__);
return -EINVAL;
}
/*
* For each attribute there is an index in info->attrs which points to a nlattr structure
* in this structure the data is stored.
*/
na = info->attrs[GNL_VRPMDV_MCMD_A_MSG];
if (!na) {
pr_err("no info->attrs[%i]\n", GNL_VRPMDV_MCMD_A_MSG);
return -EINVAL; // we return here because we expect to recv a msg
}
recv_msg = (char *) nla_data(na);
if (recv_msg == NULL) {
pr_err("error while receiving data\n");
} else {
pr_info("received: '%s'\n", recv_msg);
}
//aquire lock for cmd repmsg channel
// std::lock sendlk(cmd_cb_progress_data.sendmtx);
//send the message to the copro over RPMSG
rc = rpmsg_send(vrpmdv_mon.rpdev->ept, recv_msg, strlen(recv_msg));
if (rc) {
pr_err("rpmsg_send failed: %d\n", rc);
return rc;
}
// struct rpmsg_vrpmdv_mon_t *drv = dev_get_drvdata(&rpdev->dev);
{
pr_info("wait for response\n");
// wait until receive_queue_flag=1 , that means we have received data from Copro
wait_event_interruptible(vrpmdv_mon.receive_queue, vrpmdv_mon.receive_queue_flag != 0 );
pr_info("received data \n");
//Copy data
vrpmdv_mon.receive_queue_flag = NODATARECEIVED;
// std::unique_lock lk(cmd_cb_progress_data.receivemtx);
// if (myCondVar.wait_until(uLock, timePoint) == std::cv_status::timeout)
// {
// dev_err(&cmd_cb_progress_data.rpdev, "rpmsg_send failed, timeout: \n");
// return -1:
// }
pr_info("get response: '%s'\n", recv_msg);
}
// Send a message back after we receive the reply from rpmsg channel
// ---------------------
// Allocate some memory, since the size is not yet known use NLMSG_GOODSIZE
reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (reply_skb == NULL) {
pr_err("An error occurred in %s():\n", __func__);
return -ENOMEM;
}
// Create the message headers
// Add header to netlink message;
// afterwards the buffer looks like this:
// ----------------------------------
// | netlink header |
// | generic netlink header |
// | <space for netlink attributes> |
// ----------------------------------
// msg_head = genlmsg_put(reply_skb, // buffer for netlink message: struct sk_buff *
// // According to my findings: this is not used for routing
// // This can be used in an application specific way to target
// // different endpoints within the same user application
// // but general rule: just put sender port id here
// info->snd_portid, // sending port (not process) id: int
// info->snd_seq + 1, // sequence number: int (might be used by receiver, but not mandatory)
// &gnl_vrpmdv_mcmd_family, // struct genl_family *
// 0, // flags for Netlink header: int; application specific and not mandatory
// // The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
// GNL_VRPMDV_MCMD_C_MSG
// );
msg_head = genlmsg_put_reply(reply_skb, // buffer for netlink message: struct sk_buff *
info, // info
&gnl_vrpmdv_mcmd_family, // struct genl_family *
0, // flags for Netlink header: int; application specific and not mandatory
// The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
info->genlhdr->cmd
);
if (msg_head == NULL) {
rc = ENOMEM;
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Add a GNL_VRPMDV_MCMD_A_MSG attribute (actual value/payload to be sent)
// echo the value we just received
rc = nla_put_string(reply_skb, GNL_VRPMDV_MCMD_A_MSG, recv_msg);
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
// Finalize the message:
// Corrects the netlink message header (length) to include the appended
// attributes. Only necessary if attributes have been added to the message.
genlmsg_end(reply_skb, msg_head);
// Send the message back
rc = genlmsg_reply(reply_skb, info);
// same as genlmsg_unicast(genl_info_net(info), reply_skb, info->snd_portid)
// see https://elixir.bootlin.com/linux/v5.8.9/source/include/net/genetlink.h#L326
if (rc != 0) {
pr_err("An error occurred in %s():\n", __func__);
return -rc;
}
return 0;
}
/**
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR` is received.
* Please look into the comments where this is used as ".doit" callback above in
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
*/
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info) {
pr_info("%s() invoked, a NLMSG_ERR response will be sent back\n", __func__);
/*
* Generic Netlink is smart enough and sends a NLMSG_ERR reply automatically as reply
* Quote from https://lwn.net/Articles/208755/:
* "The 'doit' handler should do whatever processing is necessary and return
* zero on success, or a negative value on failure. Negative return values
* will cause a NLMSG_ERROR message to be sent while a zero return value will
* only cause a NLMSG_ERROR message to be sent if the request is received with
* the NLM_F_ACK flag set."
*
* You can find this in Linux code here:
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
*
* One can find more information about NLMSG_ERROR responses and how to handle them
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
*/
return -EINVAL;
}
/** ----- NETLINK Driver defintion ------------------*/
/**
* callback that is called after the copro send data
* we have to copy it in a buffer for the netlink and later send it back to the userland
*
*/
static int vrpmdv_monitoring_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret = 0;
struct instance_data *idata = dev_get_drvdata(&rpdev->dev);
dev_info(&rpdev->dev, "incoming msg %d (src: 0x%x)\n",
++idata->rx_count, src);
print_hex_dump_debug(__func__, DUMP_PREFIX_NONE, 16, 1, data, len,
true);
vrpmdv_mon.receive_queue_flag= DATARECEIVED;
wake_up_interruptible(&(vrpmdv_mon.receive_queue));
// /* samples should not live forever */
// if (idata->rx_count >= count) {
// dev_info(&rpdev->dev, "goodbye!\n");
// return 0;
// }
/* send a new message now */
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
// if (ret)
// dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
static int vrpmdv_monitoring_probe(struct rpmsg_device *rpdev)
{
int rc;
// int ret;
// struct instance_data *idata;
dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
rpdev->src, rpdev->dst);
// idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
// if (!idata)
// return -ENOMEM;
// dev_set_drvdata(&rpdev->dev, idata);
// /* send a message to our remote processor to */
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
// if (ret) {
// dev_err(&rpdev->dev, "vrpmdv_monitoring_controler_send failed: %d\n", ret);
// return ret;
// }
// return 0;
// struct device *dev;
// dev = &rpdev->dev;
// struct rpmsg_vrpmdv_mon_t *rpmsg_vrpmdv_mon;
// rpmsg_vrpmdv_mon = devm_kzalloc(dev, sizeof(*rpmsg_vrpmdv_mon), GFP_KERNEL);
// if (!rpmsg_vrpmdv_mon)
// return -ENOMEM;
mutex_init(&(vrpmdv_mon.sendMTX));
init_waitqueue_head (&(vrpmdv_mon.receive_queue));
vrpmdv_mon.rpdev = rpdev;
// dev_set_drvdata(&rpdev->dev, rpmsg_vrpmdv_mon);
pr_info("RPMSG CMD Device set.\n");
/** NEU **/
// if (cmd_cb_progress_data.rpdev == NULL) {
// cmd_cb_progress_data.rpdev = rpdev;
// pr_info("RPMSG CMD Device set.\n");
// }
// else {
// pr_info("Error: RPMSG CMD Device already set. Don't set it twice\n");
// }
pr_info("Generic Netlink VRPMDV-Monitroring_Controler Module started.\n");
// Register family with its operations and policies
rc = genl_register_family(&gnl_vrpmdv_mcmd_family);
if (rc != 0) {
pr_err("FAILED: genl_register_family(): %i\n", rc);
pr_err("An error occurred while inserting the generic netlink example module\n");
return -1;
} else {
pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
return 0;
}
static void vrpmdv_monitoring_remove(struct rpmsg_device *rpdev)
{
int ret;
pr_info("Generic Netlink Example Module unloaded.\n");
// Unregister the family
ret = genl_unregister_family(&gnl_vrpmdv_mcmd_family);
if (ret != 0) {
pr_err("genl_unregister_family() failed: %i\n", ret);
return;
} else {
pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
}
mutex_destroy(&(vrpmdv_mon.sendMTX));
wake_up_interruptible(&(vrpmdv_mon.receive_queue));
dev_info(&rpdev->dev, "vrpmdv-monitoring controler driver is removed\n");
}
static struct rpmsg_device_id vrpmdv_monitoring_controler_id_table[] = {
{ .name = "vrpmdv-monitoring-controler" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, vrpmdv_monitoring_controler_id_table);
static struct rpmsg_driver vrpmdv_monitoring_controler = {
.drv.name = KBUILD_MODNAME,
.id_table = vrpmdv_monitoring_controler_id_table,
.probe = vrpmdv_monitoring_probe,
.callback = vrpmdv_monitoring_cb,
.remove = vrpmdv_monitoring_remove,
};
module_rpmsg_driver(vrpmdv_monitoring_controler);
// static struct rpmsg_driver vrpmdv_monitoring_data = {
// .drv.name = KBUILD_MODNAME,
// .id_table = vrpmdv_monitoring_controler_id_table,
// .probe = vrpmdv_monitoring_probe,
// .callback = vrpmdv_monitoring_cb,
// .remove = vrpmdv_monitoring_remove,
// };
// module_rpmsg_driver(vrpmdv_monitoring_data);
MODULE_DESCRIPTION("Remote processor messaging vrpmdv monitoring controler");
MODULE_LICENSE("GPL v2");
// /**
// * Module/driver initializer. Called on module load/insertion.
// *
// * @return success (0) or error code.
// */
// static int __init gnl_foobar_xmpl_module_init(void) {
// int rc;
// pr_info("Generic Netlink Example Module inserted.\n");
// // Register family with its operations and policies
// rc = genl_register_family(&gnl_foobar_xmpl_family);
// if (rc != 0) {
// pr_err("FAILED: genl_register_family(): %i\n", rc);
// pr_err("An error occurred while inserting the generic netlink example module\n");
// return -1;
// } else {
// pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_init(&dumpit_cb_progress_data.mtx);
// return 0;
// }
// /**
// * Module/driver uninitializer. Called on module unload/removal.
// *
// * @return success (0) or error code.
// */
// static void __exit gnl_foobar_xmpl_module_exit(void) {
// int ret;
// pr_info("Generic Netlink Example Module unloaded.\n");
// // Unregister the family
// ret = genl_unregister_family(&gnl_foobar_xmpl_family);
// if (ret != 0) {
// pr_err("genl_unregister_family() failed: %i\n", ret);
// return;
// } else {
// pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
// }
// mutex_destroy(&dumpit_cb_progress_data.mtx);
// }
// ----
// static int __init rpmsg_sdb_drv_init(void)
// {
// int ret = 0;
// /* Register rpmsg device */
// ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// if (ret) {
// pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
// return ret;
// }
// pr_info("rpmsg_sdb: Init done\n");
// return ret;
// }
// static void __exit rpmsg_sdb_drv_exit(void)
// {
// unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
// pr_info("rpmsg_sdb: Exit\n");
// }
// module_init(rpmsg_sdb_drv_init);
// module_exit(rpmsg_sdb_drv_exit);

View File

@@ -0,0 +1,24 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "CLOSED"
# LICENSE = "GPL-2.0-only"
# LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://stm32_rpmsg_sdb.c \
"
# file://75-rpmsg-sdb.rules \
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
# do_install:append() {
# udev rules for rpmsg-sdb
# install -d ${D}${sysconfdir}/udev/rules.d/
# install -m 0644 ${WORKDIR}/75-rpmsg-sdb.rules ${D}${sysconfdir}/udev/rules.d/75-rpmsg-sdb.rules
# }
#FILES:${PN} += "${sysconfdir}/udev/rules.d/"
RPROVIDES:${PN} += "kernel-module-vrpmdv-monitoring-driver"

View File

@@ -0,0 +1,26 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv-monitoring.c \
file://75-vrpmdv-monitoring.rules \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
do_install:append() {
# udev rules for vrpmdv-monitoring
install -d ${D}${sysconfdir}/udev/rules.d/
install -m 0644 ${WORKDIR}/75-vrpmdv-monitoring.rules ${D}${sysconfdir}/udev/rules.d/75-vrpmdv-monitoring.rules
}
FILES:${PN} += "${sysconfdir}/udev/rules.d/"
RPROVIDES:${PN} += "kernel-module-vrpmdv-monitoring"

View File

@@ -0,0 +1,26 @@
SUMMARY = "VRPMDV Monitoring Driver"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv-monitoring.c \
file://75-vrpmdv-monitoring.rules \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
do_install:append() {
# udev rules for vrpmdv-monitoring
install -d ${D}${sysconfdir}/udev/rules.d/
install -m 0644 ${WORKDIR}/75-vrpmdv-monitoring.rules ${D}${sysconfdir}/udev/rules.d/75-vrpmdv-monitoring.rules
}
FILES:${PN} += "${sysconfdir}/udev/rules.d/"
FILES:${PN} += "${base_libdir}/modules/"
RPROVIDES:${PN} += "vrpmdv-monitoring"

View File

@@ -0,0 +1,17 @@
SUMMARY = "VRPMDV Monitoring Controler"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://Makefile \
file://vrpmdv-monitoring-controler.c \
"
S = "${WORKDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
RPROVIDES:${PN} += "kernel-module-vrpmdv-monitoring-controler"

View File

@@ -0,0 +1,7 @@
{
"files.associations": {
"rpmsg.h": "c",
"types.h": "c",
"ioctl.h": "c"
}
}

Some files were not shown because too many files have changed in this diff Show More