added my Recipes
This commit is contained in:
@@ -0,0 +1 @@
|
||||
SUBSYSTEM=="misc", KERNEL=="ttyMON", GROUP="tty", MODE="0666"
|
||||
@@ -0,0 +1,26 @@
|
||||
# Makefile for VRPMDV Monitoring Controler
|
||||
obj-m := vrpmdv-mon-tty.o
|
||||
|
||||
|
||||
SRC := $(shell pwd)
|
||||
|
||||
all:
|
||||
$(MAKE) -C $(KERNEL_SRC) M=$(SRC)
|
||||
|
||||
modules_install:
|
||||
$(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules_install
|
||||
|
||||
clean:
|
||||
rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c
|
||||
rm -f Module.markers Module.symvers modules.order
|
||||
rm -rf .tmp_versions Modules.symvers
|
||||
|
||||
# obj-m = vrpmdv-monitoring-controler.o
|
||||
|
||||
# KVERSION = $(shell uname -r)
|
||||
|
||||
# all:
|
||||
# make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules
|
||||
|
||||
# clean:
|
||||
# make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean
|
||||
@@ -0,0 +1,116 @@
|
||||
include <condition_variable>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
std::string data;
|
||||
bool ready = false;
|
||||
bool processed = false;
|
||||
|
||||
void worker_thread()
|
||||
{
|
||||
// wait until main() sends data
|
||||
std::unique_lock lk(m);
|
||||
cv.wait(lk, []{ return ready; });
|
||||
|
||||
// after the wait, we own the lock
|
||||
std::cout << "Worker thread is processing data\n";
|
||||
data += " after processing";
|
||||
|
||||
// send data back to main()
|
||||
processed = true;
|
||||
std::cout << "Worker thread signals data processing completed\n";
|
||||
|
||||
// manual unlocking is done before notifying, to avoid waking up
|
||||
// the waiting thread only to block again (see notify_one for details)
|
||||
lk.unlock();
|
||||
cv.notify_one();
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
std::thread worker(worker_thread);
|
||||
|
||||
data = "Example data";
|
||||
// send data to the worker thread
|
||||
{
|
||||
std::lock_guard lk(m);
|
||||
ready = true;
|
||||
std::cout << "main() signals data ready for processing\n";
|
||||
}
|
||||
cv.notify_one();
|
||||
|
||||
// wait for the worker
|
||||
{
|
||||
std::unique_lock lk(m);
|
||||
cv.wait(lk, []{ return processed; });
|
||||
}
|
||||
std::cout << "Back in main(), data = " << data << '\n';
|
||||
|
||||
worker.join();
|
||||
}
|
||||
|
||||
--------------------
|
||||
|
||||
// waiting for timeout after 5 seconds
|
||||
std::chrono::seconds timeoutPeriod = 5;
|
||||
auto timePoint = std::chrono::system_clock::now() + timeoutPeriod;
|
||||
std::unique_lock<std::mutex> uLock(myDataMutex);
|
||||
while(!DataAreReadyForProcessing())
|
||||
{
|
||||
if (myCondVar.wait_until(uLock, timePoint) //<##
|
||||
== std::cv_status::timeout)
|
||||
{
|
||||
// data conditions where not fulfilled within
|
||||
// the time period; e.g. do some error handling
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
--
|
||||
|
||||
if (myCondVar.wait_for(uLock, timeoutPeriod,
|
||||
DataAreReadyForProcessing))
|
||||
{
|
||||
// data conditions where fulfilled
|
||||
// regular processing
|
||||
}
|
||||
else // timeout occured, conditions are not fulfilled
|
||||
{
|
||||
// e.g. do some error handling
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-------------
|
||||
|
||||
|
||||
static int rpmsg_sample_probe(struct rpmsg_device *rpdev)
|
||||
{
|
||||
|
||||
int ret;
|
||||
struct instance_data *idata;
|
||||
|
||||
dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
|
||||
rpdev->src, rpdev->dst);
|
||||
|
||||
idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
|
||||
if (!idata)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(&rpdev->dev, idata);
|
||||
|
||||
/* send a message to our remote processor */
|
||||
ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
|
||||
if (ret) {
|
||||
dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -0,0 +1,795 @@
|
||||
/* Copyright 2024 Markus Lehr
|
||||
*
|
||||
*
|
||||
* This Software is owned by Markus Lehr.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
// ##################################################################################################
|
||||
/*
|
||||
* This is a Linux kernel module/driver called "vrpmdv-monitoring-cmd " which holds the family ID
|
||||
* and functions to handle the monitoring in a Generic Netlink in the kernel.
|
||||
* "It registers a Netlink family called "vrpmdv-monitoring_cmd".
|
||||
*
|
||||
*
|
||||
* You can find some more interesting documentation about Generic Netlink here:
|
||||
* "Generic Netlink HOW-TO based on Jamal's original doc" https://lwn.net/Articles/208755/
|
||||
*/
|
||||
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/rpmsg.h>
|
||||
|
||||
// basic definitions for kernel module development
|
||||
#include <linux/module.h>
|
||||
// definitions for generic netlink families, policies etc;
|
||||
// transitive dependencies for basic netlink, sockets etc
|
||||
#include <net/genetlink.h>
|
||||
// required for locking inside the .dumpit callback demonstration
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
|
||||
// data/vars/enums/properties that describes our protocol that we implement
|
||||
// on top of generic netlink (like functions we want to trigger on the receiving side)
|
||||
//#include "vrpmdv-monitoring-cmd.h"
|
||||
|
||||
|
||||
/* ######################## CONVENIENT LOGGING MACROS ######################## */
|
||||
// (Re)definition of some convenient logging macros from <linux/printk.h>. You can see the logging
|
||||
// messages when printing the kernel log, e.g. with `$ sudo dmesg`.
|
||||
// See https://elixir.bootlin.com/linux/latest/source/include/linux/printk.h
|
||||
|
||||
// with this redefinition we can easily prefix all log messages from pr_* logging macros
|
||||
#ifdef pr_fmt
|
||||
#undef pr_fmt
|
||||
#endif
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
/* ########################################################################### */
|
||||
|
||||
|
||||
#define MSG "hello Monitoring!"
|
||||
static bool retok = true;
|
||||
|
||||
static int count = 100;
|
||||
module_param(count, int, 0644);
|
||||
|
||||
struct instance_data {
|
||||
int rx_count;
|
||||
};
|
||||
|
||||
|
||||
// std::chrono::seconds timeoutPeriod = 5;
|
||||
// auto timePoint = std::chrono::system_clock::now() + timeoutPeriod;
|
||||
|
||||
|
||||
/** ----- NETLINK Driver defintion ------------------*/
|
||||
|
||||
|
||||
/**
|
||||
* Generic Netlink will create a Netlink family with this name. Kernel will asign
|
||||
* a numeric ID and afterwards we can talk to the family with its ID. To get
|
||||
* the ID we use Generic Netlink in the userland and pass the family name.
|
||||
*
|
||||
* Short for: Generic Netlink VRPMDV Monitoring gnl_foobar_mcmd
|
||||
*/
|
||||
#define FAMILY_NAME "gnl-vrpmdv-mcmd"
|
||||
|
||||
/**
|
||||
* These are the attributes that we want to share in gnl_foobar_xmpl.
|
||||
* You can understand an attribute as a semantic type. This is
|
||||
* the payload of Netlink messages.
|
||||
* GNl: Generic Netlink
|
||||
*/
|
||||
enum GNL_VRPMDV_XMPL_ATTRIBUTE {
|
||||
/**
|
||||
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
|
||||
* We do the same, although I'm not sure, if this is really enforced by code.
|
||||
*/
|
||||
GNL_VRPMDV_MCMD_A_UNSPEC,
|
||||
/** We expect a MSG to be a null-terminated C-string. */
|
||||
GNL_VRPMDV_MCMD_A_MSG,
|
||||
/** Unused marker field to get the length/count of enum entries. No real attribute. */
|
||||
__GNL_VRPMDV_MCMD_A_MAX,
|
||||
};
|
||||
/**
|
||||
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
|
||||
*/
|
||||
#define GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN (__GNL_VRPMDV_MCMD_A_MAX)
|
||||
/**
|
||||
* The number of actual usable attributes in `enum GNL_VRPMDV_MCMD_ATTRIBUTE`.
|
||||
* This is `GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN` - 1 because "UNSPEC" is never used.
|
||||
*/
|
||||
#define GNL_VRPMDV_MCMD_ATTRIBUTE_COUNT (GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN - 1)
|
||||
|
||||
/**
|
||||
* Enumeration of all commands (functions) that our custom protocol on top
|
||||
* of generic netlink supports. This can be understood as the action that
|
||||
* we want to trigger on the receiving side.
|
||||
*/
|
||||
enum GNL_VRPMDV_MCMD_COMMAND {
|
||||
/**
|
||||
* 0 is never used (=> UNSPEC), you can also see this in other family definitions in Linux code.
|
||||
* We do the same, although I'm not sure, if this is really enforced by code.
|
||||
*/
|
||||
GNL_VRPMDV_MCMD_C_UNSPEC,
|
||||
|
||||
// first real command is "1" (>0)
|
||||
/**
|
||||
* When this command is received, we expect the attribute `GNL_VRPMDV_MCMD_ATTRIBUTE::GNL_VRPMDV_MCMD_A_MSG` to
|
||||
* be present in the Generic Netlink request message. The kernel reads the message from the packet and
|
||||
* sent it to the copro. THe result will be return by creating a new Generic Netlink response message
|
||||
* with an corresponding attribute/payload.
|
||||
*
|
||||
* This command/signaling mechanism is independent of the Netlink flag `NLM_F_ECHO (0x08)`. We use it as
|
||||
* "echo specific data" instead of return a 1:1 copy of the package, which you could do with
|
||||
* `NLM_F_ECHO (0x08)` for example.
|
||||
*/
|
||||
GNL_VRPMDV_MCMD_C_MSG,
|
||||
|
||||
/**
|
||||
* Provokes a NLMSG_ERR answer to this request as described in netlink manpage
|
||||
* (https://man7.org/linux/man-pages/man7/netlink.7.html).
|
||||
*/
|
||||
GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
|
||||
|
||||
/** Unused marker field to get the length/count of enum entries. No real attribute. */
|
||||
__GNL_VRPMDV_MCMD_C_MAX,
|
||||
};
|
||||
/**
|
||||
* Number of elements in `enum GNL_VRPMDV_MCMD_COMMAND`.
|
||||
*/
|
||||
#define GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN (__GNL_VRPMDV_MCMD_C_MAX)
|
||||
/**
|
||||
* The number of actual usable commands in `enum GNL_FOOBAR_XMPL_COMMAND`.
|
||||
* This is `GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN` - 1 because "UNSPEC" is never used.
|
||||
*/
|
||||
#define GNL_VRPMDV_MCMD_COMMAND_COUNT (GNL_VRPMDV_MCMD_COMMAND_ENUM_LEN - 1)
|
||||
|
||||
#define NODATARECEIVED 0
|
||||
#define DATARECEIVED 1
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* rpmsg wait for response from copro side.
|
||||
*/
|
||||
struct mutex sendMTX;
|
||||
/**
|
||||
* rpmsg wait for response from copro side.
|
||||
*/
|
||||
struct mutex receiveCV;
|
||||
/**
|
||||
* Wait Queue: if it is signaled we have received data from copro
|
||||
*/
|
||||
wait_queue_head_t receive_queue;
|
||||
/**
|
||||
* Waitflag: 0= no data received, 1 = data received
|
||||
*/
|
||||
int receive_queue_flag = NODATARECEIVED;
|
||||
|
||||
char* received_bytes = NULL;
|
||||
int received_len = 0;
|
||||
//the rpmsg device which sends the data to the copro
|
||||
struct rpmsg_device* prpdev = NULL; /* handle rpmsg device */
|
||||
|
||||
|
||||
// Documentation is on the implementation of this function.
|
||||
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info);
|
||||
|
||||
// Documentation is on the implementation of this function.
|
||||
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info);
|
||||
|
||||
/**
|
||||
* The length of `struct genl_ops gnl_foobar_xmpl_ops[]`. Not necessarily
|
||||
* the number of commands in `enum GNlFoobarXmplCommand`. It depends on your application logic.
|
||||
* For example, you can use the same command multiple times and - dependent by flag -
|
||||
* invoke a different callback handler. In our simple example we just use one .doit callback
|
||||
* per operation/command.
|
||||
*/
|
||||
#define GNL_VRPMDV_OPS_LEN (GNL_VRPMDV_MCMD_COMMAND_COUNT)
|
||||
|
||||
/**
|
||||
* Array with all operations that our protocol on top of Generic Netlink
|
||||
* supports. An operation is the glue between a command ("cmd" field in `struct genlmsghdr` of
|
||||
* received Generic Netlink message) and the corresponding ".doit" callback function.
|
||||
* See: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L148
|
||||
*/
|
||||
struct genl_ops gnl_vrpmdv_mcmd_ops[GNL_VRPMDV_OPS_LEN] = {
|
||||
{
|
||||
/* The "cmd" field in `struct genlmsghdr` of received Generic Netlink message */
|
||||
.cmd = GNL_VRPMDV_MCMD_C_MSG,
|
||||
/* TODO Use case ? */
|
||||
.flags = 0,
|
||||
/* TODO Use case ? */
|
||||
.internal_flags = 0,
|
||||
/* Callback handler when a request with the specified ".cmd" above is received.
|
||||
* Always validates the payload except one set NO_STRICT_VALIDATION flag in ".validate"
|
||||
* See: https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
|
||||
*
|
||||
* Quote from: https://lwn.net/Articles/208755
|
||||
* "The 'doit' handler should do whatever processing is necessary and return
|
||||
* zero on success, or a negative value on failure. Negative return values
|
||||
* will cause a NLMSG_ERROR message to be sent while a zero return value will
|
||||
* only cause a NLMSG_ERROR message to be sent if the request is received with
|
||||
* the NLM_F_ACK flag set."
|
||||
*
|
||||
* You can find this in Linux code here:
|
||||
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
|
||||
*
|
||||
* One can find more information about NLMSG_ERROR responses and how to handle them
|
||||
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
|
||||
*
|
||||
*/
|
||||
.doit = gnl_cb_vrpmdv_doit,
|
||||
/* This callback is similar in use to the standard Netlink 'dumpit' callback.
|
||||
* The 'dumpit' callback is invoked when a Generic Netlink message is received
|
||||
* with the NLM_F_DUMP flag set.
|
||||
*
|
||||
* A dump can be understand as a "GET ALL DATA OF THE GIVEN ENTITY", i.e.
|
||||
* the userland can receive as long as the .dumpit callback returns data.
|
||||
*
|
||||
* .dumpit is not mandatory, but either it or .doit must be provided, see
|
||||
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L367
|
||||
*
|
||||
* To be honest I don't know in what use case one should use .dumpit and why
|
||||
* it is useful, because you can achieve the same also with .doit handlers.
|
||||
* Anyway, this is just an example/tutorial.
|
||||
*
|
||||
* Quote from: https://lwn.net/Articles/208755
|
||||
* "The main difference between a 'dumpit' handler and a 'doit' handler is
|
||||
* that a 'dumpit' handler does not allocate a message buffer for a response;
|
||||
* a pre-allocated sk_buff is passed to the 'dumpit' handler as the first
|
||||
* parameter. The 'dumpit' handler should fill the message buffer with the
|
||||
* appropriate response message and return the size of the sk_buff,
|
||||
* i.e. sk_buff->len, and the message buffer will automatically be sent to the
|
||||
* Generic Netlink client that initiated the request. As long as the 'dumpit'
|
||||
* handler returns a value greater than zero it will be called again with a
|
||||
* newly allocated message buffer to fill, when the handler has no more data
|
||||
* to send it should return zero; error conditions are indicated by returning
|
||||
* a negative value. If necessary, state can be preserved in the
|
||||
* netlink_callback parameter which is passed to the 'dumpit' handler; the
|
||||
* netlink_callback parameter values will be preserved across handler calls
|
||||
* for a single request."
|
||||
*
|
||||
* You can see the check for the NLM_F_DUMP-flag here:
|
||||
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L780
|
||||
*/
|
||||
.dumpit = NULL, //gnl_cb_echo_dumpit,
|
||||
/* Start callback for dumps. Can be used to lock data structures. */
|
||||
.start = NULL, //gnl_cb_echo_dumpit_before,
|
||||
/* Completion callback for dumps. Can be used for cleanup after a dump and releasing locks. */
|
||||
.done = NULL, //gnl_cb_echo_dumpit_before_after,
|
||||
/*
|
||||
0 (= "validate strictly") or value `enum genl_validate_flags`
|
||||
* see: https://elixir.bootlin.com/linux/v5.11/source/include/net/genetlink.h#L108
|
||||
*/
|
||||
.validate = 0,
|
||||
},
|
||||
{
|
||||
.cmd = GNL_VRPMDV_MCMD_C_REPLY_WITH_NLMSG_ERR,
|
||||
.flags = 0,
|
||||
.internal_flags = 0,
|
||||
.doit = gnl_cb_doit_reply_with_nlmsg_err,
|
||||
// .dumpit is not required, only optional; application specific/dependent on your use case
|
||||
// in a real application you probably have different .dumpit handlers per operation/command
|
||||
.dumpit = NULL,
|
||||
// in a real application you probably have different .start handlers per operation/command
|
||||
.start = NULL,
|
||||
// in a real application you probably have different .done handlers per operation/command
|
||||
.done = NULL,
|
||||
.validate = 0,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attribute policy: defines which attribute has which type (e.g int, char * etc).
|
||||
* This get validated for each received Generic Netlink message, if not deactivated
|
||||
* in `gnl_foobar_xmpl_ops[].validate`.
|
||||
* See https://elixir.bootlin.com/linux/v5.11/source/net/netlink/genetlink.c#L717
|
||||
*/
|
||||
static struct nla_policy gnl_vrpmdv_mcmd_policy[GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN] = {
|
||||
// In case you are seeing this syntax for the first time (I also learned this just after a few years of
|
||||
// coding with C myself): The following static array initiations are equivalent:
|
||||
// `int a[2] = {1, 2}` <==> `int a[2] = {[0] => 1, [1] => 2}`.
|
||||
|
||||
[GNL_VRPMDV_MCMD_A_UNSPEC] = {.type = NLA_UNSPEC},
|
||||
|
||||
// You can set this to NLA_U32 for testing and send an ECHO message from the userland
|
||||
// It will fail in this case and you see a entry in the kernel log.
|
||||
|
||||
// `enum GNL_FOOBAR_XMPL_ATTRIBUTE::GNL_FOOBAR_XMPL_A_MSG` is a null-terminated C-String
|
||||
[GNL_VRPMDV_MCMD_A_MSG] = {.type = NLA_NUL_STRING},
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Definition of the Netlink family we want to register using Generic Netlink functionality
|
||||
*/
|
||||
static struct genl_family gnl_vrpmdv_mcmd_family = {
|
||||
// automatically assign an id
|
||||
.id = 0,
|
||||
// we don't use custom additional header info / user specific header
|
||||
.hdrsize = 0,
|
||||
// The name of this family, used by userspace application to get the numeric ID
|
||||
.name = FAMILY_NAME,
|
||||
// family specific version number; can be used to evolve application over time (multiple versions)
|
||||
.version = 1,
|
||||
// delegates all incoming requests to callback functions
|
||||
.ops = gnl_vrpmdv_mcmd_ops,
|
||||
// length of array `gnl_foobar_xmpl_ops`
|
||||
.n_ops = GNL_VRPMDV_OPS_LEN,
|
||||
// attribute policy (for validation of messages). Enforced automatically, except ".validate" in
|
||||
// corresponding ".ops"-field is set accordingly.
|
||||
.policy = gnl_vrpmdv_mcmd_policy,
|
||||
// Number of attributes / bounds check for policy (array length)
|
||||
.maxattr = GNL_VRPMDV_MCMD_ATTRIBUTE_ENUM_LEN,
|
||||
// Owning Kernel module of the Netlink family we register.
|
||||
.module = THIS_MODULE,
|
||||
|
||||
// Actually not necessary because this memory region would be zeroed anyway during module load,
|
||||
// but this way one sees all possible options.
|
||||
|
||||
// if your application must handle multiple netlink calls in parallel (where one should not block the next
|
||||
// from starting), set this to true! otherwise all netlink calls are mutually exclusive
|
||||
.parallel_ops = 0,
|
||||
// set to true if the family can handle network namespaces and should be presented in all of them
|
||||
.netnsok = 0,
|
||||
// called before an operation's doit callback, it may do additional, common, filtering and return an error
|
||||
.pre_doit = NULL,
|
||||
// called after an operation's doit callback, it may undo operations done by pre_doit, for example release locks
|
||||
.post_doit = NULL,
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_VRPMDV_MCMD_C_MSG` is received.
|
||||
* Please look into the comments where this is used as ".doit" callback above in
|
||||
* `struct genl_ops gnl_vrpmdv_mcmd_ops[]` for more information about ".doit" callbacks.
|
||||
*/
|
||||
int gnl_cb_vrpmdv_doit(struct sk_buff *sender_skb, struct genl_info *info) {
|
||||
struct nlattr *na;
|
||||
struct sk_buff *reply_skb;
|
||||
int rc;
|
||||
void *msg_head;
|
||||
char *recv_msg;
|
||||
|
||||
pr_info("%s() invoked\n", __func__);
|
||||
|
||||
if (info == NULL) {
|
||||
// should never happen
|
||||
pr_err("An error occurred in %s():\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* For each attribute there is an index in info->attrs which points to a nlattr structure
|
||||
* in this structure the data is stored.
|
||||
*/
|
||||
na = info->attrs[GNL_VRPMDV_MCMD_A_MSG];
|
||||
|
||||
if (!na) {
|
||||
pr_err("no info->attrs[%i]\n", GNL_VRPMDV_MCMD_A_MSG);
|
||||
return -EINVAL; // we return here because we expect to recv a msg
|
||||
}
|
||||
|
||||
recv_msg = (char *) nla_data(na);
|
||||
if (recv_msg == NULL) {
|
||||
pr_err("error while receiving data\n");
|
||||
} else {
|
||||
pr_info("received: '%s'\n", recv_msg);
|
||||
}
|
||||
|
||||
//aquire lock for cmd repmsg channel
|
||||
// std::lock sendlk(cmd_cb_progress_data.sendmtx);
|
||||
|
||||
//send the message to the copro over RPMSG
|
||||
if (prpdev) {
|
||||
rc = rpmsg_send(prpdev->ept, recv_msg, strlen(recv_msg));
|
||||
if (rc) {
|
||||
pr_err("rpmsg_send failed: %d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// struct rpmsg_vrpmdv_mon_t *drv = dev_get_drvdata(&rpdev->dev);
|
||||
|
||||
// Send a message back after we receive the reply from rpmsg channel
|
||||
// ---------------------
|
||||
|
||||
// Allocate some memory, since the size is not yet known use NLMSG_GOODSIZE
|
||||
reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
|
||||
if (reply_skb == NULL) {
|
||||
pr_err("An error occurred in %s():\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
// Create the message headersprpdev
|
||||
|
||||
// Add header to netlink message;
|
||||
// afterwards the buffer looks like this:
|
||||
// ----------------------------------
|
||||
// | netlink header |
|
||||
// | generic netlink header |
|
||||
// | <space for netlink attributes> |
|
||||
// ----------------------------------
|
||||
// msg_head = genlmsg_put(reply_skb, // buffer for netlink message: struct sk_buff *
|
||||
// // According to my findings: this is not used for routing
|
||||
// // This can be used in an application specific way to target
|
||||
// // different endpoints within the same user application
|
||||
// // but general rule: just put sender port id here
|
||||
// info->snd_portid, // sending port (not process) id: int
|
||||
// info->snd_seq + 1, // sequence number: int (might be used by receiver, but not mandatory)
|
||||
// &gnl_vrpmdv_mcmd_family, // struct genl_family *
|
||||
// 0, // flags for Netlink header: int; application specific and not mandatory
|
||||
// // The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
|
||||
// GNL_VRPMDV_MCMD_C_MSG
|
||||
// );
|
||||
|
||||
msg_head = genlmsg_put_reply(reply_skb, // buffer for netlink message: struct sk_buff *
|
||||
info, // info
|
||||
&gnl_vrpmdv_mcmd_family, // struct genl_family *
|
||||
0, // flags for Netlink header: int; application specific and not mandatory
|
||||
// The command/operation (u8) from `enum GNL_FOOBAR_XMPL_COMMAND` for Generic Netlink header
|
||||
info->genlhdr->cmd
|
||||
);
|
||||
|
||||
if (msg_head == NULL) {
|
||||
rc = ENOMEM;
|
||||
pr_err("An error occurred in %s():\n", __func__);
|
||||
return -rc;
|
||||
}
|
||||
|
||||
{
|
||||
pr_info("wait for response\n");
|
||||
// wait until receive_queue_flag=1 , that means we have received data from Copro
|
||||
wait_event_interruptible(receive_queue, receive_queue_flag != 0 );
|
||||
|
||||
//Copy data
|
||||
receive_queue_flag = NODATARECEIVED;
|
||||
|
||||
|
||||
|
||||
// std::unique_lock lk(cmd_cb_progress_data.receivemtx);
|
||||
// if (myCondVar.wait_until(uLock, timePoint) == std::cv_status::timeout)
|
||||
// {
|
||||
// dev_err(&cmd_cb_progress_data.rpdev, "rpmsg_send failed, timeout: \n");
|
||||
// return -1:
|
||||
// }
|
||||
|
||||
//pr_info("get response: '%s'\n", recv_msg);
|
||||
if (received_len > 0) {
|
||||
pr_info("received data from copro %s\n", received_bytes);
|
||||
}
|
||||
else {
|
||||
pr_err("don't received data from Copro \n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add a GNL_VRPMDV_MCMD_A_MSG attribute (actual value/payload to be sent)
|
||||
// echo the value we just received
|
||||
rc = nla_put_string(reply_skb, GNL_VRPMDV_MCMD_A_MSG, received_bytes);
|
||||
|
||||
if (rc != 0) {
|
||||
pr_err("An error occurred in %s():\n", __func__);
|
||||
//free the buffer
|
||||
kfree(received_bytes);
|
||||
received_bytes = NULL;
|
||||
return -rc;
|
||||
}
|
||||
|
||||
// Finalize the message:
|
||||
// Corrects the netlink message header (length) to include the appended
|
||||
// attributes. Only necessary if attributes have been added to the message.
|
||||
genlmsg_end(reply_skb, msg_head);
|
||||
|
||||
// Send the message back
|
||||
rc = genlmsg_reply(reply_skb, info);
|
||||
// same as genlmsg_unicast(genl_info_net(info), reply_skb, info->snd_portid)
|
||||
// see https://elixir.bootlin.com/linux/v5.8.9/source/include/net/genetlink.h#L326
|
||||
|
||||
kfree(received_bytes);
|
||||
received_bytes = NULL;
|
||||
|
||||
if (rc != 0) {
|
||||
pr_err("An error occurred in %s():\n", __func__);
|
||||
return -rc;
|
||||
}
|
||||
return 0;
|
||||
|
||||
}
|
||||
pr_info("Device not set in Probe. Should not happen");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular ".doit"-callback function if a Generic Netlink with command `GNL_FOOBAR_XMPL_C_REPLY_WITH_NLMSG_ERR` is received.
|
||||
* Please look into the comments where this is used as ".doit" callback above in
|
||||
* `struct genl_ops gnl_foobar_xmpl_ops[]` for more information about ".doit" callbacks.
|
||||
*/
|
||||
int gnl_cb_doit_reply_with_nlmsg_err(struct sk_buff *sender_skb, struct genl_info *info) {
|
||||
pr_info("%s() invoked, a NLMSG_ERR response will be sent back\n", __func__);
|
||||
|
||||
/*
|
||||
* Generic Netlink is smart enough and sends a NLMSG_ERR reply automatically as reply
|
||||
* Quote from https://lwn.net/Articles/208755/:
|
||||
* "The 'doit' handler should do whatever processing is necessary and return
|
||||
* zero on success, or a negative value on failure. Negative return values
|
||||
* will cause a NLMSG_ERROR message to be sent while a zero return value will
|
||||
* only cause a NLMSG_ERROR message to be sent if the request is received with
|
||||
* the NLM_F_ACK flag set."
|
||||
*
|
||||
* You can find this in Linux code here:
|
||||
* https://elixir.bootlin.com/linux/v5.11/source/net/netlink/af_netlink.c#L2499
|
||||
*
|
||||
* One can find more information about NLMSG_ERROR responses and how to handle them
|
||||
* in userland in the manpage: https://man7.org/linux/man-pages/man7/netlink.7.html
|
||||
*/
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** ----- NETLINK Driver defintion ------------------*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* callback that is called after the copro send data
|
||||
* we have to copy it in a buffer for the netlink and later send it back to the userland
|
||||
*
|
||||
*/
|
||||
static int vrpmdv_monitoring_cb(struct rpmsg_device *rpdev, void *data, int len,
|
||||
void *priv, u32 src)
|
||||
{
|
||||
int ret = 0;
|
||||
// struct instance_data *idata = dev_get_drvdata(&rpdev->dev);
|
||||
|
||||
// dev_info(&rpdev->dev, "incoming msg %d (src: 0x%x)\n",
|
||||
// ++idata->rx_count, src);
|
||||
|
||||
print_hex_dump_debug(__func__, DUMP_PREFIX_NONE, 16, 1, data, len,
|
||||
true);
|
||||
if (len == 0) {
|
||||
pr_err("(%s) Empty lenght requested\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
else {
|
||||
pr_info("received logging: %s\n", (char*) data);
|
||||
}
|
||||
|
||||
ret = rpmsg_send(rpdev->ept, retok, sizeof(retok));
|
||||
|
||||
// received_bytes = (char *)kmalloc(len+1, GFP_KERNEL);
|
||||
// memcpy(received_bytes, data, len);
|
||||
|
||||
// rpmsg_RxBuf[len] = 0;
|
||||
|
||||
|
||||
|
||||
// received_bytes = (char *)kmalloc(len, GFP_KERNEL);
|
||||
// memcpy(received_bytes, data, len);
|
||||
// received_len = len;
|
||||
// receive_queue_flag= DATARECEIVED;
|
||||
// wake_up_interruptible(&receive_queue);
|
||||
|
||||
// /* samples should not live forever */
|
||||
// if (idata->rx_count >= count) {
|
||||
// dev_info(&rpdev->dev, "goodbye!\n");
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
/* send a new message now */
|
||||
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
|
||||
// if (ret)
|
||||
// dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int vrpmdv_mon_log_probe(struct rpmsg_device *rpdev)
|
||||
{
|
||||
//int rc;
|
||||
|
||||
// int ret;
|
||||
// struct instance_data *idata;
|
||||
|
||||
// dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
|
||||
// rpdev->src, rpdev->dst);
|
||||
|
||||
pr_info("RPMSG mon logger device driver started.\n");
|
||||
|
||||
// idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
|
||||
// if (!idata)
|
||||
// return -ENOMEM;
|
||||
|
||||
// dev_set_drvdata(&rpdev->dev, idata);
|
||||
|
||||
// /* send a message to our remote processor to */
|
||||
// ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
|
||||
// if (ret) {
|
||||
// dev_err(&rpdev->dev, "vrpmdv_monitoring_controler_send failed: %d\n", ret);
|
||||
// return ret;
|
||||
// }
|
||||
|
||||
// return 0;
|
||||
|
||||
// struct device *dev;
|
||||
// dev = &rpdev->dev;
|
||||
// struct rpmsg_vrpmdv_mon_t *rpmsg_vrpmdv_mon;
|
||||
|
||||
// rpmsg_vrpmdv_mon = devm_kzalloc(dev, sizeof(*rpmsg_vrpmdv_mon), GFP_KERNEL);
|
||||
// if (!rpmsg_vrpmdv_mon)
|
||||
// return -ENOMEM;
|
||||
|
||||
// mutex_init(&sendMTX);
|
||||
// init_waitqueue_head (&receive_queue);
|
||||
prpdev = rpdev;
|
||||
|
||||
// dev_set_drvdata(&rpdev->dev, rpmsg_vrpmdv_mon);
|
||||
pr_info("RPMSG Logger Device set.\n");
|
||||
|
||||
|
||||
/** NEU **/
|
||||
// if (cmd_cb_progress_data.rpdev == NULL) {
|
||||
// cmd_cb_progress_data.rpdev = rpdev;
|
||||
// pr_info("RPMSG CMD Device set.\n");
|
||||
// }
|
||||
// else {
|
||||
// pr_info("Error: RPMSG CMD Device already set. Don't set it twice\n");
|
||||
// }
|
||||
|
||||
//pr_info("Generic Netlink VRPMDV-Mon-Log Module started.\n");
|
||||
|
||||
// Register family with its operations and policies
|
||||
// rc = genl_register_family(&gnl_vrpmdv_mcmd_family);
|
||||
// if (rc != 0) {
|
||||
// pr_err("FAILED: genl_register_family(): %i\n", rc);
|
||||
// pr_err("An error occurred while inserting the generic netlink example module\n");
|
||||
// return -1;
|
||||
// } else {
|
||||
// pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
|
||||
// }
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
static void vrpmdv_mon_log_remove(struct rpmsg_device *rpdev)
|
||||
{
|
||||
//int ret;
|
||||
pr_info("Mon Logger unloaded.\n");
|
||||
|
||||
// Unregister the family
|
||||
// ret = genl_unregister_family(&gnl_vrpmdv_mcmd_family);
|
||||
// if (ret != 0) {
|
||||
// pr_err("genl_unregister_family() failed: %i\n", ret);
|
||||
// return;
|
||||
// } else {
|
||||
// pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
|
||||
// }
|
||||
|
||||
// mutex_destroy(&sendMTX);
|
||||
// wake_up_interruptible(&receive_queue);
|
||||
|
||||
pr_info("vrpmdv-mon logger driver is removed\n");
|
||||
// dev_info(&rpdev->dev, "vrpmdv-monitoring controler driver is removed\n");
|
||||
|
||||
}
|
||||
|
||||
static struct rpmsg_device_id vrpmdv_mon_log_id_table[] = {
|
||||
{ .name = "vrpmdv-mon-log" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(rpmsg, vrpmdv_mon_log_id_table);
|
||||
|
||||
static struct rpmsg_driver vrpmdv_mon_log = {
|
||||
.drv.name = KBUILD_MODNAME,
|
||||
.id_table = vrpmdv_mon_log_id_table,
|
||||
.probe = vrpmdv_mon_log_probe,
|
||||
.callback = vrpmdv_mon_log_cb,
|
||||
.remove = vrpmdv_mon_log_remove,
|
||||
};
|
||||
module_rpmsg_driver(vrpmdv_mon_log);
|
||||
|
||||
|
||||
// static struct rpmsg_driver vrpmdv_monitoring_data = {
|
||||
// .drv.name = KBUILD_MODNAME,
|
||||
// .id_table = vrpmdv_monitoring_controler_id_table,
|
||||
// .probe = vrpmdv_monitoring_probe,
|
||||
// .callback = vrpmdv_monitoring_cb,
|
||||
// .remove = vrpmdv_monitoring_remove,
|
||||
// };
|
||||
|
||||
// module_rpmsg_driver(vrpmdv_monitoring_data);
|
||||
|
||||
MODULE_DESCRIPTION("Remote processor messaging vrpmdv monitoring controler");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
|
||||
|
||||
// /**
|
||||
// * Module/driver initializer. Called on module load/insertion.
|
||||
// *
|
||||
// * @return success (0) or error code.
|
||||
// */
|
||||
// static int __init gnl_foobar_xmpl_module_init(void) {
|
||||
// int rc;
|
||||
// pr_info("Generic Netlink Example Module inserted.\n");
|
||||
|
||||
// // Register family with its operations and policies
|
||||
// rc = genl_register_family(&gnl_foobar_xmpl_family);
|
||||
// if (rc != 0) {
|
||||
// pr_err("FAILED: genl_register_family(): %i\n", rc);
|
||||
// pr_err("An error occurred while inserting the generic netlink example module\n");
|
||||
// return -1;
|
||||
// } else {
|
||||
// pr_info("successfully registered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
|
||||
// }
|
||||
|
||||
// mutex_init(&dumpit_cb_progress_data.mtx);
|
||||
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Module/driver uninitializer. Called on module unload/removal.
|
||||
// *
|
||||
// * @return success (0) or error code.
|
||||
// */
|
||||
// static void __exit gnl_foobar_xmpl_module_exit(void) {
|
||||
// int ret;
|
||||
// pr_info("Generic Netlink Example Module unloaded.\n");
|
||||
|
||||
// // Unregister the family
|
||||
// ret = genl_unregister_family(&gnl_foobar_xmpl_family);
|
||||
// if (ret != 0) {
|
||||
// pr_err("genl_unregister_family() failed: %i\n", ret);
|
||||
// return;
|
||||
// } else {
|
||||
// pr_info("successfully unregistered custom Netlink family '" FAMILY_NAME "' using Generic Netlink.\n");
|
||||
// }
|
||||
|
||||
// mutex_destroy(&dumpit_cb_progress_data.mtx);
|
||||
// }
|
||||
|
||||
|
||||
// ----
|
||||
|
||||
// static int __init rpmsg_sdb_drv_init(void)
|
||||
// {
|
||||
// int ret = 0;
|
||||
|
||||
// /* Register rpmsg device */
|
||||
// ret = register_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
|
||||
|
||||
// if (ret) {
|
||||
// pr_err("rpmsg_sdb(ERROR): Failed to register device\n");
|
||||
// return ret;
|
||||
// }
|
||||
|
||||
// pr_info("rpmsg_sdb: Init done\n");
|
||||
|
||||
// return ret;
|
||||
// }
|
||||
|
||||
// static void __exit rpmsg_sdb_drv_exit(void)
|
||||
// {
|
||||
// unregister_rpmsg_driver(&rpmsg_sdb_rmpsg_drv);
|
||||
// pr_info("rpmsg_sdb: Exit\n");
|
||||
// }
|
||||
|
||||
// module_init(rpmsg_sdb_drv_init);
|
||||
// module_exit(rpmsg_sdb_drv_exit);
|
||||
@@ -0,0 +1,308 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2021 STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* The rpmsg tty driver implements serial communication on the RPMsg bus to makes
|
||||
* possible for user-space programs to send and receive rpmsg messages as a standard
|
||||
* tty protocol.
|
||||
*
|
||||
* The remote processor can instantiate a new tty by requesting a "rpmsg-tty" RPMsg service.
|
||||
* The "rpmsg-tty" service is directly used for data exchange. No flow control is implemented yet.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/rpmsg.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/tty_flip.h>
|
||||
|
||||
#define RPMSG_TTY_NAME "ttyMON"
|
||||
#define MAX_TTY_RPMSG 32
|
||||
|
||||
static DEFINE_IDR(tty_idr); /* tty instance id */
|
||||
static DEFINE_MUTEX(idr_lock); /* protects tty_idr */
|
||||
|
||||
static struct tty_driver *rpmsg_tty_driver;
|
||||
|
||||
struct rpmsg_tty_port {
|
||||
struct tty_port port; /* TTY port data */
|
||||
int id; /* TTY rpmsg index */
|
||||
struct rpmsg_device *rpdev; /* rpmsg device */
|
||||
};
|
||||
|
||||
static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src)
|
||||
{
|
||||
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
|
||||
int copied;
|
||||
|
||||
pr_info("%s() invoked\n", __func__);
|
||||
|
||||
if (!len)
|
||||
return -EINVAL;
|
||||
|
||||
copied = tty_insert_flip_string(&cport->port, data, len);
|
||||
pr_info("%s() received\n", data);
|
||||
|
||||
if (copied != len)
|
||||
dev_err_ratelimited(&rpdev->dev, "Trunc buffer: available space is %d\n", copied);
|
||||
pr_info("%s() error cop\n", __func__);
|
||||
|
||||
tty_flip_buffer_push(&cport->port);
|
||||
pr_info("%s() invoked. succes!!\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rpmsg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
|
||||
{
|
||||
pr_info("%s() invoked\n", __func__);
|
||||
struct rpmsg_tty_port *cport = idr_find(&tty_idr, tty->index);
|
||||
struct tty_port *port;
|
||||
|
||||
tty->driver_data = cport;
|
||||
|
||||
port = tty_port_get(&cport->port);
|
||||
return tty_port_install(port, driver, tty);
|
||||
}
|
||||
|
||||
static void rpmsg_tty_cleanup(struct tty_struct *tty)
|
||||
{
|
||||
pr_info("%s() invoked\n", __func__);
|
||||
tty_port_put(tty->port);
|
||||
}
|
||||
|
||||
static int rpmsg_tty_open(struct tty_struct *tty, struct file *filp)
|
||||
{
|
||||
pr_info("%s() invoked\n", __func__);
|
||||
return tty_port_open(tty->port, tty, filp);
|
||||
}
|
||||
|
||||
static void rpmsg_tty_close(struct tty_struct *tty, struct file *filp)
|
||||
{
|
||||
pr_info("%s() invoked\n", __func__);
|
||||
return tty_port_close(tty->port, tty, filp);
|
||||
}
|
||||
|
||||
static int rpmsg_tty_write(struct tty_struct *tty, const u8 *buf, int len)
|
||||
{
|
||||
pr_info("%s() invoked\n", __func__);
|
||||
struct rpmsg_tty_port *cport = tty->driver_data;
|
||||
struct rpmsg_device *rpdev;
|
||||
int msg_max_size, msg_size;
|
||||
int ret;
|
||||
|
||||
rpdev = cport->rpdev;
|
||||
|
||||
msg_max_size = rpmsg_get_mtu(rpdev->ept);
|
||||
if (msg_max_size < 0)
|
||||
return msg_max_size;
|
||||
|
||||
msg_size = min(len, msg_max_size);
|
||||
|
||||
/*
|
||||
* Use rpmsg_trysend instead of rpmsg_send to send the message so the caller is not
|
||||
* hung until a rpmsg buffer is available. In such case rpmsg_trysend returns -ENOMEM.
|
||||
*/
|
||||
ret = rpmsg_trysend(rpdev->ept, (void *)buf, msg_size);
|
||||
if (ret) {
|
||||
dev_dbg_ratelimited(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
pr_info("%s() invoked. send succes!!\n", __func__);
|
||||
return msg_size;
|
||||
}
|
||||
|
||||
static unsigned int rpmsg_tty_write_room(struct tty_struct *tty)
|
||||
{
|
||||
pr_info("%s() invoked\n", __func__);
|
||||
struct rpmsg_tty_port *cport = tty->driver_data;
|
||||
int size;
|
||||
|
||||
size = rpmsg_get_mtu(cport->rpdev->ept);
|
||||
if (size < 0)
|
||||
return 0;
|
||||
pr_info("%s() invoked. write success\n", __func__);
|
||||
return size;
|
||||
}
|
||||
|
||||
static void rpmsg_tty_hangup(struct tty_struct *tty)
|
||||
{
|
||||
pr_info("%s() invoked\n", __func__);
|
||||
tty_port_hangup(tty->port);
|
||||
}
|
||||
|
||||
static const struct tty_operations rpmsg_tty_ops = {
|
||||
.install = rpmsg_tty_install,
|
||||
.open = rpmsg_tty_open,
|
||||
.close = rpmsg_tty_close,
|
||||
.write = rpmsg_tty_write,
|
||||
.write_room = rpmsg_tty_write_room,
|
||||
.hangup = rpmsg_tty_hangup,
|
||||
.cleanup = rpmsg_tty_cleanup,
|
||||
};
|
||||
|
||||
static struct rpmsg_tty_port *rpmsg_tty_alloc_cport(void)
|
||||
{
|
||||
pr_info("%s() invoked\n", __func__);
|
||||
struct rpmsg_tty_port *cport;
|
||||
int ret;
|
||||
|
||||
cport = kzalloc(sizeof(*cport), GFP_KERNEL);
|
||||
if (!cport)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
mutex_lock(&idr_lock);
|
||||
ret = idr_alloc(&tty_idr, cport, 0, MAX_TTY_RPMSG, GFP_KERNEL);
|
||||
mutex_unlock(&idr_lock);
|
||||
|
||||
if (ret < 0) {
|
||||
kfree(cport);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
cport->id = ret;
|
||||
|
||||
return cport;
|
||||
}
|
||||
|
||||
static void rpmsg_tty_destruct_port(struct tty_port *port)
|
||||
{
|
||||
pr_info("%s() invoked\n", __func__);
|
||||
struct rpmsg_tty_port *cport = container_of(port, struct rpmsg_tty_port, port);
|
||||
|
||||
mutex_lock(&idr_lock);
|
||||
idr_remove(&tty_idr, cport->id);
|
||||
mutex_unlock(&idr_lock);
|
||||
|
||||
kfree(cport);
|
||||
}
|
||||
|
||||
static const struct tty_port_operations rpmsg_tty_port_ops = {
|
||||
.destruct = rpmsg_tty_destruct_port,
|
||||
};
|
||||
|
||||
|
||||
static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
|
||||
{
|
||||
pr_info("%s() invoked\n", __func__);
|
||||
struct rpmsg_tty_port *cport;
|
||||
struct device *dev = &rpdev->dev;
|
||||
struct device *tty_dev;
|
||||
int ret;
|
||||
|
||||
cport = rpmsg_tty_alloc_cport();
|
||||
if (IS_ERR(cport))
|
||||
return dev_err_probe(dev, PTR_ERR(cport), "Failed to alloc tty mon port\n");
|
||||
|
||||
tty_port_init(&cport->port);
|
||||
cport->port.ops = &rpmsg_tty_port_ops;
|
||||
|
||||
tty_dev = tty_port_register_device(&cport->port, rpmsg_tty_driver,
|
||||
cport->id, dev);
|
||||
if (IS_ERR(tty_dev)) {
|
||||
ret = dev_err_probe(dev, PTR_ERR(tty_dev), "Failed to register tty mon port\n");
|
||||
tty_port_put(&cport->port);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cport->rpdev = rpdev;
|
||||
|
||||
dev_set_drvdata(dev, cport);
|
||||
|
||||
dev_dbg(dev, "New channel: 0x%x -> 0x%x: " RPMSG_TTY_NAME "%d\n",
|
||||
rpdev->src, rpdev->dst, cport->id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
|
||||
{
|
||||
pr_info("%s() invoked\n", __func__);
|
||||
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
|
||||
|
||||
dev_dbg(&rpdev->dev, "Removing mon tty device %d\n", cport->id);
|
||||
|
||||
/* User hang up to release the tty */
|
||||
tty_port_tty_hangup(&cport->port, false);
|
||||
|
||||
tty_unregister_device(rpmsg_tty_driver, cport->id);
|
||||
|
||||
tty_port_put(&cport->port);
|
||||
}
|
||||
|
||||
static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = {
|
||||
{ .name = "vrpmdv-mon-tty" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_tty_id_table);
|
||||
|
||||
static struct rpmsg_driver rpmsg_tty_rpmsg_drv = {
|
||||
.drv.name = KBUILD_MODNAME,
|
||||
.id_table = rpmsg_driver_tty_id_table,
|
||||
.probe = rpmsg_tty_probe,
|
||||
.callback = rpmsg_tty_cb,
|
||||
.remove = rpmsg_tty_remove,
|
||||
};
|
||||
|
||||
static int __init rpmsg_tty_init(void)
|
||||
{
|
||||
pr_info("%s() invoked\n", __func__);
|
||||
int ret;
|
||||
|
||||
rpmsg_tty_driver = tty_alloc_driver(MAX_TTY_RPMSG, TTY_DRIVER_REAL_RAW |
|
||||
TTY_DRIVER_DYNAMIC_DEV);
|
||||
if (IS_ERR(rpmsg_tty_driver))
|
||||
return PTR_ERR(rpmsg_tty_driver);
|
||||
|
||||
rpmsg_tty_driver->driver_name = "vrpmdv-mon-tty";
|
||||
rpmsg_tty_driver->name = RPMSG_TTY_NAME;
|
||||
rpmsg_tty_driver->major = 0;
|
||||
rpmsg_tty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
|
||||
|
||||
/* Disable unused mode by default */
|
||||
rpmsg_tty_driver->init_termios = tty_std_termios;
|
||||
rpmsg_tty_driver->init_termios.c_lflag &= ~(ECHO | ICANON);
|
||||
rpmsg_tty_driver->init_termios.c_oflag &= ~(OPOST | ONLCR);
|
||||
|
||||
tty_set_operations(rpmsg_tty_driver, &rpmsg_tty_ops);
|
||||
|
||||
ret = tty_register_driver(rpmsg_tty_driver);
|
||||
if (ret < 0) {
|
||||
pr_err("Couldn't install mon tty driver: %d\n", ret);
|
||||
goto error_put;
|
||||
}
|
||||
|
||||
ret = register_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
|
||||
if (ret < 0) {
|
||||
pr_err("Couldn't register mon tty driver: %d\n", ret);
|
||||
goto error_unregister;
|
||||
}
|
||||
pr_info("%s() invoked. Install complete!\n", __func__);
|
||||
return 0;
|
||||
|
||||
error_unregister:
|
||||
pr_info("%s() invoked unregister.\n", __func__);
|
||||
tty_unregister_driver(rpmsg_tty_driver);
|
||||
|
||||
error_put:
|
||||
pr_info("%s() invoked. error put\n", __func__);
|
||||
tty_driver_kref_put(rpmsg_tty_driver);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit rpmsg_tty_exit(void)
|
||||
{
|
||||
unregister_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
|
||||
tty_unregister_driver(rpmsg_tty_driver);
|
||||
tty_driver_kref_put(rpmsg_tty_driver);
|
||||
idr_destroy(&tty_idr);
|
||||
}
|
||||
|
||||
module_init(rpmsg_tty_init);
|
||||
module_exit(rpmsg_tty_exit);
|
||||
|
||||
MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>");
|
||||
MODULE_DESCRIPTION("remote processor messaging tty driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
@@ -0,0 +1,287 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2021 STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* The rpmsg tty driver implements serial communication on the RPMsg bus to makes
|
||||
* possible for user-space programs to send and receive rpmsg messages as a standard
|
||||
* tty protocol.
|
||||
*
|
||||
* The remote processor can instantiate a new tty by requesting a "rpmsg-tty" RPMsg service.
|
||||
* The "rpmsg-tty" service is directly used for data exchange. No flow control is implemented yet.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/rpmsg.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/tty_flip.h>
|
||||
|
||||
#define RPMSG_TTY_NAME "ttyMON"
|
||||
#define MAX_TTY_RPMSG 32
|
||||
|
||||
static DEFINE_IDR(tty_idr); /* tty instance id */
|
||||
static DEFINE_MUTEX(idr_lock); /* protects tty_idr */
|
||||
|
||||
static struct tty_driver *rpmsg_tty_driver;
|
||||
|
||||
struct rpmsg_tty_port {
|
||||
struct tty_port port; /* TTY port data */
|
||||
int id; /* TTY rpmsg index */
|
||||
struct rpmsg_device *rpdev; /* rpmsg device */
|
||||
};
|
||||
|
||||
static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src)
|
||||
{
|
||||
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
|
||||
int copied;
|
||||
|
||||
if (!len)
|
||||
return -EINVAL;
|
||||
copied = tty_insert_flip_string(&cport->port, data, len);
|
||||
if (copied != len)
|
||||
dev_err_ratelimited(&rpdev->dev, "Trunc buffer: available space is %d\n", copied);
|
||||
tty_flip_buffer_push(&cport->port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rpmsg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
|
||||
{
|
||||
struct rpmsg_tty_port *cport = idr_find(&tty_idr, tty->index);
|
||||
struct tty_port *port;
|
||||
|
||||
tty->driver_data = cport;
|
||||
|
||||
port = tty_port_get(&cport->port);
|
||||
return tty_port_install(port, driver, tty);
|
||||
}
|
||||
|
||||
static void rpmsg_tty_cleanup(struct tty_struct *tty)
|
||||
{
|
||||
tty_port_put(tty->port);
|
||||
}
|
||||
|
||||
static int rpmsg_tty_open(struct tty_struct *tty, struct file *filp)
|
||||
{
|
||||
return tty_port_open(tty->port, tty, filp);
|
||||
}
|
||||
|
||||
static void rpmsg_tty_close(struct tty_struct *tty, struct file *filp)
|
||||
{
|
||||
return tty_port_close(tty->port, tty, filp);
|
||||
}
|
||||
|
||||
static int rpmsg_tty_write(struct tty_struct *tty, const u8 *buf, int len)
|
||||
{
|
||||
struct rpmsg_tty_port *cport = tty->driver_data;
|
||||
struct rpmsg_device *rpdev;
|
||||
int msg_max_size, msg_size;
|
||||
int ret;
|
||||
|
||||
rpdev = cport->rpdev;
|
||||
|
||||
msg_max_size = rpmsg_get_mtu(rpdev->ept);
|
||||
if (msg_max_size < 0)
|
||||
return msg_max_size;
|
||||
|
||||
msg_size = min(len, msg_max_size);
|
||||
|
||||
/*
|
||||
* Use rpmsg_trysend instead of rpmsg_send to send the message so the caller is not
|
||||
* hung until a rpmsg buffer is available. In such case rpmsg_trysend returns -ENOMEM.
|
||||
*/
|
||||
ret = rpmsg_trysend(rpdev->ept, (void *)buf, msg_size);
|
||||
if (ret) {
|
||||
dev_dbg_ratelimited(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return msg_size;
|
||||
}
|
||||
|
||||
static unsigned int rpmsg_tty_write_room(struct tty_struct *tty)
|
||||
{
|
||||
struct rpmsg_tty_port *cport = tty->driver_data;
|
||||
int size;
|
||||
|
||||
size = rpmsg_get_mtu(cport->rpdev->ept);
|
||||
if (size < 0)
|
||||
return 0;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static void rpmsg_tty_hangup(struct tty_struct *tty)
|
||||
{
|
||||
tty_port_hangup(tty->port);
|
||||
}
|
||||
|
||||
static const struct tty_operations rpmsg_tty_ops = {
|
||||
.install = rpmsg_tty_install,
|
||||
.open = rpmsg_tty_open,
|
||||
.close = rpmsg_tty_close,
|
||||
.write = rpmsg_tty_write,
|
||||
.write_room = rpmsg_tty_write_room,
|
||||
.hangup = rpmsg_tty_hangup,
|
||||
.cleanup = rpmsg_tty_cleanup,
|
||||
};
|
||||
|
||||
static struct rpmsg_tty_port *rpmsg_tty_alloc_cport(void)
|
||||
{
|
||||
struct rpmsg_tty_port *cport;
|
||||
int ret;
|
||||
|
||||
cport = kzalloc(sizeof(*cport), GFP_KERNEL);
|
||||
if (!cport)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
mutex_lock(&idr_lock);
|
||||
ret = idr_alloc(&tty_idr, cport, 0, MAX_TTY_RPMSG, GFP_KERNEL);
|
||||
mutex_unlock(&idr_lock);
|
||||
|
||||
if (ret < 0) {
|
||||
kfree(cport);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
cport->id = ret;
|
||||
|
||||
return cport;
|
||||
}
|
||||
|
||||
static void rpmsg_tty_destruct_port(struct tty_port *port)
|
||||
{
|
||||
struct rpmsg_tty_port *cport = container_of(port, struct rpmsg_tty_port, port);
|
||||
|
||||
mutex_lock(&idr_lock);
|
||||
idr_remove(&tty_idr, cport->id);
|
||||
mutex_unlock(&idr_lock);
|
||||
|
||||
kfree(cport);
|
||||
}
|
||||
|
||||
static const struct tty_port_operations rpmsg_tty_port_ops = {
|
||||
.destruct = rpmsg_tty_destruct_port,
|
||||
};
|
||||
|
||||
|
||||
static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
|
||||
{
|
||||
struct rpmsg_tty_port *cport;
|
||||
struct device *dev = &rpdev->dev;
|
||||
struct device *tty_dev;
|
||||
int ret;
|
||||
|
||||
cport = rpmsg_tty_alloc_cport();
|
||||
if (IS_ERR(cport))
|
||||
return dev_err_probe(dev, PTR_ERR(cport), "Failed to alloc tty mon port\n");
|
||||
|
||||
tty_port_init(&cport->port);
|
||||
cport->port.ops = &rpmsg_tty_port_ops;
|
||||
|
||||
tty_dev = tty_port_register_device(&cport->port, rpmsg_tty_driver,
|
||||
cport->id, dev);
|
||||
if (IS_ERR(tty_dev)) {
|
||||
ret = dev_err_probe(dev, PTR_ERR(tty_dev), "Failed to register tty mon port\n");
|
||||
tty_port_put(&cport->port);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cport->rpdev = rpdev;
|
||||
|
||||
dev_set_drvdata(dev, cport);
|
||||
|
||||
dev_dbg(dev, "New channel: 0x%x -> 0x%x: " RPMSG_TTY_NAME "%d\n",
|
||||
rpdev->src, rpdev->dst, cport->id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
|
||||
{
|
||||
struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
|
||||
|
||||
dev_dbg(&rpdev->dev, "Removing mon tty device %d\n", cport->id);
|
||||
|
||||
/* User hang up to release the tty */
|
||||
tty_port_tty_hangup(&cport->port, false);
|
||||
|
||||
tty_unregister_device(rpmsg_tty_driver, cport->id);
|
||||
|
||||
tty_port_put(&cport->port);
|
||||
}
|
||||
|
||||
static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = {
|
||||
{ .name = "vrpmdv-mon-tty" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_tty_id_table);
|
||||
|
||||
static struct rpmsg_driver rpmsg_tty_rpmsg_drv = {
|
||||
.drv.name = KBUILD_MODNAME,
|
||||
.id_table = rpmsg_driver_tty_id_table,
|
||||
.probe = rpmsg_tty_probe,
|
||||
.callback = rpmsg_tty_cb,
|
||||
.remove = rpmsg_tty_remove,
|
||||
};
|
||||
|
||||
static int __init rpmsg_tty_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
rpmsg_tty_driver = tty_alloc_driver(MAX_TTY_RPMSG, TTY_DRIVER_REAL_RAW |
|
||||
TTY_DRIVER_DYNAMIC_DEV);
|
||||
if (IS_ERR(rpmsg_tty_driver))
|
||||
return PTR_ERR(rpmsg_tty_driver);
|
||||
|
||||
rpmsg_tty_driver->driver_name = "vrpmdv-mon-tty";
|
||||
rpmsg_tty_driver->name = RPMSG_TTY_NAME;
|
||||
rpmsg_tty_driver->major = 0;
|
||||
rpmsg_tty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
|
||||
|
||||
/* Disable unused mode by default */
|
||||
rpmsg_tty_driver->init_termios = tty_std_termios;
|
||||
rpmsg_tty_driver->init_termios.c_lflag &= ~(ECHO | ICANON);
|
||||
rpmsg_tty_driver->init_termios.c_oflag &= ~(OPOST | ONLCR);
|
||||
|
||||
tty_set_operations(rpmsg_tty_driver, &rpmsg_tty_ops);
|
||||
|
||||
ret = tty_register_driver(rpmsg_tty_driver);
|
||||
if (ret < 0) {
|
||||
pr_err("Couldn't install mon tty driver: %d\n", ret);
|
||||
goto error_put;
|
||||
}
|
||||
|
||||
ret = register_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
|
||||
if (ret < 0) {
|
||||
pr_err("Couldn't register mon tty driver: %d\n", ret);
|
||||
goto error_unregister;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error_unregister:
|
||||
tty_unregister_driver(rpmsg_tty_driver);
|
||||
|
||||
error_put:
|
||||
tty_driver_kref_put(rpmsg_tty_driver);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit rpmsg_tty_exit(void)
|
||||
{
|
||||
unregister_rpmsg_driver(&rpmsg_tty_rpmsg_drv);
|
||||
tty_unregister_driver(rpmsg_tty_driver);
|
||||
tty_driver_kref_put(rpmsg_tty_driver);
|
||||
idr_destroy(&tty_idr);
|
||||
}
|
||||
|
||||
module_init(rpmsg_tty_init);
|
||||
module_exit(rpmsg_tty_exit);
|
||||
|
||||
MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>");
|
||||
MODULE_DESCRIPTION("remote processor messaging tty driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
Reference in New Issue
Block a user