added my Recipes

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
SUBSYSTEM=="misc", KERNEL=="vrpmdv-monitoring", GROUP="dialout", MODE="0666"

View File

@@ -0,0 +1,16 @@
# Makefile for VRPMDV Monitoring Driver
obj-m := vrpmdv-monitoring.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

View File

@@ -0,0 +1,97 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Remote processor messaging - sample client driver
*
* Copyright (C) 2011 Texas Instruments, Inc.
* Copyright (C) 2011 Google, Inc.
*
* Ohad Ben-Cohen <ohad@wizery.com>
* Brian Swetland <swetland@google.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/rpmsg.h>
#define MSG "hello Monitoring!"
static int count = 100;
module_param(count, int, 0644);
struct instance_data {
int rx_count;
};
static int vrpmdv_monitoring_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret;
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);
/* 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 0;
}
static int vrpmdv_monitoring_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, "vrpmdv_monitoring_send failed: %d\n", ret);
return ret;
}
return 0;
}
static void vrpmdv_monitoring_remove(struct rpmsg_device *rpdev)
{
dev_info(&rpdev->dev, "vrpmdv-monitoring client driver is removed\n");
}
static struct rpmsg_device_id vrpmdv_monitoring_driver_id_table[] = {
{ .name = "vrpmdv-monitoring" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, vrpmdv_monitoring_driver_id_table);
static struct rpmsg_driver vrpmdv_monitoring = {
.drv.name = KBUILD_MODNAME,
.id_table = vrpmdv_monitoring_driver_id_table,
.probe = vrpmdv_monitoring_probe,
.callback = vrpmdv_monitoring_cb,
.remove = vrpmdv_monitoring_remove,
};
module_rpmsg_driver(vrpmdv_monitoring);
MODULE_DESCRIPTION("Remote processor messaging vrpmdv monitoring driver");
MODULE_LICENSE("GPL v2");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,38 @@
cmake_minimum_required(VERSION 3.5)
project(rt_service)
set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY")
# Find python and Boost - both are required dependencies
find_package(PythonLibs 3.10 REQUIRED)
find_package(Boost COMPONENTS python REQUIRED)
# Without this, any build libraries automatically have names "lib{x}.so"
set(CMAKE_SHARED_MODULE_PREFIX "")
add_definitions(-DBOOST_BIND_GLOBAL_PLACEHOLDERS)
#set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
#set (CMAKE_RUNTIME_OUTPUT_DIRECTORY /home/markus/git/vrpmdvweb/vrpmdvserver/extensions/rt_service)
set (RTSERVICEINCLUDE ./include)
file( GLOB LIB_SOURCES .src/*.cpp )
file( GLOB LIB_HEADERS lib/*.h )
# Add a shared module - modules are intended to be imported at runtime.
# - This is where you add the source files
add_library(rt_service MODULE ./src/rt_service.cpp ./src/monitoringTask/RTSMonitoringTask.cpp ./src/utilities/RelGILLock.cpp ./src/utilities/GILLock.cpp ./src/utilities/RTSCoproHelper.cpp)
# Set up the libraries and header search pathsfor this target
target_link_libraries(rt_service ${Boost_LIBRARIES} ${PYTHON_LIBRARIES})
target_include_directories(rt_service PRIVATE ${PYTHON_INCLUDE_DIRS} ${RTSERVICEINCLUDE})
set(MY_RESOURCE_FILE rt_service.so)
install(TARGETS rt_service LIBRARY DESTINATION lib)
# install(TARGETS t tlib
# RUNTIME DESTINATION bin
# LIBRARY DESTINATION lib
# )

View File

@@ -0,0 +1,59 @@
cmake_minimum_required(VERSION 3.5)
#set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
#SET(CMAKE_SYSTEM_PROCESSOR arm)
#SET(CMAKE_CROSSCOMPILING 1)
project(RTService)
# set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY")
# set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
# set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")
# set(CMAKE_C_COMPILER_WORKS 1)
# set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# SET(CMAKE_SYSTEM_PROCESSOR arm)
# SET(CMAKE_CROSSCOMPILING 1)
# set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
# set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(GCC_COMPILE_FLAGS "-Wall -Wextra -Werror -Wsuggest-override -Wno-register -Wno-missing-braces -Wno-unknown-pragmas -fdiagnostics-show-option")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COMPILE_FLAGS}")
# Find python and Boost - both are required dependencies
find_package(Boost)
set(source_files
${CMAKE_CURRENT_SOURCE_DIR}/src/RTService.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/monitoringTask/RTSMonitoringTask.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/utilities/RTSCoproHelper.cpp
${CMAKE_CURRENT_SOURCE_DIR}/include/RTSMonFrame.h
)
add_executable(${PROJECT_NAME} ${source_files})
#file( GLOB LIB_SOURCES .src/*.cpp )
#file( GLOB LIB_HEADERS lib/*.h )
# target_link_libraries(RTService
# PRIVATE
# Boost
#)cd
#target_include_directories(${PROJECT_NAME} PRIVATE ${RTSERVICEINCLUDE})
install(TARGETS RTService DESTINATION bin)

View File

@@ -0,0 +1,396 @@
# This is the CMakeCache file.
# For build in directory: /home/markus/STM32MPU_workspace/STM32MP1-Ecosystem-V6.1.0/Distribution-Package-VRDevice/layers/meta-st/meta-st-openstlinux/recipes-vrpmdv/recipes-base/vrpmdv-rtservice/files/_build
# It was generated by CMake: /home/markus/STM32MPU_workspace/STM32MP1-Ecosystem-V6.1.0/dev-pack/sysroots/x86_64-ostl_sdk-linux/usr/bin/cmake
# You can edit this file to change values found and used by cmake.
# If you do not want to change any of the values, simply exit the editor.
# If you do want to change a value, simply edit, save, and exit the editor.
# The syntax for the file is as follows:
# KEY:TYPE=VALUE
# KEY is the name of a variable in the cache.
# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!.
# VALUE is the current value for the KEY.
########################
# EXTERNAL cache entries
########################
//The directory containing a CMake configuration file for Boost.
Boost_DIR:PATH=/home/markus/vrpmdv-sdk/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi/usr/lib/cmake/Boost-1.81.0
//Path to a file.
Boost_INCLUDE_DIR:PATH=/home/markus/vrpmdv-sdk/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi/usr/include
//Path to a program.
CMAKE_ADDR2LINE:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-addr2line
//Path to a program.
CMAKE_AR:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-ar
//Choose the type of build, options are: None Debug Release RelWithDebInfo
// MinSizeRel ...
CMAKE_BUILD_TYPE:STRING=
//Enable/Disable color output during build.
CMAKE_COLOR_MAKEFILE:BOOL=ON
//CXX compiler
CMAKE_CXX_COMPILER:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-g++
//A wrapper around 'ar' adding the appropriate '--plugin' option
// for the GCC compiler
CMAKE_CXX_COMPILER_AR:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-gcc-ar
//Arguments to CXX compiler
CMAKE_CXX_COMPILER_ARG1:STRING= -mthumb -mfpu=neon-vfpv4 -mfloat-abi=hard -mcpu=cortex-a7 --sysroot=/home/markus/vrpmdv-sdk/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
//A wrapper around 'ranlib' adding the appropriate '--plugin' option
// for the GCC compiler
CMAKE_CXX_COMPILER_RANLIB:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-gcc-ranlib
CMAKE_CXX_FLAGS:STRING=' -O2 -pipe -g -feliminate-unused-debug-types '
//Flags used by the CXX compiler during DEBUG builds.
CMAKE_CXX_FLAGS_DEBUG:STRING=-g
//Flags used by the CXX compiler during MINSIZEREL builds.
CMAKE_CXX_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG
//Flags used by the CXX compiler during RELEASE builds.
CMAKE_CXX_FLAGS_RELEASE:STRING=-O3 -DNDEBUG
//Flags used by the CXX compiler during RELWITHDEBINFO builds.
CMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG
//C compiler
CMAKE_C_COMPILER:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-gcc
//A wrapper around 'ar' adding the appropriate '--plugin' option
// for the GCC compiler
CMAKE_C_COMPILER_AR:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-gcc-ar
//Arguments to C compiler
CMAKE_C_COMPILER_ARG1:STRING= -mthumb -mfpu=neon-vfpv4 -mfloat-abi=hard -mcpu=cortex-a7 --sysroot=/home/markus/vrpmdv-sdk/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
//A wrapper around 'ranlib' adding the appropriate '--plugin' option
// for the GCC compiler
CMAKE_C_COMPILER_RANLIB:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-gcc-ranlib
CMAKE_C_FLAGS:STRING=' -O2 -pipe -g -feliminate-unused-debug-types '
//Flags used by the C compiler during DEBUG builds.
CMAKE_C_FLAGS_DEBUG:STRING=-g
//Flags used by the C compiler during MINSIZEREL builds.
CMAKE_C_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG
//Flags used by the C compiler during RELEASE builds.
CMAKE_C_FLAGS_RELEASE:STRING=-O3 -DNDEBUG
//Flags used by the C compiler during RELWITHDEBINFO builds.
CMAKE_C_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG
//Path to a program.
CMAKE_DLLTOOL:FILEPATH=CMAKE_DLLTOOL-NOTFOUND
//Flags used by the linker during all build types.
CMAKE_EXE_LINKER_FLAGS:STRING=-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed
//Flags used by the linker during DEBUG builds.
CMAKE_EXE_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during MINSIZEREL builds.
CMAKE_EXE_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during RELEASE builds.
CMAKE_EXE_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during RELWITHDEBINFO builds.
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//Enable/Disable output of compile commands during generation.
CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=
//Value Computed by CMake.
CMAKE_FIND_PACKAGE_REDIRECTS_DIR:STATIC=/home/markus/STM32MPU_workspace/STM32MP1-Ecosystem-V6.1.0/Distribution-Package-VRDevice/layers/meta-st/meta-st-openstlinux/recipes-vrpmdv/recipes-base/vrpmdv-rtservice/files/_build/CMakeFiles/pkgRedirects
//Install path prefix, prepended onto install directories.
CMAKE_INSTALL_PREFIX:PATH=/usr/local
//Path to a program.
CMAKE_LINKER:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-ld
//Path to a program.
CMAKE_MAKE_PROGRAM:FILEPATH=/usr/bin/gmake
//Flags used by the linker during the creation of modules during
// all build types.
CMAKE_MODULE_LINKER_FLAGS:STRING=-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed
//Flags used by the linker during the creation of modules during
// DEBUG builds.
CMAKE_MODULE_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during the creation of modules during
// MINSIZEREL builds.
CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during the creation of modules during
// RELEASE builds.
CMAKE_MODULE_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during the creation of modules during
// RELWITHDEBINFO builds.
CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//Path to a program.
CMAKE_NM:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-nm
//Path to a program.
CMAKE_OBJCOPY:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-objcopy
//Path to a program.
CMAKE_OBJDUMP:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-objdump
//Value Computed by CMake
CMAKE_PROJECT_DESCRIPTION:STATIC=
//Value Computed by CMake
CMAKE_PROJECT_HOMEPAGE_URL:STATIC=
//Value Computed by CMake
CMAKE_PROJECT_NAME:STATIC=RTService
//Path to a program.
CMAKE_RANLIB:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-ranlib
//Path to a program.
CMAKE_READELF:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-readelf
//Flags used by the linker during the creation of shared libraries
// during all build types.
CMAKE_SHARED_LINKER_FLAGS:STRING=-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed
//Flags used by the linker during the creation of shared libraries
// during DEBUG builds.
CMAKE_SHARED_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during the creation of shared libraries
// during MINSIZEREL builds.
CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during the creation of shared libraries
// during RELEASE builds.
CMAKE_SHARED_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during the creation of shared libraries
// during RELWITHDEBINFO builds.
CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//If set, runtime paths are not added when installing shared libraries,
// but are added when building.
CMAKE_SKIP_INSTALL_RPATH:BOOL=NO
//If set, runtime paths are not added when using shared libraries.
CMAKE_SKIP_RPATH:BOOL=NO
//Flags used by the linker during the creation of static libraries
// during all build types.
CMAKE_STATIC_LINKER_FLAGS:STRING=
//Flags used by the linker during the creation of static libraries
// during DEBUG builds.
CMAKE_STATIC_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during the creation of static libraries
// during MINSIZEREL builds.
CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during the creation of static libraries
// during RELEASE builds.
CMAKE_STATIC_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during the creation of static libraries
// during RELWITHDEBINFO builds.
CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//Path to a program.
CMAKE_STRIP:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-strip
//The CMake toolchain file
CMAKE_TOOLCHAIN_FILE:FILEPATH=/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/share/cmake/OEToolchainConfig.cmake
//If this value is on, makefiles will be generated without the
// .SILENT directive, and all commands will be echoed to the console
// during the make. This is useful for debugging only. With Visual
// Studio IDE projects all commands are done without /nologo.
CMAKE_VERBOSE_MAKEFILE:BOOL=FALSE
//Value Computed by CMake
RTService_BINARY_DIR:STATIC=/home/markus/STM32MPU_workspace/STM32MP1-Ecosystem-V6.1.0/Distribution-Package-VRDevice/layers/meta-st/meta-st-openstlinux/recipes-vrpmdv/recipes-base/vrpmdv-rtservice/files/_build
//Value Computed by CMake
RTService_IS_TOP_LEVEL:STATIC=ON
//Value Computed by CMake
RTService_SOURCE_DIR:STATIC=/home/markus/STM32MPU_workspace/STM32MP1-Ecosystem-V6.1.0/Distribution-Package-VRDevice/layers/meta-st/meta-st-openstlinux/recipes-vrpmdv/recipes-base/vrpmdv-rtservice/files
//The directory containing a CMake configuration file for boost_headers.
boost_headers_DIR:PATH=/home/markus/vrpmdv-sdk/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi/usr/lib/cmake/boost_headers-1.81.0
########################
# INTERNAL cache entries
########################
//ADVANCED property for variable: Boost_DIR
Boost_DIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_ADDR2LINE
CMAKE_ADDR2LINE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_AR
CMAKE_AR-ADVANCED:INTERNAL=1
//This is the directory where this CMakeCache.txt was created
CMAKE_CACHEFILE_DIR:INTERNAL=/home/markus/STM32MPU_workspace/STM32MP1-Ecosystem-V6.1.0/Distribution-Package-VRDevice/layers/meta-st/meta-st-openstlinux/recipes-vrpmdv/recipes-base/vrpmdv-rtservice/files/_build
//Major version of cmake used to create the current loaded cache
CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3
//Minor version of cmake used to create the current loaded cache
CMAKE_CACHE_MINOR_VERSION:INTERNAL=25
//Patch version of cmake used to create the current loaded cache
CMAKE_CACHE_PATCH_VERSION:INTERNAL=2
//ADVANCED property for variable: CMAKE_COLOR_MAKEFILE
CMAKE_COLOR_MAKEFILE-ADVANCED:INTERNAL=1
//Path to CMake executable.
CMAKE_COMMAND:INTERNAL=/home/markus/STM32MPU_workspace/STM32MP1-Ecosystem-V6.1.0/dev-pack/sysroots/x86_64-ostl_sdk-linux/usr/bin/cmake
//Path to cpack program executable.
CMAKE_CPACK_COMMAND:INTERNAL=/home/markus/STM32MPU_workspace/STM32MP1-Ecosystem-V6.1.0/dev-pack/sysroots/x86_64-ostl_sdk-linux/usr/bin/cpack
//Path to ctest program executable.
CMAKE_CTEST_COMMAND:INTERNAL=/home/markus/STM32MPU_workspace/STM32MP1-Ecosystem-V6.1.0/dev-pack/sysroots/x86_64-ostl_sdk-linux/usr/bin/ctest
//ADVANCED property for variable: CMAKE_CXX_COMPILER
CMAKE_CXX_COMPILER-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_COMPILER_AR
CMAKE_CXX_COMPILER_AR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_COMPILER_RANLIB
CMAKE_CXX_COMPILER_RANLIB-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_FLAGS
CMAKE_CXX_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_FLAGS_DEBUG
CMAKE_CXX_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_FLAGS_MINSIZEREL
CMAKE_CXX_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELWITHDEBINFO
CMAKE_CXX_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_COMPILER
CMAKE_C_COMPILER-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_COMPILER_AR
CMAKE_C_COMPILER_AR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_COMPILER_RANLIB
CMAKE_C_COMPILER_RANLIB-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_FLAGS
CMAKE_C_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_FLAGS_MINSIZEREL
CMAKE_C_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_FLAGS_RELWITHDEBINFO
CMAKE_C_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_DLLTOOL
CMAKE_DLLTOOL-ADVANCED:INTERNAL=1
//Executable file format
CMAKE_EXECUTABLE_FORMAT:INTERNAL=ELF
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS
CMAKE_EXE_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_DEBUG
CMAKE_EXE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_MINSIZEREL
CMAKE_EXE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELEASE
CMAKE_EXE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXPORT_COMPILE_COMMANDS
CMAKE_EXPORT_COMPILE_COMMANDS-ADVANCED:INTERNAL=1
//Name of external makefile project generator.
CMAKE_EXTRA_GENERATOR:INTERNAL=
//Name of generator.
CMAKE_GENERATOR:INTERNAL=Unix Makefiles
//Generator instance identifier.
CMAKE_GENERATOR_INSTANCE:INTERNAL=
//Name of generator platform.
CMAKE_GENERATOR_PLATFORM:INTERNAL=
//Name of generator toolset.
CMAKE_GENERATOR_TOOLSET:INTERNAL=
//Source directory with the top level CMakeLists.txt file for this
// project
CMAKE_HOME_DIRECTORY:INTERNAL=/home/markus/STM32MPU_workspace/STM32MP1-Ecosystem-V6.1.0/Distribution-Package-VRDevice/layers/meta-st/meta-st-openstlinux/recipes-vrpmdv/recipes-base/vrpmdv-rtservice/files
//Install .so files without execute permission.
CMAKE_INSTALL_SO_NO_EXE:INTERNAL=1
//ADVANCED property for variable: CMAKE_LINKER
CMAKE_LINKER-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MAKE_PROGRAM
CMAKE_MAKE_PROGRAM-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS
CMAKE_MODULE_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_DEBUG
CMAKE_MODULE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL
CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELEASE
CMAKE_MODULE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_NM
CMAKE_NM-ADVANCED:INTERNAL=1
//number of local generators
CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1
//ADVANCED property for variable: CMAKE_OBJCOPY
CMAKE_OBJCOPY-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_OBJDUMP
CMAKE_OBJDUMP-ADVANCED:INTERNAL=1
//Platform information initialized
CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1
//ADVANCED property for variable: CMAKE_RANLIB
CMAKE_RANLIB-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_READELF
CMAKE_READELF-ADVANCED:INTERNAL=1
//Path to CMake installation.
CMAKE_ROOT:INTERNAL=/home/markus/STM32MPU_workspace/STM32MP1-Ecosystem-V6.1.0/dev-pack/sysroots/x86_64-ostl_sdk-linux/usr/share/cmake-3.25
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS
CMAKE_SHARED_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_DEBUG
CMAKE_SHARED_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL
CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELEASE
CMAKE_SHARED_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SKIP_INSTALL_RPATH
CMAKE_SKIP_INSTALL_RPATH-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SKIP_RPATH
CMAKE_SKIP_RPATH-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS
CMAKE_STATIC_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_DEBUG
CMAKE_STATIC_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL
CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELEASE
CMAKE_STATIC_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STRIP
CMAKE_STRIP-ADVANCED:INTERNAL=1
//uname command
CMAKE_UNAME:INTERNAL=/usr/bin/uname
//ADVANCED property for variable: CMAKE_VERBOSE_MAKEFILE
CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1
//Details about finding Boost
FIND_PACKAGE_MESSAGE_DETAILS_Boost:INTERNAL=[/home/markus/vrpmdv-sdk/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi/usr/lib/cmake/Boost-1.81.0/BoostConfig.cmake][c ][v1.81.0()]
//linker supports push/pop state
_CMAKE_LINKER_PUSHPOP_STATE_SUPPORTED:INTERNAL=TRUE
//ADVANCED property for variable: boost_headers_DIR
boost_headers_DIR-ADVANCED:INTERNAL=1

View File

@@ -0,0 +1,72 @@
set(CMAKE_C_COMPILER "/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-gcc")
set(CMAKE_C_COMPILER_ARG1 " -mthumb -mfpu=neon-vfpv4 -mfloat-abi=hard -mcpu=cortex-a7 --sysroot=/home/markus/vrpmdv-sdk/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi")
set(CMAKE_C_COMPILER_ID "GNU")
set(CMAKE_C_COMPILER_VERSION "12.3.0")
set(CMAKE_C_COMPILER_VERSION_INTERNAL "")
set(CMAKE_C_COMPILER_WRAPPER "")
set(CMAKE_C_STANDARD_COMPUTED_DEFAULT "17")
set(CMAKE_C_EXTENSIONS_COMPUTED_DEFAULT "ON")
set(CMAKE_C_COMPILE_FEATURES "c_std_90;c_function_prototypes;c_std_99;c_restrict;c_variadic_macros;c_std_11;c_static_assert;c_std_17;c_std_23")
set(CMAKE_C90_COMPILE_FEATURES "c_std_90;c_function_prototypes")
set(CMAKE_C99_COMPILE_FEATURES "c_std_99;c_restrict;c_variadic_macros")
set(CMAKE_C11_COMPILE_FEATURES "c_std_11;c_static_assert")
set(CMAKE_C17_COMPILE_FEATURES "c_std_17")
set(CMAKE_C23_COMPILE_FEATURES "c_std_23")
set(CMAKE_C_PLATFORM_ID "Linux")
set(CMAKE_C_SIMULATE_ID "")
set(CMAKE_C_COMPILER_FRONTEND_VARIANT "")
set(CMAKE_C_SIMULATE_VERSION "")
set(CMAKE_AR "/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-ar")
set(CMAKE_C_COMPILER_AR "/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-gcc-ar")
set(CMAKE_RANLIB "/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-ranlib")
set(CMAKE_C_COMPILER_RANLIB "/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-gcc-ranlib")
set(CMAKE_LINKER "/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-ld")
set(CMAKE_MT "")
set(CMAKE_COMPILER_IS_GNUCC 1)
set(CMAKE_C_COMPILER_LOADED 1)
set(CMAKE_C_COMPILER_WORKS TRUE)
set(CMAKE_C_ABI_COMPILED TRUE)
set(CMAKE_C_COMPILER_ENV_VAR "CC")
set(CMAKE_C_COMPILER_ID_RUN 1)
set(CMAKE_C_SOURCE_FILE_EXTENSIONS c;m)
set(CMAKE_C_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC)
set(CMAKE_C_LINKER_PREFERENCE 10)
# Save compiler ABI information.
set(CMAKE_C_SIZEOF_DATA_PTR "4")
set(CMAKE_C_COMPILER_ABI "ELF")
set(CMAKE_C_BYTE_ORDER "LITTLE_ENDIAN")
set(CMAKE_C_LIBRARY_ARCHITECTURE "")
if(CMAKE_C_SIZEOF_DATA_PTR)
set(CMAKE_SIZEOF_VOID_P "${CMAKE_C_SIZEOF_DATA_PTR}")
endif()
if(CMAKE_C_COMPILER_ABI)
set(CMAKE_INTERNAL_PLATFORM_ABI "${CMAKE_C_COMPILER_ABI}")
endif()
if(CMAKE_C_LIBRARY_ARCHITECTURE)
set(CMAKE_LIBRARY_ARCHITECTURE "")
endif()
set(CMAKE_C_CL_SHOWINCLUDES_PREFIX "")
if(CMAKE_C_CL_SHOWINCLUDES_PREFIX)
set(CMAKE_CL_SHOWINCLUDES_PREFIX "${CMAKE_C_CL_SHOWINCLUDES_PREFIX}")
endif()
set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES "/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/lib/arm-ostl-linux-gnueabi/gcc/arm-ostl-linux-gnueabi/12.3.0/include;/home/markus/vrpmdv-sdk/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi/usr/lib/arm-ostl-linux-gnueabi/12.3.0/include;/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/lib/arm-ostl-linux-gnueabi/gcc/arm-ostl-linux-gnueabi/12.3.0/include-fixed;/home/markus/vrpmdv-sdk/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi/usr/include")
set(CMAKE_C_IMPLICIT_LINK_LIBRARIES "gcc;gcc_s;c;gcc;gcc_s")
set(CMAKE_C_IMPLICIT_LINK_DIRECTORIES "/home/markus/vrpmdv-sdk/sysroots/x86_64-ostl_sdk-linux/usr/lib/arm-ostl-linux-gnueabi/gcc/arm-ostl-linux-gnueabi/12.3.0;/home/markus/vrpmdv-sdk/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi/lib;/home/markus/vrpmdv-sdk/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi/usr/lib/arm-ostl-linux-gnueabi/12.3.0;/home/markus/vrpmdv-sdk/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi/usr/lib")
set(CMAKE_C_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "")

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