added my Recipes
This commit is contained in:
17
meta-st/meta-st-openstlinux/recipes-vrpmdv/COPYING.MIT
Normal file
17
meta-st/meta-st-openstlinux/recipes-vrpmdv/COPYING.MIT
Normal 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.
|
||||
43
meta-st/meta-st-openstlinux/recipes-vrpmdv/README
Normal file
43
meta-st/meta-st-openstlinux/recipes-vrpmdv/README
Normal 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/
|
||||
@@ -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 \
|
||||
"
|
||||
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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"
|
||||
@@ -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)} "
|
||||
@@ -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)}"
|
||||
@@ -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)}"
|
||||
@@ -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 "
|
||||
|
||||
|
||||
|
||||
26
meta-st/meta-st-openstlinux/recipes-vrpmdv/conf/layer.conf
Normal file
26
meta-st/meta-st-openstlinux/recipes-vrpmdv/conf/layer.conf
Normal 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"
|
||||
@@ -0,0 +1,2 @@
|
||||
MACHINE ??= "vrmp-device"
|
||||
DISTRO ?= "vrmpdv-web"
|
||||
@@ -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}'} \
|
||||
"
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
76
meta-st/meta-st-openstlinux/recipes-vrpmdv/recipes-base/.vscode/settings.json
vendored
Normal file
76
meta-st/meta-st-openstlinux/recipes-vrpmdv/recipes-base/.vscode/settings.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/
|
||||
}
|
||||
@@ -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/
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=gunicorn socket for vrpmdv_web
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/vrpmdv_web.sock
|
||||
SocketUser=www
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"rpmsg.h": "c",
|
||||
"types.h": "c",
|
||||
"ioctl.h": "c",
|
||||
"wait.h": "c"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
,markus,U2204VM,02.05.2024 10:15,file:///home/markus/.config/libreoffice/4;
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
SUBSYSTEM=="misc", KERNEL=="mon-datafile", GROUP="dialout", MODE="0666"
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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");
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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);
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"rpmsg.h": "c",
|
||||
"types.h": "c",
|
||||
"ioctl.h": "c",
|
||||
"wait.h": "c"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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");
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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);
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
8
meta-st/meta-st-openstlinux/recipes-vrpmdv/recipes-base/vrpmdv-mon-tty/.vscode/settings.json
vendored
Normal file
8
meta-st/meta-st-openstlinux/recipes-vrpmdv/recipes-base/vrpmdv-mon-tty/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"rpmsg.h": "c",
|
||||
"types.h": "c",
|
||||
"ioctl.h": "c",
|
||||
"wait.h": "c"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
SUBSYSTEM=="misc", KERNEL=="ttyMON", GROUP="tty", MODE="0666"
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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);
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"rpmsg.h": "c",
|
||||
"types.h": "c",
|
||||
"ioctl.h": "c",
|
||||
"wait.h": "c"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
,markus,U2204VM,02.05.2024 10:15,file:///home/markus/.config/libreoffice/4;
|
||||
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -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;
|
||||
@@ -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 */
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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);
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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
Reference in New Issue
Block a user